From 35779c6675728fa6f0fd0a21cefb904408509c23 Mon Sep 17 00:00:00 2001 From: Sebastien Bacher Date: Mon, 5 Jul 2021 20:35:03 +0200 Subject: New upstream version 1.32.2 --- src/libnm-core-impl/README.md | 22 + src/libnm-core-impl/meson.build | 115 + src/libnm-core-impl/nm-connection-private.h | 42 + src/libnm-core-impl/nm-connection.c | 3749 +++++++ src/libnm-core-impl/nm-crypto-gnutls.c | 415 + src/libnm-core-impl/nm-crypto-impl.h | 49 + src/libnm-core-impl/nm-crypto-nss.c | 548 + src/libnm-core-impl/nm-crypto.c | 1044 ++ src/libnm-core-impl/nm-crypto.h | 96 + src/libnm-core-impl/nm-dbus-utils.c | 259 + src/libnm-core-impl/nm-default-libnm-core.h | 22 + src/libnm-core-impl/nm-errors.c | 55 + src/libnm-core-impl/nm-keyfile-utils.c | 684 ++ src/libnm-core-impl/nm-keyfile.c | 4447 ++++++++ src/libnm-core-impl/nm-meta-setting-base-impl.c | 717 ++ src/libnm-core-impl/nm-property-compare.c | 136 + src/libnm-core-impl/nm-property-compare.h | 16 + src/libnm-core-impl/nm-setting-6lowpan.c | 232 + src/libnm-core-impl/nm-setting-8021x.c | 4655 ++++++++ src/libnm-core-impl/nm-setting-adsl.c | 461 + src/libnm-core-impl/nm-setting-bluetooth.c | 348 + src/libnm-core-impl/nm-setting-bond.c | 1201 ++ src/libnm-core-impl/nm-setting-bridge-port.c | 616 ++ src/libnm-core-impl/nm-setting-bridge.c | 2308 ++++ src/libnm-core-impl/nm-setting-cdma.c | 374 + src/libnm-core-impl/nm-setting-connection.c | 2568 +++++ src/libnm-core-impl/nm-setting-dcb.c | 1289 +++ src/libnm-core-impl/nm-setting-dummy.c | 82 + src/libnm-core-impl/nm-setting-ethtool.c | 417 + src/libnm-core-impl/nm-setting-generic.c | 78 + src/libnm-core-impl/nm-setting-gsm.c | 886 ++ src/libnm-core-impl/nm-setting-hostname.c | 339 + src/libnm-core-impl/nm-setting-infiniband.c | 537 + src/libnm-core-impl/nm-setting-ip-config.c | 6565 +++++++++++ src/libnm-core-impl/nm-setting-ip-tunnel.c | 888 ++ src/libnm-core-impl/nm-setting-ip4-config.c | 1059 ++ src/libnm-core-impl/nm-setting-ip6-config.c | 1103 ++ src/libnm-core-impl/nm-setting-macsec.c | 693 ++ src/libnm-core-impl/nm-setting-macvlan.c | 354 + src/libnm-core-impl/nm-setting-match.c | 905 ++ src/libnm-core-impl/nm-setting-olpc-mesh.c | 292 + src/libnm-core-impl/nm-setting-ovs-bridge.c | 381 + src/libnm-core-impl/nm-setting-ovs-dpdk.c | 152 + src/libnm-core-impl/nm-setting-ovs-external-ids.c | 551 + src/libnm-core-impl/nm-setting-ovs-interface.c | 427 + src/libnm-core-impl/nm-setting-ovs-patch.c | 188 + src/libnm-core-impl/nm-setting-ovs-port.c | 467 + src/libnm-core-impl/nm-setting-ppp.c | 813 ++ src/libnm-core-impl/nm-setting-pppoe.c | 374 + src/libnm-core-impl/nm-setting-private.h | 525 + src/libnm-core-impl/nm-setting-proxy.c | 403 + src/libnm-core-impl/nm-setting-serial.c | 352 + src/libnm-core-impl/nm-setting-sriov.c | 1372 +++ src/libnm-core-impl/nm-setting-tc-config.c | 1886 ++++ src/libnm-core-impl/nm-setting-team-port.c | 701 ++ src/libnm-core-impl/nm-setting-team.c | 1834 ++++ src/libnm-core-impl/nm-setting-tun.c | 420 + src/libnm-core-impl/nm-setting-user.c | 594 + src/libnm-core-impl/nm-setting-veth.c | 194 + src/libnm-core-impl/nm-setting-vlan.c | 1001 ++ src/libnm-core-impl/nm-setting-vpn.c | 1259 +++ src/libnm-core-impl/nm-setting-vrf.c | 164 + src/libnm-core-impl/nm-setting-vxlan.c | 843 ++ src/libnm-core-impl/nm-setting-wifi-p2p.c | 304 + src/libnm-core-impl/nm-setting-wimax.c | 271 + src/libnm-core-impl/nm-setting-wired.c | 1748 +++ src/libnm-core-impl/nm-setting-wireguard.c | 2608 +++++ src/libnm-core-impl/nm-setting-wireless-security.c | 2000 ++++ src/libnm-core-impl/nm-setting-wireless.c | 1950 ++++ src/libnm-core-impl/nm-setting-wpan.c | 393 + src/libnm-core-impl/nm-setting.c | 2896 +++++ src/libnm-core-impl/nm-simple-connection.c | 189 + src/libnm-core-impl/nm-team-utils.c | 2862 +++++ src/libnm-core-impl/nm-team-utils.h | 271 + src/libnm-core-impl/nm-utils-private.h | 61 + src/libnm-core-impl/nm-utils.c | 5698 ++++++++++ src/libnm-core-impl/nm-vpn-editor-plugin.c | 518 + src/libnm-core-impl/nm-vpn-plugin-info.c | 1354 +++ .../tests/certs/ca-no-ending-newline.pem | 15 + .../tests/certs/pkcs8-decrypted.der | Bin 0 -> 1194 bytes src/libnm-core-impl/tests/certs/pkcs8-enc-key.pem | 29 + .../tests/certs/pkcs8-noenc-key.pem | 28 + .../tests/certs/test-aes-128-key.pem | 30 + .../tests/certs/test-aes-256-key.pem | 54 + src/libnm-core-impl/tests/certs/test-ca-cert.pem | 27 + src/libnm-core-impl/tests/certs/test-cert.p12 | Bin 0 -> 4092 bytes .../tests/certs/test-key-and-cert.pem | 118 + .../tests/certs/test-key-only-decrypted.der | Bin 0 -> 1192 bytes .../tests/certs/test-key-only-decrypted.pem | 27 + src/libnm-core-impl/tests/certs/test-key-only.pem | 92 + .../tests/certs/test-tpm2wrapped-key.pem | 14 + src/libnm-core-impl/tests/certs/test2-cert.p12 | Bin 0 -> 4136 bytes src/libnm-core-impl/tests/certs/test2_ca_cert.pem | 27 + .../tests/certs/test2_key_and_cert.pem | 119 + src/libnm-core-impl/tests/certs/test_ca_cert.der | Bin 0 -> 1162 bytes src/libnm-core-impl/tests/certs/test_ca_cert.pem | 27 + .../tests/certs/test_key_and_cert.pem | 119 + src/libnm-core-impl/tests/meson.build | 49 + .../tests/nm-core-tests-enum-types.c.template | 35 + .../tests/nm-core-tests-enum-types.h.template | 21 + src/libnm-core-impl/tests/test-compare.c | 231 + src/libnm-core-impl/tests/test-crypto.c | 455 + src/libnm-core-impl/tests/test-general-enums.h | 35 + src/libnm-core-impl/tests/test-general.c | 10840 +++++++++++++++++++ src/libnm-core-impl/tests/test-keyfile.c | 909 ++ src/libnm-core-impl/tests/test-secrets.c | 686 ++ src/libnm-core-impl/tests/test-setting.c | 4830 +++++++++ src/libnm-core-impl/tests/test-settings-defaults.c | 109 + 108 files changed, 97616 insertions(+) create mode 100644 src/libnm-core-impl/README.md create mode 100644 src/libnm-core-impl/meson.build create mode 100644 src/libnm-core-impl/nm-connection-private.h create mode 100644 src/libnm-core-impl/nm-connection.c create mode 100644 src/libnm-core-impl/nm-crypto-gnutls.c create mode 100644 src/libnm-core-impl/nm-crypto-impl.h create mode 100644 src/libnm-core-impl/nm-crypto-nss.c create mode 100644 src/libnm-core-impl/nm-crypto.c create mode 100644 src/libnm-core-impl/nm-crypto.h create mode 100644 src/libnm-core-impl/nm-dbus-utils.c create mode 100644 src/libnm-core-impl/nm-default-libnm-core.h create mode 100644 src/libnm-core-impl/nm-errors.c create mode 100644 src/libnm-core-impl/nm-keyfile-utils.c create mode 100644 src/libnm-core-impl/nm-keyfile.c create mode 100644 src/libnm-core-impl/nm-meta-setting-base-impl.c create mode 100644 src/libnm-core-impl/nm-property-compare.c create mode 100644 src/libnm-core-impl/nm-property-compare.h create mode 100644 src/libnm-core-impl/nm-setting-6lowpan.c create mode 100644 src/libnm-core-impl/nm-setting-8021x.c create mode 100644 src/libnm-core-impl/nm-setting-adsl.c create mode 100644 src/libnm-core-impl/nm-setting-bluetooth.c create mode 100644 src/libnm-core-impl/nm-setting-bond.c create mode 100644 src/libnm-core-impl/nm-setting-bridge-port.c create mode 100644 src/libnm-core-impl/nm-setting-bridge.c create mode 100644 src/libnm-core-impl/nm-setting-cdma.c create mode 100644 src/libnm-core-impl/nm-setting-connection.c create mode 100644 src/libnm-core-impl/nm-setting-dcb.c create mode 100644 src/libnm-core-impl/nm-setting-dummy.c create mode 100644 src/libnm-core-impl/nm-setting-ethtool.c create mode 100644 src/libnm-core-impl/nm-setting-generic.c create mode 100644 src/libnm-core-impl/nm-setting-gsm.c create mode 100644 src/libnm-core-impl/nm-setting-hostname.c create mode 100644 src/libnm-core-impl/nm-setting-infiniband.c create mode 100644 src/libnm-core-impl/nm-setting-ip-config.c create mode 100644 src/libnm-core-impl/nm-setting-ip-tunnel.c create mode 100644 src/libnm-core-impl/nm-setting-ip4-config.c create mode 100644 src/libnm-core-impl/nm-setting-ip6-config.c create mode 100644 src/libnm-core-impl/nm-setting-macsec.c create mode 100644 src/libnm-core-impl/nm-setting-macvlan.c create mode 100644 src/libnm-core-impl/nm-setting-match.c create mode 100644 src/libnm-core-impl/nm-setting-olpc-mesh.c create mode 100644 src/libnm-core-impl/nm-setting-ovs-bridge.c create mode 100644 src/libnm-core-impl/nm-setting-ovs-dpdk.c create mode 100644 src/libnm-core-impl/nm-setting-ovs-external-ids.c create mode 100644 src/libnm-core-impl/nm-setting-ovs-interface.c create mode 100644 src/libnm-core-impl/nm-setting-ovs-patch.c create mode 100644 src/libnm-core-impl/nm-setting-ovs-port.c create mode 100644 src/libnm-core-impl/nm-setting-ppp.c create mode 100644 src/libnm-core-impl/nm-setting-pppoe.c create mode 100644 src/libnm-core-impl/nm-setting-private.h create mode 100644 src/libnm-core-impl/nm-setting-proxy.c create mode 100644 src/libnm-core-impl/nm-setting-serial.c create mode 100644 src/libnm-core-impl/nm-setting-sriov.c create mode 100644 src/libnm-core-impl/nm-setting-tc-config.c create mode 100644 src/libnm-core-impl/nm-setting-team-port.c create mode 100644 src/libnm-core-impl/nm-setting-team.c create mode 100644 src/libnm-core-impl/nm-setting-tun.c create mode 100644 src/libnm-core-impl/nm-setting-user.c create mode 100644 src/libnm-core-impl/nm-setting-veth.c create mode 100644 src/libnm-core-impl/nm-setting-vlan.c create mode 100644 src/libnm-core-impl/nm-setting-vpn.c create mode 100644 src/libnm-core-impl/nm-setting-vrf.c create mode 100644 src/libnm-core-impl/nm-setting-vxlan.c create mode 100644 src/libnm-core-impl/nm-setting-wifi-p2p.c create mode 100644 src/libnm-core-impl/nm-setting-wimax.c create mode 100644 src/libnm-core-impl/nm-setting-wired.c create mode 100644 src/libnm-core-impl/nm-setting-wireguard.c create mode 100644 src/libnm-core-impl/nm-setting-wireless-security.c create mode 100644 src/libnm-core-impl/nm-setting-wireless.c create mode 100644 src/libnm-core-impl/nm-setting-wpan.c create mode 100644 src/libnm-core-impl/nm-setting.c create mode 100644 src/libnm-core-impl/nm-simple-connection.c create mode 100644 src/libnm-core-impl/nm-team-utils.c create mode 100644 src/libnm-core-impl/nm-team-utils.h create mode 100644 src/libnm-core-impl/nm-utils-private.h create mode 100644 src/libnm-core-impl/nm-utils.c create mode 100644 src/libnm-core-impl/nm-vpn-editor-plugin.c create mode 100644 src/libnm-core-impl/nm-vpn-plugin-info.c create mode 100644 src/libnm-core-impl/tests/certs/ca-no-ending-newline.pem create mode 100644 src/libnm-core-impl/tests/certs/pkcs8-decrypted.der create mode 100644 src/libnm-core-impl/tests/certs/pkcs8-enc-key.pem create mode 100644 src/libnm-core-impl/tests/certs/pkcs8-noenc-key.pem create mode 100644 src/libnm-core-impl/tests/certs/test-aes-128-key.pem create mode 100644 src/libnm-core-impl/tests/certs/test-aes-256-key.pem create mode 100644 src/libnm-core-impl/tests/certs/test-ca-cert.pem create mode 100644 src/libnm-core-impl/tests/certs/test-cert.p12 create mode 100644 src/libnm-core-impl/tests/certs/test-key-and-cert.pem create mode 100644 src/libnm-core-impl/tests/certs/test-key-only-decrypted.der create mode 100644 src/libnm-core-impl/tests/certs/test-key-only-decrypted.pem create mode 100644 src/libnm-core-impl/tests/certs/test-key-only.pem create mode 100644 src/libnm-core-impl/tests/certs/test-tpm2wrapped-key.pem create mode 100644 src/libnm-core-impl/tests/certs/test2-cert.p12 create mode 100644 src/libnm-core-impl/tests/certs/test2_ca_cert.pem create mode 100644 src/libnm-core-impl/tests/certs/test2_key_and_cert.pem create mode 100644 src/libnm-core-impl/tests/certs/test_ca_cert.der create mode 100644 src/libnm-core-impl/tests/certs/test_ca_cert.pem create mode 100644 src/libnm-core-impl/tests/certs/test_key_and_cert.pem create mode 100644 src/libnm-core-impl/tests/meson.build create mode 100644 src/libnm-core-impl/tests/nm-core-tests-enum-types.c.template create mode 100644 src/libnm-core-impl/tests/nm-core-tests-enum-types.h.template create mode 100644 src/libnm-core-impl/tests/test-compare.c create mode 100644 src/libnm-core-impl/tests/test-crypto.c create mode 100644 src/libnm-core-impl/tests/test-general-enums.h create mode 100644 src/libnm-core-impl/tests/test-general.c create mode 100644 src/libnm-core-impl/tests/test-keyfile.c create mode 100644 src/libnm-core-impl/tests/test-secrets.c create mode 100644 src/libnm-core-impl/tests/test-setting.c create mode 100644 src/libnm-core-impl/tests/test-settings-defaults.c (limited to 'src/libnm-core-impl') diff --git a/src/libnm-core-impl/README.md b/src/libnm-core-impl/README.md new file mode 100644 index 00000000..0fce8e1e --- /dev/null +++ b/src/libnm-core-impl/README.md @@ -0,0 +1,22 @@ +libnm-core-impl +=============== + +NetworkManager provides a client library [`libnm`](../../libnm). +NetworkManager core does not (dynamically) link against all of libnm. +Instead, it statically links against a part of it. +That part is the static helper library `libnm-core-impl`. + +`libnm-core-impl` implements (and provides) the API from +[`libnm-core-public`](../libnm-core-public), which is part of the public +API of [`libnm`](../../libnm). In this form, `libnm-core-impl` is part +of the implementation of [`libnm`](../../libnm). It also implements (and +provides) an internal API [`libnm-core-intern`](../libnm-core-intern) which +can only be used by those who link statically against `libnm-core-impl`. + +Only NetworkManager core and [`libnm`](../../libnm) are allowed to statically +link with `libnm-core-impl`. Consequently, only those are allowed to include +[`libnm-core-intern`](../libnm-core-intern). + +This directory should not be added to the include search path of other +components as they are only allowed to include [`libnm-core-public`](../libnm-core-public) +and [`libnm-core-intern`](../libnm-core-intern). diff --git a/src/libnm-core-impl/meson.build b/src/libnm-core-impl/meson.build new file mode 100644 index 00000000..2b769616 --- /dev/null +++ b/src/libnm-core-impl/meson.build @@ -0,0 +1,115 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libnm_core_impl_inc = include_directories('.') + +libnm_crypto_nss = static_library( + 'nm-crypto-nss', + sources: 'nm-crypto-nss.c', + dependencies: [ + libnm_core_public_dep, + crypto_nss_dep, + ], +) + +libnm_crypto_gnutls = static_library( + 'nm-crypto-gnutls', + sources: 'nm-crypto-gnutls.c', + dependencies: [ + libnm_core_public_dep, + crypto_gnutls_dep, + ], +) + +if crypto == 'nss' + libnm_crypto = libnm_crypto_nss +else + assert(crypto == 'gnutls', 'Unexpected setting "crypto=' + crypto + '"') + libnm_crypto = libnm_crypto_gnutls +endif + +libnm_core_settings_sources = files( + 'nm-setting-6lowpan.c', + 'nm-setting-8021x.c', + 'nm-setting-adsl.c', + 'nm-setting-bluetooth.c', + 'nm-setting-bond.c', + 'nm-setting-bridge-port.c', + 'nm-setting-bridge.c', + 'nm-setting-cdma.c', + 'nm-setting-connection.c', + 'nm-setting-dcb.c', + 'nm-setting-dummy.c', + 'nm-setting-ethtool.c', + 'nm-setting-generic.c', + 'nm-setting-gsm.c', + 'nm-setting-hostname.c', + 'nm-setting-infiniband.c', + 'nm-setting-ip-config.c', + 'nm-setting-ip-tunnel.c', + 'nm-setting-ip4-config.c', + 'nm-setting-ip6-config.c', + 'nm-setting-macsec.c', + 'nm-setting-macvlan.c', + 'nm-setting-match.c', + 'nm-setting-olpc-mesh.c', + 'nm-setting-ovs-bridge.c', + 'nm-setting-ovs-dpdk.c', + 'nm-setting-ovs-external-ids.c', + 'nm-setting-ovs-interface.c', + 'nm-setting-ovs-patch.c', + 'nm-setting-ovs-port.c', + 'nm-setting-ppp.c', + 'nm-setting-pppoe.c', + 'nm-setting-proxy.c', + 'nm-setting-serial.c', + 'nm-setting-sriov.c', + 'nm-setting-tc-config.c', + 'nm-setting-team-port.c', + 'nm-setting-team.c', + 'nm-setting-tun.c', + 'nm-setting-user.c', + 'nm-setting-veth.c', + 'nm-setting-vlan.c', + 'nm-setting-vpn.c', + 'nm-setting-vrf.c', + 'nm-setting-vxlan.c', + 'nm-setting-wifi-p2p.c', + 'nm-setting-wimax.c', + 'nm-setting-wired.c', + 'nm-setting-wireguard.c', + 'nm-setting-wireless-security.c', + 'nm-setting-wireless.c', + 'nm-setting-wpan.c', +) + +libnm_core_impl_sources = files( + 'nm-connection.c', + 'nm-crypto.c', + 'nm-dbus-utils.c', + 'nm-errors.c', + 'nm-keyfile-utils.c', + 'nm-keyfile.c', + 'nm-meta-setting-base-impl.c', + 'nm-property-compare.c', + 'nm-setting.c', + 'nm-simple-connection.c', + 'nm-team-utils.c', + 'nm-utils.c', + 'nm-vpn-editor-plugin.c', + 'nm-vpn-plugin-info.c', +) + +libnm_core_impl = static_library( + 'nm-core-impl', + sources: libnm_core_impl_sources + libnm_core_settings_sources + libnm_core_public_enum_sources + [nm_version_macro_header], + include_directories: [ + top_inc, + src_inc, + libnm_core_intern_inc, + ], + dependencies: [ + dl_dep, + libnm_core_public_dep, + uuid_dep, + ], +) diff --git a/src/libnm-core-impl/nm-connection-private.h b/src/libnm-core-impl/nm-connection-private.h new file mode 100644 index 00000000..0580625f --- /dev/null +++ b/src/libnm-core-impl/nm-connection-private.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#ifndef __NM_CONNECTION_PRIVATE_H__ +#define __NM_CONNECTION_PRIVATE_H__ + +#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE) + #error Cannot use this header. +#endif + +#include "nm-setting.h" +#include "nm-connection.h" + +NMSetting *_nm_connection_find_base_type_setting(NMConnection *connection); + +const char *_nm_connection_detect_slave_type(NMConnection *connection, NMSetting **out_s_port); + +gboolean _nm_connection_detect_slave_type_full(NMSettingConnection *s_con, + NMConnection * connection, + const char ** out_slave_type, + const char ** out_normerr_slave_setting_type, + const char ** out_normerr_missing_slave_type, + const char **out_normerr_missing_slave_type_port, + GError ** error); + +const char *_nm_connection_detect_bluetooth_type(NMConnection *self); + +gboolean _nm_setting_connection_verify_secondaries(GArray *secondaries, GError **error); + +gboolean _nm_connection_verify_required_interface_name(NMConnection *connection, GError **error); + +int _nm_setting_ovs_interface_verify_interface_type(NMSettingOvsInterface *self, + const char * type, + NMConnection * connection, + gboolean normalize, + gboolean * out_modified, + const char ** out_normalized_type, + GError ** error); + +#endif /* __NM_CONNECTION_PRIVATE_H__ */ diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c new file mode 100644 index 00000000..ed864db8 --- /dev/null +++ b/src/libnm-core-impl/nm-connection.c @@ -0,0 +1,3749 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2018 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-connection.h" + +#include + +#include "libnm-glib-aux/nm-uuid.h" +#include "nm-connection-private.h" +#include "nm-utils.h" +#include "nm-setting-private.h" +#include "libnm-core-intern/nm-core-internal.h" + +/** + * SECTION:nm-connection + * @short_description: Describes a connection to specific network or provider + * + * An #NMConnection describes all the settings and configuration values that + * are necessary to configure network devices for operation on a specific + * network. Connections are the fundamental operating object for + * NetworkManager; no device is connected without a #NMConnection, or + * disconnected without having been connected with a #NMConnection. + * + * Each #NMConnection contains a list of #NMSetting objects usually referenced + * by name (using nm_connection_get_setting_by_name()) or by type (with + * nm_connection_get_setting()). The settings describe the actual parameters + * with which the network devices are configured, including device-specific + * parameters (MTU, SSID, APN, channel, rate, etc) and IP-level parameters + * (addresses, routes, addressing methods, etc). + * + */ + +/*****************************************************************************/ + +enum { + SECRETS_UPDATED, + SECRETS_CLEARED, + CHANGED, + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_INTERFACE(NMConnection, nm_connection, G_TYPE_OBJECT) + +/*****************************************************************************/ + +static gboolean _nm_connection_clear_settings(NMConnection *connection, NMConnectionPrivate *priv); + +/*****************************************************************************/ + +#undef NM_IS_SIMPLE_CONNECTION +#define NM_IS_SIMPLE_CONNECTION(self) \ + ({ \ + gconstpointer _self1 = (self); \ + gboolean _result; \ + \ + _result = \ + (_self1 \ + && (((GTypeInstance *) _self1)->g_class == _nm_simple_connection_class_instance)); \ + \ + nm_assert(_result == G_TYPE_CHECK_INSTANCE_TYPE(_self1, NM_TYPE_SIMPLE_CONNECTION)); \ + \ + _result; \ + }) + +#undef NM_IS_CONNECTION +#define NM_IS_CONNECTION(self) \ + ({ \ + gconstpointer _self0 = (self); \ + \ + (_self0 \ + && (NM_IS_SIMPLE_CONNECTION(_self0) \ + || G_TYPE_CHECK_INSTANCE_TYPE(_self0, NM_TYPE_CONNECTION))); \ + }) + +/*****************************************************************************/ + +void +_nm_connection_private_clear(NMConnectionPrivate *priv) +{ + if (priv->self) { + _nm_connection_clear_settings(priv->self, priv); + nm_clear_g_free(&priv->path); + priv->self = NULL; + } +} + +static void +_nm_connection_private_free(gpointer data) +{ + NMConnectionPrivate *priv = data; + + _nm_connection_private_clear(priv); + + nm_g_slice_free(priv); +} + +static NMConnectionPrivate * +_nm_connection_get_private_from_qdata(NMConnection *connection) +{ + GQuark key; + NMConnectionPrivate *priv; + + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(!NM_IS_SIMPLE_CONNECTION(connection)); + + key = NM_CACHED_QUARK("NMConnectionPrivate"); + + priv = g_object_get_qdata((GObject *) connection, key); + if (G_UNLIKELY(!priv)) { + priv = g_slice_new(NMConnectionPrivate); + *priv = (NMConnectionPrivate){ + .self = connection, + }; + g_object_set_qdata_full((GObject *) connection, key, priv, _nm_connection_private_free); + } + + return priv; +} + +#define NM_CONNECTION_GET_PRIVATE(connection) \ + ({ \ + NMConnection * _connection = (connection); \ + NMConnectionPrivate *_priv; \ + \ + if (G_LIKELY(NM_IS_SIMPLE_CONNECTION(_connection))) \ + _priv = (gpointer) (&(((char *) _connection)[_nm_simple_connection_private_offset])); \ + else \ + _priv = _nm_connection_get_private_from_qdata(_connection); \ + \ + nm_assert(_priv && _priv->self == _connection); \ + \ + _priv; \ + }) + +/*****************************************************************************/ + +static void +_signal_emit_changed(NMConnection *self) +{ + g_signal_emit(self, signals[CHANGED], 0); +} + +static void +_signal_emit_secrets_updated(NMConnection *self, const char *setting_name) +{ + g_signal_emit(self, signals[SECRETS_UPDATED], 0, setting_name); +} + +static void +_signal_emit_secrets_cleared(NMConnection *self) +{ + g_signal_emit(self, signals[SECRETS_CLEARED], 0); +} + +/*****************************************************************************/ + +static void +_setting_notify_changed_cb(NMSetting *setting, GParamSpec *pspec, NMConnection *self) +{ + _signal_emit_changed(self); +} + +static void +_setting_notify_connect(NMConnection *connection, NMSetting *setting) +{ + g_signal_connect(setting, "notify", G_CALLBACK(_setting_notify_changed_cb), connection); +} + +static void +_setting_notify_disconnect(NMConnection *connection, NMSetting *setting) +{ + g_signal_handlers_disconnect_by_func(setting, + G_CALLBACK(_setting_notify_changed_cb), + connection); +} + +static void +_setting_notify_block(NMConnection *connection, NMSetting *setting) +{ + g_signal_handlers_block_by_func(setting, G_CALLBACK(_setting_notify_changed_cb), connection); +} + +static void +_setting_notify_unblock(NMConnection *connection, NMSetting *setting) +{ + g_signal_handlers_unblock_by_func(setting, G_CALLBACK(_setting_notify_changed_cb), connection); +} + +/*****************************************************************************/ + +static gboolean +_nm_connection_clear_settings(NMConnection *connection, NMConnectionPrivate *priv) +{ + gboolean changed = FALSE; + int i; + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + if (priv->settings[i]) { + _setting_notify_disconnect(connection, priv->settings[i]); + g_clear_object(&priv->settings[i]); + changed = TRUE; + } + } + return changed; +} + +static void +_nm_connection_add_setting(NMConnection *connection, NMSetting *setting) +{ + const NMMetaSettingInfo *setting_info; + NMConnectionPrivate * priv; + NMSetting * s_old; + + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(NM_IS_SETTING(setting)); + + setting_info = _nm_meta_setting_info_from_class(NM_SETTING_GET_CLASS(setting)); + if (!setting_info) + g_return_if_reached(); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + s_old = priv->settings[setting_info->meta_type]; + if (s_old == setting) + return; + + priv->settings[setting_info->meta_type] = setting; + + _setting_notify_connect(connection, setting); + + if (s_old) { + _setting_notify_disconnect(connection, s_old); + g_object_unref(s_old); + } +} + +/** + * nm_connection_add_setting: + * @connection: a #NMConnection + * @setting: (transfer full): the #NMSetting to add to the connection object + * + * Adds a #NMSetting to the connection, replacing any previous #NMSetting of the + * same name which has previously been added to the #NMConnection. The + * connection takes ownership of the #NMSetting object and does not increase + * the setting object's reference count. + **/ +void +nm_connection_add_setting(NMConnection *connection, NMSetting *setting) +{ + g_return_if_fail(NM_IS_CONNECTION(connection)); + g_return_if_fail(NM_IS_SETTING(setting)); + + _nm_connection_add_setting(connection, setting); + _signal_emit_changed(connection); +} + +gboolean +_nm_connection_remove_setting(NMConnection *connection, GType setting_type) +{ + NMConnectionPrivate * priv; + NMSetting * setting; + const NMMetaSettingInfo *setting_info; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + + setting_info = _nm_meta_setting_info_from_gtype(setting_type); + if (!setting_info) + g_return_val_if_reached(FALSE); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + setting = g_steal_pointer(&priv->settings[setting_info->meta_type]); + if (!setting) + return FALSE; + + _setting_notify_disconnect(connection, setting); + _signal_emit_changed(connection); + g_object_unref(setting); + return TRUE; +} + +/** + * nm_connection_remove_setting: + * @connection: a #NMConnection + * @setting_type: the #GType of the setting object to remove + * + * Removes the #NMSetting with the given #GType from the #NMConnection. This + * operation dereferences the #NMSetting object. + **/ +void +nm_connection_remove_setting(NMConnection *connection, GType setting_type) +{ + _nm_connection_remove_setting(connection, setting_type); +} + +static gpointer +_connection_get_setting(NMConnection *connection, GType setting_type) +{ + NMSetting * setting; + const NMMetaSettingInfo *setting_info; + + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(g_type_is_a(setting_type, NM_TYPE_SETTING)); + + setting_info = _nm_meta_setting_info_from_gtype(setting_type); + if (!setting_info) + g_return_val_if_reached(NULL); + + setting = NM_CONNECTION_GET_PRIVATE(connection)->settings[setting_info->meta_type]; + + nm_assert(!setting || G_TYPE_CHECK_INSTANCE_TYPE(setting, setting_type)); + return setting; +} + +static gpointer +_connection_get_setting_by_meta_type(NMConnectionPrivate *priv, NMMetaSettingType meta_type) +{ + nm_assert(priv); + nm_assert(_NM_INT_NOT_NEGATIVE(meta_type)); + nm_assert(meta_type < _NM_META_SETTING_TYPE_NUM); + + return priv->settings[meta_type]; +} + +static gpointer +_connection_get_setting_check(NMConnection *connection, GType setting_type) +{ + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + return _connection_get_setting(connection, setting_type); +} + +static gpointer +_connection_get_setting_by_meta_type_check(NMConnection *connection, NMMetaSettingType meta_type) +{ + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + return _connection_get_setting_by_meta_type(NM_CONNECTION_GET_PRIVATE(connection), meta_type); +} + +/** + * nm_connection_get_setting: + * @connection: a #NMConnection + * @setting_type: the #GType of the setting object to return + * + * Gets the #NMSetting with the given #GType, if one has been previously added + * to the #NMConnection. + * + * Returns: (transfer none): the #NMSetting, or %NULL if no setting of that type was previously + * added to the #NMConnection + **/ +NMSetting * +nm_connection_get_setting(NMConnection *connection, GType setting_type) +{ + g_return_val_if_fail(g_type_is_a(setting_type, NM_TYPE_SETTING), NULL); + + return _connection_get_setting_check(connection, setting_type); +} + +NMSettingIPConfig * +nm_connection_get_setting_ip_config(NMConnection *connection, int addr_family) +{ + nm_assert_addr_family(addr_family); + + return NM_SETTING_IP_CONFIG(_connection_get_setting( + connection, + (addr_family == AF_INET) ? NM_TYPE_SETTING_IP4_CONFIG : NM_TYPE_SETTING_IP6_CONFIG)); +} + +/** + * nm_connection_get_setting_by_name: + * @connection: a #NMConnection + * @name: a setting name + * + * Gets the #NMSetting with the given name, if one has been previously added + * the #NMConnection. + * + * Returns: (transfer none): the #NMSetting, or %NULL if no setting with that name was previously + * added to the #NMConnection + **/ +NMSetting * +nm_connection_get_setting_by_name(NMConnection *connection, const char *name) +{ + GType type; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + type = nm_setting_lookup_type(name); + return type ? _connection_get_setting(connection, type) : NULL; +} + +/*****************************************************************************/ + +gpointer /* (NMSetting *) */ +_nm_connection_check_main_setting(NMConnection *connection, + const char * setting_name, + GError ** error) +{ + NMSetting *setting; + + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(setting_name); + + if (!nm_connection_is_type(connection, setting_name)) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "connection type is not \"%s\"", + setting_name); + return NULL; + } + + setting = nm_connection_get_setting_by_name(connection, setting_name); + if (!setting) { + nm_utils_error_set(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "connection misses \"%s\" settings", + setting_name); + return NULL; + } + + return setting; +} + +/*****************************************************************************/ + +static gboolean +validate_permissions_type(GVariant *variant, GError **error) +{ + GVariant *s_con; + GVariant *permissions; + gboolean valid = TRUE; + + /* Ensure the connection::permissions item (if present) is the correct + * type, otherwise the g_object_set() will throw a warning and ignore the + * error, leaving us with no permissions. + */ + s_con = g_variant_lookup_value(variant, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + if (!s_con) + return TRUE; + + permissions = g_variant_lookup_value(s_con, NM_SETTING_CONNECTION_PERMISSIONS, NULL); + if (permissions) { + if (!g_variant_is_of_type(permissions, G_VARIANT_TYPE_STRING_ARRAY)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("wrong type; should be a list of strings.")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_PERMISSIONS); + valid = FALSE; + } + g_variant_unref(permissions); + } + + g_variant_unref(s_con); + return valid; +} + +static void +_auto_settings(NMSetting ***p_settings) +{ + NMSetting **settings = *p_settings; + int i; + + if (settings) { + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + if (settings[i]) + g_object_unref(settings[i]); + } + } +} + +/** + * _nm_connection_replace_settings: + * @connection: a #NMConnection + * @new_settings: a #GVariant of type %NM_VARIANT_TYPE_CONNECTION, with the new settings + * @parse_flags: flags. + * @error: location to store error, or %NULL + * + * Replaces @connection's settings with @new_settings (which must be + * syntactically valid, and describe a known type of connection, but does not + * need to result in a connection that passes nm_connection_verify()). + * + * Returns: %TRUE if connection was updated, %FALSE if @new_settings could not + * be deserialized (in which case @connection will be unchanged). + * Only exception is the NM_SETTING_PARSE_FLAGS_NORMALIZE flag: if normalization + * fails, the input @connection is already modified and the original settings + * are lost. + **/ +gboolean +_nm_connection_replace_settings(NMConnection * connection, + GVariant * new_settings, + NMSettingParseFlags parse_flags, + GError ** error) +{ + NMSetting * settings[_NM_META_SETTING_TYPE_NUM] = {}; + nm_auto(_auto_settings) NMSetting **settings_cleanup = settings; + GVariantIter iter; + const char * setting_name; + GVariant * setting_dict; + gboolean changed; + gboolean success; + guint n_settings = 0; + int i; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(g_variant_is_of_type(new_settings, NM_VARIANT_TYPE_CONNECTION), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + nm_assert(!NM_FLAGS_ANY(parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL)); + nm_assert(!NM_FLAGS_ALL(parse_flags, + NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT)); + + if (!NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT) + && !validate_permissions_type(new_settings, error)) + return FALSE; + + g_variant_iter_init(&iter, new_settings); + while (g_variant_iter_next(&iter, "{&s@a{sv}}", &setting_name, &setting_dict)) { + gs_unref_variant GVariant *setting_dict_free = NULL; + GError * local = NULL; + NMSetting * setting; + const NMMetaSettingInfo * setting_info; + + setting_dict_free = setting_dict; + + setting_info = nm_meta_setting_infos_by_name(setting_name); + if (!setting_info) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT)) + continue; + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("unknown setting name")); + g_prefix_error(error, "%s: ", setting_name); + return FALSE; + } + + _nm_assert_setting_info(setting_info, 0); + + if (settings[setting_info->meta_type]) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("duplicate setting name")); + g_prefix_error(error, "%s: ", setting_name); + return FALSE; + } + /* last wins. We remove the setting of this type, and will + * add the new one afterwards. */ + g_clear_object(&settings[setting_info->meta_type]); + break; + } + + setting = _nm_setting_new_from_dbus(setting_info->get_setting_gtype(), + setting_dict, + new_settings, + parse_flags, + &local); + + if (!setting) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT)) + continue; + g_propagate_error(error, local); + return FALSE; + } + + settings[setting_info->meta_type] = setting; + n_settings++; + } + + if (_nm_connection_clear_settings(connection, NM_CONNECTION_GET_PRIVATE(connection))) + changed = TRUE; + else + changed = (n_settings > 0); + + /* Note: @settings might be empty in which case the connection + * has no NMSetting instances... which is fine, just something + * to be aware of. */ + if (n_settings > 0) { + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + if (settings[i]) + _nm_connection_add_setting(connection, g_steal_pointer(&settings[i])); + } + settings_cleanup = NULL; + } + + /* If verification/normalization fails, the original connection + * is already lost. From an API point of view, it would be nicer + * not to touch the input argument if we fail at the end. + * However, that would require creating a temporary connection + * to validate it first. As none of the caller cares about the + * state of the @connection when normalization fails, just do it + * this way. */ + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_NORMALIZE)) + success = nm_connection_normalize(connection, NULL, NULL, error); + else + success = TRUE; + + if (changed) + _signal_emit_changed(connection); + return success; +} + +/** + * nm_connection_replace_settings: + * @connection: a #NMConnection + * @new_settings: a #GVariant of type %NM_VARIANT_TYPE_CONNECTION, with the new settings + * @error: location to store error, or %NULL + * + * Replaces @connection's settings with @new_settings (which must be + * syntactically valid, and describe a known type of connection, but does not + * need to result in a connection that passes nm_connection_verify()). + * + * Returns: %TRUE if connection was updated, %FALSE if @new_settings could not + * be deserialized (in which case @connection will be unchanged). + **/ +gboolean +nm_connection_replace_settings(NMConnection *connection, GVariant *new_settings, GError **error) +{ + return _nm_connection_replace_settings(connection, + new_settings, + NM_SETTING_PARSE_FLAGS_NONE, + error); +} + +/** + * nm_connection_replace_settings_from_connection: + * @connection: a #NMConnection + * @new_connection: a #NMConnection to replace the settings of @connection with + * + * Deep-copies the settings of @new_connection and replaces the settings of @connection + * with the copied settings. + **/ +void +nm_connection_replace_settings_from_connection(NMConnection *connection, + NMConnection *new_connection) +{ + NMConnectionPrivate *priv; + NMConnectionPrivate *new_priv; + gboolean changed; + int i; + + g_return_if_fail(NM_IS_CONNECTION(connection)); + g_return_if_fail(NM_IS_CONNECTION(new_connection)); + + /* When 'connection' and 'new_connection' are the same object simply return + * in order not to destroy 'connection'. + */ + if (connection == new_connection) + return; + + /* No need to validate permissions like nm_connection_replace_settings() + * since we're dealing with an NMConnection which has already done that. + */ + + priv = NM_CONNECTION_GET_PRIVATE(connection); + new_priv = NM_CONNECTION_GET_PRIVATE(new_connection); + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *s_old; + + if (new_priv->settings[i] == priv->settings[i]) + continue; + + changed = TRUE; + + s_old = g_steal_pointer(&priv->settings[i]); + + if (new_priv->settings[i]) { + priv->settings[i] = nm_setting_duplicate(new_priv->settings[i]); + _setting_notify_connect(connection, priv->settings[i]); + } + if (s_old) { + _setting_notify_disconnect(connection, s_old); + g_object_unref(s_old); + } + } + + if (changed) + _signal_emit_changed(connection); +} + +/** + * nm_connection_clear_settings: + * @connection: a #NMConnection + * + * Deletes all of @connection's settings. + **/ +void +nm_connection_clear_settings(NMConnection *connection) +{ + g_return_if_fail(NM_IS_CONNECTION(connection)); + + if (_nm_connection_clear_settings(connection, NM_CONNECTION_GET_PRIVATE(connection))) + _signal_emit_changed(connection); +} + +/** + * nm_connection_compare: + * @a: a #NMConnection + * @b: a second #NMConnection to compare with the first + * @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT + * + * Compares two #NMConnection objects for similarity, with comparison behavior + * modified by a set of flags. See nm_setting_compare() for a description of + * each flag's behavior. + * + * Returns: %TRUE if the comparison succeeds, %FALSE if it does not + **/ +gboolean +nm_connection_compare(NMConnection *a, NMConnection *b, NMSettingCompareFlags flags) +{ + NMConnectionPrivate *a_priv; + NMConnectionPrivate *b_priv; + int i; + + if (a == b) + return TRUE; + if (!a || !b) + return FALSE; + + a_priv = NM_CONNECTION_GET_PRIVATE(a); + b_priv = NM_CONNECTION_GET_PRIVATE(b); + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + if (a_priv->settings[i] == b_priv->settings[i]) + continue; + + if (!a_priv->settings[i] || !b_priv->settings[i]) + return FALSE; + + if (!_nm_setting_compare(a, a_priv->settings[i], b, b_priv->settings[i], flags)) + return FALSE; + } + return TRUE; +} + +static gboolean +diff_one_connection(NMConnection * a, + NMConnection * b, + NMSettingCompareFlags flags, + gboolean invert_results, + GHashTable * diffs) +{ + NMConnectionPrivate *a_priv = NM_CONNECTION_GET_PRIVATE(a); + NMConnectionPrivate *b_priv = b ? NM_CONNECTION_GET_PRIVATE(b) : NULL; + gboolean diff_found = FALSE; + int i; + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *a_setting = a_priv->settings[i]; + + if (a_setting) { + NMSetting * b_setting = b ? b_priv->settings[i] : NULL; + const char *setting_name = nm_setting_get_name(a_setting); + GHashTable *results; + gboolean new_results = TRUE; + + results = g_hash_table_lookup(diffs, setting_name); + if (results) + new_results = FALSE; + + if (!_nm_setting_diff(a, a_setting, b, b_setting, flags, invert_results, &results)) + diff_found = TRUE; + + if (new_results && results) + g_hash_table_insert(diffs, g_strdup(setting_name), results); + } + } + + return diff_found; +} + +/** + * nm_connection_diff: + * @a: a #NMConnection + * @b: a second #NMConnection to compare with the first + * @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT + * @out_settings: (element-type utf8 GLib.HashTable): if the + * connections differ, on return a hash table mapping setting names to + * second-level GHashTable (utf8 to guint32), which contains the key names that + * differ mapped to one or more of %NMSettingDiffResult as a bitfield + * + * Compares two #NMConnection objects for similarity, with comparison behavior + * modified by a set of flags. See nm_setting_compare() for a description of + * each flag's behavior. If the connections differ, settings and keys within + * each setting that differ are added to the returned @out_settings hash table. + * No values are returned, only key names. + * + * Returns: %TRUE if the connections contain the same values, %FALSE if they do + * not + **/ +gboolean +nm_connection_diff(NMConnection * a, + NMConnection * b, + NMSettingCompareFlags flags, + GHashTable ** out_settings) +{ + GHashTable *diffs; + gboolean diff_found = FALSE; + + g_return_val_if_fail(NM_IS_CONNECTION(a), FALSE); + g_return_val_if_fail(!out_settings || !*out_settings, FALSE); + g_return_val_if_fail(!b || NM_IS_CONNECTION(b), FALSE); + + if (a == b) + return TRUE; + + diffs = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_hash_table_destroy); + + /* Diff A to B, then B to A to capture keys in B that aren't in A */ + if (diff_one_connection(a, b, flags, FALSE, diffs)) + diff_found = TRUE; + if (b && diff_one_connection(b, a, flags, TRUE, diffs)) + diff_found = TRUE; + + nm_assert(diff_found == (g_hash_table_size(diffs) != 0)); + + if (g_hash_table_size(diffs) == 0) { + g_hash_table_destroy(diffs); + diffs = NULL; + } + + NM_SET_OUT(out_settings, diffs); + + return !diff_found; +} + +NMSetting * +_nm_connection_find_base_type_setting(NMConnection *connection) +{ + NMConnectionPrivate *priv = NM_CONNECTION_GET_PRIVATE(connection); + NMSetting * setting = NULL; + NMSettingPriority setting_prio = NM_SETTING_PRIORITY_USER; + int i; + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting * s_iter = priv->settings[i]; + NMSettingPriority s_iter_prio; + + if (!s_iter) + continue; + + s_iter_prio = _nm_setting_get_base_type_priority(s_iter); + if (s_iter_prio == NM_SETTING_PRIORITY_INVALID) + continue; + + if (setting) { + if (s_iter_prio > setting_prio) { + continue; + } else if (s_iter_prio == setting_prio) { + NMSettingConnection *s_con = nm_connection_get_setting_connection(connection); + const char * type; + + if (s_con) { + type = nm_setting_connection_get_connection_type(s_con); + if (type) + return nm_connection_get_setting_by_name(connection, type); + } + return NULL; + } + } + setting = s_iter; + setting_prio = s_iter_prio; + } + return setting; +} + +static gboolean +_normalize_connection_uuid(NMConnection *self) +{ + NMSettingConnection *s_con = nm_connection_get_setting_connection(self); + char uuid_normalized[37]; + const char * uuid; + + nm_assert(s_con); + + uuid = nm_setting_connection_get_uuid(s_con); + + if (uuid) { + gboolean uuid_is_normalized; + + if (!nm_uuid_is_valid_nm(uuid, &uuid_is_normalized, uuid_normalized)) + return nm_assert_unreachable_val(FALSE); + if (!uuid_is_normalized) + return FALSE; + } else + nm_uuid_generate_random_str_arr(uuid_normalized); + + g_object_set(s_con, NM_SETTING_CONNECTION_UUID, uuid_normalized, NULL); + return TRUE; +} + +gboolean +_nm_setting_connection_verify_secondaries(GArray *secondaries, GError **error) +{ + const char *const *strv; + const guint len = nm_g_array_len(secondaries); + guint has_normalizable = FALSE; + gboolean has_invalid = FALSE; + gboolean has_duplicate = FALSE; + guint i; + + if (len == 0) + return TRUE; + + /* For historic reasons, the secondaries were not normalized/validated. + * + * Now, when we find any invalid/non-normalized values, we reject/normalize + * them. We also filter out duplicates. */ + + strv = nm_strvarray_get_strv_non_empty(secondaries, NULL); + + for (i = 0; i < len; i++) { + const char *uuid = strv[i]; + gboolean normalized; + + if (!nm_uuid_is_valid_nm(uuid, &normalized, NULL)) { + has_invalid = TRUE; + goto out; + } + if (normalized) + has_normalizable = TRUE; + } + if (has_normalizable) + goto out; + + if (len > 1) { + gs_free const char **strv_to_free = NULL; + const char ** strv2; + + strv2 = nm_utils_strv_dup_shallow_maybe_a(20, strv, len, &strv_to_free); + nm_utils_strv_sort(strv2, len); + has_duplicate = nm_strv_has_duplicate(strv2, len, TRUE); + } + +out: + if (has_invalid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("has an invalid UUID")); + } else if (has_normalizable) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("has a UUID that requires normalization")); + } else if (has_duplicate) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("has duplicate UUIDs")); + } else + return TRUE; + + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SECONDARIES); + return FALSE; +} + +static gboolean +_normalize_connection_secondaries(NMConnection *self) +{ + NMSettingConnection *s_con = nm_connection_get_setting_connection(self); + GArray * secondaries; + gs_strfreev char ** strv = NULL; + guint i; + guint j; + + nm_assert(s_con); + + secondaries = _nm_setting_connection_get_secondaries(s_con); + if (nm_g_array_len(secondaries) == 0) + return FALSE; + + if (_nm_setting_connection_verify_secondaries(secondaries, NULL)) + return FALSE; + + strv = nm_strvarray_get_strv_non_empty_dup(secondaries, NULL); + for (i = 0, j = 0; strv[i]; i++) { + gs_free char *s = g_steal_pointer(&strv[i]); + char uuid_normalized[37]; + gboolean uuid_is_normalized; + + if (!nm_uuid_is_valid_nm(s, &uuid_is_normalized, uuid_normalized)) + continue; + + if (nm_utils_strv_find_first(strv, j, uuid_is_normalized ? uuid_normalized : s) >= 0) + continue; + + strv[j++] = uuid_is_normalized ? g_strdup(uuid_normalized) : g_steal_pointer(&s); + } + strv[j] = NULL; + + g_object_set(s_con, NM_SETTING_CONNECTION_SECONDARIES, strv, NULL); + return TRUE; +} + +static gboolean +_normalize_connection_type(NMConnection *self) +{ + NMSettingConnection *s_con = nm_connection_get_setting_connection(self); + NMSetting * s_base = NULL; + const char * type; + + type = nm_setting_connection_get_connection_type(s_con); + + if (type) { + s_base = nm_connection_get_setting_by_name(self, type); + + if (!s_base) { + GType base_type = nm_setting_lookup_type(type); + + g_return_val_if_fail(base_type, FALSE); + nm_connection_add_setting(self, g_object_new(base_type, NULL)); + return TRUE; + } + } else { + s_base = _nm_connection_find_base_type_setting(self); + g_return_val_if_fail(s_base, FALSE); + + type = nm_setting_get_name(s_base); + g_object_set(s_con, NM_SETTING_CONNECTION_TYPE, type, NULL); + return TRUE; + } + + return FALSE; +} + +const char * +_nm_connection_detect_bluetooth_type(NMConnection *self) +{ + NMSettingBluetooth *s_bt = nm_connection_get_setting_bluetooth(self); + + if (s_bt && nm_setting_bluetooth_get_connection_type(s_bt)) { + if (nm_connection_get_setting_gsm(self) || nm_connection_get_setting_cdma(self)) + return NM_SETTING_BLUETOOTH_TYPE_DUN; + if (nm_connection_get_setting_bridge(self)) + return NM_SETTING_BLUETOOTH_TYPE_NAP; + return NM_SETTING_BLUETOOTH_TYPE_PANU; + } + + /* NULL means the connection is not a bluetooth type, or it needs + * no normalization, as the type is set explicitly. */ + return NULL; +} + +const char * +_nm_connection_detect_slave_type(NMConnection *connection, NMSetting **out_s_port) +{ + NMConnectionPrivate *priv = NM_CONNECTION_GET_PRIVATE(connection); + const char * slave_type = NULL; + NMSetting * s_port = NULL; + int i; + static const struct { + NMMetaSettingType meta_type; + const char * controller_type_name; + } infos[] = { + {NM_META_SETTING_TYPE_BRIDGE_PORT, NM_SETTING_BRIDGE_SETTING_NAME}, + {NM_META_SETTING_TYPE_TEAM_PORT, NM_SETTING_TEAM_SETTING_NAME}, + {NM_META_SETTING_TYPE_OVS_PORT, NM_SETTING_OVS_BRIDGE_SETTING_NAME}, + {NM_META_SETTING_TYPE_OVS_INTERFACE, NM_SETTING_OVS_PORT_SETTING_NAME}, + }; + + for (i = 0; i < (int) G_N_ELEMENTS(infos); i++) { + NMSetting *setting = priv->settings[infos[i].meta_type]; + + if (!setting) + continue; + + if (slave_type) { + /* there are more then one matching port types, cannot detect the slave type. */ + slave_type = NULL; + s_port = NULL; + break; + } + slave_type = infos[i].controller_type_name; + s_port = setting; + } + + if (out_s_port) + *out_s_port = s_port; + return slave_type; +} + +static gboolean +_normalize_connection_slave_type(NMConnection *self) +{ + NMSettingConnection *s_con = nm_connection_get_setting_connection(self); + const char * slave_type, *port_type; + + if (!s_con) + return FALSE; + if (!nm_setting_connection_get_master(s_con)) + return FALSE; + + slave_type = nm_setting_connection_get_slave_type(s_con); + if (slave_type) { + if (_nm_setting_slave_type_is_valid(slave_type, &port_type) && port_type) { + NMSetting *s_port; + + s_port = nm_connection_get_setting_by_name(self, port_type); + if (!s_port) { + GType p_type = nm_setting_lookup_type(port_type); + + g_return_val_if_fail(p_type, FALSE); + nm_connection_add_setting(self, g_object_new(p_type, NULL)); + return TRUE; + } + } + } else { + if ((slave_type = _nm_connection_detect_slave_type(self, NULL))) { + g_object_set(s_con, NM_SETTING_CONNECTION_SLAVE_TYPE, slave_type, NULL); + return TRUE; + } + } + return FALSE; +} + +static gboolean +_normalize_ethernet_link_neg(NMConnection *self) +{ + NMSettingWired *s_wired = nm_connection_get_setting_wired(self); + + if (s_wired) { + guint32 speed = nm_setting_wired_get_speed(s_wired); + const char *duplex = nm_setting_wired_get_duplex(s_wired); + + if ((speed && !duplex) || (!speed && duplex)) { + speed = 0; + duplex = NULL; + g_object_set(s_wired, + NM_SETTING_WIRED_SPEED, + (guint) speed, + NM_SETTING_WIRED_DUPLEX, + duplex, + NULL); + return TRUE; + } + } + + return FALSE; +} + +/** + * _supports_addr_family: + * @self: a #NMConnection + * @family: AF_* + * + * Check whether the connection supports certain L3 address family, + * in order to be able to tell whether is should have the corresponding + * setting ("ipv4" for AF_INET and "ipv6" for AF_INET6). + * + * If AF_UNSPEC is given, then the function checks whether the connection + * supports any L3 configuration at all. + * + * Returns: %TRUE if the AF is supported, %FALSE otherwise + **/ +static gboolean +_supports_addr_family(NMConnection *self, int family) +{ + const char * connection_type = nm_connection_get_connection_type(self); + NMSettingConnection *s_con; + + g_return_val_if_fail(connection_type, TRUE); + if (strcmp(connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME) == 0) + return TRUE; + if (strcmp(connection_type, NM_SETTING_WPAN_SETTING_NAME) == 0) + return FALSE; + if (strcmp(connection_type, NM_SETTING_6LOWPAN_SETTING_NAME) == 0) + return family == AF_INET6 || family == AF_UNSPEC; + if ((s_con = nm_connection_get_setting_connection(self)) + && (nm_streq0(nm_setting_connection_get_slave_type(s_con), NM_SETTING_VRF_SETTING_NAME))) + return TRUE; + + return !nm_setting_connection_get_master(nm_connection_get_setting_connection(self)); +} + +static gboolean +_normalize_ip_config(NMConnection *self, GHashTable *parameters) +{ + NMSettingIPConfig *s_ip4, *s_ip6; + NMSettingProxy * s_proxy; + NMSetting * setting; + gboolean changed = FALSE; + guint num, i; + + s_ip4 = nm_connection_get_setting_ip4_config(self); + s_ip6 = nm_connection_get_setting_ip6_config(self); + s_proxy = nm_connection_get_setting_proxy(self); + + if (_supports_addr_family(self, AF_INET)) { + if (!s_ip4) { + const char *default_ip4_method = NM_SETTING_IP4_CONFIG_METHOD_AUTO; + + if (nm_connection_is_type(self, NM_SETTING_WIREGUARD_SETTING_NAME)) + default_ip4_method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED; + + /* But if no IP4 setting was specified, assume the caller was just + * being lazy and use the default method. + */ + setting = nm_setting_ip4_config_new(); + + g_object_set(setting, NM_SETTING_IP_CONFIG_METHOD, default_ip4_method, NULL); + nm_connection_add_setting(self, setting); + changed = TRUE; + } else { + if (nm_setting_ip_config_get_gateway(s_ip4) + && nm_setting_ip_config_get_never_default(s_ip4)) { + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_GATEWAY, NULL, NULL); + changed = TRUE; + } + + if (nm_streq0(nm_setting_ip_config_get_method(s_ip4), + NM_SETTING_IP4_CONFIG_METHOD_DISABLED) + && !nm_setting_ip_config_get_may_fail(s_ip4)) { + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE, NULL); + changed = TRUE; + } + + num = nm_setting_ip_config_get_num_addresses(s_ip4); + if (num > 1 + && nm_streq0(nm_setting_ip_config_get_method(s_ip4), + NM_SETTING_IP4_CONFIG_METHOD_SHARED)) { + for (i = num - 1; i > 0; i--) + nm_setting_ip_config_remove_address(s_ip4, i); + changed = TRUE; + } + } + } else { + if (s_ip4) { + nm_connection_remove_setting(self, NM_TYPE_SETTING_IP4_CONFIG); + changed = TRUE; + } + } + + if (_supports_addr_family(self, AF_INET6)) { + if (!s_ip6) { + const char *default_ip6_method = NULL; + + if (parameters) + default_ip6_method = + g_hash_table_lookup(parameters, + NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD); + if (!default_ip6_method) { + if (nm_connection_is_type(self, NM_SETTING_WIREGUARD_SETTING_NAME)) + default_ip6_method = NM_SETTING_IP6_CONFIG_METHOD_IGNORE; + else + default_ip6_method = NM_SETTING_IP6_CONFIG_METHOD_AUTO; + } + + /* If no IP6 setting was specified, then assume that means IP6 config is + * allowed to fail. + */ + setting = nm_setting_ip6_config_new(); + + g_object_set(setting, + NM_SETTING_IP_CONFIG_METHOD, + default_ip6_method, + NM_SETTING_IP_CONFIG_MAY_FAIL, + TRUE, + NULL); + nm_connection_add_setting(self, setting); + changed = TRUE; + } else { + const char *token; + + token = nm_setting_ip6_config_get_token((NMSettingIP6Config *) s_ip6); + if (token + && nm_setting_ip6_config_get_addr_gen_mode((NMSettingIP6Config *) s_ip6) + == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) { + struct in6_addr i6_token; + char normalized[NM_UTILS_INET_ADDRSTRLEN]; + + if (inet_pton(AF_INET6, token, &i6_token) == 1 + && _nm_utils_inet6_is_token(&i6_token)) { + _nm_utils_inet6_ntop(&i6_token, normalized); + if (g_strcmp0(token, normalized)) { + g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_TOKEN, normalized, NULL); + changed = TRUE; + } + } + } + + if (nm_setting_ip_config_get_gateway(s_ip6) + && nm_setting_ip_config_get_never_default(s_ip6)) { + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, NULL, NULL); + changed = TRUE; + } + + if (NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip6), + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED) + && !nm_setting_ip_config_get_may_fail(s_ip6)) { + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE, NULL); + changed = TRUE; + } + } + } else { + if (s_ip6) { + nm_connection_remove_setting(self, NM_TYPE_SETTING_IP6_CONFIG); + changed = TRUE; + } + } + + if (_supports_addr_family(self, AF_UNSPEC)) { + if (!s_proxy) { + setting = nm_setting_proxy_new(); + nm_connection_add_setting(self, setting); + changed = TRUE; + } + } else { + if (s_proxy) { + nm_connection_remove_setting(self, NM_TYPE_SETTING_PROXY); + changed = TRUE; + } + } + + return changed; +} + +static gboolean +_normalize_infiniband_mtu(NMConnection *self) +{ + NMSettingInfiniband *s_infini = nm_connection_get_setting_infiniband(self); + + if (!s_infini || nm_setting_infiniband_get_mtu(s_infini) <= NM_INFINIBAND_MAX_MTU + || !NM_IN_STRSET(nm_setting_infiniband_get_transport_mode(s_infini), + "datagram", + "connected")) + return FALSE; + + g_object_set(s_infini, NM_SETTING_INFINIBAND_MTU, (guint) NM_INFINIBAND_MAX_MTU, NULL); + return TRUE; +} + +static gboolean +_normalize_bond_mode(NMConnection *self) +{ + NMSettingBond *s_bond = nm_connection_get_setting_bond(self); + + /* Convert mode from numeric to string notation */ + if (s_bond) { + const char *mode = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MODE); + int mode_int = nm_utils_bond_mode_string_to_int(mode); + + if (mode_int != -1) { + const char *mode_new = nm_utils_bond_mode_int_to_string(mode_int); + + if (!nm_streq0(mode_new, mode)) { + nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_MODE, mode_new); + return TRUE; + } + } + } + return FALSE; +} + +static gboolean +_normalize_bond_options(NMConnection *self) +{ + NMSettingBond *s_bond = nm_connection_get_setting_bond(self); + gboolean changed = FALSE; + const char * name, *mode_str; + NMBondMode mode; + guint32 num, i; + + /* Strip away unsupported options for current mode */ + if (s_bond) { + mode_str = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MODE); + mode = _nm_setting_bond_mode_from_string(mode_str); + if (mode == NM_BOND_MODE_UNKNOWN) + return FALSE; +again: + num = nm_setting_bond_get_num_options(s_bond); + for (i = 0; i < num; i++) { + if (nm_setting_bond_get_option(s_bond, i, &name, NULL) + && !_nm_setting_bond_option_supported(name, mode)) { + nm_setting_bond_remove_option(s_bond, name); + changed = TRUE; + goto again; + } + } + } + + return changed; +} + +static gboolean +_normalize_wireless_mac_address_randomization(NMConnection *self) +{ + NMSettingWireless * s_wifi = nm_connection_get_setting_wireless(self); + const char * cloned_mac_address; + NMSettingMacRandomization mac_address_randomization; + + if (!s_wifi) + return FALSE; + + mac_address_randomization = nm_setting_wireless_get_mac_address_randomization(s_wifi); + if (!NM_IN_SET(mac_address_randomization, + NM_SETTING_MAC_RANDOMIZATION_DEFAULT, + NM_SETTING_MAC_RANDOMIZATION_NEVER, + NM_SETTING_MAC_RANDOMIZATION_ALWAYS)) + return FALSE; + + cloned_mac_address = nm_setting_wireless_get_cloned_mac_address(s_wifi); + if (cloned_mac_address) { + if (nm_streq(cloned_mac_address, "random")) { + if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS) + return FALSE; + mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_ALWAYS; + } else if (nm_streq(cloned_mac_address, "permanent")) { + if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_NEVER) + return FALSE; + mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_NEVER; + } else { + if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT) + return FALSE; + mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT; + } + g_object_set(s_wifi, + NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION, + mac_address_randomization, + NULL); + return TRUE; + } + if (mac_address_randomization != NM_SETTING_MAC_RANDOMIZATION_DEFAULT) { + g_object_set(s_wifi, + NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, + mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS ? "random" + : "permanent", + NULL); + return TRUE; + } + return FALSE; +} + +static gboolean +_normalize_macsec(NMConnection *self) +{ + NMSettingMacsec *s_macsec = nm_connection_get_setting_macsec(self); + gboolean changed = FALSE; + + if (!s_macsec) + return FALSE; + + if (nm_setting_macsec_get_mode(s_macsec) != NM_SETTING_MACSEC_MODE_PSK) { + if (nm_setting_macsec_get_mka_cak(s_macsec)) { + g_object_set(s_macsec, NM_SETTING_MACSEC_MKA_CAK, NULL, NULL); + changed = TRUE; + } + if (nm_setting_macsec_get_mka_ckn(s_macsec)) { + g_object_set(s_macsec, NM_SETTING_MACSEC_MKA_CKN, NULL, NULL); + changed = TRUE; + } + } + + return changed; +} + +static gboolean +_normalize_team_config(NMConnection *self) +{ + NMSettingTeam *s_team = nm_connection_get_setting_team(self); + + if (s_team) { + const char *config = nm_setting_team_get_config(s_team); + + if (config && !*config) { + g_object_set(s_team, NM_SETTING_TEAM_CONFIG, NULL, NULL); + return TRUE; + } + } + return FALSE; +} + +static gboolean +_normalize_team_port_config(NMConnection *self) +{ + NMSettingTeamPort *s_team_port = nm_connection_get_setting_team_port(self); + + if (s_team_port) { + const char *config = nm_setting_team_port_get_config(s_team_port); + + if (config && !*config) { + g_object_set(s_team_port, NM_SETTING_TEAM_PORT_CONFIG, NULL, NULL); + return TRUE; + } + } + return FALSE; +} + +static gboolean +_normalize_bluetooth_type(NMConnection *self) +{ + const char *type = _nm_connection_detect_bluetooth_type(self); + + if (type) { + g_object_set(nm_connection_get_setting_bluetooth(self), + NM_SETTING_BLUETOOTH_TYPE, + type, + NULL); + return TRUE; + } + return FALSE; +} + +static gboolean +_normalize_ovs_interface_type(NMConnection *self) +{ + NMSettingOvsInterface *s_ovs_interface = nm_connection_get_setting_ovs_interface(self); + gboolean modified; + int v; + + if (!s_ovs_interface) + return FALSE; + + v = _nm_setting_ovs_interface_verify_interface_type( + s_ovs_interface, + nm_setting_ovs_interface_get_interface_type(s_ovs_interface), + self, + TRUE, + &modified, + NULL, + NULL); + if (v != TRUE) + g_return_val_if_reached(modified); + + return modified; +} + +static gboolean +_normalize_ip_tunnel_wired_setting(NMConnection *self) +{ + NMSettingIPTunnel *s_ip_tunnel; + + s_ip_tunnel = nm_connection_get_setting_ip_tunnel(self); + if (!s_ip_tunnel) + return FALSE; + + if (nm_connection_get_setting_wired(self) + && !NM_IN_SET(nm_setting_ip_tunnel_get_mode(s_ip_tunnel), + NM_IP_TUNNEL_MODE_GRETAP, + NM_IP_TUNNEL_MODE_IP6GRETAP)) { + nm_connection_remove_setting(self, NM_TYPE_SETTING_WIRED); + return TRUE; + } + + return FALSE; +} + +static gboolean +_normalize_sriov_vf_order(NMConnection *self) +{ + NMSettingSriov *s_sriov; + + s_sriov = NM_SETTING_SRIOV(nm_connection_get_setting(self, NM_TYPE_SETTING_SRIOV)); + if (!s_sriov) + return FALSE; + + return _nm_setting_sriov_sort_vfs(s_sriov); +} + +static gboolean +_normalize_bridge_vlan_order(NMConnection *self) +{ + NMSettingBridge *s_bridge; + + s_bridge = nm_connection_get_setting_bridge(self); + if (!s_bridge) + return FALSE; + + return _nm_setting_bridge_sort_vlans(s_bridge); +} + +static gboolean +_normalize_bridge_port_vlan_order(NMConnection *self) +{ + NMSettingBridgePort *s_port; + + s_port = nm_connection_get_setting_bridge_port(self); + if (!s_port) + return FALSE; + + return _nm_setting_bridge_port_sort_vlans(s_port); +} + +static gboolean +_normalize_gsm_auto_config(NMConnection *self) +{ + NMSettingGsm *s_gsm; + + s_gsm = nm_connection_get_setting_gsm(self); + if (!s_gsm) + return FALSE; + + if (!nm_setting_gsm_get_auto_config(s_gsm)) + return FALSE; + + if (!nm_setting_gsm_get_apn(s_gsm) && !nm_setting_gsm_get_username(s_gsm) + && !nm_setting_gsm_get_password(s_gsm)) + return FALSE; + + g_object_set(s_gsm, NM_SETTING_GSM_AUTO_CONFIG, FALSE, NULL); + return TRUE; +} + +static gboolean +_normalize_required_settings(NMConnection *self) +{ + NMSettingBluetooth *s_bt = nm_connection_get_setting_bluetooth(self); + NMSetting * s_bridge; + gboolean changed = FALSE; + + if (nm_connection_get_setting_vlan(self)) { + if (!nm_connection_get_setting_wired(self)) { + nm_connection_add_setting(self, nm_setting_wired_new()); + changed = TRUE; + } + } + if (s_bt + && nm_streq0(nm_setting_bluetooth_get_connection_type(s_bt), + NM_SETTING_BLUETOOTH_TYPE_NAP)) { + if (!nm_connection_get_setting_bridge(self)) { + s_bridge = nm_setting_bridge_new(); + g_object_set(s_bridge, NM_SETTING_BRIDGE_STP, FALSE, NULL); + nm_connection_add_setting(self, s_bridge); + changed = TRUE; + } + } + return changed; +} + +static gboolean +_normalize_invalid_slave_port_settings(NMConnection *self) +{ + NMSettingConnection *s_con = nm_connection_get_setting_connection(self); + const char * slave_type; + gboolean changed = FALSE; + + slave_type = nm_setting_connection_get_slave_type(s_con); + + if (!nm_streq0(slave_type, NM_SETTING_BRIDGE_SETTING_NAME) + && _nm_connection_remove_setting(self, NM_TYPE_SETTING_BRIDGE_PORT)) + changed = TRUE; + + if (!nm_streq0(slave_type, NM_SETTING_TEAM_SETTING_NAME) + && _nm_connection_remove_setting(self, NM_TYPE_SETTING_TEAM_PORT)) + changed = TRUE; + + return changed; +} + +/** + * nm_connection_verify: + * @connection: the #NMConnection to verify + * @error: location to store error, or %NULL + * + * Validates the connection and all its settings. Each setting's properties + * have allowed values, and some values are dependent on other values. For + * example, if a Wi-Fi connection is security enabled, the #NMSettingWireless + * setting object's 'security' property must contain the setting name of the + * #NMSettingWirelessSecurity object, which must also be present in the + * connection for the connection to be valid. As another example, the + * #NMSettingWired object's 'mac-address' property must be a validly formatted + * MAC address. The returned #GError contains information about which + * setting and which property failed validation, and how it failed validation. + * + * Returns: %TRUE if the connection is valid, %FALSE if it is not + **/ +gboolean +nm_connection_verify(NMConnection *connection, GError **error) +{ + NMSettingVerifyResult result; + + result = _nm_connection_verify(connection, error); + + /* we treat normalizable connections as valid. */ + if (result == NM_SETTING_VERIFY_NORMALIZABLE) + g_clear_error(error); + + return result == NM_SETTING_VERIFY_SUCCESS || result == NM_SETTING_VERIFY_NORMALIZABLE; +} + +NMSettingVerifyResult +_nm_connection_verify(NMConnection *connection, GError **error) +{ + NMConnectionPrivate *priv; + NMSettingIPConfig * s_ip4; + NMSettingIPConfig * s_ip6; + NMSettingProxy * s_proxy; + gs_free_error GError *normalizable_error = NULL; + NMSettingVerifyResult normalizable_error_type = NM_SETTING_VERIFY_SUCCESS; + int i; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NM_SETTING_VERIFY_ERROR); + g_return_val_if_fail(!error || !*error, NM_SETTING_VERIFY_ERROR); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + if (!_connection_get_setting_by_meta_type(priv, NM_META_SETTING_TYPE_CONNECTION)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("setting not found")); + g_prefix_error(error, "%s: ", NM_SETTING_CONNECTION_SETTING_NAME); + return NM_SETTING_VERIFY_ERROR; + } + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting * setting = priv->settings[nm_meta_setting_types_by_priority[i]]; + GError * verify_error = NULL; + NMSettingVerifyResult verify_result; + + if (!setting) + continue; + + /* verify all settings. We stop if we find the first non-normalizable + * @NM_SETTING_VERIFY_ERROR. If we find normalizable errors we continue + * but remember the error to return it to the user. + * @NM_SETTING_VERIFY_NORMALIZABLE_ERROR has a higher priority then + * @NM_SETTING_VERIFY_NORMALIZABLE, so, if we encounter such an error type, + * we remember it instead (to return it as output). + **/ + verify_result = _nm_setting_verify(setting, connection, &verify_error); + if (verify_result == NM_SETTING_VERIFY_NORMALIZABLE + || verify_result == NM_SETTING_VERIFY_NORMALIZABLE_ERROR) { + if (verify_result == NM_SETTING_VERIFY_NORMALIZABLE_ERROR + && normalizable_error_type == NM_SETTING_VERIFY_NORMALIZABLE) { + /* NORMALIZABLE_ERROR has higher priority. */ + g_clear_error(&normalizable_error); + } + if (!normalizable_error) { + g_propagate_error(&normalizable_error, verify_error); + verify_error = NULL; + normalizable_error_type = verify_result; + } + } else if (verify_result != NM_SETTING_VERIFY_SUCCESS) { + g_propagate_error(error, verify_error); + g_return_val_if_fail(verify_result == NM_SETTING_VERIFY_ERROR, NM_SETTING_VERIFY_ERROR); + return NM_SETTING_VERIFY_ERROR; + } + g_clear_error(&verify_error); + } + + s_ip4 = _connection_get_setting_by_meta_type(priv, NM_META_SETTING_TYPE_IP4_CONFIG); + s_ip6 = _connection_get_setting_by_meta_type(priv, NM_META_SETTING_TYPE_IP6_CONFIG); + s_proxy = _connection_get_setting_by_meta_type(priv, NM_META_SETTING_TYPE_PROXY); + + nm_assert(normalizable_error_type != NM_SETTING_VERIFY_ERROR); + if (NM_IN_SET(normalizable_error_type, + NM_SETTING_VERIFY_SUCCESS, + NM_SETTING_VERIFY_NORMALIZABLE)) { + if (_supports_addr_family(connection, AF_INET)) { + if (!s_ip4 && normalizable_error_type == NM_SETTING_VERIFY_SUCCESS) { + g_set_error_literal(&normalizable_error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("setting is required for non-slave connections")); + g_prefix_error(&normalizable_error, "%s: ", NM_SETTING_IP4_CONFIG_SETTING_NAME); + + /* having a master without IP config was not a verify() error, accept + * it for backward compatibility. */ + normalizable_error_type = NM_SETTING_VERIFY_NORMALIZABLE; + } + } else { + if (s_ip4) { + g_clear_error(&normalizable_error); + g_set_error_literal(&normalizable_error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("setting not allowed in slave connection")); + g_prefix_error(&normalizable_error, "%s: ", NM_SETTING_IP4_CONFIG_SETTING_NAME); + /* having a slave with IP config *was* and is a verify() error. */ + normalizable_error_type = NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + } + + if (_supports_addr_family(connection, AF_INET6)) { + if (!s_ip6 && normalizable_error_type == NM_SETTING_VERIFY_SUCCESS) { + g_set_error_literal(&normalizable_error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("setting is required for non-slave connections")); + g_prefix_error(&normalizable_error, "%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME); + + /* having a master without IP config was not a verify() error, accept + * it for backward compatibility. */ + normalizable_error_type = NM_SETTING_VERIFY_NORMALIZABLE; + } + } else { + if (s_ip6) { + g_clear_error(&normalizable_error); + g_set_error_literal(&normalizable_error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("setting not allowed in slave connection")); + g_prefix_error(&normalizable_error, "%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME); + /* having a slave with IP config *was* and is a verify() error. */ + normalizable_error_type = NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + } + + if (_supports_addr_family(connection, AF_UNSPEC)) { + if (!s_proxy && normalizable_error_type == NM_SETTING_VERIFY_SUCCESS) { + g_set_error_literal(&normalizable_error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("setting is required for non-slave connections")); + g_prefix_error(&normalizable_error, "%s: ", NM_SETTING_PROXY_SETTING_NAME); + + /* having a master without proxy config was not a verify() error, accept + * it for backward compatibility. */ + normalizable_error_type = NM_SETTING_VERIFY_NORMALIZABLE; + } + } else { + if (s_proxy) { + g_clear_error(&normalizable_error); + g_set_error_literal(&normalizable_error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("setting not allowed in slave connection")); + g_prefix_error(&normalizable_error, "%s: ", NM_SETTING_PROXY_SETTING_NAME); + /* having a slave with proxy config *was* and is a verify() error. */ + normalizable_error_type = NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + } + } + + if (normalizable_error_type != NM_SETTING_VERIFY_SUCCESS) { + g_propagate_error(error, normalizable_error); + normalizable_error = NULL; + return normalizable_error_type; + } + + return NM_SETTING_VERIFY_SUCCESS; +} + +/** + * nm_connection_verify_secrets: + * @connection: the #NMConnection to verify in + * @error: location to store error, or %NULL + * + * Verifies the secrets in the connection. + * + * Returns: %TRUE if the secrets are valid, %FALSE if they are not + * + * Since: 1.2 + **/ +gboolean +nm_connection_verify_secrets(NMConnection *connection, GError **error) +{ + NMConnectionPrivate *priv; + int i; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + if (!priv->settings[i]) + continue; + if (!nm_setting_verify_secrets(priv->settings[i], connection, error)) + return FALSE; + } + return TRUE; +} + +static gboolean +_connection_normalize(NMConnection *connection, + GHashTable * parameters, + gboolean * modified, + GError ** error) +{ + NMSettingVerifyResult success; + gboolean was_modified; + +#if NM_MORE_ASSERTS > 10 + /* only call this _nm_connection_verify() confirms that the connection + * requires normalization and is normalizable. */ + nm_assert(NM_IN_SET(_nm_connection_verify(connection, NULL), + NM_SETTING_VERIFY_NORMALIZABLE, + NM_SETTING_VERIFY_NORMALIZABLE_ERROR)); +#endif + + /* Try to perform all kind of normalizations on the settings to fix it. + * We only do this, after verifying that the connection contains no un-normalizable + * errors, because in that case we rather fail without touching the settings. */ + + was_modified = FALSE; + + was_modified |= _normalize_connection_uuid(connection); + was_modified |= _normalize_connection_type(connection); + was_modified |= _normalize_connection_slave_type(connection); + was_modified |= _normalize_connection_secondaries(connection); + was_modified |= _normalize_required_settings(connection); + was_modified |= _normalize_invalid_slave_port_settings(connection); + was_modified |= _normalize_ip_config(connection, parameters); + was_modified |= _normalize_ethernet_link_neg(connection); + was_modified |= _normalize_infiniband_mtu(connection); + was_modified |= _normalize_bond_mode(connection); + was_modified |= _normalize_bond_options(connection); + was_modified |= _normalize_wireless_mac_address_randomization(connection); + was_modified |= _normalize_macsec(connection); + was_modified |= _normalize_team_config(connection); + was_modified |= _normalize_team_port_config(connection); + was_modified |= _normalize_bluetooth_type(connection); + was_modified |= _normalize_ovs_interface_type(connection); + was_modified |= _normalize_ip_tunnel_wired_setting(connection); + was_modified |= _normalize_sriov_vf_order(connection); + was_modified |= _normalize_bridge_vlan_order(connection); + was_modified |= _normalize_bridge_port_vlan_order(connection); + was_modified |= _normalize_gsm_auto_config(connection); + + was_modified = !!was_modified; + + /* Verify anew */ + success = _nm_connection_verify(connection, error); + + NM_SET_OUT(modified, was_modified); + + if (success != NM_SETTING_VERIFY_SUCCESS) { + /* we would expect, that after normalization, the connection can be verified. + * Also treat NM_SETTING_VERIFY_NORMALIZABLE as failure, because there is something + * odd going on. */ + if (error && !*error) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("Unexpected failure to normalize the connection")); + } + g_warning("connection did not verify after normalization: %s", + error ? (*error)->message : "??"); + g_return_val_if_reached(FALSE); + } + + /* we would expect, that the connection was modified during normalization. */ + g_return_val_if_fail(was_modified, TRUE); + + return TRUE; +} + +/** + * nm_connection_normalize: + * @connection: the #NMConnection to normalize + * @parameters: (allow-none) (element-type utf8 gpointer): a #GHashTable with + * normalization parameters to allow customization of the normalization by providing + * specific arguments. Unknown arguments will be ignored and the default will be + * used. The keys must be strings compared with g_str_equal() function. + * The values are opaque and depend on the parameter name. + * @modified: (out) (allow-none): outputs whether any settings were modified. + * @error: location to store error, or %NULL. Contains the reason, + * why the connection is invalid, if the function returns an error. + * + * Does some basic normalization and fixup of well known inconsistencies + * and deprecated fields. If the connection was modified in any way, + * the output parameter @modified is set %TRUE. + * + * Finally the connection will be verified and %TRUE returns if the connection + * is valid. As this function only performs some specific normalization steps + * it cannot repair all connections. If the connection has errors that + * cannot be normalized, the connection will not be modified. + * + * Returns: %TRUE if the connection is valid, %FALSE if it is not + **/ +gboolean +nm_connection_normalize(NMConnection *connection, + GHashTable * parameters, + gboolean * modified, + GError ** error) +{ + NMSettingVerifyResult success; + gs_free_error GError *normalizable_error = NULL; + + success = _nm_connection_verify(connection, &normalizable_error); + + if (!NM_IN_SET(success, NM_SETTING_VERIFY_NORMALIZABLE, NM_SETTING_VERIFY_NORMALIZABLE_ERROR)) { + if (normalizable_error) { + nm_assert(success == NM_SETTING_VERIFY_ERROR); + g_propagate_error(error, g_steal_pointer(&normalizable_error)); + } else + nm_assert(success == NM_SETTING_VERIFY_SUCCESS); + + NM_SET_OUT(modified, FALSE); + + if (success != NM_SETTING_VERIFY_SUCCESS) { + if (error && !*error) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("Unexpected failure to verify the connection")); + return FALSE; + } + return FALSE; + } + + if (error && *error) + return FALSE; + return TRUE; + } + + return _connection_normalize(connection, parameters, modified, error); +} + +gboolean +_nm_connection_ensure_normalized(NMConnection * connection, + gboolean allow_modify, + const char * expected_uuid, + gboolean coerce_uuid, + NMConnection **out_connection_clone, + GError ** error) +{ + gs_unref_object NMConnection *connection_clone = NULL; + gs_free_error GError *local = NULL; + NMSettingVerifyResult vresult; + + nm_assert(NM_IS_CONNECTION(connection)); + nm_assert(!out_connection_clone || !*out_connection_clone); + nm_assert(!expected_uuid || nm_uuid_is_normalized(expected_uuid)); + + if (expected_uuid) { + if (nm_streq0(expected_uuid, nm_connection_get_uuid(connection))) + expected_uuid = NULL; + else if (!coerce_uuid || (!allow_modify && !out_connection_clone)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unexpected uuid %s instead of %s"), + nm_connection_get_uuid(connection), + expected_uuid); + return FALSE; + } + } + + vresult = _nm_connection_verify(connection, &local); + if (vresult != NM_SETTING_VERIFY_SUCCESS) { + if (!NM_IN_SET(vresult, + NM_SETTING_VERIFY_NORMALIZABLE, + NM_SETTING_VERIFY_NORMALIZABLE_ERROR)) { + g_propagate_error(error, g_steal_pointer(&local)); + return FALSE; + } + if (!allow_modify) { + if (!out_connection_clone) { + /* even NM_SETTING_VERIFY_NORMALIZABLE is treated as an error. We could normalize, + * but are not allowed to (and no out argument is provided for cloning). */ + g_propagate_error(error, g_steal_pointer(&local)); + return FALSE; + } + connection_clone = nm_simple_connection_new_clone(connection); + connection = connection_clone; + } + if (!_connection_normalize(connection, NULL, NULL, error)) + g_return_val_if_reached(FALSE); + } + + if (expected_uuid) { + NMSettingConnection *s_con; + + if (!allow_modify && !connection_clone) { + nm_assert(out_connection_clone); + connection_clone = nm_simple_connection_new_clone(connection); + connection = connection_clone; + } + s_con = nm_connection_get_setting_connection(connection); + g_object_set(s_con, NM_SETTING_CONNECTION_UUID, expected_uuid, NULL); + } + + NM_SET_OUT(out_connection_clone, g_steal_pointer(&connection_clone)); + return TRUE; +} + +/*****************************************************************************/ + +#if NM_MORE_ASSERTS +static void +_nmtst_connection_unchanging_changed_cb(NMConnection *connection, gpointer user_data) +{ + nm_assert_not_reached(); +} + +static void +_nmtst_connection_unchanging_secrets_updated_cb(NMConnection *connection, + const char * setting_name, + gpointer user_data) +{ + nm_assert_not_reached(); +} + +const char _nmtst_connection_unchanging_user_data = 0; + +void +nmtst_connection_assert_unchanging(NMConnection *connection) +{ + if (!connection) + return; + + nm_assert(NM_IS_CONNECTION(connection)); + + if (g_signal_handler_find(connection, + G_SIGNAL_MATCH_DATA, + 0, + 0, + NULL, + NULL, + (gpointer) &_nmtst_connection_unchanging_user_data) + != 0) { + /* avoid connecting the assertion handler multiple times. */ + return; + } + + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + G_CALLBACK(_nmtst_connection_unchanging_changed_cb), + (gpointer) &_nmtst_connection_unchanging_user_data); + g_signal_connect(connection, + NM_CONNECTION_SECRETS_CLEARED, + G_CALLBACK(_nmtst_connection_unchanging_changed_cb), + (gpointer) &_nmtst_connection_unchanging_user_data); + g_signal_connect(connection, + NM_CONNECTION_SECRETS_UPDATED, + G_CALLBACK(_nmtst_connection_unchanging_secrets_updated_cb), + (gpointer) &_nmtst_connection_unchanging_user_data); +} +#endif + +/*****************************************************************************/ + +/** + * nm_connection_update_secrets: + * @connection: the #NMConnection + * @setting_name: the setting object name to which the secrets apply + * @secrets: a #GVariant of secrets, of type %NM_VARIANT_TYPE_CONNECTION + * or %NM_VARIANT_TYPE_SETTING + * @error: location to store error, or %NULL + * + * Update the specified setting's secrets, given a dictionary of secrets + * intended for that setting (deserialized from D-Bus for example). Will also + * extract the given setting's secrets hash if given a connection dictionary. + * If @setting_name is %NULL, expects a fully serialized #NMConnection as + * returned by nm_connection_to_dbus() and will update all secrets from all + * settings contained in @secrets. + * + * Returns: %TRUE if the secrets were successfully updated, %FALSE if the update + * failed (tried to update secrets for a setting that doesn't exist, etc) + **/ +gboolean +nm_connection_update_secrets(NMConnection *connection, + const char * setting_name, + GVariant * secrets, + GError ** error) +{ + NMSetting * setting; + gboolean success = TRUE; + gboolean updated = FALSE; + GVariant * setting_dict = NULL; + GVariantIter iter; + const char * key; + gboolean full_connection; + int success_detail; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + + full_connection = g_variant_is_of_type(secrets, NM_VARIANT_TYPE_CONNECTION); + + g_return_val_if_fail(full_connection || g_variant_is_of_type(secrets, NM_VARIANT_TYPE_SETTING), + FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + g_return_val_if_fail(setting_name || full_connection, FALSE); + + /* Empty @secrets means success */ + if (g_variant_n_children(secrets) == 0) + return TRUE; + + if (setting_name) { + /* Update just one setting's secrets */ + setting = nm_connection_get_setting_by_name(connection, setting_name); + if (!setting) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + setting_name); + return FALSE; + } + + if (full_connection) { + setting_dict = g_variant_lookup_value(secrets, setting_name, NM_VARIANT_TYPE_SETTING); + if (!setting_dict) { + /* The connection dictionary didn't contain any secrets for + * @setting_name; just return success. + */ + return TRUE; + } + } + + _setting_notify_block(connection, setting); + success_detail = _nm_setting_update_secrets(setting, setting_dict ?: secrets, error); + _setting_notify_unblock(connection, setting); + + nm_clear_pointer(&setting_dict, g_variant_unref); + + if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR) { + nm_assert(!error || *error); + return FALSE; + } + if (success_detail == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED) + updated = TRUE; + } else { + /* check first, whether all the settings exist... */ + g_variant_iter_init(&iter, secrets); + while (g_variant_iter_next(&iter, "{&s@a{sv}}", &key, NULL)) { + setting = nm_connection_get_setting_by_name(connection, key); + if (!setting) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + key); + return FALSE; + } + } + + /* Update each setting with any secrets from the connection dictionary */ + g_variant_iter_init(&iter, secrets); + while (g_variant_iter_next(&iter, "{&s@a{sv}}", &key, &setting_dict)) { + gs_free_error GError *local = NULL; + + /* Update the secrets for this setting */ + setting = nm_connection_get_setting_by_name(connection, key); + + _setting_notify_block(connection, setting); + success_detail = + _nm_setting_update_secrets(setting, setting_dict, error ? &local : NULL); + _setting_notify_unblock(connection, setting); + + g_variant_unref(setting_dict); + + if (success_detail == NM_SETTING_UPDATE_SECRET_ERROR) { + if (success) { + if (error) { + nm_assert(local); + g_propagate_error(error, g_steal_pointer(&local)); + error = NULL; + } else + nm_assert(!local); + success = FALSE; + } + break; + } + if (success_detail == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED) + updated = TRUE; + } + } + + if (updated) + _signal_emit_secrets_updated(connection, setting_name); + + return success; +} + +/** + * nm_connection_need_secrets: + * @connection: the #NMConnection + * @hints: (out) (element-type utf8) (allow-none) (transfer container): + * the address of a pointer to a #GPtrArray, initialized to %NULL, which on + * return points to an allocated #GPtrArray containing the property names of + * secrets of the #NMSetting which may be required; the caller owns the array + * and must free the array itself with g_ptr_array_free(), but not free its + * elements + * + * Returns the name of the first setting object in the connection which would + * need secrets to make a successful connection. The returned hints are only + * intended as a guide to what secrets may be required, because in some + * circumstances, there is no way to conclusively determine exactly which + * secrets are needed. + * + * Returns: the setting name of the #NMSetting object which has invalid or + * missing secrets + **/ +const char * +nm_connection_need_secrets(NMConnection *connection, GPtrArray **hints) +{ + NMSetting * setting_before = NULL; + NMConnectionPrivate *priv; + int i; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + if (hints) + g_return_val_if_fail(*hints == NULL, NULL); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + /* Get list of settings in priority order */ + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *setting = priv->settings[nm_meta_setting_types_by_priority[i]]; + GPtrArray *secrets; + + if (!setting) + continue; + + nm_assert(!setting_before || _nmtst_nm_setting_sort(setting_before, setting) < 0); + nm_assert(!setting_before || _nm_setting_compare_priority(setting_before, setting) <= 0); + setting_before = setting; + + secrets = _nm_setting_need_secrets(setting); + if (!secrets) + continue; + + if (hints) + *hints = secrets; + else + g_ptr_array_free(secrets, TRUE); + + return nm_setting_get_name(setting); + } + + return NULL; +} + +/** + * nm_connection_clear_secrets: + * @connection: the #NMConnection + * + * Clears and frees any secrets that may be stored in the connection, to avoid + * keeping secret data in memory when not needed. + **/ +void +nm_connection_clear_secrets(NMConnection *connection) +{ + return nm_connection_clear_secrets_with_flags(connection, NULL, NULL); +} + +/** + * nm_connection_clear_secrets_with_flags: + * @connection: the #NMConnection + * @func: (scope call) (allow-none): function to be called to determine whether a + * specific secret should be cleared or not. If %NULL, all secrets are cleared. + * @user_data: caller-supplied data passed to @func + * + * Clears and frees secrets determined by @func. + **/ +void +nm_connection_clear_secrets_with_flags(NMConnection * connection, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data) +{ + NMConnectionPrivate *priv; + int i; + + g_return_if_fail(NM_IS_CONNECTION(connection)); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *setting = priv->settings[i]; + + if (!setting) + continue; + + _setting_notify_block(connection, setting); + _nm_setting_clear_secrets(setting, func, user_data); + _setting_notify_unblock(connection, setting); + } + + _signal_emit_secrets_cleared(connection); +} + +static gboolean +_clear_secrets_by_secret_flags_cb(NMSetting * setting, + const char * secret, + NMSettingSecretFlags flags, + gpointer user_data) +{ + NMSettingSecretFlags filter_flags = GPOINTER_TO_UINT(user_data); + gboolean remove_secret; + + if (filter_flags == NM_SETTING_SECRET_FLAG_NONE) { + /* Can't use bitops with SECRET_FLAG_NONE so handle that specifically */ + remove_secret = (flags != NM_SETTING_SECRET_FLAG_NONE); + } else { + /* Otherwise, if the secret has at least one of the desired flags keep it */ + remove_secret = !NM_FLAGS_ANY(flags, filter_flags); + } + + return remove_secret; +} + +/** + * _nm_connection_clear_secrets_by_secret_flags: + * @self: the #NMConnection to filter (will be modified) + * @filter_flags: the secret flags to control whether to drop/remove + * a secret or to keep it. The meaning of the filter flags is to + * preserve the secrets. The secrets that have matching (see below) + * flags are kept, the others are dropped. + * + * Removes/drops secrets from @self according to @filter_flags. + * If @filter_flags is %NM_SETTING_SECRET_NONE, then only secrets that + * have %NM_SETTING_SECRET_NONE flags are kept. + * Otherwise, only secrets with secret flags are kept that have at least + * one of the filter flags. + */ +void +_nm_connection_clear_secrets_by_secret_flags(NMConnection *self, NMSettingSecretFlags filter_flags) +{ + nm_connection_clear_secrets_with_flags(self, + _clear_secrets_by_secret_flags_cb, + GUINT_TO_POINTER(filter_flags)); +} + +/*****************************************************************************/ + +/*****************************************************************************/ + +/* Returns always a non-NULL, floating variant that must + * be unrefed by the caller. */ +GVariant * +_nm_connection_for_each_secret(NMConnection * self, + GVariant * secrets, + gboolean remove_non_secrets, + _NMConnectionForEachSecretFunc callback, + gpointer callback_data) +{ + GVariantBuilder secrets_builder; + GVariantBuilder setting_builder; + GVariantIter secrets_iter; + GVariantIter * setting_iter; + const char * setting_name; + + /* This function, given a dict of dicts representing new secrets of + * an NMConnection, walks through each toplevel dict (which represents a + * NMSetting), and for each setting, walks through that setting dict's + * properties. For each property that's a secret, it will check that + * secret's flags in the backing NMConnection object, and call a supplied + * callback. + * + * The one complexity is that the VPN setting's 'secrets' property is + * *also* a dict (since the key/value pairs are arbitrary and known + * only to the VPN plugin itself). That means we have three levels of + * dicts that we potentially have to traverse here. The differences + * are handled by the virtual for_each_secret() function. + */ + + g_return_val_if_fail(callback, NULL); + + g_variant_iter_init(&secrets_iter, secrets); + g_variant_builder_init(&secrets_builder, NM_VARIANT_TYPE_CONNECTION); + while (g_variant_iter_next(&secrets_iter, "{&sa{sv}}", &setting_name, &setting_iter)) { + _nm_unused nm_auto_free_variant_iter GVariantIter *setting_iter_free = setting_iter; + NMSetting * setting; + const char * secret_name; + GVariant * val; + + setting = nm_connection_get_setting_by_name(self, setting_name); + if (!setting) + continue; + + g_variant_builder_init(&setting_builder, NM_VARIANT_TYPE_SETTING); + while (g_variant_iter_next(setting_iter, "{&sv}", &secret_name, &val)) { + _nm_unused gs_unref_variant GVariant *val_free = val; + + NM_SETTING_GET_CLASS(setting)->for_each_secret(setting, + secret_name, + val, + remove_non_secrets, + callback, + callback_data, + &setting_builder); + } + + g_variant_builder_add(&secrets_builder, "{sa{sv}}", setting_name, &setting_builder); + } + + return g_variant_builder_end(&secrets_builder); +} + +/*****************************************************************************/ + +typedef struct { + NMConnectionFindSecretFunc find_func; + gpointer find_func_data; + gboolean found; +} FindSecretData; + +static gboolean +find_secret_for_each_func(NMSettingSecretFlags flags, gpointer user_data) +{ + FindSecretData *data = user_data; + + if (!data->found) + data->found = data->find_func(flags, data->find_func_data); + return FALSE; +} + +gboolean +_nm_connection_find_secret(NMConnection * self, + GVariant * secrets, + NMConnectionFindSecretFunc callback, + gpointer callback_data) +{ + gs_unref_variant GVariant *dummy = NULL; + FindSecretData data = { + .find_func = callback, + .find_func_data = callback_data, + .found = FALSE, + }; + + dummy = _nm_connection_for_each_secret(self, secrets, FALSE, find_secret_for_each_func, &data); + return data.found; +} + +/*****************************************************************************/ + +static const NMConnectionSerializationOptions _connection_serialization_options_empty = { + .timestamp = + { + .has = FALSE, + .val = 0, + }, + .seen_bssids = NULL, +}; + +gboolean +nm_connection_serialization_options_equal(const NMConnectionSerializationOptions *a, + const NMConnectionSerializationOptions *b) +{ + if (!a) + a = &_connection_serialization_options_empty; + if (!b) + b = &_connection_serialization_options_empty; + + if (a == b) + return TRUE; + + if (a->timestamp.has != b->timestamp.has) + return FALSE; + if (a->timestamp.has && a->timestamp.val != b->timestamp.val) + return FALSE; + if (!nm_utils_strv_equal(a->seen_bssids ?: NM_STRV_EMPTY_CC(), + b->seen_bssids ?: NM_STRV_EMPTY_CC())) + return FALSE; + + return TRUE; +} + +/** + * nm_connection_to_dbus: + * @connection: the #NMConnection + * @flags: serialization flags, e.g. %NM_CONNECTION_SERIALIZE_ALL + * + * Converts the #NMConnection into a #GVariant of type + * %NM_VARIANT_TYPE_CONNECTION describing the connection, suitable for + * marshalling over D-Bus or otherwise serializing. + * + * Returns: (transfer none): a new floating #GVariant describing the connection, + * its settings, and each setting's properties. + **/ +GVariant * +nm_connection_to_dbus(NMConnection *connection, NMConnectionSerializationFlags flags) +{ + return nm_connection_to_dbus_full(connection, flags, NULL); +} + +GVariant * +nm_connection_to_dbus_full(NMConnection * connection, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMConnectionPrivate *priv; + GVariantBuilder builder; + gboolean any = FALSE; + int i; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + if (!options) + options = &_connection_serialization_options_empty; + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *setting = priv->settings[nm_meta_setting_types_by_priority[i]]; + GVariant * setting_dict; + + if (!setting) + continue; + + setting_dict = _nm_setting_to_dbus(setting, connection, flags, options); + if (!setting_dict) + continue; + if (!any) { + any = TRUE; + g_variant_builder_init(&builder, NM_VARIANT_TYPE_CONNECTION); + } + g_variant_builder_add(&builder, "{s@a{sv}}", nm_setting_get_name(setting), setting_dict); + } + + if (!any) + return NULL; + + return g_variant_builder_end(&builder); +} + +/** + * nm_connection_is_type: + * @connection: the #NMConnection + * @type: a setting name to check the connection's type against (like + * %NM_SETTING_WIRELESS_SETTING_NAME or %NM_SETTING_WIRED_SETTING_NAME) + * + * A convenience function to check if the given @connection is a particular + * type (ie wired, Wi-Fi, ppp, etc). Checks the #NMSettingConnection:type + * property of the connection and matches that against @type. + * + * Returns: %TRUE if the connection is of the given @type, %FALSE if not + **/ +gboolean +nm_connection_is_type(NMConnection *connection, const char *type) +{ + g_return_val_if_fail(type, FALSE); + + return nm_streq0(type, nm_connection_get_connection_type(connection)); +} + +int +_nmtst_nm_setting_sort(NMSetting *a, NMSetting *b) +{ + g_assert(NM_IS_SETTING(a)); + g_assert(NM_IS_SETTING(b)); + g_assert(a != b); + g_assert(G_OBJECT_TYPE(a) != G_OBJECT_TYPE(b)); + + NM_CMP_RETURN(_nm_setting_compare_priority(a, b)); + NM_CMP_DIRECT_STRCMP(nm_setting_get_name(a), nm_setting_get_name(b)); + + g_assert_not_reached(); + return 0; +} + +NMSetting ** +_nm_connection_get_settings_arr(NMConnection *connection) +{ + nm_assert(NM_IS_CONNECTION(connection)); + + return NM_CONNECTION_GET_PRIVATE(connection)->settings; +} + +/** + * nm_connection_get_settings: + * @connection: the #NMConnection instance + * @out_length: (allow-none) (out): the length of the returned array + * + * Retrieves the settings in @connection. + * + * The returned array is %NULL-terminated. + * + * Returns: (array length=out_length) (transfer container): a + * %NULL-terminated array containing every setting of + * @connection. + * If the connection has no settings, %NULL is returned. + * + * Since: 1.10 + */ +NMSetting ** +nm_connection_get_settings(NMConnection *connection, guint *out_length) +{ + NMConnectionPrivate *priv; + NMSetting ** arr; + int len; + int i; + int j; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + len = 0; + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + if (priv->settings[i]) + len++; + } + + NM_SET_OUT(out_length, len); + + if (len == 0) + return NULL; + + arr = g_new(NMSetting *, len + 1); + for (i = 0, j = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *setting = priv->settings[nm_meta_setting_types_by_priority[i]]; + + if (setting) { + nm_assert(j == 0 || _nmtst_nm_setting_sort(arr[j - 1], setting) < 0); + arr[j++] = setting; + } + } + arr[len] = NULL; + + return arr; +} + +/** + * nm_connection_for_each_setting_value: + * @connection: the #NMConnection + * @func: (scope call): user-supplied function called for each setting's property + * @user_data: user data passed to @func at each invocation + * + * Iterates over the properties of each #NMSetting object in the #NMConnection, + * calling the supplied user function for each property. + **/ +void +nm_connection_for_each_setting_value(NMConnection * connection, + NMSettingValueIterFn func, + gpointer user_data) +{ + gs_free NMSetting **settings = NULL; + guint length = 0; + guint i; + + g_return_if_fail(NM_IS_CONNECTION(connection)); + g_return_if_fail(func); + + settings = nm_connection_get_settings(connection, &length); + for (i = 1; i < length; i++) + g_object_ref(settings[i]); + for (i = 0; i < length; i++) + nm_setting_enumerate_values(settings[i], func, user_data); + for (i = 1; i < length; i++) + g_object_unref(settings[i]); +} + +/** + * _nm_connection_aggregate: + * @connection: the #NMConnection for which values are to be aggregated. + * @type: one of the supported aggregate types. + * @arg: the input/output argument that depends on @type. + * + * For example, with %NM_CONNECTION_AGGREGATE_ANY_SECRETS and + * %NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS @arg is a boolean + * output argument. It is either %NULL or a pointer to an gboolean + * out-argument. The function will always set @arg if given. + * Also, the return value of the function is likewise the result + * that is set to @arg. + * + * Returns: a boolean result with the meaning depending on the aggregation + * type @type. + */ +gboolean +_nm_connection_aggregate(NMConnection *connection, NMConnectionAggregateType type, gpointer arg) +{ + NMConnectionPrivate *priv; + gboolean arg_boolean; + gboolean completed_early; + gpointer my_arg; + int i; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + + switch (type) { + case NM_CONNECTION_AGGREGATE_ANY_SECRETS: + arg_boolean = FALSE; + my_arg = &arg_boolean; + goto good; + case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS: + arg_boolean = FALSE; + my_arg = &arg_boolean; + goto good; + } + g_return_val_if_reached(FALSE); + +good: + priv = NM_CONNECTION_GET_PRIVATE(connection); + + completed_early = FALSE; + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *setting = priv->settings[i]; + + if (!setting) + continue; + if (_nm_setting_aggregate(setting, type, my_arg)) { + completed_early = TRUE; + break; + } + nm_assert(my_arg != &arg_boolean || !arg_boolean); + } + + if (my_arg == &arg_boolean) { + nm_assert(completed_early == arg_boolean); + if (arg) + *((gboolean *) arg) = arg_boolean; + return arg_boolean; + } + + nm_assert_not_reached(); + return FALSE; +} + +/** + * nm_connection_dump: + * @connection: the #NMConnection + * + * Print the connection (including secrets!) to stdout. For debugging + * purposes ONLY, should NOT be used for serialization of the setting, + * or machine-parsed in any way. The output format is not guaranteed to + * be stable and may change at any time. + **/ +void +nm_connection_dump(NMConnection *connection) +{ + NMConnectionPrivate *priv; + int i; + + if (!connection) + return; + + g_return_if_fail(NM_IS_CONNECTION(connection)); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *setting = priv->settings[nm_meta_setting_types_by_priority[i]]; + + if (setting) { + gs_free char *str = NULL; + + str = nm_setting_to_string(setting); + g_print("%s\n", str); + } + } +} + +/** + * nm_connection_set_path: + * @connection: the #NMConnection + * @path: the D-Bus path of the connection as given by the settings service + * which provides the connection + * + * Sets the D-Bus path of the connection. This property is not serialized, and + * is only for the reference of the caller. Sets the #NMConnection:path + * property. + **/ +void +nm_connection_set_path(NMConnection *connection, const char *path) +{ + NMConnectionPrivate *priv; + + g_return_if_fail(NM_IS_CONNECTION(connection)); + + priv = NM_CONNECTION_GET_PRIVATE(connection); + + g_free(priv->path); + priv->path = g_strdup(path); +} + +/** + * nm_connection_get_path: + * @connection: the #NMConnection + * + * Returns the connection's D-Bus path. + * + * Returns: the D-Bus path of the connection, previously set by a call to + * nm_connection_set_path(). + **/ +const char * +nm_connection_get_path(NMConnection *connection) +{ + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + return NM_CONNECTION_GET_PRIVATE(connection)->path; +} + +/** + * nm_connection_get_interface_name: + * @connection: The #NMConnection + * + * Returns the interface name as stored in NMSettingConnection:interface_name. + * If the connection contains no NMSettingConnection, it will return %NULL. + * + * For hardware devices and software devices created outside of NetworkManager, + * this name is used to match the device. for software devices created by + * NetworkManager, this is the name of the created interface. + * + * Returns: Name of the kernel interface or %NULL + */ +const char * +nm_connection_get_interface_name(NMConnection *connection) +{ + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection(connection); + return s_con ? nm_setting_connection_get_interface_name(s_con) : NULL; +} + +NMConnectionMultiConnect +_nm_connection_get_multi_connect(NMConnection *connection) +{ + NMSettingConnection * s_con; + NMConnectionMultiConnect multi_connect; + const NMConnectionMultiConnect DEFAULT = NM_CONNECTION_MULTI_CONNECT_SINGLE; + + /* connection.multi_connect property cannot be specified via regular + * connection defaults in NetworkManager.conf, because those are per-device, + * and we need to determine the multi_connect independent of a particular + * device. + * + * There is however still a default-value, so theoretically, the default + * value could be specified in NetworkManager.conf. Just not as [connection*] + * and indepdented of a device. */ + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) + return DEFAULT; + + multi_connect = nm_setting_connection_get_multi_connect(s_con); + return multi_connect == NM_CONNECTION_MULTI_CONNECT_DEFAULT ? DEFAULT : multi_connect; +} + +gboolean +_nm_connection_verify_required_interface_name(NMConnection *connection, GError **error) +{ + const char *interface_name; + + if (!connection) + return TRUE; + + interface_name = nm_connection_get_interface_name(connection); + if (interface_name) + return TRUE; + + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME); + return FALSE; +} + +/** + * nm_connection_get_uuid: + * @connection: the #NMConnection + * + * A shortcut to return the UUID from the connection's #NMSettingConnection. + * + * Returns: the UUID from the connection's 'connection' setting + **/ +const char * +nm_connection_get_uuid(NMConnection *connection) +{ + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection(connection); + return s_con ? nm_setting_connection_get_uuid(s_con) : NULL; +} + +/** + * nm_connection_get_id: + * @connection: the #NMConnection + * + * A shortcut to return the ID from the connection's #NMSettingConnection. + * + * Returns: the ID from the connection's 'connection' setting + **/ +const char * +nm_connection_get_id(NMConnection *connection) +{ + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection(connection); + return s_con ? nm_setting_connection_get_id(s_con) : NULL; +} + +/** + * nm_connection_get_connection_type: + * @connection: the #NMConnection + * + * A shortcut to return the type from the connection's #NMSettingConnection. + * + * Returns: the type from the connection's 'connection' setting + **/ +const char * +nm_connection_get_connection_type(NMConnection *connection) +{ + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection(connection); + return s_con ? nm_setting_connection_get_connection_type(s_con) : NULL; +} + +/** + * nm_connection_is_virtual: + * @connection: an #NMConnection + * + * Checks if @connection refers to a virtual device (and thus can potentially be + * activated even if the device it refers to doesn't exist). + * + * Returns: whether @connection refers to a virtual device + */ +gboolean +nm_connection_is_virtual(NMConnection *connection) +{ + const char *type; + + type = nm_connection_get_connection_type(connection); + if (!type) + return FALSE; + + if (NM_IN_STRSET(type, + NM_SETTING_6LOWPAN_SETTING_NAME, + NM_SETTING_BOND_SETTING_NAME, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_DUMMY_SETTING_NAME, + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_MACSEC_SETTING_NAME, + NM_SETTING_MACVLAN_SETTING_NAME, + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_TEAM_SETTING_NAME, + NM_SETTING_TUN_SETTING_NAME, + NM_SETTING_VETH_SETTING_NAME, + NM_SETTING_VLAN_SETTING_NAME, + NM_SETTING_VRF_SETTING_NAME, + NM_SETTING_VXLAN_SETTING_NAME, + NM_SETTING_WIREGUARD_SETTING_NAME)) + return TRUE; + + if (nm_streq(type, NM_SETTING_INFINIBAND_SETTING_NAME)) { + NMSettingInfiniband *s_ib; + + s_ib = nm_connection_get_setting_infiniband(connection); + return s_ib && nm_setting_infiniband_get_virtual_interface_name(s_ib); + } + + if (nm_streq(type, NM_SETTING_BLUETOOTH_SETTING_NAME)) + return !!_nm_connection_get_setting_bluetooth_for_nap(connection); + + if (nm_streq(type, NM_SETTING_PPPOE_SETTING_NAME)) { + NMSettingPppoe *s_pppoe; + + s_pppoe = nm_connection_get_setting_pppoe(connection); + return !!nm_setting_pppoe_get_parent(s_pppoe); + } + + return FALSE; +} + +/** + * nm_connection_get_virtual_device_description: + * @connection: an #NMConnection for a virtual device type + * + * Returns the name that nm_device_disambiguate_names() would + * return for the virtual device that would be created for @connection. + * Eg, "VLAN (eth1.1)". + * + * Returns: (transfer full): the name of @connection's device, + * or %NULL if @connection is not a virtual connection type + */ +char * +nm_connection_get_virtual_device_description(NMConnection *connection) +{ + const char *type; + const char *iface = NULL, *display_type = NULL; + + type = nm_connection_get_connection_type(connection); + if (!type) + return NULL; + + iface = nm_connection_get_interface_name(connection); + + if (!strcmp(type, NM_SETTING_BOND_SETTING_NAME)) + display_type = _("Bond"); + else if (!strcmp(type, NM_SETTING_TEAM_SETTING_NAME)) + display_type = _("Team"); + else if (!strcmp(type, NM_SETTING_BRIDGE_SETTING_NAME)) + display_type = _("Bridge"); + else if (!strcmp(type, NM_SETTING_VLAN_SETTING_NAME)) + display_type = _("VLAN"); + else if (!strcmp(type, NM_SETTING_INFINIBAND_SETTING_NAME)) { + display_type = _("InfiniBand"); + iface = nm_setting_infiniband_get_virtual_interface_name( + nm_connection_get_setting_infiniband(connection)); + } else if (!strcmp(type, NM_SETTING_IP_TUNNEL_SETTING_NAME)) + display_type = _("IP Tunnel"); + + if (!iface || !display_type) + return NULL; + + return g_strdup_printf("%s (%s)", display_type, iface); +} + +/*****************************************************************************/ + +/** + * nm_connection_get_setting_802_1x: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSetting8021x the connection might contain. + * + * Returns: (transfer none): an #NMSetting8021x if the connection contains one, otherwise %NULL + **/ +NMSetting8021x * +nm_connection_get_setting_802_1x(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_802_1X); +} + +/** + * nm_connection_get_setting_bluetooth: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingBluetooth the connection might contain. + * + * Returns: (transfer none): an #NMSettingBluetooth if the connection contains one, otherwise %NULL + **/ +NMSettingBluetooth * +nm_connection_get_setting_bluetooth(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_BLUETOOTH); +} + +/** + * nm_connection_get_setting_bond: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingBond the connection might contain. + * + * Returns: (transfer none): an #NMSettingBond if the connection contains one, otherwise %NULL + **/ +NMSettingBond * +nm_connection_get_setting_bond(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_BOND); +} + +/** + * nm_connection_get_setting_team: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingTeam the connection might contain. + * + * Returns: (transfer none): an #NMSettingTeam if the connection contains one, otherwise %NULL + **/ +NMSettingTeam * +nm_connection_get_setting_team(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_TEAM); +} + +/** + * nm_connection_get_setting_team_port: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingTeamPort the connection might contain. + * + * Returns: (transfer none): an #NMSettingTeamPort if the connection contains one, otherwise %NULL + **/ +NMSettingTeamPort * +nm_connection_get_setting_team_port(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_TEAM_PORT); +} + +/** + * nm_connection_get_setting_bridge: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingBridge the connection might contain. + * + * Returns: (transfer none): an #NMSettingBridge if the connection contains one, otherwise %NULL + **/ +NMSettingBridge * +nm_connection_get_setting_bridge(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_BRIDGE); +} + +/** + * nm_connection_get_setting_cdma: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingCdma the connection might contain. + * + * Returns: (transfer none): an #NMSettingCdma if the connection contains one, otherwise %NULL + **/ +NMSettingCdma * +nm_connection_get_setting_cdma(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_CDMA); +} + +/** + * nm_connection_get_setting_connection: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingConnection the connection might contain. + * + * Returns: (transfer none): an #NMSettingConnection if the connection contains one, otherwise %NULL + **/ +NMSettingConnection * +nm_connection_get_setting_connection(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_CONNECTION); +} + +/** + * nm_connection_get_setting_dcb: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingDcb the connection might contain. + * + * Returns: (transfer none): an #NMSettingDcb if the connection contains one, otherwise NULL + **/ +NMSettingDcb * +nm_connection_get_setting_dcb(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_DCB); +} + +/** + * nm_connection_get_setting_dummy: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingDummy the connection might contain. + * + * Returns: (transfer none): an #NMSettingDummy if the connection contains one, otherwise %NULL + * + * Since: 1.8 + **/ +NMSettingDummy * +nm_connection_get_setting_dummy(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_DUMMY); +} + +/** + * nm_connection_get_setting_generic: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingGeneric the connection might contain. + * + * Returns: (transfer none): an #NMSettingGeneric if the connection contains one, otherwise NULL + **/ +NMSettingGeneric * +nm_connection_get_setting_generic(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_GENERIC); +} + +/** + * nm_connection_get_setting_gsm: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingGsm the connection might contain. + * + * Returns: (transfer none): an #NMSettingGsm if the connection contains one, otherwise %NULL + **/ +NMSettingGsm * +nm_connection_get_setting_gsm(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_GSM); +} + +/** + * nm_connection_get_setting_infiniband: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingInfiniband the connection might contain. + * + * Returns: (transfer none): an #NMSettingInfiniband if the connection contains one, otherwise %NULL + **/ +NMSettingInfiniband * +nm_connection_get_setting_infiniband(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_INFINIBAND); +} + +/** + * nm_connection_get_setting_ip4_config: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingIP4Config the connection might contain. + * + * Note that it returns the value as type #NMSettingIPConfig, since the vast + * majority of IPv4-setting-related methods are on that type, not + * #NMSettingIP4Config. + * + * Returns: (type NMSettingIP4Config) (transfer none): an #NMSettingIP4Config if the + * connection contains one, otherwise %NULL + **/ +NMSettingIPConfig * +nm_connection_get_setting_ip4_config(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_IP4_CONFIG); +} + +/** + * nm_connection_get_setting_ip_tunnel: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingIPTunnel the connection might contain. + * + * Returns: (transfer none): an #NMSettingIPTunnel if the connection contains one, otherwise %NULL + * + * Since: 1.2 + **/ +NMSettingIPTunnel * +nm_connection_get_setting_ip_tunnel(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_IP_TUNNEL); +} + +/** + * nm_connection_get_setting_ip6_config: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingIP6Config the connection might contain. + * + * Note that it returns the value as type #NMSettingIPConfig, since the vast + * majority of IPv6-setting-related methods are on that type, not + * #NMSettingIP6Config. + * + * Returns: (type NMSettingIP6Config) (transfer none): an #NMSettingIP6Config if the + * connection contains one, otherwise %NULL + **/ +NMSettingIPConfig * +nm_connection_get_setting_ip6_config(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_IP6_CONFIG); +} + +/** + * nm_connection_get_setting_macsec: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingMacsec the connection might contain. + * + * Returns: (transfer none): an #NMSettingMacsec if the connection contains one, otherwise %NULL + * + * Since: 1.6 + **/ +NMSettingMacsec * +nm_connection_get_setting_macsec(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_MACSEC); +} + +/** + * nm_connection_get_setting_macvlan: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingMacvlan the connection might contain. + * + * Returns: (transfer none): an #NMSettingMacvlan if the connection contains one, otherwise %NULL + * + * Since: 1.2 + **/ +NMSettingMacvlan * +nm_connection_get_setting_macvlan(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_MACVLAN); +} + +/** + * nm_connection_get_setting_olpc_mesh: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingOlpcMesh the connection might contain. + * + * Returns: (transfer none): an #NMSettingOlpcMesh if the connection contains one, otherwise %NULL + **/ +NMSettingOlpcMesh * +nm_connection_get_setting_olpc_mesh(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_OLPC_MESH); +} + +/** + * nm_connection_get_setting_ovs_bridge: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingOvsBridge the connection might contain. + * + * Returns: (transfer none): an #NMSettingOvsBridge if the connection contains one, otherwise %NULL + * + * Since: 1.10 + **/ +NMSettingOvsBridge * +nm_connection_get_setting_ovs_bridge(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_OVS_BRIDGE); +} + +/** + * nm_connection_get_setting_ovs_interface: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingOvsInterface the connection might contain. + * + * Returns: (transfer none): an #NMSettingOvsInterface if the connection contains one, otherwise %NULL + * + * Since: 1.10 + **/ +NMSettingOvsInterface * +nm_connection_get_setting_ovs_interface(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, + NM_META_SETTING_TYPE_OVS_INTERFACE); +} + +/** + * nm_connection_get_setting_ovs_patch: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingOvsPatch the connection might contain. + * + * Returns: (transfer none): an #NMSettingOvsPatch if the connection contains one, otherwise %NULL + * + * Since: 1.10 + **/ +NMSettingOvsPatch * +nm_connection_get_setting_ovs_patch(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_OVS_PATCH); +} + +/** + * nm_connection_get_setting_ovs_port: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingOvsPort the connection might contain. + * + * Returns: (transfer none): an #NMSettingOvsPort if the connection contains one, otherwise %NULL + * + * Since: 1.10 + **/ +NMSettingOvsPort * +nm_connection_get_setting_ovs_port(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_OVS_PORT); +} + +/** + * nm_connection_get_setting_ppp: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingPpp the connection might contain. + * + * Returns: (transfer none): an #NMSettingPpp if the connection contains one, otherwise %NULL + **/ +NMSettingPpp * +nm_connection_get_setting_ppp(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_PPP); +} + +/** + * nm_connection_get_setting_pppoe: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingPppoe the connection might contain. + * + * Returns: (transfer none): an #NMSettingPppoe if the connection contains one, otherwise %NULL + **/ +NMSettingPppoe * +nm_connection_get_setting_pppoe(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_PPPOE); +} + +/** + * nm_connection_get_setting_proxy: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingProxy the connection might contain. + * + * Returns: (transfer none): an #NMSettingProxy if the connection contains one, otherwise %NULL + * + * Since: 1.6 + **/ +NMSettingProxy * +nm_connection_get_setting_proxy(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_PROXY); +} + +/** + * nm_connection_get_setting_serial: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingSerial the connection might contain. + * + * Returns: (transfer none): an #NMSettingSerial if the connection contains one, otherwise %NULL + **/ +NMSettingSerial * +nm_connection_get_setting_serial(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_SERIAL); +} + +/** + * nm_connection_get_setting_tc_config: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingTCConfig the connection might contain. + * + * Returns: (transfer none): an #NMSettingTCConfig if the connection contains one, otherwise %NULL + * + * Since: 1.12 + **/ +NMSettingTCConfig * +nm_connection_get_setting_tc_config(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_TC_CONFIG); +} + +/** + * nm_connection_get_setting_tun: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingTun the connection might contain. + * + * Returns: (transfer none): an #NMSettingTun if the connection contains one, otherwise %NULL + * + * Since: 1.2 + **/ +NMSettingTun * +nm_connection_get_setting_tun(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_TUN); +} + +/** + * nm_connection_get_setting_vpn: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingVpn the connection might contain. + * + * Returns: (transfer none): an #NMSettingVpn if the connection contains one, otherwise %NULL + **/ +NMSettingVpn * +nm_connection_get_setting_vpn(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_VPN); +} + +/** + * nm_connection_get_setting_vxlan: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingVxlan the connection might contain. + * + * Returns: (transfer none): an #NMSettingVxlan if the connection contains one, otherwise %NULL + * + * Since: 1.2 + **/ +NMSettingVxlan * +nm_connection_get_setting_vxlan(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_VXLAN); +} + +/** + * nm_connection_get_setting_wimax: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingWimax the connection might contain. + * + * Returns: (transfer none): an #NMSettingWimax if the connection contains one, otherwise %NULL + **/ +NMSettingWimax * +nm_connection_get_setting_wimax(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_WIMAX); +} + +/** + * nm_connection_get_setting_wired: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingWired the connection might contain. + * + * Returns: (transfer none): an #NMSettingWired if the connection contains one, otherwise %NULL + **/ +NMSettingWired * +nm_connection_get_setting_wired(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_WIRED); +} + +/** + * nm_connection_get_setting_adsl: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingAdsl the connection might contain. + * + * Returns: (transfer none): an #NMSettingAdsl if the connection contains one, otherwise %NULL + **/ +NMSettingAdsl * +nm_connection_get_setting_adsl(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_ADSL); +} + +/** + * nm_connection_get_setting_wireless: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingWireless the connection might contain. + * + * Returns: (transfer none): an #NMSettingWireless if the connection contains one, otherwise %NULL + **/ +NMSettingWireless * +nm_connection_get_setting_wireless(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_WIRELESS); +} + +/** + * nm_connection_get_setting_wireless_security: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingWirelessSecurity the connection might contain. + * + * Returns: (transfer none): an #NMSettingWirelessSecurity if the connection contains one, otherwise %NULL + **/ +NMSettingWirelessSecurity * +nm_connection_get_setting_wireless_security(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, + NM_META_SETTING_TYPE_WIRELESS_SECURITY); +} + +/** + * nm_connection_get_setting_bridge_port: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingBridgePort the connection might contain. + * + * Returns: (transfer none): an #NMSettingBridgePort if the connection contains one, otherwise %NULL + **/ +NMSettingBridgePort * +nm_connection_get_setting_bridge_port(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_BRIDGE_PORT); +} + +/** + * nm_connection_get_setting_vlan: + * @connection: the #NMConnection + * + * A shortcut to return any #NMSettingVlan the connection might contain. + * + * Returns: (transfer none): an #NMSettingVlan if the connection contains one, otherwise %NULL + **/ +NMSettingVlan * +nm_connection_get_setting_vlan(NMConnection *connection) +{ + return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_VLAN); +} + +NMSettingBluetooth * +_nm_connection_get_setting_bluetooth_for_nap(NMConnection *connection) +{ + NMSettingBluetooth *s_bt = nm_connection_get_setting_bluetooth(connection); + + if (s_bt + && nm_streq0(nm_setting_bluetooth_get_connection_type(s_bt), NM_SETTING_BLUETOOTH_TYPE_NAP)) + return s_bt; + return NULL; +} + +/*****************************************************************************/ + +static void +nm_connection_default_init(NMConnectionInterface *iface) +{ + /** + * NMConnection::secrets-updated: + * @connection: the object on which the signal is emitted + * @setting_name: the setting name of the #NMSetting for which secrets were + * updated + * + * The ::secrets-updated signal is emitted when the secrets of a setting + * have been changed. + */ + signals[SECRETS_UPDATED] = g_signal_new(NM_CONNECTION_SECRETS_UPDATED, + NM_TYPE_CONNECTION, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(NMConnectionInterface, secrets_updated), + NULL, + NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + /** + * NMConnection::secrets-cleared: + * @connection: the object on which the signal is emitted + * + * The ::secrets-cleared signal is emitted when the secrets of a connection + * are cleared. + */ + signals[SECRETS_CLEARED] = g_signal_new(NM_CONNECTION_SECRETS_CLEARED, + NM_TYPE_CONNECTION, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(NMConnectionInterface, secrets_cleared), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + + /** + * NMConnection::changed: + * @connection: the object on which the signal is emitted + * + * The ::changed signal is emitted when any property (including secrets) + * of any setting of the connection is modified, or when settings are + * added or removed. + */ + signals[CHANGED] = g_signal_new(NM_CONNECTION_CHANGED, + NM_TYPE_CONNECTION, + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET(NMConnectionInterface, changed), + NULL, + NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); +} diff --git a/src/libnm-core-impl/nm-crypto-gnutls.c b/src/libnm-core-impl/nm-crypto-gnutls.c new file mode 100644 index 00000000..88b123ad --- /dev/null +++ b/src/libnm-core-impl/nm-crypto-gnutls.c @@ -0,0 +1,415 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Dan Williams + * Copyright (C) 2007 - 2015 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-crypto-impl.h" + +#include +#include +#include +#include + +#include "libnm-glib-aux/nm-secret-utils.h" +#include "nm-errors.h" + +/*****************************************************************************/ + +static gboolean +_get_cipher_info(NMCryptoCipherType cipher, int *out_cipher_mech, guint8 *out_real_iv_len) +{ + static const int cipher_mechs[] = { + [NM_CRYPTO_CIPHER_DES_EDE3_CBC] = GNUTLS_CIPHER_3DES_CBC, + [NM_CRYPTO_CIPHER_DES_CBC] = GNUTLS_CIPHER_DES_CBC, + [NM_CRYPTO_CIPHER_AES_128_CBC] = GNUTLS_CIPHER_AES_128_CBC, + [NM_CRYPTO_CIPHER_AES_192_CBC] = GNUTLS_CIPHER_AES_192_CBC, + [NM_CRYPTO_CIPHER_AES_256_CBC] = GNUTLS_CIPHER_AES_256_CBC, + }; + + g_return_val_if_fail(_NM_INT_NOT_NEGATIVE(cipher) + && (gsize) cipher < G_N_ELEMENTS(cipher_mechs), + FALSE); + + if (cipher_mechs[cipher] == 0) + return FALSE; + + NM_SET_OUT(out_cipher_mech, cipher_mechs[cipher]); + NM_SET_OUT(out_real_iv_len, nm_crypto_cipher_get_info(cipher)->real_iv_len); + return TRUE; +} + +/*****************************************************************************/ + +gboolean +_nm_crypto_init(GError **error) +{ + static gboolean initialized = FALSE; + + if (initialized) + return TRUE; + + if (gnutls_global_init() != 0) { + gnutls_global_deinit(); + g_set_error_literal(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_FAILED, + _("Failed to initialize the crypto engine.")); + return FALSE; + } + + initialized = TRUE; + return TRUE; +} + +/*****************************************************************************/ + +guint8 * +_nmtst_crypto_decrypt(NMCryptoCipherType cipher, + const guint8 * data, + gsize data_len, + const guint8 * iv, + gsize iv_len, + const guint8 * key, + gsize key_len, + gsize * out_len, + GError ** error) +{ + gnutls_cipher_hd_t ctx; + gnutls_datum_t key_dt, iv_dt; + int err; + int cipher_mech; + nm_auto_clear_secret_ptr NMSecretPtr output = {0}; + guint8 pad_i, pad_len; + guint8 real_iv_len; + + if (!_get_cipher_info(cipher, &cipher_mech, &real_iv_len)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_UNKNOWN_CIPHER, + _("Unsupported key cipher for decryption")); + return NULL; + } + + if (!_nm_crypto_init(error)) + return NULL; + + if (iv_len < real_iv_len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Invalid IV length (must be at least %u)."), + (guint) real_iv_len); + return NULL; + } + + output.len = data_len; + output.bin = g_malloc(data_len); + + key_dt.data = (unsigned char *) key; + key_dt.size = key_len; + iv_dt.data = (unsigned char *) iv; + iv_dt.size = iv_len; + + err = gnutls_cipher_init(&ctx, cipher_mech, &key_dt, &iv_dt); + if (err < 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to initialize the decryption cipher context: %s (%s)"), + gnutls_strerror_name(err), + gnutls_strerror(err)); + return NULL; + } + + err = gnutls_cipher_decrypt2(ctx, data, data_len, output.bin, output.len); + + gnutls_cipher_deinit(ctx); + + if (err < 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to decrypt the private key: %s (%s)"), + gnutls_strerror_name(err), + gnutls_strerror(err)); + return NULL; + } + + pad_len = output.len > 0 ? output.bin[output.len - 1] : 0; + + /* Check if the padding at the end of the decrypted data is valid */ + if (pad_len == 0 || pad_len > real_iv_len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to decrypt the private key: unexpected padding length.")); + return NULL; + } + + /* Validate tail padding; last byte is the padding size, and all pad bytes + * should contain the padding size. + */ + for (pad_i = 1; pad_i <= pad_len; ++pad_i) { + if (output.bin[data_len - pad_i] != pad_len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to decrypt the private key.")); + return NULL; + } + } + + *out_len = output.len - pad_len; + return g_steal_pointer(&output.bin); +} + +guint8 * +_nmtst_crypto_encrypt(NMCryptoCipherType cipher, + const guint8 * data, + gsize data_len, + const guint8 * iv, + gsize iv_len, + const guint8 * key, + gsize key_len, + gsize * out_len, + GError ** error) +{ + gnutls_cipher_hd_t ctx; + gnutls_datum_t key_dt, iv_dt; + int err; + int cipher_mech; + nm_auto_clear_secret_ptr NMSecretPtr output = {0}; + nm_auto_clear_secret_ptr NMSecretPtr padded_buf = {0}; + gsize i, pad_len; + + nm_assert(iv_len); + + if (cipher == NM_CRYPTO_CIPHER_DES_CBC || !_get_cipher_info(cipher, &cipher_mech, NULL)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_UNKNOWN_CIPHER, + _("Unsupported key cipher for encryption")); + return NULL; + } + + if (!_nm_crypto_init(error)) + return NULL; + + key_dt.data = (unsigned char *) key; + key_dt.size = key_len; + iv_dt.data = (unsigned char *) iv; + iv_dt.size = iv_len; + + err = gnutls_cipher_init(&ctx, cipher_mech, &key_dt, &iv_dt); + if (err < 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_ENCRYPTION_FAILED, + _("Failed to initialize the encryption cipher context: %s (%s)"), + gnutls_strerror_name(err), + gnutls_strerror(err)); + return NULL; + } + + /* If data_len % ivlen == 0, then we add another complete block + * onto the end so that the decrypter knows there's padding. + */ + pad_len = iv_len - (data_len % iv_len); + + padded_buf.len = data_len + pad_len; + padded_buf.bin = g_malloc(padded_buf.len); + memcpy(padded_buf.bin, data, data_len); + for (i = 0; i < pad_len; i++) + padded_buf.bin[data_len + i] = (guint8) (pad_len & 0xFF); + + output.len = padded_buf.len; + output.bin = g_malloc(output.len); + + err = gnutls_cipher_encrypt2(ctx, padded_buf.bin, padded_buf.len, output.bin, output.len); + + gnutls_cipher_deinit(ctx); + + if (err < 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_ENCRYPTION_FAILED, + _("Failed to encrypt the data: %s (%s)"), + gnutls_strerror_name(err), + gnutls_strerror(err)); + return NULL; + } + + *out_len = output.len; + return g_steal_pointer(&output.bin); +} + +gboolean +_nm_crypto_verify_x509(const guint8 *data, gsize len, GError **error) +{ + gnutls_x509_crt_t der; + gnutls_datum_t dt; + int err; + + if (!_nm_crypto_init(error)) + return FALSE; + + err = gnutls_x509_crt_init(&der); + if (err < 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Error initializing certificate data: %s"), + gnutls_strerror(err)); + return FALSE; + } + + /* Try DER first */ + dt.data = (unsigned char *) data; + dt.size = len; + err = gnutls_x509_crt_import(der, &dt, GNUTLS_X509_FMT_DER); + if (err == GNUTLS_E_SUCCESS) { + gnutls_x509_crt_deinit(der); + return TRUE; + } + + /* And PEM next */ + err = gnutls_x509_crt_import(der, &dt, GNUTLS_X509_FMT_PEM); + gnutls_x509_crt_deinit(der); + if (err == GNUTLS_E_SUCCESS) + return TRUE; + + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Couldn't decode certificate: %s"), + gnutls_strerror(err)); + return FALSE; +} + +gboolean +_nm_crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error) +{ + gnutls_pkcs12_t p12; + gnutls_datum_t dt; + int err; + + g_return_val_if_fail(data != NULL, FALSE); + + if (!_nm_crypto_init(error)) + return FALSE; + + dt.data = (unsigned char *) data; + dt.size = data_len; + + err = gnutls_pkcs12_init(&p12); + if (err < 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_FAILED, + _("Couldn't initialize PKCS#12 decoder: %s"), + gnutls_strerror(err)); + return FALSE; + } + + /* DER first */ + err = gnutls_pkcs12_import(p12, &dt, GNUTLS_X509_FMT_DER, 0); + if (err < 0) { + /* PEM next */ + err = gnutls_pkcs12_import(p12, &dt, GNUTLS_X509_FMT_PEM, 0); + if (err < 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Couldn't decode PKCS#12 file: %s"), + gnutls_strerror(err)); + gnutls_pkcs12_deinit(p12); + return FALSE; + } + } + + err = gnutls_pkcs12_verify_mac(p12, password); + + gnutls_pkcs12_deinit(p12); + + if (err != GNUTLS_E_SUCCESS) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Couldn't verify PKCS#12 file: %s"), + gnutls_strerror(err)); + return FALSE; + } + + return TRUE; +} + +gboolean +_nm_crypto_verify_pkcs8(const guint8 *data, + gsize data_len, + gboolean is_encrypted, + const char * password, + GError ** error) +{ + gnutls_x509_privkey_t p8; + gnutls_datum_t dt; + int err; + + g_return_val_if_fail(data != NULL, FALSE); + + if (!_nm_crypto_init(error)) + return FALSE; + + err = gnutls_x509_privkey_init(&p8); + if (err < 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_FAILED, + _("Couldn't initialize PKCS#8 decoder: %s"), + gnutls_strerror(err)); + return FALSE; + } + + dt.data = (unsigned char *) data; + dt.size = data_len; + + err = gnutls_x509_privkey_import_pkcs8(p8, + &dt, + GNUTLS_X509_FMT_DER, + is_encrypted ? password : NULL, + is_encrypted ? 0 : GNUTLS_PKCS_PLAIN); + + gnutls_x509_privkey_deinit(p8); + + if (err < 0) { + if (err == GNUTLS_E_UNKNOWN_CIPHER_TYPE) { + /* HACK: gnutls < 3.5.4 doesn't support all the cipher types that openssl + * can use with PKCS#8, so if we encounter one, we have to assume + * the given password works. gnutls needs to unsuckify, apparently. + * Specifically, by default openssl uses pbeWithMD5AndDES-CBC + * which gnutls does not support. + */ + } else { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Couldn't decode PKCS#8 file: %s"), + gnutls_strerror(err)); + return FALSE; + } + } + + return TRUE; +} + +gboolean +_nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error) +{ + if (!_nm_crypto_init(error)) + return FALSE; + + gnutls_rnd(GNUTLS_RND_RANDOM, buffer, buffer_len); + return TRUE; +} diff --git a/src/libnm-core-impl/nm-crypto-impl.h b/src/libnm-core-impl/nm-crypto-impl.h new file mode 100644 index 00000000..0fd8f194 --- /dev/null +++ b/src/libnm-core-impl/nm-crypto-impl.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Dan Williams + * Copyright (C) 2007 - 2018 Red Hat, Inc. + */ + +#ifndef __NM_CRYPTO_IMPL_H__ +#define __NM_CRYPTO_IMPL_H__ + +#include "nm-crypto.h" + +gboolean _nm_crypto_init(GError **error); + +gboolean _nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error); + +gboolean _nm_crypto_verify_x509(const guint8 *data, gsize len, GError **error); + +gboolean +_nm_crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error); + +gboolean _nm_crypto_verify_pkcs8(const guint8 *data, + gsize data_len, + gboolean is_encrypted, + const char * password, + GError ** error); + +/*****************************************************************************/ + +guint8 *_nmtst_crypto_encrypt(NMCryptoCipherType cipher, + const guint8 * data, + gsize data_len, + const guint8 * iv, + gsize iv_len, + const guint8 * key, + gsize key_len, + gsize * out_len, + GError ** error); + +guint8 *_nmtst_crypto_decrypt(NMCryptoCipherType cipher, + const guint8 * data, + gsize data_len, + const guint8 * iv, + gsize iv_len, + const guint8 * key, + gsize key_len, + gsize * out_len, + GError ** error); + +#endif /* __NM_CRYPTO_IMPL_H__ */ diff --git a/src/libnm-core-impl/nm-crypto-nss.c b/src/libnm-core-impl/nm-crypto-nss.c new file mode 100644 index 00000000..e59a11b9 --- /dev/null +++ b/src/libnm-core-impl/nm-crypto-nss.c @@ -0,0 +1,548 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Dan Williams + * Copyright (C) 2007 - 2009 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-crypto-impl.h" + +NM_PRAGMA_WARNING_DISABLE("-Wstrict-prototypes") +#include +#include +#include +#include +#include +#include +#include +#include +#include +NM_PRAGMA_WARNING_REENABLE + +#include "libnm-glib-aux/nm-secret-utils.h" +#include "nm-errors.h" + +/*****************************************************************************/ + +static gboolean +_get_cipher_info(NMCryptoCipherType cipher, + CK_MECHANISM_TYPE *out_cipher_mech, + guint8 * out_real_iv_len) +{ + static const CK_MECHANISM_TYPE cipher_mechs[] = { + [NM_CRYPTO_CIPHER_DES_EDE3_CBC] = CKM_DES3_CBC_PAD, + [NM_CRYPTO_CIPHER_DES_CBC] = CKM_DES_CBC_PAD, + [NM_CRYPTO_CIPHER_AES_128_CBC] = CKM_AES_CBC_PAD, + [NM_CRYPTO_CIPHER_AES_192_CBC] = CKM_AES_CBC_PAD, + [NM_CRYPTO_CIPHER_AES_256_CBC] = CKM_AES_CBC_PAD, + }; + + g_return_val_if_fail(_NM_INT_NOT_NEGATIVE(cipher) + && (gsize) cipher < G_N_ELEMENTS(cipher_mechs), + FALSE); + + if (!cipher_mechs[cipher]) + return FALSE; + + NM_SET_OUT(out_cipher_mech, cipher_mechs[cipher]); + NM_SET_OUT(out_real_iv_len, nm_crypto_cipher_get_info(cipher)->real_iv_len); + return TRUE; +} + +/*****************************************************************************/ + +gboolean +_nm_crypto_init(GError **error) +{ + static gboolean initialized = FALSE; + SECStatus ret; + + if (initialized) + return TRUE; + + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 1); + ret = NSS_NoDB_Init(NULL); + if (ret != SECSuccess) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_FAILED, + _("Failed to initialize the crypto engine: %d."), + PR_GetError()); + PR_Cleanup(); + return FALSE; + } + + SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1); + SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1); + SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1); + SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1); + SEC_PKCS12EnableCipher(PKCS12_DES_56, 1); + SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1); + SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1); + + initialized = TRUE; + return TRUE; +} + +guint8 * +_nmtst_crypto_decrypt(NMCryptoCipherType cipher, + const guint8 * data, + gsize data_len, + const guint8 * iv, + gsize iv_len, + const guint8 * key, + gsize key_len, + gsize * out_len, + GError ** error) +{ + CK_MECHANISM_TYPE cipher_mech; + PK11SlotInfo * slot = NULL; + SECItem key_item; + PK11SymKey * sym_key = NULL; + SECItem * sec_param = NULL; + PK11Context * ctx = NULL; + nm_auto_clear_secret_ptr NMSecretPtr output = {0}; + SECStatus s; + gboolean success = FALSE; + int decrypted_len = 0; + unsigned extra = 0; + unsigned pad_len = 0; + guint32 i; + guint8 real_iv_len; + + if (!_get_cipher_info(cipher, &cipher_mech, &real_iv_len)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_UNKNOWN_CIPHER, + _("Unsupported key cipher for decryption")); + return NULL; + } + + if (iv_len < real_iv_len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Invalid IV length (must be at least %u)."), + (guint) real_iv_len); + return NULL; + } + + if (!_nm_crypto_init(error)) + return NULL; + + slot = PK11_GetBestSlot(cipher_mech, NULL); + if (!slot) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_FAILED, + _("Failed to initialize the decryption cipher slot.")); + goto out; + } + + key_item.data = (unsigned char *) key; + key_item.len = key_len; + sym_key = PK11_ImportSymKey(slot, cipher_mech, PK11_OriginUnwrap, CKA_DECRYPT, &key_item, NULL); + if (!sym_key) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to set symmetric key for decryption.")); + goto out; + } + + key_item.data = (unsigned char *) iv; + key_item.len = real_iv_len; + sec_param = PK11_ParamFromIV(cipher_mech, &key_item); + if (!sec_param) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to set IV for decryption.")); + goto out; + } + + ctx = PK11_CreateContextBySymKey(cipher_mech, CKA_DECRYPT, sym_key, sec_param); + if (!ctx) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to initialize the decryption context.")); + goto out; + } + + output.len = data_len; + output.bin = g_malloc(data_len); + + s = PK11_CipherOp(ctx, + (unsigned char *) output.bin, + &decrypted_len, + output.len, + data, + data_len); + if (s != SECSuccess) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to decrypt the private key: %d."), + PORT_GetError()); + goto out; + } + + if (decrypted_len > data_len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to decrypt the private key: decrypted data too large.")); + goto out; + } + + s = PK11_DigestFinal(ctx, + (unsigned char *) &output.bin[decrypted_len], + &extra, + data_len - decrypted_len); + if (s != SECSuccess) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to finalize decryption of the private key: %d."), + PORT_GetError()); + goto out; + } + + decrypted_len += extra; + pad_len = data_len - decrypted_len; + + /* Check if the padding at the end of the decrypted data is valid */ + if (pad_len == 0 || pad_len > real_iv_len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to decrypt the private key: unexpected padding length.")); + goto out; + } + + /* Validate tail padding; last byte is the padding size, and all pad bytes + * should contain the padding size. + */ + for (i = pad_len; i > 0; i--) { + if (output.bin[data_len - i] != pad_len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Failed to decrypt the private key.")); + goto out; + } + } + + success = TRUE; + +out: + if (ctx) + PK11_DestroyContext(ctx, PR_TRUE); + if (sym_key) + PK11_FreeSymKey(sym_key); + if (sec_param) + SECITEM_FreeItem(sec_param, PR_TRUE); + if (slot) + PK11_FreeSlot(slot); + + if (!success) + return NULL; + + if (decrypted_len < output.len) + nm_explicit_bzero(&output.bin[decrypted_len], output.len - decrypted_len); + *out_len = decrypted_len; + return g_steal_pointer(&output.bin); +} + +guint8 * +_nmtst_crypto_encrypt(NMCryptoCipherType cipher, + const guint8 * data, + gsize data_len, + const guint8 * iv, + gsize iv_len, + const guint8 * key, + gsize key_len, + gsize * out_len, + GError ** error) +{ + SECStatus ret; + CK_MECHANISM_TYPE cipher_mech = CKM_DES3_CBC_PAD; + PK11SlotInfo * slot = NULL; + SECItem key_item = {.data = (unsigned char *) key, .len = key_len}; + SECItem iv_item = {.data = (unsigned char *) iv, .len = iv_len}; + PK11SymKey * sym_key = NULL; + SECItem * sec_param = NULL; + PK11Context * ctx = NULL; + nm_auto_clear_secret_ptr NMSecretPtr padded_buf = {0}; + nm_auto_clear_secret_ptr NMSecretPtr output = {0}; + int encrypted_len = 0, i; + gboolean success = FALSE; + gsize pad_len; + + if (cipher == NM_CRYPTO_CIPHER_DES_CBC || !_get_cipher_info(cipher, &cipher_mech, NULL)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_UNKNOWN_CIPHER, + _("Unsupported key cipher for encryption")); + return NULL; + } + + if (!_nm_crypto_init(error)) + return NULL; + + slot = PK11_GetBestSlot(cipher_mech, NULL); + if (!slot) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_FAILED, + _("Failed to initialize the encryption cipher slot.")); + return NULL; + } + + sym_key = PK11_ImportSymKey(slot, cipher_mech, PK11_OriginUnwrap, CKA_ENCRYPT, &key_item, NULL); + if (!sym_key) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_ENCRYPTION_FAILED, + _("Failed to set symmetric key for encryption.")); + goto out; + } + + sec_param = PK11_ParamFromIV(cipher_mech, &iv_item); + if (!sec_param) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_ENCRYPTION_FAILED, + _("Failed to set IV for encryption.")); + goto out; + } + + ctx = PK11_CreateContextBySymKey(cipher_mech, CKA_ENCRYPT, sym_key, sec_param); + if (!ctx) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_ENCRYPTION_FAILED, + _("Failed to initialize the encryption context.")); + goto out; + } + + /* If data->len % ivlen == 0, then we add another complete block + * onto the end so that the decrypter knows there's padding. + */ + pad_len = iv_len - (data_len % iv_len); + + padded_buf.len = data_len + pad_len; + padded_buf.bin = g_malloc(padded_buf.len); + + memcpy(padded_buf.bin, data, data_len); + for (i = 0; i < pad_len; i++) + padded_buf.bin[data_len + i] = (guint8) (pad_len & 0xFF); + + output.len = padded_buf.len; + output.bin = g_malloc(output.len); + + ret = + PK11_CipherOp(ctx, output.bin, &encrypted_len, output.len, padded_buf.bin, padded_buf.len); + if (ret != SECSuccess) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_ENCRYPTION_FAILED, + _("Failed to encrypt: %d."), + PORT_GetError()); + goto out; + } + + if (encrypted_len != output.len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_ENCRYPTION_FAILED, + _("Unexpected amount of data after encrypting.")); + goto out; + } + + success = TRUE; + +out: + if (ctx) + PK11_DestroyContext(ctx, PR_TRUE); + if (sec_param) + SECITEM_FreeItem(sec_param, PR_TRUE); + if (sym_key) + PK11_FreeSymKey(sym_key); + if (slot) + PK11_FreeSlot(slot); + + if (!success) + return NULL; + + *out_len = output.len; + return g_steal_pointer(&output.bin); +} + +gboolean +_nm_crypto_verify_x509(const guint8 *data, gsize len, GError **error) +{ + CERTCertificate *cert; + + if (!_nm_crypto_init(error)) + return FALSE; + + /* Try DER/PEM first */ + cert = CERT_DecodeCertFromPackage((char *) data, len); + if (!cert) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Couldn't decode certificate: %d"), + PORT_GetError()); + return FALSE; + } + + CERT_DestroyCertificate(cert); + return TRUE; +} + +gboolean +_nm_crypto_verify_pkcs12(const guint8 *data, gsize data_len, const char *password, GError **error) +{ + SEC_PKCS12DecoderContext *p12ctx = NULL; + SECItem pw = {0}; + PK11SlotInfo * slot = NULL; + SECStatus s; + gboolean success = FALSE; + + g_return_val_if_fail(!error || !*error, FALSE); + + if (!_nm_crypto_init(error)) + return FALSE; + + /* PKCS#12 passwords are apparently UCS2 BIG ENDIAN, and NSS doesn't do + * any conversions for us. + */ + if (password && *password) { + nm_auto_clear_secret_ptr NMSecretPtr ucs2_password = {0}; + + if (g_utf8_validate(password, -1, NULL)) { + long ucs2_chars; + + ucs2_password.bin = + (guint8 *) g_utf8_to_utf16(password, strlen(password), NULL, &ucs2_chars, NULL); + + /* cannot fail, because password is valid UTF-8*/ + nm_assert(ucs2_password.bin && ucs2_chars > 0); + + ucs2_password.len = ucs2_chars * 2; + } + + if (!ucs2_password.bin || ucs2_password.len == 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_PASSWORD, + _("Password must be UTF-8")); + return FALSE; + } + + pw.data = PORT_ZAlloc(ucs2_password.len + 2); + memcpy(pw.data, ucs2_password.bin, ucs2_password.len); + pw.len = ucs2_password.len + 2; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + { + guint16 *p, *p_end; + + p_end = (guint16 *) &(((guint8 *) pw.data)[ucs2_password.len]); + for (p = (guint16 *) pw.data; p < p_end; p++) + *p = GUINT16_SWAP_LE_BE(*p); + } +#endif + } + + slot = PK11_GetInternalKeySlot(); + if (!slot) { + g_set_error(error, NM_CRYPTO_ERROR, NM_CRYPTO_ERROR_FAILED, _("Couldn't initialize slot")); + goto out; + } + + p12ctx = SEC_PKCS12DecoderStart(&pw, slot, NULL, NULL, NULL, NULL, NULL, NULL); + if (!p12ctx) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_FAILED, + _("Couldn't initialize PKCS#12 decoder: %d"), + PORT_GetError()); + goto out; + } + + s = SEC_PKCS12DecoderUpdate(p12ctx, (guint8 *) data, data_len); + if (s != SECSuccess) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Couldn't decode PKCS#12 file: %d"), + PORT_GetError()); + goto out; + } + + s = SEC_PKCS12DecoderVerify(p12ctx); + if (s != SECSuccess) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_DECRYPTION_FAILED, + _("Couldn't verify PKCS#12 file: %d"), + PORT_GetError()); + goto out; + } + + success = TRUE; + +out: + if (p12ctx) + SEC_PKCS12DecoderFinish(p12ctx); + if (slot) + PK11_FreeSlot(slot); + + if (pw.data) + SECITEM_ZfreeItem(&pw, PR_FALSE); + + return success; +} + +gboolean +_nm_crypto_verify_pkcs8(const guint8 *data, + gsize data_len, + gboolean is_encrypted, + const char * password, + GError ** error) +{ + g_return_val_if_fail(data != NULL, FALSE); + + if (!_nm_crypto_init(error)) + return FALSE; + + /* NSS apparently doesn't do PKCS#8 natively, but you have to put the + * PKCS#8 key into a PKCS#12 file and import that?? So until we figure + * all that out, we can only assume the password is valid. + */ + return TRUE; +} + +gboolean +_nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error) +{ + SECStatus s; + + if (!_nm_crypto_init(error)) + return FALSE; + + s = PK11_GenerateRandom(buffer, buffer_len); + if (s != SECSuccess) { + g_set_error_literal(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_FAILED, + _("Could not generate random data.")); + return FALSE; + } + return TRUE; +} diff --git a/src/libnm-core-impl/nm-crypto.c b/src/libnm-core-impl/nm-crypto.c new file mode 100644 index 00000000..b14b681e --- /dev/null +++ b/src/libnm-core-impl/nm-crypto.c @@ -0,0 +1,1044 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Dan Williams + * Copyright (C) 2007 - 2018 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-crypto.h" + +#include +#include +#include + +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-glib-aux/nm-io-utils.h" + +#include "nm-crypto-impl.h" +#include "nm-utils.h" +#include "nm-errors.h" + +#define PEM_RSA_KEY_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define PEM_RSA_KEY_END "-----END RSA PRIVATE KEY-----" + +#define PEM_DSA_KEY_BEGIN "-----BEGIN DSA PRIVATE KEY-----" +#define PEM_DSA_KEY_END "-----END DSA PRIVATE KEY-----" + +#define PEM_CERT_BEGIN "-----BEGIN CERTIFICATE-----" +#define PEM_CERT_END "-----END CERTIFICATE-----" + +#define PEM_PKCS8_ENC_KEY_BEGIN "-----BEGIN ENCRYPTED PRIVATE KEY-----" +#define PEM_PKCS8_ENC_KEY_END "-----END ENCRYPTED PRIVATE KEY-----" + +#define PEM_PKCS8_DEC_KEY_BEGIN "-----BEGIN PRIVATE KEY-----" +#define PEM_PKCS8_DEC_KEY_END "-----END PRIVATE KEY-----" + +#define PEM_TPM2_WRAPPED_KEY_BEGIN "-----BEGIN TSS2 PRIVATE KEY-----" +#define PEM_TPM2_WRAPPED_KEY_END "-----END TSS2 PRIVATE KEY-----" + +#define PEM_TPM2_OLD_WRAPPED_KEY_BEGIN "-----BEGIN TSS2 KEY BLOB-----" +#define PEM_TPM2_OLD_WRAPPED_KEY_END "-----END TSS2 KEY BLOB-----" + +/*****************************************************************************/ + +static const NMCryptoCipherInfo cipher_infos[] = { +#define _CI(_cipher, _name, _digest_len, _real_iv_len) \ + [(_cipher) -1] = {.cipher = _cipher, \ + .name = ""_name \ + "", \ + .digest_len = _digest_len, \ + .real_iv_len = _real_iv_len} + _CI(NM_CRYPTO_CIPHER_DES_EDE3_CBC, "DES-EDE3-CBC", 24, 8), + _CI(NM_CRYPTO_CIPHER_DES_CBC, "DES-CBC", 8, 8), + _CI(NM_CRYPTO_CIPHER_AES_128_CBC, "AES-128-CBC", 16, 16), + _CI(NM_CRYPTO_CIPHER_AES_192_CBC, "AES-192-CBC", 24, 16), + _CI(NM_CRYPTO_CIPHER_AES_256_CBC, "AES-256-CBC", 32, 16), +}; + +const NMCryptoCipherInfo * +nm_crypto_cipher_get_info(NMCryptoCipherType cipher) +{ + g_return_val_if_fail(cipher > NM_CRYPTO_CIPHER_UNKNOWN + && (gsize) cipher < G_N_ELEMENTS(cipher_infos) + 1, + NULL); + +#if NM_MORE_ASSERTS > 10 + { + int i, j; + + for (i = 0; i < (int) G_N_ELEMENTS(cipher_infos); i++) { + const NMCryptoCipherInfo *info = &cipher_infos[i]; + + nm_assert(info->cipher == (NMCryptoCipherType) (i + 1)); + nm_assert(info->name && info->name[0]); + for (j = 0; j < i; j++) + nm_assert(g_ascii_strcasecmp(info->name, cipher_infos[j].name) != 0); + } + } +#endif + + return &cipher_infos[cipher - 1]; +} + +const NMCryptoCipherInfo * +nm_crypto_cipher_get_info_by_name(const char *cipher_name, gssize p_len) +{ + int i; + + nm_assert(nm_crypto_cipher_get_info(NM_CRYPTO_CIPHER_DES_CBC)->cipher + == NM_CRYPTO_CIPHER_DES_CBC); + + if (p_len < 0) { + if (!cipher_name) + return FALSE; + p_len = strlen(cipher_name); + } + + for (i = 0; i < (int) G_N_ELEMENTS(cipher_infos); i++) { + const NMCryptoCipherInfo *info = &cipher_infos[i]; + + if ((gsize) p_len == strlen(info->name) + && g_ascii_strncasecmp(info->name, cipher_name, p_len) == 0) + return info; + } + return NULL; +} + +/*****************************************************************************/ + +static gboolean +find_tag(const char *tag, const guint8 *data, gsize data_len, gsize start_at, gsize *out_pos) +{ + const guint8 *p; + gsize taglen; + + nm_assert(out_pos); + nm_assert(start_at <= data_len); + + taglen = strlen(tag); + + p = memmem(&data[start_at], data_len - start_at, tag, taglen); + if (!p) + return FALSE; + + *out_pos = p - data; + + nm_assert(memcmp(&data[*out_pos], tag, taglen) == 0); + + return TRUE; +} + +#define DEK_INFO_TAG "DEK-Info: " +#define PROC_TYPE_TAG "Proc-Type: " + +static char * +_extract_line(const guint8 **p, const guint8 *p_end) +{ + const guint8 *x, *x0; + + nm_assert(p); + nm_assert(p_end); + nm_assert(*p); + nm_assert(*p < p_end); + + x = x0 = *p; + while (TRUE) { + if (x == p_end) { + *p = p_end; + break; + } + if (*x == '\0') { + /* the data contains embedded NUL. This is the end. */ + *p = p_end; + break; + } + if (*x == '\n') { + *p = x + 1; + break; + } + x++; + } + + if (x == x0) + return NULL; + return g_strndup((char *) x0, x - x0); +} + +static gboolean +parse_old_openssl_key_file(const guint8 * data, + gsize data_len, + NMSecretPtr * out_parsed, + NMCryptoKeyType * out_key_type, + NMCryptoCipherType *out_cipher, + char ** out_iv, + GError ** error) +{ + gsize start = 0, end = 0; + nm_auto_free_secret char * str = NULL; + char * str_p; + gsize str_len; + int enc_tags = 0; + NMCryptoKeyType key_type; + nm_auto_clear_secret_ptr NMSecretPtr parsed = {0}; + nm_auto_free_secret char * iv = NULL; + NMCryptoCipherType cipher = NM_CRYPTO_CIPHER_UNKNOWN; + const char * start_tag; + const char * end_tag; + const guint8 * data_start, *data_end; + + nm_assert(!out_parsed || (out_parsed->len == 0 && !out_parsed->bin)); + nm_assert(!out_iv || !*out_iv); + + NM_SET_OUT(out_key_type, NM_CRYPTO_KEY_TYPE_UNKNOWN); + NM_SET_OUT(out_cipher, NM_CRYPTO_CIPHER_UNKNOWN); + + if (find_tag(PEM_RSA_KEY_BEGIN, data, data_len, 0, &start)) { + key_type = NM_CRYPTO_KEY_TYPE_RSA; + start_tag = PEM_RSA_KEY_BEGIN; + end_tag = PEM_RSA_KEY_END; + } else if (find_tag(PEM_DSA_KEY_BEGIN, data, data_len, 0, &start)) { + key_type = NM_CRYPTO_KEY_TYPE_DSA; + start_tag = PEM_DSA_KEY_BEGIN; + end_tag = PEM_DSA_KEY_END; + } else { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("PEM key file had no start tag")); + return FALSE; + } + + start += strlen(start_tag); + if (!find_tag(end_tag, data, data_len, start, &end)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("PEM key file had no end tag '%s'."), + end_tag); + return FALSE; + } + + str_len = end - start + 1; + str = g_new(char, str_len); + str[0] = '\0'; + str_p = str; + + data_start = &data[start]; + data_end = &data[end]; + + while (data_start < data_end) { + nm_auto_free_secret char *line = NULL; + char * p; + + line = _extract_line(&data_start, data_end); + if (!line) + continue; + + p = nm_secret_strchomp(nm_str_skip_leading_spaces(line)); + + if (!strncmp(p, PROC_TYPE_TAG, strlen(PROC_TYPE_TAG))) { + if (enc_tags++ != 0 || str_p != str) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Malformed PEM file: Proc-Type was not first tag.")); + return FALSE; + } + + p += strlen(PROC_TYPE_TAG); + if (strcmp(p, "4,ENCRYPTED")) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Malformed PEM file: unknown Proc-Type tag '%s'."), + p); + return FALSE; + } + } else if (!strncmp(p, DEK_INFO_TAG, strlen(DEK_INFO_TAG))) { + const NMCryptoCipherInfo *cipher_info; + char * comma; + gsize p_len; + + if (enc_tags++ != 1 || str_p != str) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Malformed PEM file: DEK-Info was not the second tag.")); + return FALSE; + } + + p += strlen(DEK_INFO_TAG); + + /* Grab the IV first */ + comma = strchr(p, ','); + if (!comma || (*(comma + 1) == '\0')) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Malformed PEM file: no IV found in DEK-Info tag.")); + return FALSE; + } + p_len = comma - p; + comma++; + if (!g_ascii_isxdigit(*comma)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Malformed PEM file: invalid format of IV in DEK-Info tag.")); + return FALSE; + } + nm_free_secret(iv); + iv = g_strdup(comma); + + /* Get the private key cipher */ + cipher_info = nm_crypto_cipher_get_info_by_name(p, p_len); + if (!cipher_info) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Malformed PEM file: unknown private key cipher '%s'."), + p); + return FALSE; + } + cipher = cipher_info->cipher; + } else { + if (enc_tags == 1) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + "Malformed PEM file: both Proc-Type and DEK-Info tags are required."); + return FALSE; + } + nm_utils_strbuf_append_str(&str_p, &str_len, p); + nm_assert(str_len > 0); + } + } + + parsed.bin = (guint8 *) g_base64_decode(str, &parsed.len); + if (!parsed.bin || parsed.len == 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Could not decode private key.")); + nm_secret_ptr_clear(&parsed); + return FALSE; + } + + NM_SET_OUT(out_key_type, key_type); + NM_SET_OUT(out_iv, g_steal_pointer(&iv)); + NM_SET_OUT(out_cipher, cipher); + nm_secret_ptr_move(out_parsed, &parsed); + return TRUE; +} + +static gboolean +parse_pkcs8_key_file(const guint8 *data, + gsize data_len, + NMSecretPtr * parsed, + gboolean * out_encrypted, + GError ** error) +{ + gsize start = 0, end = 0; + const char * start_tag = NULL, *end_tag = NULL; + gboolean encrypted = FALSE; + nm_auto_free_secret char *der_base64 = NULL; + + nm_assert(parsed); + nm_assert(!parsed->bin); + nm_assert(parsed->len == 0); + nm_assert(out_encrypted); + + /* Try encrypted first, decrypted next */ + if (find_tag(PEM_PKCS8_ENC_KEY_BEGIN, data, data_len, 0, &start)) { + start_tag = PEM_PKCS8_ENC_KEY_BEGIN; + end_tag = PEM_PKCS8_ENC_KEY_END; + encrypted = TRUE; + } else if (find_tag(PEM_PKCS8_DEC_KEY_BEGIN, data, data_len, 0, &start)) { + start_tag = PEM_PKCS8_DEC_KEY_BEGIN; + end_tag = PEM_PKCS8_DEC_KEY_END; + encrypted = FALSE; + } else { + g_set_error_literal(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Failed to find expected PKCS#8 start tag.")); + return FALSE; + } + + start += strlen(start_tag); + if (!find_tag(end_tag, data, data_len, start, &end)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Failed to find expected PKCS#8 end tag '%s'."), + end_tag); + return FALSE; + } + + /* g_base64_decode() wants a NULL-terminated string */ + der_base64 = g_strndup((char *) &data[start], end - start); + + parsed->bin = (guint8 *) g_base64_decode(der_base64, &parsed->len); + if (!parsed->bin || parsed->len == 0) { + g_set_error_literal(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Failed to decode PKCS#8 private key.")); + nm_secret_ptr_clear(parsed); + return FALSE; + } + + *out_encrypted = encrypted; + return TRUE; +} + +static gboolean +parse_tpm2_wrapped_key_file(const guint8 *data, + gsize data_len, + gboolean * out_encrypted, + GError ** error) +{ + gsize start = 0, end = 0; + const char *start_tag = NULL, *end_tag = NULL; + + nm_assert(out_encrypted); + + if (find_tag(PEM_TPM2_WRAPPED_KEY_BEGIN, data, data_len, 0, &start)) { + start_tag = PEM_TPM2_WRAPPED_KEY_BEGIN; + end_tag = PEM_TPM2_WRAPPED_KEY_END; + } else if (find_tag(PEM_TPM2_OLD_WRAPPED_KEY_BEGIN, data, data_len, 0, &start)) { + start_tag = PEM_TPM2_OLD_WRAPPED_KEY_BEGIN; + end_tag = PEM_TPM2_OLD_WRAPPED_KEY_END; + } else { + g_set_error_literal(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Failed to find expected TSS start tag.")); + return FALSE; + } + + start += strlen(start_tag); + if (!find_tag(end_tag, data, data_len, start, &end)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Failed to find expected TSS end tag '%s'."), + end_tag); + return FALSE; + } + + *out_encrypted = FALSE; + return TRUE; +} + +static gboolean +file_read_contents(const char *filename, NMSecretPtr *out_contents, GError **error) +{ + nm_assert(out_contents); + nm_assert(out_contents->len == 0); + nm_assert(!out_contents->str); + + return nm_utils_file_get_contents(-1, + filename, + 100 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_SECRET, + &out_contents->str, + &out_contents->len, + NULL, + error); +} + +GBytes * +nm_crypto_read_file(const char *filename, GError **error) +{ + nm_auto_clear_secret_ptr NMSecretPtr contents = {0}; + + g_return_val_if_fail(filename, NULL); + + if (!file_read_contents(filename, &contents, error)) + return NULL; + return nm_secret_copy_to_gbytes(contents.bin, contents.len); +} + +/* + * Convert a hex string into bytes. + */ +static guint8 * +_nmtst_convert_iv(const char *src, gsize *out_len, GError **error) +{ + gsize i, num; + gs_free guint8 *c = NULL; + int c0, c1; + + nm_assert(src); + + num = strlen(src); + if (num == 0 || (num % 2) != 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("IV must be an even number of bytes in length.")); + return NULL; + } + + num /= 2; + c = g_malloc(num + 1); + + /* defensively add trailing NUL. This function returns binary data, + * do not assume it's NUL terminated. */ + c[num] = '\0'; + + for (i = 0; i < num; i++) { + if (((c0 = nm_utils_hexchar_to_int(*(src++))) < 0) + || ((c1 = nm_utils_hexchar_to_int(*(src++))) < 0)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("IV contains non-hexadecimal digits.")); + nm_explicit_bzero(c, i); + return FALSE; + } + + c[i] = (c0 << 4) + c1; + } + *out_len = num; + return g_steal_pointer(&c); +} + +guint8 * +nmtst_crypto_make_des_aes_key(NMCryptoCipherType cipher, + const guint8 * salt, + gsize salt_len, + const char * password, + gsize * out_len, + GError ** error) +{ + guint8 * key; + const NMCryptoCipherInfo *cipher_info; + + g_return_val_if_fail(salt != NULL, NULL); + g_return_val_if_fail(salt_len >= 8, NULL); + g_return_val_if_fail(password != NULL, NULL); + g_return_val_if_fail(out_len != NULL, NULL); + + *out_len = 0; + + cipher_info = nm_crypto_cipher_get_info(cipher); + + g_return_val_if_fail(cipher_info, NULL); + + if (password[0] == '\0') + return NULL; + + key = g_malloc(cipher_info->digest_len); + + nm_crypto_md5_hash(salt, + 8, + (guint8 *) password, + strlen(password), + key, + cipher_info->digest_len); + + *out_len = cipher_info->digest_len; + return key; +} + +static gboolean +_nmtst_decrypt_key(NMCryptoCipherType cipher, + const guint8 * data, + gsize data_len, + const char * iv, + const char * password, + NMSecretPtr * parsed, + GError ** error) +{ + nm_auto_clear_secret_ptr NMSecretPtr bin_iv = {0}; + nm_auto_clear_secret_ptr NMSecretPtr key = {0}; + + nm_assert(password); + nm_assert(cipher != NM_CRYPTO_CIPHER_UNKNOWN); + nm_assert(iv); + nm_assert(parsed); + nm_assert(!parsed->bin); + nm_assert(parsed->len == 0); + + bin_iv.bin = _nmtst_convert_iv(iv, &bin_iv.len, error); + if (!bin_iv.bin) + return FALSE; + + if (bin_iv.len < 8) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("IV must contain at least 8 characters")); + return FALSE; + } + + /* Convert the password and IV into a DES or AES key */ + key.bin = + nmtst_crypto_make_des_aes_key(cipher, bin_iv.bin, bin_iv.len, password, &key.len, error); + if (!key.bin || !key.len) + return FALSE; + + parsed->bin = _nmtst_crypto_decrypt(cipher, + data, + data_len, + bin_iv.bin, + bin_iv.len, + key.bin, + key.len, + &parsed->len, + error); + if (!parsed->bin || parsed->len == 0) { + nm_secret_ptr_clear(parsed); + return FALSE; + } + + return TRUE; +} + +GBytes * +nmtst_crypto_decrypt_openssl_private_key_data(const guint8 * data, + gsize data_len, + const char * password, + NMCryptoKeyType *out_key_type, + GError ** error) +{ + NMCryptoKeyType key_type = NM_CRYPTO_KEY_TYPE_UNKNOWN; + nm_auto_clear_secret_ptr NMSecretPtr parsed = {0}; + nm_auto_free_secret char * iv = NULL; + NMCryptoCipherType cipher = NM_CRYPTO_CIPHER_UNKNOWN; + + g_return_val_if_fail(data != NULL, NULL); + + NM_SET_OUT(out_key_type, NM_CRYPTO_KEY_TYPE_UNKNOWN); + + if (!_nm_crypto_init(error)) + return NULL; + + if (!parse_old_openssl_key_file(data, data_len, &parsed, &key_type, &cipher, &iv, NULL)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Unable to determine private key type.")); + return NULL; + } + + NM_SET_OUT(out_key_type, key_type); + + if (password) { + nm_auto_clear_secret_ptr NMSecretPtr parsed2 = {0}; + + if (cipher == NM_CRYPTO_CIPHER_UNKNOWN || !iv) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_PASSWORD, + _("Password provided, but key was not encrypted.")); + return NULL; + } + + if (!_nmtst_decrypt_key(cipher, parsed.bin, parsed.len, iv, password, &parsed2, error)) + return NULL; + + return nm_secret_copy_to_gbytes(parsed2.bin, parsed2.len); + } + + if (cipher != NM_CRYPTO_CIPHER_UNKNOWN || iv) + return NULL; + + return nm_secret_copy_to_gbytes(parsed.bin, parsed.len); +} + +GBytes * +nmtst_crypto_decrypt_openssl_private_key(const char * file, + const char * password, + NMCryptoKeyType *out_key_type, + GError ** error) +{ + nm_auto_clear_secret_ptr NMSecretPtr contents = {0}; + + if (!_nm_crypto_init(error)) + return NULL; + + if (!file_read_contents(file, &contents, error)) + return NULL; + + return nmtst_crypto_decrypt_openssl_private_key_data(contents.bin, + contents.len, + password, + out_key_type, + error); +} + +static gboolean +extract_pem_cert_data(const guint8 *contents, + gsize contents_len, + NMSecretPtr * out_cert, + GError ** error) +{ + gsize start = 0; + gsize end = 0; + nm_auto_free_secret char *der_base64 = NULL; + + nm_assert(contents); + nm_assert(out_cert); + nm_assert(out_cert->len == 0); + nm_assert(!out_cert->ptr); + + if (!find_tag(PEM_CERT_BEGIN, contents, contents_len, 0, &start)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("PEM certificate had no start tag '%s'."), + PEM_CERT_BEGIN); + return FALSE; + } + + start += strlen(PEM_CERT_BEGIN); + if (!find_tag(PEM_CERT_END, contents, contents_len, start, &end)) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("PEM certificate had no end tag '%s'."), + PEM_CERT_END); + return FALSE; + } + + /* g_base64_decode() wants a NULL-terminated string */ + der_base64 = g_strndup((const char *) &contents[start], end - start); + + out_cert->bin = (guint8 *) g_base64_decode(der_base64, &out_cert->len); + if (!out_cert->bin || !out_cert->len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Failed to decode certificate.")); + nm_secret_ptr_clear(out_cert); + return FALSE; + } + + return TRUE; +} + +gboolean +nm_crypto_load_and_verify_certificate(const char * file, + NMCryptoFileFormat *out_file_format, + GBytes ** out_certificate, + GError ** error) +{ + nm_auto_clear_secret_ptr NMSecretPtr contents = {0}; + + g_return_val_if_fail(file, FALSE); + nm_assert(!error || !*error); + + if (!_nm_crypto_init(error)) + goto out; + + if (!file_read_contents(file, &contents, error)) + goto out; + + if (contents.len == 0) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Certificate file is empty")); + goto out; + } + + /* Check for PKCS#12 */ + if (nm_crypto_is_pkcs12_data(contents.bin, contents.len, NULL)) { + NM_SET_OUT(out_file_format, NM_CRYPTO_FILE_FORMAT_PKCS12); + NM_SET_OUT(out_certificate, nm_secret_copy_to_gbytes(contents.bin, contents.len)); + return TRUE; + } + + /* Check for plain DER format */ + if (contents.len > 2 && contents.bin[0] == 0x30 && contents.bin[1] == 0x82) { + if (_nm_crypto_verify_x509(contents.bin, contents.len, NULL)) { + NM_SET_OUT(out_file_format, NM_CRYPTO_FILE_FORMAT_X509); + NM_SET_OUT(out_certificate, nm_secret_copy_to_gbytes(contents.bin, contents.len)); + return TRUE; + } + } else { + nm_auto_clear_secret_ptr NMSecretPtr pem_cert = {0}; + + if (extract_pem_cert_data(contents.bin, contents.len, &pem_cert, NULL)) { + if (_nm_crypto_verify_x509(pem_cert.bin, pem_cert.len, NULL)) { + NM_SET_OUT(out_file_format, NM_CRYPTO_FILE_FORMAT_X509); + NM_SET_OUT(out_certificate, nm_secret_copy_to_gbytes(contents.bin, contents.len)); + return TRUE; + } + } + } + + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Failed to recognize certificate")); + +out: + NM_SET_OUT(out_file_format, NM_CRYPTO_FILE_FORMAT_UNKNOWN); + NM_SET_OUT(out_certificate, NULL); + return FALSE; +} + +gboolean +nm_crypto_is_pkcs12_data(const guint8 *data, gsize data_len, GError **error) +{ + GError * local = NULL; + gboolean success; + + if (!data_len) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("Certificate file is empty")); + return FALSE; + } + + g_return_val_if_fail(data != NULL, FALSE); + + if (!_nm_crypto_init(error)) + return FALSE; + + success = _nm_crypto_verify_pkcs12(data, data_len, NULL, &local); + if (success == FALSE) { + /* If the error was just a decryption error, then it's pkcs#12 */ + if (local) { + if (g_error_matches(local, NM_CRYPTO_ERROR, NM_CRYPTO_ERROR_DECRYPTION_FAILED)) { + success = TRUE; + g_error_free(local); + } else + g_propagate_error(error, local); + } + } + return success; +} + +gboolean +nm_crypto_is_pkcs12_file(const char *file, GError **error) +{ + nm_auto_clear_secret_ptr NMSecretPtr contents = {0}; + + g_return_val_if_fail(file != NULL, FALSE); + + if (!_nm_crypto_init(error)) + return FALSE; + + if (!file_read_contents(file, &contents, error)) + return FALSE; + + return nm_crypto_is_pkcs12_data(contents.bin, contents.len, error); +} + +/* Verifies that a private key can be read, and if a password is given, that + * the private key can be decrypted with that password. + */ +NMCryptoFileFormat +nm_crypto_verify_private_key_data(const guint8 *data, + gsize data_len, + const char * password, + gboolean * out_is_encrypted, + GError ** error) +{ + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + gboolean is_encrypted = FALSE; + + g_return_val_if_fail(data != NULL, NM_CRYPTO_FILE_FORMAT_UNKNOWN); + g_return_val_if_fail(out_is_encrypted == NULL || *out_is_encrypted == FALSE, + NM_CRYPTO_FILE_FORMAT_UNKNOWN); + + if (!_nm_crypto_init(error)) + return NM_CRYPTO_FILE_FORMAT_UNKNOWN; + + /* Check for PKCS#12 first */ + if (nm_crypto_is_pkcs12_data(data, data_len, NULL)) { + is_encrypted = TRUE; + if (!password || _nm_crypto_verify_pkcs12(data, data_len, password, error)) + format = NM_CRYPTO_FILE_FORMAT_PKCS12; + } else { + nm_auto_clear_secret_ptr NMSecretPtr parsed = {0}; + + /* Maybe it's PKCS#8 */ + if (parse_pkcs8_key_file(data, data_len, &parsed, &is_encrypted, NULL)) { + if (!password + || _nm_crypto_verify_pkcs8(parsed.bin, parsed.len, is_encrypted, password, error)) + format = NM_CRYPTO_FILE_FORMAT_RAW_KEY; + } else if (parse_tpm2_wrapped_key_file(data, data_len, &is_encrypted, NULL)) { + format = NM_CRYPTO_FILE_FORMAT_RAW_KEY; + } else { + NMCryptoCipherType cipher; + nm_auto_free_secret char *iv = NULL; + + /* Or it's old-style OpenSSL */ + if (parse_old_openssl_key_file(data, data_len, NULL, NULL, &cipher, &iv, NULL)) { + format = NM_CRYPTO_FILE_FORMAT_RAW_KEY; + is_encrypted = (cipher != NM_CRYPTO_CIPHER_UNKNOWN && iv); + } + } + } + + if (format == NM_CRYPTO_FILE_FORMAT_UNKNOWN && error && !*error) { + g_set_error(error, + NM_CRYPTO_ERROR, + NM_CRYPTO_ERROR_INVALID_DATA, + _("not a valid private key")); + } + + if (out_is_encrypted) + *out_is_encrypted = is_encrypted; + return format; +} + +NMCryptoFileFormat +nm_crypto_verify_private_key(const char *filename, + const char *password, + gboolean * out_is_encrypted, + GError ** error) +{ + nm_auto_clear_secret_ptr NMSecretPtr contents = {0}; + + g_return_val_if_fail(filename != NULL, NM_CRYPTO_FILE_FORMAT_UNKNOWN); + + if (!_nm_crypto_init(error)) + return NM_CRYPTO_FILE_FORMAT_UNKNOWN; + + if (!file_read_contents(filename, &contents, error)) + return NM_CRYPTO_FILE_FORMAT_UNKNOWN; + + return nm_crypto_verify_private_key_data(contents.bin, + contents.len, + password, + out_is_encrypted, + error); +} + +gboolean +nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error) +{ + return _nm_crypto_randomize(buffer, buffer_len, error); +} + +/** + * nmtst_crypto_rsa_key_encrypt: + * @data: (array length=len): RSA private key data to be encrypted + * @len: length of @data + * @in_password: (allow-none): existing password to use, if any + * @out_password: (out) (allow-none): if @in_password was %NULL, a random + * password will be generated and returned in this argument + * @error: detailed error information on return, if an error occurred + * + * Encrypts the given RSA private key data with the given password (or generates + * a password if no password was given) and converts the data to PEM format + * suitable for writing to a file. It uses Triple DES cipher for the encryption. + * + * Returns: (transfer full): on success, PEM-formatted data suitable for writing + * to a PEM-formatted certificate/private key file. + **/ +GBytes * +nmtst_crypto_rsa_key_encrypt(const guint8 *data, + gsize len, + const char * in_password, + char ** out_password, + GError ** error) +{ + guint8 salt[8]; + nm_auto_clear_secret_ptr NMSecretPtr key = {0}; + nm_auto_clear_secret_ptr NMSecretPtr enc = {0}; + gs_unref_ptrarray GPtrArray *pem = NULL; + nm_auto_free_secret char * tmp_password = NULL; + nm_auto_free_secret char * enc_base64 = NULL; + gsize enc_base64_len; + const char * p; + gsize ret_len, ret_idx; + guint i; + NMSecretBuf * ret; + + g_return_val_if_fail(data, NULL); + g_return_val_if_fail(len > 0, NULL); + g_return_val_if_fail(!out_password || !*out_password, NULL); + + /* Make the password if needed */ + if (!in_password) { + nm_auto_clear_static_secret_ptr NMSecretPtr pw_buf = NM_SECRET_PTR_STATIC(32); + + if (!nm_crypto_randomize(pw_buf.bin, pw_buf.len, error)) + return NULL; + tmp_password = nm_utils_bin2hexstr(pw_buf.bin, pw_buf.len, -1); + in_password = tmp_password; + } + + if (!nm_crypto_randomize(salt, sizeof(salt), error)) + return NULL; + + key.bin = nmtst_crypto_make_des_aes_key(NM_CRYPTO_CIPHER_DES_EDE3_CBC, + salt, + sizeof(salt), + in_password, + &key.len, + NULL); + if (!key.bin) + g_return_val_if_reached(NULL); + + enc.bin = _nmtst_crypto_encrypt(NM_CRYPTO_CIPHER_DES_EDE3_CBC, + data, + len, + salt, + sizeof(salt), + key.bin, + key.len, + &enc.len, + error); + if (!enc.bin) + return NULL; + + /* What follows is not the most efficient way to construct the pem + * file line-by-line. At least, it makes sure, that the data will be cleared + * again and not left around in memory. + * + * If this would not be test code, we should improve the implementation + * to avoid some of the copying. */ + pem = g_ptr_array_new_with_free_func((GDestroyNotify) nm_free_secret); + + g_ptr_array_add(pem, g_strdup("-----BEGIN RSA PRIVATE KEY-----\n")); + g_ptr_array_add(pem, g_strdup("Proc-Type: 4,ENCRYPTED\n")); + + /* Convert the salt to a hex string */ + g_ptr_array_add( + pem, + g_strdup_printf("DEK-Info: %s,", + nm_crypto_cipher_get_info(NM_CRYPTO_CIPHER_DES_EDE3_CBC)->name)); + g_ptr_array_add(pem, nm_utils_bin2hexstr(salt, sizeof(salt), sizeof(salt) * 2)); + g_ptr_array_add(pem, g_strdup("\n\n")); + + /* Convert the encrypted key to a base64 string */ + enc_base64 = g_base64_encode((const guchar *) enc.bin, enc.len); + enc_base64_len = strlen(enc_base64); + for (p = enc_base64; (p - enc_base64) < (ptrdiff_t) enc_base64_len; p += 64) { + g_ptr_array_add(pem, g_strndup(p, 64)); + g_ptr_array_add(pem, g_strdup("\n")); + } + + g_ptr_array_add(pem, g_strdup("-----END RSA PRIVATE KEY-----\n")); + + ret_len = 0; + for (i = 0; i < pem->len; i++) + ret_len += strlen(pem->pdata[i]); + + ret = nm_secret_buf_new(ret_len + 1); + ret_idx = 0; + for (i = 0; i < pem->len; i++) { + const char *line = pem->pdata[i]; + gsize line_l = strlen(line); + + memcpy(&ret->bin[ret_idx], line, line_l); + ret_idx += line_l; + nm_assert(ret_idx <= ret_len); + } + nm_assert(ret_idx == ret_len); + ret->bin[ret_len] = '\0'; + + NM_SET_OUT(out_password, g_strdup(tmp_password)); + return nm_secret_buf_to_gbytes_take(ret, ret_len); +} diff --git a/src/libnm-core-impl/nm-crypto.h b/src/libnm-core-impl/nm-crypto.h new file mode 100644 index 00000000..855e4f9c --- /dev/null +++ b/src/libnm-core-impl/nm-crypto.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Dan Williams + * Copyright (C) 2007 - 2014 Red Hat, Inc. + */ + +#ifndef __NM_CRYPTO_H__ +#define __NM_CRYPTO_H__ + +typedef enum { + NM_CRYPTO_CIPHER_UNKNOWN, + NM_CRYPTO_CIPHER_DES_EDE3_CBC, + NM_CRYPTO_CIPHER_DES_CBC, + NM_CRYPTO_CIPHER_AES_128_CBC, + NM_CRYPTO_CIPHER_AES_192_CBC, + NM_CRYPTO_CIPHER_AES_256_CBC, +} NMCryptoCipherType; + +typedef struct { + const char * name; + NMCryptoCipherType cipher; + guint8 digest_len; + guint8 real_iv_len; +} NMCryptoCipherInfo; + +const NMCryptoCipherInfo *nm_crypto_cipher_get_info(NMCryptoCipherType cipher); +const NMCryptoCipherInfo *nm_crypto_cipher_get_info_by_name(const char *cipher_name, gssize p_len); + +typedef enum { + NM_CRYPTO_KEY_TYPE_UNKNOWN = 0, + NM_CRYPTO_KEY_TYPE_RSA, + NM_CRYPTO_KEY_TYPE_DSA +} NMCryptoKeyType; + +typedef enum { + NM_CRYPTO_FILE_FORMAT_UNKNOWN = 0, + NM_CRYPTO_FILE_FORMAT_X509, + NM_CRYPTO_FILE_FORMAT_RAW_KEY, + NM_CRYPTO_FILE_FORMAT_PKCS12 +} NMCryptoFileFormat; + +/*****************************************************************************/ + +GBytes *nm_crypto_read_file(const char *filename, GError **error); + +gboolean nm_crypto_load_and_verify_certificate(const char * file, + NMCryptoFileFormat *out_file_format, + GBytes ** out_certificat, + GError ** error); + +gboolean nm_crypto_is_pkcs12_file(const char *file, GError **error); + +gboolean nm_crypto_is_pkcs12_data(const guint8 *data, gsize len, GError **error); + +NMCryptoFileFormat nm_crypto_verify_private_key_data(const guint8 *data, + gsize data_len, + const char * password, + gboolean * out_is_encrypted, + GError ** error); + +NMCryptoFileFormat nm_crypto_verify_private_key(const char *file, + const char *password, + gboolean * out_is_encrypted, + GError ** error); + +gboolean nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error); + +/*****************************************************************************/ + +GBytes *nmtst_crypto_decrypt_openssl_private_key_data(const guint8 * data, + gsize data_len, + const char * password, + NMCryptoKeyType *out_key_type, + GError ** error); + +GBytes *nmtst_crypto_decrypt_openssl_private_key(const char * file, + const char * password, + NMCryptoKeyType *out_key_type, + GError ** error); + +GBytes *nmtst_crypto_rsa_key_encrypt(const guint8 *data, + gsize len, + const char * in_password, + char ** out_password, + GError ** error); + +guint8 *nmtst_crypto_make_des_aes_key(NMCryptoCipherType cipher, + const guint8 * salt, + gsize salt_len, + const char * password, + gsize * out_len, + GError ** error); + +/*****************************************************************************/ + +#endif /* __NM_CRYPTO_H__ */ diff --git a/src/libnm-core-impl/nm-dbus-utils.c b/src/libnm-core-impl/nm-dbus-utils.c new file mode 100644 index 00000000..bcc8cf9c --- /dev/null +++ b/src/libnm-core-impl/nm-dbus-utils.c @@ -0,0 +1,259 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "libnm-core-intern/nm-core-internal.h" + +typedef struct { + char * signal_name; + const GVariantType *signature; +} NMDBusSignalData; + +static void +dbus_signal_data_free(gpointer data, GClosure *closure) +{ + NMDBusSignalData *sd = data; + + g_free(sd->signal_name); + g_slice_free(NMDBusSignalData, sd); +} + +static void +dbus_signal_meta_marshal(GClosure * closure, + GValue * return_value, + guint n_param_values, + const GValue *param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + NMDBusSignalData *sd = marshal_data; + const char * signal_name; + GVariant * parameters, *param; + GValue * closure_params; + gsize n_params, i; + + g_return_if_fail(n_param_values == 4); + + signal_name = g_value_get_string(¶m_values[2]); + parameters = g_value_get_variant(¶m_values[3]); + + if (strcmp(signal_name, sd->signal_name) != 0) + return; + + if (sd->signature) { + if (!g_variant_is_of_type(parameters, sd->signature)) { + g_warning("%p: got signal '%s' but parameters were of type '%s', not '%s'", + g_value_get_object(¶m_values[0]), + signal_name, + g_variant_get_type_string(parameters), + g_variant_type_peek_string(sd->signature)); + return; + } + + n_params = g_variant_n_children(parameters) + 1; + } else + n_params = 1; + + closure_params = g_new0(GValue, n_params); + g_value_init(&closure_params[0], G_TYPE_OBJECT); + g_value_copy(¶m_values[0], &closure_params[0]); + + for (i = 1; i < n_params; i++) { + param = g_variant_get_child_value(parameters, i - 1); + if (g_variant_is_of_type(param, G_VARIANT_TYPE("ay")) + || g_variant_is_of_type(param, G_VARIANT_TYPE("aay"))) { + /* g_dbus_gvariant_to_gvalue() thinks 'ay' means "non-UTF-8 NUL-terminated string" */ + g_value_init(&closure_params[i], G_TYPE_VARIANT); + g_value_set_variant(&closure_params[i], param); + } else + g_dbus_gvariant_to_gvalue(param, &closure_params[i]); + g_variant_unref(param); + } + + g_cclosure_marshal_generic(closure, NULL, n_params, closure_params, invocation_hint, NULL); + + for (i = 0; i < n_params; i++) + g_value_unset(&closure_params[i]); + g_free(closure_params); +} + +/** + * _nm_dbus_signal_connect_data: + * @proxy: a #GDBusProxy + * @signal_name: the D-Bus signal to connect to + * @signature: (allow-none): the signal's type signature (must be a tuple) + * @c_handler: the signal handler function + * @data: (allow-none): data to pass to @c_handler + * @destroy_data: (allow-none): closure destroy notify for @data + * @connect_flags: connection flags + * + * Connects to the D-Bus signal @signal_name on @proxy. @c_handler must be a + * void function whose first argument is a #GDBusProxy, followed by arguments + * for each element of @signature, ending with a #gpointer argument for @data. + * + * The argument types in @c_handler correspond to the types output by + * g_dbus_gvariant_to_gvalue(), except for 'ay' and 'aay'. In particular: + * - both 16-bit and 32-bit integers are passed as #int/#guint + * - 'as' values are passed as #GStrv (char **) + * - all other array, tuple, and dict types are passed as #GVariant + * + * If @signature is %NULL, then the signal's parameters will be ignored, and + * @c_handler should take only the #GDBusProxy and #gpointer arguments. + * + * Returns: the signal handler ID, which can be used with + * g_signal_handler_remove(). Beware that because of the way the signal is + * connected, you will not be able to remove it with + * g_signal_handlers_disconnect_by_func(), although + * g_signal_handlers_disconnect_by_data() will work correctly. + */ +gulong +_nm_dbus_signal_connect_data(GDBusProxy * proxy, + const char * signal_name, + const GVariantType *signature, + GCallback c_handler, + gpointer data, + GClosureNotify destroy_data, + GConnectFlags connect_flags) +{ + NMDBusSignalData *sd; + GClosure * closure; + gboolean swapped = !!(connect_flags & G_CONNECT_SWAPPED); + gboolean after = !!(connect_flags & G_CONNECT_AFTER); + + g_return_val_if_fail(G_IS_DBUS_PROXY(proxy), 0); + g_return_val_if_fail(signal_name != NULL, 0); + g_return_val_if_fail(signature == NULL || g_variant_type_is_tuple(signature), 0); + g_return_val_if_fail(c_handler != NULL, 0); + + sd = g_slice_new(NMDBusSignalData); + sd->signal_name = g_strdup(signal_name); + sd->signature = signature; + + closure = (swapped ? g_cclosure_new_swap : g_cclosure_new) (c_handler, data, destroy_data); + g_closure_set_marshal(closure, g_cclosure_marshal_generic); + g_closure_set_meta_marshal(closure, sd, dbus_signal_meta_marshal); + g_closure_add_finalize_notifier(closure, sd, dbus_signal_data_free); + + return g_signal_connect_closure(proxy, "g-signal", closure, after); +} + +/** + * _nm_dbus_signal_connect: + * @proxy: a #GDBusProxy + * @signal_name: the D-Bus signal to connect to + * @signature: the signal's type signature (must be a tuple) + * @c_handler: the signal handler function + * @data: (allow-none): data to pass to @c_handler + * + * Simplified version of _nm_dbus_signal_connect_data() with fewer arguments. + * + * Returns: the signal handler ID, as with _nm_signal_connect_data(). + */ + +/** + * _nm_dbus_typecheck_response: + * @response: the #GVariant response to check. + * @reply_type: the expected reply type. It may be %NULL to perform no + * checking. + * @error: (allow-none): the error in case the @reply_type does not match. + * + * Returns: %TRUE, if @response is of the expected @reply_type. + */ +gboolean +_nm_dbus_typecheck_response(GVariant *response, const GVariantType *reply_type, GError **error) +{ + g_return_val_if_fail(response, FALSE); + + if (!reply_type) + return TRUE; + if (g_variant_is_of_type(response, reply_type)) + return TRUE; + + /* This is the same error code that g_dbus_connection_call() returns if + * @reply_type doesn't match. + */ + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Method returned type '%s', but expected '%s'"), + g_variant_get_type_string(response), + g_variant_type_peek_string(reply_type)); + return FALSE; +} + +/** + * _nm_dbus_proxy_call_finish: + * @proxy: A #GDBusProxy. + * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to + * g_dbus_proxy_call(). + * @reply_type: (allow-none): the expected type of the reply, or %NULL + * @error: Return location for error or %NULL. + * + * Finishes an operation started with g_dbus_proxy_call(), as with + * g_dbus_proxy_call_finish(), except thatif @reply_type is non-%NULL, then it + * will also check that the response matches that type signature, and return + * an error if not. + * + * Returns: %NULL if @error is set. Otherwise, a #GVariant tuple with + * return values. Free with g_variant_unref(). + */ +GVariant * +_nm_dbus_proxy_call_finish(GDBusProxy * proxy, + GAsyncResult * res, + const GVariantType *reply_type, + GError ** error) +{ + GVariant *variant; + + variant = g_dbus_proxy_call_finish(proxy, res, error); + if (variant && !_nm_dbus_typecheck_response(variant, reply_type, error)) + nm_clear_pointer(&variant, g_variant_unref); + return variant; +} + +GVariant * +_nm_dbus_connection_call_finish(GDBusConnection * dbus_connection, + GAsyncResult * result, + const GVariantType *reply_type, + GError ** error) +{ + GVariant *variant; + + variant = g_dbus_connection_call_finish(dbus_connection, result, error); + if (variant && !_nm_dbus_typecheck_response(variant, reply_type, error)) + nm_clear_pointer(&variant, g_variant_unref); + return variant; +} + +/** + * _nm_dbus_error_has_name: + * @error: (allow-none): a #GError, or %NULL + * @dbus_error_name: a D-Bus error name + * + * Checks if @error is set and corresponds to the D-Bus error @dbus_error_name. + * + * This should only be used for "foreign" D-Bus errors (eg, errors + * from BlueZ or wpa_supplicant). All NetworkManager D-Bus errors + * should be properly mapped by gdbus to one of the domains/codes in + * nm-errors.h. + * + * Returns: %TRUE or %FALSE + */ +gboolean +_nm_dbus_error_has_name(GError *error, const char *dbus_error_name) +{ + gboolean has_name = FALSE; + + if (error && g_dbus_error_is_remote_error(error)) { + char *error_name; + + error_name = g_dbus_error_get_remote_error(error); + has_name = !g_strcmp0(error_name, dbus_error_name); + g_free(error_name); + } + + return has_name; +} diff --git a/src/libnm-core-impl/nm-default-libnm-core.h b/src/libnm-core-impl/nm-default-libnm-core.h new file mode 100644 index 00000000..0f27f82d --- /dev/null +++ b/src/libnm-core-impl/nm-default-libnm-core.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef __NM_DEFAULT_LIBNM_CORE_H__ +#define __NM_DEFAULT_LIBNM_CORE_H__ + +/*****************************************************************************/ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#undef NETWORKMANAGER_COMPILATION +#define NETWORKMANAGER_COMPILATION NM_NETWORKMANAGER_COMPILATION_LIBNM_CORE + +/*****************************************************************************/ + +#include "nm-version.h" + +/*****************************************************************************/ + +#endif /* __NM_DEFAULT_LIBNM_CORE_H__ */ diff --git a/src/libnm-core-impl/nm-errors.c b/src/libnm-core-impl/nm-errors.c new file mode 100644 index 00000000..ea4ca5e9 --- /dev/null +++ b/src/libnm-core-impl/nm-errors.c @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2004 - 2014 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-errors.h" + +#include "nm-vpn-dbus-interface.h" +#include "libnm-core-intern/nm-core-internal.h" + +NM_CACHED_QUARK_FCN("nm-agent-manager-error-quark", nm_agent_manager_error_quark); +NM_CACHED_QUARK_FCN("nm-connection-error-quark", nm_connection_error_quark); +NM_CACHED_QUARK_FCN("nm-crypto-error-quark", nm_crypto_error_quark); +NM_CACHED_QUARK_FCN("nm-device-error-quark", nm_device_error_quark); +NM_CACHED_QUARK_FCN("nm-secret-agent-error-quark", nm_secret_agent_error_quark); +NM_CACHED_QUARK_FCN("nm-settings-error-quark", nm_settings_error_quark); +NM_CACHED_QUARK_FCN("nm-vpn-plugin-error-quark", nm_vpn_plugin_error_quark); + +static void +register_error_domain(GQuark domain, const char *interface, GType enum_type) +{ + nm_auto_unref_gtypeclass GEnumClass *enum_class = g_type_class_ref(enum_type); + guint i; + + for (i = 0; i < enum_class->n_values; i++) { + const GEnumValue *e = &enum_class->values[i]; + char error_name[200]; + + nm_assert(e && e->value_nick && !strchr(e->value_nick, '-')); + + nm_sprintf_buf(error_name, "%s.%s", interface, e->value_nick); + if (!g_dbus_error_register_error(domain, e->value, error_name)) + nm_assert_not_reached(); + } +} + +void +_nm_dbus_errors_init(void) +{ + register_error_domain(NM_AGENT_MANAGER_ERROR, + NM_DBUS_INTERFACE_AGENT_MANAGER, + NM_TYPE_AGENT_MANAGER_ERROR); + register_error_domain(NM_CONNECTION_ERROR, + NM_DBUS_INTERFACE_SETTINGS_CONNECTION, + NM_TYPE_CONNECTION_ERROR); + register_error_domain(NM_DEVICE_ERROR, NM_DBUS_INTERFACE_DEVICE, NM_TYPE_DEVICE_ERROR); + register_error_domain(NM_MANAGER_ERROR, NM_DBUS_INTERFACE, NM_TYPE_MANAGER_ERROR); + register_error_domain(NM_SECRET_AGENT_ERROR, + NM_DBUS_INTERFACE_SECRET_AGENT, + NM_TYPE_SECRET_AGENT_ERROR); + register_error_domain(NM_SETTINGS_ERROR, NM_DBUS_INTERFACE_SETTINGS, NM_TYPE_SETTINGS_ERROR); + register_error_domain(NM_VPN_PLUGIN_ERROR, NM_DBUS_VPN_ERROR_PREFIX, NM_TYPE_VPN_PLUGIN_ERROR); +} diff --git a/src/libnm-core-impl/nm-keyfile-utils.c b/src/libnm-core-impl/nm-keyfile-utils.c new file mode 100644 index 00000000..88f9c4f9 --- /dev/null +++ b/src/libnm-core-impl/nm-keyfile-utils.c @@ -0,0 +1,684 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "libnm-core-intern/nm-keyfile-utils.h" + +#include + +#include "libnm-glib-aux/nm-str-buf.h" + +#include "nm-keyfile.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" + +#include "libnm-core-intern/nm-keyfile-internal.h" + +/*****************************************************************************/ + +/** + * nm_key_file_get_boolean: + * @kf: the #GKeyFile + * @group: the group + * @key: the key + * @default_value: the default value if the value is set or not parsable as a boolean. + * + * Replacement for g_key_file_get_boolean() (which uses g_key_file_parse_value_as_boolean()). + * g_key_file_get_boolean() seems odd to me, because it accepts trailing ASCII whitespace, + * but not leading. + * This uses _nm_utils_ascii_str_to_bool(), which accepts trailing and leading whitespace, + * case-insensitive words, and also strings like "on" and "off". + * _nm_utils_ascii_str_to_bool() is our way to parse booleans from string, and we should + * use that one consistently. + * + * Also, it doesn't have g_key_file_get_boolean()'s odd API to require an error argument + * to detect parsing failures. + * + * Returns: either %TRUE or %FALSE if the key exists and is parsable as a boolean. + * Otherwise, @default_value. Sets errno to ENODATA, EINVAL or 0, depending on whether + * the key exists, whether the value is invalid, or success. + */ +int +nm_key_file_get_boolean(GKeyFile *kf, const char *group, const char *key, int default_value) +{ + int v; + gs_free char *value = NULL; + + value = g_key_file_get_value(kf, group, key, NULL); + + if (!value) { + errno = ENODATA; + return default_value; + } + v = _nm_utils_ascii_str_to_bool(value, -1); + if (v != -1) { + errno = 0; + return v; + } + errno = EINVAL; + return default_value; +} + +/*****************************************************************************/ + +typedef struct { + const char *setting; + const char *alias; +} SettingAlias; + +static const SettingAlias alias_list[] = { + {NM_SETTING_WIRED_SETTING_NAME, "ethernet"}, + {NM_SETTING_WIRELESS_SETTING_NAME, "wifi"}, + {NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, "wifi-security"}, +}; + +const char * +nm_keyfile_plugin_get_alias_for_setting_name(const char *setting_name) +{ + guint i; + + g_return_val_if_fail(setting_name != NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS(alias_list); i++) { + if (nm_streq(setting_name, alias_list[i].setting)) + return alias_list[i].alias; + } + return NULL; +} + +const char * +nm_keyfile_plugin_get_setting_name_for_alias(const char *alias) +{ + guint i; + + g_return_val_if_fail(alias != NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS(alias_list); i++) { + if (nm_streq(alias, alias_list[i].alias)) + return alias_list[i].setting; + } + return NULL; +} + +/*****************************************************************************/ + +char ** +nm_keyfile_plugin_kf_get_string_list(GKeyFile * kf, + const char *group, + const char *key, + gsize * out_length, + GError ** error) +{ + char ** list; + const char *alias; + GError * local = NULL; + gsize l; + + list = g_key_file_get_string_list(kf, group, key, &l, &local); + if (nm_g_error_matches(local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name(group); + if (alias) { + g_clear_error(&local); + list = g_key_file_get_string_list(kf, alias, key, &l, &local); + } + } + if (local) + g_propagate_error(error, local); + if (!list) + l = 0; + NM_SET_OUT(out_length, l); + return list; +} + +guint * +nm_keyfile_plugin_kf_get_integer_list_uint(GKeyFile * key_file, + const char *group_name, + const char *key, + gsize * out_length, + GError ** error) +{ + GError * key_file_error = NULL; + gs_strfreev char **values = NULL; + gs_free guint *int_values = NULL; + gsize i, num_ints; + + NM_SET_OUT(out_length, 0); + + g_return_val_if_fail(key_file != NULL, NULL); + g_return_val_if_fail(group_name != NULL, NULL); + g_return_val_if_fail(key != NULL, NULL); + + values = + nm_keyfile_plugin_kf_get_string_list(key_file, group_name, key, &num_ints, &key_file_error); + + if (key_file_error) + g_propagate_error(error, key_file_error); + if (!values) + return NULL; + + int_values = g_new(guint, num_ints); + + for (i = 0; i < num_ints; i++) { + gint64 v; + + G_STATIC_ASSERT_EXPR(sizeof(v) > sizeof(guint)); + v = _nm_utils_ascii_str_to_int64(values[i], 10, 0, G_MAXUINT, -1); + if (v == -1) { + g_set_error(error, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("Value cannot be interpreted as a list of numbers.")); + return NULL; + } + + int_values[i] = v; + } + + NM_SET_OUT(out_length, num_ints); + return g_steal_pointer(&int_values); +} + +void +nm_keyfile_plugin_kf_set_string_list(GKeyFile * kf, + const char * group, + const char * key, + const char *const *list, + gsize length) +{ + const char *alias; + + alias = nm_keyfile_plugin_get_alias_for_setting_name(group); + g_key_file_set_string_list(kf, alias ?: group, key, list, length); +} + +void +nm_keyfile_plugin_kf_set_integer_list_uint(GKeyFile * kf, + const char * group, + const char * key, + const guint *data, + gsize length) +{ + nm_auto_str_buf NMStrBuf strbuf = {}; + gsize i; + + g_return_if_fail(kf); + g_return_if_fail(!length || data); + g_return_if_fail(group && group[0]); + g_return_if_fail(key && key[0]); + + nm_str_buf_init(&strbuf, length * 4u + 2u, FALSE); + for (i = 0; i < length; i++) + nm_str_buf_append_printf(&strbuf, "%u;", data[i]); + nm_keyfile_plugin_kf_set_value(kf, group, key, nm_str_buf_get_str(&strbuf)); +} + +void +nm_keyfile_plugin_kf_set_integer_list_uint8(GKeyFile * kf, + const char * group, + const char * key, + const guint8 *data, + gsize length) +{ + nm_auto_str_buf NMStrBuf strbuf = {}; + gsize i; + + g_return_if_fail(kf); + g_return_if_fail(!length || data); + g_return_if_fail(group && group[0]); + g_return_if_fail(key && key[0]); + + nm_str_buf_init(&strbuf, length * 4u + 2u, FALSE); + for (i = 0; i < length; i++) + nm_str_buf_append_printf(&strbuf, "%u;", (guint) data[i]); + nm_keyfile_plugin_kf_set_value(kf, group, key, nm_str_buf_get_str(&strbuf)); +} + +#define DEFINE_KF_WRAPPER_GET(fcn_name, get_ctype, key_file_get_fcn) \ + get_ctype fcn_name(GKeyFile *kf, const char *group, const char *key, GError **error) \ + { \ + get_ctype val; \ + const char *alias; \ + GError * local = NULL; \ + \ + val = key_file_get_fcn(kf, group, key, &local); \ + if (nm_g_error_matches(local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { \ + alias = nm_keyfile_plugin_get_alias_for_setting_name(group); \ + if (alias) { \ + g_clear_error(&local); \ + val = key_file_get_fcn(kf, alias, key, &local); \ + } \ + } \ + if (local) \ + g_propagate_error(error, local); \ + return val; \ + } + +DEFINE_KF_WRAPPER_GET(nm_keyfile_plugin_kf_get_string, char *, g_key_file_get_string); +DEFINE_KF_WRAPPER_GET(nm_keyfile_plugin_kf_get_boolean, gboolean, g_key_file_get_boolean); +DEFINE_KF_WRAPPER_GET(nm_keyfile_plugin_kf_get_value, char *, g_key_file_get_value); + +#define DEFINE_KF_WRAPPER_SET(fcn_name, set_ctype, key_file_set_fcn) \ + void fcn_name(GKeyFile *kf, const char *group, const char *key, set_ctype value) \ + { \ + const char *alias; \ + \ + alias = nm_keyfile_plugin_get_alias_for_setting_name(group); \ + key_file_set_fcn(kf, alias ?: group, key, value); \ + } + +DEFINE_KF_WRAPPER_SET(nm_keyfile_plugin_kf_set_string, const char *, g_key_file_set_string); +DEFINE_KF_WRAPPER_SET(nm_keyfile_plugin_kf_set_boolean, gboolean, g_key_file_set_boolean); +DEFINE_KF_WRAPPER_SET(nm_keyfile_plugin_kf_set_value, const char *, g_key_file_set_value); + +gint64 +nm_keyfile_plugin_kf_get_int64(GKeyFile * kf, + const char *group, + const char *key, + guint base, + gint64 min, + gint64 max, + gint64 fallback, + GError ** error) +{ + gs_free char *s = NULL; + int errsv; + gint64 v; + + s = nm_keyfile_plugin_kf_get_value(kf, group, key, error); + if (!s) { + errno = ENODATA; + return fallback; + } + + v = _nm_utils_ascii_str_to_int64(s, base, min, max, fallback); + errsv = errno; + if (errsv != 0 && error) { + g_set_error(error, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("value is not an integer in range [%lld, %lld]"), + (long long) min, + (long long) max); + errno = errsv; + } + return v; +} + +char ** +nm_keyfile_plugin_kf_get_keys(GKeyFile *kf, const char *group, gsize *out_length, GError **error) +{ + char ** keys; + const char *alias; + GError * local = NULL; + gsize l; + + keys = g_key_file_get_keys(kf, group, &l, &local); + if (nm_g_error_matches(local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name(group); + if (alias) { + g_clear_error(&local); + keys = g_key_file_get_keys(kf, alias, &l, error ? &local : NULL); + } + } + nm_assert((!local) != (!keys)); + if (!keys) + l = 0; + nm_assert(l == NM_PTRARRAY_LEN(keys)); + NM_SET_OUT(out_length, l); + if (local) + g_propagate_error(error, local); + return keys; +} + +gboolean +nm_keyfile_plugin_kf_has_key(GKeyFile *kf, const char *group, const char *key, GError **error) +{ + gboolean has; + const char *alias; + GError * local = NULL; + + has = g_key_file_has_key(kf, group, key, &local); + if (nm_g_error_matches(local, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)) { + alias = nm_keyfile_plugin_get_alias_for_setting_name(group); + if (alias) { + g_clear_error(&local); + has = g_key_file_has_key(kf, alias, key, &local); + } + } + if (local) + g_propagate_error(error, local); + return has; +} + +/*****************************************************************************/ + +void +_nm_keyfile_copy(GKeyFile *dst, GKeyFile *src) +{ + gs_strfreev char **groups = NULL; + guint g, k; + + groups = g_key_file_get_groups(src, NULL); + for (g = 0; groups && groups[g]; g++) { + const char * group = groups[g]; + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys(src, group, NULL, NULL); + if (!keys) + continue; + + for (k = 0; keys[k]; k++) { + const char * key = keys[k]; + gs_free char *value = NULL; + + value = g_key_file_get_value(src, group, key, NULL); + if (value) + g_key_file_set_value(dst, group, key, value); + else + g_key_file_remove_key(dst, group, key, NULL); + } + } +} + +/*****************************************************************************/ + +gboolean +_nm_keyfile_a_contains_all_in_b(GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + if (!kf_a || !kf_b) + return FALSE; + + groups = g_key_file_get_groups(kf_a, NULL); + for (i = 0; groups && groups[i]; i++) { + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys(kf_a, groups[i], NULL, NULL); + if (!keys) + continue; + + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value(kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value(kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0(key_a, key_b) != 0) + return FALSE; + } + } + return TRUE; +} + +static gboolean +_nm_keyfile_equal_ordered(GKeyFile *kf_a, GKeyFile *kf_b) +{ + gs_strfreev char **groups = NULL; + gs_strfreev char **groups_b = NULL; + guint i, j; + + if (kf_a == kf_b) + return TRUE; + if (!kf_a || !kf_b) + return FALSE; + + groups = g_key_file_get_groups(kf_a, NULL); + groups_b = g_key_file_get_groups(kf_b, NULL); + if (!groups && !groups_b) + return TRUE; + if (!groups || !groups_b) + return FALSE; + for (i = 0; groups[i] && groups_b[i] && !strcmp(groups[i], groups_b[i]); i++) + ; + if (groups[i] || groups_b[i]) + return FALSE; + + for (i = 0; groups[i]; i++) { + gs_strfreev char **keys = NULL; + gs_strfreev char **keys_b = NULL; + + keys = g_key_file_get_keys(kf_a, groups[i], NULL, NULL); + keys_b = g_key_file_get_keys(kf_b, groups[i], NULL, NULL); + + if ((!keys) != (!keys_b)) + return FALSE; + if (!keys) + continue; + + for (j = 0; keys[j] && keys_b[j] && !strcmp(keys[j], keys_b[j]); j++) + ; + if (keys[j] || keys_b[j]) + return FALSE; + + for (j = 0; keys[j]; j++) { + gs_free char *key_a = g_key_file_get_value(kf_a, groups[i], keys[j], NULL); + gs_free char *key_b = g_key_file_get_value(kf_b, groups[i], keys[j], NULL); + + if (g_strcmp0(key_a, key_b) != 0) + return FALSE; + } + } + return TRUE; +} + +gboolean +_nm_keyfile_equal(GKeyFile *kf_a, GKeyFile *kf_b, gboolean consider_order) +{ + if (!consider_order) { + return _nm_keyfile_a_contains_all_in_b(kf_a, kf_b) + && _nm_keyfile_a_contains_all_in_b(kf_b, kf_a); + } else { + return _nm_keyfile_equal_ordered(kf_a, kf_b); + } +} + +gboolean +_nm_keyfile_has_values(GKeyFile *keyfile) +{ + gs_strfreev char **groups = NULL; + + g_return_val_if_fail(keyfile, FALSE); + + groups = g_key_file_get_groups(keyfile, NULL); + return groups && groups[0]; +} + +/*****************************************************************************/ + +static const char * +_keyfile_key_encode(const char *name, char **out_to_free) +{ + NMStrBuf str; + gsize len; + gsize i; + + nm_assert(name); + nm_assert(out_to_free && !*out_to_free); + + /* See g_key_file_is_key_name(). + * + * GKeyFile allows all UTF-8 characters (even non-well formed sequences), + * except: + * - no empty keys + * - no leading/trailing ' ' + * - no '=', '[', ']' + * + * We do something more strict here. All non-ASCII characters, all non-printable + * characters, and all invalid characters are escaped with "\\XX". + * + * We don't escape \\, unless it is followed by two hex digits. + */ + + if (!name[0]) { + /* empty keys are backslash encoded. Note that usually + * \\00 is not a valid encode, the only exception is the empty + * word. */ + return "\\00"; + } + + /* find the first character that needs escaping. */ + i = 0; + if (name[0] != ' ') { + for (;; i++) { + const guchar ch = (guchar) name[i]; + + if (ch == '\0') + return name; + + if (ch < 0x20 || ch >= 127 || NM_IN_SET(ch, '=', '[', ']') + || (ch == '\\' && g_ascii_isxdigit(name[i + 1]) && g_ascii_isxdigit(name[i + 2])) + || (ch == ' ' && name[i + 1] == '\0')) + break; + } + } else if (name[1] == '\0') + return "\\20"; + + len = i + strlen(&name[i]); + nm_assert(len == strlen(name)); + + nm_str_buf_init(&str, len + 15u, FALSE); + + if (name[0] == ' ') { + nm_assert(i == 0); + nm_str_buf_append(&str, "\\20"); + i = 1; + } else + nm_str_buf_append_len(&str, name, i); + + for (;; i++) { + const guchar ch = (guchar) name[i]; + + if (ch == '\0') + break; + + if (ch < 0x20 || ch >= 127 || NM_IN_SET(ch, '=', '[', ']') + || (ch == '\\' && g_ascii_isxdigit(name[i + 1]) && g_ascii_isxdigit(name[i + 2])) + || (ch == ' ' && name[i + 1] == '\0')) { + nm_str_buf_append_c(&str, '\\'); + nm_str_buf_append_c_hex(&str, ch, TRUE); + } else + nm_str_buf_append_c(&str, (char) ch); + } + + return (*out_to_free = nm_str_buf_finalize(&str, NULL)); +} + +static const char * +_keyfile_key_decode(const char *key, char **out_to_free) +{ + char *out; + gsize len; + gsize i; + gsize j; + + nm_assert(key); + nm_assert(out_to_free && !*out_to_free); + + if (!key[0]) + return ""; + + for (i = 0; TRUE; i++) { + const char ch = key[i]; + + if (ch == '\0') + return key; + if (ch == '\\' && g_ascii_isxdigit(key[i + 1]) && g_ascii_isxdigit(key[i + 2])) + break; + } + + len = i + strlen(&key[i]); + + if (len == 3 && nm_streq(key, "\\00")) + return ""; + + nm_assert(len == strlen(key)); + + out = g_new(char, len + 1u); + + memcpy(out, key, sizeof(char) * i); + + j = i; + for (;;) { + const char ch = key[i]; + char ch1, ch2; + unsigned v; + + if (ch == '\0') + break; + + if (ch == '\\' && g_ascii_isxdigit((ch1 = key[i + 1])) + && g_ascii_isxdigit((ch2 = key[i + 2]))) { + v = (g_ascii_xdigit_value(ch1) << 4) + g_ascii_xdigit_value(ch2); + if (v != 0) { + out[j++] = (char) v; + i += 3; + continue; + } + } + out[j++] = ch; + i++; + } + + nm_assert(j <= len); + out[j] = '\0'; + return (*out_to_free = out); +} + +/*****************************************************************************/ + +const char * +nm_keyfile_key_encode(const char *name, char **out_to_free) +{ + const char *key; + + key = _keyfile_key_encode(name, out_to_free); +#if NM_MORE_ASSERTS > 5 + nm_assert(key); + nm_assert(!*out_to_free || key == *out_to_free); + nm_assert(!*out_to_free || !nm_streq0(name, key)); + { + gs_free char *to_free2 = NULL; + const char * name2; + + name2 = _keyfile_key_decode(key, &to_free2); + /* name2, the result of encode()+decode() is identical to name. + * That is because + * - encode() is a injective function. + * - decode() is a surjective function, however for output + * values of encode() is behaves injective too. */ + nm_assert(nm_streq0(name2, name)); + } +#endif + return key; +} + +const char * +nm_keyfile_key_decode(const char *key, char **out_to_free) +{ + const char *name; + + name = _keyfile_key_decode(key, out_to_free); +#if NM_MORE_ASSERTS > 5 + nm_assert(name); + nm_assert(!*out_to_free || name == *out_to_free); + { + gs_free char *to_free2 = NULL; + const char * key2; + + key2 = _keyfile_key_encode(name, &to_free2); + /* key2, the result of decode+encode may not be identical + * to the original key. That is, decode() is a surjective + * function mapping different keys to the same name. + * However, decode() behaves injective for input that + * are valid output of encode(). */ + nm_assert(key2); + } +#endif + return name; +} diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c new file mode 100644 index 00000000..88aa0d46 --- /dev/null +++ b/src/libnm-core-impl/nm-keyfile.c @@ -0,0 +1,4447 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2008 - 2009 Novell, Inc. + * Copyright (C) 2008 - 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "libnm-core-intern/nm-keyfile-internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libnm-glib-aux/nm-uuid.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-systemd-shared/nm-sd-utils-shared.h" +#include "libnm-core-aux-intern/nm-common-macros.h" + +#include "libnm-core-intern/nm-core-internal.h" +#include "nm-keyfile.h" +#include "nm-setting-private.h" +#include "nm-setting-user.h" +#include "nm-setting-ovs-external-ids.h" + +#include "libnm-core-intern/nm-keyfile-utils.h" + +#define ETHERNET_S390_OPTIONS_GROUP_NAME "ethernet-s390-options" + +#define OVS_EXTERNAL_IDS_DATA_PREFIX "data." + +/*****************************************************************************/ + +typedef struct _ParseInfoProperty ParseInfoProperty; + +typedef struct { + NMConnection * connection; + GKeyFile * keyfile; + const char * base_dir; + NMKeyfileReadHandler read_handler; + void * user_data; + GError * error; + const char * group; + NMSetting * setting; +} KeyfileReaderInfo; + +typedef struct { + NMConnection * connection; + GKeyFile * keyfile; + GError * error; + NMKeyfileWriteHandler write_handler; + void * user_data; +} KeyfileWriterInfo; + +/*****************************************************************************/ + +static void +_key_file_handler_data_init(NMKeyfileHandlerData *handler_data, + NMKeyfileHandlerType handler_type, + const char * kf_group_name, + const char * kf_key, + NMSetting * cur_setting, + const char * cur_property, + GError ** p_error) +{ + nm_assert(handler_data); + nm_assert(p_error && !*p_error); + + handler_data->type = handler_type; + handler_data->p_error = p_error; + handler_data->kf_group_name = kf_group_name; + handler_data->kf_key = kf_key; + handler_data->cur_setting = cur_setting; + handler_data->cur_property = cur_property; +} + +static void +_key_file_handler_data_init_read(NMKeyfileHandlerData *handler_data, + NMKeyfileHandlerType handler_type, + KeyfileReaderInfo * info, + const char * kf_key, + const char * cur_property) +{ + _key_file_handler_data_init(handler_data, + handler_type, + info->group, + kf_key, + info->setting, + cur_property, + &info->error); +} + +static void +_key_file_handler_data_init_write(NMKeyfileHandlerData *handler_data, + NMKeyfileHandlerType handler_type, + KeyfileWriterInfo * info, + const char * kf_group, + const char * kf_key, + NMSetting * cur_setting, + const char * cur_property) +{ + _key_file_handler_data_init(handler_data, + handler_type, + kf_group, + kf_key, + cur_setting, + cur_property, + &info->error); +} + +_nm_printf(5, 6) static void _handle_warn(KeyfileReaderInfo * info, + const char * kf_key, + const char * cur_property, + NMKeyfileWarnSeverity severity, + const char * fmt, + ...) +{ + NMKeyfileHandlerData handler_data; + + _key_file_handler_data_init_read(&handler_data, + NM_KEYFILE_HANDLER_TYPE_WARN, + info, + kf_key, + cur_property); + handler_data.warn = (NMKeyfileHandlerDataWarn){ + .severity = severity, + .message = NULL, + .fmt = fmt, + }; + + va_start(handler_data.warn.ap, fmt); + + info->read_handler(info->keyfile, + info->connection, + NM_KEYFILE_HANDLER_TYPE_WARN, + &handler_data, + info->user_data); + + va_end(handler_data.warn.ap); + + g_free(handler_data.warn.message); +} + +#define handle_warn(arg_info, arg_kf_key, arg_property_name, arg_severity, ...) \ + ({ \ + KeyfileReaderInfo *_info = (arg_info); \ + \ + nm_assert(!_info->error); \ + \ + if (_info->read_handler) { \ + _handle_warn(_info, (arg_kf_key), (arg_property_name), (arg_severity), __VA_ARGS__); \ + } \ + _info->error == NULL; \ + }) + +/*****************************************************************************/ + +static gboolean +_secret_flags_persist_secret(NMSettingSecretFlags flags) +{ + return flags == NM_SETTING_SECRET_FLAG_NONE; +} + +/*****************************************************************************/ +/* Some setting properties also contain setting names, such as + * NMSettingConnection's 'type' property (which specifies the base type of the + * connection, e.g. ethernet or wifi) or 'slave-type' (specifies type of slave + * connection, e.g. bond or bridge). This function handles translating those + * properties' values to the real setting name if they are an alias. + */ +static void +setting_alias_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + const char * key_setting_name; + gs_free char *s = NULL; + + s = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL); + if (!s) + return; + + key_setting_name = nm_keyfile_plugin_get_setting_name_for_alias(s); + g_object_set(G_OBJECT(setting), key, key_setting_name ?: s, NULL); +} + +static void +sriov_vfs_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + gs_unref_ptrarray GPtrArray *vfs = NULL; + gs_strfreev char ** keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref); + + for (i = 0; i < n_keys; i++) { + gs_free char *value = NULL; + NMSriovVF * vf; + const char * rest; + + if (!g_str_has_prefix(keys[i], "vf.")) + continue; + + rest = &keys[i][3]; + + if (!NM_STRCHAR_ALL(rest, ch, g_ascii_isdigit(ch))) + continue; + + value = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, keys[i], NULL); + + vf = _nm_utils_sriov_vf_from_strparts(rest, value, TRUE, NULL); + if (vf) + g_ptr_array_add(vfs, vf); + } + + g_object_set(G_OBJECT(setting), key, vfs, NULL); +} + +static void +read_array_of_uint(GKeyFile *file, NMSetting *setting, const char *key) +{ + gs_unref_array GArray *array = NULL; + gs_free_error GError *error = NULL; + gs_free guint *tmp = NULL; + gsize length; + + tmp = nm_keyfile_plugin_kf_get_integer_list_uint(file, + nm_setting_get_name(setting), + key, + &length, + &error); + if (error) + return; + + array = g_array_sized_new(FALSE, FALSE, sizeof(guint), length); + g_array_append_vals(array, tmp, length); + g_object_set(setting, key, array, NULL); +} + +static gboolean +get_one_int(KeyfileReaderInfo *info, + const char * kf_key, + const char * property_name, + const char * str, + guint32 max_val, + guint32 * out) +{ + gint64 tmp; + + nm_assert((!info) == (!property_name)); + nm_assert((!info) == (!kf_key)); + + if (!str || !str[0]) { + if (info) { + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring missing number")); + } + return FALSE; + } + + tmp = _nm_utils_ascii_str_to_int64(str, 10, 0, max_val, -1); + if (tmp == -1) { + if (info) { + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid number '%s'"), + str); + } + return FALSE; + } + + *out = (guint32) tmp; + return TRUE; +} + +static gpointer +build_address(KeyfileReaderInfo *info, + const char * kf_key, + const char * property_name, + int family, + const char * address_str, + guint32 plen) +{ + NMIPAddress *addr; + GError * error = NULL; + + g_return_val_if_fail(address_str, NULL); + + addr = nm_ip_address_new(family, address_str, plen, &error); + if (!addr) { + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid %s address: %s"), + family == AF_INET ? "IPv4" : "IPv6", + error->message); + g_error_free(error); + } + + return addr; +} + +static gpointer +build_route(KeyfileReaderInfo *info, + const char * kf_key, + const char * property_name, + int family, + const char * dest_str, + guint32 plen, + const char * gateway_str, + const char * metric_str) +{ + NMIPRoute *route; + guint32 u32; + gint64 metric = -1; + GError * error = NULL; + + g_return_val_if_fail(dest_str, NULL); + + /* Next hop */ + if (gateway_str && gateway_str[0]) { + if (!nm_utils_ipaddr_is_valid(family, gateway_str)) { + /* Try workaround for routes written by broken keyfile writer. + * Due to bug bgo#719851, an older version of writer would have + * written "a:b:c:d::/plen,metric" if the gateway was ::, instead + * of "a:b:c:d::/plen,,metric" or "a:b:c:d::/plen,::,metric" + * Try workaround by interpreting gateway_str as metric to accept such + * invalid routes. This broken syntax should not be not officially + * supported. + **/ + if (family == AF_INET6 && !metric_str + && get_one_int(NULL, NULL, NULL, gateway_str, G_MAXUINT32, &u32)) { + metric = u32; + gateway_str = NULL; + } else { + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid gateway '%s' for %s route"), + gateway_str, + family == AF_INET ? "IPv4" : "IPv6"); + return NULL; + } + } + } else + gateway_str = NULL; + + /* parse metric, default to -1 */ + if (metric_str) { + if (!get_one_int(info, kf_key, property_name, metric_str, G_MAXUINT32, &u32)) + return NULL; + metric = u32; + } + + route = nm_ip_route_new(family, dest_str, plen, gateway_str, metric, &error); + if (!route) { + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid %s route: %s"), + family == AF_INET ? "IPv4" : "IPv6", + error->message); + g_error_free(error); + } + + return route; +} + +/* On success, returns pointer to the zero-terminated field (original @current). + * The @current * pointer target is set to point to the rest of the input + * or %NULL if there is no more input. Sets error to %NULL for convenience. + * + * On failure, returns %NULL (unspecified). The @current pointer target is + * resets to its original value to allow skipping fields. The @error target + * is set to the character that breaks the parsing or %NULL if @current was %NULL. + * + * When @current target is %NULL, gracefully fail returning %NULL while + * leaving the @current target %NULL end setting @error to %NULL; + */ +static const char * +read_field(char **current, const char **out_err_str, const char *characters, const char *delimiters) +{ + const char *start; + + nm_assert(current); + nm_assert(out_err_str); + nm_assert(characters); + nm_assert(delimiters); + + *out_err_str = NULL; + + if (!*current) { + /* graceful failure, leave '*current' NULL */ + return NULL; + } + + /* fail on empty input */ + if (!**current) + return NULL; + + /* remember beginning of input */ + start = *current; + + while (**current && strchr(characters, **current)) + (*current)++; + if (**current) + if (strchr(delimiters, **current)) { + /* success, more data available */ + *(*current)++ = '\0'; + return start; + } else { + /* error, bad character */ + *out_err_str = *current; + *current = (char *) start; + return NULL; + } + else { + /* success, end of input */ + *current = NULL; + return start; + } +} + +/*****************************************************************************/ + +#define NM_DBUS_SERVICE_OPENCONNECT "org.freedesktop.NetworkManager.openconnect" +#define NM_OPENCONNECT_KEY_GATEWAY "gateway" +#define NM_OPENCONNECT_KEY_COOKIE "cookie" +#define NM_OPENCONNECT_KEY_GWCERT "gwcert" +#define NM_OPENCONNECT_KEY_XMLCONFIG "xmlconfig" +#define NM_OPENCONNECT_KEY_LASTHOST "lasthost" +#define NM_OPENCONNECT_KEY_AUTOCONNECT "autoconnect" +#define NM_OPENCONNECT_KEY_CERTSIGS "certsigs" + +static void +openconnect_fix_secret_flags(NMSetting *setting) +{ + NMSettingVpn * s_vpn; + NMSettingSecretFlags flags; + + /* Huge hack. There were some openconnect changes that needed to happen + * pretty late, too late to get into distros. Migration has already + * happened for many people, and their secret flags are wrong. But we + * don't want to require re-migration, so we have to fix it up here. Ugh. + */ + + if (!NM_IS_SETTING_VPN(setting)) + return; + + s_vpn = NM_SETTING_VPN(setting); + + if (!nm_streq0(nm_setting_vpn_get_service_type(s_vpn), NM_DBUS_SERVICE_OPENCONNECT)) + return; + + /* These are different for every login session, and should not be stored */ + flags = NM_SETTING_SECRET_FLAG_NOT_SAVED; + nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_GATEWAY, flags, NULL); + nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_COOKIE, flags, NULL); + nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_GWCERT, flags, NULL); + + /* These are purely internal data for the auth-dialog, and should be stored */ + flags = 0; + nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_XMLCONFIG, flags, NULL); + nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_LASTHOST, flags, NULL); + nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_AUTOCONNECT, flags, NULL); + nm_setting_set_secret_flags(NM_SETTING(s_vpn), NM_OPENCONNECT_KEY_CERTSIGS, flags, NULL); +} + +/*****************************************************************************/ + +#define IP_ADDRESS_CHARS "0123456789abcdefABCDEF:.%" +#define DIGITS "0123456789" +#define DELIMITERS "/;," + +/* The following IPv4 and IPv6 address formats are supported: + * + * address (DEPRECATED) + * address/plen + * address/gateway (DEPRECATED) + * address/plen,gateway + * + * The following IPv4 and IPv6 route formats are supported: + * + * address/plen (NETWORK dev DEVICE) + * address/plen,gateway (NETWORK via GATEWAY dev DEVICE) + * address/plen,,metric (NETWORK dev DEVICE metric METRIC) + * address/plen,gateway,metric (NETWORK via GATEWAY dev DEVICE metric METRIC) + * + * For backward, forward and sideward compatibility, slash (/), + * semicolon (;) and comma (,) are interchangeable. The choice of + * separator in the above examples is therefore not significant. + * + * Leaving out the prefix length is discouraged and DEPRECATED. The + * default value of IPv6 prefix length was 64 and has not been + * changed. The default for IPv4 is now 24, which is the closest + * IPv4 equivalent. These defaults may just as well be changed to + * match the iproute2 defaults (32 for IPv4 and 128 for IPv6). + */ +static gpointer +read_one_ip_address_or_route(KeyfileReaderInfo *info, + const char * property_name, + const char * setting_name, + const char * kf_key, + gboolean ipv6, + gboolean route, + char ** out_gateway, + NMSetting * setting) +{ + guint plen; + gpointer result; + const char * address_str; + const char * plen_str; + const char * gateway_str; + const char * metric_str; + const char * err_str = NULL; + char * current; + gs_free char *value = NULL; + gs_free char *value_orig = NULL; + +#define VALUE_ORIG() \ + (value_orig \ + ?: (value_orig = \ + nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, kf_key, NULL))) + + value = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, kf_key, NULL); + if (!value) + return NULL; + + current = value; + + /* get address field */ + address_str = read_field(¤t, &err_str, IP_ADDRESS_CHARS, DELIMITERS); + if (err_str) { + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' for address %s: '%s' (position %td)"), + *err_str, + kf_key, + VALUE_ORIG(), + err_str - current); + return NULL; + } + /* get prefix length field (skippable) */ + plen_str = read_field(¤t, &err_str, DIGITS, DELIMITERS); + /* get gateway field */ + gateway_str = read_field(¤t, &err_str, IP_ADDRESS_CHARS, DELIMITERS); + if (err_str) { + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' for %s: '%s' (position %td)"), + *err_str, + kf_key, + VALUE_ORIG(), + err_str - current); + return NULL; + } + /* for routes, get metric */ + if (route) { + metric_str = read_field(¤t, &err_str, DIGITS, DELIMITERS); + if (err_str) { + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("unexpected character '%c' in prefix length for %s: '%s' (position %td)"), + *err_str, + kf_key, + VALUE_ORIG(), + err_str - current); + return NULL; + } + } else + metric_str = NULL; + if (current) { + /* there is still some data */ + if (*current) { + /* another field follows */ + handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("garbage at the end of value %s: '%s'"), + kf_key, + VALUE_ORIG()); + return NULL; + } else { + /* semicolon at the end of input */ + if (!handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_INFO, + _("deprecated semicolon at the end of value %s: '%s'"), + kf_key, + VALUE_ORIG())) + return NULL; + } + } + +#define DEFAULT_PREFIX(for_route, for_ipv6) \ + ((for_route) ? ((for_ipv6) ? 128 : 24) : ((for_ipv6) ? 64 : 24)) + + /* parse plen, fallback to defaults */ + if (plen_str) { + if (!get_one_int(info, kf_key, property_name, plen_str, ipv6 ? 128 : 32, &plen)) { + plen = DEFAULT_PREFIX(route, ipv6); + if (info->error + || !handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid prefix length for %s '%s', defaulting to %d"), + kf_key, + VALUE_ORIG(), + plen)) + return NULL; + } + } else { + plen = DEFAULT_PREFIX(route, ipv6); + if (!handle_warn(info, + kf_key, + property_name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("missing prefix length for %s '%s', defaulting to %d"), + kf_key, + VALUE_ORIG(), + plen)) + return NULL; + } + + /* build the appropriate data structure for NetworkManager settings */ + if (route) { + result = build_route(info, + kf_key, + property_name, + ipv6 ? AF_INET6 : AF_INET, + address_str, + plen, + gateway_str, + metric_str); + } else { + result = build_address(info, + kf_key, + property_name, + ipv6 ? AF_INET6 : AF_INET, + address_str, + plen); + if (!result) + return NULL; + if (gateway_str) + NM_SET_OUT(out_gateway, g_strdup(gateway_str)); + } + +#undef VALUE_ORIG + + return result; +} + +static void +fill_route_attributes(GKeyFile * kf, + NMIPRoute * route, + const char *setting, + const char *key, + int family) +{ + gs_free char * value = NULL; + gs_unref_hashtable GHashTable *hash = NULL; + GHashTableIter iter; + char * name; + GVariant * variant; + + value = nm_keyfile_plugin_kf_get_string(kf, setting, key, NULL); + if (!value || !value[0]) + return; + + hash = nm_utils_parse_variant_attributes(value, + ',', + '=', + TRUE, + nm_ip_route_get_variant_attribute_spec(), + NULL); + if (hash) { + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) { + if (nm_ip_route_attribute_validate(name, variant, family, NULL, NULL)) + nm_ip_route_set_attribute(route, name, g_variant_ref(variant)); + } + } +} + +typedef struct { + const char *s_key; + gint32 key_idx; + gint8 key_type; +} BuildListData; + +typedef enum { + BUILD_LIST_TYPE_ADDRESSES, + BUILD_LIST_TYPE_ROUTES, + BUILD_LIST_TYPE_ROUTING_RULES, +} BuildListType; + +static int +_build_list_data_cmp(gconstpointer p_a, gconstpointer p_b, gpointer user_data) +{ + const BuildListData *a = p_a; + const BuildListData *b = p_b; + + NM_CMP_FIELD(a, b, key_idx); + NM_CMP_FIELD(a, b, key_type); + NM_CMP_FIELD_STR(a, b, s_key); + return 0; +} + +static gboolean +_build_list_data_is_shadowed(const BuildListData *build_list, gsize build_list_len, gsize idx) +{ + /* the keyfile contains duplicate keys, which are both returned + * by g_key_file_get_keys() (WHY??). + * + * Skip the earlier one. */ + return idx + 1 < build_list_len && build_list[idx].key_idx == build_list[idx + 1].key_idx + && build_list[idx].key_type == build_list[idx + 1].key_type + && nm_streq(build_list[idx].s_key, build_list[idx + 1].s_key); +} + +static gboolean +_build_list_match_key_w_name_impl(const char *key, + const char *base_name, + gsize base_name_l, + gint32 * out_key_idx) +{ + gint64 v; + + /* some very strict parsing. */ + + /* the key must start with base_name. */ + if (strncmp(key, base_name, base_name_l) != 0) + return FALSE; + + key += base_name_l; + if (key[0] == '\0') { + /* if key is identical to base_name, that's good. */ + NM_SET_OUT(out_key_idx, -1); + return TRUE; + } + + /* if base_name is followed by a zero, then it must be + * only a zero, nothing else. */ + if (key[0] == '0') { + if (key[1] != '\0') + return FALSE; + NM_SET_OUT(out_key_idx, 0); + return TRUE; + } + + /* otherwise, it can only be followed by a non-zero decimal. */ + if (!(key[0] >= '1' && key[0] <= '9')) + return FALSE; + /* and all remaining chars must be decimals too. */ + if (!NM_STRCHAR_ALL(&key[1], ch, g_ascii_isdigit(ch))) + return FALSE; + + /* and it must be convertible to a (positive) int. */ + v = _nm_utils_ascii_str_to_int64(key, 10, 0, G_MAXINT32, -1); + if (v < 0) + return FALSE; + + /* good */ + NM_SET_OUT(out_key_idx, v); + return TRUE; +} + +#define _build_list_match_key_w_name(key, base_name, out_key_idx) \ + _build_list_match_key_w_name_impl(key, base_name, NM_STRLEN(base_name), out_key_idx) + +static BuildListData * +_build_list_create(GKeyFile * keyfile, + const char * group_name, + BuildListType build_list_type, + gsize * out_build_list_len, + char *** out_keys_strv) +{ + gs_strfreev char **keys = NULL; + gsize i_keys, n_keys; + gs_free BuildListData *build_list = NULL; + gsize build_list_len = 0; + + nm_assert(out_build_list_len && *out_build_list_len == 0); + nm_assert(out_keys_strv && !*out_keys_strv); + + keys = nm_keyfile_plugin_kf_get_keys(keyfile, group_name, &n_keys, NULL); + if (n_keys == 0) + return NULL; + + for (i_keys = 0; i_keys < n_keys; i_keys++) { + const char *s_key = keys[i_keys]; + gint32 key_idx; + gint8 key_type = 0; + + switch (build_list_type) { + case BUILD_LIST_TYPE_ROUTES: + if (_build_list_match_key_w_name(s_key, "route", &key_idx)) + key_type = 0; + else if (_build_list_match_key_w_name(s_key, "routes", &key_idx)) + key_type = 1; + else + continue; + break; + case BUILD_LIST_TYPE_ADDRESSES: + if (_build_list_match_key_w_name(s_key, "address", &key_idx)) + key_type = 0; + else if (_build_list_match_key_w_name(s_key, "addresses", &key_idx)) + key_type = 1; + else + continue; + break; + case BUILD_LIST_TYPE_ROUTING_RULES: + if (_build_list_match_key_w_name(s_key, "routing-rule", &key_idx)) + key_type = 0; + else + continue; + break; + default: + nm_assert_not_reached(); + break; + } + + if (G_UNLIKELY(!build_list)) + build_list = g_new(BuildListData, n_keys - i_keys); + + build_list[build_list_len++] = (BuildListData){ + .s_key = s_key, + .key_idx = key_idx, + .key_type = key_type, + }; + } + + if (build_list_len == 0) + return NULL; + + if (build_list_len > 1) { + g_qsort_with_data(build_list, + build_list_len, + sizeof(BuildListData), + _build_list_data_cmp, + NULL); + } + + *out_build_list_len = build_list_len; + *out_keys_strv = g_steal_pointer(&keys); + return g_steal_pointer(&build_list); +} + +static void +ip_address_or_route_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key) +{ + const char * setting_name = nm_setting_get_name(setting); + gboolean is_ipv6 = nm_streq(setting_name, "ipv6"); + gboolean is_routes = nm_streq(setting_key, "routes"); + gs_free char * gateway = NULL; + gs_unref_ptrarray GPtrArray *list = NULL; + gs_strfreev char ** keys = NULL; + gs_free BuildListData *build_list = NULL; + gsize i_build_list, build_list_len = 0; + + build_list = _build_list_create(info->keyfile, + setting_name, + is_routes ? BUILD_LIST_TYPE_ROUTES : BUILD_LIST_TYPE_ADDRESSES, + &build_list_len, + &keys); + if (!build_list) + return; + + list = g_ptr_array_new_with_free_func(is_routes ? (GDestroyNotify) nm_ip_route_unref + : (GDestroyNotify) nm_ip_address_unref); + + for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { + const char *s_key; + gpointer item; + + if (_build_list_data_is_shadowed(build_list, build_list_len, i_build_list)) + continue; + + s_key = build_list[i_build_list].s_key; + item = read_one_ip_address_or_route(info, + setting_key, + setting_name, + s_key, + is_ipv6, + is_routes, + gateway ? NULL : &gateway, + setting); + if (item && is_routes) { + char options_key[128]; + + nm_sprintf_buf(options_key, "%s_options", s_key); + fill_route_attributes(info->keyfile, + item, + setting_name, + options_key, + is_ipv6 ? AF_INET6 : AF_INET); + } + + if (info->error) + return; + + if (item) + g_ptr_array_add(list, item); + } + + if (list->len >= 1) + g_object_set(setting, setting_key, list, NULL); + + if (gateway) + g_object_set(setting, "gateway", gateway, NULL); +} + +static void +ip_routing_rule_parser_full(KeyfileReaderInfo * info, + const NMMetaSettingInfo * setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty * pip, + NMSetting * setting) +{ + const char * setting_name = nm_setting_get_name(setting); + gboolean is_ipv6 = nm_streq(setting_name, "ipv6"); + gs_strfreev char **keys = NULL; + gs_free BuildListData *build_list = NULL; + gsize i_build_list, build_list_len = 0; + + build_list = _build_list_create(info->keyfile, + setting_name, + BUILD_LIST_TYPE_ROUTING_RULES, + &build_list_len, + &keys); + if (!build_list) + return; + + for (i_build_list = 0; i_build_list < build_list_len; i_build_list++) { + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + gs_free char * value = NULL; + gs_free_error GError *local = NULL; + + if (_build_list_data_is_shadowed(build_list, build_list_len, i_build_list)) + continue; + + value = nm_keyfile_plugin_kf_get_string(info->keyfile, + setting_name, + build_list[i_build_list].s_key, + NULL); + if (!value) + continue; + + rule = nm_ip_routing_rule_from_string( + value, + (NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (is_ipv6 ? NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6 + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)), + NULL, + &local); + if (!rule) { + if (!handle_warn(info, + build_list[i_build_list].s_key, + property_info->name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid value for \"%s\": %s"), + build_list[i_build_list].s_key, + local->message)) + return; + continue; + } + + nm_setting_ip_config_add_routing_rule(NM_SETTING_IP_CONFIG(setting), rule); + } +} + +static void +_parser_full_ovs_external_ids_data(KeyfileReaderInfo * info, + const NMMetaSettingInfo * setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty * pip, + NMSetting * setting) +{ + const char * setting_name = NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME; + gs_strfreev char **keys = NULL; + gsize n_keys; + gsize i; + + nm_assert(NM_IS_SETTING_OVS_EXTERNAL_IDS(setting)); + nm_assert(nm_streq(property_info->name, NM_SETTING_OVS_EXTERNAL_IDS_DATA)); + nm_assert(nm_streq(setting_name, setting_info->setting_name)); + nm_assert(nm_streq(setting_name, nm_setting_get_name(setting))); + + keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, setting_name, &n_keys, NULL); + + for (i = 0; i < n_keys; i++) { + const char * key = keys[i]; + gs_free char *name_to_free = NULL; + gs_free char *value = NULL; + const char * name; + + if (!NM_STR_HAS_PREFIX(key, OVS_EXTERNAL_IDS_DATA_PREFIX)) + continue; + + value = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL); + if (!value) + continue; + + name = &key[NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)]; + name = nm_keyfile_key_decode(name, &name_to_free); + nm_setting_ovs_external_ids_set_data(NM_SETTING_OVS_EXTERNAL_IDS(setting), name, value); + } +} + +static void +ip_dns_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + int addr_family; + gs_strfreev char **list = NULL; + gsize i, n, length; + + nm_assert(NM_IS_SETTING_IP4_CONFIG(setting) || NM_IS_SETTING_IP6_CONFIG(setting)); + + list = nm_keyfile_plugin_kf_get_string_list(info->keyfile, + nm_setting_get_name(setting), + key, + &length, + NULL); + nm_assert(length == NM_PTRARRAY_LEN(list)); + if (length == 0) + return; + + addr_family = NM_IS_SETTING_IP4_CONFIG(setting) ? AF_INET : AF_INET6; + + n = 0; + for (i = 0; i < length; i++) { + NMIPAddr addr; + + if (inet_pton(addr_family, list[i], &addr) <= 0) { + if (!handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid DNS server IPv%c address '%s'"), + nm_utils_addr_family_to_char(addr_family), + list[i])) { + do { + nm_clear_g_free(&list[i]); + } while (++i < length); + return; + } + nm_clear_g_free(&list[i]); + continue; + } + + if (n != i) + list[n] = g_steal_pointer(&list[i]); + n++; + } + + g_object_set(setting, key, list, NULL); +} + +static void +ip6_addr_gen_mode_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + const char * setting_name = nm_setting_get_name(setting); + gs_free char * s = NULL; + + s = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL); + if (s) { + if (!nm_utils_enum_from_str(nm_setting_ip6_config_addr_gen_mode_get_type(), + s, + (int *) &addr_gen_mode, + NULL)) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid option '%s', use one of [%s]"), + s, + "eui64,stable-privacy"); + return; + } + } else + addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64; + + g_object_set(G_OBJECT(setting), key, (int) addr_gen_mode, NULL); +} + +static void +mac_address_parser(KeyfileReaderInfo *info, + NMSetting * setting, + const char * key, + gsize addr_len, + gboolean cloned_mac_addr) +{ + const char * setting_name = nm_setting_get_name(setting); + char addr_str[NM_UTILS_HWADDR_LEN_MAX * 3]; + guint8 addr_bin[NM_UTILS_HWADDR_LEN_MAX]; + gs_free char *tmp_string = NULL; + gs_free guint *int_list = NULL; + const char * mac_str; + gsize int_list_len; + gsize i; + + nm_assert(addr_len > 0); + nm_assert(addr_len <= NM_UTILS_HWADDR_LEN_MAX); + + tmp_string = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL); + + if (cloned_mac_addr && NM_CLONED_MAC_IS_SPECIAL(tmp_string)) { + mac_str = tmp_string; + goto out; + } + + if (tmp_string && nm_utils_hwaddr_aton(tmp_string, addr_bin, addr_len)) + goto good_addr_bin; + + /* Old format; list of ints */ + int_list = nm_keyfile_plugin_kf_get_integer_list_uint(info->keyfile, + setting_name, + key, + &int_list_len, + NULL); + if (int_list_len == addr_len) { + for (i = 0; i < addr_len; i++) { + const guint val = int_list[i]; + + if (val > 255) + break; + addr_bin[i] = (guint8) val; + } + if (i == addr_len) + goto good_addr_bin; + } + + handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid MAC address")); + return; + +good_addr_bin: + nm_utils_bin2hexstr_full(addr_bin, addr_len, ':', TRUE, addr_str); + mac_str = addr_str; + +out: + g_object_set(setting, key, mac_str, NULL); +} + +static void +mac_address_parser_ETHER(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser(info, setting, key, ETH_ALEN, FALSE); +} + +static void +mac_address_parser_ETHER_cloned(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser(info, setting, key, ETH_ALEN, TRUE); +} + +static void +mac_address_parser_INFINIBAND(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + mac_address_parser(info, setting, key, INFINIBAND_ALEN, FALSE); +} + +static void +read_hash_of_string(KeyfileReaderInfo *info, + GKeyFile * file, + NMSetting * setting, + const char * kf_group) +{ + gs_strfreev char **keys = NULL; + const char *const *iter; + const char * setting_name = nm_setting_get_name(setting); + gboolean is_vpn; + gsize n_keys; + + nm_assert((NM_IS_SETTING_VPN(setting) && nm_streq(kf_group, NM_SETTING_VPN_DATA)) + || (NM_IS_SETTING_VPN(setting) && nm_streq(kf_group, NM_SETTING_VPN_SECRETS)) + || (NM_IS_SETTING_BOND(setting) && nm_streq(kf_group, NM_SETTING_BOND_OPTIONS)) + || (NM_IS_SETTING_USER(setting) && nm_streq(kf_group, NM_SETTING_USER_DATA))); + + keys = nm_keyfile_plugin_kf_get_keys(file, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + if ((is_vpn = NM_IS_SETTING_VPN(setting)) || NM_IS_SETTING_BOND(setting)) { + for (iter = (const char *const *) keys; *iter; iter++) { + const char * kf_key = *iter; + gs_free char *to_free = NULL; + gs_free char *value = NULL; + const char * name; + + value = nm_keyfile_plugin_kf_get_string(file, setting_name, kf_key, NULL); + if (!value) + continue; + + name = nm_keyfile_key_decode(kf_key, &to_free); + + if (is_vpn) { + /* Add any item that's not a class property to the data hash */ + if (!g_object_class_find_property(G_OBJECT_GET_CLASS(setting), name)) + nm_setting_vpn_add_data_item(NM_SETTING_VPN(setting), name, value); + } else { + if (!nm_streq(name, "interface-name")) { + gs_free_error GError *error = NULL; + + if (!_nm_setting_bond_validate_option(name, value, &error)) { + if (!handle_warn(info, + kf_key, + name, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid bond option %s%s%s = %s%s%s: %s"), + NM_PRINT_FMT_QUOTE_STRING(name), + NM_PRINT_FMT_QUOTE_STRING(value), + error->message)) + return; + } else + nm_setting_bond_add_option(NM_SETTING_BOND(setting), name, value); + } + } + } + openconnect_fix_secret_flags(setting); + return; + } + + if (NM_IS_SETTING_USER(setting)) { + gs_unref_hashtable GHashTable *data = NULL; + + data = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + for (iter = (const char *const *) keys; *iter; iter++) { + gs_free char *to_free = NULL; + char * value = NULL; + const char * name; + + value = nm_keyfile_plugin_kf_get_string(file, setting_name, *iter, NULL); + if (!value) + continue; + name = nm_keyfile_key_decode(*iter, &to_free); + g_hash_table_insert(data, g_steal_pointer(&to_free) ?: g_strdup(name), value); + } + g_object_set(setting, NM_SETTING_USER_DATA, data, NULL); + return; + } + + nm_assert_not_reached(); +} + +static gsize +unescape_semicolons(char *str) +{ + gsize i, j; + + for (i = 0, j = 0; str[i];) { + if (str[i] == '\\' && str[i + 1] == ';') + i++; + str[j++] = str[i++]; + } + nm_explicit_bzero(&str[j], i - j); + return j; +} + +static GBytes * +get_bytes(KeyfileReaderInfo *info, + const char * setting_name, + const char * key, + gboolean zero_terminate, + gboolean unescape_semicolon) +{ + nm_auto_free_secret char *tmp_string = NULL; + gboolean may_be_int_list = TRUE; + gsize length; + GBytes * result; + + /* New format: just a string + * Old format: integer list; e.g. 11;25;38; + */ + tmp_string = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL); + if (!tmp_string) + return NULL; + + /* if the string is empty, we return an empty GBytes array. + * Note that for NM_SETTING_802_1X_PASSWORD_RAW both %NULL and + * an empty GBytes are valid, and shall be destinguished. */ + if (!tmp_string[0]) { + /* note that even if @zero_terminate is TRUE, we return an empty + * byte-array. The reason is that zero_terminate is there to terminate + * *valid* strings. It's not there to terminated invalid (empty) strings. + */ + return g_bytes_new_static("", 0); + } + + for (length = 0; tmp_string[length]; length++) { + const char ch = tmp_string[length]; + + if (!g_ascii_isspace(ch) && !g_ascii_isdigit(ch) && ch != ';') { + may_be_int_list = FALSE; + length += strlen(&tmp_string[length]); + break; + } + } + + /* Try to parse the string as a integer list. */ + if (may_be_int_list && length > 0) { + nm_auto_free_secret_buf NMSecretBuf *bin = NULL; + const char *const s = tmp_string; + gsize i, d; + + bin = nm_secret_buf_new(length / 2 + 3); + +#define DIGIT(c) ((c) - '0') + i = 0; + d = 0; + while (TRUE) { + int n; + + /* leading whitespace */ + while (g_ascii_isspace(s[i])) + i++; + if (s[i] == '\0') + break; + /* then expect 1 to 3 digits */ + if (!g_ascii_isdigit(s[i])) { + d = 0; + break; + } + n = DIGIT(s[i]); + i++; + if (g_ascii_isdigit(s[i])) { + n = 10 * n + DIGIT(s[i]); + i++; + if (g_ascii_isdigit(s[i])) { + n = 10 * n + DIGIT(s[i]); + i++; + } + } + if (n > 255) { + d = 0; + break; + } + + nm_assert(d < bin->len); + bin->bin[d++] = n; + + /* allow whitespace after the digit. */ + while (g_ascii_isspace(s[i])) + i++; + /* need a semicolon as separator. */ + if (s[i] != ';') { + d = 0; + break; + } + i++; + } +#undef DIGIT + + /* Old format; list of ints. We already did a strict validation of the + * string format before. We expect that this conversion cannot fail. */ + if (d > 0) { + /* note that @zero_terminate does not add a terminating '\0' to + * binary data as an integer list. If the bytes are expressed as + * an integer list, all potential NUL characters are supposed to + * be included there explicitly. + * + * However, in the spirit of defensive programming, we do append a + * NUL character to the buffer, although this character is hidden + * and only a mitigation for bugs. */ + + if (d + 10 < bin->len) { + /* hm, too much unused memory. Copy the memory to a suitable + * sized buffer. */ + return nm_secret_copy_to_gbytes(bin->bin, d); + } + + nm_assert(d < bin->len); + bin->bin[d] = '\0'; + return nm_secret_buf_to_gbytes_take(g_steal_pointer(&bin), d); + } + } + + /* Handle as a simple string (ie, new format) */ + if (unescape_semicolon) + length = unescape_semicolons(tmp_string); + if (zero_terminate) + length++; + if (length == 0) + return NULL; + + result = + g_bytes_new_with_free_func(tmp_string, length, (GDestroyNotify) nm_free_secret, tmp_string); + tmp_string = NULL; + return result; +} + +static void +ssid_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + gs_unref_bytes GBytes *bytes = NULL; + + bytes = get_bytes(info, setting_name, key, FALSE, TRUE); + if (!bytes) { + handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("ignoring invalid SSID")); + return; + } + g_object_set(setting, key, bytes, NULL); +} + +static void +password_raw_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + gs_unref_bytes GBytes *bytes = NULL; + + bytes = get_bytes(info, setting_name, key, FALSE, TRUE); + if (!bytes) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid raw password")); + return; + } + g_object_set(setting, key, bytes, NULL); +} + +static char * +get_cert_path(const char *base_dir, const guint8 *cert_path, gsize cert_path_len) +{ + const char *base; + char * p = NULL, *path, *tmp; + + g_return_val_if_fail(base_dir != NULL, NULL); + g_return_val_if_fail(cert_path != NULL, NULL); + + path = g_strndup((char *) cert_path, cert_path_len); + + if (path[0] == '/') + return path; + + base = path; + p = strrchr(path, '/'); + if (p) + base = p + 1; + + tmp = g_build_path("/", base_dir, base, NULL); + g_free(path); + return tmp; +} + +static const char *certext[] = {".pem", ".cert", ".crt", ".cer", ".p12", ".der", ".key"}; + +static gboolean +has_cert_ext(const char *path) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS(certext); i++) { + if (g_str_has_suffix(path, certext[i])) + return TRUE; + } + return FALSE; +} + +char * +nm_keyfile_detect_unqualified_path_scheme(const char * base_dir, + gconstpointer pdata, + gsize data_len, + gboolean consider_exists, + gboolean * out_exists) +{ + const char * data = pdata; + gboolean exists = FALSE; + gsize validate_len; + gsize path_len, pathuri_len; + gs_free char *path = NULL; + gs_free char *pathuri = NULL; + + g_return_val_if_fail(base_dir && base_dir[0] == '/', NULL); + + if (!pdata) + return NULL; + if (data_len == -1) + data_len = strlen(data); + if (data_len > 500 || data_len < 1) + return NULL; + + /* If there's a trailing zero tell g_utf8_validate() to validate until the zero */ + if (data[data_len - 1] == '\0') { + /* setting it to -1, would mean we accept data to contain NUL characters before the + * end. Don't accept any NUL in [0 .. data_len-1[ . */ + validate_len = data_len - 1; + } else + validate_len = data_len; + if (validate_len == 0 || g_utf8_validate((const char *) data, validate_len, NULL) == FALSE) + return NULL; + + /* Might be a bare path without the file:// prefix; in that case + * if it's an absolute path, use that, otherwise treat it as a + * relative path to the current directory. + */ + + path = get_cert_path(base_dir, (const guint8 *) data, data_len); + + /* FIXME(keyfile-parse-in-memory): it is wrong that keyfile reader makes decisions based on + * the file systems content. The serialization/parsing should be entirely in-memory. */ + if (!memchr(data, '/', data_len) && !has_cert_ext(path)) { + if (!consider_exists) + return NULL; + exists = g_file_test(path, G_FILE_TEST_EXISTS); + if (!exists) + return NULL; + } else if (out_exists) + exists = g_file_test(path, G_FILE_TEST_EXISTS); + + /* Construct the proper value as required for the PATH scheme. + * + * When returning TRUE, we must also be sure that @data_len does not look like + * the deprecated format of list of integers. With this implementation that is the + * case, as long as @consider_exists is FALSE. */ + path_len = strlen(path); + pathuri_len = (NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) + 1) + path_len; + pathuri = g_new(char, pathuri_len); + memcpy(pathuri, + NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, + NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)); + memcpy(&pathuri[NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)], path, path_len + 1); + if (nm_setting_802_1x_check_cert_scheme(pathuri, pathuri_len, NULL) + != NM_SETTING_802_1X_CK_SCHEME_PATH) + return NULL; + + NM_SET_OUT(out_exists, exists); + return g_steal_pointer(&pathuri); +} + +#define HAS_SCHEME_PREFIX(bin, bin_len, scheme) \ + ({ \ + const char *const _bin = (bin); \ + const gsize _bin_len = (bin_len); \ + \ + nm_assert(_bin &&_bin_len > 0); \ + \ + (_bin_len > NM_STRLEN(scheme) + 1 && _bin[_bin_len - 1] == '\0' \ + && memcmp(_bin, scheme, NM_STRLEN(scheme)) == 0); \ + }) + +static void +cert_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + gs_unref_bytes GBytes *bytes = NULL; + const char * bin = NULL; + gsize bin_len = 0; + char * path; + gboolean path_exists; + + bytes = get_bytes(info, setting_name, key, TRUE, FALSE); + if (bytes) + bin = g_bytes_get_data(bytes, &bin_len); + if (bin_len == 0) { + if (!info->error) { + handle_warn(info, key, key, NM_KEYFILE_WARN_SEVERITY_WARN, _("invalid key/cert value")); + } + return; + } + + if (HAS_SCHEME_PREFIX(bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)) { + const char * path2 = &bin[NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH)]; + gs_free char *path2_free = NULL; + + if (nm_setting_802_1x_check_cert_scheme(bin, bin_len, NULL) + != NM_SETTING_802_1X_CK_SCHEME_PATH) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value path \"%s\""), + bin); + return; + } + + g_object_set(setting, key, bytes, NULL); + + if (path2[0] != '/') { + /* we want to read absolute paths because we use keyfile as exchange + * between different processes which might not have the same cwd. */ + path2_free = get_cert_path(info->base_dir, + (const guint8 *) path2, + bin_len - NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH) - 1); + path2 = path2_free; + } + + /* FIXME(keyfile-parse-in-memory): keyfile reader must not access the file system and + * (in a first step) only operate in memory-only. If the presence of files should be checked, + * then by invoking a callback (and possibly keyfile settings plugin would + * collect the file names to be checked and check them later). */ + if (!g_file_test(path2, G_FILE_TEST_EXISTS)) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, + _("certificate or key file '%s' does not exist"), + path2); + } + return; + } + + if (HAS_SCHEME_PREFIX(bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_PKCS11)) { + if (nm_setting_802_1x_check_cert_scheme(bin, bin_len, NULL) + != NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid PKCS#11 URI \"%s\""), + bin); + return; + } + + g_object_set(setting, key, bytes, NULL); + return; + } + + if (HAS_SCHEME_PREFIX(bin, bin_len, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)) { + const char *cdata = bin + NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB); + gsize cdata_len = bin_len - NM_STRLEN(NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB) - 1; + gs_free guchar *bin_decoded = NULL; + gsize bin_decoded_len = 0; + gsize i; + gboolean valid_base64; + gs_unref_bytes GBytes *val = NULL; + + /* Let's be strict here. We expect valid base64, no funny stuff!! + * We didn't write such invalid data ourselfes and refuse to read it as blob. */ + if ((valid_base64 = (cdata_len % 4 == 0))) { + for (i = 0; i < cdata_len; i++) { + char c = cdata[i]; + + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || (c == '+' || c == '/'))) { + if (c != '=' || i < cdata_len - 2) + valid_base64 = FALSE; + else { + for (; i < cdata_len; i++) { + if (cdata[i] != '=') + valid_base64 = FALSE; + } + } + break; + } + } + } + if (valid_base64) + bin_decoded = g_base64_decode(cdata, &bin_decoded_len); + + if (bin_decoded_len == 0) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value data:;base64, is not base64")); + return; + } + + if (nm_setting_802_1x_check_cert_scheme(bin_decoded, bin_decoded_len, NULL) + != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* The blob probably starts with "file://". Setting the cert data will confuse NMSetting8021x. + * In fact this is a limitation of NMSetting8021x which does not support setting blobs that start + * with file://. Just warn and return TRUE to signal that we ~handled~ the setting. */ + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value data:;base64,file://")); + return; + } + + val = g_bytes_new_take(g_steal_pointer(&bin_decoded), bin_decoded_len); + g_object_set(setting, key, val, NULL); + return; + } + + /* If not, it might be a plain path */ + path = + nm_keyfile_detect_unqualified_path_scheme(info->base_dir, bin, bin_len, TRUE, &path_exists); + if (path) { + gs_unref_bytes GBytes *val = NULL; + + /* Construct the proper value as required for the PATH scheme */ + val = g_bytes_new_take(path, strlen(path) + 1); + g_object_set(setting, key, val, NULL); + + /* Warn if the certificate didn't exist */ + if (!path_exists) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_INFO_MISSING_FILE, + _("certificate or key file '%s' does not exist"), + path); + } + return; + } + + if (nm_setting_802_1x_check_cert_scheme(bin, bin_len, NULL) + != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* The blob probably starts with "file://" but contains invalid characters for a path. + * Setting the cert data will confuse NMSetting8021x. + * In fact, NMSetting8021x does not support setting such binary data, so just warn and + * continue. */ + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key/cert value is not a valid blob")); + return; + } + + g_object_set(setting, key, bytes, NULL); +} + +static int +_parity_from_char(int ch) +{ +#if NM_MORE_ASSERTS > 5 + { + static char check = 0; + + if (check == 0) { + nm_auto_unref_gtypeclass GEnumClass *klass = + g_type_class_ref(NM_TYPE_SETTING_SERIAL_PARITY); + guint i; + + check = 1; + + /* In older versions, parity was G_TYPE_CHAR/gint8, and the character + * value was stored as integer. + * For example parity=69 equals parity=E, meaning NM_SETTING_SERIAL_PARITY_EVEN. + * + * That means, certain values are reserved. Assert that these numbers + * are not reused when we extend NMSettingSerialParity enum. + * Actually, since NM_SETTING_SERIAL_PARITY is g_param_spec_enum(), + * we anyway cannot extend the enum without breaking API... + * + * [1] commit "a91e60902e libnm-core: make NMSettingSerial:parity an enum" + * [2] https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=a91e60902eabae1de93d61323dae6ac894b5d40f + */ + g_assert(G_IS_ENUM_CLASS(klass)); + for (i = 0; i < klass->n_values; i++) { + const GEnumValue *v = &klass->values[i]; + int num = v->value; + + g_assert(_parity_from_char(num) == -1); + g_assert(!NM_IN_SET(num, 'e', 'E', 'o', 'O', 'n', 'N')); + } + } + } +#endif + + switch (ch) { + case 'E': + case 'e': + return NM_SETTING_SERIAL_PARITY_EVEN; + case 'O': + case 'o': + return NM_SETTING_SERIAL_PARITY_ODD; + case 'N': + case 'n': + return NM_SETTING_SERIAL_PARITY_NONE; + } + + return -1; +} + +static void +parity_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + gs_free_error GError *err = NULL; + int parity; + gs_free char * tmp_str = NULL; + gint64 i64; + + /* Keyfile traditionally stored this as the ASCII value for 'E', 'o', or 'n'. + * We now accept either that or the (case-insensitive) character itself (but + * still always write it the old way, for backward compatibility). + */ + tmp_str = nm_keyfile_plugin_kf_get_value(info->keyfile, setting_name, key, &err); + if (err) + goto out_err; + + if (tmp_str && tmp_str[0] != '\0' && tmp_str[1] == '\0') { + /* the ASCII characters like 'E' are taken directly... */ + parity = _parity_from_char(tmp_str[0]); + if (parity >= 0) + goto parity_good; + } + + i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); + if (i64 != G_MININT64 && errno == 0) { + if ((parity = _parity_from_char(i64)) >= 0) { + /* another oddity: the string is a valid number. However, if the numeric values + * is one of the supported ASCII codes, accept it (like 69 for 'E'). + */ + goto parity_good; + } + + /* Finally, take the numeric value as is. */ + parity = i64; + goto parity_good; + } + + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid parity value '%s'"), + tmp_str ?: ""); + return; + +parity_good: + nm_g_object_set_property_enum(G_OBJECT(setting), + key, + NM_TYPE_SETTING_SERIAL_PARITY, + parity, + &err); + +out_err: + if (!err) + return; + if (nm_keyfile_error_is_not_found(err)) { + /* ignore such errors. The key is not present. */ + return; + } + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting: %s"), + err->message); +} + +static void +team_config_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + gs_free char *conf = NULL; + gs_free_error GError *error = NULL; + + conf = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL); + + g_object_set(G_OBJECT(setting), key, conf, NULL); + + if (conf && !nm_setting_verify(setting, NULL, &error)) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid team configuration: %s"), + error->message); + g_object_set(G_OBJECT(setting), key, NULL, NULL); + } +} + +static void +bridge_vlan_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + gs_free char * value = NULL; + gs_free const char ** strv = NULL; + const char *const * iter; + GError * local = NULL; + NMBridgeVlan * vlan; + + value = nm_keyfile_plugin_kf_get_string(info->keyfile, nm_setting_get_name(setting), key, NULL); + if (!value || !value[0]) + return; + + vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref); + + strv = nm_utils_escaped_tokens_split(value, ","); + if (strv) { + for (iter = strv; *iter; iter++) { + vlan = nm_bridge_vlan_from_str(*iter, &local); + if (!vlan) { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + "invalid bridge VLAN: %s", + local->message); + g_clear_error(&local); + continue; + } + g_ptr_array_add(vlans, vlan); + } + } + + if (vlans->len > 0) + g_object_set(setting, key, vlans, NULL); +} + +static void +qdisc_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + gs_unref_ptrarray GPtrArray *qdiscs = NULL; + gs_strfreev char ** keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + qdiscs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_qdisc_unref); + + for (i = 0; i < n_keys; i++) { + NMTCQdisc * qdisc; + const char * qdisc_parent; + gs_free char *qdisc_rest = NULL; + gs_free char *qdisc_str = NULL; + gs_free_error GError *err = NULL; + + if (!g_str_has_prefix(keys[i], "qdisc.")) + continue; + + qdisc_parent = keys[i] + sizeof("qdisc.") - 1; + qdisc_rest = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, keys[i], NULL); + qdisc_str = g_strdup_printf( + "%s%s %s", + _nm_utils_parse_tc_handle(qdisc_parent, NULL) != TC_H_UNSPEC ? "parent " : "", + qdisc_parent, + qdisc_rest); + + qdisc = nm_utils_tc_qdisc_from_str(qdisc_str, &err); + if (!qdisc) { + handle_warn(info, + keys[i], + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid qdisc: %s"), + err->message); + } else { + g_ptr_array_add(qdiscs, qdisc); + } + } + + if (qdiscs->len >= 1) + g_object_set(setting, key, qdiscs, NULL); +} + +static void +tfilter_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key) +{ + const char * setting_name = nm_setting_get_name(setting); + gs_unref_ptrarray GPtrArray *tfilters = NULL; + gs_strfreev char ** keys = NULL; + gsize n_keys = 0; + int i; + + keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, setting_name, &n_keys, NULL); + if (n_keys == 0) + return; + + tfilters = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_tfilter_unref); + + for (i = 0; i < n_keys; i++) { + NMTCTfilter * tfilter; + const char * tfilter_parent; + gs_free char *tfilter_rest = NULL; + gs_free char *tfilter_str = NULL; + gs_free_error GError *err = NULL; + + if (!g_str_has_prefix(keys[i], "tfilter.")) + continue; + + tfilter_parent = keys[i] + sizeof("tfilter.") - 1; + tfilter_rest = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, keys[i], NULL); + tfilter_str = g_strdup_printf( + "%s%s %s", + _nm_utils_parse_tc_handle(tfilter_parent, NULL) != TC_H_UNSPEC ? "parent " : "", + tfilter_parent, + tfilter_rest); + + tfilter = nm_utils_tc_tfilter_from_str(tfilter_str, &err); + if (!tfilter) { + handle_warn(info, + keys[i], + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid tfilter: %s"), + err->message); + } else { + g_ptr_array_add(tfilters, tfilter); + } + } + + if (tfilters->len >= 1) + g_object_set(setting, key, tfilters, NULL); +} + +/*****************************************************************************/ + +/* Some setting properties also contain setting names, such as + * NMSettingConnection's 'type' property (which specifies the base type of the + * connection, eg ethernet or wifi) or the 802-11-wireless setting's + * 'security' property which specifies whether or not the AP requires + * encryption. This function handles translating those properties' values + * from the real setting name to the more-readable alias. + */ +static void +setting_alias_writer(KeyfileWriterInfo *info, + NMSetting * setting, + const char * key, + const GValue * value) +{ + const char *str, *alias; + + str = g_value_get_string(value); + alias = nm_keyfile_plugin_get_alias_for_setting_name(str); + nm_keyfile_plugin_kf_set_string(info->keyfile, nm_setting_get_name(setting), key, alias ?: str); +} + +static void +sriov_vfs_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) +{ + GPtrArray *vfs; + guint i; + + vfs = g_value_get_boxed(value); + if (!vfs) + return; + + for (i = 0; i < vfs->len; i++) { + const NMSriovVF *vf = vfs->pdata[i]; + gs_free char * kf_value = NULL; + char kf_key[32]; + + kf_value = nm_utils_sriov_vf_to_str(vf, TRUE, NULL); + if (!kf_value) + continue; + + nm_sprintf_buf(kf_key, "vf.%u", nm_sriov_vf_get_index(vf)); + + nm_keyfile_plugin_kf_set_string(info->keyfile, + nm_setting_get_name(setting), + kf_key, + kf_value); + } +} + +static void +write_array_of_uint(GKeyFile *file, NMSetting *setting, const char *key, const GValue *value) +{ + GArray *array; + + array = g_value_get_boxed(value); + + nm_assert(!array || g_array_get_element_size(array) == sizeof(guint)); + + if (!array || !array->len) + return; + + nm_keyfile_plugin_kf_set_integer_list_uint(file, + nm_setting_get_name(setting), + key, + (const guint *) array->data, + array->len); +} + +static void +dns_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) +{ + char **list; + + list = g_value_get_boxed(value); + if (list && list[0]) { + nm_keyfile_plugin_kf_set_string_list(info->keyfile, + nm_setting_get_name(setting), + key, + (const char **) list, + g_strv_length(list)); + } +} + +static void +ip6_addr_gen_mode_writer(KeyfileWriterInfo *info, + NMSetting * setting, + const char * key, + const GValue * value) +{ + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + gs_free char * str = NULL; + + addr_gen_mode = (NMSettingIP6ConfigAddrGenMode) g_value_get_int(value); + str = nm_utils_enum_to_str(nm_setting_ip6_config_addr_gen_mode_get_type(), addr_gen_mode); + nm_keyfile_plugin_kf_set_string(info->keyfile, nm_setting_get_name(setting), key, str); +} + +static void +write_ip_values(GKeyFile * file, + const char *setting_name, + GPtrArray * array, + const char *gateway, + gboolean is_route) +{ + if (array->len > 0) { + nm_auto_str_buf NMStrBuf output = NM_STR_BUF_INIT(2 * INET_ADDRSTRLEN + 10, FALSE); + int addr_family; + guint i; + const char * addr; + const char * gw; + guint32 plen; + char key_name[64]; + char * key_name_idx; + + addr_family = + nm_streq(setting_name, NM_SETTING_IP4_CONFIG_SETTING_NAME) ? AF_INET : AF_INET6; + + strcpy(key_name, is_route ? "route" : "address"); + key_name_idx = key_name + strlen(key_name); + + for (i = 0; i < array->len; i++) { + gint64 metric = -1; + + if (is_route) { + NMIPRoute *route = array->pdata[i]; + + addr = nm_ip_route_get_dest(route); + plen = nm_ip_route_get_prefix(route); + gw = nm_ip_route_get_next_hop(route); + metric = nm_ip_route_get_metric(route); + } else { + NMIPAddress *address = array->pdata[i]; + + addr = nm_ip_address_get_address(address); + plen = nm_ip_address_get_prefix(address); + gw = (i == 0) ? gateway : NULL; + } + + nm_str_buf_set_size(&output, 0, FALSE, FALSE); + nm_str_buf_append_printf(&output, "%s/%u", addr, plen); + if (metric != -1 || gw) { + /* Older versions of the plugin do not support the form + * "a.b.c.d/plen,,metric", so, we always have to write the + * gateway, even if there isn't one. + * The current version supports reading of the above form. + */ + if (!gw) { + if (addr_family == AF_INET) + gw = "0.0.0.0"; + else + gw = "::"; + } + + nm_str_buf_append_c(&output, ','); + nm_str_buf_append(&output, gw); + if (is_route && metric != -1) + nm_str_buf_append_printf(&output, ",%lu", (unsigned long) metric); + } + + sprintf(key_name_idx, "%u", i + 1); + nm_keyfile_plugin_kf_set_string(file, + setting_name, + key_name, + nm_str_buf_get_str(&output)); + + if (is_route) { + gs_free char *attributes = NULL; + + attributes = + nm_utils_format_variant_attributes(_nm_ip_route_get_attributes(array->pdata[i]), + ',', + '='); + if (attributes) { + g_strlcat(key_name, "_options", sizeof(key_name)); + nm_keyfile_plugin_kf_set_string(file, setting_name, key_name, attributes); + } + } + } + } +} + +static void +addr_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) +{ + GPtrArray * array; + const char *setting_name = nm_setting_get_name(setting); + const char *gateway = nm_setting_ip_config_get_gateway(NM_SETTING_IP_CONFIG(setting)); + + array = (GPtrArray *) g_value_get_boxed(value); + if (array && array->len) + write_ip_values(info->keyfile, setting_name, array, gateway, FALSE); +} + +static void +route_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) +{ + GPtrArray * array; + const char *setting_name = nm_setting_get_name(setting); + + array = (GPtrArray *) g_value_get_boxed(value); + if (array && array->len) + write_ip_values(info->keyfile, setting_name, array, NULL, TRUE); +} + +static void +bridge_vlan_writer(KeyfileWriterInfo *info, + NMSetting * setting, + const char * key, + const GValue * value) +{ + GPtrArray *vlans; + + vlans = g_value_get_boxed(value); + if (vlans && vlans->len > 0) { + const guint string_initial_size = vlans->len * 10u; + nm_auto_str_buf NMStrBuf string = NM_STR_BUF_INIT(string_initial_size, FALSE); + guint i; + + for (i = 0; i < vlans->len; i++) { + gs_free char *vlan_str = NULL; + + vlan_str = nm_bridge_vlan_to_str(vlans->pdata[i], NULL); + if (i > 0) + nm_str_buf_append_c(&string, ','); + nm_utils_escaped_tokens_escape_strbuf_assert(vlan_str, ",", &string); + } + + nm_keyfile_plugin_kf_set_string(info->keyfile, + nm_setting_get_name(setting), + "vlans", + nm_str_buf_get_str(&string)); + } +} + +static void +wired_s390_options_parser_full(KeyfileReaderInfo * info, + const NMMetaSettingInfo * setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty * pip, + NMSetting * setting) +{ + NMSettingWired * s_wired = NM_SETTING_WIRED(setting); + gs_strfreev char **keys = NULL; + gsize n_keys; + gsize i; + + keys = nm_keyfile_plugin_kf_get_keys(info->keyfile, + ETHERNET_S390_OPTIONS_GROUP_NAME, + &n_keys, + NULL); + for (i = 0; i < n_keys; i++) { + gs_free char *value = NULL; + gs_free char *key_to_free = NULL; + + value = nm_keyfile_plugin_kf_get_string(info->keyfile, + ETHERNET_S390_OPTIONS_GROUP_NAME, + keys[i], + NULL); + if (!value) + continue; + + /* Here we don't verify the key/value further. If the file contains invalid keys, + * we will later reject the connection as invalid. */ + nm_setting_wired_add_s390_option(s_wired, + nm_keyfile_key_decode(keys[i], &key_to_free), + value); + } +} + +static void +wired_s390_options_writer_full(KeyfileWriterInfo * info, + const NMMetaSettingInfo * setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty * pip, + NMSetting * setting) +{ + NMSettingWired *s_wired = NM_SETTING_WIRED(setting); + guint i, n; + + n = nm_setting_wired_get_num_s390_options(s_wired); + for (i = 0; i < n; i++) { + gs_free char *key_to_free = NULL; + const char * opt_key; + const char * opt_val; + + nm_setting_wired_get_s390_option(s_wired, i, &opt_key, &opt_val); + nm_keyfile_plugin_kf_set_string(info->keyfile, + ETHERNET_S390_OPTIONS_GROUP_NAME, + nm_keyfile_key_encode(opt_key, &key_to_free), + opt_val); + } +} + +static void +ip_routing_rule_writer_full(KeyfileWriterInfo * info, + const NMMetaSettingInfo * setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty * pip, + NMSetting * setting) +{ + const char * setting_name = nm_setting_get_name(setting); + NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG(setting); + guint i, j, n; + char key_name_full[100] = "routing-rule"; + char * key_name_num = &key_name_full[NM_STRLEN("routing-rule")]; + + n = nm_setting_ip_config_get_num_routing_rules(s_ip); + j = 0; + for (i = 0; i < n; i++) { + NMIPRoutingRule *rule = nm_setting_ip_config_get_routing_rule(s_ip, i); + gs_free char * str = NULL; + + str = + nm_ip_routing_rule_to_string(rule, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE, NULL, NULL); + if (!str) + continue; + + sprintf(key_name_num, "%u", ++j); + nm_keyfile_plugin_kf_set_string(info->keyfile, setting_name, key_name_full, str); + } +} + +static void +qdisc_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) +{ + nm_auto_free_gstring GString *key_name = NULL; + nm_auto_free_gstring GString *value_str = NULL; + GPtrArray * array; + guint i; + + array = g_value_get_boxed(value); + if (!array || !array->len) + return; + + for (i = 0; i < array->len; i++) { + NMTCQdisc *qdisc = array->pdata[i]; + + nm_gstring_prepare(&key_name); + nm_gstring_prepare(&value_str); + + g_string_append(key_name, "qdisc."); + _nm_utils_string_append_tc_parent(key_name, NULL, nm_tc_qdisc_get_parent(qdisc)); + _nm_utils_string_append_tc_qdisc_rest(value_str, qdisc); + + nm_keyfile_plugin_kf_set_string(info->keyfile, + NM_SETTING_TC_CONFIG_SETTING_NAME, + key_name->str, + value_str->str); + } +} + +static void +tfilter_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) +{ + nm_auto_free_gstring GString *key_name = NULL; + nm_auto_free_gstring GString *value_str = NULL; + GPtrArray * array; + guint i; + + array = g_value_get_boxed(value); + if (!array || !array->len) + return; + + for (i = 0; i < array->len; i++) { + NMTCTfilter *tfilter = array->pdata[i]; + + nm_gstring_prepare(&key_name); + nm_gstring_prepare(&value_str); + + g_string_append(key_name, "tfilter."); + _nm_utils_string_append_tc_parent(key_name, NULL, nm_tc_tfilter_get_parent(tfilter)); + _nm_utils_string_append_tc_tfilter_rest(value_str, tfilter, NULL); + + nm_keyfile_plugin_kf_set_string(info->keyfile, + NM_SETTING_TC_CONFIG_SETTING_NAME, + key_name->str, + value_str->str); + } +} + +static void +_writer_full_ovs_external_ids_data(KeyfileWriterInfo * info, + const NMMetaSettingInfo * setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty * pip, + NMSetting * setting) +{ + GHashTable * hash; + NMUtilsNamedValue data_static[300u / sizeof(NMUtilsNamedValue)]; + gs_free NMUtilsNamedValue *data_free = NULL; + const NMUtilsNamedValue * data; + guint data_len; + char full_key_static[NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX) + 300u]; + guint i; + + nm_assert(NM_IS_SETTING_OVS_EXTERNAL_IDS(setting)); + nm_assert(nm_streq(property_info->name, NM_SETTING_OVS_EXTERNAL_IDS_DATA)); + + hash = _nm_setting_ovs_external_ids_get_data(NM_SETTING_OVS_EXTERNAL_IDS(setting)); + if (!hash) + return; + + data = nm_utils_named_values_from_strdict(hash, &data_len, data_static, &data_free); + if (data_len == 0) + return; + + memcpy(full_key_static, OVS_EXTERNAL_IDS_DATA_PREFIX, NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)); + + for (i = 0; i < data_len; i++) { + const char * key = data[i].name; + const char * val = data[i].value_str; + gs_free char *escaped_key_to_free = NULL; + const char * escaped_key; + gsize len; + gs_free char *full_key_free = NULL; + char * full_key = full_key_static; + + escaped_key = nm_keyfile_key_encode(key, &escaped_key_to_free); + + len = strlen(escaped_key) + 1u; + if (len >= G_N_ELEMENTS(full_key_static) - NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)) { + full_key_free = g_new(char, NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX) + len); + full_key = full_key_free; + memcpy(full_key, OVS_EXTERNAL_IDS_DATA_PREFIX, NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)); + } + memcpy(&full_key[NM_STRLEN(OVS_EXTERNAL_IDS_DATA_PREFIX)], escaped_key, len); + + nm_keyfile_plugin_kf_set_string(info->keyfile, + NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME, + full_key, + val); + } +} + +static void +write_hash_of_string(GKeyFile *file, NMSetting *setting, const char *key, const GValue *value) +{ + GHashTable * hash; + const char * group_name = nm_setting_get_name(setting); + gboolean vpn_secrets = FALSE; + gs_free const char **keys = NULL; + guint i, l; + + nm_assert((NM_IS_SETTING_VPN(setting) && nm_streq(key, NM_SETTING_VPN_DATA)) + || (NM_IS_SETTING_VPN(setting) && nm_streq(key, NM_SETTING_VPN_SECRETS)) + || (NM_IS_SETTING_BOND(setting) && nm_streq(key, NM_SETTING_BOND_OPTIONS)) + || (NM_IS_SETTING_USER(setting) && nm_streq(key, NM_SETTING_USER_DATA))); + + /* Write VPN secrets out to a different group to keep them separate */ + if (NM_IS_SETTING_VPN(setting) && nm_streq(key, NM_SETTING_VPN_SECRETS)) { + group_name = NM_KEYFILE_GROUP_VPN_SECRETS; + vpn_secrets = TRUE; + } + + hash = g_value_get_boxed(value); + + keys = nm_utils_strdict_get_keys(hash, TRUE, &l); + for (i = 0; i < l; i++) { + gs_free char *to_free = NULL; + const char * property, *data; + + property = keys[i]; + + /* Handle VPN secrets specially; they are nested in the property's hash; + * we don't want to write them if the secret is not saved, not required, + * or owned by a user's secret agent. + */ + if (vpn_secrets) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags(setting, property, &secret_flags, NULL)) + nm_assert_not_reached(); + if (!_secret_flags_persist_secret(secret_flags)) + continue; + } + + data = g_hash_table_lookup(hash, property); + nm_keyfile_plugin_kf_set_string(file, + group_name, + nm_keyfile_key_encode(property, &to_free), + data); + } +} + +static void +ssid_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) +{ + GBytes * bytes; + const guint8 *ssid_data; + gsize ssid_len; + const char * setting_name = nm_setting_get_name(setting); + gboolean new_format = TRUE; + gsize semicolons = 0; + gsize i; + + g_return_if_fail(G_VALUE_HOLDS(value, G_TYPE_BYTES)); + + bytes = g_value_get_boxed(value); + if (!bytes) + return; + ssid_data = g_bytes_get_data(bytes, &ssid_len); + if (!ssid_data || !ssid_len) { + nm_keyfile_plugin_kf_set_string(info->keyfile, setting_name, key, ""); + return; + } + + /* Check whether each byte is printable. If not, we have to use an + * integer list, otherwise we can just use a string. + */ + for (i = 0; i < ssid_len; i++) { + const char c = ssid_data[i]; + + if (!g_ascii_isprint(c)) { + new_format = FALSE; + break; + } + if (c == ';') + semicolons++; + } + + if (new_format) { + gs_free char *ssid = NULL; + + if (semicolons == 0) + ssid = g_strndup((char *) ssid_data, ssid_len); + else { + /* Escape semicolons with backslashes to make strings + * containing ';', such as '16;17;' unambiguous */ + gsize j = 0; + + ssid = g_malloc(ssid_len + semicolons + 1); + for (i = 0; i < ssid_len; i++) { + if (ssid_data[i] == ';') + ssid[j++] = '\\'; + ssid[j++] = ssid_data[i]; + } + ssid[j] = '\0'; + } + nm_keyfile_plugin_kf_set_string(info->keyfile, setting_name, key, ssid); + } else + nm_keyfile_plugin_kf_set_integer_list_uint8(info->keyfile, + setting_name, + key, + ssid_data, + ssid_len); +} + +static void +password_raw_writer(KeyfileWriterInfo *info, + NMSetting * setting, + const char * key, + const GValue * value) +{ + const char * setting_name = nm_setting_get_name(setting); + GBytes * array; + gsize len; + const guint8 *data; + + g_return_if_fail(G_VALUE_HOLDS(value, G_TYPE_BYTES)); + + array = (GBytes *) g_value_get_boxed(value); + if (!array) + return; + data = g_bytes_get_data(array, &len); + if (!data) + len = 0; + nm_keyfile_plugin_kf_set_integer_list_uint8(info->keyfile, setting_name, key, data, len); +} + +/*****************************************************************************/ + +static void +cert_writer_default(NMConnection * connection, + GKeyFile * file, + NMSetting8021x * setting, + const char * setting_name, + const NMSetting8021xSchemeVtable *vtable) +{ + NMSetting8021xCKScheme scheme; + + scheme = vtable->scheme_func(setting); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + gs_free char *path_free = NULL; + gs_free char *base_dir = NULL; + gs_free char *tmp = NULL; + const char * path; + + path = vtable->path_func(setting); + g_assert(path); + + /* If the path is relative, make it an absolute path. + * Relative paths make a keyfile not easily usable in another + * context. */ + if (path[0] && path[0] != '/') { + base_dir = g_get_current_dir(); + path_free = g_strconcat(base_dir, "/", path, NULL); + path = path_free; + } else + base_dir = g_path_get_dirname(path); + + /* path cannot start with "file://" or "data:;base64,", because it is an absolute path. + * Still, make sure that a prefix-less path will be recognized. This can happen + * for example if the path is longer then 500 chars. */ + tmp = nm_keyfile_detect_unqualified_path_scheme(base_dir, path, -1, FALSE, NULL); + if (tmp) + nm_clear_g_free(&tmp); + else { + tmp = g_strconcat(NM_KEYFILE_CERT_SCHEME_PREFIX_PATH, path, NULL); + path = tmp; + } + + /* Path contains at least a '/', hence it cannot be recognized as the old + * binary format consisting of a list of integers. */ + + nm_keyfile_plugin_kf_set_string(file, setting_name, vtable->setting_key, path); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes * blob; + const guint8 *blob_data; + gsize blob_len; + gs_free char *blob_base64 = NULL; + gs_free char *val = NULL; + + blob = vtable->blob_func(setting); + g_assert(blob); + blob_data = g_bytes_get_data(blob, &blob_len); + + blob_base64 = g_base64_encode(blob_data, blob_len); + val = g_strconcat(NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB, blob_base64, NULL); + + nm_keyfile_plugin_kf_set_string(file, setting_name, vtable->setting_key, val); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + nm_keyfile_plugin_kf_set_string(file, + setting_name, + vtable->setting_key, + vtable->uri_func(setting)); + } else { + /* scheme_func() returns UNKNOWN in all other cases. The only valid case + * where a scheme is allowed to be UNKNOWN, is unsetting the value. In this + * case, we don't expect the writer to be called, because the default value + * will not be serialized. + * The only other reason for the scheme to be UNKNOWN is an invalid cert. + * But our connection verifies, so that cannot happen either. */ + g_return_if_reached(); + } +} + +static void +cert_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const GValue *value) +{ + const NMSetting8021xSchemeVtable *vtable = NULL; + const char * setting_name; + guint i; + + for (i = 0; nm_setting_8021x_scheme_vtable[i].setting_key; i++) { + if (nm_streq0(nm_setting_8021x_scheme_vtable[i].setting_key, key)) { + vtable = &nm_setting_8021x_scheme_vtable[i]; + break; + } + } + if (!vtable) + g_return_if_reached(); + + setting_name = nm_setting_get_name(NM_SETTING(setting)); + + if (info->write_handler) { + NMKeyfileHandlerData handler_data; + + _key_file_handler_data_init_write(&handler_data, + NM_KEYFILE_HANDLER_TYPE_WRITE_CERT, + info, + setting_name, + vtable->setting_key, + setting, + key); + handler_data.write_cert = (NMKeyfileHandlerDataWriteCert){ + .vtable = vtable, + }; + + if (info->write_handler(info->connection, + info->keyfile, + NM_KEYFILE_HANDLER_TYPE_WRITE_CERT, + &handler_data, + info->user_data)) + return; + if (info->error) + return; + } + + cert_writer_default(info->connection, + info->keyfile, + NM_SETTING_802_1X(setting), + setting_name, + vtable); +} + +/*****************************************************************************/ + +struct _ParseInfoProperty { + const char *property_name; + union { + void (*parser)(KeyfileReaderInfo *info, NMSetting *setting, const char *key); + void (*parser_full)(KeyfileReaderInfo * info, + const NMMetaSettingInfo * setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty * pip, + NMSetting * setting); + }; + union { + void (*writer)(KeyfileWriterInfo *info, + NMSetting * setting, + const char * key, + const GValue * value); + void (*writer_full)(KeyfileWriterInfo * info, + const NMMetaSettingInfo * setting_info, + const NMSettInfoProperty *property_info, + const ParseInfoProperty * pip, + NMSetting * setting); + }; + bool parser_skip; + bool parser_no_check_key : 1; + bool writer_skip : 1; + bool has_writer_full : 1; + bool has_parser_full : 1; + + /* usually, we skip to write values that have their + * default value. By setting this flag to TRUE, also + * default values are written. */ + bool writer_persist_default : 1; +}; + +#define PARSE_INFO_PROPERTY(_property_name, ...) \ + (&((const ParseInfoProperty){.property_name = _property_name, __VA_ARGS__})) + +#define PARSE_INFO_PROPERTIES(...) \ + .properties = ((const ParseInfoProperty *const[]){ \ + __VA_ARGS__ NULL, \ + }) + +typedef struct { + const ParseInfoProperty *const *properties; +} ParseInfoSetting; + +#define PARSE_INFO_SETTING(setting_type, ...) \ + [setting_type] = (&((const ParseInfoSetting){__VA_ARGS__})) + +static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = { + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_WIRELESS, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_BSSID, .parser = mac_address_parser_ETHER, ), + PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, + .parser = mac_address_parser_ETHER_cloned, ), + PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, ), + PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_SSID, + .parser = ssid_parser, + .writer = ssid_writer, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_802_1X, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_802_1X_CA_CERT, + .parser = cert_parser, + .writer = cert_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_802_1X_CLIENT_CERT, + .parser = cert_parser, + .writer = cert_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PASSWORD_RAW, + .parser = password_raw_parser, + .writer = password_raw_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PHASE2_CA_CERT, + .parser = cert_parser, + .writer = cert_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + .parser = cert_parser, + .writer = cert_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + .parser = cert_parser, + .writer = cert_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_802_1X_PRIVATE_KEY, + .parser = cert_parser, + .writer = cert_writer, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_WIRED, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_WIRED_CLONED_MAC_ADDRESS, + .parser = mac_address_parser_ETHER_cloned, ), + PARSE_INFO_PROPERTY(NM_SETTING_WIRED_MAC_ADDRESS, .parser = mac_address_parser_ETHER, ), + PARSE_INFO_PROPERTY(NM_SETTING_WIRED_S390_OPTIONS, + .parser_no_check_key = TRUE, + .parser_full = wired_s390_options_parser_full, + .writer_full = wired_s390_options_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_BLUETOOTH, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_BLUETOOTH_BDADDR, + .parser = mac_address_parser_ETHER, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_BOND, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_BOND_OPTIONS, .parser_no_check_key = TRUE, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_BRIDGE, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_BRIDGE_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, ), + PARSE_INFO_PROPERTY(NM_SETTING_BRIDGE_VLANS, + .parser_no_check_key = TRUE, + .parser = bridge_vlan_parser, + .writer = bridge_vlan_writer, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_BRIDGE_PORT, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_BRIDGE_PORT_VLANS, + .parser_no_check_key = TRUE, + .parser = bridge_vlan_parser, + .writer = bridge_vlan_writer, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_CONNECTION, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_CONNECTION_READ_ONLY, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_CONNECTION_TYPE, + .parser = setting_alias_parser, + .writer = setting_alias_writer, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_INFINIBAND, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_INFINIBAND_MAC_ADDRESS, + .parser = mac_address_parser_INFINIBAND, ), ), ), + PARSE_INFO_SETTING(NM_META_SETTING_TYPE_IP4_CONFIG, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ADDRESSES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = addr_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_DNS, + .parser_no_check_key = TRUE, + .parser = ip_dns_parser, + .writer = dns_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = route_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTING_RULES, + .parser_no_check_key = TRUE, + .parser_full = ip_routing_rule_parser_full, + .writer_full = ip_routing_rule_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, ), ), ), + PARSE_INFO_SETTING(NM_META_SETTING_TYPE_IP6_CONFIG, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + .parser_no_check_key = TRUE, + .parser = ip6_addr_gen_mode_parser, + .writer = ip6_addr_gen_mode_writer, + .writer_persist_default = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ADDRESSES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = addr_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_DNS, + .parser_no_check_key = TRUE, + .parser = ip_dns_parser, + .writer = dns_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTES, + .parser_no_check_key = TRUE, + .parser = ip_address_or_route_parser, + .writer = route_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTING_RULES, + .parser_no_check_key = TRUE, + .parser_full = ip_routing_rule_parser_full, + .writer_full = ip_routing_rule_writer_full, + .has_parser_full = TRUE, + .has_writer_full = TRUE, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_OVS_EXTERNAL_IDS_DATA, + .parser_no_check_key = TRUE, + .parser_full = _parser_full_ovs_external_ids_data, + .writer_full = _writer_full_ovs_external_ids_data, + .has_parser_full = TRUE, + .has_writer_full = TRUE, ), ), ), + PARSE_INFO_SETTING(NM_META_SETTING_TYPE_SERIAL, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_SERIAL_PARITY, + .parser = parity_parser, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_SRIOV, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_SRIOV_VFS, + .parser_no_check_key = TRUE, + .parser = sriov_vfs_parser, + .writer = sriov_vfs_writer, ), ), ), + PARSE_INFO_SETTING(NM_META_SETTING_TYPE_TC_CONFIG, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_TC_CONFIG_QDISCS, + .parser_no_check_key = TRUE, + .parser = qdisc_parser, + .writer = qdisc_writer, ), + PARSE_INFO_PROPERTY(NM_SETTING_TC_CONFIG_TFILTERS, + .parser_no_check_key = TRUE, + .parser = tfilter_parser, + .writer = tfilter_writer, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_TEAM, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_CONFIG, .parser = team_config_parser, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_LINK_WATCHERS, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_MCAST_REJOIN_COUNT, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER, .parser_skip = TRUE, .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_ACTIVE, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_FAST_RATE, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_MIN_PORTS, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_SYS_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_TX_BALANCER, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_RUNNER_TX_HASH, + .parser_skip = TRUE, + .writer_skip = TRUE, ), ), ), + PARSE_INFO_SETTING(NM_META_SETTING_TYPE_TEAM_PORT, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_TEAM_CONFIG, + .parser = team_config_parser, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_LACP_KEY, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_LACP_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_LINK_WATCHERS, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_PRIO, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_QUEUE_ID, + .parser_skip = TRUE, + .writer_skip = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_TEAM_PORT_STICKY, + .parser_skip = TRUE, + .writer_skip = TRUE, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_USER, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_USER_DATA, .parser_no_check_key = TRUE, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_VLAN, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_VLAN_FLAGS, .writer_persist_default = TRUE, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_VPN, + PARSE_INFO_PROPERTIES( + PARSE_INFO_PROPERTY(NM_SETTING_VPN_DATA, .parser_no_check_key = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_VPN_PERSISTENT, .parser_no_check_key = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_VPN_SECRETS, .parser_no_check_key = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_VPN_SERVICE_TYPE, .parser_no_check_key = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_VPN_TIMEOUT, .parser_no_check_key = TRUE, ), + PARSE_INFO_PROPERTY(NM_SETTING_VPN_USER_NAME, .parser_no_check_key = TRUE, ), ), ), + PARSE_INFO_SETTING( + NM_META_SETTING_TYPE_WIMAX, + PARSE_INFO_PROPERTIES(PARSE_INFO_PROPERTY(NM_SETTING_WIMAX_MAC_ADDRESS, + .parser = mac_address_parser_ETHER, ), ), ), +}; + +static void +_parse_info_find(NMSetting * setting, + const char * property_name, + const NMMetaSettingInfo **out_setting_info, + const ParseInfoSetting ** out_parse_info_setting, + const ParseInfoProperty **out_parse_info_property) +{ + const NMMetaSettingInfo *setting_info; + const ParseInfoSetting * pis; + const ParseInfoProperty *pip; + +#if NM_MORE_ASSERTS > 10 + { + guint i, j; + static int asserted = FALSE; + + if (!asserted) { + for (i = 0; i < G_N_ELEMENTS(parse_infos); i++) { + pis = parse_infos[i]; + + if (!pis) + continue; + if (!pis->properties) + continue; + + g_assert(pis->properties[0]); + for (j = 0; pis->properties[j]; j++) { + const ParseInfoProperty *pip0; + const ParseInfoProperty *pipj = pis->properties[j]; + + g_assert(pipj->property_name); + if (j > 0 && (pip0 = pis->properties[j - 1]) + && strcmp(pip0->property_name, pipj->property_name) >= 0) { + g_error("Wrong order at index #%d.%d: \"%s.%s\" before \"%s.%s\"", + i, + j - 1, + nm_meta_setting_infos[i].setting_name, + pip0->property_name, + nm_meta_setting_infos[i].setting_name, + pipj->property_name); + } + } + } + asserted = TRUE; + } + } +#endif + + if (!NM_IS_SETTING(setting) || !(setting_info = NM_SETTING_GET_CLASS(setting)->setting_info)) { + /* handle invalid setting objects gracefully. */ + NM_SET_OUT(out_setting_info, NULL); + NM_SET_OUT(out_parse_info_setting, NULL); + NM_SET_OUT(out_parse_info_property, NULL); + return; + } + + nm_assert(setting_info->setting_name); + nm_assert(_NM_INT_NOT_NEGATIVE(setting_info->meta_type)); + nm_assert(setting_info->meta_type < G_N_ELEMENTS(parse_infos)); + + pis = parse_infos[setting_info->meta_type]; + + pip = NULL; + if (pis && property_name) { + gssize idx; + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(ParseInfoProperty, property_name) == 0); + + idx = nm_utils_ptrarray_find_binary_search((gconstpointer *) pis->properties, + NM_PTRARRAY_LEN(pis->properties), + &property_name, + nm_strcmp_p_with_data, + NULL); + if (idx >= 0) + pip = pis->properties[idx]; + } + + NM_SET_OUT(out_setting_info, setting_info); + NM_SET_OUT(out_parse_info_setting, pis); + NM_SET_OUT(out_parse_info_property, pip); +} + +/*****************************************************************************/ + +static void +read_one_setting_value(KeyfileReaderInfo * info, + NMSetting * setting, + const NMSettInfoProperty *property_info) +{ + GKeyFile * keyfile = info->keyfile; + gs_free_error GError * err = NULL; + const NMMetaSettingInfo *setting_info; + const ParseInfoProperty *pip; + gs_free char * tmp_str = NULL; + const char * key; + GType type; + guint64 u64; + gint64 i64; + + nm_assert(!info->error); + nm_assert(!property_info->param_spec + || nm_streq(property_info->param_spec->name, property_info->name)); + + key = property_info->name; + + _parse_info_find(setting, key, &setting_info, NULL, &pip); + + nm_assert(setting_info); + + if (!pip) { + if (nm_streq(key, NM_SETTING_NAME)) + return; + if (!property_info->param_spec) + return; + if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) + != G_PARAM_WRITABLE) + return; + } else { + if (pip->parser_skip) + return; + if (pip->has_parser_full) { + pip->parser_full(info, setting_info, property_info, pip, setting); + return; + } + } + + nm_assert(property_info->param_spec); + nm_assert((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) + == G_PARAM_WRITABLE); + + /* Check for the exact key in the GKeyFile if required. Most setting + * properties map 1:1 to a key in the GKeyFile, but for those properties + * like IP addresses and routes where more than one value is actually + * encoded by the setting property, this won't be true. + */ + if ((!pip || !pip->parser_no_check_key) + && !nm_keyfile_plugin_kf_has_key(keyfile, setting_info->setting_name, key, &err)) { + /* Key doesn't exist or an error occurred, thus nothing to do. */ + if (err) { + if (!handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("error loading setting value: %s"), + err->message)) + return; + } + return; + } + + if (pip && pip->parser) { + pip->parser(info, setting, key); + return; + } + + type = G_PARAM_SPEC_VALUE_TYPE(property_info->param_spec); + + if (type == G_TYPE_STRING) { + gs_free char *str_val = NULL; + + str_val = nm_keyfile_plugin_kf_get_string(keyfile, setting_info->setting_name, key, &err); + if (!err) + nm_g_object_set_property_string_take(G_OBJECT(setting), + key, + g_steal_pointer(&str_val), + &err); + } else if (type == G_TYPE_UINT) { + tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64(tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); + if (u64 == G_MAXUINT64 && errno != 0) { + g_set_error_literal(&err, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_uint(G_OBJECT(setting), key, u64, &err); + } + } else if (type == G_TYPE_INT) { + tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT, G_MAXINT, G_MININT64); + if (i64 == G_MININT64 && errno != 0) { + g_set_error_literal(&err, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_int(G_OBJECT(setting), key, i64, &err); + } + } else if (type == G_TYPE_BOOLEAN) { + gboolean bool_val; + + bool_val = nm_keyfile_plugin_kf_get_boolean(keyfile, setting_info->setting_name, key, &err); + if (!err) + nm_g_object_set_property_boolean(G_OBJECT(setting), key, bool_val, &err); + } else if (type == G_TYPE_CHAR) { + tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); + if (!err) { + /* As documented by glib, G_TYPE_CHAR is really a (signed!) gint8. */ + i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT8, G_MAXINT8, G_MININT64); + if (i64 == G_MININT64 && errno != 0) { + g_set_error_literal(&err, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_char(G_OBJECT(setting), key, i64, &err); + } + } else if (type == G_TYPE_UINT64) { + tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64(tmp_str, 0, 0, G_MAXUINT64, G_MAXUINT64); + if (u64 == G_MAXUINT64 && errno != 0) { + g_set_error_literal(&err, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_uint64(G_OBJECT(setting), key, u64, &err); + } + } else if (type == G_TYPE_INT64) { + tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT64, G_MAXINT64, G_MAXINT64); + if (i64 == G_MAXINT64 && errno != 0) { + g_set_error_literal(&err, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_int64(G_OBJECT(setting), key, i64, &err); + } + } else if (type == G_TYPE_BYTES) { + nm_auto_unref_bytearray GByteArray *array = NULL; + gs_unref_bytes GBytes *bytes = NULL; + gs_free guint *tmp = NULL; + gsize length; + int i; + gboolean already_warned = FALSE; + + tmp = nm_keyfile_plugin_kf_get_integer_list_uint(keyfile, + setting_info->setting_name, + key, + &length, + NULL); + + array = g_byte_array_sized_new(length); + for (i = 0; i < length; i++) { + const guint val = tmp[i]; + unsigned char v = (unsigned char) (val & 0xFF); + + if (val > 255u) { + if (!already_warned + && !handle_warn( + info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("ignoring invalid byte element '%u' (not between 0 and 255 inclusive)"), + val)) + return; + already_warned = TRUE; + } else + g_byte_array_append(array, (const unsigned char *) &v, sizeof(v)); + } + + bytes = g_byte_array_free_to_bytes(g_steal_pointer(&array)); + g_object_set(setting, key, bytes, NULL); + } else if (type == G_TYPE_STRV) { + gs_strfreev char **sa = NULL; + gsize length; + + sa = nm_keyfile_plugin_kf_get_string_list(keyfile, + setting_info->setting_name, + key, + &length, + NULL); + g_object_set(setting, key, sa, NULL); + } else if (type == G_TYPE_HASH_TABLE) { + read_hash_of_string(info, keyfile, setting, key); + } else if (type == G_TYPE_ARRAY) { + read_array_of_uint(keyfile, setting, key); + } else if (G_TYPE_IS_FLAGS(type)) { + tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); + if (!err) { + u64 = _nm_utils_ascii_str_to_uint64(tmp_str, 0, 0, G_MAXUINT, G_MAXUINT64); + if (u64 == G_MAXUINT64 && errno != 0) { + g_set_error_literal(&err, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_flags(G_OBJECT(setting), key, type, u64, &err); + } + } else if (G_TYPE_IS_ENUM(type)) { + tmp_str = nm_keyfile_plugin_kf_get_value(keyfile, setting_info->setting_name, key, &err); + if (!err) { + i64 = _nm_utils_ascii_str_to_int64(tmp_str, 0, G_MININT, G_MAXINT, G_MAXINT64); + if (i64 == G_MAXINT64 && errno != 0) { + g_set_error_literal(&err, + G_KEY_FILE_ERROR, + G_KEY_FILE_ERROR_INVALID_VALUE, + _("value cannot be interpreted as integer")); + } else + nm_g_object_set_property_enum(G_OBJECT(setting), key, type, i64, &err); + } + } else + g_return_if_reached(); + + if (err) { + if (nm_keyfile_error_is_not_found(err)) { + /* ignore such errors. The key is not present. */ + } else { + handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting: %s"), + err->message); + } + } +} + +static void +_read_setting(KeyfileReaderInfo *info) +{ + const NMSettInfoSetting *sett_info; + gs_unref_object NMSetting *setting = NULL; + const char * alias; + GType type; + guint i; + + alias = nm_keyfile_plugin_get_setting_name_for_alias(info->group); + if (!alias) + alias = info->group; + + type = nm_setting_lookup_type(alias); + if (!type) { + handle_warn(info, + NULL, + NULL, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid setting name '%s'"), + info->group); + return; + } + + setting = g_object_new(type, NULL); + + info->setting = setting; + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); + + if (sett_info->detail.gendata_info) { + gs_free char **keys = NULL; + gsize k, n_keys; + + keys = g_key_file_get_keys(info->keyfile, info->group, &n_keys, NULL); + if (!keys) + n_keys = 0; + if (n_keys > 0) { + GHashTable *h = _nm_setting_option_hash(setting, TRUE); + + nm_utils_strv_sort(keys, n_keys); + for (k = 0; k < n_keys; k++) { + gs_free char *key = keys[k]; + gs_free_error GError *local = NULL; + const GVariantType * variant_type; + GVariant * variant; + + /* a GKeyFile can return duplicate keys, there is just no API to make sense + * of them. Skip them. */ + if (k + 1 < n_keys && nm_streq(key, keys[k + 1])) + continue; + + /* currently, the API is very simple. The setting class just returns + * the desired variant type, and keyfile reader will try to parse + * it accordingly. Note, that this does currently not allow, that + * a particular key can contain different variant types, nor is it + * very flexible in general. + * + * We add flexibility when we need it. Keep it simple for now. */ + variant_type = + sett_info->detail.gendata_info->get_variant_type(sett_info, key, &local); + if (!variant_type) { + if (!handle_warn(info, + key, + NULL, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid key '%s.%s'"), + info->group, + key)) + break; + continue; + } + + if (g_variant_type_equal(variant_type, G_VARIANT_TYPE_BOOLEAN)) { + gboolean v; + + v = g_key_file_get_boolean(info->keyfile, info->group, key, &local); + if (local) { + if (!handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not boolean"), + info->group, + key)) + break; + continue; + } + variant = g_variant_new_boolean(v); + } else if (g_variant_type_equal(variant_type, G_VARIANT_TYPE_UINT32)) { + guint64 v; + + v = g_key_file_get_uint64(info->keyfile, info->group, key, &local); + + if (local) { + if (!handle_warn(info, + key, + key, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a uint32"), + info->group, + key)) + break; + continue; + } + variant = g_variant_new_uint32((guint32) v); + } else { + nm_assert_not_reached(); + continue; + } + + g_hash_table_insert(h, g_steal_pointer(&key), g_variant_take_ref(variant)); + } + for (; k < n_keys; k++) + g_free(keys[k]); + } + } + + for (i = 0; i < sett_info->property_infos_len; i++) { + read_one_setting_value(info, setting, &sett_info->property_infos[i]); + if (info->error) + goto out; + } + +out: + info->setting = NULL; + if (!info->error) + nm_connection_add_setting(info->connection, g_steal_pointer(&setting)); +} + +static void +_read_setting_wireguard_peer(KeyfileReaderInfo *info) +{ + gs_unref_object NMSettingWireGuard *s_wg_new = NULL; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + gs_free_error GError *error = NULL; + NMSettingWireGuard * s_wg; + gs_free char * str = NULL; + const char * cstr = NULL; + const char * key; + gint64 i64; + gs_strfreev char ** sa = NULL; + gsize n_sa; + + peer = nm_wireguard_peer_new(); + + nm_assert(g_str_has_prefix(info->group, NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)); + cstr = &info->group[NM_STRLEN(NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)]; + if (!nm_utils_base64secret_normalize(cstr, NM_WIREGUARD_PUBLIC_KEY_LEN, &str) + || !nm_streq0(str, cstr)) { + /* the group name must be identical to the normalized(!) key, so that it + * is uniquely identified. */ + handle_warn(info, + NULL, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("invalid peer public key in section '%s'"), + info->group); + return; + } + nm_wireguard_peer_set_public_key(peer, cstr, TRUE); + nm_clear_g_free(&str); + + key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY; + str = nm_keyfile_plugin_kf_get_string(info->keyfile, info->group, key, NULL); + if (str) { + if (!nm_wireguard_peer_set_preshared_key(peer, str, FALSE)) { + if (!handle_warn(info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid 256 bit key in base64 encoding"), + info->group, + key)) + return; + } + nm_clear_g_free(&str); + } + + key = NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS; + i64 = nm_keyfile_plugin_kf_get_int64(info->keyfile, + info->group, + key, + 0, + 0, + NM_SETTING_SECRET_FLAG_ALL, + -1, + NULL); + if (errno != ENODATA) { + if (i64 == -1 || !_nm_setting_secret_flags_valid(i64)) { + if (!handle_warn(info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid secret flag"), + info->group, + key)) + return; + } else + nm_wireguard_peer_set_preshared_key_flags(peer, i64); + } + + key = NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE; + i64 = nm_keyfile_plugin_kf_get_int64(info->keyfile, + info->group, + key, + 0, + 0, + G_MAXUINT32, + -1, + NULL); + if (errno != ENODATA) { + if (i64 == -1) { + if (!handle_warn(info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a integer in range 0 to 2^32"), + info->group, + key)) + return; + } else + nm_wireguard_peer_set_persistent_keepalive(peer, i64); + } + + key = NM_WIREGUARD_PEER_ATTR_ENDPOINT; + str = nm_keyfile_plugin_kf_get_string(info->keyfile, info->group, key, NULL); + if (str && str[0]) { + if (!nm_wireguard_peer_set_endpoint(peer, str, FALSE)) { + if (!handle_warn(info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' is not a valid endpoint"), + info->group, + key)) + return; + } + } + nm_clear_g_free(&str); + + key = NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS; + sa = nm_keyfile_plugin_kf_get_string_list(info->keyfile, info->group, key, &n_sa, NULL); + if (n_sa > 0) { + gboolean has_error = FALSE; + gsize i; + + for (i = 0; i < n_sa; i++) { + if (!nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, sa[i], NULL, NULL, NULL)) { + has_error = TRUE; + continue; + } + nm_wireguard_peer_append_allowed_ip(peer, sa[i], TRUE); + } + if (has_error) { + if (!handle_warn(info, + key, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("key '%s.%s' has invalid allowed-ips"), + info->group, + key)) + return; + } + } + + if (info->error) + return; + + if (!nm_wireguard_peer_is_valid(peer, TRUE, TRUE, &error)) { + handle_warn(info, + NULL, + NM_SETTING_WIREGUARD_PEERS, + NM_KEYFILE_WARN_SEVERITY_WARN, + _("peer '%s' is invalid: %s"), + info->group, + error->message); + return; + } + + s_wg = NM_SETTING_WIREGUARD( + nm_connection_get_setting(info->connection, NM_TYPE_SETTING_WIREGUARD)); + if (!s_wg) { + s_wg_new = NM_SETTING_WIREGUARD(nm_setting_wireguard_new()); + s_wg = s_wg_new; + } + + nm_setting_wireguard_append_peer(s_wg, peer); + + if (s_wg_new) { + nm_connection_add_setting(info->connection, NM_SETTING(g_steal_pointer(&s_wg_new))); + } +} + +static void +_read_setting_vpn_secrets(KeyfileReaderInfo *info) +{ + gs_strfreev char **keys = NULL; + gsize i, n_keys; + NMSettingVpn * s_vpn; + + s_vpn = nm_connection_get_setting_vpn(info->connection); + if (!s_vpn) { + /* if we don't also have a [vpn] section (which must be parsed earlier), + * we don't do anything. */ + nm_assert(!g_key_file_has_group(info->keyfile, "vpn")); + return; + } + + keys = + nm_keyfile_plugin_kf_get_keys(info->keyfile, NM_KEYFILE_GROUP_VPN_SECRETS, &n_keys, NULL); + for (i = 0; i < n_keys; i++) { + gs_free char *secret = NULL; + + secret = nm_keyfile_plugin_kf_get_string(info->keyfile, + NM_KEYFILE_GROUP_VPN_SECRETS, + keys[i], + NULL); + if (secret) + nm_setting_vpn_add_secret(s_vpn, keys[i], secret); + } +} + +gboolean +nm_keyfile_read_ensure_id(NMConnection *connection, const char *fallback_id) +{ + NMSettingConnection *s_con; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(fallback_id, FALSE); + + s_con = nm_connection_get_setting_connection(connection); + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(s_con), FALSE); + + if (nm_setting_connection_get_id(s_con)) + return FALSE; + + g_object_set(s_con, NM_SETTING_CONNECTION_ID, fallback_id, NULL); + return TRUE; +} + +gboolean +nm_keyfile_read_ensure_uuid(NMConnection *connection, const char *fallback_uuid_seed) +{ + NMSettingConnection *s_con; + gs_free char * hashed_uuid = NULL; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + g_return_val_if_fail(fallback_uuid_seed, FALSE); + + s_con = nm_connection_get_setting_connection(connection); + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(s_con), FALSE); + + if (nm_setting_connection_get_uuid(s_con)) + return FALSE; + + hashed_uuid = nm_uuid_generate_from_strings("keyfile", fallback_uuid_seed, NULL); + g_object_set(s_con, NM_SETTING_CONNECTION_UUID, hashed_uuid, NULL); + return TRUE; +} + +/** + * nm_keyfile_read: + * @keyfile: the keyfile from which to create the connection + * @base_dir: when reading certificates from files with relative name, + * the relative path is made absolute using @base_dir. This must + * be an absolute path. + * @handler_flags: the #NMKeyfileHandlerFlags. + * @handler: (allow-none) (scope call): read handler + * @user_data: user data for read handler + * @error: (allow-none) (out): error + * + * Tries to create a NMConnection from a keyfile. The resulting keyfile is + * not normalized and might not even verify. + * + * Returns: (transfer full): on success, returns the created connection. + * + * Since: 1.30 + */ +NMConnection * +nm_keyfile_read(GKeyFile * keyfile, + const char * base_dir, + NMKeyfileHandlerFlags handler_flags, + NMKeyfileReadHandler handler, + void * user_data, + GError ** error) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingConnection * s_con; + gs_strfreev char ** groups = NULL; + gsize n_groups; + gsize i; + gboolean vpn_secrets = FALSE; + KeyfileReaderInfo info; + + g_return_val_if_fail(keyfile, NULL); + g_return_val_if_fail(!error || !*error, NULL); + g_return_val_if_fail(base_dir && base_dir[0] == '/', NULL); + g_return_val_if_fail(handler_flags == NM_KEYFILE_HANDLER_FLAGS_NONE, NULL); + + connection = nm_simple_connection_new(); + + info = (KeyfileReaderInfo){ + .connection = connection, + .keyfile = keyfile, + .base_dir = base_dir, + .read_handler = handler, + .user_data = user_data, + }; + + groups = g_key_file_get_groups(keyfile, &n_groups); + if (!groups) + n_groups = 0; + + for (i = 0; i < n_groups; i++) { + info.group = groups[i]; + + if (nm_streq(groups[i], NM_KEYFILE_GROUP_VPN_SECRETS)) { + /* Only read out secrets when needed */ + vpn_secrets = TRUE; + } else if (NM_STR_HAS_PREFIX(groups[i], NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER)) + _read_setting_wireguard_peer(&info); + else if (NM_IN_STRSET(groups[i], + NM_KEYFILE_GROUP_NMMETA, + ETHERNET_S390_OPTIONS_GROUP_NAME)) { + /* pass */ + } else + _read_setting(&info); + + info.group = NULL; + + if (info.error) + goto out_with_info_error; + } + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) { + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + } + + /* Make sure that we have 'interface-name' even if it was specified in the + * "wrong" (ie, deprecated) group. + */ + if (!nm_setting_connection_get_interface_name(s_con) + && nm_setting_connection_get_connection_type(s_con)) { + gs_free char *interface_name = NULL; + + interface_name = g_key_file_get_string(keyfile, + nm_setting_connection_get_connection_type(s_con), + "interface-name", + NULL); + if (interface_name) + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, interface_name, NULL); + } + + if (vpn_secrets) { + info.group = NM_KEYFILE_GROUP_VPN_SECRETS; + _read_setting_vpn_secrets(&info); + info.group = NULL; + if (info.error) + goto out_with_info_error; + } + + return g_steal_pointer(&connection); + +out_with_info_error: + g_propagate_error(error, info.error); + return NULL; +} + +/*****************************************************************************/ + +static void +write_setting_value(KeyfileWriterInfo * info, + NMSetting * setting, + const NMSettInfoProperty *property_info) +{ + const NMMetaSettingInfo *setting_info; + const ParseInfoProperty *pip; + const char * key; + char numstr[64]; + GValue value; + GType type; + + nm_assert(!info->error); + nm_assert(!property_info->param_spec + || nm_streq(property_info->param_spec->name, property_info->name)); + + key = property_info->name; + + _parse_info_find(setting, key, &setting_info, NULL, &pip); + + if (!pip) { + if (!setting_info) { + /* the setting type is unknown. That is highly unexpected + * (and as this is currently only called from NetworkManager + * daemon, not possible). + * + * Still, handle it gracefully, because later keyfile writer will become + * public API of libnm, where @setting is (untrusted) user input. + * + * Gracefully here just means: ignore the setting. */ + return; + } + if (!property_info->param_spec) + return; + if (nm_streq(key, NM_SETTING_NAME)) + return; + } else { + if (pip->has_writer_full) { + pip->writer_full(info, setting_info, property_info, pip, setting); + return; + } + if (pip->writer_skip) + return; + } + + nm_assert(property_info->param_spec); + + /* Don't write secrets that are owned by user secret agents or aren't + * supposed to be saved. VPN secrets are handled specially though since + * the secret flags there are in a third-level hash in the 'secrets' + * property. + */ + if ((property_info->param_spec->flags & NM_SETTING_PARAM_SECRET) + && !NM_IS_SETTING_VPN(setting)) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags(setting, key, &secret_flags, NULL)) + g_return_if_reached(); + if (!_secret_flags_persist_secret(secret_flags)) + return; + } + + value = (GValue){0}; + + g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(property_info->param_spec)); + g_object_get_property(G_OBJECT(setting), property_info->param_spec->name, &value); + + if ((!pip || !pip->writer_persist_default) + && g_param_value_defaults(property_info->param_spec, &value)) { + nm_assert(!g_key_file_has_key(info->keyfile, setting_info->setting_name, key, NULL)); + goto out_unset_value; + } + + if (pip && pip->writer) { + pip->writer(info, setting, key, &value); + goto out_unset_value; + } + + type = G_VALUE_TYPE(&value); + if (type == G_TYPE_STRING) { + const char *str; + + str = g_value_get_string(&value); + if (str) + nm_keyfile_plugin_kf_set_string(info->keyfile, setting_info->setting_name, key, str); + } else if (type == G_TYPE_UINT) { + nm_sprintf_buf(numstr, "%u", g_value_get_uint(&value)); + nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_INT) { + nm_sprintf_buf(numstr, "%d", g_value_get_int(&value)); + nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_UINT64) { + nm_sprintf_buf(numstr, "%" G_GUINT64_FORMAT, g_value_get_uint64(&value)); + nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_INT64) { + nm_sprintf_buf(numstr, "%" G_GINT64_FORMAT, g_value_get_int64(&value)); + nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_BOOLEAN) { + nm_keyfile_plugin_kf_set_value(info->keyfile, + setting_info->setting_name, + key, + g_value_get_boolean(&value) ? "true" : "false"); + } else if (type == G_TYPE_CHAR) { + nm_sprintf_buf(numstr, "%d", (int) g_value_get_schar(&value)); + nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); + } else if (type == G_TYPE_BYTES) { + GBytes * bytes; + const guint8 *data; + gsize len = 0; + + bytes = g_value_get_boxed(&value); + data = bytes ? g_bytes_get_data(bytes, &len) : NULL; + + if (data != NULL && len > 0) + nm_keyfile_plugin_kf_set_integer_list_uint8(info->keyfile, + setting_info->setting_name, + key, + data, + len); + } else if (type == G_TYPE_STRV) { + char **array; + + array = (char **) g_value_get_boxed(&value); + nm_keyfile_plugin_kf_set_string_list(info->keyfile, + setting_info->setting_name, + key, + (const char **const) array, + g_strv_length(array)); + } else if (type == G_TYPE_HASH_TABLE) { + write_hash_of_string(info->keyfile, setting, key, &value); + } else if (type == G_TYPE_ARRAY) { + write_array_of_uint(info->keyfile, setting, key, &value); + } else if (G_VALUE_HOLDS_FLAGS(&value)) { + nm_sprintf_buf(numstr, "%u", g_value_get_flags(&value)); + nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); + } else if (G_VALUE_HOLDS_ENUM(&value)) { + nm_sprintf_buf(numstr, "%d", g_value_get_enum(&value)); + nm_keyfile_plugin_kf_set_value(info->keyfile, setting_info->setting_name, key, numstr); + } else + g_return_if_reached(); + +out_unset_value: + g_value_unset(&value); +} + +static void +_write_setting_wireguard(NMSetting *setting, KeyfileWriterInfo *info) +{ + NMSettingWireGuard *s_wg; + guint i_peer, n_peers; + + s_wg = NM_SETTING_WIREGUARD(setting); + + n_peers = nm_setting_wireguard_get_peers_len(s_wg); + for (i_peer = 0; i_peer < n_peers; i_peer++) { + NMWireGuardPeer * peer = nm_setting_wireguard_get_peer(s_wg, i_peer); + const char * public_key; + char group[NM_STRLEN(NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER) + 200]; + NMSettingSecretFlags secret_flags; + gboolean any_key = FALSE; + guint i_aip, n_aip; + const char * cstr; + guint32 u32; + + public_key = nm_wireguard_peer_get_public_key(peer); + if (!public_key || !public_key[0] + || !NM_STRCHAR_ALL(public_key, ch, nm_sd_utils_unbase64char(ch, TRUE) >= 0)) { + /* invalid peer. Skip it */ + continue; + } + + if (g_snprintf(group, + sizeof(group), + "%s%s", + NM_KEYFILE_GROUPPREFIX_WIREGUARD_PEER, + nm_wireguard_peer_get_public_key(peer)) + >= sizeof(group)) { + /* Too long. Not a valid public key. Skip the peer. */ + continue; + } + + cstr = nm_wireguard_peer_get_endpoint(peer); + if (cstr) { + g_key_file_set_string(info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, cstr); + any_key = TRUE; + } + + secret_flags = nm_wireguard_peer_get_preshared_key_flags(peer); + if (_secret_flags_persist_secret(secret_flags)) { + cstr = nm_wireguard_peer_get_preshared_key(peer); + if (cstr) { + g_key_file_set_string(info->keyfile, + group, + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, + cstr); + any_key = TRUE; + } + } + + /* usually, we don't persist the secret-flags 0 (because they are the default). + * For WireGuard peers, the default secret-flags for preshared-key are 4 (not-required). + * So, in this case behave differently: a missing preshared-key-flag setting means + * "not-required". */ + if (secret_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) { + g_key_file_set_int64(info->keyfile, + group, + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, + secret_flags); + any_key = TRUE; + } + + u32 = nm_wireguard_peer_get_persistent_keepalive(peer); + if (u32) { + g_key_file_set_uint64(info->keyfile, + group, + NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, + u32); + any_key = TRUE; + } + + n_aip = nm_wireguard_peer_get_allowed_ips_len(peer); + if (n_aip > 0) { + gs_free const char **strv = NULL; + + strv = g_new(const char *, ((gsize) n_aip) + 1); + for (i_aip = 0; i_aip < n_aip; i_aip++) + strv[i_aip] = nm_wireguard_peer_get_allowed_ip(peer, i_aip, NULL); + strv[n_aip] = NULL; + g_key_file_set_string_list(info->keyfile, + group, + NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, + strv, + n_aip); + any_key = TRUE; + } + + if (!any_key) { + /* we cannot omit all keys. At an empty endpoint. */ + g_key_file_set_string(info->keyfile, group, NM_WIREGUARD_PEER_ATTR_ENDPOINT, ""); + } + } +} + +/** + * nm_keyfile_write: + * @connection: the #NMConnection to persist to keyfile. + * @handler_flags: the #NMKeyfileHandlerFlags. + * @handler: (allow-none) (scope call): optional handler for events and + * to override the default behavior. + * @user_data: argument for @handler. + * @error: the #GError in case writing fails. + * + * @connection must verify as a valid profile according to + * nm_connection_verify(). + * + * Returns: (transfer full): a new #GKeyFile or %NULL on error. + * + * Since: 1.30 + */ +GKeyFile * +nm_keyfile_write(NMConnection * connection, + NMKeyfileHandlerFlags handler_flags, + NMKeyfileWriteHandler handler, + void * user_data, + GError ** error) +{ + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + GError * local = NULL; + KeyfileWriterInfo info; + NMSetting ** settings; + int i; + guint j; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + g_return_val_if_fail(!error || !*error, NULL); + g_return_val_if_fail(handler_flags == NM_KEYFILE_HANDLER_FLAGS_NONE, NULL); + + /* Technically, we might not require that a profile is valid in + * order to serialize it. Like also nm_keyfile_read() does not + * ensure that the read profile validates. + * + * However, if the profile does not validate, then there might be + * unexpected edge cases when we try to serialize it. Edge cases + * that might result in dangerous crash. + * + * So, for now we require valid profiles. */ + if (!nm_connection_verify(connection, error ? &local : NULL)) { + if (error) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("the profile is not valid: %s"), + local->message); + g_error_free(local); + } else + nm_assert(!local); + return NULL; + } + + keyfile = g_key_file_new(); + + info = (KeyfileWriterInfo){ + .connection = connection, + .keyfile = keyfile, + .error = NULL, + .write_handler = handler, + .user_data = user_data, + }; + + settings = _nm_connection_get_settings_arr(connection); + for (i = 0; i < (int) _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting * setting = settings[nm_meta_setting_types_by_priority[i]]; + const NMSettInfoSetting *sett_info; + const char * setting_name; + const char * setting_alias; + + if (!setting) + continue; + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); + + setting_name = sett_info->setting_class->setting_info->setting_name; + + if (sett_info->detail.gendata_info) { + guint k, n_keys; + const char *const *keys; + + nm_assert(!nm_keyfile_plugin_get_alias_for_setting_name( + sett_info->setting_class->setting_info->setting_name)); + + n_keys = _nm_setting_option_get_all(setting, &keys, NULL); + + if (n_keys > 0) { + GHashTable *h = _nm_setting_option_hash(setting, FALSE); + + for (k = 0; k < n_keys; k++) { + const char *key = keys[k]; + GVariant * v; + + v = g_hash_table_lookup(h, key); + + if (g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) { + g_key_file_set_boolean(info.keyfile, + setting_name, + key, + g_variant_get_boolean(v)); + } else if (g_variant_is_of_type(v, G_VARIANT_TYPE_UINT32)) { + g_key_file_set_uint64(info.keyfile, + setting_name, + key, + (guint64) g_variant_get_uint32(v)); + } else { + /* BUG: The variant type is not implemented. Since the connection + * verifies, this can only mean we either wrongly didn't reject + * the connection as invalid, or we didn't properly implement the + * variant type. */ + nm_assert_not_reached(); + continue; + } + } + } + } + + for (j = 0; j < sett_info->property_infos_len; j++) { + const NMSettInfoProperty *property_info = + _nm_sett_info_property_info_get_sorted(sett_info, j); + + write_setting_value(&info, setting, property_info); + if (info.error) + goto out_with_info_error; + } + + setting_alias = nm_keyfile_plugin_get_alias_for_setting_name(setting_name); + if ((setting_alias && g_key_file_has_group(info.keyfile, setting_alias)) + || g_key_file_has_group(info.keyfile, setting_name)) { + /* we have a section for the setting. Nothing to do. */ + } else { + /* ensure the group is present. There is no API for that, so add and remove + * a dummy key. */ + g_key_file_set_value(info.keyfile, setting_alias ?: setting_name, ".X", "1"); + g_key_file_remove_key(info.keyfile, setting_alias ?: setting_name, ".X", NULL); + } + + if (NM_IS_SETTING_WIREGUARD(setting)) { + _write_setting_wireguard(setting, &info); + if (info.error) + goto out_with_info_error; + } + + nm_assert(!info.error); + } + + nm_assert(!info.error); + + return g_steal_pointer(&keyfile); + +out_with_info_error: + g_propagate_error(error, info.error); + return NULL; +} + +/*****************************************************************************/ + +static const char temp_letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* + * Check '.[a-zA-Z0-9]{6}' file suffix used for temporary files by g_file_set_contents() (mkstemp()). + */ +static gboolean +check_mkstemp_suffix(const char *path) +{ + const char *ptr; + + nm_assert(path); + + /* Matches *.[a-zA-Z0-9]{6} suffix of mkstemp()'s temporary files */ + ptr = strrchr(path, '.'); + if (ptr && strspn(&ptr[1], temp_letters) == 6 && ptr[7] == '\0') + return TRUE; + return FALSE; +} + +#define SWP_TAG ".swp" +#define SWPX_TAG ".swpx" +#define PEM_TAG ".pem" +#define DER_TAG ".der" + +gboolean +nm_keyfile_utils_ignore_filename(const char *filename, gboolean require_extension) +{ + const char *base; + gsize l; + + /* ignore_filename() must mirror nm_keyfile_utils_create_filename() */ + + g_return_val_if_fail(filename, TRUE); + + base = strrchr(filename, '/'); + if (base) + base++; + else + base = filename; + + if (!base[0]) { + /* this check above with strrchr() also rejects "/some/path/with/trailing/slash/", + * but that is fine, because such a path would name a directory, and we are not + * interested in directories. */ + return TRUE; + } + + if (base[0] == '.') { + /* don't allow hidden files */ + return TRUE; + } + + if (require_extension) { + if (!NM_STR_HAS_SUFFIX_WITH_MORE(base, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION)) + return TRUE; + return FALSE; + } + + l = strlen(base); + + /* Ignore backup files */ + if (base[l - 1] == '~') + return TRUE; + + /* Ignore temporary files + * + * This check is also important to ignore .nmload files (see + * %NM_KEYFILE_PATH_SUFFIX_NMMETA). */ + if (check_mkstemp_suffix(base)) + return TRUE; + + /* Ignore 802.1x certificates and keys */ + if (NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(base, PEM_TAG) + || NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(base, DER_TAG)) + return TRUE; + + return FALSE; +} + +char * +nm_keyfile_utils_create_filename(const char *name, gboolean with_extension) +{ + /* keyfile used to escape with '*', do not change that behavior. + * + * But for newly added escapings, use '_' instead. + * Also, @with_extension is new-style. */ + const char ESCAPE_CHAR = with_extension ? '_' : '*'; + const char ESCAPE_CHAR2 = '_'; + NMStrBuf str; + char * p; + gsize len; + gsize i; + + g_return_val_if_fail(name && name[0], NULL); + + nm_str_buf_init(&str, 0, FALSE); + + len = strlen(name); + + p = nm_str_buf_append_len0(&str, name, len); + + /* Convert '/' to ESCAPE_CHAR */ + for (i = 0; i < len; i++) { + if (p[i] == '/') + p[i] = ESCAPE_CHAR; + } + + /* nm_keyfile_utils_create_filename() must avoid anything that ignore_filename() would reject. + * We can escape here more aggressively then what we would read back. */ + if (p[0] == '.') + p[0] = ESCAPE_CHAR2; + if (p[str.len - 1] == '~') + p[str.len - 1] = ESCAPE_CHAR2; + + if (check_mkstemp_suffix(p) || NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(p, PEM_TAG) + || NM_STR_HAS_SUFFIX_ASCII_CASE_WITH_MORE(p, DER_TAG)) + nm_str_buf_append_c(&str, ESCAPE_CHAR2); + + if (with_extension) + nm_str_buf_append(&str, NM_KEYFILE_PATH_SUFFIX_NMCONNECTION); + + p = nm_str_buf_finalize(&str, NULL); + + /* nm_keyfile_utils_create_filename() must mirror ignore_filename() */ + nm_assert(!strchr(p, '/')); + nm_assert(!nm_keyfile_utils_ignore_filename(p, with_extension)); + + return p; +} + +/*****************************************************************************/ + +/** + * nm_keyfile_handler_data_fail_with_error: + * @handler_data: the #NMKeyfileHandlerData + * @src: (transfer full): error to move into the return location + * + * Set the error for the handler. This lets the operation fail + * with the provided error. You may only set the error once. + * + * @src must be non-%NULL. + * + * Note that @src is no longer valid after this call. If you want + * to keep using the same GError*, you need to set it to %NULL + * after calling this function on it. + * + * Since: 1.30 + */ +void +nm_keyfile_handler_data_fail_with_error(NMKeyfileHandlerData *handler_data, GError *src) +{ + g_return_if_fail(handler_data); + g_return_if_fail(handler_data->p_error && !*handler_data->p_error); + g_return_if_fail(src); + + *handler_data->p_error = src; +} + +/** + * nm_keyfile_handler_data_get_context: + * @handler_data: the #NMKeyfileHandlerData for any event. + * @out_kf_group_name: (out) (allow-none) (transfer none): if the event is in the + * context of a keyfile group, the group name. + * @out_kf_key_name: (out) (allow-none) (transfer none): if the event is in the + * context of a keyfile value, the key name. + * @out_cur_setting: (out) (allow-none) (transfer none): if the event happens while + * handling a particular #NMSetting instance. + * @out_cur_property_name: (out) (allow-none) (transfer none): the property name if applicable. + * + * Get context information of the current event. This function can be called + * on all events, but the context information may be unset. + * + * Since: 1.30 + */ +void +nm_keyfile_handler_data_get_context(const NMKeyfileHandlerData *handler_data, + const char ** out_kf_group_name, + const char ** out_kf_key_name, + NMSetting ** out_cur_setting, + const char ** out_cur_property_name) +{ + g_return_if_fail(handler_data); + + NM_SET_OUT(out_kf_group_name, handler_data->kf_group_name); + NM_SET_OUT(out_kf_key_name, handler_data->kf_key); + NM_SET_OUT(out_cur_setting, handler_data->cur_setting); + NM_SET_OUT(out_cur_property_name, handler_data->cur_property); +} + +const char * +_nm_keyfile_handler_data_warn_get_message(const NMKeyfileHandlerData *handler_data) +{ + nm_assert(handler_data); + nm_assert(handler_data->type == NM_KEYFILE_HANDLER_TYPE_WARN); + + if (!handler_data->warn.message) { + /* we cast the const away. @handler_data is const w.r.t. visible mutations + * from POV of the user. Internally, we construct the message in + * a lazy manner. It's like a mutable field in C++. */ + NM_PRAGMA_WARNING_DISABLE("-Wformat-nonliteral") + ((NMKeyfileHandlerData *) handler_data)->warn.message = + g_strdup_vprintf(handler_data->warn.fmt, + ((NMKeyfileHandlerData *) handler_data)->warn.ap); + NM_PRAGMA_WARNING_REENABLE + } + return handler_data->warn.message; +} + +/** + * nm_keyfile_handler_data_warn_get: + * @handler_data: the #NMKeyfileHandlerData for a %NM_KEYFILE_HANDLER_TYPE_WARN + * event. + * @out_message: (out) (allow-none) (transfer none): the warning message. + * @out_severity: (out) (allow-none): the #NMKeyfileWarnSeverity warning severity. + * + * Since: 1.30 + */ +void +nm_keyfile_handler_data_warn_get(const NMKeyfileHandlerData *handler_data, + const char ** out_message, + NMKeyfileWarnSeverity * out_severity) +{ + g_return_if_fail(handler_data); + g_return_if_fail(handler_data->type == NM_KEYFILE_HANDLER_TYPE_WARN); + + NM_SET_OUT(out_message, _nm_keyfile_handler_data_warn_get_message(handler_data)); + NM_SET_OUT(out_severity, handler_data->warn.severity); +} diff --git a/src/libnm-core-impl/nm-meta-setting-base-impl.c b/src/libnm-core-impl/nm-meta-setting-base-impl.c new file mode 100644 index 00000000..a952ced9 --- /dev/null +++ b/src/libnm-core-impl/nm-meta-setting-base-impl.c @@ -0,0 +1,717 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 - 2018 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-meta-setting-base.h" + +#if _NM_META_SETTING_BASE_IMPL_LIBNM + #include "libnm-core-impl/nm-default-libnm-core.h" +#endif + +#include "nm-setting-6lowpan.h" +#include "nm-setting-8021x.h" +#include "nm-setting-adsl.h" +#include "nm-setting-bluetooth.h" +#include "nm-setting-bond.h" +#include "nm-setting-bridge-port.h" +#include "nm-setting-bridge.h" +#include "nm-setting-cdma.h" +#include "nm-setting-connection.h" +#include "nm-setting-dcb.h" +#include "nm-setting-dummy.h" +#include "nm-setting-ethtool.h" +#include "nm-setting-generic.h" +#include "nm-setting-gsm.h" +#include "nm-setting-hostname.h" +#include "nm-setting-infiniband.h" +#include "nm-setting-ip-config.h" +#include "nm-setting-ip-tunnel.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-macsec.h" +#include "nm-setting-macvlan.h" +#include "nm-setting-match.h" +#include "nm-setting-olpc-mesh.h" +#include "nm-setting-ovs-bridge.h" +#include "nm-setting-ovs-interface.h" +#include "nm-setting-ovs-dpdk.h" +#include "nm-setting-ovs-external-ids.h" +#include "nm-setting-ovs-patch.h" +#include "nm-setting-ovs-port.h" +#include "nm-setting-ppp.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-proxy.h" +#include "nm-setting-serial.h" +#include "nm-setting-tc-config.h" +#include "nm-setting-team-port.h" +#include "nm-setting-team.h" +#include "nm-setting-tun.h" +#include "nm-setting-user.h" +#include "nm-setting-veth.h" +#include "nm-setting-vlan.h" +#include "nm-setting-vpn.h" +#include "nm-setting-vrf.h" +#include "nm-setting-vxlan.h" +#include "nm-setting-wifi-p2p.h" +#include "nm-setting-wimax.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireguard.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wpan.h" + +#if _NM_META_SETTING_BASE_IMPL_LIBNM + #include "nm-setting-private.h" +#endif + +/*****************************************************************************/ + +const NMSetting8021xSchemeVtable nm_setting_8021x_scheme_vtable[] = { + +#define _D(_scheme_type, ...) [(_scheme_type)] = {.scheme_type = (_scheme_type), __VA_ARGS__} + + _D(NM_SETTING_802_1X_SCHEME_TYPE_UNKNOWN), + + _D(NM_SETTING_802_1X_SCHEME_TYPE_CA_CERT, + .setting_key = NM_SETTING_802_1X_CA_CERT, + .scheme_func = nm_setting_802_1x_get_ca_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_ca_cert_path, + .blob_func = nm_setting_802_1x_get_ca_cert_blob, + .uri_func = nm_setting_802_1x_get_ca_cert_uri, + .passwd_func = nm_setting_802_1x_get_ca_cert_password, + .pwflag_func = nm_setting_802_1x_get_ca_cert_password_flags, + .set_cert_func = nm_setting_802_1x_set_ca_cert, + .file_suffix = "ca-cert", ), + + _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CA_CERT, + .setting_key = NM_SETTING_802_1X_PHASE2_CA_CERT, + .scheme_func = nm_setting_802_1x_get_phase2_ca_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_phase2_ca_cert_path, + .blob_func = nm_setting_802_1x_get_phase2_ca_cert_blob, + .uri_func = nm_setting_802_1x_get_phase2_ca_cert_uri, + .passwd_func = nm_setting_802_1x_get_phase2_ca_cert_password, + .pwflag_func = nm_setting_802_1x_get_phase2_ca_cert_password_flags, + .set_cert_func = nm_setting_802_1x_set_phase2_ca_cert, + .file_suffix = "inner-ca-cert", ), + + _D(NM_SETTING_802_1X_SCHEME_TYPE_CLIENT_CERT, + .setting_key = NM_SETTING_802_1X_CLIENT_CERT, + .scheme_func = nm_setting_802_1x_get_client_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_client_cert_path, + .blob_func = nm_setting_802_1x_get_client_cert_blob, + .uri_func = nm_setting_802_1x_get_client_cert_uri, + .passwd_func = nm_setting_802_1x_get_client_cert_password, + .pwflag_func = nm_setting_802_1x_get_client_cert_password_flags, + .set_cert_func = nm_setting_802_1x_set_client_cert, + .file_suffix = "client-cert", ), + + _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_CLIENT_CERT, + .setting_key = NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + .scheme_func = nm_setting_802_1x_get_phase2_client_cert_scheme, + .format_func = NULL, + .path_func = nm_setting_802_1x_get_phase2_client_cert_path, + .blob_func = nm_setting_802_1x_get_phase2_client_cert_blob, + .uri_func = nm_setting_802_1x_get_phase2_client_cert_uri, + .passwd_func = nm_setting_802_1x_get_phase2_client_cert_password, + .pwflag_func = nm_setting_802_1x_get_phase2_client_cert_password_flags, + .set_cert_func = nm_setting_802_1x_set_phase2_client_cert, + .file_suffix = "inner-client-cert", ), + + _D(NM_SETTING_802_1X_SCHEME_TYPE_PRIVATE_KEY, + .setting_key = NM_SETTING_802_1X_PRIVATE_KEY, + .scheme_func = nm_setting_802_1x_get_private_key_scheme, + .format_func = nm_setting_802_1x_get_private_key_format, + .path_func = nm_setting_802_1x_get_private_key_path, + .blob_func = nm_setting_802_1x_get_private_key_blob, + .uri_func = nm_setting_802_1x_get_private_key_uri, + .passwd_func = nm_setting_802_1x_get_private_key_password, + .pwflag_func = nm_setting_802_1x_get_private_key_password_flags, + .set_private_key_func = nm_setting_802_1x_set_private_key, + .file_suffix = "private-key", + .is_secret = TRUE, ), + + _D(NM_SETTING_802_1X_SCHEME_TYPE_PHASE2_PRIVATE_KEY, + .setting_key = NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + .scheme_func = nm_setting_802_1x_get_phase2_private_key_scheme, + .format_func = nm_setting_802_1x_get_phase2_private_key_format, + .path_func = nm_setting_802_1x_get_phase2_private_key_path, + .blob_func = nm_setting_802_1x_get_phase2_private_key_blob, + .uri_func = nm_setting_802_1x_get_phase2_private_key_uri, + .passwd_func = nm_setting_802_1x_get_phase2_private_key_password, + .pwflag_func = nm_setting_802_1x_get_phase2_private_key_password_flags, + .set_private_key_func = nm_setting_802_1x_set_phase2_private_key, + .file_suffix = "inner-private-key", + .is_secret = TRUE, ), + +#undef _D +}; + +/*****************************************************************************/ + +const NMMetaSettingInfo nm_meta_setting_infos[] = { + [NM_META_SETTING_TYPE_6LOWPAN] = + { + .meta_type = NM_META_SETTING_TYPE_6LOWPAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_6LOWPAN_SETTING_NAME, + .get_setting_gtype = nm_setting_6lowpan_get_type, + }, + [NM_META_SETTING_TYPE_802_1X] = + { + .meta_type = NM_META_SETTING_TYPE_802_1X, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, + .setting_name = NM_SETTING_802_1X_SETTING_NAME, + .get_setting_gtype = nm_setting_802_1x_get_type, + }, + [NM_META_SETTING_TYPE_ADSL] = + { + .meta_type = NM_META_SETTING_TYPE_ADSL, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_ADSL_SETTING_NAME, + .get_setting_gtype = nm_setting_adsl_get_type, + }, + [NM_META_SETTING_TYPE_BLUETOOTH] = + { + .meta_type = NM_META_SETTING_TYPE_BLUETOOTH, + .setting_priority = NM_SETTING_PRIORITY_HW_NON_BASE, + .setting_name = NM_SETTING_BLUETOOTH_SETTING_NAME, + .get_setting_gtype = nm_setting_bluetooth_get_type, + }, + [NM_META_SETTING_TYPE_BOND] = + { + .meta_type = NM_META_SETTING_TYPE_BOND, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_BOND_SETTING_NAME, + .get_setting_gtype = nm_setting_bond_get_type, + }, + [NM_META_SETTING_TYPE_BRIDGE] = + { + .meta_type = NM_META_SETTING_TYPE_BRIDGE, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_BRIDGE_SETTING_NAME, + .get_setting_gtype = nm_setting_bridge_get_type, + }, + [NM_META_SETTING_TYPE_BRIDGE_PORT] = + { + .meta_type = NM_META_SETTING_TYPE_BRIDGE_PORT, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_BRIDGE_PORT_SETTING_NAME, + .get_setting_gtype = nm_setting_bridge_port_get_type, + }, + [NM_META_SETTING_TYPE_CDMA] = + { + .meta_type = NM_META_SETTING_TYPE_CDMA, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_CDMA_SETTING_NAME, + .get_setting_gtype = nm_setting_cdma_get_type, + }, + [NM_META_SETTING_TYPE_CONNECTION] = + { + .meta_type = NM_META_SETTING_TYPE_CONNECTION, + .setting_priority = NM_SETTING_PRIORITY_CONNECTION, + .setting_name = NM_SETTING_CONNECTION_SETTING_NAME, + .get_setting_gtype = nm_setting_connection_get_type, + }, + [NM_META_SETTING_TYPE_DCB] = + { + .meta_type = NM_META_SETTING_TYPE_DCB, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, + .setting_name = NM_SETTING_DCB_SETTING_NAME, + .get_setting_gtype = nm_setting_dcb_get_type, + }, + [NM_META_SETTING_TYPE_DUMMY] = + { + .meta_type = NM_META_SETTING_TYPE_DUMMY, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_DUMMY_SETTING_NAME, + .get_setting_gtype = nm_setting_dummy_get_type, + }, + [NM_META_SETTING_TYPE_ETHTOOL] = + { + .meta_type = NM_META_SETTING_TYPE_ETHTOOL, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_ETHTOOL_SETTING_NAME, + .get_setting_gtype = nm_setting_ethtool_get_type, + }, + [NM_META_SETTING_TYPE_GENERIC] = + { + .meta_type = NM_META_SETTING_TYPE_GENERIC, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_GENERIC_SETTING_NAME, + .get_setting_gtype = nm_setting_generic_get_type, + }, + [NM_META_SETTING_TYPE_GSM] = + { + .meta_type = NM_META_SETTING_TYPE_GSM, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_GSM_SETTING_NAME, + .get_setting_gtype = nm_setting_gsm_get_type, + }, + [NM_META_SETTING_TYPE_HOSTNAME] = + { + .meta_type = NM_META_SETTING_TYPE_HOSTNAME, + .setting_priority = NM_SETTING_PRIORITY_IP, + .setting_name = NM_SETTING_HOSTNAME_SETTING_NAME, + .get_setting_gtype = nm_setting_hostname_get_type, + }, + [NM_META_SETTING_TYPE_INFINIBAND] = + { + .meta_type = NM_META_SETTING_TYPE_INFINIBAND, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_INFINIBAND_SETTING_NAME, + .get_setting_gtype = nm_setting_infiniband_get_type, + }, + [NM_META_SETTING_TYPE_IP4_CONFIG] = + { + .meta_type = NM_META_SETTING_TYPE_IP4_CONFIG, + .setting_priority = NM_SETTING_PRIORITY_IP, + .setting_name = NM_SETTING_IP4_CONFIG_SETTING_NAME, + .get_setting_gtype = nm_setting_ip4_config_get_type, + }, + [NM_META_SETTING_TYPE_IP6_CONFIG] = + { + .meta_type = NM_META_SETTING_TYPE_IP6_CONFIG, + .setting_priority = NM_SETTING_PRIORITY_IP, + .setting_name = NM_SETTING_IP6_CONFIG_SETTING_NAME, + .get_setting_gtype = nm_setting_ip6_config_get_type, + }, + [NM_META_SETTING_TYPE_IP_TUNNEL] = + { + .meta_type = NM_META_SETTING_TYPE_IP_TUNNEL, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_IP_TUNNEL_SETTING_NAME, + .get_setting_gtype = nm_setting_ip_tunnel_get_type, + }, + [NM_META_SETTING_TYPE_MACSEC] = + { + .meta_type = NM_META_SETTING_TYPE_MACSEC, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_MACSEC_SETTING_NAME, + .get_setting_gtype = nm_setting_macsec_get_type, + }, + [NM_META_SETTING_TYPE_MACVLAN] = + { + .meta_type = NM_META_SETTING_TYPE_MACVLAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_MACVLAN_SETTING_NAME, + .get_setting_gtype = nm_setting_macvlan_get_type, + }, + [NM_META_SETTING_TYPE_MATCH] = + { + .meta_type = NM_META_SETTING_TYPE_MATCH, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_MATCH_SETTING_NAME, + .get_setting_gtype = nm_setting_match_get_type, + }, + [NM_META_SETTING_TYPE_OLPC_MESH] = + { + .meta_type = NM_META_SETTING_TYPE_OLPC_MESH, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_OLPC_MESH_SETTING_NAME, + .get_setting_gtype = nm_setting_olpc_mesh_get_type, + }, + [NM_META_SETTING_TYPE_OVS_BRIDGE] = + { + .meta_type = NM_META_SETTING_TYPE_OVS_BRIDGE, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_OVS_BRIDGE_SETTING_NAME, + .get_setting_gtype = nm_setting_ovs_bridge_get_type, + }, + [NM_META_SETTING_TYPE_OVS_DPDK] = + { + .meta_type = NM_META_SETTING_TYPE_OVS_DPDK, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_OVS_DPDK_SETTING_NAME, + .get_setting_gtype = nm_setting_ovs_dpdk_get_type, + }, + [NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS] = + { + .meta_type = NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME, + .get_setting_gtype = nm_setting_ovs_external_ids_get_type, + }, + [NM_META_SETTING_TYPE_OVS_INTERFACE] = + { + .meta_type = NM_META_SETTING_TYPE_OVS_INTERFACE, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_OVS_INTERFACE_SETTING_NAME, + .get_setting_gtype = nm_setting_ovs_interface_get_type, + }, + [NM_META_SETTING_TYPE_OVS_PATCH] = + { + .meta_type = NM_META_SETTING_TYPE_OVS_PATCH, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_OVS_PATCH_SETTING_NAME, + .get_setting_gtype = nm_setting_ovs_patch_get_type, + }, + [NM_META_SETTING_TYPE_OVS_PORT] = + { + .meta_type = NM_META_SETTING_TYPE_OVS_PORT, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_OVS_PORT_SETTING_NAME, + .get_setting_gtype = nm_setting_ovs_port_get_type, + }, + [NM_META_SETTING_TYPE_PPPOE] = + { + .meta_type = NM_META_SETTING_TYPE_PPPOE, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_PPPOE_SETTING_NAME, + .get_setting_gtype = nm_setting_pppoe_get_type, + }, + [NM_META_SETTING_TYPE_PPP] = + { + .meta_type = NM_META_SETTING_TYPE_PPP, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_PPP_SETTING_NAME, + .get_setting_gtype = nm_setting_ppp_get_type, + }, + [NM_META_SETTING_TYPE_PROXY] = + { + .meta_type = NM_META_SETTING_TYPE_PROXY, + .setting_priority = NM_SETTING_PRIORITY_IP, + .setting_name = NM_SETTING_PROXY_SETTING_NAME, + .get_setting_gtype = nm_setting_proxy_get_type, + }, + [NM_META_SETTING_TYPE_SERIAL] = + { + .meta_type = NM_META_SETTING_TYPE_SERIAL, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, + .setting_name = NM_SETTING_SERIAL_SETTING_NAME, + .get_setting_gtype = nm_setting_serial_get_type, + }, + [NM_META_SETTING_TYPE_SRIOV] = + { + .meta_type = NM_META_SETTING_TYPE_SRIOV, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, + .setting_name = NM_SETTING_SRIOV_SETTING_NAME, + .get_setting_gtype = nm_setting_sriov_get_type, + }, + [NM_META_SETTING_TYPE_TC_CONFIG] = + { + .meta_type = NM_META_SETTING_TYPE_TC_CONFIG, + .setting_priority = NM_SETTING_PRIORITY_IP, + .setting_name = NM_SETTING_TC_CONFIG_SETTING_NAME, + .get_setting_gtype = nm_setting_tc_config_get_type, + }, + [NM_META_SETTING_TYPE_TEAM] = + { + .meta_type = NM_META_SETTING_TYPE_TEAM, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_TEAM_SETTING_NAME, + .get_setting_gtype = nm_setting_team_get_type, + }, + [NM_META_SETTING_TYPE_TEAM_PORT] = + { + .meta_type = NM_META_SETTING_TYPE_TEAM_PORT, + .setting_priority = NM_SETTING_PRIORITY_AUX, + .setting_name = NM_SETTING_TEAM_PORT_SETTING_NAME, + .get_setting_gtype = nm_setting_team_port_get_type, + }, + [NM_META_SETTING_TYPE_TUN] = + { + .meta_type = NM_META_SETTING_TYPE_TUN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_TUN_SETTING_NAME, + .get_setting_gtype = nm_setting_tun_get_type, + }, + [NM_META_SETTING_TYPE_USER] = + { + .meta_type = NM_META_SETTING_TYPE_USER, + .setting_priority = NM_SETTING_PRIORITY_USER, + .setting_name = NM_SETTING_USER_SETTING_NAME, + .get_setting_gtype = nm_setting_user_get_type, + }, + [NM_META_SETTING_TYPE_VETH] = + { + .meta_type = NM_META_SETTING_TYPE_VETH, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_VETH_SETTING_NAME, + .get_setting_gtype = nm_setting_veth_get_type, + }, + [NM_META_SETTING_TYPE_VLAN] = + { + .meta_type = NM_META_SETTING_TYPE_VLAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_VLAN_SETTING_NAME, + .get_setting_gtype = nm_setting_vlan_get_type, + }, + [NM_META_SETTING_TYPE_VPN] = + { + .meta_type = NM_META_SETTING_TYPE_VPN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_VPN_SETTING_NAME, + .get_setting_gtype = nm_setting_vpn_get_type, + }, + [NM_META_SETTING_TYPE_VRF] = + { + .meta_type = NM_META_SETTING_TYPE_VRF, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_VRF_SETTING_NAME, + .get_setting_gtype = nm_setting_vrf_get_type, + }, + [NM_META_SETTING_TYPE_VXLAN] = + { + .meta_type = NM_META_SETTING_TYPE_VXLAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_VXLAN_SETTING_NAME, + .get_setting_gtype = nm_setting_vxlan_get_type, + }, + [NM_META_SETTING_TYPE_WIFI_P2P] = + { + .meta_type = NM_META_SETTING_TYPE_WIFI_P2P, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_WIFI_P2P_SETTING_NAME, + .get_setting_gtype = nm_setting_wifi_p2p_get_type, + }, + [NM_META_SETTING_TYPE_WIMAX] = + { + .meta_type = NM_META_SETTING_TYPE_WIMAX, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_WIMAX_SETTING_NAME, + .get_setting_gtype = nm_setting_wimax_get_type, + }, + [NM_META_SETTING_TYPE_WIRED] = + { + .meta_type = NM_META_SETTING_TYPE_WIRED, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_WIRED_SETTING_NAME, + .get_setting_gtype = nm_setting_wired_get_type, + }, + [NM_META_SETTING_TYPE_WIREGUARD] = + { + .meta_type = NM_META_SETTING_TYPE_WIREGUARD, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_WIREGUARD_SETTING_NAME, + .get_setting_gtype = nm_setting_wireguard_get_type, + }, + [NM_META_SETTING_TYPE_WIRELESS] = + { + .meta_type = NM_META_SETTING_TYPE_WIRELESS, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_WIRELESS_SETTING_NAME, + .get_setting_gtype = nm_setting_wireless_get_type, + }, + [NM_META_SETTING_TYPE_WIRELESS_SECURITY] = + { + .meta_type = NM_META_SETTING_TYPE_WIRELESS_SECURITY, + .setting_priority = NM_SETTING_PRIORITY_HW_AUX, + .setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + .get_setting_gtype = nm_setting_wireless_security_get_type, + }, + [NM_META_SETTING_TYPE_WPAN] = + { + .meta_type = NM_META_SETTING_TYPE_WPAN, + .setting_priority = NM_SETTING_PRIORITY_HW_BASE, + .setting_name = NM_SETTING_WPAN_SETTING_NAME, + .get_setting_gtype = nm_setting_wpan_get_type, + }, + + [NM_META_SETTING_TYPE_UNKNOWN] = + { + .meta_type = NM_META_SETTING_TYPE_UNKNOWN, + }, +}; + +const NMMetaSettingType nm_meta_setting_types_by_priority[] = { + + /* NM_SETTING_PRIORITY_CONNECTION */ + NM_META_SETTING_TYPE_CONNECTION, + + /* NM_SETTING_PRIORITY_HW_BASE */ + NM_META_SETTING_TYPE_6LOWPAN, + NM_META_SETTING_TYPE_OLPC_MESH, + NM_META_SETTING_TYPE_WIRELESS, + NM_META_SETTING_TYPE_WIRED, + NM_META_SETTING_TYPE_ADSL, + NM_META_SETTING_TYPE_BOND, + NM_META_SETTING_TYPE_BRIDGE, + NM_META_SETTING_TYPE_CDMA, + NM_META_SETTING_TYPE_DUMMY, + NM_META_SETTING_TYPE_GENERIC, + NM_META_SETTING_TYPE_GSM, + NM_META_SETTING_TYPE_INFINIBAND, + NM_META_SETTING_TYPE_IP_TUNNEL, + NM_META_SETTING_TYPE_MACSEC, + NM_META_SETTING_TYPE_MACVLAN, + NM_META_SETTING_TYPE_OVS_BRIDGE, + NM_META_SETTING_TYPE_OVS_DPDK, + NM_META_SETTING_TYPE_OVS_INTERFACE, + NM_META_SETTING_TYPE_OVS_PATCH, + NM_META_SETTING_TYPE_OVS_PORT, + NM_META_SETTING_TYPE_TEAM, + NM_META_SETTING_TYPE_TUN, + NM_META_SETTING_TYPE_VETH, + NM_META_SETTING_TYPE_VLAN, + NM_META_SETTING_TYPE_VPN, + NM_META_SETTING_TYPE_VRF, + NM_META_SETTING_TYPE_VXLAN, + NM_META_SETTING_TYPE_WIFI_P2P, + NM_META_SETTING_TYPE_WIMAX, + NM_META_SETTING_TYPE_WIREGUARD, + NM_META_SETTING_TYPE_WPAN, + + /* NM_SETTING_PRIORITY_HW_NON_BASE */ + NM_META_SETTING_TYPE_BLUETOOTH, + + /* NM_SETTING_PRIORITY_HW_AUX */ + NM_META_SETTING_TYPE_WIRELESS_SECURITY, + NM_META_SETTING_TYPE_802_1X, + NM_META_SETTING_TYPE_DCB, + NM_META_SETTING_TYPE_SERIAL, + NM_META_SETTING_TYPE_SRIOV, + + /* NM_SETTING_PRIORITY_AUX */ + NM_META_SETTING_TYPE_BRIDGE_PORT, + NM_META_SETTING_TYPE_ETHTOOL, + NM_META_SETTING_TYPE_MATCH, + NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS, + NM_META_SETTING_TYPE_PPP, + NM_META_SETTING_TYPE_PPPOE, + NM_META_SETTING_TYPE_TEAM_PORT, + + /* NM_SETTING_PRIORITY_IP */ + NM_META_SETTING_TYPE_HOSTNAME, + NM_META_SETTING_TYPE_IP4_CONFIG, + NM_META_SETTING_TYPE_IP6_CONFIG, + NM_META_SETTING_TYPE_PROXY, + NM_META_SETTING_TYPE_TC_CONFIG, + + /* NM_SETTING_PRIORITY_USER */ + NM_META_SETTING_TYPE_USER, +}; + +const NMMetaSettingInfo * +nm_meta_setting_infos_by_name(const char *name) +{ + gssize idx; + + if (NM_MORE_ASSERTS > 10) { + guint i, j; + + for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { + const NMMetaSettingInfo *setting_info = &nm_meta_setting_infos[i]; + + nm_assert(setting_info->meta_type == (NMMetaSettingType) i); + nm_assert(setting_info->setting_name); + nm_assert(setting_info->setting_name[0]); + nm_assert(setting_info->get_setting_gtype); + nm_assert(setting_info->setting_priority != NM_SETTING_PRIORITY_INVALID); + if (i > 0 + && strcmp(nm_meta_setting_infos[i - 1].setting_name, setting_info->setting_name) + >= 0) { + g_error("nm_meta_setting_infos[%u, \"%s\"] is wrongly sorted before " + "nm_meta_setting_infos[%u, \"%s\"]. Rearange NMMetaSettingType enum", + i - 1, + nm_meta_setting_infos[i - 1].setting_name, + i, + setting_info->setting_name); + } + for (j = 0; j < i; j++) { + const NMMetaSettingInfo *s = &nm_meta_setting_infos[j]; + + nm_assert(setting_info->get_setting_gtype != s->get_setting_gtype); + } + } + } + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMMetaSettingInfo, setting_name) == 0); + idx = nm_utils_array_find_binary_search(nm_meta_setting_infos, + sizeof(NMMetaSettingInfo), + _NM_META_SETTING_TYPE_NUM, + &name, + nm_strcmp_p_with_data, + NULL); + + return idx >= 0 ? &nm_meta_setting_infos[idx] : NULL; +} + +const NMMetaSettingInfo * +nm_meta_setting_infos_by_gtype(GType gtype) +{ +#if _NM_META_SETTING_BASE_IMPL_LIBNM + nm_auto_unref_gtypeclass GTypeClass *gtypeclass_unref = NULL; + GTypeClass * gtypeclass; + NMSettingClass * klass; + + if (!g_type_is_a(gtype, NM_TYPE_SETTING)) + goto out_none; + + gtypeclass = g_type_class_peek(gtype); + if (!gtypeclass) + gtypeclass = gtypeclass_unref = g_type_class_ref(gtype); + + nm_assert(NM_IS_SETTING_CLASS(gtypeclass)); + + klass = (NMSettingClass *) gtypeclass; + + if (!klass->setting_info) + goto out_none; + + nm_assert(klass->setting_info->get_setting_gtype); + nm_assert(klass->setting_info->get_setting_gtype() == gtype); + + return klass->setting_info; + +out_none: + + if (NM_MORE_ASSERTS > 10) { + int i; + + /* this might hint to a bug, but it would be expected for NM_TYPE_SETTING + * and NM_TYPE_SETTING_IP_CONFIG. + * + * Assert that we didn't lookup for a gtype, which we would expect to find. + * An assertion failure here, hints to a bug in nm_setting_*_class_init(). + */ + for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) + nm_assert(nm_meta_setting_infos[i].get_setting_gtype() != gtype); + } + + return NULL; +#else + guint i; + + for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { + if (nm_meta_setting_infos[i].get_setting_gtype() == gtype) + return &nm_meta_setting_infos[i]; + } + return NULL; +#endif +} + +/*****************************************************************************/ + +NMSettingPriority +nm_meta_setting_info_get_base_type_priority(const NMMetaSettingInfo *setting_info, GType gtype) +{ + /* Historical oddity: PPPoE is a base-type even though it's not + * priority 1. It needs to be sorted *after* lower-level stuff like + * Wi-Fi security or 802.1x for secrets, but it's still allowed as a + * base type. + */ + + if (setting_info) { + if (NM_IN_SET(setting_info->setting_priority, + NM_SETTING_PRIORITY_HW_BASE, + NM_SETTING_PRIORITY_HW_NON_BASE) + || gtype == NM_TYPE_SETTING_PPPOE) + return setting_info->setting_priority; + } + + return NM_SETTING_PRIORITY_INVALID; +} + +NMSettingPriority +_nm_setting_type_get_base_type_priority(GType type) +{ + return nm_meta_setting_info_get_base_type_priority(nm_meta_setting_infos_by_gtype(type), type); +} + +/*****************************************************************************/ diff --git a/src/libnm-core-impl/nm-property-compare.c b/src/libnm-core-impl/nm-property-compare.c new file mode 100644 index 00000000..4347d6d6 --- /dev/null +++ b/src/libnm-core-impl/nm-property-compare.c @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2014 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-property-compare.h" + +#include + +static int +_nm_property_compare_collection(GVariant *value1, GVariant *value2) +{ + GVariant *child1, *child2; + int i, len1, len2; + int ret; + + len1 = g_variant_n_children(value1); + len2 = g_variant_n_children(value2); + + if (len1 != len2) + return len1 < len2 ? -1 : len1 > len2; + + for (i = 0; i < len1; i++) { + child1 = g_variant_get_child_value(value1, i); + child2 = g_variant_get_child_value(value2, i); + + ret = nm_property_compare(child1, child2); + g_variant_unref(child1); + g_variant_unref(child2); + + if (ret) + return ret; + } + + return 0; +} + +static int +_nm_property_compare_vardict(GVariant *value1, GVariant *value2) +{ + GVariantIter iter; + int len1, len2; + const char * key; + GVariant * val1, *val2; + + len1 = g_variant_n_children(value1); + len2 = g_variant_n_children(value2); + + if (len1 != len2) + return len1 < len2 ? -1 : 1; + + g_variant_iter_init(&iter, value1); + while (g_variant_iter_next(&iter, "{&sv}", &key, &val1)) { + if (!g_variant_lookup(value2, key, "v", &val2)) { + g_variant_unref(val1); + return -1; + } + if (!g_variant_equal(val1, val2)) { + g_variant_unref(val1); + g_variant_unref(val2); + return -1; + } + g_variant_unref(val1); + g_variant_unref(val2); + } + + return 0; +} + +static int +_nm_property_compare_strdict(GVariant *value1, GVariant *value2) +{ + GVariantIter iter; + int len1, len2; + const char * key, *val1, *val2; + int ret; + + len1 = g_variant_n_children(value1); + len2 = g_variant_n_children(value2); + + if (len1 != len2) + return len1 < len2 ? -1 : len1 > len2; + + g_variant_iter_init(&iter, value1); + while (g_variant_iter_next(&iter, "{&s&s}", &key, &val1)) { + if (!g_variant_lookup(value2, key, "&s", &val2)) + return -1; + + ret = strcmp(val1, val2); + if (ret) + return ret; + } + + return 0; +} + +int +nm_property_compare(GVariant *value1, GVariant *value2) +{ + const GVariantType *type1; + const GVariantType *type2; + int ret; + + if (value1 == value2) + return 0; + if (!value1) + return 1; + if (!value2) + return -1; + + type1 = g_variant_get_type(value1); + type2 = g_variant_get_type(value2); + + if (!g_variant_type_equal(type1, type2)) + return type1 < type2 ? -1 : type1 > type2; + + if (g_variant_type_is_basic(type1)) + ret = g_variant_compare(value1, value2); + else if (g_variant_is_of_type(value1, G_VARIANT_TYPE("a{ss}"))) + ret = _nm_property_compare_strdict(value1, value2); + else if (g_variant_is_of_type(value1, G_VARIANT_TYPE("a{sv}"))) + ret = _nm_property_compare_vardict(value1, value2); + else if (g_variant_type_is_array(type1)) + ret = _nm_property_compare_collection(value1, value2); + else if (g_variant_type_is_tuple(type1)) + ret = _nm_property_compare_collection(value1, value2); + else { + g_warning("Don't know how to compare variant type '%s'", (const char *) type1); + ret = value1 == value2; + } + + return ret; +} diff --git a/src/libnm-core-impl/nm-property-compare.h b/src/libnm-core-impl/nm-property-compare.h new file mode 100644 index 00000000..d422b1c8 --- /dev/null +++ b/src/libnm-core-impl/nm-property-compare.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2014 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#ifndef __NM_PROPERTY_COMPARE_H__ +#define __NM_PROPERTY_COMPARE_H__ + +#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE) + #error Cannot use this header. +#endif + +int nm_property_compare(GVariant *value1, GVariant *value2); + +#endif /* __NM_PROPERTY_COMPARE_H__ */ diff --git a/src/libnm-core-impl/nm-setting-6lowpan.c b/src/libnm-core-impl/nm-setting-6lowpan.c new file mode 100644 index 00000000..c07fb38e --- /dev/null +++ b/src/libnm-core-impl/nm-setting-6lowpan.c @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-6lowpan.h" + +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-6lowpan + * @short_description: Describes connection properties for 6LoWPAN interfaces + * + * The #NMSetting6Lowpan object is a #NMSetting subclass that describes properties + * necessary for connection to 6LoWPAN interfaces. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT, ); + +typedef struct { + char *parent; +} NMSetting6LowpanPrivate; + +/** + * NMSetting6Lowpan: + * + * 6LoWPAN Settings + */ +struct _NMSetting6Lowpan { + NMSetting parent; +}; + +struct _NMSetting6LowpanClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSetting6Lowpan, nm_setting_6lowpan, NM_TYPE_SETTING) + +#define NM_SETTING_6LOWPAN_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_6LOWPAN, NMSetting6LowpanPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_6lowpan_get_parent: + * @setting: the #NMSetting6Lowpan + * + * Returns: the #NMSetting6Lowpan:parent property of the setting + * + * Since: 1.14 + **/ +const char * +nm_setting_6lowpan_get_parent(NMSetting6Lowpan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_6LOWPAN(setting), NULL); + return NM_SETTING_6LOWPAN_GET_PRIVATE(setting)->parent; +} + +/*********************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSetting6LowpanPrivate *priv = NM_SETTING_6LOWPAN_GET_PRIVATE(setting); + NMSettingConnection * s_con = NULL; + + if (connection) + s_con = nm_connection_get_setting_connection(connection); + + if (!priv->parent) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is not specified")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_6LOWPAN_SETTING_NAME, + NM_SETTING_6LOWPAN_PARENT); + return FALSE; + } + + if (nm_utils_is_uuid(priv->parent)) { + /* If we have an NMSettingConnection:master with slave-type="6lowpan", + * then it must be the same UUID. + */ + if (s_con) { + const char *master = NULL, *slave_type = NULL; + + slave_type = nm_setting_connection_get_slave_type(s_con); + if (!g_strcmp0(slave_type, NM_SETTING_6LOWPAN_SETTING_NAME)) + master = nm_setting_connection_get_master(s_con); + + if (master && g_strcmp0(priv->parent, master) != 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' value doesn't match '%s=%s'"), + priv->parent, + NM_SETTING_CONNECTION_MASTER, + master); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_6LOWPAN_SETTING_NAME, + NM_SETTING_6LOWPAN_PARENT); + return FALSE; + } + } + } else if (!nm_utils_iface_valid_name(priv->parent)) { + /* parent must be either a UUID or an interface name */ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is neither an UUID nor an interface name"), + priv->parent); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_6LOWPAN_SETTING_NAME, + NM_SETTING_6LOWPAN_PARENT); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSetting6Lowpan * setting = NM_SETTING_6LOWPAN(object); + NMSetting6LowpanPrivate *priv = NM_SETTING_6LOWPAN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_value_set_string(value, priv->parent); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSetting6Lowpan * setting = NM_SETTING_6LOWPAN(object); + NMSetting6LowpanPrivate *priv = NM_SETTING_6LOWPAN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_free(priv->parent); + priv->parent = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_6lowpan_init(NMSetting6Lowpan *setting) +{} + +/** + * nm_setting_6lowpan_new: + * + * Creates a new #NMSetting6Lowpan object with default values. + * + * Returns: (transfer full): the new empty #NMSetting6Lowpan object + * + * Since: 1.14 + **/ +NMSetting * +nm_setting_6lowpan_new(void) +{ + return g_object_new(NM_TYPE_SETTING_6LOWPAN, NULL); +} + +static void +finalize(GObject *object) +{ + NMSetting6Lowpan * setting = NM_SETTING_6LOWPAN(object); + NMSetting6LowpanPrivate *priv = NM_SETTING_6LOWPAN_GET_PRIVATE(setting); + + g_free(priv->parent); + + G_OBJECT_CLASS(nm_setting_6lowpan_parent_class)->finalize(object); +} + +static void +nm_setting_6lowpan_class_init(NMSetting6LowpanClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSetting6LowpanPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSetting6Lowpan:parent: + * + * If given, specifies the parent interface name or parent connection UUID + * from which this 6LowPAN interface should be created. + * + * Since: 1.14 + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_6LOWPAN_PARENT, + PROP_PARENT, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_6lowpan_get_parent); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_6LOWPAN, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c new file mode 100644 index 00000000..6ddbd680 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-8021x.c @@ -0,0 +1,4655 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-8021x.h" + +#include "libnm-glib-aux/nm-secret-utils.h" +#include "nm-utils.h" +#include "nm-crypto.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" +#include "nm-core-enum-types.h" + +/** + * SECTION:nm-setting-8021x + * @short_description: Describes 802.1x-authenticated connection properties + * + * The #NMSetting8021x object is a #NMSetting subclass that describes + * properties necessary for connection to 802.1x-authenticated networks, such as + * WPA and WPA2 Enterprise Wi-Fi networks and wired 802.1x networks. 802.1x + * connections typically use certificates and/or EAP authentication methods to + * securely verify, identify, and authenticate the client to the network itself, + * instead of simply relying on a widely shared static key. + * + * It's a good idea to read up on wpa_supplicant configuration before using this + * setting extensively, since most of the options here correspond closely with + * the relevant wpa_supplicant configuration options. + * + * Furthermore, to get a good idea of 802.1x, EAP, TLS, TTLS, etc and their + * applications to Wi-Fi and wired networks, you'll want to get copies of the + * following books. + * + * 802.11 Wireless Networks: The Definitive Guide, Second Edition + * Author: Matthew Gast + * ISBN: 978-0596100520 + * + * Cisco Wireless LAN Security + * Authors: Krishna Sankar, Sri Sundaralingam, Darrin Miller, and Andrew Balinsky + * ISBN: 978-1587051548 + **/ + +/*****************************************************************************/ + +static NMSetting8021xCKFormat +_crypto_format_to_ck(NMCryptoFileFormat format) +{ + G_STATIC_ASSERT((NM_SETTING_802_1X_CK_FORMAT_UNKNOWN + == (NMSetting8021xCKFormat) NM_CRYPTO_FILE_FORMAT_UNKNOWN)); + G_STATIC_ASSERT( + (NM_SETTING_802_1X_CK_FORMAT_X509 == (NMSetting8021xCKFormat) NM_CRYPTO_FILE_FORMAT_X509)); + G_STATIC_ASSERT((NM_SETTING_802_1X_CK_FORMAT_RAW_KEY + == (NMSetting8021xCKFormat) NM_CRYPTO_FILE_FORMAT_RAW_KEY)); + G_STATIC_ASSERT((NM_SETTING_802_1X_CK_FORMAT_PKCS12 + == (NMSetting8021xCKFormat) NM_CRYPTO_FILE_FORMAT_PKCS12)); + + nm_assert(NM_IN_SET(format, + NM_CRYPTO_FILE_FORMAT_UNKNOWN, + NM_CRYPTO_FILE_FORMAT_X509, + NM_CRYPTO_FILE_FORMAT_RAW_KEY, + NM_CRYPTO_FILE_FORMAT_PKCS12)); + return (NMSetting8021xCKFormat) format; +} + +/*****************************************************************************/ + +typedef void (*EAPMethodNeedSecretsFunc)(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2); + +typedef gboolean (*EAPMethodValidateFunc)(NMSetting8021x *self, gboolean phase2, GError **error); + +typedef struct { + const char * method; + EAPMethodNeedSecretsFunc ns_func; + EAPMethodValidateFunc v_func; +} EAPMethodsTable; + +static const EAPMethodsTable eap_methods_table[]; + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSetting8021x, + PROP_EAP, + PROP_IDENTITY, + PROP_ANONYMOUS_IDENTITY, + PROP_PAC_FILE, + PROP_CA_CERT, + PROP_CA_CERT_PASSWORD, + PROP_CA_CERT_PASSWORD_FLAGS, + PROP_CA_PATH, + PROP_SUBJECT_MATCH, + PROP_ALTSUBJECT_MATCHES, + PROP_DOMAIN_SUFFIX_MATCH, + PROP_DOMAIN_MATCH, + PROP_CLIENT_CERT, + PROP_CLIENT_CERT_PASSWORD, + PROP_CLIENT_CERT_PASSWORD_FLAGS, + PROP_PHASE1_PEAPVER, + PROP_PHASE1_PEAPLABEL, + PROP_PHASE1_FAST_PROVISIONING, + PROP_PHASE1_AUTH_FLAGS, + PROP_PHASE2_AUTH, + PROP_PHASE2_AUTHEAP, + PROP_PHASE2_CA_CERT, + PROP_PHASE2_CA_CERT_PASSWORD, + PROP_PHASE2_CA_CERT_PASSWORD_FLAGS, + PROP_PHASE2_CA_PATH, + PROP_PHASE2_SUBJECT_MATCH, + PROP_PHASE2_ALTSUBJECT_MATCHES, + PROP_PHASE2_DOMAIN_SUFFIX_MATCH, + PROP_PHASE2_DOMAIN_MATCH, + PROP_PHASE2_CLIENT_CERT, + PROP_PHASE2_CLIENT_CERT_PASSWORD, + PROP_PHASE2_CLIENT_CERT_PASSWORD_FLAGS, + PROP_PASSWORD, + PROP_PASSWORD_FLAGS, + PROP_PASSWORD_RAW, + PROP_PASSWORD_RAW_FLAGS, + PROP_PRIVATE_KEY, + PROP_PRIVATE_KEY_PASSWORD, + PROP_PRIVATE_KEY_PASSWORD_FLAGS, + PROP_PHASE2_PRIVATE_KEY, + PROP_PHASE2_PRIVATE_KEY_PASSWORD, + PROP_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS, + PROP_PIN, + PROP_PIN_FLAGS, + PROP_SYSTEM_CA_CERTS, + PROP_OPTIONAL, + PROP_AUTH_TIMEOUT, ); + +typedef struct { + GSList * eap; /* GSList of strings */ + char * identity; + char * anonymous_identity; + char * pac_file; + GBytes * ca_cert; + char * ca_cert_password; + char * ca_path; + char * subject_match; + GSList * altsubject_matches; + char * domain_suffix_match; + char * domain_match; + GBytes * client_cert; + char * client_cert_password; + char * phase1_peapver; + char * phase1_peaplabel; + char * phase1_fast_provisioning; + char * phase2_auth; + char * phase2_autheap; + GBytes * phase2_ca_cert; + char * phase2_ca_cert_password; + char * phase2_ca_path; + char * phase2_subject_match; + GSList * phase2_altsubject_matches; + char * phase2_domain_suffix_match; + char * phase2_domain_match; + GBytes * phase2_client_cert; + char * phase2_client_cert_password; + char * password; + GBytes * password_raw; + char * pin; + GBytes * private_key; + char * private_key_password; + GBytes * phase2_private_key; + char * phase2_private_key_password; + int auth_timeout; + NMSetting8021xAuthFlags phase1_auth_flags; + NMSettingSecretFlags ca_cert_password_flags; + NMSettingSecretFlags client_cert_password_flags; + NMSettingSecretFlags phase2_ca_cert_password_flags; + NMSettingSecretFlags phase2_client_cert_password_flags; + NMSettingSecretFlags password_flags; + NMSettingSecretFlags password_raw_flags; + NMSettingSecretFlags pin_flags; + NMSettingSecretFlags private_key_password_flags; + NMSettingSecretFlags phase2_private_key_password_flags; + bool optional : 1; + bool system_ca_certs : 1; +} NMSetting8021xPrivate; + +/** + * NMSetting8021x: + * + * IEEE 802.1x Authentication Settings + */ +struct _NMSetting8021x { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSetting8021xClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSetting8021x, nm_setting_802_1x, NM_TYPE_SETTING) + +#define NM_SETTING_802_1X_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_802_1X, NMSetting8021xPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_802_1x_check_cert_scheme: + * @pdata: (allow-none): the data pointer + * @length: the length of the data + * @error: (allow-none) (out): validation reason + * + * Determines and verifies the blob type. + * When setting certificate properties of NMSetting8021x + * the blob must be not UNKNOWN (or NULL). + * + * Returns: the scheme of the blob or %NM_SETTING_802_1X_CK_SCHEME_UNKNOWN. + * For NULL it also returns NM_SETTING_802_1X_CK_SCHEME_UNKNOWN. + * + * Since: 1.2 + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_check_cert_scheme(gconstpointer pdata, gsize length, GError **error) +{ + const char * data = pdata; + NMSetting8021xCKScheme scheme; + gsize prefix_length; + + g_return_val_if_fail(!length || data, NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); + + if (!length || !data) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("binary data missing")); + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + + if (length >= NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH) + && !memcmp(data, + NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, + NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH))) { + scheme = NM_SETTING_802_1X_CK_SCHEME_PATH; + prefix_length = NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); + } else if (length >= NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PKCS11) + && !memcmp(data, + NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PKCS11, + NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PKCS11))) { + scheme = NM_SETTING_802_1X_CK_SCHEME_PKCS11; + prefix_length = NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PKCS11); + } else { + scheme = NM_SETTING_802_1X_CK_SCHEME_BLOB; + prefix_length = 0; + } + + if (scheme != NM_SETTING_802_1X_CK_SCHEME_BLOB) { + /* An actual URI must be NUL terminated, contain at least + * one non-NUL character, and contain only one trailing NUL + * character. + * And ensure it's UTF-8 valid too so we can pass it through + * D-Bus and stuff like that. */ + + if (data[length - 1] != '\0') { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("URI not NUL terminated")); + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + length--; + + if (length <= prefix_length) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("URI is empty")); + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + + if (!g_utf8_validate(data + prefix_length, length - prefix_length, NULL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("URI is not valid UTF-8")); + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + } + + return scheme; +} + +NMSetting8021xCKScheme +_nm_setting_802_1x_cert_get_scheme(GBytes *bytes, GError **error) +{ + const char *data; + gsize length; + + if (!bytes) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("data missing")); + return NM_SETTING_802_1X_CK_SCHEME_UNKNOWN; + } + + data = g_bytes_get_data(bytes, &length); + return nm_setting_802_1x_check_cert_scheme(data, length, error); +} + +static gboolean +_cert_verify_scheme(NMSetting8021xCKScheme scheme, GBytes *bytes, GError **error) +{ + GError * local = NULL; + NMSetting8021xCKScheme scheme_detected; + + nm_assert(bytes); + + scheme_detected = _nm_setting_802_1x_cert_get_scheme(bytes, &local); + if (scheme_detected == NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("certificate is invalid: %s"), + local->message); + return FALSE; + } + + if (scheme_detected != scheme) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("certificate detected as invalid scheme")); + return FALSE; + } + + return TRUE; +} + +GBytes * +_nm_setting_802_1x_cert_value_to_bytes(NMSetting8021xCKScheme scheme, + const guint8 * val_bin, + gssize val_len, + GError ** error) +{ + gs_unref_bytes GBytes *bytes = NULL; + guint8 * mem; + gsize total_len; + + nm_assert(val_bin); + + switch (scheme) { + case NM_SETTING_802_1X_CK_SCHEME_PKCS11: + if (val_len < 0) + val_len = strlen((char *) val_bin) + 1; + + bytes = g_bytes_new(val_bin, val_len); + break; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + if (val_len < 0) + val_len = strlen((char *) val_bin) + 1; + + total_len = NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH) + ((gsize) val_len); + + mem = g_new(guint8, total_len); + memcpy(mem, + NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, + NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)); + memcpy(&mem[NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)], val_bin, val_len); + bytes = g_bytes_new_take(mem, total_len); + break; + default: + g_return_val_if_reached(NULL); + } + + if (!_cert_verify_scheme(scheme, bytes, error)) + return NULL; + + return g_steal_pointer(&bytes); +} + +static const char * +_cert_get_path(GBytes *bytes) +{ + const guint8 *bin; + + nm_assert(bytes); + nm_assert(g_bytes_get_size(bytes) >= NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)); + + bin = g_bytes_get_data(bytes, NULL); + + nm_assert(bin); + nm_assert(bin[g_bytes_get_size(bytes) - 1] == '\0'); + nm_assert(g_str_has_prefix((const char *) bin, NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)); + + return (const char *) &bin[NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)]; +} + +#define _cert_assert_scheme(cert, check_scheme, ret_val) \ + G_STMT_START \ + { \ + NMSetting8021xCKScheme scheme; \ + \ + scheme = _nm_setting_802_1x_cert_get_scheme((cert), NULL); \ + if (scheme != check_scheme) { \ + g_return_val_if_fail(scheme == check_scheme, ret_val); \ + return ret_val; \ + } \ + } \ + G_STMT_END + +#define _cert_impl_get_scheme(setting, cert_field) \ + G_STMT_START \ + { \ + NMSetting8021x *const _setting = (setting); \ + GBytes * _cert; \ + \ + g_return_val_if_fail(NM_IS_SETTING_802_1X(_setting), NM_SETTING_802_1X_CK_SCHEME_UNKNOWN); \ + \ + _cert = NM_SETTING_802_1X_GET_PRIVATE(_setting)->cert_field; \ + \ + return _nm_setting_802_1x_cert_get_scheme(_cert, NULL); \ + } \ + G_STMT_END + +#define _cert_impl_get_blob(setting, cert_field) \ + G_STMT_START \ + { \ + NMSetting8021x *const _setting = (setting); \ + GBytes * _cert; \ + \ + g_return_val_if_fail(NM_IS_SETTING_802_1X(_setting), NULL); \ + \ + _cert = NM_SETTING_802_1X_GET_PRIVATE(_setting)->cert_field; \ + \ + _cert_assert_scheme(_cert, NM_SETTING_802_1X_CK_SCHEME_BLOB, NULL); \ + \ + return _cert; \ + } \ + G_STMT_END + +#define _cert_impl_get_path(setting, cert_field) \ + G_STMT_START \ + { \ + NMSetting8021x *const _setting = (setting); \ + GBytes * _cert; \ + \ + g_return_val_if_fail(NM_IS_SETTING_802_1X(_setting), NULL); \ + \ + _cert = NM_SETTING_802_1X_GET_PRIVATE(_setting)->cert_field; \ + \ + _cert_assert_scheme(_cert, NM_SETTING_802_1X_CK_SCHEME_PATH, NULL); \ + \ + return _cert_get_path(_cert); \ + } \ + G_STMT_END + +#define _cert_impl_get_uri(setting, cert_field) \ + G_STMT_START \ + { \ + NMSetting8021x *const _setting = (setting); \ + GBytes * _cert; \ + \ + g_return_val_if_fail(NM_IS_SETTING_802_1X(_setting), NULL); \ + \ + _cert = NM_SETTING_802_1X_GET_PRIVATE(_setting)->cert_field; \ + \ + _cert_assert_scheme(_cert, NM_SETTING_802_1X_CK_SCHEME_PKCS11, NULL); \ + \ + return g_bytes_get_data(_cert, NULL); \ + } \ + G_STMT_END + +static gboolean +_cert_impl_set(NMSetting8021x * setting, + _PropertyEnums property, + const char * value, + const char * password, + NMSetting8021xCKScheme scheme, + NMSetting8021xCKFormat *out_format, + GError ** error) +{ + NMSetting8021xPrivate *priv; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + gs_unref_bytes GBytes *cert = NULL; + GBytes ** p_cert = NULL; + GBytes ** p_client_cert = NULL; + char ** p_password = NULL; + _PropertyEnums notify_cert = property; + _PropertyEnums notify_password = PROP_0; + _PropertyEnums notify_client_cert = PROP_0; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + if (value) { + g_return_val_if_fail(g_utf8_validate(value, -1, NULL), FALSE); + g_return_val_if_fail(NM_IN_SET(scheme, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + NM_SETTING_802_1X_CK_SCHEME_PATH, + NM_SETTING_802_1X_CK_SCHEME_PKCS11), + FALSE); + } + + if (!value) { + /* coerce password to %NULL. It should be already. */ + password = NULL; + } + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + + if (!value) { + /* pass. */ + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11) { + cert = _nm_setting_802_1x_cert_value_to_bytes(scheme, (guint8 *) value, -1, error); + if (!cert) + goto err; + } else { + gs_unref_bytes GBytes *file = NULL; + + if (NM_IN_SET(property, PROP_PRIVATE_KEY, PROP_PHASE2_PRIVATE_KEY)) { + file = nm_crypto_read_file(value, error); + if (!file) + goto err; + format = nm_crypto_verify_private_key_data(g_bytes_get_data(file, NULL), + g_bytes_get_size(file), + password, + NULL, + error); + if (format == NM_CRYPTO_FILE_FORMAT_UNKNOWN) + goto err; + } else { + if (!nm_crypto_load_and_verify_certificate(value, &format, &file, error)) + goto err; + } + + nm_assert(format != NM_CRYPTO_FILE_FORMAT_UNKNOWN); + nm_assert(file); + + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + cert = g_steal_pointer(&file); + if (!_cert_verify_scheme(scheme, cert, error)) + goto err; + } else { + cert = _nm_setting_802_1x_cert_value_to_bytes(scheme, (guint8 *) value, -1, error); + if (!cert) + goto err; + } + } + + switch (property) { + case PROP_CA_CERT: + case PROP_PHASE2_CA_CERT: + if (value && scheme != NM_SETTING_802_1X_CK_SCHEME_PKCS11 + && format != NM_CRYPTO_FILE_FORMAT_X509) { + /* wpa_supplicant can only use raw x509 CA certs */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("CA certificate must be in X.509 format")); + goto err; + } + p_cert = (property == PROP_CA_CERT) ? &priv->ca_cert : &priv->phase2_ca_cert; + break; + case PROP_CLIENT_CERT: + case PROP_PHASE2_CLIENT_CERT: + if (value && scheme != NM_SETTING_802_1X_CK_SCHEME_PKCS11 + && !NM_IN_SET(format, NM_CRYPTO_FILE_FORMAT_X509, NM_CRYPTO_FILE_FORMAT_PKCS12)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid certificate format")); + goto err; + } + p_cert = (property == PROP_CLIENT_CERT) ? &priv->client_cert : &priv->phase2_client_cert; + break; + case PROP_PRIVATE_KEY: + p_cert = &priv->private_key; + p_password = &priv->private_key_password; + p_client_cert = &priv->client_cert; + notify_password = PROP_PRIVATE_KEY_PASSWORD; + notify_client_cert = PROP_CLIENT_CERT; + break; + case PROP_PHASE2_PRIVATE_KEY: + p_cert = &priv->phase2_private_key; + p_password = &priv->phase2_private_key_password; + p_client_cert = &priv->phase2_client_cert; + notify_password = PROP_PHASE2_PRIVATE_KEY_PASSWORD; + notify_client_cert = PROP_PHASE2_CLIENT_CERT; + break; + default: + nm_assert_not_reached(); + break; + } + + /* As required by NM and wpa_supplicant, set the client-cert + * property to the same PKCS#12 data. + */ + if (cert && p_client_cert && format == NM_CRYPTO_FILE_FORMAT_PKCS12 + && !nm_gbytes_equal0(cert, *p_client_cert)) { + g_bytes_unref(*p_client_cert); + *p_client_cert = g_bytes_ref(cert); + } else + notify_client_cert = PROP_0; + + if (p_cert && !nm_gbytes_equal0(cert, *p_cert)) { + g_bytes_unref(*p_cert); + *p_cert = g_steal_pointer(&cert); + } else + notify_cert = PROP_0; + + if (p_password && !nm_streq0(password, *p_password)) { + nm_free_secret(*p_password); + *p_password = g_strdup(password); + } else + notify_password = PROP_0; + + nm_gobject_notify_together(setting, notify_cert, notify_password, notify_client_cert); + + NM_SET_OUT(out_format, _crypto_format_to_ck(format)); + return TRUE; + +err: + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + obj_properties[property]->name); + NM_SET_OUT(out_format, NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + return FALSE; +} + +static NMSetting8021xCKFormat +_cert_impl_get_key_format_from_bytes(GBytes *private_key) +{ + const char *path; + GError * error = NULL; + + if (!private_key) + return NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + + switch (_nm_setting_802_1x_cert_get_scheme(private_key, NULL)) { + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + if (nm_crypto_is_pkcs12_data(g_bytes_get_data(private_key, NULL), + g_bytes_get_size(private_key), + NULL)) + return NM_SETTING_802_1X_CK_FORMAT_PKCS12; + return NM_SETTING_802_1X_CK_FORMAT_RAW_KEY; + case NM_SETTING_802_1X_CK_SCHEME_PATH: + path = _cert_get_path(private_key); + if (nm_crypto_is_pkcs12_file(path, &error)) + return NM_SETTING_802_1X_CK_FORMAT_PKCS12; + if (error && error->domain == G_FILE_ERROR) { + g_error_free(error); + return NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + } + g_error_free(error); + return NM_SETTING_802_1X_CK_FORMAT_RAW_KEY; + default: + break; + } + + return NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; +} +#define _cert_impl_get_key_format(setting, private_key_field) \ + ({ \ + NMSetting8021x * _setting = (setting); \ + NMSetting8021xPrivate *_priv; \ + \ + g_return_val_if_fail(NM_IS_SETTING_802_1X(_setting), NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); \ + \ + _priv = NM_SETTING_802_1X_GET_PRIVATE(_setting); \ + _cert_impl_get_key_format_from_bytes(_priv->private_key_field); \ + }) + +static gboolean +_cert_verify_property(GBytes * bytes, + const char *prop_name, + const char *password, + const char *password_prop_name, + GError ** error) +{ + GError * local = NULL; + NMSetting8021xCKScheme scheme; + + if (!bytes) + return TRUE; + + scheme = _nm_setting_802_1x_cert_get_scheme(bytes, &local); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_UNKNOWN) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("certificate is invalid: %s"), + local->message); + g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, prop_name); + g_error_free(local); + return FALSE; + } + + if (password && (scheme != NM_SETTING_802_1X_CK_SCHEME_PKCS11)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("password is not supported when certificate is not on a PKCS#11 token")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, password_prop_name); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +/** + * nm_setting_802_1x_get_num_eap_methods: + * @setting: the #NMSetting8021x + * + * Returns the number of eap methods allowed for use when connecting to the + * network. Generally only one EAP method is used. Use the functions + * nm_setting_802_1x_get_eap_method(), nm_setting_802_1x_add_eap_method(), + * and nm_setting_802_1x_remove_eap_method() for adding, removing, and retrieving + * allowed EAP methods. + * + * Returns: the number of allowed EAP methods + **/ +guint32 +nm_setting_802_1x_get_num_eap_methods(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), 0); + + return g_slist_length(NM_SETTING_802_1X_GET_PRIVATE(setting)->eap); +} + +/** + * nm_setting_802_1x_get_eap_method: + * @setting: the #NMSetting8021x + * @i: the index of the EAP method name to return + * + * Returns the name of the allowed EAP method at index @i. + * + * Returns: the name of the allowed EAP method at index @i + **/ +const char * +nm_setting_802_1x_get_eap_method(NMSetting8021x *setting, guint32 i) +{ + NMSetting8021xPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + g_return_val_if_fail(i <= g_slist_length(priv->eap), NULL); + + return (const char *) g_slist_nth_data(priv->eap, i); +} + +/** + * nm_setting_802_1x_add_eap_method: + * @setting: the #NMSetting8021x + * @eap: the name of the EAP method to allow for this connection + * + * Adds an allowed EAP method. The setting is not valid until at least one + * EAP method has been added. See #NMSetting8021x:eap property for a list of + * allowed EAP methods. + * + * Returns: %TRUE if the EAP method was successfully added, %FALSE if it was + * not a valid method or if it was already allowed. + **/ +gboolean +nm_setting_802_1x_add_eap_method(NMSetting8021x *setting, const char *eap) +{ + NMSetting8021xPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + g_return_val_if_fail(eap != NULL, FALSE); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + for (iter = priv->eap; iter; iter = g_slist_next(iter)) { + if (!strcmp(eap, (char *) iter->data)) + return FALSE; + } + + priv->eap = g_slist_append(priv->eap, g_ascii_strdown(eap, -1)); + _notify(setting, PROP_EAP); + return TRUE; +} + +/** + * nm_setting_802_1x_remove_eap_method: + * @setting: the #NMSetting8021x + * @i: the index of the EAP method to remove + * + * Removes the allowed EAP method at the specified index. + **/ +void +nm_setting_802_1x_remove_eap_method(NMSetting8021x *setting, guint32 i) +{ + NMSetting8021xPrivate *priv; + GSList * elt; + + g_return_if_fail(NM_IS_SETTING_802_1X(setting)); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + elt = g_slist_nth(priv->eap, i); + g_return_if_fail(elt != NULL); + + g_free(elt->data); + priv->eap = g_slist_delete_link(priv->eap, elt); + _notify(setting, PROP_EAP); +} + +/** + * nm_setting_802_1x_remove_eap_method_by_value: + * @setting: the #NMSetting8021x + * @eap: the name of the EAP method to remove + * + * Removes the allowed EAP method @method. + * + * Returns: %TRUE if the EAP method was founs and removed, %FALSE if it was not. + **/ +gboolean +nm_setting_802_1x_remove_eap_method_by_value(NMSetting8021x *setting, const char *eap) +{ + NMSetting8021xPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + g_return_val_if_fail(eap != NULL, FALSE); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + for (iter = priv->eap; iter; iter = g_slist_next(iter)) { + if (!strcmp(eap, (char *) iter->data)) { + priv->eap = g_slist_delete_link(priv->eap, iter); + _notify(setting, PROP_EAP); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_802_1x_clear_eap_methods: + * @setting: the #NMSetting8021x + * + * Clears all allowed EAP methods. + **/ +void +nm_setting_802_1x_clear_eap_methods(NMSetting8021x *setting) +{ + NMSetting8021xPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_802_1X(setting)); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + g_slist_free_full(priv->eap, g_free); + priv->eap = NULL; + _notify(setting, PROP_EAP); +} + +/** + * nm_setting_802_1x_get_identity: + * @setting: the #NMSetting8021x + * + * Returns the identifier used by some EAP methods (like TLS) to + * authenticate the user. Often this is a username or login name. + * + * Returns: the user identifier + **/ +const char * +nm_setting_802_1x_get_identity(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->identity; +} + +/** + * nm_setting_802_1x_get_anonymous_identity: + * @setting: the #NMSetting8021x + * + * Returns the anonymous identifier used by some EAP methods (like TTLS) to + * authenticate the user in the outer unencrypted "phase 1" authentication. The + * inner "phase 2" authentication will use the #NMSetting8021x:identity in + * a secure form, if applicable for that EAP method. + * + * Returns: the anonymous identifier + **/ +const char * +nm_setting_802_1x_get_anonymous_identity(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->anonymous_identity; +} + +/** + * nm_setting_802_1x_get_pac_file: + * @setting: the #NMSetting8021x + * + * Returns the file containing PAC credentials used by EAP-FAST method. + * + * Returns: the PAC file + **/ +const char * +nm_setting_802_1x_get_pac_file(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->pac_file; +} + +/** + * nm_setting_802_1x_get_ca_path: + * @setting: the #NMSetting8021x + * + * Returns the path of the CA certificate directory if previously set. Systems + * will often have a directory that contains multiple individual CA certificates + * which the supplicant can then add to the verification chain. This may be + * used in addition to the #NMSetting8021x:ca-cert property to add more CA + * certificates for verifying the network to client. + * + * Returns: the CA certificate directory path + **/ +const char * +nm_setting_802_1x_get_ca_path(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->ca_path; +} + +/** + * nm_setting_802_1x_get_system_ca_certs: + * @setting: the #NMSetting8021x + * + * Sets the #NMSetting8021x:system-ca-certs property. The + * #NMSetting8021x:ca-path and #NMSetting8021x:phase2-ca-path + * properties are ignored if the #NMSetting8021x:system-ca-certs property is + * %TRUE, in which case a system-wide CA certificate directory specified at + * compile time (using the --system-ca-path configure option) is used in place + * of these properties. + * + * Returns: %TRUE if a system CA certificate path should be used, %FALSE if not + **/ +gboolean +nm_setting_802_1x_get_system_ca_certs(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->system_ca_certs; +} + +/** + * nm_setting_802_1x_get_ca_cert_scheme: + * @setting: the #NMSetting8021x + * + * Returns the scheme used to store the CA certificate. If the returned scheme + * is %NM_SETTING_802_1X_CK_SCHEME_BLOB, use nm_setting_802_1x_get_ca_cert_blob(); + * if %NM_SETTING_802_1X_CK_SCHEME_PATH, use nm_setting_802_1x_get_ca_cert_path(); + * if %NM_SETTING_802_1X_CK_SCHEME_PKCS11, use nm_setting_802_1x_get_ca_cert_uri(). + * + * Returns: scheme used to store the CA certificate (blob or path) + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_get_ca_cert_scheme(NMSetting8021x *setting) +{ + _cert_impl_get_scheme(setting, ca_cert); +} + +/** + * nm_setting_802_1x_get_ca_cert_blob: + * @setting: the #NMSetting8021x + * + * Returns the CA certificate blob if the CA certificate is stored using the + * %NM_SETTING_802_1X_CK_SCHEME_BLOB scheme. Not all EAP methods use a + * CA certificate (LEAP for example), and those that can take advantage of the + * CA certificate allow it to be unset. Note that lack of a CA certificate + * reduces security by allowing man-in-the-middle attacks, because the identity + * of the network cannot be confirmed by the client. + * + * Returns: (transfer none): the CA certificate data + **/ +GBytes * +nm_setting_802_1x_get_ca_cert_blob(NMSetting8021x *setting) +{ + _cert_impl_get_blob(setting, ca_cert); +} + +/** + * nm_setting_802_1x_get_ca_cert_path: + * @setting: the #NMSetting8021x + * + * Returns the CA certificate path if the CA certificate is stored using the + * %NM_SETTING_802_1X_CK_SCHEME_PATH scheme. Not all EAP methods use a + * CA certificate (LEAP for example), and those that can take advantage of the + * CA certificate allow it to be unset. Note that lack of a CA certificate + * reduces security by allowing man-in-the-middle attacks, because the identity + * of the network cannot be confirmed by the client. + * + * Returns: path to the CA certificate file + **/ +const char * +nm_setting_802_1x_get_ca_cert_path(NMSetting8021x *setting) +{ + _cert_impl_get_path(setting, ca_cert); +} + +/** + * nm_setting_802_1x_get_ca_cert_uri: + * @setting: the #NMSetting8021x + * + * Returns the CA certificate URI analogously to + * nm_setting_802_1x_get_ca_cert_blob() and + * nm_setting_802_1x_get_ca_cert_path(). + * + * Currently, it's limited to PKCS#11 URIs ('pkcs11' scheme as defined by RFC + * 7512), but may be extended to other schemes in future (such as 'file' URIs + * for local files and 'data' URIs for inline certificate data). + * + * Returns: the URI string + * + * Since: 1.6 + **/ +const char * +nm_setting_802_1x_get_ca_cert_uri(NMSetting8021x *setting) +{ + _cert_impl_get_uri(setting, ca_cert); +} + +/** + * nm_setting_802_1x_set_ca_cert: + * @setting: the #NMSetting8021x + * @value: when @scheme is set to either %NM_SETTING_802_1X_CK_SCHEME_PATH + * or %NM_SETTING_802_1X_CK_SCHEME_BLOB, pass the path of the CA certificate + * file (PEM or DER format). The path must be UTF-8 encoded; use + * g_filename_to_utf8() to convert if needed. Passing %NULL with any @scheme + * clears the CA certificate. + * @scheme: desired storage scheme for the certificate + * @out_format: on successful return, the type of the certificate added + * @error: on unsuccessful return, an error + * + * Reads a certificate from disk and sets the #NMSetting8021x:ca-cert property + * with the raw certificate data if using the %NM_SETTING_802_1X_CK_SCHEME_BLOB + * scheme, or with the path to the certificate file if using the + * %NM_SETTING_802_1X_CK_SCHEME_PATH scheme. + * + * Returns: %TRUE if the operation succeeded, %FALSE if it was unsuccessful + **/ +gboolean +nm_setting_802_1x_set_ca_cert(NMSetting8021x * setting, + const char * value, + NMSetting8021xCKScheme scheme, + NMSetting8021xCKFormat *out_format, + GError ** error) +{ + return _cert_impl_set(setting, PROP_CA_CERT, value, NULL, scheme, out_format, error); +} + +/** + * nm_setting_802_1x_get_ca_cert_password: + * @setting: the #NMSetting8021x + * + * Returns: the password used to access the CA certificate stored in + * #NMSetting8021x:ca-cert property. Only makes sense if the certificate + * is stored on a PKCS#11 token that requires a login. + * + * Since: 1.8 + **/ +const char * +nm_setting_802_1x_get_ca_cert_password(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->ca_cert_password; +} + +/** + * nm_setting_802_1x_get_ca_cert_password_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSetting8021x:ca-cert-password + * + * Since: 1.8 + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_ca_cert_password_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->ca_cert_password_flags; +} + +/** + * nm_setting_802_1x_get_subject_match: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSetting8021x:subject-match property. This is the + * substring to be matched against the subject of the authentication + * server certificate, or %NULL no subject verification is to be + * performed. + **/ +const char * +nm_setting_802_1x_get_subject_match(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->subject_match; +} + +/** + * nm_setting_802_1x_get_num_altsubject_matches: + * @setting: the #NMSetting8021x + * + * Returns the number of entries in the + * #NMSetting8021x:altsubject-matches property of this setting. + * + * Returns: the number of altsubject-matches entries. + **/ +guint32 +nm_setting_802_1x_get_num_altsubject_matches(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), 0); + + return g_slist_length(NM_SETTING_802_1X_GET_PRIVATE(setting)->altsubject_matches); +} + +/** + * nm_setting_802_1x_get_altsubject_match: + * @setting: the #NMSettingConnection + * @i: the zero-based index of the array of altSubjectName matches + * + * Returns the altSubjectName match at index @i. + * + * Returns: the altSubjectName match at index @i + **/ +const char * +nm_setting_802_1x_get_altsubject_match(NMSetting8021x *setting, guint32 i) +{ + NMSetting8021xPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + g_return_val_if_fail(i <= g_slist_length(priv->altsubject_matches), NULL); + + return (const char *) g_slist_nth_data(priv->altsubject_matches, i); +} + +/** + * nm_setting_802_1x_add_altsubject_match: + * @setting: the #NMSetting8021x + * @altsubject_match: the altSubjectName to allow for this connection + * + * Adds an allowed alternate subject name match. Until at least one + * match is added, the altSubjectName of the remote authentication + * server is not verified. + * + * Returns: %TRUE if the alternative subject name match was + * successfully added, %FALSE if it was already allowed. + **/ +gboolean +nm_setting_802_1x_add_altsubject_match(NMSetting8021x *setting, const char *altsubject_match) +{ + NMSetting8021xPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + g_return_val_if_fail(altsubject_match != NULL, FALSE); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + for (iter = priv->altsubject_matches; iter; iter = g_slist_next(iter)) { + if (!strcmp(altsubject_match, (char *) iter->data)) + return FALSE; + } + + priv->altsubject_matches = g_slist_append(priv->altsubject_matches, g_strdup(altsubject_match)); + _notify(setting, PROP_ALTSUBJECT_MATCHES); + return TRUE; +} + +/** + * nm_setting_802_1x_remove_altsubject_match: + * @setting: the #NMSetting8021x + * @i: the index of the altSubjectName match to remove + * + * Removes the allowed altSubjectName at the specified index. + **/ +void +nm_setting_802_1x_remove_altsubject_match(NMSetting8021x *setting, guint32 i) +{ + NMSetting8021xPrivate *priv; + GSList * elt; + + g_return_if_fail(NM_IS_SETTING_802_1X(setting)); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + elt = g_slist_nth(priv->altsubject_matches, i); + g_return_if_fail(elt != NULL); + + g_free(elt->data); + priv->altsubject_matches = g_slist_delete_link(priv->altsubject_matches, elt); + _notify(setting, PROP_ALTSUBJECT_MATCHES); +} + +/** + * nm_setting_802_1x_remove_altsubject_match_by_value: + * @setting: the #NMSetting8021x + * @altsubject_match: the altSubjectName to remove + * + * Removes the allowed altSubjectName @altsubject_match. + * + * Returns: %TRUE if the alternative subject name match was found and removed, + * %FALSE if it was not. + **/ +gboolean +nm_setting_802_1x_remove_altsubject_match_by_value(NMSetting8021x *setting, + const char * altsubject_match) +{ + NMSetting8021xPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + g_return_val_if_fail(altsubject_match != NULL, FALSE); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + for (iter = priv->altsubject_matches; iter; iter = g_slist_next(iter)) { + if (!strcmp(altsubject_match, (char *) iter->data)) { + priv->altsubject_matches = g_slist_delete_link(priv->altsubject_matches, iter); + _notify(setting, PROP_ALTSUBJECT_MATCHES); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_802_1x_clear_altsubject_matches: + * @setting: the #NMSetting8021x + * + * Clears all altSubjectName matches. + **/ +void +nm_setting_802_1x_clear_altsubject_matches(NMSetting8021x *setting) +{ + NMSetting8021xPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_802_1X(setting)); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + g_slist_free_full(priv->altsubject_matches, g_free); + priv->altsubject_matches = NULL; + _notify(setting, PROP_ALTSUBJECT_MATCHES); +} + +/** + * nm_setting_802_1x_get_domain_suffix_match: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSetting8021x:domain-suffix-match property. + * + * Since: 1.2 + **/ +const char * +nm_setting_802_1x_get_domain_suffix_match(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->domain_suffix_match; +} + +/** + * nm_setting_802_1x_get_domain_match: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSetting8021x:domain-match property. + * + * Since: 1.24 + **/ +const char * +nm_setting_802_1x_get_domain_match(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->domain_match; +} + +/** + * nm_setting_802_1x_get_client_cert_scheme: + * @setting: the #NMSetting8021x + * + * Returns the scheme used to store the client certificate. If the returned scheme + * is %NM_SETTING_802_1X_CK_SCHEME_BLOB, use nm_setting_802_1x_get_client_cert_blob(); + * if %NM_SETTING_802_1X_CK_SCHEME_PATH, use nm_setting_802_1x_get_client_cert_path(); + * if %NM_SETTING_802_1X_CK_SCHEME_PKCS11, use nm_setting_802_1x_get_client_cert_uri(). + * + * Returns: scheme used to store the client certificate (blob or path) + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_get_client_cert_scheme(NMSetting8021x *setting) +{ + _cert_impl_get_scheme(setting, client_cert); +} + +/** + * nm_setting_802_1x_get_client_cert_blob: + * @setting: the #NMSetting8021x + * + * Client certificates are used to identify the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * Returns: (transfer none): the client certificate data + **/ +GBytes * +nm_setting_802_1x_get_client_cert_blob(NMSetting8021x *setting) +{ + _cert_impl_get_blob(setting, client_cert); +} + +/** + * nm_setting_802_1x_get_client_cert_path: + * @setting: the #NMSetting8021x + * + * Client certificates are used to identify the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * Returns: path to the client certificate file + **/ +const char * +nm_setting_802_1x_get_client_cert_path(NMSetting8021x *setting) +{ + _cert_impl_get_path(setting, client_cert); +} + +/** + * nm_setting_802_1x_get_client_cert_uri: + * @setting: the #NMSetting8021x + * + * Returns the client certificate URI analogously to + * nm_setting_802_1x_get_client_cert_blob() and + * nm_setting_802_1x_get_client_cert_path(). + * + * Currently, it's limited to PKCS#11 URIs ('pkcs11' scheme as defined by RFC + * 7512), but may be extended to other schemes in future (such as 'file' URIs + * for local files and 'data' URIs for inline certificate data). + * + * Returns: the URI string + * + * Since: 1.6 + **/ +const char * +nm_setting_802_1x_get_client_cert_uri(NMSetting8021x *setting) +{ + _cert_impl_get_uri(setting, client_cert); +} + +/** + * nm_setting_802_1x_set_client_cert: + * @setting: the #NMSetting8021x + * @value: when @scheme is set to either %NM_SETTING_802_1X_CK_SCHEME_PATH + * or %NM_SETTING_802_1X_CK_SCHEME_BLOB, pass the path of the client + * certificate file (PEM, DER, or PKCS#12 format). The path must be UTF-8 + * encoded; use g_filename_to_utf8() to convert if needed. Passing %NULL with + * any @scheme clears the client certificate. + * @scheme: desired storage scheme for the certificate + * @out_format: on successful return, the type of the certificate added + * @error: on unsuccessful return, an error + * + * Reads a certificate from disk and sets the #NMSetting8021x:client-cert + * property with the raw certificate data if using the + * %NM_SETTING_802_1X_CK_SCHEME_BLOB scheme, or with the path to the certificate + * file if using the %NM_SETTING_802_1X_CK_SCHEME_PATH scheme. + * + * Client certificates are used to identify the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * Returns: %TRUE if the operation succeeded, %FALSE if it was unsuccessful + **/ +gboolean +nm_setting_802_1x_set_client_cert(NMSetting8021x * setting, + const char * value, + NMSetting8021xCKScheme scheme, + NMSetting8021xCKFormat *out_format, + GError ** error) +{ + return _cert_impl_set(setting, PROP_CLIENT_CERT, value, NULL, scheme, out_format, error); +} + +/** + * nm_setting_802_1x_get_client_cert_password: + * @setting: the #NMSetting8021x + * + * Returns: the password used to access the client certificate stored in + * #NMSetting8021x:client-cert property. Only makes sense if the certificate + * is stored on a PKCS#11 token that requires a login. + * + * Since: 1.8 + **/ +const char * +nm_setting_802_1x_get_client_cert_password(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->client_cert_password; +} + +/** + * nm_setting_802_1x_get_client_cert_password_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSetting8021x:client-cert-password + * + * Since: 1.8 + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_client_cert_password_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->client_cert_password_flags; +} + +/** + * nm_setting_802_1x_get_phase1_peapver: + * @setting: the #NMSetting8021x + * + * Returns: the "phase 1" PEAP version to be used when authenticating with + * EAP-PEAP as contained in the #NMSetting8021x:phase1-peapver property. Valid + * values are %NULL (unset), "0" (PEAP version 0), and "1" (PEAP version 1). + **/ +const char * +nm_setting_802_1x_get_phase1_peapver(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase1_peapver; +} + +/** + * nm_setting_802_1x_get_phase1_peaplabel: + * @setting: the #NMSetting8021x + * + * Returns: whether the "phase 1" PEAP label is new-style or old-style, to be + * used when authenticating with EAP-PEAP, as contained in the + * #NMSetting8021x:phase1-peaplabel property. Valid values are %NULL (unset), + * "0" (use old-style label), and "1" (use new-style label). See the + * wpa_supplicant documentation for more details. + **/ +const char * +nm_setting_802_1x_get_phase1_peaplabel(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase1_peaplabel; +} + +/** + * nm_setting_802_1x_get_phase1_fast_provisioning: + * @setting: the #NMSetting8021x + * + * Returns: whether "phase 1" PEAP fast provisioning should be used, as specified + * by the #NMSetting8021x:phase1-fast-provisioning property. See the + * wpa_supplicant documentation for more details. + **/ +const char * +nm_setting_802_1x_get_phase1_fast_provisioning(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase1_fast_provisioning; +} + +/** + * nm_setting_802_1x_get_phase1_auth_flags: + * @setting: the #NMSetting8021x + * + * Returns: the authentication flags for "phase 1". + * + * Since: 1.8 + */ +NMSetting8021xAuthFlags +nm_setting_802_1x_get_phase1_auth_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), 0); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase1_auth_flags; +} + +/** + * nm_setting_802_1x_get_phase2_auth: + * @setting: the #NMSetting8021x + * + * Returns: the "phase 2" non-EAP (ex MD5) allowed authentication method as + * specified by the #NMSetting8021x:phase2-auth property. + **/ +const char * +nm_setting_802_1x_get_phase2_auth(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_auth; +} + +/** + * nm_setting_802_1x_get_phase2_autheap: + * @setting: the #NMSetting8021x + * + * Returns: the "phase 2" EAP-based (ex TLS) allowed authentication method as + * specified by the #NMSetting8021x:phase2-autheap property. + **/ +const char * +nm_setting_802_1x_get_phase2_autheap(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_autheap; +} + +/** + * nm_setting_802_1x_get_phase2_ca_path: + * @setting: the #NMSetting8021x + * + * Returns the path of the "phase 2" CA certificate directory if previously set. + * Systems will often have a directory that contains multiple individual CA + * certificates which the supplicant can then add to the verification chain. + * This may be used in addition to the #NMSetting8021x:phase2-ca-cert property + * to add more CA certificates for verifying the network to client. + * + * Returns: the "phase 2" CA certificate directory path + **/ +const char * +nm_setting_802_1x_get_phase2_ca_path(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_ca_path; +} + +/** + * nm_setting_802_1x_get_phase2_ca_cert_scheme: + * @setting: the #NMSetting8021x + * + * Returns the scheme used to store the "phase 2" CA certificate. If the + * returned scheme is %NM_SETTING_802_1X_CK_SCHEME_BLOB, use + * nm_setting_802_1x_get_ca_cert_blob(); if %NM_SETTING_802_1X_CK_SCHEME_PATH, + * use nm_setting_802_1x_get_ca_cert_path(); if %NM_SETTING_802_1X_CK_SCHEME_PKCS11, + * use nm_setting_802_1x_get_ca_cert_uri(). + * + * Returns: scheme used to store the "phase 2" CA certificate (blob or path) + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_get_phase2_ca_cert_scheme(NMSetting8021x *setting) +{ + _cert_impl_get_scheme(setting, phase2_ca_cert); +} + +/** + * nm_setting_802_1x_get_phase2_ca_cert_blob: + * @setting: the #NMSetting8021x + * + * Returns the "phase 2" CA certificate blob if the CA certificate is stored + * using the %NM_SETTING_802_1X_CK_SCHEME_BLOB scheme. Not all EAP methods use + * a CA certificate (LEAP for example), and those that can take advantage of the + * CA certificate allow it to be unset. Note that lack of a CA certificate + * reduces security by allowing man-in-the-middle attacks, because the identity + * of the network cannot be confirmed by the client. + * + * Returns: (transfer none): the "phase 2" CA certificate data + **/ +GBytes * +nm_setting_802_1x_get_phase2_ca_cert_blob(NMSetting8021x *setting) +{ + _cert_impl_get_blob(setting, phase2_ca_cert); +} + +/** + * nm_setting_802_1x_get_phase2_ca_cert_path: + * @setting: the #NMSetting8021x + * + * Returns the "phase 2" CA certificate path if the CA certificate is stored + * using the %NM_SETTING_802_1X_CK_SCHEME_PATH scheme. Not all EAP methods use + * a CA certificate (LEAP for example), and those that can take advantage of the + * CA certificate allow it to be unset. Note that lack of a CA certificate + * reduces security by allowing man-in-the-middle attacks, because the identity + * of the network cannot be confirmed by the client. + * + * Returns: path to the "phase 2" CA certificate file + **/ +const char * +nm_setting_802_1x_get_phase2_ca_cert_path(NMSetting8021x *setting) +{ + _cert_impl_get_path(setting, phase2_ca_cert); +} + +/** + * nm_setting_802_1x_get_phase2_ca_cert_uri: + * @setting: the #NMSetting8021x + * + * Returns the "phase 2" CA certificate URI analogously to + * nm_setting_802_1x_get_phase2_ca_cert_blob() and + * nm_setting_802_1x_get_phase2_ca_cert_path(). + * + * Currently, it's limited to PKCS#11 URIs ('pkcs11' scheme as defined by RFC + * 7512), but may be extended to other schemes in future (such as 'file' URIs + * for local files and 'data' URIs for inline certificate data). + * + * Returns: the URI string + * + * Since: 1.6 + **/ +const char * +nm_setting_802_1x_get_phase2_ca_cert_uri(NMSetting8021x *setting) +{ + _cert_impl_get_uri(setting, phase2_ca_cert); +} + +/** + * nm_setting_802_1x_set_phase2_ca_cert: + * @setting: the #NMSetting8021x + * @value: when @scheme is set to either %NM_SETTING_802_1X_CK_SCHEME_PATH + * or %NM_SETTING_802_1X_CK_SCHEME_BLOB, pass the path of the "phase2" CA + * certificate file (PEM or DER format). The path must be UTF-8 encoded; use + * g_filename_to_utf8() to convert if needed. Passing %NULL with any @scheme + * clears the "phase2" CA certificate. + * @scheme: desired storage scheme for the certificate + * @out_format: on successful return, the type of the certificate added + * @error: on unsuccessful return, an error + * + * Reads a certificate from disk and sets the #NMSetting8021x:phase2-ca-cert + * property with the raw certificate data if using the + * %NM_SETTING_802_1X_CK_SCHEME_BLOB scheme, or with the path to the certificate + * file if using the %NM_SETTING_802_1X_CK_SCHEME_PATH scheme. + * + * Returns: %TRUE if the operation succeeded, %FALSE if it was unsuccessful + **/ +gboolean +nm_setting_802_1x_set_phase2_ca_cert(NMSetting8021x * setting, + const char * value, + NMSetting8021xCKScheme scheme, + NMSetting8021xCKFormat *out_format, + GError ** error) +{ + return _cert_impl_set(setting, PROP_PHASE2_CA_CERT, value, NULL, scheme, out_format, error); +} + +/** + * nm_setting_802_1x_get_phase2_ca_cert_password: + * @setting: the #NMSetting8021x + * + * Returns: the password used to access the "phase2" CA certificate stored in + * #NMSetting8021x:phase2-ca-cert property. Only makes sense if the certificate + * is stored on a PKCS#11 token that requires a login. + * + * Since: 1.8 + **/ +const char * +nm_setting_802_1x_get_phase2_ca_cert_password(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_ca_cert_password; +} + +/** + * nm_setting_802_1x_get_phase2_ca_cert_password_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSetting8021x:phase2-private-key-password + * + * Since: 1.8 + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_phase2_ca_cert_password_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_ca_cert_password_flags; +} + +/** + * nm_setting_802_1x_get_phase2_subject_match: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSetting8021x:phase2-subject-match property. This is + * the substring to be matched against the subject of the "phase 2" + * authentication server certificate, or %NULL no subject verification + * is to be performed. + **/ +const char * +nm_setting_802_1x_get_phase2_subject_match(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_subject_match; +} + +/** + * nm_setting_802_1x_get_num_phase2_altsubject_matches: + * @setting: the #NMSetting8021x + * + * Returns the number of entries in the + * #NMSetting8021x:phase2-altsubject-matches property of this setting. + * + * Returns: the number of phase2-altsubject-matches entries. + **/ +guint32 +nm_setting_802_1x_get_num_phase2_altsubject_matches(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), 0); + + return g_slist_length(NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_altsubject_matches); +} + +/** + * nm_setting_802_1x_get_phase2_domain_suffix_match: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSetting8021x:phase2-domain-suffix-match property. + * + * Since: 1.2 + **/ +const char * +nm_setting_802_1x_get_phase2_domain_suffix_match(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_domain_suffix_match; +} + +/** + * nm_setting_802_1x_get_phase2_domain_match: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSetting8021x:phase2-domain-match property. + * + * Since: 1.24 + **/ +const char * +nm_setting_802_1x_get_phase2_domain_match(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_domain_match; +} + +/** + * nm_setting_802_1x_get_phase2_altsubject_match: + * @setting: the #NMSettingConnection + * @i: the zero-based index of the array of "phase 2" altSubjectName matches + * + * Returns the "phase 2" altSubjectName match at index @i. + * + * Returns: the "phase 2" altSubjectName match at index @i + **/ +const char * +nm_setting_802_1x_get_phase2_altsubject_match(NMSetting8021x *setting, guint32 i) +{ + NMSetting8021xPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + g_return_val_if_fail(i <= g_slist_length(priv->phase2_altsubject_matches), NULL); + + return (const char *) g_slist_nth_data(priv->phase2_altsubject_matches, i); +} + +/** + * nm_setting_802_1x_add_phase2_altsubject_match: + * @setting: the #NMSetting8021x + * @phase2_altsubject_match: the "phase 2" altSubjectName to allow for this + * connection + * + * Adds an allowed alternate subject name match for "phase 2". Until + * at least one match is added, the altSubjectName of the "phase 2" + * remote authentication server is not verified. + * + * Returns: %TRUE if the "phase 2" alternative subject name match was + * successfully added, %FALSE if it was already allowed. + **/ +gboolean +nm_setting_802_1x_add_phase2_altsubject_match(NMSetting8021x *setting, + const char * phase2_altsubject_match) +{ + NMSetting8021xPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + g_return_val_if_fail(phase2_altsubject_match != NULL, FALSE); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + for (iter = priv->phase2_altsubject_matches; iter; iter = g_slist_next(iter)) { + if (!strcmp(phase2_altsubject_match, (char *) iter->data)) + return FALSE; + } + + priv->phase2_altsubject_matches = + g_slist_append(priv->phase2_altsubject_matches, g_strdup(phase2_altsubject_match)); + _notify(setting, PROP_PHASE2_ALTSUBJECT_MATCHES); + return TRUE; +} + +/** + * nm_setting_802_1x_remove_phase2_altsubject_match: + * @setting: the #NMSetting8021x + * @i: the index of the "phase 2" altSubjectName match to remove + * + * Removes the allowed "phase 2" altSubjectName at the specified index. + **/ +void +nm_setting_802_1x_remove_phase2_altsubject_match(NMSetting8021x *setting, guint32 i) +{ + NMSetting8021xPrivate *priv; + GSList * elt; + + g_return_if_fail(NM_IS_SETTING_802_1X(setting)); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + elt = g_slist_nth(priv->phase2_altsubject_matches, i); + g_return_if_fail(elt != NULL); + + g_free(elt->data); + priv->phase2_altsubject_matches = g_slist_delete_link(priv->phase2_altsubject_matches, elt); + _notify(setting, PROP_PHASE2_ALTSUBJECT_MATCHES); +} + +/** + * nm_setting_802_1x_remove_phase2_altsubject_match_by_value: + * @setting: the #NMSetting8021x + * @phase2_altsubject_match: the "phase 2" altSubjectName to remove + * + * Removes the allowed "phase 2" altSubjectName @phase2_altsubject_match. + * + * Returns: %TRUE if the alternative subject name match for "phase 2" was found and removed, + * %FALSE if it was not. + **/ +gboolean +nm_setting_802_1x_remove_phase2_altsubject_match_by_value(NMSetting8021x *setting, + const char * phase2_altsubject_match) +{ + NMSetting8021xPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + g_return_val_if_fail(phase2_altsubject_match != NULL, FALSE); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + for (iter = priv->phase2_altsubject_matches; iter; iter = g_slist_next(iter)) { + if (!strcmp(phase2_altsubject_match, (char *) iter->data)) { + priv->phase2_altsubject_matches = + g_slist_delete_link(priv->phase2_altsubject_matches, iter); + _notify(setting, PROP_PHASE2_ALTSUBJECT_MATCHES); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_802_1x_clear_phase2_altsubject_matches: + * @setting: the #NMSetting8021x + * + * Clears all "phase 2" altSubjectName matches. + **/ +void +nm_setting_802_1x_clear_phase2_altsubject_matches(NMSetting8021x *setting) +{ + NMSetting8021xPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_802_1X(setting)); + + priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + g_slist_free_full(priv->phase2_altsubject_matches, g_free); + priv->phase2_altsubject_matches = NULL; + _notify(setting, PROP_PHASE2_ALTSUBJECT_MATCHES); +} + +/** + * nm_setting_802_1x_get_phase2_client_cert_scheme: + * @setting: the #NMSetting8021x + * + * Returns the scheme used to store the "phase 2" client certificate. If the + * returned scheme is %NM_SETTING_802_1X_CK_SCHEME_BLOB, use + * nm_setting_802_1x_get_client_cert_blob(); if + * %NM_SETTING_802_1X_CK_SCHEME_PATH, use + * nm_setting_802_1x_get_client_cert_path(); if + * %NM_SETTING_802_1X_CK_SCHEME_PKCS11, use + * nm_setting_802_1x_get_client_cert_uri(). + * + * Returns: scheme used to store the "phase 2" client certificate (blob or path) + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_get_phase2_client_cert_scheme(NMSetting8021x *setting) +{ + _cert_impl_get_scheme(setting, phase2_client_cert); +} + +/** + * nm_setting_802_1x_get_phase2_client_cert_blob: + * @setting: the #NMSetting8021x + * + * Client certificates are used to identify the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * Returns: (transfer none): the "phase 2" client certificate data + **/ +GBytes * +nm_setting_802_1x_get_phase2_client_cert_blob(NMSetting8021x *setting) +{ + _cert_impl_get_blob(setting, phase2_client_cert); +} + +/** + * nm_setting_802_1x_get_phase2_client_cert_path: + * @setting: the #NMSetting8021x + * + * Client certificates are used to identify the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * Returns: path to the "phase 2" client certificate file + **/ +const char * +nm_setting_802_1x_get_phase2_client_cert_path(NMSetting8021x *setting) +{ + _cert_impl_get_path(setting, phase2_client_cert); +} + +/** + * nm_setting_802_1x_get_phase2_client_cert_uri: + * @setting: the #NMSetting8021x + * + * Returns the "phase 2" client certificate URI analogously to + * nm_setting_802_1x_get_phase2_ca_cert_blob() and + * nm_setting_802_1x_get_phase2_ca_cert_path(). + * + * Currently, it's limited to PKCS#11 URIs ('pkcs11' scheme as defined by RFC + * 7512), but may be extended to other schemes in future (such as 'file' URIs + * for local files and 'data' URIs for inline certificate data). + * + * Returns: the URI string + * + * Since: 1.6 + **/ +const char * +nm_setting_802_1x_get_phase2_client_cert_uri(NMSetting8021x *setting) +{ + _cert_impl_get_uri(setting, phase2_client_cert); +} + +/** + * nm_setting_802_1x_set_phase2_client_cert: + * @setting: the #NMSetting8021x + * @value: when @scheme is set to either %NM_SETTING_802_1X_CK_SCHEME_PATH + * or %NM_SETTING_802_1X_CK_SCHEME_BLOB, pass the path of the "phase2" client + * certificate file (PEM, DER, or PKCS#12 format). The path must be UTF-8 + * encoded; use g_filename_to_utf8() to convert if needed. Passing %NULL with + * any @scheme clears the "phase2" client certificate. + * @scheme: desired storage scheme for the certificate + * @out_format: on successful return, the type of the certificate added + * @error: on unsuccessful return, an error + * + * Reads a certificate from disk and sets the #NMSetting8021x:phase2-client-cert + * property with the raw certificate data if using the + * %NM_SETTING_802_1X_CK_SCHEME_BLOB scheme, or with the path to the certificate + * file if using the %NM_SETTING_802_1X_CK_SCHEME_PATH scheme. + * + * Client certificates are used to identify the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * Returns: %TRUE if the operation succeeded, %FALSE if it was unsuccessful + **/ +gboolean +nm_setting_802_1x_set_phase2_client_cert(NMSetting8021x * setting, + const char * value, + NMSetting8021xCKScheme scheme, + NMSetting8021xCKFormat *out_format, + GError ** error) +{ + return _cert_impl_set(setting, PROP_PHASE2_CLIENT_CERT, value, NULL, scheme, out_format, error); +} + +/** + * nm_setting_802_1x_get_phase2_client_cert_password: + * @setting: the #NMSetting8021x + * + * Returns: the password used to access the "phase2" client certificate stored in + * #NMSetting8021x:phase2-client-cert property. Only makes sense if the certificate + * is stored on a PKCS#11 token that requires a login. + * + * Since: 1.8 + **/ +const char * +nm_setting_802_1x_get_phase2_client_cert_password(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_client_cert_password; +} + +/** + * nm_setting_802_1x_get_phase2_client_cert_password_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSetting8021x:phase2-client-cert-password + * + * Since: 1.8 + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_phase2_client_cert_password_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_client_cert_password_flags; +} + +/** + * nm_setting_802_1x_get_password: + * @setting: the #NMSetting8021x + * + * Returns: the password used by the authentication method, if any, as specified + * by the #NMSetting8021x:password property + **/ +const char * +nm_setting_802_1x_get_password(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->password; +} + +/** + * nm_setting_802_1x_get_password_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the #NMSetting8021x:password + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_password_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->password_flags; +} + +/** + * nm_setting_802_1x_get_password_raw: + * @setting: the #NMSetting8021x + * + * Returns: (transfer none): the password used by the authentication method as a + * UTF-8-encoded array of bytes, as specified by the + * #NMSetting8021x:password-raw property + **/ +GBytes * +nm_setting_802_1x_get_password_raw(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->password_raw; +} + +/** + * nm_setting_802_1x_get_password_raw_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSetting8021x:password-raw + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_password_raw_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->password_raw_flags; +} + +/** + * nm_setting_802_1x_get_pin: + * @setting: the #NMSetting8021x + * + * Returns: the PIN used by the authentication method, if any, as specified + * by the #NMSetting8021x:pin property + **/ +const char * +nm_setting_802_1x_get_pin(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->pin; +} + +/** + * nm_setting_802_1x_get_pin_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSetting8021x:pin + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_pin_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->pin_flags; +} + +/** + * nm_setting_802_1x_get_private_key_scheme: + * @setting: the #NMSetting8021x + * + * Returns the scheme used to store the private key. If the returned scheme is + * %NM_SETTING_802_1X_CK_SCHEME_BLOB, use + * nm_setting_802_1x_get_client_cert_blob(); if + * %NM_SETTING_802_1X_CK_SCHEME_PATH, use + * nm_setting_802_1x_get_client_cert_path(); if + * %NM_SETTING_802_1X_CK_SCHEME_PKCS11, use + * nm_setting_802_1x_get_client_cert_uri(). + * + * Returns: scheme used to store the private key (blob or path) + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_get_private_key_scheme(NMSetting8021x *setting) +{ + _cert_impl_get_scheme(setting, private_key); +} + +/** + * nm_setting_802_1x_get_private_key_blob: + * @setting: the #NMSetting8021x + * + * Private keys are used to authenticate the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * WARNING: the private key property is not a "secret" property, and thus + * unencrypted private key data may be readable by unprivileged users. Private + * keys should always be encrypted with a private key password. + * + * Returns: (transfer none): the private key data + **/ +GBytes * +nm_setting_802_1x_get_private_key_blob(NMSetting8021x *setting) +{ + _cert_impl_get_blob(setting, private_key); +} + +/** + * nm_setting_802_1x_get_private_key_path: + * @setting: the #NMSetting8021x + * + * Private keys are used to authenticate the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * Returns: path to the private key file + **/ +const char * +nm_setting_802_1x_get_private_key_path(NMSetting8021x *setting) +{ + _cert_impl_get_path(setting, private_key); +} + +/** + * nm_setting_802_1x_get_private_key_uri: + * @setting: the #NMSetting8021x + * + * Returns the private key URI analogously to + * nm_setting_802_1x_get_private_key_blob() and + * nm_setting_802_1x_get_private_key_path(). + * + * Currently, it's limited to PKCS#11 URIs ('pkcs11' scheme as defined by RFC + * 7512), but may be extended to other schemes in future (such as 'file' URIs + * for local files and 'data' URIs for inline certificate data). + * + * Returns: the URI string + * + * Since: 1.6 + **/ +const char * +nm_setting_802_1x_get_private_key_uri(NMSetting8021x *setting) +{ + _cert_impl_get_uri(setting, private_key); +} + +/** + * nm_setting_802_1x_set_private_key: + * @setting: the #NMSetting8021x + * @value: when @scheme is set to either %NM_SETTING_802_1X_CK_SCHEME_PATH or + * %NM_SETTING_802_1X_CK_SCHEME_BLOB, pass the path of the private key file + * (PEM, DER, or PKCS#12 format). The path must be UTF-8 encoded; use + * g_filename_to_utf8() to convert if needed. Passing %NULL with any @scheme + * clears the private key. + * @password: password used to decrypt the private key, or %NULL if the password + * is unknown. If the password is given but fails to decrypt the private key, + * an error is returned. + * @scheme: desired storage scheme for the private key + * @out_format: on successful return, the type of the private key added + * @error: on unsuccessful return, an error + * + * Private keys are used to authenticate the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * This function reads a private key from disk and sets the + * #NMSetting8021x:private-key property with the private key file data if using + * the %NM_SETTING_802_1X_CK_SCHEME_BLOB scheme, or with the path to the private + * key file if using the %NM_SETTING_802_1X_CK_SCHEME_PATH scheme. + * + * If @password is given, this function attempts to decrypt the private key to + * verify that @password is correct, and if it is, updates the + * #NMSetting8021x:private-key-password property with the given @password. If + * the decryption is unsuccessful, %FALSE is returned, @error is set, and no + * internal data is changed. If no @password is given, the private key is + * assumed to be valid, no decryption is performed, and the password may be set + * at a later time. + * + * WARNING: the private key property is not a "secret" property, and thus + * unencrypted private key data using the BLOB scheme may be readable by + * unprivileged users. Private keys should always be encrypted with a private + * key password to prevent unauthorized access to unencrypted private key data. + * + * Returns: %TRUE if the operation succeeded, %FALSE if it was unsuccessful + **/ +gboolean +nm_setting_802_1x_set_private_key(NMSetting8021x * setting, + const char * value, + const char * password, + NMSetting8021xCKScheme scheme, + NMSetting8021xCKFormat *out_format, + GError ** error) +{ + return _cert_impl_set(setting, PROP_PRIVATE_KEY, value, password, scheme, out_format, error); +} + +/** + * nm_setting_802_1x_get_private_key_password: + * @setting: the #NMSetting8021x + * + * Returns: the private key password used to decrypt the private key if + * previously set with nm_setting_802_1x_set_private_key(), or the + * #NMSetting8021x:private-key-password property. + **/ +const char * +nm_setting_802_1x_get_private_key_password(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->private_key_password; +} + +/** + * nm_setting_802_1x_get_private_key_password_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSetting8021x:private-key-password + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_private_key_password_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->private_key_password_flags; +} + +/** + * nm_setting_802_1x_get_private_key_format: + * @setting: the #NMSetting8021x + * + * Returns: the data format of the private key data stored in the + * #NMSetting8021x:private-key property + **/ +NMSetting8021xCKFormat +nm_setting_802_1x_get_private_key_format(NMSetting8021x *setting) +{ + return _cert_impl_get_key_format(setting, private_key); +} + +/** + * nm_setting_802_1x_get_phase2_private_key_password: + * @setting: the #NMSetting8021x + * + * Returns: the private key password used to decrypt the private key if + * previously set with nm_setting_802_1x_set_phase2_private_key() or the + * #NMSetting8021x:phase2-private-key-password property. + **/ +const char * +nm_setting_802_1x_get_phase2_private_key_password(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NULL); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_private_key_password; +} + +/** + * nm_setting_802_1x_get_phase2_private_key_password_flags: + * @setting: the #NMSetting8021x + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSetting8021x:phase2-private-key-password + **/ +NMSettingSecretFlags +nm_setting_802_1x_get_phase2_private_key_password_flags(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->phase2_private_key_password_flags; +} + +/** + * nm_setting_802_1x_get_phase2_private_key_scheme: + * @setting: the #NMSetting8021x + * + * Returns the scheme used to store the "phase 2" private key. If the returned + * scheme is %NM_SETTING_802_1X_CK_SCHEME_BLOB, use + * nm_setting_802_1x_get_client_cert_blob(); if + * %NM_SETTING_802_1X_CK_SCHEME_PATH, use + * nm_setting_802_1x_get_client_cert_path(); if + * %NM_SETTING_802_1X_CK_SCHEME_PKCS11, use + * nm_setting_802_1x_get_client_cert_uri(). + * + * Returns: scheme used to store the "phase 2" private key (blob or path) + **/ +NMSetting8021xCKScheme +nm_setting_802_1x_get_phase2_private_key_scheme(NMSetting8021x *setting) +{ + _cert_impl_get_scheme(setting, phase2_private_key); +} + +/** + * nm_setting_802_1x_get_phase2_private_key_blob: + * @setting: the #NMSetting8021x + * + * Private keys are used to authenticate the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * WARNING: the phase2 private key property is not a "secret" property, and thus + * unencrypted private key data may be readable by unprivileged users. Private + * keys should always be encrypted with a private key password. + * + * Returns: (transfer none): the "phase 2" private key data + **/ +GBytes * +nm_setting_802_1x_get_phase2_private_key_blob(NMSetting8021x *setting) +{ + _cert_impl_get_blob(setting, phase2_private_key); +} + +/** + * nm_setting_802_1x_get_phase2_private_key_path: + * @setting: the #NMSetting8021x + * + * Private keys are used to authenticate the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * Returns: path to the "phase 2" private key file + **/ +const char * +nm_setting_802_1x_get_phase2_private_key_path(NMSetting8021x *setting) +{ + _cert_impl_get_path(setting, phase2_private_key); +} + +/** + * nm_setting_802_1x_get_phase2_private_key_uri: + * @setting: the #NMSetting8021x + * + * Returns the "phase 2" private key URI analogously to + * nm_setting_802_1x_get_phase2_private_key_blob() and + * nm_setting_802_1x_get_phase2_private_key_path(). + * + * Currently, it's limited to PKCS#11 URIs ('pkcs11' scheme as defined by RFC + * 7512), but may be extended to other schemes in future (such as 'file' URIs + * for local files and 'data' URIs for inline certificate data). + * + * Returns: the URI string + * + * Since: 1.6 + **/ +const char * +nm_setting_802_1x_get_phase2_private_key_uri(NMSetting8021x *setting) +{ + _cert_impl_get_uri(setting, phase2_private_key); +} + +/** + * nm_setting_802_1x_set_phase2_private_key: + * @setting: the #NMSetting8021x + * @value: when @scheme is set to either %NM_SETTING_802_1X_CK_SCHEME_PATH or + * %NM_SETTING_802_1X_CK_SCHEME_BLOB, pass the path of the "phase2" private + * key file (PEM, DER, or PKCS#12 format). The path must be UTF-8 encoded; + * use g_filename_to_utf8() to convert if needed. Passing %NULL with any + * @scheme clears the private key. + * @password: password used to decrypt the private key, or %NULL if the password + * is unknown. If the password is given but fails to decrypt the private key, + * an error is returned. + * @scheme: desired storage scheme for the private key + * @out_format: on successful return, the type of the private key added + * @error: on unsuccessful return, an error + * + * Private keys are used to authenticate the connecting client to the network + * when EAP-TLS is used as either the "phase 1" or "phase 2" 802.1x + * authentication method. + * + * This function reads a private key from disk and sets the + * #NMSetting8021x:phase2-private-key property with the private key file data if + * using the %NM_SETTING_802_1X_CK_SCHEME_BLOB scheme, or with the path to the + * private key file if using the %NM_SETTING_802_1X_CK_SCHEME_PATH scheme. + * + * If @password is given, this function attempts to decrypt the private key to + * verify that @password is correct, and if it is, updates the + * #NMSetting8021x:phase2-private-key-password property with the given + * @password. If the decryption is unsuccessful, %FALSE is returned, @error is + * set, and no internal data is changed. If no @password is given, the private + * key is assumed to be valid, no decryption is performed, and the password may + * be set at a later time. + * + * WARNING: the "phase2" private key property is not a "secret" property, and + * thus unencrypted private key data using the BLOB scheme may be readable by + * unprivileged users. Private keys should always be encrypted with a private + * key password to prevent unauthorized access to unencrypted private key data. + * + * Returns: %TRUE if the operation succeeded, %FALSE if it was unsuccessful + **/ +gboolean +nm_setting_802_1x_set_phase2_private_key(NMSetting8021x * setting, + const char * value, + const char * password, + NMSetting8021xCKScheme scheme, + NMSetting8021xCKFormat *out_format, + GError ** error) +{ + return _cert_impl_set(setting, + PROP_PHASE2_PRIVATE_KEY, + value, + password, + scheme, + out_format, + error); +} + +/** + * nm_setting_802_1x_get_phase2_private_key_format: + * @setting: the #NMSetting8021x + * + * Returns: the data format of the "phase 2" private key data stored in the + * #NMSetting8021x:phase2-private-key property + **/ +NMSetting8021xCKFormat +nm_setting_802_1x_get_phase2_private_key_format(NMSetting8021x *setting) +{ + return _cert_impl_get_key_format(setting, phase2_private_key); +} + +/** + * nm_setting_802_1x_get_auth_timeout: + * @setting: the #NMSetting8021x + * + * Returns the value contained in the #NMSetting8021x:auth-timeout property. + * + * Returns: the configured authentication timeout in seconds. Zero means the + * global default value. + * + * Since: 1.8 + **/ +int +nm_setting_802_1x_get_auth_timeout(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), 0); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->auth_timeout; +} + +/** + * nm_setting_802_1x_get_optional: + * @setting: the #NMSetting8021x + * + * Returns the value contained in the #NMSetting8021x:optional property. + * + * Returns: %TRUE if the activation should proceed even when the 802.1X + * authentication fails; %FALSE otherwise + * + * Since: 1.22 + **/ +gboolean +nm_setting_802_1x_get_optional(NMSetting8021x *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_802_1X(setting), FALSE); + + return NM_SETTING_802_1X_GET_PRIVATE(setting)->optional; +} + +/*****************************************************************************/ + +static void +need_secrets_password(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2) +{ + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + + if ((!priv->password || !strlen(priv->password)) + && (!priv->password_raw || !g_bytes_get_size(priv->password_raw))) { + g_ptr_array_add(secrets, NM_SETTING_802_1X_PASSWORD); + g_ptr_array_add(secrets, NM_SETTING_802_1X_PASSWORD_RAW); + } +} + +static void +need_secrets_sim(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2) +{ + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + + if (!priv->pin || !strlen(priv->pin)) + g_ptr_array_add(secrets, NM_SETTING_802_1X_PIN); +} + +static gboolean +need_private_key_password(GBytes * blob, + NMSetting8021xCKScheme scheme, + const char * path, + const char * password, + NMSettingSecretFlags flags) +{ + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + + if (flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + return FALSE; + + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11 && flags == NM_SETTING_SECRET_FLAG_NONE) + return FALSE; + + /* Private key password is required */ + if (password) { + if (path) + format = nm_crypto_verify_private_key(path, password, NULL, NULL); + else if (blob) + format = nm_crypto_verify_private_key_data(g_bytes_get_data(blob, NULL), + g_bytes_get_size(blob), + password, + NULL, + NULL); + else + return FALSE; + } + + return (format == NM_CRYPTO_FILE_FORMAT_UNKNOWN); +} + +static void +need_secrets_tls(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2) +{ + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + NMSetting8021xCKScheme scheme; + GBytes * blob = NULL; + const char * path = NULL; + + if (phase2) { + scheme = nm_setting_802_1x_get_phase2_private_key_scheme(self); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) + path = nm_setting_802_1x_get_phase2_private_key_path(self); + else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) + blob = nm_setting_802_1x_get_phase2_private_key_blob(self); + else if (scheme != NM_SETTING_802_1X_CK_SCHEME_PKCS11) + g_warning("%s: unknown phase2 private key scheme %d", __func__, scheme); + + if (need_private_key_password(blob, + scheme, + path, + priv->phase2_private_key_password, + priv->phase2_private_key_password_flags)) + g_ptr_array_add(secrets, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD); + + scheme = nm_setting_802_1x_get_phase2_ca_cert_scheme(self); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11 + && !(priv->phase2_ca_cert_password_flags == NM_SETTING_SECRET_FLAG_NONE + || priv->phase2_ca_cert_password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + && !priv->phase2_ca_cert_password) + g_ptr_array_add(secrets, NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD); + + scheme = nm_setting_802_1x_get_phase2_client_cert_scheme(self); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11 + && !(priv->phase2_client_cert_password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED + || priv->phase2_client_cert_password_flags == NM_SETTING_SECRET_FLAG_NONE) + && !priv->phase2_client_cert_password) + g_ptr_array_add(secrets, NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD); + } else { + scheme = nm_setting_802_1x_get_private_key_scheme(self); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) + path = nm_setting_802_1x_get_private_key_path(self); + else if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) + blob = nm_setting_802_1x_get_private_key_blob(self); + else if (scheme != NM_SETTING_802_1X_CK_SCHEME_PKCS11) + g_warning("%s: unknown private key scheme %d", __func__, scheme); + + if (need_private_key_password(blob, + scheme, + path, + priv->private_key_password, + priv->private_key_password_flags)) + g_ptr_array_add(secrets, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD); + + scheme = nm_setting_802_1x_get_ca_cert_scheme(self); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11 + && !(priv->ca_cert_password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED + || priv->ca_cert_password_flags == NM_SETTING_SECRET_FLAG_NONE) + && !priv->ca_cert_password) + g_ptr_array_add(secrets, NM_SETTING_802_1X_CA_CERT_PASSWORD); + + scheme = nm_setting_802_1x_get_client_cert_scheme(self); + if (scheme == NM_SETTING_802_1X_CK_SCHEME_PKCS11 + && !(priv->client_cert_password_flags == NM_SETTING_SECRET_FLAG_NONE + || priv->client_cert_password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + && !priv->client_cert_password) + g_ptr_array_add(secrets, NM_SETTING_802_1X_CLIENT_CERT_PASSWORD); + } +} + +static gboolean +verify_tls(NMSetting8021x *self, gboolean phase2, GError **error) +{ + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + + if (phase2) { + if (!priv->phase2_client_cert) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT); + return FALSE; + } else if (!g_bytes_get_size(priv->phase2_client_cert)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT); + return FALSE; + } + + /* Private key is required for TLS */ + if (!priv->phase2_private_key) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY); + return FALSE; + } else if (!g_bytes_get_size(priv->phase2_private_key)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY); + return FALSE; + } + + /* If the private key is PKCS#12, check that it matches the client cert */ + if (nm_crypto_is_pkcs12_data(g_bytes_get_data(priv->phase2_private_key, NULL), + g_bytes_get_size(priv->phase2_private_key), + NULL)) { + if (!g_bytes_equal(priv->phase2_private_key, priv->phase2_client_cert)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("has to match '%s' property for PKCS#12"), + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT); + return FALSE; + } + } + } else { + if (!priv->client_cert) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CLIENT_CERT); + return FALSE; + } else if (!g_bytes_get_size(priv->client_cert)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CLIENT_CERT); + return FALSE; + } + + /* Private key is required for TLS */ + if (!priv->private_key) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PRIVATE_KEY); + return FALSE; + } else if (!g_bytes_get_size(priv->private_key)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PRIVATE_KEY); + return FALSE; + } + + /* If the private key is PKCS#12, check that it matches the client cert */ + if (nm_crypto_is_pkcs12_data(g_bytes_get_data(priv->private_key, NULL), + g_bytes_get_size(priv->private_key), + NULL)) { + if (!g_bytes_equal(priv->private_key, priv->client_cert)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("has to match '%s' property for PKCS#12"), + NM_SETTING_802_1X_PRIVATE_KEY); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_CLIENT_CERT); + return FALSE; + } + } + } + + return TRUE; +} + +static gboolean +verify_ttls(NMSetting8021x *self, gboolean phase2, GError **error) +{ + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + + if (!priv->identity || !strlen(priv->identity)) { + if (!priv->identity) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + } + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_IDENTITY); + return FALSE; + } + + if ((!priv->phase2_auth && !priv->phase2_autheap) + || (priv->phase2_auth && priv->phase2_autheap)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("exactly one property must be set")); + g_prefix_error(error, + "%s.%s, %s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_AUTH, + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_AUTHEAP); + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_identity(NMSetting8021x *self, gboolean phase2, GError **error) +{ + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + + if (!priv->identity) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_IDENTITY); + return FALSE; + } else if (!strlen(priv->identity)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_IDENTITY); + return FALSE; + } + + return TRUE; +} + +static void +need_secrets_phase2(NMSetting8021x *self, GPtrArray *secrets, gboolean phase2) +{ + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + char * method = NULL; + int i; + + g_return_if_fail(phase2 == FALSE); + + /* Check phase2_auth and phase2_autheap */ + method = priv->phase2_auth; + if (!method && priv->phase2_autheap) + method = priv->phase2_autheap; + + if (!method) { + g_warning("Couldn't find EAP method."); + g_assert_not_reached(); + return; + } + + /* Ask the configured phase2 method if it needs secrets */ + for (i = 0; eap_methods_table[i].method; i++) { + if (eap_methods_table[i].ns_func == NULL) + continue; + if (!strcmp(eap_methods_table[i].method, method)) { + (*eap_methods_table[i].ns_func)(self, secrets, TRUE); + break; + } + } +} + +static const EAPMethodsTable eap_methods_table[] = { + {"leap", need_secrets_password, verify_identity}, + {"pwd", need_secrets_password, verify_identity}, + {"md5", need_secrets_password, verify_identity}, + {"pap", need_secrets_password, verify_identity}, + {"chap", need_secrets_password, verify_identity}, + {"mschap", need_secrets_password, verify_identity}, + {"mschapv2", need_secrets_password, verify_identity}, + {"fast", need_secrets_password, verify_identity}, + {"tls", need_secrets_tls, verify_tls}, + {"peap", need_secrets_phase2, verify_ttls}, + {"ttls", need_secrets_phase2, verify_ttls}, + {"sim", need_secrets_sim, NULL}, + {"gtc", need_secrets_password, verify_identity}, + {"otp", NULL, NULL}, // FIXME: implement + {"external", NULL, NULL}, + {NULL, NULL, NULL}}; + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSetting8021x * self = NM_SETTING_802_1X(setting); + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + const char * valid_eap[] = + {"leap", "md5", "tls", "peap", "ttls", "sim", "fast", "pwd", "external", NULL}; + GSList *iter; + + if (error) + g_return_val_if_fail(*error == NULL, FALSE); + + if (connection && priv->optional + && !nm_streq0(nm_connection_get_connection_type(connection), + NM_SETTING_WIRED_SETTING_NAME)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("can be enabled only on Ethernet connections")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_OPTIONAL); + return FALSE; + } + + if (!priv->eap) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, NM_SETTING_802_1X_EAP); + return FALSE; + } + + if (!_nm_utils_string_slist_validate(priv->eap, valid_eap)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_802_1X_SETTING_NAME, NM_SETTING_802_1X_EAP); + return FALSE; + } + + /* Ask each configured EAP method if its valid */ + for (iter = priv->eap; iter; iter = g_slist_next(iter)) { + const char *method = (const char *) iter->data; + int i; + + for (i = 0; eap_methods_table[i].method; i++) { + if (eap_methods_table[i].v_func == NULL) + continue; + if (!strcmp(eap_methods_table[i].method, method)) { + if (!(*eap_methods_table[i].v_func) (self, FALSE, error)) + return FALSE; + break; + } + } + } + + if (!NM_IN_STRSET(priv->phase1_peapver, NULL, "0", "1")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + priv->phase1_peapver); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE1_PEAPVER); + return FALSE; + } + + if (!NM_IN_STRSET(priv->phase1_peaplabel, NULL, "0", "1")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + priv->phase1_peaplabel); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE1_PEAPLABEL); + return FALSE; + } + + if (!NM_IN_STRSET(priv->phase1_fast_provisioning, NULL, "0", "1", "2", "3")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + priv->phase1_fast_provisioning); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING); + return FALSE; + } + + if (NM_FLAGS_ANY(priv->phase1_auth_flags, ~NM_SETTING_802_1X_AUTH_FLAGS_ALL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid auth flags")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE1_AUTH_FLAGS); + return FALSE; + } + + if (!NM_IN_STRSET(priv->phase2_auth, + NULL, + "pap", + "chap", + "mschap", + "mschapv2", + "gtc", + "otp", + "md5", + "tls")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + priv->phase2_auth); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_AUTH); + return FALSE; + } + + if (!NM_IN_STRSET(priv->phase2_autheap, NULL, "md5", "mschapv2", "otp", "gtc", "tls")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + priv->phase2_autheap); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_802_1X_SETTING_NAME, + NM_SETTING_802_1X_PHASE2_AUTHEAP); + return FALSE; + } + + if (!_cert_verify_property(priv->ca_cert, + NM_SETTING_802_1X_CA_CERT, + priv->ca_cert_password, + NM_SETTING_802_1X_CA_CERT_PASSWORD, + error)) + return FALSE; + if (!_cert_verify_property(priv->phase2_ca_cert, + NM_SETTING_802_1X_PHASE2_CA_CERT, + priv->phase2_ca_cert_password, + NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD, + error)) + return FALSE; + + if (!_cert_verify_property(priv->client_cert, + NM_SETTING_802_1X_CLIENT_CERT, + priv->client_cert_password, + NM_SETTING_802_1X_CLIENT_CERT_PASSWORD, + error)) + return FALSE; + if (!_cert_verify_property(priv->phase2_client_cert, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + priv->phase2_client_cert_password, + NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD, + error)) + return FALSE; + + if (!_cert_verify_property(priv->private_key, NM_SETTING_802_1X_PRIVATE_KEY, NULL, NULL, error)) + return FALSE; + if (!_cert_verify_property(priv->phase2_private_key, + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + NULL, + NULL, + error)) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSetting8021x * self = NM_SETTING_802_1X(setting); + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + GSList * iter; + GPtrArray * secrets; + gboolean eap_method_found = FALSE; + + secrets = g_ptr_array_sized_new(4); + + /* Ask each configured EAP method if it needs secrets */ + for (iter = priv->eap; iter && !eap_method_found; iter = g_slist_next(iter)) { + const char *method = (const char *) iter->data; + int i; + + for (i = 0; eap_methods_table[i].method; i++) { + if (eap_methods_table[i].ns_func == NULL) + continue; + if (!strcmp(eap_methods_table[i].method, method)) { + (*eap_methods_table[i].ns_func)(self, secrets, FALSE); + + /* Only break out of the outer loop if this EAP method + * needed secrets. + */ + if (secrets->len > 0) + eap_method_found = TRUE; + break; + } + } + } + + if (secrets->len == 0) { + g_ptr_array_free(secrets, TRUE); + return NULL; + } + + return secrets; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSetting8021x * setting = NM_SETTING_802_1X(object); + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_EAP: + g_value_take_boxed(value, _nm_utils_slist_to_strv(priv->eap, TRUE)); + break; + case PROP_IDENTITY: + g_value_set_string(value, priv->identity); + break; + case PROP_ANONYMOUS_IDENTITY: + g_value_set_string(value, priv->anonymous_identity); + break; + case PROP_PAC_FILE: + g_value_set_string(value, priv->pac_file); + break; + case PROP_CA_CERT: + g_value_set_boxed(value, priv->ca_cert); + break; + case PROP_CA_CERT_PASSWORD: + g_value_set_string(value, priv->ca_cert_password); + break; + case PROP_CA_CERT_PASSWORD_FLAGS: + g_value_set_flags(value, priv->ca_cert_password_flags); + break; + case PROP_CA_PATH: + g_value_set_string(value, priv->ca_path); + break; + case PROP_SUBJECT_MATCH: + g_value_set_string(value, priv->subject_match); + break; + case PROP_ALTSUBJECT_MATCHES: + g_value_take_boxed(value, _nm_utils_slist_to_strv(priv->altsubject_matches, TRUE)); + break; + case PROP_DOMAIN_SUFFIX_MATCH: + g_value_set_string(value, priv->domain_suffix_match); + break; + case PROP_DOMAIN_MATCH: + g_value_set_string(value, priv->domain_match); + break; + case PROP_CLIENT_CERT: + g_value_set_boxed(value, priv->client_cert); + break; + case PROP_CLIENT_CERT_PASSWORD: + g_value_set_string(value, priv->client_cert_password); + break; + case PROP_CLIENT_CERT_PASSWORD_FLAGS: + g_value_set_flags(value, priv->client_cert_password_flags); + break; + case PROP_PHASE1_PEAPVER: + g_value_set_string(value, priv->phase1_peapver); + break; + case PROP_PHASE1_PEAPLABEL: + g_value_set_string(value, priv->phase1_peaplabel); + break; + case PROP_PHASE1_FAST_PROVISIONING: + g_value_set_string(value, priv->phase1_fast_provisioning); + break; + case PROP_PHASE1_AUTH_FLAGS: + g_value_set_uint(value, priv->phase1_auth_flags); + break; + case PROP_PHASE2_AUTH: + g_value_set_string(value, priv->phase2_auth); + break; + case PROP_PHASE2_AUTHEAP: + g_value_set_string(value, priv->phase2_autheap); + break; + case PROP_PHASE2_CA_CERT: + g_value_set_boxed(value, priv->phase2_ca_cert); + break; + case PROP_PHASE2_CA_CERT_PASSWORD: + g_value_set_string(value, priv->phase2_ca_cert_password); + break; + case PROP_PHASE2_CA_CERT_PASSWORD_FLAGS: + g_value_set_flags(value, priv->phase2_ca_cert_password_flags); + break; + case PROP_PHASE2_CA_PATH: + g_value_set_string(value, priv->phase2_ca_path); + break; + case PROP_PHASE2_SUBJECT_MATCH: + g_value_set_string(value, priv->phase2_subject_match); + break; + case PROP_PHASE2_ALTSUBJECT_MATCHES: + g_value_take_boxed(value, _nm_utils_slist_to_strv(priv->phase2_altsubject_matches, TRUE)); + break; + case PROP_PHASE2_DOMAIN_SUFFIX_MATCH: + g_value_set_string(value, priv->phase2_domain_suffix_match); + break; + case PROP_PHASE2_DOMAIN_MATCH: + g_value_set_string(value, priv->phase2_domain_match); + break; + case PROP_PHASE2_CLIENT_CERT: + g_value_set_boxed(value, priv->phase2_client_cert); + break; + case PROP_PHASE2_CLIENT_CERT_PASSWORD: + g_value_set_string(value, priv->phase2_client_cert_password); + break; + case PROP_PHASE2_CLIENT_CERT_PASSWORD_FLAGS: + g_value_set_flags(value, priv->phase2_client_cert_password_flags); + break; + case PROP_PASSWORD: + g_value_set_string(value, priv->password); + break; + case PROP_PASSWORD_FLAGS: + g_value_set_flags(value, priv->password_flags); + break; + case PROP_PASSWORD_RAW: + g_value_set_boxed(value, priv->password_raw); + break; + case PROP_PASSWORD_RAW_FLAGS: + g_value_set_flags(value, priv->password_raw_flags); + break; + case PROP_PRIVATE_KEY: + g_value_set_boxed(value, priv->private_key); + break; + case PROP_PRIVATE_KEY_PASSWORD: + g_value_set_string(value, priv->private_key_password); + break; + case PROP_PRIVATE_KEY_PASSWORD_FLAGS: + g_value_set_flags(value, priv->private_key_password_flags); + break; + case PROP_PHASE2_PRIVATE_KEY: + g_value_set_boxed(value, priv->phase2_private_key); + break; + case PROP_PHASE2_PRIVATE_KEY_PASSWORD: + g_value_set_string(value, priv->phase2_private_key_password); + break; + case PROP_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS: + g_value_set_flags(value, priv->phase2_private_key_password_flags); + break; + case PROP_PIN: + g_value_set_string(value, priv->pin); + break; + case PROP_PIN_FLAGS: + g_value_set_flags(value, priv->pin_flags); + break; + case PROP_SYSTEM_CA_CERTS: + g_value_set_boolean(value, priv->system_ca_certs); + break; + case PROP_AUTH_TIMEOUT: + g_value_set_int(value, priv->auth_timeout); + break; + case PROP_OPTIONAL: + g_value_set_boolean(value, priv->optional); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSetting8021x * setting = NM_SETTING_802_1X(object); + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_EAP: + g_slist_free_full(priv->eap, g_free); + priv->eap = _nm_utils_strv_to_slist(g_value_get_boxed(value), TRUE); + break; + case PROP_IDENTITY: + g_free(priv->identity); + priv->identity = g_value_dup_string(value); + break; + case PROP_ANONYMOUS_IDENTITY: + g_free(priv->anonymous_identity); + priv->anonymous_identity = g_value_dup_string(value); + break; + case PROP_PAC_FILE: + g_free(priv->pac_file); + priv->pac_file = g_value_dup_string(value); + break; + case PROP_CA_CERT: + g_bytes_unref(priv->ca_cert); + priv->ca_cert = g_value_dup_boxed(value); + break; + case PROP_CA_CERT_PASSWORD: + g_free(priv->ca_cert_password); + priv->ca_cert_password = g_value_dup_string(value); + break; + case PROP_CA_CERT_PASSWORD_FLAGS: + priv->ca_cert_password_flags = g_value_get_flags(value); + break; + case PROP_CA_PATH: + g_free(priv->ca_path); + priv->ca_path = g_value_dup_string(value); + break; + case PROP_SUBJECT_MATCH: + g_free(priv->subject_match); + priv->subject_match = nm_strdup_not_empty(g_value_get_string(value)); + break; + case PROP_ALTSUBJECT_MATCHES: + g_slist_free_full(priv->altsubject_matches, g_free); + priv->altsubject_matches = _nm_utils_strv_to_slist(g_value_get_boxed(value), TRUE); + break; + case PROP_DOMAIN_SUFFIX_MATCH: + g_free(priv->domain_suffix_match); + priv->domain_suffix_match = nm_strdup_not_empty(g_value_get_string(value)); + break; + case PROP_DOMAIN_MATCH: + g_free(priv->domain_match); + priv->domain_match = nm_strdup_not_empty(g_value_get_string(value)); + break; + case PROP_CLIENT_CERT: + g_bytes_unref(priv->client_cert); + priv->client_cert = g_value_dup_boxed(value); + break; + case PROP_CLIENT_CERT_PASSWORD: + g_free(priv->client_cert_password); + priv->client_cert_password = g_value_dup_string(value); + break; + case PROP_CLIENT_CERT_PASSWORD_FLAGS: + priv->client_cert_password_flags = g_value_get_flags(value); + break; + case PROP_PHASE1_PEAPVER: + g_free(priv->phase1_peapver); + priv->phase1_peapver = g_value_dup_string(value); + break; + case PROP_PHASE1_PEAPLABEL: + g_free(priv->phase1_peaplabel); + priv->phase1_peaplabel = g_value_dup_string(value); + break; + case PROP_PHASE1_FAST_PROVISIONING: + g_free(priv->phase1_fast_provisioning); + priv->phase1_fast_provisioning = g_value_dup_string(value); + break; + case PROP_PHASE1_AUTH_FLAGS: + priv->phase1_auth_flags = g_value_get_uint(value); + break; + case PROP_PHASE2_AUTH: + g_free(priv->phase2_auth); + priv->phase2_auth = g_value_dup_string(value); + break; + case PROP_PHASE2_AUTHEAP: + g_free(priv->phase2_autheap); + priv->phase2_autheap = g_value_dup_string(value); + break; + case PROP_PHASE2_CA_CERT: + g_bytes_unref(priv->phase2_ca_cert); + priv->phase2_ca_cert = g_value_dup_boxed(value); + break; + case PROP_PHASE2_CA_CERT_PASSWORD: + g_free(priv->phase2_ca_cert_password); + priv->phase2_ca_cert_password = g_value_dup_string(value); + break; + case PROP_PHASE2_CA_CERT_PASSWORD_FLAGS: + priv->phase2_ca_cert_password_flags = g_value_get_flags(value); + break; + case PROP_PHASE2_CA_PATH: + g_free(priv->phase2_ca_path); + priv->phase2_ca_path = g_value_dup_string(value); + break; + case PROP_PHASE2_SUBJECT_MATCH: + g_free(priv->phase2_subject_match); + priv->phase2_subject_match = nm_strdup_not_empty(g_value_get_string(value)); + break; + case PROP_PHASE2_ALTSUBJECT_MATCHES: + g_slist_free_full(priv->phase2_altsubject_matches, g_free); + priv->phase2_altsubject_matches = _nm_utils_strv_to_slist(g_value_get_boxed(value), TRUE); + break; + case PROP_PHASE2_DOMAIN_SUFFIX_MATCH: + g_free(priv->phase2_domain_suffix_match); + priv->phase2_domain_suffix_match = nm_strdup_not_empty(g_value_get_string(value)); + break; + case PROP_PHASE2_DOMAIN_MATCH: + g_free(priv->phase2_domain_match); + priv->phase2_domain_match = nm_strdup_not_empty(g_value_get_string(value)); + break; + case PROP_PHASE2_CLIENT_CERT: + g_bytes_unref(priv->phase2_client_cert); + priv->phase2_client_cert = g_value_dup_boxed(value); + break; + case PROP_PHASE2_CLIENT_CERT_PASSWORD: + g_free(priv->phase2_client_cert_password); + priv->phase2_client_cert_password = g_value_dup_string(value); + break; + case PROP_PHASE2_CLIENT_CERT_PASSWORD_FLAGS: + priv->phase2_client_cert_password_flags = g_value_get_flags(value); + break; + case PROP_PASSWORD: + g_free(priv->password); + priv->password = g_value_dup_string(value); + break; + case PROP_PASSWORD_FLAGS: + priv->password_flags = g_value_get_flags(value); + break; + case PROP_PASSWORD_RAW: + g_bytes_unref(priv->password_raw); + priv->password_raw = g_value_dup_boxed(value); + break; + case PROP_PASSWORD_RAW_FLAGS: + priv->password_raw_flags = g_value_get_flags(value); + break; + case PROP_PRIVATE_KEY: + g_bytes_unref(priv->private_key); + priv->private_key = g_value_dup_boxed(value); + break; + case PROP_PRIVATE_KEY_PASSWORD: + nm_free_secret(priv->private_key_password); + priv->private_key_password = g_value_dup_string(value); + break; + case PROP_PRIVATE_KEY_PASSWORD_FLAGS: + priv->private_key_password_flags = g_value_get_flags(value); + break; + case PROP_PHASE2_PRIVATE_KEY: + g_bytes_unref(priv->phase2_private_key); + priv->phase2_private_key = g_value_dup_boxed(value); + break; + case PROP_PHASE2_PRIVATE_KEY_PASSWORD: + nm_free_secret(priv->phase2_private_key_password); + priv->phase2_private_key_password = g_value_dup_string(value); + break; + case PROP_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS: + priv->phase2_private_key_password_flags = g_value_get_flags(value); + break; + case PROP_PIN: + g_free(priv->pin); + priv->pin = g_value_dup_string(value); + break; + case PROP_PIN_FLAGS: + priv->pin_flags = g_value_get_flags(value); + break; + case PROP_SYSTEM_CA_CERTS: + priv->system_ca_certs = g_value_get_boolean(value); + break; + case PROP_AUTH_TIMEOUT: + priv->auth_timeout = g_value_get_int(value); + break; + case PROP_OPTIONAL: + priv->optional = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_802_1x_init(NMSetting8021x *setting) +{} + +/** + * nm_setting_802_1x_new: + * + * Creates a new #NMSetting8021x object with default values. + * + * Returns: the new empty #NMSetting8021x object + **/ +NMSetting * +nm_setting_802_1x_new(void) +{ + return g_object_new(NM_TYPE_SETTING_802_1X, NULL); +} + +static void +finalize(GObject *object) +{ + NMSetting8021x * self = NM_SETTING_802_1X(object); + NMSetting8021xPrivate *priv = NM_SETTING_802_1X_GET_PRIVATE(self); + + g_free(priv->identity); + g_free(priv->anonymous_identity); + g_free(priv->ca_path); + g_free(priv->subject_match); + g_free(priv->domain_suffix_match); + g_free(priv->phase1_peapver); + g_free(priv->phase1_peaplabel); + g_free(priv->phase1_fast_provisioning); + g_free(priv->phase2_auth); + g_free(priv->phase2_autheap); + g_free(priv->phase2_ca_path); + g_free(priv->phase2_subject_match); + g_free(priv->phase2_domain_suffix_match); + g_free(priv->password); + g_bytes_unref(priv->password_raw); + g_free(priv->pin); + + g_slist_free_full(priv->eap, g_free); + g_slist_free_full(priv->altsubject_matches, g_free); + g_slist_free_full(priv->phase2_altsubject_matches, g_free); + + g_bytes_unref(priv->ca_cert); + g_free(priv->ca_cert_password); + g_bytes_unref(priv->client_cert); + g_free(priv->client_cert_password); + g_bytes_unref(priv->private_key); + nm_free_secret(priv->private_key_password); + g_bytes_unref(priv->phase2_ca_cert); + g_free(priv->phase2_ca_cert_password); + g_bytes_unref(priv->phase2_client_cert); + g_free(priv->phase2_client_cert_password); + g_bytes_unref(priv->phase2_private_key); + nm_free_secret(priv->phase2_private_key_password); + + G_OBJECT_CLASS(nm_setting_802_1x_parent_class)->finalize(object); +} + +static void +nm_setting_802_1x_class_init(NMSetting8021xClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSetting8021xPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->need_secrets = need_secrets; + + /** + * NMSetting8021x:eap: + * + * The allowed EAP method to be used when authenticating to the network with + * 802.1x. Valid methods are: "leap", "md5", "tls", "peap", "ttls", "pwd", + * and "fast". Each method requires different configuration using the + * properties of this setting; refer to wpa_supplicant documentation for the + * allowed combinations. + **/ + /* ---ifcfg-rh--- + * property: eap + * variable: IEEE_8021X_EAP_METHODS(+) + * values: "LEAP", "PWD", "TLS", "PEAP", "TTLS", "FAST" + * description: EAP method for 802.1X authentication. + * example: IEEE_8021X_EAP_METHODS=PEAP + * ---end--- + */ + obj_properties[PROP_EAP] = g_param_spec_boxed(NM_SETTING_802_1X_EAP, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:identity: + * + * Identity string for EAP authentication methods. Often the user's user or + * login name. + **/ + /* ---ifcfg-rh--- + * property: identity + * variable: IEEE_8021X_IDENTITY(+) + * description: Identity for EAP authentication methods. + * example: IEEE_8021X_IDENTITY=itsme + * ---end--- + */ + obj_properties[PROP_IDENTITY] = g_param_spec_string(NM_SETTING_802_1X_IDENTITY, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:anonymous-identity: + * + * Anonymous identity string for EAP authentication methods. Used as the + * unencrypted identity with EAP types that support different tunneled + * identity like EAP-TTLS. + **/ + /* ---ifcfg-rh--- + * property: anonymous-identity + * variable: IEEE_8021X_ANON_IDENTITY(+) + * description: Anonymous identity for EAP authentication methods. + * ---end--- + */ + obj_properties[PROP_ANONYMOUS_IDENTITY] = + g_param_spec_string(NM_SETTING_802_1X_ANONYMOUS_IDENTITY, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:pac-file: + * + * UTF-8 encoded file path containing PAC for EAP-FAST. + **/ + /* ---ifcfg-rh--- + * property: pac-file + * variable: IEEE_8021X_PAC_FILE(+) + * description: File with PAC (Protected Access Credential) for EAP-FAST. + * example: IEEE_8021X_PAC_FILE=/home/joe/my-fast.pac + * ---end--- + */ + obj_properties[PROP_PAC_FILE] = g_param_spec_string(NM_SETTING_802_1X_PAC_FILE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:ca-cert: + * + * Contains the CA certificate if used by the EAP method specified in the + * #NMSetting8021x:eap property. + * + * Certificate data is specified using a "scheme"; three are currently + * supported: blob, path and pkcs#11 URL. When using the blob scheme this property + * should be set to the certificate's DER encoded data. When using the path + * scheme, this property should be set to the full UTF-8 encoded path of the + * certificate, prefixed with the string "file://" and ending with a terminating + * NUL byte. + * This property can be unset even if the EAP method supports CA certificates, + * but this allows man-in-the-middle attacks and is NOT recommended. + * + * Note that enabling NMSetting8021x:system-ca-certs will override this + * setting to use the built-in path, if the built-in path is not a directory. + * + * Setting this property directly is discouraged; use the + * nm_setting_802_1x_set_ca_cert() function instead. + **/ + /* ---ifcfg-rh--- + * property: ca-cert + * variable: IEEE_8021X_CA_CERT(+) + * description: CA certificate for EAP. + * example: IEEE_8021X_CA_CERT=/home/joe/cacert.crt + * ---end--- + */ + obj_properties[PROP_CA_CERT] = g_param_spec_boxed(NM_SETTING_802_1X_CA_CERT, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:ca-cert-password: + * + * The password used to access the CA certificate stored in + * #NMSetting8021x:ca-cert property. Only makes sense if the certificate + * is stored on a PKCS#11 token that requires a login. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * ---end--- + */ + obj_properties[PROP_CA_CERT_PASSWORD] = + g_param_spec_string(NM_SETTING_802_1X_CA_CERT_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:ca-cert-password-flags: + * + * Flags indicating how to handle the #NMSetting8021x:ca-cert-password property. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * ---end--- + */ + obj_properties[PROP_CA_CERT_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_802_1X_CA_CERT_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:ca-path: + * + * UTF-8 encoded path to a directory containing PEM or DER formatted + * certificates to be added to the verification chain in addition to the + * certificate specified in the #NMSetting8021x:ca-cert property. + * + * If NMSetting8021x:system-ca-certs is enabled and the built-in CA + * path is an existing directory, then this setting is ignored. + **/ + /* ---ifcfg-rh--- + * property: ca-path + * variable: IEEE_8021X_CA_PATH(+) + * description: The search path for the certificate. + * ---end--- + */ + obj_properties[PROP_CA_PATH] = g_param_spec_string(NM_SETTING_802_1X_CA_PATH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:subject-match: + * + * Substring to be matched against the subject of the certificate presented + * by the authentication server. When unset, no verification of the + * authentication server certificate's subject is performed. This property + * provides little security, if any, and its use is deprecated in favor of + * NMSetting8021x:domain-suffix-match. + **/ + /* ---ifcfg-rh--- + * property: subject-match + * variable: IEEE_8021X_SUBJECT_MATCH(+) + * description: Substring to match subject of server certificate against. + * example: IEEE_8021X_SUBJECT_MATCH="Red Hat" + * ---end--- + */ + obj_properties[PROP_SUBJECT_MATCH] = + g_param_spec_string(NM_SETTING_802_1X_SUBJECT_MATCH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:altsubject-matches: + * + * List of strings to be matched against the altSubjectName of the + * certificate presented by the authentication server. If the list is empty, + * no verification of the server certificate's altSubjectName is performed. + **/ + /* ---ifcfg-rh--- + * property: altsubject-matches + * variable: IEEE_8021X_ALTSUBJECT_MATCHES(+) + * description: List of strings to be matched against the altSubjectName. + * example: IEEE_8021X_ALTSUBJECT_MATCHES="s1.domain.cc" + * ---end--- + */ + obj_properties[PROP_ALTSUBJECT_MATCHES] = + g_param_spec_boxed(NM_SETTING_802_1X_ALTSUBJECT_MATCHES, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:domain-suffix-match: + * + * Constraint for server domain name. If set, this FQDN is used as a suffix + * match requirement for dNSName element(s) of the certificate presented by + * the authentication server. If a matching dNSName is found, this + * constraint is met. If no dNSName values are present, this constraint is + * matched against SubjectName CN using same suffix match comparison. + * Since version 1.24, multiple valid FQDNs can be passed as a ";" delimited + * list. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: domain-suffix-match + * description: Suffix to match domain of server certificate against. + * variable: IEEE_8021X_DOMAIN_SUFFIX_MATCH(+) + * ---end--- + */ + obj_properties[PROP_DOMAIN_SUFFIX_MATCH] = + g_param_spec_string(NM_SETTING_802_1X_DOMAIN_SUFFIX_MATCH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:domain-match: + * + * Constraint for server domain name. If set, this list of FQDNs is used as + * a match requirement for dNSName element(s) of the certificate presented + * by the authentication server. If a matching dNSName is found, this + * constraint is met. If no dNSName values are present, this constraint is + * matched against SubjectName CN using the same comparison. + * Multiple valid FQDNs can be passed as a ";" delimited list. + * + * Since: 1.24 + **/ + /* ---ifcfg-rh--- + * property: domain-match + * description: Value to match domain of server certificate against. + * variable: IEEE_8021X_DOMAIN_MATCH(+) + * ---end--- + */ + obj_properties[PROP_DOMAIN_MATCH] = + g_param_spec_string(NM_SETTING_802_1X_DOMAIN_MATCH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:client-cert: + * + * Contains the client certificate if used by the EAP method specified in + * the #NMSetting8021x:eap property. + * + * Certificate data is specified using a "scheme"; two are currently + * supported: blob and path. When using the blob scheme (which is backwards + * compatible with NM 0.7.x) this property should be set to the + * certificate's DER encoded data. When using the path scheme, this property + * should be set to the full UTF-8 encoded path of the certificate, prefixed + * with the string "file://" and ending with a terminating NUL byte. + * + * Setting this property directly is discouraged; use the + * nm_setting_802_1x_set_client_cert() function instead. + **/ + /* ---ifcfg-rh--- + * property: client-cert + * variable: IEEE_8021X_CLIENT_CERT(+) + * description: Client certificate for EAP. + * example: IEEE_8021X_CLIENT_CERT=/home/joe/mycert.crt + * ---end--- + */ + obj_properties[PROP_CLIENT_CERT] = + g_param_spec_boxed(NM_SETTING_802_1X_CLIENT_CERT, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:client-cert-password: + * + * The password used to access the client certificate stored in + * #NMSetting8021x:client-cert property. Only makes sense if the certificate + * is stored on a PKCS#11 token that requires a login. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * ---end--- + */ + obj_properties[PROP_CLIENT_CERT_PASSWORD] = + g_param_spec_string(NM_SETTING_802_1X_CLIENT_CERT_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:client-cert-password-flags: + * + * Flags indicating how to handle the #NMSetting8021x:client-cert-password property. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * ---end--- + */ + obj_properties[PROP_CLIENT_CERT_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_802_1X_CLIENT_CERT_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase1-peapver: + * + * Forces which PEAP version is used when PEAP is set as the EAP method in + * the #NMSetting8021x:eap property. When unset, the version reported by + * the server will be used. Sometimes when using older RADIUS servers, it + * is necessary to force the client to use a particular PEAP version. To do + * so, this property may be set to "0" or "1" to force that specific PEAP + * version. + **/ + /* ---ifcfg-rh--- + * property: phase1-peapver + * variable: IEEE_8021X_PEAP_VERSION(+) + * values: 0, 1 + * description: Use to force a specific PEAP version. + * ---end--- + */ + obj_properties[PROP_PHASE1_PEAPVER] = + g_param_spec_string(NM_SETTING_802_1X_PHASE1_PEAPVER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase1-peaplabel: + * + * Forces use of the new PEAP label during key derivation. Some RADIUS + * servers may require forcing the new PEAP label to interoperate with + * PEAPv1. Set to "1" to force use of the new PEAP label. See the + * wpa_supplicant documentation for more details. + **/ + /* ---ifcfg-rh--- + * property: phase1-peaplabel + * variable: IEEE_8021X_PEAP_FORCE_NEW_LABEL(+) + * values: yes, no + * default: no + * description: Use to force the new PEAP label during key derivation. + * ---end--- + */ + obj_properties[PROP_PHASE1_PEAPLABEL] = + g_param_spec_string(NM_SETTING_802_1X_PHASE1_PEAPLABEL, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase1-fast-provisioning: + * + * Enables or disables in-line provisioning of EAP-FAST credentials when + * FAST is specified as the EAP method in the #NMSetting8021x:eap property. + * Recognized values are "0" (disabled), "1" (allow unauthenticated + * provisioning), "2" (allow authenticated provisioning), and "3" (allow + * both authenticated and unauthenticated provisioning). See the + * wpa_supplicant documentation for more details. + **/ + /* ---ifcfg-rh--- + * property: phase1-fast-provisioning + * variable: IEEE_8021X_FAST_PROVISIONING(+) + * values: space-separated list of these values [allow-auth, allow-unauth] + * description: Enable in-line provisioning of EAP-FAST credentials. + * example: IEEE_8021X_FAST_PROVISIONING="allow-auth allow-unauth" + * ---end--- + */ + obj_properties[PROP_PHASE1_FAST_PROVISIONING] = + g_param_spec_string(NM_SETTING_802_1X_PHASE1_FAST_PROVISIONING, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase1-auth-flags: + * + * Specifies authentication flags to use in "phase 1" outer + * authentication using #NMSetting8021xAuthFlags options. + * The individual TLS versions can be explicitly disabled. If a certain + * TLS disable flag is not set, it is up to the supplicant to allow + * or forbid it. The TLS options map to tls_disable_tlsv1_x settings. + * See the wpa_supplicant documentation for more details. + * + * Since: 1.8 + */ + /* ---ifcfg-rh--- + * property: phase1-auth-flags + * variable: IEEE_8021X_PHASE1_AUTH_FLAGS(+) + * values: space-separated list of authentication flags names + * description: Authentication flags for the supplicant + * example: IEEE_8021X_PHASE1_AUTH_FLAGS="tls-1-0-disable tls-1-1-disable" + * ---end--- + */ + obj_properties[PROP_PHASE1_AUTH_FLAGS] = + g_param_spec_uint(NM_SETTING_802_1X_PHASE1_AUTH_FLAGS, + "", + "", + 0, + G_MAXUINT32, + NM_SETTING_802_1X_AUTH_FLAGS_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-auth: + * + * Specifies the allowed "phase 2" inner authentication method when an EAP + * method that uses an inner TLS tunnel is specified in the #NMSetting8021x:eap + * property. For TTLS this property selects one of the supported non-EAP + * inner methods: "pap", "chap", "mschap", "mschapv2" while + * #NMSetting8021x:phase2-autheap selects an EAP inner method. For PEAP + * this selects an inner EAP method, one of: "gtc", "otp", "md5" and "tls". + * Each "phase 2" inner method requires specific parameters for successful + * authentication; see the wpa_supplicant documentation for more details. + * Both #NMSetting8021x:phase2-auth and #NMSetting8021x:phase2-autheap cannot + * be specified. + **/ + /* ---ifcfg-rh--- + * property: phase2-auth + * variable: IEEE_8021X_INNER_AUTH_METHODS(+) + * values: "PAP", "CHAP", "MSCHAP", "MSCHAPV2", "GTC", "OTP", "MD5" and "TLS" + * description: Inner non-EAP authentication methods for TTLS or the inner EAP + * authentication method for PEAP. IEEE_8021X_INNER_AUTH_METHODS can contain + * values both for 'phase2-auth' and 'phase2-autheap' properties. + * example: IEEE_8021X_INNER_AUTH_METHODS=PAP + * ---end--- + */ + obj_properties[PROP_PHASE2_AUTH] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_AUTH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-autheap: + * + * Specifies the allowed "phase 2" inner EAP-based authentication method + * when TTLS is specified in the #NMSetting8021x:eap property. Recognized + * EAP-based "phase 2" methods are "md5", "mschapv2", "otp", "gtc", and + * "tls". Each "phase 2" inner method requires specific parameters for + * successful authentication; see the wpa_supplicant documentation for + * more details. + **/ + /* ---ifcfg-rh--- + * property: phase2-autheap + * variable: IEEE_8021X_INNER_AUTH_METHODS(+) + * values: "EAP-MD5", "EAP-MSCHAPV2", "EAP-GTC", "EAP-OTP" and "EAP-TLS" + * description: Inner EAP-based authentication methods. Note that + * IEEE_8021X_INNER_AUTH_METHODS is also used for 'phase2-auth' values. + * example: IEEE_8021X_INNER_AUTH_METHODS="MSCHAPV2 EAP-TLS" + * ---end--- + */ + obj_properties[PROP_PHASE2_AUTHEAP] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_AUTHEAP, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-ca-cert: + * + * Contains the "phase 2" CA certificate if used by the EAP method specified + * in the #NMSetting8021x:phase2-auth or #NMSetting8021x:phase2-autheap + * properties. + * + * Certificate data is specified using a "scheme"; three are currently + * supported: blob, path and pkcs#11 URL. When using the blob scheme this property + * should be set to the certificate's DER encoded data. When using the path + * scheme, this property should be set to the full UTF-8 encoded path of the + * certificate, prefixed with the string "file://" and ending with a terminating + * NUL byte. + * This property can be unset even if the EAP method supports CA certificates, + * but this allows man-in-the-middle attacks and is NOT recommended. + * + * Note that enabling NMSetting8021x:system-ca-certs will override this + * setting to use the built-in path, if the built-in path is not a directory. + * + * Setting this property directly is discouraged; use the + * nm_setting_802_1x_set_phase2_ca_cert() function instead. + **/ + obj_properties[PROP_PHASE2_CA_CERT] = + g_param_spec_boxed(NM_SETTING_802_1X_PHASE2_CA_CERT, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-ca-cert-password: + * + * The password used to access the "phase2" CA certificate stored in + * #NMSetting8021x:phase2-ca-cert property. Only makes sense if the certificate + * is stored on a PKCS#11 token that requires a login. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * ---end--- + */ + obj_properties[PROP_PHASE2_CA_CERT_PASSWORD] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-ca-cert-password-flags: + * + * Flags indicating how to handle the #NMSetting8021x:phase2-ca-cert-password property. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * ---end--- + */ + obj_properties[PROP_PHASE2_CA_CERT_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_802_1X_PHASE2_CA_CERT_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-ca-path: + * + * UTF-8 encoded path to a directory containing PEM or DER formatted + * certificates to be added to the verification chain in addition to the + * certificate specified in the #NMSetting8021x:phase2-ca-cert property. + * + * If NMSetting8021x:system-ca-certs is enabled and the built-in CA + * path is an existing directory, then this setting is ignored. + **/ + /* ---ifcfg-rh--- + * property: phase2-ca-path + * variable: IEEE_8021X_PHASE2_CA_PATH(+) + * description: The search path for the certificate. + * ---end--- + */ + obj_properties[PROP_PHASE2_CA_PATH] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_CA_PATH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-subject-match: + * + * Substring to be matched against the subject of the certificate presented + * by the authentication server during the inner "phase 2" + * authentication. When unset, no verification of the authentication server + * certificate's subject is performed. This property provides little security, + * if any, and its use is deprecated in favor of + * NMSetting8021x:phase2-domain-suffix-match. + **/ + /* ---ifcfg-rh--- + * property: phase2-subject-match + * variable: IEEE_8021X_PHASE2_SUBJECT_MATCH(+) + * description: Substring to match subject of server certificate against. + * example: IEEE_8021X_PHASE2_SUBJECT_MATCH="Red Hat" + * ---end--- + */ + obj_properties[PROP_PHASE2_SUBJECT_MATCH] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_SUBJECT_MATCH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-altsubject-matches: + * + * List of strings to be matched against the altSubjectName of the + * certificate presented by the authentication server during the inner + * "phase 2" authentication. If the list is empty, no verification of the + * server certificate's altSubjectName is performed. + **/ + /* ---ifcfg-rh--- + * property: phase2-altsubject-matches + * variable: IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES(+) + * ---end--- + */ + obj_properties[PROP_PHASE2_ALTSUBJECT_MATCHES] = + g_param_spec_boxed(NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-domain-suffix-match: + * + * Constraint for server domain name. If set, this FQDN is used as a suffix + * match requirement for dNSName element(s) of the certificate presented by + * the authentication server during the inner "phase 2" authentication. If + * a matching dNSName is found, this constraint is met. If no dNSName + * values are present, this constraint is matched against SubjectName CN + * using same suffix match comparison. + * Since version 1.24, multiple valid FQDNs can be passed as a ";" delimited + * list. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: phase2-domain-suffix-match + * description: Suffix to match domain of server certificate for phase 2 against. + * variable: IEEE_8021X_PHASE2_DOMAIN_SUFFIX_MATCH(+) + * ---end--- + */ + obj_properties[PROP_PHASE2_DOMAIN_SUFFIX_MATCH] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_DOMAIN_SUFFIX_MATCH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-domain-match: + * + * Constraint for server domain name. If set, this list of FQDNs is used as + * a match requirement for dNSName element(s) of the certificate presented + * by the authentication server during the inner "phase 2" authentication. + * If a matching dNSName is found, this constraint is met. If no dNSName + * values are present, this constraint is matched against SubjectName CN + * using the same comparison. + * Multiple valid FQDNs can be passed as a ";" delimited list. + * + * Since: 1.24 + **/ + /* ---ifcfg-rh--- + * property: phase2-domain-match + * description: Value to match domain of server certificate for phase 2 against. + * variable: IEEE_8021X_PHASE2_DOMAIN_MATCH(+) + * ---end--- + */ + obj_properties[PROP_PHASE2_DOMAIN_MATCH] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_DOMAIN_MATCH, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-client-cert: + * + * Contains the "phase 2" client certificate if used by the EAP method + * specified in the #NMSetting8021x:phase2-auth or + * #NMSetting8021x:phase2-autheap properties. + * + * Certificate data is specified using a "scheme"; two are currently + * supported: blob and path. When using the blob scheme (which is backwards + * compatible with NM 0.7.x) this property should be set to the + * certificate's DER encoded data. When using the path scheme, this property + * should be set to the full UTF-8 encoded path of the certificate, prefixed + * with the string "file://" and ending with a terminating NUL byte. This + * property can be unset even if the EAP method supports CA certificates, + * but this allows man-in-the-middle attacks and is NOT recommended. + * + * Setting this property directly is discouraged; use the + * nm_setting_802_1x_set_phase2_client_cert() function instead. + **/ + /* ---ifcfg-rh--- + * property: phase2-client-cert + * variable: IEEE_8021X_INNER_CLIENT_CERT(+) + * description: Client certificate for inner EAP method. + * example: IEEE_8021X_INNER_CLIENT_CERT=/home/joe/mycert.crt + * ---end--- + */ + obj_properties[PROP_PHASE2_CLIENT_CERT] = + g_param_spec_boxed(NM_SETTING_802_1X_PHASE2_CLIENT_CERT, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-client-cert-password: + * + * The password used to access the "phase2" client certificate stored in + * #NMSetting8021x:phase2-client-cert property. Only makes sense if the certificate + * is stored on a PKCS#11 token that requires a login. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * ---end--- + */ + obj_properties[PROP_PHASE2_CLIENT_CERT_PASSWORD] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-client-cert-password-flags: + * + * Flags indicating how to handle the #NMSetting8021x:phase2-client-cert-password property. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * ---end--- + */ + obj_properties[PROP_PHASE2_CLIENT_CERT_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_802_1X_PHASE2_CLIENT_CERT_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:password: + * + * UTF-8 encoded password used for EAP authentication methods. If both the + * #NMSetting8021x:password property and the #NMSetting8021x:password-raw + * property are specified, #NMSetting8021x:password is preferred. + **/ + /* ---ifcfg-rh--- + * property: password + * variable: IEEE_8021X_PASSWORD(+) + * description: UTF-8 encoded password used for EAP. It can also go to "key-" + * lookaside file, or it can be owned by a secret agent. + * ---end--- + */ + obj_properties[PROP_PASSWORD] = + g_param_spec_string(NM_SETTING_802_1X_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:password-flags: + * + * Flags indicating how to handle the #NMSetting8021x:password property. + **/ + /* ---ifcfg-rh--- + * property: password-flags + * variable: IEEE_8021X_PASSWORD_FLAGS(+) + * format: NMSettingSecretFlags + * description: Password flags for IEEE_8021X_PASSWORD password. + * ---end--- + */ + obj_properties[PROP_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_802_1X_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:password-raw: + * + * Password used for EAP authentication methods, given as a byte array to + * allow passwords in other encodings than UTF-8 to be used. If both the + * #NMSetting8021x:password property and the #NMSetting8021x:password-raw + * property are specified, #NMSetting8021x:password is preferred. + **/ + /* ---ifcfg-rh--- + * property: password-raw + * variable: IEEE_8021X_PASSWORD_RAW(+) + * description: password used for EAP, encoded as a hexadecimal string. It + * can also go to "key-" lookaside file. + * example: IEEE_8021X_PASSWORD_RAW=041c8320083aa4bf + * ---end--- + */ + obj_properties[PROP_PASSWORD_RAW] = + g_param_spec_boxed(NM_SETTING_802_1X_PASSWORD_RAW, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:password-raw-flags: + * + * Flags indicating how to handle the #NMSetting8021x:password-raw property. + **/ + /* ---ifcfg-rh--- + * property: password-raw-flags + * variable: IEEE_8021X_PASSWORD_RAW_FLAGS(+) + * description: The secret flags for password-raw. + * ---end--- + */ + obj_properties[PROP_PASSWORD_RAW_FLAGS] = + g_param_spec_flags(NM_SETTING_802_1X_PASSWORD_RAW_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:private-key: + * + * Contains the private key when the #NMSetting8021x:eap property is set to + * "tls". + * + * Key data is specified using a "scheme"; two are currently supported: blob + * and path. When using the blob scheme and private keys, this property + * should be set to the key's encrypted PEM encoded data. When using private + * keys with the path scheme, this property should be set to the full UTF-8 + * encoded path of the key, prefixed with the string "file://" and ending + * with a terminating NUL byte. When using PKCS#12 format private + * keys and the blob scheme, this property should be set to the + * PKCS#12 data and the #NMSetting8021x:private-key-password + * property must be set to password used to decrypt the PKCS#12 + * certificate and key. When using PKCS#12 files and the path + * scheme, this property should be set to the full UTF-8 encoded path of the + * key, prefixed with the string "file://" and ending with a terminating + * NUL byte, and as with the blob scheme the "private-key-password" property + * must be set to the password used to decode the PKCS#12 private + * key and certificate. + * + * Setting this property directly is discouraged; use the + * nm_setting_802_1x_set_private_key() function instead. + * + * WARNING: #NMSetting8021x:private-key is not a "secret" property, and thus + * unencrypted private key data using the BLOB scheme may be readable by + * unprivileged users. Private keys should always be encrypted with a + * private key password to prevent unauthorized access to unencrypted + * private key data. + **/ + /* ---ifcfg-rh--- + * property: private-key + * variable: IEEE_8021X_PRIVATE_KEY(+) + * description: Private key for EAP-TLS. + * example: IEEE_8021X_PRIVATE_KEY=/home/joe/mykey.p12 + * ---end--- + */ + obj_properties[PROP_PRIVATE_KEY] = + g_param_spec_boxed(NM_SETTING_802_1X_PRIVATE_KEY, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:private-key-password: + * + * The password used to decrypt the private key specified in the + * #NMSetting8021x:private-key property when the private key either uses the + * path scheme, or if the private key is a PKCS#12 format key. Setting this + * property directly is not generally necessary except when returning + * secrets to NetworkManager; it is generally set automatically when setting + * the private key by the nm_setting_802_1x_set_private_key() function. + **/ + /* ---ifcfg-rh--- + * property: private-key-password + * variable: IEEE_8021X_PRIVATE_KEY_PASSWORD(+) + * description: Password for IEEE_8021X_PRIVATE_KEY. It can also go to "key-" + * lookaside file, or it can be owned by a secret agent. + * ---end--- + */ + obj_properties[PROP_PRIVATE_KEY_PASSWORD] = + g_param_spec_string(NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:private-key-password-flags: + * + * Flags indicating how to handle the #NMSetting8021x:private-key-password + * property. + **/ + /* ---ifcfg-rh--- + * property: private-key-password-flags + * variable: IEEE_8021X_PRIVATE_KEY_PASSWORD_FLAGS(+) + * format: NMSettingSecretFlags + * description: Password flags for IEEE_8021X_PRIVATE_KEY_PASSWORD password. + * ---end--- + */ + obj_properties[PROP_PRIVATE_KEY_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-private-key: + * + * Contains the "phase 2" inner private key when the + * #NMSetting8021x:phase2-auth or #NMSetting8021x:phase2-autheap property is + * set to "tls". + * + * Key data is specified using a "scheme"; two are currently supported: blob + * and path. When using the blob scheme and private keys, this property + * should be set to the key's encrypted PEM encoded data. When using private + * keys with the path scheme, this property should be set to the full UTF-8 + * encoded path of the key, prefixed with the string "file://" and ending + * with a terminating NUL byte. When using PKCS#12 format private + * keys and the blob scheme, this property should be set to the + * PKCS#12 data and the #NMSetting8021x:phase2-private-key-password + * property must be set to password used to decrypt the PKCS#12 + * certificate and key. When using PKCS#12 files and the path + * scheme, this property should be set to the full UTF-8 encoded path of the + * key, prefixed with the string "file://" and ending with a terminating + * NUL byte, and as with the blob scheme the + * #NMSetting8021x:phase2-private-key-password property must be set to the + * password used to decode the PKCS#12 private key and certificate. + * + * Setting this property directly is discouraged; use the + * nm_setting_802_1x_set_phase2_private_key() function instead. + **/ + /* ---ifcfg-rh--- + * property: phase2-private-key + * variable: IEEE_8021X_INNER_PRIVATE_KEY(+) + * description: Private key for inner authentication method for EAP-TLS. + * ---end--- + */ + obj_properties[PROP_PHASE2_PRIVATE_KEY] = + g_param_spec_boxed(NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-private-key-password: + * + * The password used to decrypt the "phase 2" private key specified in the + * #NMSetting8021x:phase2-private-key property when the private key either + * uses the path scheme, or is a PKCS#12 format key. Setting this + * property directly is not generally necessary except when returning + * secrets to NetworkManager; it is generally set automatically when setting + * the private key by the nm_setting_802_1x_set_phase2_private_key() + * function. + **/ + /* ---ifcfg-rh--- + * property: phase2-private-key-password + * variable: IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD(+) + * description: Password for IEEE_8021X_INNER_PRIVATE_KEY. It can also go to "key-" + * lookaside file, or it can be owned by a secret agent. + * ---end--- + */ + obj_properties[PROP_PHASE2_PRIVATE_KEY_PASSWORD] = + g_param_spec_string(NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:phase2-private-key-password-flags: + * + * Flags indicating how to handle the + * #NMSetting8021x:phase2-private-key-password property. + **/ + /* ---ifcfg-rh--- + * property: phase2-private-key-password-flags + * variable: IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD_FLAGS(+) + * format: NMSettingSecretFlags + * description: Password flags for IEEE_8021X_INNER_PRIVATE_KEY_PASSWORD password. + * ---end--- + */ + obj_properties[PROP_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:pin: + * + * PIN used for EAP authentication methods. + **/ + /* ---ifcfg-rh--- + * property: pin + * variable: IEEE_8021X_PIN(+) + * description: The pin secret used for EAP authentication methods. + * ---end--- + */ + obj_properties[PROP_PIN] = + g_param_spec_string(NM_SETTING_802_1X_PIN, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:pin-flags: + * + * Flags indicating how to handle the #NMSetting8021x:pin property. + **/ + /* ---ifcfg-rh--- + * property: pin-flags + * variable: IEEE_8021X_PIN_FLAGS(+) + * description: The secret flags for the pin property. + * ---end--- + */ + obj_properties[PROP_PIN_FLAGS] = g_param_spec_flags(NM_SETTING_802_1X_PIN_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:system-ca-certs: + * + * When %TRUE, overrides the #NMSetting8021x:ca-path and + * #NMSetting8021x:phase2-ca-path properties using the system CA directory + * specified at configure time with the --system-ca-path switch. The + * certificates in this directory are added to the verification chain in + * addition to any certificates specified by the #NMSetting8021x:ca-cert and + * #NMSetting8021x:phase2-ca-cert properties. If the path provided with + * --system-ca-path is rather a file name (bundle of trusted CA certificates), + * it overrides #NMSetting8021x:ca-cert and #NMSetting8021x:phase2-ca-cert + * properties instead (sets ca_cert/ca_cert2 options for wpa_supplicant). + **/ + /* ---ifcfg-rh--- + * property: system-ca-certs + * variable: IEEE_8021X_SYSTEM_CA_CERTS(+) + * description: a boolean value. + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_802_1X_SYSTEM_CA_CERTS, + PROP_SYSTEM_CA_CERTS, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_802_1x_get_system_ca_certs); + + /** + * NMSetting8021x:auth-timeout: + * + * A timeout for the authentication. Zero means the global default; if the + * global default is not set, the authentication timeout is 25 seconds. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * property: auth-timeout + * variable: IEEE_8021X_AUTH_TIMEOUT(+) + * default: 0 + * description: Timeout in seconds for the 802.1X authentication. Zero means the global default or 25. + * ---end--- + */ + obj_properties[PROP_AUTH_TIMEOUT] = g_param_spec_int( + NM_SETTING_802_1X_AUTH_TIMEOUT, + "", + "", + 0, + G_MAXINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSetting8021x:optional: + * + * Whether the 802.1X authentication is optional. If %TRUE, the activation + * will continue even after a timeout or an authentication failure. Setting + * the property to %TRUE is currently allowed only for Ethernet connections. + * If set to %FALSE, the activation can continue only after a successful + * authentication. + * + * Since: 1.22 + **/ + /* ---ifcfg-rh--- + * property: optional + * variable: IEEE_8021X_OPTIONAL(+) + * default=no + * description: whether the 802.1X authentication is optional + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_802_1X_OPTIONAL, + PROP_OPTIONAL, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_802_1x_get_optional); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_802_1X, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-adsl.c b/src/libnm-core-impl/nm-setting-adsl.c new file mode 100644 index 00000000..9f45163d --- /dev/null +++ b/src/libnm-core-impl/nm-setting-adsl.c @@ -0,0 +1,461 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 - 2013 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-adsl.h" + +#include "nm-setting-ppp.h" +#include "nm-setting-private.h" +#include "nm-utils.h" +#include "nm-core-enum-types.h" + +/** + * SECTION:nm-setting-adsl + * @short_description: Describes ADSL-based properties + * + * The #NMSettingAdsl object is a #NMSetting subclass that describes + * properties of ADSL connections. + */ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_USERNAME, + PROP_PASSWORD, + PROP_PASSWORD_FLAGS, + PROP_PROTOCOL, + PROP_ENCAPSULATION, + PROP_VPI, + PROP_VCI, ); + +typedef struct { + char * username; + char * password; + NMSettingSecretFlags password_flags; + char * protocol; + char * encapsulation; + guint32 vpi; + guint32 vci; +} NMSettingAdslPrivate; + +/** + * NMSettingAdsl: + * + * ADSL Settings + */ +struct _NMSettingAdsl { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingAdslClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingAdsl, nm_setting_adsl, NM_TYPE_SETTING) + +#define NM_SETTING_ADSL_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_ADSL, NMSettingAdslPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_adsl_get_username: + * @setting: the #NMSettingAdsl + * + * Returns: the #NMSettingAdsl:username property of the setting + **/ +const char * +nm_setting_adsl_get_username(NMSettingAdsl *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_ADSL(setting), NULL); + + return NM_SETTING_ADSL_GET_PRIVATE(setting)->username; +} + +/** + * nm_setting_adsl_get_password: + * @setting: the #NMSettingAdsl + * + * Returns: the #NMSettingAdsl:password property of the setting + **/ +const char * +nm_setting_adsl_get_password(NMSettingAdsl *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_ADSL(setting), NULL); + + return NM_SETTING_ADSL_GET_PRIVATE(setting)->password; +} + +/** + * nm_setting_adsl_get_password_flags: + * @setting: the #NMSettingAdsl + * + * Returns: the #NMSettingSecretFlags pertaining to the #NMSettingAdsl:password + **/ +NMSettingSecretFlags +nm_setting_adsl_get_password_flags(NMSettingAdsl *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_ADSL(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_ADSL_GET_PRIVATE(setting)->password_flags; +} + +/** + * nm_setting_adsl_get_protocol: + * @setting: the #NMSettingAdsl + * + * Returns: the #NMSettingAdsl:protocol property of the setting + **/ +const char * +nm_setting_adsl_get_protocol(NMSettingAdsl *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_ADSL(setting), NULL); + + return NM_SETTING_ADSL_GET_PRIVATE(setting)->protocol; +} + +/** + * nm_setting_adsl_get_encapsulation: + * @setting: the #NMSettingAdsl + * + * Returns: the #NMSettingAdsl:encapsulation property of the setting + **/ +const char * +nm_setting_adsl_get_encapsulation(NMSettingAdsl *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_ADSL(setting), NULL); + + return NM_SETTING_ADSL_GET_PRIVATE(setting)->encapsulation; +} + +/** + * nm_setting_adsl_get_vpi: + * @setting: the #NMSettingAdsl + * + * Returns: the #NMSettingAdsl:vpi property of the setting + **/ +guint32 +nm_setting_adsl_get_vpi(NMSettingAdsl *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_ADSL(setting), 0); + + return NM_SETTING_ADSL_GET_PRIVATE(setting)->vpi; +} + +/** + * nm_setting_adsl_get_vci: + * @setting: the #NMSettingAdsl + * + * Returns: the #NMSettingAdsl:vci property of the setting + **/ +guint32 +nm_setting_adsl_get_vci(NMSettingAdsl *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_ADSL(setting), 0); + + return NM_SETTING_ADSL_GET_PRIVATE(setting)->vci; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingAdslPrivate *priv = NM_SETTING_ADSL_GET_PRIVATE(setting); + + if (!priv->username) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_ADSL_SETTING_NAME, NM_SETTING_ADSL_USERNAME); + return FALSE; + } + if (!priv->username[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_ADSL_SETTING_NAME, NM_SETTING_ADSL_USERNAME); + return FALSE; + } + + if (!NM_IN_STRSET(priv->protocol, + NM_SETTING_ADSL_PROTOCOL_PPPOA, + NM_SETTING_ADSL_PROTOCOL_PPPOE, + NM_SETTING_ADSL_PROTOCOL_IPOATM)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + priv->protocol ?: "(null)"); + g_prefix_error(error, "%s.%s: ", NM_SETTING_ADSL_SETTING_NAME, NM_SETTING_ADSL_PROTOCOL); + return FALSE; + } + + if (!NM_IN_STRSET(priv->encapsulation, + NULL, + NM_SETTING_ADSL_ENCAPSULATION_VCMUX, + NM_SETTING_ADSL_ENCAPSULATION_LLC)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + priv->encapsulation); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_ADSL_SETTING_NAME, + NM_SETTING_ADSL_ENCAPSULATION); + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_secrets(NMSetting *setting, NMConnection *connection, GError **error) +{ + return _nm_setting_verify_secret_string(NM_SETTING_ADSL_GET_PRIVATE(setting)->password, + NM_SETTING_ADSL_SETTING_NAME, + NM_SETTING_ADSL_PASSWORD, + error); +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSettingAdslPrivate *priv = NM_SETTING_ADSL_GET_PRIVATE(setting); + GPtrArray * secrets = NULL; + + if (priv->password && *priv->password) + return NULL; + + if (!(priv->password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + secrets = g_ptr_array_sized_new(1); + g_ptr_array_add(secrets, NM_SETTING_ADSL_PASSWORD); + } + + return secrets; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingAdsl *setting = NM_SETTING_ADSL(object); + + switch (prop_id) { + case PROP_USERNAME: + g_value_set_string(value, nm_setting_adsl_get_username(setting)); + break; + case PROP_PASSWORD: + g_value_set_string(value, nm_setting_adsl_get_password(setting)); + break; + case PROP_PASSWORD_FLAGS: + g_value_set_flags(value, nm_setting_adsl_get_password_flags(setting)); + break; + case PROP_PROTOCOL: + g_value_set_string(value, nm_setting_adsl_get_protocol(setting)); + break; + case PROP_ENCAPSULATION: + g_value_set_string(value, nm_setting_adsl_get_encapsulation(setting)); + break; + case PROP_VPI: + g_value_set_uint(value, nm_setting_adsl_get_vpi(setting)); + break; + case PROP_VCI: + g_value_set_uint(value, nm_setting_adsl_get_vci(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingAdslPrivate *priv = NM_SETTING_ADSL_GET_PRIVATE(object); + const char * str; + + switch (prop_id) { + case PROP_USERNAME: + g_free(priv->username); + priv->username = g_value_dup_string(value); + break; + case PROP_PASSWORD: + g_free(priv->password); + priv->password = g_value_dup_string(value); + break; + case PROP_PASSWORD_FLAGS: + priv->password_flags = g_value_get_flags(value); + break; + case PROP_PROTOCOL: + g_free(priv->protocol); + str = g_value_get_string(value); + priv->protocol = str ? g_ascii_strdown(str, -1) : NULL; + break; + case PROP_ENCAPSULATION: + g_free(priv->encapsulation); + str = g_value_get_string(value); + priv->encapsulation = str ? g_ascii_strdown(str, -1) : NULL; + break; + case PROP_VPI: + priv->vpi = g_value_get_uint(value); + break; + case PROP_VCI: + priv->vci = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_adsl_init(NMSettingAdsl *setting) +{} + +/** + * nm_setting_adsl_new: + * + * Creates a new #NMSettingAdsl object with default values. + * + * Returns: the new empty #NMSettingAdsl object + **/ +NMSetting * +nm_setting_adsl_new(void) +{ + return g_object_new(NM_TYPE_SETTING_ADSL, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingAdslPrivate *priv = NM_SETTING_ADSL_GET_PRIVATE(object); + + g_free(priv->username); + g_free(priv->password); + g_free(priv->protocol); + g_free(priv->encapsulation); + + G_OBJECT_CLASS(nm_setting_adsl_parent_class)->finalize(object); +} + +static void +nm_setting_adsl_class_init(NMSettingAdslClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingAdslPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->verify_secrets = verify_secrets; + setting_class->need_secrets = need_secrets; + + /** + * NMSettingAdsl:username: + * + * Username used to authenticate with the ADSL service. + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_ADSL_USERNAME, + PROP_USERNAME, + NM_SETTING_PARAM_NONE, + nm_setting_adsl_get_username); + + /** + * NMSettingAdsl:password: + * + * Password used to authenticate with the ADSL service. + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_ADSL_PASSWORD, + PROP_PASSWORD, + NM_SETTING_PARAM_SECRET, + nm_setting_adsl_get_password); + + /** + * NMSettingAdsl:password-flags: + * + * Flags indicating how to handle the #NMSettingAdsl:password property. + **/ + obj_properties[PROP_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_ADSL_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingAdsl:protocol: + * + * ADSL connection protocol. Can be "pppoa", "pppoe" or "ipoatm". + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_ADSL_PROTOCOL, + PROP_PROTOCOL, + NM_SETTING_PARAM_NONE, + nm_setting_adsl_get_protocol); + + /** + * NMSettingAdsl:encapsulation: + * + * Encapsulation of ADSL connection. Can be "vcmux" or "llc". + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_ADSL_ENCAPSULATION, + PROP_ENCAPSULATION, + NM_SETTING_PARAM_NONE, + nm_setting_adsl_get_encapsulation); + + /** + * NMSettingAdsl:vpi: + * + * VPI of ADSL connection + **/ + obj_properties[PROP_VPI] = g_param_spec_uint(NM_SETTING_ADSL_VPI, + "", + "", + 0, + 65536, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingAdsl:vci: + * + * VCI of ADSL connection + **/ + obj_properties[PROP_VCI] = g_param_spec_uint(NM_SETTING_ADSL_VCI, + "", + "", + 0, + 65536, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_ADSL, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-bluetooth.c b/src/libnm-core-impl/nm-setting-bluetooth.c new file mode 100644 index 00000000..4bd91a69 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-bluetooth.c @@ -0,0 +1,348 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-bluetooth.h" + +#include + +#include "nm-connection-private.h" +#include "nm-setting-cdma.h" +#include "nm-setting-gsm.h" +#include "nm-setting-private.h" +#include "nm-utils.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-bluetooth + * @short_description: Describes Bluetooth connection properties + * + * The #NMSettingBluetooth object is a #NMSetting subclass that describes + * properties necessary for connection to devices that provide network + * connections via the Bluetooth Dial-Up Networking (DUN) and Network Access + * Point (NAP) profiles. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_BDADDR, PROP_TYPE, ); + +typedef struct { + char *bdaddr; + char *type; +} NMSettingBluetoothPrivate; + +/** + * NMSettingBluetooth: + * + * Bluetooth Settings + */ +struct _NMSettingBluetooth { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingBluetoothClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingBluetooth, nm_setting_bluetooth, NM_TYPE_SETTING) + +#define NM_SETTING_BLUETOOTH_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_BLUETOOTH, NMSettingBluetoothPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_bluetooth_get_connection_type: + * @setting: the #NMSettingBluetooth + * + * Returns the connection method for communicating with the remote device (i.e. + * either DUN to a DUN-capable device or PANU to a NAP-capable device). + * + * Returns: the type, either %NM_SETTING_BLUETOOTH_TYPE_PANU, + * %NM_SETTING_BLUETOOTH_TYPE_NAP or %NM_SETTING_BLUETOOTH_TYPE_DUN + **/ +const char * +nm_setting_bluetooth_get_connection_type(NMSettingBluetooth *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BLUETOOTH(setting), NULL); + + return NM_SETTING_BLUETOOTH_GET_PRIVATE(setting)->type; +} + +/** + * nm_setting_bluetooth_get_bdaddr: + * @setting: the #NMSettingBluetooth + * + * Gets the Bluetooth address of the remote device which this setting + * describes a connection to. + * + * Returns: the Bluetooth address + **/ +const char * +nm_setting_bluetooth_get_bdaddr(NMSettingBluetooth *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BLUETOOTH(setting), NULL); + + return NM_SETTING_BLUETOOTH_GET_PRIVATE(setting)->bdaddr; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingBluetoothPrivate *priv = NM_SETTING_BLUETOOTH_GET_PRIVATE(setting); + const char * type; + gboolean missing_nap_bridge = FALSE; + + if (priv->bdaddr && !nm_utils_hwaddr_valid(priv->bdaddr, ETH_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_BDADDR); + return FALSE; + } + + type = priv->type; + if (!type) { + if (connection) { + /* We may infer the type from the (non-)existence of gsm/cdma/bridge settings. */ + type = _nm_connection_detect_bluetooth_type(connection); + } + if (!type) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_TYPE); + return FALSE; + } + } + + if (!NM_IN_STRSET(type, + NM_SETTING_BLUETOOTH_TYPE_DUN, + NM_SETTING_BLUETOOTH_TYPE_NAP, + NM_SETTING_BLUETOOTH_TYPE_PANU)) { + nm_assert(priv->type == type); + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_TYPE); + return FALSE; + } + + /* Make sure the corresponding 'type' setting is present */ + if (connection && nm_streq(type, NM_SETTING_BLUETOOTH_TYPE_DUN)) { + gboolean gsm = FALSE, cdma = FALSE; + + gsm = !!nm_connection_get_setting_gsm(connection); + cdma = !!nm_connection_get_setting_cdma(connection); + + if (!gsm && !cdma) { + /* We can't return MISSING_SETTING here, because we don't know + * whether to prefix the message with NM_SETTING_GSM_SETTING_NAME or + * NM_SETTING_CDMA_SETTING_NAME. + */ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("'%s' connection requires '%s' or '%s' setting"), + NM_SETTING_BLUETOOTH_TYPE_DUN, + NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_CDMA_SETTING_NAME); + g_prefix_error(error, "%s: ", NM_SETTING_BLUETOOTH_SETTING_NAME); + return FALSE; + } + } + /* PANU doesn't need a 'type' setting since no further configuration + * is required at the interface level. + */ + + /* NAP mode needs a bridge setting, and a bridge needs a name. */ + if (nm_streq(type, NM_SETTING_BLUETOOTH_TYPE_NAP)) { + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + if (connection && !nm_connection_get_setting_bridge(connection)) + missing_nap_bridge = TRUE; + } else { + if (!priv->bdaddr) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_BDADDR); + return FALSE; + } + } + + /* errors form here are normalizable. */ + + if (!priv->type) { + /* as determined above, we can detect the bluetooth type. */ + nm_assert(!missing_nap_bridge); + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NM_SETTING_BLUETOOTH_TYPE); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + + if (missing_nap_bridge) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("'%s' connection requires '%s' setting"), + NM_SETTING_BLUETOOTH_TYPE_NAP, + NM_SETTING_BRIDGE_SETTING_NAME); + g_prefix_error(error, "%s: ", NM_SETTING_BLUETOOTH_SETTING_NAME); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingBluetooth *setting = NM_SETTING_BLUETOOTH(object); + + switch (prop_id) { + case PROP_BDADDR: + g_value_set_string(value, nm_setting_bluetooth_get_bdaddr(setting)); + break; + case PROP_TYPE: + g_value_set_string(value, nm_setting_bluetooth_get_connection_type(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingBluetoothPrivate *priv = NM_SETTING_BLUETOOTH_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_BDADDR: + g_free(priv->bdaddr); + priv->bdaddr = g_value_dup_string(value); + break; + case PROP_TYPE: + g_free(priv->type); + priv->type = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_bluetooth_init(NMSettingBluetooth *setting) +{} + +/** + * nm_setting_bluetooth_new: + * + * Creates a new #NMSettingBluetooth object with default values. + * + * Returns: (transfer full): the new empty #NMSettingBluetooth object + **/ +NMSetting * +nm_setting_bluetooth_new(void) +{ + return g_object_new(NM_TYPE_SETTING_BLUETOOTH, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingBluetoothPrivate *priv = NM_SETTING_BLUETOOTH_GET_PRIVATE(object); + + g_free(priv->bdaddr); + g_free(priv->type); + + G_OBJECT_CLASS(nm_setting_bluetooth_parent_class)->finalize(object); +} + +static void +nm_setting_bluetooth_class_init(NMSettingBluetoothClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingBluetoothPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingBluetooth:bdaddr: + * + * The Bluetooth address of the device. + **/ + obj_properties[PROP_BDADDR] = g_param_spec_string( + NM_SETTING_BLUETOOTH_BDADDR, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_BDADDR], + &nm_sett_info_propert_type_mac_address); + + /** + * NMSettingBluetooth:type: + * + * Either "dun" for Dial-Up Networking connections or "panu" for Personal + * Area Networking connections to devices supporting the NAP profile. + **/ + obj_properties[PROP_TYPE] = g_param_spec_string(NM_SETTING_BLUETOOTH_TYPE, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_BLUETOOTH, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-bond.c b/src/libnm-core-impl/nm-setting-bond.c new file mode 100644 index 00000000..93baa9d6 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-bond.c @@ -0,0 +1,1201 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 - 2013 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-bond.h" + +#include +#include +#include +#include + +#include "libnm-core-aux-intern/nm-libnm-core-utils.h" +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-connection-private.h" +#include "nm-setting-infiniband.h" +#include "libnm-core-intern/nm-core-internal.h" + +/*****************************************************************************/ + +/** + * SECTION:nm-setting-bond + * @short_description: Describes connection properties for bonds + * + * The #NMSettingBond object is a #NMSetting subclass that describes properties + * necessary for bond connections. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingBond, PROP_OPTIONS, ); + +typedef struct { + GHashTable * options; + NMUtilsNamedValue *options_idx_cache; +} NMSettingBondPrivate; + +/** + * NMSettingBond: + * + * Bonding Settings + */ +struct _NMSettingBond { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingBondClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingBond, nm_setting_bond, NM_TYPE_SETTING) + +#define NM_SETTING_BOND_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_BOND, NMSettingBondPrivate)) + +/*****************************************************************************/ + +static const char *const valid_options_lst[] = { + /* mode must be the first element. nm-device-bond.c relies on that. */ + NM_SETTING_BOND_OPTION_MODE, + NM_SETTING_BOND_OPTION_MIIMON, + NM_SETTING_BOND_OPTION_DOWNDELAY, + NM_SETTING_BOND_OPTION_UPDELAY, + NM_SETTING_BOND_OPTION_ARP_INTERVAL, + NM_SETTING_BOND_OPTION_ARP_IP_TARGET, + NM_SETTING_BOND_OPTION_ARP_VALIDATE, + NM_SETTING_BOND_OPTION_PRIMARY, + NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, + NM_SETTING_BOND_OPTION_FAIL_OVER_MAC, + NM_SETTING_BOND_OPTION_USE_CARRIER, + NM_SETTING_BOND_OPTION_AD_SELECT, + NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, + NM_SETTING_BOND_OPTION_RESEND_IGMP, + NM_SETTING_BOND_OPTION_LACP_RATE, + NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, + NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, + NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, + NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, + NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, + NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS, + NM_SETTING_BOND_OPTION_MIN_LINKS, + NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, + NM_SETTING_BOND_OPTION_NUM_UNSOL_NA, + NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, + NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB, + NM_SETTING_BOND_OPTION_LP_INTERVAL, + NULL, +}; + +typedef struct { + const char * val; + NMBondOptionType opt_type; + guint min; + guint max; + const char *const *list; +} OptionMeta; + +static gboolean +_nm_assert_bond_meta(const OptionMeta *option_meta) +{ + nm_assert(option_meta); + + switch (option_meta->opt_type) { + case NM_BOND_OPTION_TYPE_BOTH: + nm_assert(option_meta->val); + nm_assert(option_meta->list); + nm_assert(option_meta->list[0]); + nm_assert(option_meta->min == 0); + nm_assert(option_meta->max == NM_PTRARRAY_LEN(option_meta->list) - 1); + nm_assert(g_strv_contains(option_meta->list, option_meta->val)); + return TRUE; + case NM_BOND_OPTION_TYPE_INT: + nm_assert(option_meta->val); + nm_assert(!option_meta->list); + nm_assert(option_meta->min < option_meta->max); + nm_assert(NM_STRCHAR_ALL(option_meta->val, ch, g_ascii_isdigit(ch))); + nm_assert(NM_STRCHAR_ALL(option_meta->val, ch, g_ascii_isdigit(ch))); + nm_assert(({ + _nm_utils_ascii_str_to_uint64(option_meta->val, + 10, + option_meta->min, + option_meta->max, + 0); + errno == 0; + })); + return TRUE; + case NM_BOND_OPTION_TYPE_IP: + nm_assert(option_meta->val); + /* fall-through */ + case NM_BOND_OPTION_TYPE_IFNAME: + case NM_BOND_OPTION_TYPE_MAC: + nm_assert(!option_meta->list); + nm_assert(option_meta->min == 0); + nm_assert(option_meta->max == 0); + return TRUE; + } + + nm_assert_not_reached(); + return FALSE; +} + +static char const *const _option_default_strv_ad_select[] = + NM_MAKE_STRV("stable", "bandwidth", "count"); +static char const *const _option_default_strv_arp_all_targets[] = NM_MAKE_STRV("any", "all"); +static char const *const _option_default_strv_arp_validate[] = + NM_MAKE_STRV("none", "active", "backup", "all", "filter", "filter_active", "filter_backup"); +static char const *const _option_default_strv_fail_over_mac[] = + NM_MAKE_STRV("none", "active", "follow"); +static char const *const _option_default_strv_lacp_rate[] = NM_MAKE_STRV("slow", "fast"); +static char const *const _option_default_strv_mode[] = NM_MAKE_STRV("balance-rr", + "active-backup", + "balance-xor", + "broadcast", + "802.3ad", + "balance-tlb", + "balance-alb"); +static char const *const _option_default_strv_primary_reselect[] = + NM_MAKE_STRV("always", "better", "failure"); +static char const *const _option_default_strv_xmit_hash_policy[] = + NM_MAKE_STRV("layer2", "layer3+4", "layer2+3", "encap2+3", "encap3+4", "vlan+srcmac"); + +static NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE( + _get_option_meta, + OptionMeta, + { + G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(LIST) == G_N_ELEMENTS(valid_options_lst) - 1); + + if (NM_MORE_ASSERT_ONCE(5)) { + int i; + + nm_assert(G_N_ELEMENTS(LIST) == NM_PTRARRAY_LEN(valid_options_lst)); + for (i = 0; i < G_N_ELEMENTS(LIST); i++) + _nm_assert_bond_meta(&LIST[i].value); + nm_assert(nm_streq(valid_options_lst[0], NM_SETTING_BOND_OPTION_MODE)); + } + }, + { return NULL; }, + {NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, {NULL, NM_BOND_OPTION_TYPE_IFNAME}}, + {NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, {"65535", NM_BOND_OPTION_TYPE_INT, 1, 65535}}, + {NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, {NULL, NM_BOND_OPTION_TYPE_MAC}}, + {NM_SETTING_BOND_OPTION_AD_SELECT, + {"stable", NM_BOND_OPTION_TYPE_BOTH, 0, 2, _option_default_strv_ad_select}}, + {NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, {"0", NM_BOND_OPTION_TYPE_INT, 0, 1023}}, + {NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, {"0", NM_BOND_OPTION_TYPE_INT, 0, 1}}, + {NM_SETTING_BOND_OPTION_ARP_ALL_TARGETS, + {"any", NM_BOND_OPTION_TYPE_BOTH, 0, 1, _option_default_strv_arp_all_targets}}, + {NM_SETTING_BOND_OPTION_ARP_INTERVAL, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}}, + {NM_SETTING_BOND_OPTION_ARP_IP_TARGET, {"", NM_BOND_OPTION_TYPE_IP}}, + {NM_SETTING_BOND_OPTION_ARP_VALIDATE, + {"none", NM_BOND_OPTION_TYPE_BOTH, 0, 6, _option_default_strv_arp_validate}}, + {NM_SETTING_BOND_OPTION_DOWNDELAY, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}}, + {NM_SETTING_BOND_OPTION_FAIL_OVER_MAC, + {"none", NM_BOND_OPTION_TYPE_BOTH, 0, 2, _option_default_strv_fail_over_mac}}, + {NM_SETTING_BOND_OPTION_LACP_RATE, + {"slow", NM_BOND_OPTION_TYPE_BOTH, 0, 1, _option_default_strv_lacp_rate}}, + {NM_SETTING_BOND_OPTION_LP_INTERVAL, {"1", NM_BOND_OPTION_TYPE_INT, 1, G_MAXINT}}, + {NM_SETTING_BOND_OPTION_MIIMON, {"100", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}}, + {NM_SETTING_BOND_OPTION_MIN_LINKS, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}}, + {NM_SETTING_BOND_OPTION_MODE, + {"balance-rr", NM_BOND_OPTION_TYPE_BOTH, 0, 6, _option_default_strv_mode}}, + {NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, {"1", NM_BOND_OPTION_TYPE_INT, 0, 255}}, + {NM_SETTING_BOND_OPTION_NUM_UNSOL_NA, {"1", NM_BOND_OPTION_TYPE_INT, 0, 255}}, + {NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, {"1", NM_BOND_OPTION_TYPE_INT, 0, 65535}}, + {NM_SETTING_BOND_OPTION_PRIMARY, {"", NM_BOND_OPTION_TYPE_IFNAME}}, + {NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, + {"always", NM_BOND_OPTION_TYPE_BOTH, 0, 2, _option_default_strv_primary_reselect}}, + {NM_SETTING_BOND_OPTION_RESEND_IGMP, {"1", NM_BOND_OPTION_TYPE_INT, 0, 255}}, + {NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB, {"1", NM_BOND_OPTION_TYPE_INT, 0, 1}}, + {NM_SETTING_BOND_OPTION_UPDELAY, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}}, + {NM_SETTING_BOND_OPTION_USE_CARRIER, {"1", NM_BOND_OPTION_TYPE_INT, 0, 1}}, + {NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, + {"layer2", NM_BOND_OPTION_TYPE_BOTH, 0, 5, _option_default_strv_xmit_hash_policy}}, ); + +/*****************************************************************************/ + +#define BIT(x) (((guint32) 1) << (x)) + +static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE( + _bond_option_unsupp_mode, + guint32, + { ; }, + { return 0; }, + {NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, + ~(BIT(NM_BOND_MODE_ACTIVEBACKUP) | BIT(NM_BOND_MODE_TLB) | BIT(NM_BOND_MODE_ALB))}, + {NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, ~(BIT(NM_BOND_MODE_8023AD))}, + {NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM, ~(BIT(NM_BOND_MODE_8023AD))}, + {NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, ~(BIT(NM_BOND_MODE_8023AD))}, + {NM_SETTING_BOND_OPTION_ARP_INTERVAL, + (BIT(NM_BOND_MODE_8023AD) | BIT(NM_BOND_MODE_TLB) | BIT(NM_BOND_MODE_ALB))}, + {NM_SETTING_BOND_OPTION_ARP_IP_TARGET, + (BIT(NM_BOND_MODE_8023AD) | BIT(NM_BOND_MODE_TLB) | BIT(NM_BOND_MODE_ALB))}, + {NM_SETTING_BOND_OPTION_ARP_VALIDATE, + (BIT(NM_BOND_MODE_8023AD) | BIT(NM_BOND_MODE_TLB) | BIT(NM_BOND_MODE_ALB))}, + {NM_SETTING_BOND_OPTION_LACP_RATE, ~(BIT(NM_BOND_MODE_8023AD))}, + {NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, ~(BIT(NM_BOND_MODE_ROUNDROBIN))}, + {NM_SETTING_BOND_OPTION_PRIMARY, + ~(BIT(NM_BOND_MODE_ACTIVEBACKUP) | BIT(NM_BOND_MODE_TLB) | BIT(NM_BOND_MODE_ALB))}, + {NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB, ~(BIT(NM_BOND_MODE_TLB) | BIT(NM_BOND_MODE_ALB))}, ); + +gboolean +_nm_setting_bond_option_supported(const char *option, NMBondMode mode) +{ + nm_assert(option); + nm_assert(mode != NM_BOND_MODE_UNKNOWN); + nm_assert(_NM_INT_NOT_NEGATIVE(mode) && mode < 32); + + return !NM_FLAGS_ANY(_bond_option_unsupp_mode(option), BIT(mode)); +} + +static const char * +_bond_get_option(NMSettingBond *self, const char *option) +{ + g_return_val_if_fail(NM_IS_SETTING_BOND(self), NULL); + g_return_val_if_fail(option, NULL); + + return g_hash_table_lookup(NM_SETTING_BOND_GET_PRIVATE(self)->options, option); +} + +static const char * +_bond_get_option_default(NMSettingBond *self, const char *option) +{ + const OptionMeta *option_meta; + + g_return_val_if_fail(NM_IS_SETTING_BOND(self), NULL); + + option_meta = _get_option_meta(option); + + g_return_val_if_fail(option_meta, NULL); + + return option_meta->val; +} + +static const char * +_bond_get_option_or_default(NMSettingBond *self, const char *option) +{ + return _bond_get_option(self, option) ?: _bond_get_option_default(self, option); +} + +static const char * +_bond_get_option_normalized(NMSettingBond *self, const char *option, gboolean get_default_only) +{ + const char *mode_str; + NMBondMode mode; + const char *value = NULL; + + g_return_val_if_fail(NM_IS_SETTING_BOND(self), NULL); + g_return_val_if_fail(option, NULL); + + mode_str = _bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_MODE); + mode = _nm_setting_bond_mode_from_string(mode_str); + + if (mode == NM_BOND_MODE_UNKNOWN) { + /* the mode is unknown, consequently, there is no normalized/default + * value either. */ + return NULL; + } + + if (!_nm_setting_bond_option_supported(option, mode)) + return NULL; + + /* Apply custom NetworkManager policies here */ + if (!get_default_only) { + if (NM_IN_STRSET(option, + NM_SETTING_BOND_OPTION_ARP_INTERVAL, + NM_SETTING_BOND_OPTION_ARP_IP_TARGET)) { + int miimon; + + /* if arp_interval is explicitly set and miimon is not, then disable miimon + * (and related updelay and downdelay) as recommended by the kernel docs */ + miimon = + _nm_utils_ascii_str_to_int64(_bond_get_option(self, NM_SETTING_BOND_OPTION_MIIMON), + 10, + 0, + G_MAXINT, + 0); + if (miimon != 0) { + /* miimon is enabled. arp_interval values are unset. */ + if (nm_streq(option, NM_SETTING_BOND_OPTION_ARP_INTERVAL)) + return "0"; + return ""; + } + value = _bond_get_option(self, option); + } else if (NM_IN_STRSET(option, + NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, + NM_SETTING_BOND_OPTION_NUM_UNSOL_NA)) { + /* just get one of the 2, at kernel level they're the same bond option */ + value = _bond_get_option(self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP); + if (!value) + value = _bond_get_option(self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA); + } else if (NM_IN_STRSET(option, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE)) { + /* "active_slave" is deprecated, and an alias for "primary". The property + * itself always normalizes to %NULL. */ + value = NULL; + } else if (NM_IN_STRSET(option, NM_SETTING_BOND_OPTION_PRIMARY)) { + /* "active_slave" is deprecated, and an alias for "primary". */ + value = _bond_get_option(self, NM_SETTING_BOND_OPTION_PRIMARY); + if (!value) + value = _bond_get_option(self, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE); + } else + value = _bond_get_option(self, option); + + if (value) + return value; + } + + /* Apply rules that change the default value of an option */ + if (nm_streq(option, NM_SETTING_BOND_OPTION_AD_ACTOR_SYSTEM)) { + /* The default value depends on the current mode */ + if (mode == NM_BOND_MODE_8023AD) + return NM_BOND_AD_ACTOR_SYSTEM_DEFAULT; + return ""; + } + + return _bond_get_option_or_default(self, option); +} + +const char * +nm_setting_bond_get_option_or_default(NMSettingBond *self, const char *option) +{ + g_return_val_if_fail(NM_IS_SETTING_BOND(self), NULL); + g_return_val_if_fail(option, NULL); + + return _bond_get_option_normalized(self, option, FALSE); +} + +static int +_atoi(const char *value) +{ + int v; + + v = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXINT, -1); + nm_assert(v >= 0); + return v; +}; + +/** + * nm_setting_bond_get_num_options: + * @setting: the #NMSettingBond + * + * Returns the number of options that should be set for this bond when it + * is activated. This can be used to retrieve each option individually + * using nm_setting_bond_get_option(). + * + * Returns: the number of bonding options + **/ +guint32 +nm_setting_bond_get_num_options(NMSettingBond *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BOND(setting), 0); + + return g_hash_table_size(NM_SETTING_BOND_GET_PRIVATE(setting)->options); +} + +static int +_get_option_sort(gconstpointer p_a, gconstpointer p_b, gpointer _unused) +{ + const char *a = *((const char *const *) p_a); + const char *b = *((const char *const *) p_b); + + NM_CMP_DIRECT(nm_streq(b, NM_SETTING_BOND_OPTION_MODE), + nm_streq(a, NM_SETTING_BOND_OPTION_MODE)); + NM_CMP_DIRECT_STRCMP(a, b); + nm_assert_not_reached(); + return 0; +} + +static void +_ensure_options_idx_cache(NMSettingBondPrivate *priv) +{ + if (!G_UNLIKELY(priv->options_idx_cache)) + priv->options_idx_cache = nm_utils_named_values_from_strdict_full(priv->options, + NULL, + _get_option_sort, + NULL, + NULL, + 0, + NULL); +} + +/** + * nm_setting_bond_get_option: + * @setting: the #NMSettingBond + * @idx: index of the desired option, from 0 to + * nm_setting_bond_get_num_options() - 1 + * @out_name: (out) (transfer none): on return, the name of the bonding option; + * this value is owned by the setting and should not be modified + * @out_value: (out) (transfer none): on return, the value of the name of the + * bonding option; this value is owned by the setting and should not be + * modified + * + * Given an index, return the value of the bonding option at that index. Indexes + * are *not* guaranteed to be static across modifications to options done by + * nm_setting_bond_add_option() and nm_setting_bond_remove_option(), + * and should not be used to refer to options except for short periods of time + * such as during option iteration. + * + * Returns: %TRUE on success if the index was valid and an option was found, + * %FALSE if the index was invalid (ie, greater than the number of options + * currently held by the setting) + **/ +gboolean +nm_setting_bond_get_option(NMSettingBond *setting, + guint32 idx, + const char ** out_name, + const char ** out_value) +{ + NMSettingBondPrivate *priv; + guint len; + + g_return_val_if_fail(NM_IS_SETTING_BOND(setting), FALSE); + + priv = NM_SETTING_BOND_GET_PRIVATE(setting); + + len = g_hash_table_size(priv->options); + if (idx >= len) + return FALSE; + + _ensure_options_idx_cache(priv); + + NM_SET_OUT(out_name, priv->options_idx_cache[idx].name); + NM_SET_OUT(out_value, priv->options_idx_cache[idx].value_str); + return TRUE; +} + +static gboolean +validate_int(const char *name, const char *value, const OptionMeta *option_meta) +{ + guint64 num; + + if (!NM_STRCHAR_ALL(value, ch, g_ascii_isdigit(ch))) + return FALSE; + + num = _nm_utils_ascii_str_to_uint64(value, 10, option_meta->min, option_meta->max, G_MAXUINT64); + if (num == G_MAXUINT64 && errno != 0) + return FALSE; + + return TRUE; +} + +static gboolean +validate_list(const char *name, const char *value, const OptionMeta *option_meta) +{ + int i; + + nm_assert(option_meta->list); + + for (i = 0; option_meta->list[i]; i++) { + if (nm_streq(option_meta->list[i], value)) + return TRUE; + } + return FALSE; +} + +static gboolean +validate_ip(const char *name, const char *value, GError **error) +{ + gs_free const char **addrs = NULL; + gsize i; + + addrs = nm_utils_bond_option_arp_ip_targets_split(value); + if (!addrs) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option is empty"), + name); + return FALSE; + } + for (i = 0; addrs[i]; i++) { + if (!nm_utils_parse_inaddr_bin(AF_INET, addrs[i], NULL, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid IPv4 address for '%s' option"), + addrs[i], + name); + return FALSE; + } + } + return TRUE; +} + +static gboolean +validate_ifname(const char *name, const char *value) +{ + return nm_utils_ifname_valid_kernel(value, NULL); +} + +gboolean +_nm_setting_bond_validate_option(const char *name, const char *value, GError **error) +{ + const OptionMeta *option_meta; + gboolean success; + + option_meta = _get_option_meta(name); + if (!option_meta) { + if (!name) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("missing option name")); + } else { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid option '%s'"), + name); + } + return FALSE; + } + + if (!value) + return TRUE; + + switch (option_meta->opt_type) { + case NM_BOND_OPTION_TYPE_INT: + success = validate_int(name, value, option_meta); + goto handle_error; + case NM_BOND_OPTION_TYPE_BOTH: + success = + (validate_int(name, value, option_meta) || validate_list(name, value, option_meta)); + goto handle_error; + case NM_BOND_OPTION_TYPE_IP: + nm_assert(nm_streq0(name, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)); + return validate_ip(name, value, error); + case NM_BOND_OPTION_TYPE_MAC: + success = nm_utils_hwaddr_valid(value, ETH_ALEN); + goto handle_error; + case NM_BOND_OPTION_TYPE_IFNAME: + success = validate_ifname(name, value); + goto handle_error; + } + + nm_assert_not_reached(); + success = FALSE; + +handle_error: + if (!success) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid value '%s' for option '%s'"), + value, + name); + } + return success; +} + +/** + * nm_setting_bond_validate_option: + * @name: the name of the option to validate + * @value (allow-none): the value of the option to validate. + * + * Checks whether @name is a valid bond option and @value is a valid value for + * the @name. If @value is %NULL, the function only validates the option name. + * + * Returns: %TRUE, if the @value is valid for the given name. + * If the @name is not a valid option, %FALSE will be returned. + **/ +gboolean +nm_setting_bond_validate_option(const char *name, const char *value) +{ + return _nm_setting_bond_validate_option(name, value, NULL); +} + +/** + * nm_setting_bond_get_option_by_name: + * @setting: the #NMSettingBond + * @name: the option name for which to retrieve the value + * + * Returns the value associated with the bonding option specified by + * @name, if it exists. + * + * Returns: the value, or %NULL if the key/value pair was never added to the + * setting; the value is owned by the setting and must not be modified + **/ +const char * +nm_setting_bond_get_option_by_name(NMSettingBond *setting, const char *name) +{ + g_return_val_if_fail(NM_IS_SETTING_BOND(setting), NULL); + + return _bond_get_option(setting, name); +} + +/** + * nm_setting_bond_add_option: + * @setting: the #NMSettingBond + * @name: name for the option + * @value: value for the option + * + * Add an option to the table. Adding a new name replaces any existing name/value pair + * that may already exist. + * + * Returns: returns %FALSE if either @name or @value is %NULL, in that case + * the option is not set. Otherwise, the function does not fail and does not validate + * the arguments. All validation happens via nm_connection_verify() or do basic validation + * yourself with nm_setting_bond_validate_option(). + * + * Note: Before 1.30, libnm would perform basic validation of the name and the value + * via nm_setting_bond_validate_option() and reject the request by returning FALSE. + * Since 1.30, libnm no longer rejects any values as the setter is not supposed + * to perform validation. + **/ +gboolean +nm_setting_bond_add_option(NMSettingBond *setting, const char *name, const char *value) +{ + NMSettingBondPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_BOND(setting), FALSE); + + if (!name) + return FALSE; + if (!value) + return FALSE; + + priv = NM_SETTING_BOND_GET_PRIVATE(setting); + + nm_clear_g_free(&priv->options_idx_cache); + g_hash_table_insert(priv->options, g_strdup(name), g_strdup(value)); + _notify(setting, PROP_OPTIONS); + return TRUE; +} + +/** + * nm_setting_bond_remove_option: + * @setting: the #NMSettingBond + * @name: name of the option to remove + * + * Remove the bonding option referenced by @name from the internal option + * list. + * + * Returns: %TRUE if the option was found and removed from the internal option + * list, %FALSE if it was not. + **/ +gboolean +nm_setting_bond_remove_option(NMSettingBond *setting, const char *name) +{ + NMSettingBondPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_BOND(setting), FALSE); + + priv = NM_SETTING_BOND_GET_PRIVATE(setting); + + if (!g_hash_table_remove(priv->options, name)) + return FALSE; + + nm_clear_g_free(&priv->options_idx_cache); + _notify(setting, PROP_OPTIONS); + return TRUE; +} + +/** + * nm_setting_bond_get_valid_options: + * @setting: (allow-none): the #NMSettingBond + * + * Returns a list of valid bond options. + * + * The @setting argument is unused and may be passed as %NULL. + * + * Returns: (transfer none): a %NULL-terminated array of strings of valid bond options. + **/ +const char ** +nm_setting_bond_get_valid_options(NMSettingBond *setting) +{ + return (const char **) valid_options_lst; +} + +/** + * nm_setting_bond_get_option_default: + * @setting: the #NMSettingBond + * @name: the name of the option + * + * Returns: the value of the bond option if not overridden by an entry in + * the #NMSettingBond:options property. + **/ +const char * +nm_setting_bond_get_option_default(NMSettingBond *setting, const char *name) +{ + g_return_val_if_fail(NM_IS_SETTING_BOND(setting), NULL); + + if (!name) + return NULL; + + return _bond_get_option_normalized(setting, name, TRUE); +} + +/** + * nm_setting_bond_get_option_normalized: + * @setting: the #NMSettingBond + * @name: the name of the option + * + * Since: 1.24 + * + * Returns: the value of the bond option after normalization, which is what NetworkManager + * will actually apply when activating the connection. %NULL if the option won't be applied + * to the connection. + **/ +const char * +nm_setting_bond_get_option_normalized(NMSettingBond *setting, const char *name) +{ + g_return_val_if_fail(NM_IS_SETTING_BOND(setting), NULL); + g_return_val_if_fail(name, NULL); + + return _bond_get_option_normalized(setting, name, FALSE); +} + +/** + * nm_setting_bond_get_option_type: + * @setting: the #NMSettingBond + * @name: the name of the option + * + * Returns: the type of the bond option. + **/ +NMBondOptionType +_nm_setting_bond_get_option_type(NMSettingBond *setting, const char *name) +{ + const OptionMeta *option_meta; + + g_return_val_if_fail(NM_IS_SETTING_BOND(setting), NM_BOND_OPTION_TYPE_INT); + + option_meta = _get_option_meta(name); + + g_return_val_if_fail(option_meta, NM_BOND_OPTION_TYPE_INT); + + return option_meta->opt_type; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingBond * self = NM_SETTING_BOND(setting); + NMSettingBondPrivate * priv = NM_SETTING_BOND_GET_PRIVATE(setting); + int miimon; + int arp_interval; + int num_grat_arp; + int num_unsol_na; + const char * mode_str; + const char * arp_ip_target = NULL; + const char * lacp_rate; + const char * primary; + NMBondMode bond_mode; + guint i; + const NMUtilsNamedValue *n; + + _ensure_options_idx_cache(priv); + + if (priv->options_idx_cache) { + for (i = 0; priv->options_idx_cache[i].name; i++) { + n = &priv->options_idx_cache[i]; + + if (!n->value_str || !_nm_setting_bond_validate_option(n->name, n->value_str, error)) { + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BOND_SETTING_NAME, + NM_SETTING_BOND_OPTIONS); + return FALSE; + } + } + } + + miimon = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_MIIMON)); + arp_interval = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_ARP_INTERVAL)); + num_grat_arp = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP)); + num_unsol_na = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA)); + + /* Option restrictions: + * + * arp_interval conflicts [ alb, tlb ] + * arp_interval needs arp_ip_target + * arp_validate does not work with [ BOND_MODE_8023AD, BOND_MODE_TLB, BOND_MODE_ALB ] + * downdelay needs miimon + * updelay needs miimon + * primary needs [ active-backup, tlb, alb ] + */ + + /* Verify bond mode */ + mode_str = _bond_get_option(self, NM_SETTING_BOND_OPTION_MODE); + if (!mode_str) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("mandatory option '%s' is missing"), + NM_SETTING_BOND_OPTION_MODE); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + bond_mode = _nm_setting_bond_mode_from_string(mode_str); + if (bond_mode == NM_BOND_MODE_UNKNOWN) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for '%s'"), + mode_str, + NM_SETTING_BOND_OPTION_MODE); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + + /* Make sure mode is compatible with other settings */ + if (NM_IN_SET(bond_mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB)) { + if (arp_interval > 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s=%s' is incompatible with '%s > 0'"), + NM_SETTING_BOND_OPTION_MODE, + mode_str, + NM_SETTING_BOND_OPTION_ARP_INTERVAL); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + } + + primary = _bond_get_option(self, NM_SETTING_BOND_OPTION_PRIMARY); + if (NM_IN_SET(bond_mode, NM_BOND_MODE_ACTIVEBACKUP, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB)) { + GError *tmp_error = NULL; + + if (primary && !nm_utils_ifname_valid_kernel(primary, &tmp_error)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not valid for the '%s' option: %s"), + primary, + NM_SETTING_BOND_OPTION_PRIMARY, + tmp_error->message); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + g_error_free(tmp_error); + return FALSE; + } + } else if (primary) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option is only valid for '%s=%s'"), + NM_SETTING_BOND_OPTION_PRIMARY, + NM_SETTING_BOND_OPTION_MODE, + "active-backup"); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + + if (connection && nm_connection_get_setting_infiniband(connection)) { + if (bond_mode != NM_BOND_MODE_ACTIVEBACKUP) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s=%s' is not a valid configuration for '%s'"), + NM_SETTING_BOND_OPTION_MODE, + mode_str, + NM_SETTING_INFINIBAND_SETTING_NAME); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + } + + if (miimon == 0) { + /* updelay and downdelay need miimon to be enabled to be valid */ + if (_atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_UPDELAY))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option requires '%s' option to be enabled"), + NM_SETTING_BOND_OPTION_UPDELAY, + NM_SETTING_BOND_OPTION_MIIMON); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + + if (_atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_DOWNDELAY))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option requires '%s' option to be enabled"), + NM_SETTING_BOND_OPTION_DOWNDELAY, + NM_SETTING_BOND_OPTION_MIIMON); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + } + + /* arp_ip_target can only be used with arp_interval, and must + * contain a comma-separated list of IPv4 addresses. + */ + arp_ip_target = _bond_get_option(self, NM_SETTING_BOND_OPTION_ARP_IP_TARGET); + if (arp_interval > 0) { + if (!arp_ip_target) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option requires '%s' option to be set"), + NM_SETTING_BOND_OPTION_ARP_INTERVAL, + NM_SETTING_BOND_OPTION_ARP_IP_TARGET); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + } else { + if (arp_ip_target) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option requires '%s' option to be set"), + NM_SETTING_BOND_OPTION_ARP_IP_TARGET, + NM_SETTING_BOND_OPTION_ARP_INTERVAL); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + } + + lacp_rate = _bond_get_option(self, NM_SETTING_BOND_OPTION_LACP_RATE); + if (lacp_rate && bond_mode != NM_BOND_MODE_8023AD && !NM_IN_STRSET(lacp_rate, "0", "slow")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option is only valid with mode '%s'"), + NM_SETTING_BOND_OPTION_LACP_RATE, + "802.3ad"); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + + if (_bond_get_option(self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP) + && _bond_get_option(self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA) + && num_grat_arp != num_unsol_na) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' and '%s' cannot have different values"), + NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, + NM_SETTING_BOND_OPTION_NUM_UNSOL_NA); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return FALSE; + } + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + /* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */ + + if (!NM_IN_STRSET(mode_str, + "802.3ad", + "active-backup", + "balance-rr", + "balance-alb", + "balance-tlb", + "balance-xor", + "broadcast")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option should be string"), + NM_SETTING_BOND_OPTION_MODE); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + + /* normalize unsupported options for the current mode */ + for (i = 0; priv->options_idx_cache[i].name; i++) { + n = &priv->options_idx_cache[i]; + if (!_nm_setting_bond_option_supported(n->name, bond_mode)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option is not valid with mode '%s'"), + n->name, + mode_str); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +options_equal_asym(NMSettingBond *s_bond, NMSettingBond *s_bond2, NMSettingCompareFlags flags) +{ + GHashTableIter iter; + const char * key, *value; + + g_hash_table_iter_init(&iter, NM_SETTING_BOND_GET_PRIVATE(s_bond)->options); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) { + /* when doing an inferrable match, the active-slave should be ignored + * as it might be differ from the setting in the connection. + * + * Also, the fail_over_mac setting can change, see for example + * https://bugzilla.redhat.com/show_bug.cgi?id=1375558#c8 */ + if (NM_IN_STRSET(key, "fail_over_mac", "active_slave")) + continue; + } + + if (!nm_streq0(value, _bond_get_option(s_bond2, key))) + return FALSE; + } + + return TRUE; +} + +static gboolean +options_equal(NMSettingBond *s_bond, NMSettingBond *s_bond2, NMSettingCompareFlags flags) +{ + return options_equal_asym(s_bond, s_bond2, flags) && options_equal_asym(s_bond2, s_bond, flags); +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_BOND_OPTIONS)) { + return (!set_b || options_equal(NM_SETTING_BOND(set_a), NM_SETTING_BOND(set_b), flags)); + } + + return NM_SETTING_CLASS(nm_setting_bond_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_OPTIONS: + g_value_take_boxed(value, _nm_utils_copy_strdict(priv->options)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_OPTIONS: + nm_clear_g_free(&priv->options_idx_cache); + g_hash_table_unref(priv->options); + priv->options = _nm_utils_copy_strdict(g_value_get_boxed(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_bond_init(NMSettingBond *setting) +{ + NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE(setting); + + priv->options = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + + /* Default values: */ + nm_setting_bond_add_option(setting, NM_SETTING_BOND_OPTION_MODE, "balance-rr"); +} + +/** + * nm_setting_bond_new: + * + * Creates a new #NMSettingBond object with default values. + * + * Returns: (transfer full): the new empty #NMSettingBond object + **/ +NMSetting * +nm_setting_bond_new(void) +{ + return g_object_new(NM_TYPE_SETTING_BOND, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingBondPrivate *priv = NM_SETTING_BOND_GET_PRIVATE(object); + + nm_clear_g_free(&priv->options_idx_cache); + g_hash_table_destroy(priv->options); + + G_OBJECT_CLASS(nm_setting_bond_parent_class)->finalize(object); +} + +static void +nm_setting_bond_class_init(NMSettingBondClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingBondPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->compare_property = compare_property; + + /** + * NMSettingBond:options: (type GHashTable(utf8,utf8)): + * + * Dictionary of key/value pairs of bonding options. Both keys and values + * must be strings. Option names must contain only alphanumeric characters + * (ie, [a-zA-Z0-9]). + **/ + /* ---ifcfg-rh--- + * property: options + * variable: BONDING_OPTS + * description: Bonding options. + * example: BONDING_OPTS="miimon=100 mode=broadcast" + * ---end--- + */ + obj_properties[PROP_OPTIONS] = g_param_spec_boxed( + NM_SETTING_BOND_OPTIONS, + "", + "", + G_TYPE_HASH_TABLE, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_OPTIONS], + &nm_sett_info_propert_type_strdict); + + /* ---dbus--- + * property: interface-name + * format: string + * description: Deprecated in favor of connection.interface-name, but can + * be used for backward-compatibility with older daemons, to set the + * bond's interface name. + * ---end--- + */ + _nm_properties_override_dbus(properties_override, + "interface-name", + &nm_sett_info_propert_type_deprecated_interface_name); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_BOND, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-bridge-port.c b/src/libnm-core-impl/nm-setting-bridge-port.c new file mode 100644 index 00000000..33d0c009 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-bridge-port.c @@ -0,0 +1,616 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2012 - 2013 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-bridge-port.h" + +#include +#include + +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-bridge.h" + +/** + * SECTION:nm-setting-bridge-port + * @short_description: Describes connection properties for bridge ports + * + * The #NMSettingBridgePort object is a #NMSetting subclass that describes + * optional properties that apply to bridge ports. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingBridgePort, + PROP_PRIORITY, + PROP_PATH_COST, + PROP_HAIRPIN_MODE, + PROP_VLANS, ); + +typedef struct { + GPtrArray *vlans; + guint16 priority; + guint16 path_cost; + bool hairpin_mode : 1; +} NMSettingBridgePortPrivate; + +/** + * NMSettingBridgePort: + * + * Bridge Port Settings + */ +struct _NMSettingBridgePort { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingBridgePortClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingBridgePort, nm_setting_bridge_port, NM_TYPE_SETTING) + +#define NM_SETTING_BRIDGE_PORT_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_BRIDGE_PORT, NMSettingBridgePortPrivate)) + +/*****************************************************************************/ + +static int +vlan_ptr_cmp(gconstpointer a, gconstpointer b) +{ + const NMBridgeVlan *vlan_a = *(const NMBridgeVlan **) a; + const NMBridgeVlan *vlan_b = *(const NMBridgeVlan **) b; + + return nm_bridge_vlan_cmp(vlan_a, vlan_b); +} + +gboolean +_nm_setting_bridge_port_sort_vlans(NMSettingBridgePort *setting) +{ + NMSettingBridgePortPrivate *priv; + gboolean need_sort = FALSE; + guint i; + + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + for (i = 1; i < priv->vlans->len; i++) { + NMBridgeVlan *vlan_prev = priv->vlans->pdata[i - 1]; + NMBridgeVlan *vlan = priv->vlans->pdata[i]; + + if (nm_bridge_vlan_cmp(vlan_prev, vlan) > 0) { + need_sort = TRUE; + break; + } + } + + if (need_sort) { + g_ptr_array_sort(priv->vlans, vlan_ptr_cmp); + _notify(setting, PROP_VLANS); + } + + return need_sort; +} + +/*****************************************************************************/ + +/** + * nm_setting_bridge_port_get_priority: + * @setting: the #NMSettingBridgePort + * + * Returns: the #NMSettingBridgePort:priority property of the setting + **/ +guint16 +nm_setting_bridge_port_get_priority(NMSettingBridgePort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting), 0); + + return NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting)->priority; +} + +/** + * nm_setting_bridge_port_get_path_cost: + * @setting: the #NMSettingBridgePort + * + * Returns: the #NMSettingBridgePort:path-cost property of the setting + **/ +guint16 +nm_setting_bridge_port_get_path_cost(NMSettingBridgePort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting), 0); + + return NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting)->path_cost; +} + +/** + * nm_setting_bridge_port_get_hairpin_mode: + * @setting: the #NMSettingBridgePort + * + * Returns: the #NMSettingBridgePort:hairpin-mode property of the setting + **/ +gboolean +nm_setting_bridge_port_get_hairpin_mode(NMSettingBridgePort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting), FALSE); + + return NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting)->hairpin_mode; +} + +/** + * nm_setting_bridge_port_add_vlan: + * @setting: the #NMSettingBridgePort + * @vlan: the vlan to add + * + * Appends a new vlan and associated information to the setting. The + * given vlan gets sealed and a reference to it is added. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_port_add_vlan(NMSettingBridgePort *setting, NMBridgeVlan *vlan) +{ + NMSettingBridgePortPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting)); + g_return_if_fail(vlan); + + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + nm_bridge_vlan_seal(vlan); + nm_bridge_vlan_ref(vlan); + + g_ptr_array_add(priv->vlans, vlan); + _notify(setting, PROP_VLANS); +} + +/** + * nm_setting_bridge_port_get_num_vlans: + * @setting: the #NMSettingBridgePort + * + * Returns: the number of VLANs + * + * Since: 1.18 + **/ +guint +nm_setting_bridge_port_get_num_vlans(NMSettingBridgePort *setting) +{ + NMSettingBridgePortPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting), 0); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + return priv->vlans->len; +} + +/** + * nm_setting_bridge_port_get_vlan: + * @setting: the #NMSettingBridgePort + * @idx: index number of the VLAN to return + * + * Returns: (transfer none): the VLAN at index @idx + * + * Since: 1.18 + **/ +NMBridgeVlan * +nm_setting_bridge_port_get_vlan(NMSettingBridgePort *setting, guint idx) +{ + NMSettingBridgePortPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting), NULL); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + g_return_val_if_fail(idx < priv->vlans->len, NULL); + + return priv->vlans->pdata[idx]; +} + +/** + * nm_setting_bridge_port_remove_vlan: + * @setting: the #NMSettingBridgePort + * @idx: index number of the VLAN. + * + * Removes the vlan at index @idx. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_port_remove_vlan(NMSettingBridgePort *setting, guint idx) +{ + NMSettingBridgePortPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting)); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + g_return_if_fail(idx < priv->vlans->len); + + g_ptr_array_remove_index(priv->vlans, idx); + _notify(setting, PROP_VLANS); +} + +/** + * nm_setting_bridge_port_remove_vlan_by_vid: + * @setting: the #NMSettingBridgePort + * @vid_start: the vlan start index + * @vid_end: the vlan end index + * + * Remove the VLAN with range @vid_start to @vid_end. + * If @vid_end is zero, it is assumed to be equal to @vid_start + * and so the single-id VLAN with id @vid_start is removed. + * + * Returns: %TRUE if the vlan was found and removed; %FALSE otherwise + * + * Since: 1.18 + **/ +gboolean +nm_setting_bridge_port_remove_vlan_by_vid(NMSettingBridgePort *setting, + guint16 vid_start, + guint16 vid_end) +{ + NMSettingBridgePortPrivate *priv; + guint i; + + if (vid_end == 0) + vid_end = vid_start; + + g_return_val_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting), FALSE); + + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + for (i = 0; i < priv->vlans->len; i++) { + NMBridgeVlan *vlan = priv->vlans->pdata[i]; + guint16 v_start = 0; + guint16 v_end = 0; + + nm_bridge_vlan_get_vid_range(vlan, &v_start, &v_end); + if (v_start == vid_start && v_end == vid_end) { + g_ptr_array_remove_index(priv->vlans, i); + _notify(setting, PROP_VLANS); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_bridge_port_clear_vlans: + * @setting: the #NMSettingBridgePort + * + * Removes all configured VLANs. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_port_clear_vlans(NMSettingBridgePort *setting) +{ + NMSettingBridgePortPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_BRIDGE_PORT(setting)); + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + if (priv->vlans->len != 0) { + g_ptr_array_set_size(priv->vlans, 0); + _notify(setting, PROP_VLANS); + } +} + +GPtrArray * +_nm_setting_bridge_port_get_vlans(NMSettingBridgePort *setting) +{ + nm_assert(NM_IS_SETTING_BRIDGE_PORT(setting)); + + return NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting)->vlans; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingBridgePortPrivate *priv; + + priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + if (connection) { + NMSettingConnection *s_con; + const char * slave_type; + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("missing setting")); + g_prefix_error(error, "%s: ", NM_SETTING_CONNECTION_SETTING_NAME); + return FALSE; + } + + slave_type = nm_setting_connection_get_slave_type(s_con); + if (slave_type && strcmp(slave_type, NM_SETTING_BRIDGE_SETTING_NAME)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with a '%s' setting must have the slave-type set to '%s'. " + "Instead it is '%s'"), + NM_SETTING_BRIDGE_PORT_SETTING_NAME, + NM_SETTING_BRIDGE_SETTING_NAME, + slave_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return FALSE; + } + } + + if (!_nm_utils_bridge_vlan_verify_list(priv->vlans, + FALSE, + error, + NM_SETTING_BRIDGE_PORT_SETTING_NAME, + NM_SETTING_BRIDGE_PORT_VLANS)) + return FALSE; + + /* Failures from here on are NORMALIZABLE... */ + + if (!_nm_utils_bridge_vlan_verify_list(priv->vlans, + TRUE, + error, + NM_SETTING_BRIDGE_PORT_SETTING_NAME, + NM_SETTING_BRIDGE_PORT_VLANS)) + return NM_SETTING_VERIFY_NORMALIZABLE; + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingBridgePortPrivate *priv_a; + NMSettingBridgePortPrivate *priv_b; + guint i; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_BRIDGE_PORT_VLANS)) { + if (set_b) { + priv_a = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(set_a); + priv_b = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(set_b); + + if (priv_a->vlans->len != priv_b->vlans->len) + return FALSE; + for (i = 0; i < priv_a->vlans->len; i++) { + if (nm_bridge_vlan_cmp(priv_a->vlans->pdata[i], priv_b->vlans->pdata[i])) + return FALSE; + } + } + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_bridge_port_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingBridgePortPrivate *priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_PRIORITY: + g_value_set_uint(value, priv->priority); + break; + case PROP_PATH_COST: + g_value_set_uint(value, priv->path_cost); + break; + case PROP_HAIRPIN_MODE: + g_value_set_boolean(value, priv->hairpin_mode); + break; + case PROP_VLANS: + g_value_take_boxed(value, + _nm_utils_copy_array(priv->vlans, + (NMUtilsCopyFunc) nm_bridge_vlan_ref, + (GDestroyNotify) nm_bridge_vlan_unref)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingBridgePortPrivate *priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_PRIORITY: + priv->priority = g_value_get_uint(value); + break; + case PROP_PATH_COST: + priv->path_cost = g_value_get_uint(value); + break; + case PROP_HAIRPIN_MODE: + priv->hairpin_mode = g_value_get_boolean(value); + break; + case PROP_VLANS: + g_ptr_array_unref(priv->vlans); + priv->vlans = _nm_utils_copy_array(g_value_get_boxed(value), + (NMUtilsCopyFunc) _nm_bridge_vlan_dup_and_seal, + (GDestroyNotify) nm_bridge_vlan_unref); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_bridge_port_init(NMSettingBridgePort *setting) +{ + NMSettingBridgePortPrivate *priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(setting); + + priv->vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref); + + priv->priority = NM_BRIDGE_PORT_PRIORITY_DEF; + priv->path_cost = NM_BRIDGE_PORT_PATH_COST_DEF; +} + +/** + * nm_setting_bridge_port_new: + * + * Creates a new #NMSettingBridgePort object with default values. + * + * Returns: (transfer full): the new empty #NMSettingBridgePort object + **/ +NMSetting * +nm_setting_bridge_port_new(void) +{ + return g_object_new(NM_TYPE_SETTING_BRIDGE_PORT, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingBridgePortPrivate *priv = NM_SETTING_BRIDGE_PORT_GET_PRIVATE(object); + + g_ptr_array_unref(priv->vlans); + + G_OBJECT_CLASS(nm_setting_bridge_port_parent_class)->finalize(object); +} + +static void +nm_setting_bridge_port_class_init(NMSettingBridgePortClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingBridgePortPrivate)); + + object_class->finalize = finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + setting_class->compare_property = compare_property; + setting_class->verify = verify; + + /** + * NMSettingBridgePort:priority: + * + * The Spanning Tree Protocol (STP) priority of this bridge port. + **/ + /* ---ifcfg-rh--- + * property: priority + * variable: BRIDGING_OPTS: priority= + * values: 0 - 63 + * default: 32 + * description: STP priority. + * ---end--- + */ + obj_properties[PROP_PRIORITY] = + g_param_spec_uint(NM_SETTING_BRIDGE_PORT_PRIORITY, + "", + "", + NM_BRIDGE_PORT_PRIORITY_MIN, + NM_BRIDGE_PORT_PRIORITY_MAX, + NM_BRIDGE_PORT_PRIORITY_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridgePort:path-cost: + * + * The Spanning Tree Protocol (STP) port cost for destinations via this + * port. + **/ + /* ---ifcfg-rh--- + * property: path-cost + * variable: BRIDGING_OPTS: path_cost= + * values: 1 - 65535 + * default: 100 + * description: STP cost. + * ---end--- + */ + obj_properties[PROP_PATH_COST] = g_param_spec_uint(NM_SETTING_BRIDGE_PORT_PATH_COST, + "", + "", + NM_BRIDGE_PORT_PATH_COST_MIN, + NM_BRIDGE_PORT_PATH_COST_MAX, + NM_BRIDGE_PORT_PATH_COST_DEF, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridgePort:hairpin-mode: + * + * Enables or disables "hairpin mode" for the port, which allows frames to + * be sent back out through the port the frame was received on. + **/ + /* ---ifcfg-rh--- + * property: hairpin-mode + * variable: BRIDGING_OPTS: hairpin_mode= + * default: yes + * description: Hairpin mode of the bridge port. + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, + PROP_HAIRPIN_MODE, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_bridge_port_get_hairpin_mode); + + /** + * NMSettingBridgePort:vlans: (type GPtrArray(NMBridgeVlan)) + * + * Array of bridge VLAN objects. In addition to the VLANs + * specified here, the port will also have the default-pvid + * VLAN configured on the bridge by the bridge.vlan-default-pvid + * property. + * + * In nmcli the VLAN list can be specified with the following + * syntax: + * + * $vid [pvid] [untagged] [, $vid [pvid] [untagged]]... + * + * where $vid is either a single id between 1 and 4094 or a + * range, represented as a couple of ids separated by a dash. + * + * Since: 1.18 + **/ + /* ---ifcfg-rh--- + * property: vlans + * variable: BRIDGE_PORT_VLANS + * description: List of VLANs on the bridge port + * example: BRIDGE_PORT_VLANS="1 pvid untagged,20,300-400 untagged" + * ---end--- + */ + obj_properties[PROP_VLANS] = g_param_spec_boxed(NM_SETTING_BRIDGE_PORT_VLANS, + "", + "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_VLANS], + &nm_sett_info_propert_type_bridge_vlans); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_BRIDGE_PORT, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-bridge.c b/src/libnm-core-impl/nm-setting-bridge.c new file mode 100644 index 00000000..e987310f --- /dev/null +++ b/src/libnm-core-impl/nm-setting-bridge.c @@ -0,0 +1,2308 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 - 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-bridge.h" + +#include +#include +#include + +#include "libnm-glib-aux/nm-str-buf.h" +#include "nm-connection-private.h" +#include "nm-utils.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-bridge + * @short_description: Describes connection properties for bridges + * + * The #NMSettingBridge object is a #NMSetting subclass that describes properties + * necessary for bridging connections. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingBridge, + PROP_MAC_ADDRESS, + PROP_STP, + PROP_PRIORITY, + PROP_FORWARD_DELAY, + PROP_HELLO_TIME, + PROP_MAX_AGE, + PROP_AGEING_TIME, + PROP_GROUP_ADDRESS, + PROP_GROUP_FORWARD_MASK, + PROP_MULTICAST_HASH_MAX, + PROP_MULTICAST_LAST_MEMBER_COUNT, + PROP_MULTICAST_LAST_MEMBER_INTERVAL, + PROP_MULTICAST_MEMBERSHIP_INTERVAL, + PROP_MULTICAST_ROUTER, + PROP_MULTICAST_QUERIER, + PROP_MULTICAST_QUERIER_INTERVAL, + PROP_MULTICAST_QUERY_INTERVAL, + PROP_MULTICAST_QUERY_RESPONSE_INTERVAL, + PROP_MULTICAST_QUERY_USE_IFADDR, + PROP_MULTICAST_SNOOPING, + PROP_MULTICAST_STARTUP_QUERY_COUNT, + PROP_MULTICAST_STARTUP_QUERY_INTERVAL, + PROP_VLAN_FILTERING, + PROP_VLAN_DEFAULT_PVID, + PROP_VLAN_PROTOCOL, + PROP_VLAN_STATS_ENABLED, + PROP_VLANS, ); + +typedef struct { + GPtrArray *vlans; + char * mac_address; + char * multicast_router; + char * group_address; + char * vlan_protocol; + guint64 multicast_last_member_interval; + guint64 multicast_membership_interval; + guint64 multicast_querier_interval; + guint64 multicast_query_interval; + guint64 multicast_query_response_interval; + guint64 multicast_startup_query_interval; + guint32 ageing_time; + guint32 multicast_hash_max; + guint32 multicast_last_member_count; + guint32 multicast_startup_query_count; + guint16 priority; + guint16 forward_delay; + guint16 hello_time; + guint16 max_age; + guint16 vlan_default_pvid; + guint16 group_forward_mask; + bool multicast_snooping : 1; + bool vlan_filtering : 1; + bool stp : 1; + bool vlan_stats_enabled : 1; + bool multicast_query_use_ifaddr : 1; + bool multicast_querier : 1; +} NMSettingBridgePrivate; + +/** + * NMSettingBridge: + * + * Bridging Settings + */ +struct _NMSettingBridge { + NMSetting parent; + NMSettingBridgePrivate _priv; +}; + +struct _NMSettingBridgeClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingBridge, nm_setting_bridge, NM_TYPE_SETTING) + +#define NM_SETTING_BRIDGE_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSettingBridge, NM_IS_SETTING_BRIDGE, NMSetting) + +/*****************************************************************************/ + +G_DEFINE_BOXED_TYPE(NMBridgeVlan, nm_bridge_vlan, _nm_bridge_vlan_dup, nm_bridge_vlan_unref) + +struct _NMBridgeVlan { + guint refcount; + guint16 vid_start; + guint16 vid_end; + bool untagged : 1; + bool pvid : 1; + bool sealed : 1; +}; + +static gboolean +NM_IS_BRIDGE_VLAN(const NMBridgeVlan *self, gboolean also_sealed) +{ + return self && self->refcount > 0 && (also_sealed || !self->sealed); +} + +/** + * nm_bridge_vlan_new: + * @vid_start: the start VLAN id, must be between 1 and 4094. + * @vid_end: the end VLAN id, must be 0 or between @vid_start and 4094. + * + * Creates a new #NMBridgeVlan object for the given VLAN id range. + * Setting @vid_end to 0 is equivalent to setting it to @vid_start + * and creates a single-id VLAN. + * + * Returns: (transfer full): the new #NMBridgeVlan object. + * + * Since: 1.18 + **/ +NMBridgeVlan * +nm_bridge_vlan_new(guint16 vid_start, guint16 vid_end) +{ + NMBridgeVlan *vlan; + + if (vid_end == 0) + vid_end = vid_start; + + g_return_val_if_fail(vid_start >= NM_BRIDGE_VLAN_VID_MIN, NULL); + g_return_val_if_fail(vid_end <= NM_BRIDGE_VLAN_VID_MAX, NULL); + g_return_val_if_fail(vid_start <= vid_end, NULL); + + vlan = g_slice_new0(NMBridgeVlan); + vlan->refcount = 1; + vlan->vid_start = vid_start; + vlan->vid_end = vid_end; + + return vlan; +} + +/** + * nm_bridge_vlan_ref: + * @vlan: the #NMBridgeVlan + * + * Increases the reference count of the object. + * + * Returns: the input argument @vlan object. + * + * Since: 1.18 + **/ +NMBridgeVlan * +nm_bridge_vlan_ref(NMBridgeVlan *vlan) +{ + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE), NULL); + + nm_assert(vlan->refcount < G_MAXUINT); + + vlan->refcount++; + return vlan; +} + +/** + * nm_bridge_vlan_unref: + * @vlan: the #NMBridgeVlan + * + * Decreases the reference count of the object. If the reference count + * reaches zero the object will be destroyed. + * + * Since: 1.18 + **/ +void +nm_bridge_vlan_unref(NMBridgeVlan *vlan) +{ + g_return_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE)); + + if (--vlan->refcount == 0) + g_slice_free(NMBridgeVlan, vlan); +} + +/** + * nm_bridge_vlan_cmp: + * @a: a #NMBridgeVlan + * @b: another #NMBridgeVlan + * + * Compare two bridge VLAN objects. + * + * Returns: zero of the two instances are equivalent or + * a non-zero integer otherwise. This defines a total ordering + * over the VLANs. Whether a VLAN is sealed or not does not + * affect the comparison. + * + * Since: 1.18 + **/ +int +nm_bridge_vlan_cmp(const NMBridgeVlan *a, const NMBridgeVlan *b) +{ + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(a, TRUE), 0); + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(b, TRUE), 0); + + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, vid_start); + NM_CMP_FIELD(a, b, vid_end); + NM_CMP_FIELD_BOOL(a, b, untagged); + NM_CMP_FIELD_BOOL(a, b, pvid); + + return 0; +} + +NMBridgeVlan * +_nm_bridge_vlan_dup(const NMBridgeVlan *vlan) +{ + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE), NULL); + + if (vlan->sealed) { + nm_bridge_vlan_ref((NMBridgeVlan *) vlan); + return (NMBridgeVlan *) vlan; + } + + return nm_bridge_vlan_new_clone(vlan); +} + +NMBridgeVlan * +_nm_bridge_vlan_dup_and_seal(const NMBridgeVlan *vlan) +{ + NMBridgeVlan *new; + + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE), NULL); + + new = _nm_bridge_vlan_dup(vlan); + nm_bridge_vlan_seal(new); + + return new; +} + +/** + * nm_bridge_vlan_get_vid_range: + * @vlan: the #NMBridgeVlan + * @vid_start: (out): location to store the VLAN id range start. + * @vid_end: (out): location to store the VLAN id range end + * + * Gets the VLAN id range. + * + * Returns: %TRUE is the VLAN specifies a range, %FALSE if it is + * a single-id VLAN. + * + * Since: 1.18 + **/ +gboolean +nm_bridge_vlan_get_vid_range(const NMBridgeVlan *vlan, guint16 *vid_start, guint16 *vid_end) +{ + /* with LTO and optimization, the compiler complains that the + * output variables are not initialized. In practice, the function + * only sets the output on success. But make the compiler happy. + */ + NM_SET_OUT(vid_start, 0); + NM_SET_OUT(vid_end, 0); + + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE), 0); + + NM_SET_OUT(vid_start, vlan->vid_start); + NM_SET_OUT(vid_end, vlan->vid_end); + + return vlan->vid_start != vlan->vid_end; +} + +/** + * nm_bridge_vlan_is_untagged: + * @vlan: the #NMBridgeVlan + * + * Returns whether the VLAN is untagged. + * + * Returns: %TRUE if the VLAN is untagged, %FALSE otherwise + * + * Since: 1.18 + **/ +gboolean +nm_bridge_vlan_is_untagged(const NMBridgeVlan *vlan) +{ + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE), FALSE); + + return vlan->untagged; +} + +/** + * nm_bridge_vlan_is_pvid: + * @vlan: the #NMBridgeVlan + * + * Returns whether the VLAN is the PVID for the port. + * + * Returns: %TRUE if the VLAN is the PVID + * + * Since: 1.18 + **/ +gboolean +nm_bridge_vlan_is_pvid(const NMBridgeVlan *vlan) +{ + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE), FALSE); + + return vlan->pvid; +} + +/** + * nm_bridge_vlan_set_untagged: + * @vlan: the #NMBridgeVlan + * @value: the new value + * + * Change the value of the untagged property of the VLAN. + * + * Since: 1.18 + **/ +void +nm_bridge_vlan_set_untagged(NMBridgeVlan *vlan, gboolean value) +{ + g_return_if_fail(NM_IS_BRIDGE_VLAN(vlan, FALSE)); + + vlan->untagged = value; +} + +/** + * nm_bridge_vlan_set_pvid: + * @vlan: the #NMBridgeVlan + * @value: the new value + * + * Change the value of the PVID property of the VLAN. It + * is invalid to set the value to %TRUE for non-single-id + * VLANs. + * + * Since: 1.18 + **/ +void +nm_bridge_vlan_set_pvid(NMBridgeVlan *vlan, gboolean value) +{ + g_return_if_fail(NM_IS_BRIDGE_VLAN(vlan, FALSE)); + g_return_if_fail(!value || vlan->vid_start == vlan->vid_end); + + vlan->pvid = value; +} + +/** + * nm_bridge_vlan_is_sealed: + * @vlan: the #NMBridgeVlan instance + * + * Returns: whether @self is sealed or not. + * + * Since: 1.18 + */ +gboolean +nm_bridge_vlan_is_sealed(const NMBridgeVlan *vlan) +{ + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE), FALSE); + + return vlan->sealed; +} + +/** + * nm_bridge_vlan_seal: + * @vlan: the #NMBridgeVlan instance + * + * Seal the #NMBridgeVlan instance. Afterwards, it is a bug + * to call all functions that modify the instance (except ref/unref). + * A sealed instance cannot be unsealed again, but you can create + * an unsealed copy with nm_bridge_vlan_new_clone(). + * + * Since: 1.18 + */ +void +nm_bridge_vlan_seal(NMBridgeVlan *vlan) +{ + g_return_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE)); + + vlan->sealed = TRUE; +} + +/** + * nm_bridge_vlan_new_clone: + * @vlan: the #NMBridgeVlan instance to copy + * + * Returns: (transfer full): a clone of @vlan. This instance + * is always unsealed. + * + * Since: 1.18 + */ +NMBridgeVlan * +nm_bridge_vlan_new_clone(const NMBridgeVlan *vlan) +{ + NMBridgeVlan *copy; + + g_return_val_if_fail(NM_IS_BRIDGE_VLAN(vlan, TRUE), NULL); + + copy = nm_bridge_vlan_new(vlan->vid_start, vlan->vid_end); + copy->untagged = vlan->untagged; + copy->pvid = vlan->pvid; + + return copy; +} + +/** + * nm_bridge_vlan_to_str: + * @vlan: the %NMBridgeVlan + * @error: location of the error + * + * Convert a %NMBridgeVlan to a string. + * + * Returns: formatted string or %NULL + * + * Since: 1.18 + */ +char * +nm_bridge_vlan_to_str(const NMBridgeVlan *vlan, GError **error) +{ + NMStrBuf string; + + g_return_val_if_fail(vlan, NULL); + g_return_val_if_fail(!error || !*error, NULL); + + /* The function never fails at the moment, but it might in the + * future if more parameters are added to the object that could + * make it invalid. */ + + nm_str_buf_init(&string, NM_UTILS_GET_NEXT_REALLOC_SIZE_32, FALSE); + + if (vlan->vid_start == vlan->vid_end) + nm_str_buf_append_printf(&string, "%u", vlan->vid_start); + else + nm_str_buf_append_printf(&string, "%u-%u", vlan->vid_start, vlan->vid_end); + + if (nm_bridge_vlan_is_pvid(vlan)) + nm_str_buf_append(&string, " pvid"); + if (nm_bridge_vlan_is_untagged(vlan)) + nm_str_buf_append(&string, " untagged"); + + return nm_str_buf_finalize(&string, NULL); +} + +/** + * nm_bridge_vlan_from_str: + * @str: the string representation of a bridge VLAN + * @error: location of the error + * + * Parses the string representation of the queueing + * discipline to a %NMBridgeVlan instance. + * + * Returns: the %NMBridgeVlan or %NULL + * + * Since: 1.18 + */ +NMBridgeVlan * +nm_bridge_vlan_from_str(const char *str, GError **error) +{ + NMBridgeVlan * vlan = NULL; + gs_free const char **tokens = NULL; + guint i, vid_start, vid_end = 0; + gboolean pvid = FALSE; + gboolean untagged = FALSE; + char * c; + + g_return_val_if_fail(str, NULL); + g_return_val_if_fail(!error || !*error, NULL); + + tokens = nm_utils_escaped_tokens_split(str, NM_ASCII_SPACES); + if (!tokens || !tokens[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "missing VLAN id"); + return NULL; + } + + c = strchr(tokens[0], '-'); + if (c) + *c = '\0'; + + vid_start = _nm_utils_ascii_str_to_uint64(tokens[0], + 10, + NM_BRIDGE_VLAN_VID_MIN, + NM_BRIDGE_VLAN_VID_MAX, + G_MAXUINT); + if (vid_start == G_MAXUINT) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VLAN id range start '%s', must be in [1,4094]", + tokens[0]); + return NULL; + } + + if (c) { + vid_end = _nm_utils_ascii_str_to_uint64(c + 1, + 10, + NM_BRIDGE_VLAN_VID_MIN, + NM_BRIDGE_VLAN_VID_MAX, + G_MAXUINT); + if (vid_end == G_MAXUINT) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VLAN id range end '%s', must be in [1,4094]", + c + 1); + return NULL; + } + if (vid_end < vid_start) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VLAN id range %u-%u, start VLAN id must be less than end VLAN id", + vid_start, + vid_end); + return NULL; + } + } else + vid_end = vid_start; + + for (i = 1; tokens[i]; i++) { + if (nm_streq(tokens[i], "pvid")) { + if (vid_start != vid_end) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "a VLAN range can't be a PVID"); + return NULL; + } + pvid = TRUE; + } else if (nm_streq(tokens[i], "untagged")) + untagged = TRUE; + else { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid option '%s'", + tokens[i]); + return NULL; + } + } + + vlan = nm_bridge_vlan_new(vid_start, vid_end); + nm_bridge_vlan_set_pvid(vlan, pvid); + nm_bridge_vlan_set_untagged(vlan, untagged); + + return vlan; +} + +/*****************************************************************************/ + +static int +vlan_ptr_cmp(gconstpointer a, gconstpointer b) +{ + const NMBridgeVlan *vlan_a = *(const NMBridgeVlan **) a; + const NMBridgeVlan *vlan_b = *(const NMBridgeVlan **) b; + + return nm_bridge_vlan_cmp(vlan_a, vlan_b); +} + +gboolean +_nm_setting_bridge_sort_vlans(NMSettingBridge *setting) +{ + NMSettingBridgePrivate *priv; + gboolean need_sort = FALSE; + guint i; + + priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + for (i = 1; i < priv->vlans->len; i++) { + NMBridgeVlan *vlan_prev = priv->vlans->pdata[i - 1]; + NMBridgeVlan *vlan = priv->vlans->pdata[i]; + + if (nm_bridge_vlan_cmp(vlan_prev, vlan) > 0) { + need_sort = TRUE; + break; + } + } + + if (need_sort) { + g_ptr_array_sort(priv->vlans, vlan_ptr_cmp); + _notify(setting, PROP_VLANS); + } + + return need_sort; +} + +/*****************************************************************************/ + +/** + * nm_setting_bridge_get_mac_address: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:mac-address property of the setting + **/ +const char * +nm_setting_bridge_get_mac_address(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), NULL); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->mac_address; +} + +/** + * nm_setting_bridge_get_stp: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:stp property of the setting + **/ +gboolean +nm_setting_bridge_get_stp(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), FALSE); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->stp; +} + +/** + * nm_setting_bridge_get_priority: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:priority property of the setting + **/ +guint16 +nm_setting_bridge_get_priority(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->priority; +} + +/** + * nm_setting_bridge_get_forward_delay: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:forward-delay property of the setting + **/ +guint16 +nm_setting_bridge_get_forward_delay(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->forward_delay; +} + +/** + * nm_setting_bridge_get_hello_time: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:hello-time property of the setting + **/ +guint16 +nm_setting_bridge_get_hello_time(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->hello_time; +} + +/** + * nm_setting_bridge_get_max_age: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:max-age property of the setting + **/ +guint16 +nm_setting_bridge_get_max_age(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->max_age; +} + +/** + * nm_setting_bridge_get_ageing_time: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:ageing-time property of the setting + **/ +guint +nm_setting_bridge_get_ageing_time(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->ageing_time; +} + +/** + * nm_setting_bridge_get_group_forward_mask: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:group-forward-mask property of the setting + * + * Since: 1.10 + **/ +guint16 +nm_setting_bridge_get_group_forward_mask(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->group_forward_mask; +} + +/** + * nm_setting_bridge_get_multicast_snooping: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-snooping property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_bridge_get_multicast_snooping(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), FALSE); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_snooping; +} + +/** + * nm_setting_bridge_get_vlan_filtering: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:vlan-filtering property of the setting + * + * Since: 1.18 + **/ +gboolean +nm_setting_bridge_get_vlan_filtering(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), FALSE); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->vlan_filtering; +} + +/** + * nm_setting_bridge_get_vlan_default_pvid: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:vlan-default-pvid property of the setting + * + * Since: 1.18 + **/ +guint16 +nm_setting_bridge_get_vlan_default_pvid(NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 1); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->vlan_default_pvid; +} + +/** + * nm_setting_bridge_add_vlan: + * @setting: the #NMSettingBridge + * @vlan: the vlan to add + * + * Appends a new vlan and associated information to the setting. The + * given vlan gets sealed and a reference to it is added. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_add_vlan(NMSettingBridge *setting, NMBridgeVlan *vlan) +{ + NMSettingBridgePrivate *priv; + + g_return_if_fail(NM_IS_SETTING_BRIDGE(setting)); + g_return_if_fail(vlan); + + priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + nm_bridge_vlan_seal(vlan); + nm_bridge_vlan_ref(vlan); + + g_ptr_array_add(priv->vlans, vlan); + _notify(setting, PROP_VLANS); +} + +/** + * nm_setting_bridge_get_num_vlans: + * @setting: the #NMSettingBridge + * + * Returns: the number of VLANs + * + * Since: 1.18 + **/ +guint +nm_setting_bridge_get_num_vlans(NMSettingBridge *setting) +{ + NMSettingBridgePrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + return priv->vlans->len; +} + +/** + * nm_setting_bridge_get_vlan: + * @setting: the #NMSettingBridge + * @idx: index number of the VLAN to return + * + * Returns: (transfer none): the VLAN at index @idx + * + * Since: 1.18 + **/ +NMBridgeVlan * +nm_setting_bridge_get_vlan(NMSettingBridge *setting, guint idx) +{ + NMSettingBridgePrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), NULL); + priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + g_return_val_if_fail(idx < priv->vlans->len, NULL); + + return priv->vlans->pdata[idx]; +} + +/** + * nm_setting_bridge_remove_vlan: + * @setting: the #NMSettingBridge + * @idx: index number of the VLAN. + * + * Removes the vlan at index @idx. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_remove_vlan(NMSettingBridge *setting, guint idx) +{ + NMSettingBridgePrivate *priv; + + g_return_if_fail(NM_IS_SETTING_BRIDGE(setting)); + priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + g_return_if_fail(idx < priv->vlans->len); + + g_ptr_array_remove_index(priv->vlans, idx); + _notify(setting, PROP_VLANS); +} + +/** + * nm_setting_bridge_remove_vlan_by_vid: + * @setting: the #NMSettingBridge + * @vid_start: the vlan start index + * @vid_end: the vlan end index + * + * Remove the VLAN with range @vid_start to @vid_end. + * If @vid_end is zero, it is assumed to be equal to @vid_start + * and so the single-id VLAN with id @vid_start is removed. + * + * Returns: %TRUE if the vlan was found and removed; %FALSE otherwise + * + * Since: 1.18 + **/ +gboolean +nm_setting_bridge_remove_vlan_by_vid(NMSettingBridge *setting, guint16 vid_start, guint16 vid_end) +{ + NMSettingBridgePrivate *priv; + NMBridgeVlan * vlan; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), FALSE); + priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + if (vid_end == 0) + vid_end = vid_start; + + for (i = 0; i < priv->vlans->len; i++) { + vlan = (NMBridgeVlan *) priv->vlans->pdata[i]; + if (vlan->vid_start == vid_start && vlan->vid_end == vid_end) { + g_ptr_array_remove_index(priv->vlans, i); + _notify(setting, PROP_VLANS); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_bridge_clear_vlans: + * @setting: the #NMSettingBridge + * + * Removes all configured VLANs. + * + * Since: 1.18 + **/ +void +nm_setting_bridge_clear_vlans(NMSettingBridge *setting) +{ + NMSettingBridgePrivate *priv; + + g_return_if_fail(NM_IS_SETTING_BRIDGE(setting)); + priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + if (priv->vlans->len != 0) { + g_ptr_array_set_size(priv->vlans, 0); + _notify(setting, PROP_VLANS); + } +} + +GPtrArray * +_nm_setting_bridge_get_vlans(NMSettingBridge *setting) +{ + nm_assert(NM_IS_SETTING_BRIDGE(setting)); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->vlans; +} + +/*****************************************************************************/ + +/** + * nm_setting_bridge_get_group_address: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:group-address property of the setting + * + * Since 1.24 + **/ +const char * +nm_setting_bridge_get_group_address(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), NULL); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->group_address; +} + +/** + * nm_setting_bridge_get_vlan_protocol: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:vlan-protocol property of the setting + * + * Since 1.24 + **/ +const char * +nm_setting_bridge_get_vlan_protocol(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), NULL); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->vlan_protocol; +} + +/** + * nm_setting_bridge_get_vlan_stats_enabled: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:vlan-stats-enabled property of the setting + * + * Since 1.24 + **/ +gboolean +nm_setting_bridge_get_vlan_stats_enabled(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), FALSE); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->vlan_stats_enabled; +} + +/** + * nm_setting_bridge_get_multicast_router: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-router property of the setting + * + * Since 1.24 + **/ +const char * +nm_setting_bridge_get_multicast_router(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), NULL); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_router; +} + +/** + * nm_setting_bridge_get_multicast_query_use_ifaddr: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-query-use-ifaddr property of the setting + * + * Since 1.24 + **/ +gboolean +nm_setting_bridge_get_multicast_query_use_ifaddr(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), FALSE); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_query_use_ifaddr; +} + +/** + * nm_setting_bridge_get_multicast_querier: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-querier property of the setting + * + * Since 1.24 + **/ +gboolean +nm_setting_bridge_get_multicast_querier(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), FALSE); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_querier; +} + +/** + * nm_setting_bridge_get_multicast_hash_max: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-hash-max property of the setting + * + * Since 1.26 + **/ +guint32 +nm_setting_bridge_get_multicast_hash_max(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_hash_max; +} + +/** + * nm_setting_bridge_get_multicast_last_member_count: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-last-member-count property of the setting + * + * Since 1.26 + **/ +guint32 +nm_setting_bridge_get_multicast_last_member_count(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_last_member_count; +} + +/** + * nm_setting_bridge_get_multicast_last_member_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-last-member-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_last_member_interval(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_last_member_interval; +} + +/** + * nm_setting_bridge_get_multicast_membership_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-membership-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_membership_interval(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_membership_interval; +} + +/** + * nm_setting_bridge_get_multicast_querier_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-querier-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_querier_interval(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_querier_interval; +} + +/** + * nm_setting_bridge_get_multicast_query_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-query-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_query_interval(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_query_interval; +} + +/** + * nm_setting_bridge_get_multicast_query_response_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-query-response-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_query_response_interval(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_query_response_interval; +} + +/** + * nm_setting_bridge_get_multicast_startup_query_count: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-query-response-interval property of the setting + * + * Since 1.26 + **/ +guint32 +nm_setting_bridge_get_multicast_startup_query_count(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_startup_query_count; +} + +/** + * nm_setting_bridge_get_multicast_startup_query_interval: + * @setting: the #NMSettingBridge + * + * Returns: the #NMSettingBridge:multicast-startup-query-interval property of the setting + * + * Since 1.26 + **/ +guint64 +nm_setting_bridge_get_multicast_startup_query_interval(const NMSettingBridge *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_BRIDGE(setting), 0); + + return NM_SETTING_BRIDGE_GET_PRIVATE(setting)->multicast_startup_query_interval; +} + +/*****************************************************************************/ + +static gboolean +check_range(guint32 val, guint32 min, guint32 max, gboolean zero, const char *prop, GError **error) +{ + if (zero && val == 0) + return TRUE; + + if (val < min || val > max) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value '%d' is out of range <%d-%d>"), + val, + min, + max); + g_prefix_error(error, "%s.%s: ", NM_SETTING_BRIDGE_SETTING_NAME, prop); + return FALSE; + } + return TRUE; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + if (priv->mac_address && !nm_utils_hwaddr_valid(priv->mac_address, ETH_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is not a valid MAC address")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_MAC_ADDRESS); + return FALSE; + } + + if (!check_range(priv->forward_delay, + NM_BRIDGE_FORWARD_DELAY_MIN, + NM_BRIDGE_FORWARD_DELAY_MAX, + !priv->stp, + NM_SETTING_BRIDGE_FORWARD_DELAY, + error)) + return FALSE; + + if (!check_range(priv->hello_time, + NM_BRIDGE_HELLO_TIME_MIN, + NM_BRIDGE_HELLO_TIME_MAX, + !priv->stp, + NM_SETTING_BRIDGE_HELLO_TIME, + error)) + return FALSE; + + if (!check_range(priv->max_age, + NM_BRIDGE_MAX_AGE_MIN, + NM_BRIDGE_MAX_AGE_MAX, + !priv->stp, + NM_SETTING_BRIDGE_MAX_AGE, + error)) + return FALSE; + + if (!check_range(priv->ageing_time, + NM_BRIDGE_AGEING_TIME_MIN, + NM_BRIDGE_AGEING_TIME_MAX, + !priv->stp, + NM_SETTING_BRIDGE_AGEING_TIME, + error)) + return FALSE; + + if (priv->group_forward_mask & 7) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the mask can't contain bits 0 (STP), 1 (MAC) or 2 (LACP)")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_GROUP_FORWARD_MASK); + return FALSE; + } + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + if (!_nm_utils_bridge_vlan_verify_list(priv->vlans, + FALSE, + error, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_VLANS)) + return FALSE; + + if (priv->group_address && !_nm_utils_hwaddr_link_local_valid(priv->group_address)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is not a valid link local MAC address")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_GROUP_ADDRESS); + return FALSE; + } + + if (priv->vlan_protocol && !NM_IN_STRSET(priv->vlan_protocol, "802.1Q", "802.1ad")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is not a valid VLAN filtering protocol")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_VLAN_PROTOCOL); + return FALSE; + } + + if (!NM_IN_STRSET(priv->multicast_router, NULL, "auto", "enabled", "disabled")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is not a valid option")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_MULTICAST_ROUTER); + return FALSE; + } + + if (!nm_utils_is_power_of_two(priv->multicast_hash_max)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' option must be a power of 2"), + NM_SETTING_BRIDGE_MULTICAST_HASH_MAX); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_MULTICAST_HASH_MAX); + return FALSE; + } + + /* Failures from here on are NORMALIZABLE... */ + + if (!_nm_utils_bridge_vlan_verify_list(priv->vlans, + TRUE, + error, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_SETTING_BRIDGE_VLANS)) + return NM_SETTING_VERIFY_NORMALIZABLE; + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingBridgePrivate *priv_a; + NMSettingBridgePrivate *priv_b; + guint i; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_BRIDGE_VLANS)) { + if (set_b) { + priv_a = NM_SETTING_BRIDGE_GET_PRIVATE(set_a); + priv_b = NM_SETTING_BRIDGE_GET_PRIVATE(set_b); + + if (priv_a->vlans->len != priv_b->vlans->len) + return FALSE; + for (i = 0; i < priv_a->vlans->len; i++) { + if (nm_bridge_vlan_cmp(priv_a->vlans->pdata[i], priv_b->vlans->pdata[i])) + return FALSE; + } + } + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_bridge_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE(object); + NMSettingBridge * setting = NM_SETTING_BRIDGE(object); + + switch (prop_id) { + case PROP_MAC_ADDRESS: + g_value_set_string(value, nm_setting_bridge_get_mac_address(setting)); + break; + case PROP_STP: + g_value_set_boolean(value, priv->stp); + break; + case PROP_PRIORITY: + g_value_set_uint(value, priv->priority); + break; + case PROP_FORWARD_DELAY: + g_value_set_uint(value, priv->forward_delay); + break; + case PROP_HELLO_TIME: + g_value_set_uint(value, priv->hello_time); + break; + case PROP_MAX_AGE: + g_value_set_uint(value, priv->max_age); + break; + case PROP_AGEING_TIME: + g_value_set_uint(value, priv->ageing_time); + break; + case PROP_GROUP_ADDRESS: + g_value_set_string(value, priv->group_address); + break; + case PROP_GROUP_FORWARD_MASK: + g_value_set_uint(value, priv->group_forward_mask); + break; + case PROP_MULTICAST_HASH_MAX: + g_value_set_uint(value, priv->multicast_hash_max); + break; + case PROP_MULTICAST_LAST_MEMBER_COUNT: + g_value_set_uint(value, priv->multicast_last_member_count); + break; + case PROP_MULTICAST_LAST_MEMBER_INTERVAL: + g_value_set_uint64(value, priv->multicast_last_member_interval); + break; + case PROP_MULTICAST_MEMBERSHIP_INTERVAL: + g_value_set_uint64(value, priv->multicast_membership_interval); + break; + case PROP_MULTICAST_SNOOPING: + g_value_set_boolean(value, priv->multicast_snooping); + break; + case PROP_MULTICAST_ROUTER: + g_value_set_string(value, priv->multicast_router); + break; + case PROP_MULTICAST_QUERIER: + g_value_set_boolean(value, priv->multicast_querier); + break; + case PROP_MULTICAST_QUERIER_INTERVAL: + g_value_set_uint64(value, priv->multicast_querier_interval); + break; + case PROP_MULTICAST_QUERY_INTERVAL: + g_value_set_uint64(value, priv->multicast_query_interval); + break; + case PROP_MULTICAST_QUERY_RESPONSE_INTERVAL: + g_value_set_uint64(value, priv->multicast_query_response_interval); + break; + case PROP_MULTICAST_QUERY_USE_IFADDR: + g_value_set_boolean(value, priv->multicast_query_use_ifaddr); + break; + case PROP_MULTICAST_STARTUP_QUERY_COUNT: + g_value_set_uint(value, priv->multicast_startup_query_count); + break; + case PROP_MULTICAST_STARTUP_QUERY_INTERVAL: + g_value_set_uint64(value, priv->multicast_startup_query_interval); + break; + case PROP_VLAN_FILTERING: + g_value_set_boolean(value, priv->vlan_filtering); + break; + case PROP_VLAN_DEFAULT_PVID: + g_value_set_uint(value, priv->vlan_default_pvid); + break; + case PROP_VLAN_PROTOCOL: + g_value_set_string(value, priv->vlan_protocol); + break; + case PROP_VLAN_STATS_ENABLED: + g_value_set_boolean(value, priv->vlan_stats_enabled); + break; + case PROP_VLANS: + g_value_take_boxed(value, + _nm_utils_copy_array(priv->vlans, + (NMUtilsCopyFunc) nm_bridge_vlan_ref, + (GDestroyNotify) nm_bridge_vlan_unref)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MAC_ADDRESS: + g_free(priv->mac_address); + priv->mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN); + break; + case PROP_STP: + priv->stp = g_value_get_boolean(value); + break; + case PROP_PRIORITY: + priv->priority = (guint16) g_value_get_uint(value); + break; + case PROP_FORWARD_DELAY: + priv->forward_delay = (guint16) g_value_get_uint(value); + break; + case PROP_HELLO_TIME: + priv->hello_time = (guint16) g_value_get_uint(value); + break; + case PROP_MAX_AGE: + priv->max_age = (guint16) g_value_get_uint(value); + break; + case PROP_AGEING_TIME: + priv->ageing_time = g_value_get_uint(value); + break; + case PROP_GROUP_ADDRESS: + g_free(priv->group_address); + priv->group_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN); + break; + case PROP_GROUP_FORWARD_MASK: + priv->group_forward_mask = (guint16) g_value_get_uint(value); + break; + case PROP_MULTICAST_HASH_MAX: + priv->multicast_hash_max = g_value_get_uint(value); + break; + case PROP_MULTICAST_LAST_MEMBER_COUNT: + priv->multicast_last_member_count = g_value_get_uint(value); + break; + case PROP_MULTICAST_LAST_MEMBER_INTERVAL: + priv->multicast_last_member_interval = g_value_get_uint64(value); + break; + case PROP_MULTICAST_MEMBERSHIP_INTERVAL: + priv->multicast_membership_interval = g_value_get_uint64(value); + break; + case PROP_MULTICAST_SNOOPING: + priv->multicast_snooping = g_value_get_boolean(value); + break; + case PROP_MULTICAST_ROUTER: + g_free(priv->multicast_router); + priv->multicast_router = g_value_dup_string(value); + break; + case PROP_MULTICAST_QUERIER: + priv->multicast_querier = g_value_get_boolean(value); + break; + case PROP_MULTICAST_QUERIER_INTERVAL: + priv->multicast_querier_interval = g_value_get_uint64(value); + break; + case PROP_MULTICAST_QUERY_INTERVAL: + priv->multicast_query_interval = g_value_get_uint64(value); + break; + case PROP_MULTICAST_QUERY_RESPONSE_INTERVAL: + priv->multicast_query_response_interval = g_value_get_uint64(value); + break; + case PROP_MULTICAST_QUERY_USE_IFADDR: + priv->multicast_query_use_ifaddr = g_value_get_boolean(value); + break; + case PROP_MULTICAST_STARTUP_QUERY_COUNT: + priv->multicast_startup_query_count = g_value_get_uint(value); + break; + case PROP_MULTICAST_STARTUP_QUERY_INTERVAL: + priv->multicast_startup_query_interval = g_value_get_uint64(value); + break; + case PROP_VLAN_FILTERING: + priv->vlan_filtering = g_value_get_boolean(value); + break; + case PROP_VLAN_DEFAULT_PVID: + priv->vlan_default_pvid = g_value_get_uint(value); + break; + case PROP_VLAN_PROTOCOL: + g_free(priv->vlan_protocol); + priv->vlan_protocol = g_value_dup_string(value); + break; + case PROP_VLAN_STATS_ENABLED: + priv->vlan_stats_enabled = g_value_get_boolean(value); + break; + case PROP_VLANS: + g_ptr_array_unref(priv->vlans); + priv->vlans = _nm_utils_copy_array(g_value_get_boxed(value), + (NMUtilsCopyFunc) _nm_bridge_vlan_dup_and_seal, + (GDestroyNotify) nm_bridge_vlan_unref); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_bridge_init(NMSettingBridge *setting) +{ + NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE(setting); + + priv->vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref); + + priv->ageing_time = NM_BRIDGE_AGEING_TIME_DEF; + priv->forward_delay = NM_BRIDGE_FORWARD_DELAY_DEF; + priv->hello_time = NM_BRIDGE_HELLO_TIME_DEF; + priv->max_age = NM_BRIDGE_MAX_AGE_DEF; + priv->multicast_last_member_count = NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF; + priv->multicast_last_member_interval = NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF; + priv->multicast_membership_interval = NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF; + priv->multicast_hash_max = NM_BRIDGE_MULTICAST_HASH_MAX_DEF; + priv->multicast_snooping = NM_BRIDGE_MULTICAST_SNOOPING_DEF; + priv->priority = NM_BRIDGE_PRIORITY_DEF; + priv->stp = NM_BRIDGE_STP_DEF; + priv->vlan_default_pvid = NM_BRIDGE_VLAN_DEFAULT_PVID_DEF; + priv->multicast_query_interval = NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF; + priv->multicast_query_response_interval = NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF; + priv->multicast_querier_interval = NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF; + priv->multicast_startup_query_count = NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF; + priv->multicast_startup_query_interval = NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF; + + nm_assert(priv->multicast_querier == NM_BRIDGE_MULTICAST_QUERIER_DEF); + nm_assert(priv->multicast_query_use_ifaddr == NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF); + nm_assert(priv->vlan_stats_enabled == NM_BRIDGE_VLAN_STATS_ENABLED_DEF); +} + +/** + * nm_setting_bridge_new: + * + * Creates a new #NMSettingBridge object with default values. + * + * Returns: (transfer full): the new empty #NMSettingBridge object + **/ +NMSetting * +nm_setting_bridge_new(void) +{ + return g_object_new(NM_TYPE_SETTING_BRIDGE, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingBridgePrivate *priv = NM_SETTING_BRIDGE_GET_PRIVATE(object); + + g_free(priv->mac_address); + g_free(priv->multicast_router); + g_free(priv->group_address); + g_free(priv->vlan_protocol); + g_ptr_array_unref(priv->vlans); + + G_OBJECT_CLASS(nm_setting_bridge_parent_class)->finalize(object); +} + +static void +nm_setting_bridge_class_init(NMSettingBridgeClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->compare_property = compare_property; + setting_class->verify = verify; + + /** + * NMSettingBridge:mac-address: + * + * If specified, the MAC address of bridge. When creating a new bridge, this + * MAC address will be set. + * + * If this field is left unspecified, the "ethernet.cloned-mac-address" is + * referred instead to generate the initial MAC address. Note that setting + * "ethernet.cloned-mac-address" anyway overwrites the MAC address of + * the bridge later while activating the bridge. Hence, this property + * is deprecated. + * + * Deprecated: 1.12: Use the ethernet.cloned-mac-address property instead. + **/ + /* ---keyfile--- + * property: mac-address + * format: usual hex-digits-and-colons notation + * description: MAC address in traditional hex-digits-and-colons notation, + * or semicolon separated list of 6 decimal bytes (obsolete) + * example: mac-address=00:22:68:12:79:A2 + * mac-address=0;34;104;18;121;162; + * ---end--- + * ---ifcfg-rh--- + * property: mac-address + * variable: BRIDGE_MACADDR(+) + * description: MAC address of the bridge. Note that this requires a recent + * kernel support, originally introduced in 3.15 upstream kernel) + * BRIDGE_MACADDR for bridges is an NM extension. + * ---end--- + */ + obj_properties[PROP_MAC_ADDRESS] = g_param_spec_string( + NM_SETTING_BRIDGE_MAC_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_MAC_ADDRESS], + &nm_sett_info_propert_type_mac_address); + + /** + * NMSettingBridge:stp: + * + * Controls whether Spanning Tree Protocol (STP) is enabled for this bridge. + **/ + /* ---ifcfg-rh--- + * property: stp + * variable: STP + * default: no + * description: Span tree protocol participation. + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_BRIDGE_STP, + PROP_STP, + NM_BRIDGE_STP_DEF, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_bridge_get_stp); + + /** + * NMSettingBridge:priority: + * + * Sets the Spanning Tree Protocol (STP) priority for this bridge. Lower + * values are "better"; the lowest priority bridge will be elected the root + * bridge. + **/ + /* ---ifcfg-rh--- + * property: priority + * variable: BRIDGING_OPTS: priority= + * values: 0 - 32768 + * default: 32768 + * description: STP priority. + * ---end--- + */ + obj_properties[PROP_PRIORITY] = + g_param_spec_uint(NM_SETTING_BRIDGE_PRIORITY, + "", + "", + NM_BRIDGE_PRIORITY_MIN, + NM_BRIDGE_PRIORITY_MAX, + NM_BRIDGE_PRIORITY_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:forward-delay: + * + * The Spanning Tree Protocol (STP) forwarding delay, in seconds. + **/ + /* ---ifcfg-rh--- + * property: forward-delay + * variable: DELAY + * values: 2 - 30 + * default: 15 + * description: STP forwarding delay. + * ---end--- + */ + obj_properties[PROP_FORWARD_DELAY] = + g_param_spec_uint(NM_SETTING_BRIDGE_FORWARD_DELAY, + "", + "", + 0, + NM_BRIDGE_FORWARD_DELAY_MAX, + NM_BRIDGE_FORWARD_DELAY_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:hello-time: + * + * The Spanning Tree Protocol (STP) hello time, in seconds. + **/ + /* ---ifcfg-rh--- + * property: hello-time + * variable: BRIDGING_OPTS: hello_time= + * values: 1 - 10 + * default: 2 + * description: STP hello time. + * ---end--- + */ + obj_properties[PROP_HELLO_TIME] = + g_param_spec_uint(NM_SETTING_BRIDGE_HELLO_TIME, + "", + "", + 0, + NM_BRIDGE_HELLO_TIME_MAX, + NM_BRIDGE_HELLO_TIME_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:max-age: + * + * The Spanning Tree Protocol (STP) maximum message age, in seconds. + **/ + /* ---ifcfg-rh--- + * property: max-age + * variable: BRIDGING_OPTS: max_age= + * values: 6 - 40 + * default: 20 + * description: STP maximum message age. + * ---end--- + */ + obj_properties[PROP_MAX_AGE] = + g_param_spec_uint(NM_SETTING_BRIDGE_MAX_AGE, + "", + "", + 0, + NM_BRIDGE_MAX_AGE_MAX, + NM_BRIDGE_MAX_AGE_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:ageing-time: + * + * The Ethernet MAC address aging time, in seconds. + **/ + /* ---ifcfg-rh--- + * property: ageing-time + * variable: BRIDGING_OPTS: ageing_time= + * values: 0 - 1000000 + * default: 300 + * description: Ethernet MAC ageing time. + * ---end--- + */ + obj_properties[PROP_AGEING_TIME] = + g_param_spec_uint(NM_SETTING_BRIDGE_AGEING_TIME, + "", + "", + NM_BRIDGE_AGEING_TIME_MIN, + NM_BRIDGE_AGEING_TIME_MAX, + NM_BRIDGE_AGEING_TIME_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:group-forward-mask: + * + * A mask of group addresses to forward. Usually, group addresses in + * the range from 01:80:C2:00:00:00 to 01:80:C2:00:00:0F are not + * forwarded according to standards. This property is a mask of 16 bits, + * each corresponding to a group address in that range that must be + * forwarded. The mask can't have bits 0, 1 or 2 set because they are + * used for STP, MAC pause frames and LACP. + * + * Since: 1.10 + **/ + obj_properties[PROP_GROUP_FORWARD_MASK] = + g_param_spec_uint(NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, + "", + "", + 0, + 0xFFFF, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-snooping: + * + * Controls whether IGMP snooping is enabled for this bridge. + * Note that if snooping was automatically disabled due to hash collisions, + * the system may refuse to enable the feature until the collisions are + * resolved. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: multicast-snooping + * variable: BRIDGING_OPTS: multicast_snooping= + * values: 0 or 1 + * default: 1 + * description: IGMP snooping support. + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_BRIDGE_MULTICAST_SNOOPING, + PROP_MULTICAST_SNOOPING, + NM_BRIDGE_MULTICAST_SNOOPING_DEF, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_bridge_get_multicast_snooping); + + /** + * NMSettingBridge:vlan-filtering: + * + * Control whether VLAN filtering is enabled on the bridge. + * + * Since: 1.18 + **/ + /* ---ifcfg-rh--- + * property: vlan-filtering + * variable: BRIDGING_OPTS: vlan_filtering= + * values: 0 or 1 + * default: 0 + * description: VLAN filtering support. + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_BRIDGE_VLAN_FILTERING, + PROP_VLAN_FILTERING, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_bridge_get_vlan_filtering); + + /** + * NMSettingBridge:vlan-default-pvid: + * + * The default PVID for the ports of the bridge, that is the VLAN id + * assigned to incoming untagged frames. + * + * Since: 1.18 + **/ + /* ---ifcfg-rh--- + * property: vlan-default-pvid + * variable: BRIDGING_OPTS: default_pvid= + * values: 0 - 4094 + * default: 1 + * description: default VLAN PVID. + * ---end--- + */ + obj_properties[PROP_VLAN_DEFAULT_PVID] = + g_param_spec_uint(NM_SETTING_BRIDGE_VLAN_DEFAULT_PVID, + "", + "", + 0, + NM_BRIDGE_VLAN_VID_MAX, + NM_BRIDGE_VLAN_DEFAULT_PVID_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:vlans: (type GPtrArray(NMBridgeVlan)) + * + * Array of bridge VLAN objects. In addition to the VLANs + * specified here, the bridge will also have the default-pvid + * VLAN configured by the bridge.vlan-default-pvid property. + * + * In nmcli the VLAN list can be specified with the following + * syntax: + * + * $vid [pvid] [untagged] [, $vid [pvid] [untagged]]... + * + * where $vid is either a single id between 1 and 4094 or a + * range, represented as a couple of ids separated by a dash. + * + * Since: 1.18 + **/ + /* ---ifcfg-rh--- + * property: vlans + * variable: BRIDGE_VLANS + * description: List of VLANs on the bridge + * example: BRIDGE_VLANS="1 pvid untagged,20,300-400 untagged" + * ---end--- + */ + obj_properties[PROP_VLANS] = g_param_spec_boxed(NM_SETTING_BRIDGE_VLANS, + "", + "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_VLANS], + &nm_sett_info_propert_type_bridge_vlans); + + /* ---dbus--- + * property: interface-name + * format: string + * description: Deprecated in favor of connection.interface-name, but can + * be used for backward-compatibility with older daemons, to set the + * bridge's interface name. + * ---end--- + */ + _nm_properties_override_dbus(properties_override, + "interface-name", + &nm_sett_info_propert_type_deprecated_interface_name); + + /** + * NMSettingBridge:group-address: + * + * If specified, The MAC address of the multicast group this bridge uses for STP. + * + * The address must be a link-local address in standard Ethernet MAC address format, + * ie an address of the form 01:80:C2:00:00:0X, with X in [0, 4..F]. + * If not specified the default value is 01:80:C2:00:00:00. + * + * Since: 1.24 + **/ + /* ---ifcfg-rh--- + * property: group-address + * variable: BRIDGING_OPTS: group_address= + * description: STP group address. + * example: BRIDGING_OPTS="group_address=01:80:C2:00:00:0A" + * ---end--- + */ + obj_properties[PROP_GROUP_ADDRESS] = g_param_spec_string( + NM_SETTING_BRIDGE_GROUP_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_GROUP_ADDRESS], + &nm_sett_info_propert_type_mac_address); + + /** + * NMSettingBridge:vlan-protocol: + * + * If specified, the protocol used for VLAN filtering. + * + * Supported values are: '802.1Q', '802.1ad'. + * If not specified the default value is '802.1Q'. + * + * Since: 1.24 + **/ + /* ---ifcfg-rh--- + * property: vlan-protocol + * variable: BRIDGING_OPTS: vlan_protocol= + * description: VLAN filtering protocol. + * example: BRIDGING_OPTS="vlan_protocol=802.1Q" + * ---end--- + * + * Since: 1.24 + */ + obj_properties[PROP_VLAN_PROTOCOL] = g_param_spec_string( + NM_SETTING_BRIDGE_VLAN_PROTOCOL, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:vlan-stats-enabled: + * + * Controls whether per-VLAN stats accounting is enabled. + **/ + /* ---ifcfg-rh--- + * property: vlan-stats-enabled + * variable: BRIDGING_OPTS: vlan_stats_enabled= + * default: 0 + * example: BRIDGING_OPTS="vlan_stats_enabled=1" + * ---end--- + * + * Since: 1.24 + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_BRIDGE_VLAN_STATS_ENABLED, + PROP_VLAN_STATS_ENABLED, + NM_BRIDGE_VLAN_STATS_ENABLED_DEF, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_bridge_get_vlan_stats_enabled); + + /** + * NMSettingBridge:multicast-router: + * + * Sets bridge's multicast router. Multicast-snooping must be enabled + * for this option to work. + * + * Supported values are: 'auto', 'disabled', 'enabled' to which kernel + * assigns the numbers 1, 0, and 2, respectively. + * If not specified the default value is 'auto' (1). + **/ + /* ---ifcfg-rh--- + * property: multicast-router + * variable: BRIDGING_OPTS: multicast_router= + * values: auto, enabled, disabled + * default: auto + * example: BRIDGING_OPTS="multicast_router=enabled" + * ---end--- + * + * Since: 1.24 + */ + obj_properties[PROP_MULTICAST_ROUTER] = g_param_spec_string( + NM_SETTING_BRIDGE_MULTICAST_ROUTER, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-query-use-ifaddr: + * + * If enabled the bridge's own IP address is used as + * the source address for IGMP queries otherwise + * the default of 0.0.0.0 is used. + **/ + /* ---ifcfg-rh--- + * property: multicast-query-use-ifaddr + * variable: BRIDGING_OPTS: multicast_query_use_ifaddr= + * default: 0 + * example: BRIDGING_OPTS="multicast_query-use_ifaddr=1" + * ---end--- + * + * Since: 1.24 + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_BRIDGE_MULTICAST_QUERY_USE_IFADDR, + PROP_MULTICAST_QUERY_USE_IFADDR, + NM_BRIDGE_MULTICAST_QUERY_USE_IFADDR_DEF, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_bridge_get_multicast_query_use_ifaddr); + + /** + * NMSettingBridge:multicast-querier: + * + * Enable or disable sending of multicast queries by the bridge. + * If not specified the option is disabled. + **/ + /* ---ifcfg-rh--- + * property: multicast-querier + * variable: BRIDGING_OPTS: multicast_querier= + * default: 0 + * example: BRIDGING_OPTS="multicast_querier=1" + * ---end--- + * + * Since: 1.24 + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_BRIDGE_MULTICAST_QUERIER, + PROP_MULTICAST_QUERIER, + NM_BRIDGE_MULTICAST_QUERIER_DEF, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_bridge_get_multicast_querier); + + /** + * NMSettingBridge:multicast-hash-max: + * + * Set maximum size of multicast hash table (value must be a power of 2). + **/ + /* ---ifcfg-rh--- + * property: multicast-hash-max + * variable: BRIDGING_OPTS: multicast_hash_max= + * default: 4096 + * example: BRIDGING_OPTS="multicast_hash_max=8192" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_HASH_MAX] = + g_param_spec_uint(NM_SETTING_BRIDGE_MULTICAST_HASH_MAX, + "", + "", + NM_BRIDGE_MULTICAST_HASH_MAX_MIN, + NM_BRIDGE_MULTICAST_HASH_MAX_MAX, + NM_BRIDGE_MULTICAST_HASH_MAX_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-last-member-count: + * + * Set the number of queries the bridge will send before + * stopping forwarding a multicast group after a "leave" + * message has been received. + **/ + /* ---ifcfg-rh--- + * property: multicast-last-member-count + * variable: BRIDGING_OPTS: multicast_last_member_count= + * default: 2 + * example: BRIDGING_OPTS="multicast_last_member_count=4" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_LAST_MEMBER_COUNT] = + g_param_spec_uint(NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_COUNT, + "", + "", + NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MIN, + NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_MAX, + NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-last-member-interval: + * + * Set interval (in deciseconds) between queries to find remaining + * members of a group, after a "leave" message is received. + **/ + /* ---ifcfg-rh--- + * property: multicast-last-member-interval + * variable: BRIDGING_OPTS: multicast_last_member_interval= + * default: 100 + * example: BRIDGING_OPTS="multicast_last_member_interval=200" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_LAST_MEMBER_INTERVAL] = g_param_spec_uint64( + NM_SETTING_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL, + "", + "", + NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-membership-interval: + * + * Set delay (in deciseconds) after which the bridge will + * leave a group, if no membership reports for this + * group are received. + **/ + /* ---ifcfg-rh--- + * property: multicast-membership-interval + * variable: BRIDGING_OPTS: multicast_membership_interval= + * default: 26000 + * example: BRIDGING_OPTS="multicast_membership_interval=16000" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_MEMBERSHIP_INTERVAL] = g_param_spec_uint64( + NM_SETTING_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL, + "", + "", + NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-querier-interval: + * + * If no queries are seen after this delay (in deciseconds) has passed, + * the bridge will start to send its own queries. + **/ + /* ---ifcfg-rh--- + * property: multicast-querier-interval + * variable: BRIDGING_OPTS: multicast_querier_interval= + * default: 25500 + * example: BRIDGING_OPTS="multicast_querier_interval=20000" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_QUERIER_INTERVAL] = g_param_spec_uint64( + NM_SETTING_BRIDGE_MULTICAST_QUERIER_INTERVAL, + "", + "", + NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-query-interval: + * + * Interval (in deciseconds) between queries sent + * by the bridge after the end of the startup phase. + **/ + /* ---ifcfg-rh--- + * property: multicast-query-interval + * variable: BRIDGING_OPTS: multicast_query_interval= + * default: 12500 + * example: BRIDGING_OPTS="multicast_query_interval=22500" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_QUERY_INTERVAL] = g_param_spec_uint64( + NM_SETTING_BRIDGE_MULTICAST_QUERY_INTERVAL, + "", + "", + NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_QUERY_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-query-response-interval: + * + * Set the Max Response Time/Max Response Delay + * (in deciseconds) for IGMP/MLD queries sent by the bridge. + **/ + /* ---ifcfg-rh--- + * property: multicast-query-response-interval + * variable: BRIDGING_OPTS: multicast_query_response_interval= + * default: 1000 + * example: BRIDGING_OPTS="multicast_query_response_interval=2000" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_QUERY_RESPONSE_INTERVAL] = g_param_spec_uint64( + NM_SETTING_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL, + "", + "", + NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-startup-query-count: + * + * Set the number of IGMP queries to send during startup phase. + **/ + /* ---ifcfg-rh--- + * property: multicast-startup-query-count + * variable: BRIDGING_OPTS: multicast_startup_query_count= + * default: 2 + * example: BRIDGING_OPTS="multicast_startup_query_count=4" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_STARTUP_QUERY_COUNT] = + g_param_spec_uint(NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT, + "", + "", + NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MIN, + NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_MAX, + NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingBridge:multicast-startup-query-interval: + * + * Sets the time (in deciseconds) between queries sent out + * at startup to determine membership information. + **/ + /* ---ifcfg-rh--- + * property: multicast-startup-query-interval + * variable: BRIDGING_OPTS: multicast_startup_query_interval= + * default: 3125 + * example: BRIDGING_OPTS="multicast_startup_query_interval=4000" + * ---end--- + * + * Since: 1.26 + */ + obj_properties[PROP_MULTICAST_STARTUP_QUERY_INTERVAL] = g_param_spec_uint64( + NM_SETTING_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL, + "", + "", + NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MIN, + NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_MAX, + NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_BRIDGE, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-cdma.c b/src/libnm-core-impl/nm-setting-cdma.c new file mode 100644 index 00000000..6dc61e83 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-cdma.c @@ -0,0 +1,374 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-cdma.h" + +#include "nm-utils.h" +#include "nm-setting-private.h" +#include "nm-core-enum-types.h" + +/** + * SECTION:nm-setting-cdma + * @short_description: Describes CDMA-based mobile broadband properties + * + * The #NMSettingCdma object is a #NMSetting subclass that describes + * properties that allow connections to IS-95-based mobile broadband + * networks, including those using CDMA2000/EVDO technology. + */ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_NUMBER, + PROP_USERNAME, + PROP_PASSWORD, + PROP_PASSWORD_FLAGS, + PROP_MTU, ); + +typedef struct { + char * number; /* For dialing, duh */ + char * username; + char * password; + guint32 mtu; + NMSettingSecretFlags password_flags; +} NMSettingCdmaPrivate; + +/** + * NMSettingCdma: + * + * CDMA-based Mobile Broadband Settings + */ +struct _NMSettingCdma { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingCdmaClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingCdma, nm_setting_cdma, NM_TYPE_SETTING) + +#define NM_SETTING_CDMA_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_CDMA, NMSettingCdmaPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_cdma_get_number: + * @setting: the #NMSettingCdma + * + * Returns: the #NMSettingCdma:number property of the setting + **/ +const char * +nm_setting_cdma_get_number(NMSettingCdma *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CDMA(setting), NULL); + + return NM_SETTING_CDMA_GET_PRIVATE(setting)->number; +} + +/** + * nm_setting_cdma_get_username: + * @setting: the #NMSettingCdma + * + * Returns: the #NMSettingCdma:username property of the setting + **/ +const char * +nm_setting_cdma_get_username(NMSettingCdma *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CDMA(setting), NULL); + + return NM_SETTING_CDMA_GET_PRIVATE(setting)->username; +} + +/** + * nm_setting_cdma_get_password: + * @setting: the #NMSettingCdma + * + * Returns: the #NMSettingCdma:password property of the setting + **/ +const char * +nm_setting_cdma_get_password(NMSettingCdma *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CDMA(setting), NULL); + + return NM_SETTING_CDMA_GET_PRIVATE(setting)->password; +} + +/** + * nm_setting_cdma_get_password_flags: + * @setting: the #NMSettingCdma + * + * Returns: the #NMSettingSecretFlags pertaining to the #NMSettingCdma:password + **/ +NMSettingSecretFlags +nm_setting_cdma_get_password_flags(NMSettingCdma *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CDMA(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_CDMA_GET_PRIVATE(setting)->password_flags; +} + +/** + * nm_setting_cdma_get_mtu: + * @setting: the #NMSettingCdma + * + * Returns: the #NMSettingCdma:mtu property of the setting + * + * Since: 1.8 + **/ +guint32 +nm_setting_cdma_get_mtu(NMSettingCdma *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CDMA(setting), 0); + + return NM_SETTING_CDMA_GET_PRIVATE(setting)->mtu; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingCdmaPrivate *priv = NM_SETTING_CDMA_GET_PRIVATE(setting); + + if (!priv->number) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_CDMA_SETTING_NAME, NM_SETTING_CDMA_NUMBER); + return FALSE; + } else if (!strlen(priv->number)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_CDMA_SETTING_NAME, NM_SETTING_CDMA_NUMBER); + return FALSE; + } + + if (priv->username && !strlen(priv->username)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_CDMA_SETTING_NAME, NM_SETTING_CDMA_USERNAME); + return FALSE; + } + + return TRUE; +} + +static gboolean +verify_secrets(NMSetting *setting, NMConnection *connection, GError **error) +{ + return _nm_setting_verify_secret_string(NM_SETTING_CDMA_GET_PRIVATE(setting)->password, + NM_SETTING_CDMA_SETTING_NAME, + NM_SETTING_CDMA_PASSWORD, + error); +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSettingCdmaPrivate *priv = NM_SETTING_CDMA_GET_PRIVATE(setting); + GPtrArray * secrets = NULL; + + if (priv->password && *priv->password) + return NULL; + + if (priv->username) { + if (!(priv->password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + secrets = g_ptr_array_sized_new(1); + g_ptr_array_add(secrets, NM_SETTING_CDMA_PASSWORD); + } + } + + return secrets; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingCdma *setting = NM_SETTING_CDMA(object); + + switch (prop_id) { + case PROP_NUMBER: + g_value_set_string(value, nm_setting_cdma_get_number(setting)); + break; + case PROP_USERNAME: + g_value_set_string(value, nm_setting_cdma_get_username(setting)); + break; + case PROP_PASSWORD: + g_value_set_string(value, nm_setting_cdma_get_password(setting)); + break; + case PROP_PASSWORD_FLAGS: + g_value_set_flags(value, nm_setting_cdma_get_password_flags(setting)); + break; + case PROP_MTU: + g_value_set_uint(value, nm_setting_cdma_get_mtu(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingCdmaPrivate *priv = NM_SETTING_CDMA_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_NUMBER: + g_free(priv->number); + priv->number = g_value_dup_string(value); + break; + case PROP_USERNAME: + g_free(priv->username); + priv->username = g_value_dup_string(value); + break; + case PROP_PASSWORD: + g_free(priv->password); + priv->password = g_value_dup_string(value); + break; + case PROP_PASSWORD_FLAGS: + priv->password_flags = g_value_get_flags(value); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_cdma_init(NMSettingCdma *setting) +{} + +/** + * nm_setting_cdma_new: + * + * Creates a new #NMSettingCdma object with default values. + * + * Returns: the new empty #NMSettingCdma object + **/ +NMSetting * +nm_setting_cdma_new(void) +{ + return g_object_new(NM_TYPE_SETTING_CDMA, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingCdmaPrivate *priv = NM_SETTING_CDMA_GET_PRIVATE(object); + + g_free(priv->number); + g_free(priv->username); + g_free(priv->password); + + G_OBJECT_CLASS(nm_setting_cdma_parent_class)->finalize(object); +} + +static void +nm_setting_cdma_class_init(NMSettingCdmaClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NMSettingCdmaPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->verify_secrets = verify_secrets; + setting_class->need_secrets = need_secrets; + + /** + * NMSettingCdma:number: + * + * The number to dial to establish the connection to the CDMA-based mobile + * broadband network, if any. If not specified, the default number (#777) + * is used when required. + **/ + obj_properties[PROP_NUMBER] = g_param_spec_string(NM_SETTING_CDMA_NUMBER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingCdma:username: + * + * The username used to authenticate with the network, if required. Many + * providers do not require a username, or accept any username. But if a + * username is required, it is specified here. + **/ + obj_properties[PROP_USERNAME] = g_param_spec_string(NM_SETTING_CDMA_USERNAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingCdma:password: + * + * The password used to authenticate with the network, if required. Many + * providers do not require a password, or accept any password. But if a + * password is required, it is specified here. + **/ + obj_properties[PROP_PASSWORD] = + g_param_spec_string(NM_SETTING_CDMA_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingCdma:password-flags: + * + * Flags indicating how to handle the #NMSettingCdma:password property. + **/ + obj_properties[PROP_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_CDMA_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingCdma:mtu: + * + * If non-zero, only transmit packets of the specified size or smaller, + * breaking larger packets up into multiple frames. + * + * Since: 1.8 + **/ + obj_properties[PROP_MTU] = g_param_spec_uint(NM_SETTING_CDMA_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_CDMA); +} diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c new file mode 100644 index 00000000..3d57bc3e --- /dev/null +++ b/src/libnm-core-impl/nm-setting-connection.c @@ -0,0 +1,2568 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-connection.h" + +#include "libnm-glib-aux/nm-uuid.h" +#include "libnm-core-aux-intern/nm-common-macros.h" +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-core-enum-types.h" +#include "nm-connection-private.h" +#include "nm-setting-bond.h" +#include "nm-setting-bridge.h" +#include "nm-setting-team.h" +#include "nm-setting-vlan.h" +#include "libnm-systemd-shared/nm-sd-utils-shared.h" + +/** + * SECTION:nm-setting-connection + * @short_description: Describes general connection properties + * + * The #NMSettingConnection object is a #NMSetting subclass that describes + * properties that apply to all #NMConnection objects, regardless of what type + * of network connection they describe. Each #NMConnection object must contain + * a #NMSettingConnection setting. + **/ + +/*****************************************************************************/ + +typedef enum _nm_packed { + PERM_TYPE_INVALID, + PERM_TYPE_USER, +} PermType; + +typedef struct { + PermType ptype; + char * item; +} Permission; + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingConnection, + PROP_ID, + PROP_UUID, + PROP_INTERFACE_NAME, + PROP_TYPE, + PROP_PERMISSIONS, + PROP_AUTOCONNECT, + PROP_AUTOCONNECT_PRIORITY, + PROP_AUTOCONNECT_RETRIES, + PROP_MULTI_CONNECT, + PROP_TIMESTAMP, + PROP_READ_ONLY, + PROP_ZONE, + PROP_MASTER, + PROP_SLAVE_TYPE, + PROP_AUTOCONNECT_SLAVES, + PROP_SECONDARIES, + PROP_GATEWAY_PING_TIMEOUT, + PROP_METERED, + PROP_LLDP, + PROP_MDNS, + PROP_LLMNR, + PROP_STABLE_ID, + PROP_AUTH_RETRIES, + PROP_WAIT_DEVICE_TIMEOUT, + PROP_MUD_URL, ); + +typedef struct { + GArray * permissions; + GArray * secondaries; + char * id; + char * uuid; + char * stable_id; + char * interface_name; + char * type; + char * master; + char * slave_type; + char * zone; + char * mud_url; + guint64 timestamp; + int autoconnect_priority; + int autoconnect_retries; + int multi_connect; + int auth_retries; + int mdns; + int llmnr; + int wait_device_timeout; + guint gateway_ping_timeout; + NMSettingConnectionAutoconnectSlaves autoconnect_slaves; + NMMetered metered; + NMSettingConnectionLldp lldp; + bool read_only : 1; + bool autoconnect : 1; +} NMSettingConnectionPrivate; + +/** + * NMSettingConnection: + * + * General Connection Profile Settings + */ +struct _NMSettingConnection { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingConnectionClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingConnection, nm_setting_connection, NM_TYPE_SETTING) + +#define NM_SETTING_CONNECTION_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_CONNECTION, NMSettingConnectionPrivate)) + +/*****************************************************************************/ + +static void +_permission_set_stale(Permission *permission, PermType ptype, char *item_take) +{ + nm_assert(permission); + nm_assert(NM_IN_SET(ptype, PERM_TYPE_INVALID, PERM_TYPE_USER)); + nm_assert(ptype != PERM_TYPE_USER + || nm_settings_connection_validate_permission_user(item_take, -1)); + + /* we don't inspect (clear) permission before setting. It takes a + * stale instance. */ + *permission = (Permission){ + .ptype = ptype, + .item = item_take, + }; +} + +static void +_permission_clear_stale(Permission *permission) +{ + g_free(permission->item); + /* We leave the permission instance with dangling pointers. + * It is "stale". */ +} + +static gboolean +_permission_set_stale_parse(Permission *permission, const char *str) +{ + const char *str0 = str; + const char *last_colon; + gsize ulen; + + nm_assert(str); + + if (!str) + goto invalid; + + if (!NM_STR_HAS_PREFIX(str, NM_SETTINGS_CONNECTION_PERMISSION_USER_PREFIX)) + goto invalid; + + str += NM_STRLEN(NM_SETTINGS_CONNECTION_PERMISSION_USER_PREFIX); + + last_colon = strrchr(str, ':'); + if (last_colon) { + /* Reject :[detail] for now */ + if (last_colon[1] != '\0') + goto invalid; + ulen = last_colon - str; + } else + ulen = strlen(str); + + if (!nm_settings_connection_validate_permission_user(str, ulen)) + goto invalid; + + /* Yay, valid... create the new permission */ + if (permission) + _permission_set_stale(permission, PERM_TYPE_USER, g_strndup(str, ulen)); + return TRUE; + +invalid: + if (permission) + _permission_set_stale(permission, PERM_TYPE_INVALID, g_strdup(str0)); + return FALSE; +} + +static char * +_permission_to_string(Permission *permission) +{ + nm_assert(permission); + + switch (permission->ptype) { + case PERM_TYPE_USER: + return g_strdup_printf(NM_SETTINGS_CONNECTION_PERMISSION_USER_PREFIX "%s:", + permission->item); + case PERM_TYPE_INVALID: + nm_assert(permission->item); + return g_strdup(permission->item); + } + nm_assert_not_reached(); + return g_strdup(permission->item ?: ""); +} + +/*****************************************************************************/ + +/** + * nm_setting_connection_get_id: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:id property of the connection. + * + * Returns: the connection ID + **/ +const char * +nm_setting_connection_get_id(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->id; +} + +/** + * nm_setting_connection_get_uuid: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:uuid property of the connection. + * + * Returns: the connection UUID + **/ +const char * +nm_setting_connection_get_uuid(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->uuid; +} + +/** + * nm_setting_connection_get_stable_id: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:stable_id property of the connection. + * + * Returns: the stable-id for the connection + * + * Since: 1.4 + **/ +const char * +nm_setting_connection_get_stable_id(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->stable_id; +} + +/** + * nm_setting_connection_get_interface_name: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:interface-name property of the connection. + * + * Returns: the connection's interface name + **/ +const char * +nm_setting_connection_get_interface_name(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->interface_name; +} + +/** + * nm_setting_connection_get_connection_type: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:type property of the connection. + * + * Returns: the connection type + **/ +const char * +nm_setting_connection_get_connection_type(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->type; +} + +/** + * nm_setting_connection_get_num_permissions: + * @setting: the #NMSettingConnection + * + * Returns the number of entries in the #NMSettingConnection:permissions + * property of this setting. + * + * Returns: the number of permissions entries + */ +guint32 +nm_setting_connection_get_num_permissions(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), 0); + + return nm_g_array_len(NM_SETTING_CONNECTION_GET_PRIVATE(setting)->permissions); +} + +/** + * nm_setting_connection_get_permission: + * @setting: the #NMSettingConnection + * @idx: the zero-based index of the permissions entry + * @out_ptype: on return, the permission type. This is currently always "user", + * unless the entry is invalid, in which case it returns "invalid". + * @out_pitem: on return, the permission item (formatted according to @ptype, see + * #NMSettingConnection:permissions for more detail + * @out_detail: on return, the permission detail (at this time, always %NULL) + * + * Retrieve one of the entries of the #NMSettingConnection:permissions property + * of this setting. + * + * Returns: %TRUE if a permission was returned, %FALSE if @idx was invalid + */ +gboolean +nm_setting_connection_get_permission(NMSettingConnection *setting, + guint32 idx, + const char ** out_ptype, + const char ** out_pitem, + const char ** out_detail) +{ + NMSettingConnectionPrivate *priv; + Permission * permission; + + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE); + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + g_return_val_if_fail(idx < nm_g_array_len(priv->permissions), FALSE); + + permission = &g_array_index(priv->permissions, Permission, idx); + switch (permission->ptype) { + case PERM_TYPE_USER: + NM_SET_OUT(out_ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER); + NM_SET_OUT(out_pitem, permission->item); + NM_SET_OUT(out_detail, NULL); + return TRUE; + case PERM_TYPE_INVALID: + goto invalid; + } + nm_assert_not_reached(); +invalid: + NM_SET_OUT(out_ptype, "invalid"); + NM_SET_OUT(out_pitem, permission->item); + NM_SET_OUT(out_detail, NULL); + return TRUE; +} + +/** + * nm_setting_connection_permissions_user_allowed: + * @setting: the #NMSettingConnection + * @uname: the user name to check permissions for + * + * Checks whether the given username is allowed to view/access this connection. + * + * Returns: %TRUE if the requested user is allowed to view this connection, + * %FALSE if the given user is not allowed to view this connection + */ +gboolean +nm_setting_connection_permissions_user_allowed(NMSettingConnection *setting, const char *uname) +{ + NMSettingConnectionPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE); + g_return_val_if_fail(uname != NULL, FALSE); + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + if (nm_g_array_len(priv->permissions) == 0) { + /* If no permissions, visible to all */ + return TRUE; + } + + for (i = 0; i < priv->permissions->len; i++) { + const Permission *permission = &g_array_index(priv->permissions, Permission, i); + + if (permission->ptype == PERM_TYPE_USER && nm_streq(permission->item, uname)) + return TRUE; + } + + return FALSE; +} + +/** + * nm_setting_connection_add_permission: + * @setting: the #NMSettingConnection + * @ptype: the permission type; at this time only "user" is supported + * @pitem: the permission item formatted as required for @ptype + * @detail: (allow-none): unused at this time; must be %NULL + * + * Adds a permission to the connection's permission list. At this time, only + * the "user" permission type is supported, and @pitem must be a username. See + * #NMSettingConnection:permissions: for more details. + * + * Returns: %TRUE if the permission was unique and was successfully added to the + * list, %FALSE if @ptype or @pitem was invalid. + * If the permission was already present in the list, it will not be added + * a second time but %TRUE will be returned. Note that before 1.28, in this + * case %FALSE would be returned. + */ +gboolean +nm_setting_connection_add_permission(NMSettingConnection *setting, + const char * ptype, + const char * pitem, + const char * detail) +{ + NMSettingConnectionPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE); + g_return_val_if_fail(ptype, FALSE); + g_return_val_if_fail(pitem, FALSE); + + if (!nm_streq0(ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER)) + return FALSE; + + if (!nm_settings_connection_validate_permission_user(pitem, -1)) + return FALSE; + + if (detail) + return FALSE; + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + if (!priv->permissions) { + priv->permissions = g_array_sized_new(FALSE, FALSE, sizeof(Permission), 1); + g_array_set_clear_func(priv->permissions, (GDestroyNotify) _permission_clear_stale); + } + + for (i = 0; i < priv->permissions->len; i++) { + const Permission *permission = &g_array_index(priv->permissions, Permission, i); + + if (permission->ptype == PERM_TYPE_USER && nm_streq(permission->item, pitem)) + return TRUE; + } + + _permission_set_stale(nm_g_array_append_new(priv->permissions, Permission), + PERM_TYPE_USER, + g_strdup(pitem)); + _notify(setting, PROP_PERMISSIONS); + return TRUE; +} + +/** + * nm_setting_connection_remove_permission: + * @setting: the #NMSettingConnection + * @idx: the zero-based index of the permission to remove + * + * Removes the permission at index @idx from the connection. + */ +void +nm_setting_connection_remove_permission(NMSettingConnection *setting, guint32 idx) +{ + NMSettingConnectionPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_CONNECTION(setting)); + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + g_return_if_fail(idx < nm_g_array_len(priv->permissions)); + + g_array_remove_index(priv->permissions, idx); + + _notify(setting, PROP_PERMISSIONS); +} + +/** + * nm_setting_connection_remove_permission_by_value: + * @setting: the #NMSettingConnection + * @ptype: the permission type; at this time only "user" is supported + * @pitem: the permission item formatted as required for @ptype + * @detail: (allow-none): unused at this time; must be %NULL + * + * Removes the permission from the connection. + * At this time, only the "user" permission type is supported, and @pitem must + * be a username. See #NMSettingConnection:permissions: for more details. + * + * Returns: %TRUE if the permission was found and removed; %FALSE if it was not. + */ +gboolean +nm_setting_connection_remove_permission_by_value(NMSettingConnection *setting, + const char * ptype, + const char * pitem, + const char * detail) +{ + NMSettingConnectionPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE); + g_return_val_if_fail(ptype, FALSE); + g_return_val_if_fail(pitem, FALSE); + + if (!nm_streq0(ptype, NM_SETTINGS_CONNECTION_PERMISSION_USER)) + return FALSE; + + if (detail) + return FALSE; + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + if (priv->permissions) { + for (i = 0; i < priv->permissions->len; i++) { + const Permission *permission = &g_array_index(priv->permissions, Permission, i); + + if (permission->ptype == PERM_TYPE_USER && nm_streq(permission->item, pitem)) { + g_array_remove_index(priv->permissions, i); + _notify(setting, PROP_PERMISSIONS); + return TRUE; + } + } + } + return FALSE; +} + +/** + * nm_setting_connection_get_autoconnect: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:autoconnect property of the connection. + * + * Returns: the connection's autoconnect behavior + **/ +gboolean +nm_setting_connection_get_autoconnect(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->autoconnect; +} + +/** + * nm_setting_connection_get_autoconnect_priority: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:autoconnect-priority property of the connection. + * The higher number, the higher priority. + * + * Returns: the connection's autoconnect priority + **/ +int +nm_setting_connection_get_autoconnect_priority(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), 0); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->autoconnect_priority; +} + +/** + * nm_setting_connection_get_autoconnect_retries: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:autoconnect-retries property of the connection. + * Zero means infinite, -1 means the global default value. + * + * Returns: the connection's autoconnect retries + * + * Since: 1.6 + **/ +int +nm_setting_connection_get_autoconnect_retries(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), -1); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->autoconnect_retries; +} + +/** + * nm_setting_connection_get_multi_connect: + * @setting: the #NMSettingConnection + * + * Returns: the #NMSettingConnection:multi-connect property of the connection. + * + * Since: 1.14 + **/ +NMConnectionMultiConnect +nm_setting_connection_get_multi_connect(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), -1); + + return (NMConnectionMultiConnect) NM_SETTING_CONNECTION_GET_PRIVATE(setting)->multi_connect; +} + +/** + * nm_setting_connection_get_auth_retries: + * @setting: the #NMSettingConnection + * + * Returns the value contained in the #NMSettingConnection:auth-retries property. + * + * Returns: the configured authentication retries. Zero means + * infinity and -1 means a global default value. + * + * Since: 1.10 + **/ +int +nm_setting_connection_get_auth_retries(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), -1); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->auth_retries; +} + +/** + * nm_setting_connection_get_timestamp: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:timestamp property of the connection. + * + * Returns: the connection's timestamp + **/ +guint64 +nm_setting_connection_get_timestamp(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), 0); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->timestamp; +} + +static GVariant * +_to_dbus_fcn_timestamp(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + guint64 v; + + v = options && options->timestamp.has ? options->timestamp.val + : NM_SETTING_CONNECTION_GET_PRIVATE(setting)->timestamp; + + if (v == 0u) + return NULL; + + return g_variant_new_uint64(v); +} + +/** + * nm_setting_connection_get_read_only: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:read-only property of the connection. + * + * Returns: %TRUE if the connection is read-only, %FALSE if it is not + **/ +gboolean +nm_setting_connection_get_read_only(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), TRUE); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->read_only; +} + +/** + * nm_setting_connection_get_zone: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:zone property of the connection. + * + * Returns: the trust level of a connection + **/ +const char * +nm_setting_connection_get_zone(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->zone; +} + +/** + * nm_setting_connection_get_master: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:master property of the connection. + * + * Returns: interface name of the master device or UUID of the master + * connection. + */ +const char * +nm_setting_connection_get_master(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->master; +} + +/** + * nm_setting_connection_get_slave_type: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:slave-type property of the connection. + * + * Returns: the type of slave this connection is, if any + */ +const char * +nm_setting_connection_get_slave_type(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->slave_type; +} + +/** + * nm_setting_connection_is_slave_type: + * @setting: the #NMSettingConnection + * @type: the setting name (ie #NM_SETTING_BOND_SETTING_NAME) to be matched + * against @setting's slave type + * + * Returns: %TRUE if connection is of the given slave @type + */ +gboolean +nm_setting_connection_is_slave_type(NMSettingConnection *setting, const char *type) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE); + + return !g_strcmp0(NM_SETTING_CONNECTION_GET_PRIVATE(setting)->slave_type, type); +} + +/** + * nm_setting_connection_get_wait_device_timeout: + * @setting: the #NMSettingConnection + * + * Returns: the %NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT property with + * the timeout in milliseconds. -1 is the default. + * + * Since: 1.20 + */ +gint32 +nm_setting_connection_get_wait_device_timeout(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), -1); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->wait_device_timeout; +} + +/** + * nm_setting_connection_get_autoconnect_slaves: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:autoconnect-slaves property of the connection. + * + * Returns: whether slaves of the connection should be activated together + * with the connection. + * + * Since: 1.2 + **/ +NMSettingConnectionAutoconnectSlaves +nm_setting_connection_get_autoconnect_slaves(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), + NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->autoconnect_slaves; +} + +GArray * +_nm_setting_connection_get_secondaries(NMSettingConnection *setting) +{ + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->secondaries; +} + +/** + * nm_setting_connection_get_num_secondaries: + * @setting: the #NMSettingConnection + * + * Returns: the number of configured secondary connection UUIDs + **/ +guint32 +nm_setting_connection_get_num_secondaries(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), 0); + + return nm_g_array_len(NM_SETTING_CONNECTION_GET_PRIVATE(setting)->secondaries); +} + +/** + * nm_setting_connection_get_secondary: + * @setting: the #NMSettingConnection + * @idx: the zero-based index of the secondary connection UUID entry. + * Access one past the length of secondaries is ok and will return + * %NULL. Otherwise, it is a user error. + * + * Returns: the secondary connection UUID at index @idx or + * %NULL if @idx is the number of secondaries. + **/ +const char * +nm_setting_connection_get_secondary(NMSettingConnection *setting, guint32 idx) +{ + NMSettingConnectionPrivate *priv; + guint secondaries_len; + + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + secondaries_len = nm_g_array_len(priv->secondaries); + if (idx >= secondaries_len) { + /* access one past the length is OK. */ + g_return_val_if_fail(idx == secondaries_len, NULL); + return NULL; + } + + return nm_strvarray_get_idx(priv->secondaries, idx); +} + +/** + * nm_setting_connection_get_mud_url: + * @setting: the #NMSettingConnection + * + * Returns the value contained in the #NMSettingConnection:mud-url + * property. + * + * Since: 1.26 + **/ +const char * +nm_setting_connection_get_mud_url(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->mud_url; +} + +/** + * nm_setting_connection_add_secondary: + * @setting: the #NMSettingConnection + * @sec_uuid: the secondary connection UUID to add + * + * Adds a new secondary connection UUID to the setting. + * + * Returns: %TRUE if the secondary connection UUID was added; %FALSE if the UUID + * was already present + **/ +gboolean +nm_setting_connection_add_secondary(NMSettingConnection *setting, const char *sec_uuid) +{ + NMSettingConnectionPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE); + g_return_val_if_fail(sec_uuid, FALSE); + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + if (nm_strvarray_find_first(priv->secondaries, sec_uuid) >= 0) + return FALSE; + + nm_strvarray_add(nm_strvarray_ensure(&priv->secondaries), sec_uuid); + _notify(setting, PROP_SECONDARIES); + return TRUE; +} + +/** + * nm_setting_connection_remove_secondary: + * @setting: the #NMSettingConnection + * @idx: index number of the secondary connection UUID + * + * Removes the secondary connection UUID at index @idx. + **/ +void +nm_setting_connection_remove_secondary(NMSettingConnection *setting, guint32 idx) +{ + NMSettingConnectionPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_CONNECTION(setting)); + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + g_return_if_fail(idx < nm_g_array_len(priv->secondaries)); + + g_array_remove_index(priv->secondaries, idx); + _notify(setting, PROP_SECONDARIES); +} + +/** + * nm_setting_connection_remove_secondary_by_value: + * @setting: the #NMSettingConnection + * @sec_uuid: the secondary connection UUID to remove + * + * Removes the secondary connection UUID @sec_uuid. + * + * Returns: %TRUE if the secondary connection UUID was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_connection_remove_secondary_by_value(NMSettingConnection *setting, const char *sec_uuid) +{ + NMSettingConnectionPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE); + g_return_val_if_fail(sec_uuid, FALSE); + + priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + if (nm_strvarray_remove_first(priv->secondaries, sec_uuid)) { + _notify(setting, PROP_SECONDARIES); + return TRUE; + } + return FALSE; +} + +/** + * nm_setting_connection_get_gateway_ping_timeout: + * @setting: the #NMSettingConnection + * + * Returns: the value contained in the #NMSettingConnection:gateway-ping-timeout + * property. + **/ +guint32 +nm_setting_connection_get_gateway_ping_timeout(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), 0); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->gateway_ping_timeout; +} + +/** + * nm_setting_connection_get_metered: + * @setting: the #NMSettingConnection + * + * Returns: the #NMSettingConnection:metered property of the setting. + * + * Since: 1.2 + **/ +NMMetered +nm_setting_connection_get_metered(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NM_METERED_UNKNOWN); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->metered; +} + +/** + * nm_setting_connection_get_lldp: + * @setting: the #NMSettingConnection + * + * Returns the #NMSettingConnection:lldp property of the connection. + * + * Returns: a %NMSettingConnectionLldp which indicates whether LLDP must be + * enabled for the connection. + * + * Since: 1.2 + **/ +NMSettingConnectionLldp +nm_setting_connection_get_lldp(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NM_SETTING_CONNECTION_LLDP_DEFAULT); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->lldp; +} + +/** + * nm_setting_connection_get_mdns: + * @setting: the #NMSettingConnection + * + * Returns: the #NMSettingConnection:mdns property of the setting. + * + * Since: 1.12 + **/ +NMSettingConnectionMdns +nm_setting_connection_get_mdns(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NM_SETTING_CONNECTION_MDNS_DEFAULT); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->mdns; +} + +/** + * nm_setting_connection_get_llmnr: + * @setting: the #NMSettingConnection + * + * Returns: the #NMSettingConnection:llmnr property of the setting. + * + * Since: 1.14 + **/ +NMSettingConnectionLlmnr +nm_setting_connection_get_llmnr(NMSettingConnection *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NM_SETTING_CONNECTION_LLMNR_DEFAULT); + + return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->llmnr; +} + +static void +_set_error_missing_base_setting(GError **error, const char *type) +{ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("setting required for connection of type '%s'"), + type); + g_prefix_error(error, "%s: ", type); +} + +gboolean +_nm_connection_detect_slave_type_full(NMSettingConnection *s_con, + NMConnection * connection, + const char ** out_slave_type, + const char ** out_normerr_slave_setting_type, + const char ** out_normerr_missing_slave_type, + const char ** out_normerr_missing_slave_type_port, + GError ** error) +{ + NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE(s_con); + gboolean is_slave; + const char * slave_setting_type; + const char * slave_type; + const char * normerr_slave_setting_type = NULL; + const char * normerr_missing_slave_type = NULL; + const char * normerr_missing_slave_type_port = NULL; + + is_slave = FALSE; + slave_setting_type = NULL; + slave_type = priv->slave_type; + if (slave_type) { + is_slave = _nm_setting_slave_type_is_valid(slave_type, &slave_setting_type); + if (!is_slave) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Unknown slave type '%s'"), + slave_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return FALSE; + } + } + + if (is_slave) { + if (!priv->master) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("Slave connections need a valid '%s' property"), + NM_SETTING_CONNECTION_MASTER); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER); + return FALSE; + } + if (slave_setting_type && connection + && !nm_connection_get_setting_by_name(connection, slave_setting_type)) + normerr_slave_setting_type = slave_setting_type; + } else { + nm_assert(!slave_type); + if (priv->master) { + NMSetting *s_port; + + if (connection + && (slave_type = _nm_connection_detect_slave_type(connection, &s_port))) { + normerr_missing_slave_type = slave_type; + normerr_missing_slave_type_port = nm_setting_get_name(s_port); + } else { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("Cannot set '%s' without '%s'"), + NM_SETTING_CONNECTION_MASTER, + NM_SETTING_CONNECTION_SLAVE_TYPE); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return FALSE; + } + } + } + + NM_SET_OUT(out_slave_type, slave_type); + NM_SET_OUT(out_normerr_slave_setting_type, normerr_slave_setting_type); + NM_SET_OUT(out_normerr_missing_slave_type, normerr_missing_slave_type); + NM_SET_OUT(out_normerr_missing_slave_type_port, normerr_missing_slave_type_port); + return TRUE; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingConnection * self = NM_SETTING_CONNECTION(setting); + NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE(self); + NMSetting * normerr_base_type = NULL; + const char * type; + const char * slave_type; + const char * normerr_slave_setting_type = NULL; + const char * normerr_missing_slave_type = NULL; + const char * normerr_missing_slave_type_port = NULL; + gboolean normerr_base_setting = FALSE; + gboolean uuid_was_normalized = FALSE; + + if (!priv->id) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_ID); + return FALSE; + } else if (!priv->id[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_ID); + return FALSE; + } + + if (priv->uuid && !nm_uuid_is_valid_nm(priv->uuid, &uuid_was_normalized, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid UUID"), + priv->uuid); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_UUID); + return FALSE; + } + + type = priv->type; + if (!type) { + if (!connection + || !(normerr_base_type = _nm_connection_find_base_type_setting(connection))) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE); + return FALSE; + } + type = nm_setting_get_name(normerr_base_type); + } else { + GType base_type; + + if (!type[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE); + return FALSE; + } + + base_type = nm_setting_lookup_type(type); + if (base_type == G_TYPE_INVALID + || _nm_setting_type_get_base_type_priority(base_type) == NM_SETTING_PRIORITY_INVALID) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("connection type '%s' is not valid"), + type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE); + return FALSE; + } + + /* Make sure the corresponding 'type' item is present */ + if (connection && !nm_connection_get_setting_by_name(connection, type)) { + NMSetting * s_base; + NMConnection *connection2; + + s_base = g_object_new(base_type, NULL); + connection2 = nm_simple_connection_new_clone(connection); + nm_connection_add_setting(connection2, s_base); + + normerr_base_setting = nm_setting_verify(s_base, connection2, NULL); + + g_object_unref(connection2); + + if (!normerr_base_setting) { + _set_error_missing_base_setting(error, type); + return FALSE; + } + } + } + + if (priv->interface_name) { + GError * tmp_error = NULL; + NMUtilsIfaceType iface_type; + + if (NM_IN_STRSET(type, + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + NM_SETTING_OVS_PORT_SETTING_NAME)) + iface_type = NMU_IFACE_OVS; + else if (nm_streq(type, NM_SETTING_OVS_INTERFACE_SETTING_NAME)) { + NMSettingOvsInterface *s_ovs_iface = NULL; + const char * ovs_iface_type; + + if (connection) + s_ovs_iface = nm_connection_get_setting_ovs_interface(connection); + _nm_setting_ovs_interface_verify_interface_type( + s_ovs_iface, + s_ovs_iface ? nm_setting_ovs_interface_get_interface_type(s_ovs_iface) : NULL, + connection, + FALSE, + NULL, + &ovs_iface_type, + NULL); + if (!ovs_iface_type) { + /* We cannot determine to OVS interface type. Consequently, we cannot + * fully validate the interface name. + * + * If we have a connection (and we do a full validation anyway), skip the + * check. The connection will fail validation when we validate the OVS setting. + * + * Otherwise, do the most basic validation. + */ + if (connection) + goto after_interface_name; + iface_type = NMU_IFACE_ANY; + } else if (NM_IN_STRSET(ovs_iface_type, "patch")) { + /* this interface type is internal to OVS. */ + iface_type = NMU_IFACE_OVS; + } else { + /* This interface type also requires a netdev. We need to validate + * for both OVS and KERNEL. */ + nm_assert(NM_IN_STRSET(ovs_iface_type, "internal", "system", "dpdk")); + iface_type = NMU_IFACE_OVS_AND_KERNEL; + } + } else + iface_type = NMU_IFACE_KERNEL; + + if (!nm_utils_ifname_valid(priv->interface_name, iface_type, &tmp_error)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "'%s': %s", + priv->interface_name, + tmp_error->message); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME); + g_error_free(tmp_error); + return FALSE; + } + } +after_interface_name: + + if (!_nm_connection_detect_slave_type_full(self, + connection, + &slave_type, + &normerr_slave_setting_type, + &normerr_missing_slave_type, + &normerr_missing_slave_type_port, + error)) + return FALSE; + + if (nm_streq(type, NM_SETTING_OVS_PORT_SETTING_NAME) && slave_type + && !nm_streq(slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("'%s' connections must be enslaved to '%s', not '%s'"), + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + slave_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return FALSE; + } + + if (priv->metered != NM_METERED_UNKNOWN && priv->metered != NM_METERED_YES + && priv->metered != NM_METERED_NO) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("metered value %d is not valid"), + priv->metered); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_METERED); + return FALSE; + } + + if (priv->mdns < (int) NM_SETTING_CONNECTION_MDNS_DEFAULT + || priv->mdns > (int) NM_SETTING_CONNECTION_MDNS_YES) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value %d is not valid"), + priv->mdns); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_MDNS); + return FALSE; + } + + if (priv->llmnr < (int) NM_SETTING_CONNECTION_LLMNR_DEFAULT + || priv->llmnr > (int) NM_SETTING_CONNECTION_LLMNR_YES) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value %d is not valid"), + priv->llmnr); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_LLMNR); + return FALSE; + } + + if (!NM_IN_SET(priv->multi_connect, + (int) NM_CONNECTION_MULTI_CONNECT_DEFAULT, + (int) NM_CONNECTION_MULTI_CONNECT_SINGLE, + (int) NM_CONNECTION_MULTI_CONNECT_MANUAL_MULTIPLE, + (int) NM_CONNECTION_MULTI_CONNECT_MULTIPLE)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value %d is not valid"), + priv->multi_connect); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_MULTI_CONNECT); + return FALSE; + } + + if (priv->mud_url) { + if (!priv->mud_url[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_CONNECTION_MUD_URL); + return FALSE; + } + if (nm_streq(priv->mud_url, NM_CONNECTION_MUD_URL_NONE)) { + /* pass */ + } else { + if (strlen(priv->mud_url) > 255) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("DHCP option cannot be longer than 255 characters")); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_CONNECTION_MUD_URL); + return FALSE; + } + if (!nm_sd_http_url_is_valid_https(priv->mud_url)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("MUD URL is not a valid URL")); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_CONNECTION_MUD_URL); + return FALSE; + } + } + } + + if (priv->permissions) { + guint i; + + for (i = 0; i < priv->permissions->len; i++) { + const Permission *permissions = &g_array_index(priv->permissions, Permission, i); + + if (permissions->ptype != PERM_TYPE_USER) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid permissions not in format \"user:$UNAME[:]\"")); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_CONNECTION_PERMISSIONS); + return FALSE; + } + nm_assert(nm_settings_connection_validate_permission_user(permissions->item, -1)); + } + } + + /* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */ + + if (!priv->uuid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_UUID); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + if (normerr_base_type) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property type should be set to '%s'"), + nm_setting_get_name(normerr_base_type)); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + if (normerr_base_setting) { + _set_error_missing_base_setting(error, priv->type); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + if (normerr_slave_setting_type) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("slave-type '%s' requires a '%s' setting in the connection"), + priv->slave_type, + normerr_slave_setting_type); + g_prefix_error(error, "%s: ", normerr_slave_setting_type); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + if (normerr_missing_slave_type) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("Detect a slave connection with '%s' set and a port type '%s'. '%s' should " + "be set to '%s'"), + NM_SETTING_CONNECTION_MASTER, + normerr_missing_slave_type_port, + NM_SETTING_CONNECTION_SLAVE_TYPE, + normerr_missing_slave_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + if (connection) { + gboolean has_bridge_port = FALSE; + + if ((!nm_streq0(priv->slave_type, NM_SETTING_BRIDGE_SETTING_NAME) + && (has_bridge_port = + !!nm_connection_get_setting_by_name(connection, + NM_SETTING_BRIDGE_PORT_SETTING_NAME))) + || (!nm_streq0(priv->slave_type, NM_SETTING_TEAM_SETTING_NAME) + && nm_connection_get_setting_by_name(connection, + NM_SETTING_TEAM_PORT_SETTING_NAME))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("A slave connection with '%s' set to '%s' cannot have a '%s' setting"), + NM_SETTING_CONNECTION_SLAVE_TYPE, + priv->slave_type ?: "", + has_bridge_port ? NM_SETTING_BRIDGE_PORT_SETTING_NAME + : NM_SETTING_TEAM_PORT_SETTING_NAME); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + } + + if (uuid_was_normalized) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("UUID needs normalization")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_UUID); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + + if (!_nm_setting_connection_verify_secondaries(priv->secondaries, error)) + return NM_SETTING_VERIFY_NORMALIZABLE; + + return TRUE; +} + +static const char * +find_virtual_interface_name(GVariant *connection_dict, GVariant **variant_to_free) +{ + GVariant * setting_dict; + const char *interface_name; + + nm_assert(variant_to_free && !*variant_to_free); + + setting_dict = g_variant_lookup_value(connection_dict, + NM_SETTING_BOND_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + if (!setting_dict) + setting_dict = g_variant_lookup_value(connection_dict, + NM_SETTING_BRIDGE_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + if (!setting_dict) + setting_dict = g_variant_lookup_value(connection_dict, + NM_SETTING_TEAM_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + if (!setting_dict) + setting_dict = g_variant_lookup_value(connection_dict, + NM_SETTING_VLAN_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + + if (!setting_dict) + return NULL; + + *variant_to_free = setting_dict; + + /* All of the deprecated virtual interface name properties were named "interface-name". */ + if (!g_variant_lookup(setting_dict, "interface-name", "&s", &interface_name)) + interface_name = NULL; + + return interface_name; +} + +static gboolean +nm_setting_connection_no_interface_name(NMSetting * setting, + GVariant * connection_dict, + const char * property, + NMSettingParseFlags parse_flags, + GError ** error) +{ + const char * virtual_interface_name; + gs_unref_variant GVariant *variant_to_free = NULL; + + virtual_interface_name = find_virtual_interface_name(connection_dict, &variant_to_free); + g_object_set(G_OBJECT(setting), + NM_SETTING_CONNECTION_INTERFACE_NAME, + virtual_interface_name, + NULL); + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_IGNORE_ID) + && nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_CONNECTION_ID)) + return NM_TERNARY_DEFAULT; + + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP) + && nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_CONNECTION_TIMESTAMP)) + return NM_TERNARY_DEFAULT; + + return NM_SETTING_CLASS(nm_setting_connection_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingConnection * setting = NM_SETTING_CONNECTION(object); + NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_ID: + g_value_set_string(value, nm_setting_connection_get_id(setting)); + break; + case PROP_UUID: + g_value_set_string(value, nm_setting_connection_get_uuid(setting)); + break; + case PROP_STABLE_ID: + g_value_set_string(value, nm_setting_connection_get_stable_id(setting)); + break; + case PROP_INTERFACE_NAME: + g_value_set_string(value, nm_setting_connection_get_interface_name(setting)); + break; + case PROP_TYPE: + g_value_set_string(value, nm_setting_connection_get_connection_type(setting)); + break; + case PROP_PERMISSIONS: + { + char **strv; + gsize i, l; + + l = nm_g_array_len(priv->permissions); + strv = g_new(char *, l + 1u); + + for (i = 0; i < l; i++) + strv[i] = _permission_to_string(&g_array_index(priv->permissions, Permission, i)); + strv[i] = NULL; + + g_value_take_boxed(value, strv); + break; + } + case PROP_AUTOCONNECT: + g_value_set_boolean(value, nm_setting_connection_get_autoconnect(setting)); + break; + case PROP_AUTOCONNECT_PRIORITY: + g_value_set_int(value, nm_setting_connection_get_autoconnect_priority(setting)); + break; + case PROP_AUTOCONNECT_RETRIES: + g_value_set_int(value, nm_setting_connection_get_autoconnect_retries(setting)); + break; + case PROP_MULTI_CONNECT: + g_value_set_int(value, priv->multi_connect); + break; + case PROP_TIMESTAMP: + g_value_set_uint64(value, nm_setting_connection_get_timestamp(setting)); + break; + case PROP_READ_ONLY: + g_value_set_boolean(value, nm_setting_connection_get_read_only(setting)); + break; + case PROP_ZONE: + g_value_set_string(value, nm_setting_connection_get_zone(setting)); + break; + case PROP_MASTER: + g_value_set_string(value, nm_setting_connection_get_master(setting)); + break; + case PROP_SLAVE_TYPE: + g_value_set_string(value, nm_setting_connection_get_slave_type(setting)); + break; + case PROP_AUTOCONNECT_SLAVES: + g_value_set_enum(value, nm_setting_connection_get_autoconnect_slaves(setting)); + break; + case PROP_SECONDARIES: + g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(priv->secondaries, NULL)); + break; + case PROP_GATEWAY_PING_TIMEOUT: + g_value_set_uint(value, priv->gateway_ping_timeout); + break; + case PROP_METERED: + g_value_set_enum(value, priv->metered); + break; + case PROP_LLDP: + g_value_set_int(value, priv->lldp); + break; + case PROP_AUTH_RETRIES: + g_value_set_int(value, priv->auth_retries); + break; + case PROP_MDNS: + g_value_set_int(value, priv->mdns); + break; + case PROP_LLMNR: + g_value_set_int(value, priv->llmnr); + break; + case PROP_WAIT_DEVICE_TIMEOUT: + g_value_set_int(value, priv->wait_device_timeout); + break; + case PROP_MUD_URL: + g_value_set_string(value, priv->mud_url); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_ID: + g_free(priv->id); + priv->id = g_value_dup_string(value); + break; + case PROP_UUID: + g_free(priv->uuid); + priv->uuid = g_value_dup_string(value); + break; + case PROP_STABLE_ID: + g_free(priv->stable_id); + priv->stable_id = g_value_dup_string(value); + break; + case PROP_INTERFACE_NAME: + g_free(priv->interface_name); + priv->interface_name = g_value_dup_string(value); + break; + case PROP_TYPE: + g_free(priv->type); + priv->type = g_value_dup_string(value); + break; + case PROP_PERMISSIONS: + { + const char *const *strv; + guint i; + + nm_clear_pointer(&priv->permissions, g_array_unref); + strv = g_value_get_boxed(value); + if (strv && strv[0]) { + priv->permissions = + g_array_sized_new(FALSE, FALSE, sizeof(Permission), NM_PTRARRAY_LEN(strv)); + g_array_set_clear_func(priv->permissions, (GDestroyNotify) _permission_clear_stale); + + for (i = 0; strv[i]; i++) { + Permission *permission = nm_g_array_append_new(priv->permissions, Permission); + + _permission_set_stale_parse(permission, strv[i]); + } + } + break; + } + case PROP_AUTOCONNECT: + priv->autoconnect = g_value_get_boolean(value); + break; + case PROP_AUTOCONNECT_PRIORITY: + priv->autoconnect_priority = g_value_get_int(value); + break; + case PROP_AUTOCONNECT_RETRIES: + priv->autoconnect_retries = g_value_get_int(value); + break; + case PROP_MULTI_CONNECT: + priv->multi_connect = g_value_get_int(value); + break; + case PROP_TIMESTAMP: + priv->timestamp = g_value_get_uint64(value); + break; + case PROP_READ_ONLY: + priv->read_only = g_value_get_boolean(value); + break; + case PROP_ZONE: + g_free(priv->zone); + priv->zone = g_value_dup_string(value); + break; + case PROP_MASTER: + g_free(priv->master); + priv->master = g_value_dup_string(value); + break; + case PROP_SLAVE_TYPE: + g_free(priv->slave_type); + priv->slave_type = g_value_dup_string(value); + break; + case PROP_AUTOCONNECT_SLAVES: + priv->autoconnect_slaves = g_value_get_enum(value); + break; + case PROP_SECONDARIES: + nm_strvarray_set_strv(&priv->secondaries, g_value_get_boxed(value)); + break; + case PROP_GATEWAY_PING_TIMEOUT: + priv->gateway_ping_timeout = g_value_get_uint(value); + break; + case PROP_METERED: + priv->metered = g_value_get_enum(value); + break; + case PROP_LLDP: + priv->lldp = g_value_get_int(value); + break; + case PROP_AUTH_RETRIES: + priv->auth_retries = g_value_get_int(value); + break; + case PROP_MDNS: + priv->mdns = g_value_get_int(value); + break; + case PROP_LLMNR: + priv->llmnr = g_value_get_int(value); + break; + case PROP_WAIT_DEVICE_TIMEOUT: + priv->wait_device_timeout = g_value_get_int(value); + break; + case PROP_MUD_URL: + g_free(priv->mud_url); + priv->mud_url = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_connection_init(NMSettingConnection *setting) +{ + NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting); + + priv->auth_retries = -1; + priv->autoconnect = TRUE; + priv->autoconnect_priority = NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT; + priv->autoconnect_retries = -1; + priv->autoconnect_slaves = NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT; + priv->lldp = NM_SETTING_CONNECTION_LLDP_DEFAULT; + priv->llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT; + priv->mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT; + priv->wait_device_timeout = -1; +} + +/** + * nm_setting_connection_new: + * + * Creates a new #NMSettingConnection object with default values. + * + * Returns: the new empty #NMSettingConnection object + **/ +NMSetting * +nm_setting_connection_new(void) +{ + return g_object_new(NM_TYPE_SETTING_CONNECTION, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE(object); + + g_free(priv->id); + g_free(priv->uuid); + g_free(priv->stable_id); + g_free(priv->interface_name); + g_free(priv->type); + g_free(priv->zone); + g_free(priv->master); + g_free(priv->slave_type); + g_free(priv->mud_url); + nm_clear_pointer(&priv->permissions, g_array_unref); + nm_clear_pointer(&priv->secondaries, g_array_unref); + + G_OBJECT_CLASS(nm_setting_connection_parent_class)->finalize(object); +} + +static void +nm_setting_connection_class_init(NMSettingConnectionClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingConnectionPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->compare_property = compare_property; + + /** + * NMSettingConnection:id: + * + * A human readable unique identifier for the connection, like "Work Wi-Fi" + * or "T-Mobile 3G". + **/ + /* ---ifcfg-rh--- + * property: id + * variable: NAME(+) + * description: User friendly name for the connection profile. + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_CONNECTION_ID, + PROP_ID, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_connection_get_id); + + /** + * NMSettingConnection:uuid: + * + * A universally unique identifier for the connection, for example generated + * with libuuid. It should be assigned when the connection is created, and + * never changed as long as the connection still applies to the same + * network. For example, it should not be changed when the + * #NMSettingConnection:id property or #NMSettingIP4Config changes, but + * might need to be re-created when the Wi-Fi SSID, mobile broadband network + * provider, or #NMSettingConnection:type property changes. + * + * The UUID must be in the format "2815492f-7e56-435e-b2e9-246bd7cdc664" + * (ie, contains only hexadecimal characters and "-"). A suitable UUID may + * be generated by nm_utils_uuid_generate() or + * nm_uuid_generate_from_string_str(). + **/ + /* ---ifcfg-rh--- + * property: uuid + * variable: UUID(+) + * description: UUID for the connection profile. When missing, NetworkManager + * creates the UUID itself (by hashing the filename). + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_CONNECTION_UUID, + PROP_UUID, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_connection_get_uuid); + + /** + * NMSettingConnection:stable-id: + * + * This represents the identity of the connection used for various purposes. + * It allows to configure multiple profiles to share the identity. Also, + * the stable-id can contain placeholders that are substituted dynamically and + * deterministically depending on the context. + * + * The stable-id is used for generating IPv6 stable private addresses + * with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the + * generated cloned MAC address for ethernet.cloned-mac-address=stable + * and wifi.cloned-mac-address=stable. It is also used as DHCP client + * identifier with ipv4.dhcp-client-id=stable and to derive the DHCP + * DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid]. + * + * Note that depending on the context where it is used, other parameters are + * also seeded into the generation algorithm. For example, a per-host key + * is commonly also included, so that different systems end up generating + * different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device's + * name is included, so that different interfaces yield different addresses. + * The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret-key. + * + * The '$' character is treated special to perform dynamic substitutions + * at runtime. Currently, supported are "${CONNECTION}", "${DEVICE}", "${MAC}", + * "${BOOT}", "${RANDOM}". + * These effectively create unique IDs per-connection, per-device, per-boot, + * or every time. Note that "${DEVICE}" corresponds to the interface name of the + * device and "${MAC}" is the permanent MAC address of the device. + * Any unrecognized patterns following '$' are treated verbatim, however + * are reserved for future use. You are thus advised to avoid '$' or + * escape it as "$$". + * For example, set it to "${CONNECTION}-${BOOT}-${DEVICE}" to create a unique id for + * this connection that changes with every reboot and differs depending on the + * interface where the profile activates. + * + * If the value is unset, a global connection default is consulted. If the + * value is still unset, the default is similar to "${CONNECTION}" and uses + * a unique, fixed ID for the connection. + * + * Since: 1.4 + **/ + /* ---ifcfg-rh--- + * property: stable-id + * variable: STABLE_ID(+) + * description: Token to generate stable IDs. + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_CONNECTION_STABLE_ID, + PROP_STABLE_ID, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_connection_get_stable_id); + + /** + * NMSettingConnection:interface-name: + * + * The name of the network interface this connection is bound to. If not + * set, then the connection can be attached to any interface of the + * appropriate type (subject to restrictions imposed by other settings). + * + * For software devices this specifies the name of the created device. + * + * For connection types where interface names cannot easily be made + * persistent (e.g. mobile broadband or USB Ethernet), this property should + * not be used. Setting this property restricts the interfaces a connection + * can be used with, and if interface names change or are reordered the + * connection may be applied to the wrong interface. + **/ + /* ---ifcfg-rh--- + * property: interface-name + * variable: DEVICE + * description: Interface name of the device this profile is bound to. The variable + * can be left out when the profile should apply for more devices. Note that DEVICE + * can be required for some connection types. + * ---end--- + */ + _nm_setting_property_define_string_full( + properties_override, + obj_properties, + NM_SETTING_CONNECTION_INTERFACE_NAME, + PROP_INTERFACE_NAME, + NM_SETTING_PARAM_INFERRABLE, + NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING, + .to_dbus_fcn = _nm_setting_property_to_dbus_fcn_get_string, + .missing_from_dbus_fcn = + nm_setting_connection_no_interface_name), + nm_setting_connection_get_interface_name); + + /** + * NMSettingConnection:type: + * + * Base type of the connection. For hardware-dependent connections, should + * contain the setting name of the hardware-type specific setting (ie, + * "802-3-ethernet" or "802-11-wireless" or "bluetooth", etc), and for + * non-hardware dependent connections like VPN or otherwise, should contain + * the setting name of that setting type (ie, "vpn" or "bridge", etc). + **/ + /* ---ifcfg-rh--- + * property: type + * variable: TYPE (DEVICETYPE, DEVICE) + * values: Ethernet, Wireless, InfiniBand, Bridge, Bond, Vlan, Team, TeamPort + * description: Base type of the connection. DEVICETYPE is used for teaming + * connections. + * example: TYPE=Ethernet; TYPE=Bond; TYPE=Bridge; DEVICETYPE=TeamPort + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_CONNECTION_TYPE, + PROP_TYPE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_connection_get_connection_type); + + /** + * NMSettingConnection:permissions: + * + * An array of strings defining what access a given user has to this + * connection. If this is %NULL or empty, all users are allowed to access + * this connection; otherwise users are allowed if and only if they are in + * this list. When this is not empty, the connection can be active only when + * one of the specified users is logged into an active session. Each entry + * is of the form "[type]:[id]:[reserved]"; for example, "user:dcbw:blah". + * + * At this time only the "user" [type] is allowed. Any other values are + * ignored and reserved for future use. [id] is the username that this + * permission refers to, which may not contain the ":" character. Any + * [reserved] information present must be ignored and is reserved for future + * use. All of [type], [id], and [reserved] must be valid UTF-8. + */ + /* ---ifcfg-rh--- + * property: permissions + * variable: USERS(+) + * description: Restrict to certain users the access to this connection, and + * allow the connection to be active only when at least one of the + * specified users is logged into an active session. + * example: USERS="joe bob" + * ---end--- + */ + obj_properties[PROP_PERMISSIONS] = + g_param_spec_boxed(NM_SETTING_CONNECTION_PERMISSIONS, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:autoconnect: + * + * Whether or not the connection should be automatically connected by + * NetworkManager when the resources for the connection are available. + * %TRUE to automatically activate the connection, %FALSE to require manual + * intervention to activate the connection. + * + * Note that autoconnect is not implemented for VPN profiles. See + * #NMSettingConnection:secondaries as an alternative to automatically + * connect VPN profiles. + **/ + /* ---ifcfg-rh--- + * property: autoconnect + * variable: ONBOOT + * default: yes + * description: Whether the connection should be autoconnected (not only while booting). + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_CONNECTION_AUTOCONNECT, + PROP_AUTOCONNECT, + TRUE, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_connection_get_autoconnect); + + /** + * NMSettingConnection:autoconnect-priority: + * + * The autoconnect priority. If the connection is set to autoconnect, + * connections with higher priority will be preferred. Defaults to 0. + * The higher number means higher priority. + **/ + /* ---ifcfg-rh--- + * property: autoconnect-priority + * variable: AUTOCONNECT_PRIORITY(+) + * values: -999 to 999 + * default: 0 + * description: Connection priority for automatic activation. Connections with + * higher numbers are preferred when selecting profiles for automatic activation. + * example: AUTOCONNECT_PRIORITY=20 + * ---end--- + */ + obj_properties[PROP_AUTOCONNECT_PRIORITY] = g_param_spec_int( + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, + "", + "", + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MIN, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_MAX, + NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY_DEFAULT, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:autoconnect-retries: + * + * The number of times a connection should be tried when autoactivating before + * giving up. Zero means forever, -1 means the global default (4 times if not + * overridden). Setting this to 1 means to try activation only once before + * blocking autoconnect. Note that after a timeout, NetworkManager will try + * to autoconnect again. + */ + /* ---ifcfg-rh--- + * property: autoconnect-retries + * variable: AUTOCONNECT_RETRIES(+) + * description: The number of times a connection should be autoactivated + * before giving up and switching to the next one. + * values: -1 (use global default), 0 (forever) or a positive value + * example: AUTOCONNECT_RETRIES=1 + * ---end--- + */ + obj_properties[PROP_AUTOCONNECT_RETRIES] = g_param_spec_int( + NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES, + "", + "", + -1, + G_MAXINT32, + -1, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:multi-connect: + * + * Specifies whether the profile can be active multiple times at a particular + * moment. The value is of type #NMConnectionMultiConnect. + * + * Since: 1.14 + */ + /* ---ifcfg-rh--- + * property: multi-connect + * variable: MULTI_CONNECT(+) + * description: whether the profile can be active on multiple devices at a given + * moment. The values are numbers corresponding to #NMConnectionMultiConnect enum. + * example: MULTI_CONNECT=3 + * ---end--- + */ + obj_properties[PROP_MULTI_CONNECT] = g_param_spec_int( + NM_SETTING_CONNECTION_MULTI_CONNECT, + "", + "", + G_MININT32, + G_MAXINT32, + NM_CONNECTION_MULTI_CONNECT_DEFAULT, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:timestamp: + * + * The time, in seconds since the Unix Epoch, that the connection was last + * _successfully_ fully activated. + * + * NetworkManager updates the connection timestamp periodically when the + * connection is active to ensure that an active connection has the latest + * timestamp. The property is only meant for reading (changes to this + * property will not be preserved). + **/ + obj_properties[PROP_TIMESTAMP] = g_param_spec_uint64( + NM_SETTING_CONNECTION_TIMESTAMP, + "", + "", + 0, + G_MAXUINT64, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_TIMESTAMP], + NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_UINT64, + .to_dbus_fcn = _to_dbus_fcn_timestamp, )); + + /** + * NMSettingConnection:read-only: + * + * %FALSE if the connection can be modified using the provided settings + * service's D-Bus interface with the right privileges, or %TRUE if the + * connection is read-only and cannot be modified. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_CONNECTION_READ_ONLY, + PROP_READ_ONLY, + FALSE, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_connection_get_read_only); + + /** + * NMSettingConnection:zone: + * + * The trust level of a the connection. Free form case-insensitive string + * (for example "Home", "Work", "Public"). %NULL or unspecified zone means + * the connection will be placed in the default zone as defined by the + * firewall. + * + * When updating this property on a currently activated connection, + * the change takes effect immediately. + **/ + /* ---ifcfg-rh--- + * property: zone + * variable: ZONE(+) + * description: Trust level of this connection. The string is usually used + * for a firewall. + * example: ZONE=Work + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_CONNECTION_ZONE, + PROP_ZONE, + NM_SETTING_PARAM_FUZZY_IGNORE + | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY, + nm_setting_connection_get_zone); + + /** + * NMSettingConnection:master: + * + * Interface name of the master device or UUID of the master connection. + **/ + /* ---ifcfg-rh--- + * property: master + * variable: MASTER, MASTER_UUID, TEAM_MASTER, TEAM_MASTER_UUID, BRIDGE, BRIDGE_UUID + * description: Reference to master connection. The variable used depends on + * the connection type and the value. In general, if the *_UUID variant is present, + * the variant without *_UUID is ignored. NetworkManager attempts to write both + * for compatibility with legacy tooling. + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_CONNECTION_MASTER, + PROP_MASTER, + NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_INFERRABLE, + nm_setting_connection_get_master); + + /** + * NMSettingConnection:slave-type: + * + * Setting name of the device type of this slave's master connection (eg, + * %NM_SETTING_BOND_SETTING_NAME), or %NULL if this connection is not a + * slave. + **/ + /* ---ifcfg-rh--- + * property: slave-type + * variable: MASTER, MASTER_UUID, TEAM_MASTER, TEAM_MASTER_UUID, DEVICETYPE, + * BRIDGE, BRIDGE_UUID + * description: Slave type doesn't map directly to a variable, but it is + * recognized using different variables. MASTER and MASTER_UUID for bonding, + * TEAM_MASTER, TEAM_MASTER_UUID and DEVICETYPE for teaming, BRIDGE + * and BRIDGE_UUID for bridging. + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_CONNECTION_SLAVE_TYPE, + PROP_SLAVE_TYPE, + NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_INFERRABLE, + nm_setting_connection_get_slave_type); + + /** + * NMSettingConnection:autoconnect-slaves: + * + * Whether or not slaves of this connection should be automatically brought up + * when NetworkManager activates this connection. This only has a real effect + * for master connections. The properties #NMSettingConnection:autoconnect, + * #NMSettingConnection:autoconnect-priority and #NMSettingConnection:autoconnect-retries + * are unrelated to this setting. + * The permitted values are: 0: leave slave connections untouched, + * 1: activate all the slave connections with this connection, -1: default. + * If -1 (default) is set, global connection.autoconnect-slaves is read to + * determine the real value. If it is default as well, this fallbacks to 0. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: autoconnect-slaves + * variable: AUTOCONNECT_SLAVES(+) + * default: missing variable means global default + * description: Whether slaves of this connection should be auto-connected + * when this connection is activated. + * ---end--- + */ + obj_properties[PROP_AUTOCONNECT_SLAVES] = g_param_spec_enum( + NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, + "", + "", + NM_TYPE_SETTING_CONNECTION_AUTOCONNECT_SLAVES, + NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES_DEFAULT, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:secondaries: + * + * List of connection UUIDs that should be activated when the base + * connection itself is activated. Currently, only VPN connections are + * supported. + **/ + /* ---ifcfg-rh--- + * property: secondaries + * variable: SECONDARY_UUIDS(+) + * description: UUID of VPN connections that should be activated + * together with this connection. + * ---end--- + */ + obj_properties[PROP_SECONDARIES] = g_param_spec_boxed( + NM_SETTING_CONNECTION_SECONDARIES, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:gateway-ping-timeout: + * + * If greater than zero, delay success of IP addressing until either the + * timeout is reached, or an IP gateway replies to a ping. + **/ + /* ---ifcfg-rh--- + * property: gateway-ping-timeout + * variable: GATEWAY_PING_TIMEOUT(+) + * default: 0 + * description: If greater than zero, the IP connectivity will be checked by + * pinging the gateway and waiting for the specified timeout (in seconds). + * example: GATEWAY_PING_TIMEOUT=5 + * ---end--- + */ + obj_properties[PROP_GATEWAY_PING_TIMEOUT] = + g_param_spec_uint(NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, + "", + "", + 0, + 600, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:metered: + * + * Whether the connection is metered. + * + * When updating this property on a currently activated connection, + * the change takes effect immediately. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: metered + * variable: CONNECTION_METERED(+) + * values: yes,no,unknown + * description: Whether the device is metered + * example: CONNECTION_METERED=yes + * ---end--- + */ + obj_properties[PROP_METERED] = g_param_spec_enum( + NM_SETTING_CONNECTION_METERED, + "", + "", + NM_TYPE_METERED, + NM_METERED_UNKNOWN, + G_PARAM_READWRITE | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:lldp: + * + * Whether LLDP is enabled for the connection. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: lldp + * variable: LLDP(+) + * values: boolean value or 'rx' + * default: missing variable means global default + * description: whether LLDP is enabled for the connection + * example: LLDP=no + * ---end--- + */ + obj_properties[PROP_LLDP] = g_param_spec_int(NM_SETTING_CONNECTION_LLDP, + "", + "", + G_MININT32, + G_MAXINT32, + NM_SETTING_CONNECTION_LLDP_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:auth-retries: + * + * The number of retries for the authentication. Zero means to try indefinitely; -1 means + * to use a global default. If the global default is not set, the authentication + * retries for 3 times before failing the connection. + * + * Currently, this only applies to 802-1x authentication. + * + * Since: 1.10 + **/ + /* ---ifcfg-rh--- + * property: auth-retries + * variable: AUTH_RETRIES(+) + * default: 0 + * description: Number of retries for authentication. + * ---end--- + */ + obj_properties[PROP_AUTH_RETRIES] = g_param_spec_int( + NM_SETTING_CONNECTION_AUTH_RETRIES, + "", + "", + -1, + G_MAXINT32, + -1, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:mdns: + * + * Whether mDNS is enabled for the connection. + * + * The permitted values are: "yes" (2) register hostname and resolving + * for the connection, "no" (0) disable mDNS for the interface, "resolve" + * (1) do not register hostname but allow resolving of mDNS host names + * and "default" (-1) to allow lookup of a global default in NetworkManager.conf. + * If unspecified, "default" ultimately depends on the DNS plugin (which + * for systemd-resolved currently means "no"). + * + * This feature requires a plugin which supports mDNS. Otherwise, the + * setting has no effect. One such plugin is dns-systemd-resolved. +* + * Since: 1.12 + **/ + /* ---ifcfg-rh--- + * property: mdns + * variable: MDNS(+) + * values: yes,no,resolve + * default: missing variable means global default + * description: Whether or not mDNS is enabled for the connection + * example: MDNS=yes + * ---end--- + */ + obj_properties[PROP_MDNS] = g_param_spec_int(NM_SETTING_CONNECTION_MDNS, + "", + "", + G_MININT32, + G_MAXINT32, + NM_SETTING_CONNECTION_MDNS_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:llmnr: + * + * Whether Link-Local Multicast Name Resolution (LLMNR) is enabled + * for the connection. LLMNR is a protocol based on the Domain Name + * System (DNS) packet format that allows both IPv4 and IPv6 hosts + * to perform name resolution for hosts on the same local link. + * + * The permitted values are: "yes" (2) register hostname and resolving + * for the connection, "no" (0) disable LLMNR for the interface, "resolve" + * (1) do not register hostname but allow resolving of LLMNR host names + * If unspecified, "default" ultimately depends on the DNS plugin (which + * for systemd-resolved currently means "yes"). + * + * This feature requires a plugin which supports LLMNR. Otherwise, the + * setting has no effect. One such plugin is dns-systemd-resolved. + * + * Since: 1.14 + **/ + /* ---ifcfg-rh--- + * property: llmnr + * variable: LLMNR(+) + * values: yes,no,resolve + * default: missing variable means global default + * description: Whether or not LLMNR is enabled for the connection + * example: LLMNR=yes + * ---end--- + */ + obj_properties[PROP_LLMNR] = g_param_spec_int(NM_SETTING_CONNECTION_LLMNR, + "", + "", + G_MININT32, + G_MAXINT32, + NM_SETTING_CONNECTION_LLMNR_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:wait-device-timeout: + * + * Timeout in milliseconds to wait for device at startup. + * During boot, devices may take a while to be detected by the driver. + * This property will cause to delay NetworkManager-wait-online.service + * and nm-online to give the device a chance to appear. This works by + * waiting for the given timeout until a compatible device for the + * profile is available and managed. + * + * The value 0 means no wait time. The default value is -1, which + * currently has the same meaning as no wait time. + * + * Since: 1.20 + **/ + /* ---ifcfg-rh--- + * property: wait-device-timeout + * variable: DEVTIMEOUT(+) + * values: timeout in seconds. + * description: for initscripts compatibility, this variable must be + * a whole integer. If necessary, NetworkManager stores also a fractional + * component for the milliseconds. + * example: DEVTIMEOUT=5 + * ---end--- + */ + obj_properties[PROP_WAIT_DEVICE_TIMEOUT] = + g_param_spec_int(NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, + "", + "", + -1, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingConnection:mud-url: + * + * If configured, set to a Manufacturer Usage Description (MUD) URL that points + * to manufacturer-recommended network policies for IoT devices. It is transmitted + * as a DHCPv4 or DHCPv6 option. The value must be a valid URL starting with "https://". + * + * The special value "none" is allowed to indicate that no MUD URL is used. + * + * If the per-profile value is unspecified (the default), a global connection default gets + * consulted. If still unspecified, the ultimate default is "none". + * + * Since: 1.26 + **/ + /* ---ifcfg-rh--- + * property: mud-url + * variable: MUD_URL + * values: a valid URL that points to recommended policy for this device + * description: MUD_URL to be sent by device (See RFC 8520). + * example: https://yourdevice.example.com/model.json + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_CONNECTION_MUD_URL, + PROP_MUD_URL, + NM_SETTING_PARAM_NONE, + nm_setting_connection_get_mud_url); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_CONNECTION, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-dcb.c b/src/libnm-core-impl/nm-setting-dcb.c new file mode 100644 index 00000000..f9602c3b --- /dev/null +++ b/src/libnm-core-impl/nm-setting-dcb.c @@ -0,0 +1,1289 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-dcb.h" + +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" +#include "nm-core-enum-types.h" + +/** + * SECTION:nm-setting-dcb + * @short_description: Connection properties for Data Center Bridging + * + * The #NMSettingDcb object is a #NMSetting subclass that describes properties + * for enabling and using Data Center Bridging (DCB) on Ethernet networks. + * DCB is a set of protocols (including 802.1Qbb, 802.1Qaz, 802.1Qau, and + * 802.1AB) to eliminate packet loss in Ethernet networks and support the use + * of storage technologies like Fibre Channel over Ethernet (FCoE) and iSCSI. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingDcb, + + PROP_APP_FCOE_FLAGS, + PROP_APP_FCOE_PRIORITY, + PROP_APP_FCOE_MODE, + + PROP_APP_ISCSI_FLAGS, + PROP_APP_ISCSI_PRIORITY, + + PROP_APP_FIP_FLAGS, + PROP_APP_FIP_PRIORITY, + + PROP_PFC_FLAGS, + PROP_PRIORITY_FLOW_CONTROL, + + PROP_PRIORITY_GROUP_FLAGS, + PROP_PRIORITY_GROUP_ID, + PROP_PRIORITY_GROUP_BANDWIDTH, + PROP_PRIORITY_BANDWIDTH, + PROP_PRIORITY_STRICT_BANDWIDTH, + PROP_PRIORITY_TRAFFIC_CLASS, ); + +typedef struct { + char * app_fcoe_mode; + guint pfc[8]; + guint priority_group_id[8]; + guint priority_group_bandwidth[8]; + guint priority_bandwidth[8]; + guint priority_strict[8]; + guint priority_traffic_class[8]; + int app_fcoe_priority; + int app_iscsi_priority; + int app_fip_priority; + NMSettingDcbFlags app_fcoe_flags; + NMSettingDcbFlags app_iscsi_flags; + NMSettingDcbFlags app_fip_flags; + NMSettingDcbFlags pfc_flags; + NMSettingDcbFlags priority_group_flags; +} NMSettingDcbPrivate; + +/** + * NMSettingDcb: + * + * Data Center Bridging Settings + */ +struct _NMSettingDcb { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingDcbClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingDcb, nm_setting_dcb, NM_TYPE_SETTING) + +#define NM_SETTING_DCB_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_DCB, NMSettingDcbPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_dcb_get_app_fcoe_flags: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:app-fcoe-flags property of the setting + **/ +NMSettingDcbFlags +nm_setting_dcb_get_app_fcoe_flags(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->app_fcoe_flags; +} + +/** + * nm_setting_dcb_get_app_fcoe_priority: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:app-fcoe-priority property of the setting + **/ +int +nm_setting_dcb_get_app_fcoe_priority(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->app_fcoe_priority; +} + +/** + * nm_setting_dcb_get_app_fcoe_mode: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:app-fcoe-mode property of the setting + **/ +const char * +nm_setting_dcb_get_app_fcoe_mode(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), NULL); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->app_fcoe_mode; +} + +/** + * nm_setting_dcb_get_app_iscsi_flags: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:app-iscsi-flags property of the setting + **/ +NMSettingDcbFlags +nm_setting_dcb_get_app_iscsi_flags(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->app_iscsi_flags; +} + +/** + * nm_setting_dcb_get_app_iscsi_priority: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:app-iscsi-priority property of the setting + **/ +int +nm_setting_dcb_get_app_iscsi_priority(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->app_iscsi_priority; +} + +/** + * nm_setting_dcb_get_app_fip_flags: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:app-fip-flags property of the setting + **/ +NMSettingDcbFlags +nm_setting_dcb_get_app_fip_flags(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->app_fip_flags; +} + +/** + * nm_setting_dcb_get_app_fip_priority: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:app-fip-priority property of the setting + **/ +int +nm_setting_dcb_get_app_fip_priority(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->app_fip_priority; +} + +/** + * nm_setting_dcb_get_priority_flow_control_flags: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:priority-flow-control-flags property of the setting + **/ +NMSettingDcbFlags +nm_setting_dcb_get_priority_flow_control_flags(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->pfc_flags; +} + +/** + * nm_setting_dcb_get_priority_flow_control: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to retrieve flow control for + * + * Returns: %TRUE if flow control is enabled for the given @user_priority, + * %FALSE if not enabled + **/ +gboolean +nm_setting_dcb_get_priority_flow_control(NMSettingDcb *setting, guint user_priority) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), FALSE); + g_return_val_if_fail(user_priority <= 7, FALSE); + + return !!NM_SETTING_DCB_GET_PRIVATE(setting)->pfc[user_priority]; +} + +/** + * nm_setting_dcb_set_priority_flow_control: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to set flow control for + * @enabled: %TRUE to enable flow control for this priority, %FALSE to disable it + * + * These values are only valid when #NMSettingDcb:priority-flow-control includes + * the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +void +nm_setting_dcb_set_priority_flow_control(NMSettingDcb *setting, + guint user_priority, + gboolean enabled) +{ + NMSettingDcbPrivate *priv; + guint uint_enabled = enabled ? 1 : 0; + + g_return_if_fail(NM_IS_SETTING_DCB(setting)); + g_return_if_fail(user_priority <= 7); + + priv = NM_SETTING_DCB_GET_PRIVATE(setting); + if (priv->pfc[user_priority] != uint_enabled) { + priv->pfc[user_priority] = uint_enabled; + _notify(setting, PROP_PRIORITY_FLOW_CONTROL); + } +} + +/** + * nm_setting_dcb_get_priority_group_flags: + * @setting: the #NMSettingDcb + * + * Returns: the #NMSettingDcb:priority-group-flags property of the setting + **/ +NMSettingDcbFlags +nm_setting_dcb_get_priority_group_flags(NMSettingDcb *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->priority_group_flags; +} + +/** + * nm_setting_dcb_get_priority_group_id: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to retrieve the group ID for + * + * Returns: the group number @user_priority is assigned to. These values are + * only valid when #NMSettingDcb:priority-group-flags includes the + * %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +guint +nm_setting_dcb_get_priority_group_id(NMSettingDcb *setting, guint user_priority) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + g_return_val_if_fail(user_priority <= 7, 0); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->priority_group_id[user_priority]; +} + +/** + * nm_setting_dcb_set_priority_group_id: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to set flow control for + * @group_id: the group (0 - 7) to assign @user_priority to, or 15 for the + * unrestricted group. + * + * These values are only valid when #NMSettingDcb:priority-group-flags includes + * the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +void +nm_setting_dcb_set_priority_group_id(NMSettingDcb *setting, guint user_priority, guint group_id) +{ + NMSettingDcbPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_DCB(setting)); + g_return_if_fail(user_priority <= 7); + g_return_if_fail(group_id <= 7 || group_id == 15); + + priv = NM_SETTING_DCB_GET_PRIVATE(setting); + if (priv->priority_group_id[user_priority] != group_id) { + priv->priority_group_id[user_priority] = group_id; + _notify(setting, PROP_PRIORITY_GROUP_ID); + } +} + +/** + * nm_setting_dcb_get_priority_group_bandwidth: + * @setting: the #NMSettingDcb + * @group_id: the priority group (0 - 7) to retrieve the bandwidth percentage for + * + * Returns: the bandwidth percentage assigned to @group_id. These values are + * only valid when #NMSettingDcb:priority-group-flags includes the + * %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +guint +nm_setting_dcb_get_priority_group_bandwidth(NMSettingDcb *setting, guint group_id) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + g_return_val_if_fail(group_id <= 7, FALSE); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->priority_group_bandwidth[group_id]; +} + +/** + * nm_setting_dcb_set_priority_group_bandwidth: + * @setting: the #NMSettingDcb + * @group_id: the priority group (0 - 7) to set the bandwidth percentage for + * @bandwidth_percent: the bandwidth percentage (0 - 100) to assign to @group_id to + * + * These values are only valid when #NMSettingDcb:priority-group-flags includes + * the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +void +nm_setting_dcb_set_priority_group_bandwidth(NMSettingDcb *setting, + guint group_id, + guint bandwidth_percent) +{ + NMSettingDcbPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_DCB(setting)); + g_return_if_fail(group_id <= 7); + g_return_if_fail(bandwidth_percent <= 100); + + priv = NM_SETTING_DCB_GET_PRIVATE(setting); + if (priv->priority_group_bandwidth[group_id] != bandwidth_percent) { + priv->priority_group_bandwidth[group_id] = bandwidth_percent; + _notify(setting, PROP_PRIORITY_GROUP_BANDWIDTH); + } +} + +/** + * nm_setting_dcb_get_priority_bandwidth: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to retrieve the group bandwidth percentage for + * + * Returns: the allowed bandwidth percentage of @user_priority in its priority group. + * These values are only valid when #NMSettingDcb:priority-group-flags includes the + * %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +guint +nm_setting_dcb_get_priority_bandwidth(NMSettingDcb *setting, guint user_priority) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + g_return_val_if_fail(user_priority <= 7, FALSE); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->priority_bandwidth[user_priority]; +} + +/** + * nm_setting_dcb_set_priority_bandwidth: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to set the bandwidth percentage for + * @bandwidth_percent: the bandwidth percentage (0 - 100) that @user_priority is + * allowed to use within its priority group + * + * These values are only valid when #NMSettingDcb:priority-group-flags includes + * the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +void +nm_setting_dcb_set_priority_bandwidth(NMSettingDcb *setting, + guint user_priority, + guint bandwidth_percent) +{ + NMSettingDcbPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_DCB(setting)); + g_return_if_fail(user_priority <= 7); + g_return_if_fail(bandwidth_percent <= 100); + + priv = NM_SETTING_DCB_GET_PRIVATE(setting); + if (priv->priority_bandwidth[user_priority] != bandwidth_percent) { + priv->priority_bandwidth[user_priority] = bandwidth_percent; + _notify(setting, PROP_PRIORITY_BANDWIDTH); + } +} + +/** + * nm_setting_dcb_get_priority_strict_bandwidth: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to retrieve strict bandwidth for + * + * Returns: %TRUE if @user_priority may use all of the bandwidth allocated to its + * assigned group, or %FALSE if not. These values are only valid when + * #NMSettingDcb:priority-group-flags includes the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +gboolean +nm_setting_dcb_get_priority_strict_bandwidth(NMSettingDcb *setting, guint user_priority) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + g_return_val_if_fail(user_priority <= 7, FALSE); + + return !!NM_SETTING_DCB_GET_PRIVATE(setting)->priority_strict[user_priority]; +} + +/** + * nm_setting_dcb_set_priority_strict_bandwidth: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to set strict bandwidth for + * @strict: %TRUE to allow @user_priority to use all the bandwidth allocated to + * its priority group, or %FALSE if not + * + * These values are only valid when #NMSettingDcb:priority-group-flags includes + * the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +void +nm_setting_dcb_set_priority_strict_bandwidth(NMSettingDcb *setting, + guint user_priority, + gboolean strict) +{ + NMSettingDcbPrivate *priv; + guint uint_strict = strict ? 1 : 0; + + g_return_if_fail(NM_IS_SETTING_DCB(setting)); + g_return_if_fail(user_priority <= 7); + + priv = NM_SETTING_DCB_GET_PRIVATE(setting); + if (priv->priority_strict[user_priority] != uint_strict) { + priv->priority_strict[user_priority] = uint_strict; + _notify(setting, PROP_PRIORITY_STRICT_BANDWIDTH); + } +} + +/** + * nm_setting_dcb_get_priority_traffic_class: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to retrieve the traffic class for + * + * Returns: the traffic class assigned to @user_priority. These values are only + * valid when #NMSettingDcb:priority-group-flags includes the + * %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +guint +nm_setting_dcb_get_priority_traffic_class(NMSettingDcb *setting, guint user_priority) +{ + g_return_val_if_fail(NM_IS_SETTING_DCB(setting), 0); + g_return_val_if_fail(user_priority <= 7, FALSE); + + return NM_SETTING_DCB_GET_PRIVATE(setting)->priority_traffic_class[user_priority]; +} + +/** + * nm_setting_dcb_set_priority_traffic_clas: + * @setting: the #NMSettingDcb + * @user_priority: the User Priority (0 - 7) to set the bandwidth percentage for + * @traffic_class: the traffic_class (0 - 7) that @user_priority should map to + * + * These values are only valid when #NMSettingDcb:priority-group-flags includes + * the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ +void +nm_setting_dcb_set_priority_traffic_class(NMSettingDcb *setting, + guint user_priority, + guint traffic_class) +{ + NMSettingDcbPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_DCB(setting)); + g_return_if_fail(user_priority <= 7); + g_return_if_fail(traffic_class <= 7); + + priv = NM_SETTING_DCB_GET_PRIVATE(setting); + if (priv->priority_traffic_class[user_priority] != traffic_class) { + priv->priority_traffic_class[user_priority] = traffic_class; + _notify(setting, PROP_PRIORITY_TRAFFIC_CLASS); + } +} + +/*****************************************************************************/ + +#define DCB_FLAGS_ALL \ + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING) + +static gboolean +check_dcb_flags(NMSettingDcbFlags flags, const char *prop_name, GError **error) +{ + if (flags & ~DCB_FLAGS_ALL) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("flags invalid")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, prop_name); + return FALSE; + } + + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE) && (flags & ~NM_SETTING_DCB_FLAG_ENABLE)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("flags invalid - disabled")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, prop_name); + return FALSE; + } + + return TRUE; +} + +static gboolean +check_uint_array(const guint * array, + guint len, + NMSettingDcbFlags flags, + guint max, + guint extra, + gboolean sum_pct, + const char * prop_name, + GError ** error) +{ + guint i, sum = 0; + + /* Ensure each element is <= to max or equals extra */ + for (i = 0; i < len; i++) { + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE) && array[i]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property invalid (not enabled)")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, prop_name); + return FALSE; + } + + if ((array[i] > max) && (array[i] != extra)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("element invalid")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, prop_name); + return FALSE; + } + sum += array[i]; + } + + /* Verify sum of percentages */ + if (sum_pct) { + if (flags & NM_SETTING_DCB_FLAG_ENABLE) { + /* If the feature is enabled, sum must equal 100% */ + if (sum != 100) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("sum not 100%")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, prop_name); + return FALSE; + } + } else { + /* If the feature is disabled, sum must equal 0%, which was checked + * by the for() loop above. + */ + g_assert_cmpint(sum, ==, 0); + } + } + + return TRUE; +} + +static gboolean +check_priority(int val, NMSettingDcbFlags flags, const char *prop_name, GError **error) +{ + if (!(flags & NM_SETTING_DCB_FLAG_ENABLE) && (val >= 0)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property invalid (not enabled)")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, prop_name); + return FALSE; + } + + if (val < -1 || val > 7) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property invalid")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, prop_name); + return FALSE; + } + return TRUE; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingDcbPrivate *priv = NM_SETTING_DCB_GET_PRIVATE(setting); + + if (!check_dcb_flags(priv->app_fcoe_flags, NM_SETTING_DCB_APP_FCOE_FLAGS, error)) + return FALSE; + + if (!check_priority(priv->app_fcoe_priority, + priv->app_fcoe_flags, + NM_SETTING_DCB_APP_FCOE_PRIORITY, + error)) + return FALSE; + + if (!priv->app_fcoe_mode) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property missing")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, NM_SETTING_DCB_APP_FCOE_MODE); + return FALSE; + } + + if (strcmp(priv->app_fcoe_mode, NM_SETTING_DCB_FCOE_MODE_FABRIC) + && strcmp(priv->app_fcoe_mode, NM_SETTING_DCB_FCOE_MODE_VN2VN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property invalid")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_DCB_SETTING_NAME, NM_SETTING_DCB_APP_FCOE_MODE); + return FALSE; + } + + if (!check_dcb_flags(priv->app_iscsi_flags, NM_SETTING_DCB_APP_ISCSI_FLAGS, error)) + return FALSE; + + if (!check_priority(priv->app_iscsi_priority, + priv->app_iscsi_flags, + NM_SETTING_DCB_APP_ISCSI_PRIORITY, + error)) + return FALSE; + + if (!check_dcb_flags(priv->app_fip_flags, NM_SETTING_DCB_APP_FIP_FLAGS, error)) + return FALSE; + + if (!check_priority(priv->app_fip_priority, + priv->app_fip_flags, + NM_SETTING_DCB_APP_FIP_PRIORITY, + error)) + return FALSE; + + if (!check_dcb_flags(priv->pfc_flags, NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, error)) + return FALSE; + + if (!check_uint_array(priv->pfc, + G_N_ELEMENTS(priv->pfc), + priv->pfc_flags, + 1, + 0, + FALSE, + NM_SETTING_DCB_PRIORITY_FLOW_CONTROL, + error)) + return FALSE; + + if (!check_dcb_flags(priv->priority_group_flags, NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, error)) + return FALSE; + + if (!check_uint_array(priv->priority_group_id, + G_N_ELEMENTS(priv->priority_group_id), + priv->priority_group_flags, + 7, + 15, + FALSE, + NM_SETTING_DCB_PRIORITY_GROUP_ID, + error)) + return FALSE; + + if (!check_uint_array(priv->priority_group_bandwidth, + G_N_ELEMENTS(priv->priority_group_bandwidth), + priv->priority_group_flags, + 100, + 0, + TRUE, + NM_SETTING_DCB_PRIORITY_GROUP_BANDWIDTH, + error)) + return FALSE; + + /* FIXME: sum bandwidths in each group */ + if (!check_uint_array(priv->priority_bandwidth, + G_N_ELEMENTS(priv->priority_bandwidth), + priv->priority_group_flags, + 100, + 0, + FALSE, + NM_SETTING_DCB_PRIORITY_BANDWIDTH, + error)) + return FALSE; + + if (!check_uint_array(priv->priority_strict, + G_N_ELEMENTS(priv->priority_strict), + priv->priority_group_flags, + 1, + 0, + FALSE, + NM_SETTING_DCB_PRIORITY_STRICT_BANDWIDTH, + error)) + return FALSE; + + if (!check_uint_array(priv->priority_traffic_class, + G_N_ELEMENTS(priv->priority_traffic_class), + priv->priority_group_flags, + 7, + 0, + FALSE, + NM_SETTING_DCB_PRIORITY_TRAFFIC_CLASS, + error)) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ + +G_STATIC_ASSERT(sizeof(guint) == sizeof(gboolean)); + +static void +set_array_from_gvalue(const GValue *v, uint *a, size_t len) +{ + GArray * src = g_value_get_boxed(v); + const guint total_len = len * sizeof(a[0]); + + memset(a, 0, total_len); + if (src) { + g_return_if_fail(g_array_get_element_size(src) == sizeof(a[0])); + g_return_if_fail(src->len == len); + memcpy(a, src->data, total_len); + } +} +#define SET_ARRAY_FROM_GVALUE(v, a) set_array_from_gvalue(v, a, G_N_ELEMENTS(a)) + +static void +set_gvalue_from_array(GValue *v, uint *a, size_t len) +{ + GArray *src = g_array_sized_new(FALSE, TRUE, sizeof(guint), len); + + g_array_append_vals(src, a, len); + g_value_take_boxed(v, src); +} + +#define SET_GVALUE_FROM_ARRAY(v, a) set_gvalue_from_array(v, a, G_N_ELEMENTS(a)) + +static void +_nm_setting_dcb_uint_array_from_dbus(GVariant *dbus_value, GValue *prop_value) +{ + gconstpointer array; + gsize length; + + array = g_variant_get_fixed_array(dbus_value, &length, sizeof(guint32)); + set_gvalue_from_array(prop_value, (guint *) array, length); +} + +static const NMSettInfoPropertType nm_sett_info_propert_type_dcb_au = + NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT( + NM_G_VARIANT_TYPE("au"), + .typdata_to_dbus.gprop_type = NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_GARRAY_UINT, + .gprop_from_dbus_fcn = _nm_setting_dcb_uint_array_from_dbus, ); + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingDcb * setting = NM_SETTING_DCB(object); + NMSettingDcbPrivate *priv = NM_SETTING_DCB_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_APP_FCOE_FLAGS: + g_value_set_flags(value, priv->app_fcoe_flags); + break; + case PROP_APP_FCOE_PRIORITY: + g_value_set_int(value, priv->app_fcoe_priority); + break; + case PROP_APP_FCOE_MODE: + g_value_set_string(value, priv->app_fcoe_mode); + break; + case PROP_APP_ISCSI_FLAGS: + g_value_set_flags(value, priv->app_iscsi_flags); + break; + case PROP_APP_ISCSI_PRIORITY: + g_value_set_int(value, priv->app_iscsi_priority); + break; + case PROP_APP_FIP_FLAGS: + g_value_set_flags(value, priv->app_fip_flags); + break; + case PROP_APP_FIP_PRIORITY: + g_value_set_int(value, priv->app_fip_priority); + break; + case PROP_PFC_FLAGS: + g_value_set_flags(value, priv->pfc_flags); + break; + case PROP_PRIORITY_FLOW_CONTROL: + SET_GVALUE_FROM_ARRAY(value, priv->pfc); + break; + case PROP_PRIORITY_GROUP_FLAGS: + g_value_set_flags(value, priv->priority_group_flags); + break; + case PROP_PRIORITY_GROUP_ID: + SET_GVALUE_FROM_ARRAY(value, priv->priority_group_id); + break; + case PROP_PRIORITY_GROUP_BANDWIDTH: + SET_GVALUE_FROM_ARRAY(value, priv->priority_group_bandwidth); + break; + case PROP_PRIORITY_BANDWIDTH: + SET_GVALUE_FROM_ARRAY(value, priv->priority_bandwidth); + break; + case PROP_PRIORITY_STRICT_BANDWIDTH: + SET_GVALUE_FROM_ARRAY(value, priv->priority_strict); + break; + case PROP_PRIORITY_TRAFFIC_CLASS: + SET_GVALUE_FROM_ARRAY(value, priv->priority_traffic_class); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingDcbPrivate *priv = NM_SETTING_DCB_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_APP_FCOE_FLAGS: + priv->app_fcoe_flags = g_value_get_flags(value); + break; + case PROP_APP_FCOE_PRIORITY: + priv->app_fcoe_priority = g_value_get_int(value); + break; + case PROP_APP_FCOE_MODE: + g_free(priv->app_fcoe_mode); + priv->app_fcoe_mode = g_value_dup_string(value); + break; + case PROP_APP_ISCSI_FLAGS: + priv->app_iscsi_flags = g_value_get_flags(value); + break; + case PROP_APP_ISCSI_PRIORITY: + priv->app_iscsi_priority = g_value_get_int(value); + break; + case PROP_APP_FIP_FLAGS: + priv->app_fip_flags = g_value_get_flags(value); + break; + case PROP_APP_FIP_PRIORITY: + priv->app_fip_priority = g_value_get_int(value); + break; + case PROP_PFC_FLAGS: + priv->pfc_flags = g_value_get_flags(value); + break; + case PROP_PRIORITY_FLOW_CONTROL: + SET_ARRAY_FROM_GVALUE(value, priv->pfc); + break; + case PROP_PRIORITY_GROUP_FLAGS: + priv->priority_group_flags = g_value_get_flags(value); + break; + case PROP_PRIORITY_GROUP_ID: + SET_ARRAY_FROM_GVALUE(value, priv->priority_group_id); + break; + case PROP_PRIORITY_GROUP_BANDWIDTH: + SET_ARRAY_FROM_GVALUE(value, priv->priority_group_bandwidth); + break; + case PROP_PRIORITY_BANDWIDTH: + SET_ARRAY_FROM_GVALUE(value, priv->priority_bandwidth); + break; + case PROP_PRIORITY_STRICT_BANDWIDTH: + SET_ARRAY_FROM_GVALUE(value, priv->priority_strict); + break; + case PROP_PRIORITY_TRAFFIC_CLASS: + SET_ARRAY_FROM_GVALUE(value, priv->priority_traffic_class); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_dcb_init(NMSettingDcb *self) +{ + NMSettingDcbPrivate *priv = NM_SETTING_DCB_GET_PRIVATE(self); + + priv->app_fcoe_mode = g_strdup(NM_SETTING_DCB_FCOE_MODE_FABRIC); + priv->app_fcoe_priority = -1; + priv->app_fip_priority = -1; + priv->app_iscsi_priority = -1; +} + +/** + * nm_setting_dcb_new: + * + * Creates a new #NMSettingDcb object with default values. + * + * Returns: (transfer full): the new empty #NMSettingDcb object + **/ +NMSetting * +nm_setting_dcb_new(void) +{ + return g_object_new(NM_TYPE_SETTING_DCB, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingDcbPrivate *priv = NM_SETTING_DCB_GET_PRIVATE(object); + + g_free(priv->app_fcoe_mode); + + G_OBJECT_CLASS(nm_setting_dcb_parent_class)->finalize(object); +} + +static void +nm_setting_dcb_class_init(NMSettingDcbClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingDcbPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingDcb:app-fcoe-flags: + * + * Specifies the #NMSettingDcbFlags for the DCB FCoE application. Flags may + * be any combination of %NM_SETTING_DCB_FLAG_ENABLE, + * %NM_SETTING_DCB_FLAG_ADVERTISE, and %NM_SETTING_DCB_FLAG_WILLING. + **/ + /* ---ifcfg-rh--- + * property: app-fcoe-flags + * variable: DCB_APP_FCOE_ENABLE, DCB_APP_FCOE_ADVERTISE, DCB_APP_FCOE_WILLING + * description: FCOE flags. + * default: no + * example: DCB_APP_FCOE_ENABLE=yes DCB_APP_FCOE_ADVERTISE=yes + * ---end--- + */ + obj_properties[PROP_APP_FCOE_FLAGS] = + g_param_spec_flags(NM_SETTING_DCB_APP_FCOE_FLAGS, + "", + "", + NM_TYPE_SETTING_DCB_FLAGS, + NM_SETTING_DCB_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:app-fcoe-priority: + * + * The highest User Priority (0 - 7) which FCoE frames should use, or -1 for + * default priority. Only used when the #NMSettingDcb:app-fcoe-flags + * property includes the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ + /* ---ifcfg-rh--- + * property: app-fcoe-priority + * variable: DCB_APP_FCOE_PRIORITY + * values: 0 - 7 + * description: Priority of FCoE frames. + * ---end--- + */ + obj_properties[PROP_APP_FCOE_PRIORITY] = + g_param_spec_int(NM_SETTING_DCB_APP_FCOE_PRIORITY, + "", + "", + -1, + 7, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:app-fcoe-mode: + * + * The FCoE controller mode; either %NM_SETTING_DCB_FCOE_MODE_FABRIC + * (default) or %NM_SETTING_DCB_FCOE_MODE_VN2VN. + **/ + /* ---ifcfg-rh--- + * property: app-fcoe-mode + * variable: DCB_APP_FCOE_MODE + * values: fabric, vn2vn + * default: fabric + * description: FCoE controller mode. + * ---end--- + */ + obj_properties[PROP_APP_FCOE_MODE] = + g_param_spec_string(NM_SETTING_DCB_APP_FCOE_MODE, + "", + "", + NM_SETTING_DCB_FCOE_MODE_FABRIC, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:app-iscsi-flags: + * + * Specifies the #NMSettingDcbFlags for the DCB iSCSI application. Flags + * may be any combination of %NM_SETTING_DCB_FLAG_ENABLE, + * %NM_SETTING_DCB_FLAG_ADVERTISE, and %NM_SETTING_DCB_FLAG_WILLING. + **/ + /* ---ifcfg-rh--- + * property: app-iscsi-flags + * variable: DCB_APP_ISCSI_ENABLE, DCB_APP_ISCSI_ADVERTISE, DCB_APP_ISCSI_WILLING + * default: no + * description: iSCSI flags. + * ---end--- + */ + obj_properties[PROP_APP_ISCSI_FLAGS] = + g_param_spec_flags(NM_SETTING_DCB_APP_ISCSI_FLAGS, + "", + "", + NM_TYPE_SETTING_DCB_FLAGS, + NM_SETTING_DCB_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:app-iscsi-priority: + * + * The highest User Priority (0 - 7) which iSCSI frames should use, or -1 + * for default priority. Only used when the #NMSettingDcb:app-iscsi-flags + * property includes the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ + /* ---ifcfg-rh--- + * property: app-iscsi-priority + * variable: DCB_APP_ISCSI_PRIORITY + * values: 0 - 7 + * description: Priority of iSCSI frames. + * ---end--- + */ + obj_properties[PROP_APP_ISCSI_PRIORITY] = + g_param_spec_int(NM_SETTING_DCB_APP_ISCSI_PRIORITY, + "", + "", + -1, + 7, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:app-fip-flags: + * + * Specifies the #NMSettingDcbFlags for the DCB FIP application. Flags may + * be any combination of %NM_SETTING_DCB_FLAG_ENABLE, + * %NM_SETTING_DCB_FLAG_ADVERTISE, and %NM_SETTING_DCB_FLAG_WILLING. + **/ + /* ---ifcfg-rh--- + * property: app-fip-flags + * variable: DCB_APP_FIP_ENABLE, DCB_APP_FIP_ADVERTISE, DCB_APP_FIP_WILLING + * default: no + * description: FIP flags. + * ---end--- + */ + obj_properties[PROP_APP_FIP_FLAGS] = + g_param_spec_flags(NM_SETTING_DCB_APP_FIP_FLAGS, + "", + "", + NM_TYPE_SETTING_DCB_FLAGS, + NM_SETTING_DCB_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:app-fip-priority: + * + * The highest User Priority (0 - 7) which FIP frames should use, or -1 for + * default priority. Only used when the #NMSettingDcb:app-fip-flags + * property includes the %NM_SETTING_DCB_FLAG_ENABLE flag. + **/ + /* ---ifcfg-rh--- + * property: app-fip-priority + * variable: DCB_APP_FIP_PRIORITY + * values: 0 - 7 + * description: Priority of FIP frames. + * ---end--- + */ + obj_properties[PROP_APP_FIP_PRIORITY] = + g_param_spec_int(NM_SETTING_DCB_APP_FIP_PRIORITY, + "", + "", + -1, + 7, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:priority-flow-control-flags: + * + * Specifies the #NMSettingDcbFlags for DCB Priority Flow Control (PFC). + * Flags may be any combination of %NM_SETTING_DCB_FLAG_ENABLE, + * %NM_SETTING_DCB_FLAG_ADVERTISE, and %NM_SETTING_DCB_FLAG_WILLING. + **/ + /* ---ifcfg-rh--- + * property: priority-flow-control-flags + * variable: DCB_PFC_ENABLE, DCB_PFC_ADVERTISE, DCB_PFC_WILLING + * default: no + * description: Priority flow control flags. + * ---end--- + */ + obj_properties[PROP_PFC_FLAGS] = g_param_spec_flags(NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, + "", + "", + NM_TYPE_SETTING_DCB_FLAGS, + NM_SETTING_DCB_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:priority-flow-control: (type GArray(gboolean)) + * + * An array of 8 boolean values, where the array index corresponds to the User + * Priority (0 - 7) and the value indicates whether or not the corresponding + * priority should transmit priority pause. + **/ + /* ---ifcfg-rh--- + * property: priority-flow-control + * variable: DCB_PFC_UP + * description: Priority flow control values. String of 8 "0" and "1", where "0". + * means "do not transmit priority pause", "1" means "transmit pause". + * example: DCB_PFC_UP=01101110 + * ---end--- + */ + obj_properties[PROP_PRIORITY_FLOW_CONTROL] = + g_param_spec_boxed(NM_SETTING_DCB_PRIORITY_FLOW_CONTROL, + "", + "", + G_TYPE_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_PRIORITY_FLOW_CONTROL], + &nm_sett_info_propert_type_dcb_au); + + /** + * NMSettingDcb:priority-group-flags: + * + * Specifies the #NMSettingDcbFlags for DCB Priority Groups. Flags may be + * any combination of %NM_SETTING_DCB_FLAG_ENABLE, + * %NM_SETTING_DCB_FLAG_ADVERTISE, and %NM_SETTING_DCB_FLAG_WILLING. + **/ + /* ---ifcfg-rh--- + * property: priority-group-flags + * variable: DCB_PG_ENABLE, DCB_PG_ADVERTISE, DCB_PG_WILLING + * default: no + * description: Priority groups flags. + * ---end--- + */ + obj_properties[PROP_PRIORITY_GROUP_FLAGS] = + g_param_spec_flags(NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, + "", + "", + NM_TYPE_SETTING_DCB_FLAGS, + NM_SETTING_DCB_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingDcb:priority-group-id: (type GArray(guint)) + * + * An array of 8 uint values, where the array index corresponds to the User + * Priority (0 - 7) and the value indicates the Priority Group ID. Allowed + * Priority Group ID values are 0 - 7 or 15 for the unrestricted group. + **/ + /* ---ifcfg-rh--- + * property: priority-group-id + * variable: DCB_PG_ID + * description: Priority groups values. String of eight priorities (0 - 7) or "f" + * (unrestricted). + * example: DCB_PG_ID=1205f173 + * ---end--- + */ + obj_properties[PROP_PRIORITY_GROUP_ID] = + g_param_spec_boxed(NM_SETTING_DCB_PRIORITY_GROUP_ID, + "", + "", + G_TYPE_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_PRIORITY_GROUP_ID], + &nm_sett_info_propert_type_dcb_au); + + /** + * NMSettingDcb:priority-group-bandwidth: (type GArray(guint)) + * + * An array of 8 uint values, where the array index corresponds to the + * Priority Group ID (0 - 7) and the value indicates the percentage of link + * bandwidth allocated to that group. Allowed values are 0 - 100, and the + * sum of all values must total 100 percents. + **/ + /* ---ifcfg-rh--- + * property: priority-group-bandwidth + * variable: DCB_PG_PCT + * description: Priority groups values. Eight bandwidths (in percent), separated with commas. + * example: DCB_PG_PCT=10,5,10,15,10,10,10,30 + * ---end--- + */ + obj_properties[PROP_PRIORITY_GROUP_BANDWIDTH] = + g_param_spec_boxed(NM_SETTING_DCB_PRIORITY_GROUP_BANDWIDTH, + "", + "", + G_TYPE_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_PRIORITY_GROUP_BANDWIDTH], + &nm_sett_info_propert_type_dcb_au); + + /** + * NMSettingDcb:priority-bandwidth: (type GArray(guint)) + * + * An array of 8 uint values, where the array index corresponds to the User + * Priority (0 - 7) and the value indicates the percentage of bandwidth of + * the priority's assigned group that the priority may use. The sum of all + * percentages for priorities which belong to the same group must total 100 + * percents. + **/ + /* ---ifcfg-rh--- + * property: priority-bandwidth + * variable: DCB_PG_UPPCT + * description: Priority values. Eight bandwidths (in percent), separated with commas. + * The sum of the numbers must be 100. + * example: DCB_PG_UPPCT=7,13,10,10,15,15,10,20 + * ---end--- + */ + obj_properties[PROP_PRIORITY_BANDWIDTH] = + g_param_spec_boxed(NM_SETTING_DCB_PRIORITY_BANDWIDTH, + "", + "", + G_TYPE_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_PRIORITY_BANDWIDTH], + &nm_sett_info_propert_type_dcb_au); + + /** + * NMSettingDcb:priority-strict-bandwidth: (type GArray(gboolean)) + * + * An array of 8 boolean values, where the array index corresponds to the User + * Priority (0 - 7) and the value indicates whether or not the priority may + * use all of the bandwidth allocated to its assigned group. + **/ + /* ---ifcfg-rh--- + * property: priority-strict-bandwidth + * variable: DCB_PG_STRICT + * description: Priority values. String of eight "0" or "1", where "0" means + * "may not utilize all bandwidth", "1" means "may utilize all bandwidth". + * example: DCB_PG_STRICT=01101110 + * ---end--- + */ + obj_properties[PROP_PRIORITY_STRICT_BANDWIDTH] = + g_param_spec_boxed(NM_SETTING_DCB_PRIORITY_STRICT_BANDWIDTH, + "", + "", + G_TYPE_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_PRIORITY_STRICT_BANDWIDTH], + &nm_sett_info_propert_type_dcb_au); + + /** + * NMSettingDcb:priority-traffic-class: (type GArray(guint)) + * + * An array of 8 uint values, where the array index corresponds to the User + * Priority (0 - 7) and the value indicates the traffic class (0 - 7) to + * which the priority is mapped. + **/ + /* ---ifcfg-rh--- + * property: priority-traffic-class + * variable: DCB_PG_UP2TC + * description: Priority values. String of eight traffic class values (0 - 7). + * example: DCB_PG_UP2TC=01623701 + * ---end--- + */ + obj_properties[PROP_PRIORITY_TRAFFIC_CLASS] = + g_param_spec_boxed(NM_SETTING_DCB_PRIORITY_TRAFFIC_CLASS, + "", + "", + G_TYPE_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_PRIORITY_TRAFFIC_CLASS], + &nm_sett_info_propert_type_dcb_au); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_DCB, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-dummy.c b/src/libnm-core-impl/nm-setting-dummy.c new file mode 100644 index 00000000..a998a27b --- /dev/null +++ b/src/libnm-core-impl/nm-setting-dummy.c @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-dummy.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-dummy + * @short_description: Describes connection properties for dummy interfaces + * + * The #NMSettingDummy object is a #NMSetting subclass that describes properties + * necessary for connection to dummy devices + **/ + +/*****************************************************************************/ + +/** + * NMSettingDummy: + * + * Dummy Link Settings + */ +struct _NMSettingDummy { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingDummyClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingDummy, nm_setting_dummy, NM_TYPE_SETTING) + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ + +static void +nm_setting_dummy_init(NMSettingDummy *setting) +{} + +/** + * nm_setting_dummy_new: + * + * Creates a new #NMSettingDummy object with default values. + * + * Returns: (transfer full): the new empty #NMSettingDummy object + * + * Since: 1.8 + **/ +NMSetting * +nm_setting_dummy_new(void) +{ + return g_object_new(NM_TYPE_SETTING_DUMMY, NULL); +} + +static void +nm_setting_dummy_class_init(NMSettingDummyClass *klass) +{ + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + setting_class->verify = verify; + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_DUMMY); +} diff --git a/src/libnm-core-impl/nm-setting-ethtool.c b/src/libnm-core-impl/nm-setting-ethtool.c new file mode 100644 index 00000000..abedcae3 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ethtool.c @@ -0,0 +1,417 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ethtool.h" + +#include "nm-setting-private.h" +#include "libnm-base/nm-ethtool-base.h" +#include "libnm-base/nm-ethtool-utils-base.h" + +/*****************************************************************************/ + +/** + * SECTION:nm-setting-ethtool + * @short_description: Describes connection properties for ethtool related options + * + * The #NMSettingEthtool object is a #NMSetting subclass that describes properties + * to control network driver and hardware settings. + **/ + +/*****************************************************************************/ + +static const GVariantType * +get_variant_type_from_ethtool_id(NMEthtoolID ethtool_id) +{ + if (nm_ethtool_id_is_feature(ethtool_id) || nm_ethtool_id_is_pause(ethtool_id)) + return G_VARIANT_TYPE_BOOLEAN; + + if (nm_ethtool_id_is_coalesce(ethtool_id) || nm_ethtool_id_is_ring(ethtool_id)) + return G_VARIANT_TYPE_UINT32; + + return NULL; +} + +/*****************************************************************************/ + +/** + * nm_ethtool_optname_is_feature: + * @optname: (allow-none): the option name to check + * + * Checks whether @optname is a valid option name for an offload feature. + * + * %Returns: %TRUE, if @optname is valid + * + * Since: 1.20 + * + * Note that nm_ethtool_optname_is_feature() was first added to the libnm header files + * in 1.14.0 but forgot to actually add to the library. This happened belatedly in 1.20.0 and + * the stable versions 1.18.2, 1.16.4 and 1.14.8 (with linker version "libnm_1_14_8"). + */ +gboolean +nm_ethtool_optname_is_feature(const char *optname) +{ + return optname && nm_ethtool_id_is_feature(nm_ethtool_id_get_by_name(optname)); +} + +/** + * nm_ethtool_optname_is_coalesce: + * @optname: (allow-none): the option name to check + * + * Checks whether @optname is a valid option name for a coalesce setting. + * + * %Returns: %TRUE, if @optname is valid + * + * Since: 1.26 + */ +gboolean +nm_ethtool_optname_is_coalesce(const char *optname) +{ + return optname && nm_ethtool_id_is_coalesce(nm_ethtool_id_get_by_name(optname)); +} + +/** + * nm_ethtool_optname_is_ring: + * @optname: (allow-none): the option name to check + * + * Checks whether @optname is a valid option name for a ring setting. + * + * %Returns: %TRUE, if @optname is valid + * + * Since: 1.26 + */ +gboolean +nm_ethtool_optname_is_ring(const char *optname) +{ + return optname && nm_ethtool_id_is_ring(nm_ethtool_id_get_by_name(optname)); +} + +/** + * nm_ethtool_optname_is_pause: + * @optname: (allow-none): the option name to check + * + * Checks whether @optname is a valid option name for a pause setting. + * + * %Returns: %TRUE, if @optname is valid + * + * Since: 1.32 + */ +gboolean +nm_ethtool_optname_is_pause(const char *optname) +{ + return optname && nm_ethtool_id_is_pause(nm_ethtool_id_get_by_name(optname)); +} + +/*****************************************************************************/ + +/** + * NMSettingEthtool: + * + * Ethtool Ethernet Settings + * + * Since: 1.14 + */ +struct _NMSettingEthtool { + NMSetting parent; +}; + +struct _NMSettingEthtoolClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingEthtool, nm_setting_ethtool, NM_TYPE_SETTING) + +#define NM_SETTING_ETHTOOL_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSettingEthtool, NM_IS_SETTING_ETHTOOL, NMSetting) + +/*****************************************************************************/ + +/** + * nm_setting_ethtool_get_feature: + * @setting: the #NMSettingEthtool + * @optname: option name of the offload feature to get + * + * Gets and offload feature setting. Returns %NM_TERNARY_DEFAULT if the + * feature is not set. + * + * Note that @optname must be a valid name for a feature, according to + * nm_ethtool_optname_is_feature(). + * + * Returns: a #NMTernary value indicating whether the offload feature + * is enabled, disabled, or left untouched. + * + * Since: 1.14 + * + * Deprecated: 1.26: use nm_setting_option_get_boolean() instead. + */ +NMTernary +nm_setting_ethtool_get_feature(NMSettingEthtool *setting, const char *optname) +{ + gboolean v; + + g_return_val_if_fail(NM_IS_SETTING_ETHTOOL(setting), NM_TERNARY_DEFAULT); + g_return_val_if_fail(optname && nm_ethtool_optname_is_feature(optname), NM_TERNARY_DEFAULT); + + if (!nm_setting_option_get_boolean(NM_SETTING(setting), optname, &v)) + return NM_TERNARY_DEFAULT; + return v ? NM_TERNARY_TRUE : NM_TERNARY_FALSE; +} + +/** + * nm_setting_ethtool_set_feature: + * @setting: the #NMSettingEthtool + * @optname: option name of the offload feature to get + * @value: the new value to set. The special value %NM_TERNARY_DEFAULT + * means to clear the offload feature setting. + * + * Sets and offload feature setting. + * + * Note that @optname must be a valid name for a feature, according to + * nm_ethtool_optname_is_feature(). + * + * Since: 1.14 + * + * Deprecated: 1.26: use nm_setting_option_set() or nm_setting_option_set_boolean() instead. + */ +void +nm_setting_ethtool_set_feature(NMSettingEthtool *setting, const char *optname, NMTernary value) +{ + g_return_if_fail(NM_IS_SETTING_ETHTOOL(setting)); + g_return_if_fail(optname && nm_ethtool_optname_is_feature(optname)); + g_return_if_fail(NM_IN_SET(value, NM_TERNARY_DEFAULT, NM_TERNARY_FALSE, NM_TERNARY_TRUE)); + + if (value == NM_TERNARY_DEFAULT) + nm_setting_option_set(NM_SETTING(setting), optname, NULL); + else + nm_setting_option_set_boolean(NM_SETTING(setting), optname, (value != NM_TERNARY_FALSE)); +} + +/** + * nm_setting_ethtool_clear_features: + * @setting: the #NMSettingEthtool + * + * Clears all offload features settings + * + * Since: 1.14 + * + * Deprecated: 1.26: use nm_setting_option_clear_by_name() with nm_ethtool_optname_is_feature() predicate instead. + */ +void +nm_setting_ethtool_clear_features(NMSettingEthtool *setting) +{ + g_return_if_fail(NM_IS_SETTING_ETHTOOL(setting)); + + nm_setting_option_clear_by_name(NM_SETTING(setting), nm_ethtool_optname_is_feature); +} + +/*****************************************************************************/ + +guint +nm_setting_ethtool_init_features( + NMSettingEthtool *setting, + NMOptionBool * requested /* indexed by NMEthtoolID - _NM_ETHTOOL_ID_FEATURE_FIRST */) +{ + GHashTable * hash; + GHashTableIter iter; + guint i; + guint n_req = 0; + const char * name; + GVariant * variant; + + nm_assert(NM_IS_SETTING_ETHTOOL(setting)); + nm_assert(requested); + + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) + requested[i] = NM_OPTION_BOOL_DEFAULT; + + hash = _nm_setting_option_hash(NM_SETTING(setting), FALSE); + if (!hash) + return 0; + + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) { + NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name); + + if (!nm_ethtool_id_is_feature(ethtool_id)) + continue; + if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) + continue; + + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(ethtool_id)] = + g_variant_get_boolean(variant) ? NM_OPTION_BOOL_TRUE : NM_OPTION_BOOL_FALSE; + n_req++; + } + + return n_req; +} + +/*****************************************************************************/ + +/** + * nm_setting_ethtool_get_optnames: + * @setting: the #NMSettingEthtool instance. + * @out_length: (out) (optional): return location for the number of keys returned, or %NULL + * + * This returns all options names that are set. This includes the feature names + * like %NM_ETHTOOL_OPTNAME_FEATURE_GRO. See nm_ethtool_optname_is_feature() to + * check whether the option name is valid for offload features. + * + * Returns: (array zero-terminated=1) (transfer container): list of set option + * names or %NULL if no options are set. The option names are still owned by + * @setting and may get invalidated when @setting gets modified. + * + * Since: 1.20 + * + * Deprecated: 1.26: use nm_setting_option_get_all_names() instead. + */ +const char ** +nm_setting_ethtool_get_optnames(NMSettingEthtool *setting, guint *out_length) +{ + const char *const *names; + guint len = 0; + + g_return_val_if_fail(NM_IS_SETTING_ETHTOOL(setting), NULL); + + names = nm_setting_option_get_all_names(NM_SETTING(setting), &len); + NM_SET_OUT(out_length, len); + return len > 0 ? nm_memdup(names, sizeof(names[0]) * (((gsize) len) + 1u)) : NULL; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + const char *const *optnames; + GVariant *const * variants; + guint len; + guint i; + NMTernary pause_autoneg = NM_TERNARY_DEFAULT; + NMTernary pause_tx = NM_TERNARY_DEFAULT; + NMTernary pause_rx = NM_TERNARY_DEFAULT; + + len = _nm_setting_option_get_all(setting, &optnames, &variants); + + for (i = 0; i < len; i++) { + const char * optname = optnames[i]; + GVariant * variant = variants[i]; + const GVariantType *variant_type; + NMEthtoolID ethtool_id; + + ethtool_id = nm_ethtool_id_get_by_name(optname); + variant_type = get_variant_type_from_ethtool_id(ethtool_id); + + if (!variant_type) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unsupported ethtool setting")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_ETHTOOL_SETTING_NAME, optname); + return FALSE; + } + + if (!g_variant_is_of_type(variant, variant_type)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("setting has invalid variant type")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_ETHTOOL_SETTING_NAME, optname); + return FALSE; + } + + if (NM_IN_SET(ethtool_id, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_RX, + NM_ETHTOOL_ID_COALESCE_ADAPTIVE_TX)) { + if (!NM_IN_SET(g_variant_get_uint32(variant), 0, 1)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("coalesce option must be either 0 or 1")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_ETHTOOL_SETTING_NAME, optname); + return FALSE; + } + } else if (NM_IN_SET(ethtool_id, NM_ETHTOOL_ID_PAUSE_AUTONEG)) + pause_autoneg = g_variant_get_boolean(variant); + else if (NM_IN_SET(ethtool_id, NM_ETHTOOL_ID_PAUSE_RX)) + pause_rx = g_variant_get_boolean(variant); + else if (NM_IN_SET(ethtool_id, NM_ETHTOOL_ID_PAUSE_TX)) + pause_tx = g_variant_get_boolean(variant); + } + + if (pause_rx != NM_TERNARY_DEFAULT || pause_tx != NM_TERNARY_DEFAULT) { + if (pause_autoneg == NM_TERNARY_TRUE) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("pause-autoneg cannot be enabled when setting rx/tx options")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_ETHTOOL_SETTING_NAME, + NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG); + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + +static const GVariantType * +get_variant_type(const NMSettInfoSetting *sett_info, const char *name, GError **error) +{ + const GVariantType *variant_type; + + variant_type = get_variant_type_from_ethtool_id(nm_ethtool_id_get_by_name(name)); + + if (!variant_type) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unknown ethtool option '%s'"), + name); + return NULL; + } + + return variant_type; +} + +/*****************************************************************************/ + +static void +nm_setting_ethtool_init(NMSettingEthtool *setting) +{} + +/** + * nm_setting_ethtool_new: + * + * Creates a new #NMSettingEthtool object with default values. + * + * Returns: (transfer full): the new empty #NMSettingEthtool object + * + * Since: 1.14 + **/ +NMSetting * +nm_setting_ethtool_new(void) +{ + return g_object_new(NM_TYPE_SETTING_ETHTOOL, NULL); +} + +static void +nm_setting_ethtool_class_init(NMSettingEthtoolClass *klass) +{ + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + setting_class->verify = verify; + + _nm_setting_class_commit_full( + setting_class, + NM_META_SETTING_TYPE_ETHTOOL, + NM_SETT_INFO_SETT_DETAIL(.gendata_info = + NM_SETT_INFO_SETT_GENDATA(.get_variant_type = + get_variant_type, ), ), + NULL); +} diff --git a/src/libnm-core-impl/nm-setting-generic.c b/src/libnm-core-impl/nm-setting-generic.c new file mode 100644 index 00000000..0eff1ddf --- /dev/null +++ b/src/libnm-core-impl/nm-setting-generic.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2013 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-generic.h" + +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-generic + * @short_description: Describes connection properties for generic devices + * + * The #NMSettingGeneric object is a #NMSetting subclass that describes + * optional properties that apply to "generic" devices (ie, devices that + * NetworkManager does not specifically recognize). + * + * There are currently no properties on this object; it exists only to be + * the "connection type" setting on #NMConnections for generic devices. + **/ + +/*****************************************************************************/ + +typedef struct { + int dummy; +} NMSettingGenericPrivate; + +/** + * NMSettingGeneric: + * + * Generic Link Settings + */ +struct _NMSettingGeneric { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingGenericClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingGeneric, nm_setting_generic, NM_TYPE_SETTING) + +#define NM_SETTING_GENERIC_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_GENERIC, NMSettingGenericPrivate)) + +/*****************************************************************************/ + +static void +nm_setting_generic_init(NMSettingGeneric *setting) +{} + +/** + * nm_setting_generic_new: + * + * Creates a new #NMSettingGeneric object with default values. + * + * Returns: (transfer full): the new empty #NMSettingGeneric object + **/ +NMSetting * +nm_setting_generic_new(void) +{ + return g_object_new(NM_TYPE_SETTING_GENERIC, NULL); +} + +static void +nm_setting_generic_class_init(NMSettingGenericClass *klass) +{ + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NMSettingGenericPrivate)); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_GENERIC); +} diff --git a/src/libnm-core-impl/nm-setting-gsm.c b/src/libnm-core-impl/nm-setting-gsm.c new file mode 100644 index 00000000..a177f7fb --- /dev/null +++ b/src/libnm-core-impl/nm-setting-gsm.c @@ -0,0 +1,886 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-gsm.h" + +#include "nm-utils.h" +#include "nm-setting-private.h" +#include "nm-core-enum-types.h" + +/** + * SECTION:nm-setting-gsm + * @short_description: Describes GSM/3GPP-based mobile broadband properties + * + * The #NMSettingGsm object is a #NMSetting subclass that describes + * properties that allow connections to 3GPP-based mobile broadband + * networks, including those using GPRS/EDGE and UMTS/HSPA technology. + */ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_AUTO_CONFIG, + PROP_NUMBER, + PROP_USERNAME, + PROP_PASSWORD, + PROP_PASSWORD_FLAGS, + PROP_APN, + PROP_NETWORK_ID, + PROP_PIN, + PROP_PIN_FLAGS, + PROP_HOME_ONLY, + PROP_DEVICE_ID, + PROP_SIM_ID, + PROP_SIM_OPERATOR_ID, + PROP_MTU, ); + +typedef struct { + char * number; /* For dialing, duh */ + char * username; + char * password; + char * device_id; + char * sim_id; + char * sim_operator_id; + char * apn; /* NULL for dynamic */ + char * network_id; /* for manual registration or NULL for automatic */ + char * pin; + NMSettingSecretFlags password_flags; + NMSettingSecretFlags pin_flags; + guint32 mtu; + bool auto_config : 1; + bool home_only : 1; +} NMSettingGsmPrivate; + +/** + * NMSettingGsm: + * + * GSM-based Mobile Broadband Settings + */ +struct _NMSettingGsm { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingGsmClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingGsm, nm_setting_gsm, NM_TYPE_SETTING) + +#define NM_SETTING_GSM_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_GSM, NMSettingGsmPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_gsm_get_auto_config: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:auto-config property of the setting + * + * Since: 1.22 + **/ +gboolean +nm_setting_gsm_get_auto_config(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->auto_config; +} + +/** + * nm_setting_gsm_get_number: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:number property of the setting + * + * Deprecated: 1.16: User-provided values for this setting are no longer used. + **/ +const char * +nm_setting_gsm_get_number(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->number; +} + +/** + * nm_setting_gsm_get_username: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:username property of the setting + **/ +const char * +nm_setting_gsm_get_username(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->username; +} + +/** + * nm_setting_gsm_get_password: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:password property of the setting + **/ +const char * +nm_setting_gsm_get_password(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->password; +} + +/** + * nm_setting_gsm_get_password_flags: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingSecretFlags pertaining to the #NMSettingGsm:password + **/ +NMSettingSecretFlags +nm_setting_gsm_get_password_flags(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->password_flags; +} + +/** + * nm_setting_gsm_get_apn: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:apn property of the setting + **/ +const char * +nm_setting_gsm_get_apn(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->apn; +} + +/** + * nm_setting_gsm_get_network_id: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:network-id property of the setting + **/ +const char * +nm_setting_gsm_get_network_id(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->network_id; +} + +/** + * nm_setting_gsm_get_pin: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:pin property of the setting + **/ +const char * +nm_setting_gsm_get_pin(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->pin; +} + +/** + * nm_setting_gsm_get_pin_flags: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingSecretFlags pertaining to the #NMSettingGsm:pin + **/ +NMSettingSecretFlags +nm_setting_gsm_get_pin_flags(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->pin_flags; +} + +/** + * nm_setting_gsm_get_home_only: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:home-only property of the setting + **/ +gboolean +nm_setting_gsm_get_home_only(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->home_only; +} + +/** + * nm_setting_gsm_get_device_id: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:device-id property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_gsm_get_device_id(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->device_id; +} + +/** + * nm_setting_gsm_get_sim_id: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:sim-id property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_gsm_get_sim_id(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->sim_id; +} + +/** + * nm_setting_gsm_get_sim_operator_id: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:sim-operator-id property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_gsm_get_sim_operator_id(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->sim_operator_id; +} + +/** + * nm_setting_gsm_get_mtu: + * @setting: the #NMSettingGsm + * + * Returns: the #NMSettingGsm:mtu property of the setting + * + * Since: 1.8 + **/ +guint32 +nm_setting_gsm_get_mtu(NMSettingGsm *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_GSM(setting), 0); + + return NM_SETTING_GSM_GET_PRIVATE(setting)->mtu; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingGsmPrivate *priv = NM_SETTING_GSM_GET_PRIVATE(setting); + + if (priv->number && !priv->number[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_NUMBER); + return FALSE; + } + + if (priv->apn) { + gsize apn_len = strlen(priv->apn); + gsize i; + + if (apn_len > 64) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property value '%s' is empty or too long (>64)"), + priv->apn); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_APN); + return FALSE; + } + + /* APNs roughly follow the same rules as DNS domain names. Allowed + * characters are a-z, 0-9, . and -. GSM 03.03 Section 9.1 states: + * + * The syntax of the APN shall follow the Name Syntax defined in + * RFC 2181 [14] and RFC 1035 [15]. The APN consists of one or + * more labels. Each label is coded as one octet length field + * followed by that number of octets coded as 8 bit ASCII characters. + * Following RFC 1035 [15] the labels should consist only of the + * alphabetic characters (A-Z and a-z), digits (0-9) and the + * dash (-). The case of alphabetic characters is not significant. + * + * A dot (.) is commonly used to separate parts of the APN, and + * apparently the underscore (_) is used as well. RFC 2181 indicates + * that no restrictions of any kind are placed on DNS labels, and thus + * it would appear that none are placed on APNs either, but many modems + * and networks will fail to accept APNs that include odd characters + * like space ( ) and such. + */ + for (i = 0; i < apn_len; i++) { + if (!g_ascii_isalnum(priv->apn[i]) && (priv->apn[i] != '.') && (priv->apn[i] != '_') + && (priv->apn[i] != '-')) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' contains invalid char(s) (use [A-Za-z._-])"), + priv->apn); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_APN); + return FALSE; + } + } + } + + if (priv->username && priv->username[0] == '\0') { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_USERNAME); + return FALSE; + } + + if (priv->network_id) { + gsize nid_len = strlen(priv->network_id); + gsize i; + + /* Accept both 5 and 6 digit MCC/MNC codes */ + if ((nid_len < 5) || (nid_len > 6)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' length is invalid (should be 5 or 6 digits)"), + priv->network_id); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_GSM_NETWORK_ID); + return FALSE; + } + + for (i = 0; i < nid_len; i++) { + if (!g_ascii_isdigit(priv->network_id[i])) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a number"), + priv->network_id); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_GSM_NETWORK_ID); + return FALSE; + } + } + } + + if (priv->device_id && !priv->device_id[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_DEVICE_ID); + return FALSE; + } + + if (priv->sim_id && !priv->sim_id[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_SIM_ID); + return FALSE; + } + + if (priv->sim_operator_id) { + size_t len = strlen(priv->sim_operator_id); + const char *p = priv->sim_operator_id; + + if (len == 0 || (len != 5 && len != 6)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty or wrong size")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_GSM_SIM_OPERATOR_ID); + return FALSE; + } + + while (p && *p) { + if (!g_ascii_isdigit(*p++)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property must contain only digits")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_GSM_SIM_OPERATOR_ID); + return FALSE; + } + } + } + + if (priv->auto_config && (priv->apn || priv->username || priv->password)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("can't be enabled when manual configuration is present")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_AUTO_CONFIG); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + return TRUE; +} + +static gboolean +verify_secrets(NMSetting *setting, NMConnection *connection, GError **error) +{ + return _nm_setting_verify_secret_string(NM_SETTING_GSM_GET_PRIVATE(setting)->password, + NM_SETTING_GSM_SETTING_NAME, + NM_SETTING_GSM_PASSWORD, + error); +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSettingGsmPrivate *priv = NM_SETTING_GSM_GET_PRIVATE(setting); + GPtrArray * secrets = NULL; + + if (priv->password && *priv->password) + return NULL; + + if (priv->username) { + if (!(priv->password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + secrets = g_ptr_array_sized_new(1); + g_ptr_array_add(secrets, NM_SETTING_GSM_PASSWORD); + } + } + + return secrets; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingGsm *setting = NM_SETTING_GSM(object); + + switch (prop_id) { + case PROP_AUTO_CONFIG: + g_value_set_boolean(value, nm_setting_gsm_get_auto_config(setting)); + break; + case PROP_NUMBER: + g_value_set_string(value, nm_setting_gsm_get_number(setting)); + break; + case PROP_USERNAME: + g_value_set_string(value, nm_setting_gsm_get_username(setting)); + break; + case PROP_PASSWORD: + g_value_set_string(value, nm_setting_gsm_get_password(setting)); + break; + case PROP_PASSWORD_FLAGS: + g_value_set_flags(value, nm_setting_gsm_get_password_flags(setting)); + break; + case PROP_APN: + g_value_set_string(value, nm_setting_gsm_get_apn(setting)); + break; + case PROP_NETWORK_ID: + g_value_set_string(value, nm_setting_gsm_get_network_id(setting)); + break; + case PROP_PIN: + g_value_set_string(value, nm_setting_gsm_get_pin(setting)); + break; + case PROP_PIN_FLAGS: + g_value_set_flags(value, nm_setting_gsm_get_pin_flags(setting)); + break; + case PROP_HOME_ONLY: + g_value_set_boolean(value, nm_setting_gsm_get_home_only(setting)); + break; + case PROP_DEVICE_ID: + g_value_set_string(value, nm_setting_gsm_get_device_id(setting)); + break; + case PROP_SIM_ID: + g_value_set_string(value, nm_setting_gsm_get_sim_id(setting)); + break; + case PROP_SIM_OPERATOR_ID: + g_value_set_string(value, nm_setting_gsm_get_sim_operator_id(setting)); + break; + case PROP_MTU: + g_value_set_uint(value, nm_setting_gsm_get_mtu(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingGsmPrivate *priv = NM_SETTING_GSM_GET_PRIVATE(object); + char * tmp; + + switch (prop_id) { + case PROP_AUTO_CONFIG: + priv->auto_config = g_value_get_boolean(value); + break; + case PROP_NUMBER: + g_free(priv->number); + priv->number = g_value_dup_string(value); + break; + case PROP_USERNAME: + g_free(priv->username); + priv->username = g_value_dup_string(value); + break; + case PROP_PASSWORD: + g_free(priv->password); + priv->password = g_value_dup_string(value); + break; + case PROP_PASSWORD_FLAGS: + priv->password_flags = g_value_get_flags(value); + break; + case PROP_APN: + g_free(priv->apn); + priv->apn = NULL; + tmp = g_value_dup_string(value); + if (tmp) + priv->apn = g_strstrip(tmp); + break; + case PROP_NETWORK_ID: + g_free(priv->network_id); + priv->network_id = NULL; + tmp = g_value_dup_string(value); + if (tmp) + priv->network_id = g_strstrip(tmp); + break; + case PROP_PIN: + g_free(priv->pin); + priv->pin = g_value_dup_string(value); + break; + case PROP_PIN_FLAGS: + priv->pin_flags = g_value_get_flags(value); + break; + case PROP_HOME_ONLY: + priv->home_only = g_value_get_boolean(value); + break; + case PROP_DEVICE_ID: + g_free(priv->device_id); + priv->device_id = g_value_dup_string(value); + break; + case PROP_SIM_ID: + g_free(priv->sim_id); + priv->sim_id = g_value_dup_string(value); + break; + case PROP_SIM_OPERATOR_ID: + g_free(priv->sim_operator_id); + priv->sim_operator_id = g_value_dup_string(value); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_gsm_init(NMSettingGsm *setting) +{} + +/** + * nm_setting_gsm_new: + * + * Creates a new #NMSettingGsm object with default values. + * + * Returns: the new empty #NMSettingGsm object + **/ +NMSetting * +nm_setting_gsm_new(void) +{ + return g_object_new(NM_TYPE_SETTING_GSM, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingGsmPrivate *priv = NM_SETTING_GSM_GET_PRIVATE(object); + + g_free(priv->number); + g_free(priv->username); + g_free(priv->password); + g_free(priv->apn); + g_free(priv->network_id); + g_free(priv->pin); + g_free(priv->device_id); + g_free(priv->sim_id); + g_free(priv->sim_operator_id); + + G_OBJECT_CLASS(nm_setting_gsm_parent_class)->finalize(object); +} + +static void +nm_setting_gsm_class_init(NMSettingGsmClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingGsmPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->verify_secrets = verify_secrets; + setting_class->need_secrets = need_secrets; + + /** + * NMSettingGsm:auto-config: + * + * When %TRUE, the settings such as APN, username, or password will + * default to values that match the network the modem will register + * to in the Mobile Broadband Provider database. + * + * Since: 1.22 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_GSM_AUTO_CONFIG, + PROP_AUTO_CONFIG, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_gsm_get_auto_config); + + /** + * NMSettingGsm:number: + * + * Legacy setting that used to help establishing PPP data sessions for + * GSM-based modems. + * + * Deprecated: 1.16: User-provided values for this setting are no longer used. + **/ + obj_properties[PROP_NUMBER] = g_param_spec_string(NM_SETTING_GSM_NUMBER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:username: + * + * The username used to authenticate with the network, if required. Many + * providers do not require a username, or accept any username. But if a + * username is required, it is specified here. + **/ + obj_properties[PROP_USERNAME] = g_param_spec_string(NM_SETTING_GSM_USERNAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:password: + * + * The password used to authenticate with the network, if required. Many + * providers do not require a password, or accept any password. But if a + * password is required, it is specified here. + **/ + obj_properties[PROP_PASSWORD] = + g_param_spec_string(NM_SETTING_GSM_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:password-flags: + * + * Flags indicating how to handle the #NMSettingGsm:password property. + **/ + obj_properties[PROP_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_GSM_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:apn: + * + * The GPRS Access Point Name specifying the APN used when establishing a + * data session with the GSM-based network. The APN often determines how + * the user will be billed for their network usage and whether the user has + * access to the Internet or just a provider-specific walled-garden, so it + * is important to use the correct APN for the user's mobile broadband plan. + * The APN may only be composed of the characters a-z, 0-9, ., and - per GSM + * 03.60 Section 14.9. + **/ + obj_properties[PROP_APN] = g_param_spec_string(NM_SETTING_GSM_APN, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:network-id: + * + * The Network ID (GSM LAI format, ie MCC-MNC) to force specific network + * registration. If the Network ID is specified, NetworkManager will + * attempt to force the device to register only on the specified network. + * This can be used to ensure that the device does not roam when direct + * roaming control of the device is not otherwise possible. + **/ + obj_properties[PROP_NETWORK_ID] = + g_param_spec_string(NM_SETTING_GSM_NETWORK_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:pin: + * + * If the SIM is locked with a PIN it must be unlocked before any other + * operations are requested. Specify the PIN here to allow operation of the + * device. + **/ + obj_properties[PROP_PIN] = + g_param_spec_string(NM_SETTING_GSM_PIN, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:pin-flags: + * + * Flags indicating how to handle the #NMSettingGsm:pin property. + **/ + obj_properties[PROP_PIN_FLAGS] = g_param_spec_flags(NM_SETTING_GSM_PIN_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:home-only: + * + * When %TRUE, only connections to the home network will be allowed. + * Connections to roaming networks will not be made. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_GSM_HOME_ONLY, + PROP_HOME_ONLY, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_gsm_get_home_only); + + /** + * NMSettingGsm:device-id: + * + * The device unique identifier (as given by the WWAN management service) + * which this connection applies to. If given, the connection will only + * apply to the specified device. + * + * Since: 1.2 + **/ + obj_properties[PROP_DEVICE_ID] = + g_param_spec_string(NM_SETTING_GSM_DEVICE_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:sim-id: + * + * The SIM card unique identifier (as given by the WWAN management service) + * which this connection applies to. If given, the connection will apply + * to any device also allowed by #NMSettingGsm:device-id which contains a + * SIM card matching the given identifier. + * + * Since: 1.2 + **/ + obj_properties[PROP_SIM_ID] = g_param_spec_string(NM_SETTING_GSM_SIM_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:sim-operator-id: + * + * A MCC/MNC string like "310260" or "21601" identifying the specific + * mobile network operator which this connection applies to. If given, + * the connection will apply to any device also allowed by + * #NMSettingGsm:device-id and #NMSettingGsm:sim-id which contains a SIM + * card provisioned by the given operator. + * + * Since: 1.2 + **/ + obj_properties[PROP_SIM_OPERATOR_ID] = + g_param_spec_string(NM_SETTING_GSM_SIM_OPERATOR_ID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingGsm:mtu: + * + * If non-zero, only transmit packets of the specified size or smaller, + * breaking larger packets up into multiple frames. + * + * Since: 1.8 + **/ + obj_properties[PROP_MTU] = g_param_spec_uint(NM_SETTING_GSM_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /* Ignore incoming deprecated properties */ + _nm_properties_override_dbus(properties_override, + "allowed-bands", + &nm_sett_info_propert_type_deprecated_ignore_u); + _nm_properties_override_dbus(properties_override, + "network-type", + &nm_sett_info_propert_type_deprecated_ignore_i); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_GSM, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-hostname.c b/src/libnm-core-impl/nm-setting-hostname.c new file mode 100644 index 00000000..248ae556 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-hostname.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-hostname.h" + +#include "nm-setting-private.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-hostname + * @short_description: Contains properties related to the hostname + * @include: nm-setting-hostname.h + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingHostname, + PROP_PRIORITY, + PROP_FROM_DHCP, + PROP_FROM_DNS_LOOKUP, + PROP_ONLY_FROM_DEFAULT, ); + +/** + * NMSettingHostname: + * + * Hostname settings + * + * Since: 1.30 + */ +struct _NMSettingHostname { + NMSetting parent; + int priority; + NMTernary from_dhcp; + NMTernary from_dns_lookup; + NMTernary only_from_default; +}; + +struct _NMSettingHostnameClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingHostname, nm_setting_hostname, NM_TYPE_SETTING) + +/** + * nm_setting_hostname_get_priority: + * @setting: the #NMSettingHostname + * + * Returns the value contained in the #NMSettingHostname:priority + * property. + * + * Returns: the 'priority' property value + * + * Since: 1.30 + **/ +int +nm_setting_hostname_get_priority(NMSettingHostname *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_HOSTNAME(setting), 0); + + return setting->priority; +} + +/** + * nm_setting_hostname_get_from_dhcp: + * @setting: the #NMSettingHostname + * + * Returns the value contained in the #NMSettingHostname:from-dhcp + * property. + * + * Returns: the 'from-dhcp' property value + * + * Since: 1.30 + **/ +NMTernary +nm_setting_hostname_get_from_dhcp(NMSettingHostname *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_HOSTNAME(setting), NM_TERNARY_DEFAULT); + + return setting->from_dhcp; +} + +/** + * nm_setting_hostname_get_from_dns_lookup: + * @setting: the #NMSettingHostname + * + * Returns the value contained in the #NMSettingHostname:from-dns-lookup + * property. + * + * Returns: the 'from-dns-lookup' property value + * + * Since: 1.30 + **/ +NMTernary +nm_setting_hostname_get_from_dns_lookup(NMSettingHostname *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_HOSTNAME(setting), NM_TERNARY_DEFAULT); + + return setting->from_dns_lookup; +} + +/** + * nm_setting_hostname_get_only_from_default: + * @setting: the #NMSettingHostname + * + * Returns the value contained in the #NMSettingHostname:only-from-default + * property. + * + * Returns: the 'only-from-default' property value + * + * Since: 1.30 + **/ +NMTernary +nm_setting_hostname_get_only_from_default(NMSettingHostname *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_HOSTNAME(setting), NM_TERNARY_DEFAULT); + + return setting->only_from_default; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingHostname *self = NM_SETTING_HOSTNAME(object); + + switch (prop_id) { + case PROP_PRIORITY: + g_value_set_int(value, self->priority); + break; + case PROP_FROM_DHCP: + g_value_set_enum(value, self->from_dhcp); + break; + case PROP_FROM_DNS_LOOKUP: + g_value_set_enum(value, self->from_dns_lookup); + break; + case PROP_ONLY_FROM_DEFAULT: + g_value_set_enum(value, self->only_from_default); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingHostname *self = NM_SETTING_HOSTNAME(object); + + switch (prop_id) { + case PROP_PRIORITY: + self->priority = g_value_get_int(value); + break; + case PROP_FROM_DHCP: + self->from_dhcp = g_value_get_enum(value); + break; + case PROP_FROM_DNS_LOOKUP: + self->from_dns_lookup = g_value_get_enum(value); + break; + case PROP_ONLY_FROM_DEFAULT: + self->only_from_default = g_value_get_enum(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_hostname_init(NMSettingHostname *setting) +{ + setting->from_dhcp = NM_TERNARY_DEFAULT; + setting->from_dns_lookup = NM_TERNARY_DEFAULT; + setting->only_from_default = NM_TERNARY_DEFAULT; +} + +/** + * nm_setting_hostname_new: + * + * Creates a new #NMSettingHostname object with default values. + * + * Returns: (transfer full): the new empty #NMSettingHostname object + * + * Since: 1.30 + **/ +NMSetting * +nm_setting_hostname_new(void) +{ + return g_object_new(NM_TYPE_SETTING_HOSTNAME, NULL); +} + +static void +nm_setting_hostname_class_init(NMSettingHostnameClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + /** + * NMSettingHostname:priority + * + * The relative priority of this connection to determine the + * system hostname. A lower numerical value is better (higher + * priority). A connection with higher priority is considered + * before connections with lower priority. + * + * If the value is zero, it can be overridden by a global value + * from NetworkManager configuration. If the property doesn't have + * a value in the global configuration, the value is assumed to be + * 100. + * + * Negative values have the special effect of excluding other + * connections with a greater numerical priority value; so in + * presence of at least one negative priority, only connections + * with the lowest priority value will be used to determine the + * hostname. + * + * Since: 1.30 + **/ + /* ---ifcfg-rh--- + * property: priority + * variable: HOSTNAME_PRIORITY(+) + * default: missing variable means global value or 100 + * description: hostname priority + * example: HOSTNAME_PRIORITY=50 + * ---end--- + */ + obj_properties[PROP_PRIORITY] = g_param_spec_int(NM_SETTING_HOSTNAME_PRIORITY, + "", + "", + G_MININT32, + G_MAXINT32, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingHostname:from-dhcp + * + * Whether the system hostname can be determined from DHCP on + * this connection. + * + * When set to %NM_TERNARY_DEFAULT, the value from global configuration + * is used. If the property doesn't have a value in the global + * configuration, NetworkManager assumes the value to be %NM_TERNARY_TRUE. + * + * Since: 1.30 + **/ + /* ---ifcfg-rh--- + * property: from-dhcp + * variable: HOSTNAME_FROM_DHCP(+) + * default: missing variable means global default or 1 + * description: whether the system hostname can be determined from DHCP + * example: HOSTNAME_FROM_DHCP=0,1 + * ---end--- + */ + obj_properties[PROP_FROM_DHCP] = g_param_spec_enum( + NM_SETTING_HOSTNAME_FROM_DHCP, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingHostname:from-dns-lookup + * + * Whether the system hostname can be determined from reverse + * DNS lookup of addresses on this device. + * + * When set to %NM_TERNARY_DEFAULT, the value from global configuration + * is used. If the property doesn't have a value in the global + * configuration, NetworkManager assumes the value to be %NM_TERNARY_TRUE. + * + * Since: 1.30 + **/ + /* ---ifcfg-rh--- + * property: from-dhcp + * variable: HOSTNAME_FROM_DNS_LOOKUP(+) + * default: missing variable means global default or 1 + * description: whether the system hostname can be determined from reverse + * DNS lookup + * example: HOSTNAME_FROM_DNS_LOOKUP=0,1 + * ---end--- + */ + obj_properties[PROP_FROM_DNS_LOOKUP] = g_param_spec_enum( + NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingHostname:only-from-default + * + * If set to %NM_TERNARY_TRUE, NetworkManager attempts to get + * the hostname via DHCPv4/DHCPv6 or reverse DNS lookup on this + * device only when the device has the default route for the given + * address family (IPv4/IPv6). + * + * If set to %NM_TERNARY_FALSE, the hostname can be set from this + * device even if it doesn't have the default route. + * + * When set to %NM_TERNARY_DEFAULT, the value from global configuration + * is used. If the property doesn't have a value in the global + * configuration, NetworkManager assumes the value to be %NM_TERNARY_FALSE. + * + * Since: 1.30 + **/ + /* ---ifcfg-rh--- + * property: only-best-device + * variable: HOSTNAME_ONLY_FROM_DEFAULT(+) + * default: missing variable means global default or 1 + * description: whether the hostname can be determined only from + * devices with the default route + * example: HOSTNAME_ONLY_FROM_DEFAULT=0,1 + * ---end--- + */ + obj_properties[PROP_ONLY_FROM_DEFAULT] = g_param_spec_enum( + NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_HOSTNAME); +} diff --git a/src/libnm-core-impl/nm-setting-infiniband.c b/src/libnm-core-impl/nm-setting-infiniband.c new file mode 100644 index 00000000..95dc7b66 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-infiniband.c @@ -0,0 +1,537 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 - 2013 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-infiniband.h" + +#include +#include + +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" +#include "nm-setting-connection.h" + +/** + * SECTION:nm-setting-infiniband + * @short_description: Describes connection properties for IP-over-InfiniBand networks + * + * The #NMSettingInfiniband object is a #NMSetting subclass that describes properties + * necessary for connection to IP-over-InfiniBand networks. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_MAC_ADDRESS, + PROP_MTU, + PROP_TRANSPORT_MODE, + PROP_P_KEY, + PROP_PARENT, ); + +typedef struct { + char * mac_address; + char * transport_mode; + char * parent; + char * virtual_iface_name; + int p_key; + guint32 mtu; +} NMSettingInfinibandPrivate; + +/** + * NMSettingInfiniband: + * + * Infiniband Settings + */ +struct _NMSettingInfiniband { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingInfinibandClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingInfiniband, nm_setting_infiniband, NM_TYPE_SETTING) + +#define NM_SETTING_INFINIBAND_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_INFINIBAND, NMSettingInfinibandPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_infiniband_get_mac_address: + * @setting: the #NMSettingInfiniband + * + * Returns: the #NMSettingInfiniband:mac-address property of the setting + **/ +const char * +nm_setting_infiniband_get_mac_address(NMSettingInfiniband *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_INFINIBAND(setting), NULL); + + return NM_SETTING_INFINIBAND_GET_PRIVATE(setting)->mac_address; +} + +/** + * nm_setting_infiniband_get_mtu: + * @setting: the #NMSettingInfiniband + * + * Returns: the #NMSettingInfiniband:mtu property of the setting + **/ +guint32 +nm_setting_infiniband_get_mtu(NMSettingInfiniband *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_INFINIBAND(setting), 0); + + return NM_SETTING_INFINIBAND_GET_PRIVATE(setting)->mtu; +} + +/** + * nm_setting_infiniband_get_transport_mode: + * @setting: the #NMSettingInfiniband + * + * Returns the transport mode for this device. Either 'datagram' or + * 'connected'. + * + * Returns: the IPoIB transport mode + **/ +const char * +nm_setting_infiniband_get_transport_mode(NMSettingInfiniband *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_INFINIBAND(setting), NULL); + + return NM_SETTING_INFINIBAND_GET_PRIVATE(setting)->transport_mode; +} + +/** + * nm_setting_infiniband_get_p_key: + * @setting: the #NMSettingInfiniband + * + * Returns the P_Key to use for this device. A value of -1 means to + * use the default P_Key (aka "the P_Key at index 0"). Otherwise, it is + * a 16-bit unsigned integer. + * + * Returns: the IPoIB P_Key + **/ +int +nm_setting_infiniband_get_p_key(NMSettingInfiniband *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_INFINIBAND(setting), -1); + + return NM_SETTING_INFINIBAND_GET_PRIVATE(setting)->p_key; +} + +/** + * nm_setting_infiniband_get_parent: + * @setting: the #NMSettingInfiniband + * + * Returns the parent interface name for this device, if set. + * + * Returns: the parent interface name + **/ +const char * +nm_setting_infiniband_get_parent(NMSettingInfiniband *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_INFINIBAND(setting), NULL); + + return NM_SETTING_INFINIBAND_GET_PRIVATE(setting)->parent; +} + +/** + * nm_setting_infiniband_get_virtual_interface_name: + * @setting: the #NMSettingInfiniband + * + * Returns the interface name created by combining #NMSettingInfiniband:parent + * and #NMSettingInfiniband:p-key. (If either property is unset, this will + * return %NULL.) + * + * Returns: the interface name, or %NULL + **/ +const char * +nm_setting_infiniband_get_virtual_interface_name(NMSettingInfiniband *setting) +{ + NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE(setting); + + if (priv->p_key == -1 || !priv->parent) + return NULL; + + if (!priv->virtual_iface_name) + priv->virtual_iface_name = g_strdup_printf("%s.%04x", priv->parent, priv->p_key); + + return NM_SETTING_INFINIBAND_GET_PRIVATE(setting)->virtual_iface_name; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingConnection * s_con = NULL; + NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE(setting); + + if (priv->mac_address && !nm_utils_hwaddr_valid(priv->mac_address, INFINIBAND_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_INFINIBAND_MAC_ADDRESS); + return FALSE; + } + + if (!NM_IN_STRSET(priv->transport_mode, "datagram", "connected")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_INFINIBAND_TRANSPORT_MODE); + return FALSE; + } + + if (priv->parent) { + GError *tmp_error = NULL; + + if (!nm_utils_ifname_valid_kernel(priv->parent, &tmp_error)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "'%s': %s", + priv->parent, + tmp_error->message); + g_prefix_error(error, "%s: ", NM_SETTING_INFINIBAND_PARENT); + g_error_free(tmp_error); + return FALSE; + } + if (priv->p_key == -1) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Must specify a P_Key if specifying parent")); + g_prefix_error(error, "%s: ", NM_SETTING_INFINIBAND_PARENT); + } + } + + if (priv->p_key != -1) { + if (!priv->mac_address && !priv->parent) { + g_set_error_literal( + error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("InfiniBand P_Key connection did not specify parent interface name")); + g_prefix_error(error, "%s: ", NM_SETTING_INFINIBAND_PARENT); + return FALSE; + } + } + + if (connection) + s_con = nm_connection_get_setting_connection(connection); + if (s_con) { + const char *interface_name = nm_setting_connection_get_interface_name(s_con); + + if (interface_name && priv->p_key != -1) { + if (!priv->virtual_iface_name) + priv->virtual_iface_name = g_strdup_printf("%s.%04x", priv->parent, priv->p_key); + + if (strcmp(interface_name, priv->virtual_iface_name) != 0) { + /* We don't support renaming software infiniband devices. Later we might, but + * for now just reject such connections. + **/ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("interface name of software infiniband device must be '%s' or unset " + "(instead it is '%s')"), + priv->virtual_iface_name, + interface_name); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME); + return FALSE; + } + } + } + + /* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */ + + if (priv->mtu > NM_INFINIBAND_MAX_MTU) { + /* Traditionally, MTU for "datagram" mode was limited to 2044 + * and for "connected" mode it was 65520. + * + * This is no longer the case, and both transport modes use the same + * maximum of 65520 (NM_INFINIBAND_MAX_MTU). + * + * Note that this is the MTU in the connection profile. Whether + * we will be able to configure large MTUs later (during activation) + * is unknown at this point. */ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("mtu can be at most %u but it is %u"), + NM_INFINIBAND_MAX_MTU, + priv->mtu); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_INFINIBAND_SETTING_NAME, + NM_SETTING_INFINIBAND_MTU); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingInfiniband *setting = NM_SETTING_INFINIBAND(object); + + switch (prop_id) { + case PROP_MAC_ADDRESS: + g_value_set_string(value, nm_setting_infiniband_get_mac_address(setting)); + break; + case PROP_MTU: + g_value_set_uint(value, nm_setting_infiniband_get_mtu(setting)); + break; + case PROP_TRANSPORT_MODE: + g_value_set_string(value, nm_setting_infiniband_get_transport_mode(setting)); + break; + case PROP_P_KEY: + g_value_set_int(value, nm_setting_infiniband_get_p_key(setting)); + break; + case PROP_PARENT: + g_value_set_string(value, nm_setting_infiniband_get_parent(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MAC_ADDRESS: + g_free(priv->mac_address); + priv->mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), INFINIBAND_ALEN); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + case PROP_TRANSPORT_MODE: + g_free(priv->transport_mode); + priv->transport_mode = g_value_dup_string(value); + break; + case PROP_P_KEY: + priv->p_key = g_value_get_int(value); + nm_clear_g_free(&priv->virtual_iface_name); + break; + case PROP_PARENT: + g_free(priv->parent); + priv->parent = g_value_dup_string(value); + nm_clear_g_free(&priv->virtual_iface_name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_infiniband_init(NMSettingInfiniband *self) +{ + NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE(self); + + priv->p_key = -1; +} + +/** + * nm_setting_infiniband_new: + * + * Creates a new #NMSettingInfiniband object with default values. + * + * Returns: (transfer full): the new empty #NMSettingInfiniband object + **/ +NMSetting * +nm_setting_infiniband_new(void) +{ + return g_object_new(NM_TYPE_SETTING_INFINIBAND, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE(object); + + g_free(priv->transport_mode); + g_free(priv->mac_address); + g_free(priv->parent); + g_free(priv->virtual_iface_name); + + G_OBJECT_CLASS(nm_setting_infiniband_parent_class)->finalize(object); +} + +static void +nm_setting_infiniband_class_init(NMSettingInfinibandClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingInfinibandPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingInfiniband:mac-address: + * + * If specified, this connection will only apply to the IPoIB device whose + * permanent MAC address matches. This property does not change the MAC + * address of the device (i.e. MAC spoofing). + **/ + /* ---keyfile--- + * property: mac-address + * format: usual hex-digits-and-colons notation + * description: MAC address in traditional hex-digits-and-colons notation, or + * or semicolon separated list of 20 decimal bytes (obsolete) + * example: mac-address= 80:00:00:6d:fe:80:00:00:00:00:00:00:00:02:55:00:70:33:cf:01 + * ---end--- + * ---ifcfg-rh--- + * property: mac-address + * variable: HWADDR + * description: IBoIP 20-byte hardware address of the device (in traditional + * hex-digits-and-colons notation). + * Note that for initscripts this is the current MAC address of the device as found + * during ifup. For NetworkManager this is the permanent MAC address. Or in case no + * permanent MAC address exists, the MAC address initially configured on the device. + * example: HWADDR=01:02:03:04:05:06:07:08:09:0A:01:02:03:04:05:06:07:08:09:11 + * ---end--- + */ + obj_properties[PROP_MAC_ADDRESS] = g_param_spec_string( + NM_SETTING_INFINIBAND_MAC_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_MAC_ADDRESS], + &nm_sett_info_propert_type_mac_address); + + /** + * NMSettingInfiniband:mtu: + * + * If non-zero, only transmit packets of the specified size or smaller, + * breaking larger packets up into multiple frames. + **/ + /* ---ifcfg-rh--- + * property: mtu + * variable: MTU + * description: MTU of the interface. + * ---end--- + */ + obj_properties[PROP_MTU] = g_param_spec_uint(NM_SETTING_INFINIBAND_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingInfiniband:transport-mode: + * + * The IP-over-InfiniBand transport mode. Either "datagram" or + * "connected". + **/ + /* ---ifcfg-rh--- + * property: transport-mode + * variable: CONNECTED_MODE + * default: CONNECTED_MODE=no + * description: CONNECTED_MODE=yes for "connected" mode, CONNECTED_MODE=no for + * "datagram" mode + * ---end--- + */ + obj_properties[PROP_TRANSPORT_MODE] = g_param_spec_string( + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingInfiniband:p-key: + * + * The InfiniBand P_Key to use for this device. A value of -1 means to use + * the default P_Key (aka "the P_Key at index 0"). Otherwise, it is a 16-bit + * unsigned integer, whose high bit is set if it is a "full membership" + * P_Key. + **/ + /* ---ifcfg-rh--- + * property: p-key + * variable: PKEY_ID (and PKEY=yes) + * default: PKEY=no + * description: InfiniBand P_Key. The value can be a hex number prefixed with "0x" + * or a decimal number. + * When PKEY_ID is specified, PHYSDEV and DEVICE also must be specified. + * example: PKEY=yes PKEY_ID=2 PHYSDEV=mlx4_ib0 DEVICE=mlx4_ib0.8002 + * ---end--- + */ + obj_properties[PROP_P_KEY] = + g_param_spec_int(NM_SETTING_INFINIBAND_P_KEY, + "", + "", + -1, + 0xFFFF, + -1, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingInfiniband:parent: + * + * The interface name of the parent device of this device. Normally %NULL, + * but if the #NMSettingInfiniband:p_key property is set, then you must + * specify the base device by setting either this property or + * #NMSettingInfiniband:mac-address. + **/ + /* ---ifcfg-rh--- + * property: parent + * variable: PHYSDEV (PKEY=yes) + * default: PKEY=no + * description: InfiniBand parent device. + * example: PHYSDEV=ib0 + * ---end--- + */ + obj_properties[PROP_PARENT] = g_param_spec_string( + NM_SETTING_INFINIBAND_PARENT, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_INFINIBAND, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c new file mode 100644 index 00000000..59ab5ca2 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ip-config.c @@ -0,0 +1,6565 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2017 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ip-config.h" + +#include +#include + +#include "libnm-base/nm-net-aux.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-utils.h" +#include "nm-setting-private.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-ip-config + * @short_description: Abstract base class for IPv4 and IPv6 + * addressing, routing, and name service properties + * @include: nm-setting-ip-config.h + * @see_also: #NMSettingIP4Config, #NMSettingIP6Config + * + * #NMSettingIPConfig is the abstract base class of + * #NMSettingIP4Config and #NMSettingIP6Config, providing properties + * related to IP addressing, routing, and Domain Name Service. + **/ + +/*****************************************************************************/ + +const NMUtilsDNSOptionDesc _nm_utils_dns_option_descs[] = { + {NM_SETTING_DNS_OPTION_DEBUG, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_NDOTS, TRUE, FALSE}, + {NM_SETTING_DNS_OPTION_TIMEOUT, TRUE, FALSE}, + {NM_SETTING_DNS_OPTION_ATTEMPTS, TRUE, FALSE}, + {NM_SETTING_DNS_OPTION_ROTATE, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_NO_CHECK_NAMES, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_INET6, FALSE, TRUE}, + {NM_SETTING_DNS_OPTION_IP6_BYTESTRING, FALSE, TRUE}, + {NM_SETTING_DNS_OPTION_IP6_DOTINT, FALSE, TRUE}, + {NM_SETTING_DNS_OPTION_NO_IP6_DOTINT, FALSE, TRUE}, + {NM_SETTING_DNS_OPTION_EDNS0, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_SINGLE_REQUEST, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_SINGLE_REQUEST_REOPEN, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_NO_TLD_QUERY, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_USE_VC, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_NO_RELOAD, FALSE, FALSE}, + {NM_SETTING_DNS_OPTION_TRUST_AD, FALSE, FALSE}, + {NULL, FALSE, FALSE}}; + +static char * +canonicalize_ip(int family, const char *ip, gboolean null_any) +{ + guint8 addr_bytes[sizeof(struct in6_addr)]; + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + int ret; + + if (!ip) { + if (null_any) + return NULL; + if (family == AF_INET) + return g_strdup("0.0.0.0"); + if (family == AF_INET6) + return g_strdup("::"); + g_return_val_if_reached(NULL); + } + + ret = inet_pton(family, ip, addr_bytes); + g_return_val_if_fail(ret == 1, NULL); + + if (null_any) { + if (!memcmp(addr_bytes, &in6addr_any, nm_utils_addr_family_to_size(family))) + return NULL; + } + + return g_strdup(inet_ntop(family, addr_bytes, addr_str, sizeof(addr_str))); +} + +static char * +canonicalize_ip_binary(int family, gconstpointer ip, gboolean null_any) +{ + char string[NM_UTILS_INET_ADDRSTRLEN]; + + if (!ip) { + if (null_any) + return NULL; + if (family == AF_INET) + return g_strdup("0.0.0.0"); + if (family == AF_INET6) + return g_strdup("::"); + g_return_val_if_reached(NULL); + } + if (null_any) { + if (!memcmp(ip, &in6addr_any, nm_utils_addr_family_to_size(family))) + return NULL; + } + return g_strdup(inet_ntop(family, ip, string, sizeof(string))); +} + +static gboolean +valid_ip(int family, const char *ip, GError **error) +{ + if (!ip) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + family == AF_INET ? _("Missing IPv4 address") : _("Missing IPv6 address")); + return FALSE; + } + if (!nm_utils_ipaddr_is_valid(family, ip)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + family == AF_INET ? _("Invalid IPv4 address '%s'") + : _("Invalid IPv6 address '%s'"), + ip); + return FALSE; + } else + return TRUE; +} + +static gboolean +valid_prefix(int family, guint prefix, GError **error) +{ + if ((family == AF_INET && prefix <= 32) || (family == AF_INET6 && prefix <= 128)) + return TRUE; + + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + family == AF_INET ? _("Invalid IPv4 address prefix '%u'") + : _("Invalid IPv6 address prefix '%u'"), + prefix); + return FALSE; +} + +static gboolean +valid_metric(gint64 metric, GError **error) +{ + if (metric < -1 || metric > G_MAXUINT32) { + if (error) { + char buf[64]; + + /* We can't concatenate G_GINT64_FORMAT into a translatable string */ + g_snprintf(buf, sizeof(buf), "%" G_GINT64_FORMAT, metric); + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("Invalid routing metric '%s'"), + buf); + } + return FALSE; + } + + return TRUE; +} + +/***************************************************************************** + * NMIPAddress + *****************************************************************************/ + +G_DEFINE_BOXED_TYPE(NMIPAddress, nm_ip_address, nm_ip_address_dup, nm_ip_address_unref) + +struct NMIPAddress { + guint refcount; + + char *address; + int prefix, family; + + GHashTable *attributes; +}; + +/** + * nm_ip_address_new: + * @family: the IP address family (AF_INET or + * AF_INET6) + * @addr: the IP address + * @prefix: the address prefix length + * @error: location to store error, or %NULL + * + * Creates a new #NMIPAddress object. + * + * Returns: (transfer full): the new #NMIPAddress object, or %NULL on error + **/ +NMIPAddress * +nm_ip_address_new(int family, const char *addr, guint prefix, GError **error) +{ + NMIPAddress *address; + + g_return_val_if_fail(family == AF_INET || family == AF_INET6, NULL); + g_return_val_if_fail(addr != NULL, NULL); + + if (!valid_ip(family, addr, error)) + return NULL; + if (!valid_prefix(family, prefix, error)) + return NULL; + + address = g_slice_new0(NMIPAddress); + address->refcount = 1; + + address->family = family; + address->address = canonicalize_ip(family, addr, FALSE); + address->prefix = prefix; + + return address; +} + +/** + * nm_ip_address_new_binary: + * @family: the IP address family (AF_INET or + * AF_INET6) + * @addr: the IP address + * @prefix: the address prefix length + * @error: location to store error, or %NULL + * + * Creates a new #NMIPAddress object. @addr must point to a buffer of the + * correct size for @family. + * + * Returns: (transfer full): the new #NMIPAddress object, or %NULL on error + **/ +NMIPAddress * +nm_ip_address_new_binary(int family, gconstpointer addr, guint prefix, GError **error) +{ + NMIPAddress *address; + char string[NM_UTILS_INET_ADDRSTRLEN]; + + g_return_val_if_fail(family == AF_INET || family == AF_INET6, NULL); + g_return_val_if_fail(addr != NULL, NULL); + + if (!valid_prefix(family, prefix, error)) + return NULL; + + address = g_slice_new0(NMIPAddress); + address->refcount = 1; + + address->family = family; + address->address = g_strdup(inet_ntop(family, addr, string, sizeof(string))); + address->prefix = prefix; + + return address; +} + +/** + * nm_ip_address_ref: + * @address: the #NMIPAddress + * + * Increases the reference count of the object. + **/ +void +nm_ip_address_ref(NMIPAddress *address) +{ + g_return_if_fail(address != NULL); + g_return_if_fail(address->refcount > 0); + + address->refcount++; +} + +/** + * nm_ip_address_unref: + * @address: the #NMIPAddress + * + * Decreases the reference count of the object. If the reference count + * reaches zero, the object will be destroyed. + **/ +void +nm_ip_address_unref(NMIPAddress *address) +{ + g_return_if_fail(address != NULL); + g_return_if_fail(address->refcount > 0); + + address->refcount--; + if (address->refcount == 0) { + g_free(address->address); + if (address->attributes) + g_hash_table_unref(address->attributes); + g_slice_free(NMIPAddress, address); + } +} + +/** + * nm_ip_address_cmp_full: + * @a: the #NMIPAddress + * @b: the #NMIPAddress to compare @address to. + * @cmp_flags: the #NMIPAddressCmpFlags that indicate what to compare. + * + * Note that with @cmp_flags #NM_IP_ADDRESS_CMP_FLAGS_WITH_ATTRS, there + * is no total order for comparing GVariant. That means, if the two addresses + * only differ by their attributes, the sort order is undefined and the return + * value only indicates equality. + * + * Returns: 0 if the two objects have the same values (according to their flags) + * or a integer indicating the compare order. + **/ +int +nm_ip_address_cmp_full(const NMIPAddress *a, const NMIPAddress *b, NMIPAddressCmpFlags cmp_flags) +{ + g_return_val_if_fail(!a || a->refcount > 0, 0); + g_return_val_if_fail(!b || b->refcount > 0, 0); + g_return_val_if_fail(!NM_FLAGS_ANY(cmp_flags, ~NM_IP_ADDRESS_CMP_FLAGS_WITH_ATTRS), 0); + + NM_CMP_SELF(a, b); + + NM_CMP_FIELD(a, b, family); + NM_CMP_FIELD(a, b, prefix); + NM_CMP_FIELD_STR(a, b, address); + + if (NM_FLAGS_HAS(cmp_flags, NM_IP_ADDRESS_CMP_FLAGS_WITH_ATTRS)) { + GHashTableIter iter; + const char * key; + GVariant * value, *value2; + guint n; + + n = a->attributes ? g_hash_table_size(a->attributes) : 0u; + NM_CMP_DIRECT(n, (b->attributes ? g_hash_table_size(b->attributes) : 0u)); + + if (n > 0) { + g_hash_table_iter_init(&iter, a->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { + value2 = g_hash_table_lookup(b->attributes, key); + /* We cannot really compare GVariants, because g_variant_compare() does + * not work in general. So, don't bother. NM_IP_ADDRESS_CMP_FLAGS_WITH_ATTRS is + * documented to not provide a total order for the attribute contents. + * + * Theoretically, we can implement also a total order. However we should + * not do that by default because it would require us to sort the keys + * first. Most callers don't care about total order, so they shouldn't + * pay the overhead. */ + if (!value2) + return -2; + if (!g_variant_equal(value, value2)) + return -2; + } + } + } + + return 0; +} + +/** + * nm_ip_address_equal: + * @address: the #NMIPAddress + * @other: the #NMIPAddress to compare @address to. + * + * Determines if two #NMIPAddress objects contain the same address and prefix + * (attributes are not compared). + * + * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. + **/ +gboolean +nm_ip_address_equal(NMIPAddress *address, NMIPAddress *other) +{ + return nm_ip_address_cmp_full(address, other, NM_IP_ADDRESS_CMP_FLAGS_NONE) == 0; +} + +/** + * nm_ip_address_dup: + * @address: the #NMIPAddress + * + * Creates a copy of @address + * + * Returns: (transfer full): a copy of @address + * + * This API was part of public headers before 1.32.0 but + * was erroneously not exported in the ABI. It is thus only + * usable since 1.32.0. + * + * Since: 1.32 + **/ +NMIPAddress * +nm_ip_address_dup(NMIPAddress *address) +{ + NMIPAddress *copy; + + g_return_val_if_fail(address != NULL, NULL); + g_return_val_if_fail(address->refcount > 0, NULL); + + copy = nm_ip_address_new(address->family, address->address, address->prefix, NULL); + if (address->attributes) { + GHashTableIter iter; + const char * key; + GVariant * value; + + g_hash_table_iter_init(&iter, address->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) + nm_ip_address_set_attribute(copy, key, value); + } + + return copy; +} + +/** + * nm_ip_address_get_family: + * @address: the #NMIPAddress + * + * Gets the IP address family (eg, AF_INET) property of this address + * object. + * + * Returns: the IP address family + **/ +int +nm_ip_address_get_family(NMIPAddress *address) +{ + g_return_val_if_fail(address != NULL, 0); + g_return_val_if_fail(address->refcount > 0, 0); + + return address->family; +} + +/** + * nm_ip_address_get_address: + * @address: the #NMIPAddress + * + * Gets the IP address property of this address object. + * + * Returns: the IP address + **/ +const char * +nm_ip_address_get_address(NMIPAddress *address) +{ + g_return_val_if_fail(address != NULL, NULL); + g_return_val_if_fail(address->refcount > 0, NULL); + + return address->address; +} + +/** + * nm_ip_address_set_address: + * @address: the #NMIPAddress + * @addr: the IP address, as a string + * + * Sets the IP address property of this address object. + * + * @addr must be a valid address of @address's family. If you aren't sure you + * have a valid address, use nm_utils_ipaddr_valid() to check it. + **/ +void +nm_ip_address_set_address(NMIPAddress *address, const char *addr) +{ + g_return_if_fail(address != NULL); + g_return_if_fail(addr != NULL); + g_return_if_fail(nm_utils_ipaddr_is_valid(address->family, addr)); + + g_free(address->address); + address->address = canonicalize_ip(address->family, addr, FALSE); +} + +/** + * nm_ip_address_get_address_binary: (skip) + * @address: the #NMIPAddress + * @addr: a buffer in which to store the address in binary format. + * + * Gets the IP address property of this address object. + * + * @addr must point to a buffer that is the correct size for @address's family. + **/ +void +nm_ip_address_get_address_binary(NMIPAddress *address, gpointer addr) +{ + g_return_if_fail(address != NULL); + g_return_if_fail(addr != NULL); + + inet_pton(address->family, address->address, addr); +} + +/** + * nm_ip_address_set_address_binary: (skip) + * @address: the #NMIPAddress + * @addr: the address, in binary format + * + * Sets the IP address property of this address object. + * + * @addr must point to a buffer that is the correct size for @address's family. + **/ +void +nm_ip_address_set_address_binary(NMIPAddress *address, gconstpointer addr) +{ + char string[NM_UTILS_INET_ADDRSTRLEN]; + + g_return_if_fail(address != NULL); + g_return_if_fail(addr != NULL); + + g_free(address->address); + address->address = g_strdup(inet_ntop(address->family, addr, string, sizeof(string))); +} + +/** + * nm_ip_address_get_prefix: + * @address: the #NMIPAddress + * + * Gets the IP address prefix (ie "24" or "30" etc) property of this address + * object. + * + * Returns: the IP address prefix + **/ +guint +nm_ip_address_get_prefix(NMIPAddress *address) +{ + g_return_val_if_fail(address != NULL, 0); + g_return_val_if_fail(address->refcount > 0, 0); + + return address->prefix; +} + +/** + * nm_ip_address_set_prefix: + * @address: the #NMIPAddress + * @prefix: the IP address prefix + * + * Sets the IP address prefix property of this address object. + **/ +void +nm_ip_address_set_prefix(NMIPAddress *address, guint prefix) +{ + g_return_if_fail(address != NULL); + g_return_if_fail(valid_prefix(address->family, prefix, NULL)); + + address->prefix = prefix; +} + +const char ** +_nm_ip_address_get_attribute_names(const NMIPAddress *address, gboolean sorted, guint *out_length) +{ + nm_assert(address); + + return nm_utils_strdict_get_keys(address->attributes, sorted, out_length); +} + +/** + * nm_ip_address_get_attribute_names: + * @address: the #NMIPAddress + * + * Gets an array of attribute names defined on @address. + * + * Returns: (transfer full): a %NULL-terminated array of attribute names, + **/ +char ** +nm_ip_address_get_attribute_names(NMIPAddress *address) +{ + const char **names; + + g_return_val_if_fail(address, NULL); + + names = _nm_ip_address_get_attribute_names(address, TRUE, NULL); + return nm_utils_strv_make_deep_copied_nonnull(names); +} + +/** + * nm_ip_address_get_attribute: + * @address: the #NMIPAddress + * @name: the name of an address attribute + * + * Gets the value of the attribute with name @name on @address + * + * Returns: (transfer none): the value of the attribute with name @name on + * @address, or %NULL if @address has no such attribute. + **/ +GVariant * +nm_ip_address_get_attribute(NMIPAddress *address, const char *name) +{ + g_return_val_if_fail(address != NULL, NULL); + g_return_val_if_fail(name != NULL && *name != '\0', NULL); + + if (address->attributes) + return g_hash_table_lookup(address->attributes, name); + else + return NULL; +} + +/** + * nm_ip_address_set_attribute: + * @address: the #NMIPAddress + * @name: the name of an address attribute + * @value: (transfer none) (allow-none): the value + * + * Sets or clears the named attribute on @address to the given value. + **/ +void +nm_ip_address_set_attribute(NMIPAddress *address, const char *name, GVariant *value) +{ + g_return_if_fail(address != NULL); + g_return_if_fail(name != NULL && *name != '\0'); + g_return_if_fail(strcmp(name, "address") != 0 && strcmp(name, "prefix") != 0); + + if (!address->attributes) { + address->attributes = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref); + } + + if (value) + g_hash_table_insert(address->attributes, g_strdup(name), g_variant_ref_sink(value)); + else + g_hash_table_remove(address->attributes, name); +} + +/***************************************************************************** + * NMIPRoute + *****************************************************************************/ + +G_DEFINE_BOXED_TYPE(NMIPRoute, nm_ip_route, nm_ip_route_dup, nm_ip_route_unref) + +struct NMIPRoute { + guint refcount; + + int family; + char * dest; + guint prefix; + char * next_hop; + gint64 metric; + + GHashTable *attributes; +}; + +/** + * nm_ip_route_new: + * @family: the IP address family (AF_INET or + * AF_INET6) + * @dest: the IP address of the route's destination + * @prefix: the address prefix length + * @next_hop: (allow-none): the IP address of the next hop (or %NULL) + * @metric: the route metric (or -1 for "default") + * @error: location to store error, or %NULL + * + * Creates a new #NMIPRoute object. + * + * Returns: (transfer full): the new #NMIPRoute object, or %NULL on error + **/ +NMIPRoute * +nm_ip_route_new(int family, + const char *dest, + guint prefix, + const char *next_hop, + gint64 metric, + GError ** error) +{ + NMIPRoute *route; + + g_return_val_if_fail(family == AF_INET || family == AF_INET6, NULL); + g_return_val_if_fail(dest, NULL); + + if (!valid_ip(family, dest, error)) + return NULL; + if (!valid_prefix(family, prefix, error)) + return NULL; + if (next_hop && !valid_ip(family, next_hop, error)) + return NULL; + if (!valid_metric(metric, error)) + return NULL; + + route = g_slice_new0(NMIPRoute); + route->refcount = 1; + + route->family = family; + route->dest = canonicalize_ip(family, dest, FALSE); + route->prefix = prefix; + route->next_hop = canonicalize_ip(family, next_hop, TRUE); + route->metric = metric; + + return route; +} + +/** + * nm_ip_route_new_binary: + * @family: the IP address family (AF_INET or + * AF_INET6) + * @dest: the IP address of the route's destination + * @prefix: the address prefix length + * @next_hop: (allow-none): the IP address of the next hop (or %NULL) + * @metric: the route metric (or -1 for "default") + * @error: location to store error, or %NULL + * + * Creates a new #NMIPRoute object. @dest and @next_hop (if non-%NULL) must + * point to buffers of the correct size for @family. + * + * Returns: (transfer full): the new #NMIPRoute object, or %NULL on error + **/ +NMIPRoute * +nm_ip_route_new_binary(int family, + gconstpointer dest, + guint prefix, + gconstpointer next_hop, + gint64 metric, + GError ** error) +{ + NMIPRoute *route; + + g_return_val_if_fail(family == AF_INET || family == AF_INET6, NULL); + g_return_val_if_fail(dest, NULL); + + if (!valid_prefix(family, prefix, error)) + return NULL; + if (!valid_metric(metric, error)) + return NULL; + + route = g_slice_new0(NMIPRoute); + route->refcount = 1; + + route->family = family; + route->dest = canonicalize_ip_binary(family, dest, FALSE); + route->prefix = prefix; + route->next_hop = canonicalize_ip_binary(family, next_hop, TRUE); + route->metric = metric; + + return route; +} + +/** + * nm_ip_route_ref: + * @route: the #NMIPRoute + * + * Increases the reference count of the object. + **/ +void +nm_ip_route_ref(NMIPRoute *route) +{ + g_return_if_fail(route != NULL); + g_return_if_fail(route->refcount > 0); + + route->refcount++; +} + +/** + * nm_ip_route_unref: + * @route: the #NMIPRoute + * + * Decreases the reference count of the object. If the reference count + * reaches zero, the object will be destroyed. + **/ +void +nm_ip_route_unref(NMIPRoute *route) +{ + g_return_if_fail(route != NULL); + g_return_if_fail(route->refcount > 0); + + route->refcount--; + if (route->refcount == 0) { + g_free(route->dest); + g_free(route->next_hop); + if (route->attributes) + g_hash_table_unref(route->attributes); + g_slice_free(NMIPRoute, route); + } +} + +/** + * nm_ip_route_equal_full: + * @route: the #NMIPRoute + * @other: the #NMIPRoute to compare @route to. + * @cmp_flags: tune how to compare attributes. Currently, only + * NM_IP_ROUTE_EQUAL_CMP_FLAGS_NONE (0) and NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS (1) + * is supported. + * + * Determines if two #NMIPRoute objects contain the same destination, prefix, + * next hop, and metric. + * + * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. + * + * Since: 1.10 + **/ +gboolean +nm_ip_route_equal_full(NMIPRoute *route, NMIPRoute *other, guint cmp_flags) +{ + g_return_val_if_fail(route != NULL, FALSE); + g_return_val_if_fail(route->refcount > 0, FALSE); + + g_return_val_if_fail(other != NULL, FALSE); + g_return_val_if_fail(other->refcount > 0, FALSE); + + g_return_val_if_fail(NM_IN_SET(cmp_flags, + NM_IP_ROUTE_EQUAL_CMP_FLAGS_NONE, + NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS), + FALSE); + + if (route->prefix != other->prefix || route->metric != other->metric + || strcmp(route->dest, other->dest) != 0 + || g_strcmp0(route->next_hop, other->next_hop) != 0) + return FALSE; + if (cmp_flags == NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS) { + GHashTableIter iter; + const char * key; + GVariant * value, *value2; + guint n; + + n = route->attributes ? g_hash_table_size(route->attributes) : 0u; + if (n != (other->attributes ? g_hash_table_size(other->attributes) : 0u)) + return FALSE; + if (n) { + g_hash_table_iter_init(&iter, route->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { + value2 = g_hash_table_lookup(other->attributes, key); + if (!value2) + return FALSE; + if (!g_variant_equal(value, value2)) + return FALSE; + } + } + } + return TRUE; +} + +/** + * nm_ip_route_equal: + * @route: the #NMIPRoute + * @other: the #NMIPRoute to compare @route to. + * + * Determines if two #NMIPRoute objects contain the same destination, prefix, + * next hop, and metric. (Attributes are not compared.) + * + * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. + **/ +gboolean +nm_ip_route_equal(NMIPRoute *route, NMIPRoute *other) +{ + return nm_ip_route_equal_full(route, other, NM_IP_ROUTE_EQUAL_CMP_FLAGS_NONE); +} + +/** + * nm_ip_route_dup: + * @route: the #NMIPRoute + * + * Creates a copy of @route + * + * Returns: (transfer full): a copy of @route + * + * This API was part of public headers before 1.32.0 but + * was erroneously not exported in the ABI. It is thus only + * usable since 1.32.0. + * + * Since: 1.32 + **/ +NMIPRoute * +nm_ip_route_dup(NMIPRoute *route) +{ + NMIPRoute *copy; + + g_return_val_if_fail(route != NULL, NULL); + g_return_val_if_fail(route->refcount > 0, NULL); + + copy = nm_ip_route_new(route->family, + route->dest, + route->prefix, + route->next_hop, + route->metric, + NULL); + if (route->attributes) { + GHashTableIter iter; + const char * key; + GVariant * value; + + g_hash_table_iter_init(&iter, route->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) + nm_ip_route_set_attribute(copy, key, value); + } + + return copy; +} + +/** + * nm_ip_route_get_family: + * @route: the #NMIPRoute + * + * Gets the IP address family (eg, AF_INET) property of this route + * object. + * + * Returns: the IP address family + **/ +int +nm_ip_route_get_family(NMIPRoute *route) +{ + g_return_val_if_fail(route != NULL, 0); + g_return_val_if_fail(route->refcount > 0, 0); + + return route->family; +} + +/** + * nm_ip_route_get_dest: + * @route: the #NMIPRoute + * + * Gets the IP destination address property of this route object. + * + * Returns: the IP address of the route's destination + **/ +const char * +nm_ip_route_get_dest(NMIPRoute *route) +{ + g_return_val_if_fail(route != NULL, NULL); + g_return_val_if_fail(route->refcount > 0, NULL); + + return route->dest; +} + +/** + * nm_ip_route_set_dest: + * @route: the #NMIPRoute + * @dest: the route's destination, as a string + * + * Sets the destination property of this route object. + * + * @dest must be a valid address of @route's family. If you aren't sure you + * have a valid address, use nm_utils_ipaddr_is_valid() to check it. + **/ +void +nm_ip_route_set_dest(NMIPRoute *route, const char *dest) +{ + g_return_if_fail(route != NULL); + g_return_if_fail(nm_utils_ipaddr_is_valid(route->family, dest)); + + g_free(route->dest); + route->dest = canonicalize_ip(route->family, dest, FALSE); +} + +/** + * nm_ip_route_get_dest_binary: (skip) + * @route: the #NMIPRoute + * @dest: a buffer in which to store the destination in binary format. + * + * Gets the destination property of this route object. + * + * @dest must point to a buffer that is the correct size for @route's family. + **/ +void +nm_ip_route_get_dest_binary(NMIPRoute *route, gpointer dest) +{ + g_return_if_fail(route != NULL); + g_return_if_fail(dest != NULL); + + inet_pton(route->family, route->dest, dest); +} + +/** + * nm_ip_route_set_dest_binary: (skip) + * @route: the #NMIPRoute + * @dest: the route's destination, in binary format + * + * Sets the destination property of this route object. + * + * @dest must point to a buffer that is the correct size for @route's family. + **/ +void +nm_ip_route_set_dest_binary(NMIPRoute *route, gconstpointer dest) +{ + char string[NM_UTILS_INET_ADDRSTRLEN]; + + g_return_if_fail(route != NULL); + g_return_if_fail(dest != NULL); + + g_free(route->dest); + route->dest = g_strdup(inet_ntop(route->family, dest, string, sizeof(string))); +} + +/** + * nm_ip_route_get_prefix: + * @route: the #NMIPRoute + * + * Gets the IP prefix (ie "24" or "30" etc) of this route. + * + * Returns: the IP prefix + **/ +guint +nm_ip_route_get_prefix(NMIPRoute *route) +{ + g_return_val_if_fail(route != NULL, 0); + g_return_val_if_fail(route->refcount > 0, 0); + + return route->prefix; +} + +/** + * nm_ip_route_set_prefix: + * @route: the #NMIPRoute + * @prefix: the route prefix + * + * Sets the prefix property of this route object. + **/ +void +nm_ip_route_set_prefix(NMIPRoute *route, guint prefix) +{ + g_return_if_fail(route != NULL); + g_return_if_fail(valid_prefix(route->family, prefix, NULL)); + + route->prefix = prefix; +} + +/** + * nm_ip_route_get_next_hop: + * @route: the #NMIPRoute + * + * Gets the IP address of the next hop of this route; this will be %NULL if the + * route has no next hop. + * + * Returns: the IP address of the next hop, or %NULL if this is a device route. + **/ +const char * +nm_ip_route_get_next_hop(NMIPRoute *route) +{ + g_return_val_if_fail(route != NULL, NULL); + g_return_val_if_fail(route->refcount > 0, NULL); + + return route->next_hop; +} + +/** + * nm_ip_route_set_next_hop: + * @route: the #NMIPRoute + * @next_hop: (allow-none): the route's next hop, as a string + * + * Sets the next-hop property of this route object. + * + * @next_hop (if non-%NULL) must be a valid address of @route's family. If you + * aren't sure you have a valid address, use nm_utils_ipaddr_valid() to check + * it. + **/ +void +nm_ip_route_set_next_hop(NMIPRoute *route, const char *next_hop) +{ + g_return_if_fail(route != NULL); + g_return_if_fail(!next_hop || nm_utils_ipaddr_is_valid(route->family, next_hop)); + + g_free(route->next_hop); + route->next_hop = canonicalize_ip(route->family, next_hop, TRUE); +} + +/** + * nm_ip_route_get_next_hop_binary: (skip) + * @route: the #NMIPRoute + * @next_hop: a buffer in which to store the next hop in binary format. + * + * Gets the next hop property of this route object. + * + * @next_hop must point to a buffer that is the correct size for @route's family. + * + * Returns: %TRUE if @route has a next hop, %FALSE if not (in which case + * @next_hop will be zeroed out) + **/ +gboolean +nm_ip_route_get_next_hop_binary(NMIPRoute *route, gpointer next_hop) +{ + g_return_val_if_fail(route != NULL, FALSE); + g_return_val_if_fail(next_hop != NULL, FALSE); + + if (route->next_hop) { + inet_pton(route->family, route->next_hop, next_hop); + return TRUE; + } else { + memset(next_hop, 0, nm_utils_addr_family_to_size(route->family)); + return FALSE; + } +} + +/** + * nm_ip_route_set_next_hop_binary: (skip) + * @route: the #NMIPRoute + * @next_hop: the route's next hop, in binary format + * + * Sets the destination property of this route object. + * + * @next_hop (if non-%NULL) must point to a buffer that is the correct size for + * @route's family. + **/ +void +nm_ip_route_set_next_hop_binary(NMIPRoute *route, gconstpointer next_hop) +{ + g_return_if_fail(route != NULL); + + g_free(route->next_hop); + route->next_hop = canonicalize_ip_binary(route->family, next_hop, TRUE); +} + +/** + * nm_ip_route_get_metric: + * @route: the #NMIPRoute + * + * Gets the route metric property of this route object; lower values + * indicate "better" or more preferred routes; -1 indicates "default" + * (meaning NetworkManager will set it appropriately). + * + * Returns: the route metric + **/ +gint64 +nm_ip_route_get_metric(NMIPRoute *route) +{ + g_return_val_if_fail(route != NULL, 0); + g_return_val_if_fail(route->refcount > 0, 0); + + return route->metric; +} + +/** + * nm_ip_route_set_metric: + * @route: the #NMIPRoute + * @metric: the route metric (or -1 for "default") + * + * Sets the metric property of this route object. + **/ +void +nm_ip_route_set_metric(NMIPRoute *route, gint64 metric) +{ + g_return_if_fail(route != NULL); + g_return_if_fail(valid_metric(metric, NULL)); + + route->metric = metric; +} + +GHashTable * +_nm_ip_route_get_attributes(NMIPRoute *route) +{ + nm_assert(route); + + return route->attributes; +} + +/** + * _nm_ip_route_get_attribute_names: + * @route: the #NMIPRoute + * @sorted: whether to sort the names. Otherwise, their order is + * undefined and unstable. + * @out_length: (allow-none) (out): the number of elements + * + * Gets an array of attribute names defined on @route. + * + * Returns: (array length=out_length) (transfer container): a %NULL-terminated array + * of attribute names or %NULL if there are no attributes. The order of the returned + * names depends on @sorted. + **/ +const char ** +_nm_ip_route_get_attribute_names(const NMIPRoute *route, gboolean sorted, guint *out_length) +{ + nm_assert(route); + + return nm_utils_strdict_get_keys(route->attributes, sorted, out_length); +} + +/** + * nm_ip_route_get_attribute_names: + * @route: the #NMIPRoute + * + * Gets an array of attribute names defined on @route. + * + * Returns: (transfer full): a %NULL-terminated array of attribute names + **/ +char ** +nm_ip_route_get_attribute_names(NMIPRoute *route) +{ + const char **names; + + g_return_val_if_fail(route != NULL, NULL); + + names = _nm_ip_route_get_attribute_names(route, TRUE, NULL); + return nm_utils_strv_make_deep_copied_nonnull(names); +} + +/** + * nm_ip_route_get_attribute: + * @route: the #NMIPRoute + * @name: the name of an route attribute + * + * Gets the value of the attribute with name @name on @route + * + * Returns: (transfer none): the value of the attribute with name @name on + * @route, or %NULL if @route has no such attribute. + **/ +GVariant * +nm_ip_route_get_attribute(NMIPRoute *route, const char *name) +{ + g_return_val_if_fail(route != NULL, NULL); + g_return_val_if_fail(name != NULL && *name != '\0', NULL); + + if (route->attributes) + return g_hash_table_lookup(route->attributes, name); + else + return NULL; +} + +/** + * nm_ip_route_set_attribute: + * @route: the #NMIPRoute + * @name: the name of a route attribute + * @value: (transfer none) (allow-none): the value + * + * Sets the named attribute on @route to the given value. + **/ +void +nm_ip_route_set_attribute(NMIPRoute *route, const char *name, GVariant *value) +{ + g_return_if_fail(route != NULL); + g_return_if_fail(name != NULL && *name != '\0'); + g_return_if_fail(strcmp(name, "dest") != 0 && strcmp(name, "prefix") != 0 + && strcmp(name, "next-hop") != 0 && strcmp(name, "metric") != 0); + + if (!route->attributes) { + route->attributes = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref); + } + + if (value) + g_hash_table_insert(route->attributes, g_strdup(name), g_variant_ref_sink(value)); + else + g_hash_table_remove(route->attributes, name); +} + +static const NMVariantAttributeSpec *const ip_route_attribute_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_CWND, + G_VARIANT_TYPE_UINT32, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_FROM, + G_VARIANT_TYPE_STRING, + .v6 = TRUE, + .str_type = 'p', ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_INITCWND, + G_VARIANT_TYPE_UINT32, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_INITRWND, + G_VARIANT_TYPE_UINT32, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_LOCK_CWND, + G_VARIANT_TYPE_BOOLEAN, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_LOCK_INITCWND, + G_VARIANT_TYPE_BOOLEAN, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_LOCK_INITRWND, + G_VARIANT_TYPE_BOOLEAN, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, + G_VARIANT_TYPE_BOOLEAN, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_LOCK_WINDOW, + G_VARIANT_TYPE_BOOLEAN, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_MTU, + G_VARIANT_TYPE_UINT32, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_ONLINK, + G_VARIANT_TYPE_BOOLEAN, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_SCOPE, + G_VARIANT_TYPE_BYTE, + .v4 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_SRC, + G_VARIANT_TYPE_STRING, + .v4 = TRUE, + .v6 = TRUE, + .str_type = 'a', ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_TABLE, + G_VARIANT_TYPE_UINT32, + .v4 = TRUE, + .v6 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_TOS, G_VARIANT_TYPE_BYTE, .v4 = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_TYPE, + G_VARIANT_TYPE_STRING, + .v4 = TRUE, + .v6 = TRUE, + .str_type = 'T', ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_IP_ROUTE_ATTRIBUTE_WINDOW, + G_VARIANT_TYPE_UINT32, + .v4 = TRUE, + .v6 = TRUE, ), + NULL, +}; + +/** + * nm_ip_route_get_variant_attribute_spec: + * + * Returns: the specifiers for route attributes + * + * Since: 1.8 + */ +const NMVariantAttributeSpec *const * +nm_ip_route_get_variant_attribute_spec(void) +{ + return ip_route_attribute_spec; +} + +/** + * nm_ip_route_attribute_validate: + * @name: the attribute name + * @value: the attribute value + * @family: IP address family of the route + * @known: (out): on return, whether the attribute name is a known one + * @error: (allow-none): return location for a #GError, or %NULL + * + * Validates a route attribute, i.e. checks that the attribute is a known one + * and the value is of the correct type and well-formed. + * + * Returns: %TRUE if the attribute is valid, %FALSE otherwise + * + * Since: 1.8 + */ +gboolean +nm_ip_route_attribute_validate(const char *name, + GVariant * value, + int family, + gboolean * known, + GError ** error) +{ + const NMVariantAttributeSpec *spec; + + g_return_val_if_fail(name, FALSE); + g_return_val_if_fail(value, FALSE); + g_return_val_if_fail(family == AF_INET || family == AF_INET6, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + spec = _nm_variant_attribute_spec_find_binary_search(ip_route_attribute_spec, + G_N_ELEMENTS(ip_route_attribute_spec) - 1, + name); + if (!spec) { + NM_SET_OUT(known, FALSE); + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unknown attribute")); + return FALSE; + } + + NM_SET_OUT(known, TRUE); + + if (!g_variant_is_of_type(value, spec->type)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid attribute type '%s'"), + g_variant_get_type_string(value)); + return FALSE; + } + + if ((family == AF_INET && !spec->v4) || (family == AF_INET6 && !spec->v6)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + family == AF_INET ? _("attribute is not valid for a IPv4 route") + : _("attribute is not valid for a IPv6 route")); + return FALSE; + } + + if (g_variant_type_equal(spec->type, G_VARIANT_TYPE_STRING)) { + const char *string = g_variant_get_string(value, NULL); + + switch (spec->str_type) { + case 'a': /* IP address */ + if (!nm_utils_ipaddr_is_valid(family, string)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + family == AF_INET ? _("'%s' is not a valid IPv4 address") + : _("'%s' is not a valid IPv6 address"), + string); + return FALSE; + } + break; + case 'p': /* IP address + optional prefix */ + { + gs_free char *addr_free = NULL; + const char * addr = string; + const char * str; + + str = strchr(addr, '/'); + if (str) { + addr = nm_strndup_a(200, addr, str - addr, &addr_free); + str++; + if (_nm_utils_ascii_str_to_int64(str, 10, 0, family == AF_INET ? 32 : 128, -1) + < 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid prefix %s"), + str); + return FALSE; + } + } + if (!nm_utils_ipaddr_is_valid(family, addr)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + family == AF_INET ? _("'%s' is not a valid IPv4 address") + : _("'%s' is not a valid IPv6 address"), + string); + return FALSE; + } + break; + } + case 'T': /* route type. */ + if (!NM_IN_SET(nm_net_aux_rtnl_rtntype_a2n(string), RTN_UNICAST, RTN_LOCAL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%s is not a valid route type"), + string); + return FALSE; + } + break; + default: + break; + } + } + + return TRUE; +} + +gboolean +_nm_ip_route_attribute_validate_all(const NMIPRoute *route, GError **error) +{ + NMUtilsNamedValue attrs_static[G_N_ELEMENTS(ip_route_attribute_spec)]; + gs_free NMUtilsNamedValue *attrs_free = NULL; + const NMUtilsNamedValue * attrs; + guint attrs_len; + GVariant * val; + guint i; + guint8 u8; + + g_return_val_if_fail(route, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + if (!route->attributes) + return TRUE; + + attrs = nm_utils_named_values_from_strdict(route->attributes, + &attrs_len, + attrs_static, + &attrs_free); + for (i = 0; i < attrs_len; i++) { + const char *key = attrs[i].name; + GVariant * val2 = attrs[i].value_ptr; + + if (!nm_ip_route_attribute_validate(key, val2, route->family, NULL, NULL)) + return FALSE; + } + + if ((val = g_hash_table_lookup(route->attributes, NM_IP_ROUTE_ATTRIBUTE_TYPE))) { + int v_i; + + nm_assert(g_variant_is_of_type(val, G_VARIANT_TYPE_STRING)); + + v_i = nm_net_aux_rtnl_rtntype_a2n(g_variant_get_string(val, NULL)); + nm_assert(v_i >= 0); + + if (v_i == RTN_LOCAL && route->family == AF_INET + && (val = g_hash_table_lookup(route->attributes, NM_IP_ROUTE_ATTRIBUTE_SCOPE))) { + nm_assert(g_variant_is_of_type(val, G_VARIANT_TYPE_BYTE)); + u8 = g_variant_get_byte(val); + + if (!NM_IN_SET(u8, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("route scope is invalid")); + return FALSE; + } + } + } + + return TRUE; +} + +/*****************************************************************************/ + +struct NMIPRoutingRule { + NMIPAddr from_bin; + NMIPAddr to_bin; + char * from_str; + char * to_str; + char * iifname; + char * oifname; + guint ref_count; + guint32 priority; + guint32 table; + gint32 suppress_prefixlength; + guint32 fwmark; + guint32 fwmask; + guint32 uid_range_start; + guint32 uid_range_end; + guint16 sport_start; + guint16 sport_end; + guint16 dport_start; + guint16 dport_end; + guint8 action; + guint8 from_len; + guint8 to_len; + guint8 tos; + guint8 ipproto; + bool is_v4 : 1; + bool sealed : 1; + bool priority_has : 1; + bool uid_range_has : 1; + bool from_has : 1; + bool from_valid : 1; + bool to_has : 1; + bool to_valid : 1; + bool invert : 1; +}; + +static NMIPRoutingRule *_ip_routing_rule_dup(const NMIPRoutingRule *rule); + +G_DEFINE_BOXED_TYPE(NMIPRoutingRule, + nm_ip_routing_rule, + _ip_routing_rule_dup, + nm_ip_routing_rule_unref) + +static gboolean +NM_IS_IP_ROUTING_RULE(const NMIPRoutingRule *self, gboolean also_sealed) +{ + return self && self->ref_count > 0 && (also_sealed || !self->sealed); +} + +static int +_ip_routing_rule_get_addr_family(const NMIPRoutingRule *self) +{ + nm_assert(NM_IS_IP_ROUTING_RULE(self, TRUE)); + + return self->is_v4 ? AF_INET : AF_INET6; +} + +static int +_ip_routing_rule_get_addr_size(const NMIPRoutingRule *self) +{ + nm_assert(NM_IS_IP_ROUTING_RULE(self, TRUE)); + + return self->is_v4 ? sizeof(struct in_addr) : sizeof(struct in6_addr); +} + +static NMIPRoutingRule * +_ip_routing_rule_dup(const NMIPRoutingRule *rule) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(rule, TRUE), NULL); + + if (rule->sealed) + return nm_ip_routing_rule_ref((NMIPRoutingRule *) rule); + return nm_ip_routing_rule_new_clone(rule); +} + +/** + * nm_ip_routing_rule_new: + * @addr_family: the address family of the routing rule. Must be either + * %AF_INET (2) or %AF_INET6 (10). + * + * Returns: (transfer full): a newly created rule instance with the + * provided address family. The instance is unsealed. + * + * Since: 1.18 + */ +NMIPRoutingRule * +nm_ip_routing_rule_new(int addr_family) +{ + NMIPRoutingRule *self; + + g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NULL); + + self = g_slice_new(NMIPRoutingRule); + *self = (NMIPRoutingRule){ + .ref_count = 1, + .is_v4 = (addr_family == AF_INET), + .action = FR_ACT_TO_TBL, + .table = RT_TABLE_MAIN, + .suppress_prefixlength = -1, + }; + return self; +} + +/** + * nm_ip_routing_rule_new_clone: + * @rule: the #NMIPRoutingRule to clone. + * + * Returns: (transfer full): a newly created rule instance with + * the same settings as @rule. Note that the instance will + * always be unsealred. + * + * Since: 1.18 + */ +NMIPRoutingRule * +nm_ip_routing_rule_new_clone(const NMIPRoutingRule *rule) +{ + NMIPRoutingRule *self; + + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(rule, TRUE), NULL); + + self = g_slice_new(NMIPRoutingRule); + *self = (NMIPRoutingRule){ + .ref_count = 1, + .sealed = FALSE, + .is_v4 = rule->is_v4, + + .priority = rule->priority, + .priority_has = rule->priority_has, + + .invert = rule->invert, + + .tos = rule->tos, + + .fwmark = rule->fwmark, + .fwmask = rule->fwmask, + + .sport_start = rule->sport_start, + .sport_end = rule->sport_end, + .dport_start = rule->dport_start, + .dport_end = rule->dport_end, + + .uid_range_start = rule->uid_range_start, + .uid_range_end = rule->uid_range_end, + .uid_range_has = rule->uid_range_has, + + .ipproto = rule->ipproto, + + .from_len = rule->from_len, + .from_bin = rule->from_bin, + .from_str = (rule->from_has && !rule->from_valid) ? g_strdup(rule->from_str) : NULL, + .from_has = rule->from_has, + .from_valid = rule->from_valid, + + .to_len = rule->to_len, + .to_bin = rule->to_bin, + .to_str = (rule->to_has && !rule->to_valid) ? g_strdup(rule->to_str) : NULL, + .to_has = rule->to_has, + .to_valid = rule->to_valid, + + .iifname = g_strdup(rule->iifname), + .oifname = g_strdup(rule->oifname), + + .action = rule->action, + .table = rule->table, + + .suppress_prefixlength = rule->suppress_prefixlength, + }; + return self; +} + +/** + * nm_ip_routing_rule_ref: + * @self: (allow-none): the #NMIPRoutingRule instance + * + * Increases the reference count of the instance. + * This is not thread-safe. + * + * Returns: (transfer full): the @self argument with incremented + * reference count. + * + * Since: 1.18 + */ +NMIPRoutingRule * +nm_ip_routing_rule_ref(NMIPRoutingRule *self) +{ + if (!self) + return NULL; + + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + nm_assert(self->ref_count < G_MAXUINT); + self->ref_count++; + return self; +} + +/** + * nm_ip_routing_rule_unref: + * @self: (allow-none): the #NMIPRoutingRule instance + * + * Decreases the reference count of the instance and destroys + * the instance if the reference count reaches zero. + * This is not thread-safe. + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_unref(NMIPRoutingRule *self) +{ + if (!self) + return; + + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE)); + + if (--self->ref_count > 0) + return; + + g_free(self->from_str); + g_free(self->to_str); + g_free(self->iifname); + g_free(self->oifname); + + g_slice_free(NMIPRoutingRule, self); +} + +/** + * nm_ip_routing_rule_is_sealed: + * @self: the #NMIPRoutingRule instance + * + * Returns: whether @self is sealed. Once sealed, an instance + * cannot be modified nor unsealed. + * + * Since: 1.18 + */ +gboolean +nm_ip_routing_rule_is_sealed(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), FALSE); + + return self->sealed; +} + +/** + * nm_ip_routing_rule_seal: + * @self: the #NMIPRoutingRule instance + * + * Seals the routing rule. Afterwards, the instance can no longer be + * modified, and it is a bug to call any of the accessors that would + * modify the rule. If @self was already sealed, this has no effect. + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_seal(NMIPRoutingRule *self) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE)); + + self->sealed = TRUE; +} + +/** + * nm_ip_routing_rule_get_addr_family: + * @self: the #NMIPRoutingRule instance + * + * Returns: the address family of the rule. Either %AF_INET or %AF_INET6. + * + * Since: 1.18 + */ +int +nm_ip_routing_rule_get_addr_family(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), AF_UNSPEC); + + return _ip_routing_rule_get_addr_family(self); +} + +/** + * nm_ip_routing_rule_get_priority: + * @self: the #NMIPRoutingRule instance + * + * Returns: the priority. A valid priority is in the range from + * 0 to %G_MAXUINT32. If unset, -1 is returned. + * + * Since: 1.18 + */ +gint64 +nm_ip_routing_rule_get_priority(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), -1); + + return self->priority_has ? (gint64) self->priority : (gint64) -1; +} + +/** + * nm_ip_routing_rule_set_priority: + * @self: the #NMIPRoutingRule instance + * @priority: the priority to set + * + * A valid priority ranges from 0 to %G_MAXUINT32. "-1" is also allowed + * to reset the priority. It is a bug calling this function with any + * other value. + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_priority(NMIPRoutingRule *self, gint64 priority) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + if (priority >= 0 && priority <= (gint64) G_MAXUINT32) { + self->priority = (guint32) priority; + self->priority_has = TRUE; + } else { + g_return_if_fail(priority == -1); + self->priority = 0; + self->priority_has = FALSE; + } +} + +/** + * nm_ip_routing_rule_get_invert: + * @self: the #NMIPRoutingRule instance + * + * Returns: the "invert" setting of the rule. + * + * Since: 1.18 + */ +gboolean +nm_ip_routing_rule_get_invert(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), FALSE); + + return self->invert; +} + +/** + * nm_ip_routing_rule_set_invert: + * @self: the #NMIPRoutingRule instance + * @invert: the new value to set + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_invert(NMIPRoutingRule *self, gboolean invert) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->invert = invert; +} + +/** + * nm_ip_routing_rule_get_from_len: + * @self: the #NMIPRoutingRule instance + * + * Returns: the set prefix length for the from/src parameter. + * + * Since: 1.18 + */ +guint8 +nm_ip_routing_rule_get_from_len(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->from_len; +} + +/** + * nm_ip_routing_rule_get_from: + * @self: the #NMIPRoutingRule instance + * + * Returns: (transfer none): the set from/src parameter or + * %NULL, if no value is set. + * + * Since: 1.18 + */ +const char * +nm_ip_routing_rule_get_from(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + if (!self->from_has) + return NULL; + if (!self->from_str) { + nm_assert(self->from_valid); + ((NMIPRoutingRule *) self)->from_str = + nm_utils_inet_ntop_dup(_ip_routing_rule_get_addr_family(self), &self->from_bin); + } + return self->from_str; +} + +const NMIPAddr * +nm_ip_routing_rule_get_from_bin(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + return (self->from_has && self->from_valid) ? &self->from_bin : NULL; +} + +void +nm_ip_routing_rule_set_from_bin(NMIPRoutingRule *self, gconstpointer from, guint8 len) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + nm_clear_g_free(&self->from_str); + + if (!from) { + self->from_has = FALSE; + self->from_len = len; + return; + } + + self->from_has = TRUE; + self->from_len = len; + self->from_valid = TRUE; + nm_ip_addr_set(_ip_routing_rule_get_addr_family(self), &self->from_bin, from); +} + +/** + * nm_ip_routing_rule_set_from: + * @self: the #NMIPRoutingRule instance + * @from: (allow-none): the from/src address to set. + * The address family must match. + * @len: the corresponding prefix length of the address. + * + * Setting invalid values is accepted, but will later fail + * during nm_ip_routing_rule_validate(). + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_from(NMIPRoutingRule *self, const char *from, guint8 len) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + if (!from) { + nm_clear_g_free(&self->from_str); + self->from_has = FALSE; + self->from_len = len; + return; + } + + nm_clear_g_free(&self->from_str); + self->from_has = TRUE; + self->from_len = len; + self->from_valid = nm_utils_parse_inaddr_bin(_ip_routing_rule_get_addr_family(self), + from, + NULL, + &self->from_bin); + if (!self->from_valid) + self->from_str = g_strdup(from); +} + +/** + * nm_ip_routing_rule_get_to_len: + * @self: the #NMIPRoutingRule instance + * + * Returns: the set prefix length for the to/dst parameter. + * + * Since: 1.18 + */ +guint8 +nm_ip_routing_rule_get_to_len(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->to_len; +} + +/** + * nm_ip_routing_rule_get_to: + * @self: the #NMIPRoutingRule instance + * + * Returns: (transfer none): the set to/dst parameter or + * %NULL, if no value is set. + * + * Since: 1.18 + */ +const char * +nm_ip_routing_rule_get_to(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + if (!self->to_has) + return NULL; + if (!self->to_str) { + nm_assert(self->to_valid); + ((NMIPRoutingRule *) self)->to_str = + nm_utils_inet_ntop_dup(_ip_routing_rule_get_addr_family(self), &self->to_bin); + } + return self->to_str; +} + +const NMIPAddr * +nm_ip_routing_rule_get_to_bin(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + return (self->to_has && self->to_valid) ? &self->to_bin : NULL; +} + +void +nm_ip_routing_rule_set_to_bin(NMIPRoutingRule *self, gconstpointer to, guint8 len) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + nm_clear_g_free(&self->to_str); + + if (!to) { + self->to_has = FALSE; + self->to_len = len; + return; + } + + self->to_has = TRUE; + self->to_len = len; + self->to_valid = TRUE; + nm_ip_addr_set(_ip_routing_rule_get_addr_family(self), &self->to_bin, to); +} + +/** + * nm_ip_routing_rule_set_to: + * @self: the #NMIPRoutingRule instance + * @to: (allow-none): the to/dst address to set. + * The address family must match. + * @len: the corresponding prefix length of the address. + * If @to is %NULL, this valid is ignored. + * + * Setting invalid values is accepted, but will later fail + * during nm_ip_routing_rule_validate(). + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_to(NMIPRoutingRule *self, const char *to, guint8 len) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + if (!to) { + nm_clear_g_free(&self->to_str); + self->to_has = FALSE; + self->to_len = len; + return; + } + + nm_clear_g_free(&self->to_str); + self->to_has = TRUE; + self->to_len = len; + self->to_valid = + nm_utils_parse_inaddr_bin(_ip_routing_rule_get_addr_family(self), to, NULL, &self->to_bin); + if (!self->to_valid) + self->to_str = g_strdup(to); +} + +/** + * nm_ip_routing_rule_get_tos: + * @self: the #NMIPRoutingRule instance + * + * Returns: the tos of the rule. + * + * Since: 1.18 + */ +guint8 +nm_ip_routing_rule_get_tos(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->tos; +} + +/** + * nm_ip_routing_rule_set_tos: + * @self: the #NMIPRoutingRule instance + * @tos: the tos to set + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_tos(NMIPRoutingRule *self, guint8 tos) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->tos = tos; +} + +/** + * nm_ip_routing_rule_get_ipproto: + * @self: the #NMIPRoutingRule instance + * + * Returns: the ipproto of the rule. + * + * Since: 1.18 + */ +guint8 +nm_ip_routing_rule_get_ipproto(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->ipproto; +} + +/** + * nm_ip_routing_rule_set_ipproto: + * @self: the #NMIPRoutingRule instance + * @ipproto: the ipproto to set + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_ipproto(NMIPRoutingRule *self, guint8 ipproto) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->ipproto = ipproto; +} + +/** + * nm_ip_routing_rule_get_source_port_start: + * @self: the #NMIPRoutingRule instance + * + * Returns: the source port start setting. + * + * Since: 1.18 + */ +guint16 +nm_ip_routing_rule_get_source_port_start(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->sport_start; +} + +/** + * nm_ip_routing_rule_get_source_port_end: + * @self: the #NMIPRoutingRule instance + * + * Returns: the source port end setting. + * + * Since: 1.18 + */ +guint16 +nm_ip_routing_rule_get_source_port_end(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->sport_end; +} + +/** + * nm_ip_routing_rule_set_source_port: + * @self: the #NMIPRoutingRule instance + * @start: the start port to set. + * @end: the end port to set. + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_source_port(NMIPRoutingRule *self, guint16 start, guint16 end) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->sport_start = start; + self->sport_end = end; +} + +/** + * nm_ip_routing_rule_get_destination_port_start: + * @self: the #NMIPRoutingRule instance + * + * Returns: the destination port start setting. + * + * Since: 1.18 + */ +guint16 +nm_ip_routing_rule_get_destination_port_start(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->dport_start; +} + +/** + * nm_ip_routing_rule_get_destination_port_end: + * @self: the #NMIPRoutingRule instance + * + * Returns: the destination port end setting. + * + * Since: 1.18 + */ +guint16 +nm_ip_routing_rule_get_destination_port_end(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->dport_end; +} + +/** + * nm_ip_routing_rule_set_destination_port: + * @self: the #NMIPRoutingRule instance + * @start: the start port to set. + * @end: the end port to set. + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_destination_port(NMIPRoutingRule *self, guint16 start, guint16 end) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->dport_start = start; + self->dport_end = end; +} + +/** + * nm_ip_routing_rule_get_fwmark: + * @self: the #NMIPRoutingRule instance + * + * Returns: the fwmark setting. + * + * Since: 1.18 + */ +guint32 +nm_ip_routing_rule_get_fwmark(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->fwmark; +} + +/** + * nm_ip_routing_rule_get_fwmask: + * @self: the #NMIPRoutingRule instance + * + * Returns: the fwmask setting. + * + * Since: 1.18 + */ +guint32 +nm_ip_routing_rule_get_fwmask(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->fwmask; +} + +/** + * nm_ip_routing_rule_set_fwmark: + * @self: the #NMIPRoutingRule instance + * @fwmark: the fwmark + * @fwmask: the fwmask + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_fwmark(NMIPRoutingRule *self, guint32 fwmark, guint32 fwmask) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->fwmark = fwmark; + self->fwmask = fwmask; +} + +/** + * nm_ip_routing_rule_get_iifname: + * @self: the #NMIPRoutingRule instance. + * + * Returns: (transfer none): the set iifname or %NULL if unset. + * + * Since: 1.18 + */ +const char * +nm_ip_routing_rule_get_iifname(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + return self->iifname; +} + +gboolean +nm_ip_routing_rule_get_xifname_bin(const NMIPRoutingRule *self, + gboolean iif /* or else oif */, + char out_xifname[static 16 /* IFNAMSIZ */]) +{ + gs_free gpointer bin_to_free = NULL; + const char * xifname; + gconstpointer bin; + gsize len; + + nm_assert(NM_IS_IP_ROUTING_RULE(self, TRUE)); + nm_assert(out_xifname); + + xifname = iif ? self->iifname : self->oifname; + + if (!xifname) + return FALSE; + + bin = nm_utils_buf_utf8safe_unescape(xifname, + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, + &len, + &bin_to_free); + + strncpy(out_xifname, bin, 16 /* IFNAMSIZ */); + out_xifname[15] = '\0'; + return TRUE; +} + +/** + * nm_ip_routing_rule_set_iifname: + * @self: the #NMIPRoutingRule instance. + * @iifname: (allow-none): the iifname to set or %NULL to unset. + * + * The name supports C backslash escaping for non-UTF-8 characters. + * Note that nm_ip_routing_rule_from_string() too uses backslash + * escaping when tokenizing the words by whitespace. So, in string + * representation you'd get double backslashes. + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_iifname(NMIPRoutingRule *self, const char *iifname) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + g_free(self->iifname); + self->iifname = g_strdup(iifname); +} + +/** + * nm_ip_routing_rule_get_oifname: + * @self: the #NMIPRoutingRule instance. + * + * Returns: (transfer none): the set oifname or %NULL if unset. + * + * Since: 1.18 + */ +const char * +nm_ip_routing_rule_get_oifname(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + return self->oifname; +} + +/** + * nm_ip_routing_rule_set_oifname: + * @self: the #NMIPRoutingRule instance. + * @oifname: (allow-none): the oifname to set or %NULL to unset. + * + * The name supports C backslash escaping for non-UTF-8 characters. + * Note that nm_ip_routing_rule_from_string() too uses backslash + * escaping when tokenizing the words by whitespace. So, in string + * representation you'd get double backslashes. + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_oifname(NMIPRoutingRule *self, const char *oifname) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + g_free(self->oifname); + self->oifname = g_strdup(oifname); +} + +/** + * nm_ip_routing_rule_get_action: + * @self: the #NMIPRoutingRule instance + * + * Returns: the set action. + * + * Since: 1.18 + */ +guint8 +nm_ip_routing_rule_get_action(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->action; +} + +/** + * nm_ip_routing_rule_set_action: + * @self: the #NMIPRoutingRule instance + * @action: the action to set + * + * Note that currently only certain actions are allowed. nm_ip_routing_rule_validate() + * will reject unsupported actions as invalid. + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_action(NMIPRoutingRule *self, guint8 action) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->action = action; +} + +/** + * nm_ip_routing_rule_get_table: + * @self: the #NMIPRoutingRule instance + * + * Returns: the set table. + * + * Since: 1.18 + */ +guint32 +nm_ip_routing_rule_get_table(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), 0); + + return self->table; +} + +/** + * nm_ip_routing_rule_set_table: + * @self: the #NMIPRoutingRule instance + * @table: the table to set + * + * Since: 1.18 + */ +void +nm_ip_routing_rule_set_table(NMIPRoutingRule *self, guint32 table) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->table = table; +} + +/** + * nm_ip_routing_rule_get_suppress_prefixlength: + * @self: the #NMIPRoutingRule instance + * + * Returns: the suppress_prefixlength of the rule. -1 means that the value is unset. + * + * Since: 1.20 + */ +gint32 +nm_ip_routing_rule_get_suppress_prefixlength(const NMIPRoutingRule *self) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), -1); + + return self->suppress_prefixlength; +} + +/** + * nm_ip_routing_rule_set_suppress_prefixlength: + * @self: the #NMIPRoutingRule instance + * @suppress_prefixlength: the suppress_prefixlength to set. The value -1 means + * unset. + * + * Since: 1.20 + */ +void +nm_ip_routing_rule_set_suppress_prefixlength(NMIPRoutingRule *self, gint32 suppress_prefixlength) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + self->suppress_prefixlength = suppress_prefixlength; +} + +/** + * nm_ip_routing_rule_get_uid_range: + * @self: the #NMIPRoutingRule instance + * @out_range_start: (out) (allow-none): returns the start of the range + * or 0 if the range is not set. + * @out_range_end: (out) (allow-none): returns the end of the range + * or 0 if the range is not set. + * + * Returns: %TRUE if a uid range is set. + * + * Since: 1.32. Due to a bug, the symbols of this function + * are only exported starting with version 1.34.0. The function + * is unusable in 1.32. + */ +gboolean +nm_ip_routing_rule_get_uid_range(const NMIPRoutingRule *self, + guint32 * out_range_start, + guint32 * out_range_end) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), -1); + + nm_assert(self->uid_range_has || (self->uid_range_start == 0 && self->uid_range_end == 0)); + + NM_SET_OUT(out_range_start, self->uid_range_start); + NM_SET_OUT(out_range_end, self->uid_range_end); + return self->uid_range_has; +} + +/** + * nm_ip_routing_rule_set_uid_range: + * @self: the #NMIPRoutingRule instance + * @uid_range_start: the uid_range start to set. + * @uid_range_end: the uid_range start to set. + * + * For a valid range, start must be less or equal to end. + * If set to an invalid range, the range gets unset. + * + * Since: 1.32. Due to a bug, the symbols of this function + * are only exported starting with version 1.34.0. The function + * is unusable in 1.32. + */ +void +nm_ip_routing_rule_set_uid_range(NMIPRoutingRule *self, + guint32 uid_range_start, + guint32 uid_range_end) +{ + g_return_if_fail(NM_IS_IP_ROUTING_RULE(self, FALSE)); + + if (uid_range_start > uid_range_end) { + self->uid_range_start = 0; + self->uid_range_end = 0; + self->uid_range_has = FALSE; + return; + } + self->uid_range_start = uid_range_start; + self->uid_range_end = uid_range_end; + self->uid_range_has = TRUE; +} + +/** + * nm_ip_routing_rule_cmp: + * @rule: (allow-none): the #NMIPRoutingRule instance to compare + * @other: (allow-none): the other #NMIPRoutingRule instance to compare + * + * Returns: zero, a positive, or a negative integer to indicate + * equality or how the arguments compare. + * + * Since: 1.18 + */ +int +nm_ip_routing_rule_cmp(const NMIPRoutingRule *rule, const NMIPRoutingRule *other) +{ + NM_CMP_SELF(rule, other); + + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(rule, TRUE), 0); + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(other, TRUE), 0); + + NM_CMP_FIELD_UNSAFE(rule, other, priority_has); + if (rule->priority_has) + NM_CMP_FIELD(rule, other, priority); + + NM_CMP_FIELD_UNSAFE(rule, other, is_v4); + + NM_CMP_FIELD_UNSAFE(rule, other, invert); + + NM_CMP_FIELD(rule, other, tos); + + NM_CMP_FIELD(rule, other, fwmark); + NM_CMP_FIELD(rule, other, fwmask); + + NM_CMP_FIELD(rule, other, action); + + NM_CMP_FIELD(rule, other, table); + + NM_CMP_FIELD(rule, other, suppress_prefixlength); + + NM_CMP_FIELD(rule, other, sport_start); + NM_CMP_FIELD(rule, other, sport_end); + NM_CMP_FIELD(rule, other, dport_start); + NM_CMP_FIELD(rule, other, dport_end); + + NM_CMP_FIELD(rule, other, ipproto); + + NM_CMP_FIELD_UNSAFE(rule, other, uid_range_has); + if (rule->uid_range_has) { + NM_CMP_FIELD(rule, other, uid_range_end); + NM_CMP_FIELD(rule, other, uid_range_start); + } + + /* We compare the plain strings, not the binary values after utf8safe unescaping. + * + * The reason is, that the rules differ already when the direct strings differ, not + * only when the unescaped names differ. */ + NM_CMP_FIELD_STR0(rule, other, iifname); + NM_CMP_FIELD_STR0(rule, other, oifname); + + NM_CMP_FIELD(rule, other, from_len); + + NM_CMP_FIELD_UNSAFE(rule, other, from_has); + if (rule->from_has) { + NM_CMP_FIELD_UNSAFE(rule, other, from_valid); + if (rule->from_valid) { + NM_CMP_RETURN( + memcmp(&rule->from_bin, &other->from_bin, _ip_routing_rule_get_addr_size(rule))); + } else + NM_CMP_FIELD_STR(rule, other, from_str); + } + + NM_CMP_FIELD(rule, other, to_len); + + NM_CMP_FIELD_UNSAFE(rule, other, to_has); + if (rule->to_has) { + NM_CMP_FIELD_UNSAFE(rule, other, to_valid); + if (rule->to_valid) { + NM_CMP_RETURN( + memcmp(&rule->to_bin, &other->to_bin, _ip_routing_rule_get_addr_size(rule))); + } else + NM_CMP_FIELD_STR(rule, other, to_str); + } + + return 0; +} + +static gboolean +_rr_xport_range_valid(guint16 xport_start, guint16 xport_end) +{ + if (xport_start == 0) + return (xport_end == 0); + + return xport_start <= xport_end && xport_end < 0xFFFFu; +} + +static gboolean +_rr_xport_range_parse(char *str, gint64 *out_start, guint16 *out_end) +{ + guint16 start, end; + gint64 i64; + char * s; + + s = strchr(str, '-'); + if (s) + *(s++) = '\0'; + + i64 = _nm_utils_ascii_str_to_int64(str, 10, 0, 0xFFFF, -1); + if (i64 == -1) + return FALSE; + + start = i64; + if (s) { + i64 = _nm_utils_ascii_str_to_int64(s, 10, 0, 0xFFFF, -1); + if (i64 == -1) + return FALSE; + end = i64; + } else + end = start; + + *out_start = start; + *out_end = end; + return TRUE; +} + +/** + * nm_ip_routing_rule_validate: + * @self: the #NMIPRoutingRule instance to validate + * @error: (allow-none) (out): the error result if validation fails. + * + * Returns: %TRUE if the rule validates. + * + * Since: 1.18 + */ +gboolean +nm_ip_routing_rule_validate(const NMIPRoutingRule *self, GError **error) +{ + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + /* Kernel may be more flexible about validating. We do a strict validation + * here and reject certain settings eagerly. We can always relax it later. */ + + if (!self->priority_has) { + /* iproute2 accepts not specifying the priority, in which case kernel will select + * an unused priority. We don't allow for that, and will always require the user to + * select a priority. + * + * Note that if the user selects priority 0 or a non-unique priority, this is problematic + * due to kernel bugs rh#1685816 and rh#1685816. It may result in NetworkManager wrongly being + * unable to add a rule or deleting the wrong rule. + * This problem is not at all specific to the priority, it affects all rules that + * have default values which confuse kernel. But setting a unique priority avoids + * this problem nicely. */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid priority")); + return FALSE; + } + + if (NM_IN_SET(self->action, FR_ACT_TO_TBL)) { + if (self->table == 0) { + /* with IPv4, kernel allows a table (in RTM_NEWRULE) of zero to automatically select + * an unused table. We don't. The user needs to specify the table. + * + * For IPv6, kernel doesn't allow a table of zero, so we are consistent here. */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("missing table")); + return FALSE; + } + } else if (NM_IN_SET(self->action, FR_ACT_BLACKHOLE, FR_ACT_PROHIBIT, FR_ACT_UNREACHABLE)) { + /* pass */ + } else { + /* we currently only support the listed actions. */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid action type")); + return FALSE; + } + + if (self->from_len == 0) { + if (self->from_has) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("has from/src but the prefix-length is zero")); + return FALSE; + } + } else if (self->from_len > 0 && self->from_len <= 8 * _ip_routing_rule_get_addr_size(self)) { + if (!self->from_has) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("missing from/src for a non zero prefix-length")); + return FALSE; + } + if (!self->from_valid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid from/src")); + return FALSE; + } + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid prefix length for from/src")); + return FALSE; + } + + if (self->to_len == 0) { + if (self->to_has) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("has to/dst but the prefix-length is zero")); + return FALSE; + } + } else if (self->to_len > 0 && self->to_len <= 8 * _ip_routing_rule_get_addr_size(self)) { + if (!self->to_has) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("missing to/dst for a non zero prefix-length")); + return FALSE; + } + if (!self->to_valid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid to/dst")); + return FALSE; + } + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid prefix length for to/dst")); + return FALSE; + } + + if (self->iifname + && (!g_utf8_validate(self->iifname, -1, NULL) + || !nm_utils_is_valid_iface_name_utf8safe(self->iifname))) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid iifname")); + return FALSE; + } + + if (self->oifname + && (!g_utf8_validate(self->oifname, -1, NULL) + || !nm_utils_is_valid_iface_name_utf8safe(self->oifname))) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid oifname")); + return FALSE; + } + + if (!_rr_xport_range_valid(self->sport_start, self->sport_end)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid source port range")); + return FALSE; + } + + if (!_rr_xport_range_valid(self->dport_start, self->dport_end)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid destination port range")); + return FALSE; + } + + if (self->suppress_prefixlength != -1) { + if (self->suppress_prefixlength < -1 + || self->suppress_prefixlength > (self->is_v4 ? 32 : 128)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("suppress_prefixlength out of range")); + return FALSE; + } + if (self->action != FR_ACT_TO_TBL) { + g_set_error_literal( + error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("suppress_prefixlength is only allowed with the to-table action")); + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + +typedef enum { + RR_DBUS_ATTR_ACTION, + RR_DBUS_ATTR_DPORT_END, + RR_DBUS_ATTR_DPORT_START, + RR_DBUS_ATTR_FAMILY, + RR_DBUS_ATTR_FROM, + RR_DBUS_ATTR_FROM_LEN, + RR_DBUS_ATTR_FWMARK, + RR_DBUS_ATTR_FWMASK, + RR_DBUS_ATTR_IIFNAME, + RR_DBUS_ATTR_INVERT, + RR_DBUS_ATTR_IPPROTO, + RR_DBUS_ATTR_OIFNAME, + RR_DBUS_ATTR_PRIORITY, + RR_DBUS_ATTR_SPORT_END, + RR_DBUS_ATTR_SPORT_START, + RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH, + RR_DBUS_ATTR_TABLE, + RR_DBUS_ATTR_TO, + RR_DBUS_ATTR_TO_LEN, + RR_DBUS_ATTR_TOS, + RR_DBUS_ATTR_UID_RANGE_END, + RR_DBUS_ATTR_UID_RANGE_START, + + _RR_DBUS_ATTR_NUM, +} RRDbusAttr; + +typedef struct { + const char * name; + const GVariantType *dbus_type; +} RRDbusData; + +static const RRDbusData rr_dbus_data[_RR_DBUS_ATTR_NUM] = { +#define _D(attr, _name, type) \ + [attr] = { \ + .name = _name, \ + .dbus_type = type, \ + } + _D(RR_DBUS_ATTR_ACTION, NM_IP_ROUTING_RULE_ATTR_ACTION, G_VARIANT_TYPE_BYTE), + _D(RR_DBUS_ATTR_DPORT_END, NM_IP_ROUTING_RULE_ATTR_DPORT_END, G_VARIANT_TYPE_UINT16), + _D(RR_DBUS_ATTR_DPORT_START, NM_IP_ROUTING_RULE_ATTR_DPORT_START, G_VARIANT_TYPE_UINT16), + _D(RR_DBUS_ATTR_FAMILY, NM_IP_ROUTING_RULE_ATTR_FAMILY, G_VARIANT_TYPE_INT32), + _D(RR_DBUS_ATTR_FROM, NM_IP_ROUTING_RULE_ATTR_FROM, G_VARIANT_TYPE_STRING), + _D(RR_DBUS_ATTR_FROM_LEN, NM_IP_ROUTING_RULE_ATTR_FROM_LEN, G_VARIANT_TYPE_BYTE), + _D(RR_DBUS_ATTR_FWMARK, NM_IP_ROUTING_RULE_ATTR_FWMARK, G_VARIANT_TYPE_UINT32), + _D(RR_DBUS_ATTR_FWMASK, NM_IP_ROUTING_RULE_ATTR_FWMASK, G_VARIANT_TYPE_UINT32), + _D(RR_DBUS_ATTR_IIFNAME, NM_IP_ROUTING_RULE_ATTR_IIFNAME, G_VARIANT_TYPE_STRING), + _D(RR_DBUS_ATTR_INVERT, NM_IP_ROUTING_RULE_ATTR_INVERT, G_VARIANT_TYPE_BOOLEAN), + _D(RR_DBUS_ATTR_IPPROTO, NM_IP_ROUTING_RULE_ATTR_IPPROTO, G_VARIANT_TYPE_BYTE), + _D(RR_DBUS_ATTR_OIFNAME, NM_IP_ROUTING_RULE_ATTR_OIFNAME, G_VARIANT_TYPE_STRING), + _D(RR_DBUS_ATTR_PRIORITY, NM_IP_ROUTING_RULE_ATTR_PRIORITY, G_VARIANT_TYPE_UINT32), + _D(RR_DBUS_ATTR_SPORT_END, NM_IP_ROUTING_RULE_ATTR_SPORT_END, G_VARIANT_TYPE_UINT16), + _D(RR_DBUS_ATTR_SPORT_START, NM_IP_ROUTING_RULE_ATTR_SPORT_START, G_VARIANT_TYPE_UINT16), + _D(RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH, + NM_IP_ROUTING_RULE_ATTR_SUPPRESS_PREFIXLENGTH, + G_VARIANT_TYPE_INT32), + _D(RR_DBUS_ATTR_TABLE, NM_IP_ROUTING_RULE_ATTR_TABLE, G_VARIANT_TYPE_UINT32), + _D(RR_DBUS_ATTR_TO, NM_IP_ROUTING_RULE_ATTR_TO, G_VARIANT_TYPE_STRING), + _D(RR_DBUS_ATTR_TOS, NM_IP_ROUTING_RULE_ATTR_TOS, G_VARIANT_TYPE_BYTE), + _D(RR_DBUS_ATTR_TO_LEN, NM_IP_ROUTING_RULE_ATTR_TO_LEN, G_VARIANT_TYPE_BYTE), + _D(RR_DBUS_ATTR_UID_RANGE_END, NM_IP_ROUTING_RULE_ATTR_UID_RANGE_END, G_VARIANT_TYPE_UINT32), + _D(RR_DBUS_ATTR_UID_RANGE_START, + NM_IP_ROUTING_RULE_ATTR_UID_RANGE_START, + G_VARIANT_TYPE_UINT32), +#undef _D +}; + +static RRDbusAttr +_rr_dbus_attr_from_name(const char *name) +{ + gssize idx; + + nm_assert(name); + + if (NM_MORE_ASSERT_ONCE(10)) { + int i; + + for (i = 0; i < _RR_DBUS_ATTR_NUM; i++) { + nm_assert(rr_dbus_data[i].name); + nm_assert(g_variant_type_string_is_valid((const char *) rr_dbus_data[i].dbus_type)); + if (i > 0) + nm_assert(strcmp(rr_dbus_data[i - 1].name, rr_dbus_data[i].name) < 0); + } + } + + idx = nm_utils_array_find_binary_search(rr_dbus_data, + sizeof(rr_dbus_data[0]), + _RR_DBUS_ATTR_NUM, + &name, + nm_strcmp_p_with_data, + NULL); + if (idx < 0) + return _RR_DBUS_ATTR_NUM; + return idx; +} + +static void +_rr_variants_free(GVariant *(*p_variants)[]) +{ + int i; + + for (i = 0; i < _RR_DBUS_ATTR_NUM; i++) { + if ((*p_variants)[i]) + g_variant_unref((*p_variants)[i]); + } +} + +NMIPRoutingRule * +nm_ip_routing_rule_from_dbus(GVariant *variant, gboolean strict, GError **error) +{ + nm_auto(_rr_variants_free) GVariant *variants[_RR_DBUS_ATTR_NUM] = {}; + nm_auto_unref_ip_routing_rule NMIPRoutingRule *self = NULL; + RRDbusAttr attr; + GVariantIter iter; + const char * iter_key; + GVariant * iter_val; + int addr_family; + int i; + GVariant * v_start; + GVariant * v_end; + + g_variant_iter_init(&iter, variant); + + while (g_variant_iter_next(&iter, "{&sv}", &iter_key, &iter_val)) { + gs_unref_variant GVariant *iter_val2 = iter_val; + + attr = _rr_dbus_attr_from_name(iter_key); + + if (attr >= _RR_DBUS_ATTR_NUM) { + if (strict) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid key \"%s\""), + iter_key); + return NULL; + } + continue; + } + + if (variants[attr]) { + if (strict) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("duplicate key %s"), + iter_key); + return NULL; + } + g_variant_unref(variants[attr]); + } + variants[attr] = g_steal_pointer(&iter_val2); + } + + for (attr = 0; attr < _RR_DBUS_ATTR_NUM; attr++) { + if (!variants[attr]) + continue; + if (!g_variant_is_of_type(variants[attr], rr_dbus_data[attr].dbus_type)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid variant type '%s' for \"%s\""), + (const char *) rr_dbus_data[attr].dbus_type, + rr_dbus_data[attr].name); + return NULL; + } + } + + if (!variants[RR_DBUS_ATTR_FAMILY]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("missing \"family\"")); + return NULL; + } + addr_family = g_variant_get_int32(variants[RR_DBUS_ATTR_FAMILY]); + if (!NM_IN_SET(addr_family, AF_INET, AF_INET6)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid \"family\"")); + return NULL; + } + + self = nm_ip_routing_rule_new(addr_family); + + if (variants[RR_DBUS_ATTR_PRIORITY]) + nm_ip_routing_rule_set_priority(self, + g_variant_get_uint32(variants[RR_DBUS_ATTR_PRIORITY])); + + if (variants[RR_DBUS_ATTR_INVERT]) + nm_ip_routing_rule_set_invert(self, g_variant_get_boolean(variants[RR_DBUS_ATTR_INVERT])); + + if (variants[RR_DBUS_ATTR_TOS]) + nm_ip_routing_rule_set_tos(self, g_variant_get_byte(variants[RR_DBUS_ATTR_TOS])); + + if (variants[RR_DBUS_ATTR_IPPROTO]) + nm_ip_routing_rule_set_ipproto(self, g_variant_get_byte(variants[RR_DBUS_ATTR_IPPROTO])); + + for (i = 0; i < 2; i++) { + guint16 start, end; + + v_start = variants[i ? RR_DBUS_ATTR_SPORT_START : RR_DBUS_ATTR_DPORT_START]; + v_end = variants[i ? RR_DBUS_ATTR_SPORT_END : RR_DBUS_ATTR_DPORT_END]; + if (!v_start && !v_end) + continue; + + /* if start or end is missing, it defaults to the other parameter, respectively. */ + start = g_variant_get_uint16(v_start ?: v_end); + if (v_end && v_start) + end = g_variant_get_uint16(v_end); + else + end = start; + + if (i) + nm_ip_routing_rule_set_source_port(self, start, end); + else + nm_ip_routing_rule_set_destination_port(self, start, end); + } + + v_start = variants[RR_DBUS_ATTR_UID_RANGE_START]; + v_end = variants[RR_DBUS_ATTR_UID_RANGE_END]; + if (v_start || v_end) { + guint32 start, end; + + /* if start or end is missing, it defaults to the other parameter, respectively. */ + start = g_variant_get_uint32(v_start ?: v_end); + if (v_end && v_start) + end = g_variant_get_uint32(v_end); + else + end = start; + + if (end < start) { + if (strict) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("\"uid-range-start\" is greater than \"uid-range-end\"")); + return FALSE; + } + } else + nm_ip_routing_rule_set_uid_range(self, start, end); + } + + if (variants[RR_DBUS_ATTR_FWMARK] || variants[RR_DBUS_ATTR_FWMASK]) { + nm_ip_routing_rule_set_fwmark( + self, + variants[RR_DBUS_ATTR_FWMARK] ? g_variant_get_uint32(variants[RR_DBUS_ATTR_FWMARK]) + : 0u, + variants[RR_DBUS_ATTR_FWMASK] ? g_variant_get_uint32(variants[RR_DBUS_ATTR_FWMASK]) + : 0u); + } + + if (variants[RR_DBUS_ATTR_FROM] || variants[RR_DBUS_ATTR_FROM_LEN]) { + nm_ip_routing_rule_set_from( + self, + variants[RR_DBUS_ATTR_FROM] ? g_variant_get_string(variants[RR_DBUS_ATTR_FROM], NULL) + : NULL, + variants[RR_DBUS_ATTR_FROM_LEN] ? g_variant_get_byte(variants[RR_DBUS_ATTR_FROM_LEN]) + : 0u); + } + + if (variants[RR_DBUS_ATTR_TO] || variants[RR_DBUS_ATTR_TO_LEN]) { + nm_ip_routing_rule_set_to( + self, + variants[RR_DBUS_ATTR_TO] ? g_variant_get_string(variants[RR_DBUS_ATTR_TO], NULL) + : NULL, + variants[RR_DBUS_ATTR_TO_LEN] ? g_variant_get_byte(variants[RR_DBUS_ATTR_TO_LEN]) : 0u); + } + + if (variants[RR_DBUS_ATTR_IIFNAME]) + nm_ip_routing_rule_set_iifname(self, + g_variant_get_string(variants[RR_DBUS_ATTR_IIFNAME], NULL)); + + if (variants[RR_DBUS_ATTR_OIFNAME]) + nm_ip_routing_rule_set_oifname(self, + g_variant_get_string(variants[RR_DBUS_ATTR_OIFNAME], NULL)); + + /* For the ip-rule string format, the table default depends on the action. For + * our D-Bus format it's always the same: either a table is specified or it defaults + * to zero. And either the action is specified or it defaults to FR_ACT_TO_TBL. */ + nm_ip_routing_rule_set_action(self, + !variants[RR_DBUS_ATTR_ACTION] + ? (guint8) FR_ACT_TO_TBL + : g_variant_get_byte(variants[RR_DBUS_ATTR_ACTION])); + nm_ip_routing_rule_set_table(self, + !variants[RR_DBUS_ATTR_TABLE] + ? (guint32) 0 + : g_variant_get_uint32(variants[RR_DBUS_ATTR_TABLE])); + + if (variants[RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH]) + nm_ip_routing_rule_set_suppress_prefixlength( + self, + g_variant_get_int32(variants[RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH])); + + if (strict && !nm_ip_routing_rule_validate(self, error)) + return NULL; + + return g_steal_pointer(&self); +} + +static void +_rr_to_dbus_add(GVariantBuilder *builder, RRDbusAttr attr, GVariant *value) +{ + nm_assert(builder); + nm_assert(value); + nm_assert(g_variant_is_floating(value)); + nm_assert(g_variant_is_of_type(value, rr_dbus_data[attr].dbus_type)); + + g_variant_builder_add(builder, "{sv}", rr_dbus_data[attr].name, value); +} + +GVariant * +nm_ip_routing_rule_to_dbus(const NMIPRoutingRule *self) +{ + GVariantBuilder builder; + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + _rr_to_dbus_add(&builder, + RR_DBUS_ATTR_FAMILY, + g_variant_new_int32(_ip_routing_rule_get_addr_family(self))); + + if (self->invert) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_INVERT, g_variant_new_boolean(TRUE)); + + if (self->priority_has) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_PRIORITY, g_variant_new_uint32(self->priority)); + + if (self->tos != 0) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_TOS, g_variant_new_byte(self->tos)); + + if (self->ipproto != 0) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_IPPROTO, g_variant_new_byte(self->ipproto)); + + if (self->fwmark != 0) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_FWMARK, g_variant_new_uint32(self->fwmark)); + + if (self->fwmask != 0) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_FWMASK, g_variant_new_uint32(self->fwmask)); + + if (self->sport_start != 0 || self->sport_end != 0) { + _rr_to_dbus_add(&builder, + RR_DBUS_ATTR_SPORT_START, + g_variant_new_uint16(self->sport_start)); + if (self->sport_start != self->sport_end) + _rr_to_dbus_add(&builder, + RR_DBUS_ATTR_SPORT_END, + g_variant_new_uint16(self->sport_end)); + } + + if (self->dport_start != 0 || self->dport_end != 0) { + _rr_to_dbus_add(&builder, + RR_DBUS_ATTR_DPORT_START, + g_variant_new_uint16(self->dport_start)); + if (self->dport_start != self->dport_end) + _rr_to_dbus_add(&builder, + RR_DBUS_ATTR_DPORT_END, + g_variant_new_uint16(self->dport_end)); + } + + if (self->from_has || self->from_len != 0) { + _rr_to_dbus_add( + &builder, + RR_DBUS_ATTR_FROM, + g_variant_new_string(self->from_str + ?: nm_utils_inet_ntop(_ip_routing_rule_get_addr_family(self), + &self->from_bin, + addr_str))); + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_FROM_LEN, g_variant_new_byte(self->from_len)); + } + + if (self->to_has || self->to_len != 0) { + _rr_to_dbus_add( + &builder, + RR_DBUS_ATTR_TO, + g_variant_new_string(self->to_str + ?: nm_utils_inet_ntop(_ip_routing_rule_get_addr_family(self), + &self->to_bin, + addr_str))); + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_TO_LEN, g_variant_new_byte(self->to_len)); + } + + if (self->iifname) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_IIFNAME, g_variant_new_string(self->iifname)); + + if (self->oifname) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_OIFNAME, g_variant_new_string(self->oifname)); + + if (self->action != FR_ACT_TO_TBL) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_ACTION, g_variant_new_byte(self->action)); + + if (self->table != 0) + _rr_to_dbus_add(&builder, RR_DBUS_ATTR_TABLE, g_variant_new_uint32(self->table)); + + if (self->suppress_prefixlength != -1) + _rr_to_dbus_add(&builder, + RR_DBUS_ATTR_SUPPRESS_PREFIXLENGTH, + g_variant_new_int32(self->suppress_prefixlength)); + + if (self->uid_range_has) { + _rr_to_dbus_add(&builder, + RR_DBUS_ATTR_UID_RANGE_START, + g_variant_new_uint32(self->uid_range_start)); + if (self->uid_range_start != self->uid_range_end) + _rr_to_dbus_add(&builder, + RR_DBUS_ATTR_UID_RANGE_END, + g_variant_new_uint32(self->uid_range_end)); + } + + return g_variant_builder_end(&builder); +} + +/*****************************************************************************/ + +static gboolean +_rr_string_validate(gboolean for_from /* or else to-string */, + NMIPRoutingRuleAsStringFlags to_string_flags, + GHashTable * extra_args, + GError ** error) +{ + if (NM_FLAGS_ANY(to_string_flags, + ~(NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET + | NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6 + | NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE))) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("Unsupported to-string-flags argument")); + return FALSE; + } + + if (extra_args && g_hash_table_size(extra_args) > 0) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("Unsupported extra-argument")); + return FALSE; + } + + return TRUE; +} + +static int +_rr_string_addr_family_from_flags(NMIPRoutingRuleAsStringFlags to_string_flags) +{ + if (NM_FLAGS_HAS(to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET)) { + if (!NM_FLAGS_HAS(to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6)) + return AF_INET; + } else if (NM_FLAGS_HAS(to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6)) + return AF_INET6; + return AF_UNSPEC; +} + +/** + * nm_ip_routing_rule_from_string: + * @str: the string representation to convert to an #NMIPRoutingRule + * @to_string_flags: #NMIPRoutingRuleAsStringFlags for controlling the + * string conversion. + * @extra_args: (allow-none): extra arguments for controlling the string + * conversion. Currently, not extra arguments are supported. + * @error: (allow-none) (out): the error reason. + * + * Returns: (transfer full): the new #NMIPRoutingRule or %NULL on error. + * + * Since: 1.18 + */ +NMIPRoutingRule * +nm_ip_routing_rule_from_string(const char * str, + NMIPRoutingRuleAsStringFlags to_string_flags, + GHashTable * extra_args, + GError ** error) +{ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *self = NULL; + gs_free const char ** tokens = NULL; + gsize i_token; + gboolean any_words = FALSE; + char * word0 = NULL; + char * word1 = NULL; + char * word_from = NULL; + char * word_to = NULL; + char * word_iifname = NULL; + char * word_oifname = NULL; + gint64 i64_priority = -1; + gint64 i64_table = -1; + gint64 i64_tos = -1; + gint64 i64_fwmark = -1; + gint64 i64_fwmask = -1; + gint64 i64_sport_start = -1; + gint64 i64_ipproto = -1; + gint64 i64_suppress_prefixlength = -1; + guint16 sport_end = 0; + gint64 i64_dport_start = -1; + int i_action = -1; + guint16 dport_end = 0; + guint32 uid_range_start = 0; + guint32 uid_range_end = 0; + gboolean uid_range_has = FALSE; + gboolean val_invert = FALSE; + int addr_family = AF_UNSPEC; + NMIPAddr val_from = {}; + NMIPAddr val_to = {}; + int val_from_len = -1; + int val_to_len = -1; + char * s; + + g_return_val_if_fail(str, NULL); + + if (!_rr_string_validate(TRUE, to_string_flags, extra_args, error)) + return NULL; + + /* nm_ip_routing_rule_from_string() / nm_ip_routing_rule_to_string() has a string representation + * for rules that is partly compatibly with iproute2. That is, the part after + * `ip -[46] rule add $ARGS`. + * + * There are differences though: + * + * - trying to convert an invalid rule to string may not be possible. The reason is for + * example that an invalid rule can have nm_ip_routing_rule_get_from() like "bogus", + * but we don't write that as "from bogus". In general, if you try to convert an invalid + * rule to string, the operation may fail or the result may itself not be parsable. + * Of course, valid rules can be converted to string and read back the same (round-trip). + * + * - iproute2 in many regards is flexible about the command lines. For example + * - for tables it accepts table names from /etc/iproute2/rt_tables. We only + * accept numeric values and the special aliases "main", "local", and "default". + * - key names like "preference" can be abbreviated to "pref", we don't do that. + * - the "preference"/"priority" may be unspecified, in which kernel automatically + * chooses an unused priority (during `ip rule add`). We don't allow for that, the + * priority must be explicitly set. + * + * - iproute2 does not support any escaping. Well, it's the shell that supports quoting + * and escaping and splits the command line. We need to split the command line ourself, + * but we don't support full shell quotation. + * from-string tokenizes words at (ASCII) whitespaces (removing the whitespaces). + * It also supports backslash escaping (e.g. to contain whitespace), but it does + * not support special escape sequences. Values are taken literally, meaning + * "\n\ \111" gives results in "n 111". + * The strings really shouldn't contain any special characters that require escaping. + * This also goes together with nm_utils_escaped_tokens_split(). + * If you concatenate multiple rule expressions with a delimiter, the delimiter inside + * each word can be backslash escaped, and nm_utils_escaped_tokens_split() will + * properly split the words, preserving the backslashes, which then will be removed by + * nm_ip_routing_rule_from_string(). + */ + + addr_family = _rr_string_addr_family_from_flags(to_string_flags); + + tokens = nm_utils_escaped_tokens_split(str, NM_ASCII_SPACES); + for (i_token = 0; tokens && tokens[i_token]; i_token++) { + char *str_word = (char *) tokens[i_token]; + + any_words = TRUE; + if (!word0) + word0 = str_word; + else { + nm_assert(!word1); + word1 = str_word; + } + + /* iproute2 matches keywords with any partial prefix. We don't allow + * for that flexibility. */ + + if (NM_IN_STRSET(word0, "from")) { + if (!word1) + continue; + if (word_from) + goto next_fail_word0_duplicate_key; + word_from = word1; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "to")) { + if (!word1) + continue; + if (word_to) + goto next_fail_word0_duplicate_key; + word_to = word1; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "not")) { + /* we accept multiple "not" specifiers. "not not" still means + * not. */ + val_invert = TRUE; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "priority", "order", "pref", "preference")) { + if (!word1) + continue; + if (i64_priority != -1) + goto next_fail_word0_duplicate_key; + i64_priority = _nm_utils_ascii_str_to_int64(word1, 0, 0, G_MAXUINT32, -1); + if (i64_priority == -1) + goto next_fail_word1_invalid_value; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "table", "lookup")) { + if (!word1) + continue; + if (i64_table != -1) + goto next_fail_word0_duplicate_key; + i64_table = _nm_utils_ascii_str_to_int64(word1, 0, 0, G_MAXUINT32, -1); + if (i64_table == -1) { + if (nm_streq(word1, "main")) + i64_table = RT_TABLE_MAIN; + else if (nm_streq(word1, "local")) + i64_table = RT_TABLE_LOCAL; + else if (nm_streq(word1, "default")) + i64_table = RT_TABLE_DEFAULT; + else + goto next_fail_word1_invalid_value; + } + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "tos", "dsfield")) { + if (!word1) + continue; + if (i64_tos != -1) + goto next_fail_word0_duplicate_key; + i64_tos = _nm_utils_ascii_str_to_int64(word1, 16, 0, G_MAXUINT8, -1); + if (i64_tos == -1) + goto next_fail_word1_invalid_value; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "ipproto")) { + if (!word1) + continue; + if (i64_ipproto != -1) + goto next_fail_word0_duplicate_key; + i64_ipproto = _nm_utils_ascii_str_to_int64(word1, 10, 0, G_MAXUINT8, -1); + if (i64_ipproto == -1) + goto next_fail_word1_invalid_value; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "sport")) { + if (!word1) + continue; + if (i64_sport_start != -1) + goto next_fail_word0_duplicate_key; + if (!_rr_xport_range_parse(word1, &i64_sport_start, &sport_end)) + goto next_fail_word1_invalid_value; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "dport")) { + if (!word1) + continue; + if (i64_dport_start != -1) + goto next_fail_word0_duplicate_key; + if (!_rr_xport_range_parse(word1, &i64_dport_start, &dport_end)) + goto next_fail_word1_invalid_value; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "fwmark")) { + if (!word1) + continue; + if (i64_fwmark != -1) + goto next_fail_word0_duplicate_key; + s = strchr(word1, '/'); + if (s) + *(s++) = '\0'; + i64_fwmark = _nm_utils_ascii_str_to_int64(word1, 0, 0, G_MAXUINT32, -1); + if (i64_fwmark == -1) + goto next_fail_word1_invalid_value; + if (s) { + i64_fwmask = _nm_utils_ascii_str_to_int64(s, 0, 0, G_MAXUINT32, -1); + if (i64_fwmask == -1) + goto next_fail_word1_invalid_value; + } else + i64_fwmask = 0xFFFFFFFFu; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "iif", "dev")) { + if (!word1) + continue; + if (word_iifname) + goto next_fail_word0_duplicate_key; + word_iifname = word1; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "oif")) { + if (!word1) + continue; + if (word_oifname) + goto next_fail_word0_duplicate_key; + word_oifname = word1; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "suppress_prefixlength", "sup_pl")) { + if (!word1) + continue; + if (i64_suppress_prefixlength != -1) + goto next_fail_word0_duplicate_key; + i64_suppress_prefixlength = _nm_utils_ascii_str_to_int64(word1, 0, 0, G_MAXINT32, -1); + if (i64_suppress_prefixlength == -1) + goto next_fail_word1_invalid_value; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "uidrange")) { + if (!word1) + continue; + if (uid_range_has) + goto next_fail_word0_duplicate_key; + s = strchr(word1, '-'); + if (s) + (s++)[0] = '\0'; + uid_range_start = _nm_utils_ascii_str_to_int64(word1, 0, 0, G_MAXUINT32, 0); + if (errno) + goto next_fail_word1_invalid_value; + if (s) { + uid_range_end = _nm_utils_ascii_str_to_int64(s, 0, 0, G_MAXUINT32, 0); + if (errno) + goto next_fail_word1_invalid_value; + if (uid_range_end < uid_range_start) + goto next_fail_word1_invalid_value; + } else + uid_range_end = uid_range_start; + uid_range_has = TRUE; + goto next_words_consumed; + } + if (NM_IN_STRSET(word0, "type")) { + if (!word1) + continue; + if (i_action >= 0) + goto next_fail_word0_duplicate_key; + i_action = nm_net_aux_rtnl_rtntype_a2n(word1); + if (i_action < 0) + goto next_fail_word1_invalid_value; + goto next_words_consumed; + } + + if (i_action < 0) { + i_action = nm_net_aux_rtnl_rtntype_a2n(word0); + if (i_action >= 0) + goto next_words_consumed; + } + + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unsupported key \"%s\""), + word0); + return FALSE; +next_fail_word0_duplicate_key: + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("duplicate key \"%s\""), + word0); + return FALSE; +next_fail_word1_invalid_value: + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid value for \"%s\""), + word0); + return FALSE; +next_words_consumed: + word0 = NULL; + word1 = NULL; + } + + if (!any_words) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("empty text does not describe a rule")); + return FALSE; + } + + if (word0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("missing argument for \"%s\""), + word0); + return FALSE; + } + + if (!NM_IN_STRSET(word_from, NULL, "all")) { + if (!nm_utils_parse_inaddr_prefix_bin(addr_family, + word_from, + &addr_family, + &val_from, + &val_from_len)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid \"from\" part")); + return FALSE; + } + if (val_from_len == -1) + val_from_len = nm_utils_addr_family_to_size(addr_family) * 8; + } + + if (!NM_IN_STRSET(word_to, NULL, "all")) { + if (!nm_utils_parse_inaddr_prefix_bin(addr_family, + word_to, + &addr_family, + &val_to, + &val_to_len)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid \"to\" part")); + return FALSE; + } + if (val_to_len == -1) + val_to_len = nm_utils_addr_family_to_size(addr_family) * 8; + } + + if (!NM_IN_SET(addr_family, AF_INET, AF_INET6)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("cannot detect address family for rule")); + return FALSE; + } + + self = nm_ip_routing_rule_new(addr_family); + + if (val_invert) + self->invert = TRUE; + + if (i64_priority != -1) + nm_ip_routing_rule_set_priority(self, i64_priority); + + if (i_action >= 0) { + nm_ip_routing_rule_set_action(self, i_action); + if (i64_table == -1) { + if (i_action != FR_ACT_TO_TBL) + i64_table = 0; + else + i64_table = RT_TABLE_MAIN; + } + } + + if (i64_tos != -1) + nm_ip_routing_rule_set_tos(self, i64_tos); + + if (i64_ipproto != -1) + nm_ip_routing_rule_set_ipproto(self, i64_ipproto); + + if (i64_fwmark != -1) + nm_ip_routing_rule_set_fwmark(self, i64_fwmark, i64_fwmask); + + if (i64_sport_start != -1) + nm_ip_routing_rule_set_source_port(self, i64_sport_start, sport_end); + + if (i64_dport_start != -1) + nm_ip_routing_rule_set_destination_port(self, i64_dport_start, dport_end); + + if (i64_suppress_prefixlength != -1) + nm_ip_routing_rule_set_suppress_prefixlength(self, i64_suppress_prefixlength); + + if (val_from_len > 0 || (val_from_len == 0 && !nm_ip_addr_is_null(addr_family, &val_from))) { + nm_ip_routing_rule_set_from_bin(self, &val_from, val_from_len); + } + + if (val_to_len > 0 || (val_to_len == 0 && !nm_ip_addr_is_null(addr_family, &val_to))) { + nm_ip_routing_rule_set_to_bin(self, &val_to, val_to_len); + } + + if (word_iifname) + nm_ip_routing_rule_set_iifname(self, word_iifname); + + if (word_oifname) + nm_ip_routing_rule_set_oifname(self, word_oifname); + + if (i64_table != -1) + nm_ip_routing_rule_set_table(self, i64_table); + + if (uid_range_has) + nm_ip_routing_rule_set_uid_range(self, uid_range_start, uid_range_end); + + if (NM_FLAGS_HAS(to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE)) { + gs_free_error GError *local = NULL; + + if (!nm_ip_routing_rule_validate(self, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("rule is invalid: %s"), + local->message); + return NULL; + } + } + + return g_steal_pointer(&self); +} + +static void +_rr_string_append_inet_addr(NMStrBuf * str, + gboolean is_from /* or else is-to */, + gboolean required, + int addr_family, + const NMIPAddr *addr_bin, + guint8 addr_len) +{ + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + + if (addr_len == 0) { + if (required) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(str, ' '), + "%s %s/0", + is_from ? "from" : "to", + (addr_family == AF_INET) ? "0.0.0.0" : "::"); + } + return; + } + + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(str, ' '), + "%s %s", + is_from ? "from" : "to", + nm_utils_inet_ntop(addr_family, addr_bin, addr_str)); + if (addr_len != nm_utils_addr_family_to_size(addr_family) * 8) { + nm_str_buf_append_printf(str, "/%u", addr_len); + } +} + +/** + * nm_ip_routing_rule_to_string: + * @self: the #NMIPRoutingRule instance to convert to string. + * @to_string_flags: #NMIPRoutingRuleAsStringFlags for controlling the + * string conversion. + * @extra_args: (allow-none): extra arguments for controlling the string + * conversion. Currently, not extra arguments are supported. + * @error: (allow-none) (out): the error reason. + * + * Returns: (transfer full): the string representation or %NULL on error. + * + * Since: 1.18 + */ +char * +nm_ip_routing_rule_to_string(const NMIPRoutingRule * self, + NMIPRoutingRuleAsStringFlags to_string_flags, + GHashTable * extra_args, + GError ** error) +{ + int addr_family; + NMStrBuf str; + + g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(self, TRUE), NULL); + + if (!_rr_string_validate(FALSE, to_string_flags, extra_args, error)) + return NULL; + + addr_family = nm_ip_routing_rule_get_addr_family(self); + + if (!NM_IN_SET(_rr_string_addr_family_from_flags(to_string_flags), AF_UNSPEC, addr_family)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid address family")); + return NULL; + } + + /* It is only guaranteed that valid rules can be expressed as string. + * + * Still, unless requested proceed to convert to string without validating and + * hope for the best. + * + * That is, because self->from_str might contain an invalid IP address (indicated + * by self->from_valid). But we don't support serializing such arbitrary strings + * as "from %s". */ + if (NM_FLAGS_HAS(to_string_flags, NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE)) { + gs_free_error GError *local = NULL; + + if (!nm_ip_routing_rule_validate(self, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("rule is invalid: %s"), + local->message); + return NULL; + } + } + + nm_str_buf_init(&str, NM_UTILS_GET_NEXT_REALLOC_SIZE_32, FALSE); + + if (self->priority_has) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "priority %u", + (guint) self->priority); + } + + if (self->invert) + nm_str_buf_append(nm_str_buf_append_required_delimiter(&str, ' '), "not"); + + _rr_string_append_inet_addr(&str, + TRUE, + (!self->to_has || !self->to_valid), + addr_family, + &self->from_bin, + (self->from_has && self->from_valid) ? self->from_len : 0); + + _rr_string_append_inet_addr(&str, + FALSE, + FALSE, + addr_family, + &self->to_bin, + (self->to_has && self->to_valid) ? self->to_len : 0); + + if (self->tos != 0) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "tos 0x%02x", + (guint) self->tos); + } + + if (self->ipproto != 0) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "ipproto %u", + (guint) self->ipproto); + } + + if (self->fwmark != 0 || self->fwmask != 0) { + if (self->fwmark != 0) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "fwmark 0x%x", + self->fwmark); + } else { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), "fwmark 0"); + } + if (self->fwmask != 0xFFFFFFFFu) { + if (self->fwmask != 0) + nm_str_buf_append_printf(&str, "/0x%x", self->fwmask); + else + nm_str_buf_append_printf(&str, "/0"); + } + } + + if (self->sport_start != 0 || self->sport_end != 0) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "sport %u", + self->sport_start); + if (self->sport_start != self->sport_end) { + nm_str_buf_append_printf(&str, "-%u", self->sport_end); + } + } + + if (self->dport_start != 0 || self->dport_end != 0) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "dport %u", + self->dport_start); + if (self->dport_start != self->dport_end) { + nm_str_buf_append_printf(&str, "-%u", self->dport_end); + } + } + + if (self->iifname) { + nm_str_buf_append(nm_str_buf_append_required_delimiter(&str, ' '), "iif "); + nm_utils_escaped_tokens_escape_strbuf(self->iifname, NM_ASCII_SPACES, &str); + } + + if (self->oifname) { + nm_str_buf_append(nm_str_buf_append_required_delimiter(&str, ' '), "oif "); + nm_utils_escaped_tokens_escape_strbuf(self->oifname, NM_ASCII_SPACES, &str); + } + + if (self->uid_range_has) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "uidrange %u-%u", + self->uid_range_start, + self->uid_range_end); + } + + if (self->suppress_prefixlength != -1) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "suppress_prefixlength %d", + (int) self->suppress_prefixlength); + } + + if (self->table != 0 || self->action == FR_ACT_TO_TBL) { + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "table %u", + (guint) self->table); + } + + if (self->action != FR_ACT_TO_TBL) { + char sbuf[100]; + + nm_str_buf_append_printf(nm_str_buf_append_required_delimiter(&str, ' '), + "type %s", + nm_net_aux_rtnl_rtntype_n2a_maybe_buf(self->action, sbuf)); + } + + return nm_str_buf_finalize(&str, NULL); +} + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingIPConfig, + PROP_METHOD, + PROP_DNS, + PROP_DNS_SEARCH, + PROP_DNS_OPTIONS, + PROP_DNS_PRIORITY, + PROP_ADDRESSES, + PROP_GATEWAY, + PROP_ROUTES, + PROP_ROUTE_METRIC, + PROP_ROUTE_TABLE, + PROP_IGNORE_AUTO_ROUTES, + PROP_IGNORE_AUTO_DNS, + PROP_DHCP_HOSTNAME, + PROP_DHCP_HOSTNAME_FLAGS, + PROP_DHCP_SEND_HOSTNAME, + PROP_NEVER_DEFAULT, + PROP_MAY_FAIL, + PROP_DAD_TIMEOUT, + PROP_DHCP_TIMEOUT, + PROP_DHCP_IAID, + PROP_DHCP_REJECT_SERVERS, ); + +typedef struct { + GPtrArray *dns; /* array of IP address strings */ + GPtrArray *dns_search; /* array of domain name strings */ + GPtrArray *dns_options; /* array of DNS options */ + GPtrArray *addresses; /* array of NMIPAddress */ + GPtrArray *routes; /* array of NMIPRoute */ + GPtrArray *routing_rules; + GArray * dhcp_reject_servers; + char * method; + char * gateway; + char * dhcp_hostname; + char * dhcp_iaid; + gint64 route_metric; + guint dhcp_hostname_flags; + int dns_priority; + int dad_timeout; + int dhcp_timeout; + guint32 route_table; + bool ignore_auto_routes : 1; + bool ignore_auto_dns : 1; + bool dhcp_send_hostname : 1; + bool never_default : 1; + bool may_fail : 1; +} NMSettingIPConfigPrivate; + +G_DEFINE_ABSTRACT_TYPE(NMSettingIPConfig, nm_setting_ip_config, NM_TYPE_SETTING) + +#define NM_SETTING_IP_CONFIG_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_IP_CONFIG, NMSettingIPConfigPrivate)) + +/*****************************************************************************/ + +#define NM_SETTING_IP_CONFIG_GET_FAMILY(setting) \ + (NM_IS_SETTING_IP4_CONFIG(setting) ? AF_INET : AF_INET6) + +/** + * nm_setting_ip_config_get_method: + * @setting: the #NMSettingIPConfig + * + * Returns: the #NMSettingIPConfig:method property of the setting; see + * #NMSettingIP4Config and #NMSettingIP6Config for details of the + * methods available with each type. + **/ +const char * +nm_setting_ip_config_get_method(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->method; +} + +/** + * nm_setting_ip_config_get_num_dns: + * @setting: the #NMSettingIPConfig + * + * Returns: the number of configured DNS servers + **/ +guint +nm_setting_ip_config_get_num_dns(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns->len; +} + +/** + * nm_setting_ip_config_get_dns: + * @setting: the #NMSettingIPConfig + * @idx: index number of the DNS server to return + * + * Returns: the IP address of the DNS server at index @idx + **/ +const char * +nm_setting_ip_config_get_dns(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_val_if_fail(idx >= 0 && idx < priv->dns->len, NULL); + + return priv->dns->pdata[idx]; +} + +/** + * nm_setting_ip_config_add_dns: + * @setting: the #NMSettingIPConfig + * @dns: the IP address of the DNS server to add + * + * Adds a new DNS server to the setting. + * + * Returns: %TRUE if the DNS server was added; %FALSE if the server was already + * known + **/ +gboolean +nm_setting_ip_config_add_dns(NMSettingIPConfig *setting, const char *dns) +{ + NMSettingIPConfigPrivate *priv; + char * dns_canonical; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(dns != NULL, FALSE); + g_return_val_if_fail(nm_utils_ipaddr_is_valid(NM_SETTING_IP_CONFIG_GET_FAMILY(setting), dns), + FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + dns_canonical = canonicalize_ip(NM_SETTING_IP_CONFIG_GET_FAMILY(setting), dns, FALSE); + for (i = 0; i < priv->dns->len; i++) { + if (!strcmp(dns_canonical, priv->dns->pdata[i])) { + g_free(dns_canonical); + return FALSE; + } + } + + g_ptr_array_add(priv->dns, dns_canonical); + _notify(setting, PROP_DNS); + return TRUE; +} + +/** + * nm_setting_ip_config_remove_dns: + * @setting: the #NMSettingIPConfig + * @idx: index number of the DNS server to remove + * + * Removes the DNS server at index @idx. + **/ +void +nm_setting_ip_config_remove_dns(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_if_fail(idx >= 0 && idx < priv->dns->len); + + g_ptr_array_remove_index(priv->dns, idx); + _notify(setting, PROP_DNS); +} + +/** + * nm_setting_ip_config_remove_dns_by_value: + * @setting: the #NMSettingIPConfig + * @dns: the DNS server to remove + * + * Removes the DNS server @dns. + * + * Returns: %TRUE if the DNS server was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_ip_config_remove_dns_by_value(NMSettingIPConfig *setting, const char *dns) +{ + NMSettingIPConfigPrivate *priv; + char * dns_canonical; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(dns != NULL, FALSE); + g_return_val_if_fail(nm_utils_ipaddr_is_valid(NM_SETTING_IP_CONFIG_GET_FAMILY(setting), dns), + FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + dns_canonical = canonicalize_ip(NM_SETTING_IP_CONFIG_GET_FAMILY(setting), dns, FALSE); + for (i = 0; i < priv->dns->len; i++) { + if (!strcmp(dns_canonical, priv->dns->pdata[i])) { + g_ptr_array_remove_index(priv->dns, i); + _notify(setting, PROP_DNS); + g_free(dns_canonical); + return TRUE; + } + } + g_free(dns_canonical); + return FALSE; +} + +/** + * nm_setting_ip_config_clear_dns: + * @setting: the #NMSettingIPConfig + * + * Removes all configured DNS servers. + **/ +void +nm_setting_ip_config_clear_dns(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + if (priv->dns->len != 0) { + g_ptr_array_set_size(priv->dns, 0); + _notify(setting, PROP_DNS); + } +} + +/** + * nm_setting_ip_config_get_num_dns_searches: + * @setting: the #NMSettingIPConfig + * + * Returns: the number of configured DNS search domains + **/ +guint +nm_setting_ip_config_get_num_dns_searches(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_search->len; +} + +/** + * nm_setting_ip_config_get_dns_search: + * @setting: the #NMSettingIPConfig + * @idx: index number of the DNS search domain to return + * + * Returns: the DNS search domain at index @idx + **/ +const char * +nm_setting_ip_config_get_dns_search(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_val_if_fail(idx >= 0 && idx < priv->dns_search->len, NULL); + + return priv->dns_search->pdata[idx]; +} + +/** + * nm_setting_ip_config_add_dns_search: + * @setting: the #NMSettingIPConfig + * @dns_search: the search domain to add + * + * Adds a new DNS search domain to the setting. + * + * Returns: %TRUE if the DNS search domain was added; %FALSE if the search + * domain was already known + **/ +gboolean +nm_setting_ip_config_add_dns_search(NMSettingIPConfig *setting, const char *dns_search) +{ + NMSettingIPConfigPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(dns_search != NULL, FALSE); + g_return_val_if_fail(dns_search[0] != '\0', FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + for (i = 0; i < priv->dns_search->len; i++) { + if (!strcmp(dns_search, priv->dns_search->pdata[i])) + return FALSE; + } + + g_ptr_array_add(priv->dns_search, g_strdup(dns_search)); + _notify(setting, PROP_DNS_SEARCH); + return TRUE; +} + +/** + * nm_setting_ip_config_remove_dns_search: + * @setting: the #NMSettingIPConfig + * @idx: index number of the DNS search domain + * + * Removes the DNS search domain at index @idx. + **/ +void +nm_setting_ip_config_remove_dns_search(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_if_fail(idx >= 0 && idx < priv->dns_search->len); + + g_ptr_array_remove_index(priv->dns_search, idx); + _notify(setting, PROP_DNS_SEARCH); +} + +/** + * nm_setting_ip_config_remove_dns_search_by_value: + * @setting: the #NMSettingIPConfig + * @dns_search: the search domain to remove + * + * Removes the DNS search domain @dns_search. + * + * Returns: %TRUE if the DNS search domain was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_ip_config_remove_dns_search_by_value(NMSettingIPConfig *setting, const char *dns_search) +{ + NMSettingIPConfigPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(dns_search != NULL, FALSE); + g_return_val_if_fail(dns_search[0] != '\0', FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + for (i = 0; i < priv->dns_search->len; i++) { + if (!strcmp(dns_search, priv->dns_search->pdata[i])) { + g_ptr_array_remove_index(priv->dns_search, i); + _notify(setting, PROP_DNS_SEARCH); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_ip_config_clear_dns_searches: + * @setting: the #NMSettingIPConfig + * + * Removes all configured DNS search domains. + **/ +void +nm_setting_ip_config_clear_dns_searches(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + if (priv->dns_search->len != 0) { + g_ptr_array_set_size(priv->dns_search, 0); + _notify(setting, PROP_DNS_SEARCH); + } +} + +/** + * nm_setting_ip_config_get_num_dns_options: + * @setting: the #NMSettingIPConfig + * + * Returns: the number of configured DNS options + * + * Since: 1.2 + **/ +guint +nm_setting_ip_config_get_num_dns_options(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + return priv->dns_options ? priv->dns_options->len : 0; +} + +/** + * nm_setting_ip_config_has_dns_options: + * @setting: the #NMSettingIPConfig + * + * NMSettingIPConfig can have a list of dns-options. If the list + * is empty, there are two similar (but differentiated) states. + * Either the options are explicitly set to have no values, + * or the options are left undefined. The latter means to use + * a default configuration, while the former explicitly means "no-options". + * + * Returns: whether DNS options are initialized or left unset (the default). + **/ +gboolean +nm_setting_ip_config_has_dns_options(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return !!NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_options; +} + +/** + * nm_setting_ip_config_get_dns_option: + * @setting: the #NMSettingIPConfig + * @idx: index number of the DNS option + * + * Returns: the DNS option at index @idx + * + * Since: 1.2 + **/ +const char * +nm_setting_ip_config_get_dns_option(NMSettingIPConfig *setting, guint idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_val_if_fail(priv->dns_options, NULL); + g_return_val_if_fail(idx < priv->dns_options->len, NULL); + + return priv->dns_options->pdata[idx]; +} + +/** + * nm_setting_ip_config_next_valid_dns_option: + * @setting: the #NMSettingIPConfig + * @idx: index to start the search from + * + * Returns: the index, greater or equal than @idx, of the first valid + * DNS option, or -1 if no valid option is found + **/ +int +nm_setting_ip_config_next_valid_dns_option(NMSettingIPConfig *setting, guint idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), -1); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + if (!priv->dns_options) + return -1; + + for (; idx < priv->dns_options->len; idx++) { + if (_nm_utils_dns_option_validate(priv->dns_options->pdata[idx], + NULL, + NULL, + NM_IS_SETTING_IP6_CONFIG(setting), + _nm_utils_dns_option_descs)) + return idx; + } + + return -1; +} + +/** + * nm_setting_ip_config_add_dns_option: + * @setting: the #NMSettingIPConfig + * @dns_option: the DNS option to add + * + * Adds a new DNS option to the setting. + * + * Returns: %TRUE if the DNS option was added; %FALSE otherwise + * + * Since: 1.2 + **/ +gboolean +nm_setting_ip_config_add_dns_option(NMSettingIPConfig *setting, const char *dns_option) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(dns_option != NULL, FALSE); + g_return_val_if_fail(dns_option[0] != '\0', FALSE); + + if (!_nm_utils_dns_option_validate(dns_option, NULL, NULL, FALSE, NULL)) + return FALSE; + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + if (!priv->dns_options) + priv->dns_options = g_ptr_array_new_with_free_func(g_free); + else { + if (_nm_utils_dns_option_find_idx(priv->dns_options, dns_option) >= 0) + return FALSE; + } + + g_ptr_array_add(priv->dns_options, g_strdup(dns_option)); + _notify(setting, PROP_DNS_OPTIONS); + return TRUE; +} + +/** + * nm_setting_ip_config_remove_dns_option: + * @setting: the #NMSettingIPConfig + * @idx: index number of the DNS option + * + * Removes the DNS option at index @idx. + * + * Since: 1.2 + **/ +void +nm_setting_ip_config_remove_dns_option(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_if_fail(priv->dns_options); + g_return_if_fail(idx >= 0 && idx < priv->dns_options->len); + + g_ptr_array_remove_index(priv->dns_options, idx); + _notify(setting, PROP_DNS_OPTIONS); +} + +/** + * nm_setting_ip_config_remove_dns_option_by_value: + * @setting: the #NMSettingIPConfig + * @dns_option: the DNS option to remove + * + * Removes the DNS option @dns_option. + * + * Returns: %TRUE if the DNS option was found and removed; %FALSE if it was not. + * + * Since: 1.2 + **/ +gboolean +nm_setting_ip_config_remove_dns_option_by_value(NMSettingIPConfig *setting, const char *dns_option) +{ + NMSettingIPConfigPrivate *priv; + gssize i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(dns_option != NULL, FALSE); + g_return_val_if_fail(dns_option[0] != '\0', FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + if (!priv->dns_options) + return FALSE; + + i = _nm_utils_dns_option_find_idx(priv->dns_options, dns_option); + if (i >= 0) { + g_ptr_array_remove_index(priv->dns_options, i); + _notify(setting, PROP_DNS_OPTIONS); + return TRUE; + } + + return FALSE; +} + +/** + * nm_setting_ip_config_clear_dns_options: + * @setting: the #NMSettingIPConfig + * @is_set: the dns-options can be either empty or unset (default). + * Specify how to clear the options. + * + * Removes all configured DNS options. + * + * Since: 1.2 + **/ +void +nm_setting_ip_config_clear_dns_options(NMSettingIPConfig *setting, gboolean is_set) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + if (!priv->dns_options) { + if (!is_set) + return; + priv->dns_options = g_ptr_array_new_with_free_func(g_free); + } else { + if (!is_set) { + g_ptr_array_unref(priv->dns_options); + priv->dns_options = NULL; + } else { + if (priv->dns_options->len == 0) + return; + g_ptr_array_set_size(priv->dns_options, 0); + } + } + _notify(setting, PROP_DNS_OPTIONS); +} + +/** + * nm_setting_ip_config_get_dns_priority: + * @setting: the #NMSettingIPConfig + * + * Returns: the priority of DNS servers + * + * Since: 1.4 + **/ +int +nm_setting_ip_config_get_dns_priority(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_priority; +} + +/** + * nm_setting_ip_config_get_num_addresses: + * @setting: the #NMSettingIPConfig + * + * Returns: the number of configured addresses + **/ +guint +nm_setting_ip_config_get_num_addresses(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->addresses->len; +} + +/** + * nm_setting_ip_config_get_address: + * @setting: the #NMSettingIPConfig + * @idx: index number of the address to return + * + * Returns: (transfer none): the address at index @idx + **/ +NMIPAddress * +nm_setting_ip_config_get_address(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_val_if_fail(idx >= 0 && idx < priv->addresses->len, NULL); + + return priv->addresses->pdata[idx]; +} + +/** + * nm_setting_ip_config_add_address: + * @setting: the #NMSettingIPConfig + * @address: the new address to add + * + * Adds a new IP address and associated information to the setting. The + * given address is duplicated internally and is not changed by this function. + * + * Returns: %TRUE if the address was added; %FALSE if the address was already + * known. + **/ +gboolean +nm_setting_ip_config_add_address(NMSettingIPConfig *setting, NMIPAddress *address) +{ + NMSettingIPConfigPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(address != NULL, FALSE); + g_return_val_if_fail(address->family == NM_SETTING_IP_CONFIG_GET_FAMILY(setting), FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + for (i = 0; i < priv->addresses->len; i++) { + if (nm_ip_address_equal(priv->addresses->pdata[i], address)) + return FALSE; + } + + g_ptr_array_add(priv->addresses, nm_ip_address_dup(address)); + + _notify(setting, PROP_ADDRESSES); + return TRUE; +} + +/** + * nm_setting_ip_config_remove_address: + * @setting: the #NMSettingIPConfig + * @idx: index number of the address to remove + * + * Removes the address at index @idx. + **/ +void +nm_setting_ip_config_remove_address(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_if_fail(idx >= 0 && idx < priv->addresses->len); + + g_ptr_array_remove_index(priv->addresses, idx); + + _notify(setting, PROP_ADDRESSES); +} + +/** + * nm_setting_ip_config_remove_address_by_value: + * @setting: the #NMSettingIPConfig + * @address: the IP address to remove + * + * Removes the address @address. + * + * Returns: %TRUE if the address was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_ip_config_remove_address_by_value(NMSettingIPConfig *setting, NMIPAddress *address) +{ + NMSettingIPConfigPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(address != NULL, FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + for (i = 0; i < priv->addresses->len; i++) { + if (nm_ip_address_equal(priv->addresses->pdata[i], address)) { + g_ptr_array_remove_index(priv->addresses, i); + _notify(setting, PROP_ADDRESSES); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_ip_config_clear_addresses: + * @setting: the #NMSettingIPConfig + * + * Removes all configured addresses. + **/ +void +nm_setting_ip_config_clear_addresses(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + if (priv->addresses->len != 0) { + g_ptr_array_set_size(priv->addresses, 0); + _notify(setting, PROP_ADDRESSES); + } +} + +/** + * nm_setting_ip_config_get_gateway: + * @setting: the #NMSettingIPConfig + * + * Returns: the IP address of the gateway associated with this configuration, or + * %NULL. + **/ +const char * +nm_setting_ip_config_get_gateway(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->gateway; +} + +/** + * nm_setting_ip_config_get_num_routes: + * @setting: the #NMSettingIPConfig + * + * Returns: the number of configured routes + **/ +guint +nm_setting_ip_config_get_num_routes(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->routes->len; +} + +/** + * nm_setting_ip_config_get_route: + * @setting: the #NMSettingIPConfig + * @idx: index number of the route to return + * + * Returns: (transfer none): the route at index @idx + **/ +NMIPRoute * +nm_setting_ip_config_get_route(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_val_if_fail(idx >= 0 && idx < priv->routes->len, NULL); + + return priv->routes->pdata[idx]; +} + +/** + * nm_setting_ip_config_add_route: + * @setting: the #NMSettingIPConfig + * @route: the route to add + * + * Appends a new route and associated information to the setting. The + * given route is duplicated internally and is not changed by this function. + * If an identical route (considering attributes as well) already exists, the + * route is not added and the function returns %FALSE. + * + * Note that before 1.10, this function would not consider route attributes + * and not add a route that has an existing route with same dest/prefix,next_hop,metric + * parameters. + * + * Returns: %TRUE if the route was added; %FALSE if the route was already known. + **/ +gboolean +nm_setting_ip_config_add_route(NMSettingIPConfig *setting, NMIPRoute *route) +{ + NMSettingIPConfigPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(route != NULL, FALSE); + g_return_val_if_fail(route->family == NM_SETTING_IP_CONFIG_GET_FAMILY(setting), FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + for (i = 0; i < priv->routes->len; i++) { + if (nm_ip_route_equal_full(priv->routes->pdata[i], + route, + NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS)) + return FALSE; + } + + g_ptr_array_add(priv->routes, nm_ip_route_dup(route)); + _notify(setting, PROP_ROUTES); + return TRUE; +} + +/** + * nm_setting_ip_config_remove_route: + * @setting: the #NMSettingIPConfig + * @idx: index number of the route + * + * Removes the route at index @idx. + **/ +void +nm_setting_ip_config_remove_route(NMSettingIPConfig *setting, int idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_if_fail(idx >= 0 && idx < priv->routes->len); + + g_ptr_array_remove_index(priv->routes, idx); + _notify(setting, PROP_ROUTES); +} + +/** + * nm_setting_ip_config_remove_route_by_value: + * @setting: the #NMSettingIPConfig + * @route: the route to remove + * + * Removes the first matching route that matches @route. + * Note that before 1.10, this function would only compare dest/prefix,next_hop,metric + * and ignore route attributes. Now, @route must match exactly. + * + * Returns: %TRUE if the route was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_ip_config_remove_route_by_value(NMSettingIPConfig *setting, NMIPRoute *route) +{ + NMSettingIPConfigPrivate *priv; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + g_return_val_if_fail(route != NULL, FALSE); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + for (i = 0; i < priv->routes->len; i++) { + if (nm_ip_route_equal_full(priv->routes->pdata[i], + route, + NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS)) { + g_ptr_array_remove_index(priv->routes, i); + _notify(setting, PROP_ROUTES); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_ip_config_clear_routes: + * @setting: the #NMSettingIPConfig + * + * Removes all configured routes. + **/ +void +nm_setting_ip_config_clear_routes(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + if (priv->routes->len != 0) { + g_ptr_array_set_size(priv->routes, 0); + _notify(setting, PROP_ROUTES); + } +} + +/** + * nm_setting_ip_config_get_route_metric: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:route-metric + * property. + * + * Returns: the route metric that is used for routes that don't explicitly + * specify a metric. See #NMSettingIPConfig:route-metric for more details. + **/ +gint64 +nm_setting_ip_config_get_route_metric(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), -1); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->route_metric; +} + +/** + * nm_setting_ip_config_get_route_table: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:route-table + * property. + * + * Returns: the configured route-table. + * + * Since: 1.10 + **/ +guint32 +nm_setting_ip_config_get_route_table(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->route_table; +} + +/*****************************************************************************/ + +static void +_routing_rules_notify(NMSettingIPConfig *setting) +{ + _nm_setting_emit_property_changed(NM_SETTING(setting)); +} + +/** + * nm_setting_ip_config_get_num_routing_rules: + * @setting: the #NMSettingIPConfig + * + * Returns: the number of configured routing rules + * + * Since: 1.18 + **/ +guint +nm_setting_ip_config_get_num_routing_rules(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + return priv->routing_rules ? priv->routing_rules->len : 0u; +} + +/** + * nm_setting_ip_config_get_routing_rule: + * @setting: the #NMSettingIPConfig + * @idx: index number of the routing_rule to return + * + * Returns: (transfer none): the routing rule at index @idx + * + * Since: 1.18 + **/ +NMIPRoutingRule * +nm_setting_ip_config_get_routing_rule(NMSettingIPConfig *setting, guint idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + g_return_val_if_fail(priv->routing_rules && idx < priv->routing_rules->len, NULL); + + return priv->routing_rules->pdata[idx]; +} + +/** + * nm_setting_ip_config_add_routing_rule: + * @setting: the #NMSettingIPConfig + * @routing_rule: the #NMIPRoutingRule to add. The address family + * of the added rule must be compatible with the setting. + * + * Appends a new routing-rule and associated information to the setting. The + * given routing rules gets sealed and the reference count is incremented. + * The function does not check whether an identical rule already exists + * and always appends the rule to the end of the list. + * + * Since: 1.18 + **/ +void +nm_setting_ip_config_add_routing_rule(NMSettingIPConfig *setting, NMIPRoutingRule *routing_rule) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + g_return_if_fail(NM_IS_IP_ROUTING_RULE(routing_rule, TRUE)); + g_return_if_fail(_ip_routing_rule_get_addr_family(routing_rule) + == NM_SETTING_IP_CONFIG_GET_FAMILY(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + if (!priv->routing_rules) + priv->routing_rules = + g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_routing_rule_unref); + + nm_ip_routing_rule_seal(routing_rule); + g_ptr_array_add(priv->routing_rules, nm_ip_routing_rule_ref(routing_rule)); + _routing_rules_notify(setting); +} + +/** + * nm_setting_ip_config_remove_routing_rule: + * @setting: the #NMSettingIPConfig + * @idx: index number of the routing_rule + * + * Removes the routing_rule at index @idx. + * + * Since: 1.18 + **/ +void +nm_setting_ip_config_remove_routing_rule(NMSettingIPConfig *setting, guint idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_if_fail(priv->routing_rules && idx < priv->routing_rules->len); + + g_ptr_array_remove_index(priv->routing_rules, idx); + _routing_rules_notify(setting); +} + +/** + * nm_setting_ip_config_clear_routing_rules: + * @setting: the #NMSettingIPConfig + * + * Removes all configured routing rules. + * + * Since: 1.18 + **/ +void +nm_setting_ip_config_clear_routing_rules(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + if (priv->routing_rules && priv->routing_rules->len > 0) { + g_ptr_array_set_size(priv->routing_rules, 0); + _routing_rules_notify(setting); + } +} + +static GVariant * +_routing_rules_dbus_only_synth(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMSettingIPConfig * self = NM_SETTING_IP_CONFIG(setting); + NMSettingIPConfigPrivate *priv; + GVariantBuilder builder; + gboolean any = FALSE; + guint i; + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(self); + + if (!priv->routing_rules || priv->routing_rules->len == 0) + return NULL; + + for (i = 0; i < priv->routing_rules->len; i++) { + GVariant *variant; + + variant = nm_ip_routing_rule_to_dbus(priv->routing_rules->pdata[i]); + if (!variant) + continue; + + if (!any) { + any = TRUE; + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + } + g_variant_builder_add(&builder, "@a{sv}", variant); + } + + return any ? g_variant_builder_end(&builder) : NULL; +} + +static gboolean +_routing_rules_dbus_only_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GVariantIter iter_rules; + GVariant * rule_var; + guint i_rule; + gboolean success = FALSE; + gboolean rules_changed = FALSE; + + nm_assert(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}"))); + + g_variant_iter_init(&iter_rules, value); + + i_rule = 0; + while (g_variant_iter_next(&iter_rules, "@a{sv}", &rule_var)) { + _nm_unused gs_unref_variant GVariant *rule_var_unref = rule_var; + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rule = NULL; + gs_free_error GError *local = NULL; + + i_rule++; + + rule = + nm_ip_routing_rule_from_dbus(rule_var, + NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT), + &local); + if (!rule) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("rule #%u is invalid: %s"), + i_rule, + local->message); + goto out; + } + continue; + } + + nm_setting_ip_config_add_routing_rule(NM_SETTING_IP_CONFIG(setting), rule); + rules_changed = TRUE; + } + + success = TRUE; + +out: + if (rules_changed) + _routing_rules_notify(NM_SETTING_IP_CONFIG(setting)); + return success; +} + +/*****************************************************************************/ + +/** + * nm_setting_ip_config_get_ignore_auto_routes: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:ignore-auto-routes + * property. + * + * Returns: %TRUE if automatically configured (ie via DHCP) routes should be + * ignored. + **/ +gboolean +nm_setting_ip_config_get_ignore_auto_routes(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->ignore_auto_routes; +} + +/** + * nm_setting_ip_config_get_ignore_auto_dns: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:ignore-auto-dns + * property. + * + * Returns: %TRUE if automatically configured (ie via DHCP) DNS information + * should be ignored. + **/ +gboolean +nm_setting_ip_config_get_ignore_auto_dns(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->ignore_auto_dns; +} + +/** + * nm_setting_ip_config_get_dhcp_hostname: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:dhcp-hostname + * property. + * + * Returns: the configured hostname to send to the DHCP server + **/ +const char * +nm_setting_ip_config_get_dhcp_hostname(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_hostname; +} + +/** + * nm_setting_ip_config_get_dhcp_send_hostname: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:dhcp-send-hostname + * property. + * + * Returns: %TRUE if NetworkManager should send the machine hostname to the + * DHCP server when requesting addresses to allow the server to automatically + * update DNS information for this machine. + **/ +gboolean +nm_setting_ip_config_get_dhcp_send_hostname(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_send_hostname; +} + +/** + * nm_setting_ip_config_get_never_default: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:never-default + * property. + * + * Returns: %TRUE if this connection should never be the default + * connection + **/ +gboolean +nm_setting_ip_config_get_never_default(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->never_default; +} + +/** + * nm_setting_ip_config_get_may_fail: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:may-fail + * property. + * + * Returns: %TRUE if this connection doesn't require this type of IP + * addressing to complete for the connection to succeed. + **/ +gboolean +nm_setting_ip_config_get_may_fail(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->may_fail; +} + +/** + * nm_setting_ip_config_get_dad_timeout: + * @setting: the #NMSettingIPConfig + * + * Returns: the #NMSettingIPConfig:dad-timeout property. + * + * Since: 1.2 + **/ +int +nm_setting_ip_config_get_dad_timeout(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dad_timeout; +} + +/** + * nm_setting_ip_config_get_dhcp_hostname_flags: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:dhcp-hostname-flags + * property. + * + * Returns: flags for the DHCP hostname and FQDN + * + * Since: 1.22 + */ +NMDhcpHostnameFlags +nm_setting_ip_config_get_dhcp_hostname_flags(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NM_DHCP_HOSTNAME_FLAG_NONE); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_hostname_flags; +} + +/** + * nm_setting_ip_config_get_dhcp_timeout: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:dhcp-timeout + * property. + * + * Returns: the configured DHCP timeout in seconds. 0 = default for + * the particular kind of device. + * + * Since: 1.2 + **/ +int +nm_setting_ip_config_get_dhcp_timeout(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_timeout; +} + +/** + * nm_setting_ip_config_get_dhcp_iaid: + * @setting: the #NMSettingIPConfig + * + * Returns the value contained in the #NMSettingIPConfig:dhcp-iaid + * property. + * + * Returns: the configured DHCP IAID (Identity Association Identifier) + * + * Since: 1.22 + **/ +const char * +nm_setting_ip_config_get_dhcp_iaid(NMSettingIPConfig *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + + return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_iaid; +} + +/** + * nm_setting_ip_config_get_dhcp_reject_servers: + * @setting: the #NMSettingIPConfig + * @out_len: (allow-none) (out): the number of returned elements + * + * Returns: (array length=out_len zero-terminated=1) (transfer none): + * A %NULL terminated array of DHCP reject servers. Even if no reject + * servers are configured, this always returns a non %NULL value. + * + * Since: 1.28 + */ +const char *const * +nm_setting_ip_config_get_dhcp_reject_servers(NMSettingIPConfig *setting, guint *out_len) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL); + return nm_strvarray_get_strv(&NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_reject_servers, + out_len); +} + +/** + * nm_setting_ip_config_add_dhcp_reject_server: + * @setting: the #NMSettingIPConfig + * @server: the DHCP reject server to add + * + * Adds a new DHCP reject server to the setting. + * + * Since: 1.28 + **/ +void +nm_setting_ip_config_add_dhcp_reject_server(NMSettingIPConfig *setting, const char *server) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + g_return_if_fail(server != NULL); + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + nm_strvarray_add(nm_strvarray_ensure(&priv->dhcp_reject_servers), server); + _notify(setting, PROP_DHCP_REJECT_SERVERS); +} + +/** + * nm_setting_ip_config_remove_dhcp_reject_server: + * @setting: the #NMSettingIPConfig + * @idx: index number of the DHCP reject server + * + * Removes the DHCP reject server at index @idx. + * + * Since: 1.28 + **/ +void +nm_setting_ip_config_remove_dhcp_reject_server(NMSettingIPConfig *setting, guint idx) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + g_return_if_fail(priv->dhcp_reject_servers && idx < priv->dhcp_reject_servers->len); + + g_array_remove_index(priv->dhcp_reject_servers, idx); + _notify(setting, PROP_DHCP_REJECT_SERVERS); +} + +/** + * nm_setting_ip_config_clear_dhcp_reject_servers: + * @setting: the #NMSettingIPConfig + * + * Removes all configured DHCP reject servers. + * + * Since: 1.28 + **/ +void +nm_setting_ip_config_clear_dhcp_reject_servers(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting)); + + priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + if (nm_g_array_len(priv->dhcp_reject_servers) != 0) { + nm_clear_pointer(&priv->dhcp_reject_servers, g_array_unref); + _notify(setting, PROP_DHCP_REJECT_SERVERS); + } +} + +static gboolean +verify_label(const char *label) +{ + const char *p; + char * iface; + + p = strchr(label, ':'); + if (!p) + return FALSE; + iface = g_strndup(label, p - label); + if (!nm_utils_ifname_valid_kernel(iface, NULL)) { + g_free(iface); + return FALSE; + } + g_free(iface); + + for (p++; *p; p++) { + if (!g_ascii_isalnum(*p) && *p != '_') + return FALSE; + } + + return TRUE; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + guint i; + + if (!priv->method) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), NM_SETTING_IP_CONFIG_METHOD); + return FALSE; + } + + if (priv->dhcp_hostname && !*priv->dhcp_hostname) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME); + return FALSE; + } + + /* Validate DNS */ + for (i = 0; i < priv->dns->len; i++) { + const char *dns = priv->dns->pdata[i]; + + if (!nm_utils_ipaddr_is_valid(NM_SETTING_IP_CONFIG_GET_FAMILY(setting), dns)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%d. DNS server address is invalid"), + (int) (i + 1)); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_DNS); + return FALSE; + } + } + + /* Validate addresses */ + for (i = 0; i < priv->addresses->len; i++) { + NMIPAddress *addr = (NMIPAddress *) priv->addresses->pdata[i]; + GVariant * label; + + if (nm_ip_address_get_family(addr) != NM_SETTING_IP_CONFIG_GET_FAMILY(setting)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%d. IP address is invalid"), + (int) (i + 1)); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_ADDRESSES); + return FALSE; + } + + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + if (label) { + if (!g_variant_is_of_type(label, G_VARIANT_TYPE_STRING)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%d. IP address has 'label' property with invalid type"), + (int) (i + 1)); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_ADDRESSES); + return FALSE; + } + if (!verify_label(g_variant_get_string(label, NULL))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%d. IP address has invalid label '%s'"), + (int) (i + 1), + g_variant_get_string(label, NULL)); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_ADDRESSES); + return FALSE; + } + } + } + + /* Validate gateway */ + if (priv->gateway) { + if (!priv->addresses->len) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("gateway cannot be set if there are no addresses configured")); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_GATEWAY); + return FALSE; + } + + if (!nm_utils_ipaddr_is_valid(NM_SETTING_IP_CONFIG_GET_FAMILY(setting), priv->gateway)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("gateway is invalid")); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_GATEWAY); + return FALSE; + } + } + + /* Validate routes */ + for (i = 0; i < priv->routes->len; i++) { + gs_free_error GError *local = NULL; + NMIPRoute * route = (NMIPRoute *) priv->routes->pdata[i]; + + if (nm_ip_route_get_family(route) != NM_SETTING_IP_CONFIG_GET_FAMILY(setting)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%d. route is invalid"), + (int) (i + 1)); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_ROUTES); + return FALSE; + } + + if (!_nm_ip_route_attribute_validate_all(route, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid attribute: %s"), + local->message); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_ROUTES); + return FALSE; + } + } + + if (priv->routing_rules) { + for (i = 0; i < priv->routing_rules->len; i++) { + NMIPRoutingRule *rule = priv->routing_rules->pdata[i]; + gs_free_error GError *local = NULL; + + if (_ip_routing_rule_get_addr_family(rule) + != NM_SETTING_IP_CONFIG_GET_FAMILY(setting)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%u. rule has wrong address-family"), + i + 1); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_ROUTING_RULES); + return FALSE; + } + if (!nm_ip_routing_rule_validate(rule, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%u. rule is invalid: %s"), + i + 1, + local->message); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_ROUTES); + return FALSE; + } + } + } + + if (priv->dhcp_iaid && !_nm_utils_iaid_verify(priv->dhcp_iaid, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid IAID"), + priv->dhcp_iaid); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_DHCP_IAID); + return FALSE; + } + + /* Validate DHCP hostname flags */ + if (priv->dhcp_hostname_flags != NM_DHCP_HOSTNAME_FLAG_NONE && !priv->dhcp_send_hostname) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the property cannot be set when '%s' is disabled"), + NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS); + return FALSE; + } + + if (!_nm_utils_validate_dhcp_hostname_flags(priv->dhcp_hostname_flags, + NM_SETTING_IP_CONFIG_GET_FAMILY(setting), + error)) { + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS); + return FALSE; + } + + /* Validate reject servers */ + if (priv->dhcp_reject_servers && priv->dhcp_reject_servers->len != 0) { + if (NM_SETTING_IP_CONFIG_GET_FAMILY(setting) != AF_INET) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the property is currently supported only for DHCPv4")); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_DHCP_REJECT_SERVERS); + return FALSE; + } + + for (i = 0; i < priv->dhcp_reject_servers->len; i++) { + if (!nm_utils_parse_inaddr_prefix( + NM_SETTING_IP_CONFIG_GET_FAMILY(setting), + g_array_index(priv->dhcp_reject_servers, const char *, i), + NULL, + NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid IP or subnet"), + g_array_index(priv->dhcp_reject_servers, const char *, i)); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_DHCP_REJECT_SERVERS); + return FALSE; + } + } + } + + /* Normalizable errors */ + if (priv->gateway && priv->never_default) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("a gateway is incompatible with '%s'"), + NM_SETTING_IP_CONFIG_NEVER_DEFAULT); + g_prefix_error(error, + "%s.%s: ", + nm_setting_get_name(setting), + NM_SETTING_IP_CONFIG_GATEWAY); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingIPConfigPrivate *a_priv; + NMSettingIPConfigPrivate *b_priv; + guint i; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_IP_CONFIG_ADDRESSES)) { + if (set_b) { + a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(set_a); + b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(set_b); + + if (a_priv->addresses->len != b_priv->addresses->len) + return FALSE; + for (i = 0; i < a_priv->addresses->len; i++) { + if (nm_ip_address_cmp_full(a_priv->addresses->pdata[i], + b_priv->addresses->pdata[i], + NM_IP_ADDRESS_CMP_FLAGS_WITH_ATTRS) + != 0) + return FALSE; + } + } + return TRUE; + } + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_IP_CONFIG_ROUTES)) { + if (set_b) { + a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(set_a); + b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(set_b); + + if (a_priv->routes->len != b_priv->routes->len) + return FALSE; + for (i = 0; i < a_priv->routes->len; i++) { + if (!nm_ip_route_equal_full(a_priv->routes->pdata[i], + b_priv->routes->pdata[i], + NM_IP_ROUTE_EQUAL_CMP_FLAGS_WITH_ATTRS)) + return FALSE; + } + } + return TRUE; + } + + if (nm_streq(sett_info->property_infos[property_idx].name, + NM_SETTING_IP_CONFIG_ROUTING_RULES)) { + if (set_b) { + guint n; + + a_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(set_a); + b_priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(set_b); + + n = (a_priv->routing_rules) ? a_priv->routing_rules->len : 0u; + if (n != (b_priv->routing_rules ? b_priv->routing_rules->len : 0u)) + return FALSE; + for (i = 0; i < n; i++) { + if (nm_ip_routing_rule_cmp(a_priv->routing_rules->pdata[i], + b_priv->routing_rules->pdata[i]) + != 0) + return FALSE; + } + } + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_ip_config_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +static void +duplicate_copy_properties(const NMSettInfoSetting *sett_info, NMSetting *src, NMSetting *dst) +{ + NMSettingIPConfigPrivate *priv_src = NM_SETTING_IP_CONFIG_GET_PRIVATE(src); + NMSettingIPConfigPrivate *priv_dst = NM_SETTING_IP_CONFIG_GET_PRIVATE(dst); + guint i; + gboolean changed = FALSE; + + NM_SETTING_CLASS(nm_setting_ip_config_parent_class) + ->duplicate_copy_properties(sett_info, src, dst); + + if (priv_dst->routing_rules && priv_dst->routing_rules->len > 0) { + changed = TRUE; + g_ptr_array_set_size(priv_dst->routing_rules, 0); + } + if (priv_src->routing_rules && priv_src->routing_rules->len > 0) { + changed = TRUE; + if (!priv_dst->routing_rules) + priv_dst->routing_rules = + g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_routing_rule_unref); + for (i = 0; i < priv_src->routing_rules->len; i++) { + g_ptr_array_add(priv_dst->routing_rules, + nm_ip_routing_rule_ref(priv_src->routing_rules->pdata[i])); + } + } + if (changed) + _routing_rules_notify(NM_SETTING_IP_CONFIG(dst)); +} + +static void +enumerate_values(const NMSettInfoProperty *property_info, + NMSetting * setting, + NMSettingValueIterFn func, + gpointer user_data) +{ + if (nm_streq(property_info->name, NM_SETTING_IP_CONFIG_ROUTING_RULES)) { + NMSettingIPConfigPrivate * priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + nm_auto_unset_gvalue GValue value = G_VALUE_INIT; + GPtrArray * ptr = NULL; + guint i; + + if (priv->routing_rules && priv->routing_rules->len > 0) { + ptr = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_routing_rule_unref); + for (i = 0; i < priv->routing_rules->len; i++) + g_ptr_array_add(ptr, nm_ip_routing_rule_ref(priv->routing_rules->pdata[i])); + } + g_value_init(&value, G_TYPE_PTR_ARRAY); + g_value_take_boxed(&value, ptr); + func(setting, property_info->name, &value, 0, user_data); + return; + } + + NM_SETTING_CLASS(nm_setting_ip_config_parent_class) + ->enumerate_values(property_info, setting, func, user_data); +} + +/*****************************************************************************/ + +static gboolean +ip_gateway_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + /* FIXME: properly handle errors */ + + /* Don't set from 'gateway' if we're going to use the gateway in 'addresses' */ + if (_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "gateway")) + return TRUE; + + g_object_set(setting, property, g_variant_get_string(value, NULL), NULL); + return TRUE; +} + +GArray * +_nm_sett_info_property_override_create_array_ip_config(void) +{ + GArray *properties_override = _nm_sett_info_property_override_create_array(); + + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_METHOD], + &nm_sett_info_propert_type_string, + .to_dbus_data.get_string = + (const char *(*) (NMSetting *) ) nm_setting_ip_config_get_method); + + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_GATEWAY], + NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING, + .to_dbus_fcn = _nm_setting_property_to_dbus_fcn_get_string, + .from_dbus_fcn = ip_gateway_set), + .to_dbus_data.get_string = + (const char *(*) (NMSetting *) ) nm_setting_ip_config_get_gateway); + + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_DHCP_HOSTNAME], + &nm_sett_info_propert_type_string, + .to_dbus_data.get_string = + (const char *(*) (NMSetting *) ) nm_setting_ip_config_get_dhcp_hostname); + + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_DHCP_IAID], + &nm_sett_info_propert_type_string, + .to_dbus_data.get_string = + (const char *(*) (NMSetting *) ) nm_setting_ip_config_get_dhcp_iaid); + + /* ---dbus--- + * property: routing-rules + * format: array of 'a{sv}' + * description: Array of dictionaries for routing rules. + * ---end--- + */ + _nm_properties_override_dbus( + properties_override, + NM_SETTING_IP_CONFIG_ROUTING_RULES, + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = _routing_rules_dbus_only_synth, + .from_dbus_fcn = _routing_rules_dbus_only_set, )); + + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_IGNORE_AUTO_ROUTES], + &nm_sett_info_propert_type_boolean, + .to_dbus_data.get_boolean = (gboolean(*)( + NMSetting *)) nm_setting_ip_config_get_ignore_auto_routes); + + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_IGNORE_AUTO_DNS], + &nm_sett_info_propert_type_boolean, + .to_dbus_data.get_boolean = (gboolean(*)( + NMSetting *)) nm_setting_ip_config_get_ignore_auto_dns); + + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_DHCP_SEND_HOSTNAME], + &nm_sett_info_propert_type_boolean, + .to_dbus_data.get_boolean = (gboolean(*)( + NMSetting *)) nm_setting_ip_config_get_dhcp_send_hostname); + + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_NEVER_DEFAULT], + &nm_sett_info_propert_type_boolean, + .to_dbus_data.get_boolean = (gboolean(*)( + NMSetting *)) nm_setting_ip_config_get_never_default); + + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_MAY_FAIL], + &nm_sett_info_propert_type_boolean, + .to_dbus_data.get_boolean = + (gboolean(*)(NMSetting *)) nm_setting_ip_config_get_may_fail); + + return properties_override; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingIPConfig * setting = NM_SETTING_IP_CONFIG(object); + NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_METHOD: + g_value_set_string(value, nm_setting_ip_config_get_method(setting)); + break; + case PROP_DNS: + g_value_take_boxed(value, _nm_utils_ptrarray_to_strv(priv->dns)); + break; + case PROP_DNS_SEARCH: + g_value_take_boxed(value, _nm_utils_ptrarray_to_strv(priv->dns_search)); + break; + case PROP_DNS_OPTIONS: + g_value_take_boxed(value, + priv->dns_options ? _nm_utils_ptrarray_to_strv(priv->dns_options) + : NULL); + break; + case PROP_DNS_PRIORITY: + g_value_set_int(value, priv->dns_priority); + break; + case PROP_ADDRESSES: + g_value_take_boxed(value, + _nm_utils_copy_array(priv->addresses, + (NMUtilsCopyFunc) nm_ip_address_dup, + (GDestroyNotify) nm_ip_address_unref)); + break; + case PROP_GATEWAY: + g_value_set_string(value, nm_setting_ip_config_get_gateway(setting)); + break; + case PROP_ROUTES: + g_value_take_boxed(value, + _nm_utils_copy_array(priv->routes, + (NMUtilsCopyFunc) nm_ip_route_dup, + (GDestroyNotify) nm_ip_route_unref)); + break; + case PROP_ROUTE_METRIC: + g_value_set_int64(value, priv->route_metric); + break; + case PROP_ROUTE_TABLE: + g_value_set_uint(value, priv->route_table); + break; + case PROP_IGNORE_AUTO_ROUTES: + g_value_set_boolean(value, nm_setting_ip_config_get_ignore_auto_routes(setting)); + break; + case PROP_IGNORE_AUTO_DNS: + g_value_set_boolean(value, nm_setting_ip_config_get_ignore_auto_dns(setting)); + break; + case PROP_DHCP_HOSTNAME: + g_value_set_string(value, nm_setting_ip_config_get_dhcp_hostname(setting)); + break; + case PROP_DHCP_SEND_HOSTNAME: + g_value_set_boolean(value, nm_setting_ip_config_get_dhcp_send_hostname(setting)); + break; + case PROP_NEVER_DEFAULT: + g_value_set_boolean(value, priv->never_default); + break; + case PROP_MAY_FAIL: + g_value_set_boolean(value, priv->may_fail); + break; + case PROP_DAD_TIMEOUT: + g_value_set_int(value, nm_setting_ip_config_get_dad_timeout(setting)); + break; + case PROP_DHCP_TIMEOUT: + g_value_set_int(value, nm_setting_ip_config_get_dhcp_timeout(setting)); + break; + case PROP_DHCP_IAID: + g_value_set_string(value, nm_setting_ip_config_get_dhcp_iaid(setting)); + break; + case PROP_DHCP_HOSTNAME_FLAGS: + g_value_set_uint(value, nm_setting_ip_config_get_dhcp_hostname_flags(setting)); + break; + case PROP_DHCP_REJECT_SERVERS: + g_value_set_boxed(value, nm_strvarray_get_strv_non_empty(priv->dhcp_reject_servers, NULL)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingIPConfig * setting = NM_SETTING_IP_CONFIG(object); + NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + const char * gateway; + char ** strv; + guint i; + + switch (prop_id) { + case PROP_METHOD: + g_free(priv->method); + priv->method = g_value_dup_string(value); + break; + case PROP_DNS: + g_ptr_array_unref(priv->dns); + priv->dns = _nm_utils_strv_to_ptrarray(g_value_get_boxed(value)); + break; + case PROP_DNS_SEARCH: + g_ptr_array_unref(priv->dns_search); + priv->dns_search = _nm_utils_strv_to_ptrarray(g_value_get_boxed(value)); + break; + case PROP_DNS_OPTIONS: + strv = g_value_get_boxed(value); + if (!strv) { + if (priv->dns_options) { + g_ptr_array_unref(priv->dns_options); + priv->dns_options = NULL; + } + } else { + if (priv->dns_options) + g_ptr_array_set_size(priv->dns_options, 0); + else + priv->dns_options = g_ptr_array_new_with_free_func(g_free); + for (i = 0; strv[i]; i++) { + if (_nm_utils_dns_option_validate(strv[i], NULL, NULL, FALSE, NULL) + && _nm_utils_dns_option_find_idx(priv->dns_options, strv[i]) < 0) + g_ptr_array_add(priv->dns_options, g_strdup(strv[i])); + } + } + break; + case PROP_DNS_PRIORITY: + priv->dns_priority = g_value_get_int(value); + break; + case PROP_ADDRESSES: + g_ptr_array_unref(priv->addresses); + priv->addresses = _nm_utils_copy_array(g_value_get_boxed(value), + (NMUtilsCopyFunc) nm_ip_address_dup, + (GDestroyNotify) nm_ip_address_unref); + break; + case PROP_GATEWAY: + gateway = g_value_get_string(value); + g_return_if_fail( + !gateway + || nm_utils_ipaddr_is_valid(NM_SETTING_IP_CONFIG_GET_FAMILY(setting), gateway)); + g_free(priv->gateway); + priv->gateway = canonicalize_ip(NM_SETTING_IP_CONFIG_GET_FAMILY(setting), gateway, TRUE); + break; + case PROP_ROUTES: + g_ptr_array_unref(priv->routes); + priv->routes = _nm_utils_copy_array(g_value_get_boxed(value), + (NMUtilsCopyFunc) nm_ip_route_dup, + (GDestroyNotify) nm_ip_route_unref); + break; + case PROP_ROUTE_METRIC: + priv->route_metric = g_value_get_int64(value); + break; + case PROP_ROUTE_TABLE: + priv->route_table = g_value_get_uint(value); + break; + case PROP_IGNORE_AUTO_ROUTES: + priv->ignore_auto_routes = g_value_get_boolean(value); + break; + case PROP_IGNORE_AUTO_DNS: + priv->ignore_auto_dns = g_value_get_boolean(value); + break; + case PROP_DHCP_HOSTNAME: + g_free(priv->dhcp_hostname); + priv->dhcp_hostname = g_value_dup_string(value); + break; + case PROP_DHCP_SEND_HOSTNAME: + priv->dhcp_send_hostname = g_value_get_boolean(value); + break; + case PROP_NEVER_DEFAULT: + priv->never_default = g_value_get_boolean(value); + break; + case PROP_MAY_FAIL: + priv->may_fail = g_value_get_boolean(value); + break; + case PROP_DAD_TIMEOUT: + priv->dad_timeout = g_value_get_int(value); + break; + case PROP_DHCP_TIMEOUT: + priv->dhcp_timeout = g_value_get_int(value); + break; + case PROP_DHCP_IAID: + priv->dhcp_iaid = g_value_dup_string(value); + break; + case PROP_DHCP_HOSTNAME_FLAGS: + priv->dhcp_hostname_flags = g_value_get_uint(value); + break; + case PROP_DHCP_REJECT_SERVERS: + nm_strvarray_set_strv(&priv->dhcp_reject_servers, g_value_get_boxed(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ip_config_init(NMSettingIPConfig *setting) +{ + NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting); + + priv->dns = g_ptr_array_new_with_free_func(g_free); + priv->dns_search = g_ptr_array_new_with_free_func(g_free); + priv->addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref); + priv->routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref); + priv->route_metric = -1; + priv->dhcp_send_hostname = TRUE; + priv->may_fail = TRUE; + priv->dad_timeout = -1; +} + +static void +finalize(GObject *object) +{ + NMSettingIPConfig * self = NM_SETTING_IP_CONFIG(object); + NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(self); + + g_free(priv->method); + g_free(priv->gateway); + g_free(priv->dhcp_hostname); + g_free(priv->dhcp_iaid); + + g_ptr_array_unref(priv->dns); + g_ptr_array_unref(priv->dns_search); + if (priv->dns_options) + g_ptr_array_unref(priv->dns_options); + g_ptr_array_unref(priv->addresses); + g_ptr_array_unref(priv->routes); + if (priv->routing_rules) + g_ptr_array_unref(priv->routing_rules); + nm_clear_pointer(&priv->dhcp_reject_servers, g_array_unref); + + G_OBJECT_CLASS(nm_setting_ip_config_parent_class)->finalize(object); +} + +static void +nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NMSettingIPConfigPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->compare_property = compare_property; + setting_class->duplicate_copy_properties = duplicate_copy_properties; + setting_class->enumerate_values = enumerate_values; + + /** + * NMSettingIPConfig:method: + * + * IP configuration method. + * + * #NMSettingIP4Config and #NMSettingIP6Config both support "disabled", + * "auto", "manual", and "link-local". See the subclass-specific + * documentation for other values. + * + * In general, for the "auto" method, properties such as + * #NMSettingIPConfig:dns and #NMSettingIPConfig:routes specify information + * that is added on to the information returned from automatic + * configuration. The #NMSettingIPConfig:ignore-auto-routes and + * #NMSettingIPConfig:ignore-auto-dns properties modify this behavior. + * + * For methods that imply no upstream network, such as "shared" or + * "link-local", these properties must be empty. + * + * For IPv4 method "shared", the IP subnet can be configured by adding one + * manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the + * shared method must be configured on the interface which shares the internet + * to a subnet, not on the uplink which is shared. + **/ + obj_properties[PROP_METHOD] = g_param_spec_string( + NM_SETTING_IP_CONFIG_METHOD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dns: + * + * Array of IP addresses of DNS servers. + **/ + obj_properties[PROP_DNS] = g_param_spec_boxed(NM_SETTING_IP_CONFIG_DNS, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dns-search: + * + * Array of DNS search domains. Domains starting with a tilde ('~') + * are considered 'routing' domains and are used only to decide the + * interface over which a query must be forwarded; they are not used + * to complete unqualified host names. + * + * When using a DNS plugin that supports Conditional Forwarding or + * Split DNS, then the search domains specify which name servers to + * query. This makes the behavior different from running with plain + * /etc/resolv.conf. For more information see also the dns-priority setting. + **/ + obj_properties[PROP_DNS_SEARCH] = + g_param_spec_boxed(NM_SETTING_IP_CONFIG_DNS_SEARCH, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dns-options: + * + * Array of DNS options as described in man 5 resolv.conf. + * + * %NULL means that the options are unset and left at the default. + * In this case NetworkManager will use default options. This is + * distinct from an empty list of properties. + * + * The currently supported options are "attempts", "debug", "edns0", + * "inet6", "ip6-bytestring", "ip6-dotint", "ndots", "no-check-names", + * "no-ip6-dotint", "no-reload", "no-tld-query", "rotate", "single-request", + * "single-request-reopen", "timeout", "trust-ad", "use-vc". + * + * The "trust-ad" setting is only honored if the profile contributes + * name servers to resolv.conf, and if all contributing profiles have + * "trust-ad" enabled. + * + * When using a caching DNS plugin (dnsmasq or systemd-resolved in + * NetworkManager.conf) then "edns0" and "trust-ad" are automatically + * added. + * + * Since: 1.2 + **/ + obj_properties[PROP_DNS_OPTIONS] = + g_param_spec_boxed(NM_SETTING_IP_CONFIG_DNS_OPTIONS, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dns-priority: + * + * DNS servers priority. + * + * The relative priority for DNS servers specified by this setting. A lower + * numerical value is better (higher priority). + * + * Negative values have the special effect of excluding other configurations + * with a greater numerical priority value; so in presence of at least one negative + * priority, only DNS servers from connections with the lowest priority value will be used. + * To avoid all DNS leaks, set the priority of the profile that should be used + * to the most negative value of all active connections profiles. + * + * Zero selects a globally configured default value. If the latter is missing + * or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for + * other connections. + * + * Note that the priority is to order DNS settings for multiple active + * connections. It does not disambiguate multiple DNS servers within the + * same connection profile. + * + * When multiple devices have configurations with the same priority, VPNs will be + * considered first, then devices with the best (lowest metric) default + * route and then all other devices. + * + * When using dns=default, servers with higher priority will be on top of + * resolv.conf. To prioritize a given server over another one within the + * same connection, just specify them in the desired order. + * Note that commonly the resolver tries name servers in /etc/resolv.conf + * in the order listed, proceeding with the next server in the list + * on failure. See for example the "rotate" option of the dns-options setting. + * If there are any negative DNS priorities, then only name servers from + * the devices with that lowest priority will be considered. + * + * When using a DNS resolver that supports Conditional Forwarding or + * Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection + * is used to query domains in its search list. The search domains determine which + * name servers to ask, and the DNS priority is used to prioritize + * name servers based on the domain. Queries for domains not present in any + * search list are routed through connections having the '~.' special wildcard + * domain, which is added automatically to connections with the default route + * (or can be added manually). When multiple connections specify the same domain, the + * one with the best priority (lowest numerical value) wins. If a sub domain + * is configured on another interface it will be accepted regardless the priority, + * unless parent domain on the other interface has a negative priority, which causes + * the sub domain to be shadowed. + * With Split DNS one can avoid undesired DNS leaks by properly configuring + * DNS priorities and the search domains, so that only name servers of the desired + * interface are configured. + * + * Since: 1.4 + **/ + obj_properties[PROP_DNS_PRIORITY] = + g_param_spec_int(NM_SETTING_IP_CONFIG_DNS_PRIORITY, + "", + "", + G_MININT32, + G_MAXINT32, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:addresses: (type GPtrArray(NMIPAddress)) + * + * Array of IP addresses. + **/ + obj_properties[PROP_ADDRESSES] = + g_param_spec_boxed(NM_SETTING_IP_CONFIG_ADDRESSES, + "", + "", + G_TYPE_PTR_ARRAY, + /* "addresses" is a legacy D-Bus property, because the + * "addresses" GObject property normally gets set from + * the "address-data" D-Bus property... + */ + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | NM_SETTING_PARAM_LEGACY + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:gateway: + * + * The gateway associated with this configuration. This is only meaningful + * if #NMSettingIPConfig:addresses is also set. + * + * The gateway's main purpose is to control the next hop of the standard default route on the device. + * Hence, the gateway property conflicts with #NMSettingIPConfig:never-default and will be + * automatically dropped if the IP configuration is set to never-default. + * + * As an alternative to set the gateway, configure a static default route with /0 as prefix + * length. + **/ + obj_properties[PROP_GATEWAY] = g_param_spec_string( + NM_SETTING_IP_CONFIG_GATEWAY, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:routes: (type GPtrArray(NMIPRoute)) + * + * Array of IP routes. + **/ + obj_properties[PROP_ROUTES] = + g_param_spec_boxed(NM_SETTING_IP_CONFIG_ROUTES, + "", + "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | + /* See :addresses above Re: LEGACY */ + NM_SETTING_PARAM_LEGACY | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:route-metric: + * + * The default metric for routes that don't explicitly specify a metric. + * The default value -1 means that the metric is chosen automatically + * based on the device type. + * The metric applies to dynamic routes, manual (static) routes that + * don't have an explicit metric setting, address prefix routes, and + * the default route. + * Note that for IPv6, the kernel accepts zero (0) but coerces it to + * 1024 (user default). Hence, setting this property to zero effectively + * mean setting it to 1024. + * For IPv4, zero is a regular value for the metric. + **/ + obj_properties[PROP_ROUTE_METRIC] = + g_param_spec_int64(NM_SETTING_IP_CONFIG_ROUTE_METRIC, + "", + "", + -1, + G_MAXUINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:route-table: + * + * Enable policy routing (source routing) and set the routing table used when adding routes. + * + * This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes + * and static routes. But note that static routes can individually overwrite the setting + * by explicitly specifying a non-zero routing table. + * + * If the table setting is left at zero, it is eligible to be overwritten via global + * configuration. If the property is zero even after applying the global configuration + * value, policy routing is disabled for the address family of this connection. + * + * Policy routing disabled means that NetworkManager will add all routes to the main + * table (except static routes that explicitly configure a different table). Additionally, + * NetworkManager will not delete any extraneous routes from tables except the main table. + * This is to preserve backward compatibility for users who manage routing tables outside + * of NetworkManager. + * + * Since: 1.10 + **/ + obj_properties[PROP_ROUTE_TABLE] = g_param_spec_uint( + NM_SETTING_IP_CONFIG_ROUTE_TABLE, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + /** + * NMSettingIPConfig:ignore-auto-routes: + * + * When #NMSettingIPConfig:method is set to "auto" and this property to + * %TRUE, automatically configured routes are ignored and only routes + * specified in the #NMSettingIPConfig:routes property, if any, are used. + **/ + obj_properties[PROP_IGNORE_AUTO_ROUTES] = + g_param_spec_boolean(NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:ignore-auto-dns: + * + * When #NMSettingIPConfig:method is set to "auto" and this property to + * %TRUE, automatically configured name servers and search domains are + * ignored and only name servers and search domains specified in the + * #NMSettingIPConfig:dns and #NMSettingIPConfig:dns-search properties, if + * any, are used. + **/ + obj_properties[PROP_IGNORE_AUTO_DNS] = + g_param_spec_boolean(NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dhcp-hostname: + * + * If the #NMSettingIPConfig:dhcp-send-hostname property is %TRUE, then the + * specified name will be sent to the DHCP server when acquiring a lease. + * This property and #NMSettingIP4Config:dhcp-fqdn are mutually exclusive and + * cannot be set at the same time. + **/ + obj_properties[PROP_DHCP_HOSTNAME] = + g_param_spec_string(NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dhcp-send-hostname: + * + * If %TRUE, a hostname is sent to the DHCP server when acquiring a lease. + * Some DHCP servers use this hostname to update DNS databases, essentially + * providing a static hostname for the computer. If the + * #NMSettingIPConfig:dhcp-hostname property is %NULL and this property is + * %TRUE, the current persistent hostname of the computer is sent. + **/ + obj_properties[PROP_DHCP_SEND_HOSTNAME] = + g_param_spec_boolean(NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:never-default: + * + * If %TRUE, this connection will never be the default connection for this + * IP type, meaning it will never be assigned the default route by + * NetworkManager. + **/ + obj_properties[PROP_NEVER_DEFAULT] = + g_param_spec_boolean(NM_SETTING_IP_CONFIG_NEVER_DEFAULT, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:may-fail: + * + * If %TRUE, allow overall network configuration to proceed even if the + * configuration specified by this property times out. Note that at least + * one IP configuration must succeed or overall network configuration will + * still fail. For example, in IPv6-only networks, setting this property to + * %TRUE on the #NMSettingIP4Config allows the overall network configuration + * to succeed if IPv4 configuration fails but IPv6 configuration completes + * successfully. + **/ + obj_properties[PROP_MAY_FAIL] = + g_param_spec_boolean(NM_SETTING_IP_CONFIG_MAY_FAIL, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dad-timeout: + * + * Timeout in milliseconds used to check for the presence of duplicate IP + * addresses on the network. If an address conflict is detected, the + * activation will fail. A zero value means that no duplicate address + * detection is performed, -1 means the default value (either configuration + * ipvx.dad-timeout override or zero). A value greater than zero is a + * timeout in milliseconds. + * + * The property is currently implemented only for IPv4. + * + * Since: 1.2 + **/ + obj_properties[PROP_DAD_TIMEOUT] = g_param_spec_int( + NM_SETTING_IP_CONFIG_DAD_TIMEOUT, + "", + "", + -1, + NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX, + -1, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dhcp-timeout: + * + * A timeout for a DHCP transaction in seconds. If zero (the default), a + * globally configured default is used. If still unspecified, a device specific + * timeout is used (usually 45 seconds). + * + * Set to 2147483647 (MAXINT32) for infinity. + **/ + obj_properties[PROP_DHCP_TIMEOUT] = g_param_spec_int( + NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, + "", + "", + 0, + G_MAXINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dhcp-iaid: + * + * A string containing the "Identity Association Identifier" (IAID) used + * by the DHCP client. The property is a 32-bit decimal value or a + * special value among "mac", "perm-mac", "ifname" and "stable". When + * set to "mac" (or "perm-mac"), the last 4 bytes of the current (or + * permanent) MAC address are used as IAID. When set to "ifname", the + * IAID is computed by hashing the interface name. The special value + * "stable" can be used to generate an IAID based on the stable-id (see + * connection.stable-id), a per-host key and the interface name. When + * the property is unset, the value from global configuration is used; + * if no global default is set then the IAID is assumed to be + * "ifname". Note that at the moment this property is ignored for IPv6 + * by dhclient, which always derives the IAID from the MAC address. + * + * Since: 1.22 + **/ + obj_properties[PROP_DHCP_IAID] = + g_param_spec_string(NM_SETTING_IP_CONFIG_DHCP_IAID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dhcp-hostname-flags: + * + * Flags for the DHCP hostname and FQDN. + * + * Currently, this property only includes flags to control the FQDN flags + * set in the DHCP FQDN option. Supported FQDN flags are + * %NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE, + * %NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED and + * %NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE. When no FQDN flag is set and + * %NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS is set, the DHCP FQDN option will + * contain no flag. Otherwise, if no FQDN flag is set and + * %NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS is not set, the standard FQDN flags + * are set in the request: + * %NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE, + * %NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED for IPv4 and + * %NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE for IPv6. + * + * When this property is set to the default value %NM_DHCP_HOSTNAME_FLAG_NONE, + * a global default is looked up in NetworkManager configuration. If that value + * is unset or also %NM_DHCP_HOSTNAME_FLAG_NONE, then the standard FQDN flags + * described above are sent in the DHCP requests. + * + * Since: 1.22 + */ + obj_properties[PROP_DHCP_HOSTNAME_FLAGS] = + g_param_spec_uint(NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, + "", + "", + 0, + G_MAXUINT32, + NM_DHCP_HOSTNAME_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPConfig:dhcp-reject-servers: + * + * Array of servers from which DHCP offers must be rejected. This property + * is useful to avoid getting a lease from misconfigured or rogue servers. + * + * For DHCPv4, each element must be an IPv4 address, optionally + * followed by a slash and a prefix length (e.g. "192.168.122.0/24"). + * + * This property is currently not implemented for DHCPv6. + * + * Since: 1.28 + **/ + obj_properties[PROP_DHCP_REJECT_SERVERS] = + g_param_spec_boxed(NM_SETTING_IP_CONFIG_DHCP_REJECT_SERVERS, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/libnm-core-impl/nm-setting-ip-tunnel.c b/src/libnm-core-impl/nm-setting-ip-tunnel.c new file mode 100644 index 00000000..d87a59f4 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ip-tunnel.c @@ -0,0 +1,888 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ip-tunnel.h" + +#include "nm-setting-private.h" +#include "nm-utils.h" + +/** + * SECTION:nm-setting-ip-tunnel + * @short_description: Describes connection properties for IP tunnel devices + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT, + PROP_MODE, + PROP_LOCAL, + PROP_REMOTE, + PROP_TTL, + PROP_TOS, + PROP_PATH_MTU_DISCOVERY, + PROP_INPUT_KEY, + PROP_OUTPUT_KEY, + PROP_ENCAPSULATION_LIMIT, + PROP_FLOW_LABEL, + PROP_MTU, + PROP_FLAGS, ); + +typedef struct { + char * parent; + char * local; + char * remote; + char * input_key; + char * output_key; + guint ttl; + guint tos; + guint encapsulation_limit; + guint flow_label; + NMIPTunnelMode mode; + guint32 mtu; + guint32 flags; + bool path_mtu_discovery : 1; +} NMSettingIPTunnelPrivate; + +/** + * NMSettingIPTunnel: + * + * IP Tunneling Settings + */ +struct _NMSettingIPTunnel { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingIPTunnelClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingIPTunnel, nm_setting_ip_tunnel, NM_TYPE_SETTING) + +#define NM_SETTING_IP_TUNNEL_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_IP_TUNNEL, NMSettingIPTunnelPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_ip_tunnel_get_parent: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:parent property of the setting + * + * Returns: the parent device + * + * Since: 1.2 + **/ +const char * +nm_setting_ip_tunnel_get_parent(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), NULL); + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->parent; +} + +/** + * nm_setting_ip_tunnel_get_mode: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:mode property of the setting. + * + * Returns: the tunnel mode + * + * Since: 1.2 + **/ +NMIPTunnelMode +nm_setting_ip_tunnel_get_mode(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), 0); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->mode; +} + +/** + * nm_setting_ip_tunnel_get_local: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:local property of the setting. + * + * Returns: the local endpoint + * + * Since: 1.2 + **/ +const char * +nm_setting_ip_tunnel_get_local(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), NULL); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->local; +} + +/** + * nm_setting_ip_tunnel_get_remote: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:remote property of the setting. + * + * Returns: the remote endpoint + * + * Since: 1.2 + **/ +const char * +nm_setting_ip_tunnel_get_remote(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), NULL); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->remote; +} + +/** + * nm_setting_ip_tunnel_get_ttl: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:ttl property of the setting. + * + * Returns: the Time-to-live value + * + * Since: 1.2 + **/ + +guint +nm_setting_ip_tunnel_get_ttl(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), 0); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->ttl; +} + +/** + * nm_setting_ip_tunnel_get_tos: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:tos property of the setting. + * + * Returns: the TOS value + * + * Since: 1.2 + **/ +guint +nm_setting_ip_tunnel_get_tos(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), 0); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->tos; +} + +/** + * nm_setting_ip_tunnel_get_path_mtu_discovery: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:path-mtu-discovery property of the setting. + * + * Returns: whether path MTU discovery is enabled + * + * Since: 1.2 + **/ +gboolean +nm_setting_ip_tunnel_get_path_mtu_discovery(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), TRUE); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->path_mtu_discovery; +} + +/** + * nm_setting_ip_tunnel_get_input_key: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:input-key property of the setting. + * + * Returns: the input key + * + * Since: 1.2 + **/ +const char * +nm_setting_ip_tunnel_get_input_key(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), NULL); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->input_key; +} + +/** + * nm_setting_ip_tunnel_get_output_key: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:output-key property of the setting. + * + * Returns: the output key + * + * Since: 1.2 + **/ +const char * +nm_setting_ip_tunnel_get_output_key(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), NULL); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->output_key; +} + +/** + * nm_setting_ip_tunnel_get_encapsulation_limit: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:encapsulation-limit property of the setting. + * + * Returns: the encapsulation limit value + * + * Since: 1.2 + **/ +guint +nm_setting_ip_tunnel_get_encapsulation_limit(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), 0); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->encapsulation_limit; +} + +/** + * nm_setting_ip_tunnel_get_flow_label: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:flow-label property of the setting. + * + * Returns: the flow label value + * + * Since: 1.2 + **/ +guint +nm_setting_ip_tunnel_get_flow_label(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), 0); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->flow_label; +} + +/** + * nm_setting_ip_tunnel_get_mtu: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:mtu property of the setting. + * + * Returns: the MTU + * + * Since: 1.2 + **/ +guint +nm_setting_ip_tunnel_get_mtu(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), 0); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->mtu; +} + +/* + * nm_setting_ip_tunnel_get_flags: + * @setting: the #NMSettingIPTunnel + * + * Returns the #NMSettingIPTunnel:flags property of the setting. + * + * Returns: the tunnel flags + * + * Since: 1.12 + **/ +NMIPTunnelFlags +nm_setting_ip_tunnel_get_flags(NMSettingIPTunnel *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP_TUNNEL(setting), NM_IP_TUNNEL_FLAG_NONE); + + return NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting)->flags; +} + +/*****************************************************************************/ + +gboolean +_nm_ip_tunnel_mode_is_layer2(NMIPTunnelMode mode) +{ + return NM_IN_SET(mode, NM_IP_TUNNEL_MODE_GRETAP, NM_IP_TUNNEL_MODE_IP6GRETAP); +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingIPTunnelPrivate *priv = NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting); + int family = AF_UNSPEC; + guint32 flags; + + switch (priv->mode) { + case NM_IP_TUNNEL_MODE_IPIP: + case NM_IP_TUNNEL_MODE_SIT: + case NM_IP_TUNNEL_MODE_ISATAP: + case NM_IP_TUNNEL_MODE_GRE: + case NM_IP_TUNNEL_MODE_VTI: + case NM_IP_TUNNEL_MODE_GRETAP: + family = AF_INET; + break; + case NM_IP_TUNNEL_MODE_IP6IP6: + case NM_IP_TUNNEL_MODE_IPIP6: + case NM_IP_TUNNEL_MODE_IP6GRE: + case NM_IP_TUNNEL_MODE_VTI6: + case NM_IP_TUNNEL_MODE_IP6GRETAP: + family = AF_INET6; + break; + case NM_IP_TUNNEL_MODE_UNKNOWN: + break; + } + + if (family == AF_UNSPEC) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%d' is not a valid tunnel mode"), + (int) priv->mode); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_MODE); + return FALSE; + } + + if (priv->parent && !nm_utils_ifname_valid_kernel(priv->parent, NULL) + && !nm_utils_is_uuid(priv->parent)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is neither an UUID nor an interface name"), + priv->parent); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_PARENT); + return FALSE; + } + + if (priv->local && !nm_utils_ipaddr_is_valid(family, priv->local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid IPv%c address"), + priv->local, + family == AF_INET ? '4' : '6'); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_LOCAL); + return FALSE; + } + + if (!priv->remote) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_REMOTE); + return FALSE; + } + + if (!nm_utils_ipaddr_is_valid(family, priv->remote)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid IPv%c address"), + priv->remote, + family == AF_INET ? '4' : '6'); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_REMOTE); + return FALSE; + } + + if ((priv->input_key && priv->input_key[0]) || (priv->output_key && priv->output_key[0])) { + if (!NM_IN_SET(priv->mode, + NM_IP_TUNNEL_MODE_GRE, + NM_IP_TUNNEL_MODE_GRETAP, + NM_IP_TUNNEL_MODE_IP6GRE, + NM_IP_TUNNEL_MODE_IP6GRETAP)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("tunnel keys can only be specified for GRE tunnels")); + return FALSE; + } + } + + if (priv->input_key && priv->input_key[0]) { + gint64 val; + + val = _nm_utils_ascii_str_to_int64(priv->input_key, 10, 0, G_MAXUINT32, -1); + if (val == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid tunnel key"), + priv->input_key); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_INPUT_KEY); + return FALSE; + } + } + + if (priv->output_key && priv->output_key[0]) { + gint64 val; + + val = _nm_utils_ascii_str_to_int64(priv->output_key, 10, 0, G_MAXUINT32, -1); + if (val == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid tunnel key"), + priv->output_key); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_OUTPUT_KEY); + return FALSE; + } + } + + if (!priv->path_mtu_discovery && priv->ttl != 0) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("a fixed TTL is allowed only when path MTU discovery is enabled")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_TTL); + return FALSE; + } + + flags = priv->flags; + if (NM_IN_SET(priv->mode, NM_IP_TUNNEL_MODE_IPIP6, NM_IP_TUNNEL_MODE_IP6IP6)) + flags &= (guint32) (~_NM_IP_TUNNEL_FLAG_ALL_IP6TNL); + if (flags) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("some flags are invalid for the select mode: %s"), + nm_utils_enum_to_str(nm_ip_tunnel_flags_get_type(), flags)); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_FLAGS); + return FALSE; + } + + if (nm_connection_get_setting_wired(connection) && !_nm_ip_tunnel_mode_is_layer2(priv->mode)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("wired setting not allowed for mode %s"), + nm_utils_enum_to_str(nm_ip_tunnel_mode_get_type(), priv->mode)); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP_TUNNEL_SETTING_NAME, + NM_SETTING_IP_TUNNEL_MODE); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingIPTunnel * setting = NM_SETTING_IP_TUNNEL(object); + NMSettingIPTunnelPrivate *priv = NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_value_set_string(value, priv->parent); + break; + case PROP_MODE: + g_value_set_uint(value, priv->mode); + break; + case PROP_LOCAL: + g_value_set_string(value, priv->local); + break; + case PROP_REMOTE: + g_value_set_string(value, priv->remote); + break; + case PROP_TTL: + g_value_set_uint(value, priv->ttl); + break; + case PROP_TOS: + g_value_set_uint(value, priv->tos); + break; + case PROP_PATH_MTU_DISCOVERY: + g_value_set_boolean(value, priv->path_mtu_discovery); + break; + case PROP_INPUT_KEY: + g_value_set_string(value, priv->input_key); + break; + case PROP_OUTPUT_KEY: + g_value_set_string(value, priv->output_key); + break; + case PROP_ENCAPSULATION_LIMIT: + g_value_set_uint(value, priv->encapsulation_limit); + break; + case PROP_FLOW_LABEL: + g_value_set_uint(value, priv->flow_label); + break; + case PROP_MTU: + g_value_set_uint(value, priv->mtu); + break; + case PROP_FLAGS: + g_value_set_uint(value, priv->flags); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingIPTunnel * setting = NM_SETTING_IP_TUNNEL(object); + NMSettingIPTunnelPrivate *priv = NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_free(priv->parent); + priv->parent = g_value_dup_string(value); + break; + case PROP_MODE: + priv->mode = g_value_get_uint(value); + break; + case PROP_LOCAL: + g_free(priv->local); + priv->local = g_value_dup_string(value); + break; + case PROP_REMOTE: + g_free(priv->remote); + priv->remote = g_value_dup_string(value); + break; + case PROP_TTL: + priv->ttl = g_value_get_uint(value); + break; + case PROP_TOS: + priv->tos = g_value_get_uint(value); + break; + case PROP_PATH_MTU_DISCOVERY: + priv->path_mtu_discovery = g_value_get_boolean(value); + break; + case PROP_INPUT_KEY: + g_free(priv->input_key); + priv->input_key = g_value_dup_string(value); + break; + case PROP_OUTPUT_KEY: + g_free(priv->output_key); + priv->output_key = g_value_dup_string(value); + break; + case PROP_ENCAPSULATION_LIMIT: + priv->encapsulation_limit = g_value_get_uint(value); + break; + case PROP_FLOW_LABEL: + priv->flow_label = g_value_get_uint(value); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + case PROP_FLAGS: + priv->flags = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ip_tunnel_init(NMSettingIPTunnel *self) +{ + NMSettingIPTunnelPrivate *priv = NM_SETTING_IP_TUNNEL_GET_PRIVATE(self); + + priv->path_mtu_discovery = TRUE; +} + +/** + * nm_setting_ip_tunnel_new: + * + * Creates a new #NMSettingIPTunnel object with default values. + * + * Returns: (transfer full): the new empty #NMSettingIPTunnel object + * + * Since: 1.2 + **/ +NMSetting * +nm_setting_ip_tunnel_new(void) +{ + return g_object_new(NM_TYPE_SETTING_IP_TUNNEL, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingIPTunnel * setting = NM_SETTING_IP_TUNNEL(object); + NMSettingIPTunnelPrivate *priv = NM_SETTING_IP_TUNNEL_GET_PRIVATE(setting); + + g_free(priv->parent); + g_free(priv->local); + g_free(priv->remote); + g_free(priv->input_key); + g_free(priv->output_key); + + G_OBJECT_CLASS(nm_setting_ip_tunnel_parent_class)->finalize(object); +} + +static void +nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingIPTunnelPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingIPTunnel:parent: + * + * If given, specifies the parent interface name or parent connection UUID + * the new device will be bound to so that tunneled packets will only be + * routed via that interface. + * + * Since: 1.2 + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP_TUNNEL_PARENT, + PROP_PARENT, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_ip_tunnel_get_parent); + + /** + * NMSettingIPTunnel:mode: + * + * The tunneling mode, for example %NM_IP_TUNNEL_MODE_IPIP or + * %NM_IP_TUNNEL_MODE_GRE. + * + * Since: 1.2 + **/ + obj_properties[PROP_MODE] = + g_param_spec_uint(NM_SETTING_IP_TUNNEL_MODE, + "", + "", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPTunnel:local: + * + * The local endpoint of the tunnel; the value can be empty, otherwise it + * must contain an IPv4 or IPv6 address. + * + * Since: 1.2 + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP_TUNNEL_LOCAL, + PROP_LOCAL, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_ip_tunnel_get_local); + + /** + * NMSettingIPTunnel:remote: + * + * The remote endpoint of the tunnel; the value must contain an IPv4 or IPv6 + * address. + * + * Since: 1.2 + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP_TUNNEL_REMOTE, + PROP_REMOTE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_ip_tunnel_get_remote); + + /** + * NMSettingIPTunnel:ttl + * + * The TTL to assign to tunneled packets. 0 is a special value meaning that + * packets inherit the TTL value. + * + * Since: 1.2 + **/ + obj_properties[PROP_TTL] = + g_param_spec_uint(NM_SETTING_IP_TUNNEL_TTL, + "", + "", + 0, + 255, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPTunnel:tos + * + * The type of service (IPv4) or traffic class (IPv6) field to be set on + * tunneled packets. + * + * Since: 1.2 + **/ + obj_properties[PROP_TOS] = + g_param_spec_uint(NM_SETTING_IP_TUNNEL_TOS, + "", + "", + 0, + 255, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPTunnel:path-mtu-discovery + * + * Whether to enable Path MTU Discovery on this tunnel. + * + * Since: 1.2 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_IP_TUNNEL_PATH_MTU_DISCOVERY, + PROP_PATH_MTU_DISCOVERY, + TRUE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_ip_tunnel_get_path_mtu_discovery); + + /** + * NMSettingIPTunnel:input-key: + * + * The key used for tunnel input packets; the property is valid only for + * certain tunnel modes (GRE, IP6GRE). If empty, no key is used. + * + * Since: 1.2 + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP_TUNNEL_INPUT_KEY, + PROP_INPUT_KEY, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_ip_tunnel_get_input_key); + + /** + * NMSettingIPTunnel:output-key: + * + * The key used for tunnel output packets; the property is valid only for + * certain tunnel modes (GRE, IP6GRE). If empty, no key is used. + * + * Since: 1.2 + **/ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP_TUNNEL_OUTPUT_KEY, + PROP_OUTPUT_KEY, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_ip_tunnel_get_output_key); + + /** + * NMSettingIPTunnel:encapsulation-limit: + * + * How many additional levels of encapsulation are permitted to be prepended + * to packets. This property applies only to IPv6 tunnels. + * + * Since: 1.2 + **/ + obj_properties[PROP_ENCAPSULATION_LIMIT] = + g_param_spec_uint(NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT, + "", + "", + 0, + 255, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPTunnel:flow-label: + * + * The flow label to assign to tunnel packets. This property applies only to + * IPv6 tunnels. + * + * Since: 1.2 + **/ + obj_properties[PROP_FLOW_LABEL] = + g_param_spec_uint(NM_SETTING_IP_TUNNEL_FLOW_LABEL, + "", + "", + 0, + (1 << 20) - 1, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPTunnel:mtu: + * + * If non-zero, only transmit packets of the specified size or smaller, + * breaking larger packets up into multiple fragments. + * + * Since: 1.2 + **/ + obj_properties[PROP_MTU] = g_param_spec_uint(NM_SETTING_IP_TUNNEL_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIPTunnel:flags: + * + * Tunnel flags. Currently, the following values are supported: + * %NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT, %NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS, + * %NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL, %NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV, + * %NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY, %NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK. + * They are valid only for IPv6 tunnels. + * + * Since: 1.12 + **/ + obj_properties[PROP_FLAGS] = g_param_spec_uint(NM_SETTING_IP_TUNNEL_FLAGS, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_IP_TUNNEL, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-ip4-config.c b/src/libnm-core-impl/nm-setting-ip4-config.c new file mode 100644 index 00000000..15bae0cd --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ip4-config.c @@ -0,0 +1,1059 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2014 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ip4-config.h" + +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-ip4-config + * @short_description: Describes IPv4 addressing, routing, and name service properties + * + * The #NMSettingIP4Config object is a #NMSetting subclass that describes + * properties related to IPv4 addressing, routing, and Domain Name Service. + * + * #NMSettingIP4Config has few properties or methods of its own; it inherits + * almost everything from #NMSettingIPConfig. + * + * NetworkManager supports 5 values for the #NMSettingIPConfig:method property + * for IPv4. If "auto" is specified then the appropriate automatic method + * (DHCP, PPP, etc) is used for the interface and most other properties can be + * left unset. If "link-local" is specified, then a link-local address in the + * 169.254/16 range will be assigned to the interface. If "manual" is + * specified, static IP addressing is used and at least one IP address must be + * given in the "addresses" property. If "shared" is specified (indicating that + * this connection will provide network access to other computers) then the + * interface is assigned an address in the 10.42.x.1/24 range and a DHCP and + * forwarding DNS server are started, and the interface is NAT-ed to the current + * default network connection. "disabled" means IPv4 will not be used on this + * connection. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_DHCP_CLIENT_ID, + PROP_DHCP_FQDN, + PROP_DHCP_VENDOR_CLASS_IDENTIFIER, ); + +typedef struct { + char *dhcp_client_id; + char *dhcp_fqdn; + char *dhcp_vendor_class_identifier; +} NMSettingIP4ConfigPrivate; + +/** + * NMSettingIP4Config: + * + * IPv4 Settings + */ +struct _NMSettingIP4Config { + NMSettingIPConfig parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingIP4ConfigClass { + NMSettingIPConfigClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingIP4Config, nm_setting_ip4_config, NM_TYPE_SETTING_IP_CONFIG) + +#define NM_SETTING_IP4_CONFIG_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_IP4_CONFIG, NMSettingIP4ConfigPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_ip4_config_get_dhcp_client_id: + * @setting: the #NMSettingIP4Config + * + * Returns the value contained in the #NMSettingIP4Config:dhcp-client-id + * property. + * + * Returns: the configured Client ID to send to the DHCP server when requesting + * addresses via DHCP. + **/ +const char * +nm_setting_ip4_config_get_dhcp_client_id(NMSettingIP4Config *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP4_CONFIG(setting), NULL); + + return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->dhcp_client_id; +} + +/** + * nm_setting_ip4_config_get_dhcp_fqdn: + * @setting: the #NMSettingIP4Config + * + * Returns the value contained in the #NMSettingIP4Config:dhcp-fqdn + * property. + * + * Returns: the configured FQDN to send to the DHCP server + * + * Since: 1.2 + **/ +const char * +nm_setting_ip4_config_get_dhcp_fqdn(NMSettingIP4Config *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP4_CONFIG(setting), NULL); + + return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->dhcp_fqdn; +} + +/** + * nm_setting_ip4_config_get_dhcp_vendor_class_identifier: + * @setting: the #NMSettingIP4Config + * + * Returns the value contained in the #NMSettingIP4Config:dhcp_vendor_class_identifier + * property. + * + * Returns: the vendor class identifier option to send to the DHCP server + * + * Since: 1.28 + **/ +const char * +nm_setting_ip4_config_get_dhcp_vendor_class_identifier(NMSettingIP4Config *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP4_CONFIG(setting), NULL); + + return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->dhcp_vendor_class_identifier; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingIP4ConfigPrivate *priv = NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting); + NMSettingIPConfig * s_ip = NM_SETTING_IP_CONFIG(setting); + NMSettingVerifyResult ret; + const char * method; + + ret = NM_SETTING_CLASS(nm_setting_ip4_config_parent_class)->verify(setting, connection, error); + if (ret != NM_SETTING_VERIFY_SUCCESS) + return ret; + + method = nm_setting_ip_config_get_method(s_ip); + /* Base class already checked that it exists */ + g_assert(method); + + if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + if (nm_setting_ip_config_get_num_addresses(s_ip) == 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("this property cannot be empty for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES); + return FALSE; + } + } else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) + || !strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) + || !strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) { + if (nm_setting_ip_config_get_num_dns(s_ip) > 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("this property is not allowed for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DNS); + return FALSE; + } + + if (nm_setting_ip_config_get_num_dns_searches(s_ip) > 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("this property is not allowed for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DNS_SEARCH); + return FALSE; + } + + /* Shared allows IP addresses; link-local and disabled do not */ + if (strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0) { + if (nm_setting_ip_config_get_num_addresses(s_ip) > 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("this property is not allowed for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES); + return FALSE; + } + } + } else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) { + /* nothing to do */ + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + return FALSE; + } + + if (priv->dhcp_client_id && !priv->dhcp_client_id[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID); + return FALSE; + } + + if (priv->dhcp_fqdn && !*priv->dhcp_fqdn) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP4_CONFIG_DHCP_FQDN); + return FALSE; + } + + if (priv->dhcp_fqdn && !strchr(priv->dhcp_fqdn, '.')) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid FQDN"), + priv->dhcp_fqdn); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP4_CONFIG_DHCP_FQDN); + return FALSE; + } + + if (priv->dhcp_fqdn && nm_setting_ip_config_get_dhcp_hostname(s_ip)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property cannot be set when dhcp-hostname is also set")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP4_CONFIG_DHCP_FQDN); + return FALSE; + } + + if (NM_FLAGS_ANY(nm_setting_ip_config_get_dhcp_hostname_flags(s_ip), + NM_DHCP_HOSTNAME_FLAGS_FQDN_MASK) + && !priv->dhcp_fqdn) { + /* Currently, we send a FQDN option only when ipv4.dhcp-fqdn is set */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("FQDN flags requires a FQDN set")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS); + return FALSE; + } + + if (priv->dhcp_vendor_class_identifier + && !nm_utils_validate_dhcp4_vendor_class_id(priv->dhcp_vendor_class_identifier, error)) + return FALSE; + + /* Failures from here on are NORMALIZABLE_ERROR... */ + + if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) + && nm_setting_ip_config_get_num_addresses(s_ip) > 1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("multiple addresses are not allowed for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_SHARED); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + /* Failures from here on are NORMALIZABLE... */ + + if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) + && !nm_setting_ip_config_get_may_fail(s_ip)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property should be TRUE when method is set to disabled")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_MAY_FAIL); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + + return TRUE; +} + +static GVariant * +ip4_dns_to_dbus(const GValue *prop_value) +{ + return nm_utils_ip4_dns_to_variant(g_value_get_boxed(prop_value)); +} + +static void +ip4_dns_from_dbus(GVariant *dbus_value, GValue *prop_value) +{ + g_value_take_boxed(prop_value, nm_utils_ip4_dns_from_variant(dbus_value)); +} + +static GVariant * +ip4_addresses_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *addrs = NULL; + const char * gateway; + + g_object_get(setting, NM_SETTING_IP_CONFIG_ADDRESSES, &addrs, NULL); + gateway = nm_setting_ip_config_get_gateway(NM_SETTING_IP_CONFIG(setting)); + return nm_utils_ip4_addresses_to_variant(addrs, gateway); +} + +static gboolean +ip4_addresses_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *addrs; + GVariant * s_ip4; + char ** labels, *gateway = NULL; + int i; + + /* FIXME: properly handle errors */ + + if (!_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "address-data")) + return TRUE; + + addrs = nm_utils_ip4_addresses_from_variant(value, &gateway); + + s_ip4 = g_variant_lookup_value(connection_dict, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + if (g_variant_lookup(s_ip4, "address-labels", "^as", &labels)) { + for (i = 0; i < addrs->len && labels[i]; i++) + if (*labels[i]) + nm_ip_address_set_attribute(addrs->pdata[i], + NM_IP_ADDRESS_ATTRIBUTE_LABEL, + g_variant_new_string(labels[i])); + g_strfreev(labels); + } + g_variant_unref(s_ip4); + + g_object_set(setting, + NM_SETTING_IP_CONFIG_ADDRESSES, + addrs, + NM_SETTING_IP_CONFIG_GATEWAY, + gateway, + NULL); + g_ptr_array_unref(addrs); + g_free(gateway); + return TRUE; +} + +static GVariant * +ip4_address_labels_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMSettingIPConfig *s_ip = NM_SETTING_IP_CONFIG(setting); + gboolean have_labels = FALSE; + GPtrArray * labels; + GVariant * ret; + int num_addrs, i; + + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + + num_addrs = nm_setting_ip_config_get_num_addresses(s_ip); + for (i = 0; i < num_addrs; i++) { + NMIPAddress *addr = nm_setting_ip_config_get_address(s_ip, i); + GVariant * label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + + if (label) { + have_labels = TRUE; + break; + } + } + if (!have_labels) + return NULL; + + labels = g_ptr_array_sized_new(num_addrs); + for (i = 0; i < num_addrs; i++) { + NMIPAddress *addr = nm_setting_ip_config_get_address(s_ip, i); + GVariant * label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + + g_ptr_array_add(labels, (char *) (label ? g_variant_get_string(label, NULL) : "")); + } + + ret = g_variant_new_strv((const char *const *) labels->pdata, labels->len); + g_ptr_array_unref(labels); + + return ret; +} + +static GVariant * +ip4_address_data_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *addrs = NULL; + + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + + g_object_get(setting, NM_SETTING_IP_CONFIG_ADDRESSES, &addrs, NULL); + return nm_utils_ip_addresses_to_variant(addrs); +} + +static gboolean +ip4_address_data_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *addrs; + + /* FIXME: properly handle errors */ + + /* Ignore 'address-data' if we're going to process 'addresses' */ + if (_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "address-data")) + return TRUE; + + addrs = nm_utils_ip_addresses_from_variant(value, AF_INET); + g_object_set(setting, NM_SETTING_IP_CONFIG_ADDRESSES, addrs, NULL); + g_ptr_array_unref(addrs); + return TRUE; +} + +static GVariant * +ip4_routes_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *routes = NULL; + + g_object_get(setting, NM_SETTING_IP_CONFIG_ROUTES, &routes, NULL); + return nm_utils_ip4_routes_to_variant(routes); +} + +static gboolean +ip4_routes_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *routes; + + /* FIXME: properly handle errors */ + + if (!_nm_setting_use_legacy_property(setting, connection_dict, "routes", "route-data")) + return TRUE; + + routes = nm_utils_ip4_routes_from_variant(value); + g_object_set(setting, property, routes, NULL); + g_ptr_array_unref(routes); + return TRUE; +} + +static GVariant * +ip4_route_data_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *routes = NULL; + + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + + g_object_get(setting, NM_SETTING_IP_CONFIG_ROUTES, &routes, NULL); + return nm_utils_ip_routes_to_variant(routes); +} + +static gboolean +ip4_route_data_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *routes; + + /* FIXME: properly handle errors */ + + /* Ignore 'route-data' if we're going to process 'routes' */ + if (_nm_setting_use_legacy_property(setting, connection_dict, "routes", "route-data")) + return TRUE; + + routes = nm_utils_ip_routes_from_variant(value, AF_INET); + g_object_set(setting, NM_SETTING_IP_CONFIG_ROUTES, routes, NULL); + g_ptr_array_unref(routes); + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingIP4Config *s_ip4 = NM_SETTING_IP4_CONFIG(object); + + switch (prop_id) { + case PROP_DHCP_CLIENT_ID: + g_value_set_string(value, nm_setting_ip4_config_get_dhcp_client_id(s_ip4)); + break; + case PROP_DHCP_FQDN: + g_value_set_string(value, nm_setting_ip4_config_get_dhcp_fqdn(s_ip4)); + break; + case PROP_DHCP_VENDOR_CLASS_IDENTIFIER: + g_value_set_string(value, nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingIP4ConfigPrivate *priv = NM_SETTING_IP4_CONFIG_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_DHCP_CLIENT_ID: + g_free(priv->dhcp_client_id); + priv->dhcp_client_id = g_value_dup_string(value); + break; + case PROP_DHCP_FQDN: + g_free(priv->dhcp_fqdn); + priv->dhcp_fqdn = g_value_dup_string(value); + break; + case PROP_DHCP_VENDOR_CLASS_IDENTIFIER: + g_free(priv->dhcp_vendor_class_identifier); + priv->dhcp_vendor_class_identifier = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ip4_config_init(NMSettingIP4Config *setting) +{} + +/** + * nm_setting_ip4_config_new: + * + * Creates a new #NMSettingIP4Config object with default values. + * + * Returns: (transfer full): the new empty #NMSettingIP4Config object + **/ +NMSetting * +nm_setting_ip4_config_new(void) +{ + return g_object_new(NM_TYPE_SETTING_IP4_CONFIG, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingIP4ConfigPrivate *priv = NM_SETTING_IP4_CONFIG_GET_PRIVATE(object); + + g_free(priv->dhcp_client_id); + g_free(priv->dhcp_fqdn); + g_free(priv->dhcp_vendor_class_identifier); + + G_OBJECT_CLASS(nm_setting_ip4_config_parent_class)->finalize(object); +} + +static void +nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array_ip_config(); + + g_type_class_add_private(setting_class, sizeof(NMSettingIP4ConfigPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /* ---ifcfg-rh--- + * property: method + * variable: BOOTPROTO + * format: string + * values: none, dhcp (bootp), static, ibft, autoip, shared + * default: none + * description: Method used for IPv4 protocol configuration. + * ---end--- + */ + + /* ---keyfile--- + * property: dns + * format: list of DNS IP addresses + * description: List of DNS servers. + * example: dns=1.2.3.4;8.8.8.8;8.8.4.4; + * ---end--- + * ---ifcfg-rh--- + * property: dns + * variable: DNS1, DNS2, ... + * format: string + * description: List of DNS servers. Even if NetworkManager supports many DNS + * servers, initscripts and resolver only care about the first three, usually. + * example: DNS1=1.2.3.4 DNS2=10.0.0.254 DNS3=8.8.8.8 + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dns-search + * variable: DOMAIN + * format: string (space-separated domains) + * description: List of DNS search domains. + * ---end--- + */ + + /* ---keyfile--- + * property: addresses + * variable: address1, address2, ... + * format: address/plen + * description: List of static IP addresses. + * example: address1=192.168.100.100/24 address2=10.1.1.5/24 + * ---end--- + * ---ifcfg-rh--- + * property: addresses + * variable: IPADDR, PREFIX (NETMASK), IPADDR1, PREFIX1 (NETMASK1), ... + * description: List of static IP addresses. + * example: IPADDR=10.5.5.23 PREFIX=24 IPADDR1=1.1.1.2 PREFIX1=16 + * ---end--- + */ + + /* ---keyfile--- + * property: gateway + * variable: gateway + * format: string + * description: Gateway IP addresses as a string. + * example: gateway=192.168.100.1 + * ---end--- + * ---ifcfg-rh--- + * property: gateway + * variable: GATEWAY + * description: Gateway IP address. + * example: GATEWAY=10.5.5.1 + * ---end--- + */ + + /* ---keyfile--- + * property: routes + * variable: route1, route2, ... + * format: route/plen[,gateway,metric] + * description: List of IP routes. + * example: route1=8.8.8.0/24,10.1.1.1,77 + * route2=7.7.0.0/16 + * ---end--- + * ---ifcfg-rh--- + * property: routes + * variable: ADDRESS1, NETMASK1, GATEWAY1, METRIC1, OPTIONS1, ... + * description: List of static routes. They are not stored in ifcfg-* file, + * but in route-* file instead. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: ignore-auto-routes + * variable: PEERROUTES(+) + * default: yes + * description: PEERROUTES has the opposite meaning as 'ignore-auto-routes' property. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: ignore-auto-dns + * variable: PEERDNS + * default: yes + * description: PEERDNS has the opposite meaning as 'ignore-auto-dns' property. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dhcp-send-hostname + * variable: DHCP_SEND_HOSTNAME(+) + * default: yes + * description: Whether DHCP_HOSTNAME should be sent to the DHCP server. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dhcp-hostname + * variable: DHCP_HOSTNAME + * description: Hostname to send to the DHCP server. When both DHCP_HOSTNAME and + * DHCP_FQDN are specified only the latter is used. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: never-default + * variable: DEFROUTE (GATEWAYDEV in /etc/sysconfig/network) + * default: yes + * description: DEFROUTE=no tells NetworkManager that this connection + * should not be assigned the default route. DEFROUTE has the opposite + * meaning as 'never-default' property. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: may-fail + * variable: IPV4_FAILURE_FATAL(+) + * default: no + * description: IPV4_FAILURE_FATAL has the opposite meaning as 'may-fail' property. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: route-metric + * variable: IPV4_ROUTE_METRIC(+) + * default: -1 + * description: IPV4_ROUTE_METRIC is the default IPv4 metric for routes on this connection. + * If set to -1, a default metric based on the device type is used. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: route-table + * variable: IPV4_ROUTE_TABLE(+) + * default: 0 + * description: IPV4_ROUTE_TABLE enables policy-routing and sets the default routing table. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dns-options + * variable: RES_OPTIONS(+) + * description: List of DNS options to be added to /etc/resolv.conf + * example: RES_OPTIONS=ndots:2 timeout:3 + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dns-priority + * variable: IPV4_DNS_PRIORITY(+) + * description: The priority for DNS servers of this connection. Lower values have higher priority. + * If zero, the default value will be used (50 for VPNs, 100 for other connections). + * A negative value prevents DNS from other connections with greater values to be used. + * default: 0 + * example: IPV4_DNS_PRIORITY=20 + * ---end--- + */ + + /** + * NMSettingIP4Config:dhcp-client-id: + * + * A string sent to the DHCP server to identify the local machine which the + * DHCP server may use to customize the DHCP lease and options. + * When the property is a hex string ('aa:bb:cc') it is interpreted as a + * binary client ID, in which case the first byte is assumed to be the + * 'type' field as per RFC 2132 section 9.14 and the remaining bytes may be + * an hardware address (e.g. '01:xx:xx:xx:xx:xx:xx' where 1 is the Ethernet + * ARP type and the rest is a MAC address). + * If the property is not a hex string it is considered as a + * non-hardware-address client ID and the 'type' field is set to 0. + * + * The special values "mac" and "perm-mac" are supported, which use the + * current or permanent MAC address of the device to generate a client identifier + * with type ethernet (01). Currently, these options only work for ethernet + * type of links. + * + * The special value "ipv6-duid" uses the DUID from "ipv6.dhcp-duid" property as + * an RFC4361-compliant client identifier. As IAID it uses "ipv4.dhcp-iaid" + * and falls back to "ipv6.dhcp-iaid" if unset. + * + * The special value "duid" generates a RFC4361-compliant client identifier based + * on "ipv4.dhcp-iaid" and uses a DUID generated by hashing /etc/machine-id. + * + * The special value "stable" is supported to generate a type 0 client identifier based + * on the stable-id (see connection.stable-id) and a per-host key. If you set the + * stable-id, you may want to include the "${DEVICE}" or "${MAC}" specifier to get a + * per-device key. + * + * If unset, a globally configured default is used. If still unset, the default + * depends on the DHCP plugin. + **/ + /* ---ifcfg-rh--- + * property: dhcp-client-id + * variable: DHCP_CLIENT_ID(+) + * description: A string sent to the DHCP server to identify the local machine. + * A binary value can be specified using hex notation ('aa:bb:cc'). + * example: DHCP_CLIENT_ID=ax-srv-1; DHCP_CLIENT_ID=01:44:44:44:44:44:44 + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, + PROP_DHCP_CLIENT_ID, + NM_SETTING_PARAM_NONE, + nm_setting_ip4_config_get_dhcp_client_id); + + /* ---ifcfg-rh--- + * property: dad-timeout + * variable: ACD_TIMEOUT(+), ARPING_WAIT + * default: missing variable means global default (config override or zero) + * description: Timeout (in milliseconds for ACD_TIMEOUT or in seconds + * for ARPING_WAIT) for address conflict detection before configuring + * IPv4 addresses. 0 turns off the ACD completely, -1 means default value. + * example: ACD_TIMEOUT=2000 or ARPING_WAIT=2 + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dhcp-timeout + * variable: IPV4_DHCP_TIMEOUT(+) + * description: A timeout after which the DHCP transaction fails in case of no response. + * example: IPV4_DHCP_TIMEOUT=10 + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dhcp-hostname-flags + * variable: DHCP_HOSTNAME_FLAGS + * description: flags for the DHCP hostname and FQDN properties + * example: DHCP_HOSTNAME_FLAGS=5 + */ + + /** + * NMSettingIP4Config:dhcp-fqdn: + * + * If the #NMSettingIPConfig:dhcp-send-hostname property is %TRUE, then the + * specified FQDN will be sent to the DHCP server when acquiring a lease. This + * property and #NMSettingIPConfig:dhcp-hostname are mutually exclusive and + * cannot be set at the same time. + * + * Since: 1.2 + */ + /* ---ifcfg-rh--- + * property: dhcp-fqdn + * variable: DHCP_FQDN + * description: FQDN to send to the DHCP server. When both DHCP_HOSTNAME and + * DHCP_FQDN are specified only the latter is used. + * example: DHCP_FQDN=foo.bar.com + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP4_CONFIG_DHCP_FQDN, + PROP_DHCP_FQDN, + NM_SETTING_PARAM_NONE, + nm_setting_ip4_config_get_dhcp_fqdn); + + /** + * NMSettingIP4Config:dhcp-vendor-class-identifier: + * + * The Vendor Class Identifier DHCP option (60). + * Special characters in the data string may be escaped using C-style escapes, + * nevertheless this property cannot contain nul bytes. + * If the per-profile value is unspecified (the default), + * a global connection default gets consulted. + * If still unspecified, the DHCP option is not sent to the server. + * + * Since 1.28 + */ + /* ---ifcfg-rh--- + * property: dhcp-vendor-class-identifier + * variable: DHCP_VENDOR_CLASS_IDENTIFIER(+) + * description: The Vendor Class Identifier DHCP option (60). + * example: DHCP_VENDOR_CLASS_IDENTIFIER=foo + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER, + PROP_DHCP_VENDOR_CLASS_IDENTIFIER, + NM_SETTING_PARAM_NONE, + nm_setting_ip4_config_get_dhcp_vendor_class_identifier); + + /* IP4-specific property overrides */ + + /* ---dbus--- + * property: dns + * format: array of uint32 + * description: Array of IP addresses of DNS servers (as network-byte-order + * integers) + * ---end--- + */ + _nm_properties_override_gobj( + properties_override, + g_object_class_find_property(G_OBJECT_CLASS(setting_class), NM_SETTING_IP_CONFIG_DNS), + NM_SETT_INFO_PROPERT_TYPE_GPROP(NM_G_VARIANT_TYPE("au"), + .gprop_from_dbus_fcn = ip4_dns_from_dbus, ), + .to_dbus_data.gprop_to_dbus_fcn = ip4_dns_to_dbus); + + /* ---dbus--- + * property: addresses + * format: array of array of uint32 + * description: Deprecated in favor of the 'address-data' and 'gateway' + * properties, but this can be used for backward-compatibility with older + * daemons. Note that if you send this property the daemon will ignore + * 'address-data' and 'gateway'. + * + * Array of IPv4 address structures. Each IPv4 address structure is + * composed of 3 32-bit values; the first being the IPv4 address (network + * byte order), the second the prefix (1 - 32), and last the IPv4 gateway + * (network byte order). The gateway may be left as 0 if no gateway exists + * for that subnet. + * ---end--- + */ + /* ---nmcli--- + * property: addresses + * format: a comma separated list of addresses + * description: A list of IPv4 addresses and their prefix length. Multiple addresses + * can be separated by comma. For example "192.168.1.5/24, 10.1.0.5/24". + * ---end--- + */ + _nm_properties_override_gobj( + properties_override, + g_object_class_find_property(G_OBJECT_CLASS(setting_class), NM_SETTING_IP_CONFIG_ADDRESSES), + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aau"), + .to_dbus_fcn = ip4_addresses_get, + .from_dbus_fcn = ip4_addresses_set, )); + _nm_properties_override_dbus( + properties_override, + "address-labels", + NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING_ARRAY, + .to_dbus_fcn = ip4_address_labels_get, )); + + /* ---dbus--- + * property: address-data + * format: array of vardict + * description: Array of IPv4 addresses. Each address dictionary contains at + * least 'address' and 'prefix' entries, containing the IP address as a + * string, and the prefix length as a uint32. Additional attributes may + * also exist on some addresses. + * ---end--- + */ + _nm_properties_override_dbus( + properties_override, + "address-data", + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = ip4_address_data_get, + .from_dbus_fcn = ip4_address_data_set, )); + + /* ---dbus--- + * property: routes + * format: array of array of uint32 + * description: Deprecated in favor of the 'route-data' property, but this + * can be used for backward-compatibility with older daemons. Note that if + * you send this property the daemon will ignore 'route-data'. + * + * Array of IPv4 route structures. Each IPv4 route structure is composed + * of 4 32-bit values; the first being the destination IPv4 network or + * address (network byte order), the second the destination network or + * address prefix (1 - 32), the third being the next-hop (network byte + * order) if any, and the fourth being the route metric. If the metric is + * 0, NM will choose an appropriate default metric for the device. (There + * is no way to explicitly specify an actual metric of 0 with this + * property.) + * ---end--- + */ + /* ---nmcli--- + * property: routes + * format: a comma separated list of routes + * description: A list of IPv4 destination addresses, prefix length, optional IPv4 + * next hop addresses, optional route metric, optional attribute. The valid syntax is: + * "ip[/prefix] [next-hop] [metric] [attribute=val]...[,ip[/prefix]...]". For example + * "192.0.2.0/24 10.1.1.1 77, 198.51.100.0/24". + * ---end--- + */ + _nm_properties_override_gobj( + properties_override, + g_object_class_find_property(G_OBJECT_CLASS(setting_class), NM_SETTING_IP_CONFIG_ROUTES), + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aau"), + .to_dbus_fcn = ip4_routes_get, + .from_dbus_fcn = ip4_routes_set, )); + + /* ---dbus--- + * property: route-data + * format: array of vardict + * description: Array of IPv4 routes. Each route dictionary contains at + * least 'dest' and 'prefix' entries, containing the destination IP + * address as a string, and the prefix length as a uint32. Most routes + * will also have a 'next-hop' entry, containing the next hop IP address as + * a string. If the route has a 'metric' entry (containing a uint32), that + * will be used as the metric for the route (otherwise NM will pick a + * default value appropriate to the device). Additional attributes may + * also exist on some routes. + * ---end--- + */ + _nm_properties_override_dbus( + properties_override, + "route-data", + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = ip4_route_data_get, + .from_dbus_fcn = ip4_route_data_set, )); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_IP4_CONFIG, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-ip6-config.c b/src/libnm-core-impl/nm-setting-ip6-config.c new file mode 100644 index 00000000..f6d59a46 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ip6-config.c @@ -0,0 +1,1103 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2014 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ip6-config.h" + +#include + +#include "nm-setting-private.h" +#include "nm-core-enum-types.h" +#include "libnm-core-intern/nm-core-internal.h" + +/** + * SECTION:nm-setting-ip6-config + * @short_description: Describes IPv6 addressing, routing, and name service properties + * + * The #NMSettingIP6Config object is a #NMSetting subclass that describes + * properties related to IPv6 addressing, routing, and Domain Name Service + * + * #NMSettingIP6Config has few properties or methods of its own; it inherits + * almost everything from #NMSettingIPConfig. + * + * NetworkManager supports 7 values for the #NMSettingIPConfig:method property + * for IPv6. If "auto" is specified then the appropriate automatic method (PPP, + * router advertisement, etc) is used for the device and most other properties + * can be left unset. To force the use of DHCP only, specify "dhcp"; this + * method is only valid for Ethernet- based hardware. If "link-local" is + * specified, then an IPv6 link-local address will be assigned to the interface. + * If "manual" is specified, static IP addressing is used and at least one IP + * address must be given in the "addresses" property. If "ignore" is specified, + * IPv6 configuration is not done. Note: the "shared" method is not yet + * supported. If "disabled" is specified, IPv6 is disabled completely for the + * interface. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_IP6_PRIVACY, + PROP_ADDR_GEN_MODE, + PROP_TOKEN, + PROP_DHCP_DUID, + PROP_RA_TIMEOUT, ); + +typedef struct { + char * token; + char * dhcp_duid; + NMSettingIP6ConfigPrivacy ip6_privacy; + NMSettingIP6ConfigAddrGenMode addr_gen_mode; + gint32 ra_timeout; +} NMSettingIP6ConfigPrivate; + +/** + * NMSettingIP6Config: + * + * IPv6 Settings + */ +struct _NMSettingIP6Config { + NMSettingIPConfig parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingIP6ConfigClass { + NMSettingIPConfigClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingIP6Config, nm_setting_ip6_config, NM_TYPE_SETTING_IP_CONFIG) + +#define NM_SETTING_IP6_CONFIG_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_IP6_CONFIG, NMSettingIP6ConfigPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_ip6_config_get_ip6_privacy: + * @setting: the #NMSettingIP6Config + * + * Returns the value contained in the #NMSettingIP6Config:ip6-privacy + * property. + * + * Returns: IPv6 Privacy Extensions configuration value (#NMSettingIP6ConfigPrivacy). + **/ +NMSettingIP6ConfigPrivacy +nm_setting_ip6_config_get_ip6_privacy(NMSettingIP6Config *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP6_CONFIG(setting), NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN); + + return NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting)->ip6_privacy; +} + +/** + * nm_setting_ip6_config_get_addr_gen_mode: + * @setting: the #NMSettingIP6Config + * + * Returns the value contained in the #NMSettingIP6Config:addr-gen-mode + * property. + * + * Returns: IPv6 Address Generation Mode. + * + * Since: 1.2 + **/ +NMSettingIP6ConfigAddrGenMode +nm_setting_ip6_config_get_addr_gen_mode(NMSettingIP6Config *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP6_CONFIG(setting), + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY); + + return NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting)->addr_gen_mode; +} + +/** + * nm_setting_ip6_config_get_token: + * @setting: the #NMSettingIP6Config + * + * Returns the value contained in the #NMSettingIP6Config:token + * property. + * + * Returns: A string. + * + * Since: 1.4 + **/ +const char * +nm_setting_ip6_config_get_token(NMSettingIP6Config *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP6_CONFIG(setting), NULL); + + return NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting)->token; +} + +/** + * nm_setting_ip6_config_get_dhcp_duid: + * @setting: the #NMSettingIP6Config + * + * Returns the value contained in the #NMSettingIP6Config:dhcp-duid + * property. + * + * Returns: The configured DUID value to be included in the DHCPv6 requests + * sent to the DHCPv6 servers. + * + * Since: 1.12 + **/ +const char * +nm_setting_ip6_config_get_dhcp_duid(NMSettingIP6Config *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP6_CONFIG(setting), NULL); + + return NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting)->dhcp_duid; +} + +/** + * nm_setting_ip6_config_get_ra_timeout: + * @setting: the #NMSettingIP6Config + * + * Returns: The configured %NM_SETTING_IP6_CONFIG_RA_TIMEOUT value with the + * timeout for router advertisements in seconds. + * + * Since: 1.24 + **/ +gint32 +nm_setting_ip6_config_get_ra_timeout(NMSettingIP6Config *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_IP6_CONFIG(setting), 0); + + return NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting)->ra_timeout; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingIP6ConfigPrivate *priv = NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting); + NMSettingIPConfig * s_ip = NM_SETTING_IP_CONFIG(setting); + NMSettingVerifyResult ret; + const char * method; + gboolean token_needs_normalization = FALSE; + + ret = NM_SETTING_CLASS(nm_setting_ip6_config_parent_class)->verify(setting, connection, error); + if (ret != NM_SETTING_VERIFY_SUCCESS) + return ret; + + method = nm_setting_ip_config_get_method(s_ip); + /* Base class already checked that it exists */ + g_assert(method); + + if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) { + if (nm_setting_ip_config_get_num_addresses(s_ip) == 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("this property cannot be empty for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES); + return FALSE; + } + } else if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP6_CONFIG_METHOD_SHARED, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { + /* Shared allows IP addresses and DNS; other methods do not */ + if (!nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) { + if (nm_setting_ip_config_get_num_dns(s_ip) > 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("this property is not allowed for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DNS); + return FALSE; + } + + if (nm_setting_ip_config_get_num_dns_searches(s_ip) > 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("this property is not allowed for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_DNS_SEARCH); + return FALSE; + } + + if (nm_setting_ip_config_get_num_addresses(s_ip) > 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("this property is not allowed for '%s=%s'"), + NM_SETTING_IP_CONFIG_METHOD, + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES); + return FALSE; + } + } + } else if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP6_CONFIG_METHOD_DHCP)) { + /* nothing to do */ + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + return FALSE; + } + + if (!NM_IN_SET(priv->addr_gen_mode, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE); + return FALSE; + } + + if (priv->token) { + if (priv->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64) { + struct in6_addr i6_token; + char s_token[NM_UTILS_INET_ADDRSTRLEN]; + + if (inet_pton(AF_INET6, priv->token, &i6_token) != 1 + || !_nm_utils_inet6_is_token(&i6_token)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value is not a valid token")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_TOKEN); + return FALSE; + } + + if (g_strcmp0(priv->token, _nm_utils_inet6_ntop(&i6_token, s_token))) + token_needs_normalization = TRUE; + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("only makes sense with EUI64 address generation mode")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_TOKEN); + return FALSE; + } + } + + if (priv->dhcp_duid) { + if (!_nm_utils_dhcp_duid_valid(priv->dhcp_duid, NULL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid DUID")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_DHCP_DUID); + return FALSE; + } + } + + /* Failures from here on, are NORMALIZABLE_ERROR... */ + + if (token_needs_normalization) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("token is not in canonical form")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_TOKEN); + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + /* Failures from here on are NORMALIZABLE... */ + + if (NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED) + && !nm_setting_ip_config_get_may_fail(s_ip)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property should be TRUE when method is set to ignore or disabled")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_MAY_FAIL); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + + return TRUE; +} + +static GVariant * +ip6_dns_to_dbus(const GValue *prop_value) +{ + return nm_utils_ip6_dns_to_variant(g_value_get_boxed(prop_value)); +} + +static void +ip6_dns_from_dbus(GVariant *dbus_value, GValue *prop_value) +{ + g_value_take_boxed(prop_value, nm_utils_ip6_dns_from_variant(dbus_value)); +} + +static GVariant * +ip6_addresses_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *addrs = NULL; + const char * gateway; + + g_object_get(setting, NM_SETTING_IP_CONFIG_ADDRESSES, &addrs, NULL); + gateway = nm_setting_ip_config_get_gateway(NM_SETTING_IP_CONFIG(setting)); + return nm_utils_ip6_addresses_to_variant(addrs, gateway); +} + +static gboolean +ip6_addresses_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *addrs; + char * gateway = NULL; + + /* FIXME: properly handle errors */ + + if (!_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "address-data")) + return TRUE; + + addrs = nm_utils_ip6_addresses_from_variant(value, &gateway); + + g_object_set(setting, + NM_SETTING_IP_CONFIG_ADDRESSES, + addrs, + NM_SETTING_IP_CONFIG_GATEWAY, + gateway, + NULL); + g_ptr_array_unref(addrs); + g_free(gateway); + return TRUE; +} + +static GVariant * +ip6_address_data_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *addrs = NULL; + + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + + g_object_get(setting, NM_SETTING_IP_CONFIG_ADDRESSES, &addrs, NULL); + return nm_utils_ip_addresses_to_variant(addrs); +} + +static gboolean +ip6_address_data_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *addrs; + + /* FIXME: properly handle errors */ + + /* Ignore 'address-data' if we're going to process 'addresses' */ + if (_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "address-data")) + return TRUE; + + addrs = nm_utils_ip_addresses_from_variant(value, AF_INET6); + g_object_set(setting, NM_SETTING_IP_CONFIG_ADDRESSES, addrs, NULL); + g_ptr_array_unref(addrs); + return TRUE; +} + +static GVariant * +ip6_routes_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *routes = NULL; + + g_object_get(setting, NM_SETTING_IP_CONFIG_ROUTES, &routes, NULL); + return nm_utils_ip6_routes_to_variant(routes); +} + +static gboolean +ip6_routes_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *routes; + + /* FIXME: properly handle errors */ + + if (!_nm_setting_use_legacy_property(setting, connection_dict, "routes", "route-data")) + return TRUE; + + routes = nm_utils_ip6_routes_from_variant(value); + g_object_set(setting, property, routes, NULL); + g_ptr_array_unref(routes); + return TRUE; +} + +static GVariant * +ip6_route_data_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *routes = NULL; + + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + + g_object_get(setting, NM_SETTING_IP_CONFIG_ROUTES, &routes, NULL); + return nm_utils_ip_routes_to_variant(routes); +} + +static gboolean +ip6_route_data_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *routes; + + /* FIXME: properly handle errors */ + + /* Ignore 'route-data' if we're going to process 'routes' */ + if (_nm_setting_use_legacy_property(setting, connection_dict, "routes", "route-data")) + return TRUE; + + routes = nm_utils_ip_routes_from_variant(value, AF_INET6); + g_object_set(setting, NM_SETTING_IP_CONFIG_ROUTES, routes, NULL); + g_ptr_array_unref(routes); + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingIP6ConfigPrivate *priv = NM_SETTING_IP6_CONFIG_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_IP6_PRIVACY: + g_value_set_enum(value, priv->ip6_privacy); + break; + case PROP_ADDR_GEN_MODE: + g_value_set_int(value, priv->addr_gen_mode); + break; + case PROP_TOKEN: + g_value_set_string(value, priv->token); + break; + case PROP_DHCP_DUID: + g_value_set_string(value, priv->dhcp_duid); + break; + case PROP_RA_TIMEOUT: + g_value_set_int(value, priv->ra_timeout); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingIP6ConfigPrivate *priv = NM_SETTING_IP6_CONFIG_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_IP6_PRIVACY: + priv->ip6_privacy = g_value_get_enum(value); + break; + case PROP_ADDR_GEN_MODE: + priv->addr_gen_mode = g_value_get_int(value); + break; + case PROP_TOKEN: + g_free(priv->token); + priv->token = g_value_dup_string(value); + break; + case PROP_DHCP_DUID: + g_free(priv->dhcp_duid); + priv->dhcp_duid = g_value_dup_string(value); + break; + case PROP_RA_TIMEOUT: + priv->ra_timeout = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ip6_config_init(NMSettingIP6Config *setting) +{ + NMSettingIP6ConfigPrivate *priv = NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting); + + priv->ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + priv->addr_gen_mode = NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY; +} + +/** + * nm_setting_ip6_config_new: + * + * Creates a new #NMSettingIP6Config object with default values. + * + * Returns: (transfer full): the new empty #NMSettingIP6Config object + **/ +NMSetting * +nm_setting_ip6_config_new(void) +{ + return g_object_new(NM_TYPE_SETTING_IP6_CONFIG, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingIP6Config * self = NM_SETTING_IP6_CONFIG(object); + NMSettingIP6ConfigPrivate *priv = NM_SETTING_IP6_CONFIG_GET_PRIVATE(self); + + g_free(priv->token); + g_free(priv->dhcp_duid); + + G_OBJECT_CLASS(nm_setting_ip6_config_parent_class)->finalize(object); +} + +static void +nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array_ip_config(); + + g_type_class_add_private(klass, sizeof(NMSettingIP6ConfigPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /* ---ifcfg-rh--- + * property: method + * variable: IPV6INIT, IPV6FORWARDING, IPV6_AUTOCONF, DHCPV6C, IPV6_DISABLED + * default: IPV6INIT=yes; IPV6FORWARDING=no; IPV6_AUTOCONF=!IPV6FORWARDING, DHCPV6=no + * description: Method used for IPv6 protocol configuration. + * ignore ~ IPV6INIT=no; auto ~ IPV6_AUTOCONF=yes; dhcp ~ IPV6_AUTOCONF=no and DHCPV6C=yes; + * disabled ~ IPV6_DISABLED=yes + * ---end--- + */ + + /* ---keyfile--- + * property: dns + * format: list of DNS IP addresses + * description: List of DNS servers. + * example: dns=2001:4860:4860::8888;2001:4860:4860::8844; + * ---end--- + * ---ifcfg-rh--- + * property: dns + * variable: DNS1, DNS2, ... + * format: string + * description: List of DNS servers. NetworkManager uses the variables both + * for IPv4 and IPv6. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dns-search + * variable: IPV6_DOMAIN(+) + * format: string (space-separated domains) + * description: List of DNS search domains. + * ---end--- + */ + + /* ---keyfile--- + * property: addresses + * variable: address1, address2, ... + * format: address/plen + * description: List of static IP addresses. + * example: address1=abbe::cafe/96 address2=2001::1234 + * ---end--- + * ---ifcfg-rh--- + * property: addresses + * variable: IPV6ADDR, IPV6ADDR_SECONDARIES + * description: List of static IP addresses. + * example: IPV6ADDR=ab12:9876::1 + * IPV6ADDR_SECONDARIES="ab12:9876::2 ab12:9876::3" + * ---end--- + */ + + /* ---keyfile--- + * property: gateway + * variable: gateway + * format: string + * description: Gateway IP addresses as a string. + * example: gateway=abbe::1 + * ---end--- + * ---ifcfg-rh--- + * property: gateway + * variable: IPV6_DEFAULTGW + * description: Gateway IP address. + * example: IPV6_DEFAULTGW=abbe::1 + * ---end--- + */ + + /* ---keyfile--- + * property: routes + * variable: route1, route2, ... + * format: route/plen[,gateway,metric] + * description: List of IP routes. + * example: route1=2001:4860:4860::/64,2620:52:0:2219:222:68ff:fe11:5403 + * ---end--- + * ---ifcfg-rh--- + * property: routes + * variable: (none) + * description: List of static routes. They are not stored in ifcfg-* file, + * but in route6-* file instead in the form of command line for 'ip route add'. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: ignore-auto-routes + * variable: IPV6_PEERROUTES(+) + * default: yes + * description: IPV6_PEERROUTES has the opposite meaning as 'ignore-auto-routes' property. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: ignore-auto-dns + * variable: IPV6_PEERDNS(+) + * default: yes + * description: IPV6_PEERDNS has the opposite meaning as 'ignore-auto-dns' property. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dhcp-hostname + * variable: DHCPV6_HOSTNAME + * description: Hostname to send the DHCP server. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dhcp-timeout + * variable: IPV6_DHCP_TIMEOUT(+) + * description: A timeout after which the DHCP transaction fails in case of no response. + * example: IPV6_DHCP_TIMEOUT=10 + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dhcp-hostname-flags + * variable: DHCPV6_HOSTNAME_FLAGS + * description: flags for the DHCP hostname property + * example: DHCPV6_HOSTNAME_FLAGS=5 + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: never-default + * variable: IPV6_DEFROUTE(+), (and IPV6_DEFAULTGW, IPV6_DEFAULTDEV in /etc/sysconfig/network) + * default: IPV6_DEFROUTE=yes (when no variable specified) + * description: IPV6_DEFROUTE=no tells NetworkManager that this connection + * should not be assigned the default IPv6 route. IPV6_DEFROUTE has the opposite + * meaning as 'never-default' property. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: may-fail + * variable: IPV6_FAILURE_FATAL(+) + * default: no + * description: IPV6_FAILURE_FATAL has the opposite meaning as 'may-fail' property. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: route-metric + * variable: IPV6_ROUTE_METRIC(+) + * default: -1 + * description: IPV6_ROUTE_METRIC is the default IPv6 metric for routes on this connection. + * If set to -1, a default metric based on the device type is used. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: route-table + * variable: IPV6_ROUTE_TABLE(+) + * default: 0 + * description: IPV6_ROUTE_TABLE enables policy-routing and sets the default routing table. + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dns-priority + * variable: IPV6_DNS_PRIORITY(+) + * description: The priority for DNS servers of this connection. Lower values have higher priority. + * If zero, the default value will be used (50 for VPNs, 100 for other connections). + * A negative value prevents DNS from other connections with greater values to be used. + * default: 0 + * example: IPV6_DNS_PRIORITY=20 + * ---end--- + */ + + /* ---ifcfg-rh--- + * property: dns-options + * variable: IPV6_RES_OPTIONS(+) + * description: List of DNS options to be added to /etc/resolv.conf + * example: IPV6_RES_OPTIONS=ndots:2 timeout:3 + * ---end--- + */ + + /** + * NMSettingIP6Config:ip6-privacy: + * + * Configure IPv6 Privacy Extensions for SLAAC, described in RFC4941. If + * enabled, it makes the kernel generate a temporary IPv6 address in + * addition to the public one generated from MAC address via modified + * EUI-64. This enhances privacy, but could cause problems in some + * applications, on the other hand. The permitted values are: -1: unknown, + * 0: disabled, 1: enabled (prefer public address), 2: enabled (prefer temporary + * addresses). + * + * Having a per-connection setting set to "-1" (unknown) means fallback to + * global configuration "ipv6.ip6-privacy". + * + * If also global configuration is unspecified or set to "-1", fallback to read + * "/proc/sys/net/ipv6/conf/default/use_tempaddr". + * + * Note that this setting is distinct from the Stable Privacy addresses + * that can be enabled with the "addr-gen-mode" property's "stable-privacy" + * setting as another way of avoiding host tracking with IPv6 addresses. + **/ + /* ---ifcfg-rh--- + * property: ip6-privacy + * variable: IPV6_PRIVACY, IPV6_PRIVACY_PREFER_PUBLIC_IP(+) + * values: IPV6_PRIVACY: no, yes (rfc3041 or rfc4941); + * IPV6_PRIVACY_PREFER_PUBLIC_IP: yes, no + * default: no + * description: Configure IPv6 Privacy Extensions for SLAAC (RFC4941). + * example: IPV6_PRIVACY=rfc3041 IPV6_PRIVACY_PREFER_PUBLIC_IP=yes + * ---end--- + */ + obj_properties[PROP_IP6_PRIVACY] = + g_param_spec_enum(NM_SETTING_IP6_CONFIG_IP6_PRIVACY, + "", + "", + NM_TYPE_SETTING_IP6_CONFIG_PRIVACY, + NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIP6Config:addr-gen-mode: + * + * Configure method for creating the address for use with RFC4862 IPv6 + * Stateless Address Autoconfiguration. The permitted values are: + * %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64 or + * %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY. + * + * If the property is set to EUI64, the addresses will be generated + * using the interface tokens derived from hardware address. This makes + * the host part of the address to stay constant, making it possible + * to track host's presence when it changes networks. The address changes + * when the interface hardware is replaced. + * + * The value of stable-privacy enables use of cryptographically + * secure hash of a secret host-specific key along with the connection's + * stable-id and the network address as specified by RFC7217. + * This makes it impossible to use the address track host's presence, + * and makes the address stable when the network interface hardware is + * replaced. + * + * On D-Bus, the absence of an addr-gen-mode setting equals enabling + * stable-privacy. For keyfile plugin, the absence of the setting + * on disk means EUI64 so that the property doesn't change on upgrade + * from older versions. + * + * Note that this setting is distinct from the Privacy Extensions as + * configured by "ip6-privacy" property and it does not affect the + * temporary addresses configured with this option. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: addr-gen-mode + * variable: IPV6_ADDR_GEN_MODE + * values: IPV6_ADDR_GEN_MODE: eui64, stable-privacy + * default: eui64 + * description: Configure IPv6 Stable Privacy addressing for SLAAC (RFC7217). + * example: IPV6_ADDR_GEN_MODE=stable-privacy + * ---end--- + */ + obj_properties[PROP_ADDR_GEN_MODE] = + g_param_spec_int(NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE, + "", + "", + G_MININT, + G_MAXINT, + NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIP6Config:token: + * + * Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 + * IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode. + * + * Since: 1.4 + **/ + /* ---ifcfg-rh--- + * property: token + * variable: IPV6_TOKEN + * description: The IPv6 tokenized interface identifier token + * example: IPV6_TOKEN=::53 + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP6_CONFIG_TOKEN, + PROP_TOKEN, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_ip6_config_get_token); + + /** + * NMSettingIP6Config:ra-timeout: + * + * A timeout for waiting Router Advertisements in seconds. If zero (the default), a + * globally configured default is used. If still unspecified, the timeout depends on the + * sysctl settings of the device. + * + * Set to 2147483647 (MAXINT32) for infinity. + * + * Since: 1.24 + **/ + /* ---ifcfg-rh--- + * property: dhcp-timeout + * variable: IPV6_RA_TIMEOUT(+) + * description: A timeout for waiting Router Advertisements in seconds. + * example: IPV6_RA_TIMEOUT=10 + * ---end--- + */ + + obj_properties[PROP_RA_TIMEOUT] = g_param_spec_int( + NM_SETTING_IP6_CONFIG_RA_TIMEOUT, + "", + "", + 0, + G_MAXINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingIP6Config:dhcp-duid: + * + * A string containing the DHCPv6 Unique Identifier (DUID) used by the dhcp + * client to identify itself to DHCPv6 servers (RFC 3315). The DUID is carried + * in the Client Identifier option. + * If the property is a hex string ('aa:bb:cc') it is interpreted as a binary + * DUID and filled as an opaque value in the Client Identifier option. + * + * The special value "lease" will retrieve the DUID previously used from the + * lease file belonging to the connection. If no DUID is found and "dhclient" + * is the configured dhcp client, the DUID is searched in the system-wide + * dhclient lease file. If still no DUID is found, or another dhcp client is + * used, a global and permanent DUID-UUID (RFC 6355) will be generated based + * on the machine-id. + * + * The special values "llt" and "ll" will generate a DUID of type LLT or LL + * (see RFC 3315) based on the current MAC address of the device. In order to + * try providing a stable DUID-LLT, the time field will contain a constant + * timestamp that is used globally (for all profiles) and persisted to disk. + * + * The special values "stable-llt", "stable-ll" and "stable-uuid" will generate + * a DUID of the corresponding type, derived from the connection's stable-id and + * a per-host unique key. You may want to include the "${DEVICE}" or "${MAC}" specifier + * in the stable-id, in case this profile gets activated on multiple devices. + * So, the link-layer address of "stable-ll" and "stable-llt" will be a generated + * address derived from the stable id. The DUID-LLT time value in the "stable-llt" + * option will be picked among a static timespan of three years (the upper bound + * of the interval is the same constant timestamp used in "llt"). + * + * When the property is unset, the global value provided for "ipv6.dhcp-duid" is + * used. If no global value is provided, the default "lease" value is assumed. + * + * Since: 1.12 + **/ + /* ---ifcfg-rh--- + * property: dhcp-duid + * variable: DHCPV6_DUID(+) + * description: A string sent to the DHCPv6 server to identify the local machine. + * Apart from the special values "lease", "stable-llt", "stable-ll", "stable-uuid", + * "llt" and "ll" a binary value in hex format is expected. An hex string where + * each octet is separated by a colon is also accepted. + * example: DHCPV6_DUID=LL; DHCPV6_DUID=0301deadbeef0001; DHCPV6_DUID=03:01:de:ad:be:ef:00:01 + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_IP6_CONFIG_DHCP_DUID, + PROP_DHCP_DUID, + NM_SETTING_PARAM_NONE, + nm_setting_ip6_config_get_dhcp_duid); + + /* IP6-specific property overrides */ + + /* ---dbus--- + * property: dns + * format: array of byte array + * description: Array of IP addresses of DNS servers (in network byte order) + * ---end--- + */ + _nm_properties_override_gobj( + properties_override, + g_object_class_find_property(G_OBJECT_CLASS(setting_class), NM_SETTING_IP_CONFIG_DNS), + NM_SETT_INFO_PROPERT_TYPE_GPROP(NM_G_VARIANT_TYPE("aay"), + .gprop_from_dbus_fcn = ip6_dns_from_dbus, ), + .to_dbus_data.gprop_to_dbus_fcn = ip6_dns_to_dbus); + + /* ---dbus--- + * property: addresses + * format: array of legacy IPv6 address struct (a(ayuay)) + * description: Deprecated in favor of the 'address-data' and 'gateway' + * properties, but this can be used for backward-compatibility with older + * daemons. Note that if you send this property the daemon will ignore + * 'address-data' and 'gateway'. + * + * Array of IPv6 address structures. Each IPv6 address structure is + * composed of an IPv6 address, a prefix length (1 - 128), and an IPv6 + * gateway address. The gateway may be zeroed out if no gateway exists for + * that subnet. + * ---end--- + */ + _nm_properties_override_gobj( + properties_override, + g_object_class_find_property(G_OBJECT_CLASS(setting_class), NM_SETTING_IP_CONFIG_ADDRESSES), + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("a(ayuay)"), + .to_dbus_fcn = ip6_addresses_get, + .from_dbus_fcn = ip6_addresses_set, )); + + /* ---dbus--- + * property: address-data + * format: array of vardict + * description: Array of IPv6 addresses. Each address dictionary contains at + * least 'address' and 'prefix' entries, containing the IP address as a + * string, and the prefix length as a uint32. Additional attributes may + * also exist on some addresses. + * ---end--- + */ + _nm_properties_override_dbus( + properties_override, + "address-data", + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = ip6_address_data_get, + .from_dbus_fcn = ip6_address_data_set, )); + + /* ---dbus--- + * property: routes + * format: array of legacy IPv6 route struct (a(ayuayu)) + * description: Deprecated in favor of the 'route-data' property, but this + * can be used for backward-compatibility with older daemons. Note that if + * you send this property the daemon will ignore 'route-data'. + * + * Array of IPv6 route structures. Each IPv6 route structure is + * composed of an IPv6 address, a prefix length (1 - 128), an IPv6 + * next hop address (which may be zeroed out if there is no next hop), + * and a metric. If the metric is 0, NM will choose an appropriate + * default metric for the device. + * ---end--- + */ + _nm_properties_override_gobj( + properties_override, + g_object_class_find_property(G_OBJECT_CLASS(setting_class), NM_SETTING_IP_CONFIG_ROUTES), + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("a(ayuayu)"), + .to_dbus_fcn = ip6_routes_get, + .from_dbus_fcn = ip6_routes_set, )); + + /* ---dbus--- + * property: route-data + * format: array of vardict + * description: Array of IPv6 routes. Each route dictionary contains at + * least 'dest' and 'prefix' entries, containing the destination IP + * address as a string, and the prefix length as a uint32. Most routes + * will also have a 'next-hop' entry, containing the next hop IP address as + * a string. If the route has a 'metric' entry (containing a uint32), that + * will be used as the metric for the route (otherwise NM will pick a + * default value appropriate to the device). Additional attributes may + * also exist on some routes. + * ---end--- + */ + _nm_properties_override_dbus( + properties_override, + "route-data", + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = ip6_route_data_get, + .from_dbus_fcn = ip6_route_data_set, )); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_IP6_CONFIG, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-macsec.c b/src/libnm-core-impl/nm-setting-macsec.c new file mode 100644 index 00000000..55024bb4 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-macsec.c @@ -0,0 +1,693 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-macsec.h" + +#include + +#include "libnm-glib-aux/nm-secret-utils.h" + +#include "nm-utils.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" +#include "nm-setting-wired.h" +#include "nm-connection-private.h" + +/** + * SECTION:nm-setting-macsec + * @short_description: Describes connection properties for MACSEC interfaces + * + * The #NMSettingMacsec object is a #NMSetting subclass that describes properties + * necessary for connection to MACsec (IEEE 802.1AE) interfaces. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT, + PROP_MODE, + PROP_ENCRYPT, + PROP_MKA_CAK, + PROP_MKA_CAK_FLAGS, + PROP_MKA_CKN, + PROP_PORT, + PROP_VALIDATION, + PROP_SEND_SCI, ); + +typedef struct { + char * parent; + char * mka_cak; + char * mka_ckn; + int port; + NMSettingMacsecMode mode; + NMSettingSecretFlags mka_cak_flags; + NMSettingMacsecValidation validation; + bool encrypt : 1; + bool send_sci : 1; +} NMSettingMacsecPrivate; + +/** + * NMSettingMacsec: + * + * MACSec Settings + */ +struct _NMSettingMacsec { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingMacsecClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingMacsec, nm_setting_macsec, NM_TYPE_SETTING) + +#define NM_SETTING_MACSEC_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_MACSEC, NMSettingMacsecPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_macsec_get_parent: + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingMacsec:parent property of the setting + * + * Since: 1.6 + **/ +const char * +nm_setting_macsec_get_parent(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), NULL); + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->parent; +} + +/** + * nm_setting_macsec_get_mode: + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingMacsec:mode property of the setting + * + * Since: 1.6 + **/ +NMSettingMacsecMode +nm_setting_macsec_get_mode(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), NM_SETTING_MACSEC_MODE_PSK); + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->mode; +} + +/** + * nm_setting_macsec_get_encrypt: + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingMacsec:encrypt property of the setting + * + * Since: 1.6 + **/ +gboolean +nm_setting_macsec_get_encrypt(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), TRUE); + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->encrypt; +} + +/** + * nm_setting_macsec_get_mka_cak + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingMacsec:mka-cak property of the setting + * + * Since: 1.6 + **/ +const char * +nm_setting_macsec_get_mka_cak(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), NULL); + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->mka_cak; +} + +/** + * nm_setting_macsec_get_mka_cak_flags: + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingSecretFlags pertaining to the #NMSettingMacsec:mka-cak + * + * Since: 1.6 + **/ +NMSettingSecretFlags +nm_setting_macsec_get_mka_cak_flags(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->mka_cak_flags; +} + +/** + * nm_setting_macsec_get_mka_ckn: + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingMacsec:mka-ckn property of the setting + * + * Since: 1.6 + **/ +const char * +nm_setting_macsec_get_mka_ckn(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), NULL); + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->mka_ckn; +} + +/** + * nm_setting_macsec_get_port: + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingMacsec:port property of the setting + * + * Since: 1.6 + **/ +int +nm_setting_macsec_get_port(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), 1); + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->port; +} + +/** + * nm_setting_macsec_get_validation: + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingMacsec:validation property of the setting + * + * Since: 1.6 + **/ +NMSettingMacsecValidation +nm_setting_macsec_get_validation(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), NM_SETTING_MACSEC_VALIDATION_DISABLE); + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->validation; +} + +/** + * nm_setting_macsec_get_send_sci: + * @setting: the #NMSettingMacsec + * + * Returns: the #NMSettingMacsec:send-sci property of the setting + * + * Since: 1.12 + **/ +gboolean +nm_setting_macsec_get_send_sci(NMSettingMacsec *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACSEC(setting), TRUE); + return NM_SETTING_MACSEC_GET_PRIVATE(setting)->send_sci; +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSettingMacsecPrivate *priv = NM_SETTING_MACSEC_GET_PRIVATE(setting); + GPtrArray * secrets = NULL; + + if (priv->mode == NM_SETTING_MACSEC_MODE_PSK) { + if (!priv->mka_cak + && !NM_FLAGS_HAS(priv->mka_cak_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + secrets = g_ptr_array_sized_new(1); + g_ptr_array_add(secrets, NM_SETTING_MACSEC_MKA_CAK); + } + } + + return secrets; +} + +/*********************************************************************/ + +static gboolean +verify_macsec_key(const char *key, gboolean cak, GError **error) +{ + int req_len; + + /* CAK is a connection secret and can be NULL for various + * reasons (agent-owned, no permissions to get secrets, etc.) + */ + if (cak && !key) + return TRUE; + + if (!key || !key[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the key is empty")); + return FALSE; + } + + req_len = cak ? NM_SETTING_MACSEC_MKA_CAK_LENGTH : NM_SETTING_MACSEC_MKA_CKN_LENGTH; + if (strlen(key) != (gsize) req_len) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the key must be %d characters"), + req_len); + return FALSE; + } + + if (!NM_STRCHAR_ALL(key, ch, g_ascii_isxdigit(ch))) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the key contains non-hexadecimal characters")); + return FALSE; + } + + return TRUE; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingMacsecPrivate *priv = NM_SETTING_MACSEC_GET_PRIVATE(setting); + NMSettingConnection * s_con = NULL; + NMSettingWired * s_wired = NULL; + NMSetting8021x * s_8021x = NULL; + + if (connection) { + s_con = nm_connection_get_setting_connection(connection); + s_wired = nm_connection_get_setting_wired(connection); + s_8021x = nm_connection_get_setting_802_1x(connection); + } + + if (priv->parent) { + if (nm_utils_is_uuid(priv->parent)) { + /* If we have an NMSettingConnection:master with slave-type="macsec", + * then it must be the same UUID. + */ + if (s_con) { + const char *master = NULL, *slave_type = NULL; + + slave_type = nm_setting_connection_get_slave_type(s_con); + if (!g_strcmp0(slave_type, NM_SETTING_MACSEC_SETTING_NAME)) + master = nm_setting_connection_get_master(s_con); + + if (master && g_strcmp0(priv->parent, master) != 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' value doesn't match '%s=%s'"), + priv->parent, + NM_SETTING_CONNECTION_MASTER, + master); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACSEC_SETTING_NAME, + NM_SETTING_MACSEC_PARENT); + return FALSE; + } + } + } else if (!nm_utils_iface_valid_name(priv->parent)) { + /* parent must be either a UUID or an interface name */ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is neither an UUID nor an interface name"), + priv->parent); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACSEC_SETTING_NAME, + NM_SETTING_MACSEC_PARENT); + return FALSE; + } + } else { + /* If parent is NULL, the parent must be specified via + * NMSettingWired:mac-address. + */ + if (connection && (!s_wired || !nm_setting_wired_get_mac_address(s_wired))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is not specified and neither is '%s:%s'"), + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_MAC_ADDRESS); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACSEC_SETTING_NAME, + NM_SETTING_MACSEC_PARENT); + return FALSE; + } + } + + if (priv->mode == NM_SETTING_MACSEC_MODE_PSK) { + if (!verify_macsec_key(priv->mka_ckn, FALSE, error)) { + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACSEC_SETTING_NAME, + NM_SETTING_MACSEC_MKA_CKN); + return FALSE; + } + if (!verify_macsec_key(priv->mka_cak, TRUE, error)) { + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACSEC_SETTING_NAME, + NM_SETTING_MACSEC_MKA_CAK); + return FALSE; + } + } else if (priv->mode == NM_SETTING_MACSEC_MODE_EAP) { + if (!s_8021x) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("EAP key management requires '%s' setting presence"), + NM_SETTING_802_1X_SETTING_NAME); + g_prefix_error(error, "%s: ", NM_SETTING_MACSEC_SETTING_NAME); + return FALSE; + } + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("must be either psk (0) or eap (1)")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_MACSEC_SETTING_NAME, NM_SETTING_MACSEC_MODE); + return FALSE; + } + + if (priv->port <= 0 || priv->port > 65534) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("invalid port %d"), + priv->port); + g_prefix_error(error, "%s.%s: ", NM_SETTING_MACSEC_SETTING_NAME, NM_SETTING_MACSEC_PORT); + return FALSE; + } + + if (priv->mode != NM_SETTING_MACSEC_MODE_PSK && (priv->mka_cak || priv->mka_ckn)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("only valid for psk mode")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACSEC_SETTING_NAME, + priv->mka_cak ? NM_SETTING_MACSEC_MKA_CAK : NM_SETTING_MACSEC_MKA_CKN); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingMacsec * setting = NM_SETTING_MACSEC(object); + NMSettingMacsecPrivate *priv = NM_SETTING_MACSEC_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_value_set_string(value, priv->parent); + break; + case PROP_MODE: + g_value_set_int(value, priv->mode); + break; + case PROP_ENCRYPT: + g_value_set_boolean(value, priv->encrypt); + break; + case PROP_MKA_CAK: + g_value_set_string(value, priv->mka_cak); + break; + case PROP_MKA_CAK_FLAGS: + g_value_set_flags(value, priv->mka_cak_flags); + break; + case PROP_MKA_CKN: + g_value_set_string(value, priv->mka_ckn); + break; + case PROP_PORT: + g_value_set_int(value, priv->port); + break; + case PROP_VALIDATION: + g_value_set_int(value, priv->validation); + break; + case PROP_SEND_SCI: + g_value_set_boolean(value, priv->send_sci); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingMacsec * setting = NM_SETTING_MACSEC(object); + NMSettingMacsecPrivate *priv = NM_SETTING_MACSEC_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_free(priv->parent); + priv->parent = g_value_dup_string(value); + break; + case PROP_MODE: + priv->mode = g_value_get_int(value); + break; + case PROP_ENCRYPT: + priv->encrypt = g_value_get_boolean(value); + break; + case PROP_MKA_CAK: + nm_free_secret(priv->mka_cak); + priv->mka_cak = g_value_dup_string(value); + break; + case PROP_MKA_CAK_FLAGS: + priv->mka_cak_flags = g_value_get_flags(value); + break; + case PROP_MKA_CKN: + g_free(priv->mka_ckn); + priv->mka_ckn = g_value_dup_string(value); + break; + case PROP_PORT: + priv->port = g_value_get_int(value); + break; + case PROP_VALIDATION: + priv->validation = g_value_get_int(value); + break; + case PROP_SEND_SCI: + priv->send_sci = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_macsec_init(NMSettingMacsec *self) +{ + NMSettingMacsecPrivate *priv = NM_SETTING_MACSEC_GET_PRIVATE(self); + + nm_assert(priv->mode == NM_SETTING_MACSEC_MODE_PSK); + priv->encrypt = TRUE; + priv->port = 1; + priv->send_sci = TRUE; + priv->validation = NM_SETTING_MACSEC_VALIDATION_STRICT; +} + +/** + * nm_setting_macsec_new: + * + * Creates a new #NMSettingMacsec object with default values. + * + * Returns: (transfer full): the new empty #NMSettingMacsec object + * + * Since: 1.6 + **/ +NMSetting * +nm_setting_macsec_new(void) +{ + return g_object_new(NM_TYPE_SETTING_MACSEC, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingMacsec * setting = NM_SETTING_MACSEC(object); + NMSettingMacsecPrivate *priv = NM_SETTING_MACSEC_GET_PRIVATE(setting); + + g_free(priv->parent); + nm_free_secret(priv->mka_cak); + g_free(priv->mka_ckn); + + G_OBJECT_CLASS(nm_setting_macsec_parent_class)->finalize(object); +} + +static void +nm_setting_macsec_class_init(NMSettingMacsecClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingMacsecPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->need_secrets = need_secrets; + + /** + * NMSettingMacsec:parent: + * + * If given, specifies the parent interface name or parent connection UUID + * from which this MACSEC interface should be created. If this property is + * not specified, the connection must contain an #NMSettingWired setting + * with a #NMSettingWired:mac-address property. + * + * Since: 1.6 + **/ + obj_properties[PROP_PARENT] = g_param_spec_string( + NM_SETTING_MACSEC_PARENT, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacsec:mode: + * + * Specifies how the CAK (Connectivity Association Key) for MKA (MACsec Key + * Agreement) is obtained. + * + * Since: 1.6 + **/ + obj_properties[PROP_MODE] = + g_param_spec_int(NM_SETTING_MACSEC_MODE, + "", + "", + G_MININT, + G_MAXINT, + NM_SETTING_MACSEC_MODE_PSK, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacsec:encrypt: + * + * Whether the transmitted traffic must be encrypted. + * + * Since: 1.6 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_MACSEC_ENCRYPT, + PROP_ENCRYPT, + TRUE, + NM_SETTING_PARAM_NONE, + nm_setting_macsec_get_encrypt); + + /** + * NMSettingMacsec:mka-cak: + * + * The pre-shared CAK (Connectivity Association Key) for MACsec + * Key Agreement. + * + * Since: 1.6 + **/ + obj_properties[PROP_MKA_CAK] = + g_param_spec_string(NM_SETTING_MACSEC_MKA_CAK, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacsec:mka-cak-flags: + * + * Flags indicating how to handle the #NMSettingMacsec:mka-cak + * property. + * + * Since: 1.6 + **/ + obj_properties[PROP_MKA_CAK_FLAGS] = + g_param_spec_flags(NM_SETTING_MACSEC_MKA_CAK_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacsec:mka-ckn: + * + * The pre-shared CKN (Connectivity-association Key Name) for + * MACsec Key Agreement. + * + * Since: 1.6 + **/ + obj_properties[PROP_MKA_CKN] = g_param_spec_string(NM_SETTING_MACSEC_MKA_CKN, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacsec:port: + * + * The port component of the SCI (Secure Channel Identifier), between 1 and 65534. + * + * Since: 1.6 + **/ + obj_properties[PROP_PORT] = + g_param_spec_int(NM_SETTING_MACSEC_PORT, + "", + "", + 1, + 65534, + 1, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacsec:validation: + * + * Specifies the validation mode for incoming frames. + * + * Since: 1.6 + **/ + obj_properties[PROP_VALIDATION] = + g_param_spec_int(NM_SETTING_MACSEC_VALIDATION, + "", + "", + G_MININT, + G_MAXINT, + NM_SETTING_MACSEC_VALIDATION_STRICT, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacsec:send-sci: + * + * Specifies whether the SCI (Secure Channel Identifier) is included + * in every packet. + * + * Since: 1.12 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_MACSEC_SEND_SCI, + PROP_SEND_SCI, + TRUE, + NM_SETTING_PARAM_NONE, + nm_setting_macsec_get_send_sci); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_MACSEC, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-macvlan.c b/src/libnm-core-impl/nm-setting-macvlan.c new file mode 100644 index 00000000..9cb2b262 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-macvlan.c @@ -0,0 +1,354 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-macvlan.h" + +#include + +#include "nm-utils.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" +#include "nm-setting-wired.h" +#include "nm-connection-private.h" + +/** + * SECTION:nm-setting-macvlan + * @short_description: Describes connection properties for macvlan interfaces + * + * The #NMSettingMacvlan object is a #NMSetting subclass that describes properties + * necessary for connection to macvlan interfaces. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT, PROP_MODE, PROP_PROMISCUOUS, PROP_TAP, ); + +typedef struct { + char * parent; + NMSettingMacvlanMode mode; + bool promiscuous : 1; + bool tap : 1; +} NMSettingMacvlanPrivate; + +/** + * NMSettingMacvlan: + * + * MAC VLAN Settings + */ +struct _NMSettingMacvlan { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingMacvlanClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingMacvlan, nm_setting_macvlan, NM_TYPE_SETTING) + +#define NM_SETTING_MACVLAN_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_MACVLAN, NMSettingMacvlanPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_macvlan_get_parent: + * @setting: the #NMSettingMacvlan + * + * Returns: the #NMSettingMacvlan:parent property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_macvlan_get_parent(NMSettingMacvlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACVLAN(setting), NULL); + return NM_SETTING_MACVLAN_GET_PRIVATE(setting)->parent; +} + +/** + * nm_setting_macvlan_get_mode: + * @setting: the #NMSettingMacvlan + * + * Returns: the #NMSettingMacvlan:mode property of the setting + * + * Since: 1.2 + **/ +NMSettingMacvlanMode +nm_setting_macvlan_get_mode(NMSettingMacvlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACVLAN(setting), NM_SETTING_MACVLAN_MODE_UNKNOWN); + return NM_SETTING_MACVLAN_GET_PRIVATE(setting)->mode; +} + +/** + * nm_setting_macvlan_get_promiscuous: + * @setting: the #NMSettingMacvlan + * + * Returns: the #NMSettingMacvlan:promiscuous property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_macvlan_get_promiscuous(NMSettingMacvlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACVLAN(setting), FALSE); + return NM_SETTING_MACVLAN_GET_PRIVATE(setting)->promiscuous; +} + +/** + * nm_setting_macvlan_get_tap: + * @setting: the #NMSettingMacvlan + * + * Returns: the #NMSettingMacvlan:tap property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_macvlan_get_tap(NMSettingMacvlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MACVLAN(setting), FALSE); + return NM_SETTING_MACVLAN_GET_PRIVATE(setting)->tap; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingMacvlanPrivate *priv = NM_SETTING_MACVLAN_GET_PRIVATE(setting); + NMSettingWired * s_wired; + + if (connection) + s_wired = nm_connection_get_setting_wired(connection); + else + s_wired = NULL; + + if (priv->parent) { + if (!nm_utils_is_uuid(priv->parent) && !nm_utils_ifname_valid_kernel(priv->parent, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is neither an UUID nor an interface name"), + priv->parent); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACVLAN_SETTING_NAME, + NM_SETTING_MACVLAN_PARENT); + return FALSE; + } + } else { + /* If parent is NULL, the parent must be specified via + * NMSettingWired:mac-address. + */ + if (connection && (!s_wired || !nm_setting_wired_get_mac_address(s_wired))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is not specified and neither is '%s:%s'"), + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_MAC_ADDRESS); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACVLAN_SETTING_NAME, + NM_SETTING_MACVLAN_PARENT); + return FALSE; + } + } + + if (!priv->promiscuous && priv->mode != NM_SETTING_MACVLAN_MODE_PASSTHRU) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("non promiscuous operation is allowed only in passthru mode")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MACVLAN_SETTING_NAME, + NM_SETTING_MACVLAN_PROMISCUOUS); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingMacvlan * setting = NM_SETTING_MACVLAN(object); + NMSettingMacvlanPrivate *priv = NM_SETTING_MACVLAN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_value_set_string(value, priv->parent); + break; + case PROP_MODE: + g_value_set_uint(value, priv->mode); + break; + case PROP_PROMISCUOUS: + g_value_set_boolean(value, priv->promiscuous); + break; + case PROP_TAP: + g_value_set_boolean(value, priv->tap); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingMacvlan * setting = NM_SETTING_MACVLAN(object); + NMSettingMacvlanPrivate *priv = NM_SETTING_MACVLAN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_free(priv->parent); + priv->parent = g_value_dup_string(value); + break; + case PROP_MODE: + priv->mode = g_value_get_uint(value); + break; + case PROP_PROMISCUOUS: + priv->promiscuous = g_value_get_boolean(value); + break; + case PROP_TAP: + priv->tap = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_macvlan_init(NMSettingMacvlan *self) +{ + NMSettingMacvlanPrivate *priv = NM_SETTING_MACVLAN_GET_PRIVATE(self); + + priv->promiscuous = TRUE; +} + +/** + * nm_setting_macvlan_new: + * + * Creates a new #NMSettingMacvlan object with default values. + * + * Returns: (transfer full): the new empty #NMSettingMacvlan object + * + * Since: 1.2 + **/ +NMSetting * +nm_setting_macvlan_new(void) +{ + return g_object_new(NM_TYPE_SETTING_MACVLAN, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingMacvlan * setting = NM_SETTING_MACVLAN(object); + NMSettingMacvlanPrivate *priv = NM_SETTING_MACVLAN_GET_PRIVATE(setting); + + g_free(priv->parent); + + G_OBJECT_CLASS(nm_setting_macvlan_parent_class)->finalize(object); +} + +static void +nm_setting_macvlan_class_init(NMSettingMacvlanClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingMacvlanPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingMacvlan:parent: + * + * If given, specifies the parent interface name or parent connection UUID + * from which this MAC-VLAN interface should be created. If this property is + * not specified, the connection must contain an #NMSettingWired setting + * with a #NMSettingWired:mac-address property. + * + * Since: 1.2 + **/ + obj_properties[PROP_PARENT] = g_param_spec_string( + NM_SETTING_MACVLAN_PARENT, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacvlan:mode: + * + * The macvlan mode, which specifies the communication mechanism between multiple + * macvlans on the same lower device. + * + * Since: 1.2 + **/ + obj_properties[PROP_MODE] = + g_param_spec_uint(NM_SETTING_MACVLAN_MODE, + "", + "", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMacvlan:promiscuous: + * + * Whether the interface should be put in promiscuous mode. + * + * Since: 1.2 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_MACVLAN_PROMISCUOUS, + PROP_PROMISCUOUS, + TRUE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_macvlan_get_promiscuous); + + /** + * NMSettingMacvlan:tap: + * + * Whether the interface should be a MACVTAP. + * + * Since: 1.2 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_MACVLAN_TAP, + PROP_TAP, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_macvlan_get_tap); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_MACVLAN, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-match.c b/src/libnm-core-impl/nm-setting-match.c new file mode 100644 index 00000000..e4f7c1a8 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-match.c @@ -0,0 +1,905 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-match.h" + +#include "nm-setting-private.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-match + * @short_description: Properties to match a connection with a device. + * @include: nm-setting-match.h + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingMatch, + PROP_INTERFACE_NAME, + PROP_KERNEL_COMMAND_LINE, + PROP_DRIVER, + PROP_PATH, ); + +/** + * NMSettingMatch: + * + * Match settings + * + * Since: 1.14 + */ +struct _NMSettingMatch { + NMSetting parent; + GArray * interface_name; + GArray * kernel_command_line; + GArray * driver; + GArray * path; +}; + +struct _NMSettingMatchClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingMatch, nm_setting_match, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_match_get_num_interface_names: + * @setting: the #NMSettingMatch + * + * Returns: the number of configured interface names + * + * Since: 1.14 + **/ +guint +nm_setting_match_get_num_interface_names(NMSettingMatch *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), 0); + + return nm_g_array_len(setting->interface_name); +} + +/** + * nm_setting_match_get_interface_name: + * @setting: the #NMSettingMatch + * @idx: index number of the DNS search domain to return + * + * Returns: the interface name at index @idx + * + * Since: 1.14 + **/ +const char * +nm_setting_match_get_interface_name(NMSettingMatch *setting, int idx) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL); + + g_return_val_if_fail(setting->interface_name && idx >= 0 && idx < setting->interface_name->len, + NULL); + + return nm_strvarray_get_idx(setting->interface_name, idx); +} + +/** + * nm_setting_match_add_interface_name: + * @setting: the #NMSettingMatch + * @interface_name: the interface name to add + * + * Adds a new interface name to the setting. + * + * Since: 1.14 + **/ +void +nm_setting_match_add_interface_name(NMSettingMatch *setting, const char *interface_name) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + g_return_if_fail(interface_name); + + nm_strvarray_add(nm_strvarray_ensure(&setting->interface_name), interface_name); + _notify(setting, PROP_INTERFACE_NAME); +} + +/** + * nm_setting_match_remove_interface_name: + * @setting: the #NMSettingMatch + * @idx: index number of the interface name + * + * Removes the interface name at index @idx. + * + * Since: 1.14 + **/ +void +nm_setting_match_remove_interface_name(NMSettingMatch *setting, int idx) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + + g_return_if_fail(setting->interface_name && idx >= 0 && idx < setting->interface_name->len); + + g_array_remove_index(setting->interface_name, idx); + _notify(setting, PROP_INTERFACE_NAME); +} + +/** + * nm_setting_match_remove_interface_name_by_value: + * @setting: the #NMSettingMatch + * @interface_name: the interface name to remove + * + * Removes @interface_name. + * + * Returns: %TRUE if the interface name was found and removed; %FALSE if it was not. + * + * Since: 1.14 + **/ +gboolean +nm_setting_match_remove_interface_name_by_value(NMSettingMatch *setting, const char *interface_name) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE); + g_return_val_if_fail(interface_name, FALSE); + + if (nm_strvarray_remove_first(setting->interface_name, interface_name)) { + _notify(setting, PROP_INTERFACE_NAME); + return TRUE; + } + + return FALSE; +} + +/** + * nm_setting_match_clear_interface_names: + * @setting: the #NMSettingMatch + * + * Removes all configured interface names. + * + * Since: 1.14 + **/ +void +nm_setting_match_clear_interface_names(NMSettingMatch *setting) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + + if (nm_g_array_len(setting->interface_name) != 0) { + nm_clear_pointer(&setting->interface_name, g_array_unref); + _notify(setting, PROP_INTERFACE_NAME); + } +} + +/** + * nm_setting_match_get_interface_names: + * @setting: the #NMSettingMatch + * @length: (out) (allow-none): the length of the returned interface names array. + * + * Returns all the interface names. + * + * Returns: (transfer none) (array length=length): the NULL terminated list of + * configured interface names. + * + * Before 1.26, the returned array was not %NULL terminated and you MUST provide a length. + * + * Since: 1.14 + **/ +const char *const * +nm_setting_match_get_interface_names(NMSettingMatch *setting, guint *length) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL); + + return nm_strvarray_get_strv(&setting->interface_name, length); +} + +/*****************************************************************************/ + +/** + * nm_setting_match_get_num_kernel_command_lines: + * @setting: the #NMSettingMatch + * + * Returns: the number of configured kernel command line arguments + * + * Since: 1.26 + **/ +guint +nm_setting_match_get_num_kernel_command_lines(NMSettingMatch *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), 0); + + return nm_g_array_len(setting->kernel_command_line); +} + +/** + * nm_setting_match_get_kernel_command_line: + * @setting: the #NMSettingMatch + * @idx: index number of the kernel command line argument to return + * + * Returns: the kernel command line argument at index @idx + * + * Since: 1.26 + **/ +const char * +nm_setting_match_get_kernel_command_line(NMSettingMatch *setting, guint idx) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL); + + g_return_val_if_fail(setting->kernel_command_line && idx < setting->kernel_command_line->len, + NULL); + + return nm_strvarray_get_idx(setting->kernel_command_line, idx); +} + +/** + * nm_setting_match_add_kernel_command_line: + * @setting: the #NMSettingMatch + * @kernel_command_line: the kernel command line argument to add + * + * Adds a new kernel command line argument to the setting. + * + * Since: 1.26 + **/ +void +nm_setting_match_add_kernel_command_line(NMSettingMatch *setting, const char *kernel_command_line) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + g_return_if_fail(kernel_command_line); + + nm_strvarray_add(nm_strvarray_ensure(&setting->kernel_command_line), kernel_command_line); + _notify(setting, PROP_KERNEL_COMMAND_LINE); +} + +/** + * nm_setting_match_remove_kernel_command_line: + * @setting: the #NMSettingMatch + * @idx: index number of the kernel command line argument + * + * Removes the kernel command line argument at index @idx. + * + * Since: 1.26 + **/ +void +nm_setting_match_remove_kernel_command_line(NMSettingMatch *setting, guint idx) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + + g_return_if_fail(setting->kernel_command_line && idx < setting->kernel_command_line->len); + + g_array_remove_index(setting->kernel_command_line, idx); + _notify(setting, PROP_KERNEL_COMMAND_LINE); +} + +/** + * nm_setting_match_remove_kernel_command_line_by_value: + * @setting: the #NMSettingMatch + * @kernel_command_line: the kernel command line argument name to remove + * + * Removes @kernel_command_line. + * + * Returns: %TRUE if the kernel command line argument was found and removed; %FALSE if it was not. + * + * Since: 1.26 + **/ +gboolean +nm_setting_match_remove_kernel_command_line_by_value(NMSettingMatch *setting, + const char * kernel_command_line) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE); + g_return_val_if_fail(kernel_command_line, FALSE); + + if (nm_strvarray_remove_first(setting->kernel_command_line, kernel_command_line)) { + _notify(setting, PROP_KERNEL_COMMAND_LINE); + return TRUE; + } + + return FALSE; +} + +/** + * nm_setting_match_clear_kernel_command_lines: + * @setting: the #NMSettingMatch + * + * Removes all configured kernel command line arguments. + * + * Since: 1.26 + **/ +void +nm_setting_match_clear_kernel_command_lines(NMSettingMatch *setting) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + + if (nm_g_array_len(setting->kernel_command_line) != 0) { + nm_clear_pointer(&setting->kernel_command_line, g_array_unref); + _notify(setting, PROP_KERNEL_COMMAND_LINE); + } +} + +/** + * nm_setting_match_get_kernel_command_lines: + * @setting: the #NMSettingMatch + * @length: (out) (allow-none): the length of the returned interface names array. + * + * Returns all the interface names. + * + * Returns: (transfer none) (array length=length): the configured interface names. + * + * Since: 1.26 + **/ +const char *const * +nm_setting_match_get_kernel_command_lines(NMSettingMatch *setting, guint *length) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL); + + return nm_strvarray_get_strv(&setting->kernel_command_line, length); +} + +/*****************************************************************************/ + +/** + * nm_setting_match_get_num_drivers: + * @setting: the #NMSettingMatch + * + * Returns: the number of configured drivers + * + * Since: 1.26 + **/ +guint +nm_setting_match_get_num_drivers(NMSettingMatch *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), 0); + + return nm_g_array_len(setting->driver); +} + +/** + * nm_setting_match_get_driver: + * @setting: the #NMSettingMatch + * @idx: index number of the DNS search domain to return + * + * Returns: the driver at index @idx + * + * Since: 1.26 + **/ +const char * +nm_setting_match_get_driver(NMSettingMatch *setting, guint idx) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL); + + g_return_val_if_fail(setting->driver && idx < setting->driver->len, NULL); + + return nm_strvarray_get_idx(setting->driver, idx); +} + +/** + * nm_setting_match_add_driver: + * @setting: the #NMSettingMatch + * @driver: the driver to add + * + * Adds a new driver to the setting. + * + * Since: 1.26 + **/ +void +nm_setting_match_add_driver(NMSettingMatch *setting, const char *driver) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + g_return_if_fail(driver); + + nm_strvarray_add(nm_strvarray_ensure(&setting->driver), driver); + _notify(setting, PROP_DRIVER); +} + +/** + * nm_setting_match_remove_driver: + * @setting: the #NMSettingMatch + * @idx: index number of the driver + * + * Removes the driver at index @idx. + * + * Since: 1.26 + **/ +void +nm_setting_match_remove_driver(NMSettingMatch *setting, guint idx) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + + g_return_if_fail(setting->driver && idx < setting->driver->len); + + g_array_remove_index(setting->driver, idx); + _notify(setting, PROP_DRIVER); +} + +/** + * nm_setting_match_remove_driver_by_value: + * @setting: the #NMSettingMatch + * @driver: the driver to remove + * + * Removes @driver. + * + * Returns: %TRUE if the driver was found and removed; %FALSE if it was not. + * + * Since: 1.26 + **/ +gboolean +nm_setting_match_remove_driver_by_value(NMSettingMatch *setting, const char *driver) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE); + g_return_val_if_fail(driver, FALSE); + + if (nm_strvarray_remove_first(setting->driver, driver)) { + _notify(setting, PROP_DRIVER); + return TRUE; + } + + return FALSE; +} + +/** + * nm_setting_match_clear_drivers: + * @setting: the #NMSettingMatch + * + * Removes all configured drivers. + * + * Since: 1.26 + **/ +void +nm_setting_match_clear_drivers(NMSettingMatch *setting) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + + if (nm_g_array_len(setting->driver) != 0) { + nm_clear_pointer(&setting->driver, g_array_unref); + _notify(setting, PROP_DRIVER); + } +} + +/** + * nm_setting_match_get_drivers: + * @setting: the #NMSettingMatch + * @length: (out) (allow-none): the length of the returned interface names array. + * + * Returns all the drivers. + * + * Returns: (transfer none) (array length=length): the configured drivers. + * + * Since: 1.26 + **/ +const char *const * +nm_setting_match_get_drivers(NMSettingMatch *setting, guint *length) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL); + + return nm_strvarray_get_strv(&setting->driver, length); +} + +/*****************************************************************************/ + +/** + * nm_setting_match_get_num_paths: + * @setting: the #NMSettingMatch + * + * Returns: the number of configured paths + * + * Since: 1.26 + **/ +guint +nm_setting_match_get_num_paths(NMSettingMatch *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), 0); + + return nm_g_array_len(setting->path); +} + +/** + * nm_setting_match_get_path: + * @setting: the #NMSettingMatch + * @idx: index number of the path to return + * + * Returns: the path at index @idx + * + * Since: 1.26 + **/ +const char * +nm_setting_match_get_path(NMSettingMatch *setting, guint idx) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL); + + g_return_val_if_fail(setting->path && idx < setting->path->len, NULL); + + return nm_strvarray_get_idx(setting->path, idx); +} + +/** + * nm_setting_match_add_path: + * @setting: the #NMSettingMatch + * @path: the path to add + * + * Adds a new path to the setting. + * + * Since: 1.26 + **/ +void +nm_setting_match_add_path(NMSettingMatch *setting, const char *path) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + g_return_if_fail(path); + + nm_strvarray_add(nm_strvarray_ensure(&setting->path), path); + _notify(setting, PROP_PATH); +} + +/** + * nm_setting_match_remove_path: + * @setting: the #NMSettingMatch + * @idx: index number of the path + * + * Removes the path at index @idx. + * + * Since: 1.26 + **/ +void +nm_setting_match_remove_path(NMSettingMatch *setting, guint idx) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + + g_return_if_fail(setting->path && idx < setting->path->len); + + g_array_remove_index(setting->path, idx); + _notify(setting, PROP_PATH); +} + +/** + * nm_setting_match_remove_path_by_value: + * @setting: the #NMSettingMatch + * @path: the path to remove + * + * Removes @path. + * + * Returns: %TRUE if the path was found and removed; %FALSE if it was not. + * + * Since: 1.26 + **/ +gboolean +nm_setting_match_remove_path_by_value(NMSettingMatch *setting, const char *path) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE); + g_return_val_if_fail(path, FALSE); + + if (nm_strvarray_remove_first(setting->path, path)) { + _notify(setting, PROP_PATH); + return TRUE; + } + + return FALSE; +} + +/** + * nm_setting_match_clear_paths: + * @setting: the #NMSettingMatch + * + * Removes all configured paths. + * + * Since: 1.26 + **/ +void +nm_setting_match_clear_paths(NMSettingMatch *setting) +{ + g_return_if_fail(NM_IS_SETTING_MATCH(setting)); + + if (nm_g_array_len(setting->path) != 0) { + nm_clear_pointer(&setting->path, g_array_unref); + _notify(setting, PROP_PATH); + } +} + +/** + * nm_setting_match_get_paths: + * @setting: the #NMSettingMatch + * @length: (out) (allow-none): the length of the returned paths array. + * + * Returns all the paths. + * + * Returns: (transfer none) (array length=length): the configured paths. + * + * Since: 1.26 + **/ +const char *const * +nm_setting_match_get_paths(NMSettingMatch *setting, guint *length) +{ + g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL); + + return nm_strvarray_get_strv(&setting->path, length); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingMatch *self = NM_SETTING_MATCH(object); + + switch (prop_id) { + case PROP_INTERFACE_NAME: + g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(self->interface_name, NULL)); + break; + case PROP_KERNEL_COMMAND_LINE: + g_value_take_boxed(value, + nm_strvarray_get_strv_non_empty_dup(self->kernel_command_line, NULL)); + break; + case PROP_DRIVER: + g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(self->driver, NULL)); + break; + case PROP_PATH: + g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(self->path, NULL)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingMatch *self = NM_SETTING_MATCH(object); + + switch (prop_id) { + case PROP_INTERFACE_NAME: + nm_strvarray_set_strv(&self->interface_name, g_value_get_boxed(value)); + break; + case PROP_KERNEL_COMMAND_LINE: + nm_strvarray_set_strv(&self->kernel_command_line, g_value_get_boxed(value)); + break; + case PROP_DRIVER: + nm_strvarray_set_strv(&self->driver, g_value_get_boxed(value)); + break; + case PROP_PATH: + nm_strvarray_set_strv(&self->path, g_value_get_boxed(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_match_init(NMSettingMatch *setting) +{} + +/** + * nm_setting_match_new: + * + * Creates a new #NMSettingMatch object with default values. + * + * Returns: (transfer full): the new empty #NMSettingMatch object + * + * Note that this function was present in header files since version 1.14. + * But due to a bug the symbol is only exposed and usable since version 1.32. + * As workaround, use g_object_new(NM_TYPE_SETTING_MATCH) which works with all + * versions since 1.14. + * + * Since: 1.32 + **/ +NMSetting * +nm_setting_match_new(void) +{ + /* Note that function nm_setting_match_new() was libnm headers from 1.14+, + * but due to a bug was the symbol only exposed since version 1.32+. + * + * The workaround is to always use g_object_new(), which works for all + * versions since 1.14. + * + * As such, this function must never do anything except calling + * g_object_new() to not break the workaround. */ + return g_object_new(NM_TYPE_SETTING_MATCH, NULL); +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingMatch *self = NM_SETTING_MATCH(setting); + guint i; + + if (self->interface_name) { + for (i = 0; i < self->interface_name->len; i++) { + if (nm_str_is_empty(nm_strvarray_get_idx(self->interface_name, i))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MATCH_SETTING_NAME, + NM_SETTING_MATCH_INTERFACE_NAME); + return FALSE; + } + } + } + + if (self->kernel_command_line) { + for (i = 0; i < self->kernel_command_line->len; i++) { + if (nm_str_is_empty(nm_strvarray_get_idx(self->kernel_command_line, i))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MATCH_SETTING_NAME, + NM_SETTING_MATCH_KERNEL_COMMAND_LINE); + return FALSE; + } + } + } + + if (self->driver) { + for (i = 0; i < self->driver->len; i++) { + if (nm_str_is_empty(nm_strvarray_get_idx(self->driver, i))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MATCH_SETTING_NAME, + NM_SETTING_MATCH_DRIVER); + return FALSE; + } + } + } + + if (self->path) { + for (i = 0; i < self->path->len; i++) { + if (nm_str_is_empty(nm_strvarray_get_idx(self->path, i))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_MATCH_SETTING_NAME, + NM_SETTING_MATCH_PATH); + return FALSE; + } + } + } + + return TRUE; +} + +static void +finalize(GObject *object) +{ + NMSettingMatch *self = NM_SETTING_MATCH(object); + + nm_clear_pointer(&self->interface_name, g_array_unref); + nm_clear_pointer(&self->kernel_command_line, g_array_unref); + nm_clear_pointer(&self->driver, g_array_unref); + nm_clear_pointer(&self->path, g_array_unref); + + G_OBJECT_CLASS(nm_setting_match_parent_class)->finalize(object); +} + +static void +nm_setting_match_class_init(NMSettingMatchClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingMatch:interface-name + * + * A list of interface names to match. Each element is a shell wildcard + * pattern. + * + * An element can be prefixed with a pipe symbol (|) or an ampersand (&). + * The former means that the element is optional and the latter means that + * it is mandatory. If there are any optional elements, than the match + * evaluates to true if at least one of the optional element matches + * (logical OR). If there are any mandatory elements, then they all + * must match (logical AND). By default, an element is optional. This means + * that an element "foo" behaves the same as "|foo". An element can also be inverted + * with exclamation mark (!) between the pipe symbol (or the ampersand) and before + * the pattern. Note that "!foo" is a shortcut for the mandatory match "&!foo". Finally, + * a backslash can be used at the beginning of the element (after the optional special characters) + * to escape the start of the pattern. For example, "&\\!a" is an mandatory match for literally "!a". + * + * Since: 1.14 + **/ + obj_properties[PROP_INTERFACE_NAME] = g_param_spec_boxed( + NM_SETTING_MATCH_INTERFACE_NAME, + "", + "", + G_TYPE_STRV, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMatch:kernel-command-line + * + * A list of kernel command line arguments to match. This may be used to check + * whether a specific kernel command line option is set (or unset, if prefixed with + * the exclamation mark). The argument must either be a single word, or + * an assignment (i.e. two words, joined by "="). In the former case the kernel + * command line is searched for the word appearing as is, or as left hand side + * of an assignment. In the latter case, the exact assignment is looked for + * with right and left hand side matching. Wildcard patterns are not supported. + * + * See NMSettingMatch:interface-name for how special characters '|', '&', + * '!' and '\\' are used for optional and mandatory matches and inverting the + * match. + * + * Since: 1.26 + **/ + obj_properties[PROP_KERNEL_COMMAND_LINE] = g_param_spec_boxed( + NM_SETTING_MATCH_KERNEL_COMMAND_LINE, + "", + "", + G_TYPE_STRV, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMatch:driver + * + * A list of driver names to match. Each element is a shell wildcard pattern. + * + * See NMSettingMatch:interface-name for how special characters '|', '&', + * '!' and '\\' are used for optional and mandatory matches and inverting the + * pattern. + * + * Since: 1.26 + **/ + obj_properties[PROP_DRIVER] = g_param_spec_boxed( + NM_SETTING_MATCH_DRIVER, + "", + "", + G_TYPE_STRV, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingMatch:path + * + * A list of paths to match against the ID_PATH udev property of + * devices. ID_PATH represents the topological persistent path of a + * device. It typically contains a subsystem string (pci, usb, platform, + * etc.) and a subsystem-specific identifier. + * + * For PCI devices the path has the form + * "pci-$domain:$bus:$device.$function", where each variable is an + * hexadecimal value; for example "pci-0000:0a:00.0". + * + * The path of a device can be obtained with "udevadm info + * /sys/class/net/$dev | grep ID_PATH=" or by looking at the "path" + * property exported by NetworkManager ("nmcli -f general.path device + * show $dev"). + * + * Each element of the list is a shell wildcard pattern. + * + * See NMSettingMatch:interface-name for how special characters '|', '&', + * '!' and '\\' are used for optional and mandatory matches and inverting the + * pattern. + * + * Since: 1.26 + **/ + /* ---ifcfg-rh--- + * property: path + * variable: MATCH_PATH + * description: space-separated list of paths to match against the udev + * property ID_PATHS of devices + * example: MATCH_PATH="pci-0000:01:00.0 pci-0000:0c:00.0" + * ---end--- + */ + obj_properties[PROP_PATH] = g_param_spec_boxed(NM_SETTING_MATCH_PATH, + "", + "", + G_TYPE_STRV, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_MATCH); +} diff --git a/src/libnm-core-impl/nm-setting-olpc-mesh.c b/src/libnm-core-impl/nm-setting-olpc-mesh.c new file mode 100644 index 00000000..103f6195 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-olpc-mesh.c @@ -0,0 +1,292 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2009 One Laptop per Child + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-olpc-mesh.h" + +#include + +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-olpc-mesh + * @short_description: Describes connection properties for OLPC-Mesh devices + * + * The #NMSettingOlpcMesh object is a #NMSetting subclass that describes properties + * necessary for connection to OLPC-Mesh devices. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_SSID, PROP_CHANNEL, PROP_DHCP_ANYCAST_ADDRESS, ); + +typedef struct { + GBytes *ssid; + char * dhcp_anycast_addr; + guint32 channel; +} NMSettingOlpcMeshPrivate; + +/** + * NMSettingOlpcMesh: + * + * OLPC Wireless Mesh Settings + */ +struct _NMSettingOlpcMesh { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingOlpcMeshClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingOlpcMesh, nm_setting_olpc_mesh, NM_TYPE_SETTING) + +#define NM_SETTING_OLPC_MESH_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_OLPC_MESH, NMSettingOlpcMeshPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_olpc_mesh_get_ssid: + * @setting: the #NMSettingOlpcMesh + * + * Returns: (transfer none): + */ +GBytes * +nm_setting_olpc_mesh_get_ssid(NMSettingOlpcMesh *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_OLPC_MESH(setting), NULL); + + return NM_SETTING_OLPC_MESH_GET_PRIVATE(setting)->ssid; +} + +guint32 +nm_setting_olpc_mesh_get_channel(NMSettingOlpcMesh *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_OLPC_MESH(setting), 0); + + return NM_SETTING_OLPC_MESH_GET_PRIVATE(setting)->channel; +} + +const char * +nm_setting_olpc_mesh_get_dhcp_anycast_address(NMSettingOlpcMesh *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_OLPC_MESH(setting), NULL); + + return NM_SETTING_OLPC_MESH_GET_PRIVATE(setting)->dhcp_anycast_addr; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingOlpcMeshPrivate *priv = NM_SETTING_OLPC_MESH_GET_PRIVATE(setting); + gsize length; + + if (!priv->ssid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OLPC_MESH_SETTING_NAME, + NM_SETTING_OLPC_MESH_SSID); + return FALSE; + } + + length = g_bytes_get_size(priv->ssid); + if (length == 0 || length > 32) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("SSID length is out of range <1-32> bytes")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OLPC_MESH_SETTING_NAME, + NM_SETTING_OLPC_MESH_SSID); + return FALSE; + } + + if (priv->channel == 0 || priv->channel > 13) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%d' is not a valid channel"), + priv->channel); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OLPC_MESH_SETTING_NAME, + NM_SETTING_OLPC_MESH_CHANNEL); + return FALSE; + } + + if (priv->dhcp_anycast_addr && !nm_utils_hwaddr_valid(priv->dhcp_anycast_addr, ETH_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OLPC_MESH_SETTING_NAME, + NM_SETTING_OLPC_MESH_DHCP_ANYCAST_ADDRESS); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingOlpcMesh *setting = NM_SETTING_OLPC_MESH(object); + + switch (prop_id) { + case PROP_SSID: + g_value_set_boxed(value, nm_setting_olpc_mesh_get_ssid(setting)); + break; + case PROP_CHANNEL: + g_value_set_uint(value, nm_setting_olpc_mesh_get_channel(setting)); + break; + case PROP_DHCP_ANYCAST_ADDRESS: + g_value_set_string(value, nm_setting_olpc_mesh_get_dhcp_anycast_address(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingOlpcMeshPrivate *priv = NM_SETTING_OLPC_MESH_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_SSID: + if (priv->ssid) + g_bytes_unref(priv->ssid); + priv->ssid = g_value_dup_boxed(value); + break; + case PROP_CHANNEL: + priv->channel = g_value_get_uint(value); + break; + case PROP_DHCP_ANYCAST_ADDRESS: + g_free(priv->dhcp_anycast_addr); + priv->dhcp_anycast_addr = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_olpc_mesh_init(NMSettingOlpcMesh *setting) +{} + +/** + * nm_setting_olpc_mesh_new: + * + * Creates a new #NMSettingOlpcMesh object with default values. + * + * Returns: the new empty #NMSettingOlpcMesh object + **/ +NMSetting * +nm_setting_olpc_mesh_new(void) +{ + return g_object_new(NM_TYPE_SETTING_OLPC_MESH, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingOlpcMeshPrivate *priv = NM_SETTING_OLPC_MESH_GET_PRIVATE(object); + + if (priv->ssid) + g_bytes_unref(priv->ssid); + g_free(priv->dhcp_anycast_addr); + + G_OBJECT_CLASS(nm_setting_olpc_mesh_parent_class)->finalize(object); +} + +static void +nm_setting_olpc_mesh_class_init(NMSettingOlpcMeshClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingOlpcMeshPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingOlpcMesh:ssid: + * + * SSID of the mesh network to join. + **/ + obj_properties[PROP_SSID] = g_param_spec_boxed(NM_SETTING_OLPC_MESH_SSID, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingOlpcMesh:channel: + * + * Channel on which the mesh network to join is located. + **/ + obj_properties[PROP_CHANNEL] = + g_param_spec_uint(NM_SETTING_OLPC_MESH_CHANNEL, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingOlpcMesh:dhcp-anycast-address: + * + * Anycast DHCP MAC address used when requesting an IP address via DHCP. + * The specific anycast address used determines which DHCP server class + * answers the request. + * + * This is currently only implemented by dhclient DHCP plugin. + **/ + obj_properties[PROP_DHCP_ANYCAST_ADDRESS] = + g_param_spec_string(NM_SETTING_OLPC_MESH_DHCP_ANYCAST_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_DHCP_ANYCAST_ADDRESS], + &nm_sett_info_propert_type_mac_address); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_OLPC_MESH, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-ovs-bridge.c b/src/libnm-core-impl/nm-setting-ovs-bridge.c new file mode 100644 index 00000000..22738d20 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ovs-bridge.c @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ovs-bridge.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-ovs-bridge + * @short_description: Describes connection properties for Open vSwitch bridges. + * + * The #NMSettingOvsBridge object is a #NMSetting subclass that describes properties + * necessary for Open vSwitch bridges. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_FAIL_MODE, + PROP_MCAST_SNOOPING_ENABLE, + PROP_RSTP_ENABLE, + PROP_STP_ENABLE, + PROP_DATAPATH_TYPE, ); + +/** + * NMSettingOvsBridge: + * + * OvsBridge Link Settings + */ +struct _NMSettingOvsBridge { + NMSetting parent; + + char *fail_mode; + char *datapath_type; + bool mcast_snooping_enable : 1; + bool rstp_enable : 1; + bool stp_enable : 1; +}; + +struct _NMSettingOvsBridgeClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingOvsBridge, nm_setting_ovs_bridge, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_ovs_bridge_get_fail_mode: + * @self: the #NMSettingOvsBridge + * + * Returns: the #NMSettingOvsBridge:fail_mode property of the setting + * + * Since: 1.10 + **/ +const char * +nm_setting_ovs_bridge_get_fail_mode(NMSettingOvsBridge *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_BRIDGE(self), NULL); + + return self->fail_mode; +} + +/** + * nm_setting_ovs_bridge_get_mcast_snooping_enable: + * @self: the #NMSettingOvsBridge + * + * Returns: the #NMSettingOvsBridge:mcast_snooping_enable property of the setting + * + * Since: 1.10 + **/ +gboolean +nm_setting_ovs_bridge_get_mcast_snooping_enable(NMSettingOvsBridge *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_BRIDGE(self), FALSE); + + return self->mcast_snooping_enable; +} + +/** + * nm_setting_ovs_bridge_get_rstp_enable: + * @self: the #NMSettingOvsBridge + * + * Returns: the #NMSettingOvsBridge:rstp_enable property of the setting + * + * Since: 1.10 + **/ +gboolean +nm_setting_ovs_bridge_get_rstp_enable(NMSettingOvsBridge *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_BRIDGE(self), FALSE); + + return self->rstp_enable; +} + +/** + * nm_setting_ovs_bridge_get_stp_enable: + * @self: the #NMSettingOvsBridge + * + * Returns: the #NMSettingOvsBridge:stp_enable property of the setting + * + * Since: 1.10 + **/ +gboolean +nm_setting_ovs_bridge_get_stp_enable(NMSettingOvsBridge *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_BRIDGE(self), FALSE); + + return self->stp_enable; +} + +/** + * nm_setting_ovs_bridge_get_datapath_type: + * @self: the #NMSettingOvsBridge + * + * Returns: the #NMSettingOvsBridge:datapath_type property of the setting + * + * Since: 1.20 + **/ +const char * +nm_setting_ovs_bridge_get_datapath_type(NMSettingOvsBridge *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_BRIDGE(self), NULL); + + return self->datapath_type; +} + +/*****************************************************************************/ + +static int +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingOvsBridge *self = NM_SETTING_OVS_BRIDGE(setting); + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + if (connection) { + NMSettingConnection *s_con; + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("missing setting")); + g_prefix_error(error, "%s: ", NM_SETTING_CONNECTION_SETTING_NAME); + return FALSE; + } + + if (nm_setting_connection_get_master(s_con)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with a '%s' setting must not have a master."), + NM_SETTING_OVS_BRIDGE_SETTING_NAME); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER); + return FALSE; + } + } + + if (!NM_IN_STRSET(self->fail_mode, "secure", "standalone", NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not allowed in fail_mode"), + self->fail_mode); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + NM_SETTING_OVS_BRIDGE_FAIL_MODE); + return FALSE; + } + + if (!NM_IN_STRSET(self->datapath_type, "system", "netdev", NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not valid"), + self->datapath_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + NM_SETTING_OVS_BRIDGE_DATAPATH_TYPE); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingOvsBridge *self = NM_SETTING_OVS_BRIDGE(object); + + switch (prop_id) { + case PROP_FAIL_MODE: + g_value_set_string(value, self->fail_mode); + break; + case PROP_MCAST_SNOOPING_ENABLE: + g_value_set_boolean(value, self->mcast_snooping_enable); + break; + case PROP_RSTP_ENABLE: + g_value_set_boolean(value, self->rstp_enable); + break; + case PROP_STP_ENABLE: + g_value_set_boolean(value, self->stp_enable); + break; + case PROP_DATAPATH_TYPE: + g_value_set_string(value, self->datapath_type); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingOvsBridge *self = NM_SETTING_OVS_BRIDGE(object); + + switch (prop_id) { + case PROP_FAIL_MODE: + g_free(self->fail_mode); + self->fail_mode = g_value_dup_string(value); + break; + case PROP_MCAST_SNOOPING_ENABLE: + self->mcast_snooping_enable = g_value_get_boolean(value); + break; + case PROP_RSTP_ENABLE: + self->rstp_enable = g_value_get_boolean(value); + break; + case PROP_STP_ENABLE: + self->stp_enable = g_value_get_boolean(value); + break; + case PROP_DATAPATH_TYPE: + g_free(self->datapath_type); + self->datapath_type = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ovs_bridge_init(NMSettingOvsBridge *self) +{} + +/** + * nm_setting_ovs_bridge_new: + * + * Creates a new #NMSettingOvsBridge object with default values. + * + * Returns: (transfer full): the new empty #NMSettingOvsBridge object + * + * Since: 1.10 + **/ +NMSetting * +nm_setting_ovs_bridge_new(void) +{ + return g_object_new(NM_TYPE_SETTING_OVS_BRIDGE, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingOvsBridge *self = NM_SETTING_OVS_BRIDGE(object); + + g_free(self->fail_mode); + g_free(self->datapath_type); + + G_OBJECT_CLASS(nm_setting_ovs_bridge_parent_class)->finalize(object); +} + +static void +nm_setting_ovs_bridge_class_init(NMSettingOvsBridgeClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingOvsBridge:fail-mode: + * + * The bridge failure mode. One of "secure", "standalone" or empty. + * + * Since: 1.10 + **/ + obj_properties[PROP_FAIL_MODE] = g_param_spec_string( + NM_SETTING_OVS_BRIDGE_FAIL_MODE, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingOvsBridge:mcast-snooping-enable: + * + * Enable or disable multicast snooping. + * + * Since: 1.10 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_OVS_BRIDGE_MCAST_SNOOPING_ENABLE, + PROP_MCAST_SNOOPING_ENABLE, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ovs_bridge_get_mcast_snooping_enable); + + /** + * NMSettingOvsBridge:rstp-enable: + * + * Enable or disable RSTP. + * + * Since: 1.10 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_OVS_BRIDGE_RSTP_ENABLE, + PROP_RSTP_ENABLE, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ovs_bridge_get_rstp_enable); + + /** + * NMSettingOvsBridge:stp-enable: + * + * Enable or disable STP. + * + * Since: 1.10 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_OVS_BRIDGE_STP_ENABLE, + PROP_STP_ENABLE, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ovs_bridge_get_stp_enable); + + /** + * NMSettingOvsBridge:datapath-type: + * + * The data path type. One of "system", "netdev" or empty. + * + * Since: 1.20 + **/ + obj_properties[PROP_DATAPATH_TYPE] = g_param_spec_string( + NM_SETTING_OVS_BRIDGE_DATAPATH_TYPE, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_OVS_BRIDGE, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-ovs-dpdk.c b/src/libnm-core-impl/nm-setting-ovs-dpdk.c new file mode 100644 index 00000000..957b0f57 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ovs-dpdk.c @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ovs-dpdk.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-ovs-dpdk + * @short_description: Describes connection properties for Open vSwitch DPDK interfaces. + * + * The #NMSettingOvsDpdk object is a #NMSetting subclass that describes properties + * necessary for Open vSwitch interfaces of type "dpdk". + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_DEVARGS, ); + +/** + * NMSettingOvsDpdk: + * + * OvsDpdk Link Settings + */ +struct _NMSettingOvsDpdk { + NMSetting parent; + + char *devargs; +}; + +struct _NMSettingOvsDpdkClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingOvsDpdk, nm_setting_ovs_dpdk, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_ovs_dpdk_get_devargs: + * @self: the #NMSettingOvsDpdk + * + * Returns: the #NMSettingOvsDpdk:devargs property of the setting + * + * Since: 1.20 + **/ +const char * +nm_setting_ovs_dpdk_get_devargs(NMSettingOvsDpdk *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_DPDK(self), NULL); + + return self->devargs; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingOvsDpdk *self = NM_SETTING_OVS_DPDK(object); + + switch (prop_id) { + case PROP_DEVARGS: + g_value_set_string(value, self->devargs); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingOvsDpdk *self = NM_SETTING_OVS_DPDK(object); + + switch (prop_id) { + case PROP_DEVARGS: + g_free(self->devargs); + self->devargs = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ovs_dpdk_init(NMSettingOvsDpdk *self) +{} + +/** + * nm_setting_ovs_dpdk_new: + * + * Creates a new #NMSettingOvsDpdk object with default values. + * + * Returns: (transfer full): the new empty #NMSettingOvsDpdk object + * + * Since: 1.20 + **/ +NMSetting * +nm_setting_ovs_dpdk_new(void) +{ + return g_object_new(NM_TYPE_SETTING_OVS_DPDK, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingOvsDpdk *self = NM_SETTING_OVS_DPDK(object); + + g_free(self->devargs); + + G_OBJECT_CLASS(nm_setting_ovs_dpdk_parent_class)->finalize(object); +} + +static void +nm_setting_ovs_dpdk_class_init(NMSettingOvsDpdkClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + /** + * NMSettingOvsDpdk:devargs: + * + * Open vSwitch DPDK device arguments. + * + * Since: 1.20 + **/ + obj_properties[PROP_DEVARGS] = g_param_spec_string( + NM_SETTING_OVS_DPDK_DEVARGS, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_OVS_DPDK); +} diff --git a/src/libnm-core-impl/nm-setting-ovs-external-ids.c b/src/libnm-core-impl/nm-setting-ovs-external-ids.c new file mode 100644 index 00000000..b73c1af4 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ovs-external-ids.c @@ -0,0 +1,551 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 - 2020 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ovs-external-ids.h" + +#include "nm-setting-private.h" +#include "nm-utils-private.h" +#include "nm-connection-private.h" + +#define MAX_NUM_KEYS 256 + +/*****************************************************************************/ + +/** + * SECTION:nm-setting-ovs-external-ids + * @short_description: External-IDs for OVS database + * + * The #NMSettingOvsExternalIDs object is a #NMSetting subclass that allow to + * configure external ids for OVS. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingOvsExternalIDs, PROP_DATA, ); + +typedef struct { + GHashTable * data; + const char **data_keys; +} NMSettingOvsExternalIDsPrivate; + +/** + * NMSettingOvsExternalIDs: + * + * OVS External IDs Settings + */ +struct _NMSettingOvsExternalIDs { + NMSetting parent; + NMSettingOvsExternalIDsPrivate _priv; +}; + +struct _NMSettingOvsExternalIDsClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingOvsExternalIDs, nm_setting_ovs_external_ids, NM_TYPE_SETTING) + +#define NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSettingOvsExternalIDs, NM_IS_SETTING_OVS_EXTERNAL_IDS) + +/*****************************************************************************/ + +static gboolean +_exid_key_char_is_regular(char ch) +{ + /* allow words of printable characters, plus some + * special characters, for example to support base64 encoding. */ + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') + || NM_IN_SET(ch, '-', '_', '+', '/', '=', '.'); +} + +/** + * nm_setting_ovs_external_ids_check_key: + * @key: (allow-none): the key to check + * @error: a #GError, %NULL to ignore. + * + * Checks whether @key is a valid key for OVS' external-ids. + * This means, the key cannot be %NULL, not too large and valid ASCII. + * Also, only digits and numbers are allowed with a few special + * characters. They key must also not start with "NM.". + * + * Since: 1.30 + * + * Returns: %TRUE if @key is a valid user data key. + */ +gboolean +nm_setting_ovs_external_ids_check_key(const char *key, GError **error) +{ + gsize len; + + g_return_val_if_fail(!error || !*error, FALSE); + + if (!key || !key[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("missing key")); + return FALSE; + } + len = strlen(key); + if (len > 255u) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key is too long")); + return FALSE; + } + if (!g_utf8_validate(key, len, NULL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key must be UTF8")); + return FALSE; + } + if (!NM_STRCHAR_ALL(key, ch, _exid_key_char_is_regular(ch))) { + /* Probably OVS is more forgiving about what makes a valid key for + * an external-id. However, we are strict (at least, for now). */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key contains invalid characters")); + return FALSE; + } + + if (NM_STR_HAS_PREFIX(key, NM_OVS_EXTERNAL_ID_NM_PREFIX)) { + /* these keys are reserved. */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key cannot start with \"NM.\"")); + return FALSE; + } + + return TRUE; +} + +/** + * nm_setting_ovs_external_ids_check_val: + * @val: (allow-none): the value to check + * @error: a #GError, %NULL to ignore. + * + * Checks whether @val is a valid user data value. This means, + * value is not %NULL, not too large and valid UTF-8. + * + * Since: 1.30 + * + * Returns: %TRUE if @val is a valid user data value. + */ +gboolean +nm_setting_ovs_external_ids_check_val(const char *val, GError **error) +{ + gsize len; + + g_return_val_if_fail(!error || !*error, FALSE); + + if (!val) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value is missing")); + return FALSE; + } + + len = strlen(val); + if (len > (8u * 1024u)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value is too large")); + return FALSE; + } + + if (!g_utf8_validate(val, len, NULL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value is not valid UTF8")); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static GHashTable * +_create_data_hash(void) +{ + return g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); +} + +GHashTable * +_nm_setting_ovs_external_ids_get_data(NMSettingOvsExternalIDs *self) +{ + return NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self)->data; +} + +/** + * nm_setting_ovs_external_ids_get_data_keys: + * @setting: the #NMSettingOvsExternalIDs + * @out_len: (out): the length of the returned array + * + * Returns: (array length=out_len) (transfer none): a + * %NULL-terminated array containing each key from the table. + **/ +const char *const * +nm_setting_ovs_external_ids_get_data_keys(NMSettingOvsExternalIDs *setting, guint *out_len) +{ + NMSettingOvsExternalIDs * self = setting; + NMSettingOvsExternalIDsPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_OVS_EXTERNAL_IDS(self), NULL); + + priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); + + if (priv->data_keys) { + NM_SET_OUT(out_len, g_hash_table_size(priv->data)); + return priv->data_keys; + } + + priv->data_keys = nm_utils_strdict_get_keys(priv->data, TRUE, out_len); + + /* don't return %NULL, but hijack the @data_keys fields as a pseudo + * empty strv array. */ + return priv->data_keys ?: ((const char **) &priv->data_keys); +} + +/*****************************************************************************/ + +/** + * nm_setting_ovs_external_ids_get_data: + * @setting: the #NMSettingOvsExternalIDs instance + * @key: the external-id to lookup + * + * Since: 1.30 + * + * Returns: (transfer none): the value associated with @key or %NULL if no such + * value exists. + */ +const char * +nm_setting_ovs_external_ids_get_data(NMSettingOvsExternalIDs *setting, const char *key) +{ + NMSettingOvsExternalIDs * self = setting; + NMSettingOvsExternalIDsPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_OVS_EXTERNAL_IDS(self), NULL); + g_return_val_if_fail(key, NULL); + + priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); + + if (!priv->data) + return NULL; + + return g_hash_table_lookup(priv->data, key); +} + +/** + * nm_setting_ovs_external_ids_set_data: + * @setting: the #NMSettingOvsExternalIDs instance + * @key: the key to set + * @val: (allow-none): the value to set or %NULL to clear a key. + * + * Since: 1.30 + */ +void +nm_setting_ovs_external_ids_set_data(NMSettingOvsExternalIDs *setting, + const char * key, + const char * val) +{ + NMSettingOvsExternalIDs * self = setting; + NMSettingOvsExternalIDsPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_OVS_EXTERNAL_IDS(self)); + + priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); + + if (!val) { + if (priv->data && g_hash_table_remove(priv->data, key)) + goto out_changed; + return; + } + + if (priv->data) { + const char *val2; + + if (g_hash_table_lookup_extended(priv->data, key, NULL, (gpointer *) &val2)) { + if (nm_streq(val, val2)) + return; + } + } else + priv->data = _create_data_hash(); + + g_hash_table_insert(priv->data, g_strdup(key), g_strdup(val)); + +out_changed: + nm_clear_g_free(&priv->data_keys); + _notify(self, PROP_DATA); +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingOvsExternalIDs * self = NM_SETTING_OVS_EXTERNAL_IDS(setting); + NMSettingOvsExternalIDsPrivate *priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); + + if (priv->data) { + gs_free_error GError *local = NULL; + GHashTableIter iter; + const char * key; + const char * val; + + g_hash_table_iter_init(&iter, priv->data); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + if (!nm_setting_ovs_external_ids_check_key(key, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid key \"%s\": %s"), + key, + local->message); + } else if (!nm_setting_ovs_external_ids_check_val(val, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid value for \"%s\": %s"), + key, + local->message); + } else + continue; + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME, + NM_SETTING_OVS_EXTERNAL_IDS_DATA); + return FALSE; + } + } + + if (priv->data && g_hash_table_size(priv->data) > MAX_NUM_KEYS) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("maximum number of user data entries reached (%u instead of %u)"), + g_hash_table_size(priv->data), + (unsigned) MAX_NUM_KEYS); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME, + NM_SETTING_OVS_EXTERNAL_IDS_DATA); + return FALSE; + } + + if (connection) { + NMSettingConnection *s_con; + const char * type; + const char * slave_type; + + type = nm_connection_get_connection_type(connection); + if (!type) { + NMSetting *s_base; + + s_base = _nm_connection_find_base_type_setting(connection); + if (s_base) + type = nm_setting_get_name(s_base); + } + if (NM_IN_STRSET(type, + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME)) + goto connection_type_is_good; + + if ((s_con = nm_connection_get_setting_connection(connection)) + && _nm_connection_detect_slave_type_full(s_con, + connection, + &slave_type, + NULL, + NULL, + NULL, + NULL) + && nm_streq0(slave_type, NM_SETTING_OVS_PORT_SETTING_NAME)) + goto connection_type_is_good; + + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("OVS external IDs can only be added to a profile of type OVS " + "bridge/port/interface or to OVS system interface")); + return FALSE; + } +connection_type_is_good: + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingOvsExternalIDsPrivate *priv; + NMSettingOvsExternalIDsPrivate *pri2; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_OVS_EXTERNAL_IDS_DATA)) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) + return NM_TERNARY_DEFAULT; + + if (!set_b) + return TRUE; + + priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(NM_SETTING_OVS_EXTERNAL_IDS(set_a)); + pri2 = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(NM_SETTING_OVS_EXTERNAL_IDS(set_b)); + return nm_utils_hashtable_equal(priv->data, pri2->data, TRUE, g_str_equal); + } + + return NM_SETTING_CLASS(nm_setting_ovs_external_ids_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingOvsExternalIDs * self = NM_SETTING_OVS_EXTERNAL_IDS(object); + NMSettingOvsExternalIDsPrivate *priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); + GHashTableIter iter; + GHashTable * data; + const char * key; + const char * val; + + switch (prop_id) { + case PROP_DATA: + data = _create_data_hash(); + if (priv->data) { + g_hash_table_iter_init(&iter, priv->data); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) + g_hash_table_insert(data, g_strdup(key), g_strdup(val)); + } + g_value_take_boxed(value, data); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingOvsExternalIDs * self = NM_SETTING_OVS_EXTERNAL_IDS(object); + NMSettingOvsExternalIDsPrivate *priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); + + switch (prop_id) { + case PROP_DATA: + { + gs_unref_hashtable GHashTable *old = NULL; + GHashTableIter iter; + GHashTable * data; + const char * key; + const char * val; + + nm_clear_g_free(&priv->data_keys); + + old = g_steal_pointer(&priv->data); + + data = g_value_get_boxed(value); + if (nm_g_hash_table_size(data) <= 0) + return; + + priv->data = _create_data_hash(); + g_hash_table_iter_init(&iter, data); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) + g_hash_table_insert(priv->data, g_strdup(key), g_strdup(val)); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ovs_external_ids_init(NMSettingOvsExternalIDs *self) +{} + +/** + * nm_setting_ovs_external_ids_new: + * + * Creates a new #NMSettingOvsExternalIDs object with default values. + * + * Returns: (transfer full) (type NMSettingOvsExternalIDs): the new empty + * #NMSettingOvsExternalIDs object + * + * Since: 1.30 + */ +NMSetting * +nm_setting_ovs_external_ids_new(void) +{ + return g_object_new(NM_TYPE_SETTING_OVS_EXTERNAL_IDS, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingOvsExternalIDs * self = NM_SETTING_OVS_EXTERNAL_IDS(object); + NMSettingOvsExternalIDsPrivate *priv = NM_SETTING_OVS_EXTERNAL_IDS_GET_PRIVATE(self); + + g_free(priv->data_keys); + if (priv->data) + g_hash_table_unref(priv->data); + + G_OBJECT_CLASS(nm_setting_ovs_external_ids_parent_class)->finalize(object); +} + +static void +nm_setting_ovs_external_ids_class_init(NMSettingOvsExternalIDsClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->compare_property = compare_property; + setting_class->verify = verify; + + /** + * NMSettingOvsExternalIDs:data: (type GHashTable(utf8,utf8)) + * + * A dictionary of key/value pairs with exernal-ids for OVS. + * + * Since: 1.30 + **/ + obj_properties[PROP_DATA] = g_param_spec_boxed(NM_SETTING_OVS_EXTERNAL_IDS_DATA, + "", + "", + G_TYPE_HASH_TABLE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_DATA], + &nm_sett_info_propert_type_strdict); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-ovs-interface.c b/src/libnm-core-impl/nm-setting-ovs-interface.c new file mode 100644 index 00000000..1fde9605 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ovs-interface.c @@ -0,0 +1,427 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ovs-interface.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-ovs-interface + * @short_description: Describes connection properties for Open vSwitch interfaces. + * + * The #NMSettingOvsInterface object is a #NMSetting subclass that describes properties + * necessary for Open vSwitch interfaces. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_TYPE, ); + +/** + * NMSettingOvsInterface: + * + * Open vSwitch Interface Settings + */ +struct _NMSettingOvsInterface { + NMSetting parent; + + char *type; +}; + +struct _NMSettingOvsInterfaceClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingOvsInterface, nm_setting_ovs_interface, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_ovs_interface_get_interface_type: + * @self: the #NMSettingOvsInterface + * + * Returns: the #NMSettingOvsInterface:type property of the setting + * + * Since: 1.10 + **/ +const char * +nm_setting_ovs_interface_get_interface_type(NMSettingOvsInterface *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_INTERFACE(self), NULL); + + return self->type; +} + +/*****************************************************************************/ + +int +_nm_setting_ovs_interface_verify_interface_type(NMSettingOvsInterface *self, + const char * type, + NMConnection * connection, + gboolean normalize, + gboolean * out_modified, + const char ** out_normalized_type, + GError ** error) +{ + const char *type_from_setting = NULL; + const char *type_setting = NULL; + const char *connection_type; + gboolean is_ovs_connection_type; + + if (normalize) { + g_return_val_if_fail(NM_IS_SETTING_OVS_INTERFACE(self), FALSE); + g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE); + nm_assert(self == nm_connection_get_setting_ovs_interface(connection)); + } else { + g_return_val_if_fail(!self || NM_IS_SETTING_OVS_INTERFACE(self), FALSE); + g_return_val_if_fail(!connection || NM_IS_CONNECTION(connection), FALSE); + } + + NM_SET_OUT(out_modified, FALSE); + NM_SET_OUT(out_normalized_type, NULL); + + if (type && !NM_IN_STRSET(type, "internal", "system", "patch", "dpdk")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid interface type"), + type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_TYPE); + return FALSE; + } + + if (!connection) { + NM_SET_OUT(out_normalized_type, type); + return TRUE; + } + + connection_type = nm_connection_get_connection_type(connection); + if (!connection_type) { + /* if we have an ovs-interface, then the connection type must be either + * "ovs-interface" (for non "system" type) or anything else (for "system" type). + * + * The connection type usually can be normalized based on the presence of a + * base setting. However, in this case, if the connection type is missing, + * that is too complicate to guess what the user wanted. + * + * Require the use to be explicit and fail. */ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with a '%s' setting needs connection.type explicitly set"), + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_TYPE); + return FALSE; + } + + if (nm_streq(connection_type, NM_SETTING_OVS_INTERFACE_SETTING_NAME)) { + if (type && nm_streq(type, "system")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection of type '%s' cannot have ovs-interface.type \"system\""), + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_TYPE); + return FALSE; + } + is_ovs_connection_type = TRUE; + } else { + if (type && !nm_streq(type, "system")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection of type '%s' cannot have an ovs-interface.type \"%s\""), + connection_type, + type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_TYPE); + return FALSE; + } + is_ovs_connection_type = FALSE; + } + + if (nm_connection_get_setting_by_name(connection, NM_SETTING_OVS_PATCH_SETTING_NAME)) { + type_from_setting = "patch"; + type_setting = NM_SETTING_OVS_PATCH_SETTING_NAME; + } + + if (nm_connection_get_setting_by_name(connection, NM_SETTING_OVS_DPDK_SETTING_NAME)) { + if (type_from_setting) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection can not have both '%s' and '%s' settings at the same time"), + NM_SETTING_OVS_DPDK_SETTING_NAME, + type_setting); + return FALSE; + } + type_from_setting = "dpdk"; + type_setting = NM_SETTING_OVS_DPDK_SETTING_NAME; + } + + if (type_from_setting) { + if (!is_ovs_connection_type) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with '%s' setting must be of connection.type " + "\"ovs-interface\" but is \"%s\""), + NM_SETTING_OVS_PATCH_SETTING_NAME, + connection_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_TYPE); + return FALSE; + } + + if (type) { + if (!nm_streq(type, type_from_setting)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with '%s' setting needs to be of '%s' interface type, " + "not '%s'"), + type_setting, + type_from_setting, + type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_TYPE); + return FALSE; + } + NM_SET_OUT(out_normalized_type, type); + return TRUE; + } + type = type_from_setting; + goto normalize; + } else { + if (nm_streq0(type, "patch")) { + g_set_error( + error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("A connection with ovs-interface.type '%s' setting a 'ovs-patch' setting"), + type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_TYPE); + return FALSE; + } + } + + if (type) { + NM_SET_OUT(out_normalized_type, type); + return TRUE; + } + + if (is_ovs_connection_type) + type = "internal"; + else + type = "system"; + + NM_SET_OUT(out_normalized_type, type); + +normalize: + if (!normalize) { + if (!self) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("Missing ovs interface setting")); + g_prefix_error(error, "%s: ", NM_SETTING_OVS_INTERFACE_SETTING_NAME); + } else { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("Missing ovs interface type")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_TYPE); + } + return NM_SETTING_VERIFY_NORMALIZABLE_ERROR; + } + + if (!self) { + self = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new()); + nm_connection_add_setting(connection, NM_SETTING(self)); + } + g_object_set(self, NM_SETTING_OVS_INTERFACE_TYPE, type, NULL); + NM_SET_OUT(out_modified, TRUE); + + return TRUE; +} + +static int +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingOvsInterface *self = NM_SETTING_OVS_INTERFACE(setting); + NMSettingConnection * s_con = NULL; + + if (connection) { + const char *slave_type; + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("missing setting")); + g_prefix_error(error, "%s: ", NM_SETTING_CONNECTION_SETTING_NAME); + return FALSE; + } + + if (!nm_setting_connection_get_master(s_con)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with a '%s' setting must have a master."), + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER); + return FALSE; + } + + slave_type = nm_setting_connection_get_slave_type(s_con); + if (slave_type && !nm_streq(slave_type, NM_SETTING_OVS_PORT_SETTING_NAME)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with a '%s' setting must have the slave-type set to '%s'. " + "Instead it is '%s'"), + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_PORT_SETTING_NAME, + slave_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return FALSE; + } + } + + return _nm_setting_ovs_interface_verify_interface_type(self, + self->type, + connection, + FALSE, + NULL, + NULL, + error); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingOvsInterface *self = NM_SETTING_OVS_INTERFACE(object); + + switch (prop_id) { + case PROP_TYPE: + g_value_set_string(value, self->type); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingOvsInterface *self = NM_SETTING_OVS_INTERFACE(object); + + switch (prop_id) { + case PROP_TYPE: + g_free(self->type); + self->type = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ovs_interface_init(NMSettingOvsInterface *self) +{} + +/** + * nm_setting_ovs_interface_new: + * + * Creates a new #NMSettingOvsInterface object with default values. + * + * Returns: (transfer full): the new empty #NMSettingOvsInterface object + * + * Since: 1.10 + **/ +NMSetting * +nm_setting_ovs_interface_new(void) +{ + return g_object_new(NM_TYPE_SETTING_OVS_INTERFACE, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingOvsInterface *self = NM_SETTING_OVS_INTERFACE(object); + + g_free(self->type); + + G_OBJECT_CLASS(nm_setting_ovs_interface_parent_class)->finalize(object); +} + +static void +nm_setting_ovs_interface_class_init(NMSettingOvsInterfaceClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingOvsInterface:type: + * + * The interface type. Either "internal", "system", "patch", "dpdk", or empty. + * + * Since: 1.10 + **/ + obj_properties[PROP_TYPE] = g_param_spec_string(NM_SETTING_OVS_INTERFACE_TYPE, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_OVS_INTERFACE); +} diff --git a/src/libnm-core-impl/nm-setting-ovs-patch.c b/src/libnm-core-impl/nm-setting-ovs-patch.c new file mode 100644 index 00000000..a226a26d --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ovs-patch.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ovs-patch.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-ovs-patch + * @short_description: Describes connection properties for Open vSwitch patch interfaces. + * + * The #NMSettingOvsPatch object is a #NMSetting subclass that describes properties + * necessary for Open vSwitch interfaces of type "patch". + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PEER, ); + +/** + * NMSettingOvsPatch: + * + * OvsPatch Link Settings + */ +struct _NMSettingOvsPatch { + NMSetting parent; + + char *peer; +}; + +struct _NMSettingOvsPatchClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingOvsPatch, nm_setting_ovs_patch, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_ovs_patch_get_peer: + * @self: the #NMSettingOvsPatch + * + * Returns: the #NMSettingOvsPatch:peer property of the setting + * + * Since: 1.10 + **/ +const char * +nm_setting_ovs_patch_get_peer(NMSettingOvsPatch *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_PATCH(self), NULL); + + return self->peer; +} + +/*****************************************************************************/ + +static int +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingOvsPatch *self = NM_SETTING_OVS_PATCH(setting); + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + if (!self->peer) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_PATCH_SETTING_NAME, + NM_SETTING_OVS_PATCH_PEER); + return FALSE; + } + + if (!nm_utils_ifname_valid(self->peer, NMU_IFACE_OVS, error)) { + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_PATCH_SETTING_NAME, + NM_SETTING_OVS_PATCH_PEER); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingOvsPatch *self = NM_SETTING_OVS_PATCH(object); + + switch (prop_id) { + case PROP_PEER: + g_value_set_string(value, self->peer); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingOvsPatch *self = NM_SETTING_OVS_PATCH(object); + + switch (prop_id) { + case PROP_PEER: + g_free(self->peer); + self->peer = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ovs_patch_init(NMSettingOvsPatch *self) +{} + +/** + * nm_setting_ovs_patch_new: + * + * Creates a new #NMSettingOvsPatch object with default values. + * + * Returns: (transfer full): the new empty #NMSettingOvsPatch object + * + * Since: 1.10 + **/ +NMSetting * +nm_setting_ovs_patch_new(void) +{ + return g_object_new(NM_TYPE_SETTING_OVS_PATCH, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingOvsPatch *self = NM_SETTING_OVS_PATCH(object); + + g_free(self->peer); + + G_OBJECT_CLASS(nm_setting_ovs_patch_parent_class)->finalize(object); +} + +static void +nm_setting_ovs_patch_class_init(NMSettingOvsPatchClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingOvsPatch:peer: + * + * Specifies the name of the interface for the other side of the patch. + * The patch on the other side must also set this interface as peer. + * + * Since: 1.10 + **/ + obj_properties[PROP_PEER] = g_param_spec_string(NM_SETTING_OVS_PATCH_PEER, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_OVS_PATCH); +} diff --git a/src/libnm-core-impl/nm-setting-ovs-port.c b/src/libnm-core-impl/nm-setting-ovs-port.c new file mode 100644 index 00000000..821f29ab --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ovs-port.c @@ -0,0 +1,467 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ovs-port.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-ovs-port + * @short_description: Describes connection properties for Open vSwitch ports. + * + * The #NMSettingOvsPort object is a #NMSetting subclass that describes properties + * necessary for Open vSwitch ports. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_VLAN_MODE, + PROP_TAG, + PROP_LACP, + PROP_BOND_MODE, + PROP_BOND_UPDELAY, + PROP_BOND_DOWNDELAY, ); + +/** + * NMSettingOvsPort: + * + * OvsPort Link Settings + */ +struct _NMSettingOvsPort { + NMSetting parent; + + char *vlan_mode; + char *lacp; + char *bond_mode; + guint tag; + guint bond_updelay; + guint bond_downdelay; +}; + +struct _NMSettingOvsPortClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingOvsPort, nm_setting_ovs_port, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_ovs_port_get_vlan_mode: + * @self: the #NMSettingOvsPort + * + * Returns: the #NMSettingOvsPort:vlan-mode property of the setting + * + * Since: 1.10 + **/ +const char * +nm_setting_ovs_port_get_vlan_mode(NMSettingOvsPort *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), NULL); + + return self->vlan_mode; +} + +/** + * nm_setting_ovs_port_get_tag: + * @self: the #NMSettingOvsPort + * + * Returns: the #NMSettingOvsPort:tag property of the setting + * + * Since: 1.10 + **/ +guint +nm_setting_ovs_port_get_tag(NMSettingOvsPort *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), 0); + + return self->tag; +} + +/** + * nm_setting_ovs_port_get_lacp: + * @self: the #NMSettingOvsPort + * + * Returns: the #NMSettingOvsPort:lacp property of the setting + * + * Since: 1.10 + **/ +const char * +nm_setting_ovs_port_get_lacp(NMSettingOvsPort *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), NULL); + + return self->lacp; +} + +/** + * nm_setting_ovs_port_get_bond_mode: + * @self: the #NMSettingOvsPort + * + * Returns: the #NMSettingOvsPort:bond-mode property of the setting + * + * Since: 1.10 + **/ +const char * +nm_setting_ovs_port_get_bond_mode(NMSettingOvsPort *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), NULL); + + return self->bond_mode; +} + +/** + * nm_setting_ovs_port_get_bond_updelay: + * @self: the #NMSettingOvsPort + * + * Returns: the #NMSettingOvsPort:bond-updelay property of the setting + * + * Since: 1.10 + **/ +guint +nm_setting_ovs_port_get_bond_updelay(NMSettingOvsPort *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), 0); + + return self->bond_updelay; +} + +/** + * nm_setting_ovs_port_get_bond_downdelay: + * @self: the #NMSettingOvsPort + * + * Returns: the #NMSettingOvsPort:bond-downdelay property of the setting + * + * Since: 1.10 + **/ +guint +nm_setting_ovs_port_get_bond_downdelay(NMSettingOvsPort *self) +{ + g_return_val_if_fail(NM_IS_SETTING_OVS_PORT(self), 0); + + return self->bond_downdelay; +} + +/*****************************************************************************/ + +static int +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingOvsPort *self = NM_SETTING_OVS_PORT(setting); + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + if (connection) { + NMSettingConnection *s_con; + const char * slave_type; + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("missing setting")); + g_prefix_error(error, "%s: ", NM_SETTING_CONNECTION_SETTING_NAME); + return FALSE; + } + + if (!nm_setting_connection_get_master(s_con)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with a '%s' setting must have a master."), + NM_SETTING_OVS_PORT_SETTING_NAME); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_MASTER); + return FALSE; + } + + slave_type = nm_setting_connection_get_slave_type(s_con); + if (slave_type && strcmp(slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with a '%s' setting must have the slave-type set to '%s'. " + "Instead it is '%s'"), + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + slave_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return FALSE; + } + } + + if (!NM_IN_STRSET(self->vlan_mode, + "access", + "native-tagged", + "native-untagged", + "trunk", + NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not allowed in vlan_mode"), + self->vlan_mode); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_OVS_PORT_VLAN_MODE); + return FALSE; + } + + if (self->tag >= 4095) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the tag id must be in range 0-4094 but is %u"), + self->tag); + g_prefix_error(error, "%s.%s: ", NM_SETTING_OVS_PORT_SETTING_NAME, NM_SETTING_OVS_PORT_TAG); + return FALSE; + } + + if (!NM_IN_STRSET(self->lacp, "active", "off", "passive", NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not allowed in lacp"), + self->lacp); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_OVS_PORT_LACP); + return FALSE; + } + + if (!NM_IN_STRSET(self->bond_mode, "active-backup", "balance-slb", "balance-tcp", NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not allowed in bond_mode"), + self->bond_mode); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_OVS_PORT_BOND_MODE); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingOvsPort *self = NM_SETTING_OVS_PORT(object); + + switch (prop_id) { + case PROP_VLAN_MODE: + g_value_set_string(value, self->vlan_mode); + break; + case PROP_TAG: + g_value_set_uint(value, self->tag); + break; + case PROP_LACP: + g_value_set_string(value, self->lacp); + break; + case PROP_BOND_MODE: + g_value_set_string(value, self->bond_mode); + break; + case PROP_BOND_UPDELAY: + g_value_set_uint(value, self->bond_updelay); + break; + case PROP_BOND_DOWNDELAY: + g_value_set_uint(value, self->bond_downdelay); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingOvsPort *self = NM_SETTING_OVS_PORT(object); + + switch (prop_id) { + case PROP_VLAN_MODE: + g_free(self->vlan_mode); + self->vlan_mode = g_value_dup_string(value); + break; + case PROP_TAG: + self->tag = g_value_get_uint(value); + break; + case PROP_LACP: + g_free(self->lacp); + self->lacp = g_value_dup_string(value); + break; + case PROP_BOND_MODE: + g_free(self->bond_mode); + self->bond_mode = g_value_dup_string(value); + break; + case PROP_BOND_UPDELAY: + self->bond_updelay = g_value_get_uint(value); + break; + case PROP_BOND_DOWNDELAY: + self->bond_downdelay = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ovs_port_init(NMSettingOvsPort *self) +{} + +/** + * nm_setting_ovs_port_new: + * + * Creates a new #NMSettingOvsPort object with default values. + * + * Returns: (transfer full): the new empty #NMSettingOvsPort object + * + * Since: 1.10 + **/ +NMSetting * +nm_setting_ovs_port_new(void) +{ + return g_object_new(NM_TYPE_SETTING_OVS_PORT, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingOvsPort *self = NM_SETTING_OVS_PORT(object); + + g_free(self->vlan_mode); + g_free(self->lacp); + g_free(self->bond_mode); + + G_OBJECT_CLASS(nm_setting_ovs_port_parent_class)->finalize(object); +} + +static void +nm_setting_ovs_port_class_init(NMSettingOvsPortClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingOvsPort:vlan-mode: + * + * The VLAN mode. One of "access", "native-tagged", "native-untagged", + * "trunk" or unset. + * + * Since: 1.10 + **/ + obj_properties[PROP_VLAN_MODE] = g_param_spec_string( + NM_SETTING_OVS_PORT_VLAN_MODE, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingOvsPort:tag: + * + * The VLAN tag in the range 0-4095. + * + * Since: 1.10 + **/ + obj_properties[PROP_TAG] = + g_param_spec_uint(NM_SETTING_OVS_PORT_TAG, + "", + "", + 0, + 4095, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingOvsPort:lacp: + * + * LACP mode. One of "active", "off", or "passive". + * + * Since: 1.10 + **/ + obj_properties[PROP_LACP] = g_param_spec_string(NM_SETTING_OVS_PORT_LACP, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingOvsPort:bond-mode: + * + * Bonding mode. One of "active-backup", "balance-slb", or "balance-tcp". + * + * Since: 1.10 + **/ + obj_properties[PROP_BOND_MODE] = g_param_spec_string( + NM_SETTING_OVS_PORT_BOND_MODE, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingOvsPort:bond-updelay: + * + * The time port must be active before it starts forwarding traffic. + * + * Since: 1.10 + **/ + obj_properties[PROP_BOND_UPDELAY] = + g_param_spec_uint(NM_SETTING_OVS_PORT_BOND_UPDELAY, + "", + "", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingOvsPort:bond-downdelay: + * + * The time port must be inactive in order to be considered down. + * + * Since: 1.10 + **/ + obj_properties[PROP_BOND_DOWNDELAY] = + g_param_spec_uint(NM_SETTING_OVS_PORT_BOND_DOWNDELAY, + "", + "", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_OVS_PORT); +} diff --git a/src/libnm-core-impl/nm-setting-ppp.c b/src/libnm-core-impl/nm-setting-ppp.c new file mode 100644 index 00000000..cc722c44 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-ppp.c @@ -0,0 +1,813 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-ppp.h" + +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-ppp + * @short_description: Describes connection properties for devices/networks + * that require PPP to deliver IP capability + * + * The #NMSettingPpp object is a #NMSetting subclass that describes properties + * necessary for connection to networks that require PPP transport, like PPPoE + * cable and DSL modems and some mobile broadband devices. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_NOAUTH, + PROP_REFUSE_EAP, + PROP_REFUSE_PAP, + PROP_REFUSE_CHAP, + PROP_REFUSE_MSCHAP, + PROP_REFUSE_MSCHAPV2, + PROP_NOBSDCOMP, + PROP_NODEFLATE, + PROP_NO_VJ_COMP, + PROP_REQUIRE_MPPE, + PROP_REQUIRE_MPPE_128, + PROP_MPPE_STATEFUL, + PROP_CRTSCTS, + PROP_BAUD, + PROP_MRU, + PROP_MTU, + PROP_LCP_ECHO_FAILURE, + PROP_LCP_ECHO_INTERVAL, ); + +typedef struct { + guint32 baud; + guint32 mru; + guint32 mtu; + guint32 lcp_echo_failure; + guint32 lcp_echo_interval; + bool noauth : 1; + bool refuse_eap : 1; + bool refuse_pap : 1; + bool refuse_chap : 1; + bool refuse_mschap : 1; + bool refuse_mschapv2 : 1; + bool nobsdcomp : 1; + bool nodeflate : 1; + bool no_vj_comp : 1; + bool require_mppe : 1; + bool require_mppe_128 : 1; + bool mppe_stateful : 1; + bool crtscts : 1; +} NMSettingPppPrivate; + +/** + * NMSettingPpp: + * + * Point-to-Point Protocol Settings + */ +struct _NMSettingPpp { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingPppClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingPpp, nm_setting_ppp, NM_TYPE_SETTING) + +#define NM_SETTING_PPP_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_PPP, NMSettingPppPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_ppp_get_noauth: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:noauth property of the setting + **/ +gboolean +nm_setting_ppp_get_noauth(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->noauth; +} + +/** + * nm_setting_ppp_get_refuse_eap: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:refuse-eap property of the setting + **/ +gboolean +nm_setting_ppp_get_refuse_eap(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->refuse_eap; +} + +/** + * nm_setting_ppp_get_refuse_pap: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:refuse-pap property of the setting + **/ +gboolean +nm_setting_ppp_get_refuse_pap(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->refuse_pap; +} + +/** + * nm_setting_ppp_get_refuse_chap: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:refuse-chap property of the setting + **/ +gboolean +nm_setting_ppp_get_refuse_chap(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->refuse_chap; +} + +/** + * nm_setting_ppp_get_refuse_mschap: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:refuse-mschap property of the setting + **/ +gboolean +nm_setting_ppp_get_refuse_mschap(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->refuse_mschap; +} + +/** + * nm_setting_ppp_get_refuse_mschapv2: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:refuse-mschapv2 property of the setting + **/ +gboolean +nm_setting_ppp_get_refuse_mschapv2(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->refuse_mschapv2; +} + +/** + * nm_setting_ppp_get_nobsdcomp: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:nobsdcomp property of the setting + **/ +gboolean +nm_setting_ppp_get_nobsdcomp(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->nobsdcomp; +} + +/** + * nm_setting_ppp_get_nodeflate: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:nodeflate property of the setting + **/ +gboolean +nm_setting_ppp_get_nodeflate(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->nodeflate; +} + +/** + * nm_setting_ppp_get_no_vj_comp: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:no-vj-comp property of the setting + **/ +gboolean +nm_setting_ppp_get_no_vj_comp(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->no_vj_comp; +} + +/** + * nm_setting_ppp_get_require_mppe: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:require-mppe property of the setting + **/ +gboolean +nm_setting_ppp_get_require_mppe(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->require_mppe; +} + +/** + * nm_setting_ppp_get_require_mppe_128: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:require-mppe-128 property of the setting + **/ +gboolean +nm_setting_ppp_get_require_mppe_128(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->require_mppe_128; +} + +/** + * nm_setting_ppp_get_mppe_stateful: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:mppe-stateful property of the setting + **/ +gboolean +nm_setting_ppp_get_mppe_stateful(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->mppe_stateful; +} + +/** + * nm_setting_ppp_get_crtscts: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:crtscts property of the setting + **/ +gboolean +nm_setting_ppp_get_crtscts(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), FALSE); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->crtscts; +} + +/** + * nm_setting_ppp_get_baud: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:baud property of the setting + **/ +guint32 +nm_setting_ppp_get_baud(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), 0); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->baud; +} + +/** + * nm_setting_ppp_get_mru: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:mru property of the setting + **/ +guint32 +nm_setting_ppp_get_mru(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), 0); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->mru; +} + +/** + * nm_setting_ppp_get_mtu: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:mtu property of the setting + **/ +guint32 +nm_setting_ppp_get_mtu(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), 0); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->mtu; +} + +/** + * nm_setting_ppp_get_lcp_echo_failure: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:lcp-echo-failure property of the setting + **/ +guint32 +nm_setting_ppp_get_lcp_echo_failure(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), 0); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->lcp_echo_failure; +} + +/** + * nm_setting_ppp_get_lcp_echo_interval: + * @setting: the #NMSettingPpp + * + * Returns: the #NMSettingPpp:lcp-echo-interval property of the setting + **/ +guint32 +nm_setting_ppp_get_lcp_echo_interval(NMSettingPpp *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPP(setting), 0); + + return NM_SETTING_PPP_GET_PRIVATE(setting)->lcp_echo_interval; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingPppPrivate *priv = NM_SETTING_PPP_GET_PRIVATE(setting); + + /* FIXME: Do we even want this or can we just let pppd evaluate the options? */ + if (priv->mru > 0) { + if (priv->mru < 128 || priv->mru > 16384) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%d' is out of valid range <128-16384>"), + priv->mru); + g_prefix_error(error, "%s.%s: ", NM_SETTING_PPP_SETTING_NAME, NM_SETTING_PPP_MRU); + return FALSE; + } + } + + if (priv->lcp_echo_failure > 0) { + /* lcp_echo_interval must also be non-zero */ + if (priv->lcp_echo_interval == 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("setting this property requires non-zero '%s' property"), + NM_SETTING_PPP_LCP_ECHO_INTERVAL); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_PPP_SETTING_NAME, + NM_SETTING_PPP_LCP_ECHO_FAILURE); + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingPpp *setting = NM_SETTING_PPP(object); + + switch (prop_id) { + case PROP_NOAUTH: + g_value_set_boolean(value, nm_setting_ppp_get_noauth(setting)); + break; + case PROP_REFUSE_EAP: + g_value_set_boolean(value, nm_setting_ppp_get_refuse_eap(setting)); + break; + case PROP_REFUSE_PAP: + g_value_set_boolean(value, nm_setting_ppp_get_refuse_pap(setting)); + break; + case PROP_REFUSE_CHAP: + g_value_set_boolean(value, nm_setting_ppp_get_refuse_chap(setting)); + break; + case PROP_REFUSE_MSCHAP: + g_value_set_boolean(value, nm_setting_ppp_get_refuse_mschap(setting)); + break; + case PROP_REFUSE_MSCHAPV2: + g_value_set_boolean(value, nm_setting_ppp_get_refuse_mschapv2(setting)); + break; + case PROP_NOBSDCOMP: + g_value_set_boolean(value, nm_setting_ppp_get_nobsdcomp(setting)); + break; + case PROP_NODEFLATE: + g_value_set_boolean(value, nm_setting_ppp_get_nodeflate(setting)); + break; + case PROP_NO_VJ_COMP: + g_value_set_boolean(value, nm_setting_ppp_get_no_vj_comp(setting)); + break; + case PROP_REQUIRE_MPPE: + g_value_set_boolean(value, nm_setting_ppp_get_require_mppe(setting)); + break; + case PROP_REQUIRE_MPPE_128: + g_value_set_boolean(value, nm_setting_ppp_get_require_mppe_128(setting)); + break; + case PROP_MPPE_STATEFUL: + g_value_set_boolean(value, nm_setting_ppp_get_mppe_stateful(setting)); + break; + case PROP_CRTSCTS: + g_value_set_boolean(value, nm_setting_ppp_get_crtscts(setting)); + break; + case PROP_BAUD: + g_value_set_uint(value, nm_setting_ppp_get_baud(setting)); + break; + case PROP_MRU: + g_value_set_uint(value, nm_setting_ppp_get_mru(setting)); + break; + case PROP_MTU: + g_value_set_uint(value, nm_setting_ppp_get_mtu(setting)); + break; + case PROP_LCP_ECHO_FAILURE: + g_value_set_uint(value, nm_setting_ppp_get_lcp_echo_failure(setting)); + break; + case PROP_LCP_ECHO_INTERVAL: + g_value_set_uint(value, nm_setting_ppp_get_lcp_echo_interval(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingPppPrivate *priv = NM_SETTING_PPP_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_NOAUTH: + priv->noauth = g_value_get_boolean(value); + break; + case PROP_REFUSE_EAP: + priv->refuse_eap = g_value_get_boolean(value); + break; + case PROP_REFUSE_PAP: + priv->refuse_pap = g_value_get_boolean(value); + break; + case PROP_REFUSE_CHAP: + priv->refuse_chap = g_value_get_boolean(value); + break; + case PROP_REFUSE_MSCHAP: + priv->refuse_mschap = g_value_get_boolean(value); + break; + case PROP_REFUSE_MSCHAPV2: + priv->refuse_mschapv2 = g_value_get_boolean(value); + break; + case PROP_NOBSDCOMP: + priv->nobsdcomp = g_value_get_boolean(value); + break; + case PROP_NODEFLATE: + priv->nodeflate = g_value_get_boolean(value); + break; + case PROP_NO_VJ_COMP: + priv->no_vj_comp = g_value_get_boolean(value); + break; + case PROP_REQUIRE_MPPE: + priv->require_mppe = g_value_get_boolean(value); + break; + case PROP_REQUIRE_MPPE_128: + priv->require_mppe_128 = g_value_get_boolean(value); + break; + case PROP_MPPE_STATEFUL: + priv->mppe_stateful = g_value_get_boolean(value); + break; + case PROP_CRTSCTS: + priv->crtscts = g_value_get_boolean(value); + break; + case PROP_BAUD: + priv->baud = g_value_get_uint(value); + break; + case PROP_MRU: + priv->mru = g_value_get_uint(value); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + case PROP_LCP_ECHO_FAILURE: + priv->lcp_echo_failure = g_value_get_uint(value); + break; + case PROP_LCP_ECHO_INTERVAL: + priv->lcp_echo_interval = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_ppp_init(NMSettingPpp *self) +{ + NMSettingPppPrivate *priv = NM_SETTING_PPP_GET_PRIVATE(self); + + priv->noauth = TRUE; +} + +/** + * nm_setting_ppp_new: + * + * Creates a new #NMSettingPpp object with default values. + * + * Returns: (transfer full): the new empty #NMSettingPpp object + **/ +NMSetting * +nm_setting_ppp_new(void) +{ + return g_object_new(NM_TYPE_SETTING_PPP, NULL); +} + +static void +nm_setting_ppp_class_init(NMSettingPppClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingPppPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + setting_class->verify = verify; + + /** + * NMSettingPpp:noauth: + * + * If %TRUE, do not require the other side (usually the PPP server) to + * authenticate itself to the client. If %FALSE, require authentication + * from the remote side. In almost all cases, this should be %TRUE. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_NOAUTH, + PROP_NOAUTH, + TRUE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_noauth); + + /** + * NMSettingPpp:refuse-eap: + * + * If %TRUE, the EAP authentication method will not be used. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_REFUSE_EAP, + PROP_REFUSE_EAP, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_refuse_eap); + + /** + * NMSettingPpp:refuse-pap: + * + * If %TRUE, the PAP authentication method will not be used. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_REFUSE_PAP, + PROP_REFUSE_PAP, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_refuse_pap); + + /** + * NMSettingPpp:refuse-chap: + * + * If %TRUE, the CHAP authentication method will not be used. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_REFUSE_CHAP, + PROP_REFUSE_CHAP, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_refuse_chap); + + /** + * NMSettingPpp:refuse-mschap: + * + * If %TRUE, the MSCHAP authentication method will not be used. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_REFUSE_MSCHAP, + PROP_REFUSE_MSCHAP, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_refuse_mschap); + + /** + * NMSettingPpp:refuse-mschapv2: + * + * If %TRUE, the MSCHAPv2 authentication method will not be used. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_REFUSE_MSCHAPV2, + PROP_REFUSE_MSCHAPV2, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_refuse_mschapv2); + + /** + * NMSettingPpp:nobsdcomp: + * + * If %TRUE, BSD compression will not be requested. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_NOBSDCOMP, + PROP_NOBSDCOMP, + FALSE, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_ppp_get_nobsdcomp); + + /** + * NMSettingPpp:nodeflate: + * + * If %TRUE, "deflate" compression will not be requested. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_NODEFLATE, + PROP_NODEFLATE, + FALSE, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_ppp_get_nodeflate); + + /** + * NMSettingPpp:no-vj-comp: + * + * If %TRUE, Van Jacobsen TCP header compression will not be requested. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_NO_VJ_COMP, + PROP_NO_VJ_COMP, + FALSE, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_ppp_get_no_vj_comp); + + /** + * NMSettingPpp:require-mppe: + * + * If %TRUE, MPPE (Microsoft Point-to-Point Encryption) will be required for + * the PPP session. If either 64-bit or 128-bit MPPE is not available the + * session will fail. Note that MPPE is not used on mobile broadband + * connections. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_REQUIRE_MPPE, + PROP_REQUIRE_MPPE, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_require_mppe); + + /** + * NMSettingPpp:require-mppe-128: + * + * If %TRUE, 128-bit MPPE (Microsoft Point-to-Point Encryption) will be + * required for the PPP session, and the "require-mppe" property must also + * be set to %TRUE. If 128-bit MPPE is not available the session will fail. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_REQUIRE_MPPE_128, + PROP_REQUIRE_MPPE_128, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_require_mppe_128); + + /** + * NMSettingPpp:mppe-stateful: + * + * If %TRUE, stateful MPPE is used. See pppd documentation for more + * information on stateful MPPE. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_MPPE_STATEFUL, + PROP_MPPE_STATEFUL, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_mppe_stateful); + + /** + * NMSettingPpp:crtscts: + * + * If %TRUE, specify that pppd should set the serial port to use hardware + * flow control with RTS and CTS signals. This value should normally be set + * to %FALSE. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PPP_CRTSCTS, + PROP_CRTSCTS, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_ppp_get_crtscts); + + /** + * NMSettingPpp:baud: + * + * If non-zero, instruct pppd to set the serial port to the specified + * baudrate. This value should normally be left as 0 to automatically + * choose the speed. + **/ + obj_properties[PROP_BAUD] = g_param_spec_uint(NM_SETTING_PPP_BAUD, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingPpp:mru: + * + * If non-zero, instruct pppd to request that the peer send packets no + * larger than the specified size. If non-zero, the MRU should be between + * 128 and 16384. + */ + obj_properties[PROP_MRU] = g_param_spec_uint(NM_SETTING_PPP_MRU, + "", + "", + 0, + 16384, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingPpp:mtu: + * + * If non-zero, instruct pppd to send packets no larger than the specified + * size. + **/ + obj_properties[PROP_MTU] = g_param_spec_uint(NM_SETTING_PPP_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingPpp:lcp-echo-failure: + * + * If non-zero, instruct pppd to presume the connection to the peer has + * failed if the specified number of LCP echo-requests go unanswered by the + * peer. The "lcp-echo-interval" property must also be set to a non-zero + * value if this property is used. + **/ + obj_properties[PROP_LCP_ECHO_FAILURE] = g_param_spec_uint( + NM_SETTING_PPP_LCP_ECHO_FAILURE, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingPpp:lcp-echo-interval: + * + * If non-zero, instruct pppd to send an LCP echo-request frame to the peer + * every n seconds (where n is the specified value). Note that some PPP + * peers will respond to echo requests and some will not, and it is not + * possible to autodetect this. + **/ + obj_properties[PROP_LCP_ECHO_INTERVAL] = g_param_spec_uint( + NM_SETTING_PPP_LCP_ECHO_INTERVAL, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_PPP, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-pppoe.c b/src/libnm-core-impl/nm-setting-pppoe.c new file mode 100644 index 00000000..12beb51a --- /dev/null +++ b/src/libnm-core-impl/nm-setting-pppoe.c @@ -0,0 +1,374 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-pppoe.h" + +#include "nm-setting-ppp.h" +#include "nm-setting-private.h" +#include "nm-core-enum-types.h" + +/** + * SECTION:nm-setting-pppoe + * @short_description: Describes PPPoE connection properties + * + * The #NMSettingPppoe object is a #NMSetting subclass that describes + * properties necessary for connection to networks that require PPPoE connections + * to provide IP transport, for example cable or DSL modems. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT, + PROP_SERVICE, + PROP_USERNAME, + PROP_PASSWORD, + PROP_PASSWORD_FLAGS, ); + +typedef struct { + char * parent; + char * service; + char * username; + char * password; + NMSettingSecretFlags password_flags; +} NMSettingPppoePrivate; + +/** + * NMSettingPppoe: + * + * PPP-over-Ethernet Settings + */ +struct _NMSettingPppoe { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingPppoeClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingPppoe, nm_setting_pppoe, NM_TYPE_SETTING) + +#define NM_SETTING_PPPOE_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_PPPOE, NMSettingPppoePrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_pppoe_get_parent: + * @setting: the #NMSettingPppoe + * + * Returns: the #NMSettingPppoe:parent property of the setting + * + * Since: 1.10 + **/ +const char * +nm_setting_pppoe_get_parent(NMSettingPppoe *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPPOE(setting), NULL); + + return NM_SETTING_PPPOE_GET_PRIVATE(setting)->parent; +} + +/** + * nm_setting_pppoe_get_service: + * @setting: the #NMSettingPppoe + * + * Returns: the #NMSettingPppoe:service property of the setting + **/ +const char * +nm_setting_pppoe_get_service(NMSettingPppoe *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPPOE(setting), NULL); + + return NM_SETTING_PPPOE_GET_PRIVATE(setting)->service; +} + +/** + * nm_setting_pppoe_get_username: + * @setting: the #NMSettingPppoe + * + * Returns: the #NMSettingPppoe:username property of the setting + **/ +const char * +nm_setting_pppoe_get_username(NMSettingPppoe *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPPOE(setting), NULL); + + return NM_SETTING_PPPOE_GET_PRIVATE(setting)->username; +} + +/** + * nm_setting_pppoe_get_password: + * @setting: the #NMSettingPppoe + * + * Returns: the #NMSettingPppoe:password property of the setting + **/ +const char * +nm_setting_pppoe_get_password(NMSettingPppoe *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPPOE(setting), NULL); + + return NM_SETTING_PPPOE_GET_PRIVATE(setting)->password; +} + +/** + * nm_setting_pppoe_get_password_flags: + * @setting: the #NMSettingPppoe + * + * Returns: the #NMSettingSecretFlags pertaining to the #NMSettingPppoe:password + **/ +NMSettingSecretFlags +nm_setting_pppoe_get_password_flags(NMSettingPppoe *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PPPOE(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_PPPOE_GET_PRIVATE(setting)->password_flags; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingPppoePrivate *priv = NM_SETTING_PPPOE_GET_PRIVATE(setting); + gs_free_error GError *local_error = NULL; + + if (!priv->username) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_PPPOE_SETTING_NAME, NM_SETTING_PPPOE_USERNAME); + return FALSE; + } else if (!strlen(priv->username)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_PPPOE_SETTING_NAME, NM_SETTING_PPPOE_USERNAME); + return FALSE; + } + + if (priv->service && !strlen(priv->service)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_PPPOE_SETTING_NAME, NM_SETTING_PPPOE_SERVICE); + return FALSE; + } + + if (priv->parent && !nm_utils_ifname_valid_kernel(priv->parent, &local_error)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + "'%s': %s", + priv->parent, + local_error->message); + g_prefix_error(error, "%s.%s: ", NM_SETTING_PPPOE_SETTING_NAME, NM_SETTING_PPPOE_PARENT); + return FALSE; + } + + return TRUE; +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSettingPppoePrivate *priv = NM_SETTING_PPPOE_GET_PRIVATE(setting); + GPtrArray * secrets = NULL; + + if (priv->password) + return NULL; + + if (!(priv->password_flags & NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + secrets = g_ptr_array_sized_new(1); + g_ptr_array_add(secrets, NM_SETTING_PPPOE_PASSWORD); + } + + return secrets; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingPppoe *setting = NM_SETTING_PPPOE(object); + + switch (prop_id) { + case PROP_PARENT: + g_value_set_string(value, nm_setting_pppoe_get_parent(setting)); + break; + case PROP_SERVICE: + g_value_set_string(value, nm_setting_pppoe_get_service(setting)); + break; + case PROP_USERNAME: + g_value_set_string(value, nm_setting_pppoe_get_username(setting)); + break; + case PROP_PASSWORD: + g_value_set_string(value, nm_setting_pppoe_get_password(setting)); + break; + case PROP_PASSWORD_FLAGS: + g_value_set_flags(value, nm_setting_pppoe_get_password_flags(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingPppoePrivate *priv = NM_SETTING_PPPOE_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_PARENT: + g_free(priv->parent); + priv->parent = g_value_dup_string(value); + break; + case PROP_SERVICE: + g_free(priv->service); + priv->service = g_value_dup_string(value); + break; + case PROP_USERNAME: + g_free(priv->username); + priv->username = g_value_dup_string(value); + break; + case PROP_PASSWORD: + g_free(priv->password); + priv->password = g_value_dup_string(value); + break; + case PROP_PASSWORD_FLAGS: + priv->password_flags = g_value_get_flags(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_pppoe_init(NMSettingPppoe *setting) +{} + +/** + * nm_setting_pppoe_new: + * + * Creates a new #NMSettingPppoe object with default values. + * + * Returns: (transfer full): the new empty #NMSettingPppoe object + **/ +NMSetting * +nm_setting_pppoe_new(void) +{ + return g_object_new(NM_TYPE_SETTING_PPPOE, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingPppoePrivate *priv = NM_SETTING_PPPOE_GET_PRIVATE(object); + + g_free(priv->parent); + g_free(priv->username); + g_free(priv->password); + g_free(priv->service); + + G_OBJECT_CLASS(nm_setting_pppoe_parent_class)->finalize(object); +} + +static void +nm_setting_pppoe_class_init(NMSettingPppoeClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NMSettingPppoePrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->need_secrets = need_secrets; + + /** + * NMSettingPppoe:parent: + * + * If given, specifies the parent interface name on which this PPPoE + * connection should be created. If this property is not specified, + * the connection is activated on the interface specified in + * #NMSettingConnection:interface-name of #NMSettingConnection. + * + * Since: 1.10 + **/ + obj_properties[PROP_PARENT] = g_param_spec_string( + NM_SETTING_PPPOE_PARENT, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingPppoe:service: + * + * If specified, instruct PPPoE to only initiate sessions with access + * concentrators that provide the specified service. For most providers, + * this should be left blank. It is only required if there are multiple + * access concentrators or a specific service is known to be required. + **/ + obj_properties[PROP_SERVICE] = g_param_spec_string(NM_SETTING_PPPOE_SERVICE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingPppoe:username: + * + * Username used to authenticate with the PPPoE service. + **/ + obj_properties[PROP_USERNAME] = g_param_spec_string(NM_SETTING_PPPOE_USERNAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingPppoe:password: + * + * Password used to authenticate with the PPPoE service. + **/ + obj_properties[PROP_PASSWORD] = + g_param_spec_string(NM_SETTING_PPPOE_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingPppoe:password-flags: + * + * Flags indicating how to handle the #NMSettingPppoe:password property. + **/ + obj_properties[PROP_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_PPPOE_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_PPPOE); +} diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h new file mode 100644 index 00000000..061fc901 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-private.h @@ -0,0 +1,525 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 Red Hat, Inc. + */ + +#ifndef __NM_SETTING_PRIVATE_H__ +#define __NM_SETTING_PRIVATE_H__ + +#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE) + #error Cannot use this header. +#endif + +#include "nm-setting.h" +#include "nm-setting-bridge.h" +#include "nm-connection.h" +#include "nm-core-enum-types.h" + +#include "libnm-core-intern/nm-core-internal.h" + +/*****************************************************************************/ + +typedef struct { + NMConnection *self; + + NMSetting *settings[_NM_META_SETTING_TYPE_NUM]; + + /* D-Bus path of the connection, if any */ + char *path; +} NMConnectionPrivate; + +extern GTypeClass *_nm_simple_connection_class_instance; +extern int _nm_simple_connection_private_offset; + +void _nm_connection_private_clear(NMConnectionPrivate *priv); + +/*****************************************************************************/ + +/** + * NMSetting: + * + * The NMSetting struct contains only private data. + * It should only be accessed through the functions described below. + */ +struct _NMSetting { + GObject parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingClass { + GObjectClass parent; + + /* In the past, this struct was public API. Preserve ABI! */ + + int (*verify)(NMSetting *setting, NMConnection *connection, GError **error); + + gboolean (*verify_secrets)(NMSetting *setting, NMConnection *connection, GError **error); + + GPtrArray *(*need_secrets)(NMSetting *setting); + + int (*update_one_secret)(NMSetting *setting, const char *key, GVariant *value, GError **error); + + gboolean (*get_secret_flags)(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags *out_flags, + GError ** error); + + gboolean (*set_secret_flags)(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags flags, + GError ** error); + + gboolean (*clear_secrets)(const struct _NMSettInfoSetting *sett_info, + guint property_idx, + NMSetting * setting, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data); + + /* compare_property() returns a ternary, where DEFAULT means that the property should not + * be compared due to the compare @flags. A TRUE/FALSE result means that the property is + * equal/not-equal. + * + * @other may be %NULL, in which case the function only determines whether + * the setting should be compared (TRUE) or not (DEFAULT). */ + NMTernary (*compare_property)(const struct _NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags); + + void (*duplicate_copy_properties)(const struct _NMSettInfoSetting *sett_info, + NMSetting * src, + NMSetting * dst); + + void (*enumerate_values)(const struct _NMSettInfoProperty *property_info, + NMSetting * setting, + NMSettingValueIterFn func, + gpointer user_data); + + gboolean (*aggregate)(NMSetting *setting, int type_i, gpointer arg); + + void (*for_each_secret)(NMSetting * setting, + const char * secret_name, + GVariant * val, + gboolean remove_non_secrets, + _NMConnectionForEachSecretFunc callback, + gpointer callback_data, + GVariantBuilder * setting_builder); + + gboolean (*init_from_dbus)(NMSetting * setting, + GHashTable * keys, + GVariant * setting_dict, + GVariant * connection_dict, + guint /* NMSettingParseFlags */ parse_flags, + GError ** error); + + gpointer padding[1]; + + const struct _NMMetaSettingInfo *setting_info; +}; + +/*****************************************************************************/ + +/** + * NMSettingIPConfig: + */ +struct _NMSettingIPConfig { + NMSetting parent; +}; + +struct _NMSettingIPConfigClass { + NMSettingClass parent; + + /* Padding for future expansion */ + gpointer padding[8]; +}; + +/*****************************************************************************/ + +NMSettingPriority _nm_setting_get_base_type_priority(NMSetting *setting); +int _nm_setting_compare_priority(gconstpointer a, gconstpointer b); + +int _nmtst_nm_setting_sort(NMSetting *a, NMSetting *b); + +/*****************************************************************************/ + +#define _nm_assert_setting_info(setting_info, gtype) \ + G_STMT_START \ + { \ + const NMMetaSettingInfo *_setting_info = (setting_info); \ + \ + if (NM_MORE_ASSERTS > 0) { \ + GType _gtype = (gtype); \ + \ + nm_assert(_setting_info); \ + nm_assert(_NM_INT_NOT_NEGATIVE(_setting_info->meta_type)); \ + nm_assert(_setting_info->meta_type < _NM_META_SETTING_TYPE_NUM); \ + nm_assert(_setting_info->get_setting_gtype); \ + if (_gtype != 0) \ + nm_assert(_setting_info->get_setting_gtype() == _gtype); \ + else \ + _gtype = _setting_info->get_setting_gtype(); \ + nm_assert(g_type_is_a(_gtype, NM_TYPE_SETTING)); \ + } \ + } \ + G_STMT_END + +static inline const NMMetaSettingInfo * +_nm_meta_setting_info_from_class(NMSettingClass *klass) +{ + const NMMetaSettingInfo *setting_info; + + if (!NM_IS_SETTING_CLASS(klass)) + return NULL; + + setting_info = klass->setting_info; + if (!setting_info) + return NULL; + + _nm_assert_setting_info(setting_info, G_OBJECT_CLASS_TYPE(klass)); + return setting_info; +} + +static inline const NMMetaSettingInfo * +_nm_meta_setting_info_from_gtype(GType gtype) +{ + const NMMetaSettingInfo *setting_info; + + setting_info = nm_meta_setting_infos_by_gtype(gtype); + if (!setting_info) + return NULL; + + _nm_assert_setting_info(setting_info, gtype); + return setting_info; +} + +/*****************************************************************************/ + +void _nm_setting_emit_property_changed(NMSetting *setting); + +typedef enum NMSettingUpdateSecretResult { + NM_SETTING_UPDATE_SECRET_ERROR = FALSE, + NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED = TRUE, + NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED = 2, +} NMSettingUpdateSecretResult; + +NMSettingUpdateSecretResult + _nm_setting_update_secrets(NMSetting *setting, GVariant *secrets, GError **error); +gboolean _nm_setting_clear_secrets(NMSetting * setting, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data); + +/*****************************************************************************/ + +#define NM_SETTING_PARAM_NONE 0 + +/* The property of the #NMSetting should be considered during comparisons that + * use the %NM_SETTING_COMPARE_FLAG_INFERRABLE flag. Properties that don't have + * this flag, are ignored when doing an infrerrable comparison. This flag should + * be set on all properties that are read from the kernel or the system when a + * connection is generated. eg, IP addresses/routes can be read from the + * kernel, but the 'autoconnect' property cannot, so + * %NM_SETTING_IP4_CONFIG_ADDRESSES gets the INFERRABLE flag, but + * %NM_SETTING_CONNECTION_AUTOCONNECT would not. + * + * This flag should not be used with properties where the default cannot be + * read separately from the current value, like MTU or wired duplex mode. + */ +#define NM_SETTING_PARAM_INFERRABLE (1 << (4 + G_PARAM_USER_SHIFT)) + +/* This is a legacy property, which clients should not send to the daemon. */ +#define NM_SETTING_PARAM_LEGACY (1 << (5 + G_PARAM_USER_SHIFT)) + +/* When a connection is active and gets modified, usually the change + * to the settings-connection does not propagate automatically to the + * applied-connection of the device. For certain properties like the + * firewall zone and the metered property, this is different. + * + * Such fields can be ignored during nm_connection_compare() with the + * NMSettingCompareFlag NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY. + */ +#define NM_SETTING_PARAM_REAPPLY_IMMEDIATELY (1 << (6 + G_PARAM_USER_SHIFT)) + +/* property_to_dbus() should ignore the property flags, and instead always calls to_dbus_fcn() + */ +#define NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS (1 << (7 + G_PARAM_USER_SHIFT)) + +extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name; +extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i; +extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_u; + +extern const NMSettInfoPropertType nm_sett_info_propert_type_plain_i; +extern const NMSettInfoPropertType nm_sett_info_propert_type_plain_u; + +extern const NMSettInfoPropertType nm_sett_info_propert_type_boolean; +extern const NMSettInfoPropertType nm_sett_info_propert_type_string; + +NMSettingVerifyResult +_nm_setting_verify(NMSetting *setting, NMConnection *connection, GError **error); + +gboolean _nm_setting_verify_secret_string(const char *str, + const char *setting_name, + const char *property, + GError ** error); + +gboolean _nm_setting_aggregate(NMSetting *setting, NMConnectionAggregateType type, gpointer arg); + +gboolean _nm_setting_slave_type_is_valid(const char *slave_type, const char **out_port_type); + +GVariant *_nm_setting_property_to_dbus_fcn_gprop(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options); + +GVariant * +_nm_setting_property_to_dbus_fcn_get_boolean(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options); + +GVariant * +_nm_setting_property_to_dbus_fcn_get_string(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options); + +GVariant *_nm_setting_to_dbus(NMSetting * setting, + NMConnection * connection, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options); + +NMSetting *_nm_setting_new_from_dbus(GType setting_type, + GVariant * setting_dict, + GVariant * connection_dict, + NMSettingParseFlags parse_flags, + GError ** error); + +gboolean _nm_setting_property_is_regular_secret(NMSetting *setting, const char *secret_name); +gboolean _nm_setting_property_is_regular_secret_flags(NMSetting * setting, + const char *secret_flags_name); + +/*****************************************************************************/ + +static inline GArray * +_nm_sett_info_property_override_create_array(void) +{ + return g_array_new(FALSE, FALSE, sizeof(NMSettInfoProperty)); +} + +GArray *_nm_sett_info_property_override_create_array_ip_config(void); + +void _nm_setting_class_commit_full(NMSettingClass * setting_class, + NMMetaSettingType meta_type, + const NMSettInfoSettDetail *detail, + GArray * properties_override); + +static inline void +_nm_setting_class_commit(NMSettingClass *setting_class, NMMetaSettingType meta_type) +{ + _nm_setting_class_commit_full(setting_class, meta_type, NULL, NULL); +} + +#define NM_SETT_INFO_SETT_GENDATA(...) \ + ({ \ + static const NMSettInfoSettGendata _g = {__VA_ARGS__}; \ + \ + &_g; \ + }) + +#define NM_SETT_INFO_SETT_DETAIL(...) (&((const NMSettInfoSettDetail){__VA_ARGS__})) + +#define NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(_dbus_type, ...) \ + { \ + .dbus_type = _dbus_type, __VA_ARGS__ \ + } + +#define NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT(_dbus_type, ...) \ + { \ + .dbus_type = _dbus_type, .to_dbus_fcn = _nm_setting_property_to_dbus_fcn_gprop, \ + __VA_ARGS__ \ + } + +#define NM_SETT_INFO_PROPERT_TYPE(init) \ + ({ \ + static const NMSettInfoPropertType _g = init; \ + \ + &_g; \ + }) + +#define NM_SETT_INFO_PROPERT_TYPE_DBUS(_dbus_type, ...) \ + NM_SETT_INFO_PROPERT_TYPE(NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(_dbus_type, __VA_ARGS__)) + +#define NM_SETT_INFO_PROPERT_TYPE_GPROP(_dbus_type, ...) \ + NM_SETT_INFO_PROPERT_TYPE(NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT(_dbus_type, __VA_ARGS__)) + +#define NM_SETT_INFO_PROPERTY(...) (&((const NMSettInfoProperty){__VA_ARGS__})) + +gboolean _nm_properties_override_assert(const NMSettInfoProperty *prop_info); + +static inline void +_nm_properties_override(GArray *properties_override, const NMSettInfoProperty *prop_info) +{ + nm_assert(properties_override); + nm_assert(_nm_properties_override_assert(prop_info)); + g_array_append_vals(properties_override, prop_info, 1); +} + +#define _nm_properties_override_gobj(properties_override, p_param_spec, p_property_type, ...) \ + _nm_properties_override((properties_override), \ + NM_SETT_INFO_PROPERTY(.name = NULL, \ + .param_spec = (p_param_spec), \ + .property_type = (p_property_type), \ + __VA_ARGS__)) + +#define _nm_properties_override_dbus(properties_override, p_name, p_property_type) \ + _nm_properties_override( \ + (properties_override), \ + NM_SETT_INFO_PROPERTY(.name = ("" p_name ""), .property_type = (p_property_type), )) + +/*****************************************************************************/ + +#define _nm_setting_property_define_boolean_full(properties_override, \ + obj_properties, \ + prop_name, \ + prop_id, \ + default_value, \ + param_flags, \ + property_type, \ + get_fcn, \ + ...) \ + G_STMT_START \ + { \ + const gboolean _default_value = (default_value); \ + GParamSpec * _param_spec; \ + const NMSettInfoPropertType *const _property_type = (property_type); \ + \ + G_STATIC_ASSERT( \ + !NM_FLAGS_ANY((param_flags), \ + ~(NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_INFERRABLE \ + | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY))); \ + \ + nm_assert(_property_type); \ + nm_assert(_property_type->to_dbus_fcn == _nm_setting_property_to_dbus_fcn_get_boolean); \ + \ + nm_assert(NM_IN_SET(_default_value, 0, 1)); \ + \ + _param_spec = \ + g_param_spec_boolean("" prop_name "", \ + "", \ + "", \ + _default_value, \ + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags)); \ + \ + (obj_properties)[(prop_id)] = _param_spec; \ + \ + _nm_properties_override_gobj((properties_override), \ + _param_spec, \ + _property_type, \ + .to_dbus_data.get_boolean = \ + (gboolean(*)(NMSetting *)) (get_fcn), \ + __VA_ARGS__); \ + } \ + G_STMT_END + +#define _nm_setting_property_define_boolean(properties_override, \ + obj_properties, \ + prop_name, \ + prop_id, \ + default_value, \ + param_flags, \ + get_fcn, \ + ...) \ + _nm_setting_property_define_boolean_full((properties_override), \ + (obj_properties), \ + prop_name, \ + (prop_id), \ + (default_value), \ + (param_flags), \ + &nm_sett_info_propert_type_boolean, \ + (get_fcn), \ + __VA_ARGS__) + +/*****************************************************************************/ + +#define _nm_setting_property_define_string_full(properties_override, \ + obj_properties, \ + prop_name, \ + prop_id, \ + param_flags, \ + property_type, \ + get_fcn, \ + ...) \ + G_STMT_START \ + { \ + GParamSpec * _param_spec; \ + const NMSettInfoPropertType *const _property_type = (property_type); \ + \ + G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags), \ + ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_FUZZY_IGNORE \ + | NM_SETTING_PARAM_INFERRABLE \ + | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY))); \ + nm_assert(_property_type); \ + nm_assert(_property_type->to_dbus_fcn == _nm_setting_property_to_dbus_fcn_get_string); \ + \ + _param_spec = \ + g_param_spec_string("" prop_name "", \ + "", \ + "", \ + NULL, \ + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags)); \ + \ + (obj_properties)[(prop_id)] = _param_spec; \ + \ + _nm_properties_override_gobj((properties_override), \ + _param_spec, \ + _property_type, \ + .to_dbus_data.get_string = \ + (const char *(*) (NMSetting *) ) (get_fcn), \ + __VA_ARGS__); \ + } \ + G_STMT_END + +#define _nm_setting_property_define_string(properties_override, \ + obj_properties, \ + prop_name, \ + prop_id, \ + param_flags, \ + get_fcn, \ + ...) \ + _nm_setting_property_define_string_full((properties_override), \ + (obj_properties), \ + prop_name, \ + (prop_id), \ + (param_flags), \ + &nm_sett_info_propert_type_string, \ + (get_fcn), \ + __VA_ARGS__) + +/*****************************************************************************/ + +gboolean _nm_setting_use_legacy_property(NMSetting * setting, + GVariant * connection_dict, + const char *legacy_property, + const char *new_property); + +GPtrArray *_nm_setting_need_secrets(NMSetting *setting); + +gboolean _nm_setting_should_compare_secret_property(NMSetting * setting, + NMSetting * other, + const char * secret_name, + NMSettingCompareFlags flags); + +NMBridgeVlan *_nm_bridge_vlan_dup(const NMBridgeVlan *vlan); +NMBridgeVlan *_nm_bridge_vlan_dup_and_seal(const NMBridgeVlan *vlan); + +/*****************************************************************************/ + +#endif /* NM_SETTING_PRIVATE_H */ diff --git a/src/libnm-core-impl/nm-setting-proxy.c b/src/libnm-core-impl/nm-setting-proxy.c new file mode 100644 index 00000000..fd09e1d4 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-proxy.c @@ -0,0 +1,403 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2016 Atul Anand . + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-proxy.h" + +#include "nm-utils.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-proxy + * @short_description: Describes proxy URL, script and other related properties + * + * The #NMSettingProxy object is a #NMSetting subclass that describes properties + * related to Proxy settings like PAC URL, PAC script etc. + * + * NetworkManager support 2 values for the #NMSettingProxy:method property for + * proxy. If "auto" is specified then WPAD takes place and the appropriate details + * are pushed into PacRunner or user can override this URL with a new PAC URL or a + * PAC script. If "none" is selected then no proxy configuration is given to PacRunner + * to fulfill client queries. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_METHOD, PROP_BROWSER_ONLY, PROP_PAC_URL, PROP_PAC_SCRIPT, ); + +typedef struct { + char *pac_url; + char *pac_script; + int method; + bool browser_only : 1; +} NMSettingProxyPrivate; + +/** + * NMSettingProxy: + * + * WWW Proxy Settings + */ +struct _NMSettingProxy { + NMSetting parent; +}; + +struct _NMSettingProxyClass { + NMSettingClass parent; + + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingProxy, nm_setting_proxy, NM_TYPE_SETTING) + +#define NM_SETTING_PROXY_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_PROXY, NMSettingProxyPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_proxy_get_method: + * @setting: the #NMSettingProxy + * + * Returns the proxy configuration method. By default the value is %NM_SETTING_PROXY_METHOD_NONE. + * %NM_SETTING_PROXY_METHOD_NONE should be selected for a connection intended for direct network + * access. + * + * Returns: the proxy configuration method + * + * Since: 1.6 + **/ +NMSettingProxyMethod +nm_setting_proxy_get_method(NMSettingProxy *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PROXY(setting), NM_SETTING_PROXY_METHOD_NONE); + + return NM_SETTING_PROXY_GET_PRIVATE(setting)->method; +} + +/** + * nm_setting_proxy_get_browser_only: + * @setting: the #NMSettingProxy + * + * Returns: %TRUE if this proxy configuration is only for browser + * clients/schemes, %FALSE otherwise. + * + * Since: 1.6 + **/ +gboolean +nm_setting_proxy_get_browser_only(NMSettingProxy *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PROXY(setting), FALSE); + + return NM_SETTING_PROXY_GET_PRIVATE(setting)->browser_only; +} + +/** + * nm_setting_proxy_get_pac_url: + * @setting: the #NMSettingProxy + * + * Returns: the PAC URL for obtaining PAC file + * + * Since: 1.6 + **/ +const char * +nm_setting_proxy_get_pac_url(NMSettingProxy *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PROXY(setting), NULL); + + return NM_SETTING_PROXY_GET_PRIVATE(setting)->pac_url; +} + +/** + * nm_setting_proxy_get_pac_script: + * @setting: the #NMSettingProxy + * + * Returns: the PAC script + * + * Since: 1.6 + **/ +const char * +nm_setting_proxy_get_pac_script(NMSettingProxy *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_PROXY(setting), NULL); + + return NM_SETTING_PROXY_GET_PRIVATE(setting)->pac_script; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingProxyPrivate *priv = NM_SETTING_PROXY_GET_PRIVATE(setting); + + if (!NM_IN_SET(priv->method, NM_SETTING_PROXY_METHOD_NONE, NM_SETTING_PROXY_METHOD_AUTO)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid proxy method")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_PROXY_SETTING_NAME, NM_SETTING_PROXY_PAC_URL); + return FALSE; + } + + if (priv->method != NM_SETTING_PROXY_METHOD_AUTO) { + if (priv->pac_url) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("this property is not allowed for method none")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_PROXY_PAC_URL); + return FALSE; + } + + if (priv->pac_script) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("this property is not allowed for method none")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_PROXY_PAC_SCRIPT); + return FALSE; + } + } + + if (priv->pac_script) { + if (strlen(priv->pac_script) > 1 * 1024 * 1024) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the script is too large")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_PROXY_PAC_SCRIPT); + return FALSE; + } + if (!g_utf8_validate(priv->pac_script, -1, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the script is not valid utf8")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_PROXY_PAC_SCRIPT); + return FALSE; + } + if (!strstr(priv->pac_script, "FindProxyForURL")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the script lacks FindProxyForURL function")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_PROXY_PAC_SCRIPT); + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingProxy *setting = NM_SETTING_PROXY(object); + + switch (prop_id) { + case PROP_METHOD: + g_value_set_int(value, nm_setting_proxy_get_method(setting)); + break; + case PROP_BROWSER_ONLY: + g_value_set_boolean(value, nm_setting_proxy_get_browser_only(setting)); + break; + case PROP_PAC_URL: + g_value_set_string(value, nm_setting_proxy_get_pac_url(setting)); + break; + case PROP_PAC_SCRIPT: + g_value_set_string(value, nm_setting_proxy_get_pac_script(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingProxyPrivate *priv = NM_SETTING_PROXY_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_METHOD: + priv->method = g_value_get_int(value); + break; + case PROP_BROWSER_ONLY: + priv->browser_only = g_value_get_boolean(value); + break; + case PROP_PAC_URL: + g_free(priv->pac_url); + priv->pac_url = g_value_dup_string(value); + break; + case PROP_PAC_SCRIPT: + g_free(priv->pac_script); + priv->pac_script = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_proxy_init(NMSettingProxy *self) +{ + nm_assert(NM_SETTING_PROXY_GET_PRIVATE(self)->method == NM_SETTING_PROXY_METHOD_NONE); +} + +/** + * nm_setting_proxy_new: + * + * Creates a new #NMSettingProxy object. + * + * Returns: the new empty #NMSettingProxy object + * + * Since: 1.6 + **/ +NMSetting * +nm_setting_proxy_new(void) +{ + return g_object_new(NM_TYPE_SETTING_PROXY, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingProxy * self = NM_SETTING_PROXY(object); + NMSettingProxyPrivate *priv = NM_SETTING_PROXY_GET_PRIVATE(self); + + g_free(priv->pac_url); + g_free(priv->pac_script); + + G_OBJECT_CLASS(nm_setting_proxy_parent_class)->finalize(object); +} + +static void +nm_setting_proxy_class_init(NMSettingProxyClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingProxyPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingProxy:method: + * + * Method for proxy configuration, Default is %NM_SETTING_PROXY_METHOD_NONE + * + * Since: 1.6 + **/ + /* ---ifcfg-rh--- + * property: method + * variable: PROXY_METHOD(+) + * default: none + * description: Method for proxy configuration. For "auto", WPAD is used for + * proxy configuration, or set the PAC file via PAC_URL or PAC_SCRIPT. + * values: none, auto + * ---end--- + */ + obj_properties[PROP_METHOD] = g_param_spec_int(NM_SETTING_PROXY_METHOD, + "", + "", + G_MININT32, + G_MAXINT32, + NM_SETTING_PROXY_METHOD_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingProxy:browser-only: + * + * Whether the proxy configuration is for browser only. + * + * Since: 1.6 + **/ + /* ---ifcfg-rh--- + * property: browser-only + * variable: BROWSER_ONLY(+) + * default: no + * description: Whether the proxy configuration is for browser only. + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_PROXY_BROWSER_ONLY, + PROP_BROWSER_ONLY, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_proxy_get_browser_only); + + /** + * NMSettingProxy:pac-url: + * + * PAC URL for obtaining PAC file. + * + * Since: 1.6 + **/ + /* ---ifcfg-rh--- + * property: pac-url + * variable: PAC_URL(+) + * description: URL for PAC file. + * example: PAC_URL=http://wpad.mycompany.com/wpad.dat + * ---end--- + */ + obj_properties[PROP_PAC_URL] = g_param_spec_string(NM_SETTING_PROXY_PAC_URL, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingProxy:pac-script: + * + * PAC script for the connection. + * + * Since: 1.6 + **/ + /* ---ifcfg-rh--- + * property: pac-script + * variable: PAC_SCRIPT(+) + * description: Path of the PAC script. + * example: PAC_SCRIPT=/home/joe/proxy.pac + * ---end--- + */ + obj_properties[PROP_PAC_SCRIPT] = + g_param_spec_string(NM_SETTING_PROXY_PAC_SCRIPT, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_PROXY, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-serial.c b/src/libnm-core-impl/nm-setting-serial.c new file mode 100644 index 00000000..c87de166 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-serial.c @@ -0,0 +1,352 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2018 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-serial.h" + +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-serial + * @short_description: Describes connection properties for devices that use + * serial communications + * + * The #NMSettingSerial object is a #NMSetting subclass that describes + * properties necessary for connections that may use serial communications, + * such as mobile broadband or analog telephone connections. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_BAUD, + PROP_BITS, + PROP_PARITY, + PROP_STOPBITS, + PROP_SEND_DELAY, ); + +typedef struct { + guint64 send_delay; + guint baud; + guint bits; + guint stopbits; + char parity; +} NMSettingSerialPrivate; + +/** + * NMSettingSerial: + * + * Serial Link Settings + */ +struct _NMSettingSerial { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingSerialClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingSerial, nm_setting_serial, NM_TYPE_SETTING) + +#define NM_SETTING_SERIAL_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_SERIAL, NMSettingSerialPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_serial_get_baud: + * @setting: the #NMSettingSerial + * + * Returns: the #NMSettingSerial:baud property of the setting + **/ +guint +nm_setting_serial_get_baud(NMSettingSerial *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_SERIAL(setting), 0); + + return NM_SETTING_SERIAL_GET_PRIVATE(setting)->baud; +} + +/** + * nm_setting_serial_get_bits: + * @setting: the #NMSettingSerial + * + * Returns: the #NMSettingSerial:bits property of the setting + **/ +guint +nm_setting_serial_get_bits(NMSettingSerial *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_SERIAL(setting), 0); + + return NM_SETTING_SERIAL_GET_PRIVATE(setting)->bits; +} + +/** + * nm_setting_serial_get_parity: + * @setting: the #NMSettingSerial + * + * Returns: the #NMSettingSerial:parity property of the setting + **/ +NMSettingSerialParity +nm_setting_serial_get_parity(NMSettingSerial *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_SERIAL(setting), 0); + + return NM_SETTING_SERIAL_GET_PRIVATE(setting)->parity; +} + +/** + * nm_setting_serial_get_stopbits: + * @setting: the #NMSettingSerial + * + * Returns: the #NMSettingSerial:stopbits property of the setting + **/ +guint +nm_setting_serial_get_stopbits(NMSettingSerial *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_SERIAL(setting), 0); + + return NM_SETTING_SERIAL_GET_PRIVATE(setting)->stopbits; +} + +/** + * nm_setting_serial_get_send_delay: + * @setting: the #NMSettingSerial + * + * Returns: the #NMSettingSerial:send-delay property of the setting + **/ +guint64 +nm_setting_serial_get_send_delay(NMSettingSerial *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_SERIAL(setting), 0); + + return NM_SETTING_SERIAL_GET_PRIVATE(setting)->send_delay; +} + +static GVariant * +parity_to_dbus(const GValue *from) +{ + switch (g_value_get_enum(from)) { + case NM_SETTING_SERIAL_PARITY_EVEN: + return g_variant_new_byte('E'); + case NM_SETTING_SERIAL_PARITY_ODD: + return g_variant_new_byte('o'); + case NM_SETTING_SERIAL_PARITY_NONE: + default: + return g_variant_new_byte('n'); + } +} + +static void +parity_from_dbus(GVariant *from, GValue *to) +{ + switch (g_variant_get_byte(from)) { + case 'E': + g_value_set_enum(to, NM_SETTING_SERIAL_PARITY_EVEN); + break; + case 'o': + g_value_set_enum(to, NM_SETTING_SERIAL_PARITY_ODD); + break; + case 'n': + default: + g_value_set_enum(to, NM_SETTING_SERIAL_PARITY_NONE); + break; + } +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingSerial *setting = NM_SETTING_SERIAL(object); + + switch (prop_id) { + case PROP_BAUD: + g_value_set_uint(value, nm_setting_serial_get_baud(setting)); + break; + case PROP_BITS: + g_value_set_uint(value, nm_setting_serial_get_bits(setting)); + break; + case PROP_PARITY: + g_value_set_enum(value, nm_setting_serial_get_parity(setting)); + break; + case PROP_STOPBITS: + g_value_set_uint(value, nm_setting_serial_get_stopbits(setting)); + break; + case PROP_SEND_DELAY: + g_value_set_uint64(value, nm_setting_serial_get_send_delay(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingSerialPrivate *priv = NM_SETTING_SERIAL_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_BAUD: + priv->baud = g_value_get_uint(value); + break; + case PROP_BITS: + priv->bits = g_value_get_uint(value); + break; + case PROP_PARITY: + priv->parity = g_value_get_enum(value); + break; + case PROP_STOPBITS: + priv->stopbits = g_value_get_uint(value); + break; + case PROP_SEND_DELAY: + priv->send_delay = g_value_get_uint64(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_serial_init(NMSettingSerial *self) +{ + NMSettingSerialPrivate *priv = NM_SETTING_SERIAL_GET_PRIVATE(self); + + nm_assert(priv->parity == NM_SETTING_SERIAL_PARITY_NONE); + priv->stopbits = 1; + priv->baud = 57600; + priv->bits = 8; +} + +/** + * nm_setting_serial_new: + * + * Creates a new #NMSettingSerial object with default values. + * + * Returns: (transfer full): the new empty #NMSettingSerial object + **/ +NMSetting * +nm_setting_serial_new(void) +{ + return g_object_new(NM_TYPE_SETTING_SERIAL, NULL); +} + +static void +nm_setting_serial_class_init(NMSettingSerialClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingSerialPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + /** + * NMSettingSerial:baud: + * + * Speed to use for communication over the serial port. Note that this + * value usually has no effect for mobile broadband modems as they generally + * ignore speed settings and use the highest available speed. + **/ + obj_properties[PROP_BAUD] = g_param_spec_uint(NM_SETTING_SERIAL_BAUD, + "", + "", + 0, + G_MAXUINT, + 57600, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingSerial:bits: + * + * Byte-width of the serial communication. The 8 in "8n1" for example. + **/ + obj_properties[PROP_BITS] = g_param_spec_uint(NM_SETTING_SERIAL_BITS, + "", + "", + 5, + 8, + 8, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingSerial:parity: + * + * Parity setting of the serial port. + **/ + /* ---keyfile--- + * property: parity + * format: 'e', 'o', or 'n' + * description: The connection parity; even, odd, or none. Note that older + * versions of NetworkManager stored this as an integer: 69 ('E') for even, + * 111 ('o') for odd, or 110 ('n') for none. + * example: parity=n + * ---end--- + * ---dbus--- + * property: parity + * format: byte + * description: The connection parity: 69 (ASCII 'E') for even parity, + * 111 (ASCII 'o') for odd, 110 (ASCII 'n') for none. + * ---end--- + */ + obj_properties[PROP_PARITY] = g_param_spec_enum(NM_SETTING_SERIAL_PARITY, + "", + "", + NM_TYPE_SETTING_SERIAL_PARITY, + NM_SETTING_SERIAL_PARITY_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_PARITY], + NM_SETT_INFO_PROPERT_TYPE_GPROP(G_VARIANT_TYPE_BYTE, + .gprop_from_dbus_fcn = parity_from_dbus, ), + .to_dbus_data.gprop_to_dbus_fcn = parity_to_dbus, ); + + /** + * NMSettingSerial:stopbits: + * + * Number of stop bits for communication on the serial port. Either 1 or 2. + * The 1 in "8n1" for example. + **/ + obj_properties[PROP_STOPBITS] = g_param_spec_uint(NM_SETTING_SERIAL_STOPBITS, + "", + "", + 1, + 2, + 1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingSerial:send-delay: + * + * Time to delay between each byte sent to the modem, in microseconds. + **/ + obj_properties[PROP_SEND_DELAY] = + g_param_spec_uint64(NM_SETTING_SERIAL_SEND_DELAY, + "", + "", + 0, + G_MAXUINT64, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_SERIAL, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-sriov.c b/src/libnm-core-impl/nm-setting-sriov.c new file mode 100644 index 00000000..b18d184a --- /dev/null +++ b/src/libnm-core-impl/nm-setting-sriov.c @@ -0,0 +1,1372 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-sriov.h" + +#include "nm-setting-private.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-sriov + * @short_description: Describes SR-IOV connection properties + * @include: nm-setting-sriov.h + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingSriov, PROP_TOTAL_VFS, PROP_VFS, PROP_AUTOPROBE_DRIVERS, ); + +/** + * NMSettingSriov: + * + * SR-IOV settings + * + * Since: 1.14 + */ +struct _NMSettingSriov { + NMSetting parent; + GPtrArray *vfs; + guint total_vfs; + NMTernary autoprobe_drivers; +}; + +struct _NMSettingSriovClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingSriov, nm_setting_sriov, NM_TYPE_SETTING) + +/*****************************************************************************/ + +G_DEFINE_BOXED_TYPE(NMSriovVF, nm_sriov_vf, nm_sriov_vf_dup, nm_sriov_vf_unref) + +struct _NMSriovVF { + guint refcount; + guint index; + GHashTable *attributes; + GHashTable *vlans; + guint * vlan_ids; +}; + +typedef struct { + guint id; + guint qos; + NMSriovVFVlanProtocol protocol; +} VFVlan; + +static guint +_vf_vlan_hash(gconstpointer ptr) +{ + return nm_hash_val(1348254767u, *((guint *) ptr)); +} + +static gboolean +_vf_vlan_equal(gconstpointer a, gconstpointer b) +{ + return *((guint *) a) == *((guint *) b); +} + +static GHashTable * +_vf_vlan_create_hash(void) +{ + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(VFVlan, id) == 0); + return g_hash_table_new_full(_vf_vlan_hash, _vf_vlan_equal, NULL, nm_g_slice_free_fcn(VFVlan)); +} + +/** + * nm_sriov_vf_new: + * @index: the VF index + * + * Creates a new #NMSriovVF object. + * + * Returns: (transfer full): the new #NMSriovVF object. + * + * Since: 1.14 + **/ +NMSriovVF * +nm_sriov_vf_new(guint index) +{ + NMSriovVF *vf; + + vf = g_slice_new(NMSriovVF); + *vf = (NMSriovVF){ + .refcount = 1, + .index = index, + .attributes = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref), + }; + return vf; +} + +/** + * nm_sriov_vf_ref: + * @vf: the #NMSriovVF + * + * Increases the reference count of the object. + * + * Since: 1.14 + **/ +void +nm_sriov_vf_ref(NMSriovVF *vf) +{ + g_return_if_fail(vf); + g_return_if_fail(vf->refcount > 0); + + vf->refcount++; +} + +/** + * nm_sriov_vf_unref: + * @vf: the #NMSriovVF + * + * Decreases the reference count of the object. If the reference count + * reaches zero, the object will be destroyed. + * + * Since: 1.14 + **/ +void +nm_sriov_vf_unref(NMSriovVF *vf) +{ + g_return_if_fail(vf); + g_return_if_fail(vf->refcount > 0); + + vf->refcount--; + if (vf->refcount == 0) { + g_hash_table_unref(vf->attributes); + if (vf->vlans) + g_hash_table_unref(vf->vlans); + g_free(vf->vlan_ids); + nm_g_slice_free(vf); + } +} + +/** + * nm_sriov_vf_equal: + * @vf: the #NMSriovVF + * @other: the #NMSriovVF to compare @vf to. + * + * Determines if two #NMSriovVF objects have the same index, + * attributes and VLANs. + * + * Returns: %TRUE if the objects contain the same values, %FALSE + * if they do not. + * + * Since: 1.14 + **/ +gboolean +nm_sriov_vf_equal(const NMSriovVF *vf, const NMSriovVF *other) +{ + GHashTableIter iter; + const char * key; + GVariant * value, *value2; + VFVlan * vlan, *vlan2; + guint n_vlans; + + g_return_val_if_fail(vf, FALSE); + g_return_val_if_fail(vf->refcount > 0, FALSE); + g_return_val_if_fail(other, FALSE); + g_return_val_if_fail(other->refcount > 0, FALSE); + + if (vf == other) + return TRUE; + + if (vf->index != other->index) + return FALSE; + + if (g_hash_table_size(vf->attributes) != g_hash_table_size(other->attributes)) + return FALSE; + g_hash_table_iter_init(&iter, vf->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { + value2 = g_hash_table_lookup(other->attributes, key); + if (!value2) + return FALSE; + if (!g_variant_equal(value, value2)) + return FALSE; + } + + n_vlans = vf->vlans ? g_hash_table_size(vf->vlans) : 0u; + if (n_vlans != (other->vlans ? g_hash_table_size(other->vlans) : 0u)) + return FALSE; + if (n_vlans > 0) { + g_hash_table_iter_init(&iter, vf->vlans); + while (g_hash_table_iter_next(&iter, (gpointer *) &vlan, NULL)) { + vlan2 = g_hash_table_lookup(other->vlans, vlan); + if (!vlan2) + return FALSE; + if (vlan->qos != vlan2->qos || vlan->protocol != vlan2->protocol) + return FALSE; + } + } + + return TRUE; +} + +static void +vf_add_vlan(NMSriovVF *vf, guint vlan_id, guint qos, NMSriovVFVlanProtocol protocol) +{ + VFVlan *vlan; + + vlan = g_slice_new(VFVlan); + *vlan = (VFVlan){ + .id = vlan_id, + .qos = qos, + .protocol = protocol, + }; + + if (!vf->vlans) + vf->vlans = _vf_vlan_create_hash(); + + g_hash_table_add(vf->vlans, vlan); + nm_clear_g_free(&vf->vlan_ids); +} + +/** + * nm_sriov_vf_dup: + * @vf: the #NMSriovVF + * + * Creates a copy of @vf. + * + * Returns: (transfer full): a copy of @vf + * + * Since: 1.14 + **/ +NMSriovVF * +nm_sriov_vf_dup(const NMSriovVF *vf) +{ + NMSriovVF * copy; + GHashTableIter iter; + const char * name; + GVariant * variant; + VFVlan * vlan; + + g_return_val_if_fail(vf, NULL); + g_return_val_if_fail(vf->refcount > 0, NULL); + + copy = nm_sriov_vf_new(vf->index); + + g_hash_table_iter_init(&iter, vf->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) + nm_sriov_vf_set_attribute(copy, name, variant); + + if (vf->vlans) { + g_hash_table_iter_init(&iter, vf->vlans); + while (g_hash_table_iter_next(&iter, (gpointer *) &vlan, NULL)) + vf_add_vlan(copy, vlan->id, vlan->qos, vlan->protocol); + } + + return copy; +} + +/** + * nm_sriov_vf_get_index: + * @vf: the #NMSriovVF + * + * Gets the index property of this VF object. + * + * Returns: the VF index + * + * Since: 1.14 + **/ +guint +nm_sriov_vf_get_index(const NMSriovVF *vf) +{ + g_return_val_if_fail(vf, 0); + g_return_val_if_fail(vf->refcount > 0, 0); + + return vf->index; +} + +/** + * nm_sriov_vf_set_attribute: + * @vf: the #NMSriovVF + * @name: the name of a route attribute + * @value: (transfer none) (allow-none): the value + * + * Sets the named attribute on @vf to the given value. + * + * Since: 1.14 + **/ +void +nm_sriov_vf_set_attribute(NMSriovVF *vf, const char *name, GVariant *value) +{ + g_return_if_fail(vf); + g_return_if_fail(vf->refcount > 0); + g_return_if_fail(name && *name != '\0'); + g_return_if_fail(!nm_streq(name, "index")); + + if (value) { + g_hash_table_insert(vf->attributes, g_strdup(name), g_variant_ref_sink(value)); + } else + g_hash_table_remove(vf->attributes, name); +} + +/** + * nm_sriov_vf_get_attribute_names: + * @vf: the #NMSriovVF + * + * Gets an array of attribute names defined on @vf. + * + * Returns: (transfer container): a %NULL-terminated array of attribute names + * + * Since: 1.14 + **/ +const char ** +nm_sriov_vf_get_attribute_names(const NMSriovVF *vf) +{ + g_return_val_if_fail(vf, NULL); + g_return_val_if_fail(vf->refcount > 0, NULL); + + return nm_utils_strdict_get_keys(vf->attributes, TRUE, NULL); +} + +/** + * nm_sriov_vf_get_attribute: + * @vf: the #NMSriovVF + * @name: the name of a VF attribute + * + * Gets the value of the attribute with name @name on @vf + * + * Returns: (transfer none): the value of the attribute with name @name on + * @vf, or %NULL if @vf has no such attribute. + * + * Since: 1.14 + **/ +GVariant * +nm_sriov_vf_get_attribute(const NMSriovVF *vf, const char *name) +{ + g_return_val_if_fail(vf, NULL); + g_return_val_if_fail(vf->refcount > 0, NULL); + g_return_val_if_fail(name && *name != '\0', NULL); + + return g_hash_table_lookup(vf->attributes, name); +} + +const NMVariantAttributeSpec *const _nm_sriov_vf_attribute_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_MAC, + G_VARIANT_TYPE_STRING, + .str_type = 'm', ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK, G_VARIANT_TYPE_BOOLEAN, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_TRUST, G_VARIANT_TYPE_BOOLEAN, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE, G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE, G_VARIANT_TYPE_UINT32, ), + /* D-Bus only, synthetic attributes */ + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("vlans", G_VARIANT_TYPE_STRING, .str_type = 'd', ), + NULL, +}; + +/** + * nm_sriov_vf_attribute_validate: + * @name: the attribute name + * @value: the attribute value + * @known: (out): on return, whether the attribute name is a known one + * @error: (allow-none): return location for a #GError, or %NULL + * + * Validates a VF attribute, i.e. checks that the attribute is a known one, + * the value is of the correct type and well-formed. + * + * Returns: %TRUE if the attribute is valid, %FALSE otherwise + * + * Since: 1.14 + */ +gboolean +nm_sriov_vf_attribute_validate(const char *name, GVariant *value, gboolean *known, GError **error) +{ + const NMVariantAttributeSpec *const *iter; + const NMVariantAttributeSpec * spec = NULL; + + g_return_val_if_fail(name, FALSE); + g_return_val_if_fail(value, FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + for (iter = _nm_sriov_vf_attribute_spec; *iter; iter++) { + if (nm_streq(name, (*iter)->name)) { + spec = *iter; + break; + } + } + + if (!spec || spec->str_type == 'd') { + NM_SET_OUT(known, FALSE); + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unknown attribute")); + return FALSE; + } + + NM_SET_OUT(known, TRUE); + + if (!g_variant_is_of_type(value, spec->type)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid attribute type '%s'"), + g_variant_get_type_string(value)); + return FALSE; + } + + if (g_variant_type_equal(spec->type, G_VARIANT_TYPE_STRING)) { + const char *string; + + switch (spec->str_type) { + case 'm': /* MAC address */ + string = g_variant_get_string(value, NULL); + if (!nm_utils_hwaddr_valid(string, -1)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("'%s' is not a valid MAC address"), + string); + return FALSE; + } + break; + default: + break; + } + } + + return TRUE; +} + +gboolean +_nm_sriov_vf_attribute_validate_all(const NMSriovVF *vf, GError **error) +{ + GHashTableIter iter; + const char * name; + GVariant * variant; + GVariant * min, *max; + + g_return_val_if_fail(vf, FALSE); + g_return_val_if_fail(vf->refcount > 0, FALSE); + + g_hash_table_iter_init(&iter, vf->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) { + if (!nm_sriov_vf_attribute_validate(name, variant, NULL, error)) { + g_prefix_error(error, "attribute '%s':", name); + return FALSE; + } + } + + min = g_hash_table_lookup(vf->attributes, NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE); + max = g_hash_table_lookup(vf->attributes, NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE); + if (min && max && g_variant_get_uint32(min) > g_variant_get_uint32(max)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "min_tx_rate is greater than max_tx_rate"); + return FALSE; + } + + return TRUE; +} + +/** + * nm_sriov_vf_add_vlan: + * @vf: the #NMSriovVF + * @vlan_id: the VLAN id + * + * Adds a VLAN to the VF. + * + * Returns: %TRUE if the VLAN was added; %FALSE if it already existed + * + * Since: 1.14 + **/ +gboolean +nm_sriov_vf_add_vlan(NMSriovVF *vf, guint vlan_id) +{ + g_return_val_if_fail(vf, FALSE); + g_return_val_if_fail(vf->refcount > 0, FALSE); + + if (vf->vlans && g_hash_table_contains(vf->vlans, &vlan_id)) + return FALSE; + + vf_add_vlan(vf, vlan_id, 0, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + + return TRUE; +} + +/** + * nm_sriov_vf_remove_vlan: + * @vf: the #NMSriovVF + * @vlan_id: the VLAN id + * + * Removes a VLAN from a VF. + * + * Returns: %TRUE if the VLAN was removed, %FALSE if the VLAN @vlan_id + * did not belong to the VF. + * + * Since: 1.14 + */ +gboolean +nm_sriov_vf_remove_vlan(NMSriovVF *vf, guint vlan_id) +{ + g_return_val_if_fail(vf, FALSE); + g_return_val_if_fail(vf->refcount > 0, FALSE); + + if (!vf->vlans || !g_hash_table_remove(vf->vlans, &vlan_id)) + return FALSE; + + nm_clear_g_free(&vf->vlan_ids); + return TRUE; +} + +static int +vlan_id_compare(gconstpointer a, gconstpointer b, gpointer user_data) +{ + guint id_a = *(guint *) a; + guint id_b = *(guint *) b; + + if (id_a < id_b) + return -1; + else if (id_a > id_b) + return 1; + else + return 0; +} + +/** + * nm_sriov_vf_get_vlan_ids: + * @vf: the #NMSriovVF + * @length: (out) (allow-none): on return, the number of VLANs configured + * + * Returns the VLANs currently configured on the VF. + * + * Returns: (transfer none) (array length=length): a list of VLAN ids configured on the VF. + * + * Since: 1.14 + */ +const guint * +nm_sriov_vf_get_vlan_ids(const NMSriovVF *vf, guint *length) +{ + GHashTableIter iter; + VFVlan * vlan; + guint num, i; + + g_return_val_if_fail(vf, NULL); + g_return_val_if_fail(vf->refcount > 0, NULL); + + num = vf->vlans ? g_hash_table_size(vf->vlans) : 0u; + NM_SET_OUT(length, num); + + if (vf->vlan_ids) + return vf->vlan_ids; + if (num == 0) + return NULL; + + /* vf is const, however, vlan_ids is a mutable field caching the + * result ("mutable" in C++ terminology) */ + ((NMSriovVF *) vf)->vlan_ids = g_new0(guint, num); + + i = 0; + g_hash_table_iter_init(&iter, vf->vlans); + while (g_hash_table_iter_next(&iter, (gpointer *) &vlan, NULL)) + vf->vlan_ids[i++] = vlan->id; + + nm_assert(num == i); + + g_qsort_with_data(vf->vlan_ids, num, sizeof(guint), vlan_id_compare, NULL); + + return vf->vlan_ids; +} + +/** + * nm_sriov_vf_set_vlan_qos: + * @vf: the #NMSriovVF + * @vlan_id: the VLAN id + * @qos: a QoS (priority) value + * + * Sets a QoS value for the given VLAN. + * + * Since: 1.14 + */ +void +nm_sriov_vf_set_vlan_qos(NMSriovVF *vf, guint vlan_id, guint32 qos) +{ + VFVlan *vlan; + + g_return_if_fail(vf); + g_return_if_fail(vf->refcount > 0); + + if (!vf->vlans || !(vlan = g_hash_table_lookup(vf->vlans, &vlan_id))) + g_return_if_reached(); + + vlan->qos = qos; +} + +/** + * nm_sriov_vf_set_vlan_protocol: + * @vf: the #NMSriovVF + * @vlan_id: the VLAN id + * @protocol: the VLAN protocol + * + * Sets the protocol for the given VLAN. + * + * Since: 1.14 + */ +void +nm_sriov_vf_set_vlan_protocol(NMSriovVF *vf, guint vlan_id, NMSriovVFVlanProtocol protocol) +{ + VFVlan *vlan; + + g_return_if_fail(vf); + g_return_if_fail(vf->refcount > 0); + + if (!vf->vlans || !(vlan = g_hash_table_lookup(vf->vlans, &vlan_id))) + g_return_if_reached(); + + vlan->protocol = protocol; +} + +/** + * nm_sriov_vf_get_vlan_qos: + * @vf: the #NMSriovVF + * @vlan_id: the VLAN id + * + * Returns the QoS value for the given VLAN. + * + * Returns: the QoS value + * + * Since: 1.14 + */ +guint32 +nm_sriov_vf_get_vlan_qos(const NMSriovVF *vf, guint vlan_id) +{ + VFVlan *vlan; + + g_return_val_if_fail(vf, 0); + g_return_val_if_fail(vf->refcount > 0, 0); + + if (!vf->vlans || !(vlan = g_hash_table_lookup(vf->vlans, &vlan_id))) + g_return_val_if_reached(0); + + return vlan->qos; +} + +/* + * nm_sriov_vf_get_vlan_protocol: + * @vf: the #NMSriovVF + * @vlan_id: the VLAN id + * + * Returns the configured protocol for the given VLAN. + * + * Returns: the configured protocol + * + * Since: 1.14 + */ +NMSriovVFVlanProtocol +nm_sriov_vf_get_vlan_protocol(const NMSriovVF *vf, guint vlan_id) +{ + VFVlan *vlan; + + g_return_val_if_fail(vf, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + g_return_val_if_fail(vf->refcount > 0, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + + if (!vf->vlans || !(vlan = g_hash_table_lookup(vf->vlans, &vlan_id))) + g_return_val_if_reached(NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + + return vlan->protocol; +} + +/*****************************************************************************/ + +/** + * nm_setting_sriov_get_total_vfs: + * @setting: the #NMSettingSriov + * + * Returns the value contained in the #NMSettingSriov:total-vfs + * property. + * + * Returns: the total number of SR-IOV virtual functions to create + * + * Since: 1.14 + **/ +guint +nm_setting_sriov_get_total_vfs(NMSettingSriov *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), 0); + + return setting->total_vfs; +} + +/** + * nm_setting_sriov_get_num_vfs: + * @setting: the #NMSettingSriov + * + * Returns: the number of configured VFs + * + * Since: 1.14 + **/ +guint +nm_setting_sriov_get_num_vfs(NMSettingSriov *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), 0); + + return setting->vfs->len; +} + +/** + * nm_setting_sriov_get_vf: + * @setting: the #NMSettingSriov + * @idx: index number of the VF to return + * + * Returns: (transfer none): the VF at index @idx + * + * Since: 1.14 + **/ +NMSriovVF * +nm_setting_sriov_get_vf(NMSettingSriov *setting, guint idx) +{ + g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), NULL); + g_return_val_if_fail(idx < setting->vfs->len, NULL); + + return setting->vfs->pdata[idx]; +} + +/** + * nm_setting_sriov_add_vf: + * @setting: the #NMSettingSriov + * @vf: the VF to add + * + * Appends a new VF and associated information to the setting. The + * given VF is duplicated internally and is not changed by this function. + * + * Since: 1.14 + **/ +void +nm_setting_sriov_add_vf(NMSettingSriov *setting, NMSriovVF *vf) +{ + g_return_if_fail(NM_IS_SETTING_SRIOV(setting)); + g_return_if_fail(vf); + g_return_if_fail(vf->refcount > 0); + + g_ptr_array_add(setting->vfs, nm_sriov_vf_dup(vf)); + _notify(setting, PROP_VFS); +} + +/** + * nm_setting_sriov_remove_vf: + * @setting: the #NMSettingSriov + * @idx: index number of the VF + * + * Removes the VF at index @idx. + * + * Since: 1.14 + **/ +void +nm_setting_sriov_remove_vf(NMSettingSriov *setting, guint idx) +{ + g_return_if_fail(NM_IS_SETTING_SRIOV(setting)); + g_return_if_fail(idx < setting->vfs->len); + + g_ptr_array_remove_index(setting->vfs, idx); + _notify(setting, PROP_VFS); +} + +/** + * nm_setting_sriov_remove_vf_by_index: + * @setting: the #NMSettingSriov + * @index: the VF index of the VF to remove + * + * Removes the VF with VF index @index. + * + * Returns: %TRUE if the VF was found and removed; %FALSE if it was not + * + * Since: 1.14 + **/ +gboolean +nm_setting_sriov_remove_vf_by_index(NMSettingSriov *setting, guint index) +{ + guint i; + + g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), FALSE); + + for (i = 0; i < setting->vfs->len; i++) { + if (nm_sriov_vf_get_index(setting->vfs->pdata[i]) == index) { + g_ptr_array_remove_index(setting->vfs, i); + _notify(setting, PROP_VFS); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_sriov_clear_vfs: + * @setting: the #NMSettingSriov + * + * Removes all configured VFs. + * + * Since: 1.14 + **/ +void +nm_setting_sriov_clear_vfs(NMSettingSriov *setting) +{ + g_return_if_fail(NM_IS_SETTING_SRIOV(setting)); + + if (setting->vfs->len != 0) { + g_ptr_array_set_size(setting->vfs, 0); + _notify(setting, PROP_VFS); + } +} + +/** + * nm_setting_sriov_get_autoprobe_drivers: + * @setting: the #NMSettingSriov + * + * Returns the value contained in the #NMSettingSriov:autoprobe-drivers + * property. + * + * Returns: the autoprobe-drivers property value + * + * Since: 1.14 + **/ +NMTernary +nm_setting_sriov_get_autoprobe_drivers(NMSettingSriov *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), NM_TERNARY_DEFAULT); + + return setting->autoprobe_drivers; +} + +static int +vf_index_compare(gconstpointer a, gconstpointer b) +{ + NMSriovVF *vf_a = *(NMSriovVF **) a; + NMSriovVF *vf_b = *(NMSriovVF **) b; + + if (vf_a->index < vf_b->index) + return -1; + else if (vf_a->index > vf_b->index) + return 1; + else + return 0; +} + +gboolean +_nm_setting_sriov_sort_vfs(NMSettingSriov *setting) +{ + gboolean need_sort = FALSE; + guint i; + + for (i = 1; i < setting->vfs->len; i++) { + NMSriovVF *vf_prev = setting->vfs->pdata[i - 1]; + NMSriovVF *vf = setting->vfs->pdata[i]; + + if (vf->index <= vf_prev->index) { + need_sort = TRUE; + break; + } + } + + if (need_sort) { + g_ptr_array_sort(setting->vfs, vf_index_compare); + _notify(setting, PROP_VFS); + } + + return need_sort; +} + +/*****************************************************************************/ + +static GVariant * +vfs_to_dbus(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *vfs = NULL; + GVariantBuilder builder; + guint i; + + g_object_get(setting, NM_SETTING_SRIOV_VFS, &vfs, NULL); + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + + if (vfs) { + for (i = 0; i < vfs->len; i++) { + gs_free const char **attr_names = NULL; + NMSriovVF * vf = vfs->pdata[i]; + GVariantBuilder vf_builder; + const guint * vlan_ids; + const char ** name; + guint num_vlans = 0; + + g_variant_builder_init(&vf_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&vf_builder, + "{sv}", + "index", + g_variant_new_uint32(nm_sriov_vf_get_index(vf))); + + attr_names = nm_utils_strdict_get_keys(vf->attributes, TRUE, NULL); + if (attr_names) { + for (name = attr_names; *name; name++) { + g_variant_builder_add(&vf_builder, + "{sv}", + *name, + nm_sriov_vf_get_attribute(vf, *name)); + } + } + + /* VLANs are translated into an array of maps, where each map has + * keys 'id', 'qos' and 'proto'. This guarantees enough flexibility + * to accommodate any future new option. */ + vlan_ids = nm_sriov_vf_get_vlan_ids(vf, &num_vlans); + if (num_vlans) { + GVariantBuilder vlans_builder; + guint j; + + g_variant_builder_init(&vlans_builder, G_VARIANT_TYPE("aa{sv}")); + for (j = 0; j < num_vlans; j++) { + GVariantBuilder vlan_builder; + + g_variant_builder_init(&vlan_builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&vlan_builder, + "{sv}", + "id", + g_variant_new_uint32(vlan_ids[j])); + g_variant_builder_add( + &vlan_builder, + "{sv}", + "qos", + g_variant_new_uint32(nm_sriov_vf_get_vlan_qos(vf, vlan_ids[j]))); + g_variant_builder_add( + &vlan_builder, + "{sv}", + "protocol", + g_variant_new_uint32(nm_sriov_vf_get_vlan_protocol(vf, vlan_ids[j]))); + g_variant_builder_add(&vlans_builder, "a{sv}", &vlan_builder); + } + g_variant_builder_add(&vf_builder, + "{sv}", + "vlans", + g_variant_builder_end(&vlans_builder)); + } + g_variant_builder_add(&builder, "a{sv}", &vf_builder); + } + } + + return g_variant_builder_end(&builder); +} + +static gboolean +vfs_from_dbus(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray * vfs; + GVariantIter vf_iter; + GVariant * vf_var; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), FALSE); + + vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref); + g_variant_iter_init(&vf_iter, value); + while (g_variant_iter_next(&vf_iter, "@a{sv}", &vf_var)) { + NMSriovVF * vf; + guint32 index; + GVariantIter attr_iter; + const char * attr_name; + GVariant * attr_var, *vlans_var; + + if (!g_variant_lookup(vf_var, "index", "u", &index)) + goto next; + + vf = nm_sriov_vf_new(index); + + g_variant_iter_init(&attr_iter, vf_var); + while (g_variant_iter_next(&attr_iter, "{&sv}", &attr_name, &attr_var)) { + if (!NM_IN_STRSET(attr_name, "index", "vlans")) + nm_sriov_vf_set_attribute(vf, attr_name, attr_var); + g_variant_unref(attr_var); + } + + if (g_variant_lookup(vf_var, "vlans", "@aa{sv}", &vlans_var)) { + GVariantIter vlan_iter; + GVariant * vlan_var; + + g_variant_iter_init(&vlan_iter, vlans_var); + while (g_variant_iter_next(&vlan_iter, "@a{sv}", &vlan_var)) { + NMSriovVFVlanProtocol proto = NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q; + gint64 vlan_id = -1; + guint qos = 0; + + g_variant_iter_init(&attr_iter, vlan_var); + while (g_variant_iter_next(&attr_iter, "{&sv}", &attr_name, &attr_var)) { + if (nm_streq(attr_name, "id") + && g_variant_is_of_type(attr_var, G_VARIANT_TYPE_UINT32)) + vlan_id = g_variant_get_uint32(attr_var); + else if (nm_streq(attr_name, "qos") + && g_variant_is_of_type(attr_var, G_VARIANT_TYPE_UINT32)) + qos = g_variant_get_uint32(attr_var); + else if (nm_streq(attr_name, "protocol") + && g_variant_is_of_type(attr_var, G_VARIANT_TYPE_UINT32)) + proto = g_variant_get_uint32(attr_var); + g_variant_unref(attr_var); + } + if (vlan_id != -1) + vf_add_vlan(vf, vlan_id, qos, proto); + g_variant_unref(vlan_var); + } + g_variant_unref(vlans_var); + } + + g_ptr_array_add(vfs, vf); +next: + g_variant_unref(vf_var); + } + + g_object_set(setting, NM_SETTING_SRIOV_VFS, vfs, NULL); + g_ptr_array_unref(vfs); + + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingSriov *self = NM_SETTING_SRIOV(setting); + guint i; + + if (self->vfs->len) { + gs_unref_hashtable GHashTable *h = NULL; + + h = g_hash_table_new(nm_direct_hash, NULL); + for (i = 0; i < self->vfs->len; i++) { + NMSriovVF * vf = self->vfs->pdata[i]; + gs_free_error GError *local = NULL; + + if (vf->index >= self->total_vfs) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("VF with index %u, but the total number of VFs is %u"), + vf->index, + self->total_vfs); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_SRIOV_SETTING_NAME, + NM_SETTING_SRIOV_VFS); + return FALSE; + } + + if (!_nm_sriov_vf_attribute_validate_all(vf, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid VF %u: %s"), + vf->index, + local->message); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_SRIOV_SETTING_NAME, + NM_SETTING_SRIOV_VFS); + return FALSE; + } + + if (g_hash_table_contains(h, GUINT_TO_POINTER(vf->index))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("duplicate VF index %u"), + vf->index); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_SRIOV_SETTING_NAME, + NM_SETTING_SRIOV_VFS); + return FALSE; + } + + g_hash_table_add(h, GUINT_TO_POINTER(vf->index)); + } + } + + /* Failures from here on are NORMALIZABLE... */ + + if (self->vfs->len) { + for (i = 1; i < self->vfs->len; i++) { + NMSriovVF *vf_prev = self->vfs->pdata[i - 1]; + NMSriovVF *vf = self->vfs->pdata[i]; + + if (vf->index <= vf_prev->index) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("VFs %d and %d are not sorted by ascending index"), + vf_prev->index, + vf->index); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_SRIOV_SETTING_NAME, + NM_SETTING_SRIOV_VFS); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + } + } + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingSriov *a; + NMSettingSriov *b; + guint i; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_SRIOV_VFS)) { + if (set_b) { + a = NM_SETTING_SRIOV(set_a); + b = NM_SETTING_SRIOV(set_b); + + if (a->vfs->len != b->vfs->len) + return FALSE; + for (i = 0; i < a->vfs->len; i++) { + if (!nm_sriov_vf_equal(a->vfs->pdata[i], b->vfs->pdata[i])) + return FALSE; + } + } + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_sriov_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingSriov *self = NM_SETTING_SRIOV(object); + + switch (prop_id) { + case PROP_TOTAL_VFS: + g_value_set_uint(value, self->total_vfs); + break; + case PROP_VFS: + g_value_take_boxed(value, + _nm_utils_copy_array(self->vfs, + (NMUtilsCopyFunc) nm_sriov_vf_dup, + (GDestroyNotify) nm_sriov_vf_unref)); + break; + case PROP_AUTOPROBE_DRIVERS: + g_value_set_enum(value, self->autoprobe_drivers); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingSriov *self = NM_SETTING_SRIOV(object); + + switch (prop_id) { + case PROP_TOTAL_VFS: + self->total_vfs = g_value_get_uint(value); + break; + case PROP_VFS: + g_ptr_array_unref(self->vfs); + self->vfs = _nm_utils_copy_array(g_value_get_boxed(value), + (NMUtilsCopyFunc) nm_sriov_vf_dup, + (GDestroyNotify) nm_sriov_vf_unref); + break; + case PROP_AUTOPROBE_DRIVERS: + self->autoprobe_drivers = g_value_get_enum(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_sriov_init(NMSettingSriov *setting) +{ + setting->vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref); + + setting->autoprobe_drivers = NM_TERNARY_DEFAULT; +} + +/** + * nm_setting_sriov_new: + * + * Creates a new #NMSettingSriov object with default values. + * + * Returns: (transfer full): the new empty #NMSettingSriov object + * + * Since: 1.14 + **/ +NMSetting * +nm_setting_sriov_new(void) +{ + return g_object_new(NM_TYPE_SETTING_SRIOV, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingSriov *self = NM_SETTING_SRIOV(object); + + g_ptr_array_unref(self->vfs); + + G_OBJECT_CLASS(nm_setting_sriov_parent_class)->finalize(object); +} + +static void +nm_setting_sriov_class_init(NMSettingSriovClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->compare_property = compare_property; + setting_class->verify = verify; + + /** + * NMSettingSriov:total-vfs + * + * The total number of virtual functions to create. + * + * Note that when the sriov setting is present NetworkManager + * enforces the number of virtual functions on the interface + * (also when it is zero) during activation and resets it + * upon deactivation. To prevent any changes to SR-IOV + * parameters don't add a sriov setting to the connection. + * + * Since: 1.14 + **/ + /* ---ifcfg-rh--- + * property: total-vfs + * variable: SRIOV_TOTAL_VFS(+) + * description: The total number of virtual functions to create + * example: SRIOV_TOTAL_VFS=16 + * ---end--- + */ + obj_properties[PROP_TOTAL_VFS] = g_param_spec_uint( + NM_SETTING_SRIOV_TOTAL_VFS, + "", + "", + 0, + G_MAXUINT32, + 0, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingSriov:vfs: (type GPtrArray(NMSriovVF)) + * + * Array of virtual function descriptors. + * + * Each VF descriptor is a dictionary mapping attribute names + * to GVariant values. The 'index' entry is mandatory for + * each VF. + * + * When represented as string a VF is in the form: + * + * "INDEX [ATTR=VALUE[ ATTR=VALUE]...]". + * + * for example: + * + * "2 mac=00:11:22:33:44:55 spoof-check=true". + * + * Multiple VFs can be specified using a comma as separator. + * Currently, the following attributes are supported: mac, + * spoof-check, trust, min-tx-rate, max-tx-rate, vlans. + * + * The "vlans" attribute is represented as a semicolon-separated + * list of VLAN descriptors, where each descriptor has the form + * + * "ID[.PRIORITY[.PROTO]]". + * + * PROTO can be either 'q' for 802.1Q (the default) or 'ad' for + * 802.1ad. + * + + * Since: 1.14 + **/ + /* ---ifcfg-rh--- + * property: vfs + * variable: SRIOV_VF1(+), SRIOV_VF2(+), ... + * description: SR-IOV virtual function descriptors + * example: SRIOV_VF10="mac=00:11:22:33:44:55", ... + * ---end--- + */ + obj_properties[PROP_VFS] = g_param_spec_boxed(NM_SETTING_SRIOV_VFS, + "", + "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_VFS], + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = vfs_to_dbus, + .from_dbus_fcn = vfs_from_dbus, )); + + /** + * NMSettingSriov:autoprobe-drivers + * + * Whether to autoprobe virtual functions by a compatible driver. + * + * If set to %NM_TERNARY_TRUE, the kernel will try to bind VFs to + * a compatible driver and if this succeeds a new network + * interface will be instantiated for each VF. + * + * If set to %NM_TERNARY_FALSE, VFs will not be claimed and no + * network interfaces will be created for them. + * + * When set to %NM_TERNARY_DEFAULT, the global default is used; in + * case the global default is unspecified it is assumed to be + * %NM_TERNARY_TRUE. + * + * Since: 1.14 + **/ + /* ---ifcfg-rh--- + * property: autoprobe-drivers + * variable: SRIOV_AUTOPROBE_DRIVERS(+) + * default: missing variable means global default + * description: Whether to autoprobe virtual functions by a compatible driver + * example: SRIOV_AUTOPROBE_DRIVERS=0,1 + * ---end--- + */ + obj_properties[PROP_AUTOPROBE_DRIVERS] = g_param_spec_enum( + NM_SETTING_SRIOV_AUTOPROBE_DRIVERS, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_SRIOV, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-tc-config.c b/src/libnm-core-impl/nm-setting-tc-config.c new file mode 100644 index 00000000..f44c89cb --- /dev/null +++ b/src/libnm-core-impl/nm-setting-tc-config.c @@ -0,0 +1,1886 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-tc-config.h" + +#include + +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-tc-config + * @short_description: Describes connection properties for the Linux Traffic Control + * @include: nm-setting-tc-config.h + **/ + +/*****************************************************************************/ + +G_DEFINE_BOXED_TYPE(NMTCQdisc, nm_tc_qdisc, nm_tc_qdisc_dup, nm_tc_qdisc_unref) + +struct NMTCQdisc { + guint refcount; + + char * kind; + guint32 handle; + guint32 parent; + GHashTable *attributes; +}; + +/** + * nm_tc_qdisc_new: + * @kind: name of the queueing discipline + * @parent: the parent queueing discipline + * @error: location to store error, or %NULL + * + * Creates a new #NMTCQdisc object. + * + * Returns: (transfer full): the new #NMTCQdisc object, or %NULL on error + * + * Since: 1.12 + **/ +NMTCQdisc * +nm_tc_qdisc_new(const char *kind, guint32 parent, GError **error) +{ + NMTCQdisc *qdisc; + + if (!kind || !*kind) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("kind is missing")); + return NULL; + } + + if (strchr(kind, ' ') || strchr(kind, '\t')) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid kind"), + kind); + return NULL; + } + + if (!parent) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("parent handle missing")); + return NULL; + } + + qdisc = g_slice_new0(NMTCQdisc); + qdisc->refcount = 1; + + qdisc->kind = g_strdup(kind); + qdisc->parent = parent; + + return qdisc; +} + +/** + * nm_tc_qdisc_ref: + * @qdisc: the #NMTCQdisc + * + * Increases the reference count of the object. + * + * Since: 1.12 + **/ +void +nm_tc_qdisc_ref(NMTCQdisc *qdisc) +{ + g_return_if_fail(qdisc != NULL); + g_return_if_fail(qdisc->refcount > 0); + + qdisc->refcount++; +} + +/** + * nm_tc_qdisc_unref: + * @qdisc: the #NMTCQdisc + * + * Decreases the reference count of the object. If the reference count + * reaches zero, the object will be destroyed. + * + * Since: 1.12 + **/ +void +nm_tc_qdisc_unref(NMTCQdisc *qdisc) +{ + g_return_if_fail(qdisc != NULL); + g_return_if_fail(qdisc->refcount > 0); + + qdisc->refcount--; + if (qdisc->refcount == 0) { + g_free(qdisc->kind); + if (qdisc->attributes) + g_hash_table_unref(qdisc->attributes); + g_slice_free(NMTCQdisc, qdisc); + } +} + +/** + * nm_tc_qdisc_equal: + * @qdisc: the #NMTCQdisc + * @other: the #NMTCQdisc to compare @qdisc to. + * + * Determines if two #NMTCQdisc objects contain the same kind, * handle + * and parent. + * + * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. + * + * Since: 1.12 + **/ +gboolean +nm_tc_qdisc_equal(NMTCQdisc *qdisc, NMTCQdisc *other) +{ + GHashTableIter iter; + const char * key; + GVariant * value, *value2; + guint n; + + g_return_val_if_fail(qdisc != NULL, FALSE); + g_return_val_if_fail(qdisc->refcount > 0, FALSE); + + g_return_val_if_fail(other != NULL, FALSE); + g_return_val_if_fail(other->refcount > 0, FALSE); + + if (qdisc->handle != other->handle || qdisc->parent != other->parent + || g_strcmp0(qdisc->kind, other->kind) != 0) + return FALSE; + + n = qdisc->attributes ? g_hash_table_size(qdisc->attributes) : 0; + if (n != (other->attributes ? g_hash_table_size(other->attributes) : 0)) + return FALSE; + if (n) { + g_hash_table_iter_init(&iter, qdisc->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { + value2 = g_hash_table_lookup(other->attributes, key); + if (!value2) + return FALSE; + if (!g_variant_equal(value, value2)) + return FALSE; + } + } + + return TRUE; +} + +static guint +_nm_tc_qdisc_hash(NMTCQdisc *qdisc) +{ + NMUtilsNamedValue attrs_static[30]; + gs_free NMUtilsNamedValue *attrs_free = NULL; + const NMUtilsNamedValue * attrs; + NMHashState h; + guint length; + guint i; + + attrs = + nm_utils_named_values_from_strdict(qdisc->attributes, &length, attrs_static, &attrs_free); + + nm_hash_init(&h, 43869703); + nm_hash_update_vals(&h, qdisc->handle, qdisc->parent, length); + nm_hash_update_str0(&h, qdisc->kind); + for (i = 0; i < length; i++) { + const char * key = attrs[i].name; + GVariant * variant = attrs[i].value_ptr; + const GVariantType *vtype; + + vtype = g_variant_get_type(variant); + + nm_hash_update_str(&h, key); + nm_hash_update_str(&h, (const char *) vtype); + if (g_variant_type_is_basic(vtype)) + nm_hash_update_val(&h, g_variant_hash(variant)); + } + + return nm_hash_complete(&h); +} + +/** + * nm_tc_qdisc_dup: + * @qdisc: the #NMTCQdisc + * + * Creates a copy of @qdisc + * + * Returns: (transfer full): a copy of @qdisc + * + * Since: 1.12 + **/ +NMTCQdisc * +nm_tc_qdisc_dup(NMTCQdisc *qdisc) +{ + NMTCQdisc *copy; + + g_return_val_if_fail(qdisc != NULL, NULL); + g_return_val_if_fail(qdisc->refcount > 0, NULL); + + copy = nm_tc_qdisc_new(qdisc->kind, qdisc->parent, NULL); + nm_tc_qdisc_set_handle(copy, qdisc->handle); + + if (qdisc->attributes) { + GHashTableIter iter; + const char * key; + GVariant * value; + + g_hash_table_iter_init(&iter, qdisc->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) + nm_tc_qdisc_set_attribute(copy, key, value); + } + + return copy; +} + +/** + * nm_tc_qdisc_get_kind: + * @qdisc: the #NMTCQdisc + * + * Returns: + * + * Since: 1.12 + **/ +const char * +nm_tc_qdisc_get_kind(NMTCQdisc *qdisc) +{ + g_return_val_if_fail(qdisc != NULL, NULL); + g_return_val_if_fail(qdisc->refcount > 0, NULL); + + return qdisc->kind; +} + +/** + * nm_tc_qdisc_get_handle: + * @qdisc: the #NMTCQdisc + * + * Returns: the queueing discipline handle + * + * Since: 1.12 + **/ +guint32 +nm_tc_qdisc_get_handle(NMTCQdisc *qdisc) +{ + g_return_val_if_fail(qdisc != NULL, TC_H_UNSPEC); + g_return_val_if_fail(qdisc->refcount > 0, TC_H_UNSPEC); + + return qdisc->handle; +} + +/** + * nm_tc_qdisc_set_handle: + * @qdisc: the #NMTCQdisc + * @handle: the queueing discipline handle + * + * Sets the queueing discipline handle. + * + * Since: 1.12 + **/ +void +nm_tc_qdisc_set_handle(NMTCQdisc *qdisc, guint32 handle) +{ + g_return_if_fail(qdisc != NULL); + g_return_if_fail(qdisc->refcount > 0); + + qdisc->handle = handle; +} + +/** + * nm_tc_qdisc_get_parent: + * @qdisc: the #NMTCQdisc + * + * Returns: the parent class + * + * Since: 1.12 + **/ +guint32 +nm_tc_qdisc_get_parent(NMTCQdisc *qdisc) +{ + g_return_val_if_fail(qdisc != NULL, TC_H_UNSPEC); + g_return_val_if_fail(qdisc->refcount > 0, TC_H_UNSPEC); + + return qdisc->parent; +} + +/** + * nm_tc_qdisc_get_attribute_names: + * @qdisc: the #NMTCQdisc + * + * Gets an array of attribute names defined on @qdisc. + * + * Returns: (transfer container): a %NULL-terminated array of attribute names + * or %NULL if no attributes are set. + * + * Since: 1.18 + **/ +const char ** +nm_tc_qdisc_get_attribute_names(NMTCQdisc *qdisc) +{ + g_return_val_if_fail(qdisc, NULL); + + return nm_utils_strdict_get_keys(qdisc->attributes, TRUE, NULL); +} + +GHashTable * +_nm_tc_qdisc_get_attributes(NMTCQdisc *qdisc) +{ + nm_assert(qdisc); + + return qdisc->attributes; +} + +/** + * nm_tc_qdisc_get_attribute: + * @qdisc: the #NMTCQdisc + * @name: the name of an qdisc attribute + * + * Gets the value of the attribute with name @name on @qdisc + * + * Returns: (transfer none): the value of the attribute with name @name on + * @qdisc, or %NULL if @qdisc has no such attribute. + * + * Since: 1.18 + **/ +GVariant * +nm_tc_qdisc_get_attribute(NMTCQdisc *qdisc, const char *name) +{ + g_return_val_if_fail(qdisc != NULL, NULL); + g_return_val_if_fail(name != NULL && *name != '\0', NULL); + + if (qdisc->attributes) + return g_hash_table_lookup(qdisc->attributes, name); + else + return NULL; +} + +/** + * nm_tc_qdisc_set_attribute: + * @qdisc: the #NMTCQdisc + * @name: the name of an qdisc attribute + * @value: (transfer none) (allow-none): the value + * + * Sets or clears the named attribute on @qdisc to the given value. + * + * Since: 1.18 + **/ +void +nm_tc_qdisc_set_attribute(NMTCQdisc *qdisc, const char *name, GVariant *value) +{ + g_return_if_fail(qdisc != NULL); + g_return_if_fail(name != NULL && *name != '\0'); + g_return_if_fail(strcmp(name, "kind") != 0); + + if (!qdisc->attributes) { + qdisc->attributes = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref); + } + + if (value) + g_hash_table_insert(qdisc->attributes, g_strdup(name), g_variant_ref_sink(value)); + else + g_hash_table_remove(qdisc->attributes, name); +} + +/*****************************************************************************/ + +G_DEFINE_BOXED_TYPE(NMTCAction, nm_tc_action, nm_tc_action_dup, nm_tc_action_unref) + +struct NMTCAction { + guint refcount; + + char *kind; + + GHashTable *attributes; +}; + +/** + * nm_tc_action_new: + * @kind: name of the queueing discipline + * @error: location to store error, or %NULL + * + * Creates a new #NMTCAction object. + * + * Returns: (transfer full): the new #NMTCAction object, or %NULL on error + * + * Since: 1.12 + **/ +NMTCAction * +nm_tc_action_new(const char *kind, GError **error) +{ + NMTCAction *action; + + if (!kind || !*kind) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("kind is missing")); + return NULL; + } + + if (strchr(kind, ' ') || strchr(kind, '\t')) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid kind"), + kind); + return NULL; + } + + action = g_slice_new0(NMTCAction); + action->refcount = 1; + + action->kind = g_strdup(kind); + + return action; +} + +/** + * nm_tc_action_ref: + * @action: the #NMTCAction + * + * Increases the reference count of the object. + * + * Since: 1.12 + **/ +void +nm_tc_action_ref(NMTCAction *action) +{ + g_return_if_fail(action != NULL); + g_return_if_fail(action->refcount > 0); + + action->refcount++; +} + +/** + * nm_tc_action_unref: + * @action: the #NMTCAction + * + * Decreases the reference count of the object. If the reference count + * reaches zero, the object will be destroyed. + * + * Since: 1.12 + **/ +void +nm_tc_action_unref(NMTCAction *action) +{ + g_return_if_fail(action != NULL); + g_return_if_fail(action->refcount > 0); + + action->refcount--; + if (action->refcount == 0) { + g_free(action->kind); + if (action->attributes) + g_hash_table_unref(action->attributes); + g_slice_free(NMTCAction, action); + } +} + +/** + * nm_tc_action_equal: + * @action: the #NMTCAction + * @other: the #NMTCAction to compare @action to. + * + * Determines if two #NMTCAction objects contain the same kind, family, + * handle, parent and info. + * + * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. + * + * Since: 1.12 + **/ +gboolean +nm_tc_action_equal(NMTCAction *action, NMTCAction *other) +{ + GHashTableIter iter; + const char * key; + GVariant * value, *value2; + guint n; + + g_return_val_if_fail(!action || action->refcount > 0, FALSE); + g_return_val_if_fail(!other || other->refcount > 0, FALSE); + + if (action == other) + return TRUE; + if (!action || !other) + return FALSE; + + if (g_strcmp0(action->kind, other->kind) != 0) + return FALSE; + + n = action->attributes ? g_hash_table_size(action->attributes) : 0; + if (n != (other->attributes ? g_hash_table_size(other->attributes) : 0)) + return FALSE; + if (n) { + g_hash_table_iter_init(&iter, action->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) { + value2 = g_hash_table_lookup(other->attributes, key); + if (!value2) + return FALSE; + if (!g_variant_equal(value, value2)) + return FALSE; + } + } + + return TRUE; +} + +/** + * nm_tc_action_dup: + * @action: the #NMTCAction + * + * Creates a copy of @action + * + * Returns: (transfer full): a copy of @action + * + * Since: 1.12 + **/ +NMTCAction * +nm_tc_action_dup(NMTCAction *action) +{ + NMTCAction *copy; + + g_return_val_if_fail(action != NULL, NULL); + g_return_val_if_fail(action->refcount > 0, NULL); + + copy = nm_tc_action_new(action->kind, NULL); + + if (action->attributes) { + GHashTableIter iter; + const char * key; + GVariant * value; + + g_hash_table_iter_init(&iter, action->attributes); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) + nm_tc_action_set_attribute(copy, key, value); + } + + return copy; +} + +/** + * nm_tc_action_get_kind: + * @action: the #NMTCAction + * + * Returns: + * + * Since: 1.12 + **/ +const char * +nm_tc_action_get_kind(NMTCAction *action) +{ + g_return_val_if_fail(action != NULL, NULL); + g_return_val_if_fail(action->refcount > 0, NULL); + + return action->kind; +} + +/** + * nm_tc_action_get_attribute_names: + * @action: the #NMTCAction + * + * Gets an array of attribute names defined on @action. + * + * Returns: (transfer full): a %NULL-terminated array of attribute names, + * + * Since: 1.12 + **/ +char ** +nm_tc_action_get_attribute_names(NMTCAction *action) +{ + const char **names; + + g_return_val_if_fail(action, NULL); + + names = nm_utils_strdict_get_keys(action->attributes, TRUE, NULL); + return nm_utils_strv_make_deep_copied_nonnull(names); +} + +GHashTable * +_nm_tc_action_get_attributes(NMTCAction *action) +{ + nm_assert(action); + + return action->attributes; +} + +/** + * nm_tc_action_get_attribute: + * @action: the #NMTCAction + * @name: the name of an action attribute + * + * Gets the value of the attribute with name @name on @action + * + * Returns: (transfer none): the value of the attribute with name @name on + * @action, or %NULL if @action has no such attribute. + * + * Since: 1.12 + **/ +GVariant * +nm_tc_action_get_attribute(NMTCAction *action, const char *name) +{ + g_return_val_if_fail(action != NULL, NULL); + g_return_val_if_fail(name != NULL && *name != '\0', NULL); + + if (action->attributes) + return g_hash_table_lookup(action->attributes, name); + else + return NULL; +} + +/** + * nm_tc_action_set_attribute: + * @action: the #NMTCAction + * @name: the name of an action attribute + * @value: (transfer none) (allow-none): the value + * + * Sets or clears the named attribute on @action to the given value. + * + * Since: 1.12 + **/ +void +nm_tc_action_set_attribute(NMTCAction *action, const char *name, GVariant *value) +{ + g_return_if_fail(action != NULL); + g_return_if_fail(name != NULL && *name != '\0'); + g_return_if_fail(strcmp(name, "kind") != 0); + + if (!action->attributes) { + action->attributes = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref); + } + + if (value) + g_hash_table_insert(action->attributes, g_strdup(name), g_variant_ref_sink(value)); + else + g_hash_table_remove(action->attributes, name); +} + +/*****************************************************************************/ + +G_DEFINE_BOXED_TYPE(NMTCTfilter, nm_tc_tfilter, nm_tc_tfilter_dup, nm_tc_tfilter_unref) + +struct NMTCTfilter { + guint refcount; + + char * kind; + guint32 handle; + guint32 parent; + NMTCAction *action; +}; + +/** + * nm_tc_tfilter_new: + * @kind: name of the queueing discipline + * @parent: the parent queueing discipline + * @error: location to store error, or %NULL + * + * Creates a new #NMTCTfilter object. + * + * Returns: (transfer full): the new #NMTCTfilter object, or %NULL on error + * + * Since: 1.12 + **/ +NMTCTfilter * +nm_tc_tfilter_new(const char *kind, guint32 parent, GError **error) +{ + NMTCTfilter *tfilter; + + if (!kind || !*kind) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("kind is missing")); + return NULL; + } + + if (strchr(kind, ' ') || strchr(kind, '\t')) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid kind"), + kind); + return NULL; + } + + if (!parent) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("parent handle missing")); + return NULL; + } + + tfilter = g_slice_new0(NMTCTfilter); + tfilter->refcount = 1; + + tfilter->kind = g_strdup(kind); + tfilter->parent = parent; + + return tfilter; +} + +/** + * nm_tc_tfilter_ref: + * @tfilter: the #NMTCTfilter + * + * Increases the reference count of the object. + * + * Since: 1.12 + **/ +void +nm_tc_tfilter_ref(NMTCTfilter *tfilter) +{ + g_return_if_fail(tfilter != NULL); + g_return_if_fail(tfilter->refcount > 0); + + tfilter->refcount++; +} + +/** + * nm_tc_tfilter_unref: + * @tfilter: the #NMTCTfilter + * + * Decreases the reference count of the object. If the reference count + * reaches zero, the object will be destroyed. + * + * Since: 1.12 + **/ +void +nm_tc_tfilter_unref(NMTCTfilter *tfilter) +{ + g_return_if_fail(tfilter != NULL); + g_return_if_fail(tfilter->refcount > 0); + + tfilter->refcount--; + if (tfilter->refcount == 0) { + g_free(tfilter->kind); + if (tfilter->action) + nm_tc_action_unref(tfilter->action); + g_slice_free(NMTCTfilter, tfilter); + } +} + +/** + * nm_tc_tfilter_equal: + * @tfilter: the #NMTCTfilter + * @other: the #NMTCTfilter to compare @tfilter to. + * + * Determines if two #NMTCTfilter objects contain the same kind, family, + * handle, parent and info. + * + * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. + * + * Since: 1.12 + **/ +gboolean +nm_tc_tfilter_equal(NMTCTfilter *tfilter, NMTCTfilter *other) +{ + g_return_val_if_fail(tfilter != NULL, FALSE); + g_return_val_if_fail(tfilter->refcount > 0, FALSE); + + g_return_val_if_fail(other != NULL, FALSE); + g_return_val_if_fail(other->refcount > 0, FALSE); + + if (tfilter->handle != other->handle || tfilter->parent != other->parent + || g_strcmp0(tfilter->kind, other->kind) != 0 + || !nm_tc_action_equal(tfilter->action, other->action)) + return FALSE; + + return TRUE; +} + +static guint +_nm_tc_tfilter_hash(NMTCTfilter *tfilter) +{ + NMHashState h; + + nm_hash_init(&h, 63624437); + nm_hash_update_vals(&h, tfilter->handle, tfilter->parent); + nm_hash_update_str0(&h, tfilter->kind); + + if (tfilter->action) { + gs_free NMUtilsNamedValue *attrs_free = NULL; + NMUtilsNamedValue attrs_static[30]; + const NMUtilsNamedValue * attrs; + guint length; + guint i; + + nm_hash_update_str0(&h, tfilter->action->kind); + + attrs = nm_utils_named_values_from_strdict(tfilter->action->attributes, + &length, + attrs_static, + &attrs_free); + for (i = 0; i < length; i++) { + GVariant *variant = attrs[i].value_ptr; + + nm_hash_update_str(&h, attrs[i].name); + if (g_variant_type_is_basic(g_variant_get_type(variant))) { + guint attr_hash; + + /* g_variant_hash() works only for basic types, thus + * we ignore any non-basic attribute. Actions differing + * only for non-basic attributes will collide. */ + attr_hash = g_variant_hash(variant); + nm_hash_update_val(&h, attr_hash); + } + } + } + + return nm_hash_complete(&h); +} + +/** + * nm_tc_tfilter_dup: + * @tfilter: the #NMTCTfilter + * + * Creates a copy of @tfilter + * + * Returns: (transfer full): a copy of @tfilter + * + * Since: 1.12 + **/ +NMTCTfilter * +nm_tc_tfilter_dup(NMTCTfilter *tfilter) +{ + NMTCTfilter *copy; + + g_return_val_if_fail(tfilter != NULL, NULL); + g_return_val_if_fail(tfilter->refcount > 0, NULL); + + copy = nm_tc_tfilter_new(tfilter->kind, tfilter->parent, NULL); + nm_tc_tfilter_set_handle(copy, tfilter->handle); + nm_tc_tfilter_set_action(copy, tfilter->action); + + return copy; +} + +/** + * nm_tc_tfilter_get_kind: + * @tfilter: the #NMTCTfilter + * + * Returns: + * + * Since: 1.12 + **/ +const char * +nm_tc_tfilter_get_kind(NMTCTfilter *tfilter) +{ + g_return_val_if_fail(tfilter != NULL, NULL); + g_return_val_if_fail(tfilter->refcount > 0, NULL); + + return tfilter->kind; +} + +/** + * nm_tc_tfilter_get_handle: + * @tfilter: the #NMTCTfilter + * + * Returns: the queueing discipline handle + * + * Since: 1.12 + **/ +guint32 +nm_tc_tfilter_get_handle(NMTCTfilter *tfilter) +{ + g_return_val_if_fail(tfilter != NULL, TC_H_UNSPEC); + g_return_val_if_fail(tfilter->refcount > 0, TC_H_UNSPEC); + + return tfilter->handle; +} + +/** + * nm_tc_tfilter_set_handle: + * @tfilter: the #NMTCTfilter + * @handle: the queueing discipline handle + * + * Sets the queueing discipline handle. + * + * Since: 1.12 + **/ +void +nm_tc_tfilter_set_handle(NMTCTfilter *tfilter, guint32 handle) +{ + g_return_if_fail(tfilter != NULL); + g_return_if_fail(tfilter->refcount > 0); + + tfilter->handle = handle; +} + +/** + * nm_tc_tfilter_get_parent: + * @tfilter: the #NMTCTfilter + * + * Returns: the parent class + * + * Since: 1.12 + **/ +guint32 +nm_tc_tfilter_get_parent(NMTCTfilter *tfilter) +{ + g_return_val_if_fail(tfilter != NULL, TC_H_UNSPEC); + g_return_val_if_fail(tfilter->refcount > 0, TC_H_UNSPEC); + + return tfilter->parent; +} + +/** + * nm_tc_tfilter_get_action: + * @tfilter: the #NMTCTfilter + * + * Returns: the action associated with a traffic filter. + * + * Since: 1.12 + **/ +NMTCAction * +nm_tc_tfilter_get_action(NMTCTfilter *tfilter) +{ + g_return_val_if_fail(tfilter != NULL, TC_H_UNSPEC); + g_return_val_if_fail(tfilter->refcount > 0, TC_H_UNSPEC); + + if (tfilter->action == NULL) + return NULL; + + return tfilter->action; +} + +/** + * nm_tc_tfilter_set_action: + * @tfilter: the #NMTCTfilter + * @action: the action object + * + * Sets the action associated with a traffic filter. + * + * Since: 1.12 + **/ +void +nm_tc_tfilter_set_action(NMTCTfilter *tfilter, NMTCAction *action) +{ + g_return_if_fail(tfilter != NULL); + g_return_if_fail(tfilter->refcount > 0); + + if (action) + nm_tc_action_ref(action); + if (tfilter->action) + nm_tc_action_unref(tfilter->action); + tfilter->action = action; +} + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingTCConfig, PROP_QDISCS, PROP_TFILTERS, ); + +/** + * NMSettingTCConfig: + * + * Linux Traffic Control Settings + * + * Since: 1.12 + */ +struct _NMSettingTCConfig { + NMSetting parent; + GPtrArray *qdiscs; + GPtrArray *tfilters; +}; + +struct _NMSettingTCConfigClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingTCConfig, nm_setting_tc_config, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_tc_config_get_num_qdiscs: + * @setting: the #NMSettingTCConfig + * + * Returns: the number of configured queueing disciplines + * + * Since: 1.12 + **/ +guint +nm_setting_tc_config_get_num_qdiscs(NMSettingTCConfig *self) +{ + g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), 0); + + return self->qdiscs->len; +} + +/** + * nm_setting_tc_config_get_qdisc: + * @setting: the #NMSettingTCConfig + * @idx: index number of the qdisc to return + * + * Returns: (transfer none): the qdisc at index @idx + * + * Since: 1.12 + **/ +NMTCQdisc * +nm_setting_tc_config_get_qdisc(NMSettingTCConfig *self, guint idx) +{ + g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), NULL); + g_return_val_if_fail(idx < self->qdiscs->len, NULL); + + return self->qdiscs->pdata[idx]; +} + +/** + * nm_setting_tc_config_add_qdisc: + * @setting: the #NMSettingTCConfig + * @qdisc: the qdisc to add + * + * Appends a new qdisc and associated information to the setting. The + * given qdisc is duplicated internally and is not changed by this function. + * If an identical qdisc (considering attributes as well) already exists, the + * qdisc is not added and the function returns %FALSE. + * + * Returns: %TRUE if the qdisc was added; %FALSE if the qdisc was already known. + * + * Since: 1.12 + **/ +gboolean +nm_setting_tc_config_add_qdisc(NMSettingTCConfig *self, NMTCQdisc *qdisc) +{ + guint i; + + g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), FALSE); + g_return_val_if_fail(qdisc != NULL, FALSE); + + for (i = 0; i < self->qdiscs->len; i++) { + if (nm_tc_qdisc_equal(self->qdiscs->pdata[i], qdisc)) + return FALSE; + } + + g_ptr_array_add(self->qdiscs, nm_tc_qdisc_dup(qdisc)); + _notify(self, PROP_QDISCS); + return TRUE; +} + +/** + * nm_setting_tc_config_remove_qdisc: + * @setting: the #NMSettingTCConfig + * @idx: index number of the qdisc + * + * Removes the qdisc at index @idx. + * + * Since: 1.12 + **/ +void +nm_setting_tc_config_remove_qdisc(NMSettingTCConfig *self, guint idx) +{ + g_return_if_fail(NM_IS_SETTING_TC_CONFIG(self)); + + g_return_if_fail(idx < self->qdiscs->len); + + g_ptr_array_remove_index(self->qdiscs, idx); + _notify(self, PROP_QDISCS); +} + +/** + * nm_setting_tc_config_remove_qdisc_by_value: + * @setting: the #NMSettingTCConfig + * @qdisc: the qdisc to remove + * + * Removes the first matching qdisc that matches @qdisc. + * + * Returns: %TRUE if the qdisc was found and removed; %FALSE if it was not. + * + * Since: 1.12 + **/ +gboolean +nm_setting_tc_config_remove_qdisc_by_value(NMSettingTCConfig *self, NMTCQdisc *qdisc) +{ + guint i; + + g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), FALSE); + g_return_val_if_fail(qdisc != NULL, FALSE); + + for (i = 0; i < self->qdiscs->len; i++) { + if (nm_tc_qdisc_equal(self->qdiscs->pdata[i], qdisc)) { + g_ptr_array_remove_index(self->qdiscs, i); + _notify(self, PROP_QDISCS); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_tc_config_clear_qdiscs: + * @setting: the #NMSettingTCConfig + * + * Removes all configured queueing disciplines. + * + * Since: 1.12 + **/ +void +nm_setting_tc_config_clear_qdiscs(NMSettingTCConfig *self) +{ + g_return_if_fail(NM_IS_SETTING_TC_CONFIG(self)); + + if (self->qdiscs->len != 0) { + g_ptr_array_set_size(self->qdiscs, 0); + _notify(self, PROP_QDISCS); + } +} + +/*****************************************************************************/ +/** + * nm_setting_tc_config_get_num_tfilters: + * @setting: the #NMSettingTCConfig + * + * Returns: the number of configured queueing disciplines + * + * Since: 1.12 + **/ +guint +nm_setting_tc_config_get_num_tfilters(NMSettingTCConfig *self) +{ + g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), 0); + + return self->tfilters->len; +} + +/** + * nm_setting_tc_config_get_tfilter: + * @setting: the #NMSettingTCConfig + * @idx: index number of the tfilter to return + * + * Returns: (transfer none): the tfilter at index @idx + * + * Since: 1.12 + **/ +NMTCTfilter * +nm_setting_tc_config_get_tfilter(NMSettingTCConfig *self, guint idx) +{ + g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), NULL); + g_return_val_if_fail(idx < self->tfilters->len, NULL); + + return self->tfilters->pdata[idx]; +} + +/** + * nm_setting_tc_config_add_tfilter: + * @setting: the #NMSettingTCConfig + * @tfilter: the tfilter to add + * + * Appends a new tfilter and associated information to the setting. The + * given tfilter is duplicated internally and is not changed by this function. + * If an identical tfilter (considering attributes as well) already exists, the + * tfilter is not added and the function returns %FALSE. + * + * Returns: %TRUE if the tfilter was added; %FALSE if the tfilter was already known. + * + * Since: 1.12 + **/ +gboolean +nm_setting_tc_config_add_tfilter(NMSettingTCConfig *self, NMTCTfilter *tfilter) +{ + guint i; + + g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), FALSE); + g_return_val_if_fail(tfilter != NULL, FALSE); + + for (i = 0; i < self->tfilters->len; i++) { + if (nm_tc_tfilter_equal(self->tfilters->pdata[i], tfilter)) + return FALSE; + } + + g_ptr_array_add(self->tfilters, nm_tc_tfilter_dup(tfilter)); + _notify(self, PROP_TFILTERS); + return TRUE; +} + +/** + * nm_setting_tc_config_remove_tfilter: + * @setting: the #NMSettingTCConfig + * @idx: index number of the tfilter + * + * Removes the tfilter at index @idx. + * + * Since: 1.12 + **/ +void +nm_setting_tc_config_remove_tfilter(NMSettingTCConfig *self, guint idx) +{ + g_return_if_fail(NM_IS_SETTING_TC_CONFIG(self)); + g_return_if_fail(idx < self->tfilters->len); + + g_ptr_array_remove_index(self->tfilters, idx); + _notify(self, PROP_TFILTERS); +} + +/** + * nm_setting_tc_config_remove_tfilter_by_value: + * @setting: the #NMSettingTCConfig + * @tfilter: the tfilter to remove + * + * Removes the first matching tfilter that matches @tfilter. + * + * Returns: %TRUE if the tfilter was found and removed; %FALSE if it was not. + * + * Since: 1.12 + **/ +gboolean +nm_setting_tc_config_remove_tfilter_by_value(NMSettingTCConfig *self, NMTCTfilter *tfilter) +{ + guint i; + + g_return_val_if_fail(NM_IS_SETTING_TC_CONFIG(self), FALSE); + g_return_val_if_fail(tfilter != NULL, FALSE); + + for (i = 0; i < self->tfilters->len; i++) { + if (nm_tc_tfilter_equal(self->tfilters->pdata[i], tfilter)) { + g_ptr_array_remove_index(self->tfilters, i); + _notify(self, PROP_TFILTERS); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_tc_config_clear_tfilters: + * @setting: the #NMSettingTCConfig + * + * Removes all configured queueing disciplines. + * + * Since: 1.12 + **/ +void +nm_setting_tc_config_clear_tfilters(NMSettingTCConfig *self) +{ + g_return_if_fail(NM_IS_SETTING_TC_CONFIG(self)); + + if (self->tfilters->len != 0) { + g_ptr_array_set_size(self->tfilters, 0); + _notify(self, PROP_TFILTERS); + } +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingTCConfig *self = NM_SETTING_TC_CONFIG(setting); + guint i; + + if (self->qdiscs->len != 0) { + gs_unref_hashtable GHashTable *ht = NULL; + + ht = g_hash_table_new((GHashFunc) _nm_tc_qdisc_hash, (GEqualFunc) nm_tc_qdisc_equal); + for (i = 0; i < self->qdiscs->len; i++) { + if (!g_hash_table_add(ht, self->qdiscs->pdata[i])) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("there are duplicate TC qdiscs")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_TC_CONFIG_SETTING_NAME, + NM_SETTING_TC_CONFIG_QDISCS); + return FALSE; + } + } + } + + if (self->tfilters->len != 0) { + gs_unref_hashtable GHashTable *ht = NULL; + + ht = g_hash_table_new((GHashFunc) _nm_tc_tfilter_hash, (GEqualFunc) nm_tc_tfilter_equal); + for (i = 0; i < self->tfilters->len; i++) { + if (!g_hash_table_add(ht, self->tfilters->pdata[i])) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("there are duplicate TC filters")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_TC_CONFIG_SETTING_NAME, + NM_SETTING_TC_CONFIG_TFILTERS); + return FALSE; + } + } + } + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingTCConfig *a_tc_config = NM_SETTING_TC_CONFIG(set_a); + NMSettingTCConfig *b_tc_config = NM_SETTING_TC_CONFIG(set_b); + guint i; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_TC_CONFIG_QDISCS)) { + if (set_b) { + if (a_tc_config->qdiscs->len != b_tc_config->qdiscs->len) + return FALSE; + for (i = 0; i < a_tc_config->qdiscs->len; i++) { + if (!nm_tc_qdisc_equal(a_tc_config->qdiscs->pdata[i], + b_tc_config->qdiscs->pdata[i])) + return FALSE; + } + } + return TRUE; + } + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_TC_CONFIG_TFILTERS)) { + if (set_b) { + if (a_tc_config->tfilters->len != b_tc_config->tfilters->len) + return FALSE; + for (i = 0; i < a_tc_config->tfilters->len; i++) { + if (!nm_tc_tfilter_equal(a_tc_config->tfilters->pdata[i], + b_tc_config->tfilters->pdata[i])) + return FALSE; + } + } + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_tc_config_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/** + * _qdiscs_to_variant: + * @qdiscs: (element-type NMTCQdisc): an array of #NMTCQdisc objects + * + * Utility function to convert a #GPtrArray of #NMTCQdisc objects representing + * TC qdiscs into a #GVariant of type 'aa{sv}' representing an array + * of NetworkManager TC qdiscs. + * + * Returns: (transfer none): a new floating #GVariant representing @qdiscs. + **/ +static GVariant * +_qdiscs_to_variant(GPtrArray *qdiscs) +{ + GVariantBuilder builder; + int i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + + if (qdiscs) { + for (i = 0; i < qdiscs->len; i++) { + NMUtilsNamedValue attrs_static[30]; + gs_free NMUtilsNamedValue *attrs_free = NULL; + const NMUtilsNamedValue * attrs; + NMTCQdisc * qdisc = qdiscs->pdata[i]; + guint length; + GVariantBuilder qdisc_builder; + guint y; + + g_variant_builder_init(&qdisc_builder, G_VARIANT_TYPE_VARDICT); + + g_variant_builder_add(&qdisc_builder, + "{sv}", + "kind", + g_variant_new_string(nm_tc_qdisc_get_kind(qdisc))); + + g_variant_builder_add(&qdisc_builder, + "{sv}", + "handle", + g_variant_new_uint32(nm_tc_qdisc_get_handle(qdisc))); + + g_variant_builder_add(&qdisc_builder, + "{sv}", + "parent", + g_variant_new_uint32(nm_tc_qdisc_get_parent(qdisc))); + + attrs = nm_utils_named_values_from_strdict(qdisc->attributes, + &length, + attrs_static, + &attrs_free); + for (y = 0; y < length; y++) { + g_variant_builder_add(&qdisc_builder, "{sv}", attrs[y].name, attrs[y].value_ptr); + } + + g_variant_builder_add(&builder, "a{sv}", &qdisc_builder); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * _qdiscs_from_variant: + * @value: a #GVariant of type 'aa{sv}' + * + * Utility function to convert a #GVariant representing a list of TC qdiscs + * into a #GPtrArray of * #NMTCQdisc objects. + * + * Returns: (transfer full) (element-type NMTCQdisc): a newly allocated + * #GPtrArray of #NMTCQdisc objects + **/ +static GPtrArray * +_qdiscs_from_variant(GVariant *value) +{ + GPtrArray * qdiscs; + GVariant * qdisc_var; + GVariantIter iter; + GError * error = NULL; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL); + + g_variant_iter_init(&iter, value); + qdiscs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_qdisc_unref); + + while (g_variant_iter_next(&iter, "@a{sv}", &qdisc_var)) { + const char * kind; + guint32 parent; + NMTCQdisc * qdisc; + GVariantIter qdisc_iter; + const char * key; + GVariant * attr_value; + + if (!g_variant_lookup(qdisc_var, "kind", "&s", &kind) + || !g_variant_lookup(qdisc_var, "parent", "u", &parent)) { + //g_warning ("Ignoring invalid qdisc"); + goto next; + } + + qdisc = nm_tc_qdisc_new(kind, parent, &error); + if (!qdisc) { + //g_warning ("Ignoring invalid qdisc: %s", error->message); + g_clear_error(&error); + goto next; + } + + g_variant_iter_init(&qdisc_iter, qdisc_var); + while (g_variant_iter_next(&qdisc_iter, "{&sv}", &key, &attr_value)) { + if (strcmp(key, "kind") == 0 || strcmp(key, "parent") == 0) { + /* Already processed above */ + } else if (strcmp(key, "handle") == 0) { + nm_tc_qdisc_set_handle(qdisc, g_variant_get_uint32(attr_value)); + } else { + nm_tc_qdisc_set_attribute(qdisc, key, attr_value); + } + g_variant_unref(attr_value); + } + + g_ptr_array_add(qdiscs, qdisc); +next: + g_variant_unref(qdisc_var); + } + + return qdiscs; +} + +static GVariant * +tc_qdiscs_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *qdiscs = NULL; + + g_object_get(setting, NM_SETTING_TC_CONFIG_QDISCS, &qdiscs, NULL); + return _qdiscs_to_variant(qdiscs); +} + +static gboolean +tc_qdiscs_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GPtrArray *qdiscs; + + qdiscs = _qdiscs_from_variant(value); + g_object_set(setting, NM_SETTING_TC_CONFIG_QDISCS, qdiscs, NULL); + g_ptr_array_unref(qdiscs); + + return TRUE; +} + +static GVariant * +_action_to_variant(NMTCAction *action) +{ + GVariantBuilder builder; + gs_strfreev char **attrs = nm_tc_action_get_attribute_names(action); + int i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + + g_variant_builder_add(&builder, + "{sv}", + "kind", + g_variant_new_string(nm_tc_action_get_kind(action))); + + for (i = 0; attrs[i]; i++) { + g_variant_builder_add(&builder, + "{sv}", + attrs[i], + nm_tc_action_get_attribute(action, attrs[i])); + } + + return g_variant_builder_end(&builder); +} + +/** + * _tfilters_to_variant: + * @tfilters: (element-type NMTCTfilter): an array of #NMTCTfilter objects + * + * Utility function to convert a #GPtrArray of #NMTCTfilter objects representing + * TC tfilters into a #GVariant of type 'aa{sv}' representing an array + * of NetworkManager TC tfilters. + * + * Returns: (transfer none): a new floating #GVariant representing @tfilters. + **/ +static GVariant * +_tfilters_to_variant(GPtrArray *tfilters) +{ + GVariantBuilder builder; + int i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + + if (tfilters) { + for (i = 0; i < tfilters->len; i++) { + NMTCTfilter * tfilter = tfilters->pdata[i]; + NMTCAction * action = nm_tc_tfilter_get_action(tfilter); + GVariantBuilder tfilter_builder; + + g_variant_builder_init(&tfilter_builder, G_VARIANT_TYPE("a{sv}")); + + g_variant_builder_add(&tfilter_builder, + "{sv}", + "kind", + g_variant_new_string(nm_tc_tfilter_get_kind(tfilter))); + g_variant_builder_add(&tfilter_builder, + "{sv}", + "handle", + g_variant_new_uint32(nm_tc_tfilter_get_handle(tfilter))); + g_variant_builder_add(&tfilter_builder, + "{sv}", + "parent", + g_variant_new_uint32(nm_tc_tfilter_get_parent(tfilter))); + + if (action) { + g_variant_builder_add(&tfilter_builder, + "{sv}", + "action", + _action_to_variant(action)); + } + + g_variant_builder_add(&builder, "a{sv}", &tfilter_builder); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * _tfilters_from_variant: + * @value: a #GVariant of type 'aa{sv}' + * + * Utility function to convert a #GVariant representing a list of TC tfilters + * into a #GPtrArray of * #NMTCTfilter objects. + * + * Returns: (transfer full) (element-type NMTCTfilter): a newly allocated + * #GPtrArray of #NMTCTfilter objects + **/ +static GPtrArray * +_tfilters_from_variant(GVariant *value) +{ + GPtrArray * tfilters; + GVariant * tfilter_var; + GVariantIter iter; + GError * error = NULL; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL); + + g_variant_iter_init(&iter, value); + tfilters = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_tfilter_unref); + + while (g_variant_iter_next(&iter, "@a{sv}", &tfilter_var)) { + NMTCTfilter *tfilter = NULL; + const char * kind; + guint32 handle; + guint32 parent; + NMTCAction * action; + const char * action_kind = NULL; + char * action_key; + GVariantIter action_iter; + GVariant * action_var = NULL; + GVariant * action_val; + + if (!g_variant_lookup(tfilter_var, "kind", "&s", &kind) + || !g_variant_lookup(tfilter_var, "parent", "u", &parent)) { + //g_warning ("Ignoring invalid tfilter"); + goto next; + } + + tfilter = nm_tc_tfilter_new(kind, parent, &error); + if (!tfilter) { + //g_warning ("Ignoring invalid tfilter: %s", error->message); + g_clear_error(&error); + goto next; + } + + if (g_variant_lookup(tfilter_var, "handle", "u", &handle)) + nm_tc_tfilter_set_handle(tfilter, handle); + + action_var = g_variant_lookup_value(tfilter_var, "action", G_VARIANT_TYPE_VARDICT); + + if (action_var) { + if (!g_variant_lookup(action_var, "kind", "&s", &action_kind)) { + //g_warning ("Ignoring tfilter with invalid action"); + goto next; + } + + action = nm_tc_action_new(action_kind, &error); + if (!action) { + //g_warning ("Ignoring tfilter with invalid action: %s", error->message); + g_clear_error(&error); + goto next; + } + + g_variant_iter_init(&action_iter, action_var); + while (g_variant_iter_next(&action_iter, "{&sv}", &action_key, &action_val)) { + if (strcmp(action_key, "kind") != 0) + nm_tc_action_set_attribute(action, action_key, action_val); + g_variant_unref(action_val); + } + + nm_tc_tfilter_set_action(tfilter, action); + nm_tc_action_unref(action); + } + + nm_tc_tfilter_ref(tfilter); + g_ptr_array_add(tfilters, tfilter); +next: + if (tfilter) + nm_tc_tfilter_unref(tfilter); + if (action_var) + g_variant_unref(action_var); + g_variant_unref(tfilter_var); + } + + return tfilters; +} + +static GVariant * +tc_tfilters_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *tfilters = NULL; + + g_object_get(setting, NM_SETTING_TC_CONFIG_TFILTERS, &tfilters, NULL); + return _tfilters_to_variant(tfilters); +} + +static gboolean +tc_tfilters_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + gs_unref_ptrarray GPtrArray *tfilters = NULL; + + tfilters = _tfilters_from_variant(value); + g_object_set(setting, NM_SETTING_TC_CONFIG_TFILTERS, tfilters, NULL); + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingTCConfig *self = NM_SETTING_TC_CONFIG(object); + + switch (prop_id) { + case PROP_QDISCS: + g_value_take_boxed(value, + _nm_utils_copy_array(self->qdiscs, + (NMUtilsCopyFunc) nm_tc_qdisc_dup, + (GDestroyNotify) nm_tc_qdisc_unref)); + break; + case PROP_TFILTERS: + g_value_take_boxed(value, + _nm_utils_copy_array(self->tfilters, + (NMUtilsCopyFunc) nm_tc_tfilter_dup, + (GDestroyNotify) nm_tc_tfilter_unref)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingTCConfig *self = NM_SETTING_TC_CONFIG(object); + + switch (prop_id) { + case PROP_QDISCS: + g_ptr_array_unref(self->qdiscs); + self->qdiscs = _nm_utils_copy_array(g_value_get_boxed(value), + (NMUtilsCopyFunc) nm_tc_qdisc_dup, + (GDestroyNotify) nm_tc_qdisc_unref); + break; + case PROP_TFILTERS: + g_ptr_array_unref(self->tfilters); + self->tfilters = _nm_utils_copy_array(g_value_get_boxed(value), + (NMUtilsCopyFunc) nm_tc_tfilter_dup, + (GDestroyNotify) nm_tc_tfilter_unref); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_tc_config_init(NMSettingTCConfig *self) +{ + self->qdiscs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_qdisc_unref); + self->tfilters = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_tfilter_unref); +} + +/** + * nm_setting_tc_config_new: + * + * Creates a new #NMSettingTCConfig object with default values. + * + * Returns: (transfer full): the new empty #NMSettingTCConfig object + * + * Since: 1.12 + **/ +NMSetting * +nm_setting_tc_config_new(void) +{ + return g_object_new(NM_TYPE_SETTING_TC_CONFIG, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingTCConfig *self = NM_SETTING_TC_CONFIG(object); + + g_ptr_array_unref(self->qdiscs); + g_ptr_array_unref(self->tfilters); + + G_OBJECT_CLASS(nm_setting_tc_config_parent_class)->finalize(object); +} + +static void +nm_setting_tc_config_class_init(NMSettingTCConfigClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->compare_property = compare_property; + setting_class->verify = verify; + + /** + * NMSettingTCConfig:qdiscs: (type GPtrArray(NMTCQdisc)) + * + * Array of TC queueing disciplines. + * + * When the #NMSettingTCConfig setting is present, qdiscs from this + * property are applied upon activation. If the property is empty, + * all qdiscs are removed and the device will only + * have the default qdisc assigned by kernel according to the + * "net.core.default_qdisc" sysctl. + * + * If the #NMSettingTCConfig setting is not present, NetworkManager + * doesn't touch the qdiscs present on the interface. + **/ + /* ---ifcfg-rh--- + * property: qdiscs + * variable: QDISC1(+), QDISC2(+), ..., TC_COMMIT(+) + * description: Queueing disciplines to set on the interface. When no + * QDISC1, QDISC2, ..., FILTER1, FILTER2, ... keys are present, + * NetworkManager doesn't touch qdiscs and filters present on the + * interface, unless TC_COMMIT is set to 'yes'. + * example: QDISC1=ingress, QDISC2="root handle 1234: fq_codel" + * ---end--- + */ + obj_properties[PROP_QDISCS] = g_param_spec_boxed(NM_SETTING_TC_CONFIG_QDISCS, + "", + "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_QDISCS], + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = tc_qdiscs_get, + .from_dbus_fcn = tc_qdiscs_set, )); + + /** + * NMSettingTCConfig:tfilters: (type GPtrArray(NMTCTfilter)) + * + * Array of TC traffic filters. + * + * When the #NMSettingTCConfig setting is present, filters from this + * property are applied upon activation. If the property is empty, + * NetworkManager removes all the filters. + * + * If the #NMSettingTCConfig setting is not present, NetworkManager + * doesn't touch the filters present on the interface. + **/ + /* ---ifcfg-rh--- + * property: qdiscs + * variable: FILTER1(+), FILTER2(+), ..., TC_COMMIT(+) + * description: Traffic filters to set on the interface. When no + * QDISC1, QDISC2, ..., FILTER1, FILTER2, ... keys are present, + * NetworkManager doesn't touch qdiscs and filters present on the + * interface, unless TC_COMMIT is set to 'yes'. + * example: FILTER1="parent ffff: matchall action simple sdata Input", ... + * ---end--- + */ + obj_properties[PROP_TFILTERS] = g_param_spec_boxed( + NM_SETTING_TC_CONFIG_TFILTERS, + "", + "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_TFILTERS], + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = tc_tfilters_get, + .from_dbus_fcn = tc_tfilters_set, )); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_TC_CONFIG, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-team-port.c b/src/libnm-core-impl/nm-setting-team-port.c new file mode 100644 index 00000000..316fc67c --- /dev/null +++ b/src/libnm-core-impl/nm-setting-team-port.c @@ -0,0 +1,701 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + * Copyright (C) 2013 Jiri Pirko + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-team-port.h" + +#include +#include + +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-team-utils.h" + +/** + * SECTION:nm-setting-team-port + * @short_description: Describes connection properties for team ports + * + * The #NMSettingTeamPort object is a #NMSetting subclass that describes + * optional properties that apply to team ports. + **/ + +/*****************************************************************************/ + +static GParamSpec *obj_properties[_NM_TEAM_ATTRIBUTE_PORT_NUM] = { + NULL, +}; + +typedef struct { + NMTeamSetting *team_setting; +} NMSettingTeamPortPrivate; + +/** + * NMSettingTeamPort: + * + * Team Port Settings + */ +struct _NMSettingTeamPort { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingTeamPortClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingTeamPort, nm_setting_team_port, NM_TYPE_SETTING) + +#define NM_SETTING_TEAM_PORT_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_TEAM_PORT, NMSettingTeamPortPrivate)) + +/*****************************************************************************/ + +NMTeamSetting * +_nm_setting_team_port_get_team_setting(NMSettingTeamPort *setting) +{ + return NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting; +} + +/*****************************************************************************/ + +#define _maybe_changed(self, changed) \ + nm_team_setting_maybe_changed(NM_SETTING(_NM_ENSURE_TYPE(NMSettingTeamPort *, self)), \ + (const GParamSpec *const *) obj_properties, \ + (changed)) + +#define _maybe_changed_with_assert(self, changed) \ + G_STMT_START \ + { \ + if (!_maybe_changed((self), (changed))) \ + nm_assert_not_reached(); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +/** + * nm_setting_team_port_get_config: + * @setting: the #NMSettingTeamPort + * + * Returns: the #NMSettingTeamPort:config property of the setting + **/ +const char * +nm_setting_team_port_get_config(NMSettingTeamPort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), NULL); + + return nm_team_setting_config_get(NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting); +} + +/** + * nm_setting_team_port_get_queue_id: + * @setting: the #NMSettingTeamPort + * + * Returns: the #NMSettingTeamPort:queue_id property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_port_get_queue_id(NMSettingTeamPort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), -1); + + return NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting->d.port.queue_id; +} + +/** + * nm_setting_team_port_get_prio: + * @setting: the #NMSettingTeamPort + * + * Returns: the #NMSettingTeamPort:prio property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_port_get_prio(NMSettingTeamPort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), 0); + + return NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting->d.port.prio; +} + +/** + * nm_setting_team_port_get_sticky: + * @setting: the #NMSettingTeamPort + * + * Returns: the #NMSettingTeamPort:sticky property of the setting + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_port_get_sticky(NMSettingTeamPort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), FALSE); + + return NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting->d.port.sticky; +} + +/** + * nm_setting_team_port_get_lacp_prio: + * @setting: the #NMSettingTeamPort + * + * Returns: the #NMSettingTeamPort:lacp-prio property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_port_get_lacp_prio(NMSettingTeamPort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), 0); + + return NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting->d.port.lacp_prio; +} + +/** + * nm_setting_team_port_get_lacp_key: + * @setting: the #NMSettingTeamPort + * + * Returns: the #NMSettingTeamPort:lacp-key property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_port_get_lacp_key(NMSettingTeamPort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), 0); + + return NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting->d.port.lacp_key; +} + +/** + * nm_setting_team_port_get_num_link_watchers: + * @setting: the #NMSettingTeamPort + * + * Returns: the number of configured link watchers + * + * Since: 1.12 + **/ +guint +nm_setting_team_port_get_num_link_watchers(NMSettingTeamPort *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), 0); + + return NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting->d.link_watchers->len; +} + +/** + * nm_setting_team_port_get_link_watcher: + * @setting: the #NMSettingTeamPort + * @idx: index number of the link watcher to return + * + * Returns: (transfer none): the link watcher at index @idx. + * + * Since: 1.12 + **/ +NMTeamLinkWatcher * +nm_setting_team_port_get_link_watcher(NMSettingTeamPort *setting, guint idx) +{ + NMSettingTeamPortPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), NULL); + + priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(setting); + + g_return_val_if_fail(idx < priv->team_setting->d.link_watchers->len, NULL); + + return priv->team_setting->d.link_watchers->pdata[idx]; +} + +/** + * nm_setting_team_port_add_link_watcher: + * @setting: the #NMSettingTeamPort + * @link_watcher: the link watcher to add + * + * Appends a new link watcher to the setting. + * + * Returns: %TRUE if the link watcher is added; %FALSE if an identical link + * watcher was already there. + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_port_add_link_watcher(NMSettingTeamPort *setting, NMTeamLinkWatcher *link_watcher) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), FALSE); + g_return_val_if_fail(link_watcher != NULL, FALSE); + + return _maybe_changed(setting, + nm_team_setting_value_link_watchers_add( + NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting, + link_watcher)); +} + +/** + * nm_setting_team_port_remove_link_watcher: + * @setting: the #NMSettingTeamPort + * @idx: index number of the link watcher to remove + * + * Removes the link watcher at index #idx. + * + * Since: 1.12 + **/ +void +nm_setting_team_port_remove_link_watcher(NMSettingTeamPort *setting, guint idx) +{ + NMSettingTeamPortPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_TEAM_PORT(setting)); + + priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(setting); + + g_return_if_fail(idx < priv->team_setting->d.link_watchers->len); + + _maybe_changed_with_assert(setting, + nm_team_setting_value_link_watchers_remove(priv->team_setting, idx)); +} + +/** + * nm_setting_team_port_remove_link_watcher_by_value: + * @setting: the #NMSettingTeamPort + * @link_watcher: the link watcher to remove + * + * Removes the link watcher entry matching link_watcher. + * + * Returns: %TRUE if the link watcher was found and removed, %FALSE otherwise. + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_port_remove_link_watcher_by_value(NMSettingTeamPort *setting, + NMTeamLinkWatcher *link_watcher) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM_PORT(setting), FALSE); + g_return_val_if_fail(link_watcher, FALSE); + + return _maybe_changed(setting, + nm_team_setting_value_link_watchers_remove_by_value( + NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting, + link_watcher)); +} + +/** + * nm_setting_team_port_clear_link_watchers: + * @setting: the #NMSettingTeamPort + * + * Removes all configured link watchers. + * + * Since: 1.12 + **/ +void +nm_setting_team_port_clear_link_watchers(NMSettingTeamPort *setting) +{ + g_return_if_fail(NM_IS_SETTING_TEAM_PORT(setting)); + + _maybe_changed(setting, + nm_team_setting_value_link_watchers_set_list( + NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting, + NULL, + 0)); +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(setting); + + if (connection) { + NMSettingConnection *s_con; + const char * slave_type; + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("missing setting")); + g_prefix_error(error, "%s: ", NM_SETTING_CONNECTION_SETTING_NAME); + return FALSE; + } + + slave_type = nm_setting_connection_get_slave_type(s_con); + if (slave_type && strcmp(slave_type, NM_SETTING_TEAM_SETTING_NAME)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("A connection with a '%s' setting must have the slave-type set to '%s'. " + "Instead it is '%s'"), + NM_SETTING_TEAM_PORT_SETTING_NAME, + NM_SETTING_TEAM_SETTING_NAME, + slave_type); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_SLAVE_TYPE); + return FALSE; + } + } + + if (!nm_team_setting_verify(priv->team_setting, error)) + return FALSE; + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingTeamPortPrivate *a_priv; + NMSettingTeamPortPrivate *b_priv; + + if (nm_streq(sett_info->property_infos[property_idx].name, + NM_SETTING_TEAM_PORT_LINK_WATCHERS)) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) + return NM_TERNARY_DEFAULT; + if (!set_b) + return TRUE; + a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(set_a); + b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(set_b); + return nm_team_link_watchers_equal(a_priv->team_setting->d.link_watchers, + b_priv->team_setting->d.link_watchers, + TRUE); + } + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_PORT_CONFIG)) { + if (set_b) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) { + /* If we are trying to match a connection in order to assume it (and thus + * @flags contains INFERRABLE), use the "relaxed" matching for team + * configuration. Otherwise, for all other purposes (including connection + * comparison before an update), resort to the default string comparison. */ + return TRUE; + } + + a_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(set_a); + b_priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(set_b); + + return nm_streq0(nm_team_setting_config_get(a_priv->team_setting), + nm_team_setting_config_get(b_priv->team_setting)); + } + + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_team_port_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +static void +duplicate_copy_properties(const NMSettInfoSetting *sett_info, NMSetting *src, NMSetting *dst) +{ + _maybe_changed(NM_SETTING_TEAM_PORT(dst), + nm_team_setting_reset(NM_SETTING_TEAM_PORT_GET_PRIVATE(dst)->team_setting, + NM_SETTING_TEAM_PORT_GET_PRIVATE(src)->team_setting)); +} + +static gboolean +init_from_dbus(NMSetting * setting, + GHashTable * keys, + GVariant * setting_dict, + GVariant * connection_dict, + guint /* NMSettingParseFlags */ parse_flags, + GError ** error) +{ + guint32 changed = 0; + gboolean success; + + success = + nm_team_setting_reset_from_dbus(NM_SETTING_TEAM_PORT_GET_PRIVATE(setting)->team_setting, + setting_dict, + keys, + &changed, + parse_flags, + error); + _maybe_changed(NM_SETTING_TEAM_PORT(setting), changed); + return success; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingTeamPort * setting = NM_SETTING_TEAM_PORT(object); + NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(setting); + + switch (prop_id) { + case NM_TEAM_ATTRIBUTE_CONFIG: + g_value_set_string(value, nm_team_setting_config_get(priv->team_setting)); + break; + case NM_TEAM_ATTRIBUTE_PORT_STICKY: + g_value_set_boolean(value, nm_team_setting_value_get_bool(priv->team_setting, prop_id)); + break; + case NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID: + case NM_TEAM_ATTRIBUTE_PORT_PRIO: + case NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO: + case NM_TEAM_ATTRIBUTE_PORT_LACP_KEY: + g_value_set_int(value, nm_team_setting_value_get_int32(priv->team_setting, prop_id)); + break; + case NM_TEAM_ATTRIBUTE_LINK_WATCHERS: + g_value_take_boxed(value, + _nm_utils_copy_array(priv->team_setting->d.link_watchers, + (NMUtilsCopyFunc) _nm_team_link_watcher_ref, + (GDestroyNotify) nm_team_link_watcher_unref)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingTeamPort * setting = NM_SETTING_TEAM_PORT(object); + NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(setting); + guint32 changed; + const GPtrArray * v_ptrarr; + + switch (prop_id) { + case NM_TEAM_ATTRIBUTE_CONFIG: + changed = nm_team_setting_config_set(priv->team_setting, g_value_get_string(value)); + break; + case NM_TEAM_ATTRIBUTE_PORT_STICKY: + changed = + nm_team_setting_value_set_bool(priv->team_setting, prop_id, g_value_get_boolean(value)); + break; + case NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID: + case NM_TEAM_ATTRIBUTE_PORT_PRIO: + case NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO: + case NM_TEAM_ATTRIBUTE_PORT_LACP_KEY: + changed = + nm_team_setting_value_set_int32(priv->team_setting, prop_id, g_value_get_int(value)); + break; + case NM_TEAM_ATTRIBUTE_LINK_WATCHERS: + v_ptrarr = g_value_get_boxed(value); + changed = nm_team_setting_value_link_watchers_set_list( + priv->team_setting, + v_ptrarr ? (const NMTeamLinkWatcher *const *) v_ptrarr->pdata : NULL, + v_ptrarr ? v_ptrarr->len : 0u); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + return; + } + + _maybe_changed(setting, changed & ~(((guint32) 1) << prop_id)); +} + +/*****************************************************************************/ + +static void +nm_setting_team_port_init(NMSettingTeamPort *setting) +{ + NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(setting); + + priv->team_setting = nm_team_setting_new(TRUE, NULL); +} + +/** + * nm_setting_team_port_new: + * + * Creates a new #NMSettingTeamPort object with default values. + * + * Returns: (transfer full): the new empty #NMSettingTeamPort object + **/ +NMSetting * +nm_setting_team_port_new(void) +{ + return g_object_new(NM_TYPE_SETTING_TEAM_PORT, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingTeamPortPrivate *priv = NM_SETTING_TEAM_PORT_GET_PRIVATE(object); + + nm_team_setting_free(priv->team_setting); + + G_OBJECT_CLASS(nm_setting_team_port_parent_class)->finalize(object); +} + +static void +nm_setting_team_port_class_init(NMSettingTeamPortClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingTeamPortPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->compare_property = compare_property; + setting_class->verify = verify; + setting_class->duplicate_copy_properties = duplicate_copy_properties; + setting_class->init_from_dbus = init_from_dbus; + + /** + * NMSettingTeamPort:config: + * + * The JSON configuration for the team port. The property should contain raw + * JSON configuration data suitable for teamd, because the value is passed + * directly to teamd. If not specified, the default configuration is + * used. See man teamd.conf for the format details. + **/ + /* ---ifcfg-rh--- + * property: config + * variable: TEAM_PORT_CONFIG + * description: Team port configuration in JSON. See man teamd.conf for details. + * ---end--- + */ + obj_properties[NM_TEAM_ATTRIBUTE_CONFIG] = g_param_spec_string( + NM_SETTING_TEAM_PORT_CONFIG, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_CONFIG], + &nm_sett_info_propert_type_team_s); + + /** + * NMSettingTeamPort:queue-id: + * + * Corresponds to the teamd ports.PORTIFNAME.queue_id. + * When set to -1 means the parameter is skipped from the json config. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID] = + g_param_spec_int(NM_SETTING_TEAM_PORT_QUEUE_ID, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeamPort:prio: + * + * Corresponds to the teamd ports.PORTIFNAME.prio. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_PORT_PRIO] = + g_param_spec_int(NM_SETTING_TEAM_PORT_PRIO, + "", + "", + G_MININT32, + G_MAXINT32, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_PORT_PRIO], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeamPort:sticky: + * + * Corresponds to the teamd ports.PORTIFNAME.sticky. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_PORT_STICKY] = + g_param_spec_boolean(NM_SETTING_TEAM_PORT_STICKY, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_PORT_STICKY], + &nm_sett_info_propert_type_team_b); + + /** + * NMSettingTeamPort:lacp-prio: + * + * Corresponds to the teamd ports.PORTIFNAME.lacp_prio. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO] = + g_param_spec_int(NM_SETTING_TEAM_PORT_LACP_PRIO, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeamPort:lacp-key: + * + * Corresponds to the teamd ports.PORTIFNAME.lacp_key. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_PORT_LACP_KEY] = + g_param_spec_int(NM_SETTING_TEAM_PORT_LACP_KEY, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_PORT_LACP_KEY], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeamPort:link-watchers: (type GPtrArray(NMTeamLinkWatcher)) + * + * Link watchers configuration for the connection: each link watcher is + * defined by a dictionary, whose keys depend upon the selected link + * watcher. Available link watchers are 'ethtool', 'nsna_ping' and + * 'arp_ping' and it is specified in the dictionary with the key 'name'. + * Available keys are: ethtool: 'delay-up', 'delay-down', 'init-wait'; + * nsna_ping: 'init-wait', 'interval', 'missed-max', 'target-host'; + * arp_ping: all the ones in nsna_ping and 'source-host', 'validate-active', + * 'validate-inactive', 'send-always'. See teamd.conf man for more details. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_LINK_WATCHERS] = + g_param_spec_boxed(NM_SETTING_TEAM_PORT_LINK_WATCHERS, + "", + "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_LINK_WATCHERS], + &nm_sett_info_propert_type_team_link_watchers); + + g_object_class_install_properties(object_class, G_N_ELEMENTS(obj_properties), obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_TEAM_PORT, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-team.c b/src/libnm-core-impl/nm-setting-team.c new file mode 100644 index 00000000..b9a368c5 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-team.c @@ -0,0 +1,1834 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + * Copyright (C) 2013 Jiri Pirko + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-team.h" + +#include + +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-team-utils.h" +#include "nm-connection-private.h" + +/** + * SECTION:nm-setting-team + * @short_description: Describes connection properties for teams + * + * The #NMSettingTeam object is a #NMSetting subclass that describes properties + * necessary for team connections. + **/ + +/***************************************************************************** + * NMTeamLinkWatcher + *****************************************************************************/ + +G_DEFINE_BOXED_TYPE(NMTeamLinkWatcher, + nm_team_link_watcher, + _nm_team_link_watcher_ref, + nm_team_link_watcher_unref) + +typedef enum { + LINK_WATCHER_ETHTOOL = 0, + LINK_WATCHER_NSNA_PING = 1, + LINK_WATCHER_ARP_PING = 2, +} LinkWatcherTypes; + +static const char *_link_watcher_name[] = {[LINK_WATCHER_ETHTOOL] = NM_TEAM_LINK_WATCHER_ETHTOOL, + [LINK_WATCHER_NSNA_PING] = + NM_TEAM_LINK_WATCHER_NSNA_PING, + [LINK_WATCHER_ARP_PING] = NM_TEAM_LINK_WATCHER_ARP_PING}; + +struct NMTeamLinkWatcher { + int ref_count; + + guint8 type; /* LinkWatcherTypes */ + + union { + struct { + int delay_up; + int delay_down; + } ethtool; + struct { + const char *target_host; + int init_wait; + int interval; + int missed_max; + } nsna_ping; + struct { + const char * target_host; + const char * source_host; + int init_wait; + int interval; + int missed_max; + int vlanid; + NMTeamLinkWatcherArpPingFlags flags; + } arp_ping; + }; +}; + +#define _CHECK_WATCHER_VOID(watcher) \ + G_STMT_START \ + { \ + g_return_if_fail(watcher != NULL); \ + g_return_if_fail(watcher->ref_count > 0); \ + nm_assert(watcher->type <= LINK_WATCHER_ARP_PING); \ + } \ + G_STMT_END + +#define _CHECK_WATCHER(watcher, err_val) \ + G_STMT_START \ + { \ + g_return_val_if_fail(watcher != NULL, err_val); \ + g_return_val_if_fail(watcher->ref_count > 0, err_val); \ + nm_assert(watcher->type <= LINK_WATCHER_ARP_PING); \ + } \ + G_STMT_END + +/** + * nm_team_link_watcher_new_ethtool: + * @delay_up: delay_up value + * @delay_down: delay_down value + * @error: this call never fails, so this var is not used but kept for format + * consistency with the link_watcher constructors of other type + * + * Creates a new ethtool #NMTeamLinkWatcher object + * + * Returns: (transfer full): the new #NMTeamLinkWatcher object + * + * Since: 1.12 + **/ +NMTeamLinkWatcher * +nm_team_link_watcher_new_ethtool(int delay_up, int delay_down, GError **error) +{ + NMTeamLinkWatcher *watcher; + const char * val_fail = NULL; + + if (delay_up < 0 || !_NM_INT_LE_MAXINT32(delay_up)) + val_fail = "delay-up"; + if (delay_down < 0 || !_NM_INT_LE_MAXINT32(delay_down)) + val_fail = "delay-down"; + if (val_fail) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("%s is out of range [0, %d]"), + val_fail, + G_MAXINT32); + return NULL; + } + + NM_PRAGMA_WARNING_DISABLE("-Warray-bounds") + + watcher = g_malloc(nm_offsetofend(NMTeamLinkWatcher, ethtool)); + + watcher->ref_count = 1; + watcher->type = LINK_WATCHER_ETHTOOL; + watcher->ethtool.delay_up = delay_up; + watcher->ethtool.delay_down = delay_down; + + NM_PRAGMA_WARNING_REENABLE + + return watcher; +} + +/** + * nm_team_link_watcher_new_nsna_ping: + * @init_wait: init_wait value + * @interval: interval value + * @missed_max: missed_max value + * @target_host: the host name or the ipv6 address that will be used as + * target address in the NS packet + * @error: (out) (allow-none): location to store the error on failure + * + * Creates a new nsna_ping #NMTeamLinkWatcher object + * + * Returns: (transfer full): the new #NMTeamLinkWatcher object, or %NULL on error + * + * Since: 1.12 + **/ +NMTeamLinkWatcher * +nm_team_link_watcher_new_nsna_ping(int init_wait, + int interval, + int missed_max, + const char *target_host, + GError ** error) +{ + NMTeamLinkWatcher *watcher; + const char * val_fail = NULL; + char * str; + gsize l_target_host; + + if (!target_host) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("Missing target-host in nsna_ping link watcher")); + return NULL; + } + + if (strpbrk(target_host, " \\/\t=\"\'")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("target-host '%s' contains invalid characters"), + target_host); + return NULL; + } + + if (init_wait < 0 || !_NM_INT_LE_MAXINT32(init_wait)) + val_fail = "init-wait"; + if (interval < 0 || !_NM_INT_LE_MAXINT32(interval)) + val_fail = "interval"; + if (missed_max < 0 || !_NM_INT_LE_MAXINT32(missed_max)) + val_fail = "missed-max"; + if (val_fail) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("%s is out of range [0, %d]"), + val_fail, + G_MAXINT32); + return NULL; + } + + l_target_host = strlen(target_host) + 1; + + watcher = g_malloc(nm_offsetofend(NMTeamLinkWatcher, nsna_ping) + l_target_host); + + watcher->ref_count = 1; + watcher->type = LINK_WATCHER_NSNA_PING; + watcher->nsna_ping.init_wait = init_wait; + watcher->nsna_ping.interval = interval; + watcher->nsna_ping.missed_max = missed_max; + + str = &((char *) watcher)[nm_offsetofend(NMTeamLinkWatcher, nsna_ping)]; + watcher->nsna_ping.target_host = str; + memcpy(str, target_host, l_target_host); + + return watcher; +} + +/** + * nm_team_link_watcher_new_arp_ping: + * @init_wait: init_wait value + * @interval: interval value + * @missed_max: missed_max value + * @target_host: the host name or the ip address that will be used as destination + * address in the arp request + * @source_host: the host name or the ip address that will be used as source + * address in the arp request + * @flags: the watcher #NMTeamLinkWatcherArpPingFlags + * @error: (out) (allow-none): location to store the error on failure + * + * Creates a new arp_ping #NMTeamLinkWatcher object + * + * Returns: (transfer full): the new #NMTeamLinkWatcher object, or %NULL on error + * + * Since: 1.12 + **/ +NMTeamLinkWatcher * +nm_team_link_watcher_new_arp_ping(int init_wait, + int interval, + int missed_max, + const char * target_host, + const char * source_host, + NMTeamLinkWatcherArpPingFlags flags, + GError ** error) +{ + return nm_team_link_watcher_new_arp_ping2(init_wait, + interval, + missed_max, + -1, + target_host, + source_host, + flags, + error); +} + +/** + * nm_team_link_watcher_new_arp_ping2: + * @init_wait: init_wait value + * @interval: interval value + * @missed_max: missed_max value + * @vlanid: vlanid value + * @target_host: the host name or the ip address that will be used as destination + * address in the arp request + * @source_host: the host name or the ip address that will be used as source + * address in the arp request + * @flags: the watcher #NMTeamLinkWatcherArpPingFlags + * @error: (out) (allow-none): location to store the error on failure + * + * Creates a new arp_ping #NMTeamLinkWatcher object + * + * Returns: (transfer full): the new #NMTeamLinkWatcher object, or %NULL on error + * + * Since: 1.16 + **/ +NMTeamLinkWatcher * +nm_team_link_watcher_new_arp_ping2(int init_wait, + int interval, + int missed_max, + int vlanid, + const char * target_host, + const char * source_host, + NMTeamLinkWatcherArpPingFlags flags, + GError ** error) +{ + NMTeamLinkWatcher *watcher; + const char * val_fail = NULL; + char * str; + gsize l_target_host; + gsize l_source_host; + + if (!target_host || !source_host) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("Missing %s in arp_ping link watcher"), + target_host ? "source-host" : "target-host"); + return NULL; + } + + if (strpbrk(target_host, " \\/\t=\"\'")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("target-host '%s' contains invalid characters"), + target_host); + return NULL; + } + + if (strpbrk(source_host, " \\/\t=\"\'")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("source-host '%s' contains invalid characters"), + source_host); + return NULL; + } + + else if (init_wait < 0 || !_NM_INT_LE_MAXINT32(init_wait)) + val_fail = "init-wait"; + else if (interval < 0 || !_NM_INT_LE_MAXINT32(interval)) + val_fail = "interval"; + else if (missed_max < 0 || !_NM_INT_LE_MAXINT32(missed_max)) + val_fail = "missed-max"; + if (val_fail) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("%s is out of range [0, %d]"), + val_fail, + G_MAXINT32); + return NULL; + } + + if (vlanid < -1 || vlanid > 4094) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("vlanid is out of range [-1, 4094]")); + return NULL; + } + + l_target_host = strlen(target_host) + 1; + l_source_host = strlen(source_host) + 1; + + watcher = g_malloc(nm_offsetofend(NMTeamLinkWatcher, arp_ping) + l_target_host + l_source_host); + + watcher->ref_count = 1; + watcher->type = LINK_WATCHER_ARP_PING; + watcher->arp_ping.init_wait = init_wait; + watcher->arp_ping.interval = interval; + watcher->arp_ping.missed_max = missed_max; + watcher->arp_ping.flags = flags; + watcher->arp_ping.vlanid = vlanid; + + str = &((char *) watcher)[nm_offsetofend(NMTeamLinkWatcher, arp_ping)]; + watcher->arp_ping.target_host = str; + memcpy(str, target_host, l_target_host); + + str += l_target_host; + watcher->arp_ping.source_host = str; + memcpy(str, source_host, l_source_host); + + return watcher; +} + +NMTeamLinkWatcher * +_nm_team_link_watcher_ref(NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, NULL); + + g_atomic_int_inc(&watcher->ref_count); + return watcher; +} + +/** + * nm_team_link_watcher_ref: + * @watcher: the #NMTeamLinkWatcher + * + * Increases the reference count of the object. + * + * Since: 1.12 + **/ +void +nm_team_link_watcher_ref(NMTeamLinkWatcher *watcher) +{ + _nm_team_link_watcher_ref(watcher); +} + +/** + * nm_team_link_watcher_unref: + * @watcher: the #NMTeamLinkWatcher + * + * Decreases the reference count of the object. If the reference count + * reaches zero, the object will be destroyed. + * + * Since: 1.12 + **/ +void +nm_team_link_watcher_unref(NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER_VOID(watcher); + + if (g_atomic_int_dec_and_test(&watcher->ref_count)) + g_free(watcher); +} + +int +nm_team_link_watcher_cmp(const NMTeamLinkWatcher *watcher, const NMTeamLinkWatcher *other) +{ + NM_CMP_SELF(watcher, other); + + NM_CMP_FIELD(watcher, other, type); + + switch (watcher->type) { + case LINK_WATCHER_ETHTOOL: + NM_CMP_FIELD(watcher, other, ethtool.delay_up); + NM_CMP_FIELD(watcher, other, ethtool.delay_down); + break; + case LINK_WATCHER_NSNA_PING: + NM_CMP_FIELD_STR(watcher, other, nsna_ping.target_host); + NM_CMP_FIELD(watcher, other, nsna_ping.init_wait); + NM_CMP_FIELD(watcher, other, nsna_ping.interval); + NM_CMP_FIELD(watcher, other, nsna_ping.missed_max); + break; + case LINK_WATCHER_ARP_PING: + NM_CMP_FIELD_STR(watcher, other, arp_ping.target_host); + NM_CMP_FIELD_STR(watcher, other, arp_ping.source_host); + NM_CMP_FIELD(watcher, other, arp_ping.init_wait); + NM_CMP_FIELD(watcher, other, arp_ping.interval); + NM_CMP_FIELD(watcher, other, arp_ping.missed_max); + NM_CMP_FIELD(watcher, other, arp_ping.vlanid); + NM_CMP_FIELD(watcher, other, arp_ping.flags); + break; + } + return 0; +} + +/** + * nm_team_link_watcher_equal: + * @watcher: the #NMTeamLinkWatcher + * @other: the #NMTeamLinkWatcher to compare @watcher to. + * + * Determines if two #NMTeamLinkWatcher objects contain the same values + * in all the properties. + * + * Returns: %TRUE if the objects contain the same values, %FALSE if they do not. + * + * Since: 1.12 + **/ +gboolean +nm_team_link_watcher_equal(const NMTeamLinkWatcher *watcher, const NMTeamLinkWatcher *other) +{ + return nm_team_link_watcher_cmp(watcher, other) == 0; +} + +static int +_team_link_watchers_cmp_p_with_data(gconstpointer data_a, gconstpointer data_b, gpointer user_data) +{ + return nm_team_link_watcher_cmp(*((const NMTeamLinkWatcher *const *) data_a), + *((const NMTeamLinkWatcher *const *) data_b)); +} + +int +nm_team_link_watchers_cmp(const NMTeamLinkWatcher *const *a, + const NMTeamLinkWatcher *const *b, + gsize len, + gboolean ignore_order) +{ + gs_free const NMTeamLinkWatcher **a_free = NULL; + gs_free const NMTeamLinkWatcher **b_free = NULL; + guint i; + + if (ignore_order && len > 1) { + a = nm_memdup_maybe_a(200, a, len * sizeof(*a), &a_free); + b = nm_memdup_maybe_a(200, b, len * sizeof(*b), &b_free); + g_qsort_with_data((gpointer) a, len, sizeof(*a), _team_link_watchers_cmp_p_with_data, NULL); + g_qsort_with_data((gpointer) b, len, sizeof(*b), _team_link_watchers_cmp_p_with_data, NULL); + } + for (i = 0; i < len; i++) { + NM_CMP_RETURN(nm_team_link_watcher_cmp(a[i], b[i])); + } + return 0; +} + +gboolean +nm_team_link_watchers_equal(const GPtrArray *a, const GPtrArray *b, gboolean ignore_order) +{ + return a == b + || (a && b && a->len == b->len + && (nm_team_link_watchers_cmp((const NMTeamLinkWatcher *const *) a->pdata, + (const NMTeamLinkWatcher *const *) b->pdata, + a->len, + ignore_order) + == 0)); +} + +/** + * nm_team_link_watcher_dup: + * @watcher: the #NMTeamLinkWatcher + * + * Creates a copy of @watcher + * + * Returns: (transfer full): a copy of @watcher + * + * Since: 1.12 + **/ +NMTeamLinkWatcher * +nm_team_link_watcher_dup(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, NULL); + + switch (watcher->type) { + case LINK_WATCHER_ETHTOOL: + return nm_team_link_watcher_new_ethtool(watcher->ethtool.delay_up, + watcher->ethtool.delay_down, + NULL); + break; + case LINK_WATCHER_NSNA_PING: + return nm_team_link_watcher_new_nsna_ping(watcher->nsna_ping.init_wait, + watcher->nsna_ping.interval, + watcher->nsna_ping.missed_max, + watcher->nsna_ping.target_host, + NULL); + break; + case LINK_WATCHER_ARP_PING: + return nm_team_link_watcher_new_arp_ping2(watcher->arp_ping.init_wait, + watcher->arp_ping.interval, + watcher->arp_ping.missed_max, + watcher->arp_ping.vlanid, + watcher->arp_ping.target_host, + watcher->arp_ping.source_host, + watcher->arp_ping.flags, + NULL); + default: + nm_assert_not_reached(); + return NULL; + } +} + +/** + * nm_team_link_watcher_get_name: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the name of the link watcher to be used. + * + * Since: 1.12 + **/ +const char * +nm_team_link_watcher_get_name(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, NULL); + + return _link_watcher_name[watcher->type]; +} + +/** + * nm_team_link_watcher_get_delay_up: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the delay_up interval (in milliseconds) that elapses between the link + * coming up and the runner being notified about it. + * + * Since: 1.12 + **/ +int +nm_team_link_watcher_get_delay_up(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, 0); + + if (watcher->type == LINK_WATCHER_ETHTOOL) + return watcher->ethtool.delay_up; + return -1; +} + +/** + * nm_team_link_watcher_get_delay_down: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the delay_down interval (in milliseconds) that elapses between the link + * going down and the runner being notified about it. + * + * Since: 1.12 + **/ +int +nm_team_link_watcher_get_delay_down(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, 0); + + if (watcher->type == LINK_WATCHER_ETHTOOL) + return watcher->ethtool.delay_down; + return -1; +} + +/** + * nm_team_link_watcher_get_init_wait: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the init_wait interval (in milliseconds) that the team slave should + * wait before sending the first packet to the target host. + * + * Since: 1.12 + **/ +int +nm_team_link_watcher_get_init_wait(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, 0); + + if (watcher->type == LINK_WATCHER_NSNA_PING) + return watcher->nsna_ping.init_wait; + if (watcher->type == LINK_WATCHER_ARP_PING) + return watcher->arp_ping.init_wait; + return -1; +} + +/** + * nm_team_link_watcher_get_interval: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the interval (in milliseconds) that the team slave should wait between + * sending two check packets to the target host. + * + * Since: 1.12 + **/ +int +nm_team_link_watcher_get_interval(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, 0); + + if (watcher->type == LINK_WATCHER_NSNA_PING) + return watcher->nsna_ping.interval; + if (watcher->type == LINK_WATCHER_ARP_PING) + return watcher->arp_ping.interval; + return -1; +} + +/** + * nm_team_link_watcher_get_missed_max: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the number of missed replies after which the link is considered down. + * + * Since: 1.12 + **/ +int +nm_team_link_watcher_get_missed_max(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, 0); + + if (watcher->type == LINK_WATCHER_NSNA_PING) + return watcher->nsna_ping.missed_max; + if (watcher->type == LINK_WATCHER_ARP_PING) + return watcher->arp_ping.missed_max; + return -1; +} + +/** + * nm_team_link_watcher_get_vlanid: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the VLAN tag ID to be used to outgoing link probes + * + * Since: 1.16 + **/ +int +nm_team_link_watcher_get_vlanid(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, -1); + + if (watcher->type == LINK_WATCHER_ARP_PING) + return watcher->arp_ping.vlanid; + return -1; +} + +/** + * nm_team_link_watcher_get_target_host: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the host name/ip address to be used as destination for the link probing + * packets. + * + * Since: 1.12 + **/ +const char * +nm_team_link_watcher_get_target_host(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, NULL); + + if (watcher->type == LINK_WATCHER_NSNA_PING) + return watcher->nsna_ping.target_host; + if (watcher->type == LINK_WATCHER_ARP_PING) + return watcher->arp_ping.target_host; + return NULL; +} + +/** + * nm_team_link_watcher_get_source_host: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the ip address to be used as source for the link probing packets. + * + * Since: 1.12 + **/ +const char * +nm_team_link_watcher_get_source_host(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, NULL); + + if (watcher->type == LINK_WATCHER_ARP_PING) + return watcher->arp_ping.source_host; + return NULL; +} + +/** + * nm_team_link_watcher_get_flags: + * @watcher: the #NMTeamLinkWatcher + * + * Gets the arp ping watcher flags. + * + * Since: 1.12 + **/ +NMTeamLinkWatcherArpPingFlags +nm_team_link_watcher_get_flags(const NMTeamLinkWatcher *watcher) +{ + _CHECK_WATCHER(watcher, 0); + + if (watcher->type == LINK_WATCHER_ARP_PING) + return watcher->arp_ping.flags; + return 0; +} + +/*****************************************************************************/ + +static GParamSpec *obj_properties[_NM_TEAM_ATTRIBUTE_MASTER_NUM] = { + NULL, +}; + +typedef struct { + NMTeamSetting *team_setting; +} NMSettingTeamPrivate; + +/** + * NMSettingTeam: + * + * Teaming Settings + */ +struct _NMSettingTeam { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingTeamClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingTeam, nm_setting_team, NM_TYPE_SETTING) + +#define NM_SETTING_TEAM_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_TEAM, NMSettingTeamPrivate)) + +/*****************************************************************************/ + +NMTeamSetting * +_nm_setting_team_get_team_setting(NMSettingTeam *setting) +{ + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting; +} + +/*****************************************************************************/ + +#define _maybe_changed(self, changed) \ + nm_team_setting_maybe_changed(NM_SETTING(_NM_ENSURE_TYPE(NMSettingTeam *, self)), \ + (const GParamSpec *const *) obj_properties, \ + (changed)) + +#define _maybe_changed_with_assert(self, changed) \ + G_STMT_START \ + { \ + if (!_maybe_changed((self), (changed))) \ + nm_assert_not_reached(); \ + } \ + G_STMT_END + +/** + * nm_setting_team_get_config: + * @setting: the #NMSettingTeam + * + * Returns: the #NMSettingTeam:config property of the setting + **/ +const char * +nm_setting_team_get_config(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), NULL); + + return nm_team_setting_config_get(NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting); +} + +/** + * nm_setting_team_get_notify_peers_count: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:notify-peers-count property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_get_notify_peers_count(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.notify_peers_count; +} + +/** + * nm_setting_team_get_notify_peers_interval: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:notify-peers-interval property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_get_notify_peers_interval(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.notify_peers_interval; +} + +/** + * nm_setting_team_get_mcast_rejoin_count: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:mcast-rejoin-count property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_get_mcast_rejoin_count(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.mcast_rejoin_count; +} + +/** + * nm_setting_team_get_mcast_rejoin_interval: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:mcast-rejoin-interval property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_get_mcast_rejoin_interval(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.mcast_rejoin_interval; +} + +/** + * nm_setting_team_get_runner: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner property of the setting + * + * Since: 1.12 + **/ +const char * +nm_setting_team_get_runner(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), NULL); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner; +} + +/** + * nm_setting_team_get_runner_hwaddr_policy: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner-hwaddr-policy property of the setting + * + * Since: 1.12 + **/ +const char * +nm_setting_team_get_runner_hwaddr_policy(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), NULL); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_hwaddr_policy; +} + +/** + * nm_setting_team_get_runner_tx_balancer: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner-tx-balancer property of the setting + * + * Since: 1.12 + **/ +const char * +nm_setting_team_get_runner_tx_balancer(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), NULL); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_tx_balancer; +} + +/** + * nm_setting_team_get_runner_tx_balancer_interval: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner-tx-balancer_interval property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_get_runner_tx_balancer_interval(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_tx_balancer_interval; +} + +/** + * nm_setting_team_get_runner_active: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner_active property of the setting + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_get_runner_active(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), FALSE); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_active; +} + +/** + * nm_setting_team_get_runner_fast_rate: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner-fast-rate property of the setting + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_get_runner_fast_rate(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), FALSE); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_fast_rate; +} + +/** + * nm_setting_team_get_runner_sys_prio: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner-sys-prio property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_get_runner_sys_prio(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_sys_prio; +} + +/** + * nm_setting_team_get_runner_min_ports: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner-min-ports property of the setting + * + * Since: 1.12 + **/ +int +nm_setting_team_get_runner_min_ports(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_min_ports; +} + +/** + * nm_setting_team_get_runner_agg_select_policy: + * @setting: the #NMSettingTeam + * + * Returns: the ##NMSettingTeam:runner-agg-select-policy property of the setting + * + * Since: 1.12 + **/ +const char * +nm_setting_team_get_runner_agg_select_policy(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), NULL); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_agg_select_policy; +} + +/** + * nm_setting_team_remove_runner_tx_hash_by_value: + * @setting: the #NMSetetingTeam + * @txhash: the txhash element to remove + * + * Removes the txhash element #txhash + * + * Returns: %TRUE if the txhash element was found and removed; %FALSE if it was not. + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_remove_runner_tx_hash_by_value(NMSettingTeam *setting, const char *txhash) +{ + NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE(setting); + const GPtrArray * arr; + guint i; + + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), FALSE); + g_return_val_if_fail(txhash != NULL, FALSE); + + arr = priv->team_setting->d.master.runner_tx_hash; + if (arr) { + for (i = 0; i < arr->len; i++) { + if (nm_streq(txhash, arr->pdata[i])) { + _maybe_changed_with_assert( + setting, + nm_team_setting_value_master_runner_tx_hash_remove(priv->team_setting, i)); + return TRUE; + } + } + } + return FALSE; +} + +/** + * nm_setting_team_get_num_runner_tx_hash: + * @setting: the #NMSettingTeam + * + * Returns: the number of elements in txhash + * + * Since: 1.12 + **/ +guint +nm_setting_team_get_num_runner_tx_hash(NMSettingTeam *setting) +{ + const GPtrArray *arr; + + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + arr = NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_tx_hash; + return arr ? arr->len : 0u; +} + +/** + * nm_setting_team_get_runner_tx_hash + * @setting: the #NMSettingTeam + * @idx: index number of the txhash element to return + * + * Returns: the txhash element at index @idx + * + * Since: 1.12 + **/ +const char * +nm_setting_team_get_runner_tx_hash(NMSettingTeam *setting, guint idx) +{ + const GPtrArray *arr; + + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), NULL); + + arr = NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.master.runner_tx_hash; + + g_return_val_if_fail(arr, NULL); + g_return_val_if_fail(idx < arr->len, NULL); + + return arr->pdata[idx]; +} + +/** + * nm_setting_team_remove_runner_tx_hash: + * @setting: the #NMSettingTeam + * @idx: index number of the element to remove from txhash + * + * Removes the txhash element at index @idx. + * + * Since: 1.12 + **/ +void +nm_setting_team_remove_runner_tx_hash(NMSettingTeam *setting, guint idx) +{ + NMSettingTeamPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_TEAM(setting)); + + priv = NM_SETTING_TEAM_GET_PRIVATE(setting); + + g_return_if_fail(priv->team_setting->d.master.runner_tx_hash); + g_return_if_fail(idx < priv->team_setting->d.master.runner_tx_hash->len); + + _maybe_changed_with_assert( + setting, + nm_team_setting_value_master_runner_tx_hash_remove(priv->team_setting, idx)); +} + +/** + * nm_setting_team_add_runner_tx_hash: + * @setting: the #NMSettingTeam + * @txhash: the element to add to txhash + * + * Adds a new txhash element to the setting. + * + * Returns: %TRUE if the txhash element was added; %FALSE if the element + * was already knnown. + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_add_runner_tx_hash(NMSettingTeam *setting, const char *txhash) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), FALSE); + g_return_val_if_fail(txhash, FALSE); + + return _maybe_changed(setting, + nm_team_setting_value_master_runner_tx_hash_add( + NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting, + txhash)); +} + +/** + * nm_setting_team_get_num_link_watchers: + * @setting: the #NMSettingTeam + * + * Returns: the number of configured link watchers + * + * Since: 1.12 + **/ +guint +nm_setting_team_get_num_link_watchers(NMSettingTeam *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), 0); + + return NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.link_watchers->len; +} + +/** + * nm_setting_team_get_link_watcher: + * @setting: the #NMSettingTeam + * @idx: index number of the link watcher to return + * + * Returns: (transfer none): the link watcher at index @idx. + * + * Since: 1.12 + **/ +NMTeamLinkWatcher * +nm_setting_team_get_link_watcher(NMSettingTeam *setting, guint idx) +{ + const GPtrArray *arr; + + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), NULL); + + arr = NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting->d.link_watchers; + + g_return_val_if_fail(idx < arr->len, NULL); + + return arr->pdata[idx]; +} + +/** + * nm_setting_team_add_link_watcher: + * @setting: the #NMSettingTeam + * @link_watcher: the link watcher to add + * + * Appends a new link watcher to the setting. + * + * Returns: %TRUE if the link watcher is added; %FALSE if an identical link + * watcher was already there. + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_add_link_watcher(NMSettingTeam *setting, NMTeamLinkWatcher *link_watcher) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), FALSE); + g_return_val_if_fail(link_watcher != NULL, FALSE); + + return _maybe_changed( + setting, + nm_team_setting_value_link_watchers_add(NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting, + link_watcher)); +} + +/** + * nm_setting_team_remove_link_watcher: + * @setting: the #NMSettingTeam + * @idx: index number of the link watcher to remove + * + * Removes the link watcher at index #idx. + * + * Since: 1.12 + **/ +void +nm_setting_team_remove_link_watcher(NMSettingTeam *setting, guint idx) +{ + NMSettingTeamPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_TEAM(setting)); + + priv = NM_SETTING_TEAM_GET_PRIVATE(setting); + + g_return_if_fail(idx < priv->team_setting->d.link_watchers->len); + + _maybe_changed_with_assert(setting, + nm_team_setting_value_link_watchers_remove(priv->team_setting, idx)); +} + +/** + * nm_setting_team_remove_link_watcher_by_value: + * @setting: the #NMSettingTeam + * @link_watcher: the link watcher to remove + * + * Removes the link watcher entry matching link_watcher. + * + * Returns: %TRUE if the link watcher was found and removed, %FALSE otherwise. + * + * Since: 1.12 + **/ +gboolean +nm_setting_team_remove_link_watcher_by_value(NMSettingTeam * setting, + NMTeamLinkWatcher *link_watcher) +{ + g_return_val_if_fail(NM_IS_SETTING_TEAM(setting), FALSE); + g_return_val_if_fail(link_watcher, FALSE); + + return _maybe_changed(setting, + nm_team_setting_value_link_watchers_remove_by_value( + NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting, + link_watcher)); +} + +/** + * nm_setting_team_clear_link_watchers: + * @setting: the #NMSettingTeam + * + * Removes all configured link watchers. + * + * Since: 1.12 + **/ +void +nm_setting_team_clear_link_watchers(NMSettingTeam *setting) +{ + g_return_if_fail(NM_IS_SETTING_TEAM(setting)); + + _maybe_changed(setting, + nm_team_setting_value_link_watchers_set_list( + NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting, + NULL, + 0)); +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE(setting); + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + if (!nm_team_setting_verify(priv->team_setting, error)) + return FALSE; + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingTeamPrivate *a_priv, *b_priv; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_LINK_WATCHERS)) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) + return NM_TERNARY_DEFAULT; + if (!set_b) + return TRUE; + a_priv = NM_SETTING_TEAM_GET_PRIVATE(set_a); + b_priv = NM_SETTING_TEAM_GET_PRIVATE(set_b); + return nm_team_link_watchers_equal(a_priv->team_setting->d.link_watchers, + b_priv->team_setting->d.link_watchers, + TRUE); + } + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_TEAM_CONFIG)) { + if (set_b) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) { + /* If we are trying to match a connection in order to assume it (and thus + * @flags contains INFERRABLE), use the "relaxed" matching for team + * configuration. Otherwise, for all other purposes (including connection + * comparison before an update), resort to the default string comparison. */ + return TRUE; + } + + a_priv = NM_SETTING_TEAM_GET_PRIVATE(set_a); + b_priv = NM_SETTING_TEAM_GET_PRIVATE(set_b); + + return nm_streq0(nm_team_setting_config_get(a_priv->team_setting), + nm_team_setting_config_get(b_priv->team_setting)); + } + + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_team_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +static void +duplicate_copy_properties(const NMSettInfoSetting *sett_info, NMSetting *src, NMSetting *dst) +{ + _maybe_changed(NM_SETTING_TEAM(dst), + nm_team_setting_reset(NM_SETTING_TEAM_GET_PRIVATE(dst)->team_setting, + NM_SETTING_TEAM_GET_PRIVATE(src)->team_setting)); +} + +static gboolean +init_from_dbus(NMSetting * setting, + GHashTable * keys, + GVariant * setting_dict, + GVariant * connection_dict, + guint /* NMSettingParseFlags */ parse_flags, + GError ** error) +{ + guint32 changed = 0; + gboolean success; + + if (keys) + g_hash_table_remove(keys, "interface-name"); + + success = nm_team_setting_reset_from_dbus(NM_SETTING_TEAM_GET_PRIVATE(setting)->team_setting, + setting_dict, + keys, + &changed, + parse_flags, + error); + _maybe_changed(NM_SETTING_TEAM(setting), changed); + return success; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingTeam * setting = NM_SETTING_TEAM(object); + NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE(setting); + const GPtrArray * v_ptrarr; + + switch (prop_id) { + case NM_TEAM_ATTRIBUTE_CONFIG: + g_value_set_string(value, nm_team_setting_config_get(priv->team_setting)); + break; + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE: + g_value_set_boolean(value, nm_team_setting_value_get_bool(priv->team_setting, prop_id)); + break; + case NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT: + case NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL: + case NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT: + case NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS: + g_value_set_int(value, nm_team_setting_value_get_int32(priv->team_setting, prop_id)); + break; + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY: + g_value_set_string(value, nm_team_setting_value_get_string(priv->team_setting, prop_id)); + break; + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH: + v_ptrarr = priv->team_setting->d.master.runner_tx_hash; + g_value_take_boxed(value, + v_ptrarr ? _nm_utils_ptrarray_to_strv((GPtrArray *) v_ptrarr) : NULL); + break; + case NM_TEAM_ATTRIBUTE_LINK_WATCHERS: + g_value_take_boxed(value, + _nm_utils_copy_array(priv->team_setting->d.link_watchers, + (NMUtilsCopyFunc) _nm_team_link_watcher_ref, + (GDestroyNotify) nm_team_link_watcher_unref)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingTeam * setting = NM_SETTING_TEAM(object); + NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE(object); + guint32 changed; + const GPtrArray * v_ptrarr; + + switch (prop_id) { + case NM_TEAM_ATTRIBUTE_CONFIG: + changed = nm_team_setting_config_set(priv->team_setting, g_value_get_string(value)); + break; + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE: + changed = + nm_team_setting_value_set_bool(priv->team_setting, prop_id, g_value_get_boolean(value)); + break; + case NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT: + case NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL: + case NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT: + case NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS: + changed = + nm_team_setting_value_set_int32(priv->team_setting, prop_id, g_value_get_int(value)); + break; + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY: + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY: + changed = nm_team_setting_value_set_string(priv->team_setting, + prop_id, + g_value_get_string(value)); + break; + case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH: + v_ptrarr = g_value_get_boxed(value); + changed = nm_team_setting_value_master_runner_tx_hash_set_list( + priv->team_setting, + v_ptrarr ? (const char *const *) v_ptrarr->pdata : NULL, + v_ptrarr ? v_ptrarr->len : 0u); + break; + case NM_TEAM_ATTRIBUTE_LINK_WATCHERS: + v_ptrarr = g_value_get_boxed(value); + changed = nm_team_setting_value_link_watchers_set_list( + priv->team_setting, + v_ptrarr ? (const NMTeamLinkWatcher *const *) v_ptrarr->pdata : NULL, + v_ptrarr ? v_ptrarr->len : 0u); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + return; + } + + _maybe_changed(setting, changed & ~(((guint32) 1) << prop_id)); +} + +/*****************************************************************************/ + +static void +nm_setting_team_init(NMSettingTeam *setting) +{ + NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE(setting); + + priv->team_setting = nm_team_setting_new(FALSE, NULL); +} + +/** + * nm_setting_team_new: + * + * Creates a new #NMSettingTeam object with default values. + * + * Returns: (transfer full): the new empty #NMSettingTeam object + **/ +NMSetting * +nm_setting_team_new(void) +{ + return g_object_new(NM_TYPE_SETTING_TEAM, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingTeamPrivate *priv = NM_SETTING_TEAM_GET_PRIVATE(object); + + nm_team_setting_free(priv->team_setting); + + G_OBJECT_CLASS(nm_setting_team_parent_class)->finalize(object); +} + +static void +nm_setting_team_class_init(NMSettingTeamClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingTeamPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->compare_property = compare_property; + setting_class->verify = verify; + setting_class->duplicate_copy_properties = duplicate_copy_properties; + setting_class->init_from_dbus = init_from_dbus; + + /** + * NMSettingTeam:config: + * + * The JSON configuration for the team network interface. The property + * should contain raw JSON configuration data suitable for teamd, because + * the value is passed directly to teamd. If not specified, the default + * configuration is used. See man teamd.conf for the format details. + **/ + /* ---ifcfg-rh--- + * property: config + * variable: TEAM_CONFIG + * description: Team configuration in JSON. See man teamd.conf for details. + * ---end--- + */ + obj_properties[NM_TEAM_ATTRIBUTE_CONFIG] = g_param_spec_string( + NM_SETTING_TEAM_CONFIG, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_CONFIG], + &nm_sett_info_propert_type_team_s); + + /** + * NMSettingTeam:notify-peers-count: + * + * Corresponds to the teamd notify_peers.count. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT] = + g_param_spec_int(NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeam:notify-peers-interval: + * + * Corresponds to the teamd notify_peers.interval. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL] = + g_param_spec_int(NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeam:mcast-rejoin-count: + * + * Corresponds to the teamd mcast_rejoin.count. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT] = + g_param_spec_int(NM_SETTING_TEAM_MCAST_REJOIN_COUNT, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeam:mcast-rejoin-interval: + * + * Corresponds to the teamd mcast_rejoin.interval. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL] = + g_param_spec_int(NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeam:runner: + * + * Corresponds to the teamd runner.name. + * Permitted values are: "roundrobin", "broadcast", "activebackup", + * "loadbalance", "lacp", "random". + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER] = + g_param_spec_string(NM_SETTING_TEAM_RUNNER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER], + &nm_sett_info_propert_type_team_s); + + /** + * NMSettingTeam:runner-hwaddr-policy: + * + * Corresponds to the teamd runner.hwaddr_policy. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY] = + g_param_spec_string(NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY], + &nm_sett_info_propert_type_team_s); + + /** + * NMSettingTeam:runner-tx-hash: + * + * Corresponds to the teamd runner.tx_hash. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH] = g_param_spec_boxed( + NM_SETTING_TEAM_RUNNER_TX_HASH, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH], + &nm_sett_info_propert_type_team_as); + + /** + * NMSettingTeam:runner-tx-balancer: + * + * Corresponds to the teamd runner.tx_balancer.name. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER] = + g_param_spec_string(NM_SETTING_TEAM_RUNNER_TX_BALANCER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER], + &nm_sett_info_propert_type_team_s); + + /** + * NMSettingTeam:runner-tx-balancer-interval: + * + * Corresponds to the teamd runner.tx_balancer.interval. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL] = + g_param_spec_int(NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj( + properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeam:runner-active: + * + * Corresponds to the teamd runner.active. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE] = + g_param_spec_boolean(NM_SETTING_TEAM_RUNNER_ACTIVE, + "", + "", + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE], + &nm_sett_info_propert_type_team_b); + + /** + * NMSettingTeam:runner-fast-rate: + * + * Corresponds to the teamd runner.fast_rate. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE] = + g_param_spec_boolean(NM_SETTING_TEAM_RUNNER_FAST_RATE, + "", + "", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE], + &nm_sett_info_propert_type_team_b); + + /** + * NMSettingTeam:runner-sys-prio: + * + * Corresponds to the teamd runner.sys_prio. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO] = + g_param_spec_int(NM_SETTING_TEAM_RUNNER_SYS_PRIO, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeam:runner-min-ports: + * + * Corresponds to the teamd runner.min_ports. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS] = + g_param_spec_int(NM_SETTING_TEAM_RUNNER_MIN_PORTS, + "", + "", + G_MININT32, + G_MAXINT32, + -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS], + &nm_sett_info_propert_type_team_i); + + /** + * NMSettingTeam:runner-agg-select-policy: + * + * Corresponds to the teamd runner.agg_select_policy. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY] = + g_param_spec_string(NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY], + &nm_sett_info_propert_type_team_s); + + /** + * NMSettingTeam:link-watchers: (type GPtrArray(NMTeamLinkWatcher)) + * + * Link watchers configuration for the connection: each link watcher is + * defined by a dictionary, whose keys depend upon the selected link + * watcher. Available link watchers are 'ethtool', 'nsna_ping' and + * 'arp_ping' and it is specified in the dictionary with the key 'name'. + * Available keys are: ethtool: 'delay-up', 'delay-down', 'init-wait'; + * nsna_ping: 'init-wait', 'interval', 'missed-max', 'target-host'; + * arp_ping: all the ones in nsna_ping and 'source-host', 'validate-active', + * 'validate-inactive', 'send-always'. See teamd.conf man for more details. + * + * Since: 1.12 + **/ + obj_properties[NM_TEAM_ATTRIBUTE_LINK_WATCHERS] = + g_param_spec_boxed(NM_SETTING_TEAM_LINK_WATCHERS, + "", + "", + G_TYPE_PTR_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[NM_TEAM_ATTRIBUTE_LINK_WATCHERS], + &nm_sett_info_propert_type_team_link_watchers); + + /* ---dbus--- + * property: interface-name + * format: string + * description: Deprecated in favor of connection.interface-name, but can + * be used for backward-compatibility with older daemons, to set the + * team's interface name. + * ---end--- + */ + _nm_properties_override_dbus(properties_override, + "interface-name", + &nm_sett_info_propert_type_deprecated_interface_name); + + g_object_class_install_properties(object_class, G_N_ELEMENTS(obj_properties), obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_TEAM, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-tun.c b/src/libnm-core-impl/nm-setting-tun.c new file mode 100644 index 00000000..d75dcf80 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-tun.c @@ -0,0 +1,420 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-tun.h" + +#include + +#include "nm-utils.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" +#include "nm-connection-private.h" + +/** + * SECTION:nm-setting-tun + * @short_description: Describes connection properties for TUN/TAP interfaces + * + * The #NMSettingTun object is a #NMSetting subclass that describes properties + * necessary for connection to TUN/TAP interfaces. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_MODE, + PROP_OWNER, + PROP_GROUP, + PROP_PI, + PROP_VNET_HDR, + PROP_MULTI_QUEUE, ); + +typedef struct { + char * owner; + char * group; + NMSettingTunMode mode; + bool pi : 1; + bool vnet_hdr : 1; + bool multi_queue : 1; +} NMSettingTunPrivate; + +/** + * NMSettingTun: + * + * Tunnel Settings + */ +struct _NMSettingTun { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingTunClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingTun, nm_setting_tun, NM_TYPE_SETTING) + +#define NM_SETTING_TUN_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_TUN, NMSettingTunPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_tun_get_mode: + * @setting: the #NMSettingTun + * + * Returns: the #NMSettingTun:mode property of the setting + * + * Since: 1.2 + **/ +NMSettingTunMode +nm_setting_tun_get_mode(NMSettingTun *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TUN(setting), NM_SETTING_TUN_MODE_TUN); + return NM_SETTING_TUN_GET_PRIVATE(setting)->mode; +} + +/** + * nm_setting_tun_get_owner: + * @setting: the #NMSettingTun + * + * Returns: the #NMSettingTun:owner property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_tun_get_owner(NMSettingTun *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TUN(setting), NULL); + return NM_SETTING_TUN_GET_PRIVATE(setting)->owner; +} + +/** + * nm_setting_tun_get_group: + * @setting: the #NMSettingTun + * + * Returns: the #NMSettingTun:group property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_tun_get_group(NMSettingTun *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TUN(setting), NULL); + return NM_SETTING_TUN_GET_PRIVATE(setting)->group; +} + +/** + * nm_setting_tun_get_pi: + * @setting: the #NMSettingTun + * + * Returns: the #NMSettingTun:pi property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_tun_get_pi(NMSettingTun *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TUN(setting), FALSE); + return NM_SETTING_TUN_GET_PRIVATE(setting)->pi; +} + +/** + * nm_setting_tun_get_vnet_hdr: + * @setting: the #NMSettingTun + * + * Returns: the #NMSettingTun:vnet_hdr property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_tun_get_vnet_hdr(NMSettingTun *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TUN(setting), FALSE); + return NM_SETTING_TUN_GET_PRIVATE(setting)->vnet_hdr; +} + +/** + * nm_setting_tun_get_multi_queue: + * @setting: the #NMSettingTun + * + * Returns: the #NMSettingTun:multi-queue property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_tun_get_multi_queue(NMSettingTun *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_TUN(setting), FALSE); + return NM_SETTING_TUN_GET_PRIVATE(setting)->multi_queue; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingTunPrivate *priv = NM_SETTING_TUN_GET_PRIVATE(setting); + + if (!NM_IN_SET(priv->mode, NM_SETTING_TUN_MODE_TUN, NM_SETTING_TUN_MODE_TAP)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%u': invalid mode"), + (unsigned) priv->mode); + g_prefix_error(error, "%s.%s: ", NM_SETTING_TUN_SETTING_NAME, NM_SETTING_TUN_MODE); + return FALSE; + } + + if (priv->owner) { + if (_nm_utils_ascii_str_to_int64(priv->owner, 10, 0, G_MAXINT32, -1) == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s': invalid user ID"), + priv->owner); + g_prefix_error(error, "%s.%s: ", NM_SETTING_TUN_SETTING_NAME, NM_SETTING_TUN_OWNER); + return FALSE; + } + } + + if (priv->group) { + if (_nm_utils_ascii_str_to_int64(priv->group, 10, 0, G_MAXINT32, -1) == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s': invalid group ID"), + priv->group); + g_prefix_error(error, "%s.%s: ", NM_SETTING_TUN_SETTING_NAME, NM_SETTING_TUN_GROUP); + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingTun * setting = NM_SETTING_TUN(object); + NMSettingTunPrivate *priv = NM_SETTING_TUN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_MODE: + g_value_set_uint(value, priv->mode); + break; + case PROP_OWNER: + g_value_set_string(value, priv->owner); + break; + case PROP_GROUP: + g_value_set_string(value, priv->group); + break; + case PROP_PI: + g_value_set_boolean(value, priv->pi); + break; + case PROP_VNET_HDR: + g_value_set_boolean(value, priv->vnet_hdr); + break; + case PROP_MULTI_QUEUE: + g_value_set_boolean(value, priv->multi_queue); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingTun * setting = NM_SETTING_TUN(object); + NMSettingTunPrivate *priv = NM_SETTING_TUN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_MODE: + priv->mode = g_value_get_uint(value); + break; + case PROP_OWNER: + g_free(priv->owner); + priv->owner = g_value_dup_string(value); + break; + case PROP_GROUP: + g_free(priv->group); + priv->group = g_value_dup_string(value); + break; + case PROP_PI: + priv->pi = g_value_get_boolean(value); + break; + case PROP_VNET_HDR: + priv->vnet_hdr = g_value_get_boolean(value); + break; + case PROP_MULTI_QUEUE: + priv->multi_queue = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} +/*****************************************************************************/ + +static void +nm_setting_tun_init(NMSettingTun *self) +{ + NMSettingTunPrivate *priv = NM_SETTING_TUN_GET_PRIVATE(self); + + priv->mode = NM_SETTING_TUN_MODE_TUN; +} + +/** + * nm_setting_tun_new: + * + * Creates a new #NMSettingTun object with default values. + * + * Returns: (transfer full): the new empty #NMSettingTun object + * + * Since: 1.2 + **/ +NMSetting * +nm_setting_tun_new(void) +{ + return g_object_new(NM_TYPE_SETTING_TUN, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingTun * setting = NM_SETTING_TUN(object); + NMSettingTunPrivate *priv = NM_SETTING_TUN_GET_PRIVATE(setting); + + g_free(priv->owner); + g_free(priv->group); + + G_OBJECT_CLASS(nm_setting_tun_parent_class)->finalize(object); +} + +static void +nm_setting_tun_class_init(NMSettingTunClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingTunPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingTun:mode: + * + * The operating mode of the virtual device. Allowed values are + * %NM_SETTING_TUN_MODE_TUN to create a layer 3 device and + * %NM_SETTING_TUN_MODE_TAP to create an Ethernet-like layer 2 + * one. + * + * Since: 1.2 + */ + obj_properties[PROP_MODE] = + g_param_spec_uint(NM_SETTING_TUN_MODE, + "", + "", + 0, + G_MAXUINT, + NM_SETTING_TUN_MODE_TUN, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingTun:owner: + * + * The user ID which will own the device. If set to %NULL everyone + * will be able to use the device. + * + * Since: 1.2 + */ + obj_properties[PROP_OWNER] = g_param_spec_string(NM_SETTING_TUN_OWNER, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingTun:group: + * + * The group ID which will own the device. If set to %NULL everyone + * will be able to use the device. + * + * Since: 1.2 + */ + obj_properties[PROP_GROUP] = g_param_spec_string(NM_SETTING_TUN_GROUP, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingTun:pi: + * + * If %TRUE the interface will prepend a 4 byte header describing the + * physical interface to the packets. + * + * Since: 1.2 + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_TUN_PI, + PROP_PI, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_tun_get_pi); + + /** + * NMSettingTun:vnet-hdr: + * + * If %TRUE the IFF_VNET_HDR the tunnel packets will include a virtio + * network header. + * + * Since: 1.2 + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_TUN_VNET_HDR, + PROP_VNET_HDR, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_tun_get_vnet_hdr); + + /** + * NMSettingTun:multi-queue: + * + * If the property is set to %TRUE, the interface will support + * multiple file descriptors (queues) to parallelize packet + * sending or receiving. Otherwise, the interface will only + * support a single queue. + * + * Since: 1.2 + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_TUN_MULTI_QUEUE, + PROP_MULTI_QUEUE, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_tun_get_multi_queue); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_TUN, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-user.c b/src/libnm-core-impl/nm-setting-user.c new file mode 100644 index 00000000..0f99baa2 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-user.c @@ -0,0 +1,594 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-user.h" + +#include "nm-setting.h" +#include "nm-setting-private.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-user + * @short_description: Describes user properties + * + * The #NMSettingUser object is a #NMSetting subclass that allow to attach + * arbitrary user data to #NMConnection objects. + **/ + +#define MAX_NUM_KEYS 256 + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingUser, PROP_DATA, ); + +typedef struct { + GHashTable * data; + GHashTable * data_invalid; + const char **keys; +} NMSettingUserPrivate; + +/** + * NMSettingUser: + * + * General User Profile Settings + */ +struct _NMSettingUser { + NMSetting parent; + NMSettingUserPrivate _priv; +}; + +struct _NMSettingUserClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingUser, nm_setting_user, NM_TYPE_SETTING) + +#define NM_SETTING_USER_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMSettingUser, NM_IS_SETTING_USER) + +/*****************************************************************************/ + +static gboolean +_key_char_is_regular(char ch) +{ + /* allow words of printable characters, plus some + * special characters, for example to support base64 encoding. */ + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') + || NM_IN_SET(ch, '-', '_', '+', '/', '='); +} + +/** + * nm_setting_user_check_key: + * @key: the key to check + * @error: a #GError, %NULL to ignore. + * + * Checks whether @key is a valid user data key. This means, + * key is not %NULL, not too large and valid ASCII. Also, + * only digits and numbers are allowed with a few special + * characters. The key must contain at least one '.' and + * look like a fully qualified DNS name. + * + * Since: 1.8 + * + * Returns: %TRUE if @key is a valid user data key. + */ +gboolean +nm_setting_user_check_key(const char *key, GError **error) +{ + gsize len; + gboolean has_dot; + char ch; + + g_return_val_if_fail(!error || !*error, FALSE); + + if (!key || !key[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("missing key")); + return FALSE; + } + len = strlen(key); + if (len > 255) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key is too long")); + return FALSE; + } + if (!g_utf8_validate(key, len, NULL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key must be UTF8")); + return FALSE; + } + + has_dot = FALSE; + while (TRUE) { + ch = (key++)[0]; + + /* Allow something that looks like a FQN, separating namespaces by a single '.' + * We want to print the keys nicely in nmcli requiring escaping. + * + * If a user really has to encode special values in the name, he may base64 encode it. */ + + if (!_key_char_is_regular(ch)) + break; + + while (_key_char_is_regular(key[0])) + key++; + + ch = key[0]; + if (ch == '\0') { + if (!has_dot) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key requires a '.' for a namespace")); + return FALSE; + } + return TRUE; + } + + if (ch != '.') + break; + + has_dot = TRUE; + ch = (++key)[0]; + if (ch == '.') { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key cannot contain \"..\"")); + return FALSE; + } + } + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key contains invalid characters")); + return FALSE; +} + +/** + * nm_setting_user_check_val: + * @val: the value to check + * @error: a #GError, %NULL to ignore. + * + * Checks whether @val is a valid user data value. This means, + * value is not %NULL, not too large and valid UTF-8. + * + * Since: 1.8 + * + * Returns: %TRUE if @val is a valid user data value. + */ +gboolean +nm_setting_user_check_val(const char *val, GError **error) +{ + gsize len; + + g_return_val_if_fail(!error || !*error, FALSE); + + if (!val) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value is missing")); + return FALSE; + } + + len = strlen(val); + if (len > 8 * 1024) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value is too large")); + return FALSE; + } + + if (!g_utf8_validate(val, len, NULL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("value is not valid UTF8")); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static GHashTable * +_create_data_hash(void) +{ + return g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); +} + +/** + * nm_setting_user_get_keys: + * @setting: the #NMSettingUser + * @out_len: (out): the length of the returned array + * + * Returns: (array length=out_len) (transfer none): a + * %NULL-terminated array containing each key from the table. + **/ +const char *const * +nm_setting_user_get_keys(NMSettingUser *setting, guint *out_len) +{ + NMSettingUser * self = setting; + NMSettingUserPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_USER(self), NULL); + + priv = NM_SETTING_USER_GET_PRIVATE(self); + + if (priv->keys) { + NM_SET_OUT(out_len, g_hash_table_size(priv->data)); + return priv->keys; + } + + priv->keys = nm_utils_strdict_get_keys(priv->data, TRUE, out_len); + + /* don't return %NULL, but hijack the @keys fields as a pseudo + * empty strv array. */ + return priv->keys ?: ((const char **) &priv->keys); +} + +/*****************************************************************************/ + +/** + * nm_setting_user_get_data: + * @setting: the #NMSettingUser instance + * @key: the key to lookup + * + * Since: 1.8 + * + * Returns: (transfer none): the value associated with @key or %NULL if no such + * value exists. + */ +const char * +nm_setting_user_get_data(NMSettingUser *setting, const char *key) +{ + NMSettingUser * self = setting; + NMSettingUserPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_USER(self), NULL); + g_return_val_if_fail(key, NULL); + + priv = NM_SETTING_USER_GET_PRIVATE(self); + + if (!priv->data) + return NULL; + + return g_hash_table_lookup(priv->data, key); +} + +/** + * nm_setting_user_set_data: + * @setting: the #NMSettingUser instance + * @key: the key to set + * @val: (allow-none): the value to set or %NULL to clear a key. + * @error: (allow-none): optional error argument + * + * Since: 1.8 + * + * Returns: %TRUE if the operation was successful. The operation + * can fail if @key or @val are not valid strings according + * to nm_setting_user_check_key() and nm_setting_user_check_val(). + */ +gboolean +nm_setting_user_set_data(NMSettingUser *setting, const char *key, const char *val, GError **error) +{ + NMSettingUser * self = setting; + NMSettingUserPrivate *priv; + gboolean changed = FALSE; + + g_return_val_if_fail(NM_IS_SETTING_USER(self), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + if (!nm_setting_user_check_key(key, error)) + return FALSE; + + if (val && !nm_setting_user_check_val(val, error)) + return FALSE; + + priv = NM_SETTING_USER_GET_PRIVATE(self); + + if (!val) { + if (priv->data && g_hash_table_remove(priv->data, key)) { + nm_clear_g_free(&priv->keys); + changed = TRUE; + } + goto out; + } + + if (priv->data) { + const char *key2, *val2; + + if (g_hash_table_lookup_extended(priv->data, key, (gpointer *) &key2, (gpointer *) &val2)) { + if (nm_streq(val, val2)) + goto out; + } else { + if (g_hash_table_size(priv->data) >= MAX_NUM_KEYS) { + /* limit the number of valid keys */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("maximum number of user data entries reached")); + return FALSE; + } + + nm_clear_g_free(&priv->keys); + } + } else + priv->data = _create_data_hash(); + + g_hash_table_insert(priv->data, g_strdup(key), g_strdup(val)); + changed = TRUE; + +out: + if (priv->data_invalid) { + /* setting a value purges all invalid values that were set + * via GObject property. */ + changed = TRUE; + nm_clear_pointer(&priv->data_invalid, g_hash_table_unref); + } + if (changed) + _notify(self, PROP_DATA); + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingUser * self = NM_SETTING_USER(setting); + NMSettingUserPrivate *priv = NM_SETTING_USER_GET_PRIVATE(self); + + if (priv->data_invalid) { + const char * key, *val; + GHashTableIter iter; + gs_free_error GError *local = NULL; + + g_hash_table_iter_init(&iter, priv->data_invalid); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + if (!nm_setting_user_check_key(key, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid key \"%s\": %s"), + key, + local->message); + } else if (!nm_setting_user_check_val(val, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid value for \"%s\": %s"), + key, + local->message); + } else { + nm_assert_not_reached(); + continue; + } + g_prefix_error(error, "%s.%s: ", NM_SETTING_USER_SETTING_NAME, NM_SETTING_USER_DATA); + return FALSE; + } + nm_assert_not_reached(); + } + + if (priv->data && g_hash_table_size(priv->data) > MAX_NUM_KEYS) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("maximum number of user data entries reached (%u instead of %u)"), + g_hash_table_size(priv->data), + (unsigned) MAX_NUM_KEYS); + g_prefix_error(error, "%s.%s: ", NM_SETTING_USER_SETTING_NAME, NM_SETTING_USER_DATA); + return FALSE; + } + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingUserPrivate *priv, *pri2; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_USER_DATA)) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) + return NM_TERNARY_DEFAULT; + + if (!set_b) + return TRUE; + + priv = NM_SETTING_USER_GET_PRIVATE(NM_SETTING_USER(set_a)); + pri2 = NM_SETTING_USER_GET_PRIVATE(NM_SETTING_USER(set_b)); + return nm_utils_hashtable_equal(priv->data, pri2->data, TRUE, g_str_equal) + && nm_utils_hashtable_equal(priv->data_invalid, + pri2->data_invalid, + TRUE, + g_str_equal); + } + + return NM_SETTING_CLASS(nm_setting_user_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingUser * self = NM_SETTING_USER(object); + NMSettingUserPrivate *priv = NM_SETTING_USER_GET_PRIVATE(self); + GHashTableIter iter; + GHashTable * data; + const char * key, *val; + + switch (prop_id) { + case PROP_DATA: + data = _create_data_hash(); + if (priv->data) { + g_hash_table_iter_init(&iter, priv->data); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) + g_hash_table_insert(data, g_strdup(key), g_strdup(val)); + } + if (priv->data_invalid) { + g_hash_table_iter_init(&iter, priv->data_invalid); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) + g_hash_table_insert(data, g_strdup(key), g_strdup(val)); + } + g_value_take_boxed(value, data); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingUser * self = NM_SETTING_USER(object); + NMSettingUserPrivate *priv = NM_SETTING_USER_GET_PRIVATE(self); + GHashTableIter iter; + GHashTable * data; + const char * key, *val; + + switch (prop_id) { + case PROP_DATA: + nm_clear_g_free(&priv->keys); + + data = g_value_get_boxed(value); + if (!data || !g_hash_table_size(data)) { + nm_clear_pointer(&priv->data, g_hash_table_unref); + nm_clear_pointer(&priv->data_invalid, g_hash_table_unref); + return; + } + + if (priv->data) + g_hash_table_remove_all(priv->data); + else + priv->data = _create_data_hash(); + + if (priv->data_invalid) + g_hash_table_remove_all(priv->data_invalid); + + g_hash_table_iter_init(&iter, data); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + if (nm_setting_user_check_key(key, NULL) && nm_setting_user_check_val(val, NULL)) + g_hash_table_insert(priv->data, g_strdup(key), g_strdup(val)); + else { + if (!priv->data_invalid) + priv->data_invalid = _create_data_hash(); + g_hash_table_insert(priv->data_invalid, g_strdup(key), g_strdup(val)); + } + } + if (priv->data_invalid && !g_hash_table_size(priv->data_invalid)) + nm_clear_pointer(&priv->data_invalid, g_hash_table_unref); + + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_user_init(NMSettingUser *self) +{} + +/** + * nm_setting_user_new: + * + * Creates a new #NMSettingUser object with default values. + * + * Returns: the new empty #NMSettingUser object + **/ +NMSetting * +nm_setting_user_new(void) +{ + return g_object_new(NM_TYPE_SETTING_USER, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingUser * self = NM_SETTING_USER(object); + NMSettingUserPrivate *priv = NM_SETTING_USER_GET_PRIVATE(self); + + g_free(priv->keys); + if (priv->data) + g_hash_table_unref(priv->data); + if (priv->data_invalid) + g_hash_table_unref(priv->data_invalid); + + G_OBJECT_CLASS(nm_setting_user_parent_class)->finalize(object); +} + +static void +nm_setting_user_class_init(NMSettingUserClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->compare_property = compare_property; + setting_class->verify = verify; + + /** + * NMSettingUser:data: (type GHashTable(utf8,utf8)) + * + * A dictionary of key/value pairs with user data. This data is ignored by NetworkManager + * and can be used at the users discretion. The keys only support a strict ascii format, + * but the values can be arbitrary UTF8 strings up to a certain length. + * + * Since: 1.8 + **/ + /* ---ifcfg-rh--- + * property: data + * variable: NM_USER_* + * description: each key/value pair is stored as a separate variable with + * name composed by concatenating NM_USER_ with the encoded key. The key is + * encoded by substituting lowercase letters with uppercase and prepending + * uppercase letters with an underscore. A dot is encoded as a double + * underscore. Remaining characters are encoded as underscore followed by a + * 3 digit octal representation of the character. + * example: NM_USER_FOO__BAR=something + * ---end--- + */ + obj_properties[PROP_DATA] = g_param_spec_boxed(NM_SETTING_USER_DATA, + "", + "", + G_TYPE_HASH_TABLE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_DATA], + &nm_sett_info_propert_type_strdict); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_USER, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-veth.c b/src/libnm-core-impl/nm-setting-veth.c new file mode 100644 index 00000000..70cfe50b --- /dev/null +++ b/src/libnm-core-impl/nm-setting-veth.c @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2020 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-veth.h" + +#include + +#include "nm-utils.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" +#include "nm-connection-private.h" + +/** + * SECTION:nm-setting-veth + * @short_description: Describes connection properties for veth interfaces + * + * The #NMSettingVeth object is a #NMSetting subclass that describes properties + * necessary for connection to veth interfaces. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PEER, ); + +typedef struct { + char *peer; +} NMSettingVethPrivate; + +/** + * NMSettingVeth: + * + * Veth Settings + */ +struct _NMSettingVeth { + NMSetting parent; + NMSettingVethPrivate _priv; +}; + +struct _NMSettingVethClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingVeth, nm_setting_veth, NM_TYPE_SETTING) + +#define NM_SETTING_VETH_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSettingVeth, NM_IS_SETTING_VETH, NMSetting) + +/*****************************************************************************/ + +/** + * nm_setting_veth_get_peer: + * @setting: the #NMSettingVeth + * + * Returns: the #NMSettingVeth:peer property of the setting + * + * Since: 1.30 + **/ +const char * +nm_setting_veth_get_peer(NMSettingVeth *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VETH(setting), NULL); + return NM_SETTING_VETH_GET_PRIVATE(setting)->peer; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingVethPrivate *priv = NM_SETTING_VETH_GET_PRIVATE(setting); + + if (!priv->peer) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is not specified")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VETH_SETTING_NAME, NM_SETTING_VETH_PEER); + return FALSE; + } + + if (!nm_utils_ifname_valid(priv->peer, NMU_IFACE_KERNEL, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid interface name"), + priv->peer); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VETH_SETTING_NAME, NM_SETTING_VETH_PEER); + return FALSE; + } + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingVeth * setting = NM_SETTING_VETH(object); + NMSettingVethPrivate *priv = NM_SETTING_VETH_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PEER: + g_value_set_string(value, priv->peer); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingVeth * setting = NM_SETTING_VETH(object); + NMSettingVethPrivate *priv = NM_SETTING_VETH_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PEER: + g_free(priv->peer); + priv->peer = g_value_dup_string(value); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_veth_init(NMSettingVeth *setting) +{} + +/** + * nm_setting_veth_new: + * + * Creates a new #NMSettingVeth object with default values. + * + * Returns: (transfer full): the new empty #NMSettingVeth object + * + * Since: 1.30 + **/ +NMSetting * +nm_setting_veth_new(void) +{ + return g_object_new(NM_TYPE_SETTING_VETH, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingVeth * setting = NM_SETTING_VETH(object); + NMSettingVethPrivate *priv = NM_SETTING_VETH_GET_PRIVATE(setting); + + g_free(priv->peer); + + G_OBJECT_CLASS(nm_setting_veth_parent_class)->finalize(object); +} + +static void +nm_setting_veth_class_init(NMSettingVethClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NMSettingVethPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingVeth:peer: + * + * This property specifies the peer interface name of the veth. This + * property is mandatory. + * + * Since: 1.30 + **/ + obj_properties[PROP_PEER] = g_param_spec_string(NM_SETTING_VETH_PEER, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_VETH); +} diff --git a/src/libnm-core-impl/nm-setting-vlan.c b/src/libnm-core-impl/nm-setting-vlan.c new file mode 100644 index 00000000..e4e07e35 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-vlan.c @@ -0,0 +1,1001 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 - 2014 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-vlan.h" + +#include + +#include "libnm-core-aux-intern/nm-libnm-core-utils.h" +#include "nm-utils.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" +#include "nm-setting-wired.h" +#include "nm-connection-private.h" + +/** + * SECTION:nm-setting-vlan + * @short_description: Describes connection properties for VLAN interfaces + * + * The #NMSettingVlan object is a #NMSetting subclass that describes properties + * necessary for connection to VLAN interfaces. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingVlan, + PROP_PARENT, + PROP_ID, + PROP_FLAGS, + PROP_INGRESS_PRIORITY_MAP, + PROP_EGRESS_PRIORITY_MAP, ); + +typedef struct { + GSList *ingress_priority_map; + GSList *egress_priority_map; + char * parent; + guint32 id; + guint32 flags; +} NMSettingVlanPrivate; + +/** + * NMSettingVlan: + * + * VLAN Settings + */ +struct _NMSettingVlan { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingVlanClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingVlan, nm_setting_vlan, NM_TYPE_SETTING) + +#define NM_SETTING_VLAN_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_VLAN, NMSettingVlanPrivate)) + +/*****************************************************************************/ + +#define MAX_SKB_PRIO G_MAXUINT32 +#define MAX_8021P_PRIO 7 /* Max 802.1p priority */ + +/** + * nm_setting_vlan_get_parent: + * @setting: the #NMSettingVlan + * + * Returns: the #NMSettingVlan:parent property of the setting + **/ +const char * +nm_setting_vlan_get_parent(NMSettingVlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), NULL); + return NM_SETTING_VLAN_GET_PRIVATE(setting)->parent; +} + +/** + * nm_setting_vlan_get_id: + * @setting: the #NMSettingVlan + * + * Returns: the #NMSettingVlan:id property of the setting + **/ +guint32 +nm_setting_vlan_get_id(NMSettingVlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), 0); + return NM_SETTING_VLAN_GET_PRIVATE(setting)->id; +} + +/** + * nm_setting_vlan_get_flags: + * @setting: the #NMSettingVlan + * + * Returns: the #NMSettingVlan:flags property of the setting + **/ +guint32 +nm_setting_vlan_get_flags(NMSettingVlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), 0); + return NM_SETTING_VLAN_GET_PRIVATE(setting)->flags; +} + +static NMVlanQosMapping * +priority_map_new(guint32 from, guint32 to) +{ + NMVlanQosMapping *mapping; + + mapping = g_new(NMVlanQosMapping, 1); + *mapping = (NMVlanQosMapping){ + .from = from, + .to = to, + }; + return mapping; +} + +static NMVlanQosMapping * +priority_map_new_from_str(NMVlanPriorityMap map, const char *str) +{ + guint32 from, to; + + if (!nm_utils_vlan_priority_map_parse_str(map, str, FALSE, &from, &to, NULL)) + return NULL; + return priority_map_new(from, to); +} + +static void +priority_map_free(NMVlanQosMapping *map) +{ + nm_assert(map); + g_free(map); +} + +static GSList * +get_map(NMSettingVlan *self, NMVlanPriorityMap map) +{ + if (map == NM_VLAN_INGRESS_MAP) + return NM_SETTING_VLAN_GET_PRIVATE(self)->ingress_priority_map; + else if (map == NM_VLAN_EGRESS_MAP) + return NM_SETTING_VLAN_GET_PRIVATE(self)->egress_priority_map; + nm_assert_not_reached(); + return NULL; +} + +static int +prio_map_compare(gconstpointer p_a, gconstpointer p_b) +{ + const NMVlanQosMapping *a = p_a; + const NMVlanQosMapping *b = p_b; + + return a->from < b->from + ? -1 + : (a->from > b->from ? 1 : (a->to < b->to ? -1 : (a->to > b->to ? 1 : 0))); +} + +static void +set_map(NMSettingVlan *self, NMVlanPriorityMap map, GSList *list) +{ + /* Assert that the list is sorted */ +#if NM_MORE_ASSERTS >= 2 + { + GSList *iter, *last; + + last = list; + iter = list ? list->next : NULL; + while (iter) { + const NMVlanQosMapping *l = last->data; + const NMVlanQosMapping *m = iter->data; + + nm_assert(prio_map_compare(last->data, iter->data) < 0); + + /* Also reject duplicates (based on "from") */ + nm_assert(l->from < m->from); + + last = iter; + iter = iter->next; + } + } +#endif + + if (map == NM_VLAN_INGRESS_MAP) { + NM_SETTING_VLAN_GET_PRIVATE(self)->ingress_priority_map = list; + _notify(self, PROP_INGRESS_PRIORITY_MAP); + } else if (map == NM_VLAN_EGRESS_MAP) { + NM_SETTING_VLAN_GET_PRIVATE(self)->egress_priority_map = list; + _notify(self, PROP_EGRESS_PRIORITY_MAP); + } else + nm_assert_not_reached(); +} + +static gboolean +check_replace_duplicate_priority(GSList *list, guint32 from, guint32 to) +{ + GSList * iter; + NMVlanQosMapping *p; + + for (iter = list; iter; iter = g_slist_next(iter)) { + p = iter->data; + if (p->from == from) { + p->to = to; + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_vlan_add_priority_str: + * @setting: the #NMSettingVlan + * @map: the type of priority map + * @str: the string which contains a priority map, like "3:7" + * + * Adds a priority map entry into either the #NMSettingVlan:ingress_priority_map + * or the #NMSettingVlan:egress_priority_map properties. The priority map maps + * the Linux SKB priorities to 802.1p priorities. + * + * Returns: %TRUE if the entry was successfully added to the list, or it + * overwrote the old value, %FALSE if @str is not a valid mapping. + */ +gboolean +nm_setting_vlan_add_priority_str(NMSettingVlan *setting, NMVlanPriorityMap map, const char *str) +{ + GSList * list = NULL; + NMVlanQosMapping *item = NULL; + + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE); + g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE); + g_return_val_if_fail(str && str[0], FALSE); + + item = priority_map_new_from_str(map, str); + if (!item) + return FALSE; + + list = get_map(setting, map); + + /* Duplicates get replaced */ + if (check_replace_duplicate_priority(list, item->from, item->to)) { + g_free(item); + if (map == NM_VLAN_INGRESS_MAP) + _notify(setting, PROP_INGRESS_PRIORITY_MAP); + else + _notify(setting, PROP_EGRESS_PRIORITY_MAP); + return TRUE; + } + + set_map(setting, map, g_slist_insert_sorted(list, item, prio_map_compare)); + return TRUE; +} + +/** + * nm_setting_vlan_get_num_priorities: + * @setting: the #NMSettingVlan + * @map: the type of priority map + * + * Returns the number of entries in the + * #NMSettingVlan:ingress_priority_map or #NMSettingVlan:egress_priority_map + * properties of this setting. + * + * Returns: return the number of ingress/egress priority entries. + **/ +gint32 +nm_setting_vlan_get_num_priorities(NMSettingVlan *setting, NMVlanPriorityMap map) +{ + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), -1); + g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, -1); + + return g_slist_length(get_map(setting, map)); +} + +/** + * nm_setting_vlan_get_priority: + * @setting: the #NMSettingVlan + * @map: the type of priority map + * @idx: the zero-based index of the ingress/egress priority map entry + * @out_from: (out) (allow-none): on return the value of the priority map's 'from' item + * @out_to: (out) (allow-none): on return the value of priority map's 'to' item + * + * Retrieve one of the entries of the #NMSettingVlan:ingress_priority_map + * or #NMSettingVlan:egress_priority_map properties of this setting. + * + * Returns: returns %TRUE if @idx is in range. Otherwise, %FALSE. + **/ +gboolean +nm_setting_vlan_get_priority(NMSettingVlan * setting, + NMVlanPriorityMap map, + guint32 idx, + guint32 * out_from, + guint32 * out_to) +{ + NMVlanQosMapping *item; + GSList * list; + + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE); + g_return_val_if_fail(NM_IN_SET(map, NM_VLAN_INGRESS_MAP, NM_VLAN_EGRESS_MAP), FALSE); + + list = get_map(setting, map); + item = g_slist_nth_data(list, idx); + + if (!item) { + NM_SET_OUT(out_from, 0); + NM_SET_OUT(out_to, 0); + return FALSE; + } + + NM_SET_OUT(out_from, item->from); + NM_SET_OUT(out_to, item->to); + return TRUE; +} + +/** + * nm_setting_vlan_add_priority: + * @setting: the #NMSettingVlan + * @map: the type of priority map + * @from: the priority to map to @to + * @to: the priority to map @from to + * + * Adds a priority mapping to the #NMSettingVlan:ingress_priority_map or + * #NMSettingVlan:egress_priority_map properties of the setting. If @from is + * already in the given priority map, this function will overwrite the + * existing entry with the new @to. + * + * If @map is #NM_VLAN_INGRESS_MAP then @from is the incoming 802.1q VLAN + * Priority Code Point (PCP) value, and @to is the Linux SKB priority value. + * + * If @map is #NM_VLAN_EGRESS_MAP then @from is the Linux SKB priority value and + * @to is the outgoing 802.1q VLAN Priority Code Point (PCP) value. + * + * Returns: %TRUE. + */ +gboolean +nm_setting_vlan_add_priority(NMSettingVlan * setting, + NMVlanPriorityMap map, + guint32 from, + guint32 to) +{ + GSList * list = NULL; + NMVlanQosMapping *item; + + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE); + g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE); + + list = get_map(setting, map); + if (check_replace_duplicate_priority(list, from, to)) { + if (map == NM_VLAN_INGRESS_MAP) + _notify(setting, PROP_INGRESS_PRIORITY_MAP); + else + _notify(setting, PROP_EGRESS_PRIORITY_MAP); + return TRUE; + } + + item = g_malloc0(sizeof(NMVlanQosMapping)); + item->from = from; + item->to = to; + set_map(setting, map, g_slist_insert_sorted(list, item, prio_map_compare)); + + return TRUE; +} + +gboolean +_nm_setting_vlan_set_priorities(NMSettingVlan * setting, + NMVlanPriorityMap map, + const NMVlanQosMapping *qos_map, + guint n_qos_map) +{ + gboolean has_changes = FALSE; + GSList * map_prev, *map_new; + guint i; + gint64 from_last; + + map_prev = get_map(setting, map); + + if (n_qos_map != g_slist_length(map_prev)) + has_changes = TRUE; + else { + const GSList *iter; + + iter = map_prev; + for (i = 0; i < n_qos_map; i++, iter = iter->next) { + const NMVlanQosMapping *m = iter->data; + + if (m->from != qos_map[i].from || m->to != qos_map[i].to) { + has_changes = TRUE; + break; + } + } + } + + if (!has_changes) + return FALSE; + + map_new = NULL; + from_last = G_MAXINT64; + for (i = n_qos_map; i > 0;) { + const NMVlanQosMapping *m = &qos_map[--i]; + NMVlanQosMapping * item; + + /* We require the array to be presorted. */ + if (m->from >= from_last) + g_return_val_if_reached(FALSE); + from_last = m->from; + + item = g_malloc0(sizeof(NMVlanQosMapping)); + item->from = m->from; + item->to = m->to; + map_new = g_slist_prepend(map_new, item); + } + + g_slist_free_full(map_prev, g_free); + set_map(setting, map, map_new); + + return TRUE; +} + +void +_nm_setting_vlan_get_priorities(NMSettingVlan * setting, + NMVlanPriorityMap map, + NMVlanQosMapping **out_qos_map, + guint * out_n_qos_map) +{ + GSList * list; + NMVlanQosMapping *qos_map = NULL; + guint n_qos_map, i; + + list = get_map(setting, map); + + n_qos_map = g_slist_length(list); + + if (n_qos_map > 0) { + qos_map = g_new(NMVlanQosMapping, n_qos_map); + + for (i = 0; list; i++, list = list->next) { + nm_assert(i < n_qos_map); + qos_map[i] = *((const NMVlanQosMapping *) list->data); + } + } + *out_qos_map = qos_map; + *out_n_qos_map = n_qos_map; +} + +/** + * nm_setting_vlan_remove_priority: + * @setting: the #NMSettingVlan + * @map: the type of priority map + * @idx: the zero-based index of the priority map to remove + * + * Removes the priority map at index @idx from the + * #NMSettingVlan:ingress_priority_map or #NMSettingVlan:egress_priority_map + * properties. + */ +void +nm_setting_vlan_remove_priority(NMSettingVlan *setting, NMVlanPriorityMap map, guint32 idx) +{ + GSList *list = NULL, *item = NULL; + + g_return_if_fail(NM_IS_SETTING_VLAN(setting)); + g_return_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP); + + list = get_map(setting, map); + g_return_if_fail(idx < g_slist_length(list)); + + item = g_slist_nth(list, idx); + priority_map_free((NMVlanQosMapping *) (item->data)); + set_map(setting, map, g_slist_delete_link(list, item)); +} + +static gboolean +priority_map_remove_by_value(NMSettingVlan * setting, + NMVlanPriorityMap map, + guint32 from, + guint32 to, + gboolean wildcard_to) +{ + GSList * list = NULL, *iter = NULL; + NMVlanQosMapping *item; + + nm_assert(NM_IS_SETTING_VLAN(setting)); + nm_assert(NM_IN_SET(map, NM_VLAN_INGRESS_MAP, NM_VLAN_EGRESS_MAP)); + + list = get_map(setting, map); + for (iter = list; iter; iter = g_slist_next(iter)) { + item = iter->data; + + if (item->from != from) + continue; + if (!wildcard_to && item->to != to) + continue; + + priority_map_free((NMVlanQosMapping *) (iter->data)); + set_map(setting, map, g_slist_delete_link(list, iter)); + return TRUE; + } + return FALSE; +} + +/** + * nm_setting_vlan_remove_priority_by_value: + * @setting: the #NMSettingVlan + * @map: the type of priority map + * @from: the priority to map to @to + * @to: the priority to map @from to + * + * Removes the priority map @form:@to from the #NMSettingVlan:ingress_priority_map + * or #NMSettingVlan:egress_priority_map (according to @map argument) + * properties. + * + * Returns: %TRUE if the priority mapping was found and removed; %FALSE if it was not. + */ +gboolean +nm_setting_vlan_remove_priority_by_value(NMSettingVlan * setting, + NMVlanPriorityMap map, + guint32 from, + guint32 to) +{ + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE); + g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE); + + return priority_map_remove_by_value(setting, map, from, to, FALSE); +} + +/** + * nm_setting_vlan_remove_priority_str_by_value: + * @setting: the #NMSettingVlan + * @map: the type of priority map + * @str: the string which contains a priority map, like "3:7" + * + * Removes the priority map @str from the #NMSettingVlan:ingress_priority_map + * or #NMSettingVlan:egress_priority_map (according to @map argument) + * properties. + * + * Returns: %TRUE if the priority mapping was found and removed; %FALSE if it was not. + */ +gboolean +nm_setting_vlan_remove_priority_str_by_value(NMSettingVlan * setting, + NMVlanPriorityMap map, + const char * str) +{ + gboolean is_wildcard_to; + guint32 from, to; + + g_return_val_if_fail(NM_IS_SETTING_VLAN(setting), FALSE); + g_return_val_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP, FALSE); + + if (!nm_utils_vlan_priority_map_parse_str(map, str, TRUE, &from, &to, &is_wildcard_to)) + return FALSE; + return priority_map_remove_by_value(setting, map, from, to, is_wildcard_to); +} + +/** + * nm_setting_vlan_clear_priorities: + * @setting: the #NMSettingVlan + * @map: the type of priority map + * + * Clear all the entries from #NMSettingVlan:ingress_priority_map or + * #NMSettingVlan:egress_priority_map properties. + */ +void +nm_setting_vlan_clear_priorities(NMSettingVlan *setting, NMVlanPriorityMap map) +{ + GSList *list = NULL; + + g_return_if_fail(NM_IS_SETTING_VLAN(setting)); + g_return_if_fail(map == NM_VLAN_INGRESS_MAP || map == NM_VLAN_EGRESS_MAP); + + list = get_map(setting, map); + g_slist_free_full(list, g_free); + set_map(setting, map, NULL); +} + +/*****************************************************************************/ + +static int +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingVlanPrivate *priv = NM_SETTING_VLAN_GET_PRIVATE(setting); + NMSettingConnection * s_con; + NMSettingWired * s_wired; + + if (connection) { + s_con = nm_connection_get_setting_connection(connection); + s_wired = nm_connection_get_setting_wired(connection); + } else { + s_con = NULL; + s_wired = NULL; + } + + if (priv->parent) { + if (nm_utils_is_uuid(priv->parent)) { + /* If we have an NMSettingConnection:master with slave-type="vlan", + * then it must be the same UUID. + */ + if (s_con) { + const char *master = NULL, *slave_type = NULL; + + slave_type = nm_setting_connection_get_slave_type(s_con); + if (!g_strcmp0(slave_type, NM_SETTING_VLAN_SETTING_NAME)) + master = nm_setting_connection_get_master(s_con); + + if (master && g_strcmp0(priv->parent, master) != 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' value doesn't match '%s=%s'"), + priv->parent, + NM_SETTING_CONNECTION_MASTER, + master); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_VLAN_SETTING_NAME, + NM_SETTING_VLAN_PARENT); + return FALSE; + } + } + } else if (!nm_utils_ifname_valid_kernel(priv->parent, NULL)) { + /* parent must be either a UUID or an interface name */ + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is neither an UUID nor an interface name"), + priv->parent); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_PARENT); + return FALSE; + } + } else { + /* If parent is NULL, the parent must be specified via + * NMSettingWired:mac-address. + */ + if (connection && (!s_wired || !nm_setting_wired_get_mac_address(s_wired))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is not specified and neither is '%s:%s'"), + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_MAC_ADDRESS); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_PARENT); + return FALSE; + } + } + + if (priv->id >= 4095) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("the vlan id must be in range 0-4094 but is %u"), + priv->id); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_ID); + return FALSE; + } + + if (priv->flags & ~NM_VLAN_FLAGS_ALL) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("flags are invalid")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_FLAGS); + return FALSE; + } + + if (connection && !s_wired) { + /* technically, a VLAN setting does not require an ethernet setting. However, + * the ifcfg-rh reader always adds a ethernet setting when reading a vlan setting. + * Thus, in order to be consistent, always add one via normalization. */ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_SETTING_NOT_FOUND, + _("vlan setting should have a ethernet setting as well")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VLAN_SETTING_NAME, NM_SETTING_VLAN_FLAGS); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + + return TRUE; +} + +static GVariant * +_override_flags_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + return g_variant_new_uint32(nm_setting_vlan_get_flags((NMSettingVlan *) setting)); +} + +static gboolean +_override_flags_not_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + NMSettingParseFlags parse_flags, + GError ** error) +{ + /* we changed the default value for FLAGS. When an older client + * doesn't serialize the property, we assume it is the old default. */ + g_object_set(G_OBJECT(setting), NM_SETTING_VLAN_FLAGS, (NMVlanFlags) 0, NULL); + return TRUE; +} + +static GSList * +priority_strv_to_maplist(NMVlanPriorityMap map, char **strv) +{ + GSList *list = NULL; + gsize i; + + for (i = 0; strv && strv[i]; i++) { + guint32 from, to; + + if (!nm_utils_vlan_priority_map_parse_str(map, strv[i], FALSE, &from, &to, NULL)) + continue; + if (check_replace_duplicate_priority(list, from, to)) + continue; + list = g_slist_prepend(list, priority_map_new(from, to)); + } + return g_slist_sort(list, prio_map_compare); +} + +static char ** +priority_maplist_to_strv(GSList *list) +{ + GSList * iter; + GPtrArray *strv; + + strv = g_ptr_array_new(); + + for (iter = list; iter; iter = g_slist_next(iter)) { + NMVlanQosMapping *item = iter->data; + + g_ptr_array_add(strv, g_strdup_printf("%d:%d", item->from, item->to)); + } + g_ptr_array_add(strv, NULL); + + return (char **) g_ptr_array_free(strv, FALSE); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingVlan * setting = NM_SETTING_VLAN(object); + NMSettingVlanPrivate *priv = NM_SETTING_VLAN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_value_set_string(value, priv->parent); + break; + case PROP_ID: + g_value_set_uint(value, priv->id); + break; + case PROP_FLAGS: + g_value_set_flags(value, priv->flags); + break; + case PROP_INGRESS_PRIORITY_MAP: + g_value_take_boxed(value, priority_maplist_to_strv(priv->ingress_priority_map)); + break; + case PROP_EGRESS_PRIORITY_MAP: + g_value_take_boxed(value, priority_maplist_to_strv(priv->egress_priority_map)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingVlan * setting = NM_SETTING_VLAN(object); + NMSettingVlanPrivate *priv = NM_SETTING_VLAN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_free(priv->parent); + priv->parent = g_value_dup_string(value); + break; + case PROP_ID: + priv->id = g_value_get_uint(value); + break; + case PROP_FLAGS: + priv->flags = g_value_get_flags(value); + break; + case PROP_INGRESS_PRIORITY_MAP: + g_slist_free_full(priv->ingress_priority_map, g_free); + priv->ingress_priority_map = + priority_strv_to_maplist(NM_VLAN_INGRESS_MAP, g_value_get_boxed(value)); + break; + case PROP_EGRESS_PRIORITY_MAP: + g_slist_free_full(priv->egress_priority_map, g_free); + priv->egress_priority_map = + priority_strv_to_maplist(NM_VLAN_EGRESS_MAP, g_value_get_boxed(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_vlan_init(NMSettingVlan *self) +{ + NMSettingVlanPrivate *priv = NM_SETTING_VLAN_GET_PRIVATE(self); + + priv->flags = NM_VLAN_FLAG_REORDER_HEADERS; +} + +/** + * nm_setting_vlan_new: + * + * Creates a new #NMSettingVlan object with default values. + * + * Returns: (transfer full): the new empty #NMSettingVlan object + **/ +NMSetting * +nm_setting_vlan_new(void) +{ + return g_object_new(NM_TYPE_SETTING_VLAN, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingVlan * setting = NM_SETTING_VLAN(object); + NMSettingVlanPrivate *priv = NM_SETTING_VLAN_GET_PRIVATE(setting); + + g_free(priv->parent); + g_slist_free_full(priv->ingress_priority_map, g_free); + g_slist_free_full(priv->egress_priority_map, g_free); + + G_OBJECT_CLASS(nm_setting_vlan_parent_class)->finalize(object); +} + +static void +nm_setting_vlan_class_init(NMSettingVlanClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingVlanPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingVlan:parent: + * + * If given, specifies the parent interface name or parent connection UUID + * from which this VLAN interface should be created. If this property is + * not specified, the connection must contain an #NMSettingWired setting + * with a #NMSettingWired:mac-address property. + **/ + /* ---ifcfg-rh--- + * property: parent + * variable: DEVICE or PHYSDEV + * description: Parent interface of the VLAN. + * ---end--- + */ + obj_properties[PROP_PARENT] = g_param_spec_string( + NM_SETTING_VLAN_PARENT, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVlan:id: + * + * The VLAN identifier that the interface created by this connection should + * be assigned. The valid range is from 0 to 4094, without the reserved id 4095. + **/ + /* ---ifcfg-rh--- + * property: id + * variable: VLAN_ID, DEVICE. + * description: VLAN identifier. If VLAN_ID is not set, it is attempted + * to be detected from the suffix of DEVICE=. + * Note that older versions of NetworkManager had a bug where they would + * prefer the detected ID from the DEVICE over VLAN_ID. + * ---end--- + */ + obj_properties[PROP_ID] = + g_param_spec_uint(NM_SETTING_VLAN_ID, + "", + "", + 0, + 4095, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVlan:flags: + * + * One or more flags which control the behavior and features of the VLAN + * interface. Flags include %NM_VLAN_FLAG_REORDER_HEADERS (reordering of + * output packet headers), %NM_VLAN_FLAG_GVRP (use of the GVRP protocol), + * and %NM_VLAN_FLAG_LOOSE_BINDING (loose binding of the interface to its + * master device's operating state). %NM_VLAN_FLAG_MVRP (use of the MVRP + * protocol). + * + * The default value of this property is NM_VLAN_FLAG_REORDER_HEADERS, + * but it used to be 0. To preserve backward compatibility, the default-value + * in the D-Bus API continues to be 0 and a missing property on D-Bus + * is still considered as 0. + **/ + /* ---ifcfg-rh--- + * property: flags + * variable: GVRP, MVRP, VLAN_FLAGS + * values: "yes or "no" for GVRP and MVRP; "LOOSE_BINDING" and "NO_REORDER_HDR" for VLAN_FLAGS + * description: VLAN flags. + * ---end--- + */ + obj_properties[PROP_FLAGS] = g_param_spec_flags(NM_SETTING_VLAN_FLAGS, + "", + "", + NM_TYPE_VLAN_FLAGS, + NM_VLAN_FLAG_REORDER_HEADERS, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_FLAGS], + NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_UINT32, + .to_dbus_fcn = _override_flags_get, + .missing_from_dbus_fcn = _override_flags_not_set, )); + + /** + * NMSettingVlan:ingress-priority-map: + * + * For incoming packets, a list of mappings from 802.1p priorities to Linux + * SKB priorities. The mapping is given in the format "from:to" where both + * "from" and "to" are unsigned integers, ie "7:3". + **/ + /* ---ifcfg-rh--- + * property: ingress-priority-map + * variable: VLAN_INGRESS_PRIORITY_MAP + * description: Ingress priority mapping. + * example: VLAN_INGRESS_PRIORITY_MAP=4:2,3:5 + * ---end--- + */ + obj_properties[PROP_INGRESS_PRIORITY_MAP] = g_param_spec_boxed( + NM_SETTING_VLAN_INGRESS_PRIORITY_MAP, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVlan:egress-priority-map: + * + * For outgoing packets, a list of mappings from Linux SKB priorities to + * 802.1p priorities. The mapping is given in the format "from:to" where + * both "from" and "to" are unsigned integers, ie "7:3". + **/ + /* ---ifcfg-rh--- + * property: egress-priority-map + * variable: VLAN_EGRESS_PRIORITY_MAP + * description: Egress priority mapping. + * example: VLAN_EGRESS_PRIORITY_MAP=5:4,4:1,3:7 + * ---end--- + */ + obj_properties[PROP_EGRESS_PRIORITY_MAP] = g_param_spec_boxed( + NM_SETTING_VLAN_EGRESS_PRIORITY_MAP, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /* ---ifcfg-rh--- + * property: interface-name + * variable: PHYSDEV and VLAN_ID, or DEVICE + * description: VLAN interface name. + * If all variables are set, parent device from PHYSDEV takes precedence over DEVICE, + * but VLAN id from DEVICE takes precedence over VLAN_ID. + * example: PHYSDEV=eth0, VLAN_ID=12; or DEVICE=eth0.12 + * ---end--- + * ---dbus--- + * property: interface-name + * format: string + * description: Deprecated in favor of connection.interface-name, but can + * be used for backward-compatibility with older daemons, to set the + * vlan's interface name. + * ---end--- + */ + _nm_properties_override_dbus(properties_override, + "interface-name", + &nm_sett_info_propert_type_deprecated_interface_name); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_VLAN, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-vpn.c b/src/libnm-core-impl/nm-setting-vpn.c new file mode 100644 index 00000000..351f3fd7 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-vpn.c @@ -0,0 +1,1259 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2013 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-vpn.h" + +#include + +#include "libnm-glib-aux/nm-secret-utils.h" +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-vpn + * @short_description: Describes connection properties for Virtual Private Networks + * + * The #NMSettingVpn object is a #NMSetting subclass that describes properties + * necessary for connection to Virtual Private Networks. NetworkManager uses + * a plugin architecture to allow easier use of new VPN types, and this + * setting abstracts the configuration for those plugins. Since the configuration + * options are only known to the VPN plugins themselves, the VPN configuration + * options are stored as key/value pairs of strings rather than GObject + * properties. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingVpn, + PROP_SERVICE_TYPE, + PROP_USER_NAME, + PROP_PERSISTENT, + PROP_DATA, + PROP_SECRETS, + PROP_TIMEOUT, ); + +typedef struct { + char *service_type; + + /* username of the user requesting this connection, thus + * it's really only valid for user connections, and it also + * should never be saved out to persistent config. + */ + char *user_name; + + /* Whether the VPN stays up across link changes, until the user + * explicitly disconnects it. + */ + gboolean persistent; + + /* The hash table is created at setting object + * init time and should not be replaced. It is + * a char * -> char * mapping, and both the key + * and value are owned by the hash table, and should + * be allocated with functions whose value can be + * freed with g_free(). Should not contain secrets. + */ + GHashTable *data; + + /* The hash table is created at setting object + * init time and should not be replaced. It is + * a char * -> char * mapping, and both the key + * and value are owned by the hash table, and should + * be allocated with functions whose value can be + * freed with g_free(). Should contain secrets only. + */ + GHashTable *secrets; + + /* Timeout for the VPN service to establish the connection */ + guint32 timeout; +} NMSettingVpnPrivate; + +/** + * NMSettingVpn: + * + * VPN Settings + */ +struct _NMSettingVpn { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingVpnClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingVpn, nm_setting_vpn, NM_TYPE_SETTING) + +#define NM_SETTING_VPN_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_VPN, NMSettingVpnPrivate)) + +/*****************************************************************************/ + +static GHashTable * +_ensure_strdict(GHashTable **p_hash, gboolean for_secrets) +{ + if (!*p_hash) { + *p_hash = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + for_secrets ? (GDestroyNotify) nm_free_secret : g_free); + } + return *p_hash; +} + +/*****************************************************************************/ + +/** + * nm_setting_vpn_get_service_type: + * @setting: the #NMSettingVpn + * + * Returns the service name of the VPN, which identifies the specific VPN + * plugin that should be used to connect to this VPN. + * + * Returns: the VPN plugin's service name + **/ +const char * +nm_setting_vpn_get_service_type(NMSettingVpn *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), NULL); + + return NM_SETTING_VPN_GET_PRIVATE(setting)->service_type; +} + +/** + * nm_setting_vpn_get_user_name: + * @setting: the #NMSettingVpn + * + * Returns: the #NMSettingVpn:user-name property of the setting + **/ +const char * +nm_setting_vpn_get_user_name(NMSettingVpn *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), NULL); + + return NM_SETTING_VPN_GET_PRIVATE(setting)->user_name; +} + +/** + * nm_setting_vpn_get_persistent: + * @setting: the #NMSettingVpn + * + * Returns: the #NMSettingVpn:persistent property of the setting + **/ +gboolean +nm_setting_vpn_get_persistent(NMSettingVpn *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), FALSE); + + return NM_SETTING_VPN_GET_PRIVATE(setting)->persistent; +} + +/** + * nm_setting_vpn_get_num_data_items: + * @setting: the #NMSettingVpn + * + * Gets number of key/value pairs of VPN configuration data. + * + * Returns: the number of VPN plugin specific configuration data items + **/ +guint32 +nm_setting_vpn_get_num_data_items(NMSettingVpn *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), 0); + + return nm_g_hash_table_size(NM_SETTING_VPN_GET_PRIVATE(setting)->data); +} + +/** + * nm_setting_vpn_add_data_item: + * @setting: the #NMSettingVpn + * @key: a name that uniquely identifies the given value @item + * @item: (allow-none): the value to be referenced by @key + * + * Establishes a relationship between @key and @item internally in the + * setting which may be retrieved later. Should not be used to store passwords + * or other secrets, which is what nm_setting_vpn_add_secret() is for. + * + * Before 1.24, @item must not be %NULL and not an empty string. Since 1.24, + * @item can be set to an empty string. It can also be set to %NULL to unset + * the key. In that case, the behavior is as if calling nm_setting_vpn_remove_data_item(). + **/ +void +nm_setting_vpn_add_data_item(NMSettingVpn *setting, const char *key, const char *item) +{ + if (!item) { + nm_setting_vpn_remove_data_item(setting, key); + return; + } + + g_return_if_fail(NM_IS_SETTING_VPN(setting)); + g_return_if_fail(key && key[0]); + + g_hash_table_insert(_ensure_strdict(&NM_SETTING_VPN_GET_PRIVATE(setting)->data, FALSE), + g_strdup(key), + g_strdup(item)); + _notify(setting, PROP_DATA); +} + +/** + * nm_setting_vpn_get_data_item: + * @setting: the #NMSettingVpn + * @key: the name of the data item to retrieve + * + * Retrieves the data item of a key/value relationship previously established + * by nm_setting_vpn_add_data_item(). + * + * Returns: the data item, if any + **/ +const char * +nm_setting_vpn_get_data_item(NMSettingVpn *setting, const char *key) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), NULL); + g_return_val_if_fail(key && key[0], NULL); + + return nm_g_hash_table_lookup(NM_SETTING_VPN_GET_PRIVATE(setting)->data, key); +} + +/** + * nm_setting_vpn_get_data_keys: + * @setting: the #NMSettingVpn + * @out_length: (allow-none) (out): the length of the returned array + * + * Retrieves every data key inside @setting, as an array. + * + * Returns: (array length=out_length) (transfer container): a + * %NULL-terminated array containing each data key or %NULL if + * there are no data items. + * + * Since: 1.12 + */ +const char ** +nm_setting_vpn_get_data_keys(NMSettingVpn *setting, guint *out_length) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), NULL); + + return nm_utils_strdict_get_keys(NM_SETTING_VPN_GET_PRIVATE(setting)->data, TRUE, out_length); +} + +/** + * nm_setting_vpn_remove_data_item: + * @setting: the #NMSettingVpn + * @key: the name of the data item to remove + * + * Deletes a key/value relationship previously established by + * nm_setting_vpn_add_data_item(). + * + * Returns: %TRUE if the data item was found and removed from the internal list, + * %FALSE if it was not. + **/ +gboolean +nm_setting_vpn_remove_data_item(NMSettingVpn *setting, const char *key) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), FALSE); + g_return_val_if_fail(key && key[0], FALSE); + + if (nm_g_hash_table_remove(NM_SETTING_VPN_GET_PRIVATE(setting)->data, key)) { + _notify(setting, PROP_DATA); + return TRUE; + } + return FALSE; +} + +static void +foreach_item_helper(NMSettingVpn *self, GHashTable **p_hash, NMVpnIterFunc func, gpointer user_data) +{ + gs_unref_object NMSettingVpn *self_keep_alive = NULL; + gs_strfreev char ** keys = NULL; + guint i, len; + + nm_assert(NM_IS_SETTING_VPN(self)); + nm_assert(func); + + keys = nm_utils_strv_make_deep_copied(nm_utils_strdict_get_keys(*p_hash, TRUE, &len)); + if (len == 0u) { + nm_assert(!keys); + return; + } + + if (len > 1u) + self_keep_alive = g_object_ref(self); + + for (i = 0; i < len; i++) { + /* NOTE: note that we call the function with a clone of @key, + * not with the actual key from the dictionary. + * + * The @value on the other hand, is not cloned but retrieved before + * invoking @func(). That means, if @func() modifies the setting while + * being called, the values are as they currently are, but the + * keys (and their order) were pre-determined before starting to + * invoke the callbacks. + * + * The idea is to give some sensible, stable behavior in case the user + * modifies the settings. Whether this particular behavior is optimal + * is unclear. It's probably a bad idea to modify the settings while + * iterating the values. But at least, it's a safe thing to do and we + * do something sensible. */ + func(keys[i], nm_g_hash_table_lookup(*p_hash, keys[i]), user_data); + } +} + +/** + * nm_setting_vpn_foreach_data_item: + * @setting: a #NMSettingVpn + * @func: (scope call): an user provided function + * @user_data: data to be passed to @func + * + * Iterates all data items stored in this setting. It is safe to add, remove, + * and modify data items inside @func, though any additions or removals made + * during iteration will not be part of the iteration. + */ +void +nm_setting_vpn_foreach_data_item(NMSettingVpn *setting, NMVpnIterFunc func, gpointer user_data) +{ + g_return_if_fail(NM_IS_SETTING_VPN(setting)); + g_return_if_fail(func); + + foreach_item_helper(setting, &NM_SETTING_VPN_GET_PRIVATE(setting)->data, func, user_data); +} + +/** + * nm_setting_vpn_get_num_secrets: + * @setting: the #NMSettingVpn + * + * Gets number of VPN plugin specific secrets in the setting. + * + * Returns: the number of VPN plugin specific secrets + **/ +guint32 +nm_setting_vpn_get_num_secrets(NMSettingVpn *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), 0); + + return nm_g_hash_table_size(NM_SETTING_VPN_GET_PRIVATE(setting)->secrets); +} + +/** + * nm_setting_vpn_add_secret: + * @setting: the #NMSettingVpn + * @key: a name that uniquely identifies the given secret @secret + * @secret: (allow-none): the secret to be referenced by @key + * + * Establishes a relationship between @key and @secret internally in the + * setting which may be retrieved later. + * + * Before 1.24, @secret must not be %NULL and not an empty string. Since 1.24, + * @secret can be set to an empty string. It can also be set to %NULL to unset + * the key. In that case, the behavior is as if calling nm_setting_vpn_remove_secret(). + **/ +void +nm_setting_vpn_add_secret(NMSettingVpn *setting, const char *key, const char *secret) +{ + if (!secret) { + nm_setting_vpn_remove_secret(setting, key); + return; + } + + g_return_if_fail(NM_IS_SETTING_VPN(setting)); + g_return_if_fail(key && key[0]); + + g_hash_table_insert(_ensure_strdict(&NM_SETTING_VPN_GET_PRIVATE(setting)->secrets, TRUE), + g_strdup(key), + g_strdup(secret)); + _notify(setting, PROP_SECRETS); +} + +/** + * nm_setting_vpn_get_secret: + * @setting: the #NMSettingVpn + * @key: the name of the secret to retrieve + * + * Retrieves the secret of a key/value relationship previously established + * by nm_setting_vpn_add_secret(). + * + * Returns: the secret, if any + **/ +const char * +nm_setting_vpn_get_secret(NMSettingVpn *setting, const char *key) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), NULL); + g_return_val_if_fail(key && key[0], NULL); + + return nm_g_hash_table_lookup(NM_SETTING_VPN_GET_PRIVATE(setting)->secrets, key); +} + +/** + * nm_setting_vpn_get_secret_keys: + * @setting: the #NMSettingVpn + * @out_length: (allow-none) (out): the length of the returned array + * + * Retrieves every secret key inside @setting, as an array. + * + * Returns: (array length=out_length) (transfer container): a + * %NULL-terminated array containing each secret key or %NULL if + * there are no secrets. + * + * Since: 1.12 + */ +const char ** +nm_setting_vpn_get_secret_keys(NMSettingVpn *setting, guint *out_length) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), NULL); + + return nm_utils_strdict_get_keys(NM_SETTING_VPN_GET_PRIVATE(setting)->secrets, + TRUE, + out_length); +} + +/** + * nm_setting_vpn_remove_secret: + * @setting: the #NMSettingVpn + * @key: the name of the secret to remove + * + * Deletes a key/value relationship previously established by + * nm_setting_vpn_add_secret(). + * + * Returns: %TRUE if the secret was found and removed from the internal list, + * %FALSE if it was not. + **/ +gboolean +nm_setting_vpn_remove_secret(NMSettingVpn *setting, const char *key) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), FALSE); + g_return_val_if_fail(key && key[0], FALSE); + + if (nm_g_hash_table_remove(NM_SETTING_VPN_GET_PRIVATE(setting)->secrets, key)) { + _notify(setting, PROP_SECRETS); + return TRUE; + } + return FALSE; +} + +/** + * nm_setting_vpn_foreach_secret: + * @setting: a #NMSettingVpn + * @func: (scope call): an user provided function + * @user_data: data to be passed to @func + * + * Iterates all secrets stored in this setting. It is safe to add, remove, + * and modify secrets inside @func, though any additions or removals made during + * iteration will not be part of the iteration. + */ +void +nm_setting_vpn_foreach_secret(NMSettingVpn *setting, NMVpnIterFunc func, gpointer user_data) +{ + g_return_if_fail(NM_IS_SETTING_VPN(setting)); + g_return_if_fail(func); + + foreach_item_helper(setting, &NM_SETTING_VPN_GET_PRIVATE(setting)->secrets, func, user_data); +} + +static gboolean +aggregate(NMSetting *setting, int type_i, gpointer arg) +{ + NMSettingVpnPrivate * priv = NM_SETTING_VPN_GET_PRIVATE(setting); + NMConnectionAggregateType type = type_i; + NMSettingSecretFlags secret_flags; + const char * key_name; + GHashTableIter iter; + + switch (type) { + case NM_CONNECTION_AGGREGATE_ANY_SECRETS: + if (nm_g_hash_table_size(priv->secrets) > 0u) { + *((gboolean *) arg) = TRUE; + return TRUE; + } + return FALSE; + + case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS: + + if (priv->secrets) { + g_hash_table_iter_init(&iter, priv->secrets); + while (g_hash_table_iter_next(&iter, (gpointer *) &key_name, NULL)) { + if (!nm_setting_get_secret_flags(NM_SETTING(setting), + key_name, + &secret_flags, + NULL)) + nm_assert_not_reached(); + if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) { + *((gboolean *) arg) = TRUE; + return TRUE; + } + } + } + + /* OK, we have no secrets with system-secret flags. + * But do we have any secret-flags (without secrets) that indicate system secrets? */ + if (priv->data) { + g_hash_table_iter_init(&iter, priv->data); + while (g_hash_table_iter_next(&iter, (gpointer *) &key_name, NULL)) { + gs_free char *secret_name = NULL; + + if (!NM_STR_HAS_SUFFIX(key_name, "-flags")) + continue; + secret_name = g_strndup(key_name, strlen(key_name) - NM_STRLEN("-flags")); + if (secret_name[0] == '\0') + continue; + if (!nm_setting_get_secret_flags(NM_SETTING(setting), + secret_name, + &secret_flags, + NULL)) + nm_assert_not_reached(); + if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) { + *((gboolean *) arg) = TRUE; + return TRUE; + } + } + } + + return FALSE; + } + + g_return_val_if_reached(FALSE); +} + +/** + * nm_setting_vpn_get_timeout: + * @setting: the #NMSettingVpn + * + * Returns: the #NMSettingVpn:timeout property of the setting + * + * Since: 1.2 + **/ +guint32 +nm_setting_vpn_get_timeout(NMSettingVpn *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VPN(setting), 0); + + return NM_SETTING_VPN_GET_PRIVATE(setting)->timeout; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(setting); + NMSettingConnection *s_con; + + if (!priv->service_type) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, NM_SETTING_VPN_SERVICE_TYPE); + return FALSE; + } + if (!priv->service_type[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, NM_SETTING_VPN_SERVICE_TYPE); + return FALSE; + } + + /* default username can be NULL, but can't be zero-length */ + if (priv->user_name && !priv->user_name[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, NM_SETTING_VPN_USER_NAME); + return FALSE; + } + + if (connection && (s_con = nm_connection_get_setting_connection(connection)) + && nm_setting_connection_get_multi_connect(s_con) != NM_CONNECTION_MULTI_CONNECT_DEFAULT) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("cannot set connection.multi-connect for VPN setting")); + return FALSE; + } + + return TRUE; +} + +static NMSettingUpdateSecretResult +update_secret_string(NMSetting *setting, const char *key, const char *value, GError **error) +{ + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(setting); + + g_return_val_if_fail(key && key[0], NM_SETTING_UPDATE_SECRET_ERROR); + g_return_val_if_fail(value, NM_SETTING_UPDATE_SECRET_ERROR); + + if (nm_streq0(nm_g_hash_table_lookup(priv->secrets, key), value)) + return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; + + g_hash_table_insert(_ensure_strdict(&priv->secrets, TRUE), g_strdup(key), g_strdup(value)); + return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED; +} + +static NMSettingUpdateSecretResult +update_secret_dict(NMSetting *setting, GVariant *secrets, GError **error) +{ + NMSettingVpnPrivate * priv = NM_SETTING_VPN_GET_PRIVATE(setting); + GVariantIter iter; + const char * name, *value; + NMSettingUpdateSecretResult result = NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; + + g_return_val_if_fail(secrets != NULL, NM_SETTING_UPDATE_SECRET_ERROR); + + /* Make sure the items are valid */ + g_variant_iter_init(&iter, secrets); + while (g_variant_iter_next(&iter, "{&s&s}", &name, &value)) { + if (!name[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("setting contained a secret with an empty name")); + g_prefix_error(error, "%s: ", NM_SETTING_VPN_SETTING_NAME); + return NM_SETTING_UPDATE_SECRET_ERROR; + } + } + + /* Now add the items to the settings' secrets list */ + g_variant_iter_init(&iter, secrets); + while (g_variant_iter_next(&iter, "{&s&s}", &name, &value)) { + if (nm_streq0(nm_g_hash_table_lookup(priv->secrets, name), value)) + continue; + + g_hash_table_insert(_ensure_strdict(&priv->secrets, TRUE), g_strdup(name), g_strdup(value)); + result = NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED; + } + + return result; +} + +static int +update_one_secret(NMSetting *setting, const char *key, GVariant *value, GError **error) +{ + NMSettingUpdateSecretResult success = NM_SETTING_UPDATE_SECRET_ERROR; + + g_return_val_if_fail(key != NULL, NM_SETTING_UPDATE_SECRET_ERROR); + g_return_val_if_fail(value != NULL, NM_SETTING_UPDATE_SECRET_ERROR); + + if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING)) { + /* Passing the string properties individually isn't correct, and won't + * produce the correct result, but for some reason that's how it used + * to be done. So even though it's not correct, keep the code around + * for compatibility's sake. + */ + success = update_secret_string(setting, key, g_variant_get_string(value, NULL), error); + } else if (g_variant_is_of_type(value, G_VARIANT_TYPE("a{ss}"))) { + if (!nm_streq(key, NM_SETTING_VPN_SECRETS)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("not a secret property")); + g_prefix_error(error, "%s.%s ", NM_SETTING_VPN_SETTING_NAME, key); + } else + success = update_secret_dict(setting, value, error); + } else { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("secret is not of correct type")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, key); + } + + if (success == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED) + _notify(NM_SETTING_VPN(setting), PROP_SECRETS); + + return success; +} + +static void +for_each_secret(NMSetting * setting, + const char * secret_name, + GVariant * val, + gboolean remove_non_secrets, + _NMConnectionForEachSecretFunc callback, + gpointer callback_data, + GVariantBuilder * setting_builder) +{ + GVariantBuilder vpn_secrets_builder; + GVariantIter vpn_secrets_iter; + const char * vpn_secret_name; + const char * secret; + + if (!nm_streq(secret_name, NM_SETTING_VPN_SECRETS)) { + NM_SETTING_CLASS(nm_setting_vpn_parent_class) + ->for_each_secret(setting, + secret_name, + val, + remove_non_secrets, + callback, + callback_data, + setting_builder); + return; + } + + if (!g_variant_is_of_type(val, G_VARIANT_TYPE("a{ss}"))) { + /* invalid type. Silently ignore the secrets as we cannot find out the + * secret-flags. */ + return; + } + + /* Iterate through each secret from the VPN dict in the overall secrets dict */ + g_variant_builder_init(&vpn_secrets_builder, G_VARIANT_TYPE("a{ss}")); + g_variant_iter_init(&vpn_secrets_iter, val); + while (g_variant_iter_next(&vpn_secrets_iter, "{&s&s}", &vpn_secret_name, &secret)) { + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + /* we ignore the return value of get_secret_flags. The function may determine + * that this is not a secret, based on having not secret-flags and no secrets. + * But we have the secret at hand. We know it would be a valid secret, if we + * only add it to the VPN settings. */ + nm_setting_get_secret_flags(setting, vpn_secret_name, &secret_flags, NULL); + + if (callback(secret_flags, callback_data)) + g_variant_builder_add(&vpn_secrets_builder, "{ss}", vpn_secret_name, secret); + } + + g_variant_builder_add(setting_builder, + "{sv}", + secret_name, + g_variant_builder_end(&vpn_secrets_builder)); +} + +static gboolean +get_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags *out_flags, + GError ** error) +{ + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(setting); + gs_free char * flags_key_free = NULL; + const char * flags_key; + const char * flags_val; + gint64 i64; + + nm_assert(secret_name); + + if (!secret_name[0]) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("secret name cannot be empty")); + return FALSE; + } + + flags_key = nm_construct_name_a("%s-flags", secret_name, &flags_key_free); + + if (!priv->data + || !g_hash_table_lookup_extended(priv->data, flags_key, NULL, (gpointer *) &flags_val)) { + NM_SET_OUT(out_flags, NM_SETTING_SECRET_FLAG_NONE); + + /* having no secret flag for the secret is fine, as long as there + * is the secret itself... */ + if (!nm_g_hash_table_lookup(priv->secrets, secret_name)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("secret flags property not found")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VPN_SETTING_NAME, flags_key); + return FALSE; + } + return TRUE; + } + + i64 = _nm_utils_ascii_str_to_int64(flags_val, 10, 0, NM_SETTING_SECRET_FLAG_ALL, -1); + if (i64 == -1 || !_nm_setting_secret_flags_valid(i64)) { + /* The flags keys is set to an unexpected value. That is a configuration + * error. Note that keys named "*-flags" are reserved for secrets. The user + * must not use this for anything but secret flags. Hence, we cannot fail + * to read the secret, we pretend that the secret flag is set to the default + * NM_SETTING_SECRET_FLAG_NONE. */ + NM_SET_OUT(out_flags, NM_SETTING_SECRET_FLAG_NONE); + return TRUE; + } + + NM_SET_OUT(out_flags, (NMSettingSecretFlags) i64); + return TRUE; +} + +static gboolean +set_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags flags, + GError ** error) +{ + nm_assert(secret_name); + + if (!secret_name[0]) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("secret name cannot be empty")); + return FALSE; + } + + g_hash_table_insert(_ensure_strdict(&NM_SETTING_VPN_GET_PRIVATE(setting)->data, FALSE), + g_strdup_printf("%s-flags", secret_name), + g_strdup_printf("%u", flags)); + _notify(NM_SETTING_VPN(setting), PROP_SECRETS); + return TRUE; +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + /* Assume that VPN connections need secrets since they almost always will */ + return g_ptr_array_sized_new(1); +} + +static NMTernary +compare_property_secrets(NMSettingVpn *a, NMSettingVpn *b, NMSettingCompareFlags flags) +{ + GHashTableIter iter; + const char * key, *val; + int run; + + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_FUZZY)) + return NM_TERNARY_DEFAULT; + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)) + return NM_TERNARY_DEFAULT; + + if (!b) + return TRUE; + + for (run = 0; run < 2; run++) { + NMSettingVpn * current_a = (run == 0) ? a : b; + NMSettingVpn * current_b = (run == 0) ? b : a; + NMSettingVpnPrivate *priv_a = NM_SETTING_VPN_GET_PRIVATE(current_a); + + if (!priv_a->secrets) + continue; + + g_hash_table_iter_init(&iter, priv_a->secrets); + while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &val)) { + if (nm_streq0(val, nm_setting_vpn_get_secret(current_b, key))) + continue; + if (!_nm_setting_should_compare_secret_property(NM_SETTING(current_a), + NM_SETTING(current_b), + key, + flags)) + continue; + + return FALSE; + } + } + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_VPN_SECRETS)) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) + return NM_TERNARY_DEFAULT; + return compare_property_secrets(NM_SETTING_VPN(set_a), NM_SETTING_VPN(set_b), flags); + } + + return NM_SETTING_CLASS(nm_setting_vpn_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +static gboolean +clear_secrets(const NMSettInfoSetting * sett_info, + guint property_idx, + NMSetting * setting, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data) +{ + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(setting); + GParamSpec * prop_spec = sett_info->property_infos[property_idx].param_spec; + GHashTableIter iter; + const char * secret; + gboolean changed = TRUE; + + if (!prop_spec || !NM_FLAGS_HAS(prop_spec->flags, NM_SETTING_PARAM_SECRET)) + return FALSE; + + nm_assert(nm_streq(prop_spec->name, NM_SETTING_VPN_SECRETS)); + + if (!priv->secrets) + return FALSE; + + g_hash_table_iter_init(&iter, priv->secrets); + while (g_hash_table_iter_next(&iter, (gpointer) &secret, NULL)) { + if (func) { + NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags(setting, secret, &flags, NULL)) + nm_assert_not_reached(); + + if (!func(setting, secret, flags, user_data)) + continue; + } else + nm_assert(nm_setting_get_secret_flags(setting, secret, NULL, NULL)); + + g_hash_table_iter_remove(&iter); + changed = TRUE; + } + + if (changed) + _notify(NM_SETTING_VPN(setting), PROP_SECRETS); + + return changed; +} + +static gboolean +vpn_secrets_from_dbus(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + NMSettingVpn * self = NM_SETTING_VPN(setting); + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(self); + gs_unref_hashtable GHashTable *hash_free = NULL; + GVariantIter iter; + const char * key; + const char * val; + + hash_free = g_steal_pointer(&priv->secrets); + + g_variant_iter_init(&iter, value); + while (g_variant_iter_next(&iter, "{&s&s}", &key, &val)) { + if (!key[0]) + continue; + g_hash_table_insert(_ensure_strdict(&priv->secrets, TRUE), g_strdup(key), g_strdup(val)); + } + + _notify(self, PROP_SECRETS); + return TRUE; +} + +static GVariant * +vpn_secrets_to_dbus(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(setting); + GVariantBuilder builder; + gs_free const char **keys = NULL; + guint i, len; + + if (flags != NM_CONNECTION_SERIALIZE_ALL + && !NM_FLAGS_ANY(flags, + NM_CONNECTION_SERIALIZE_WITH_SECRETS + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_SYSTEM_OWNED + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_NOT_SAVED)) + return NULL; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); + + keys = nm_utils_strdict_get_keys(priv->secrets, TRUE, &len); + for (i = 0; i < len; i++) { + const char * key = keys[i]; + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (NM_FLAGS_ANY(flags, + NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_SYSTEM_OWNED + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_NOT_SAVED)) + nm_setting_get_secret_flags(setting, key, &secret_flags, NULL); + + if (!_nm_connection_serialize_secrets(flags, secret_flags)) + continue; + + g_variant_builder_add(&builder, "{ss}", key, g_hash_table_lookup(priv->secrets, key)); + } + + return g_variant_builder_end(&builder); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingVpn * setting = NM_SETTING_VPN(object); + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_SERVICE_TYPE: + g_value_set_string(value, nm_setting_vpn_get_service_type(setting)); + break; + case PROP_USER_NAME: + g_value_set_string(value, nm_setting_vpn_get_user_name(setting)); + break; + case PROP_PERSISTENT: + g_value_set_boolean(value, priv->persistent); + break; + case PROP_DATA: + g_value_take_boxed(value, _nm_utils_copy_strdict(priv->data)); + break; + case PROP_SECRETS: + g_value_take_boxed(value, _nm_utils_copy_strdict(priv->secrets)); + break; + case PROP_TIMEOUT: + g_value_set_uint(value, nm_setting_vpn_get_timeout(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_SERVICE_TYPE: + g_free(priv->service_type); + priv->service_type = g_value_dup_string(value); + break; + case PROP_USER_NAME: + g_free(priv->user_name); + priv->user_name = g_value_dup_string(value); + break; + case PROP_PERSISTENT: + priv->persistent = g_value_get_boolean(value); + break; + case PROP_DATA: + case PROP_SECRETS: + { + gs_unref_hashtable GHashTable *hash_free = NULL; + GHashTable * src_hash = g_value_get_boxed(value); + GHashTable ** p_hash; + const gboolean is_secrets = (prop_id == PROP_SECRETS); + + if (is_secrets) + p_hash = &priv->secrets; + else + p_hash = &priv->data; + + hash_free = g_steal_pointer(p_hash); + + if (src_hash && g_hash_table_size(src_hash) > 0) { + GHashTableIter iter; + const char * key; + const char * val; + + g_hash_table_iter_init(&iter, src_hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + if (!key || !key[0] || !val) { + /* NULL keys/values and empty key are not allowed. Usually, we would reject them in verify(), but + * then our nm_setting_vpn_remove_data_item() also doesn't allow empty keys. So, if we failed + * it in verify(), it would be only fixable by setting PROP_DATA again. Instead, + * silently ignore them. */ + continue; + } + g_hash_table_insert(_ensure_strdict(p_hash, is_secrets), + g_strdup(key), + g_strdup(val)); + } + } + } break; + case PROP_TIMEOUT: + priv->timeout = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_vpn_init(NMSettingVpn *setting) +{} + +/** + * nm_setting_vpn_new: + * + * Creates a new #NMSettingVpn object with default values. + * + * Returns: (transfer full): the new empty #NMSettingVpn object + **/ +NMSetting * +nm_setting_vpn_new(void) +{ + return g_object_new(NM_TYPE_SETTING_VPN, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingVpnPrivate *priv = NM_SETTING_VPN_GET_PRIVATE(object); + + g_free(priv->service_type); + g_free(priv->user_name); + if (priv->data) + g_hash_table_unref(priv->data); + if (priv->secrets) + g_hash_table_unref(priv->secrets); + + G_OBJECT_CLASS(nm_setting_vpn_parent_class)->finalize(object); +} + +static void +nm_setting_vpn_class_init(NMSettingVpnClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingVpnPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->update_one_secret = update_one_secret; + setting_class->for_each_secret = for_each_secret; + setting_class->get_secret_flags = get_secret_flags; + setting_class->set_secret_flags = set_secret_flags; + setting_class->need_secrets = need_secrets; + setting_class->compare_property = compare_property; + setting_class->clear_secrets = clear_secrets; + setting_class->aggregate = aggregate; + + /** + * NMSettingVpn:service-type: + * + * D-Bus service name of the VPN plugin that this setting uses to connect to + * its network. i.e. org.freedesktop.NetworkManager.vpnc for the vpnc + * plugin. + **/ + obj_properties[PROP_SERVICE_TYPE] = + g_param_spec_string(NM_SETTING_VPN_SERVICE_TYPE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVpn:user-name: + * + * If the VPN connection requires a user name for authentication, that name + * should be provided here. If the connection is available to more than one + * user, and the VPN requires each user to supply a different name, then + * leave this property empty. If this property is empty, NetworkManager + * will automatically supply the username of the user which requested the + * VPN connection. + **/ + obj_properties[PROP_USER_NAME] = + g_param_spec_string(NM_SETTING_VPN_USER_NAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVpn:persistent: + * + * If the VPN service supports persistence, and this property is %TRUE, + * the VPN will attempt to stay connected across link changes and outages, + * until explicitly disconnected. + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_VPN_PERSISTENT, + PROP_PERSISTENT, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_vpn_get_persistent); + + /** + * NMSettingVpn:data: (type GHashTable(utf8,utf8)): + * + * Dictionary of key/value pairs of VPN plugin specific data. Both keys and + * values must be strings. + **/ + /* ---keyfile--- + * property: data + * variable: separate variables named after keys of the dictionary + * description: The keys of the data dictionary are used as variable names directly + * under [vpn] section. + * example: remote=ovpn.corp.com cipher=AES-256-CBC username=joe + * ---end--- + */ + obj_properties[PROP_DATA] = g_param_spec_boxed(NM_SETTING_VPN_DATA, + "", + "", + G_TYPE_HASH_TABLE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_DATA], + &nm_sett_info_propert_type_strdict); + + /** + * NMSettingVpn:secrets: (type GHashTable(utf8,utf8)): + * + * Dictionary of key/value pairs of VPN plugin specific secrets like + * passwords or private keys. Both keys and values must be strings. + **/ + /* ---keyfile--- + * property: secrets + * variable: separate variables named after keys of the dictionary + * description: The keys of the secrets dictionary are used as variable names directly + * under [vpn-secrets] section. + * example: password=Popocatepetl + * ---end--- + */ + obj_properties[PROP_SECRETS] = + g_param_spec_boxed(NM_SETTING_VPN_SECRETS, + "", + "", + G_TYPE_HASH_TABLE, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET + | NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_SECRETS], + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("a{ss}"), + .to_dbus_fcn = vpn_secrets_to_dbus, + .from_dbus_fcn = vpn_secrets_from_dbus, )); + + /** + * NMSettingVpn:timeout: + * + * Timeout for the VPN service to establish the connection. Some services + * may take quite a long time to connect. + * Value of 0 means a default timeout, which is 60 seconds (unless overridden + * by vpn.timeout in configuration file). Values greater than zero mean + * timeout in seconds. + * + * Since: 1.2 + **/ + obj_properties[PROP_TIMEOUT] = g_param_spec_uint(NM_SETTING_VPN_TIMEOUT, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_VPN, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-vrf.c b/src/libnm-core-impl/nm-setting-vrf.c new file mode 100644 index 00000000..a5ee7395 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-vrf.c @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-vrf.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-vrf + * @short_description: Describes connection properties for vrf interfaces + * + * The #NMSettingVrf object is a #NMSetting subclass that describes properties + * necessary for connection to vrf devices + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_TABLE, ); + +/** + * NMSettingVrf: + * + * VRF settings + * + * Since: 1.24 + */ +struct _NMSettingVrf { + NMSetting parent; + guint32 table; +}; + +struct _NMSettingVrfClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingVrf, nm_setting_vrf, NM_TYPE_SETTING) + +/*****************************************************************************/ + +/** + * nm_setting_vrf_get_table: + * @setting: the #NMSettingVrf + * + * Returns: the routing table for the VRF + * + * Since: 1.24 + **/ +guint32 +nm_setting_vrf_get_table(NMSettingVrf *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VRF(setting), 0); + + return setting->table; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingVrf *self = NM_SETTING_VRF(setting); + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + if (self->table == 0) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("table cannot be zero")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VRF_SETTING_NAME, NM_SETTING_VRF_TABLE); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingVrf *self = NM_SETTING_VRF(object); + + switch (prop_id) { + case PROP_TABLE: + g_value_set_uint(value, self->table); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingVrf *self = NM_SETTING_VRF(object); + + switch (prop_id) { + case PROP_TABLE: + self->table = g_value_get_uint(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_vrf_init(NMSettingVrf *setting) +{} + +/** + * nm_setting_vrf_new: + * + * Creates a new #NMSettingVrf object with default values. + * + * Returns: (transfer full): the new empty #NMSettingVrf object + * + * Since: 1.24 + **/ +NMSetting * +nm_setting_vrf_new(void) +{ + return g_object_new(NM_TYPE_SETTING_VRF, NULL); +} + +static void +nm_setting_vrf_class_init(NMSettingVrfClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + object_class->get_property = get_property; + object_class->set_property = set_property; + + setting_class->verify = verify; + + /** + * NMSettingVrf:table: + * + * The routing table for this VRF. + * + * Since: 1.24 + **/ + obj_properties[PROP_TABLE] = + g_param_spec_uint(NM_SETTING_VRF_TABLE, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_VRF); +} diff --git a/src/libnm-core-impl/nm-setting-vxlan.c b/src/libnm-core-impl/nm-setting-vxlan.c new file mode 100644 index 00000000..f2aeb66a --- /dev/null +++ b/src/libnm-core-impl/nm-setting-vxlan.c @@ -0,0 +1,843 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-vxlan.h" + +#include + +#include "nm-utils.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-vxlan + * @short_description: Describes connection properties for VXLAN interfaces + * + * The #NMSettingVxlan object is a #NMSetting subclass that describes properties + * necessary for connection to VXLAN interfaces. + **/ + +#define DST_PORT_DEFAULT 8472 + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT, + PROP_ID, + PROP_LOCAL, + PROP_REMOTE, + PROP_SOURCE_PORT_MIN, + PROP_SOURCE_PORT_MAX, + PROP_DESTINATION_PORT, + PROP_TOS, + PROP_TTL, + PROP_AGEING, + PROP_LIMIT, + PROP_LEARNING, + PROP_PROXY, + PROP_RSC, + PROP_L2_MISS, + PROP_L3_MISS, ); + +typedef struct { + char *parent; + char *local; + char *remote; + guint id; + guint source_port_min; + guint source_port_max; + guint destination_port; + guint tos; + guint ttl; + guint ageing; + guint limit; + bool learning : 1; + bool proxy : 1; + bool rsc : 1; + bool l2_miss : 1; + bool l3_miss : 1; +} NMSettingVxlanPrivate; + +/** + * NMSettingVxlan: + * + * VXLAN Settings + */ +struct _NMSettingVxlan { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingVxlanClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingVxlan, nm_setting_vxlan, NM_TYPE_SETTING) + +#define NM_SETTING_VXLAN_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_VXLAN, NMSettingVxlanPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_vxlan_get_parent: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:parent property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_vxlan_get_parent(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), NULL); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->parent; +} + +/** + * nm_setting_vxlan_get_id: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:id property of the setting + * + * Since: 1.2 + **/ +guint +nm_setting_vxlan_get_id(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), 0); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->id; +} + +/** + * nm_setting_vxlan_get_local: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:local property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_vxlan_get_local(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), NULL); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->local; +} + +/** + * nm_setting_vxlan_get_remote: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:remote property of the setting + * + * Since: 1.2 + **/ +const char * +nm_setting_vxlan_get_remote(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), NULL); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->remote; +} + +/** + * nm_setting_vxlan_get_source_port_min: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:source-port-min property of the setting + * + * Since: 1.2 + **/ +guint +nm_setting_vxlan_get_source_port_min(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), 0); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->source_port_min; +} + +/** + * nm_setting_vxlan_get_source_port_max: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:source-port-max property of the setting + * + * Since: 1.2 + **/ +guint +nm_setting_vxlan_get_source_port_max(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), 0); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->source_port_max; +} + +/** + * nm_setting_vxlan_get_destination_port: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:destination-port property of the setting + * + * Since: 1.2 + **/ +guint +nm_setting_vxlan_get_destination_port(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), DST_PORT_DEFAULT); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->destination_port; +} + +/** + * nm_setting_vxlan_get_proxy: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:proxy property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_vxlan_get_proxy(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), FALSE); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->proxy; +} + +/** + * nm_setting_vxlan_get_ageing: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:ageing property of the setting + * + * Since: 1.2 + **/ +guint +nm_setting_vxlan_get_ageing(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), 0); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->ageing; +} + +/** + * nm_setting_vxlan_get_limit: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:limit property of the setting + * + * Since: 1.2 + **/ +guint +nm_setting_vxlan_get_limit(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), 0); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->limit; +} + +/** + * nm_setting_vxlan_get_tos: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:tos property of the setting + * + * Since: 1.2 + **/ +guint +nm_setting_vxlan_get_tos(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), 0); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->tos; +} + +/** + * nm_setting_vxlan_get_ttl: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:ttl property of the setting + * + * Since: 1.2 + **/ +guint +nm_setting_vxlan_get_ttl(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), 0); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->ttl; +} + +/** + * nm_setting_vxlan_get_learning: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:learning property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_vxlan_get_learning(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), FALSE); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->learning; +} + +/** + * nm_setting_vxlan_get_rsc: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:rsc property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_vxlan_get_rsc(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), FALSE); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->rsc; +} + +/** + * nm_setting_vxlan_get_l2_miss: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:l2_miss property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_vxlan_get_l2_miss(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), FALSE); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->l2_miss; +} + +/** + * nm_setting_vxlan_get_l3_miss: + * @setting: the #NMSettingVxlan + * + * Returns: the #NMSettingVxlan:l3_miss property of the setting + * + * Since: 1.2 + **/ +gboolean +nm_setting_vxlan_get_l3_miss(NMSettingVxlan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_VXLAN(setting), FALSE); + return NM_SETTING_VXLAN_GET_PRIVATE(setting)->l3_miss; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingVxlanPrivate *priv = NM_SETTING_VXLAN_GET_PRIVATE(setting); + int addr_family = AF_UNSPEC; + gboolean remote_is_valid = TRUE; + gboolean local_is_valid = TRUE; + + if (priv->remote && !nm_utils_parse_inaddr_bin(addr_family, priv->remote, &addr_family, NULL)) + remote_is_valid = FALSE; + if (priv->local && !nm_utils_parse_inaddr_bin(addr_family, priv->local, &addr_family, NULL)) + local_is_valid = FALSE; + + if (!remote_is_valid) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid IP%s address"), + priv->remote, + addr_family == AF_UNSPEC ? "" : (addr_family == AF_INET ? "4" : "6")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VXLAN_SETTING_NAME, NM_SETTING_VXLAN_REMOTE); + return FALSE; + } + + if (!local_is_valid) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid IP%s address"), + priv->local, + addr_family == AF_UNSPEC ? "" : (addr_family == AF_INET ? "4" : "6")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VXLAN_SETTING_NAME, NM_SETTING_VXLAN_LOCAL); + return FALSE; + } + + if (priv->parent && !nm_utils_ifname_valid_kernel(priv->parent, NULL) + && !nm_utils_is_uuid(priv->parent)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is neither an UUID nor an interface name"), + priv->parent); + g_prefix_error(error, "%s.%s: ", NM_SETTING_VXLAN_SETTING_NAME, NM_SETTING_VXLAN_PARENT); + return FALSE; + } + + if ((priv->source_port_min || priv->source_port_max) + && (priv->source_port_min > priv->source_port_max)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("%d is greater than local port max %d"), + priv->source_port_min, + priv->source_port_max); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_VXLAN_SETTING_NAME, + NM_SETTING_VXLAN_SOURCE_PORT_MIN); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +_addrstr_set(char **dst, const char *src) +{ + gs_free char *old = NULL; + + old = *dst; + if (!src) + *dst = NULL; + else if (!nm_utils_parse_inaddr(AF_UNSPEC, src, dst)) + *dst = g_strdup(src); +} +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingVxlan * setting = NM_SETTING_VXLAN(object); + NMSettingVxlanPrivate *priv = NM_SETTING_VXLAN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_value_set_string(value, priv->parent); + break; + case PROP_ID: + g_value_set_uint(value, priv->id); + break; + case PROP_LOCAL: + g_value_set_string(value, priv->local); + break; + case PROP_REMOTE: + g_value_set_string(value, priv->remote); + break; + case PROP_SOURCE_PORT_MIN: + g_value_set_uint(value, priv->source_port_min); + break; + case PROP_SOURCE_PORT_MAX: + g_value_set_uint(value, priv->source_port_max); + break; + case PROP_DESTINATION_PORT: + g_value_set_uint(value, priv->destination_port); + break; + case PROP_TOS: + g_value_set_uint(value, priv->tos); + break; + case PROP_AGEING: + g_value_set_uint(value, priv->ageing); + break; + case PROP_LIMIT: + g_value_set_uint(value, priv->limit); + break; + case PROP_PROXY: + g_value_set_boolean(value, priv->proxy); + break; + case PROP_TTL: + g_value_set_uint(value, priv->ttl); + break; + case PROP_LEARNING: + g_value_set_boolean(value, priv->learning); + break; + case PROP_RSC: + g_value_set_boolean(value, priv->rsc); + break; + case PROP_L2_MISS: + g_value_set_boolean(value, priv->l2_miss); + break; + case PROP_L3_MISS: + g_value_set_boolean(value, priv->l3_miss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingVxlan * setting = NM_SETTING_VXLAN(object); + NMSettingVxlanPrivate *priv = NM_SETTING_VXLAN_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_PARENT: + g_free(priv->parent); + priv->parent = g_value_dup_string(value); + break; + case PROP_ID: + priv->id = g_value_get_uint(value); + break; + case PROP_LOCAL: + _addrstr_set(&priv->local, g_value_get_string(value)); + break; + case PROP_REMOTE: + _addrstr_set(&priv->remote, g_value_get_string(value)); + break; + case PROP_SOURCE_PORT_MIN: + priv->source_port_min = g_value_get_uint(value); + break; + case PROP_SOURCE_PORT_MAX: + priv->source_port_max = g_value_get_uint(value); + break; + case PROP_DESTINATION_PORT: + priv->destination_port = g_value_get_uint(value); + break; + case PROP_TOS: + priv->tos = g_value_get_uint(value); + break; + case PROP_AGEING: + priv->ageing = g_value_get_uint(value); + break; + case PROP_LIMIT: + priv->limit = g_value_get_uint(value); + break; + case PROP_PROXY: + priv->proxy = g_value_get_boolean(value); + break; + case PROP_TTL: + priv->ttl = g_value_get_uint(value); + break; + case PROP_LEARNING: + priv->learning = g_value_get_boolean(value); + break; + case PROP_RSC: + priv->rsc = g_value_get_boolean(value); + break; + case PROP_L2_MISS: + priv->l2_miss = g_value_get_boolean(value); + break; + case PROP_L3_MISS: + priv->l3_miss = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_vxlan_init(NMSettingVxlan *self) +{ + NMSettingVxlanPrivate *priv = NM_SETTING_VXLAN_GET_PRIVATE(self); + + priv->destination_port = DST_PORT_DEFAULT; + priv->ageing = 300; + priv->learning = TRUE; +} + +/** + * nm_setting_vxlan_new: + * + * Creates a new #NMSettingVxlan object with default values. + * + * Returns: (transfer full): the new empty #NMSettingVxlan object + * + * Since: 1.2 + **/ +NMSetting * +nm_setting_vxlan_new(void) +{ + return g_object_new(NM_TYPE_SETTING_VXLAN, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingVxlan * setting = NM_SETTING_VXLAN(object); + NMSettingVxlanPrivate *priv = NM_SETTING_VXLAN_GET_PRIVATE(setting); + + g_free(priv->parent); + g_free(priv->local); + g_free(priv->remote); + + G_OBJECT_CLASS(nm_setting_vxlan_parent_class)->finalize(object); +} + +static void +nm_setting_vxlan_class_init(NMSettingVxlanClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingVxlanPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingVxlan:parent: + * + * If given, specifies the parent interface name or parent connection UUID. + * + * Since: 1.2 + **/ + obj_properties[PROP_PARENT] = g_param_spec_string( + NM_SETTING_VXLAN_PARENT, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + /** + * NMSettingVxlan:id: + * + * Specifies the VXLAN Network Identifier (or VXLAN Segment Identifier) to + * use. + * + * Since: 1.2 + **/ + obj_properties[PROP_ID] = + g_param_spec_uint(NM_SETTING_VXLAN_ID, + "", + "", + 0, + (1 << 24) - 1, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:local: + * + * If given, specifies the source IP address to use in outgoing packets. + * + * Since: 1.2 + **/ + obj_properties[PROP_LOCAL] = g_param_spec_string(NM_SETTING_VXLAN_LOCAL, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:remote: + * + * Specifies the unicast destination IP address to use in outgoing packets + * when the destination link layer address is not known in the VXLAN device + * forwarding database, or the multicast IP address to join. + * + * Since: 1.2 + **/ + obj_properties[PROP_REMOTE] = g_param_spec_string( + NM_SETTING_VXLAN_REMOTE, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:source-port-min: + * + * Specifies the minimum UDP source port to communicate to the remote VXLAN + * tunnel endpoint. + * + * Since: 1.2 + **/ + obj_properties[PROP_SOURCE_PORT_MIN] = + g_param_spec_uint(NM_SETTING_VXLAN_SOURCE_PORT_MIN, + "", + "", + 0, + G_MAXUINT16, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:source-port-max: + * + * Specifies the maximum UDP source port to communicate to the remote VXLAN + * tunnel endpoint. + * + * Since: 1.2 + **/ + obj_properties[PROP_SOURCE_PORT_MAX] = + g_param_spec_uint(NM_SETTING_VXLAN_SOURCE_PORT_MAX, + "", + "", + 0, + G_MAXUINT16, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:destination-port: + * + * Specifies the UDP destination port to communicate to the remote VXLAN + * tunnel endpoint. + * + * Since: 1.2 + **/ + obj_properties[PROP_DESTINATION_PORT] = + g_param_spec_uint(NM_SETTING_VXLAN_DESTINATION_PORT, + "", + "", + 0, + G_MAXUINT16, + DST_PORT_DEFAULT, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:ageing: + * + * Specifies the lifetime in seconds of FDB entries learnt by the kernel. + * + * Since: 1.2 + **/ + obj_properties[PROP_AGEING] = + g_param_spec_uint(NM_SETTING_VXLAN_AGEING, + "", + "", + 0, + G_MAXUINT32, + 300, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:limit: + * + * Specifies the maximum number of FDB entries. A value of zero means that + * the kernel will store unlimited entries. + * + * Since: 1.2 + **/ + obj_properties[PROP_LIMIT] = + g_param_spec_uint(NM_SETTING_VXLAN_LIMIT, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:tos: + * + * Specifies the TOS value to use in outgoing packets. + * + * Since: 1.2 + **/ + obj_properties[PROP_TOS] = + g_param_spec_uint(NM_SETTING_VXLAN_TOS, + "", + "", + 0, + 255, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:ttl: + * + * Specifies the time-to-live value to use in outgoing packets. + * + * Since: 1.2 + **/ + obj_properties[PROP_TTL] = + g_param_spec_uint(NM_SETTING_VXLAN_TTL, + "", + "", + 0, + 255, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingVxlan:proxy: + * + * Specifies whether ARP proxy is turned on. + * + * Since: 1.2 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_VXLAN_PROXY, + PROP_PROXY, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_vxlan_get_proxy); + + /** + * NMSettingVxlan:learning: + * + * Specifies whether unknown source link layer addresses and IP addresses + * are entered into the VXLAN device forwarding database. + * + * Since: 1.2 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_VXLAN_LEARNING, + PROP_LEARNING, + TRUE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_vxlan_get_learning); + + /** + * NMSettingVxlan:rsc: + * + * Specifies whether route short circuit is turned on. + * + * Since: 1.2 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_VXLAN_RSC, + PROP_RSC, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_vxlan_get_rsc); + + /** + * NMSettingVxlan:l2-miss: + * + * Specifies whether netlink LL ADDR miss notifications are generated. + * + * Since: 1.2 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_VXLAN_L2_MISS, + PROP_L2_MISS, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_vxlan_get_l2_miss); + + /** + * NMSettingVxlan:l3-miss: + * + * Specifies whether netlink IP ADDR miss notifications are generated. + * + * Since: 1.2 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_VXLAN_L3_MISS, + PROP_L3_MISS, + FALSE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_vxlan_get_l3_miss); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_VXLAN, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-wifi-p2p.c b/src/libnm-core-impl/nm-setting-wifi-p2p.c new file mode 100644 index 00000000..8f677479 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-wifi-p2p.c @@ -0,0 +1,304 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-wifi-p2p.h" + +#include + +#include "nm-utils.h" +#include "libnm-core-aux-intern/nm-common-macros.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-wifi-p2p + * @short_description: Describes connection properties for 802.11 Wi-Fi P2P networks + * + * The #NMSettingWifiP2P object is a #NMSetting subclass that describes properties + * necessary for connection to 802.11 Wi-Fi P2P networks (aka Wi-Fi Direct). + **/ + +/** + * NMSettingWifiP2P: + * + * Wi-Fi P2P Settings + * + * Since: 1.16 + */ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PEER, PROP_WPS_METHOD, PROP_WFD_IES, ); + +typedef struct { + char * peer_mac_address; + GBytes *wfd_ies; + + NMSettingWirelessSecurityWpsMethod wps_method; +} NMSettingWifiP2PPrivate; + +struct _NMSettingWifiP2P { + NMSetting parent; + NMSettingWifiP2PPrivate _priv; +}; + +struct _NMSettingWifiP2PClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingWifiP2P, nm_setting_wifi_p2p, NM_TYPE_SETTING) + +#define NM_SETTING_WIFI_P2P_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSettingWifiP2P, NM_IS_SETTING_WIFI_P2P, NMSetting) + +/*****************************************************************************/ + +/** + * nm_setting_wifi_p2p_get_peer: + * @setting: the #NMSettingWifiP2P + * + * Returns: the #NMSettingWifiP2P:peer property of the setting + * + * Since: 1.16 + **/ +const char * +nm_setting_wifi_p2p_get_peer(NMSettingWifiP2P *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIFI_P2P(setting), NULL); + + return NM_SETTING_WIFI_P2P_GET_PRIVATE(setting)->peer_mac_address; +} + +/** + * nm_setting_wifi_p2p_get_wps_method: + * @setting: the #NMSettingWifiP2P + * + * Returns: the #NMSettingWifiP2P:wps-method property of the setting + * + * Since: 1.16 + **/ +NMSettingWirelessSecurityWpsMethod +nm_setting_wifi_p2p_get_wps_method(NMSettingWifiP2P *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIFI_P2P(setting), + NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT); + + return NM_SETTING_WIFI_P2P_GET_PRIVATE(setting)->wps_method; +} + +/** + * nm_setting_wifi_p2p_get_wfd_ies: + * @setting: the #NMSettingWiFiP2P + * + * Returns: (transfer none): the #NMSettingWiFiP2P:wfd-ies property of the setting + * + * Since: 1.16 + **/ +GBytes * +nm_setting_wifi_p2p_get_wfd_ies(NMSettingWifiP2P *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIFI_P2P(setting), NULL); + + return NM_SETTING_WIFI_P2P_GET_PRIVATE(setting)->wfd_ies; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWifiP2PPrivate *priv = NM_SETTING_WIFI_P2P_GET_PRIVATE(setting); + + if (!priv->peer_mac_address) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIFI_P2P_SETTING_NAME, + NM_SETTING_WIFI_P2P_PEER); + return FALSE; + } + + if (!nm_utils_hwaddr_valid(priv->peer_mac_address, ETH_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIFI_P2P_SETTING_NAME, + NM_SETTING_WIFI_P2P_PEER); + return FALSE; + } + + if (!_nm_utils_wps_method_validate(priv->wps_method, + NM_SETTING_WIFI_P2P_SETTING_NAME, + NM_SETTING_WIFI_P2P_WPS_METHOD, + TRUE, + error)) + return FALSE; + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingWifiP2P *setting = NM_SETTING_WIFI_P2P(object); + + switch (prop_id) { + case PROP_PEER: + g_value_set_string(value, nm_setting_wifi_p2p_get_peer(setting)); + break; + case PROP_WPS_METHOD: + g_value_set_uint(value, nm_setting_wifi_p2p_get_wps_method(setting)); + break; + case PROP_WFD_IES: + g_value_set_boxed(value, nm_setting_wifi_p2p_get_wfd_ies(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingWifiP2PPrivate *priv = NM_SETTING_WIFI_P2P_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_PEER: + g_free(priv->peer_mac_address); + priv->peer_mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN); + break; + case PROP_WPS_METHOD: + priv->wps_method = g_value_get_uint(value); + break; + case PROP_WFD_IES: + nm_clear_pointer(&priv->wfd_ies, g_bytes_unref); + priv->wfd_ies = g_value_dup_boxed(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_wifi_p2p_init(NMSettingWifiP2P *setting) +{} + +/** + * nm_setting_wifi_p2p_new: + * + * Creates a new #NMSettingWifiP2P object with default values. + * + * Returns: (transfer full): the new empty #NMSettingWifiP2P object + * + * Since: 1.16 + **/ +NMSetting * +nm_setting_wifi_p2p_new(void) +{ + return g_object_new(NM_TYPE_SETTING_WIFI_P2P, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingWifiP2PPrivate *priv = NM_SETTING_WIFI_P2P_GET_PRIVATE(object); + + g_free(priv->peer_mac_address); + g_bytes_unref(priv->wfd_ies); + + G_OBJECT_CLASS(nm_setting_wifi_p2p_parent_class)->finalize(object); +} + +static void +nm_setting_wifi_p2p_class_init(NMSettingWifiP2PClass *setting_wifi_p2p_class) +{ + GObjectClass * object_class = G_OBJECT_CLASS(setting_wifi_p2p_class); + NMSettingClass *setting_class = NM_SETTING_CLASS(setting_wifi_p2p_class); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingWifiP2P:peer: + * + * The P2P device that should be connected to. Currently, this is the only + * way to create or join a group. + * + * Since: 1.16 + */ + /* ---keyfile--- + * property: peer + * format: usual hex-digits-and-colons notation + * description: MAC address in traditional hex-digits-and-colons notation + * (e.g. 00:22:68:12:79:A2), or semicolon separated list of 6 bytes (obsolete) + * (e.g. 0;34;104;18;121;162). + * ---end--- + */ + obj_properties[PROP_PEER] = g_param_spec_string(NM_SETTING_WIFI_P2P_PEER, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWifiP2P:wps-method: + * + * Flags indicating which mode of WPS is to be used. + * + * There's little point in changing the default setting as NetworkManager will + * automatically determine the best method to use. + * + * Since: 1.16 + */ + obj_properties[PROP_WPS_METHOD] = g_param_spec_uint( + NM_SETTING_WIFI_P2P_WPS_METHOD, + "", + "", + 0, + G_MAXUINT32, + NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWifiP2P:wfd-ies: + * + * The Wi-Fi Display (WFD) Information Elements (IEs) to set. + * + * Wi-Fi Display requires a protocol specific information element to be + * set in certain Wi-Fi frames. These can be specified here for the + * purpose of establishing a connection. + * This setting is only useful when implementing a Wi-Fi Display client. + * + * Since: 1.16 + */ + obj_properties[PROP_WFD_IES] = g_param_spec_boxed( + NM_SETTING_WIFI_P2P_WFD_IES, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_WIFI_P2P); +} diff --git a/src/libnm-core-impl/nm-setting-wimax.c b/src/libnm-core-impl/nm-setting-wimax.c new file mode 100644 index 00000000..6d9f3284 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-wimax.c @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011 - 2013 Red Hat, Inc. + * Copyright (C) 2009 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-wimax.h" + +#include + +#include "nm-setting-private.h" +#include "nm-utils.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-wimax + * @short_description: Describes 802.16e Mobile WiMAX connection properties + * + * The #NMSettingWimax object is a #NMSetting subclass that describes properties + * necessary for connection to 802.16e Mobile WiMAX networks. + * + * NetworkManager no longer supports WiMAX; while this API remains available for + * backward-compatibility reasons, it serves no real purpose, since WiMAX + * connections cannot be activated. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_NETWORK_NAME, PROP_MAC_ADDRESS, ); + +typedef struct { + char *network_name; + char *mac_address; +} NMSettingWimaxPrivate; + +/** + * NMSettingWimax: + * + * WiMax Settings + */ +struct _NMSettingWimax { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingWimaxClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingWimax, nm_setting_wimax, NM_TYPE_SETTING) + +#define NM_SETTING_WIMAX_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_WIMAX, NMSettingWimaxPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_wimax_get_network_name: + * @setting: the #NMSettingWimax + * + * Returns the WiMAX NSP name (ex "Sprint" or "CLEAR") which identifies the + * specific WiMAX network this setting describes a connection to. + * + * Returns: the WiMAX NSP name + * + * Deprecated: 1.2: WiMAX is no longer supported. + **/ +const char * +nm_setting_wimax_get_network_name(NMSettingWimax *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIMAX(setting), NULL); + + return NM_SETTING_WIMAX_GET_PRIVATE(setting)->network_name; +} + +/** + * nm_setting_wimax_get_mac_address: + * @setting: the #NMSettingWimax + * + * Returns the MAC address of a WiMAX device which this connection is locked + * to. + * + * Returns: the MAC address + * + * Deprecated: 1.2: WiMAX is no longer supported. + **/ +const char * +nm_setting_wimax_get_mac_address(NMSettingWimax *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIMAX(setting), NULL); + + return NM_SETTING_WIMAX_GET_PRIVATE(setting)->mac_address; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWimaxPrivate *priv = NM_SETTING_WIMAX_GET_PRIVATE(setting); + + if (!priv->network_name) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIMAX_SETTING_NAME, + NM_SETTING_WIMAX_NETWORK_NAME); + return FALSE; + } + + if (!strlen(priv->network_name)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIMAX_SETTING_NAME, + NM_SETTING_WIMAX_NETWORK_NAME); + return FALSE; + } + + if (priv->mac_address && !nm_utils_hwaddr_valid(priv->mac_address, ETH_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIMAX_SETTING_NAME, + NM_SETTING_WIMAX_MAC_ADDRESS); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingWimax *setting = NM_SETTING_WIMAX(object); + + switch (prop_id) { + case PROP_NETWORK_NAME: + g_value_set_string(value, nm_setting_wimax_get_network_name(setting)); + break; + case PROP_MAC_ADDRESS: + g_value_set_string(value, nm_setting_wimax_get_mac_address(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingWimaxPrivate *priv = NM_SETTING_WIMAX_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_NETWORK_NAME: + g_free(priv->network_name); + priv->network_name = g_value_dup_string(value); + break; + case PROP_MAC_ADDRESS: + g_free(priv->mac_address); + priv->mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_wimax_init(NMSettingWimax *setting) +{} + +/** + * nm_setting_wimax_new: + * + * Creates a new #NMSettingWimax object with default values. + * + * Returns: the new empty #NMSettingWimax object + * + * Deprecated: 1.2: WiMAX is no longer supported. + **/ +NMSetting * +nm_setting_wimax_new(void) +{ + return g_object_new(NM_TYPE_SETTING_WIMAX, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingWimaxPrivate *priv = NM_SETTING_WIMAX_GET_PRIVATE(object); + + g_free(priv->network_name); + g_free(priv->mac_address); + + G_OBJECT_CLASS(nm_setting_wimax_parent_class)->finalize(object); +} + +static void +nm_setting_wimax_class_init(NMSettingWimaxClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingWimaxPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingWimax:network-name: + * + * Network Service Provider (NSP) name of the WiMAX network this connection + * should use. + * + * Deprecated: 1.2: WiMAX is no longer supported. + **/ + obj_properties[PROP_NETWORK_NAME] = + g_param_spec_string(NM_SETTING_WIMAX_NETWORK_NAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWimax:mac-address: + * + * If specified, this connection will only apply to the WiMAX device whose + * MAC address matches. This property does not change the MAC address of the + * device (known as MAC spoofing). + * + * Deprecated: 1.2: WiMAX is no longer supported. + **/ + obj_properties[PROP_MAC_ADDRESS] = + g_param_spec_string(NM_SETTING_WIMAX_MAC_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_MAC_ADDRESS], + &nm_sett_info_propert_type_mac_address); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_WIMAX, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-wired.c b/src/libnm-core-impl/nm-setting-wired.c new file mode 100644 index 00000000..4acc5641 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-wired.c @@ -0,0 +1,1748 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2014 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-wired.h" + +#include + +#include "nm-utils.h" +#include "libnm-core-aux-intern/nm-common-macros.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-wired + * @short_description: Describes connection properties for Ethernet-based networks + * + * The #NMSettingWired object is a #NMSetting subclass that describes properties + * necessary for connection to Ethernet networks. + **/ + +/*****************************************************************************/ + +G_STATIC_ASSERT(NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS + == (NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT | NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE)); + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingWired, + PROP_PORT, + PROP_SPEED, + PROP_DUPLEX, + PROP_AUTO_NEGOTIATE, + PROP_MAC_ADDRESS, + PROP_CLONED_MAC_ADDRESS, + PROP_GENERATE_MAC_ADDRESS_MASK, + PROP_MAC_ADDRESS_BLACKLIST, + PROP_MTU, + PROP_S390_SUBCHANNELS, + PROP_S390_NETTYPE, + PROP_S390_OPTIONS, + PROP_WAKE_ON_LAN, + PROP_WAKE_ON_LAN_PASSWORD, + PROP_ACCEPT_ALL_MAC_ADDRESSES, ); + +typedef struct { + struct { + NMUtilsNamedValue *arr; + guint len; + guint n_alloc; + } s390_options; + GArray * mac_address_blacklist; + char ** s390_subchannels; + char * port; + char * duplex; + char * device_mac_address; + char * cloned_mac_address; + char * generate_mac_address_mask; + char * s390_nettype; + char * wol_password; + NMSettingWiredWakeOnLan wol; + NMTernary accept_all_mac_addresses; + guint32 speed; + guint32 mtu; + bool auto_negotiate : 1; +} NMSettingWiredPrivate; + +/** + * NMSettingWired: + * + * Wired Ethernet Settings + */ +struct _NMSettingWired { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingWiredClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingWired, nm_setting_wired, NM_TYPE_SETTING) + +#define NM_SETTING_WIRED_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_WIRED, NMSettingWiredPrivate)) + +/*****************************************************************************/ + +static const char *const valid_s390_opts[] = { + "bridge_role", + "broadcast_mode", + "buffer_count", + "canonical_macaddr", + "checksumming", + "ctcprot", + "fake_broadcast", + "inter", + "inter_jumbo", + "ipato_add4", + "ipato_add6", + "ipato_enable", + "ipato_invert4", + "ipato_invert6", + "isolation", + "lancmd_timeout", + "large_send", + "layer2", + "portname", + "portno", + "priority_queueing", + "protocol", + "route4", + "route6", + "rxip_add4", + "rxip_add6", + "sniffer", + "total", + "vipa_add4", + "vipa_add6", + NULL, +}; + +gboolean +_nm_setting_wired_is_valid_s390_option(const char *option) +{ + if (NM_MORE_ASSERT_ONCE(10)) { + gsize i; + + nm_assert(NM_PTRARRAY_LEN(valid_s390_opts) + 1u == G_N_ELEMENTS(valid_s390_opts)); + + for (i = 0; i < G_N_ELEMENTS(valid_s390_opts); i++) { + if (i == G_N_ELEMENTS(valid_s390_opts) - 1u) + nm_assert(!valid_s390_opts[i]); + else { + nm_assert(valid_s390_opts[i]); + nm_assert(valid_s390_opts[i][0] != '\0'); + if (i > 0) + nm_assert(strcmp(valid_s390_opts[i - 1], valid_s390_opts[i]) < 0); + } + } + } + + return option + && (nm_utils_strv_find_binary_search(valid_s390_opts, + G_N_ELEMENTS(valid_s390_opts) - 1, + option) + >= 0); +} + +gboolean +_nm_setting_wired_is_valid_s390_option_value(const char *name, const char *option) +{ + nm_assert(name); + + if (!option) + return FALSE; + + /* For historic reasons, the s390-options values were not validated beyond + * simple length check (below). + * + * Here, for certain (recently added) options we add strict validation. + * As this is only done for a few hand picked options, do it right here. + * + * Maybe we should find a backward compatible way to validate all options. + * In that case, the validation should become more elaborate, like we do + * for bond options. */ + + if (nm_streq(name, "bridge_role")) { + return NM_IN_STRSET(option, "primary", "secondary", "none"); + } + + return option[0] != '\0' && strlen(option) <= NM_SETTING_WIRED_S390_OPTION_MAX_LEN; +} + +/** + * nm_setting_wired_get_port: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:port property of the setting + **/ +const char * +nm_setting_wired_get_port(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->port; +} + +/** + * nm_setting_wired_get_speed: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:speed property of the setting + **/ +guint32 +nm_setting_wired_get_speed(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), 0); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->speed; +} + +/** + * nm_setting_wired_get_duplex: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:duplex property of the setting + **/ +const char * +nm_setting_wired_get_duplex(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->duplex; +} + +/** + * nm_setting_wired_get_auto_negotiate: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:auto-negotiate property of the setting + **/ +gboolean +nm_setting_wired_get_auto_negotiate(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), FALSE); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->auto_negotiate; +} + +/** + * nm_setting_wired_get_mac_address: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:mac-address property of the setting + **/ +const char * +nm_setting_wired_get_mac_address(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->device_mac_address; +} + +/** + * nm_setting_wired_get_cloned_mac_address: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:cloned-mac-address property of the setting + **/ +const char * +nm_setting_wired_get_cloned_mac_address(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->cloned_mac_address; +} + +/** + * nm_setting_wired_get_generate_mac_address_mask: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:generate-mac-address-mask property of the setting + * + * Since: 1.4 + **/ +const char * +nm_setting_wired_get_generate_mac_address_mask(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->generate_mac_address_mask; +} + +/** + * nm_setting_wired_get_mac_address_blacklist: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:mac-address-blacklist property of the setting + **/ +const char *const * +nm_setting_wired_get_mac_address_blacklist(NMSettingWired *setting) +{ + NMSettingWiredPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + return (const char *const *) priv->mac_address_blacklist->data; +} + +/** + * nm_setting_wired_get_num_mac_blacklist_items: + * @setting: the #NMSettingWired + * + * Returns: the number of blacklisted MAC addresses + **/ +guint32 +nm_setting_wired_get_num_mac_blacklist_items(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), 0); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->mac_address_blacklist->len; +} + +/** + * nm_setting_wired_get_mac_blacklist_item: + * @setting: the #NMSettingWired + * @idx: the zero-based index of the MAC address entry + * + * Returns: the blacklisted MAC address string (hex-digits-and-colons notation) + * at index @idx + **/ +const char * +nm_setting_wired_get_mac_blacklist_item(NMSettingWired *setting, guint32 idx) +{ + NMSettingWiredPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + g_return_val_if_fail(idx <= priv->mac_address_blacklist->len, NULL); + + return g_array_index(priv->mac_address_blacklist, const char *, idx); +} + +/** + * nm_setting_wired_add_mac_blacklist_item: + * @setting: the #NMSettingWired + * @mac: the MAC address string (hex-digits-and-colons notation) to blacklist + * + * Adds a new MAC address to the #NMSettingWired:mac-address-blacklist property. + * + * Returns: %TRUE if the MAC address was added; %FALSE if the MAC address + * is invalid or was already present + **/ +gboolean +nm_setting_wired_add_mac_blacklist_item(NMSettingWired *setting, const char *mac) +{ + NMSettingWiredPrivate *priv; + const char * candidate; + int i; + + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), FALSE); + g_return_val_if_fail(mac != NULL, FALSE); + + if (!nm_utils_hwaddr_valid(mac, ETH_ALEN)) + return FALSE; + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + for (i = 0; i < priv->mac_address_blacklist->len; i++) { + candidate = g_array_index(priv->mac_address_blacklist, char *, i); + if (nm_utils_hwaddr_matches(mac, -1, candidate, -1)) + return FALSE; + } + + mac = nm_utils_hwaddr_canonical(mac, ETH_ALEN); + g_array_append_val(priv->mac_address_blacklist, mac); + _notify(setting, PROP_MAC_ADDRESS_BLACKLIST); + return TRUE; +} + +/** + * nm_setting_wired_remove_mac_blacklist_item: + * @setting: the #NMSettingWired + * @idx: index number of the MAC address + * + * Removes the MAC address at index @idx from the blacklist. + **/ +void +nm_setting_wired_remove_mac_blacklist_item(NMSettingWired *setting, guint32 idx) +{ + NMSettingWiredPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_WIRED(setting)); + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + g_return_if_fail(idx < priv->mac_address_blacklist->len); + + g_array_remove_index(priv->mac_address_blacklist, idx); + _notify(setting, PROP_MAC_ADDRESS_BLACKLIST); +} + +/** + * nm_setting_wired_remove_mac_blacklist_item_by_value: + * @setting: the #NMSettingWired + * @mac: the MAC address string (hex-digits-and-colons notation) to remove from + * the blacklist + * + * Removes the MAC address @mac from the blacklist. + * + * Returns: %TRUE if the MAC address was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_wired_remove_mac_blacklist_item_by_value(NMSettingWired *setting, const char *mac) +{ + NMSettingWiredPrivate *priv; + const char * candidate; + int i; + + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), FALSE); + g_return_val_if_fail(mac != NULL, FALSE); + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + for (i = 0; i < priv->mac_address_blacklist->len; i++) { + candidate = g_array_index(priv->mac_address_blacklist, char *, i); + if (!nm_utils_hwaddr_matches(mac, -1, candidate, -1)) { + g_array_remove_index(priv->mac_address_blacklist, i); + _notify(setting, PROP_MAC_ADDRESS_BLACKLIST); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_wired_clear_mac_blacklist_items: + * @setting: the #NMSettingWired + * + * Removes all blacklisted MAC addresses. + **/ +void +nm_setting_wired_clear_mac_blacklist_items(NMSettingWired *setting) +{ + g_return_if_fail(NM_IS_SETTING_WIRED(setting)); + + g_array_set_size(NM_SETTING_WIRED_GET_PRIVATE(setting)->mac_address_blacklist, 0); + _notify(setting, PROP_MAC_ADDRESS_BLACKLIST); +} + +/** + * nm_setting_wired_get_mtu: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:mtu property of the setting + **/ +guint32 +nm_setting_wired_get_mtu(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), 0); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->mtu; +} + +/** + * nm_setting_wired_get_s390_subchannels: + * @setting: the #NMSettingWired + * + * Return the list of s390 subchannels that identify the device that this + * connection is applicable to. The connection should only be used in + * conjunction with that device. + * + * Returns: (transfer none) (element-type utf8): array of strings, each specifying + * one subchannel the s390 device uses to communicate to the host. + **/ +const char *const * +nm_setting_wired_get_s390_subchannels(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + return (const char *const *) NM_SETTING_WIRED_GET_PRIVATE(setting)->s390_subchannels; +} + +/** + * nm_setting_wired_get_s390_nettype: + * @setting: the #NMSettingWired + * + * Returns the s390 device type this connection should apply to. Will be one + * of 'qeth', 'lcs', or 'ctc'. + * + * Returns: the s390 device type + **/ +const char * +nm_setting_wired_get_s390_nettype(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->s390_nettype; +} + +/** + * nm_setting_wired_get_num_s390_options: + * @setting: the #NMSettingWired + * + * Returns the number of s390-specific options that should be set for this + * device when it is activated. This can be used to retrieve each s390 + * option individually using nm_setting_wired_get_s390_option(). + * + * Returns: the number of s390-specific device options + **/ +guint32 +nm_setting_wired_get_num_s390_options(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), 0); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->s390_options.len; +} + +/** + * nm_setting_wired_get_s390_option: + * @setting: the #NMSettingWired + * @idx: index of the desired option, from 0 to + * nm_setting_wired_get_num_s390_options() - 1 + * @out_key: (allow-none) (out) (transfer none): on return, the key name of the s390 specific + * option; this value is owned by the setting and should not be modified + * @out_value: (allow-none) (out) (transfer none): on return, the value of the key of the + * s390 specific option; this value is owned by the setting and should not be + * modified + * + * Given an index, return the value of the s390 option at that index. indexes + * are *not* guaranteed to be static across modifications to options done by + * nm_setting_wired_add_s390_option() and nm_setting_wired_remove_s390_option(), + * and should not be used to refer to options except for short periods of time + * such as during option iteration. + * + * Returns: %TRUE on success if the index was valid and an option was found, + * %FALSE if the index was invalid (ie, greater than the number of options + * currently held by the setting) + **/ +gboolean +nm_setting_wired_get_s390_option(NMSettingWired *setting, + guint32 idx, + const char ** out_key, + const char ** out_value) +{ + NMSettingWiredPrivate *priv; + + /* with LTO and optimization, the compiler complains that the + * output variables are not initialized. In practice, the function + * only sets the output on success. But make the compiler happy. + */ + NM_SET_OUT(out_key, NULL); + NM_SET_OUT(out_value, NULL); + + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), FALSE); + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + + g_return_val_if_fail(idx < priv->s390_options.len, FALSE); + + NM_SET_OUT(out_key, priv->s390_options.arr[idx].name); + NM_SET_OUT(out_value, priv->s390_options.arr[idx].value_str); + return TRUE; +} + +/** + * nm_setting_wired_get_s390_option_by_key: + * @setting: the #NMSettingWired + * @key: the key for which to retrieve the value + * + * Returns the value associated with the s390-specific option specified by + * @key, if it exists. + * + * Returns: the value, or %NULL if the key/value pair was never added to the + * setting; the value is owned by the setting and must not be modified + **/ +const char * +nm_setting_wired_get_s390_option_by_key(NMSettingWired *setting, const char *key) +{ + NMSettingWiredPrivate *priv; + gssize idx; + + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + g_return_val_if_fail(key, NULL); + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + + idx = nm_utils_named_value_list_find(priv->s390_options.arr, priv->s390_options.len, key, TRUE); + if (idx < 0) + return NULL; + return priv->s390_options.arr[idx].value_str; +} + +/** + * nm_setting_wired_add_s390_option: + * @setting: the #NMSettingWired + * @key: key name for the option + * @value: value for the option + * + * Add an option to the table. If the key already exists, the value gets + * replaced. + * + * Before 1.32, the function would assert that the key is valid. Since then, + * an invalid key gets silently added but renders the profile as invalid. + * + * Returns: since 1.32 this always returns %TRUE. + **/ +gboolean +nm_setting_wired_add_s390_option(NMSettingWired *setting, const char *key, const char *value) +{ + NMSettingWiredPrivate *priv; + gssize idx; + + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), FALSE); + g_return_val_if_fail(key, FALSE); + g_return_val_if_fail(value, FALSE); + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + + idx = nm_utils_named_value_list_find(priv->s390_options.arr, priv->s390_options.len, key, TRUE); + if (idx < 0) { + gsize dst_idx = ~idx; + + g_return_val_if_fail(priv->s390_options.len < G_MAXUINT32 - 1u, FALSE); + + if (priv->s390_options.n_alloc < ((gsize) priv->s390_options.len) + 1u) { + priv->s390_options.n_alloc = NM_MAX(4u, (((gsize) priv->s390_options.len) + 1u) * 2u); + priv->s390_options.arr = + g_realloc(priv->s390_options.arr, + priv->s390_options.n_alloc * sizeof(NMUtilsNamedValue)); + } + if (dst_idx < priv->s390_options.len) { + memmove(&priv->s390_options.arr[dst_idx + 1u], + &priv->s390_options.arr[dst_idx], + (priv->s390_options.len - dst_idx) * sizeof(NMUtilsNamedValue)); + } + priv->s390_options.arr[dst_idx] = (NMUtilsNamedValue){ + .name = g_strdup(key), + .value_str = g_strdup(value), + }; + priv->s390_options.len++; + } else { + if (!nm_utils_strdup_reset(&priv->s390_options.arr[idx].value_str_mutable, value)) + return TRUE; + } + + _notify(setting, PROP_S390_OPTIONS); + return TRUE; +} + +/** + * nm_setting_wired_remove_s390_option: + * @setting: the #NMSettingWired + * @key: key name for the option to remove + * + * Remove the s390-specific option referenced by @key from the internal option + * list. + * + * Returns: %TRUE if the option was found and removed from the internal option + * list, %FALSE if it was not. + **/ +gboolean +nm_setting_wired_remove_s390_option(NMSettingWired *setting, const char *key) +{ + NMSettingWiredPrivate *priv; + gsize dst_idx; + gssize idx; + + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), FALSE); + g_return_val_if_fail(key, FALSE); + + priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + + idx = nm_utils_named_value_list_find(priv->s390_options.arr, priv->s390_options.len, key, TRUE); + if (idx < 0) + return FALSE; + + dst_idx = idx; + + g_free((char *) priv->s390_options.arr[dst_idx].name); + g_free((char *) priv->s390_options.arr[dst_idx].value_str); + if (dst_idx + 1u != priv->s390_options.len) { + memmove(&priv->s390_options.arr[dst_idx], + &priv->s390_options.arr[dst_idx + 1u], + (priv->s390_options.len - dst_idx - 1u) * sizeof(NMUtilsNamedValue)); + } + + priv->s390_options.len--; + + _notify(setting, PROP_S390_OPTIONS); + return TRUE; +} + +static void +_s390_options_clear(NMSettingWiredPrivate *priv) +{ + guint i; + + for (i = 0; i < priv->s390_options.len; i++) { + g_free((char *) priv->s390_options.arr[i].name); + g_free((char *) priv->s390_options.arr[i].value_str); + } + nm_clear_g_free(&priv->s390_options.arr); + priv->s390_options.len = 0; + priv->s390_options.n_alloc = 0; +} + +void +_nm_setting_wired_clear_s390_options(NMSettingWired *setting) +{ + g_return_if_fail(NM_IS_SETTING_WIRED(setting)); + + _s390_options_clear(NM_SETTING_WIRED_GET_PRIVATE(setting)); +} + +/** + * nm_setting_wired_get_valid_s390_options: + * @setting: (allow-none): the #NMSettingWired. This argument is unused + * and you may pass %NULL. + * + * Returns a list of valid s390 options. + * + * The @setting argument is unused and %NULL may be passed instead. + * + * Returns: (transfer none): a %NULL-terminated array of strings of valid s390 options. + **/ +const char ** +nm_setting_wired_get_valid_s390_options(NMSettingWired *setting) +{ + return (const char **) valid_s390_opts; +} + +/** + * nm_setting_wired_get_wake_on_lan: + * @setting: the #NMSettingWired + * + * Returns the Wake-on-LAN options enabled for the connection + * + * Returns: the Wake-on-LAN options + * + * Since: 1.2 + */ +NMSettingWiredWakeOnLan +nm_setting_wired_get_wake_on_lan(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NM_SETTING_WIRED_WAKE_ON_LAN_NONE); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->wol; +} + +/** + * nm_setting_wired_get_wake_on_lan_password: + * @setting: the #NMSettingWired + * + * Returns the Wake-on-LAN password. This only applies to + * %NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC. + * + * Returns: the Wake-on-LAN setting password, or %NULL if there is no password. + * + * Since: 1.2 + */ +const char * +nm_setting_wired_get_wake_on_lan_password(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->wol_password; +} + +/** + * nm_setting_wired_get_accept_all_mac_addresses: + * @setting: the #NMSettingWired + * + * Returns: the #NMSettingWired:accept-all-mac-addresses property of the setting + * + * Since: 1.32 + **/ +NMTernary +nm_setting_wired_get_accept_all_mac_addresses(NMSettingWired *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NM_TERNARY_DEFAULT); + + return NM_SETTING_WIRED_GET_PRIVATE(setting)->accept_all_mac_addresses; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + GError * local = NULL; + guint i; + + if (!NM_IN_STRSET(priv->port, NULL, "tp", "aui", "bnc", "mii")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid Ethernet port value"), + priv->port); + g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_PORT); + return FALSE; + } + + if (!NM_IN_STRSET(priv->duplex, NULL, "half", "full")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid duplex value"), + priv->duplex); + g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_DUPLEX); + return FALSE; + } + + if (priv->device_mac_address && !nm_utils_hwaddr_valid(priv->device_mac_address, ETH_ALEN)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid MAC address"), + priv->device_mac_address); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_MAC_ADDRESS); + return FALSE; + } + + for (i = 0; i < priv->mac_address_blacklist->len; i++) { + const char *mac = g_array_index(priv->mac_address_blacklist, const char *, i); + + if (!nm_utils_hwaddr_valid(mac, ETH_ALEN)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid MAC address"), + mac); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST); + return FALSE; + } + } + + if (priv->s390_subchannels) { + guint len = g_strv_length(priv->s390_subchannels); + + if (len != 2 && len != 3) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_SUBCHANNELS); + return FALSE; + } + } + + if (!NM_IN_STRSET(priv->s390_nettype, NULL, "qeth", "lcs", "ctc")) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_NETTYPE); + return FALSE; + } + + for (i = 0; i < priv->s390_options.len; i++) { + const NMUtilsNamedValue *v = &priv->s390_options.arr[i]; + + nm_assert(v->name); + + if (!_nm_setting_wired_is_valid_s390_option(v->name)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid key '%s'"), + v->name); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_OPTIONS); + return FALSE; + } + if (!_nm_setting_wired_is_valid_s390_option_value(v->name, v->value_str)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid value for key '%s'"), + v->name); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_S390_OPTIONS); + return FALSE; + } + } + + if (priv->cloned_mac_address && !NM_CLONED_MAC_IS_SPECIAL(priv->cloned_mac_address) + && !nm_utils_hwaddr_valid(priv->cloned_mac_address, ETH_ALEN)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid MAC address"), + priv->cloned_mac_address); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_CLONED_MAC_ADDRESS); + return FALSE; + } + + /* generate-mac-address-mask only makes sense with cloned-mac-address "random" or + * "stable". Still, let's not be so strict about that and accept the value + * even if it is unused. */ + if (!_nm_utils_generate_mac_address_mask_parse(priv->generate_mac_address_mask, + NULL, + NULL, + NULL, + &local)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + local->message); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK); + g_error_free(local); + return FALSE; + } + + if (NM_FLAGS_ANY(priv->wol, NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS) + && !nm_utils_is_power_of_two(priv->wol)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Wake-on-LAN mode 'default' and 'ignore' are exclusive flags")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_WAKE_ON_LAN); + return FALSE; + } + + if (priv->wol_password && !NM_FLAGS_HAS(priv->wol, NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Wake-on-LAN password can only be used with magic packet mode")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD); + return FALSE; + } + + if (priv->wol_password && !nm_utils_hwaddr_valid(priv->wol_password, ETH_ALEN)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid MAC address"), + priv->wol_password); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD); + return FALSE; + } + + /* Normalizable properties - just return NM_SETTING_VERIFY_NORMALIZABLE for compatibility + * with legacy nm-connection-editor which used to save "full" duplex connection as default + */ + + if (((priv->speed) && (!priv->duplex)) || ((!priv->speed) && (priv->duplex))) { + g_set_error_literal( + error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + priv->auto_negotiate + ? _("both speed and duplex should have a valid value or both should be unset") + : _("both speed and duplex are required for static link configuration")); + return NM_SETTING_VERIFY_NORMALIZABLE; + } + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + if (nm_streq(sett_info->property_infos[property_idx].name, + NM_SETTING_WIRED_CLONED_MAC_ADDRESS)) { + return !set_b + || nm_streq0(NM_SETTING_WIRED_GET_PRIVATE(set_a)->cloned_mac_address, + NM_SETTING_WIRED_GET_PRIVATE(set_b)->cloned_mac_address); + } + + return NM_SETTING_CLASS(nm_setting_wired_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static void +clear_blacklist_item(char **item_p) +{ + g_free(*item_p); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingWired * setting = NM_SETTING_WIRED(object); + NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + GHashTable * hash; + guint i; + + switch (prop_id) { + case PROP_PORT: + g_value_set_string(value, nm_setting_wired_get_port(setting)); + break; + case PROP_SPEED: + g_value_set_uint(value, nm_setting_wired_get_speed(setting)); + break; + case PROP_DUPLEX: + g_value_set_string(value, nm_setting_wired_get_duplex(setting)); + break; + case PROP_AUTO_NEGOTIATE: + g_value_set_boolean(value, nm_setting_wired_get_auto_negotiate(setting)); + break; + case PROP_MAC_ADDRESS: + g_value_set_string(value, nm_setting_wired_get_mac_address(setting)); + break; + case PROP_CLONED_MAC_ADDRESS: + g_value_set_string(value, nm_setting_wired_get_cloned_mac_address(setting)); + break; + case PROP_GENERATE_MAC_ADDRESS_MASK: + g_value_set_string(value, nm_setting_wired_get_generate_mac_address_mask(setting)); + break; + case PROP_MAC_ADDRESS_BLACKLIST: + g_value_set_boxed(value, (char **) priv->mac_address_blacklist->data); + break; + case PROP_MTU: + g_value_set_uint(value, nm_setting_wired_get_mtu(setting)); + break; + case PROP_S390_SUBCHANNELS: + g_value_set_boxed(value, priv->s390_subchannels); + break; + case PROP_S390_NETTYPE: + g_value_set_string(value, nm_setting_wired_get_s390_nettype(setting)); + break; + case PROP_S390_OPTIONS: + hash = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + for (i = 0; i < priv->s390_options.len; i++) { + g_hash_table_insert(hash, + g_strdup(priv->s390_options.arr[i].name), + g_strdup(priv->s390_options.arr[i].value_str)); + } + g_value_take_boxed(value, hash); + break; + case PROP_WAKE_ON_LAN: + g_value_set_uint(value, priv->wol); + break; + case PROP_WAKE_ON_LAN_PASSWORD: + g_value_set_string(value, priv->wol_password); + break; + case PROP_ACCEPT_ALL_MAC_ADDRESSES: + g_value_set_enum(value, priv->accept_all_mac_addresses); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE(object); + const char *const * blacklist; + const char * mac; + + switch (prop_id) { + case PROP_PORT: + g_free(priv->port); + priv->port = g_value_dup_string(value); + break; + case PROP_SPEED: + priv->speed = g_value_get_uint(value); + break; + case PROP_DUPLEX: + g_free(priv->duplex); + priv->duplex = g_value_dup_string(value); + break; + case PROP_AUTO_NEGOTIATE: + priv->auto_negotiate = g_value_get_boolean(value); + break; + case PROP_MAC_ADDRESS: + g_free(priv->device_mac_address); + priv->device_mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN); + break; + case PROP_CLONED_MAC_ADDRESS: + g_free(priv->cloned_mac_address); + priv->cloned_mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN); + break; + case PROP_GENERATE_MAC_ADDRESS_MASK: + g_free(priv->generate_mac_address_mask); + priv->generate_mac_address_mask = g_value_dup_string(value); + break; + case PROP_MAC_ADDRESS_BLACKLIST: + blacklist = g_value_get_boxed(value); + g_array_set_size(priv->mac_address_blacklist, 0); + if (blacklist && *blacklist) { + guint i; + + for (i = 0; blacklist[i]; i++) { + mac = _nm_utils_hwaddr_canonical_or_invalid(blacklist[i], ETH_ALEN); + g_array_append_val(priv->mac_address_blacklist, mac); + } + } + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + case PROP_S390_SUBCHANNELS: + if (priv->s390_subchannels) + g_strfreev(priv->s390_subchannels); + priv->s390_subchannels = g_value_dup_boxed(value); + break; + case PROP_S390_NETTYPE: + g_free(priv->s390_nettype); + priv->s390_nettype = g_value_dup_string(value); + break; + case PROP_S390_OPTIONS: + { + GHashTable *hash; + + _s390_options_clear(priv); + + hash = g_value_get_boxed(value); + + priv->s390_options.n_alloc = nm_g_hash_table_size(hash); + + if (priv->s390_options.n_alloc > 0u) { + gboolean invalid_content = FALSE; + GHashTableIter iter; + const char * key; + const char * val; + guint j; + guint i; + + priv->s390_options.arr = g_new(NMUtilsNamedValue, priv->s390_options.n_alloc); + + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + if (!key || !val) { + invalid_content = TRUE; + continue; + } + + nm_assert(priv->s390_options.len < priv->s390_options.n_alloc); + + priv->s390_options.arr[priv->s390_options.len] = (NMUtilsNamedValue){ + .name = g_strdup(key), + .value_str = g_strdup(val), + }; + priv->s390_options.len++; + } + if (priv->s390_options.len > 1) { + nm_utils_named_value_list_sort(priv->s390_options.arr, + priv->s390_options.len, + NULL, + NULL); + /* prune duplicate keys. This is only possible if @hash does not use + * g_str_equal() as compare function (which would be a bug). + * Still, handle this, because we use later binary sort and rely + * on unique names. One bug here, should not bork the remainder + * of the program. */ + j = 1; + for (i = 1; i < priv->s390_options.len; i++) { + if (nm_streq(priv->s390_options.arr[j - 1].name, + priv->s390_options.arr[i].name)) { + g_free((char *) priv->s390_options.arr[i].name); + g_free((char *) priv->s390_options.arr[i].value_str); + invalid_content = TRUE; + continue; + } + priv->s390_options.arr[j++] = priv->s390_options.arr[i]; + } + priv->s390_options.len = j; + } + + g_return_if_fail(!invalid_content); + } + } break; + case PROP_WAKE_ON_LAN: + priv->wol = g_value_get_uint(value); + break; + case PROP_WAKE_ON_LAN_PASSWORD: + g_free(priv->wol_password); + priv->wol_password = g_value_dup_string(value); + break; + case PROP_ACCEPT_ALL_MAC_ADDRESSES: + priv->accept_all_mac_addresses = g_value_get_enum(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_wired_init(NMSettingWired *setting) +{ + NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE(setting); + + /* We use GArray rather than GPtrArray so it will automatically be NULL-terminated */ + priv->mac_address_blacklist = g_array_new(TRUE, FALSE, sizeof(char *)); + g_array_set_clear_func(priv->mac_address_blacklist, (GDestroyNotify) clear_blacklist_item); + + priv->wol = NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT; + priv->accept_all_mac_addresses = NM_TERNARY_DEFAULT; +} + +/** + * nm_setting_wired_new: + * + * Creates a new #NMSettingWired object with default values. + * + * Returns: (transfer full): the new empty #NMSettingWired object + **/ +NMSetting * +nm_setting_wired_new(void) +{ + return g_object_new(NM_TYPE_SETTING_WIRED, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingWiredPrivate *priv = NM_SETTING_WIRED_GET_PRIVATE(object); + + g_free(priv->port); + g_free(priv->duplex); + g_free(priv->s390_nettype); + + _s390_options_clear(priv); + + g_free(priv->device_mac_address); + g_free(priv->cloned_mac_address); + g_free(priv->generate_mac_address_mask); + g_array_unref(priv->mac_address_blacklist); + + if (priv->s390_subchannels) + g_strfreev(priv->s390_subchannels); + + g_free(priv->wol_password); + + G_OBJECT_CLASS(nm_setting_wired_parent_class)->finalize(object); +} + +static void +nm_setting_wired_class_init(NMSettingWiredClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingWiredPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->compare_property = compare_property; + + /** + * NMSettingWired:port: + * + * Specific port type to use if the device supports multiple + * attachment methods. One of "tp" (Twisted Pair), "aui" (Attachment Unit + * Interface), "bnc" (Thin Ethernet) or "mii" (Media Independent Interface). + * If the device supports only one port type, this setting is ignored. + **/ + /* ---ifcfg-rh--- + * property: port + * variable: (none) + * description: The property is not saved by the plugin. + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_WIRED_PORT, + PROP_PORT, + NM_SETTING_PARAM_NONE, + nm_setting_wired_get_port); + + /** + * NMSettingWired:speed: + * + * When a value greater than 0 is set, configures the device to use + * the specified speed. If "auto-negotiate" is "yes" the specified + * speed will be the only one advertised during link negotiation: + * this works only for BASE-T 802.3 specifications and is useful for + * enforcing gigabit speeds, as in this case link negotiation is + * mandatory. + * If the value is unset (0, the default), the link configuration will be + * either skipped (if "auto-negotiate" is "no", the default) or will + * be auto-negotiated (if "auto-negotiate" is "yes") and the local device + * will advertise all the supported speeds. + * In Mbit/s, ie 100 == 100Mbit/s. + * Must be set together with the "duplex" property when non-zero. + * Before specifying a speed value be sure your device supports it. + **/ + /* ---ifcfg-rh--- + * property: speed + * variable: ETHTOOL_OPTS + * description: Fixed speed for the ethernet link. It is added as "speed" + * parameter in the ETHTOOL_OPTS variable. + * ---end--- + */ + obj_properties[PROP_SPEED] = g_param_spec_uint(NM_SETTING_WIRED_SPEED, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWired:duplex: + * + * When a value is set, either "half" or "full", configures the device + * to use the specified duplex mode. If "auto-negotiate" is "yes" the + * specified duplex mode will be the only one advertised during link + * negotiation: this works only for BASE-T 802.3 specifications and is + * useful for enforcing gigabits modes, as in these cases link negotiation + * is mandatory. + * If the value is unset (the default), the link configuration will be + * either skipped (if "auto-negotiate" is "no", the default) or will + * be auto-negotiated (if "auto-negotiate" is "yes") and the local device + * will advertise all the supported duplex modes. + * Must be set together with the "speed" property if specified. + * Before specifying a duplex mode be sure your device supports it. + **/ + /* ---ifcfg-rh--- + * property: duplex + * variable: ETHTOOL_OPTS + * description: Fixed duplex mode for the ethernet link. It is added as + * "duplex" parameter in the ETHOOL_OPTS variable. + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_WIRED_DUPLEX, + PROP_DUPLEX, + NM_SETTING_PARAM_NONE, + nm_setting_wired_get_duplex); + + /** + * NMSettingWired:auto-negotiate: + * + * When %TRUE, enforce auto-negotiation of speed and duplex mode. + * If "speed" and "duplex" properties are both specified, only that + * single mode will be advertised and accepted during the link + * auto-negotiation process: this works only for BASE-T 802.3 specifications + * and is useful for enforcing gigabits modes, as in these cases link + * negotiation is mandatory. + * When %FALSE, "speed" and "duplex" properties should be both set or + * link configuration will be skipped. + **/ + /* ---ifcfg-rh--- + * property: auto-negotiate + * variable: ETHTOOL_OPTS + * description: Whether link speed and duplex autonegotiation is enabled. + * It is not saved only if disabled and no values are provided for the + * "speed" and "duplex" parameters (skips link configuration). + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_WIRED_AUTO_NEGOTIATE, + PROP_AUTO_NEGOTIATE, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_wired_get_auto_negotiate, + .to_dbus_data.including_default = TRUE); + + /** + * NMSettingWired:mac-address: + * + * If specified, this connection will only apply to the Ethernet device + * whose permanent MAC address matches. This property does not change the + * MAC address of the device (i.e. MAC spoofing). + **/ + /* ---keyfile--- + * property: mac-address + * format: usual hex-digits-and-colons notation + * description: MAC address in traditional hex-digits-and-colons notation + * (e.g. 00:22:68:12:79:A2), or semicolon separated list of 6 bytes (obsolete) + * (e.g. 0;34;104;18;121;162) + * ---end--- + * ---ifcfg-rh--- + * property: mac-address + * variable: HWADDR + * description: Hardware address of the device in traditional hex-digits-and-colons + * notation (e.g. 00:22:68:14:5A:05). + * Note that for initscripts this is the current MAC address of the device as found + * during ifup. For NetworkManager this is the permanent MAC address. Or in case no + * permanent MAC address exists, the MAC address initially configured on the device. + * ---end--- + */ + obj_properties[PROP_MAC_ADDRESS] = g_param_spec_string( + NM_SETTING_WIRED_MAC_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_MAC_ADDRESS], + &nm_sett_info_propert_type_mac_address); + + /** + * NMSettingWired:cloned-mac-address: + * + * If specified, request that the device use this MAC address instead. + * This is known as MAC cloning or spoofing. + * + * Beside explicitly specifying a MAC address, the special values "preserve", "permanent", + * "random" and "stable" are supported. + * "preserve" means not to touch the MAC address on activation. + * "permanent" means to use the permanent hardware address if the device + * has one (otherwise this is treated as "preserve"). + * "random" creates a random MAC address on each connect. + * "stable" creates a hashed MAC address based on connection.stable-id and a + * machine dependent key. + * + * If unspecified, the value can be overwritten via global defaults, see manual + * of NetworkManager.conf. If still unspecified, it defaults to "preserve" + * (older versions of NetworkManager may use a different default value). + * + * On D-Bus, this field is expressed as "assigned-mac-address" or the deprecated + * "cloned-mac-address". + **/ + /* ---keyfile--- + * property: cloned-mac-address + * format: usual hex-digits-and-colons notation + * description: Cloned MAC address in traditional hex-digits-and-colons notation + * (e.g. 00:22:68:12:79:B2), or semicolon separated list of 6 bytes (obsolete) + * (e.g. 0;34;104;18;121;178). + * ---end--- + * ---ifcfg-rh--- + * property: cloned-mac-address + * variable: MACADDR + * description: Cloned (spoofed) MAC address in traditional hex-digits-and-colons + * notation (e.g. 00:22:68:14:5A:99). + * ---end--- + * ---dbus--- + * property: cloned-mac-address + * format: byte array + * description: This D-Bus field is deprecated in favor of "assigned-mac-address" + * which is more flexible and allows specifying special variants like "random". + * For libnm and nmcli, this field is called "cloned-mac-address". + * ---end--- + */ + obj_properties[PROP_CLONED_MAC_ADDRESS] = g_param_spec_string( + NM_SETTING_WIRED_CLONED_MAC_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_CLONED_MAC_ADDRESS], + &nm_sett_info_propert_type_cloned_mac_address); + + /* ---dbus--- + * property: assigned-mac-address + * format: string + * description: The new field for the cloned MAC address. It can be either + * a hardware address in ASCII representation, or one of the special values + * "preserve", "permanent", "random" or "stable". + * This field replaces the deprecated "cloned-mac-address" on D-Bus, which + * can only contain explicit hardware addresses. Note that this property + * only exists in D-Bus API. libnm and nmcli continue to call this property + * "cloned-mac-address". + * ---end--- + */ + _nm_properties_override_dbus(properties_override, + "assigned-mac-address", + &nm_sett_info_propert_type_assigned_mac_address); + + /** + * NMSettingWired:generate-mac-address-mask: + * + * With #NMSettingWired:cloned-mac-address setting "random" or "stable", + * by default all bits of the MAC address are scrambled and a locally-administered, + * unicast MAC address is created. This property allows to specify that certain bits + * are fixed. Note that the least significant bit of the first MAC address will + * always be unset to create a unicast MAC address. + * + * If the property is %NULL, it is eligible to be overwritten by a default + * connection setting. If the value is still %NULL or an empty string, the + * default is to create a locally-administered, unicast MAC address. + * + * If the value contains one MAC address, this address is used as mask. The set + * bits of the mask are to be filled with the current MAC address of the device, + * while the unset bits are subject to randomization. + * Setting "FE:FF:FF:00:00:00" means to preserve the OUI of the current MAC address + * and only randomize the lower 3 bytes using the "random" or "stable" algorithm. + * + * If the value contains one additional MAC address after the mask, + * this address is used instead of the current MAC address to fill the bits + * that shall not be randomized. For example, a value of + * "FE:FF:FF:00:00:00 68:F7:28:00:00:00" will set the OUI of the MAC address + * to 68:F7:28, while the lower bits are randomized. A value of + * "02:00:00:00:00:00 00:00:00:00:00:00" will create a fully scrambled + * globally-administered, burned-in MAC address. + * + * If the value contains more than one additional MAC addresses, one of + * them is chosen randomly. For example, "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00" + * will create a fully scrambled MAC address, randomly locally or globally + * administered. + **/ + /* ---ifcfg-rh--- + * property: generate-mac-address-mask + * variable: GENERATE_MAC_ADDRESS_MASK(+) + * description: the MAC address mask for generating randomized and stable + * cloned-mac-address. + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, + PROP_GENERATE_MAC_ADDRESS_MASK, + NM_SETTING_PARAM_FUZZY_IGNORE, + nm_setting_wired_get_generate_mac_address_mask); + + /** + * NMSettingWired:mac-address-blacklist: + * + * If specified, this connection will never apply to the Ethernet device + * whose permanent MAC address matches an address in the list. Each MAC + * address is in the standard hex-digits-and-colons notation + * (00:11:22:33:44:55). + **/ + /* ---keyfile--- + * property: mac-address-blacklist + * format: list of MACs (separated with semicolons) + * description: MAC address blacklist. + * example: mac-address-blacklist= 00:22:68:12:79:A6;00:22:68:12:79:78 + * ---end--- + * ---ifcfg-rh--- + * property: mac-address-blacklist + * variable: HWADDR_BLACKLIST(+) + * description: It denies usage of the connection for any device whose address + * is listed. + * example: HWADDR_BLACKLIST="00:22:68:11:69:08 00:11:22:11:44:55" + * ---end--- + */ + obj_properties[PROP_MAC_ADDRESS_BLACKLIST] = g_param_spec_boxed( + NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWired:mtu: + * + * If non-zero, only transmit packets of the specified size or smaller, + * breaking larger packets up into multiple Ethernet frames. + **/ + /* ---ifcfg-rh--- + * property: mtu + * variable: MTU + * description: MTU of the interface. + * ---end--- + */ + obj_properties[PROP_MTU] = g_param_spec_uint(NM_SETTING_WIRED_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWired:s390-subchannels: + * + * Identifies specific subchannels that this network device uses for + * communication with z/VM or s390 host. Like the + * #NMSettingWired:mac-address property for non-z/VM devices, this property + * can be used to ensure this connection only applies to the network device + * that uses these subchannels. The list should contain exactly 3 strings, + * and each string may only be composed of hexadecimal characters and the + * period (.) character. + **/ + /* ---ifcfg-rh--- + * property: s390-subchannels + * variable: SUBCHANNELS + * description: Subchannels for IBM S390 hosts. + * example: SUBCHANNELS=0.0.b00a,0.0.b00b,0.0.b00c + * ---end--- + */ + obj_properties[PROP_S390_SUBCHANNELS] = g_param_spec_boxed( + NM_SETTING_WIRED_S390_SUBCHANNELS, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWired:s390-nettype: + * + * s390 network device type; one of "qeth", "lcs", or "ctc", representing + * the different types of virtual network devices available on s390 systems. + **/ + /* ---ifcfg-rh--- + * property: s390-nettype + * variable: NETTYPE + * values: "qeth", "lcs" or "ctc" + * description: Network type of the S390 host. + * example: NETTYPE=qeth + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_WIRED_S390_NETTYPE, + PROP_S390_NETTYPE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_wired_get_s390_nettype); + + /** + * NMSettingWired:s390-options: (type GHashTable(utf8,utf8)): + * + * Dictionary of key/value pairs of s390-specific device options. Both keys + * and values must be strings. Allowed keys include "portno", "layer2", + * "portname", "protocol", among others. Key names must contain only + * alphanumeric characters (ie, [a-zA-Z0-9]). + **/ + /* ---ifcfg-rh--- + * property: s390-options + * variable: OPTIONS and PORTNAME, CTCPROTO, + * description: S390 device options. All options go to OPTIONS, except for + * "portname" and "ctcprot" that have their own variables. + * ---end--- + */ + obj_properties[PROP_S390_OPTIONS] = g_param_spec_boxed( + NM_SETTING_WIRED_S390_OPTIONS, + "", + "", + G_TYPE_HASH_TABLE, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_S390_OPTIONS], + &nm_sett_info_propert_type_strdict); + + /** + * NMSettingWired:wake-on-lan: + * + * The #NMSettingWiredWakeOnLan options to enable. Not all devices support all options. + * May be any combination of %NM_SETTING_WIRED_WAKE_ON_LAN_PHY, + * %NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST, %NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST, + * %NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST, %NM_SETTING_WIRED_WAKE_ON_LAN_ARP, + * %NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC or the special values + * %NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT (to use global settings) and + * %NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE (to disable management of Wake-on-LAN in + * NetworkManager). + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: wake-on-lan + * variable: ETHTOOL_OPTS, ETHTOOL_WAKE_ON_LAN + * description: Wake on Lan mode for ethernet. The setting "ignore" is expressed + * with "ETHTOOL_WAKE_ON_LAN=ignore". Otherwise, the "ETHTOOL_OPTS" variable is set + * with the value "wol" and several of the characters "p|u|m|b|a|g|s|f|d" as explained + * in the ethtool manual page. + * ---end--- + */ + obj_properties[PROP_WAKE_ON_LAN] = + g_param_spec_uint(NM_SETTING_WIRED_WAKE_ON_LAN, + "", + "", + 0, + G_MAXUINT32, + NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWired:wake-on-lan-password: + * + * If specified, the password used with magic-packet-based + * Wake-on-LAN, represented as an Ethernet MAC address. If %NULL, + * no password will be required. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: wake-on-lan-password + * variable: ETHTOOL_OPTS + * description: Password for secure-on based Wake-on-Lan. It is added as "sopass" + * parameter in the ETHTOOL_OPTS variable. + * example: ETHTOOL_OPTS="wol gs sopass 00:11:22:33:44:55" + * ---end--- + */ + _nm_setting_property_define_string(properties_override, + obj_properties, + NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD, + PROP_WAKE_ON_LAN_PASSWORD, + NM_SETTING_PARAM_NONE, + nm_setting_wired_get_wake_on_lan_password); + + /** + * NMSettingWired:accept-all-mac-addresses: + * + * When %TRUE, setup the interface to accept packets for all MAC addresses. + * This is enabling the kernel interface flag IFF_PROMISC. + * When %FALSE, the interface will only accept the packets with the + * interface destination mac address or broadcast. + * + * Since: 1.32 + **/ + /* ---ifcfg-rh--- + * property: accept-all-mac-addresses + * variable: ACCEPT_ALL_MAC_ADDRESSES + * description: Enforce the interface to accept all the packets. + * ---end--- + */ + obj_properties[PROP_ACCEPT_ALL_MAC_ADDRESSES] = + g_param_spec_enum(NM_SETTING_WIRED_ACCEPT_ALL_MAC_ADDRESSES, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_WIRED, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c new file mode 100644 index 00000000..3290ff56 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-wireguard.c @@ -0,0 +1,2608 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 - 2019 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-wireguard.h" + +#include "nm-setting-private.h" +#include "nm-utils-private.h" +#include "nm-connection-private.h" +#include "libnm-glib-aux/nm-secret-utils.h" + +/*****************************************************************************/ + +/** + * SECTION:nm-setting-wireguard + * @short_description: Describes connection properties for wireguard related options + * + * The #NMSettingWireGuard object is a #NMSetting subclass that contains settings + * for configuring WireGuard. + **/ + +/*****************************************************************************/ + +static NMWireGuardPeer *_wireguard_peer_dup(const NMWireGuardPeer *self); + +G_DEFINE_BOXED_TYPE(NMWireGuardPeer, + nm_wireguard_peer, + _wireguard_peer_dup, + nm_wireguard_peer_unref) + +/* NMWireGuardPeer can also track invalid allowed-ip settings, and only reject + * them later during is_valid(). Such values are marked by a leading 'X' character + * in the @allowed_ips. It is expected, that such values are the exception, and + * commonly not present. */ +#define ALLOWED_IP_INVALID_X 'X' +#define ALLOWED_IP_INVALID_X_STR "X" + +/** + * NMWireGuardPeer: + * + * The settings of one WireGuard peer. + * + * Since: 1.16 + */ +struct _NMWireGuardPeer { + NMSockAddrEndpoint * endpoint; + char * public_key; + char * preshared_key; + GPtrArray * allowed_ips; + guint refcount; + NMSettingSecretFlags preshared_key_flags; + guint16 persistent_keepalive; + bool public_key_valid : 1; + bool preshared_key_valid : 1; + bool sealed : 1; +}; + +static gboolean +NM_IS_WIREGUARD_PEER(const NMWireGuardPeer *self, gboolean also_sealed) +{ + return self && self->refcount > 0 && (also_sealed || !self->sealed); +} + +/** + * nm_wireguard_peer_new: + * + * Returns: (transfer full): a new, default, unsealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_wireguard_peer_new(void) +{ + NMWireGuardPeer *self; + + self = g_slice_new(NMWireGuardPeer); + *self = (NMWireGuardPeer){ + .refcount = 1, + .preshared_key_flags = NM_SETTING_SECRET_FLAG_NOT_REQUIRED, + }; + return self; +} + +/** + * nm_wireguard_peer_new_clone: + * @self: the #NMWireGuardPeer instance to copy. + * @with_secrets: if %TRUE, the preshared-key secrets are copied + * as well. Otherwise, they will be removed. + * + * Returns: (transfer full): a clone of @self. This instance + * is always unsealed. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_wireguard_peer_new_clone(const NMWireGuardPeer *self, gboolean with_secrets) +{ + NMWireGuardPeer *new; + guint i; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + new = g_slice_new(NMWireGuardPeer); + *new = (NMWireGuardPeer){ + .refcount = 1, + .public_key = g_strdup(self->public_key), + .public_key_valid = self->public_key_valid, + .preshared_key = with_secrets ? g_strdup(self->preshared_key) : NULL, + .preshared_key_valid = self->preshared_key_valid, + .preshared_key_flags = self->preshared_key_flags, + .endpoint = nm_sock_addr_endpoint_ref(self->endpoint), + .persistent_keepalive = self->persistent_keepalive, + }; + if (self->allowed_ips && self->allowed_ips->len > 0) { + new->allowed_ips = g_ptr_array_new_full(self->allowed_ips->len, g_free); + for (i = 0; i < self->allowed_ips->len; i++) { + g_ptr_array_add(new->allowed_ips, g_strdup(self->allowed_ips->pdata[i])); + } + } + return new; +} + +/** + * nm_wireguard_peer_ref: + * @self: (allow-none): the #NMWireGuardPeer instance + * + * This is not thread-safe. + * + * Returns: returns the input argument @self after incrementing + * the reference count. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_wireguard_peer_ref(NMWireGuardPeer *self) +{ + if (!self) + return NULL; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + nm_assert(self->refcount < G_MAXUINT); + + self->refcount++; + return self; +} + +/** + * nm_wireguard_peer_unref: + * @self: (allow-none): the #NMWireGuardPeer instance + * + * Drop a reference to @self. If the last reference is dropped, + * the instance is freed and all associate data released. + * + * This is not thread-safe. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_unref(NMWireGuardPeer *self) +{ + if (!self) + return; + + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE)); + + if (--self->refcount > 0) + return; + + nm_sock_addr_endpoint_unref(self->endpoint); + if (self->allowed_ips) + g_ptr_array_unref(self->allowed_ips); + g_free(self->public_key); + nm_free_secret(self->preshared_key); + g_slice_free(NMWireGuardPeer, self); +} + +/** + * _wireguard_peer_dup: + * @self: the #NMWireGuardPeer instance + * + * Duplicates the #NMWireGuardPeer instance. Note that if @self + * is already sealed, this increments the reference count and + * returns it. If the instance is still unsealed, it is copied. + * + * Returns: (transfer full): a duplicate of @self, or (if the + * instance is sealed and thus immutable) a reference to @self. + * As such, the instance will be sealed if and only if @self is + * sealed. + */ +static NMWireGuardPeer * +_wireguard_peer_dup(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + if (self->sealed) + return nm_wireguard_peer_ref((NMWireGuardPeer *) self); + return nm_wireguard_peer_new_clone(self, TRUE); +} + +/** + * nm_wireguard_peer_seal: + * @self: the #NMWireGuardPeer instance + * + * Seal the #NMWireGuardPeer instance. Afterwards, it is a bug + * to call all functions that modify the instance (except ref/unref). + * A sealed instance cannot be unsealed again, but you can create + * an unsealed copy with nm_wireguard_peer_new_clone(). + * + * Since: 1.16 + */ +void +nm_wireguard_peer_seal(NMWireGuardPeer *self) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE)); + + self->sealed = TRUE; + + if (self->allowed_ips) { + if (self->allowed_ips->len == 0) + nm_clear_pointer(&self->allowed_ips, g_ptr_array_unref); + } +} + +/** + * nm_wireguard_peer_is_sealed: + * @self: the #NMWireGuardPeer instance + * + * Returns: whether @self is sealed or not. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_is_sealed(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), FALSE); + + return self->sealed; +} + +/** + * nm_wireguard_peer_get_public_key: + * @self: the #NMWireGuardPeer instance + * + * Returns: (transfer none): the public key or %NULL if unset. + * + * Since: 1.16 + */ +const char * +nm_wireguard_peer_get_public_key(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + return self->public_key; +} + +/** + * nm_wireguard_peer_set_public_key: + * @self: the unsealed #NMWireGuardPeer instance + * @public_key: (allow-none) (transfer none): the new public + * key or %NULL to clear the public key. + * @accept_invalid: if %TRUE and @public_key is not %NULL and + * invalid, then do not modify the instance. + * + * Reset the public key. Note that if the public key is valid, it + * will be normalized (which may or may not modify the set value). + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if the key was valid or %NULL. Returns + * %FALSE for invalid keys. Depending on @accept_invalid + * will an invalid key be set or not. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_set_public_key(NMWireGuardPeer *self, + const char * public_key, + gboolean accept_invalid) +{ + char * public_key_normalized = NULL; + gboolean is_valid; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + + if (!public_key) { + nm_clear_g_free(&self->public_key); + return TRUE; + } + + is_valid = nm_utils_base64secret_normalize(public_key, + NM_WIREGUARD_PUBLIC_KEY_LEN, + &public_key_normalized); + nm_assert(is_valid == (public_key_normalized != NULL)); + + if (!is_valid && !accept_invalid) + return FALSE; + + self->public_key_valid = is_valid; + g_free(self->public_key); + self->public_key = public_key_normalized ?: g_strdup(public_key); + return is_valid; +} + +void +_nm_wireguard_peer_set_public_key_bin(NMWireGuardPeer *self, + const guint8 public_key[static NM_WIREGUARD_PUBLIC_KEY_LEN]) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE)); + + nm_clear_g_free(&self->public_key); + + if (!public_key) + return; + + self->public_key = g_base64_encode(public_key, NM_WIREGUARD_PUBLIC_KEY_LEN); + self->public_key_valid = TRUE; +} + +/** + * nm_wireguard_peer_get_preshared_key: + * @self: the #NMWireGuardPeer instance + * + * Returns: (transfer none): the preshared key or %NULL if unset. + * + * Since: 1.16 + */ +const char * +nm_wireguard_peer_get_preshared_key(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + return self->preshared_key; +} + +/** + * nm_wireguard_peer_set_preshared_key: + * @self: the unsealed #NMWireGuardPeer instance + * @preshared_key: (allow-none) (transfer none): the new preshared + * key or %NULL to clear the preshared key. + * @accept_invalid: whether to allow setting the key to an invalid + * value. If %FALSE, @self is unchanged if the key is invalid + * and if %FALSE is returned. + * + * Reset the preshared key. Note that if the preshared key is valid, it + * will be normalized (which may or may not modify the set value). + * + * Note that the preshared-key is a secret and consequently has corresponding + * preshared-key-flags property. This is so that secrets can be optional + * and requested on demand from a secret-agent. Also, an invalid preshared-key + * may optionally cause nm_wireguard_peer_is_valid() to fail or it may + * be accepted. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if the preshared-key is valid, otherwise %FALSE. + * %NULL is considered a valid value. + * If the key is invalid, it depends on @accept_invalid whether the + * previous value was reset. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_set_preshared_key(NMWireGuardPeer *self, + const char * preshared_key, + gboolean accept_invalid) +{ + char * preshared_key_normalized = NULL; + gboolean is_valid; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + + if (!preshared_key) { + nm_clear_pointer(&self->preshared_key, nm_free_secret); + return TRUE; + } + + is_valid = nm_utils_base64secret_normalize(preshared_key, + NM_WIREGUARD_SYMMETRIC_KEY_LEN, + &preshared_key_normalized); + nm_assert(is_valid == (preshared_key_normalized != NULL)); + + if (!is_valid && !accept_invalid) + return FALSE; + + self->preshared_key_valid = is_valid; + nm_free_secret(self->preshared_key); + self->preshared_key = preshared_key_normalized ?: g_strdup(preshared_key); + return is_valid; +} + +/** + * nm_wireguard_peer_get_preshared_key_flags: + * @self: the #NMWireGuardPeer instance + * + * Returns: get the secret flags for the preshared-key. + * + * Since: 1.16 + */ +NMSettingSecretFlags +nm_wireguard_peer_get_preshared_key_flags(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0); + + return self->preshared_key_flags; +} + +/** + * nm_wireguard_peer_set_preshared_key_flags: + * @self: the unsealed #NMWireGuardPeer instance + * @preshared_key_flags: the secret flags to set. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_set_preshared_key_flags(NMWireGuardPeer * self, + NMSettingSecretFlags preshared_key_flags) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE)); + + self->preshared_key_flags = preshared_key_flags; +} + +/** + * nm_wireguard_peer_get_persistent_keepalive: + * @self: the #NMWireGuardPeer instance + * + * Returns: get the persistent-keepalive setting in seconds. Set to zero to disable + * keep-alive. + * + * Since: 1.16 + */ +guint16 +nm_wireguard_peer_get_persistent_keepalive(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0); + + return self->persistent_keepalive; +} + +/** + * nm_wireguard_peer_set_persistent_keepalive: + * @self: the unsealed #NMWireGuardPeer instance + * @persistent_keepalive: the keep-alive value to set. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_set_persistent_keepalive(NMWireGuardPeer *self, guint16 persistent_keepalive) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE)); + + self->persistent_keepalive = persistent_keepalive; +} + +NMSockAddrEndpoint * +_nm_wireguard_peer_get_endpoint(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + return self->endpoint; +} + +/** + * nm_wireguard_peer_get_endpoint: + * @self: the #NMWireGuardPeer instance + * + * Returns: (transfer none): the endpoint or %NULL if none was set. + * + * Since: 1.16 + */ +const char * +nm_wireguard_peer_get_endpoint(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + return self->endpoint ? nm_sock_addr_endpoint_get_endpoint(self->endpoint) : NULL; +} + +void +_nm_wireguard_peer_set_endpoint(NMWireGuardPeer *self, NMSockAddrEndpoint *endpoint) +{ + NMSockAddrEndpoint *old; + + nm_assert(NM_IS_WIREGUARD_PEER(self, FALSE)); + + old = self->endpoint; + self->endpoint = nm_sock_addr_endpoint_ref(endpoint); + nm_sock_addr_endpoint_unref(old); +} + +/** + * nm_wireguard_peer_set_endpoint: + * @self: the unsealed #NMWireGuardPeer instance + * @endpoint: the socket address endpoint to set or %NULL. + * @allow_invalid: if %TRUE, also invalid values are set. + * If %FALSE, the function does nothing for invalid @endpoint + * arguments. + * + * Sets or clears the endpoint of @self. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if the endpoint is %NULL or valid. For an + * invalid @endpoint argument, %FALSE is returned. Depending + * on @allow_invalid, the instance will be modified. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_set_endpoint(NMWireGuardPeer *self, const char *endpoint, gboolean allow_invalid) +{ + NMSockAddrEndpoint *old; + NMSockAddrEndpoint *new; + gboolean is_valid; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + + if (!endpoint) { + nm_clear_pointer(&self->endpoint, nm_sock_addr_endpoint_unref); + return TRUE; + } + + new = nm_sock_addr_endpoint_new(endpoint); + + is_valid = (nm_sock_addr_endpoint_get_host(new) != NULL); + + if (!allow_invalid && !is_valid) { + nm_sock_addr_endpoint_unref(new); + return FALSE; + } + + old = self->endpoint; + self->endpoint = new; + nm_sock_addr_endpoint_unref(old); + return is_valid; +} + +/** + * nm_wireguard_peer_get_allowed_ips_len: + * @self: the #NMWireGuardPeer instance + * + * Returns: the number of allowed-ips entries. + * + * Since: 1.16 + */ +guint +nm_wireguard_peer_get_allowed_ips_len(const NMWireGuardPeer *self) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), 0); + + return self->allowed_ips ? self->allowed_ips->len : 0u; +} + +/** + * nm_wireguard_peer_get_allowed_ip: + * @self: the #NMWireGuardPeer instance + * @idx: the index from zero to (allowed-ips-len - 1) to + * retrieve. + * @out_is_valid: (allow-none): %TRUE if the returned value is a valid allowed-ip + * setting. + * This parameter is wrongly not marked as (out) argument, it is + * thus not accessible via introspection. This cannot be fixed without + * breaking API for introspection users. + * + * Returns: (transfer none): the allowed-ip setting at index @idx. + * If @idx is out of range, %NULL will be returned. + * + * Since: 1.16 + */ +const char * +nm_wireguard_peer_get_allowed_ip(const NMWireGuardPeer *self, guint idx, gboolean *out_is_valid) +{ + const char *s; + + /* With LTO, the compiler might warn about the g_return_val_if_fail() + * code path not initializing the output argument. Workaround that by + * always setting the out argument. */ + NM_SET_OUT(out_is_valid, FALSE); + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL); + + if (!self->allowed_ips || idx >= self->allowed_ips->len) + return NULL; + + s = self->allowed_ips->pdata[idx]; + NM_SET_OUT(out_is_valid, s[0] != ALLOWED_IP_INVALID_X); + return s[0] == ALLOWED_IP_INVALID_X ? &s[1] : s; +} + +/** + * nm_wireguard_peer_clear_allowed_ips: + * @self: the unsealed #NMWireGuardPeer instance + * + * Removes all allowed-ip entries. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Since: 1.16 + */ +void +nm_wireguard_peer_clear_allowed_ips(NMWireGuardPeer *self) +{ + g_return_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE)); + + if (self->allowed_ips) + g_ptr_array_set_size(self->allowed_ips, 0); +} + +static gboolean +_peer_append_allowed_ip(NMWireGuardPeer *self, const char *allowed_ip, gboolean accept_invalid) +{ + int addr_family; + int prefix; + NMIPAddr addrbin; + char * str; + gboolean is_valid = TRUE; + + nm_assert(NM_IS_WIREGUARD_PEER(self, FALSE)); + nm_assert(allowed_ip); + + /* normalize the address (if it is valid. Otherwise, take it + * as-is (it will render the instance invalid). */ + if (!nm_utils_parse_inaddr_prefix_bin(AF_UNSPEC, allowed_ip, &addr_family, &addrbin, &prefix)) { + if (!accept_invalid) + return FALSE; + /* mark the entry as invalid by having a "X" prefix. */ + str = g_strconcat(ALLOWED_IP_INVALID_X_STR, allowed_ip, NULL); + is_valid = FALSE; + } else { + char addrstr[NM_UTILS_INET_ADDRSTRLEN]; + + nm_assert_addr_family(addr_family); + + nm_utils_inet_ntop(addr_family, &addrbin, addrstr); + if (prefix >= 0) + str = g_strdup_printf("%s/%d", addrstr, prefix); + else + str = g_strdup(addrstr); + nm_assert(str[0] != ALLOWED_IP_INVALID_X); + } + + if (!self->allowed_ips) + self->allowed_ips = g_ptr_array_new_with_free_func(g_free); + + g_ptr_array_add(self->allowed_ips, str); + return is_valid; +} + +/** + * nm_wireguard_peer_append_allowed_ip: + * @self: the unsealed #NMWireGuardPeer instance + * @allowed_ip: the allowed-ip entry to set. + * @accept_invalid: if %TRUE, also invalid @allowed_ip value + * will be appended. Otherwise, the function does nothing + * in face of invalid values and returns %FALSE. + * + * Appends @allowed_ip setting to the list. This does not check + * for duplicates and always appends @allowed_ip to the end of the + * list. If @allowed_ip is valid, it will be normalized and a modified + * for might be appended. If @allowed_ip is invalid, it will still be + * appended, but later verification will fail. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if the value is a valid allowed-ips value, %FALSE otherwise. + * Depending on @accept_invalid, also invalid values are added. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_append_allowed_ip(NMWireGuardPeer *self, + const char * allowed_ip, + gboolean accept_invalid) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + g_return_val_if_fail(allowed_ip, FALSE); + + return _peer_append_allowed_ip(self, allowed_ip, accept_invalid); +} + +/** + * nm_wireguard_peer_remove_allowed_ip: + * @self: the unsealed #NMWireGuardPeer instance + * @idx: the index from zero to (allowed-ips-len - 1) to + * retrieve. If the index is out of range, %FALSE is returned + * and nothing is done. + * + * Removes the allowed-ip at the given @idx. This shifts all + * following entries one index down. + * + * It is a bug trying to modify a sealed #NMWireGuardPeer instance. + * + * Returns: %TRUE if @idx was valid and the allowed-ip was removed. + * %FALSE otherwise, and the peer will not be changed. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_remove_allowed_ip(NMWireGuardPeer *self, guint idx) +{ + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, FALSE), FALSE); + + if (!self->allowed_ips || idx >= self->allowed_ips->len) + return FALSE; + + g_ptr_array_remove_index(self->allowed_ips, idx); + return TRUE; +} + +/** + * nm_wireguard_peer_is_valid: + * @self: the #NMWireGuardPeer instance + * @check_secrets: if %TRUE, non-secret properties are validated. + * Otherwise, they are ignored for this purpose. + * @check_non_secrets: if %TRUE, secret properties are validated. + * Otherwise, they are ignored for this purpose. + * @error: the #GError location for returning the failure reason. + * + * Returns: %TRUE if the peer is valid or fails with an error + * reason. + * + * Since: 1.16 + */ +gboolean +nm_wireguard_peer_is_valid(const NMWireGuardPeer *self, + gboolean check_non_secrets, + gboolean check_secrets, + GError ** error) +{ + guint i; + + g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), FALSE); + g_return_val_if_fail(!error || !*error, FALSE); + + if (check_non_secrets) { + if (!self->public_key) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("missing public-key for peer")); + return FALSE; + } else if (!self->public_key_valid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid public-key for peer")); + return FALSE; + } + } + + if (check_secrets) { + if (self->preshared_key && !self->preshared_key_valid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid preshared-key for peer")); + return FALSE; + } + } + + if (check_non_secrets) { + if (!_nm_utils_secret_flags_validate(self->preshared_key_flags, + NULL, + NULL, + NM_SETTING_SECRET_FLAG_NONE, + error)) + return FALSE; + } + + if (check_non_secrets) { + if (self->endpoint && !nm_sock_addr_endpoint_get_host(self->endpoint)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid endpoint for peer")); + return FALSE; + } + + if (self->allowed_ips) { + for (i = 0; i < self->allowed_ips->len; i++) { + const char *s = self->allowed_ips->pdata[i]; + + if (s[0] == ALLOWED_IP_INVALID_X) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid IP address \"%s\" for allowed-ip of peer"), + &s[1]); + return FALSE; + } + } + } + + if (!_nm_setting_secret_flags_valid(self->preshared_key_flags)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid preshared-key-flags for peer")); + return FALSE; + } + } + + return TRUE; +} + +/** + * nm_wireguard_peer_cmp: + * @a: (allow-none): the #NMWireGuardPeer to compare. + * @b: (allow-none): the other #NMWireGuardPeer to compare. + * @compare_flags: #NMSettingCompareFlags to affect the comparison. + * + * Returns: zero of the two instances are equivalent or + * a non-zero integer otherwise. This defines a total ordering + * over the peers. Whether a peer is sealed or not, does not + * affect the comparison. + * + * Since: 1.16 + */ +int +nm_wireguard_peer_cmp(const NMWireGuardPeer *a, + const NMWireGuardPeer *b, + NMSettingCompareFlags compare_flags) +{ + guint i, n; + + NM_CMP_SELF(a, b); + + /* regardless of the @compare_flags, the public-key is the ID of the peer. It must + * always be compared. */ + NM_CMP_FIELD_BOOL(a, b, public_key_valid); + NM_CMP_FIELD_STR0(a, b, public_key); + + if (NM_FLAGS_ANY(compare_flags, + NM_SETTING_COMPARE_FLAG_INFERRABLE | NM_SETTING_COMPARE_FLAG_FUZZY)) + return 0; + + NM_CMP_FIELD_BOOL(a, b, endpoint); + if (a->endpoint) { + NM_CMP_DIRECT_STRCMP0(nm_sock_addr_endpoint_get_endpoint(a->endpoint), + nm_sock_addr_endpoint_get_endpoint(b->endpoint)); + } + + NM_CMP_FIELD(a, b, persistent_keepalive); + + NM_CMP_DIRECT((n = (a->allowed_ips ? a->allowed_ips->len : 0u)), + (b->allowed_ips ? b->allowed_ips->len : 0u)); + for (i = 0; i < n; i++) + NM_CMP_DIRECT_STRCMP0(a->allowed_ips->pdata[i], b->allowed_ips->pdata[i]); + + NM_CMP_FIELD(a, b, preshared_key_flags); + + if (!NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)) { + if (NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS) + && NM_FLAGS_HAS(a->preshared_key_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)) { + /* pass */ + } else if (NM_FLAGS_HAS(compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS) + && NM_FLAGS_HAS(a->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)) { + /* pass */ + } else { + NM_CMP_FIELD_BOOL(a, b, preshared_key_valid); + NM_CMP_FIELD_STR0(a, b, preshared_key); + } + } + + return 0; +} + +/*****************************************************************************/ + +typedef struct { + const char * public_key; + NMWireGuardPeer *peer; + guint idx; +} PeerData; + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_FWMARK, + PROP_IP4_AUTO_DEFAULT_ROUTE, + PROP_IP6_AUTO_DEFAULT_ROUTE, + PROP_LISTEN_PORT, + PROP_MTU, + PROP_PEER_ROUTES, + PROP_PRIVATE_KEY, + PROP_PRIVATE_KEY_FLAGS, ); + +typedef struct { + char * private_key; + GPtrArray * peers_arr; + GHashTable * peers_hash; + NMSettingSecretFlags private_key_flags; + NMTernary ip4_auto_default_route; + NMTernary ip6_auto_default_route; + guint32 fwmark; + guint32 mtu; + guint16 listen_port; + bool private_key_valid : 1; + bool peer_routes : 1; +} NMSettingWireGuardPrivate; + +/** + * NMSettingWireGuard: + * + * WireGuard Settings + * + * Since: 1.16 + */ +struct _NMSettingWireGuard { + NMSetting parent; + NMSettingWireGuardPrivate _priv; +}; + +struct _NMSettingWireGuardClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingWireGuard, nm_setting_wireguard, NM_TYPE_SETTING) + +#define NM_SETTING_WIREGUARD_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMSettingWireGuard, NM_IS_SETTING_WIREGUARD, NMSetting) + +/*****************************************************************************/ + +#define peers_psk_get_secret_name_a(public_key, to_free) \ + nm_construct_name_a(NM_SETTING_WIREGUARD_PEERS ".%s." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, \ + (public_key), \ + (to_free)) + +#define peers_psk_get_secret_name_dup(public_key) \ + g_strdup_printf(NM_SETTING_WIREGUARD_PEERS ".%s." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, \ + (public_key)) + +#define peers_psk_get_secret_parse_a(secret_public_key, public_key_free) \ + ({ \ + const char *_secret_public_key = (secret_public_key); \ + char ** _public_key_free = (public_key_free); \ + const char *_public_key = NULL; \ + \ + nm_assert(_public_key_free && !*_public_key_free); \ + \ + if (NM_STR_HAS_PREFIX(_secret_public_key, NM_SETTING_WIREGUARD_PEERS ".")) { \ + _secret_public_key += NM_STRLEN(NM_SETTING_WIREGUARD_PEERS "."); \ + if (NM_STR_HAS_SUFFIX(_secret_public_key, "." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { \ + _public_key = \ + nm_strndup_a(300, \ + _secret_public_key, \ + strlen(_secret_public_key) \ + - NM_STRLEN("." NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY), \ + _public_key_free); \ + } \ + } \ + \ + _public_key; \ + }) + +/*****************************************************************************/ + +/** + * nm_setting_wireguard_get_private_key: + * @self: the #NMSettingWireGuard instance + * + * Returns: (transfer none): the set private-key or %NULL. + * + * Since: 1.16 + */ +const char * +nm_setting_wireguard_get_private_key(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->private_key; +} + +/** + * nm_setting_wireguard_get_private_key_flags: + * @self: the #NMSettingWireGuard instance + * + * Returns: the secret-flags for #NMSettingWireGuard:private-key. + * + * Since: 1.16 + */ +NMSettingSecretFlags +nm_setting_wireguard_get_private_key_flags(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->private_key_flags; +} + +/** + * nm_setting_wireguard_get_fwmark: + * @self: the #NMSettingWireGuard instance + * + * Returns: the set firewall mark. + * + * Since: 1.16 + */ +guint32 +nm_setting_wireguard_get_fwmark(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->fwmark; +} + +/** + * nm_setting_wireguard_get_listen_port: + * @self: the #NMSettingWireGuard instance + * + * Returns: the set UDP listen port. + * + * Since: 1.16 + */ +guint16 +nm_setting_wireguard_get_listen_port(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->listen_port; +} + +/** + * nm_setting_wireguard_get_peer_routes: + * @self: the #NMSettingWireGuard instance + * + * Returns: whether automatically add peer routes. + * + * Since: 1.16 + */ +gboolean +nm_setting_wireguard_get_peer_routes(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), TRUE); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->peer_routes; +} + +/** + * nm_setting_wireguard_get_mtu: + * @self: the #NMSettingWireGuard instance + * + * Returns: the MTU of the setting. + * + * Since: 1.16 + */ +guint32 +nm_setting_wireguard_get_mtu(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->mtu; +} + +/** + * nm_setting_wireguard_get_ip4_auto_default_route: + * @self: the #NMSettingWireGuard setting. + * + * Returns: the "ip4-auto-default-route" property of the setting. + * + * Since: 1.20 + */ +NMTernary +nm_setting_wireguard_get_ip4_auto_default_route(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NM_TERNARY_DEFAULT); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->ip4_auto_default_route; +} + +/** + * nm_setting_wireguard_get_ip6_auto_default_route: + * @self: the #NMSettingWireGuard setting. + * + * Returns: the "ip6-auto-default-route" property of the setting. + * + * Since: 1.20 + */ +NMTernary +nm_setting_wireguard_get_ip6_auto_default_route(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NM_TERNARY_DEFAULT); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->ip6_auto_default_route; +} + +/*****************************************************************************/ + +static void +_peer_free(PeerData *pd) +{ + nm_assert(pd); + + nm_wireguard_peer_unref(pd->peer); + g_slice_free(PeerData, pd); +} + +/*****************************************************************************/ + +static void +_peers_notify(gpointer self) +{ + _nm_setting_emit_property_changed(self); +} + +static PeerData * +_peers_get(NMSettingWireGuardPrivate *priv, guint idx) +{ + PeerData *pd; + + nm_assert(priv); + nm_assert(idx < priv->peers_arr->len); + + pd = priv->peers_arr->pdata[idx]; + + nm_assert(pd); + nm_assert(pd->idx == idx); + nm_assert(NM_IS_WIREGUARD_PEER(pd->peer, TRUE)); + nm_assert(nm_wireguard_peer_is_sealed(pd->peer)); + nm_assert(pd->public_key == nm_wireguard_peer_get_public_key(pd->peer)); + nm_assert(g_hash_table_lookup(priv->peers_hash, pd) == pd); + + return pd; +} + +static PeerData * +_peers_get_by_public_key(NMSettingWireGuardPrivate *priv, + const char * public_key, + gboolean try_with_normalized_key) +{ + gs_free char *public_key_normalized = NULL; + PeerData * pd; + +again: + nm_assert(priv); + nm_assert(public_key); + + pd = g_hash_table_lookup(priv->peers_hash, &public_key); + if (pd) { + nm_assert(_peers_get(priv, pd->idx) == pd); + return pd; + } + if (try_with_normalized_key + && nm_utils_base64secret_normalize(public_key, + NM_WIREGUARD_PUBLIC_KEY_LEN, + &public_key_normalized)) { + public_key = public_key_normalized; + try_with_normalized_key = FALSE; + goto again; + } + return NULL; +} + +static void +_peers_remove(NMSettingWireGuardPrivate *priv, PeerData *pd, gboolean do_free) +{ + guint i; + + nm_assert(pd); + nm_assert(_peers_get(priv, pd->idx) == pd); + + for (i = pd->idx + 1; i < priv->peers_arr->len; i++) + _peers_get(priv, i)->idx--; + + g_ptr_array_remove_index(priv->peers_arr, pd->idx); + if (!g_hash_table_remove(priv->peers_hash, pd)) + nm_assert_not_reached(); + if (do_free) + _peer_free(pd); +} + +/** + * nm_setting_wireguard_get_peers_len: + * @self: the #NMSettingWireGuard instance + * + * Returns: the number of registered peers. + * + * Since: 1.16 + */ +guint +nm_setting_wireguard_get_peers_len(NMSettingWireGuard *self) +{ + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + return NM_SETTING_WIREGUARD_GET_PRIVATE(self)->peers_arr->len; +} + +/** + * nm_setting_wireguard_get_peer: + * @self: the #NMSettingWireGuard instance + * @idx: the index to lookup. + * + * Returns: (transfer none): the #NMWireGuardPeer entry at + * index @idx. If the index is out of range, %NULL is returned. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_setting_wireguard_get_peer(NMSettingWireGuard *self, guint idx) +{ + NMSettingWireGuardPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL); + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + if (idx >= priv->peers_arr->len) + return NULL; + + return _peers_get(priv, idx)->peer; +} + +/** + * nm_setting_wireguard_get_peer_by_public_key: + * @self: the #NMSettingWireGuard instance + * @public_key: the public key for looking up the + * peer. + * @out_idx: (out) (allow-none): optional output argument + * for the index of the found peer. If no index is found, + * this is set to the nm_setting_wireguard_get_peers_len(). + * + * Returns: (transfer none): the #NMWireGuardPeer instance with a + * matching public key. If no such peer exists, %NULL is returned. + * + * Since: 1.16 + */ +NMWireGuardPeer * +nm_setting_wireguard_get_peer_by_public_key(NMSettingWireGuard *self, + const char * public_key, + guint * out_idx) +{ + NMSettingWireGuardPrivate *priv; + PeerData * pd; + + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), NULL); + g_return_val_if_fail(public_key, NULL); + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + pd = _peers_get_by_public_key(priv, public_key, TRUE); + if (!pd) { + NM_SET_OUT(out_idx, priv->peers_arr->len); + return NULL; + } + NM_SET_OUT(out_idx, pd->idx); + return pd->peer; +} + +static gboolean +_peers_set(NMSettingWireGuardPrivate *priv, + NMWireGuardPeer * peer, + guint idx, + gboolean check_same_key) +{ + PeerData * pd_same_key = NULL; + PeerData * pd_idx = NULL; + const char *public_key; + + nm_assert(idx <= priv->peers_arr->len); + + public_key = nm_wireguard_peer_get_public_key(peer); + + if (idx < priv->peers_arr->len) { + pd_idx = _peers_get(priv, idx); + + if (pd_idx->peer == peer) + return FALSE; + + if (check_same_key && nm_streq(public_key, nm_wireguard_peer_get_public_key(pd_idx->peer))) + check_same_key = FALSE; + } + + nm_wireguard_peer_seal(peer); + nm_wireguard_peer_ref(peer); + + if (check_same_key) { + pd_same_key = _peers_get_by_public_key(priv, public_key, FALSE); + if (pd_same_key) { + if (pd_idx) { + nm_assert(pd_same_key != pd_idx); + _peers_remove(priv, pd_same_key, TRUE); + pd_same_key = NULL; + } else { + if (pd_same_key->peer == peer && pd_same_key->idx == priv->peers_arr->len - 1) { + nm_wireguard_peer_unref(peer); + return FALSE; + } + _peers_remove(priv, pd_same_key, FALSE); + nm_wireguard_peer_unref(pd_same_key->peer); + } + } + } else + nm_assert(_peers_get_by_public_key(priv, public_key, FALSE) == pd_idx); + + if (pd_idx) { + g_hash_table_remove(priv->peers_hash, pd_idx); + nm_wireguard_peer_unref(pd_idx->peer); + pd_idx->public_key = public_key; + pd_idx->peer = peer; + g_hash_table_add(priv->peers_hash, pd_idx); + return TRUE; + } + + if (!pd_same_key) + pd_same_key = g_slice_new(PeerData); + + *pd_same_key = (PeerData){ + .peer = peer, + .public_key = public_key, + .idx = priv->peers_arr->len, + }; + + g_ptr_array_add(priv->peers_arr, pd_same_key); + if (!nm_g_hash_table_add(priv->peers_hash, pd_same_key)) + nm_assert_not_reached(); + + nm_assert(_peers_get(priv, pd_same_key->idx) == pd_same_key); + + return TRUE; +} + +static gboolean +_peers_append(NMSettingWireGuardPrivate *priv, NMWireGuardPeer *peer, gboolean check_same_key) +{ + return _peers_set(priv, peer, priv->peers_arr->len, check_same_key); +} + +/** + * nm_setting_wireguard_set_peer: + * @self: the #NMSettingWireGuard instance + * @peer: the #NMWireGuardPeer instance to set. + * This seals @peer and keeps a reference on the + * instance. + * @idx: the index, in the range of 0 to the number of + * peers (including). That means, if @idx is one past + * the end of the number of peers, this is the same as + * nm_setting_wireguard_append_peer(). Otherwise, the + * peer at this index is replaced. + * + * If @idx is one past the last peer, the behavior is the same + * as nm_setting_wireguard_append_peer(). + * Otherwise, the peer will be at @idx and replace the peer + * instance at that index. Note that if a peer with the same + * public-key exists on another index, then that peer will also + * be replaced. In that case, the number of peers will shrink + * by one (because the one at @idx got replace and then one + * with the same public-key got removed). This also means, + * that the resulting index afterwards may be one less than + * @idx (if another peer with a lower index was dropped). + * + * Since: 1.16 + */ +void +nm_setting_wireguard_set_peer(NMSettingWireGuard *self, NMWireGuardPeer *peer, guint idx) +{ + NMSettingWireGuardPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_WIREGUARD(self)); + g_return_if_fail(NM_IS_WIREGUARD_PEER(peer, TRUE)); + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + g_return_if_fail(idx <= priv->peers_arr->len); + + if (_peers_set(priv, peer, idx, TRUE)) + _peers_notify(self); +} + +/** + * nm_setting_wireguard_append_peer: + * @self: the #NMSettingWireGuard instance + * @peer: the #NMWireGuardPeer instance to append. + * This seals @peer and keeps a reference on the + * instance. + * + * If a peer with the same public-key already exists, that + * one is replaced by @peer. The new @peer is always appended + * (or moved to) the end, so in case a peer is replaced, the + * indexes are shifted and the number of peers stays unchanged. + * + * Since: 1.16 + */ +void +nm_setting_wireguard_append_peer(NMSettingWireGuard *self, NMWireGuardPeer *peer) +{ + g_return_if_fail(NM_IS_SETTING_WIREGUARD(self)); + g_return_if_fail(NM_IS_WIREGUARD_PEER(peer, TRUE)); + + if (_peers_append(NM_SETTING_WIREGUARD_GET_PRIVATE(self), peer, TRUE)) + _peers_notify(self); +} + +/** + * nm_setting_wireguard_remove_peer + * @self: the #NMSettingWireGuard instance + * @idx: the index to remove. + * + * Returns: %TRUE if @idx was in range and a peer + * was removed. Otherwise, @self is unchanged. + * + * Since: 1.16 + */ +gboolean +nm_setting_wireguard_remove_peer(NMSettingWireGuard *self, guint idx) +{ + NMSettingWireGuardPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), FALSE); + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + if (idx >= priv->peers_arr->len) + return FALSE; + + _peers_remove(priv, _peers_get(priv, idx), TRUE); + _peers_notify(self); + return TRUE; +} + +static guint +_peers_clear(NMSettingWireGuardPrivate *priv) +{ + guint l; + + l = priv->peers_arr->len; + while (priv->peers_arr->len > 0) { + _peers_remove(priv, _peers_get(priv, priv->peers_arr->len - 1), TRUE); + } + return l; +} + +/** + * nm_setting_wireguard_: + * @self: the #NMSettingWireGuard instance + * + * Returns: the number of cleared peers. + * + * Since: 1.16 + */ +guint +nm_setting_wireguard_clear_peers(NMSettingWireGuard *self) +{ + guint l; + + g_return_val_if_fail(NM_IS_SETTING_WIREGUARD(self), 0); + + l = _peers_clear(NM_SETTING_WIREGUARD_GET_PRIVATE(self)); + if (l > 0) + _peers_notify(self); + return l; +} + +/*****************************************************************************/ + +static GVariant * +_peers_dbus_only_synth(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMSettingWireGuard * self = NM_SETTING_WIREGUARD(setting); + NMSettingWireGuardPrivate *priv; + gboolean any_peers = FALSE; + GVariantBuilder peers_builder; + guint i_peer, n_peers; + guint i; + + n_peers = nm_setting_wireguard_get_peers_len(self); + if (n_peers == 0) + return NULL; + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + for (i_peer = 0; i_peer < n_peers; i_peer++) { + const NMWireGuardPeer *peer = _peers_get(priv, i_peer)->peer; + GVariantBuilder builder; + + if (!peer->public_key) + continue; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, + g_variant_new_string(peer->public_key)); + + if (_nm_connection_serialize_non_secret(flags) && peer->endpoint) + g_variant_builder_add( + &builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_ENDPOINT, + g_variant_new_string(nm_sock_addr_endpoint_get_endpoint(peer->endpoint))); + + if (_nm_connection_serialize_secrets(flags, peer->preshared_key_flags) + && peer->preshared_key) + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, + g_variant_new_string(peer->preshared_key)); + + if (_nm_connection_serialize_non_secret(flags) + && peer->preshared_key_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED) + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, + g_variant_new_uint32(peer->preshared_key_flags)); + + if (_nm_connection_serialize_non_secret(flags) && peer->persistent_keepalive != 0) + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, + g_variant_new_uint32(peer->persistent_keepalive)); + + if (_nm_connection_serialize_non_secret(flags) && peer->allowed_ips + && peer->allowed_ips->len > 0) { + const char *const * strv = (const char *const *) peer->allowed_ips->pdata; + gs_free const char **strv_fixed = NULL; + + for (i = 0; i < peer->allowed_ips->len; i++) { + /* This is a bit odd. The original peer->allowed_ips is a strv array + * of all allowed-ips, but some of them may have an ALLOWED_IP_INVALID_X prefix + * (as first character). + * + * When we serialize the array to GVariant, we need to get rid of this + * character, by pointing to the second character. + * + * In that case, first clone the strv array to strv_fixed. Once we cloned + * it, adjust the pointer to drop the first character. */ + if (strv[i][0] != ALLOWED_IP_INVALID_X) { + /* this string is valid, no need for adjustment. */ + continue; + } + if (!strv_fixed) { + strv_fixed = nm_memdup(strv, sizeof(strv[0]) * peer->allowed_ips->len); + strv = strv_fixed; + } + ((const char **) strv)[i]++; + } + g_variant_builder_add(&builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, + g_variant_new_strv(strv, peer->allowed_ips->len)); + } + + if (!any_peers) { + g_variant_builder_init(&peers_builder, G_VARIANT_TYPE("aa{sv}")); + any_peers = TRUE; + } + g_variant_builder_add(&peers_builder, "a{sv}", &builder); + } + + return any_peers ? g_variant_builder_end(&peers_builder) : NULL; +} + +static gboolean +_peers_dbus_only_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + GVariantIter iter_peers; + GVariant * peer_var; + guint i_peer; + gboolean success = FALSE; + gboolean peers_changed = FALSE; + + nm_assert(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}"))); + + g_variant_iter_init(&iter_peers, value); + + i_peer = 0; + while (g_variant_iter_next(&iter_peers, "@a{sv}", &peer_var)) { + _nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + const char * cstr; + guint32 u32; + GVariant * var; + + i_peer++; + + if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u has no public-key"), + i_peer); + goto out; + } + continue; + } + + peer = nm_wireguard_peer_new(); + if (!nm_wireguard_peer_set_public_key(peer, cstr, TRUE)) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u has invalid public-key"), + i_peer); + goto out; + } + continue; + } + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "&s", &cstr)) { + nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL; + + ep = nm_sock_addr_endpoint_new(cstr); + if (!nm_sock_addr_endpoint_get_host(ep)) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u has invalid endpoint"), + i_peer); + goto out; + } + } else + _nm_wireguard_peer_set_endpoint(peer, ep); + } + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr)) + nm_wireguard_peer_set_preshared_key(peer, cstr, TRUE); + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, "u", &u32)) + nm_wireguard_peer_set_preshared_key_flags(peer, u32); + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, "u", &u32)) + nm_wireguard_peer_set_persistent_keepalive(peer, u32); + + if (g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, "@as", &var)) { + _nm_unused gs_unref_variant GVariant *var_free = var; + gs_free const char ** allowed_ips = NULL; + gsize i, l; + + allowed_ips = g_variant_get_strv(var, &l); + if (allowed_ips) { + for (i = 0; i < l; i++) { + if (_peer_append_allowed_ip(peer, allowed_ips[i], FALSE)) + continue; + if (!NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) + continue; + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u has invalid allowed-ips setting"), + i_peer); + goto out; + } + } + } + + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + gs_free_error GError *local = NULL; + + if (!nm_wireguard_peer_is_valid(peer, TRUE, FALSE, &local)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("peer #%u is invalid: %s"), + i_peer, + local->message); + goto out; + } + } + + /* we could easily reject duplicate peers (by public-key) or duplicate GVariant attributes. + * However, don't do that. In case of duplicate values, the latter peer overwrite the earlier + * and GVariant attributes are ignored by g_variant_lookup() above. */ + if (_peers_append(NM_SETTING_WIREGUARD_GET_PRIVATE(setting), peer, TRUE)) + peers_changed = TRUE; + } + + success = TRUE; + +out: + if (peers_changed) + _peers_notify(setting); + return success; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWireGuard * s_wg = NM_SETTING_WIREGUARD(setting); + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + guint i; + + if (!_nm_connection_verify_required_interface_name(connection, error)) + return FALSE; + + if (!_nm_utils_secret_flags_validate(nm_setting_wireguard_get_private_key_flags(s_wg), + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_REQUIRED, + error)) + return FALSE; + + for (i = 0; i < priv->peers_arr->len; i++) { + NMWireGuardPeer *peer = _peers_get(priv, i)->peer; + + if (!nm_wireguard_peer_is_valid(peer, TRUE, FALSE, error)) { + g_prefix_error(error, + "%s.%s[%u]: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS, + i); + return FALSE; + } + } + + if (connection) { + NMSettingIPConfig *s_ip4; + NMSettingIPConfig *s_ip6; + const char * method; + + /* WireGuard is Layer 3 only. For the moment, we only support a restricted set of + * IP methods. We may relax that later, once we fix the implementations so they + * actually work. */ + + if ((s_ip4 = nm_connection_get_setting_ip4_config(connection)) + && (method = nm_setting_ip_config_get_method(s_ip4)) + && !NM_IN_STRSET(method, + NM_SETTING_IP4_CONFIG_METHOD_DISABLED, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("method \"%s\" is not supported for WireGuard"), + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + return FALSE; + } + + if ((s_ip6 = nm_connection_get_setting_ip6_config(connection)) + && (method = nm_setting_ip_config_get_method(s_ip6)) + && !NM_IN_STRSET(method, + NM_SETTING_IP6_CONFIG_METHOD_IGNORE, + NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("method \"%s\" is not supported for WireGuard"), + method); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_METHOD); + return FALSE; + } + } + + /* private-key is a secret, hence we cannot verify it like a regular property. */ + return TRUE; +} + +static gboolean +verify_secrets(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + guint i; + + if (priv->private_key && !priv->private_key_valid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("key must be 32 bytes base64 encoded")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PRIVATE_KEY); + return FALSE; + } + + for (i = 0; i < priv->peers_arr->len; i++) { + NMWireGuardPeer *peer = _peers_get(priv, i)->peer; + + if (!nm_wireguard_peer_is_valid(peer, FALSE, TRUE, error)) { + g_prefix_error(error, + "%s.%s[%u]: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS, + i); + return FALSE; + } + } + + return TRUE; +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + GPtrArray * secrets = NULL; + guint i; + + if (!priv->private_key || !priv->private_key_valid) { + secrets = g_ptr_array_new_full(1, g_free); + g_ptr_array_add(secrets, g_strdup(NM_SETTING_WIREGUARD_PRIVATE_KEY)); + } + + for (i = 0; i < priv->peers_arr->len; i++) { + NMWireGuardPeer *peer = _peers_get(priv, i)->peer; + + if (NM_FLAGS_HAS(peer->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) + continue; + + if (peer->preshared_key_valid) + continue; + + if (!peer->public_key_valid) + continue; + + if (!secrets) + secrets = g_ptr_array_new_full(1, g_free); + g_ptr_array_add(secrets, peers_psk_get_secret_name_dup(peer->public_key)); + } + + return secrets; +} + +static gboolean +clear_secrets(const NMSettInfoSetting * sett_info, + guint property_idx, + NMSetting * setting, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data) +{ + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) { + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + gboolean peers_changed = FALSE; + guint i, j; + + j = 0; + for (i = 0; i < priv->peers_arr->len; i++) { + NMWireGuardPeer *peer = _peers_get(priv, i)->peer; + + if (!peer->preshared_key) + continue; + + if (func) { + gs_free char *name_free = NULL; + const char * name; + + if (j++ < 5) { + /* we use alloca() inside a loop here, but it is guarded to happen at most + * a few times. */ + name = peers_psk_get_secret_name_a(/* lgtm [cpp/alloca-in-loop] */ + peer->public_key, + &name_free); + } else { + name_free = peers_psk_get_secret_name_dup(peer->public_key); + name = name_free; + } + + if (!func(setting, name, peer->preshared_key_flags, user_data)) + continue; + } + + { + nm_auto_unref_wgpeer NMWireGuardPeer *peer2 = NULL; + + peer2 = nm_wireguard_peer_new_clone(peer, FALSE); + + if (_peers_set(priv, peer2, i, FALSE)) + peers_changed = TRUE; + } + } + + if (peers_changed) + _peers_notify(setting); + return peers_changed; + } + + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->clear_secrets(sett_info, property_idx, setting, func, user_data); +} + +static int +update_one_secret(NMSetting *setting, const char *key, GVariant *value, GError **error) +{ + NMSettingWireGuard * self = NM_SETTING_WIREGUARD(setting); + NMSettingWireGuardPrivate *priv; + gboolean has_changes = FALSE; + gboolean has_error = FALSE; + GVariantIter iter_peers; + GVariant * peer_var; + guint i_peer; + + if (!nm_streq(key, NM_SETTING_WIREGUARD_PEERS)) { + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->update_one_secret(setting, key, value, error); + } + + if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}"))) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("invalid peer secrets")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS); + return NM_SETTING_UPDATE_SECRET_ERROR; + } + + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + + g_variant_iter_init(&iter_peers, value); + + i_peer = 0; + while (g_variant_iter_next(&iter_peers, "@a{sv}", &peer_var)) { + _nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var; + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + PeerData * pd; + const char * cstr; + + i_peer++; + + if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) { + if (!has_error) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("peer #%u lacks public-key"), + i_peer - 1); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS); + has_error = TRUE; + } + continue; + } + + pd = _peers_get_by_public_key(priv, cstr, TRUE); + if (!pd) { + if (!has_error) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET, + _("non-existing peer '%s'"), + cstr); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIREGUARD_SETTING_NAME, + NM_SETTING_WIREGUARD_PEERS); + has_error = TRUE; + } + continue; + } + + if (!g_variant_lookup(peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr)) { + /* no preshared-key. Ignore the rest. + * + * In particular, we don't reject all unknown fields. */ + continue; + } + + if (nm_streq0(cstr, nm_wireguard_peer_get_preshared_key(pd->peer))) + continue; + + peer = nm_wireguard_peer_new_clone(pd->peer, FALSE); + nm_wireguard_peer_set_preshared_key(peer, cstr, TRUE); + + if (!_peers_set(priv, peer, pd->idx, FALSE)) + nm_assert_not_reached(); + has_changes = TRUE; + } + + if (has_error) + return NM_SETTING_UPDATE_SECRET_ERROR; + if (has_changes) + return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED; + return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMSettingWireGuardPrivate *a_priv; + NMSettingWireGuardPrivate *b_priv; + guint i; + + if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) { + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)) + return NM_TERNARY_DEFAULT; + + if (!set_b) + return TRUE; + + a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE(set_a); + b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE(set_b); + + if (a_priv->peers_arr->len != b_priv->peers_arr->len) + return FALSE; + for (i = 0; i < a_priv->peers_arr->len; i++) { + NMWireGuardPeer *a_peer = _peers_get(a_priv, i)->peer; + NMWireGuardPeer *b_peer = _peers_get(b_priv, i)->peer; + + if (nm_wireguard_peer_cmp(a_peer, b_peer, flags) != 0) + return FALSE; + } + + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +static void +duplicate_copy_properties(const NMSettInfoSetting *sett_info, NMSetting *src, NMSetting *dst) +{ + NMSettingWireGuardPrivate *priv_src = NM_SETTING_WIREGUARD_GET_PRIVATE(src); + NMSettingWireGuardPrivate *priv_dst = NM_SETTING_WIREGUARD_GET_PRIVATE(dst); + guint i; + gboolean peers_changed = FALSE; + + NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->duplicate_copy_properties(sett_info, src, dst); + + /* We don't bother comparing the existing peers with what we are about to set. + * Always reset all. */ + if (_peers_clear(priv_dst) > 0) + peers_changed = TRUE; + for (i = 0; i < priv_src->peers_arr->len; i++) { + if (_peers_append(priv_dst, _peers_get(priv_src, i)->peer, FALSE)) + peers_changed = TRUE; + } + if (peers_changed) + _peers_notify(dst); +} + +static void +enumerate_values(const NMSettInfoProperty *property_info, + NMSetting * setting, + NMSettingValueIterFn func, + gpointer user_data) +{ + if (nm_streq(property_info->name, NM_SETTING_WIREGUARD_PEERS)) { + NMSettingWireGuardPrivate * priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + nm_auto_unset_gvalue GValue value = G_VALUE_INIT; + GPtrArray * ptr = NULL; + guint i; + + if (priv->peers_arr && priv->peers_arr->len > 0) { + ptr = g_ptr_array_new_with_free_func((GDestroyNotify) nm_wireguard_peer_unref); + for (i = 0; i < priv->peers_arr->len; i++) + g_ptr_array_add(ptr, nm_wireguard_peer_ref(_peers_get(priv, i)->peer)); + } + g_value_init(&value, G_TYPE_PTR_ARRAY); + g_value_take_boxed(&value, ptr); + func(setting, property_info->name, &value, 0, user_data); + return; + } + + NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->enumerate_values(property_info, setting, func, user_data); +} + +static gboolean +aggregate(NMSetting *setting, int type_i, gpointer arg) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + NMConnectionAggregateType type = type_i; + NMSettingSecretFlags secret_flags; + guint i; + + nm_assert(NM_IN_SET(type, + NM_CONNECTION_AGGREGATE_ANY_SECRETS, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS)); + + switch (type) { + case NM_CONNECTION_AGGREGATE_ANY_SECRETS: + if (priv->private_key) + goto out_done; + for (i = 0; i < priv->peers_arr->len; i++) { + if (nm_wireguard_peer_get_preshared_key(_peers_get(priv, i)->peer)) + goto out_done; + } + break; + + case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS: +#if NM_MORE_ASSERTS + if (!nm_setting_get_secret_flags(setting, + NM_SETTING_WIREGUARD_PRIVATE_KEY, + &secret_flags, + NULL)) + nm_assert_not_reached(); + nm_assert(secret_flags == priv->private_key_flags); +#endif + if (priv->private_key_flags == NM_SETTING_SECRET_FLAG_NONE) + goto out_done; + for (i = 0; i < priv->peers_arr->len; i++) { + secret_flags = nm_wireguard_peer_get_preshared_key_flags(_peers_get(priv, i)->peer); + if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) + goto out_done; + } + break; + } + + return FALSE; + +out_done: + *((gboolean *) arg) = TRUE; + return TRUE; +} + +static gboolean +get_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags *out_flags, + GError ** error) +{ + if (NM_STR_HAS_PREFIX(secret_name, NM_SETTING_WIREGUARD_PEERS ".")) { + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + gs_free char * public_key_free = NULL; + const char * public_key; + PeerData * pd; + + public_key = peers_psk_get_secret_parse_a(secret_name, &public_key_free); + if (public_key && (pd = _peers_get_by_public_key(priv, public_key, FALSE))) { + NM_SET_OUT(out_flags, nm_wireguard_peer_get_preshared_key_flags(pd->peer)); + return TRUE; + } + } + + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->get_secret_flags(setting, secret_name, out_flags, error); +} + +static gboolean +set_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags flags, + GError ** error) +{ + if (NM_STR_HAS_PREFIX(secret_name, NM_SETTING_WIREGUARD_PEERS ".")) { + NMSettingWireGuard * self = NM_SETTING_WIREGUARD(setting); + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(self); + gs_free char * public_key_free = NULL; + const char * public_key; + PeerData * pd; + + public_key = peers_psk_get_secret_parse_a(secret_name, &public_key_free); + if (public_key && (pd = _peers_get_by_public_key(priv, public_key, FALSE))) { + if (nm_wireguard_peer_get_preshared_key_flags(pd->peer) != flags) { + nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL; + + peer = nm_wireguard_peer_new_clone(pd->peer, TRUE); + peer->preshared_key_flags = flags; + if (_peers_set(priv, peer, pd->idx, FALSE)) + _peers_notify(self); + } + + return TRUE; + } + } + + return NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->set_secret_flags(setting, secret_name, flags, error); +} + +static void +for_each_secret(NMSetting * setting, + const char * data_key, + GVariant * data_val, + gboolean remove_non_secrets, + _NMConnectionForEachSecretFunc callback, + gpointer callback_data, + GVariantBuilder * setting_builder) +{ + NMSettingWireGuard * s_wg; + NMSettingWireGuardPrivate *priv; + GVariantBuilder peers_builder; + GVariantIter * peer_iter; + GVariantIter data_iter; + const char * key; + + if (!nm_streq(data_key, NM_SETTING_WIREGUARD_PEERS)) { + NM_SETTING_CLASS(nm_setting_wireguard_parent_class) + ->for_each_secret(setting, + data_key, + data_val, + remove_non_secrets, + callback, + callback_data, + setting_builder); + return; + } + + if (!g_variant_is_of_type(data_val, G_VARIANT_TYPE("aa{sv}"))) { + /* invalid type. Silently ignore content as we cannot find secret-keys + * here. */ + return; + } + + s_wg = NM_SETTING_WIREGUARD(setting); + priv = NM_SETTING_WIREGUARD_GET_PRIVATE(s_wg); + + g_variant_builder_init(&peers_builder, G_VARIANT_TYPE("aa{sv}")); + g_variant_iter_init(&data_iter, data_val); + while (g_variant_iter_next(&data_iter, "a{sv}", &peer_iter)) { + _nm_unused nm_auto_free_variant_iter GVariantIter *peer_iter_free = peer_iter; + gs_unref_variant GVariant *preshared_key = NULL; + PeerData * pd = NULL; + NMSettingSecretFlags secret_flags; + GVariant * val; + GVariantBuilder peer_builder; + + g_variant_builder_init(&peer_builder, G_VARIANT_TYPE("a{sv}")); + + while (g_variant_iter_next(peer_iter, "{&sv}", &key, &val)) { + _nm_unused gs_unref_variant GVariant *val_free = val; + + if (nm_streq(key, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { + if (!preshared_key && g_variant_is_of_type(val, G_VARIANT_TYPE_STRING)) + preshared_key = g_variant_ref(val); + continue; + } + + if (nm_streq(key, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY)) { + if (!pd && g_variant_is_of_type(val, G_VARIANT_TYPE_STRING)) + pd = _peers_get_by_public_key(priv, g_variant_get_string(val, NULL), TRUE); + } else if (remove_non_secrets) + continue; + + g_variant_builder_add(&peer_builder, "{sv}", key, val); + } + + if (pd && preshared_key) { + /* without specifying a public-key of an existing peer, the secret is + * ignored. */ + secret_flags = nm_wireguard_peer_get_preshared_key_flags(pd->peer); + if (callback(secret_flags, callback_data)) + g_variant_builder_add(&peer_builder, + "{sv}", + NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, + preshared_key); + } + + g_variant_builder_add(&peers_builder, "a{sv}", &peer_builder); + } + + g_variant_builder_add(setting_builder, + "{sv}", + NM_SETTING_WIREGUARD_PEERS, + g_variant_builder_end(&peers_builder)); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingWireGuard * setting = NM_SETTING_WIREGUARD(object); + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_FWMARK: + g_value_set_uint(value, priv->fwmark); + break; + case PROP_IP4_AUTO_DEFAULT_ROUTE: + g_value_set_enum(value, priv->ip4_auto_default_route); + break; + case PROP_IP6_AUTO_DEFAULT_ROUTE: + g_value_set_enum(value, priv->ip6_auto_default_route); + break; + case PROP_LISTEN_PORT: + g_value_set_uint(value, priv->listen_port); + break; + case PROP_MTU: + g_value_set_uint(value, priv->mtu); + break; + case PROP_PEER_ROUTES: + g_value_set_boolean(value, priv->peer_routes); + break; + case PROP_PRIVATE_KEY: + g_value_set_string(value, priv->private_key); + break; + case PROP_PRIVATE_KEY_FLAGS: + g_value_set_flags(value, priv->private_key_flags); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(object); + const char * str; + + switch (prop_id) { + case PROP_FWMARK: + priv->fwmark = g_value_get_uint(value); + break; + case PROP_IP4_AUTO_DEFAULT_ROUTE: + priv->ip4_auto_default_route = g_value_get_enum(value); + break; + case PROP_IP6_AUTO_DEFAULT_ROUTE: + priv->ip6_auto_default_route = g_value_get_enum(value); + break; + case PROP_LISTEN_PORT: + priv->listen_port = g_value_get_uint(value); + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + case PROP_PEER_ROUTES: + priv->peer_routes = g_value_get_boolean(value); + break; + case PROP_PRIVATE_KEY: + nm_clear_pointer(&priv->private_key, nm_free_secret); + str = g_value_get_string(value); + if (str) { + if (nm_utils_base64secret_normalize(str, + NM_WIREGUARD_PUBLIC_KEY_LEN, + &priv->private_key)) + priv->private_key_valid = TRUE; + else { + priv->private_key = g_strdup(str); + priv->private_key_valid = FALSE; + } + } + break; + case PROP_PRIVATE_KEY_FLAGS: + priv->private_key_flags = g_value_get_flags(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_wireguard_init(NMSettingWireGuard *setting) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(setting); + + priv->peers_arr = g_ptr_array_new(); + priv->peers_hash = g_hash_table_new(nm_pstr_hash, nm_pstr_equal); + priv->peer_routes = TRUE; + priv->ip4_auto_default_route = NM_TERNARY_DEFAULT; + priv->ip6_auto_default_route = NM_TERNARY_DEFAULT; +} + +/** + * nm_setting_wireguard_new: + * + * Creates a new #NMSettingWireGuard object with default values. + * + * Returns: (transfer full): the new empty #NMSettingWireGuard object + * + * Since: 1.16 + **/ +NMSetting * +nm_setting_wireguard_new(void) +{ + return g_object_new(NM_TYPE_SETTING_WIREGUARD, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE(object); + + nm_free_secret(priv->private_key); + + _peers_clear(priv); + g_ptr_array_unref(priv->peers_arr); + g_hash_table_unref(priv->peers_hash); + + G_OBJECT_CLASS(nm_setting_wireguard_parent_class)->finalize(object); +} + +static void +nm_setting_wireguard_class_init(NMSettingWireGuardClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->verify_secrets = verify_secrets; + setting_class->need_secrets = need_secrets; + setting_class->clear_secrets = clear_secrets; + setting_class->update_one_secret = update_one_secret; + setting_class->compare_property = compare_property; + setting_class->duplicate_copy_properties = duplicate_copy_properties; + setting_class->enumerate_values = enumerate_values; + setting_class->aggregate = aggregate; + setting_class->get_secret_flags = get_secret_flags; + setting_class->set_secret_flags = set_secret_flags; + setting_class->for_each_secret = for_each_secret; + + /** + * NMSettingWireGuard:private-key: + * + * The 256 bit private-key in base64 encoding. + * + * Since: 1.16 + **/ + obj_properties[PROP_PRIVATE_KEY] = + g_param_spec_string(NM_SETTING_WIREGUARD_PRIVATE_KEY, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:private-key-flags: + * + * Flags indicating how to handle the #NMSettingWirelessSecurity:private-key + * property. + * + * Since: 1.16 + **/ + obj_properties[PROP_PRIVATE_KEY_FLAGS] = + g_param_spec_flags(NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:fwmark: + * + * The use of fwmark is optional and is by default off. Setting it to 0 + * disables it. Otherwise, it is a 32-bit fwmark for outgoing packets. + * + * Note that "ip4-auto-default-route" or "ip6-auto-default-route" enabled, + * implies to automatically choose a fwmark. + * + * Since: 1.16 + **/ + obj_properties[PROP_FWMARK] = + g_param_spec_uint(NM_SETTING_WIREGUARD_FWMARK, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:listen-port: + * + * The listen-port. If listen-port is not specified, the port will be chosen + * randomly when the interface comes up. + * + * Since: 1.16 + **/ + obj_properties[PROP_LISTEN_PORT] = + g_param_spec_uint(NM_SETTING_WIREGUARD_LISTEN_PORT, + "", + "", + 0, + 65535, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:peer-routes: + * + * Whether to automatically add routes for the AllowedIPs ranges + * of the peers. If %TRUE (the default), NetworkManager will automatically + * add routes in the routing tables according to ipv4.route-table and + * ipv6.route-table. Usually you want this automatism enabled. + * If %FALSE, no such routes are added automatically. In this case, the + * user may want to configure static routes in ipv4.routes and ipv6.routes, + * respectively. + * + * Note that if the peer's AllowedIPs is "0.0.0.0/0" or "::/0" and the profile's + * ipv4.never-default or ipv6.never-default setting is enabled, the peer route for + * this peer won't be added automatically. + * + * Since: 1.16 + **/ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_WIREGUARD_PEER_ROUTES, + PROP_PEER_ROUTES, + TRUE, + NM_SETTING_PARAM_INFERRABLE, + nm_setting_wireguard_get_peer_routes); + + /** + * NMSettingWireGuard:mtu: + * + * If non-zero, only transmit packets of the specified size or smaller, + * breaking larger packets up into multiple fragments. + * + * If zero a default MTU is used. Note that contrary to wg-quick's MTU + * setting, this does not take into account the current routes at the + * time of activation. + * + * Since: 1.16 + **/ + obj_properties[PROP_MTU] = + g_param_spec_uint(NM_SETTING_WIREGUARD_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:ip4-auto-default-route: + * + * Whether to enable special handling of the IPv4 default route. + * If enabled, the IPv4 default route from wireguard.peer-routes + * will be placed to a dedicated routing-table and two policy routing rules + * will be added. The fwmark number is also used as routing-table for the default-route, + * and if fwmark is zero, an unused fwmark/table is chosen automatically. + * This corresponds to what wg-quick does with Table=auto and what WireGuard + * calls "Improved Rule-based Routing". + * + * Note that for this automatism to work, you usually don't want to set + * ipv4.gateway, because that will result in a conflicting default route. + * + * Leaving this at the default will enable this option automatically + * if ipv4.never-default is not set and there are any peers that use + * a default-route as allowed-ips. + * + * Since: 1.20 + **/ + obj_properties[PROP_IP4_AUTO_DEFAULT_ROUTE] = g_param_spec_enum( + NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireGuard:ip6-auto-default-route: + * + * Like ip4-auto-default-route, but for the IPv6 default route. + * + * Since: 1.20 + **/ + obj_properties[PROP_IP6_AUTO_DEFAULT_ROUTE] = g_param_spec_enum( + NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /* ---dbus--- + * property: peers + * format: array of 'a{sv}' + * description: Array of dictionaries for the WireGuard peers. + * ---end--- + */ + _nm_properties_override_dbus( + properties_override, + NM_SETTING_WIREGUARD_PEERS, + NM_SETT_INFO_PROPERT_TYPE_DBUS(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = _peers_dbus_only_synth, + .from_dbus_fcn = _peers_dbus_only_set, )); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_WIREGUARD, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-wireless-security.c b/src/libnm-core-impl/nm-setting-wireless-security.c new file mode 100644 index 00000000..9e3c8d81 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-wireless-security.c @@ -0,0 +1,2000 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2017 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-wireless-security.h" + +#include "nm-setting-8021x.h" +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" +#include "nm-setting-wireless.h" +#include "libnm-glib-aux/nm-secret-utils.h" + +/** + * SECTION:nm-setting-wireless-security + * @short_description: Describes connection properties for Wi-Fi networks that + * use WEP, LEAP, WPA or WPA2/RSN security + * + * The #NMSettingWirelessSecurity object is a #NMSetting subclass that describes + * properties necessary for connection to encrypted Wi-Fi networks. + * + * It's a good idea to read up on wpa_supplicant configuration before using this + * setting extensively, since most of the options here correspond closely with + * the relevant wpa_supplicant configuration options. To get a better overview + * of how Wi-Fi security works, you may want to get copies of the following books. + * + * 802.11 Wireless Networks: The Definitive Guide, Second Edition + * Author: Matthew Gast + * ISBN: 978-0596100520 + * + * Cisco Wireless LAN Security + * Authors: Krishna Sankar, Sri Sundaralingam, Darrin Miller, and Andrew Balinsky + * ISBN: 978-1587051548 + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingWirelessSecurity, + PROP_KEY_MGMT, + PROP_WEP_TX_KEYIDX, + PROP_AUTH_ALG, + PROP_PROTO, + PROP_PAIRWISE, + PROP_GROUP, + PROP_PMF, + PROP_LEAP_USERNAME, + PROP_WEP_KEY0, + PROP_WEP_KEY1, + PROP_WEP_KEY2, + PROP_WEP_KEY3, + PROP_WEP_KEY_FLAGS, + PROP_WEP_KEY_TYPE, + PROP_PSK, + PROP_PSK_FLAGS, + PROP_LEAP_PASSWORD, + PROP_LEAP_PASSWORD_FLAGS, + PROP_WPS_METHOD, + PROP_FILS, ); + +typedef struct { + GSList * proto; /* GSList of strings */ + GSList * pairwise; /* GSList of strings */ + GSList * group; /* GSList of strings */ + char * key_mgmt; + char * auth_alg; + char * leap_username; + char * leap_password; + char * wep_key0; + char * wep_key1; + char * wep_key2; + char * wep_key3; + char * psk; + NMSettingSecretFlags leap_password_flags; + NMSettingSecretFlags wep_key_flags; + NMSettingSecretFlags psk_flags; + NMSettingWirelessSecurityPmf pmf; + NMWepKeyType wep_key_type; + NMSettingWirelessSecurityWpsMethod wps_method; + NMSettingWirelessSecurityFils fils; + guint32 wep_tx_keyidx; +} NMSettingWirelessSecurityPrivate; + +/** + * NMSettingWirelessSecurity: + * + * Wi-Fi Security Settings + */ +struct _NMSettingWirelessSecurity { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingWirelessSecurityClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingWirelessSecurity, nm_setting_wireless_security, NM_TYPE_SETTING) + +#define NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), \ + NM_TYPE_SETTING_WIRELESS_SECURITY, \ + NMSettingWirelessSecurityPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_wireless_security_get_key_mgmt: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:key-mgmt property of the setting + **/ +const char * +nm_setting_wireless_security_get_key_mgmt(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->key_mgmt; +} + +/** + * nm_setting_wireless_security_get_num_protos: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the number of security protocols this connection allows when + * connecting to secure Wi-Fi networks + **/ +guint32 +nm_setting_wireless_security_get_num_protos(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), 0); + + return g_slist_length(NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->proto); +} + +/** + * nm_setting_wireless_security_get_proto: + * @setting: the #NMSettingWirelessSecurity + * @i: an index into the protocol list + * + * Returns: the protocol at index @i + **/ +const char * +nm_setting_wireless_security_get_proto(NMSettingWirelessSecurity *setting, guint32 i) +{ + NMSettingWirelessSecurityPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + g_return_val_if_fail(i <= g_slist_length(priv->proto), NULL); + + return (const char *) g_slist_nth_data(priv->proto, i); +} + +/** + * nm_setting_wireless_security_add_proto: + * @setting: the #NMSettingWirelessSecurity + * @proto: the protocol to add, one of "wpa" or "rsn" + * + * Adds a Wi-Fi security protocol (one of "wpa" or "rsn") to the allowed list; + * only protocols in this list will be used when finding and connecting to + * the Wi-Fi network specified by this connection. For example, if the + * protocol list contains only "wpa" but the access point for the SSID specified + * by this connection only supports WPA2/RSN, the connection cannot be used + * with the access point. + * + * Returns: %TRUE if the protocol was new and was added to the allowed + * protocol list, or %FALSE if it was already in the list + **/ +gboolean +nm_setting_wireless_security_add_proto(NMSettingWirelessSecurity *setting, const char *proto) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), FALSE); + g_return_val_if_fail(proto != NULL, FALSE); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + for (iter = priv->proto; iter; iter = g_slist_next(iter)) { + if (g_ascii_strcasecmp(proto, (char *) iter->data) == 0) + return FALSE; + } + + priv->proto = g_slist_append(priv->proto, g_ascii_strdown(proto, -1)); + _notify(setting, PROP_PROTO); + return TRUE; +} + +/** + * nm_setting_wireless_security_remove_proto: + * @setting: the #NMSettingWirelessSecurity + * @i: index of the protocol to remove + * + * Removes a protocol from the allowed protocol list. + **/ +void +nm_setting_wireless_security_remove_proto(NMSettingWirelessSecurity *setting, guint32 i) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * elt; + + g_return_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting)); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + elt = g_slist_nth(priv->proto, i); + g_return_if_fail(elt != NULL); + + g_free(elt->data); + priv->proto = g_slist_delete_link(priv->proto, elt); + _notify(setting, PROP_PROTO); +} + +/** + * nm_setting_wireless_security_remove_proto_by_value: + * @setting: the #NMSettingWirelessSecurity + * @proto: the protocol to remove, one of "wpa" or "rsn" + * + * Removes a protocol from the allowed protocol list. + * + * Returns: %TRUE if the protocol was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_wireless_security_remove_proto_by_value(NMSettingWirelessSecurity *setting, + const char * proto) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), FALSE); + g_return_val_if_fail(proto != NULL, FALSE); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + for (iter = priv->proto; iter; iter = g_slist_next(iter)) { + if (g_ascii_strcasecmp(proto, (char *) iter->data) == 0) { + priv->proto = g_slist_delete_link(priv->proto, iter); + _notify(setting, PROP_PROTO); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_wireless_security_clear_protos: + * @setting: the #NMSettingWirelessSecurity + * + * Removes all protocols from the allowed list. If there are no protocols + * specified then all protocols are allowed. + **/ +void +nm_setting_wireless_security_clear_protos(NMSettingWirelessSecurity *setting) +{ + NMSettingWirelessSecurityPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting)); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + g_slist_free_full(priv->proto, g_free); + priv->proto = NULL; + _notify(setting, PROP_PROTO); +} + +/** + * nm_setting_wireless_security_get_num_pairwise: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the number of pairwise encryption algorithms in the allowed list + **/ +guint32 +nm_setting_wireless_security_get_num_pairwise(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), 0); + + return g_slist_length(NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->pairwise); +} + +/** + * nm_setting_wireless_security_get_pairwise: + * @setting: the #NMSettingWirelessSecurity + * @i: index of an item in the allowed pairwise encryption algorithm list + * + * Returns the allowed pairwise encryption algorithm from allowed algorithm + * list. + * + * Returns: the pairwise encryption algorithm at index @i + **/ +const char * +nm_setting_wireless_security_get_pairwise(NMSettingWirelessSecurity *setting, guint32 i) +{ + NMSettingWirelessSecurityPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + g_return_val_if_fail(i <= g_slist_length(priv->pairwise), NULL); + + return (const char *) g_slist_nth_data(priv->pairwise, i); +} + +/** + * nm_setting_wireless_security_add_pairwise: + * @setting: the #NMSettingWirelessSecurity + * @pairwise: the encryption algorithm to add, one of "tkip" or "ccmp" + * + * Adds an encryption algorithm to the list of allowed pairwise encryption + * algorithms. If the list is not empty, then only access points that support + * one or more of the encryption algorithms in the list will be considered + * compatible with this connection. + * + * Returns: %TRUE if the algorithm was added to the list, %FALSE if it was + * already in the list + **/ +gboolean +nm_setting_wireless_security_add_pairwise(NMSettingWirelessSecurity *setting, const char *pairwise) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), FALSE); + g_return_val_if_fail(pairwise != NULL, FALSE); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + for (iter = priv->pairwise; iter; iter = g_slist_next(iter)) { + if (g_ascii_strcasecmp(pairwise, (char *) iter->data) == 0) + return FALSE; + } + + priv->pairwise = g_slist_append(priv->pairwise, g_ascii_strdown(pairwise, -1)); + _notify(setting, PROP_PAIRWISE); + return TRUE; +} + +/** + * nm_setting_wireless_security_remove_pairwise: + * @setting: the #NMSettingWirelessSecurity + * @i: the index of an item in the allowed pairwise encryption algorithm list + * + * Removes an encryption algorithm from the allowed pairwise encryption + * algorithm list. + **/ +void +nm_setting_wireless_security_remove_pairwise(NMSettingWirelessSecurity *setting, guint32 i) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * elt; + + g_return_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting)); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + elt = g_slist_nth(priv->pairwise, i); + g_return_if_fail(elt != NULL); + + g_free(elt->data); + priv->pairwise = g_slist_delete_link(priv->pairwise, elt); + _notify(setting, PROP_PAIRWISE); +} + +/** + * nm_setting_wireless_security_remove_pairwise_by_value: + * @setting: the #NMSettingWirelessSecurity + * @pairwise: the encryption algorithm to remove, one of "tkip" or "ccmp" + * + * Removes an encryption algorithm from the allowed pairwise encryption + * algorithm list. + * + * Returns: %TRUE if the encryption algorithm was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_wireless_security_remove_pairwise_by_value(NMSettingWirelessSecurity *setting, + const char * pairwise) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), FALSE); + g_return_val_if_fail(pairwise != NULL, FALSE); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + for (iter = priv->pairwise; iter; iter = g_slist_next(iter)) { + if (g_ascii_strcasecmp(pairwise, (char *) iter->data) == 0) { + priv->pairwise = g_slist_delete_link(priv->pairwise, iter); + _notify(setting, PROP_PAIRWISE); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_wireless_security_clear_pairwise: + * @setting: the #NMSettingWirelessSecurity + * + * Removes all algorithms from the allowed list. If there are no algorithms + * specified then all pairwise encryption algorithms are allowed. + **/ +void +nm_setting_wireless_security_clear_pairwise(NMSettingWirelessSecurity *setting) +{ + NMSettingWirelessSecurityPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting)); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + g_slist_free_full(priv->pairwise, g_free); + priv->pairwise = NULL; + _notify(setting, PROP_PAIRWISE); +} + +/** + * nm_setting_wireless_security_get_num_groups: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the number of groupwise encryption algorithms in the allowed list + **/ +guint32 +nm_setting_wireless_security_get_num_groups(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), 0); + + return g_slist_length(NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->group); +} + +/** + * nm_setting_wireless_security_get_group: + * @setting: the #NMSettingWirelessSecurity + * @i: index of an item in the allowed groupwise encryption algorithm list + * + * Returns the allowed groupwise encryption algorithm from allowed algorithm + * list. + * + * Returns: the groupwise encryption algorithm at index @i + **/ +const char * +nm_setting_wireless_security_get_group(NMSettingWirelessSecurity *setting, guint32 i) +{ + NMSettingWirelessSecurityPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + g_return_val_if_fail(i <= g_slist_length(priv->group), NULL); + + return (const char *) g_slist_nth_data(priv->group, i); +} + +/** + * nm_setting_wireless_security_add_group: + * @setting: the #NMSettingWirelessSecurity + * @group: the encryption algorithm to add, one of "wep40", "wep104", + * "tkip", or "ccmp" + * + * Adds an encryption algorithm to the list of allowed groupwise encryption + * algorithms. If the list is not empty, then only access points that support + * one or more of the encryption algorithms in the list will be considered + * compatible with this connection. + * + * Returns: %TRUE if the algorithm was added to the list, %FALSE if it was + * already in the list + **/ +gboolean +nm_setting_wireless_security_add_group(NMSettingWirelessSecurity *setting, const char *group) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), FALSE); + g_return_val_if_fail(group != NULL, FALSE); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + for (iter = priv->group; iter; iter = g_slist_next(iter)) { + if (g_ascii_strcasecmp(group, (char *) iter->data) == 0) + return FALSE; + } + + priv->group = g_slist_append(priv->group, g_ascii_strdown(group, -1)); + _notify(setting, PROP_GROUP); + return TRUE; +} + +/** + * nm_setting_wireless_security_remove_group: + * @setting: the #NMSettingWirelessSecurity + * @i: the index of an item in the allowed groupwise encryption algorithm list + * + * Removes an encryption algorithm from the allowed groupwise encryption + * algorithm list. + **/ +void +nm_setting_wireless_security_remove_group(NMSettingWirelessSecurity *setting, guint32 i) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * elt; + + g_return_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting)); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + elt = g_slist_nth(priv->group, i); + g_return_if_fail(elt != NULL); + + g_free(elt->data); + priv->group = g_slist_delete_link(priv->group, elt); + _notify(setting, PROP_GROUP); +} + +/** + * nm_setting_wireless_security_remove_group_by_value: + * @setting: the #NMSettingWirelessSecurity + * @group: the encryption algorithm to remove, one of "wep40", "wep104", + * "tkip", or "ccmp" + * + * Removes an encryption algorithm from the allowed groupwise encryption + * algorithm list. + * + * Returns: %TRUE if the algorithm was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_wireless_security_remove_group_by_value(NMSettingWirelessSecurity *setting, + const char * group) +{ + NMSettingWirelessSecurityPrivate *priv; + GSList * iter; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), FALSE); + g_return_val_if_fail(group != NULL, FALSE); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + for (iter = priv->group; iter; iter = g_slist_next(iter)) { + if (g_ascii_strcasecmp(group, (char *) iter->data) == 0) { + priv->group = g_slist_delete_link(priv->group, iter); + _notify(setting, PROP_GROUP); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_wireless_security_clear_groups: + * @setting: the #NMSettingWirelessSecurity + * + * Removes all algorithms from the allowed list. If there are no algorithms + * specified then all groupwise encryption algorithms are allowed. + **/ +void +nm_setting_wireless_security_clear_groups(NMSettingWirelessSecurity *setting) +{ + NMSettingWirelessSecurityPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting)); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + g_slist_free_full(priv->group, g_free); + priv->group = NULL; + _notify(setting, PROP_GROUP); +} + +/* + * nm_setting_wireless_security_get_pmf: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:pmf property of the setting + * + * Since: 1.10 + **/ +NMSettingWirelessSecurityPmf +nm_setting_wireless_security_get_pmf(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), 0); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->pmf; +} + +/** + * nm_setting_wireless_security_get_psk: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:psk property of the setting + **/ +const char * +nm_setting_wireless_security_get_psk(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->psk; +} + +/** + * nm_setting_wireless_security_get_psk_flags: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSettingWirelessSecurity:psk + **/ +NMSettingSecretFlags +nm_setting_wireless_security_get_psk_flags(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->psk_flags; +} + +/** + * nm_setting_wireless_security_get_leap_username: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:leap-username property of the setting + **/ +const char * +nm_setting_wireless_security_get_leap_username(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->leap_username; +} + +/** + * nm_setting_wireless_security_get_leap_password: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:leap-password property of the setting + **/ +const char * +nm_setting_wireless_security_get_leap_password(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->leap_password; +} + +/** + * nm_setting_wireless_security_get_leap_password_flags: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingSecretFlags pertaining to the + * #NMSettingWirelessSecurity:leap-password + **/ +NMSettingSecretFlags +nm_setting_wireless_security_get_leap_password_flags(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->leap_password_flags; +} + +/** + * nm_setting_wireless_security_get_wep_key: + * @setting: the #NMSettingWirelessSecurity + * @idx: the WEP key index (0..3 inclusive) + * + * Returns: the WEP key at the given index + **/ +const char * +nm_setting_wireless_security_get_wep_key(NMSettingWirelessSecurity *setting, guint32 idx) +{ + NMSettingWirelessSecurityPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + g_return_val_if_fail(idx < 4, NULL); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + if (idx == 0) + return priv->wep_key0; + else if (idx == 1) + return priv->wep_key1; + else if (idx == 2) + return priv->wep_key2; + else if (idx == 3) + return priv->wep_key3; + + g_assert_not_reached(); + return NULL; +} + +/** + * nm_setting_wireless_security_set_wep_key: + * @setting: the #NMSettingWirelessSecurity + * @idx: the index of the key (0..3 inclusive) + * @key: the WEP key as a string, in either hexadecimal, ASCII, or passphrase + * form as determined by the value of the #NMSettingWirelessSecurity:wep-key-type + * property. + * + * Sets a WEP key in the given index. + **/ +void +nm_setting_wireless_security_set_wep_key(NMSettingWirelessSecurity *setting, + guint32 idx, + const char * key) +{ + NMSettingWirelessSecurityPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting)); + g_return_if_fail(idx < 4); + + priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + switch (idx) { + case 0: + g_free(priv->wep_key0); + priv->wep_key0 = g_strdup(key); + _notify(setting, PROP_WEP_KEY0); + break; + case 1: + g_free(priv->wep_key1); + priv->wep_key1 = g_strdup(key); + _notify(setting, PROP_WEP_KEY1); + break; + case 2: + g_free(priv->wep_key2); + priv->wep_key2 = g_strdup(key); + _notify(setting, PROP_WEP_KEY2); + break; + case 3: + g_free(priv->wep_key3); + priv->wep_key3 = g_strdup(key); + _notify(setting, PROP_WEP_KEY3); + break; + default: + g_assert_not_reached(); + } +} + +/** + * nm_setting_wireless_security_get_wep_tx_keyidx: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:wep-tx-keyidx property of the setting + **/ +guint32 +nm_setting_wireless_security_get_wep_tx_keyidx(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), 0); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->wep_tx_keyidx; +} + +/** + * nm_setting_wireless_security_get_auth_alg: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:auth-alg property of the setting + **/ +const char * +nm_setting_wireless_security_get_auth_alg(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NULL); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->auth_alg; +} + +/** + * nm_setting_wireless_security_get_wep_key_flags: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingSecretFlags pertaining to the all WEP keys + **/ +NMSettingSecretFlags +nm_setting_wireless_security_get_wep_key_flags(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), NM_SETTING_SECRET_FLAG_NONE); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->wep_key_flags; +} + +/** + * nm_setting_wireless_security_get_wep_key_type: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:wep-key-type property of the setting + **/ +NMWepKeyType +nm_setting_wireless_security_get_wep_key_type(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), 0); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->wep_key_type; +} + +/** + * nm_setting_wireless_security_get_wps_method: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:wps-method property of the setting + * + * Since: 1.10 + **/ +NMSettingWirelessSecurityWpsMethod +nm_setting_wireless_security_get_wps_method(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), + NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->wps_method; +} + +/* + * nm_setting_wireless_security_get_fils: + * @setting: the #NMSettingWirelessSecurity + * + * Returns: the #NMSettingWirelessSecurity:fils property of the setting + * + * Since: 1.12 + **/ +NMSettingWirelessSecurityFils +nm_setting_wireless_security_get_fils(NMSettingWirelessSecurity *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS_SECURITY(setting), 0); + + return NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting)->fils; +} + +static GPtrArray * +need_secrets(NMSetting *setting) +{ + NMSettingWirelessSecurity * self = NM_SETTING_WIRELESS_SECURITY(setting); + NMSettingWirelessSecurityPrivate *priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(self); + GPtrArray * secrets; + + secrets = g_ptr_array_sized_new(4); + + g_assert(priv->key_mgmt); + + /* Static WEP */ + if (strcmp(priv->key_mgmt, "none") == 0) { + if ((priv->wep_tx_keyidx == 0) + && !nm_utils_wep_key_valid(priv->wep_key0, priv->wep_key_type)) { + g_ptr_array_add(secrets, NM_SETTING_WIRELESS_SECURITY_WEP_KEY0); + return secrets; + } + if ((priv->wep_tx_keyidx == 1) + && !nm_utils_wep_key_valid(priv->wep_key1, priv->wep_key_type)) { + g_ptr_array_add(secrets, NM_SETTING_WIRELESS_SECURITY_WEP_KEY1); + return secrets; + } + if ((priv->wep_tx_keyidx == 2) + && !nm_utils_wep_key_valid(priv->wep_key2, priv->wep_key_type)) { + g_ptr_array_add(secrets, NM_SETTING_WIRELESS_SECURITY_WEP_KEY2); + return secrets; + } + if ((priv->wep_tx_keyidx == 3) + && !nm_utils_wep_key_valid(priv->wep_key3, priv->wep_key_type)) { + g_ptr_array_add(secrets, NM_SETTING_WIRELESS_SECURITY_WEP_KEY3); + return secrets; + } + goto no_secrets; + } + + /* WPA-PSK infrastructure */ + if (strcmp(priv->key_mgmt, "wpa-psk") == 0) { + if (!nm_utils_wpa_psk_valid(priv->psk)) { + g_ptr_array_add(secrets, NM_SETTING_WIRELESS_SECURITY_PSK); + return secrets; + } + goto no_secrets; + } + + /* SAE, used in MESH and WPA3-Personal */ + if (strcmp(priv->key_mgmt, "sae") == 0) { + if (!priv->psk || !*priv->psk) { + g_ptr_array_add(secrets, NM_SETTING_WIRELESS_SECURITY_PSK); + return secrets; + } + goto no_secrets; + } + + /* LEAP */ + if (priv->auth_alg && !strcmp(priv->auth_alg, "leap") && !strcmp(priv->key_mgmt, "ieee8021x")) { + if (!priv->leap_password || !*priv->leap_password) { + g_ptr_array_add(secrets, NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD); + return secrets; + } + goto no_secrets; + } + + if (NM_IN_STRSET(priv->key_mgmt, "ieee8021x", "wpa-eap", "owe", "wpa-eap-suite-b-192")) { + /* Let caller check the 802.1x setting for secrets */ + goto no_secrets; + } + + /* If we get here, we're an older libnm talking to a newer NetworkManager + * service (perhaps from a container or during an upgrade). Assume that + * unknown/future key management modes don't need any extra secrets. */ + +no_secrets: + if (secrets) + g_ptr_array_free(secrets, TRUE); + return NULL; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWirelessSecurity * self = NM_SETTING_WIRELESS_SECURITY(setting); + NMSettingWirelessSecurityPrivate *priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(self); + const char * valid_key_mgmt[] = + {"none", "ieee8021x", "wpa-psk", "wpa-eap", "wpa-eap-suite-b-192", "sae", "owe", NULL}; + const char * valid_auth_algs[] = {"open", "shared", "leap", NULL}; + const char * valid_protos[] = {"wpa", "rsn", NULL}; + const char * valid_pairwise[] = {"tkip", "ccmp", NULL}; + const char * valid_groups[] = {"wep40", "wep104", "tkip", "ccmp", NULL}; + NMSettingWireless *s_wifi; + const char * wifi_mode; + + s_wifi = connection ? nm_connection_get_setting_wireless(connection) : NULL; + wifi_mode = s_wifi ? nm_setting_wireless_get_mode(s_wifi) : NULL; + + if (!priv->key_mgmt) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + + if (!g_strv_contains(valid_key_mgmt, priv->key_mgmt)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for the property"), + priv->key_mgmt); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + + if (NM_IN_STRSET(wifi_mode, NM_SETTING_WIRELESS_MODE_MESH) + && !NM_IN_STRSET(priv->key_mgmt, "none", "sae")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid value for '%s' mode connections"), + priv->key_mgmt, + NM_SETTING_WIRELESS_MODE_MESH); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT); + return FALSE; + } + + if (priv->auth_alg && !strcmp(priv->auth_alg, "leap")) { + /* LEAP must use ieee8021x key management */ + if (strcmp(priv->key_mgmt, "ieee8021x")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' security requires '%s=%s'"), + "leap", + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "ieee8021x"); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + if (!priv->leap_username) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME); + return FALSE; + } + } else { + if (nm_streq(priv->key_mgmt, "ieee8021x") || nm_streq(priv->key_mgmt, "wpa-eap") + || nm_streq(priv->key_mgmt, "wpa-eap-suite-b-192")) { + /* Need an 802.1x setting too */ + if (connection && !nm_connection_get_setting_802_1x(connection)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING, + _("'%s' security requires '%s' setting presence"), + priv->key_mgmt, + NM_SETTING_802_1X_SETTING_NAME); + g_prefix_error(error, "%s: ", NM_SETTING_802_1X_SETTING_NAME); + return FALSE; + } + } + } + + if (priv->leap_username && !strlen(priv->leap_username)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME); + return FALSE; + } + + if (priv->wep_tx_keyidx > 3) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%d' value is out of range <0-3>"), + priv->wep_tx_keyidx); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX); + return FALSE; + } + + if (priv->wep_key_type > NM_WEP_KEY_TYPE_LAST) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE); + return FALSE; + } + + if (priv->auth_alg && !g_strv_contains(valid_auth_algs, priv->auth_alg)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + + if (priv->proto && !_nm_utils_string_slist_validate(priv->proto, valid_protos)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PROTO); + return FALSE; + } + + if (priv->pairwise) { + if (!_nm_utils_string_slist_validate(priv->pairwise, valid_pairwise)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PAIRWISE); + return FALSE; + } + } + + if (priv->group && !_nm_utils_string_slist_validate(priv->group, valid_groups)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_GROUP); + return FALSE; + } + + /* Shared Key auth can only be used with WEP */ + if (priv->auth_alg && !strcmp(priv->auth_alg, "shared")) { + if (priv->key_mgmt && strcmp(priv->key_mgmt, "none")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' can only be used with '%s=%s' (WEP)"), + "shared", + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "none"); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG); + return FALSE; + } + } + + G_STATIC_ASSERT_EXPR(((NMSettingWirelessSecurityPmf) -1) > 0); + if (priv->pmf > NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PMF); + return FALSE; + } + + if (NM_IN_SET(priv->pmf, + NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL, + NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED) + && !NM_IN_STRSET(priv->key_mgmt, + "wpa-eap", + "wpa-eap-suite-b-192", + "wpa-psk", + "sae", + "owe")) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' can only be used with 'owe', 'wpa-psk', 'sae', 'wpa-eap' " + "or 'wpa-eap-suite-b-192' key management"), + priv->pmf == NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL ? "optional" + : "required"); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PMF); + return FALSE; + } + + if (NM_IN_STRSET(priv->key_mgmt, "owe", "sae", "wpa-eap-suite-b-192") + && !NM_IN_SET(priv->pmf, + NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT, + NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("pmf can only be 'default' or 'required' when using 'owe', 'sae' or " + "'wpa-eap-suite-b-192' key management")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PMF); + return FALSE; + } + + if (!_nm_utils_wps_method_validate(priv->wps_method, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_WPS_METHOD, + FALSE, + error)) + return FALSE; + + return TRUE; +} + +static gboolean +_verify_wep_key(const char * wep_key, + NMWepKeyType wep_key_type, + const char * property, + GError ** error) +{ + if (wep_key && !nm_utils_wep_key_valid(wep_key, wep_key_type)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, property); + return FALSE; + } + return TRUE; +} + +static gboolean +verify_secrets(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWirelessSecurity * self = NM_SETTING_WIRELESS_SECURITY(setting); + NMSettingWirelessSecurityPrivate *priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(self); + + /* LEAP */ + if (priv->auth_alg && !strcmp(priv->auth_alg, "leap") && !strcmp(priv->key_mgmt, "ieee8021x")) { + if (!_nm_setting_verify_secret_string(priv->leap_password, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, + error)) + return FALSE; + } + + /* WEP */ + if (!_verify_wep_key(priv->wep_key0, + priv->wep_key_type, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + error)) + return FALSE; + if (!_verify_wep_key(priv->wep_key1, + priv->wep_key_type, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY1, + error)) + return FALSE; + if (!_verify_wep_key(priv->wep_key2, + priv->wep_key_type, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY2, + error)) + return FALSE; + if (!_verify_wep_key(priv->wep_key3, + priv->wep_key_type, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY3, + error)) + return FALSE; + + /* WPA-PSK */ + if (priv->psk && strcmp(priv->key_mgmt, "sae") != 0 && !nm_utils_wpa_psk_valid(priv->psk)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_PSK); + return FALSE; + } + + return TRUE; +} + +static gboolean +get_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags *out_flags, + GError ** error) +{ + NMSettingSecretFlags flags; + + if (NM_IN_STRSET(secret_name, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY1, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY2, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY3)) { + /* There's only one 'flags' property for WEP keys, so alias all the WEP key + * property names to that flags property. */ + nm_assert(_nm_setting_property_is_regular_secret(setting, secret_name)); + nm_assert(_nm_setting_property_is_regular_secret_flags( + setting, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS)); + + g_object_get(G_OBJECT(setting), NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, &flags, NULL); + NM_SET_OUT(out_flags, flags); + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_wireless_security_parent_class) + ->get_secret_flags(setting, secret_name, out_flags, error); +} + +static gboolean +set_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags flags, + GError ** error) +{ + if (NM_IN_STRSET(secret_name, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY1, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY2, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY3)) { + /* There's only one 'flags' property for WEP keys, so alias all the WEP key + * property names to that flags property. */ + nm_assert(_nm_setting_property_is_regular_secret(setting, secret_name)); + nm_assert(_nm_setting_property_is_regular_secret_flags( + setting, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS)); + + if (!nm_g_object_set_property_flags(G_OBJECT(setting), + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, + NM_TYPE_SETTING_SECRET_FLAGS, + flags, + error)) + g_return_val_if_reached(FALSE); + return TRUE; + } + + return NM_SETTING_CLASS(nm_setting_wireless_security_parent_class) + ->set_secret_flags(setting, secret_name, flags, error); +} + +/* NMSettingWirelessSecurity:wep-key-type is an enum, but needs to be marshalled + * as 'u', not 'i', for backward-compatibility. + */ +static GVariant * +wep_key_type_to_dbus(const GValue *from) +{ + return g_variant_new_uint32(g_value_get_enum(from)); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingWirelessSecurity * setting = NM_SETTING_WIRELESS_SECURITY(object); + NMSettingWirelessSecurityPrivate *priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + + switch (prop_id) { + case PROP_KEY_MGMT: + g_value_set_string(value, priv->key_mgmt); + break; + case PROP_WEP_TX_KEYIDX: + g_value_set_uint(value, priv->wep_tx_keyidx); + break; + case PROP_AUTH_ALG: + g_value_set_string(value, priv->auth_alg); + break; + case PROP_PROTO: + g_value_take_boxed(value, _nm_utils_slist_to_strv(priv->proto, TRUE)); + break; + case PROP_PAIRWISE: + g_value_take_boxed(value, _nm_utils_slist_to_strv(priv->pairwise, TRUE)); + break; + case PROP_GROUP: + g_value_take_boxed(value, _nm_utils_slist_to_strv(priv->group, TRUE)); + break; + case PROP_PMF: + g_value_set_int(value, nm_setting_wireless_security_get_pmf(setting)); + break; + case PROP_LEAP_USERNAME: + g_value_set_string(value, priv->leap_username); + break; + case PROP_WEP_KEY0: + g_value_set_string(value, priv->wep_key0); + break; + case PROP_WEP_KEY1: + g_value_set_string(value, priv->wep_key1); + break; + case PROP_WEP_KEY2: + g_value_set_string(value, priv->wep_key2); + break; + case PROP_WEP_KEY3: + g_value_set_string(value, priv->wep_key3); + break; + case PROP_WEP_KEY_FLAGS: + g_value_set_flags(value, priv->wep_key_flags); + break; + case PROP_PSK: + g_value_set_string(value, priv->psk); + break; + case PROP_PSK_FLAGS: + g_value_set_flags(value, priv->psk_flags); + break; + case PROP_LEAP_PASSWORD: + g_value_set_string(value, priv->leap_password); + break; + case PROP_LEAP_PASSWORD_FLAGS: + g_value_set_flags(value, priv->leap_password_flags); + break; + case PROP_WEP_KEY_TYPE: + g_value_set_enum(value, priv->wep_key_type); + break; + case PROP_WPS_METHOD: + g_value_set_uint(value, priv->wps_method); + break; + case PROP_FILS: + g_value_set_int(value, nm_setting_wireless_security_get_fils(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingWirelessSecurity * setting = NM_SETTING_WIRELESS_SECURITY(object); + NMSettingWirelessSecurityPrivate *priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(setting); + const char * str; + + switch (prop_id) { + case PROP_KEY_MGMT: + g_free(priv->key_mgmt); + str = g_value_get_string(value); + priv->key_mgmt = str ? g_ascii_strdown(str, -1) : NULL; + break; + case PROP_WEP_TX_KEYIDX: + priv->wep_tx_keyidx = g_value_get_uint(value); + break; + case PROP_AUTH_ALG: + g_free(priv->auth_alg); + str = g_value_get_string(value); + priv->auth_alg = str ? g_ascii_strdown(str, -1) : NULL; + break; + case PROP_PROTO: + g_slist_free_full(priv->proto, g_free); + priv->proto = _nm_utils_strv_to_slist(g_value_get_boxed(value), TRUE); + break; + case PROP_PAIRWISE: + g_slist_free_full(priv->pairwise, g_free); + priv->pairwise = _nm_utils_strv_to_slist(g_value_get_boxed(value), TRUE); + break; + case PROP_GROUP: + g_slist_free_full(priv->group, g_free); + priv->group = _nm_utils_strv_to_slist(g_value_get_boxed(value), TRUE); + break; + case PROP_PMF: + priv->pmf = g_value_get_int(value); + break; + case PROP_LEAP_USERNAME: + g_free(priv->leap_username); + priv->leap_username = g_value_dup_string(value); + break; + case PROP_WEP_KEY0: + nm_free_secret(priv->wep_key0); + priv->wep_key0 = g_value_dup_string(value); + break; + case PROP_WEP_KEY1: + nm_free_secret(priv->wep_key1); + priv->wep_key1 = g_value_dup_string(value); + break; + case PROP_WEP_KEY2: + nm_free_secret(priv->wep_key2); + priv->wep_key2 = g_value_dup_string(value); + break; + case PROP_WEP_KEY3: + nm_free_secret(priv->wep_key3); + priv->wep_key3 = g_value_dup_string(value); + break; + case PROP_WEP_KEY_FLAGS: + priv->wep_key_flags = g_value_get_flags(value); + break; + case PROP_PSK: + nm_free_secret(priv->psk); + priv->psk = g_value_dup_string(value); + break; + case PROP_PSK_FLAGS: + priv->psk_flags = g_value_get_flags(value); + break; + case PROP_LEAP_PASSWORD: + nm_free_secret(priv->leap_password); + priv->leap_password = g_value_dup_string(value); + break; + case PROP_LEAP_PASSWORD_FLAGS: + priv->leap_password_flags = g_value_get_flags(value); + break; + case PROP_WEP_KEY_TYPE: + priv->wep_key_type = g_value_get_enum(value); + break; + case PROP_WPS_METHOD: + priv->wps_method = g_value_get_uint(value); + break; + case PROP_FILS: + priv->fils = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_wireless_security_init(NMSettingWirelessSecurity *self) +{ + nm_assert(NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(self)->wep_key_type + == NM_WEP_KEY_TYPE_UNKNOWN); + nm_assert(NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(self)->wps_method + == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT); +} + +/** + * nm_setting_wireless_security_new: + * + * Creates a new #NMSettingWirelessSecurity object with default values. + * + * Returns: (transfer full): the new empty #NMSettingWirelessSecurity object + **/ +NMSetting * +nm_setting_wireless_security_new(void) +{ + return g_object_new(NM_TYPE_SETTING_WIRELESS_SECURITY, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingWirelessSecurity * self = NM_SETTING_WIRELESS_SECURITY(object); + NMSettingWirelessSecurityPrivate *priv = NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(self); + + g_free(priv->key_mgmt); + g_free(priv->auth_alg); + g_free(priv->leap_username); + nm_free_secret(priv->wep_key0); + nm_free_secret(priv->wep_key1); + nm_free_secret(priv->wep_key2); + nm_free_secret(priv->wep_key3); + nm_free_secret(priv->psk); + nm_free_secret(priv->leap_password); + + g_slist_free_full(priv->proto, g_free); + g_slist_free_full(priv->pairwise, g_free); + g_slist_free_full(priv->group, g_free); + + G_OBJECT_CLASS(nm_setting_wireless_security_parent_class)->finalize(object); +} + +static void +nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingWirelessSecurityPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->verify_secrets = verify_secrets; + setting_class->need_secrets = need_secrets; + setting_class->get_secret_flags = get_secret_flags; + setting_class->set_secret_flags = set_secret_flags; + + /** + * NMSettingWirelessSecurity:key-mgmt: + * + * Key management used for the connection. One of "none" (WEP or no + * password protection), "ieee8021x" (Dynamic WEP), "owe" (Opportunistic + * Wireless Encryption), "wpa-psk" (WPA2 + WPA3 personal), "sae" (WPA3 + * personal only), "wpa-eap" (WPA2 + WPA3 enterprise) or + * "wpa-eap-suite-b-192" (WPA3 enterprise only). + * + * This property must be set for any Wi-Fi connection that uses security. + **/ + /* ---ifcfg-rh--- + * property: key-mgmt + * variable: KEY_MGMT(+) + * values: none, ieee8021x, owe, wpa-psk, sae, wpa-eap, wpa-eap-suite-b-192 + * description: Key management method. + * ---end--- + */ + obj_properties[PROP_KEY_MGMT] = + g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_REQUIRED | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:wep-tx-keyidx: + * + * When static WEP is used (ie, key-mgmt = "none") and a non-default WEP key + * index is used by the AP, put that WEP key index here. Valid values are 0 + * (default key) through 3. Note that some consumer access points (like the + * Linksys WRT54G) number the keys 1 - 4. + **/ + /* ---ifcfg-rh--- + * property: wep-tx-keyidx + * variable: DEFAULTKEY + * values: 1, 2, 3, 4 + * default: 1 + * description: Index of active WEP key. + * ---end--- + */ + obj_properties[PROP_WEP_TX_KEYIDX] = + g_param_spec_uint(NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, + "", + "", + 0, + 3, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:auth-alg: + * + * When WEP is used (ie, key-mgmt = "none" or "ieee8021x") indicate the + * 802.11 authentication algorithm required by the AP here. One of "open" + * for Open System, "shared" for Shared Key, or "leap" for Cisco LEAP. When + * using Cisco LEAP (ie, key-mgmt = "ieee8021x" and auth-alg = "leap") the + * "leap-username" and "leap-password" properties must be specified. + **/ + /* ---ifcfg-rh--- + * property: auth-alg + * variable: SECURITYMODE(+) + * values: restricted, open, leap + * description: Authentication algorithm for WEP. + * ---end--- + */ + obj_properties[PROP_AUTH_ALG] = g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:proto: + * + * List of strings specifying the allowed WPA protocol versions to use. + * Each element may be one "wpa" (allow WPA) or "rsn" (allow WPA2/RSN). If + * not specified, both WPA and RSN connections are allowed. + **/ + /* ---ifcfg-rh--- + * property: proto + * variable: WPA_ALLOW_WPA(+), WPA_ALLOW_WPA2(+) + * values: yes, no + * default: no + * description: Allowed WPA protocols, WPA and WPA2 (RSN). + * ---end--- + */ + obj_properties[PROP_PROTO] = g_param_spec_boxed(NM_SETTING_WIRELESS_SECURITY_PROTO, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:pairwise: + * + * A list of pairwise encryption algorithms which prevents connections to + * Wi-Fi networks that do not utilize one of the algorithms in the list. + * For maximum compatibility leave this property empty. Each list element + * may be one of "tkip" or "ccmp". + **/ + /* ---ifcfg-rh--- + * property: pairwise + * variable: CIPHER_PAIRWISE(+) + * values: CCMP, TKIP + * description: Restrict pairwise encryption algorithms, specified as a space + * separated list. + * ---end--- + */ + obj_properties[PROP_PAIRWISE] = g_param_spec_boxed(NM_SETTING_WIRELESS_SECURITY_PAIRWISE, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:group: + * + * A list of group/broadcast encryption algorithms which prevents + * connections to Wi-Fi networks that do not utilize one of the algorithms + * in the list. For maximum compatibility leave this property empty. Each + * list element may be one of "wep40", "wep104", "tkip", or "ccmp". + **/ + /* ---ifcfg-rh--- + * property: group + * variable: CIPHER_GROUP(+) + * values: CCMP, TKIP, WEP40, WEP104 + * description: Restrict group/broadcast encryption algorithms, specified as a space + * separated list. + * ---end--- + */ + obj_properties[PROP_GROUP] = g_param_spec_boxed(NM_SETTING_WIRELESS_SECURITY_GROUP, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:pmf: + * + * Indicates whether Protected Management Frames (802.11w) must be enabled + * for the connection. One of %NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT + * (use global default value), %NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE + * (disable PMF), %NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL (enable PMF if + * the supplicant and the access point support it) or + * %NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED (enable PMF and fail if not + * supported). When set to %NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT and no + * global default is set, PMF will be optionally enabled. + * + * Since: 1.10 + **/ + /* ---ifcfg-rh--- + * property: pmf + * variable: PMF(+) + * values: default, disable, optional, required + * description: Enables or disables PMF (802.11w) + * example: PMF=required + * ---end--- + */ + obj_properties[PROP_PMF] = g_param_spec_int(NM_SETTING_WIRELESS_SECURITY_PMF, + "", + "", + G_MININT32, + G_MAXINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:leap-username: + * + * The login username for legacy LEAP connections (ie, key-mgmt = + * "ieee8021x" and auth-alg = "leap"). + **/ + /* ---ifcfg-rh--- + * property: leap-username + * variable: IEEE_8021X_IDENTITY(+) + * description: Login name for LEAP. + * ---end--- + */ + obj_properties[PROP_LEAP_USERNAME] = + g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:wep-key0: + * + * Index 0 WEP key. This is the WEP key used in most networks. See the + * "wep-key-type" property for a description of how this key is interpreted. + **/ + /* ---ifcfg-rh--- + * property: wep-key0 + * variable: KEY1, KEY_PASSPHRASE1(+) + * description: The first WEP key (used in most networks). See also DEFAULTKEY for key index. + * ---end--- + */ + obj_properties[PROP_WEP_KEY0] = + g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:wep-key1: + * + * Index 1 WEP key. This WEP index is not used by most networks. See the + * "wep-key-type" property for a description of how this key is interpreted. + **/ + /* ---ifcfg-rh--- + * property: wep-key1 + * variable: KEY2, KEY_PASSPHRASE2(+) + * description: WEP key with index 1. See also DEFAULTKEY for key index. + * ---end--- + */ + obj_properties[PROP_WEP_KEY1] = + g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_WEP_KEY1, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:wep-key2: + * + * Index 2 WEP key. This WEP index is not used by most networks. See the + * "wep-key-type" property for a description of how this key is interpreted. + **/ + /* ---ifcfg-rh--- + * property: wep-key2 + * variable: KEY3, KEY_PASSPHRASE3(+) + * description: WEP key with index 2. See also DEFAULTKEY for key index. + * ---end--- + */ + obj_properties[PROP_WEP_KEY2] = + g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_WEP_KEY2, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:wep-key3: + * + * Index 3 WEP key. This WEP index is not used by most networks. See the + * "wep-key-type" property for a description of how this key is interpreted. + **/ + /* ---ifcfg-rh--- + * property: wep-key3 + * variable: KEY4, KEY_PASSPHRASE4(+) + * description: WEP key with index 3. See also DEFAULTKEY for key index. + * ---end--- + */ + obj_properties[PROP_WEP_KEY3] = + g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_WEP_KEY3, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:wep-key-flags: + * + * Flags indicating how to handle the #NMSettingWirelessSecurity:wep-key0, + * #NMSettingWirelessSecurity:wep-key1, #NMSettingWirelessSecurity:wep-key2, + * and #NMSettingWirelessSecurity:wep-key3 properties. + **/ + /* ---ifcfg-rh--- + * property: wep-key-flags + * variable: WEP_KEY_FLAGS(+) + * format: NMSettingSecretFlags + * description: Password flags for KEY, KEY_PASSPHRASE password. + * ---end--- + */ + obj_properties[PROP_WEP_KEY_FLAGS] = + g_param_spec_flags(NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:psk: + * + * Pre-Shared-Key for WPA networks. For WPA-PSK, it's either an ASCII + * passphrase of 8 to 63 characters that is (as specified in the 802.11i + * standard) hashed to derive the actual key, or the key in form of 64 + * hexadecimal character. The WPA3-Personal networks use a passphrase + * of any length for SAE authentication. + **/ + /* ---ifcfg-rh--- + * property: psk + * variable: WPA_PSK + * description: Pre-Shared-Key for WPA networks. + * ---end--- + */ + obj_properties[PROP_PSK] = + g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_PSK, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:psk-flags: + * + * Flags indicating how to handle the #NMSettingWirelessSecurity:psk + * property. + **/ + /* ---ifcfg-rh--- + * property: psk-flags + * variable: WPA_PSK_FLAGS(+) + * format: NMSettingSecretFlags + * description: Password flags for WPA_PSK_FLAGS. + * example: WPA_PSK_FLAGS=user + * ---end--- + */ + obj_properties[PROP_PSK_FLAGS] = g_param_spec_flags(NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:leap-password: + * + * The login password for legacy LEAP connections (ie, key-mgmt = + * "ieee8021x" and auth-alg = "leap"). + **/ + /* ---ifcfg-rh--- + * property: leap-password + * variable: IEEE_8021X_PASSWORD(+) + * description: Password for LEAP. It can also go to "key-" + * lookaside file, or it can be owned by a secret agent. + * ---end--- + */ + obj_properties[PROP_LEAP_PASSWORD] = + g_param_spec_string(NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_SECRET | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:leap-password-flags: + * + * Flags indicating how to handle the + * #NMSettingWirelessSecurity:leap-password property. + **/ + /* ---ifcfg-rh--- + * property: leap-password-flags + * variable: IEEE_8021X_PASSWORD_FLAGS(+) + * format: NMSettingSecretFlags + * description: Password flags for IEEE_8021X_PASSWORD_FLAGS. + * ---end--- + */ + obj_properties[PROP_LEAP_PASSWORD_FLAGS] = + g_param_spec_flags(NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, + "", + "", + NM_TYPE_SETTING_SECRET_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:wep-key-type: + * + * Controls the interpretation of WEP keys. Allowed values are + * %NM_WEP_KEY_TYPE_KEY, in which case the key is either a 10- or + * 26-character hexadecimal string, or a 5- or 13-character ASCII password; + * or %NM_WEP_KEY_TYPE_PASSPHRASE, in which case the passphrase is provided + * as a string and will be hashed using the de-facto MD5 method to derive + * the actual WEP key. + **/ + /* ---ifcfg-rh--- + * property: wep-key-type + * variable: KEY or KEY_PASSPHRASE(+); KEY_TYPE(+) + * description: KEY is used for "key" type (10 or 26 hexadecimal characters, + * or 5 or 13 character string prefixed with "s:"). KEY_PASSPHRASE is used + * for WEP passphrases. KEY_TYPE specifies the key type and can be either + * 'key' or 'passphrase'. KEY_TYPE is redundant and can be omitted. + * example: KEY1=s:ahoj, KEY1=0a1c45bc02, KEY_PASSPHRASE1=mysupersecretkey + * ---end--- + */ + obj_properties[PROP_WEP_KEY_TYPE] = + g_param_spec_enum(NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + "", + "", + NM_TYPE_WEP_KEY_TYPE, + NM_WEP_KEY_TYPE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_WEP_KEY_TYPE], + &nm_sett_info_propert_type_plain_u, + .to_dbus_data.gprop_to_dbus_fcn = wep_key_type_to_dbus, ); + + /** + * NMSettingWirelessSecurity:wps-method: + * + * Flags indicating which mode of WPS is to be used if any. + * + * There's little point in changing the default setting as NetworkManager will + * automatically determine whether it's feasible to start WPS enrollment from + * the Access Point capabilities. + * + * WPS can be disabled by setting this property to a value of 1. + * + * Since: 1.10 + **/ + /* ---ifcfg-rh--- + * property: wps-method + * variable: WPS_METHOD + * description: Used to control the WPS methods to be used + * Valid values are "default", "auto", "disabled", "pin" and "pbc". + * If omitted, whatver the AP announces is used. + * example: WPS_METHOD=disabled, WPS_METHOD="pin pbc" + * ---end--- + */ + obj_properties[PROP_WPS_METHOD] = g_param_spec_uint( + NM_SETTING_WIRELESS_SECURITY_WPS_METHOD, + "", + "", + 0, + G_MAXUINT32, + NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DEFAULT, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWirelessSecurity:fils: + * + * Indicates whether Fast Initial Link Setup (802.11ai) must be enabled for + * the connection. One of %NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (use + * global default value), %NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE + * (disable FILS), %NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL (enable FILS + * if the supplicant and the access point support it) or + * %NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED (enable FILS and fail if not + * supported). When set to %NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT and + * no global default is set, FILS will be optionally enabled. + * + * Since: 1.12 + **/ + /* ---ifcfg-rh--- + * property: fils + * variable: FILS(+) + * values: default, disable, optional, required + * description: Enables or disables FILS (802.11ai) + * example: FILS=required + * ---end--- + */ + obj_properties[PROP_FILS] = g_param_spec_int(NM_SETTING_WIRELESS_SECURITY_FILS, + "", + "", + G_MININT32, + G_MAXINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_WIRELESS_SECURITY, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-wireless.c b/src/libnm-core-impl/nm-setting-wireless.c new file mode 100644 index 00000000..11111696 --- /dev/null +++ b/src/libnm-core-impl/nm-setting-wireless.c @@ -0,0 +1,1950 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2014 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-wireless.h" + +#include + +#include "nm-utils.h" +#include "libnm-core-aux-intern/nm-common-macros.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" + +/** + * SECTION:nm-setting-wireless + * @short_description: Describes connection properties for 802.11 Wi-Fi networks + * + * The #NMSettingWireless object is a #NMSetting subclass that describes properties + * necessary for connection to 802.11 Wi-Fi networks. + **/ + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE(NMSettingWireless, + PROP_SSID, + PROP_MODE, + PROP_BAND, + PROP_CHANNEL, + PROP_BSSID, + PROP_RATE, + PROP_TX_POWER, + PROP_MAC_ADDRESS, + PROP_CLONED_MAC_ADDRESS, + PROP_GENERATE_MAC_ADDRESS_MASK, + PROP_MAC_ADDRESS_BLACKLIST, + PROP_MTU, + PROP_SEEN_BSSIDS, + PROP_HIDDEN, + PROP_POWERSAVE, + PROP_MAC_ADDRESS_RANDOMIZATION, + PROP_WAKE_ON_WLAN, + PROP_AP_ISOLATION, ); + +typedef struct { + GBytes * ssid; + GArray * mac_address_blacklist; + GPtrArray * seen_bssids; + char * mode; + char * band; + char * bssid; + char * device_mac_address; + char * cloned_mac_address; + char * generate_mac_address_mask; + NMSettingMacRandomization mac_address_randomization; + NMTernary ap_isolation; + guint32 channel; + guint32 rate; + guint32 tx_power; + guint32 mtu; + guint32 powersave; + guint32 wowl; + bool hidden : 1; +} NMSettingWirelessPrivate; + +/** + * NMSettingWireless: + * + * Wi-Fi Settings + */ +struct _NMSettingWireless { + NMSetting parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSettingWirelessClass { + NMSettingClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +G_DEFINE_TYPE(NMSettingWireless, nm_setting_wireless, NM_TYPE_SETTING) + +#define NM_SETTING_WIRELESS_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_WIRELESS, NMSettingWirelessPrivate)) + +/*****************************************************************************/ + +static gboolean +match_cipher(const char *cipher, + const char *expected, + guint32 wpa_flags, + guint32 rsn_flags, + guint32 flag) +{ + if (strcmp(cipher, expected) != 0) + return FALSE; + + if (!(wpa_flags & flag) && !(rsn_flags & flag)) + return FALSE; + + return TRUE; +} + +/** + * nm_setting_wireless_ap_security_compatible: + * @s_wireless: a #NMSettingWireless + * @s_wireless_sec: a #NMSettingWirelessSecurity or %NULL + * @ap_flags: the %NM80211ApFlags of the given access point + * @ap_wpa: the %NM80211ApSecurityFlags of the given access point's WPA + * capabilities + * @ap_rsn: the %NM80211ApSecurityFlags of the given access point's WPA2/RSN + * capabilities + * @ap_mode: the 802.11 mode of the AP, either Ad-Hoc or Infrastructure + * + * Given a #NMSettingWireless and an optional #NMSettingWirelessSecurity, + * determine if the configuration given by the settings is compatible with + * the security of an access point using that access point's capability flags + * and mode. Useful for clients that wish to filter a set of connections + * against a set of access points and determine which connections are + * compatible with which access points. + * + * Returns: %TRUE if the given settings are compatible with the access point's + * security flags and mode, %FALSE if they are not. + */ +gboolean +nm_setting_wireless_ap_security_compatible(NMSettingWireless * s_wireless, + NMSettingWirelessSecurity *s_wireless_sec, + NM80211ApFlags ap_flags, + NM80211ApSecurityFlags ap_wpa, + NM80211ApSecurityFlags ap_rsn, + NM80211Mode ap_mode) +{ + const char *key_mgmt = NULL, *cipher; + guint32 num, i; + gboolean found = FALSE; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(s_wireless), FALSE); + + if (!s_wireless_sec) { + if ((ap_flags & NM_802_11_AP_FLAGS_PRIVACY) || (ap_wpa != NM_802_11_AP_SEC_NONE) + || (ap_rsn != NM_802_11_AP_SEC_NONE)) + return FALSE; + return TRUE; + } + + key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wireless_sec); + if (!key_mgmt) + return FALSE; + + /* Static WEP */ + if (!strcmp(key_mgmt, "none")) { + if (!(ap_flags & NM_802_11_AP_FLAGS_PRIVACY) || (ap_wpa != NM_802_11_AP_SEC_NONE) + || (ap_rsn != NM_802_11_AP_SEC_NONE)) + return FALSE; + return TRUE; + } + + /* Adhoc WPA2 (ie, RSN IBSS) */ + if (ap_mode == NM_802_11_MODE_ADHOC) { + if (strcmp(key_mgmt, "wpa-psk")) + return FALSE; + + /* Ensure the AP has RSN PSK capability */ + if (!(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_PSK)) + return FALSE; + + /* Fall through and check ciphers in generic WPA-PSK code */ + } + + /* Dynamic WEP or LEAP */ + if (!strcmp(key_mgmt, "ieee8021x")) { + if (!(ap_flags & NM_802_11_AP_FLAGS_PRIVACY)) + return FALSE; + + /* If the AP is advertising a WPA IE, make sure it supports WEP ciphers */ + if (ap_wpa != NM_802_11_AP_SEC_NONE) { + if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return FALSE; + + /* quick check; can't use AP if it doesn't support at least one + * WEP cipher in both pairwise and group suites. + */ + if (!(ap_wpa & (NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)) + || !(ap_wpa & (NM_802_11_AP_SEC_GROUP_WEP40 | NM_802_11_AP_SEC_GROUP_WEP104))) + return FALSE; + + /* Match at least one pairwise cipher with AP's capability if the + * wireless-security setting explicitly lists pairwise ciphers + */ + num = nm_setting_wireless_security_get_num_pairwise(s_wireless_sec); + for (i = 0, found = FALSE; i < num; i++) { + cipher = nm_setting_wireless_security_get_pairwise(s_wireless_sec, i); + if ((found = match_cipher(cipher, + "wep40", + ap_wpa, + ap_wpa, + NM_802_11_AP_SEC_PAIR_WEP40))) + break; + if ((found = match_cipher(cipher, + "wep104", + ap_wpa, + ap_wpa, + NM_802_11_AP_SEC_PAIR_WEP104))) + break; + } + if (!found && num) + return FALSE; + + /* Match at least one group cipher with AP's capability if the + * wireless-security setting explicitly lists group ciphers + */ + num = nm_setting_wireless_security_get_num_groups(s_wireless_sec); + for (i = 0, found = FALSE; i < num; i++) { + cipher = nm_setting_wireless_security_get_group(s_wireless_sec, i); + if ((found = match_cipher(cipher, + "wep40", + ap_wpa, + ap_wpa, + NM_802_11_AP_SEC_GROUP_WEP40))) + break; + if ((found = match_cipher(cipher, + "wep104", + ap_wpa, + ap_wpa, + NM_802_11_AP_SEC_GROUP_WEP104))) + break; + } + if (!found && num) + return FALSE; + } + return TRUE; + } + + /* WPA[2]-PSK and WPA[2] Enterprise */ + if (!strcmp(key_mgmt, "wpa-psk") || !strcmp(key_mgmt, "wpa-eap") || !strcmp(key_mgmt, "sae") + || !strcmp(key_mgmt, "owe")) { + if (!strcmp(key_mgmt, "wpa-psk")) { + if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_PSK) + && !(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_PSK)) + return FALSE; + } else if (!strcmp(key_mgmt, "wpa-eap")) { + if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + && !(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return FALSE; + } else if (!strcmp(key_mgmt, "sae")) { + if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_SAE) + && !(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_SAE)) + return FALSE; + } else if (!strcmp(key_mgmt, "owe")) { + if (!NM_FLAGS_ANY(ap_wpa, + NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM) + && !NM_FLAGS_ANY(ap_rsn, + NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) + return FALSE; + } + + // FIXME: should handle WPA and RSN separately here to ensure that + // if the Connection only uses WPA we don't match a cipher against + // the AP's RSN IE instead + + /* Match at least one pairwise cipher with AP's capability if the + * wireless-security setting explicitly lists pairwise ciphers + */ + num = nm_setting_wireless_security_get_num_pairwise(s_wireless_sec); + for (i = 0, found = FALSE; i < num; i++) { + cipher = nm_setting_wireless_security_get_pairwise(s_wireless_sec, i); + if ((found = match_cipher(cipher, "tkip", ap_wpa, ap_rsn, NM_802_11_AP_SEC_PAIR_TKIP))) + break; + if ((found = match_cipher(cipher, "ccmp", ap_wpa, ap_rsn, NM_802_11_AP_SEC_PAIR_CCMP))) + break; + } + if (!found && num) + return FALSE; + + /* Match at least one group cipher with AP's capability if the + * wireless-security setting explicitly lists group ciphers + */ + num = nm_setting_wireless_security_get_num_groups(s_wireless_sec); + for (i = 0, found = FALSE; i < num; i++) { + cipher = nm_setting_wireless_security_get_group(s_wireless_sec, i); + + if ((found = + match_cipher(cipher, "wep40", ap_wpa, ap_rsn, NM_802_11_AP_SEC_GROUP_WEP40))) + break; + if ((found = + match_cipher(cipher, "wep104", ap_wpa, ap_rsn, NM_802_11_AP_SEC_GROUP_WEP104))) + break; + if ((found = match_cipher(cipher, "tkip", ap_wpa, ap_rsn, NM_802_11_AP_SEC_GROUP_TKIP))) + break; + if ((found = match_cipher(cipher, "ccmp", ap_wpa, ap_rsn, NM_802_11_AP_SEC_GROUP_CCMP))) + break; + } + if (!found && num) + return FALSE; + + return TRUE; + } + + return FALSE; +} + +/** + * nm_setting_wireless_get_ssid: + * @setting: the #NMSettingWireless + * + * Returns: (transfer none): the #NMSettingWireless:ssid property of the setting + **/ +GBytes * +nm_setting_wireless_get_ssid(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->ssid; +} + +/** + * nm_setting_wireless_get_mode: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:mode property of the setting + **/ +const char * +nm_setting_wireless_get_mode(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->mode; +} + +/** + * nm_setting_wireless_get_band: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:band property of the setting + **/ +const char * +nm_setting_wireless_get_band(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->band; +} + +/** + * nm_setting_wireless_get_channel: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:channel property of the setting + **/ +guint32 +nm_setting_wireless_get_channel(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->channel; +} + +/** + * nm_setting_wireless_get_bssid: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:bssid property of the setting + **/ +const char * +nm_setting_wireless_get_bssid(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->bssid; +} + +/** + * nm_setting_wireless_get_rate: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:rate property of the setting + **/ +guint32 +nm_setting_wireless_get_rate(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->rate; +} + +/** + * nm_setting_wireless_get_tx_power: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:tx-power property of the setting + **/ +guint32 +nm_setting_wireless_get_tx_power(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->tx_power; +} + +/** + * nm_setting_wireless_get_mac_address: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:mac-address property of the setting + **/ +const char * +nm_setting_wireless_get_mac_address(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->device_mac_address; +} + +/** + * nm_setting_wireless_get_cloned_mac_address: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:cloned-mac-address property of the setting + **/ +const char * +nm_setting_wireless_get_cloned_mac_address(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->cloned_mac_address; +} + +/** + * nm_setting_wireless_get_generate_mac_address_mask: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:generate-mac-address-mask property of the setting + * + * Since: 1.4 + **/ +const char * +nm_setting_wireless_get_generate_mac_address_mask(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->generate_mac_address_mask; +} + +/** + * nm_setting_wireless_get_mac_address_blacklist: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:mac-address-blacklist property of the setting + **/ +const char *const * +nm_setting_wireless_get_mac_address_blacklist(NMSettingWireless *setting) +{ + NMSettingWirelessPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + return (const char *const *) priv->mac_address_blacklist->data; +} + +/** + * nm_setting_wireless_get_num_mac_blacklist_items: + * @setting: the #NMSettingWireless + * + * Returns: the number of blacklisted MAC addresses + **/ +guint32 +nm_setting_wireless_get_num_mac_blacklist_items(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->mac_address_blacklist->len; +} + +/** + * nm_setting_wireless_get_mac_blacklist_item: + * @setting: the #NMSettingWireless + * @idx: the zero-based index of the MAC address entry + * + * Returns: the blacklisted MAC address string (hex-digits-and-colons notation) + * at index @idx + **/ +const char * +nm_setting_wireless_get_mac_blacklist_item(NMSettingWireless *setting, guint32 idx) +{ + NMSettingWirelessPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL); + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + g_return_val_if_fail(idx <= priv->mac_address_blacklist->len, NULL); + + return g_array_index(priv->mac_address_blacklist, const char *, idx); +} + +/** + * nm_setting_wireless_add_mac_blacklist_item: + * @setting: the #NMSettingWireless + * @mac: the MAC address string (hex-digits-and-colons notation) to blacklist + * + * Adds a new MAC address to the #NMSettingWireless:mac-address-blacklist property. + * + * Returns: %TRUE if the MAC address was added; %FALSE if the MAC address + * is invalid or was already present + **/ +gboolean +nm_setting_wireless_add_mac_blacklist_item(NMSettingWireless *setting, const char *mac) +{ + NMSettingWirelessPrivate *priv; + const char * candidate; + int i; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), FALSE); + g_return_val_if_fail(mac != NULL, FALSE); + + if (!nm_utils_hwaddr_valid(mac, ETH_ALEN)) + return FALSE; + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + for (i = 0; i < priv->mac_address_blacklist->len; i++) { + candidate = g_array_index(priv->mac_address_blacklist, char *, i); + if (nm_utils_hwaddr_matches(mac, -1, candidate, -1)) + return FALSE; + } + + mac = nm_utils_hwaddr_canonical(mac, ETH_ALEN); + g_array_append_val(priv->mac_address_blacklist, mac); + _notify(setting, PROP_MAC_ADDRESS_BLACKLIST); + return TRUE; +} + +/** + * nm_setting_wireless_remove_mac_blacklist_item: + * @setting: the #NMSettingWireless + * @idx: index number of the MAC address + * + * Removes the MAC address at index @idx from the blacklist. + **/ +void +nm_setting_wireless_remove_mac_blacklist_item(NMSettingWireless *setting, guint32 idx) +{ + NMSettingWirelessPrivate *priv; + + g_return_if_fail(NM_IS_SETTING_WIRELESS(setting)); + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + g_return_if_fail(idx < priv->mac_address_blacklist->len); + + g_array_remove_index(priv->mac_address_blacklist, idx); + _notify(setting, PROP_MAC_ADDRESS_BLACKLIST); +} + +/** + * nm_setting_wireless_remove_mac_blacklist_item_by_value: + * @setting: the #NMSettingWireless + * @mac: the MAC address string (hex-digits-and-colons notation) to remove from + * the blacklist + * + * Removes the MAC address @mac from the blacklist. + * + * Returns: %TRUE if the MAC address was found and removed; %FALSE if it was not. + **/ +gboolean +nm_setting_wireless_remove_mac_blacklist_item_by_value(NMSettingWireless *setting, const char *mac) +{ + NMSettingWirelessPrivate *priv; + const char * candidate; + int i; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), FALSE); + g_return_val_if_fail(mac != NULL, FALSE); + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + for (i = 0; i < priv->mac_address_blacklist->len; i++) { + candidate = g_array_index(priv->mac_address_blacklist, char *, i); + if (!nm_utils_hwaddr_matches(mac, -1, candidate, -1)) { + g_array_remove_index(priv->mac_address_blacklist, i); + _notify(setting, PROP_MAC_ADDRESS_BLACKLIST); + return TRUE; + } + } + return FALSE; +} + +/** + * nm_setting_wireless_clear_mac_blacklist_items: + * @setting: the #NMSettingWireless + * + * Removes all blacklisted MAC addresses. + **/ +void +nm_setting_wireless_clear_mac_blacklist_items(NMSettingWireless *setting) +{ + g_return_if_fail(NM_IS_SETTING_WIRELESS(setting)); + + g_array_set_size(NM_SETTING_WIRELESS_GET_PRIVATE(setting)->mac_address_blacklist, 0); + _notify(setting, PROP_MAC_ADDRESS_BLACKLIST); +} + +/** + * nm_setting_wireless_get_mtu: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:mtu property of the setting + **/ +guint32 +nm_setting_wireless_get_mtu(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->mtu; +} + +/** + * nm_setting_wireless_get_hidden: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:hidden property of the setting + **/ +gboolean +nm_setting_wireless_get_hidden(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), FALSE); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->hidden; +} + +/** + * nm_setting_wireless_get_powersave: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:powersave property of the setting + * + * Since: 1.2 + **/ +guint32 +nm_setting_wireless_get_powersave(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->powersave; +} + +/** + * nm_setting_wireless_get_mac_address_randomization: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:mac-address-randomization property of the + * setting + * + * Since: 1.2 + **/ +NMSettingMacRandomization +nm_setting_wireless_get_mac_address_randomization(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->mac_address_randomization; +} + +/** + * nm_setting_wireless_add_seen_bssid: + * @setting: the #NMSettingWireless + * @bssid: the new BSSID to add to the list + * + * Adds a new Wi-Fi AP's BSSID to the previously seen BSSID list of the setting. + * NetworkManager now tracks previously seen BSSIDs internally so this function + * no longer has much use. Actually, changes you make using this function will + * not be preserved. + * + * Returns: %TRUE if @bssid was already known, %FALSE if not + **/ +gboolean +nm_setting_wireless_add_seen_bssid(NMSettingWireless *setting, const char *bssid) +{ + NMSettingWirelessPrivate *priv; + gs_free char * lower_bssid = NULL; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), FALSE); + g_return_val_if_fail(bssid != NULL, FALSE); + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + + lower_bssid = g_ascii_strdown(bssid, -1); + + if (!priv->seen_bssids) { + priv->seen_bssids = g_ptr_array_new_with_free_func(g_free); + } else { + if (nm_utils_strv_find_first((char **) priv->seen_bssids->pdata, + priv->seen_bssids->len, + lower_bssid) + >= 0) + return FALSE; + } + + g_ptr_array_add(priv->seen_bssids, g_steal_pointer(&lower_bssid)); + _notify(setting, PROP_SEEN_BSSIDS); + return TRUE; +} + +/** + * nm_setting_wireless_get_num_seen_bssids: + * @setting: the #NMSettingWireless + * + * Returns: the number of BSSIDs in the previously seen BSSID list + **/ +guint32 +nm_setting_wireless_get_num_seen_bssids(NMSettingWireless *setting) +{ + NMSettingWirelessPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + + return priv->seen_bssids ? priv->seen_bssids->len : 0u; +} + +/** + * nm_setting_wireless_get_seen_bssid: + * @setting: the #NMSettingWireless + * @i: index of a BSSID in the previously seen BSSID list + * + * Returns: the BSSID at index @i + **/ +const char * +nm_setting_wireless_get_seen_bssid(NMSettingWireless *setting, guint32 i) +{ + NMSettingWirelessPrivate *priv; + + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), 0); + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + + if (!priv->seen_bssids || i >= priv->seen_bssids->len) + return NULL; + + return priv->seen_bssids->pdata[i]; +} + +static GVariant * +_to_dbus_fcn_seen_bssids(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMSettingWirelessPrivate *priv; + + if (options && options->seen_bssids) { + return options->seen_bssids[0] ? g_variant_new_strv(options->seen_bssids, -1) : NULL; + } + + priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + + if (!priv->seen_bssids || priv->seen_bssids->len == 0) + return NULL; + + return g_variant_new_strv((const char *const *) priv->seen_bssids->pdata, + priv->seen_bssids->len); +} + +/** + * nm_setting_wireless_get_ap_isolation: + * @setting: the #NMSettingWireless + * + * Returns: the #NMSettingWireless:ap-isolation property of the setting + * + * Since: 1.28 + */ +NMTernary +nm_setting_wireless_get_ap_isolation(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NM_TERNARY_DEFAULT); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->ap_isolation; +} + +/*****************************************************************************/ + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + const char * valid_modes[] = {NM_SETTING_WIRELESS_MODE_INFRA, + NM_SETTING_WIRELESS_MODE_ADHOC, + NM_SETTING_WIRELESS_MODE_AP, + NM_SETTING_WIRELESS_MODE_MESH, + NULL}; + const char * valid_bands[] = {"a", "bg", NULL}; + guint i; + gsize length; + GError * local = NULL; + + if (!priv->ssid) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("property is missing")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_SSID); + return FALSE; + } + + length = g_bytes_get_size(priv->ssid); + if (length == 0 || length > 32) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("SSID length is out of range <1-32> bytes")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_SSID); + return FALSE; + } + + if (priv->mode && !g_strv_contains(valid_modes, priv->mode)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid Wi-Fi mode"), + priv->mode); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MODE); + return FALSE; + } + + if (priv->band && !g_strv_contains(valid_bands, priv->band)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid band"), + priv->band); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_BAND); + return FALSE; + } + + if (priv->channel && !priv->band) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("'%s' requires setting '%s' property"), + NM_SETTING_WIRELESS_CHANNEL, + NM_SETTING_WIRELESS_BAND); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_BAND); + return FALSE; + } + + if (priv->channel) { + if (!nm_utils_wifi_is_channel_valid(priv->channel, priv->band)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%d' is not a valid channel"), + priv->channel); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_CHANNEL); + return FALSE; + } + } + + if ((g_strcmp0(priv->mode, NM_SETTING_WIRELESS_MODE_MESH) == 0) + && !(priv->channel && priv->band)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY, + _("'%s' requires '%s' and '%s' property"), + priv->mode, + NM_SETTING_WIRELESS_BAND, + NM_SETTING_WIRELESS_CHANNEL); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MODE); + return FALSE; + } + + if (priv->bssid && !nm_utils_hwaddr_valid(priv->bssid, ETH_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_BSSID); + return FALSE; + } + + if (priv->device_mac_address && !nm_utils_hwaddr_valid(priv->device_mac_address, ETH_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MAC_ADDRESS); + return FALSE; + } + + if (priv->cloned_mac_address && !NM_CLONED_MAC_IS_SPECIAL(priv->cloned_mac_address) + && !nm_utils_hwaddr_valid(priv->cloned_mac_address, ETH_ALEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS); + return FALSE; + } + + /* generate-mac-address-mask only makes sense with cloned-mac-address "random" or + * "stable". Still, let's not be so strict about that and accept the value + * even if it is unused. */ + if (!_nm_utils_generate_mac_address_mask_parse(priv->generate_mac_address_mask, + NULL, + NULL, + NULL, + &local)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + local->message); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK); + g_error_free(local); + return FALSE; + } + + for (i = 0; i < priv->mac_address_blacklist->len; i++) { + const char *mac = g_array_index(priv->mac_address_blacklist, const char *, i); + + if (!nm_utils_hwaddr_valid(mac, ETH_ALEN)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid MAC address"), + mac); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST); + return FALSE; + } + } + + if (priv->seen_bssids) { + for (i = 0; i < priv->seen_bssids->len; i++) { + const char *b; + + b = priv->seen_bssids->pdata[i]; + if (!nm_utils_hwaddr_valid(b, ETH_ALEN)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'%s' is not a valid MAC address"), + b); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_SEEN_BSSIDS); + return FALSE; + } + } + } + + if (!NM_IN_SET(priv->mac_address_randomization, + NM_SETTING_MAC_RANDOMIZATION_DEFAULT, + NM_SETTING_MAC_RANDOMIZATION_NEVER, + NM_SETTING_MAC_RANDOMIZATION_ALWAYS)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid value")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION); + return FALSE; + } + + if (NM_FLAGS_ANY(priv->wowl, NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS)) { + if (!nm_utils_is_power_of_two(priv->wowl)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Wake-on-WLAN mode 'default' and 'ignore' are exclusive flags")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_WAKE_ON_WLAN); + return FALSE; + } + } else if (NM_FLAGS_ANY(priv->wowl, ~NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Wake-on-WLAN trying to set unknown flag")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_WAKE_ON_WLAN); + return FALSE; + } + + if (priv->ap_isolation != NM_TERNARY_DEFAULT + && !nm_streq0(priv->mode, NM_SETTING_WIRELESS_MODE_AP)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("AP isolation can be set only in AP mode")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_AP_ISOLATION); + return FALSE; + } + + /* from here on, check for NM_SETTING_VERIFY_NORMALIZABLE conditions. */ + + if (priv->cloned_mac_address) { + if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS + && nm_streq(priv->cloned_mac_address, "random")) + goto mac_addr_rand_ok; + if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_NEVER + && nm_streq(priv->cloned_mac_address, "permanent")) + goto mac_addr_rand_ok; + if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT) + goto mac_addr_rand_ok; + } else if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT) + goto mac_addr_rand_ok; + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("conflicting value of mac-address-randomization and cloned-mac-address")); + g_prefix_error(error, + "%s.%s: ", + NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS); + return NM_SETTING_VERIFY_NORMALIZABLE; +mac_addr_rand_ok: + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + if (nm_streq(sett_info->property_infos[property_idx].name, + NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS)) { + return !set_b + || nm_streq0(NM_SETTING_WIRELESS_GET_PRIVATE(set_a)->cloned_mac_address, + NM_SETTING_WIRELESS_GET_PRIVATE(set_b)->cloned_mac_address); + } + + return NM_SETTING_CLASS(nm_setting_wireless_parent_class) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); +} + +/*****************************************************************************/ + +static GVariant * +nm_setting_wireless_get_security(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + + if (!connection) + return NULL; + + if (!nm_connection_get_setting_wireless_security(connection)) + return NULL; + + return g_variant_new_string(NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); +} + +/** + * nm_setting_wireless_get_wake_on_wlan: + * @setting: the #NMSettingWireless + * + * Returns the Wake-on-WLAN options enabled for the connection + * + * Returns: the Wake-on-WLAN options + * + * Since: 1.12 + */ +NMSettingWirelessWakeOnWLan +nm_setting_wireless_get_wake_on_wlan(NMSettingWireless *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE); + + return NM_SETTING_WIRELESS_GET_PRIVATE(setting)->wowl; +} + +static void +clear_blacklist_item(char **item_p) +{ + g_free(*item_p); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingWireless * setting = NM_SETTING_WIRELESS(object); + NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_SSID: + g_value_set_boxed(value, nm_setting_wireless_get_ssid(setting)); + break; + case PROP_MODE: + g_value_set_string(value, nm_setting_wireless_get_mode(setting)); + break; + case PROP_BAND: + g_value_set_string(value, nm_setting_wireless_get_band(setting)); + break; + case PROP_CHANNEL: + g_value_set_uint(value, nm_setting_wireless_get_channel(setting)); + break; + case PROP_BSSID: + g_value_set_string(value, nm_setting_wireless_get_bssid(setting)); + break; + case PROP_RATE: + g_value_set_uint(value, nm_setting_wireless_get_rate(setting)); + break; + case PROP_TX_POWER: + g_value_set_uint(value, nm_setting_wireless_get_tx_power(setting)); + break; + case PROP_MAC_ADDRESS: + g_value_set_string(value, nm_setting_wireless_get_mac_address(setting)); + break; + case PROP_CLONED_MAC_ADDRESS: + g_value_set_string(value, nm_setting_wireless_get_cloned_mac_address(setting)); + break; + case PROP_GENERATE_MAC_ADDRESS_MASK: + g_value_set_string(value, nm_setting_wireless_get_generate_mac_address_mask(setting)); + break; + case PROP_MAC_ADDRESS_BLACKLIST: + g_value_set_boxed(value, (char **) priv->mac_address_blacklist->data); + break; + case PROP_MTU: + g_value_set_uint(value, nm_setting_wireless_get_mtu(setting)); + break; + case PROP_SEEN_BSSIDS: + g_value_take_boxed(value, + priv->seen_bssids ? nm_utils_strv_dup((char **) priv->seen_bssids->pdata, + priv->seen_bssids->len, + TRUE) + : NULL); + break; + case PROP_HIDDEN: + g_value_set_boolean(value, nm_setting_wireless_get_hidden(setting)); + break; + case PROP_POWERSAVE: + g_value_set_uint(value, nm_setting_wireless_get_powersave(setting)); + break; + case PROP_MAC_ADDRESS_RANDOMIZATION: + g_value_set_uint(value, nm_setting_wireless_get_mac_address_randomization(setting)); + break; + case PROP_WAKE_ON_WLAN: + g_value_set_uint(value, nm_setting_wireless_get_wake_on_wlan(setting)); + break; + case PROP_AP_ISOLATION: + g_value_set_enum(value, priv->ap_isolation); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE(object); + const char *const * blacklist; + const char * mac; + gboolean bool_val; + + switch (prop_id) { + case PROP_SSID: + if (priv->ssid) + g_bytes_unref(priv->ssid); + priv->ssid = g_value_dup_boxed(value); + break; + case PROP_MODE: + g_free(priv->mode); + priv->mode = g_value_dup_string(value); + break; + case PROP_BAND: + g_free(priv->band); + priv->band = g_value_dup_string(value); + break; + case PROP_CHANNEL: + priv->channel = g_value_get_uint(value); + break; + case PROP_BSSID: + g_free(priv->bssid); + priv->bssid = g_value_dup_string(value); + break; + case PROP_RATE: + priv->rate = g_value_get_uint(value); + break; + case PROP_TX_POWER: + priv->tx_power = g_value_get_uint(value); + break; + case PROP_MAC_ADDRESS: + g_free(priv->device_mac_address); + priv->device_mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN); + break; + case PROP_CLONED_MAC_ADDRESS: + bool_val = !!priv->cloned_mac_address; + g_free(priv->cloned_mac_address); + priv->cloned_mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN); + if (bool_val && !priv->cloned_mac_address) { + /* cloned-mac-address was set before but was now explicitly cleared. + * In this case, we also clear mac-address-randomization flag */ + if (priv->mac_address_randomization != NM_SETTING_MAC_RANDOMIZATION_DEFAULT) { + priv->mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT; + _notify(NM_SETTING_WIRELESS(object), PROP_MAC_ADDRESS_RANDOMIZATION); + } + } + break; + case PROP_GENERATE_MAC_ADDRESS_MASK: + g_free(priv->generate_mac_address_mask); + priv->generate_mac_address_mask = g_value_dup_string(value); + break; + case PROP_MAC_ADDRESS_BLACKLIST: + blacklist = g_value_get_boxed(value); + g_array_set_size(priv->mac_address_blacklist, 0); + if (blacklist && blacklist[0]) { + gsize i; + + for (i = 0; blacklist[i]; i++) { + mac = _nm_utils_hwaddr_canonical_or_invalid(blacklist[i], ETH_ALEN); + g_array_append_val(priv->mac_address_blacklist, mac); + } + } + break; + case PROP_MTU: + priv->mtu = g_value_get_uint(value); + break; + case PROP_SEEN_BSSIDS: + { + gs_unref_ptrarray GPtrArray *arr_old = NULL; + const char *const * strv; + + arr_old = g_steal_pointer(&priv->seen_bssids); + + strv = g_value_get_boxed(value); + if (strv && strv[0]) { + gsize i, l; + + l = NM_PTRARRAY_LEN(strv); + priv->seen_bssids = g_ptr_array_new_full(l, g_free); + for (i = 0; i < l; i++) + g_ptr_array_add(priv->seen_bssids, g_strdup(strv[i])); + } + break; + } + case PROP_HIDDEN: + priv->hidden = g_value_get_boolean(value); + break; + case PROP_POWERSAVE: + priv->powersave = g_value_get_uint(value); + break; + case PROP_MAC_ADDRESS_RANDOMIZATION: + priv->mac_address_randomization = g_value_get_uint(value); + break; + case PROP_WAKE_ON_WLAN: + priv->wowl = g_value_get_uint(value); + break; + case PROP_AP_ISOLATION: + priv->ap_isolation = g_value_get_enum(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_wireless_init(NMSettingWireless *setting) +{ + NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting); + + /* We use GArray rather than GPtrArray so it will automatically be NULL-terminated */ + priv->mac_address_blacklist = g_array_new(TRUE, FALSE, sizeof(char *)); + g_array_set_clear_func(priv->mac_address_blacklist, (GDestroyNotify) clear_blacklist_item); + + priv->wowl = NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT; + priv->ap_isolation = NM_TERNARY_DEFAULT; +} + +/** + * nm_setting_wireless_new: + * + * Creates a new #NMSettingWireless object with default values. + * + * Returns: (transfer full): the new empty #NMSettingWireless object + **/ +NMSetting * +nm_setting_wireless_new(void) +{ + return g_object_new(NM_TYPE_SETTING_WIRELESS, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE(object); + + g_free(priv->mode); + g_free(priv->band); + + if (priv->ssid) + g_bytes_unref(priv->ssid); + g_free(priv->bssid); + g_free(priv->device_mac_address); + g_free(priv->cloned_mac_address); + g_free(priv->generate_mac_address_mask); + g_array_unref(priv->mac_address_blacklist); + nm_clear_pointer(&priv->seen_bssids, g_ptr_array_unref); + + G_OBJECT_CLASS(nm_setting_wireless_parent_class)->finalize(object); +} + +static void +nm_setting_wireless_class_init(NMSettingWirelessClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + GArray * properties_override = _nm_sett_info_property_override_create_array(); + + g_type_class_add_private(klass, sizeof(NMSettingWirelessPrivate)); + + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + setting_class->compare_property = compare_property; + + /** + * NMSettingWireless:ssid: + * + * SSID of the Wi-Fi network. Must be specified. + **/ + /* ---keyfile--- + * property: ssid + * format: string (or decimal-byte list - obsolete) + * description: SSID of Wi-Fi network. + * example: ssid=Quick Net + * ---end--- + * ---ifcfg-rh--- + * property: ssid + * variable: ESSID + * description: SSID of Wi-Fi network. + * example: ESSID="Quick Net" + * ---end--- + */ + obj_properties[PROP_SSID] = g_param_spec_boxed(NM_SETTING_WIRELESS_SSID, + "", + "", + G_TYPE_BYTES, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:mode: + * + * Wi-Fi network mode; one of "infrastructure", "mesh", "adhoc" or "ap". If blank, + * infrastructure is assumed. + **/ + /* ---ifcfg-rh--- + * property: mode + * variable: MODE + * values: Ad-Hoc, Managed (Auto) [case insensitive] + * description: Wi-Fi network mode. + * ---end--- + */ + obj_properties[PROP_MODE] = g_param_spec_string(NM_SETTING_WIRELESS_MODE, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:band: + * + * 802.11 frequency band of the network. One of "a" for 5GHz 802.11a or + * "bg" for 2.4GHz 802.11. This will lock associations to the Wi-Fi network + * to the specific band, i.e. if "a" is specified, the device will not + * associate with the same network in the 2.4GHz band even if the network's + * settings are compatible. This setting depends on specific driver + * capability and may not work with all drivers. + **/ + /* ---ifcfg-rh--- + * property: band + * variable: BAND(+) + * values: a, bg + * description: BAND alone is honored, but CHANNEL overrides BAND since it + * implies a band. + * example: BAND=bg + * ---end--- + */ + obj_properties[PROP_BAND] = g_param_spec_string(NM_SETTING_WIRELESS_BAND, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:channel: + * + * Wireless channel to use for the Wi-Fi connection. The device will only + * join (or create for Ad-Hoc networks) a Wi-Fi network on the specified + * channel. Because channel numbers overlap between bands, this property + * also requires the "band" property to be set. + **/ + /* ---ifcfg-rh--- + * property: channel + * variable: CHANNEL + * description: Channel used for the Wi-Fi communication. + * Channels greater than 14 mean "a" band, otherwise the + * band is "bg". + * example: CHANNEL=6 + * ---end--- + */ + obj_properties[PROP_CHANNEL] = g_param_spec_uint(NM_SETTING_WIRELESS_CHANNEL, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:bssid: + * + * If specified, directs the device to only associate with the given access + * point. This capability is highly driver dependent and not supported by + * all devices. Note: this property does not control the BSSID used when + * creating an Ad-Hoc network and is unlikely to in the future. + **/ + /* ---ifcfg-rh--- + * property: bssid + * variable: BSSID(+) + * description: Restricts association only to a single AP. + * example: BSSID=00:1E:BD:64:83:21 + * ---end--- + */ + obj_properties[PROP_BSSID] = g_param_spec_string(NM_SETTING_WIRELESS_BSSID, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_BSSID], + &nm_sett_info_propert_type_mac_address); + + /** + * NMSettingWireless:rate: + * + * If non-zero, directs the device to only use the specified bitrate for + * communication with the access point. Units are in Kb/s, ie 5500 = 5.5 + * Mbit/s. This property is highly driver dependent and not all devices + * support setting a static bitrate. + **/ + /* ---ifcfg-rh--- + * property: rate + * variable: (none) + * description: This property is not handled by ifcfg-rh plugin. + * ---end--- + */ + obj_properties[PROP_RATE] = g_param_spec_uint(NM_SETTING_WIRELESS_RATE, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:tx-power: + * + * If non-zero, directs the device to use the specified transmit power. + * Units are dBm. This property is highly driver dependent and not all + * devices support setting a static transmit power. + **/ + /* ---ifcfg-rh--- + * property: tx-power + * variable: (none) + * description: This property is not handled by ifcfg-rh plugin. + * ---end--- + */ + obj_properties[PROP_TX_POWER] = g_param_spec_uint( + NM_SETTING_WIRELESS_TX_POWER, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:mac-address: + * + * If specified, this connection will only apply to the Wi-Fi device whose + * permanent MAC address matches. This property does not change the MAC + * address of the device (i.e. MAC spoofing). + **/ + /* ---keyfile--- + * property: mac-address + * format: usual hex-digits-and-colons notation + * description: MAC address in traditional hex-digits-and-colons notation + * (e.g. 00:22:68:12:79:A2), or semicolon separated list of 6 bytes (obsolete) + * (e.g. 0;34;104;18;121;162). + * ---end--- + * ---ifcfg-rh--- + * property: mac-address + * variable: HWADDR + * description: Hardware address of the device in traditional hex-digits-and-colons + * notation (e.g. 00:22:68:14:5A:05). + * Note that for initscripts this is the current MAC address of the device as found + * during ifup. For NetworkManager this is the permanent MAC address. Or in case no + * permanent MAC address exists, the MAC address initially configured on the device. + * ---end--- + */ + obj_properties[PROP_MAC_ADDRESS] = + g_param_spec_string(NM_SETTING_WIRELESS_MAC_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_MAC_ADDRESS], + &nm_sett_info_propert_type_mac_address); + + /** + * NMSettingWireless:cloned-mac-address: + * + * If specified, request that the device use this MAC address instead. + * This is known as MAC cloning or spoofing. + * + * Beside explicitly specifying a MAC address, the special values "preserve", "permanent", + * "random" and "stable" are supported. + * "preserve" means not to touch the MAC address on activation. + * "permanent" means to use the permanent hardware address of the device. + * "random" creates a random MAC address on each connect. + * "stable" creates a hashed MAC address based on connection.stable-id and a + * machine dependent key. + * + * If unspecified, the value can be overwritten via global defaults, see manual + * of NetworkManager.conf. If still unspecified, it defaults to "preserve" + * (older versions of NetworkManager may use a different default value). + * + * On D-Bus, this field is expressed as "assigned-mac-address" or the deprecated + * "cloned-mac-address". + **/ + /* ---keyfile--- + * property: cloned-mac-address + * format: usual hex-digits-and-colons notation + * description: Cloned MAC address in traditional hex-digits-and-colons notation + * (e.g. 00:22:68:12:79:B2), or semicolon separated list of 6 bytes (obsolete) + * (e.g. 0;34;104;18;121;178). + * ---end--- + * ---ifcfg-rh--- + * property: cloned-mac-address + * variable: MACADDR + * description: Cloned (spoofed) MAC address in traditional hex-digits-and-colons + * notation (e.g. 00:22:68:14:5A:99). + * ---end--- + * ---dbus--- + * property: cloned-mac-address + * format: byte array + * description: This D-Bus field is deprecated in favor of "assigned-mac-address" + * which is more flexible and allows specifying special variants like "random". + * For libnm and nmcli, this field is called "cloned-mac-address". + * ---end--- + */ + obj_properties[PROP_CLONED_MAC_ADDRESS] = g_param_spec_string( + NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj(properties_override, + obj_properties[PROP_CLONED_MAC_ADDRESS], + &nm_sett_info_propert_type_cloned_mac_address); + + /* ---dbus--- + * property: assigned-mac-address + * format: string + * description: The new field for the cloned MAC address. It can be either + * a hardware address in ASCII representation, or one of the special values + * "preserve", "permanent", "random" or "stable". + * This field replaces the deprecated "cloned-mac-address" on D-Bus, which + * can only contain explicit hardware addresses. Note that this property + * only exists in D-Bus API. libnm and nmcli continue to call this property + * "cloned-mac-address". + * ---end--- + */ + _nm_properties_override_dbus(properties_override, + "assigned-mac-address", + &nm_sett_info_propert_type_assigned_mac_address); + + /** + * NMSettingWireless:generate-mac-address-mask: + * + * With #NMSettingWireless:cloned-mac-address setting "random" or "stable", + * by default all bits of the MAC address are scrambled and a locally-administered, + * unicast MAC address is created. This property allows to specify that certain bits + * are fixed. Note that the least significant bit of the first MAC address will + * always be unset to create a unicast MAC address. + * + * If the property is %NULL, it is eligible to be overwritten by a default + * connection setting. If the value is still %NULL or an empty string, the + * default is to create a locally-administered, unicast MAC address. + * + * If the value contains one MAC address, this address is used as mask. The set + * bits of the mask are to be filled with the current MAC address of the device, + * while the unset bits are subject to randomization. + * Setting "FE:FF:FF:00:00:00" means to preserve the OUI of the current MAC address + * and only randomize the lower 3 bytes using the "random" or "stable" algorithm. + * + * If the value contains one additional MAC address after the mask, + * this address is used instead of the current MAC address to fill the bits + * that shall not be randomized. For example, a value of + * "FE:FF:FF:00:00:00 68:F7:28:00:00:00" will set the OUI of the MAC address + * to 68:F7:28, while the lower bits are randomized. A value of + * "02:00:00:00:00:00 00:00:00:00:00:00" will create a fully scrambled + * globally-administered, burned-in MAC address. + * + * If the value contains more than one additional MAC addresses, one of + * them is chosen randomly. For example, "02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00" + * will create a fully scrambled MAC address, randomly locally or globally + * administered. + **/ + /* ---ifcfg-rh--- + * property: generate-mac-address-mask + * variable: GENERATE_MAC_ADDRESS_MASK(+) + * description: the MAC address mask for generating randomized and stable + * cloned-mac-address. + * ---end--- + */ + obj_properties[PROP_GENERATE_MAC_ADDRESS_MASK] = g_param_spec_string( + NM_SETTING_WIRELESS_GENERATE_MAC_ADDRESS_MASK, + "", + "", + NULL, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:mac-address-blacklist: + * + * A list of permanent MAC addresses of Wi-Fi devices to which this + * connection should never apply. Each MAC address should be given in the + * standard hex-digits-and-colons notation (eg "00:11:22:33:44:55"). + **/ + /* ---keyfile--- + * property: mac-address-blacklist + * format: list of MACs (separated with semicolons) + * description: MAC address blacklist. + * example: mac-address-blacklist= 00:22:68:12:79:A6;00:22:68:12:79:78 + * ---end--- + * ---ifcfg-rh--- + * property: mac-address-blacklist + * variable: HWADDR_BLACKLIST(+) + * description: It denies usage of the connection for any device whose address + * is listed. + * ---end--- + */ + obj_properties[PROP_MAC_ADDRESS_BLACKLIST] = g_param_spec_boxed( + NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:seen-bssids: + * + * A list of BSSIDs (each BSSID formatted as a MAC address like + * "00:11:22:33:44:55") that have been detected as part of the Wi-Fi + * network. NetworkManager internally tracks previously seen BSSIDs. The + * property is only meant for reading and reflects the BSSID list of + * NetworkManager. The changes you make to this property will not be + * preserved. + **/ + /* ---ifcfg-rh--- + * property: seen-bssids + * variable: (none) + * description: This property is not handled by ifcfg-rh plugin. + * ---end--- + */ + obj_properties[PROP_SEEN_BSSIDS] = g_param_spec_boxed( + NM_SETTING_WIRELESS_SEEN_BSSIDS, + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS); + _nm_properties_override_gobj( + properties_override, + obj_properties[PROP_SEEN_BSSIDS], + NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING_ARRAY, + .to_dbus_fcn = _to_dbus_fcn_seen_bssids, )); + + /** + * NMSettingWireless:mtu: + * + * If non-zero, only transmit packets of the specified size or smaller, + * breaking larger packets up into multiple Ethernet frames. + **/ + /* ---ifcfg-rh--- + * property: mtu + * variable: MTU + * description: MTU of the wireless interface. + * ---end--- + */ + obj_properties[PROP_MTU] = g_param_spec_uint(NM_SETTING_WIRELESS_MTU, + "", + "", + 0, + G_MAXUINT32, + 0, + G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE + | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:hidden: + * + * If %TRUE, indicates that the network is a non-broadcasting network that + * hides its SSID. This works both in infrastructure and AP mode. + * + * In infrastructure mode, various workarounds are used for a more reliable + * discovery of hidden networks, such as probe-scanning the SSID. However, + * these workarounds expose inherent insecurities with hidden SSID networks, + * and thus hidden SSID networks should be used with caution. + * + * In AP mode, the created network does not broadcast its SSID. + * + * Note that marking the network as hidden may be a privacy issue for you + * (in infrastructure mode) or client stations (in AP mode), as the explicit + * probe-scans are distinctly recognizable on the air. + * + **/ + /* ---ifcfg-rh--- + * property: hidden + * variable: SSID_HIDDEN(+) + * description: Whether the network hides the SSID. + * ---end--- + */ + _nm_setting_property_define_boolean(properties_override, + obj_properties, + NM_SETTING_WIRELESS_HIDDEN, + PROP_HIDDEN, + FALSE, + NM_SETTING_PARAM_NONE, + nm_setting_wireless_get_hidden); + + /** + * NMSettingWireless:powersave: + * + * One of %NM_SETTING_WIRELESS_POWERSAVE_DISABLE (disable Wi-Fi power + * saving), %NM_SETTING_WIRELESS_POWERSAVE_ENABLE (enable Wi-Fi power + * saving), %NM_SETTING_WIRELESS_POWERSAVE_IGNORE (don't touch currently + * configure setting) or %NM_SETTING_WIRELESS_POWERSAVE_DEFAULT (use the + * globally configured value). All other values are reserved. + * + * Since: 1.2 + **/ + /* ---ifcfg-rh--- + * property: powersave + * variable: POWERSAVE(+) + * values: default, ignore, enable, disable + * description: Enables or disables Wi-Fi power saving. + * example: POWERSAVE=enable + * ---end--- + */ + obj_properties[PROP_POWERSAVE] = g_param_spec_uint(NM_SETTING_WIRELESS_POWERSAVE, + "", + "", + 0, + G_MAXUINT32, + NM_SETTING_WIRELESS_POWERSAVE_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:mac-address-randomization: + * + * One of %NM_SETTING_MAC_RANDOMIZATION_DEFAULT (never randomize unless + * the user has set a global default to randomize and the supplicant + * supports randomization), %NM_SETTING_MAC_RANDOMIZATION_NEVER (never + * randomize the MAC address), or %NM_SETTING_MAC_RANDOMIZATION_ALWAYS + * (always randomize the MAC address). This property is deprecated for + * 'cloned-mac-address'. + * + * Since: 1.2 + * Deprecated: 1.4: Deprecated by NMSettingWireless:cloned-mac-address property. + **/ + /* ---ifcfg-rh--- + * property: mac-address-randomization + * variable: MAC_ADDRESS_RANDOMIZATION(+) + * values: default, never, always + * description: Enables or disables Wi-Fi MAC address randomization. + * example: MAC_ADDRESS_RANDOMIZATION=always + * ---end--- + */ + obj_properties[PROP_MAC_ADDRESS_RANDOMIZATION] = + g_param_spec_uint(NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION, + "", + "", + 0, + G_MAXUINT32, + NM_SETTING_MAC_RANDOMIZATION_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /* Compatibility for deprecated property */ + /* ---ifcfg-rh--- + * property: security + * variable: (none) + * description: This property is deprecated and not handled by ifcfg-rh-plugin. + * ---end--- + * ---dbus--- + * property: security + * description: This property is deprecated, but can be set to the value + * '802-11-wireless-security' when a wireless security setting is also + * present in the connection dictionary, for compatibility with very old + * NetworkManager daemons. + * ---end--- + */ + _nm_properties_override_dbus( + properties_override, + "security", + NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING, + .to_dbus_fcn = nm_setting_wireless_get_security, )); + + /** + * NMSettingWireless:wake-on-wlan: + * + * The #NMSettingWirelessWakeOnWLan options to enable. Not all devices support all options. + * May be any combination of %NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY, + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT, + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC, + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE, + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST, + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE, + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE, + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP or the special values + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT (to use global settings) and + * %NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE (to disable management of Wake-on-LAN in + * NetworkManager). + * + * Since: 1.12 + **/ + obj_properties[PROP_WAKE_ON_WLAN] = + g_param_spec_uint(NM_SETTING_WIRELESS_WAKE_ON_WLAN, + "", + "", + 0, + G_MAXUINT32, + NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWireless:ap-isolation + * + * Configures AP isolation, which prevents communication between + * wireless devices connected to this AP. This property can be set + * to a value different from %NM_TERNARY_DEFAULT only when the + * interface is configured in AP mode. + * + * If set to %NM_TERNARY_TRUE, devices are not able to communicate + * with each other. This increases security because it protects + * devices against attacks from other clients in the network. At + * the same time, it prevents devices to access resources on the + * same wireless networks as file shares, printers, etc. + * + * If set to %NM_TERNARY_FALSE, devices can talk to each other. + * + * When set to %NM_TERNARY_DEFAULT, the global default is used; in + * case the global default is unspecified it is assumed to be + * %NM_TERNARY_FALSE. + * + * Since: 1.28 + **/ + /* ---ifcfg-rh--- + * property: ap-isolation + * variable: AP_ISOLATION(+) + * values: "yes", "no" + * default: missing variable means global default + * description: Whether AP isolation is enabled + * ---end--- + */ + obj_properties[PROP_AP_ISOLATION] = g_param_spec_enum( + NM_SETTING_WIRELESS_AP_ISOLATION, + "", + "", + NM_TYPE_TERNARY, + NM_TERNARY_DEFAULT, + NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit_full(setting_class, + NM_META_SETTING_TYPE_WIRELESS, + NULL, + properties_override); +} diff --git a/src/libnm-core-impl/nm-setting-wpan.c b/src/libnm-core-impl/nm-setting-wpan.c new file mode 100644 index 00000000..28a6478f --- /dev/null +++ b/src/libnm-core-impl/nm-setting-wpan.c @@ -0,0 +1,393 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2018 Lubomir Rintel + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-wpan.h" + +#include "nm-connection-private.h" +#include "nm-setting-connection.h" +#include "nm-setting-private.h" +#include "nm-utils-private.h" + +/** + * SECTION:nm-setting-wpan + * @short_description: Describes connection properties for IEEE 802.15.4 (WPAN) MAC + * + * The #NMSettingWpan object is a #NMSetting subclass that describes properties + * necessary for configuring IEEE 802.15.4 (WPAN) MAC layer devices. + **/ + +/* Ideally we'll be able to get these from a public header. */ +#ifndef IEEE802154_ADDR_LEN + #define IEEE802154_ADDR_LEN 8 +#endif + +#ifndef IEEE802154_MAX_PAGE + #define IEEE802154_MAX_PAGE 31 +#endif + +#ifndef IEEE802154_MAX_CHANNEL + #define IEEE802154_MAX_CHANNEL 26 +#endif + +/*****************************************************************************/ + +NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_MAC_ADDRESS, + PROP_PAN_ID, + PROP_SHORT_ADDRESS, + PROP_PAGE, + PROP_CHANNEL, ); + +typedef struct { + char * mac_address; + guint16 pan_id; + guint16 short_address; + gint16 page; + gint16 channel; +} NMSettingWpanPrivate; + +/** + * NMSettingWpan: + * + * IEEE 802.15.4 (WPAN) MAC Settings + */ +struct _NMSettingWpan { + NMSetting parent; +}; + +struct _NMSettingWpanClass { + NMSettingClass parent; +}; + +G_DEFINE_TYPE(NMSettingWpan, nm_setting_wpan, NM_TYPE_SETTING) + +#define NM_SETTING_WPAN_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_WPAN, NMSettingWpanPrivate)) + +/*****************************************************************************/ + +/** + * nm_setting_wpan_get_mac_address: + * @setting: the #NMSettingWpan + * + * Returns: the #NMSettingWpan:mac-address property of the setting + * + * Since: 1.14 + **/ +const char * +nm_setting_wpan_get_mac_address(NMSettingWpan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WPAN(setting), NULL); + + return NM_SETTING_WPAN_GET_PRIVATE(setting)->mac_address; +} + +/** + * nm_setting_wpan_get_pan_id: + * @setting: the #NMSettingWpan + * + * Returns: the #NMSettingWpan:pan-id property of the setting + * + * Since: 1.14 + **/ +guint16 +nm_setting_wpan_get_pan_id(NMSettingWpan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WPAN(setting), G_MAXUINT16); + + return NM_SETTING_WPAN_GET_PRIVATE(setting)->pan_id; +} + +/** + * nm_setting_wpan_get_short_address: + * @setting: the #NMSettingWpan + * + * Returns: the #NMSettingWpan:short-address property of the setting + * + * Since: 1.14 + **/ +guint16 +nm_setting_wpan_get_short_address(NMSettingWpan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WPAN(setting), G_MAXUINT16); + + return NM_SETTING_WPAN_GET_PRIVATE(setting)->short_address; +} + +/** + * nm_setting_wpan_get_page: + * @setting: the #NMSettingWpan + * + * Returns: the #NMSettingWpan:page property of the setting + * + * Since: 1.16 + **/ +gint16 +nm_setting_wpan_get_page(NMSettingWpan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WPAN(setting), NM_SETTING_WPAN_PAGE_DEFAULT); + + return NM_SETTING_WPAN_GET_PRIVATE(setting)->page; +} + +/** + * nm_setting_wpan_get_channel: + * @setting: the #NMSettingWpan + * + * Returns: the #NMSettingWpan:channel property of the setting + * + * Since: 1.16 + **/ +gint16 +nm_setting_wpan_get_channel(NMSettingWpan *setting) +{ + g_return_val_if_fail(NM_IS_SETTING_WPAN(setting), NM_SETTING_WPAN_CHANNEL_DEFAULT); + + return NM_SETTING_WPAN_GET_PRIVATE(setting)->channel; +} + +static gboolean +verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingWpanPrivate *priv = NM_SETTING_WPAN_GET_PRIVATE(setting); + + if (priv->mac_address && !nm_utils_hwaddr_valid(priv->mac_address, IEEE802154_ADDR_LEN)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_WPAN_SETTING_NAME, NM_SETTING_WPAN_MAC_ADDRESS); + return FALSE; + } + + if ((priv->page == NM_SETTING_WPAN_PAGE_DEFAULT) + != (priv->channel == NM_SETTING_WPAN_CHANNEL_DEFAULT)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("page must be defined along with a channel")); + g_prefix_error(error, "%s.%s: ", NM_SETTING_WPAN_SETTING_NAME, NM_SETTING_WPAN_PAGE); + return FALSE; + } + + if (priv->page < NM_SETTING_WPAN_PAGE_DEFAULT || priv->page > IEEE802154_MAX_PAGE) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("page must be between %d and %d"), + NM_SETTING_WPAN_PAGE_DEFAULT, + IEEE802154_MAX_PAGE); + g_prefix_error(error, "%s.%s: ", NM_SETTING_WPAN_SETTING_NAME, NM_SETTING_WPAN_PAGE); + return FALSE; + } + + if (priv->channel < NM_SETTING_WPAN_CHANNEL_DEFAULT || priv->channel > IEEE802154_MAX_CHANNEL) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("channel must not be between %d and %d"), + NM_SETTING_WPAN_CHANNEL_DEFAULT, + IEEE802154_MAX_CHANNEL); + g_prefix_error(error, "%s.%s: ", NM_SETTING_WPAN_SETTING_NAME, NM_SETTING_WPAN_CHANNEL); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSettingWpan *setting = NM_SETTING_WPAN(object); + + switch (prop_id) { + case PROP_MAC_ADDRESS: + g_value_set_string(value, nm_setting_wpan_get_mac_address(setting)); + break; + case PROP_PAN_ID: + g_value_set_uint(value, nm_setting_wpan_get_pan_id(setting)); + break; + case PROP_SHORT_ADDRESS: + g_value_set_uint(value, nm_setting_wpan_get_short_address(setting)); + break; + case PROP_PAGE: + g_value_set_int(value, nm_setting_wpan_get_page(setting)); + break; + case PROP_CHANNEL: + g_value_set_int(value, nm_setting_wpan_get_channel(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMSettingWpanPrivate *priv = NM_SETTING_WPAN_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_MAC_ADDRESS: + g_free(priv->mac_address); + priv->mac_address = + _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), IEEE802154_ADDR_LEN); + break; + case PROP_PAN_ID: + priv->pan_id = g_value_get_uint(value); + break; + case PROP_SHORT_ADDRESS: + priv->short_address = g_value_get_uint(value); + break; + case PROP_PAGE: + priv->page = g_value_get_int(value); + break; + case PROP_CHANNEL: + priv->channel = g_value_get_int(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_wpan_init(NMSettingWpan *setting) +{ + NMSettingWpanPrivate *priv = NM_SETTING_WPAN_GET_PRIVATE(setting); + + priv->pan_id = G_MAXUINT16; + priv->short_address = G_MAXUINT16; + priv->page = NM_SETTING_WPAN_PAGE_DEFAULT; + priv->channel = NM_SETTING_WPAN_CHANNEL_DEFAULT; +} + +/** + * nm_setting_wpan_new: + * + * Creates a new #NMSettingWpan object with default values. + * + * Returns: (transfer full): the new empty #NMSettingWpan object + * + * Since: 1.14 + **/ +NMSetting * +nm_setting_wpan_new(void) +{ + return g_object_new(NM_TYPE_SETTING_WPAN, NULL); +} + +static void +finalize(GObject *object) +{ + NMSettingWpanPrivate *priv = NM_SETTING_WPAN_GET_PRIVATE(object); + + g_free(priv->mac_address); + + G_OBJECT_CLASS(nm_setting_wpan_parent_class)->finalize(object); +} + +static void +nm_setting_wpan_class_init(NMSettingWpanClass *klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS(klass); + NMSettingClass *setting_class = NM_SETTING_CLASS(klass); + + g_type_class_add_private(setting_class, sizeof(NMSettingWpanPrivate)); + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->finalize = finalize; + + setting_class->verify = verify; + + /** + * NMSettingWpan:mac-address: + * + * If specified, this connection will only apply to the IEEE 802.15.4 (WPAN) + * MAC layer device whose permanent MAC address matches. + **/ + /* ---keyfile--- + * property: mac-address + * format: usual hex-digits-and-colons notation + * description: MAC address in hex-digits-and-colons notation + * (e.g. 76:d8:9b:87:66:60:84:ee). + * ---end--- + */ + obj_properties[PROP_MAC_ADDRESS] = + g_param_spec_string(NM_SETTING_WPAN_MAC_ADDRESS, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWpan:pan-id: + * + * IEEE 802.15.4 Personal Area Network (PAN) identifier. + **/ + obj_properties[PROP_PAN_ID] = g_param_spec_uint(NM_SETTING_WPAN_PAN_ID, + "", + "", + 0, + G_MAXUINT16, + G_MAXUINT16, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWpan:short-address: + * + * Short IEEE 802.15.4 address to be used within a restricted environment. + **/ + obj_properties[PROP_SHORT_ADDRESS] = + g_param_spec_uint(NM_SETTING_WPAN_SHORT_ADDRESS, + "", + "", + 0, + G_MAXUINT16, + G_MAXUINT16, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWpan:page: + * + * IEEE 802.15.4 channel page. A positive integer or -1, meaning "do not + * set, use whatever the device is already set to". + * + * Since: 1.16 + **/ + obj_properties[PROP_PAGE] = g_param_spec_int(NM_SETTING_WPAN_PAGE, + "", + "", + G_MININT16, + G_MAXINT16, + NM_SETTING_WPAN_PAGE_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + /** + * NMSettingWpan:channel: + * + * IEEE 802.15.4 channel. A positive integer or -1, meaning "do not + * set, use whatever the device is already set to". + * + * Since: 1.16 + **/ + obj_properties[PROP_CHANNEL] = g_param_spec_int(NM_SETTING_WPAN_CHANNEL, + "", + "", + G_MININT16, + G_MAXINT16, + NM_SETTING_WPAN_CHANNEL_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); + + _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_WPAN); +} diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c new file mode 100644 index 00000000..54e1a176 --- /dev/null +++ b/src/libnm-core-impl/nm-setting.c @@ -0,0 +1,2896 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2011 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting.h" + +#include "nm-setting-private.h" +#include "nm-utils.h" +#include "libnm-core-intern/nm-core-internal.h" +#include "nm-utils-private.h" +#include "nm-property-compare.h" + +/** + * SECTION:nm-setting + * @short_description: Describes related configuration information + * + * Each #NMSetting contains properties that describe configuration that applies + * to a specific network layer (like IPv4 or IPv6 configuration) or device type + * (like Ethernet, or Wi-Fi). A collection of individual settings together + * make up an #NMConnection. Each property is strongly typed and usually has + * a number of allowed values. See each #NMSetting subclass for a description + * of properties and allowed values. + */ + +/*****************************************************************************/ + +typedef struct { + GHashTable * hash; + const char **names; + GVariant ** values; +} GenData; + +typedef struct { + const char * name; + GType type; + NMSettingPriority priority; +} SettingInfo; + +NM_GOBJECT_PROPERTIES_DEFINE(NMSetting, PROP_NAME, ); + +typedef struct { + GenData *gendata; +} NMSettingPrivate; + +G_DEFINE_ABSTRACT_TYPE(NMSetting, nm_setting, G_TYPE_OBJECT) + +#define NM_SETTING_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING, NMSettingPrivate)) + +/*****************************************************************************/ + +static GenData *_gendata_hash(NMSetting *setting, gboolean create_if_necessary); + +/*****************************************************************************/ + +NMSettingPriority +_nm_setting_get_setting_priority(NMSetting *setting) +{ + const NMMetaSettingInfo *setting_info; + + g_return_val_if_fail(NM_IS_SETTING(setting), NM_SETTING_PRIORITY_INVALID); + + setting_info = NM_SETTING_GET_CLASS(setting)->setting_info; + return setting_info ? setting_info->setting_priority : NM_SETTING_PRIORITY_INVALID; +} + +NMSettingPriority +_nm_setting_get_base_type_priority(NMSetting *setting) +{ + g_return_val_if_fail(NM_IS_SETTING(setting), NM_SETTING_PRIORITY_INVALID); + + return nm_meta_setting_info_get_base_type_priority(NM_SETTING_GET_CLASS(setting)->setting_info, + G_OBJECT_TYPE(setting)); +} + +/** + * nm_setting_lookup_type: + * @name: a setting name + * + * Returns the #GType of the setting's class for a given setting name. + * + * Returns: the #GType of the setting's class, or %G_TYPE_INVALID if + * @name is not recognized. + **/ +GType +nm_setting_lookup_type(const char *name) +{ + const NMMetaSettingInfo *setting_info; + + g_return_val_if_fail(name, G_TYPE_INVALID); + + setting_info = nm_meta_setting_infos_by_name(name); + return setting_info ? setting_info->get_setting_gtype() : G_TYPE_INVALID; +} + +int +_nm_setting_compare_priority(gconstpointer a, gconstpointer b) +{ + NMSettingPriority prio_a, prio_b; + + prio_a = _nm_setting_get_setting_priority((NMSetting *) a); + prio_b = _nm_setting_get_setting_priority((NMSetting *) b); + + if (prio_a < prio_b) + return -1; + else if (prio_a == prio_b) + return 0; + return 1; +} + +/*****************************************************************************/ + +gboolean +_nm_setting_slave_type_is_valid(const char *slave_type, const char **out_port_type) +{ + const char *port_type = NULL; + gboolean found = TRUE; + + if (!slave_type) + found = FALSE; + else if (NM_IN_STRSET(slave_type, NM_SETTING_BOND_SETTING_NAME, NM_SETTING_VRF_SETTING_NAME)) { + /* pass */ + } else if (nm_streq(slave_type, NM_SETTING_BRIDGE_SETTING_NAME)) + port_type = NM_SETTING_BRIDGE_PORT_SETTING_NAME; + else if (nm_streq(slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME)) + port_type = NM_SETTING_OVS_PORT_SETTING_NAME; + else if (nm_streq(slave_type, NM_SETTING_OVS_PORT_SETTING_NAME)) + port_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME; + else if (nm_streq(slave_type, NM_SETTING_TEAM_SETTING_NAME)) + port_type = NM_SETTING_TEAM_PORT_SETTING_NAME; + else + found = FALSE; + + if (out_port_type) + *out_port_type = port_type; + return found; +} + +/*****************************************************************************/ + +static const NMSettInfoProperty * +_nm_sett_info_property_find_in_array(const NMSettInfoProperty *properties, + guint len, + const char * name) +{ + guint i; + + for (i = 0; i < len; i++) { + if (nm_streq(name, properties[i].name)) + return &properties[i]; + } + return NULL; +} + +gboolean +_nm_properties_override_assert(const NMSettInfoProperty *prop_info) +{ +#if NM_MORE_ASSERTS + nm_assert(prop_info); + nm_assert((!!prop_info->name) != (!!prop_info->param_spec)); + nm_assert(!prop_info->param_spec || !prop_info->name + || nm_streq0(prop_info->name, prop_info->param_spec->name)); + + if (prop_info->property_type) { + const NMSettInfoPropertType *property_type = prop_info->property_type; + + /* we always require a dbus_type. */ + nm_assert(property_type->dbus_type); + + /* from_dbus_fcn and gprop_from_dbus_fcn cannot both be set. */ + nm_assert(!property_type->from_dbus_fcn || !property_type->gprop_from_dbus_fcn); + + if (!prop_info->param_spec) { + /* if we don't have a param_spec, we cannot have gprop_from_dbus_fcn. */ + nm_assert(property_type->from_dbus_fcn || !property_type->gprop_from_dbus_fcn); + } + } +#endif + return TRUE; +} + +static NMSettInfoSetting _sett_info_settings[_NM_META_SETTING_TYPE_NUM]; + +const NMSettInfoSetting * +nmtst_sett_info_settings(void) +{ + return _sett_info_settings; +} + +static int +_property_infos_sort_cmp_setting_connection(gconstpointer p_a, + gconstpointer p_b, + gpointer user_data) +{ + const NMSettInfoProperty *a = *((const NMSettInfoProperty *const *) p_a); + const NMSettInfoProperty *b = *((const NMSettInfoProperty *const *) p_b); + int c_name; + + c_name = strcmp(a->name, b->name); + nm_assert(c_name != 0); + +#define CMP_AND_RETURN(n_a, n_b, name) \ + G_STMT_START \ + { \ + gboolean _is = nm_streq(n_a, "" name); \ + \ + if (_is || nm_streq(n_b, "" name)) \ + return _is ? -1 : 1; \ + } \ + G_STMT_END + + /* for [connection], report first id, uuid, type in that order. */ + if (c_name != 0) { + CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_ID); + CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_UUID); + CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_TYPE); + } + +#undef CMP_AND_RETURN + + return c_name; +} + +static const NMSettInfoProperty *const * +_property_infos_sort(const NMSettInfoProperty *property_infos, + guint property_infos_len, + NMSettingClass * setting_class) +{ + const NMSettInfoProperty **arr; + guint i; + +#if NM_MORE_ASSERTS > 5 + /* assert that the property names are all unique and sorted. */ + for (i = 0; i < property_infos_len; i++) { + if (property_infos[i].param_spec) + nm_assert(nm_streq(property_infos[i].name, property_infos[i].param_spec->name)); + if (i > 0) + nm_assert(strcmp(property_infos[i - 1].name, property_infos[i].name) < 0); + } +#endif + + if (property_infos_len <= 1) + return NULL; + if (G_TYPE_FROM_CLASS(setting_class) != NM_TYPE_SETTING_CONNECTION) { + /* we only do something special for certain setting types. This one, + * has just alphabetical sorting. */ + return NULL; + } + + arr = g_new(const NMSettInfoProperty *, property_infos_len); + for (i = 0; i < property_infos_len; i++) + arr[i] = &property_infos[i]; + + g_qsort_with_data(arr, + property_infos_len, + sizeof(const NMSettInfoProperty *), + _property_infos_sort_cmp_setting_connection, + NULL); + return arr; +} + +void +_nm_setting_class_commit_full(NMSettingClass * setting_class, + NMMetaSettingType meta_type, + const NMSettInfoSettDetail *detail, + GArray * properties_override) +{ + NMSettInfoSetting *sett_info; + gs_free GParamSpec **property_specs = NULL; + guint i, n_property_specs, override_len; + + nm_assert(NM_IS_SETTING_CLASS(setting_class)); + nm_assert(!setting_class->setting_info); + + nm_assert(meta_type < G_N_ELEMENTS(_sett_info_settings)); + + sett_info = &_sett_info_settings[meta_type]; + + nm_assert(!sett_info->setting_class); + nm_assert(!sett_info->property_infos_len); + nm_assert(!sett_info->property_infos); + + if (!properties_override) { + override_len = 0; + properties_override = _nm_sett_info_property_override_create_array(); + } else + override_len = properties_override->len; + + property_specs = + g_object_class_list_properties(G_OBJECT_CLASS(setting_class), &n_property_specs); + + for (i = 0; i < properties_override->len; i++) { + NMSettInfoProperty *p = &g_array_index(properties_override, NMSettInfoProperty, i); + + nm_assert((!!p->name) != (!!p->param_spec)); + + if (!p->name) { + nm_assert(p->param_spec); + p->name = p->param_spec->name; + } else + nm_assert(!p->param_spec); + } + +#if NM_MORE_ASSERTS > 10 + /* assert that properties_override is constructed consistently. */ + for (i = 0; i < override_len; i++) { + const NMSettInfoProperty *p = &g_array_index(properties_override, NMSettInfoProperty, i); + gboolean found = FALSE; + guint j; + + nm_assert( + !_nm_sett_info_property_find_in_array((NMSettInfoProperty *) properties_override->data, + i, + p->name)); + for (j = 0; j < n_property_specs; j++) { + if (!nm_streq(property_specs[j]->name, p->name)) + continue; + nm_assert(!found); + found = TRUE; + nm_assert(p->param_spec == property_specs[j]); + } + nm_assert(found == (p->param_spec != NULL)); + } +#endif + + for (i = 0; i < n_property_specs; i++) { + const char * name = property_specs[i]->name; + NMSettInfoProperty *p; + + if (_nm_sett_info_property_find_in_array((NMSettInfoProperty *) properties_override->data, + override_len, + name)) + continue; + + g_array_set_size(properties_override, properties_override->len + 1); + p = &g_array_index(properties_override, NMSettInfoProperty, properties_override->len - 1); + memset(p, 0, sizeof(*p)); + p->name = name; + p->param_spec = property_specs[i]; + } + + for (i = 0; i < properties_override->len; i++) { + NMSettInfoProperty *p = &g_array_index(properties_override, NMSettInfoProperty, i); + GType vtype; + + if (p->property_type) + goto has_property_type; + + nm_assert(p->param_spec); + + vtype = p->param_spec->value_type; + if (vtype == G_TYPE_BOOLEAN) + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP(G_VARIANT_TYPE_BOOLEAN); + else if (vtype == G_TYPE_UCHAR) + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP(G_VARIANT_TYPE_BYTE); + else if (vtype == G_TYPE_INT) + p->property_type = &nm_sett_info_propert_type_plain_i; + else if (vtype == G_TYPE_UINT) + p->property_type = &nm_sett_info_propert_type_plain_u; + else if (vtype == G_TYPE_INT64) + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP(G_VARIANT_TYPE_INT64); + else if (vtype == G_TYPE_UINT64) + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP(G_VARIANT_TYPE_UINT64); + else if (vtype == G_TYPE_STRING) + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP(G_VARIANT_TYPE_STRING); + else if (vtype == G_TYPE_DOUBLE) + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP(G_VARIANT_TYPE_DOUBLE); + else if (vtype == G_TYPE_STRV) + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP(G_VARIANT_TYPE_STRING_ARRAY); + else if (vtype == G_TYPE_BYTES) { + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP( + G_VARIANT_TYPE_BYTESTRING, + .typdata_to_dbus.gprop_type = NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_BYTES); + } else if (g_type_is_a(vtype, G_TYPE_ENUM)) { + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP( + G_VARIANT_TYPE_INT32, + .typdata_to_dbus.gprop_type = NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_ENUM); + } else if (g_type_is_a(vtype, G_TYPE_FLAGS)) { + p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP( + G_VARIANT_TYPE_UINT32, + .typdata_to_dbus.gprop_type = NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_FLAGS); + } else + nm_assert_not_reached(); + +has_property_type: + nm_assert(p->property_type); + nm_assert(p->property_type->dbus_type); + nm_assert(g_variant_type_string_is_valid((const char *) p->property_type->dbus_type)); + } + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMSettInfoProperty, name) == 0); + g_array_sort(properties_override, nm_strcmp_p); + + setting_class->setting_info = &nm_meta_setting_infos[meta_type]; + sett_info->setting_class = setting_class; + if (detail) + sett_info->detail = *detail; + nm_assert(properties_override->len > 0); + sett_info->property_infos_len = properties_override->len; + sett_info->property_infos = + nm_memdup(properties_override->data, sizeof(NMSettInfoProperty) * properties_override->len); + + sett_info->property_infos_sorted = _property_infos_sort(sett_info->property_infos, + sett_info->property_infos_len, + setting_class); + + g_array_free(properties_override, TRUE); +} + +const NMSettInfoProperty * +_nm_sett_info_setting_get_property_info(const NMSettInfoSetting *sett_info, + const char * property_name) +{ + const NMSettInfoProperty *property; + gssize idx; + + nm_assert(property_name); + + if (!sett_info) + return NULL; + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMSettInfoProperty, name) == 0); + idx = nm_utils_array_find_binary_search(sett_info->property_infos, + sizeof(NMSettInfoProperty), + sett_info->property_infos_len, + &property_name, + nm_strcmp_p_with_data, + NULL); + + if (idx < 0) + return NULL; + + property = &sett_info->property_infos[idx]; + + nm_assert(idx == 0 || strcmp(property[-1].name, property[0].name) < 0); + nm_assert(idx == sett_info->property_infos_len - 1 + || strcmp(property[0].name, property[1].name) < 0); + + return property; +} + +const NMSettInfoSetting * +_nm_setting_class_get_sett_info(NMSettingClass *setting_class) +{ + const NMSettInfoSetting *sett_info; + + if (!NM_IS_SETTING_CLASS(setting_class) || !setting_class->setting_info) + return NULL; + + nm_assert(setting_class->setting_info->meta_type < G_N_ELEMENTS(_sett_info_settings)); + sett_info = &_sett_info_settings[setting_class->setting_info->meta_type]; + nm_assert(sett_info->setting_class == setting_class); + return sett_info; +} + +/*****************************************************************************/ + +void +_nm_setting_emit_property_changed(NMSetting *setting) +{ + /* Some settings have "properties" that are not implemented as GObject properties. + * + * For example: + * + * - gendata-base settings like NMSettingEthtool. Here properties are just + * GVariant values in the gendata hash. + * + * - NMSettingWireGuard's peers are not backed by a GObject property. Instead + * there is C-API to access/modify peers. + * + * We still want to emit property-changed notifications for such properties, + * in particular because NMConnection registers to such signals to re-emit + * it as NM_CONNECTION_CHANGED signal. In fact, there are unlikely any other + * uses of such a property-changed signal, because generally it doesn't make + * too much sense. + * + * So, instead of adding yet another (artificial) signal "setting-changed", + * hijack the "notify" signal and just notify about changes of the "name". + * Of course, the "name" doesn't really ever change, because it's tied to + * the GObject's type. + */ + _notify(setting, PROP_NAME); +} + +/*****************************************************************************/ + +gboolean +_nm_setting_use_legacy_property(NMSetting * setting, + GVariant * connection_dict, + const char *legacy_property, + const char *new_property) +{ + GVariant *setting_dict, *value; + + setting_dict = g_variant_lookup_value(connection_dict, + nm_setting_get_name(NM_SETTING(setting)), + NM_VARIANT_TYPE_SETTING); + g_return_val_if_fail(setting_dict != NULL, FALSE); + + /* If the new property isn't set, we have to use the legacy property. */ + value = g_variant_lookup_value(setting_dict, new_property, NULL); + if (!value) { + g_variant_unref(setting_dict); + return TRUE; + } + g_variant_unref(value); + + /* Otherwise, clients always prefer new properties sent from the daemon. */ + if (!_nm_utils_is_manager_process) { + g_variant_unref(setting_dict); + return FALSE; + } + + /* The daemon prefers the legacy property if it exists. */ + value = g_variant_lookup_value(setting_dict, legacy_property, NULL); + g_variant_unref(setting_dict); + + if (value) { + g_variant_unref(value); + return TRUE; + } else + return FALSE; +} + +/*****************************************************************************/ + +GVariant * +_nm_setting_property_to_dbus_fcn_get_boolean(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + const NMSettInfoProperty *property_info = &sett_info->property_infos[property_idx]; + gboolean val; + + val = !!property_info->to_dbus_data.get_boolean(setting); + if (!property_info->to_dbus_data.including_default + && val == NM_G_PARAM_SPEC_GET_DEFAULT_BOOLEAN(property_info->param_spec)) + return NULL; + return g_variant_ref(nm_g_variant_singleton_b(val)); +} + +GVariant * +_nm_setting_property_to_dbus_fcn_get_string(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + const NMSettInfoProperty *property_info = &sett_info->property_infos[property_idx]; + const char * val; + + /* For string properties that are implemented via this function, the default is always NULL. + * In general, having strings default to NULL is most advisable. + * + * Setting "including_default" for a string makes no sense because a + * GVariant of type "s" cannot express NULL. */ + nm_assert(!NM_G_PARAM_SPEC_GET_DEFAULT_STRING(property_info->param_spec)); + nm_assert(!property_info->to_dbus_data.including_default); + + val = property_info->to_dbus_data.get_string(setting); + if (!val) + return NULL; + if (!val[0]) + return g_variant_ref(nm_g_variant_singleton_s_empty()); + return g_variant_new_string(val); +} + +GVariant * +_nm_setting_property_to_dbus_fcn_gprop(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + const NMSettInfoProperty *const property = &sett_info->property_infos[property_idx]; + nm_auto_unset_gvalue GValue prop_value = { + 0, + }; + GArray *tmp_array; + + nm_assert(property->param_spec); + nm_assert(property->property_type->to_dbus_fcn == _nm_setting_property_to_dbus_fcn_gprop); + nm_assert(property->property_type->typdata_to_dbus.gprop_type + == NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_DEFAULT + || !property->to_dbus_data.gprop_to_dbus_fcn); + + g_value_init(&prop_value, property->param_spec->value_type); + + g_object_get_property(G_OBJECT(setting), property->param_spec->name, &prop_value); + + if (!property->to_dbus_data.including_default + && g_param_value_defaults(property->param_spec, &prop_value)) + return NULL; + + switch (property->property_type->typdata_to_dbus.gprop_type) { + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_DEFAULT: + if (property->to_dbus_data.gprop_to_dbus_fcn) + return property->to_dbus_data.gprop_to_dbus_fcn(&prop_value); + + return g_dbus_gvalue_to_gvariant(&prop_value, property->property_type->dbus_type); + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_BYTES: + nm_assert(G_VALUE_HOLDS(&prop_value, G_TYPE_BYTES)); + return nm_utils_gbytes_to_variant_ay(g_value_get_boxed(&prop_value)); + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_ENUM: + return g_variant_new_int32(g_value_get_enum(&prop_value)); + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_FLAGS: + return g_variant_new_uint32(g_value_get_flags(&prop_value)); + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_GARRAY_UINT: + G_STATIC_ASSERT_EXPR(sizeof(guint) == sizeof(guint32)); + nm_assert(G_VALUE_HOLDS(&prop_value, G_TYPE_ARRAY)); + tmp_array = g_value_get_boxed(&prop_value); + nm_assert(tmp_array); + return nm_g_variant_new_au((const guint32 *) tmp_array->data, tmp_array->len); + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_STRDICT: + nm_assert(G_VALUE_HOLDS(&prop_value, G_TYPE_HASH_TABLE)); + return nm_utils_strdict_to_variant_ass(g_value_get_boxed(&prop_value)); + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_MAC_ADDRESS: + return nm_utils_hwaddr_to_dbus(g_value_get_string(&prop_value)); + } + + return nm_assert_unreachable_val(NULL); +} + +static GVariant * +property_to_dbus(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options, + gboolean ignore_flags) +{ + const NMSettInfoProperty *property = &sett_info->property_infos[property_idx]; + GVariant * variant; + + nm_assert(property->property_type->dbus_type); + + if (!property->property_type->to_dbus_fcn) { + nm_assert(!property->param_spec); + nm_assert(!property->to_dbus_data.none); + return NULL; + } + + if (property->param_spec + && (!ignore_flags + && !NM_FLAGS_HAS(property->param_spec->flags, NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS))) { + if (!NM_FLAGS_HAS(property->param_spec->flags, G_PARAM_WRITABLE)) + return NULL; + + if (NM_FLAGS_HAS(property->param_spec->flags, NM_SETTING_PARAM_LEGACY) + && !_nm_utils_is_manager_process) + return NULL; + + if (NM_FLAGS_HAS(property->param_spec->flags, NM_SETTING_PARAM_SECRET)) { + NMSettingSecretFlags f = NM_SETTING_SECRET_FLAG_NONE; + + if (NM_FLAGS_ANY(flags, + NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_SYSTEM_OWNED + | NM_CONNECTION_SERIALIZE_WITH_SECRETS_NOT_SAVED)) { + if (!nm_setting_get_secret_flags(setting, property->param_spec->name, &f, NULL)) + return NULL; + } + + if (!_nm_connection_serialize_secrets(flags, f)) + return NULL; + } else { + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + } + } + + variant = property->property_type + ->to_dbus_fcn(sett_info, property_idx, connection, setting, flags, options); + nm_g_variant_take_ref(variant); + + nm_assert(!variant || g_variant_is_of_type(variant, property->property_type->dbus_type)); + + return variant; +} + +static gboolean +set_property_from_dbus(const NMSettInfoProperty *property, GVariant *src_value, GValue *dst_value) +{ + nm_assert(property->param_spec); + nm_assert(property->property_type->dbus_type); + + if (property->property_type->gprop_from_dbus_fcn) { + if (!g_variant_type_equal(g_variant_get_type(src_value), + property->property_type->dbus_type)) + return FALSE; + property->property_type->gprop_from_dbus_fcn(src_value, dst_value); + } else if (dst_value->g_type == G_TYPE_BYTES) { + if (!g_variant_is_of_type(src_value, G_VARIANT_TYPE_BYTESTRING)) + return FALSE; + + _nm_utils_bytes_from_dbus(src_value, dst_value); + } else { + GValue tmp = G_VALUE_INIT; + + g_dbus_gvariant_to_gvalue(src_value, &tmp); + if (G_VALUE_TYPE(&tmp) == G_VALUE_TYPE(dst_value)) + *dst_value = tmp; + else { + gboolean success; + + success = g_value_transform(&tmp, dst_value); + g_value_unset(&tmp); + if (!success) + return FALSE; + } + } + + return TRUE; +} + +/** + * _nm_setting_to_dbus: + * @setting: the #NMSetting + * @connection: the #NMConnection containing @setting + * @flags: hash flags, e.g. %NM_CONNECTION_SERIALIZE_ALL + * @options: the #NMConnectionSerializationOptions options to control + * what/how gets serialized. + * + * Converts the #NMSetting into a #GVariant of type #NM_VARIANT_TYPE_SETTING + * mapping each setting property name to a value describing that property, + * suitable for marshalling over D-Bus or serializing. + * + * Returns: (transfer none): a new floating #GVariant describing the setting's + * properties + **/ +GVariant * +_nm_setting_to_dbus(NMSetting * setting, + NMConnection * connection, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMSettingPrivate * priv; + GVariantBuilder builder; + const NMSettInfoSetting *sett_info; + guint n_properties, i; + const char *const * gendata_keys; + + g_return_val_if_fail(NM_IS_SETTING(setting), NULL); + + priv = NM_SETTING_GET_PRIVATE(setting); + + g_variant_builder_init(&builder, NM_VARIANT_TYPE_SETTING); + + n_properties = _nm_setting_option_get_all(setting, &gendata_keys, NULL); + for (i = 0; i < n_properties; i++) { + g_variant_builder_add(&builder, + "{sv}", + gendata_keys[i], + g_hash_table_lookup(priv->gendata->hash, gendata_keys[i])); + } + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); + for (i = 0; i < sett_info->property_infos_len; i++) { + gs_unref_variant GVariant *dbus_value = NULL; + + dbus_value = property_to_dbus(sett_info, i, connection, setting, flags, options, FALSE); + if (dbus_value) { + g_variant_builder_add(&builder, "{sv}", sett_info->property_infos[i].name, dbus_value); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * _nm_setting_new_from_dbus: + * @setting_type: the #NMSetting type which the hash contains properties for + * @setting_dict: the #GVariant containing an %NM_VARIANT_TYPE_SETTING dictionary + * mapping property names to values + * @connection_dict: the #GVariant containing an %NM_VARIANT_TYPE_CONNECTION + * dictionary mapping setting names to dictionaries. + * @parse_flags: flags to determine behavior during parsing. + * @error: location to store error, or %NULL + * + * Creates a new #NMSetting object and populates that object with the properties + * contained in @setting_dict, using each key as the property to set, and each + * value as the value to set that property to. Setting properties are strongly + * typed, thus the #GVariantType of the dict value must be correct. See the + * documentation on each #NMSetting object subclass for the correct property + * names and value types. + * + * Returns: a new #NMSetting object populated with the properties from the + * hash table, or %NULL if @setting_hash could not be deserialized. + **/ +NMSetting * +_nm_setting_new_from_dbus(GType setting_type, + GVariant * setting_dict, + GVariant * connection_dict, + NMSettingParseFlags parse_flags, + GError ** error) +{ + gs_unref_ptrarray GPtrArray *keys_keep_variant = NULL; + gs_unref_object NMSetting *setting = NULL; + gs_unref_hashtable GHashTable *keys = NULL; + + g_return_val_if_fail(G_TYPE_IS_INSTANTIATABLE(setting_type), NULL); + g_return_val_if_fail(g_variant_is_of_type(setting_dict, NM_VARIANT_TYPE_SETTING), NULL); + + nm_assert(!NM_FLAGS_ANY(parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL)); + nm_assert(!NM_FLAGS_ALL(parse_flags, + NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT)); + + /* connection_dict is not technically optional, but some tests in test-general + * don't bother with it in cases where they know it's not needed. + */ + if (connection_dict) + g_return_val_if_fail(g_variant_is_of_type(connection_dict, NM_VARIANT_TYPE_CONNECTION), + NULL); + + /* Build the setting object from the properties we know about; we assume + * that any propreties in @setting_dict that we don't know about can + * either be ignored or else has a backward-compatibility equivalent + * that we do know about. + */ + setting = g_object_new(setting_type, NULL); + + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + GVariantIter iter; + GVariant * entry, *entry_key; + const char * key; + + keys_keep_variant = g_ptr_array_new_with_free_func((GDestroyNotify) g_variant_unref); + keys = g_hash_table_new(nm_str_hash, g_str_equal); + + g_variant_iter_init(&iter, setting_dict); + while ((entry = g_variant_iter_next_value(&iter))) { + entry_key = g_variant_get_child_value(entry, 0); + g_ptr_array_add(keys_keep_variant, entry_key); + g_variant_unref(entry); + + key = g_variant_get_string(entry_key, NULL); + if (!g_hash_table_add(keys, (char *) key)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("duplicate property")); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), key); + return NULL; + } + } + } + + if (!NM_SETTING_GET_CLASS(setting) + ->init_from_dbus(setting, keys, setting_dict, connection_dict, parse_flags, error)) + return NULL; + + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT) && g_hash_table_size(keys) > 0) { + GHashTableIter iter; + const char * key; + + g_hash_table_iter_init(&iter, keys); + if (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unknown property")); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), key); + return NULL; + } + } + + return g_steal_pointer(&setting); +} + +static gboolean +init_from_dbus(NMSetting * setting, + GHashTable * keys, + GVariant * setting_dict, + GVariant * connection_dict, + guint /* NMSettingParseFlags */ parse_flags, + GError ** error) +{ + const NMSettInfoSetting *sett_info; + + guint i; + + nm_assert(NM_IS_SETTING(setting)); + nm_assert(!NM_FLAGS_ANY(parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL)); + nm_assert(!NM_FLAGS_ALL(parse_flags, + NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT)); + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); + + if (sett_info->detail.gendata_info) { + GHashTable * hash; + GVariantIter iter; + char * key; + GVariant * val; + + hash = _gendata_hash(setting, TRUE)->hash; + + g_variant_iter_init(&iter, setting_dict); + while (g_variant_iter_next(&iter, "{sv}", &key, &val)) { + g_hash_table_insert(hash, key, val); + if (keys) + g_hash_table_remove(keys, key); + } + + _nm_setting_option_notify(setting, TRUE); + + /* Currently, only NMSettingEthtool supports gendata based options, and + * that one has no other properties (except "name"). That means, we + * consumed all options above. + * + * In the future it may be interesting to have settings that are both + * based on gendata and regular properties. In that case, we would need + * to handle this case differently. */ + nm_assert(nm_streq(G_OBJECT_TYPE_NAME(setting), "NMSettingEthtool")); + nm_assert(sett_info->property_infos_len == 1); + + return TRUE; + } + + for (i = 0; i < sett_info->property_infos_len; i++) { + const NMSettInfoProperty *property_info = &sett_info->property_infos[i]; + gs_unref_variant GVariant *value = NULL; + gs_free_error GError *local = NULL; + + if (property_info->param_spec && !(property_info->param_spec->flags & G_PARAM_WRITABLE)) + continue; + + value = g_variant_lookup_value(setting_dict, property_info->name, NULL); + + if (value && keys) + g_hash_table_remove(keys, property_info->name); + + if (value && property_info->property_type->from_dbus_fcn) { + if (!g_variant_type_equal(g_variant_get_type(value), + property_info->property_type->dbus_type)) { + /* for backward behavior, fail unless best-effort is chosen. */ + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT)) + continue; + g_set_error( + error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("can't set property of type '%s' from value of type '%s'"), + property_info->property_type->dbus_type + ? g_variant_type_peek_string(property_info->property_type->dbus_type) + : property_info->param_spec ? g_type_name(property_info->param_spec->value_type) + : "(unknown)", + g_variant_get_type_string(value)); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), property_info->name); + return FALSE; + } + + if (!property_info->property_type->from_dbus_fcn(setting, + connection_dict, + property_info->name, + value, + parse_flags, + &local)) { + if (!NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) + continue; + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("failed to set property: %s"), + local->message); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), property_info->name); + return FALSE; + } + } else if (!value && property_info->property_type->missing_from_dbus_fcn) { + if (!property_info->property_type->missing_from_dbus_fcn(setting, + connection_dict, + property_info->name, + parse_flags, + &local)) { + if (!NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) + continue; + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("failed to set property: %s"), + local->message); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), property_info->name); + return FALSE; + } + } else if (value && property_info->param_spec) { + nm_auto_unset_gvalue GValue object_value = G_VALUE_INIT; + + g_value_init(&object_value, property_info->param_spec->value_type); + if (!set_property_from_dbus(property_info, value, &object_value)) { + /* for backward behavior, fail unless best-effort is chosen. */ + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT)) + continue; + g_set_error( + error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("can't set property of type '%s' from value of type '%s'"), + property_info->property_type->dbus_type + ? g_variant_type_peek_string(property_info->property_type->dbus_type) + : (property_info->param_spec + ? g_type_name(property_info->param_spec->value_type) + : "(unknown)"), + g_variant_get_type_string(value)); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), property_info->name); + return FALSE; + } + + if (!nm_g_object_set_property(G_OBJECT(setting), + property_info->param_spec->name, + &object_value, + &local)) { + if (!NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) + continue; + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("can not set property: %s"), + local->message); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), property_info->name); + return FALSE; + } + } + } + + return TRUE; +} + +/** + * nm_setting_get_dbus_property_type: + * @setting: an #NMSetting + * @property_name: the property of @setting to get the type of + * + * Gets the D-Bus marshalling type of a property. @property_name is a D-Bus + * property name, which may not necessarily be a #GObject property. + * + * Returns: the D-Bus marshalling type of @property on @setting. + */ +const GVariantType * +nm_setting_get_dbus_property_type(NMSetting *setting, const char *property_name) +{ + const NMSettInfoProperty *property; + + g_return_val_if_fail(NM_IS_SETTING(setting), NULL); + g_return_val_if_fail(property_name != NULL, NULL); + + property = _nm_setting_class_get_property_info(NM_SETTING_GET_CLASS(setting), property_name); + + g_return_val_if_fail(property != NULL, NULL); + + nm_assert(property->property_type); + nm_assert(g_variant_type_string_is_valid((const char *) property->property_type->dbus_type)); + + return property->property_type->dbus_type; +} + +gboolean +_nm_setting_get_property(NMSetting *setting, const char *property_name, GValue *value) +{ + const NMSettInfoSetting * sett_info; + const NMSettInfoProperty *property_info; + + g_return_val_if_fail(NM_IS_SETTING(setting), FALSE); + g_return_val_if_fail(property_name, FALSE); + g_return_val_if_fail(value, FALSE); + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); + + if (sett_info->detail.gendata_info) { + GVariant *variant; + GenData * gendata = _gendata_hash(setting, FALSE); + + variant = gendata ? g_hash_table_lookup(gendata->hash, property_name) : NULL; + + if (!variant) { + g_value_unset(value); + return FALSE; + } + + g_value_init(value, G_TYPE_VARIANT); + g_value_set_variant(value, variant); + return TRUE; + } + + property_info = _nm_sett_info_setting_get_property_info(sett_info, property_name); + if (!property_info || !property_info->param_spec) { + g_value_unset(value); + return FALSE; + } + + g_value_init(value, property_info->param_spec->value_type); + g_object_get_property(G_OBJECT(setting), property_name, value); + return TRUE; +} + +static void +_gobject_copy_property(GObject *src, GObject *dst, const char *property_name, GType gtype) +{ + nm_auto_unset_gvalue GValue value = G_VALUE_INIT; + + nm_assert(G_IS_OBJECT(src)); + nm_assert(G_IS_OBJECT(dst)); + + g_value_init(&value, gtype); + g_object_get_property(src, property_name, &value); + g_object_set_property(dst, property_name, &value); +} + +static void +duplicate_copy_properties(const NMSettInfoSetting *sett_info, NMSetting *src, NMSetting *dst) +{ + if (sett_info->detail.gendata_info) { + GenData *gendata = _gendata_hash(src, FALSE); + + nm_assert(!_gendata_hash(dst, FALSE)); + + if (gendata && g_hash_table_size(gendata->hash) > 0) { + GHashTableIter iter; + GHashTable * h = _gendata_hash(dst, TRUE)->hash; + const char * key; + GVariant * val; + + g_hash_table_iter_init(&iter, gendata->hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + g_hash_table_insert(h, g_strdup(key), g_variant_ref(val)); + } + } + } + + if (sett_info->property_infos_len > 0) { + gboolean frozen = FALSE; + guint i; + + for (i = 0; i < sett_info->property_infos_len; i++) { + const NMSettInfoProperty *property_info = &sett_info->property_infos[i]; + + if (property_info->param_spec) { + if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) + != G_PARAM_WRITABLE) + continue; + + if (!frozen) { + g_object_freeze_notify(G_OBJECT(dst)); + frozen = TRUE; + } + _gobject_copy_property(G_OBJECT(src), + G_OBJECT(dst), + property_info->param_spec->name, + G_PARAM_SPEC_VALUE_TYPE(property_info->param_spec)); + continue; + } + } + + if (frozen) + g_object_thaw_notify(G_OBJECT(dst)); + } +} + +/** + * nm_setting_duplicate: + * @setting: the #NMSetting to duplicate + * + * Duplicates a #NMSetting. + * + * Returns: (transfer full): a new #NMSetting containing the same properties and values as the + * source #NMSetting + **/ +NMSetting * +nm_setting_duplicate(NMSetting *setting) +{ + const NMSettInfoSetting *sett_info; + NMSettingClass * klass; + NMSetting * dst; + + g_return_val_if_fail(NM_IS_SETTING(setting), NULL); + + klass = NM_SETTING_GET_CLASS(setting); + nm_assert(NM_IS_SETTING_CLASS(klass)); + nm_assert(klass->duplicate_copy_properties); + + dst = g_object_new(G_TYPE_FROM_CLASS(klass), NULL); + + sett_info = _nm_setting_class_get_sett_info(klass); + nm_assert(sett_info); + + klass->duplicate_copy_properties(sett_info, setting, dst); + return dst; +} + +/** + * nm_setting_get_name: + * @setting: the #NMSetting + * + * Returns the type name of the #NMSetting object + * + * Returns: a string containing the type name of the #NMSetting object, + * like 'ppp' or 'wireless' or 'wired'. + **/ +const char * +nm_setting_get_name(NMSetting *setting) +{ + const NMMetaSettingInfo *setting_info; + + g_return_val_if_fail(NM_IS_SETTING(setting), NULL); + + setting_info = NM_SETTING_GET_CLASS(setting)->setting_info; + return setting_info ? setting_info->setting_name : NULL; +} + +/** + * nm_setting_verify: + * @setting: the #NMSetting to verify + * @connection: (allow-none): the #NMConnection that @setting came from, or + * %NULL if @setting is being verified in isolation. + * @error: location to store error, or %NULL + * + * Validates the setting. Each setting's properties have allowed values, and + * some are dependent on other values (hence the need for @connection). The + * returned #GError contains information about which property of the setting + * failed validation, and in what way that property failed validation. + * + * Returns: %TRUE if the setting is valid, %FALSE if it is not + **/ +gboolean +nm_setting_verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + NMSettingVerifyResult result = _nm_setting_verify(setting, connection, error); + + if (result == NM_SETTING_VERIFY_NORMALIZABLE) + g_clear_error(error); + + return result == NM_SETTING_VERIFY_SUCCESS || result == NM_SETTING_VERIFY_NORMALIZABLE; +} + +NMSettingVerifyResult +_nm_setting_verify(NMSetting *setting, NMConnection *connection, GError **error) +{ + g_return_val_if_fail(NM_IS_SETTING(setting), NM_SETTING_VERIFY_ERROR); + g_return_val_if_fail(!connection || NM_IS_CONNECTION(connection), NM_SETTING_VERIFY_ERROR); + g_return_val_if_fail(!error || *error == NULL, NM_SETTING_VERIFY_ERROR); + + if (NM_SETTING_GET_CLASS(setting)->verify) + return NM_SETTING_GET_CLASS(setting)->verify(setting, connection, error); + + return NM_SETTING_VERIFY_SUCCESS; +} + +/** + * nm_setting_verify_secrets: + * @setting: the #NMSetting to verify secrets in + * @connection: (allow-none): the #NMConnection that @setting came from, or + * %NULL if @setting is being verified in isolation. + * @error: location to store error, or %NULL + * + * Verifies the secrets in the setting. + * The returned #GError contains information about which secret of the setting + * failed validation, and in what way that secret failed validation. + * The secret validation is done separately from main setting validation, because + * in some cases connection failure is not desired just for the secrets. + * + * Returns: %TRUE if the setting secrets are valid, %FALSE if they are not + * + * Since: 1.2 + **/ +gboolean +nm_setting_verify_secrets(NMSetting *setting, NMConnection *connection, GError **error) +{ + g_return_val_if_fail(NM_IS_SETTING(setting), NM_SETTING_VERIFY_ERROR); + g_return_val_if_fail(!connection || NM_IS_CONNECTION(connection), NM_SETTING_VERIFY_ERROR); + g_return_val_if_fail(!error || *error == NULL, NM_SETTING_VERIFY_ERROR); + + if (NM_SETTING_GET_CLASS(setting)->verify_secrets) + return NM_SETTING_GET_CLASS(setting)->verify_secrets(setting, connection, error); + + return NM_SETTING_VERIFY_SUCCESS; +} + +gboolean +_nm_setting_verify_secret_string(const char *str, + const char *setting_name, + const char *property, + GError ** error) +{ + if (str && !*str) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is empty")); + g_prefix_error(error, "%s.%s: ", setting_name, property); + return FALSE; + } + return TRUE; +} + +gboolean +_nm_setting_should_compare_secret_property(NMSetting * setting, + NMSetting * other, + const char * secret_name, + NMSettingCompareFlags flags) +{ + NMSettingSecretFlags a_secret_flags = NM_SETTING_SECRET_FLAG_NONE; + NMSettingSecretFlags b_secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + nm_assert(NM_IS_SETTING(setting)); + nm_assert(!other || G_OBJECT_TYPE(setting) == G_OBJECT_TYPE(other)); + + /* secret_name must be a valid secret for @setting. */ + nm_assert(nm_setting_get_secret_flags(setting, secret_name, NULL, NULL)); + + if (!NM_FLAGS_ANY(flags, + NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS + | NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)) + return TRUE; + + nm_setting_get_secret_flags(setting, secret_name, &a_secret_flags, NULL); + if (other) { + if (!nm_setting_get_secret_flags(other, secret_name, &b_secret_flags, NULL)) { + /* secret-name may not be a valid secret for @other. That is fine, we ignore that + * and treat @b_secret_flags as NM_SETTING_SECRET_FLAG_NONE. + * + * This can happen with VPN secrets, where the caller knows that @secret_name + * is a secret for setting, but it may not be a secret for @other. Accept that. + * + * Mark @other as missing. */ + other = NULL; + } + } + + /* when @setting has the secret-flags that should be ignored, + * we skip the comparison if: + * + * - @other is not present, + * - @other does not have a secret named @secret_name + * - @other also has the secret flat to be ignored. + * + * This makes the check symmetric (aside the fact that @setting must + * have the secret while @other may not -- which is asymmetric). */ + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS) + && NM_FLAGS_HAS(a_secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED) + && (!other || NM_FLAGS_HAS(b_secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED))) + return FALSE; + + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS) + && NM_FLAGS_HAS(a_secret_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED) + && (!other || NM_FLAGS_HAS(b_secret_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED))) + return FALSE; + + return TRUE; +} + +static NMTernary +compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + const NMSettInfoProperty *property_info = &sett_info->property_infos[property_idx]; + const GParamSpec * param_spec = property_info->param_spec; + + if (!param_spec) + return NM_TERNARY_DEFAULT; + + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_FUZZY) + && NM_FLAGS_ANY(param_spec->flags, NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_SECRET)) + return NM_TERNARY_DEFAULT; + + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE) + && !NM_FLAGS_HAS(param_spec->flags, NM_SETTING_PARAM_INFERRABLE)) + return NM_TERNARY_DEFAULT; + + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY) + && NM_FLAGS_HAS(param_spec->flags, NM_SETTING_PARAM_REAPPLY_IMMEDIATELY)) + return NM_TERNARY_DEFAULT; + + if (NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS) + && NM_FLAGS_HAS(param_spec->flags, NM_SETTING_PARAM_SECRET)) + return NM_TERNARY_DEFAULT; + + if (nm_streq(param_spec->name, NM_SETTING_NAME)) + return NM_TERNARY_DEFAULT; + + if (NM_FLAGS_HAS(param_spec->flags, NM_SETTING_PARAM_SECRET) + && !_nm_setting_should_compare_secret_property(set_a, set_b, param_spec->name, flags)) + return NM_TERNARY_DEFAULT; + + if (set_b) { + gs_unref_variant GVariant *value1 = NULL; + gs_unref_variant GVariant *value2 = NULL; + + value1 = property_to_dbus(sett_info, + property_idx, + con_a, + set_a, + NM_CONNECTION_SERIALIZE_ALL, + NULL, + TRUE); + value2 = property_to_dbus(sett_info, + property_idx, + con_b, + set_b, + NM_CONNECTION_SERIALIZE_ALL, + NULL, + TRUE); + if (nm_property_compare(value1, value2) != 0) + return NM_TERNARY_FALSE; + } + + return NM_TERNARY_TRUE; +} + +static NMTernary +_compare_property(const NMSettInfoSetting *sett_info, + guint property_idx, + NMConnection * con_a, + NMSetting * set_a, + NMConnection * con_b, + NMSetting * set_b, + NMSettingCompareFlags flags) +{ + NMTernary compare_result; + + nm_assert(sett_info); + nm_assert(NM_IS_SETTING_CLASS(sett_info->setting_class)); + nm_assert(property_idx < sett_info->property_infos_len); + nm_assert(NM_SETTING_GET_CLASS(set_a) == sett_info->setting_class); + nm_assert(!set_b || NM_SETTING_GET_CLASS(set_b) == sett_info->setting_class); + + compare_result = + NM_SETTING_GET_CLASS(set_a) + ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags); + + nm_assert(NM_IN_SET(compare_result, NM_TERNARY_DEFAULT, NM_TERNARY_FALSE, NM_TERNARY_TRUE)); + + /* check that the inferable flag and the GObject property flag corresponds. */ + nm_assert(!NM_FLAGS_HAS(flags, NM_SETTING_COMPARE_FLAG_INFERRABLE) + || !sett_info->property_infos[property_idx].param_spec + || NM_FLAGS_HAS(sett_info->property_infos[property_idx].param_spec->flags, + NM_SETTING_PARAM_INFERRABLE) + || compare_result == NM_TERNARY_DEFAULT); + +#if NM_MORE_ASSERTS > 10 + /* assert that compare_property() is symeric. */ + nm_assert(!set_b + || compare_result + == NM_SETTING_GET_CLASS(set_a)->compare_property(sett_info, + property_idx, + con_b, + set_b, + con_a, + set_a, + flags)); +#endif + + return compare_result; +} + +/** + * nm_setting_compare: + * @a: a #NMSetting + * @b: a second #NMSetting to compare with the first + * @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT + * + * Compares two #NMSetting objects for similarity, with comparison behavior + * modified by a set of flags. See the documentation for #NMSettingCompareFlags + * for a description of each flag's behavior. + * + * Returns: %TRUE if the comparison succeeds, %FALSE if it does not + **/ +gboolean +nm_setting_compare(NMSetting *a, NMSetting *b, NMSettingCompareFlags flags) +{ + return _nm_setting_compare(NULL, a, NULL, b, flags); +} + +gboolean +_nm_setting_compare(NMConnection * con_a, + NMSetting * a, + NMConnection * con_b, + NMSetting * b, + NMSettingCompareFlags flags) +{ + const NMSettInfoSetting *sett_info; + guint i; + + g_return_val_if_fail(NM_IS_SETTING(a), FALSE); + g_return_val_if_fail(NM_IS_SETTING(b), FALSE); + + nm_assert(!con_a || NM_IS_CONNECTION(con_a)); + nm_assert(!con_b || NM_IS_CONNECTION(con_b)); + + /* First check that both have the same type */ + if (G_OBJECT_TYPE(a) != G_OBJECT_TYPE(b)) + return FALSE; + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(a)); + + if (sett_info->detail.gendata_info) { + GenData *a_gendata = _gendata_hash(a, FALSE); + GenData *b_gendata = _gendata_hash(b, FALSE); + + return nm_utils_hashtable_equal(a_gendata ? a_gendata->hash : NULL, + b_gendata ? b_gendata->hash : NULL, + TRUE, + g_variant_equal); + } + + for (i = 0; i < sett_info->property_infos_len; i++) { + if (_compare_property(sett_info, i, con_a, a, con_b, b, flags) == NM_TERNARY_FALSE) + return FALSE; + } + + return TRUE; +} + +static void +_setting_diff_add_result(GHashTable *results, const char *prop_name, NMSettingDiffResult r) +{ + void *p; + + if (r == NM_SETTING_DIFF_RESULT_UNKNOWN) + return; + + if (g_hash_table_lookup_extended(results, prop_name, NULL, &p)) { + if (!NM_FLAGS_ALL((guint) r, GPOINTER_TO_UINT(p))) + g_hash_table_insert(results, + g_strdup(prop_name), + GUINT_TO_POINTER(((guint) r) | GPOINTER_TO_UINT(p))); + } else + g_hash_table_insert(results, g_strdup(prop_name), GUINT_TO_POINTER(r)); +} + +/** + * nm_setting_diff: + * @a: a #NMSetting + * @b: a second #NMSetting to compare with the first + * @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT + * @invert_results: this parameter is used internally by libnm and should + * be set to %FALSE. If %TRUE inverts the meaning of the #NMSettingDiffResult. + * @results: (inout) (transfer full) (element-type utf8 guint32): if the + * settings differ, on return a hash table mapping the differing keys to one or + * more %NMSettingDiffResult values OR-ed together. If the settings do not + * differ, any hash table passed in is unmodified. If no hash table is passed + * in and the settings differ, a new one is created and returned. + * + * Compares two #NMSetting objects for similarity, with comparison behavior + * modified by a set of flags. See the documentation for #NMSettingCompareFlags + * for a description of each flag's behavior. If the settings differ, the keys + * of each setting that differ from the other are added to @results, mapped to + * one or more #NMSettingDiffResult values. + * + * Returns: %TRUE if the settings contain the same values, %FALSE if they do not + **/ +gboolean +nm_setting_diff(NMSetting * a, + NMSetting * b, + NMSettingCompareFlags flags, + gboolean invert_results, + GHashTable ** results) +{ + return _nm_setting_diff(NULL, a, NULL, b, flags, invert_results, results); +} + +gboolean +_nm_setting_diff(NMConnection * con_a, + NMSetting * a, + NMConnection * con_b, + NMSetting * b, + NMSettingCompareFlags flags, + gboolean invert_results, + GHashTable ** results) +{ + const NMSettInfoSetting *sett_info; + guint i; + NMSettingDiffResult a_result = NM_SETTING_DIFF_RESULT_IN_A; + NMSettingDiffResult b_result = NM_SETTING_DIFF_RESULT_IN_B; + NMSettingDiffResult a_result_default = NM_SETTING_DIFF_RESULT_IN_A_DEFAULT; + NMSettingDiffResult b_result_default = NM_SETTING_DIFF_RESULT_IN_B_DEFAULT; + gboolean results_created = FALSE; + gboolean compared_any = FALSE; + gboolean diff_found = FALSE; + + g_return_val_if_fail(results != NULL, FALSE); + g_return_val_if_fail(NM_IS_SETTING(a), FALSE); + if (b) { + g_return_val_if_fail(NM_IS_SETTING(b), FALSE); + g_return_val_if_fail(G_OBJECT_TYPE(a) == G_OBJECT_TYPE(b), FALSE); + } + + nm_assert(!con_a || NM_IS_CONNECTION(con_a)); + nm_assert(!con_b || NM_IS_CONNECTION(con_b)); + + if ((flags + & (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT + | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) + == (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT + | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) { + /* conflicting flags: default to WITH_DEFAULT (clearing NO_DEFAULT). */ + flags &= ~NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT; + } + + /* If the caller is calling this function in a pattern like this to get + * complete diffs: + * + * nm_setting_diff (A, B, FALSE, &results); + * nm_setting_diff (B, A, TRUE, &results); + * + * and wants us to invert the results so that the second invocation comes + * out correctly, do that here. + */ + if (invert_results) { + a_result = NM_SETTING_DIFF_RESULT_IN_B; + b_result = NM_SETTING_DIFF_RESULT_IN_A; + a_result_default = NM_SETTING_DIFF_RESULT_IN_B_DEFAULT; + b_result_default = NM_SETTING_DIFF_RESULT_IN_A_DEFAULT; + } + + if (*results == NULL) { + *results = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, NULL); + results_created = TRUE; + } + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(a)); + + if (sett_info->detail.gendata_info) { + const char * key; + GVariant * val, *val2; + GHashTableIter iter; + GenData * a_gendata = _gendata_hash(a, FALSE); + GenData * b_gendata = b ? _gendata_hash(b, FALSE) : NULL; + + if (!a_gendata || !b_gendata) { + if (a_gendata || b_gendata) { + NMSettingDiffResult one_sided_result; + + one_sided_result = a_gendata ? a_result : b_result; + g_hash_table_iter_init(&iter, a_gendata ? a_gendata->hash : b_gendata->hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, NULL)) { + diff_found = TRUE; + _setting_diff_add_result(*results, key, one_sided_result); + } + } + } else { + g_hash_table_iter_init(&iter, a_gendata->hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + val2 = g_hash_table_lookup(b_gendata->hash, key); + compared_any = TRUE; + if (!val2 || !g_variant_equal(val, val2)) { + diff_found = TRUE; + _setting_diff_add_result(*results, key, a_result); + } + } + g_hash_table_iter_init(&iter, b_gendata->hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) { + val2 = g_hash_table_lookup(a_gendata->hash, key); + compared_any = TRUE; + if (!val2 || !g_variant_equal(val, val2)) { + diff_found = TRUE; + _setting_diff_add_result(*results, key, b_result); + } + } + } + } else { + for (i = 0; i < sett_info->property_infos_len; i++) { + NMSettingDiffResult r = NM_SETTING_DIFF_RESULT_UNKNOWN; + const NMSettInfoProperty *property_info; + NMTernary compare_result; + GParamSpec * prop_spec; + + compare_result = _compare_property(sett_info, i, con_a, a, con_b, b, flags); + if (compare_result == NM_TERNARY_DEFAULT) + continue; + + if (NM_FLAGS_ANY(flags, + NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS + | NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS) + && b && compare_result == NM_TERNARY_FALSE) { + /* we have setting @b and the property is not the same. But we also are instructed + * to ignore secrets based on the flags. + * + * Note that compare_property() called with two settings will ignore secrets + * based on the flags, but it will do so if *both* settings have the flag we + * look for. So that is symmetric behavior and good. + * + * But for the purpose of diff(), we do a asymmetric comparison because and + * we want to skip testing the property if setting @a alone indicates to do + * so. + * + * We need to double-check whether the property should be ignored by + * looking at @a alone. */ + if (_compare_property(sett_info, i, con_a, a, NULL, NULL, flags) + == NM_TERNARY_DEFAULT) + continue; + } + + compared_any = TRUE; + + property_info = &sett_info->property_infos[i]; + prop_spec = property_info->param_spec; + + if (b) { + if (compare_result == NM_TERNARY_FALSE) { + if (prop_spec) { + gboolean a_is_default, b_is_default; + GValue value = G_VALUE_INIT; + + g_value_init(&value, prop_spec->value_type); + g_object_get_property(G_OBJECT(a), prop_spec->name, &value); + a_is_default = g_param_value_defaults(prop_spec, &value); + + g_value_reset(&value); + g_object_get_property(G_OBJECT(b), prop_spec->name, &value); + b_is_default = g_param_value_defaults(prop_spec, &value); + + g_value_unset(&value); + if (!NM_FLAGS_HAS(flags, + NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT)) { + if (!a_is_default) + r |= a_result; + if (!b_is_default) + r |= b_result; + } else { + r |= a_result | b_result; + if (a_is_default) + r |= a_result_default; + if (b_is_default) + r |= b_result_default; + } + } else + r |= a_result | b_result; + } + } else if ((flags + & (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT + | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) + == 0) + r = a_result; /* only in A */ + else { + if (prop_spec) { + GValue value = G_VALUE_INIT; + + g_value_init(&value, prop_spec->value_type); + g_object_get_property(G_OBJECT(a), prop_spec->name, &value); + if (!g_param_value_defaults(prop_spec, &value)) + r |= a_result; + else if (flags & NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT) + r |= a_result | a_result_default; + + g_value_unset(&value); + } else + r |= a_result; + } + + if (r != NM_SETTING_DIFF_RESULT_UNKNOWN) { + diff_found = TRUE; + _setting_diff_add_result(*results, property_info->name, r); + } + } + } + + if (!compared_any && !b) { + /* special case: the setting has no properties, and the opposite + * setting @b is not given. The settings differ, and we signal that + * by returning an empty results hash. */ + diff_found = TRUE; + } + + if (diff_found) { + /* if there is a difference, we always return FALSE. It also means, we might + * have allocated a new @results hash, and return it to the caller. */ + return FALSE; + } else { + if (results_created) { + /* the allocated hash is unused. Clear it again. */ + g_hash_table_destroy(*results); + *results = NULL; + } else { + /* we found no diff, and return false. However, the input + * @result is returned unmodified. */ + } + return TRUE; + } +} + +static void +enumerate_values(const NMSettInfoProperty *property_info, + NMSetting * setting, + NMSettingValueIterFn func, + gpointer user_data) +{ + GValue value = G_VALUE_INIT; + + if (!property_info->param_spec) + return; + + g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(property_info->param_spec)); + g_object_get_property(G_OBJECT(setting), property_info->param_spec->name, &value); + func(setting, + property_info->param_spec->name, + &value, + property_info->param_spec->flags, + user_data); + g_value_unset(&value); +} + +/** + * nm_setting_enumerate_values: + * @setting: the #NMSetting + * @func: (scope call): user-supplied function called for each property of the setting + * @user_data: user data passed to @func at each invocation + * + * Iterates over each property of the #NMSetting object, calling the supplied + * user function for each property. + **/ +void +nm_setting_enumerate_values(NMSetting *setting, NMSettingValueIterFn func, gpointer user_data) +{ + const NMSettInfoSetting *sett_info; + guint i; + + g_return_if_fail(NM_IS_SETTING(setting)); + g_return_if_fail(func != NULL); + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); + + if (sett_info->detail.gendata_info) { + const char *const *names; + guint n_properties; + + /* the properties of this setting are not real GObject properties. + * Hence, this API makes little sense (or does it?). Still, call + * @func with each value. */ + n_properties = _nm_setting_option_get_all(setting, &names, NULL); + if (n_properties > 0) { + gs_strfreev char **keys = g_strdupv((char **) names); + GHashTable * h = _gendata_hash(setting, FALSE)->hash; + + for (i = 0; i < n_properties; i++) { + GValue value = G_VALUE_INIT; + GVariant *val = g_hash_table_lookup(h, keys[i]); + + if (!val) { + /* was deleted in the meantime? Skip */ + continue; + } + + g_value_init(&value, G_TYPE_VARIANT); + g_value_set_variant(&value, val); + /* call it will GParamFlags 0. It shall indicate that this + * is not a "real" GObject property. */ + func(setting, keys[i], &value, 0, user_data); + g_value_unset(&value); + } + } + return; + } + + for (i = 0; i < sett_info->property_infos_len; i++) { + NM_SETTING_GET_CLASS(setting)->enumerate_values( + _nm_sett_info_property_info_get_sorted(sett_info, i), + setting, + func, + user_data); + } +} + +static gboolean +aggregate(NMSetting *setting, int type_i, gpointer arg) +{ + NMConnectionAggregateType type = type_i; + const NMSettInfoSetting * sett_info; + guint i; + + nm_assert(NM_IN_SET(type, + NM_CONNECTION_AGGREGATE_ANY_SECRETS, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS)); + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); + for (i = 0; i < sett_info->property_infos_len; i++) { + const NMSettInfoProperty * property_info = &sett_info->property_infos[i]; + GParamSpec * prop_spec = property_info->param_spec; + nm_auto_unset_gvalue GValue value = G_VALUE_INIT; + NMSettingSecretFlags secret_flags; + + if (!prop_spec || !NM_FLAGS_HAS(prop_spec->flags, NM_SETTING_PARAM_SECRET)) { + nm_assert(!nm_setting_get_secret_flags(setting, property_info->name, NULL, NULL)); + continue; + } + + /* for the moment, all aggregate types only care about secrets. */ + nm_assert(nm_setting_get_secret_flags(setting, property_info->name, NULL, NULL)); + + switch (type) { + case NM_CONNECTION_AGGREGATE_ANY_SECRETS: + g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(prop_spec)); + g_object_get_property(G_OBJECT(setting), prop_spec->name, &value); + if (!g_param_value_defaults(prop_spec, &value)) { + *((gboolean *) arg) = TRUE; + return TRUE; + } + break; + + case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS: + if (!nm_setting_get_secret_flags(setting, prop_spec->name, &secret_flags, NULL)) + nm_assert_not_reached(); + if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) { + *((gboolean *) arg) = TRUE; + return TRUE; + } + break; + } + } + + return FALSE; +} + +/** + * _nm_setting_aggregate: + * @setting: the #NMSetting to aggregate. + * @type: the #NMConnectionAggregateType aggregate type. + * @arg: the in/out arguments for aggregation. They depend on @type. + * + * This is the implementation detail of _nm_connection_aggregate(). It + * makes no sense to call this function directly outside of _nm_connection_aggregate(). + * + * Returns: %TRUE if afterwards the aggregation is complete. That means, + * the only caller _nm_connection_aggregate() will not visit other settings + * after a setting returns %TRUE (indicating that there is nothing further + * to aggregate). Note that is very different from the boolean return + * argument of _nm_connection_aggregate(), which serves a different purpose. + */ +gboolean +_nm_setting_aggregate(NMSetting *setting, NMConnectionAggregateType type, gpointer arg) +{ + g_return_val_if_fail(NM_IS_SETTING(setting), FALSE); + g_return_val_if_fail(arg, FALSE); + g_return_val_if_fail(NM_IN_SET(type, + NM_CONNECTION_AGGREGATE_ANY_SECRETS, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS), + FALSE); + + return NM_SETTING_GET_CLASS(setting)->aggregate(setting, type, arg); +} + +static gboolean +clear_secrets(const NMSettInfoSetting * sett_info, + guint property_idx, + NMSetting * setting, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data) +{ + NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE; + GParamSpec * param_spec = sett_info->property_infos[property_idx].param_spec; + + if (!param_spec) + return FALSE; + + if (!NM_FLAGS_HAS(param_spec->flags, NM_SETTING_PARAM_SECRET)) + return FALSE; + + if (func) { + if (!nm_setting_get_secret_flags(setting, param_spec->name, &flags, NULL)) + nm_assert_not_reached(); + if (!func(setting, param_spec->name, flags, user_data)) + return FALSE; + } else + nm_assert(nm_setting_get_secret_flags(setting, param_spec->name, NULL, NULL)); + + { + nm_auto_unset_gvalue GValue value = G_VALUE_INIT; + + g_value_init(&value, param_spec->value_type); + g_object_get_property(G_OBJECT(setting), param_spec->name, &value); + if (g_param_value_defaults(param_spec, &value)) + return FALSE; + + g_param_value_set_default(param_spec, &value); + g_object_set_property(G_OBJECT(setting), param_spec->name, &value); + } + + return TRUE; +} + +/** + * _nm_setting_clear_secrets: + * @setting: the #NMSetting + * @func: (scope call): function to be called to determine whether a + * specific secret should be cleared or not + * @user_data: caller-supplied data passed to @func + * + * Clears and frees secrets determined by @func. + * + * Returns: %TRUE if the setting changed at all + **/ +gboolean +_nm_setting_clear_secrets(NMSetting * setting, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data) +{ + const NMSettInfoSetting *sett_info; + gboolean changed = FALSE; + guint i; + gboolean (*my_clear_secrets)(const struct _NMSettInfoSetting *sett_info, + guint property_idx, + NMSetting * setting, + NMSettingClearSecretsWithFlagsFn func, + gpointer user_data); + + g_return_val_if_fail(NM_IS_SETTING(setting), FALSE); + + my_clear_secrets = NM_SETTING_GET_CLASS(setting)->clear_secrets; + + sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting)); + for (i = 0; i < sett_info->property_infos_len; i++) { + changed |= my_clear_secrets(sett_info, i, setting, func, user_data); + } + return changed; +} + +/** + * _nm_setting_need_secrets: + * @setting: the #NMSetting + * + * Returns an array of property names for each secret which may be required + * to make a successful connection. The returned hints are only intended as a + * guide to what secrets may be required, because in some circumstances, there + * is no way to conclusively determine exactly which secrets are needed. + * + * Returns: (transfer container) (element-type utf8): a #GPtrArray containing + * the property names of secrets of the #NMSetting which may be required; the + * caller owns the array and must free it with g_ptr_array_free(), but must not + * free the elements. + **/ +GPtrArray * +_nm_setting_need_secrets(NMSetting *setting) +{ + GPtrArray *secrets = NULL; + + g_return_val_if_fail(NM_IS_SETTING(setting), NULL); + + if (NM_SETTING_GET_CLASS(setting)->need_secrets) + secrets = NM_SETTING_GET_CLASS(setting)->need_secrets(setting); + + return secrets; +} + +static int +update_one_secret(NMSetting *setting, const char *key, GVariant *value, GError **error) +{ + const NMSettInfoProperty *property; + GParamSpec * prop_spec; + GValue prop_value = { + 0, + }; + + property = _nm_setting_class_get_property_info(NM_SETTING_GET_CLASS(setting), key); + if (!property) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_FOUND, + _("secret not found")); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), key); + return NM_SETTING_UPDATE_SECRET_ERROR; + } + + /* Silently ignore non-secrets */ + prop_spec = property->param_spec; + if (!prop_spec || !(prop_spec->flags & NM_SETTING_PARAM_SECRET)) + return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; + + if (g_variant_is_of_type(value, G_VARIANT_TYPE_STRING) && G_IS_PARAM_SPEC_STRING(prop_spec)) { + /* String is expected to be a common case. Handle it specially and check + * whether the value is already set. Otherwise, we just reset the + * property and assume the value got modified. + */ + char *v; + + g_object_get(G_OBJECT(setting), prop_spec->name, &v, NULL); + if (g_strcmp0(v, g_variant_get_string(value, NULL)) == 0) { + g_free(v); + return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; + } + g_free(v); + } + + g_value_init(&prop_value, prop_spec->value_type); + set_property_from_dbus(property, value, &prop_value); + g_object_set_property(G_OBJECT(setting), prop_spec->name, &prop_value); + g_value_unset(&prop_value); + + return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED; +} + +/** + * _nm_setting_update_secrets: + * @setting: the #NMSetting + * @secrets: a #GVariant of type #NM_VARIANT_TYPE_SETTING, mapping property + * names to secrets. + * @error: location to store error, or %NULL + * + * Update the setting's secrets, given a dictionary of secrets intended for that + * setting (deserialized from D-Bus for example). + * + * Returns: an #NMSettingUpdateSecretResult + **/ +NMSettingUpdateSecretResult +_nm_setting_update_secrets(NMSetting *setting, GVariant *secrets, GError **error) +{ + GVariantIter iter; + const char * secret_key; + GVariant * secret_value; + GError * tmp_error = NULL; + NMSettingUpdateSecretResult result = NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED; + + g_return_val_if_fail(NM_IS_SETTING(setting), NM_SETTING_UPDATE_SECRET_ERROR); + g_return_val_if_fail(g_variant_is_of_type(secrets, NM_VARIANT_TYPE_SETTING), + NM_SETTING_UPDATE_SECRET_ERROR); + if (error) + g_return_val_if_fail(*error == NULL, NM_SETTING_UPDATE_SECRET_ERROR); + + g_variant_iter_init(&iter, secrets); + while (g_variant_iter_next(&iter, "{&sv}", &secret_key, &secret_value)) { + int success; + + success = NM_SETTING_GET_CLASS(setting)->update_one_secret(setting, + secret_key, + secret_value, + &tmp_error); + nm_assert(!((success == NM_SETTING_UPDATE_SECRET_ERROR) ^ (!!tmp_error))); + + g_variant_unref(secret_value); + + if (success == NM_SETTING_UPDATE_SECRET_ERROR) { + g_propagate_error(error, tmp_error); + return NM_SETTING_UPDATE_SECRET_ERROR; + } + + if (success == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED) + result = NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED; + } + + return result; +} + +static void +for_each_secret(NMSetting * setting, + const char * secret_name, + GVariant * val, + gboolean remove_non_secrets, + _NMConnectionForEachSecretFunc callback, + gpointer callback_data, + GVariantBuilder * setting_builder) +{ + NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE; + + if (!nm_setting_get_secret_flags(setting, secret_name, &secret_flags, NULL)) { + if (!remove_non_secrets) + g_variant_builder_add(setting_builder, "{sv}", secret_name, val); + return; + } + if (callback(secret_flags, callback_data)) + g_variant_builder_add(setting_builder, "{sv}", secret_name, val); +} + +static void +_set_error_secret_property_not_found(GError **error, NMSetting *setting, const char *secret_name) +{ + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_PROPERTY_NOT_FOUND, + _("not a secret property")); + g_prefix_error(error, "%s.%s: ", nm_setting_get_name(setting), secret_name); +} + +gboolean +_nm_setting_property_is_regular_secret(NMSetting *setting, const char *secret_name) +{ + const NMSettInfoProperty *property; + + nm_assert(NM_IS_SETTING(setting)); + nm_assert(secret_name); + + property = _nm_setting_class_get_property_info(NM_SETTING_GET_CLASS(setting), secret_name); + return property && property->param_spec + && NM_FLAGS_HAS(property->param_spec->flags, NM_SETTING_PARAM_SECRET); +} + +gboolean +_nm_setting_property_is_regular_secret_flags(NMSetting *setting, const char *secret_flags_name) +{ + const NMSettInfoProperty *property; + + nm_assert(NM_IS_SETTING(setting)); + nm_assert(secret_flags_name); + + property = + _nm_setting_class_get_property_info(NM_SETTING_GET_CLASS(setting), secret_flags_name); + return property && property->param_spec + && !NM_FLAGS_HAS(property->param_spec->flags, NM_SETTING_PARAM_SECRET) + && G_PARAM_SPEC_VALUE_TYPE(property->param_spec) == NM_TYPE_SETTING_SECRET_FLAGS; +} + +static gboolean +get_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags *out_flags, + GError ** error) +{ + gs_free char * secret_flags_name_free = NULL; + const char * secret_flags_name; + NMSettingSecretFlags flags; + + if (!_nm_setting_property_is_regular_secret(setting, secret_name)) { + _set_error_secret_property_not_found(error, setting, secret_name); + NM_SET_OUT(out_flags, NM_SETTING_SECRET_FLAG_NONE); + return FALSE; + } + + secret_flags_name = nm_construct_name_a("%s-flags", secret_name, &secret_flags_name_free); + + nm_assert(_nm_setting_property_is_regular_secret_flags(setting, secret_flags_name)); + + g_object_get(G_OBJECT(setting), secret_flags_name, &flags, NULL); + NM_SET_OUT(out_flags, flags); + return TRUE; +} + +/** + * nm_setting_get_secret_flags: + * @setting: the #NMSetting + * @secret_name: the secret key name to get flags for + * @out_flags: on success, the #NMSettingSecretFlags for the secret + * @error: location to store error, or %NULL + * + * For a given secret, retrieves the #NMSettingSecretFlags describing how to + * handle that secret. + * + * Returns: %TRUE on success (if the given secret name was a valid property of + * this setting, and if that property is secret), %FALSE if not + **/ +gboolean +nm_setting_get_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags *out_flags, + GError ** error) +{ + g_return_val_if_fail(NM_IS_SETTING(setting), FALSE); + g_return_val_if_fail(secret_name != NULL, FALSE); + + return NM_SETTING_GET_CLASS(setting)->get_secret_flags(setting, secret_name, out_flags, error); +} + +static gboolean +set_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags flags, + GError ** error) +{ + gs_free char *secret_flags_name_free = NULL; + const char * secret_flags_name; + + if (!_nm_setting_property_is_regular_secret(setting, secret_name)) { + _set_error_secret_property_not_found(error, setting, secret_name); + return FALSE; + } + + secret_flags_name = nm_construct_name_a("%s-flags", secret_name, &secret_flags_name_free); + + nm_assert(_nm_setting_property_is_regular_secret_flags(setting, secret_flags_name)); + + if (!nm_g_object_set_property_flags(G_OBJECT(setting), + secret_flags_name, + NM_TYPE_SETTING_SECRET_FLAGS, + flags, + error)) + g_return_val_if_reached(FALSE); + return TRUE; +} + +/** + * nm_setting_set_secret_flags: + * @setting: the #NMSetting + * @secret_name: the secret key name to set flags for + * @flags: the #NMSettingSecretFlags for the secret + * @error: location to store error, or %NULL + * + * For a given secret, stores the #NMSettingSecretFlags describing how to + * handle that secret. + * + * Returns: %TRUE on success (if the given secret name was a valid property of + * this setting, and if that property is secret), %FALSE if not + **/ +gboolean +nm_setting_set_secret_flags(NMSetting * setting, + const char * secret_name, + NMSettingSecretFlags flags, + GError ** error) +{ + g_return_val_if_fail(NM_IS_SETTING(setting), FALSE); + g_return_val_if_fail(secret_name != NULL, FALSE); + g_return_val_if_fail(_nm_setting_secret_flags_valid(flags), FALSE); + + return NM_SETTING_GET_CLASS(setting)->set_secret_flags(setting, secret_name, flags, error); +} + +/** + * nm_setting_to_string: + * @setting: the #NMSetting + * + * Convert the setting (including secrets!) into a string. For debugging + * purposes ONLY, should NOT be used for serialization of the setting, + * or machine-parsed in any way. The output format is not guaranteed to + * be stable and may change at any time. + * + * Returns: an allocated string containing a textual representation of the + * setting's properties and values, which the caller should + * free with g_free() + **/ +char * +nm_setting_to_string(NMSetting *setting) +{ + GString * string; + gs_unref_variant GVariant *variant = NULL; + GVariant * child; + GVariantIter iter; + + string = g_string_new(nm_setting_get_name(setting)); + g_string_append_c(string, '\n'); + + variant = _nm_setting_to_dbus(setting, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + + g_variant_iter_init(&iter, variant); + while ((child = g_variant_iter_next_value(&iter))) { + gs_free char * name = NULL; + gs_free char * value_str = NULL; + gs_unref_variant GVariant *value = NULL; + + g_variant_get(child, "{sv}", &name, &value); + value_str = g_variant_print(value, FALSE); + + g_string_append_printf(string, "\t%s : %s\n", name, value_str); + } + + return g_string_free(string, FALSE); +} + +static GVariant * +_nm_setting_get_deprecated_virtual_interface_name(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMSettingConnection *s_con; + + if (!connection) + return NULL; + + s_con = nm_connection_get_setting_connection(connection); + if (!s_con) + return NULL; + + if (nm_setting_connection_get_interface_name(s_con)) + return g_variant_new_string(nm_setting_connection_get_interface_name(s_con)); + else + return NULL; +} + +const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_STRING, + .to_dbus_fcn = + _nm_setting_get_deprecated_virtual_interface_name, ); + +const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT( + G_VARIANT_TYPE_INT32, + /* No functions set. This property type is to silently ignore the value on D-Bus. */ + ); + +const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_u = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT( + G_VARIANT_TYPE_UINT32, + /* No functions set. This property type is to silently ignore the value on D-Bus. */ + ); + +const NMSettInfoPropertType nm_sett_info_propert_type_plain_i = + NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT(G_VARIANT_TYPE_INT32); + +const NMSettInfoPropertType nm_sett_info_propert_type_plain_u = + NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT(G_VARIANT_TYPE_UINT32); + +const NMSettInfoPropertType nm_sett_info_propert_type_boolean = NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT( + G_VARIANT_TYPE_BOOLEAN, + .to_dbus_fcn = _nm_setting_property_to_dbus_fcn_get_boolean); + +const NMSettInfoPropertType nm_sett_info_propert_type_string = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_STRING, + .to_dbus_fcn = _nm_setting_property_to_dbus_fcn_get_string); + +/*****************************************************************************/ + +static GenData * +_gendata_hash(NMSetting *setting, gboolean create_if_necessary) +{ + NMSettingPrivate *priv; + + nm_assert(NM_IS_SETTING(setting)); + + priv = NM_SETTING_GET_PRIVATE(setting); + + if (G_UNLIKELY(!priv->gendata)) { + if (!create_if_necessary) + return NULL; + priv->gendata = g_slice_new(GenData); + priv->gendata->hash = g_hash_table_new_full(nm_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) g_variant_unref); + priv->gendata->names = NULL; + priv->gendata->values = NULL; + } + + return priv->gendata; +} + +GHashTable * +_nm_setting_option_hash(NMSetting *setting, gboolean create_if_necessary) +{ + GenData *gendata; + + gendata = _gendata_hash(setting, create_if_necessary); + return gendata ? gendata->hash : NULL; +} + +void +_nm_setting_option_notify(NMSetting *setting, gboolean names_changed) +{ + GenData *gendata; + + gendata = _gendata_hash(setting, FALSE); + if (!gendata) + goto out; + + nm_clear_g_free(&gendata->values); + + if (names_changed) { + /* if only the values changed, it's sufficient to invalidate the + * values cache. Otherwise, the names cache must be invalidated too. */ + nm_clear_g_free(&gendata->names); + } + + /* Note, currently there is no way to notify the subclass when gendata changed. + * gendata is only changed in two situations: + * 1) from within NMSetting itself, for example when creating a NMSetting instance + * from keyfile or a D-Bus GVariant. + * 2) actively from the subclass itself + * For 2), we don't need the notification, because the subclass knows that something + * changed. + * For 1), we currently don't need the notification either, because all that the subclass + * currently would do, is emit a g_object_notify() signal. However, 1) only happens when + * the setting instance is newly created, at that point, nobody listens to the signal. + * + * If we ever need it, then we would need to call a virtual function to notify the subclass + * that gendata changed. */ + +out: + _nm_setting_emit_property_changed(setting); +} + +guint +_nm_setting_option_get_all(NMSetting * setting, + const char *const **out_names, + GVariant *const ** out_values) +{ + GenData * gendata; + GHashTable *hash; + guint i, len; + + nm_assert(NM_IS_SETTING(setting)); + + gendata = _gendata_hash(setting, FALSE); + if (!gendata) + goto out_zero; + + hash = gendata->hash; + len = g_hash_table_size(hash); + if (len == 0) + goto out_zero; + + if (!out_names && !out_values) + return len; + + if (G_UNLIKELY(!gendata->names)) { + gendata->names = nm_utils_strdict_get_keys(hash, TRUE, NULL); + } + + if (out_values) { + if (G_UNLIKELY(!gendata->values)) { + gendata->values = g_new(GVariant *, len + 1); + for (i = 0; i < len; i++) + gendata->values[i] = g_hash_table_lookup(hash, gendata->names[i]); + gendata->values[i] = NULL; + } + *out_values = gendata->values; + } + + NM_SET_OUT(out_names, (const char *const *) gendata->names); + return len; + +out_zero: + NM_SET_OUT(out_names, NULL); + NM_SET_OUT(out_values, NULL); + return 0; +} + +/** + * nm_setting_option_get_all_names: + * @setting: the #NMSetting + * @out_len: (allow-none) (out): + * + * Gives the name of all set options. + * + * Returns: (array length=out_len zero-terminated=1) (transfer none): + * A %NULL terminated array of key names. If no names are present, this returns + * %NULL. The returned array and the names are owned by %NMSetting and might be invalidated + * by the next operation. + * + * Since: 1.26 + **/ +const char *const * +nm_setting_option_get_all_names(NMSetting *setting, guint *out_len) +{ + const char *const *names; + guint len; + + g_return_val_if_fail(NM_IS_SETTING(setting), NULL); + + len = _nm_setting_option_get_all(setting, &names, NULL); + NM_SET_OUT(out_len, len); + return names; +} + +gboolean +_nm_setting_option_clear(NMSetting *setting, const char *optname) +{ + GHashTable *ht; + + nm_assert(NM_IS_SETTING(setting)); + nm_assert(nm_str_not_empty(optname)); + + ht = _nm_setting_option_hash(setting, FALSE); + if (!ht) + return FALSE; + + return g_hash_table_remove(ht, optname); +} + +/** + * nm_setting_option_clear_by_name: + * @setting: the #NMSetting + * @predicate: (allow-none) (scope call): the predicate for which names + * should be clear. + * If the predicate returns %TRUE for an option name, the option + * gets removed. If %NULL, all options will be removed. + * + * Since: 1.26 + */ +void +nm_setting_option_clear_by_name(NMSetting *setting, NMUtilsPredicateStr predicate) +{ + GHashTable * hash; + GHashTableIter iter; + const char * name; + gboolean changed = FALSE; + + g_return_if_fail(NM_IS_SETTING(setting)); + + hash = _nm_setting_option_hash(NM_SETTING(setting), FALSE); + if (!hash) + return; + + if (!predicate) { + changed = (g_hash_table_size(hash) > 0); + if (changed) + g_hash_table_remove_all(hash); + } else { + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, (gpointer *) &name, NULL)) { + if (predicate(name)) { + g_hash_table_iter_remove(&iter); + changed = TRUE; + } + } + } + + if (changed) + _nm_setting_option_notify(setting, TRUE); +} + +/*****************************************************************************/ + +/** + * nm_setting_option_get: + * @setting: the #NMSetting + * @opt_name: the option name to request. + * + * Returns: (transfer none): the #GVariant or %NULL if the option + * is not set. + * + * Since: 1.26. + */ +GVariant * +nm_setting_option_get(NMSetting *setting, const char *opt_name) +{ + GenData *gendata; + + g_return_val_if_fail(NM_IS_SETTING(setting), FALSE); + g_return_val_if_fail(opt_name, FALSE); + + gendata = _gendata_hash(setting, FALSE); + return gendata ? g_hash_table_lookup(gendata->hash, opt_name) : NULL; +} + +/** + * nm_setting_option_get_boolean: + * @setting: the #NMSetting + * @opt_name: the option to get + * @out_value: (allow-none) (out): the optional output value. + * If the option is unset, %FALSE will be returned. + * + * Returns: %TRUE if @opt_name is set to a boolean variant. + * + * Since: 1.26 + */ +gboolean +nm_setting_option_get_boolean(NMSetting *setting, const char *opt_name, gboolean *out_value) +{ + GVariant *v; + + v = nm_setting_option_get(NM_SETTING(setting), opt_name); + if (v && g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) { + NM_SET_OUT(out_value, g_variant_get_boolean(v)); + return TRUE; + } + NM_SET_OUT(out_value, FALSE); + return FALSE; +} + +/** + * nm_setting_option_get_uint32: + * @setting: the #NMSetting + * @opt_name: the option to get + * @out_value: (allow-none) (out): the optional output value. + * If the option is unset, 0 will be returned. + * + * Returns: %TRUE if @opt_name is set to a uint32 variant. + * + * Since: 1.26 + */ +gboolean +nm_setting_option_get_uint32(NMSetting *setting, const char *opt_name, guint32 *out_value) +{ + GVariant *v; + + v = nm_setting_option_get(NM_SETTING(setting), opt_name); + if (v && g_variant_is_of_type(v, G_VARIANT_TYPE_UINT32)) { + NM_SET_OUT(out_value, g_variant_get_uint32(v)); + return TRUE; + } + NM_SET_OUT(out_value, 0); + return FALSE; +} + +/** + * nm_setting_option_set: + * @setting: the #NMSetting + * @opt_name: the option name to set + * @variant: (allow-none): the variant to set. + * + * If @variant is %NULL, this clears the option if it is set. + * Otherwise, @variant is set as the option. If @variant is + * a floating reference, it will be consumed. + * + * Note that not all setting types support options. It is a bug + * setting a variant to a setting that doesn't support it. + * Currently, only #NMSettingEthtool supports it. + * + * Since: 1.26 + */ +void +nm_setting_option_set(NMSetting *setting, const char *opt_name, GVariant *variant) +{ + GVariant * old_variant; + gboolean changed_name; + gboolean changed_value; + GHashTable *hash; + + g_return_if_fail(NM_IS_SETTING(setting)); + g_return_if_fail(opt_name); + + hash = _nm_setting_option_hash(setting, variant != NULL); + + if (!variant) { + if (hash) { + if (g_hash_table_remove(hash, opt_name)) + _nm_setting_option_notify(setting, TRUE); + } + return; + } + + /* Currently, it is a bug setting any option, unless the setting type supports it. + * And currently, only NMSettingEthtool supports it. + * + * In the future, more setting types may support it. Or we may relax this so + * that options can be attached to all setting types (to indicate "unsupported" + * settings for forward compatibility). + * + * As it is today, internal code will only add gendata options to NMSettingEthtool, + * and there exists not public API to add such options. Still, it is permissible + * to call get(), clear() and set(variant=NULL) also on settings that don't support + * it, as these operations don't add options. + */ + g_return_if_fail( + _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting))->detail.gendata_info); + + old_variant = g_hash_table_lookup(hash, opt_name); + + changed_name = (old_variant == NULL); + changed_value = changed_name || !g_variant_equal(old_variant, variant); + + /* We always want to replace the variant, even if it has + * the same value according to g_variant_equal(). The reason + * is that we want to take a reference on @variant, because + * that is what the user might expect. */ + g_hash_table_insert(hash, g_strdup(opt_name), g_variant_ref_sink(variant)); + + if (changed_value) + _nm_setting_option_notify(setting, !changed_name); +} + +/** + * nm_setting_option_set_boolean: + * @setting: the #NMSetting + * @value: the value to set. + * + * Like nm_setting_option_set() to set a boolean GVariant. + * + * Since: 1.26 + */ +void +nm_setting_option_set_boolean(NMSetting *setting, const char *opt_name, gboolean value) +{ + GVariant * old_variant; + gboolean changed_name; + gboolean changed_value; + GHashTable *hash; + + g_return_if_fail(NM_IS_SETTING(setting)); + g_return_if_fail(opt_name); + + value = (!!value); + + hash = _nm_setting_option_hash(setting, TRUE); + + old_variant = g_hash_table_lookup(hash, opt_name); + + changed_name = (old_variant == NULL); + changed_value = changed_name + || (!g_variant_is_of_type(old_variant, G_VARIANT_TYPE_BOOLEAN) + || g_variant_get_boolean(old_variant) != value); + + g_hash_table_insert(hash, g_strdup(opt_name), g_variant_ref_sink(g_variant_new_boolean(value))); + + if (changed_value) + _nm_setting_option_notify(setting, !changed_name); +} + +/** + * nm_setting_option_set_uint32: + * @setting: the #NMSetting + * @value: the value to set. + * + * Like nm_setting_option_set() to set a uint32 GVariant. + * + * Since: 1.26 + */ +void +nm_setting_option_set_uint32(NMSetting *setting, const char *opt_name, guint32 value) +{ + GVariant * old_variant; + gboolean changed_name; + gboolean changed_value; + GHashTable *hash; + + g_return_if_fail(NM_IS_SETTING(setting)); + g_return_if_fail(opt_name); + + hash = _nm_setting_option_hash(setting, TRUE); + + old_variant = g_hash_table_lookup(hash, opt_name); + + changed_name = (old_variant == NULL); + changed_value = changed_name + || (!g_variant_is_of_type(old_variant, G_VARIANT_TYPE_UINT32) + || g_variant_get_uint32(old_variant) != value); + + g_hash_table_insert(hash, g_strdup(opt_name), g_variant_ref_sink(g_variant_new_uint32(value))); + + if (changed_value) + _nm_setting_option_notify(setting, !changed_name); +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMSetting *setting = NM_SETTING(object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string(value, nm_setting_get_name(setting)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_setting_init(NMSetting *setting) +{} + +static void +finalize(GObject *object) +{ + NMSettingPrivate *priv = NM_SETTING_GET_PRIVATE(object); + + if (priv->gendata) { + g_free(priv->gendata->names); + g_free(priv->gendata->values); + g_hash_table_unref(priv->gendata->hash); + g_slice_free(GenData, priv->gendata); + } + + G_OBJECT_CLASS(nm_setting_parent_class)->finalize(object); +} + +static void +nm_setting_class_init(NMSettingClass *setting_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(setting_class); + + g_type_class_add_private(setting_class, sizeof(NMSettingPrivate)); + + object_class->get_property = get_property; + object_class->finalize = finalize; + + setting_class->update_one_secret = update_one_secret; + setting_class->get_secret_flags = get_secret_flags; + setting_class->set_secret_flags = set_secret_flags; + setting_class->compare_property = compare_property; + setting_class->clear_secrets = clear_secrets; + setting_class->for_each_secret = for_each_secret; + setting_class->duplicate_copy_properties = duplicate_copy_properties; + setting_class->enumerate_values = enumerate_values; + setting_class->aggregate = aggregate; + setting_class->init_from_dbus = init_from_dbus; + + /** + * NMSetting:name: + * + * The setting's name, which uniquely identifies the setting within the + * connection. Each setting type has a name unique to that type, for + * example "ppp" or "802-11-wireless" or "802-3-ethernet". + **/ + obj_properties[PROP_NAME] = g_param_spec_string(NM_SETTING_NAME, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties); +} diff --git a/src/libnm-core-impl/nm-simple-connection.c b/src/libnm-core-impl/nm-simple-connection.c new file mode 100644 index 00000000..6b5b1184 --- /dev/null +++ b/src/libnm-core-impl/nm-simple-connection.c @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2008 Novell, Inc. + * Copyright (C) 2007 - 2014 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-simple-connection.h" + +#include "nm-setting-private.h" + +/** + * SECTION:nm-simple-connection + * @short_description: An unmanaged connection + * + * An #NMSimpleConnection does not directly represent a D-Bus-exported connection, + * but might be used in the process of creating a new one. + **/ + +/*****************************************************************************/ + +GTypeClass *_nm_simple_connection_class_instance = NULL; +int _nm_simple_connection_private_offset; + +/*****************************************************************************/ + +/** + * NMSimpleConnection: + */ +struct _NMSimpleConnection { + GObject parent; + /* In the past, this struct was public API. Preserve ABI! */ +}; + +struct _NMSimpleConnectionClass { + GObjectClass parent; + /* In the past, this struct was public API. Preserve ABI! */ + gpointer padding[4]; +}; + +static void nm_simple_connection_interface_init(NMConnectionInterface *iface); + +G_DEFINE_TYPE_WITH_CODE(NMSimpleConnection, + nm_simple_connection, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(NM_TYPE_CONNECTION, + nm_simple_connection_interface_init);) + +#define _GET_PRIVATE(self) \ + G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_SIMPLE_CONNECTION, NMConnectionPrivate) + +/*****************************************************************************/ + +static void +nm_simple_connection_init(NMSimpleConnection *self) +{ + NMConnectionPrivate *priv; + + priv = _GET_PRIVATE(self); + + priv->self = (NMConnection *) self; +} + +/** + * nm_simple_connection_new: + * + * Creates a new #NMSimpleConnection object with no #NMSetting objects. + * + * Returns: (transfer full): the new empty #NMConnection object + **/ +NMConnection * +nm_simple_connection_new(void) +{ + return g_object_new(NM_TYPE_SIMPLE_CONNECTION, NULL); +} + +/** + * _nm_simple_connection_new_from_dbus: + * @dict: a #GVariant of type %NM_VARIANT_TYPE_CONNECTION describing the connection + * @error: on unsuccessful return, an error + * + * Creates a new #NMSimpleConnection from a hash table describing the + * connection. See nm_connection_to_dbus() for a description of the expected + * hash table. + * + * Returns: (transfer full): the new #NMSimpleConnection object, populated with + * settings created from the values in the hash table, or %NULL if there was + * an error. + **/ +NMConnection * +_nm_simple_connection_new_from_dbus(GVariant *dict, NMSettingParseFlags parse_flags, GError **error) +{ + NMConnection *connection; + + g_return_val_if_fail(dict != NULL, NULL); + g_return_val_if_fail(g_variant_is_of_type(dict, NM_VARIANT_TYPE_CONNECTION), NULL); + g_return_val_if_fail(!NM_FLAGS_ANY(parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL), NULL); + g_return_val_if_fail( + !NM_FLAGS_ALL(parse_flags, + NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT), + NULL); + + connection = nm_simple_connection_new(); + if (!_nm_connection_replace_settings(connection, dict, parse_flags, error)) + g_clear_object(&connection); + return connection; +} + +/** + * nm_simple_connection_new_from_dbus: + * @dict: a #GVariant of type %NM_VARIANT_TYPE_CONNECTION describing the connection + * @error: on unsuccessful return, an error + * + * Creates a new #NMSimpleConnection from a hash table describing the + * connection and normalize the connection. See nm_connection_to_dbus() for a + * description of the expected hash table. + * + * Returns: (transfer full): the new #NMSimpleConnection object, populated with + * settings created from the values in the hash table, or %NULL if the + * connection failed to normalize. + **/ +NMConnection * +nm_simple_connection_new_from_dbus(GVariant *dict, GError **error) +{ + return _nm_simple_connection_new_from_dbus(dict, NM_SETTING_PARSE_FLAGS_NORMALIZE, error); +} + +/** + * nm_simple_connection_new_clone: + * @connection: the #NMConnection to clone + * + * Clones an #NMConnection as an #NMSimpleConnection. + * + * Returns: (transfer full): a new #NMConnection containing the same settings + * and properties as the source #NMConnection + **/ +NMConnection * +nm_simple_connection_new_clone(NMConnection *connection) +{ + NMConnection *clone; + const char * path; + + g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL); + + clone = nm_simple_connection_new(); + + path = nm_connection_get_path(connection); + if (path) + nm_connection_set_path(clone, path); + + nm_connection_replace_settings_from_connection(clone, connection); + + return clone; +} + +static void +dispose(GObject *object) +{ + NMConnection *connection = NM_CONNECTION(object); + +#if NM_MORE_ASSERTS + g_signal_handlers_disconnect_by_data(object, + (gpointer) &_nmtst_connection_unchanging_user_data); +#endif + + nm_connection_clear_secrets(connection); + + _nm_connection_private_clear(_GET_PRIVATE(connection)); + + G_OBJECT_CLASS(nm_simple_connection_parent_class)->dispose(object); +} + +static void +nm_simple_connection_class_init(NMSimpleConnectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(NMConnectionPrivate)); + + object_class->dispose = dispose; + + _nm_simple_connection_private_offset = g_type_class_get_instance_private_offset(klass); + _nm_simple_connection_class_instance = (GTypeClass *) klass; +} + +static void +nm_simple_connection_interface_init(NMConnectionInterface *iface) +{} diff --git a/src/libnm-core-impl/nm-team-utils.c b/src/libnm-core-impl/nm-team-utils.c new file mode 100644 index 00000000..bd9ef510 --- /dev/null +++ b/src/libnm-core-impl/nm-team-utils.c @@ -0,0 +1,2862 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#define NM_VALUE_TYPE_DEFINE_FUNCTIONS + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-team-utils.h" + +#include "nm-errors.h" +#include "nm-utils-private.h" +#include "libnm-glib-aux/nm-json-aux.h" +#include "libnm-core-intern/nm-core-internal.h" +#include "nm-setting-team.h" +#include "nm-setting-team-port.h" + +/*****************************************************************************/ + +typedef enum { + SET_FIELD_MODE_UNSET = 0, + SET_FIELD_MODE_SET = 1, + + /* Sets the field as set, unless the field is at the default. + * This is the case for API that is called from NMSettingTeam/NMSettingTeamPort. + * This means, using libnm API to reset the value of a NMSetting to the default, + * will mark the field as unset. + * This is different from initializing the field when parsing JSON/GVariant. In + * that case an explicitly set field (even set to the default value) will be remembered + * to be set. */ + SET_FIELD_MODE_SET_UNLESS_DEFAULT = 2, +} SetFieldModeEnum; + +typedef enum { + RESET_JSON_NO = FALSE, + RESET_JSON_YES = TRUE, +} ResetJsonEnum; + +/* we rely on "config" being the first. At various places we iterate over attribute types, + * starting after "config".*/ +G_STATIC_ASSERT(_NM_TEAM_ATTRIBUTE_0 == 0); +G_STATIC_ASSERT(NM_TEAM_ATTRIBUTE_CONFIG == 1); + +static const char *const _valid_names_runner[] = { + NM_SETTING_TEAM_RUNNER_BROADCAST, + NM_SETTING_TEAM_RUNNER_ROUNDROBIN, + NM_SETTING_TEAM_RUNNER_RANDOM, + NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP, + NM_SETTING_TEAM_RUNNER_LOADBALANCE, + NM_SETTING_TEAM_RUNNER_LACP, + NULL, +}; + +static const char *const _valid_names_runner_hwaddr_policy[] = { + NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_SAME_ALL, + NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_BY_ACTIVE, + NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_ONLY_ACTIVE, + NULL, +}; + +static const char *const _valid_names_runner_tx_balancer[] = { + "basic", + NULL, +}; + +static const char *const _valid_names_runner_tx_hash[] = { + "eth", + "vlan", + "ipv4", + "ipv6", + "ip", + "l3", + "l4", + "tcp", + "udp", + "sctp", + NULL, +}; + +static const char *const _valid_names_runner_agg_select_policy[] = { + "lacp_prio", + "lacp_prio_stable", + "bandwidth", + "count", + "port_config", + NULL, +}; + +typedef struct { + NMTeamAttribute team_attr; + const char *const *valid_runners; +} RunnerCompatElem; + +static const RunnerCompatElem _runner_compat_lst[] = { + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP), + }, + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LOADBALANCE, NM_SETTING_TEAM_RUNNER_LACP), + }, + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LOADBALANCE, NM_SETTING_TEAM_RUNNER_LACP), + }, + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LOADBALANCE, NM_SETTING_TEAM_RUNNER_LACP), + }, + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), + }, + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), + }, + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), + }, + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), + }, + { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY, + NM_MAKE_STRV(NM_SETTING_TEAM_RUNNER_LACP), + }, +}; + +typedef struct { + const char *const *js_keys; + const char * property_name; + NMValueTypUnion default_val; + union { + struct { + gint32 min; + gint32 max; + } r_int32; + struct { + const char *const *valid_names; + } r_string; + } range; + NMTeamAttribute team_attr; + NMValueType value_type; + guint8 field_offset; + guint8 js_keys_len; + bool for_master : 1; + bool for_port : 1; + bool has_range : 1; +} TeamAttrData; + +#define TEAM_ATTR_IDX(_is_port, _team_attr) \ + (((!(_is_port) || (_team_attr) < _NM_TEAM_ATTRIBUTE_START) \ + ? (int) (_team_attr) \ + : (((int) (_NM_TEAM_ATTRIBUTE_MASTER_NUM - _NM_TEAM_ATTRIBUTE_START)) \ + + ((int) (_team_attr)))) \ + - 1) + +#define TEAM_ATTR_IDX_CONFIG (TEAM_ATTR_IDX(FALSE, NM_TEAM_ATTRIBUTE_CONFIG)) + +static const TeamAttrData team_attr_datas[] = { + +#define _JS_KEYS(...) .js_keys = NM_MAKE_STRV(__VA_ARGS__), .js_keys_len = NM_NARG(__VA_ARGS__) + +#define _VAL_BOOL(_default) .default_val.v_bool = (_default) + +#define _VAL_INT32(_default) .default_val.v_int32 = (_default) + +#define _VAL_INT32_RANGE(_default, _min, _max) \ + _VAL_INT32(_default), .has_range = TRUE, \ + .range.r_int32 = { \ + .min = _min, \ + .max = _max, \ + } + +#define _VAL_STRING() .default_val.v_string = NULL + +#define _VAL_STRING_RANGE(_valid_names) \ + _VAL_STRING(), .has_range = TRUE, \ + .range.r_string = { \ + .valid_names = (_valid_names), \ + } + +#define _VAL_UNSPEC() .default_val.v_string = (NULL) + +#define _INIT(_is_port, _team_attr, field, _value_type, _property_name, ...) \ + [TEAM_ATTR_IDX(_is_port, _team_attr)] = { \ + .for_master = (_team_attr) < _NM_TEAM_ATTRIBUTE_START || !(_is_port), \ + .for_port = (_team_attr) < _NM_TEAM_ATTRIBUTE_START || (_is_port), \ + .team_attr = (_team_attr), \ + .field_offset = G_STRUCT_OFFSET(NMTeamSetting, _data_priv.field), \ + .value_type = (_value_type), \ + .property_name = ""_property_name \ + "", \ + __VA_ARGS__} + + _INIT(0, NM_TEAM_ATTRIBUTE_CONFIG, _js_str, NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_CONFIG, ), + + _INIT(0, + NM_TEAM_ATTRIBUTE_LINK_WATCHERS, + link_watchers, + NM_VALUE_TYPE_UNSPEC, + NM_SETTING_TEAM_LINK_WATCHERS, + _JS_KEYS("link_watch"), + _VAL_UNSPEC(), ), + + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT, + master.notify_peers_count, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_NOTIFY_PEERS_COUNT, + _JS_KEYS("notify_peers", "count"), + _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL, + master.notify_peers_interval, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL, + _JS_KEYS("notify_peers", "interval"), + _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT, + master.mcast_rejoin_count, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_MCAST_REJOIN_COUNT, + _JS_KEYS("mcast_rejoin", "count"), + _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL, + master.mcast_rejoin_interval, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL, + _JS_KEYS("mcast_rejoin", "interval"), + _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER, + master.runner, + NM_VALUE_TYPE_STRING, + NM_SETTING_TEAM_RUNNER, + _JS_KEYS("runner", "name"), + _VAL_STRING_RANGE(_valid_names_runner), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY, + master.runner_hwaddr_policy, + NM_VALUE_TYPE_STRING, + NM_SETTING_TEAM_RUNNER_HWADDR_POLICY, + _JS_KEYS("runner", "hwaddr_policy"), + _VAL_STRING_RANGE(_valid_names_runner_hwaddr_policy), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, + master.runner_tx_hash, + NM_VALUE_TYPE_UNSPEC, + NM_SETTING_TEAM_RUNNER_TX_HASH, + _JS_KEYS("runner", "tx_hash"), + _VAL_UNSPEC(), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER, + master.runner_tx_balancer, + NM_VALUE_TYPE_STRING, + NM_SETTING_TEAM_RUNNER_TX_BALANCER, + _JS_KEYS("runner", "tx_balancer", "name"), + _VAL_STRING_RANGE(_valid_names_runner_tx_balancer), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, + master.runner_tx_balancer_interval, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, + _JS_KEYS("runner", "tx_balancer", "balancing_interval"), + _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE, + master.runner_active, + NM_VALUE_TYPE_BOOL, + NM_SETTING_TEAM_RUNNER_ACTIVE, + _JS_KEYS("runner", "active"), + _VAL_BOOL(TRUE), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE, + master.runner_fast_rate, + NM_VALUE_TYPE_BOOL, + NM_SETTING_TEAM_RUNNER_FAST_RATE, + _JS_KEYS("runner", "fast_rate"), + _VAL_BOOL(FALSE), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO, + master.runner_sys_prio, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_RUNNER_SYS_PRIO, + _JS_KEYS("runner", "sys_prio"), + _VAL_INT32_RANGE(-1, 0, USHRT_MAX + 1), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS, + master.runner_min_ports, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_RUNNER_MIN_PORTS, + _JS_KEYS("runner", "min_ports"), + _VAL_INT32_RANGE(-1, 1, UCHAR_MAX + 1), ), + _INIT(0, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY, + master.runner_agg_select_policy, + NM_VALUE_TYPE_STRING, + NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY, + _JS_KEYS("runner", "agg_select_policy"), + _VAL_STRING_RANGE(_valid_names_runner_agg_select_policy), ), + + _INIT(1, + NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID, + port.queue_id, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_PORT_QUEUE_ID, + _JS_KEYS("queue_id"), + _VAL_INT32_RANGE(-1, 0, G_MAXINT32), ), + _INIT(1, + NM_TEAM_ATTRIBUTE_PORT_PRIO, + port.prio, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_PORT_PRIO, + _JS_KEYS("prio"), + _VAL_INT32(0), ), + _INIT(1, + NM_TEAM_ATTRIBUTE_PORT_STICKY, + port.sticky, + NM_VALUE_TYPE_BOOL, + NM_SETTING_TEAM_PORT_STICKY, + _JS_KEYS("sticky"), + _VAL_BOOL(FALSE), ), + _INIT(1, + NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO, + port.lacp_prio, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_PORT_LACP_PRIO, + _JS_KEYS("lacp_prio"), + _VAL_INT32_RANGE(-1, 0, USHRT_MAX + 1), ), + _INIT(1, + NM_TEAM_ATTRIBUTE_PORT_LACP_KEY, + port.lacp_key, + NM_VALUE_TYPE_INT32, + NM_SETTING_TEAM_PORT_LACP_KEY, + _JS_KEYS("lacp_key"), + _VAL_INT32_RANGE(-1, 0, USHRT_MAX + 1), ), + +#undef _INIT +}; + +/*****************************************************************************/ + +typedef enum { + LINK_WATCHER_ATTRIBUTE_NAME, + LINK_WATCHER_ATTRIBUTE_DELAY_UP, + LINK_WATCHER_ATTRIBUTE_DELAY_DOWN, + LINK_WATCHER_ATTRIBUTE_INTERVAL, + LINK_WATCHER_ATTRIBUTE_INIT_WAIT, + LINK_WATCHER_ATTRIBUTE_MISSED_MAX, + LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, + LINK_WATCHER_ATTRIBUTE_TARGET_HOST, + LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, + LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, + LINK_WATCHER_ATTRIBUTE_VLANID, + LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS, +} LinkWatcherAttribute; + +#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL \ + LINK_WATCHER_ATTRIBUTE_NAME, LINK_WATCHER_ATTRIBUTE_DELAY_UP, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN +#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING \ + LINK_WATCHER_ATTRIBUTE_NAME, LINK_WATCHER_ATTRIBUTE_INTERVAL, \ + LINK_WATCHER_ATTRIBUTE_INIT_WAIT, LINK_WATCHER_ATTRIBUTE_MISSED_MAX, \ + LINK_WATCHER_ATTRIBUTE_TARGET_HOST +#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING \ + LINK_WATCHER_ATTRIBUTE_NAME, LINK_WATCHER_ATTRIBUTE_INTERVAL, \ + LINK_WATCHER_ATTRIBUTE_INIT_WAIT, LINK_WATCHER_ATTRIBUTE_MISSED_MAX, \ + LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, LINK_WATCHER_ATTRIBUTE_TARGET_HOST, \ + LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, \ + LINK_WATCHER_ATTRIBUTE_VLANID, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS + +typedef struct { + const char * js_key; + const char * dbus_name; + NMValueTypUnion default_val; + LinkWatcherAttribute link_watcher_attr; + NMValueType value_type; +} LinkWatcherAttrData; + +static const LinkWatcherAttrData link_watcher_attr_datas[] = { +#define _INIT(_link_watcher_attr, _js_key, _dbus_name, _value_type, ...) \ + [_link_watcher_attr] = {.link_watcher_attr = (_link_watcher_attr), \ + .value_type = (_value_type), \ + .js_key = (""_js_key \ + ""), \ + .dbus_name = (""_dbus_name \ + ""), \ + __VA_ARGS__} + _INIT(LINK_WATCHER_ATTRIBUTE_NAME, "name", "name", NM_VALUE_TYPE_STRING, ), + _INIT(LINK_WATCHER_ATTRIBUTE_DELAY_UP, "delay_up", "delay-up", NM_VALUE_TYPE_INT, ), + _INIT(LINK_WATCHER_ATTRIBUTE_DELAY_DOWN, "delay_down", "delay-down", NM_VALUE_TYPE_INT, ), + _INIT(LINK_WATCHER_ATTRIBUTE_INTERVAL, "interval", "interval", NM_VALUE_TYPE_INT, ), + _INIT(LINK_WATCHER_ATTRIBUTE_INIT_WAIT, "init_wait", "init-wait", NM_VALUE_TYPE_INT, ), + _INIT(LINK_WATCHER_ATTRIBUTE_MISSED_MAX, + "missed_max", + "missed-max", + NM_VALUE_TYPE_INT, + .default_val.v_int = 3, ), + _INIT(LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, "source_host", "source-host", NM_VALUE_TYPE_STRING, ), + _INIT(LINK_WATCHER_ATTRIBUTE_TARGET_HOST, "target_host", "target-host", NM_VALUE_TYPE_STRING, ), + _INIT(LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, + "validate_active", + "validate-active", + NM_VALUE_TYPE_BOOL, ), + _INIT(LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, + "validate_inactive", + "validate-inactive", + NM_VALUE_TYPE_BOOL, ), + _INIT(LINK_WATCHER_ATTRIBUTE_VLANID, + "vlanid", + "vlanid", + NM_VALUE_TYPE_INT, + .default_val.v_int = -1, ), + _INIT(LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS, "send_always", "send-always", NM_VALUE_TYPE_BOOL, ), +#undef _INIT +}; + +/*****************************************************************************/ + +static const TeamAttrData *_team_attr_data_get(gboolean is_port, NMTeamAttribute team_attr); +static gpointer _team_setting_get_field(const NMTeamSetting *self, const TeamAttrData *attr_data); +static void _link_watcher_to_json(const NMTeamLinkWatcher *link_watcher, GString *gstr); + +/*****************************************************************************/ + +static void +_team_attr_data_ASSERT(const TeamAttrData *attr_data) +{ +#if NM_MORE_ASSERTS > 5 + nm_assert(attr_data); + if (attr_data->for_port) + nm_assert(attr_data == _team_attr_data_get(TRUE, attr_data->team_attr)); + if (attr_data->for_master) + nm_assert(attr_data == _team_attr_data_get(FALSE, attr_data->team_attr)); + nm_assert((attr_data - team_attr_datas) + == TEAM_ATTR_IDX(attr_data->for_port, attr_data->team_attr)); + nm_assert(attr_data->value_type > 0); + nm_assert(attr_data->field_offset < sizeof(NMTeamSetting)); + nm_assert(attr_data->js_keys_len == NM_PTRARRAY_LEN(attr_data->js_keys)); + nm_assert(attr_data->property_name); + { + static int checked = 0; + + if (checked == 0) { + checked = 1; + + for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; + attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) + _team_attr_data_ASSERT(attr_data); + } + } +#endif +} + +static gboolean +_team_attr_data_is_relevant(const TeamAttrData *attr_data, gboolean is_port) +{ + return is_port ? attr_data->for_port : attr_data->for_master; +} + +static const TeamAttrData * +_team_attr_data_get(gboolean is_port, NMTeamAttribute team_attr) +{ + const int idx = TEAM_ATTR_IDX(is_port, team_attr); + + nm_assert(idx >= 0 && idx < G_N_ELEMENTS(team_attr_datas)); + nm_assert(team_attr_datas[idx].team_attr == team_attr); + nm_assert(_team_attr_data_is_relevant(&team_attr_datas[idx], is_port)); + + return &team_attr_datas[idx]; +} + +static const TeamAttrData * +_team_attr_data_find_for_property_name(gboolean is_port, const char *property_name) +{ + const TeamAttrData *attr_data; + + for (attr_data = team_attr_datas; attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) { + if (_team_attr_data_is_relevant(attr_data, is_port) + && nm_streq(property_name, attr_data->property_name)) + return attr_data; + } + return NULL; +} + +static int +_team_attr_data_cmp(const TeamAttrData *attr_data, + gboolean is_port, + gconstpointer val_a, + gconstpointer val_b) +{ + const GPtrArray *v_ptrarray_a; + const GPtrArray *v_ptrarray_b; + guint len; + + _team_attr_data_ASSERT(attr_data); + nm_assert(val_a); + nm_assert(val_b); + + if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) + NM_CMP_RETURN(nm_value_type_cmp(attr_data->value_type, val_a, val_b)); + else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { + v_ptrarray_a = *((const GPtrArray *const *) val_a); + v_ptrarray_b = *((const GPtrArray *const *) val_b); + len = v_ptrarray_a ? v_ptrarray_a->len : 0u; + NM_CMP_DIRECT(len, (v_ptrarray_b ? v_ptrarray_b->len : 0u)); + if (len > 0) { + NM_CMP_RETURN( + nm_team_link_watchers_cmp((const NMTeamLinkWatcher *const *) v_ptrarray_a->pdata, + (const NMTeamLinkWatcher *const *) v_ptrarray_b->pdata, + len, + FALSE)); + } + } else if (!is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { + v_ptrarray_a = *((const GPtrArray *const *) val_a); + v_ptrarray_b = *((const GPtrArray *const *) val_b); + NM_CMP_RETURN( + nm_utils_strv_cmp_n(v_ptrarray_a ? (const char *const *) v_ptrarray_a->pdata : NULL, + v_ptrarray_a ? v_ptrarray_a->len : 0u, + v_ptrarray_b ? (const char *const *) v_ptrarray_b->pdata : NULL, + v_ptrarray_b ? v_ptrarray_b->len : 0u)); + } else + nm_assert_not_reached(); + return 0; +} + +static gboolean +_team_attr_data_equal(const TeamAttrData *attr_data, + gboolean is_port, + gconstpointer val_a, + gconstpointer val_b) +{ + return _team_attr_data_cmp(attr_data, is_port, val_a, val_b) == 0; +} + +static void +_team_attr_data_copy(const TeamAttrData *attr_data, + gboolean is_port, + gpointer dst, + gconstpointer src) +{ + GPtrArray * v_ptrarray_dst; + const GPtrArray *v_ptrarray_src; + GPtrArray * dst_array; + guint i, len; + + if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) + nm_value_type_copy(attr_data->value_type, dst, src); + else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { + v_ptrarray_src = *((const GPtrArray *const *) src); + v_ptrarray_dst = *((GPtrArray **) dst); + len = (v_ptrarray_src ? v_ptrarray_src->len : 0u); + + if (len == 0) { + if (v_ptrarray_dst) + g_ptr_array_set_size(v_ptrarray_dst, 0); + } else { + dst_array = g_ptr_array_new_full(len, (GDestroyNotify) nm_team_link_watcher_unref); + for (i = 0; i < len; i++) { + if (v_ptrarray_src->pdata[i]) { + nm_team_link_watcher_ref(v_ptrarray_src->pdata[i]); + g_ptr_array_add(dst_array, v_ptrarray_src->pdata[i]); + } + } + if (v_ptrarray_dst) + g_ptr_array_unref(v_ptrarray_dst); + *((GPtrArray **) dst) = dst_array; + } + } else if (!is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { + v_ptrarray_src = *((const GPtrArray *const *) src); + v_ptrarray_dst = *((GPtrArray **) dst); + len = (v_ptrarray_src ? v_ptrarray_src->len : 0u); + + if (v_ptrarray_src && v_ptrarray_src->len > 0) { + dst_array = g_ptr_array_new_full(v_ptrarray_src->len, g_free); + for (i = 0; i < v_ptrarray_src->len; i++) + g_ptr_array_add(dst_array, g_strdup(v_ptrarray_src->pdata[i])); + } else + dst_array = NULL; + if (v_ptrarray_dst) + g_ptr_array_unref(v_ptrarray_dst); + *((GPtrArray **) dst) = dst_array; + } else + nm_assert_not_reached(); +} + +static void +_team_attr_data_to_json(const TeamAttrData *attr_data, + gboolean is_port, + GString * gstr, + gconstpointer p_field) +{ + guint i; + + _team_attr_data_ASSERT(attr_data); + nm_assert(p_field); + + nm_json_gstr_append_obj_name(gstr, attr_data->js_keys[attr_data->js_keys_len - 1], '\0'); + + if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) { + nm_value_type_to_json(attr_data->value_type, gstr, p_field); + return; + } + + if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { + const GPtrArray *v_ptrarray = *((const GPtrArray *const *) p_field); + + if (!v_ptrarray) + g_string_append(gstr, "null"); + else if (v_ptrarray->len == 0) + g_string_append(gstr, "[ ]"); + else if (v_ptrarray->len == 1) + _link_watcher_to_json(v_ptrarray->pdata[0], gstr); + else { + g_string_append(gstr, "[ "); + for (i = 0; i < v_ptrarray->len; i++) { + if (i > 0) + nm_json_gstr_append_delimiter(gstr); + _link_watcher_to_json(v_ptrarray->pdata[i], gstr); + } + g_string_append(gstr, " ]"); + } + return; + } + + if (!is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { + const GPtrArray *v_ptrarray = *((const GPtrArray *const *) p_field); + + if (!v_ptrarray) + g_string_append(gstr, "null"); + else { + g_string_append(gstr, "[ "); + for (i = 0; i < v_ptrarray->len; i++) { + if (i > 0) + nm_json_gstr_append_delimiter(gstr); + nm_json_gstr_append_string(gstr, v_ptrarray->pdata[i]); + } + g_string_append(gstr, i > 0 ? " ]" : "]"); + } + return; + } + + nm_assert_not_reached(); +} + +/*****************************************************************************/ + +static void +_team_setting_ASSERT(const NMTeamSetting *self) +{ + nm_assert(self); + nm_assert(!self->d._js_str_need_synthetize || !self->d._js_str); +#if NM_MORE_ASSERTS > 2 + if (!self->d.strict_validated) { + nm_assert(!self->d._js_str_need_synthetize); + nm_assert(self->d._js_str); + } + nm_assert(self->d.link_watchers); + nm_assert(self->d.is_port || !self->d.master.runner_tx_hash + || self->d.master.runner_tx_hash->len > 0); +#endif +} + +static gboolean +_team_setting_has_field(const NMTeamSetting *self, const TeamAttrData *attr_data) +{ + _team_setting_ASSERT(self); + return NM_FLAGS_ALL(self->d.has_fields_mask, nm_team_attribute_to_flags(attr_data->team_attr)); +} + +static gboolean +_team_setting_has_fields_any_v(const NMTeamSetting * self, + const NMTeamAttribute *team_attrs, + gsize n_team_attrs) +{ + gsize i; + + for (i = 0; i < n_team_attrs; i++) { + const TeamAttrData *attr_data = _team_attr_data_get(self->d.is_port, team_attrs[i]); + + if (_team_setting_has_field(self, attr_data)) + return TRUE; + } + return FALSE; +} + +#define _team_setting_has_fields_any(self, ...) \ + _team_setting_has_fields_any_v((self), \ + ((const NMTeamAttribute[]){__VA_ARGS__}), \ + NM_NARG(__VA_ARGS__)) + +static void +_team_setting_has_field_set(NMTeamSetting * self, + const TeamAttrData *attr_data, + SetFieldModeEnum set_field_mode) +{ + guint32 mask = nm_team_attribute_to_flags(attr_data->team_attr); + + _team_setting_ASSERT(self); + + switch (set_field_mode) { + case SET_FIELD_MODE_UNSET: + goto do_unset; + case SET_FIELD_MODE_SET: + goto do_set; + case SET_FIELD_MODE_SET_UNLESS_DEFAULT: + if (_team_attr_data_equal(attr_data, + self->d.is_port, + _team_setting_get_field(self, attr_data), + &attr_data->default_val)) + goto do_unset; + goto do_set; + } + nm_assert_not_reached(); + +do_unset: + self->_data_priv.has_fields_mask &= ~mask; + return; +do_set: + self->_data_priv.has_fields_mask |= mask; +} + +static gpointer +_team_setting_get_field(const NMTeamSetting *self, const TeamAttrData *attr_data) +{ + _team_setting_ASSERT(self); + _team_attr_data_ASSERT(attr_data); + nm_assert(_team_attr_data_is_relevant(attr_data, self->d.is_port)); + +#if NM_MORE_ASSERTS > 5 + if (attr_data->for_master && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO) + nm_assert((gpointer) (((char *) self) + attr_data->field_offset) + == &self->d.master.runner_sys_prio); +#endif + + return (((char *) self) + attr_data->field_offset); +} + +static guint32 +_team_setting_attribute_changed(NMTeamSetting * self, + const TeamAttrData *attr_data, + gboolean changed, + SetFieldModeEnum set_field_mode, + ResetJsonEnum reset_json) +{ + guint32 changed_flags; + + _team_setting_has_field_set(self, attr_data, set_field_mode); + + if (!reset_json) { + return changed ? nm_team_attribute_to_flags(attr_data->team_attr) : 0u; + } + + if (!changed) { + /* a regular attribute was set, but the value did not change. + * + * If we previously were in non-strict mode, then + * + * - switch to strict-mode. Clearly the user set a regular attribute + * and hence now we want to validate the setting. + * + * - clear the JSON string. We need to regenerate it. + */ + if (self->_data_priv.strict_validated) + return 0; + changed_flags = nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); + } else { + changed_flags = nm_team_attribute_to_flags(attr_data->team_attr) + | nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); + } + + nm_clear_g_free((char **) &self->_data_priv._js_str); + self->_data_priv.strict_validated = TRUE; + self->_data_priv._js_str_need_synthetize = TRUE; + + return changed_flags; +} + +static guint32 +_team_setting_attribute_changed_attr(NMTeamSetting * self, + NMTeamAttribute team_attr, + gboolean changed, + SetFieldModeEnum set_field_mode, + ResetJsonEnum reset_json) +{ + return _team_setting_attribute_changed(self, + _team_attr_data_get(self->d.is_port, team_attr), + changed, + set_field_mode, + reset_json); +} + +static gboolean +_team_setting_field_to_json(const NMTeamSetting *self, + GString * gstr, + gboolean prepend_delimiter, + const TeamAttrData * attr_data) +{ + if (!_team_setting_has_field(self, attr_data)) + return FALSE; + + if (prepend_delimiter) + nm_json_gstr_append_delimiter(gstr); + _team_attr_data_to_json(attr_data, + self->d.is_port, + gstr, + _team_setting_get_field(self, attr_data)); + return TRUE; +} + +static gboolean +_team_setting_fields_to_json_maybe(const NMTeamSetting * self, + GString * gstr, + gboolean prepend_delimiter, + const NMTeamAttribute *team_attrs_lst, + gsize team_attrs_lst_len) +{ + gsize i; + gboolean any_added = FALSE; + + for (i = 0; i < team_attrs_lst_len; i++) { + if (_team_setting_field_to_json(self, + gstr, + prepend_delimiter, + _team_attr_data_get(self->d.is_port, team_attrs_lst[i]))) { + any_added = TRUE; + prepend_delimiter = TRUE; + } + } + return any_added; +} + +static guint32 +_team_setting_set(NMTeamSetting * self, + gboolean modify, + const bool * has_lst, + const NMValueTypUnion *val_lst) +{ + guint32 changed_flags = 0; + const TeamAttrData *attr_data; + + nm_assert((!has_lst) == (!val_lst)); + + for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; + attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) { + const NMValueTypUnion *p_val; + gconstpointer p_field; + gboolean has_field; + + if (!_team_attr_data_is_relevant(attr_data, self->d.is_port)) + continue; + + has_field = (has_lst && has_lst[attr_data->team_attr]); + + p_val = has_field ? &val_lst[attr_data->team_attr] : &attr_data->default_val; + + p_field = _team_setting_get_field(self, attr_data); + + if (!_team_attr_data_equal(attr_data, self->d.is_port, p_val, p_field)) { + if (modify) { + _team_attr_data_copy(attr_data, self->d.is_port, (gpointer) p_field, p_val); + } + changed_flags |= nm_team_attribute_to_flags(attr_data->team_attr); + } + + if (modify) { + _team_setting_has_field_set(self, + attr_data, + has_field ? SET_FIELD_MODE_SET : SET_FIELD_MODE_UNSET); + } + } + + return changed_flags; +} + +static guint32 +_team_setting_check_default(const NMTeamSetting *self) +{ + return _team_setting_set((NMTeamSetting *) self, FALSE, NULL, NULL); +} + +static guint32 +_team_setting_set_default(NMTeamSetting *self) +{ + return _team_setting_set(self, TRUE, NULL, NULL); +} + +/*****************************************************************************/ + +gconstpointer +_nm_team_setting_value_get(const NMTeamSetting *self, + NMTeamAttribute team_attr, + NMValueType value_type) +{ + const TeamAttrData *attr_data = _team_attr_data_get(self->d.is_port, team_attr); + + nm_assert(value_type == attr_data->value_type); + + nm_assert(_team_setting_has_field(self, attr_data) + || _team_attr_data_equal(attr_data, + self->d.is_port, + _team_setting_get_field(self, attr_data), + &attr_data->default_val)); + return _team_setting_get_field(self, attr_data); +} + +static guint32 +_team_setting_value_set(NMTeamSetting * self, + const TeamAttrData *attr_data, + gconstpointer val, + SetFieldModeEnum set_field_mode, + ResetJsonEnum reset_json) +{ + gpointer p_field; + gboolean changed; + + nm_assert(self); + _team_attr_data_ASSERT(attr_data); + nm_assert(val); + + p_field = _team_setting_get_field(self, attr_data); + + changed = !_team_attr_data_equal(attr_data, self->d.is_port, p_field, val); + if (changed) + nm_value_type_copy(attr_data->value_type, p_field, val); + return _team_setting_attribute_changed(self, attr_data, changed, set_field_mode, reset_json); +} + +guint32 +nm_team_setting_value_reset(NMTeamSetting * self, + NMTeamAttribute team_attr, + gboolean to_default /* or else unset */) +{ + const TeamAttrData *attr_data; + + nm_assert(self); + + attr_data = _team_attr_data_get(self->d.is_port, team_attr); + + return _team_setting_value_set(self, + attr_data, + &attr_data->default_val, + to_default ? SET_FIELD_MODE_SET : SET_FIELD_MODE_UNSET, + RESET_JSON_YES); +} + +guint32 +_nm_team_setting_value_set(NMTeamSetting * self, + NMTeamAttribute team_attr, + NMValueType value_type, + gconstpointer val) +{ + const TeamAttrData *attr_data; + + nm_assert(self); + + attr_data = _team_attr_data_get(self->d.is_port, team_attr); + + nm_assert(value_type == attr_data->value_type); + + return _team_setting_value_set(self, + attr_data, + val, + SET_FIELD_MODE_SET_UNLESS_DEFAULT, + RESET_JSON_YES); +} + +guint32 +nm_team_setting_value_link_watchers_add(NMTeamSetting *self, const NMTeamLinkWatcher *link_watcher) +{ + guint i; + gboolean changed; + + for (i = 0; i < self->d.link_watchers->len; i++) { + if (nm_team_link_watcher_equal(self->d.link_watchers->pdata[i], link_watcher)) { + changed = FALSE; + goto out; + } + } + changed = TRUE; + g_ptr_array_add((GPtrArray *) self->d.link_watchers, + _nm_team_link_watcher_ref((NMTeamLinkWatcher *) link_watcher)); +out: + return _team_setting_attribute_changed_attr(self, + NM_TEAM_ATTRIBUTE_LINK_WATCHERS, + changed, + SET_FIELD_MODE_SET_UNLESS_DEFAULT, + RESET_JSON_YES); +} + +guint32 +nm_team_setting_value_link_watchers_remove_by_value(NMTeamSetting * self, + const NMTeamLinkWatcher *link_watcher) +{ + guint i; + + for (i = 0; i < self->d.link_watchers->len; i++) { + if (nm_team_link_watcher_equal(self->d.link_watchers->pdata[i], link_watcher)) + return nm_team_setting_value_link_watchers_remove(self, i); + } + return _team_setting_attribute_changed_attr(self, + NM_TEAM_ATTRIBUTE_LINK_WATCHERS, + FALSE, + SET_FIELD_MODE_SET_UNLESS_DEFAULT, + RESET_JSON_YES); +} + +guint32 +nm_team_setting_value_link_watchers_remove(NMTeamSetting *self, guint idx) +{ + g_ptr_array_remove_index((GPtrArray *) self->d.link_watchers, idx); + return _team_setting_attribute_changed_attr(self, + NM_TEAM_ATTRIBUTE_LINK_WATCHERS, + TRUE, + SET_FIELD_MODE_SET_UNLESS_DEFAULT, + RESET_JSON_YES); +} + +static guint32 +_team_setting_value_link_watchers_set_list(NMTeamSetting * self, + const NMTeamLinkWatcher *const *arr, + guint len, + SetFieldModeEnum set_field_mode, + ResetJsonEnum reset_json) +{ + gboolean changed; + + if (self->d.link_watchers->len == len + && nm_team_link_watchers_cmp( + (const NMTeamLinkWatcher *const *) self->d.link_watchers->pdata, + arr, + len, + FALSE) + == 0) { + changed = FALSE; + goto out; + } + + changed = TRUE; + if (len == 0) + g_ptr_array_set_size((GPtrArray *) self->d.link_watchers, 0); + else { + _nm_unused gs_unref_ptrarray GPtrArray *old_val_destroy = NULL; + guint i; + + old_val_destroy = (GPtrArray *) g_steal_pointer(&self->_data_priv.link_watchers); + + self->_data_priv.link_watchers = + g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); + + for (i = 0; i < len; i++) { + if (arr[i]) { + g_ptr_array_add((GPtrArray *) self->d.link_watchers, + _nm_team_link_watcher_ref((NMTeamLinkWatcher *) arr[i])); + } + } + } + +out: + return _team_setting_attribute_changed_attr(self, + NM_TEAM_ATTRIBUTE_LINK_WATCHERS, + changed, + set_field_mode, + reset_json); +} + +guint32 +nm_team_setting_value_link_watchers_set_list(NMTeamSetting * self, + const NMTeamLinkWatcher *const *arr, + guint len) +{ + return _team_setting_value_link_watchers_set_list(self, + arr, + len, + SET_FIELD_MODE_SET_UNLESS_DEFAULT, + RESET_JSON_YES); +} + +/*****************************************************************************/ + +guint32 +nm_team_setting_value_master_runner_tx_hash_add(NMTeamSetting *self, const char *txhash) +{ + gboolean changed; + guint i; + + if (!self->d.master.runner_tx_hash) + self->_data_priv.master.runner_tx_hash = g_ptr_array_new_with_free_func(g_free); + else { + for (i = 0; i < self->d.master.runner_tx_hash->len; i++) { + if (nm_streq(txhash, self->d.master.runner_tx_hash->pdata[i])) { + changed = FALSE; + goto out; + } + } + } + changed = TRUE; + g_ptr_array_add((GPtrArray *) self->d.master.runner_tx_hash, g_strdup(txhash)); +out: + return _team_setting_attribute_changed_attr(self, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, + changed, + SET_FIELD_MODE_SET_UNLESS_DEFAULT, + RESET_JSON_YES); +} + +guint32 +nm_team_setting_value_master_runner_tx_hash_remove(NMTeamSetting *self, guint idx) +{ + g_ptr_array_remove_index((GPtrArray *) self->d.master.runner_tx_hash, idx); + return _team_setting_attribute_changed_attr(self, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, + TRUE, + SET_FIELD_MODE_SET_UNLESS_DEFAULT, + RESET_JSON_YES); +} + +static guint32 +_team_setting_value_master_runner_tx_hash_set_list(NMTeamSetting * self, + const char *const *arr, + guint len, + SetFieldModeEnum set_field_mode, + ResetJsonEnum reset_json) +{ + _nm_unused gs_unref_ptrarray GPtrArray *old_val_destroy = NULL; + gboolean changed; + guint i; + + if (nm_utils_strv_cmp_n(self->d.master.runner_tx_hash + ? (const char *const *) self->d.master.runner_tx_hash->pdata + : NULL, + self->d.master.runner_tx_hash ? self->d.master.runner_tx_hash->len : 0u, + arr, + len) + == 0) { + changed = FALSE; + goto out; + } + + changed = TRUE; + + old_val_destroy = (GPtrArray *) g_steal_pointer(&self->_data_priv.master.runner_tx_hash); + + for (i = 0; i < len; i++) { + if (!arr[i]) + continue; + if (!self->d.master.runner_tx_hash) + self->_data_priv.master.runner_tx_hash = g_ptr_array_new_with_free_func(g_free); + g_ptr_array_add((GPtrArray *) self->d.master.runner_tx_hash, g_strdup(arr[i])); + } + +out: + return _team_setting_attribute_changed_attr(self, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, + changed, + set_field_mode, + reset_json); +} + +guint32 +nm_team_setting_value_master_runner_tx_hash_set_list(NMTeamSetting * self, + const char *const *arr, + guint len) +{ + return _team_setting_value_master_runner_tx_hash_set_list(self, + arr, + len, + SET_FIELD_MODE_SET_UNLESS_DEFAULT, + RESET_JSON_YES); +} + +/*****************************************************************************/ + +#define _LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, _value_type) \ + ({ \ + const NMValueTypUnioMaybe *const _args = (args); \ + \ + nm_assert(link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \ + \ + _args[(link_watcher_attribute)].has \ + ? &_args[(link_watcher_attribute)].val \ + : &link_watcher_attr_datas[(link_watcher_attribute)].default_val; \ + }) +#define _LINK_WATCHER_ATTR_GET_BOOL(args, link_watcher_attribute) \ + (_LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, NM_VALUE_TYPE_BOOL)->v_bool) +#define _LINK_WATCHER_ATTR_GET_INT(args, link_watcher_attribute) \ + (_LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, NM_VALUE_TYPE_INT)->v_int) +#define _LINK_WATCHER_ATTR_GET_STRING(args, link_watcher_attribute) \ + (_LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, NM_VALUE_TYPE_STRING)->v_string) + +#define _LINK_WATCHER_ATTR_SET(args, link_watcher_attribute, _value_type, c_type, val) \ + ({ \ + nm_assert(link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \ + \ + NM_VALUE_TYP_UNIO_MAYBE_SET(&(args)[(link_watcher_attribute)], c_type, (val)); \ + }) +#define _LINK_WATCHER_ATTR_SET_BOOL(args, link_watcher_attribute, val) \ + _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_BOOL, v_bool, (val)) +#define _LINK_WATCHER_ATTR_SET_INT(args, link_watcher_attribute, val) \ + _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_INT, v_int, (val)) +#define _LINK_WATCHER_ATTR_SET_STRING(args, link_watcher_attribute, val) \ + _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_STRING, v_string, (val)) + +static void +_link_watcher_unpack(const NMTeamLinkWatcher *link_watcher, + NMValueTypUnioMaybe args[static G_N_ELEMENTS(link_watcher_attr_datas)]) +{ + const char * v_name = nm_team_link_watcher_get_name(link_watcher); + NMTeamLinkWatcherArpPingFlags v_arp_ping_flags; + + memset(args, 0, sizeof(args[0]) * G_N_ELEMENTS(link_watcher_attr_datas)); + + _LINK_WATCHER_ATTR_SET_STRING(args, LINK_WATCHER_ATTRIBUTE_NAME, v_name); + + if (nm_streq(v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) { + _LINK_WATCHER_ATTR_SET_INT(args, + LINK_WATCHER_ATTRIBUTE_DELAY_UP, + nm_team_link_watcher_get_delay_up(link_watcher)); + _LINK_WATCHER_ATTR_SET_INT(args, + LINK_WATCHER_ATTRIBUTE_DELAY_DOWN, + nm_team_link_watcher_get_delay_down(link_watcher)); + } else if (NM_IN_STRSET(v_name, + NM_TEAM_LINK_WATCHER_NSNA_PING, + NM_TEAM_LINK_WATCHER_ARP_PING)) { + _LINK_WATCHER_ATTR_SET_INT(args, + LINK_WATCHER_ATTRIBUTE_INIT_WAIT, + nm_team_link_watcher_get_init_wait(link_watcher)); + _LINK_WATCHER_ATTR_SET_INT(args, + LINK_WATCHER_ATTRIBUTE_INTERVAL, + nm_team_link_watcher_get_interval(link_watcher)); + _LINK_WATCHER_ATTR_SET_INT(args, + LINK_WATCHER_ATTRIBUTE_MISSED_MAX, + nm_team_link_watcher_get_missed_max(link_watcher)); + _LINK_WATCHER_ATTR_SET_STRING(args, + LINK_WATCHER_ATTRIBUTE_TARGET_HOST, + nm_team_link_watcher_get_target_host(link_watcher)); + if (nm_streq(v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) { + v_arp_ping_flags = nm_team_link_watcher_get_flags(link_watcher); + _LINK_WATCHER_ATTR_SET_INT(args, + LINK_WATCHER_ATTRIBUTE_VLANID, + nm_team_link_watcher_get_vlanid(link_watcher)); + _LINK_WATCHER_ATTR_SET_STRING(args, + LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, + nm_team_link_watcher_get_source_host(link_watcher)); + _LINK_WATCHER_ATTR_SET_BOOL( + args, + LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, + NM_FLAGS_HAS(v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE)); + _LINK_WATCHER_ATTR_SET_BOOL( + args, + LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, + NM_FLAGS_HAS(v_arp_ping_flags, + NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE)); + _LINK_WATCHER_ATTR_SET_BOOL( + args, + LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS, + NM_FLAGS_HAS(v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS)); + } + } +} + +static void +_link_watcher_to_json(const NMTeamLinkWatcher *link_watcher, GString *gstr) +{ + NMValueTypUnioMaybe args[G_N_ELEMENTS(link_watcher_attr_datas)]; + int i; + gboolean is_first = TRUE; + + if (!link_watcher) { + g_string_append(gstr, "null"); + return; + } + + _link_watcher_unpack(link_watcher, args); + + g_string_append(gstr, "{ "); + + for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) { + const NMValueTypUnioMaybe *p_val = &args[i]; + const LinkWatcherAttrData *attr_data = &link_watcher_attr_datas[i]; + + if (!p_val->has) + continue; + if (nm_value_type_equal(attr_data->value_type, &attr_data->default_val, &p_val->val)) + continue; + + if (is_first) + is_first = FALSE; + else + nm_json_gstr_append_delimiter(gstr); + nm_json_gstr_append_obj_name(gstr, attr_data->js_key, '\0'); + nm_value_type_to_json(attr_data->value_type, gstr, &p_val->val); + } + + g_string_append(gstr, " }"); +} + +static NMTeamLinkWatcher * +_link_watcher_from_json(const NMJsonVt * vt, + const nm_json_t *root_js_obj, + gboolean * out_unrecognized_content) +{ + NMValueTypUnioMaybe args[G_N_ELEMENTS(link_watcher_attr_datas)] = {}; + const char * j_key; + nm_json_t * j_val; + const char * v_name; + NMTeamLinkWatcher * result = NULL; + + if (!nm_json_is_object(root_js_obj)) + goto fail; + + nm_json_object_foreach (vt, (nm_json_t *) root_js_obj, j_key, j_val) { + const LinkWatcherAttrData *attr_data = NULL; + NMValueTypUnioMaybe * parse_result; + + if (j_key) { + int i; + + for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) { + if (nm_streq(link_watcher_attr_datas[i].js_key, j_key)) { + attr_data = &link_watcher_attr_datas[i]; + break; + } + } + } + if (!attr_data) { + *out_unrecognized_content = TRUE; + continue; + } + + parse_result = &args[attr_data->link_watcher_attr]; + + if (parse_result->has) + *out_unrecognized_content = TRUE; + + if (!nm_value_type_from_json(vt, attr_data->value_type, j_val, &parse_result->val)) + *out_unrecognized_content = TRUE; + else + parse_result->has = TRUE; + } + +#define _PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(_parse_results, ...) \ + ({ \ + int _i; \ + \ + for (_i = 0; _i < (int) G_N_ELEMENTS((_parse_results)); _i++) { \ + if ((_parse_results)[_i].has && !NM_IN_SET((LinkWatcherAttribute) _i, __VA_ARGS__)) \ + break; \ + } \ + \ + (_i == (int) G_N_ELEMENTS((_parse_results))); \ + }) + + v_name = _LINK_WATCHER_ATTR_GET_STRING(args, LINK_WATCHER_ATTRIBUTE_NAME); + + if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) { + if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(args, + _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL)) + *out_unrecognized_content = TRUE; + result = nm_team_link_watcher_new_ethtool( + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_DELAY_UP), + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN), + NULL); + } else if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_NSNA_PING)) { + if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(args, + _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING)) + *out_unrecognized_content = TRUE; + result = nm_team_link_watcher_new_nsna_ping( + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT), + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_INTERVAL), + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX), + _LINK_WATCHER_ATTR_GET_STRING(args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST), + NULL); + } else if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) { + NMTeamLinkWatcherArpPingFlags v_flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE; + + if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(args, + _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING)) + *out_unrecognized_content = TRUE; + + if (_LINK_WATCHER_ATTR_GET_BOOL(args, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE)) + v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE; + if (_LINK_WATCHER_ATTR_GET_BOOL(args, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE)) + v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE; + if (_LINK_WATCHER_ATTR_GET_BOOL(args, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS)) + v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS; + + result = nm_team_link_watcher_new_arp_ping2( + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT), + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_INTERVAL), + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX), + _LINK_WATCHER_ATTR_GET_INT(args, LINK_WATCHER_ATTRIBUTE_VLANID), + _LINK_WATCHER_ATTR_GET_STRING(args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST), + _LINK_WATCHER_ATTR_GET_STRING(args, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST), + v_flags, + NULL); + } + + if (result) + return result; +fail: + *out_unrecognized_content = TRUE; + return NULL; +} + +/*****************************************************************************/ + +static GVariant * +_link_watcher_to_variant(const NMTeamLinkWatcher *link_watcher) +{ + NMValueTypUnioMaybe args[G_N_ELEMENTS(link_watcher_attr_datas)]; + GVariantBuilder builder; + int i; + + if (!link_watcher) + return NULL; + + _link_watcher_unpack(link_watcher, args); + + if (!args[LINK_WATCHER_ATTRIBUTE_NAME].has) + return NULL; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + + for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) { + const NMValueTypUnioMaybe *p_val = &args[i]; + const LinkWatcherAttrData *attr_data = &link_watcher_attr_datas[i]; + GVariant * v; + + if (!p_val->has) + continue; + if (nm_value_type_equal(attr_data->value_type, &attr_data->default_val, &p_val->val)) + continue; + + if (attr_data->value_type == NM_VALUE_TYPE_INT) + v = g_variant_new_int32(p_val->val.v_int); + else { + v = nm_value_type_to_variant(attr_data->value_type, &p_val->val); + } + if (!v) + continue; + + nm_assert(g_variant_is_floating(v)); + g_variant_builder_add(&builder, "{sv}", attr_data->dbus_name, v); + } + + return g_variant_builder_end(&builder); +} + +#define _LINK_WATCHER_ATTR_VARGET(variants, link_watcher_attribute, _value_type, c_type, _cmd) \ + ({ \ + GVariant *const *_variants = (variants); \ + GVariant * _cc; \ + \ + nm_assert(link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \ + \ + (_cc = _variants[(link_watcher_attribute)]) \ + ? (_cmd) \ + : link_watcher_attr_datas[(link_watcher_attribute)].default_val.c_type; \ + }) +#define _LINK_WATCHER_ATTR_VARGET_BOOL(variants, link_watcher_attribute) \ + (_LINK_WATCHER_ATTR_VARGET(variants, \ + link_watcher_attribute, \ + NM_VALUE_TYPE_BOOL, \ + v_bool, \ + g_variant_get_boolean(_cc))) +#define _LINK_WATCHER_ATTR_VARGET_INT(variants, link_watcher_attribute) \ + (_LINK_WATCHER_ATTR_VARGET(variants, \ + link_watcher_attribute, \ + NM_VALUE_TYPE_INT, \ + v_int, \ + g_variant_get_int32(_cc))) +#define _LINK_WATCHER_ATTR_VARGET_STRING(variants, link_watcher_attribute) \ + (_LINK_WATCHER_ATTR_VARGET(variants, \ + link_watcher_attribute, \ + NM_VALUE_TYPE_STRING, \ + v_string, \ + g_variant_get_string(_cc, NULL))) + +static void +_variants_list_link_watcher_unref_auto(GVariant *(*p_variants)[]) +{ + int i; + + for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) + nm_g_variant_unref((*p_variants)[i]); +} + +static NMTeamLinkWatcher * +_link_watcher_from_variant(GVariant *watcher_var, gboolean strict_parsing, GError **error) +{ + nm_auto(_variants_list_link_watcher_unref_auto) + GVariant *variants[G_N_ELEMENTS(link_watcher_attr_datas)] = { + NULL, + }; + const char * v_key; + GVariant * v_val; + const char * v_name; + GVariantIter iter; + + g_return_val_if_fail(g_variant_is_of_type(watcher_var, G_VARIANT_TYPE("a{sv}")), NULL); + + g_variant_iter_init(&iter, watcher_var); + while (g_variant_iter_next(&iter, "{&sv}", &v_key, &v_val)) { + _nm_unused gs_unref_variant GVariant *v_val_free = v_val; + const LinkWatcherAttrData * attr_data = NULL; + const GVariantType * variant_type; + int i; + + for (i = 0; i < (int) G_N_ELEMENTS(link_watcher_attr_datas); i++) { + if (nm_streq(link_watcher_attr_datas[i].dbus_name, v_key)) { + attr_data = &link_watcher_attr_datas[i]; + break; + } + } + if (!attr_data) { + if (strict_parsing) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid D-Bus property \"%s\""), + v_key); + return NULL; + } + continue; + } + + if (attr_data->value_type == NM_VALUE_TYPE_INT) + variant_type = G_VARIANT_TYPE_INT32; + else + variant_type = nm_value_type_get_variant_type(attr_data->value_type); + + if (!g_variant_is_of_type(v_val, variant_type)) { + if (strict_parsing) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid D-Bus property \"%s\""), + v_key); + return NULL; + } + continue; + } + + if (variants[attr_data->link_watcher_attr]) { + if (strict_parsing) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("duplicate D-Bus property \"%s\""), + v_key); + return NULL; + } + g_variant_unref(variants[attr_data->link_watcher_attr]); + } + variants[attr_data->link_watcher_attr] = g_steal_pointer(&v_val_free); + } + +#define _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(_type, _variants, _error, ...) \ + ({ \ + int _i; \ + gboolean _has_error = FALSE; \ + \ + for (_i = 0; _i < (int) G_N_ELEMENTS((_variants)); _i++) { \ + if ((_variants)[_i] && !NM_IN_SET((LinkWatcherAttribute) _i, __VA_ARGS__)) { \ + _has_error = TRUE; \ + g_set_error(_error, \ + NM_CONNECTION_ERROR, \ + NM_CONNECTION_ERROR_INVALID_PROPERTY, \ + _("invalid D-Bus property \"%s\" for \"%s\""), \ + link_watcher_attr_datas[_i].dbus_name, \ + _type); \ + break; \ + } \ + } \ + \ + _has_error; \ + }) + + v_name = _LINK_WATCHER_ATTR_VARGET_STRING(variants, LINK_WATCHER_ATTRIBUTE_NAME); + + if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) { + if (strict_parsing + && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(v_name, + variants, + error, + _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL)) + return NULL; + return nm_team_link_watcher_new_ethtool( + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_DELAY_UP), + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN), + strict_parsing ? error : NULL); + } + + if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_NSNA_PING)) { + if (strict_parsing + && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(v_name, + variants, + error, + _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING)) + return NULL; + return nm_team_link_watcher_new_nsna_ping( + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_INIT_WAIT), + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_INTERVAL), + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_MISSED_MAX), + _LINK_WATCHER_ATTR_VARGET_STRING(variants, LINK_WATCHER_ATTRIBUTE_TARGET_HOST), + strict_parsing ? error : NULL); + } + + if (nm_streq0(v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) { + NMTeamLinkWatcherArpPingFlags v_flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE; + + if (strict_parsing + && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(v_name, + variants, + error, + _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING)) + return NULL; + + if (_LINK_WATCHER_ATTR_VARGET_BOOL(variants, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE)) + v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE; + if (_LINK_WATCHER_ATTR_VARGET_BOOL(variants, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE)) + v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE; + if (_LINK_WATCHER_ATTR_VARGET_BOOL(variants, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS)) + v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS; + + return nm_team_link_watcher_new_arp_ping2( + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_INIT_WAIT), + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_INTERVAL), + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_MISSED_MAX), + _LINK_WATCHER_ATTR_VARGET_INT(variants, LINK_WATCHER_ATTRIBUTE_VLANID), + _LINK_WATCHER_ATTR_VARGET_STRING(variants, LINK_WATCHER_ATTRIBUTE_TARGET_HOST), + _LINK_WATCHER_ATTR_VARGET_STRING(variants, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST), + v_flags, + strict_parsing ? error : NULL); + } + + if (strict_parsing) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unknown link-watcher name \"%s\""), + v_name); + } + return NULL; +} + +/*****************************************************************************/ + +/** + * _nm_utils_team_link_watchers_to_variant: + * @link_watchers: (element-type NMTeamLinkWatcher): array of #NMTeamLinkWatcher + * + * Utility function to convert a #GPtrArray of #NMTeamLinkWatcher objects + * representing link watcher configuration for team devices into a #GVariant + * of type 'aa{sv}' representing an array of link watchers. + * + * Returns: (transfer full): a new floating #GVariant representing link watchers. + **/ +GVariant * +_nm_utils_team_link_watchers_to_variant(const GPtrArray *link_watchers) +{ + GVariantBuilder builder; + guint i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + if (link_watchers) { + for (i = 0; i < link_watchers->len; i++) { + g_variant_builder_add(&builder, + "@a{sv}", + _link_watcher_to_variant(link_watchers->pdata[i])); + } + } + return g_variant_builder_end(&builder); +} + +/** + * _nm_utils_team_link_watchers_from_variant: + * @value: a #GVariant of type 'aa{sv}' + * @strict_parsing: whether to parse strictly or ignore everything invalid. + * @error: error reason. + * + * Utility function to convert a #GVariant representing a list of team link + * watchers int a #GPtrArray of #NMTeamLinkWatcher objects. + * + * Returns: (transfer full) (element-type NMTeamLinkWatcher): a newly allocated + * #GPtrArray of #NMTeamLinkWatcher objects. + * + * Note that if you provide an @error, then the function can only fail (and return %NULL) + * or succeed (and not return %NULL). If you don't provide an @error, then the function + * never returns %NULL. + **/ +GPtrArray * +_nm_utils_team_link_watchers_from_variant(GVariant *value, gboolean strict_parsing, GError **error) +{ + gs_unref_ptrarray GPtrArray *link_watchers = NULL; + GVariantIter iter; + GVariant * watcher_var; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL); + + link_watchers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); + + g_variant_iter_init(&iter, value); + while (g_variant_iter_next(&iter, "@a{sv}", &watcher_var)) { + _nm_unused gs_unref_variant GVariant *watcher_var_free = watcher_var; + NMTeamLinkWatcher * watcher; + + watcher = _link_watcher_from_variant(watcher_var, strict_parsing, error); + if (error && *error) + return NULL; + if (watcher) + g_ptr_array_add(link_watchers, watcher); + } + + return g_steal_pointer(&link_watchers); +} + +/*****************************************************************************/ + +const char * +nm_team_setting_config_get(const NMTeamSetting *self) +{ + char *js_str; + + nm_assert(self); + + if (G_LIKELY(!self->d._js_str_need_synthetize)) + return self->d._js_str; + + nm_assert(!self->d._js_str); + nm_assert(self->d.strict_validated); + + if (_team_setting_check_default(self) == 0) { + /* the default is set. We signal this as a NULL JSON string. + * Nothing to do. */ + js_str = NULL; + } else { + gboolean list_is_empty = TRUE; + GString *gstr; + + gstr = g_string_new(NULL); + + g_string_append(gstr, "{ "); + + if (self->d.is_port) { + static const NMTeamAttribute attr_lst_port[] = { + NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID, + NM_TEAM_ATTRIBUTE_PORT_PRIO, + NM_TEAM_ATTRIBUTE_PORT_STICKY, + NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO, + NM_TEAM_ATTRIBUTE_PORT_LACP_KEY, + }; + + if (_team_setting_fields_to_json_maybe(self, + gstr, + !list_is_empty, + attr_lst_port, + G_N_ELEMENTS(attr_lst_port))) + list_is_empty = FALSE; + } else { + static const NMTeamAttribute attr_lst_runner_pt1[] = { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, + }; + static const NMTeamAttribute attr_lst_runner_pt2[] = { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, + }; + static const NMTeamAttribute attr_lst_runner_pt3[] = { + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY, + }; + static const NMTeamAttribute attr_lst_notify_peers[] = { + NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT, + NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL, + }; + static const NMTeamAttribute attr_lst_mcast_rejoin[] = { + NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT, + NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL, + }; + + if (_team_setting_has_fields_any_v(self, + attr_lst_runner_pt1, + G_N_ELEMENTS(attr_lst_runner_pt1)) + || _team_setting_has_fields_any_v(self, + attr_lst_runner_pt2, + G_N_ELEMENTS(attr_lst_runner_pt2)) + || _team_setting_has_fields_any_v(self, + attr_lst_runner_pt3, + G_N_ELEMENTS(attr_lst_runner_pt3))) { + gboolean list_is_empty2 = TRUE; + + nm_assert(list_is_empty); + + nm_json_gstr_append_obj_name(gstr, "runner", '{'); + + if (_team_setting_fields_to_json_maybe(self, + gstr, + !list_is_empty2, + attr_lst_runner_pt1, + G_N_ELEMENTS(attr_lst_runner_pt1))) + list_is_empty2 = FALSE; + + if (_team_setting_has_fields_any_v(self, + attr_lst_runner_pt2, + G_N_ELEMENTS(attr_lst_runner_pt2))) { + if (!list_is_empty2) + nm_json_gstr_append_delimiter(gstr); + nm_json_gstr_append_obj_name(gstr, "tx_balancer", '{'); + if (!_team_setting_fields_to_json_maybe(self, + gstr, + FALSE, + attr_lst_runner_pt2, + G_N_ELEMENTS(attr_lst_runner_pt2))) + nm_assert_not_reached(); + g_string_append(gstr, " }"); + list_is_empty2 = FALSE; + } + + if (_team_setting_fields_to_json_maybe(self, + gstr, + !list_is_empty2, + attr_lst_runner_pt3, + G_N_ELEMENTS(attr_lst_runner_pt3))) + list_is_empty2 = FALSE; + + nm_assert(!list_is_empty2); + g_string_append(gstr, " }"); + list_is_empty = FALSE; + } + + if (_team_setting_has_fields_any_v(self, + attr_lst_notify_peers, + G_N_ELEMENTS(attr_lst_notify_peers))) { + if (!list_is_empty) + nm_json_gstr_append_delimiter(gstr); + nm_json_gstr_append_obj_name(gstr, "notify_peers", '{'); + if (!_team_setting_fields_to_json_maybe(self, + gstr, + FALSE, + attr_lst_notify_peers, + G_N_ELEMENTS(attr_lst_notify_peers))) + nm_assert_not_reached(); + g_string_append(gstr, " }"); + list_is_empty = FALSE; + } + + if (_team_setting_has_fields_any_v(self, + attr_lst_mcast_rejoin, + G_N_ELEMENTS(attr_lst_mcast_rejoin))) { + if (!list_is_empty) + nm_json_gstr_append_delimiter(gstr); + nm_json_gstr_append_obj_name(gstr, "mcast_rejoin", '{'); + if (!_team_setting_fields_to_json_maybe(self, + gstr, + FALSE, + attr_lst_mcast_rejoin, + G_N_ELEMENTS(attr_lst_mcast_rejoin))) + nm_assert_not_reached(); + g_string_append(gstr, " }"); + list_is_empty = FALSE; + } + } + + if (_team_setting_field_to_json( + self, + gstr, + !list_is_empty, + _team_attr_data_get(self->d.is_port, NM_TEAM_ATTRIBUTE_LINK_WATCHERS))) + list_is_empty = FALSE; + + if (!list_is_empty) + g_string_append(gstr, " }"); + + js_str = g_string_free(gstr, list_is_empty); + } + + /* mutate the constant object. In C++ speak, these fields are "mutable". + * That is because we construct the JSON string lazily/on-demand. */ + *((char **) &self->_data_priv._js_str) = js_str; + *((bool *) &self->_data_priv._js_str_need_synthetize) = FALSE; + + return self->d._js_str; +} + +/*****************************************************************************/ + +static gboolean +_attr_data_match_keys(const TeamAttrData *attr_data, const char *const *keys, guint8 n_keys) +{ + guint8 i; + + _team_attr_data_ASSERT(attr_data); + nm_assert(keys); + nm_assert(n_keys > 0); + nm_assert(({ + gboolean all_non_null = TRUE; + + for (i = 0; i < n_keys; i++) + all_non_null = all_non_null && keys[i] && keys[i][0] != '\0'; + all_non_null; + })); + + if (attr_data->js_keys_len < n_keys) + return FALSE; + for (i = 0; i < n_keys; i++) { + if (!nm_streq(keys[i], attr_data->js_keys[i])) + return FALSE; + } + return TRUE; +} + +static const TeamAttrData * +_attr_data_find_by_json_key(gboolean is_port, const char *const *keys, guint8 n_keys) +{ + const TeamAttrData *attr_data; + + for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; + attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) { + if (_team_attr_data_is_relevant(attr_data, is_port) + && _attr_data_match_keys(attr_data, keys, n_keys)) + return attr_data; + } + + return NULL; +} + +static void +_js_parse_locate_keys(const NMJsonVt *vt, + NMTeamSetting * self, + nm_json_t * root_js_obj, + nm_json_t * found_keys[static _NM_TEAM_ATTRIBUTE_NUM], + gboolean * out_unrecognized_content) +{ + const char *keys[3]; + const char *cur_key1; + const char *cur_key2; + const char *cur_key3; + nm_json_t * cur_val1; + nm_json_t * cur_val2; + nm_json_t * cur_val3; + + nm_assert(vt); + +#define _handle(_self, _cur_key, _cur_val, _keys, _level, _found_keys, _out_unrecognized_content) \ + ({ \ + const TeamAttrData *_attr_data; \ + gboolean _handled = FALSE; \ + \ + (_keys)[(_level) -1] = (_cur_key); \ + _attr_data = _attr_data_find_by_json_key((_self)->d.is_port, (_keys), (_level)); \ + if (_attr_data && _attr_data->js_keys_len == (_level)) { \ + if ((_found_keys)[_attr_data->team_attr]) \ + *(_out_unrecognized_content) = TRUE; \ + (_found_keys)[_attr_data->team_attr] = (_cur_val); \ + _handled = TRUE; \ + } else if (!_attr_data || !nm_json_is_object((_cur_val))) { \ + *(_out_unrecognized_content) = TRUE; \ + _handled = TRUE; \ + } \ + _handled; \ + }) + + nm_json_object_foreach (vt, root_js_obj, cur_key1, cur_val1) { + if (!_handle(self, cur_key1, cur_val1, keys, 1, found_keys, out_unrecognized_content)) { + nm_json_object_foreach (vt, cur_val1, cur_key2, cur_val2) { + if (!_handle(self, + cur_key2, + cur_val2, + keys, + 2, + found_keys, + out_unrecognized_content)) { + nm_json_object_foreach (vt, cur_val2, cur_key3, cur_val3) { + if (!_handle(self, + cur_key3, + cur_val3, + keys, + 3, + found_keys, + out_unrecognized_content)) + *out_unrecognized_content = TRUE; + } + } + } + } + } + +#undef _handle +} + +static void +_js_parse_unpack(const NMJsonVt *vt, + gboolean is_port, + nm_json_t * found_keys[static _NM_TEAM_ATTRIBUTE_NUM], + bool out_has_lst[static _NM_TEAM_ATTRIBUTE_NUM], + NMValueTypUnion out_val_lst[static _NM_TEAM_ATTRIBUTE_NUM], + gboolean * out_unrecognized_content, + GPtrArray ** out_ptr_array_link_watchers_free, + GPtrArray ** out_ptr_array_master_runner_tx_hash_free) +{ + const TeamAttrData *attr_data; + + nm_assert(vt); + + for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; + attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) { + NMValueTypUnion *p_out_val; + gboolean valid = FALSE; + nm_json_t * arg_js_obj; + + if (!_team_attr_data_is_relevant(attr_data, is_port)) + continue; + + nm_assert(!out_has_lst[attr_data->team_attr]); + + arg_js_obj = found_keys[attr_data->team_attr]; + if (!arg_js_obj) + continue; + + p_out_val = &out_val_lst[attr_data->team_attr]; + + if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) + valid = nm_value_type_from_json(vt, attr_data->value_type, arg_js_obj, p_out_val); + else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { + GPtrArray * link_watchers = NULL; + NMTeamLinkWatcher *link_watcher; + + nm_assert(out_ptr_array_link_watchers_free && !*out_ptr_array_link_watchers_free); + if (nm_json_is_array(arg_js_obj)) { + gsize i, len; + + len = vt->nm_json_array_size(arg_js_obj); + link_watchers = + g_ptr_array_new_full(len, (GDestroyNotify) nm_team_link_watcher_unref); + for (i = 0; i < len; i++) { + link_watcher = _link_watcher_from_json(vt, + vt->nm_json_array_get(arg_js_obj, i), + out_unrecognized_content); + if (link_watcher) + g_ptr_array_add(link_watchers, link_watcher); + } + } else { + link_watcher = _link_watcher_from_json(vt, arg_js_obj, out_unrecognized_content); + if (link_watcher) { + link_watchers = + g_ptr_array_new_full(1, (GDestroyNotify) nm_team_link_watcher_unref); + g_ptr_array_add(link_watchers, link_watcher); + } + } + if (link_watchers) { + valid = TRUE; + p_out_val->v_ptrarray = link_watchers; + *out_ptr_array_link_watchers_free = link_watchers; + } + } else if (!is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { + GPtrArray *strv = NULL; + + nm_assert(out_ptr_array_master_runner_tx_hash_free + && !*out_ptr_array_master_runner_tx_hash_free); + if (nm_json_is_array(arg_js_obj)) { + gsize i, len; + + len = vt->nm_json_array_size(arg_js_obj); + if (len > 0) { + strv = g_ptr_array_sized_new(len); + for (i = 0; i < len; i++) { + const char *v_string; + + if (nm_jansson_json_as_string(vt, + vt->nm_json_array_get(arg_js_obj, i), + &v_string) + <= 0 + || !v_string || v_string[0] == '\0') { + /* we remember that there was some invalid content, but parts of the + * list could still be parsed. */ + *out_unrecognized_content = TRUE; + continue; + } + g_ptr_array_add(strv, (char *) v_string); + } + } + valid = TRUE; + *out_ptr_array_master_runner_tx_hash_free = strv; + } + p_out_val->v_ptrarray = strv; + } else + nm_assert_not_reached(); + + out_has_lst[attr_data->team_attr] = valid; + if (!valid) + *out_unrecognized_content = TRUE; + } +} + +guint32 +nm_team_setting_config_set(NMTeamSetting *self, const char *js_str) +{ + const NMJsonVt *vt; + guint32 changed_flags = 0; + gboolean do_set_default = TRUE; + gboolean new_js_str_invalid = FALSE; + + _team_setting_ASSERT(self); + + if (!js_str || js_str[0] == '\0') { + changed_flags = _team_setting_set_default(self); + if (changed_flags != 0 || !nm_streq0(js_str, self->d._js_str)) + changed_flags |= nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); + nm_clear_g_free((char **) &self->_data_priv._js_str); + self->_data_priv._js_str = g_strdup(js_str); + self->_data_priv._js_str_need_synthetize = FALSE; + self->_data_priv.strict_validated = TRUE; + self->_data_priv.js_str_invalid = FALSE; + return changed_flags; + } + + if (self->d._js_str && nm_streq(js_str, self->d._js_str)) { + if (!self->d.strict_validated) { + /* setting the same JSON string twice in a row has no effect. */ + return 0; + } + } else + changed_flags |= nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); + + if ((vt = nm_json_vt())) { + nm_auto_decref_json nm_json_t *root_js_obj = NULL; + + root_js_obj = vt->nm_json_loads(js_str, 0, NULL); + if (!root_js_obj || !nm_json_is_object(root_js_obj)) + new_js_str_invalid = TRUE; + else { + gboolean unrecognized_content = FALSE; + bool has_lst[_NM_TEAM_ATTRIBUTE_NUM] = { + FALSE, + }; + NMValueTypUnion val_lst[_NM_TEAM_ATTRIBUTE_NUM]; + nm_json_t * found_keys[_NM_TEAM_ATTRIBUTE_NUM] = { + NULL, + }; + gs_unref_ptrarray GPtrArray *ptr_array_master_runner_tx_hash_free = NULL; + gs_unref_ptrarray GPtrArray *ptr_array_link_watchers_free = NULL; + + _js_parse_locate_keys(vt, self, root_js_obj, found_keys, &unrecognized_content); + + _js_parse_unpack(vt, + self->d.is_port, + found_keys, + has_lst, + val_lst, + &unrecognized_content, + &ptr_array_link_watchers_free, + &ptr_array_master_runner_tx_hash_free); + + do_set_default = FALSE; + + changed_flags |= _team_setting_set(self, TRUE, has_lst, val_lst); + } + } + + if (do_set_default) + changed_flags |= _team_setting_set_default(self); + + self->_data_priv.strict_validated = FALSE; + self->_data_priv._js_str_need_synthetize = FALSE; + self->_data_priv.js_str_invalid = new_js_str_invalid; + g_free((char *) self->_data_priv._js_str); + self->_data_priv._js_str = g_strdup(js_str); + + return changed_flags; +} + +/*****************************************************************************/ + +static void +_team_setting_prefix_error_plain(gboolean is_port, const char *property_name, GError **error) +{ + g_prefix_error(error, + "%s.%s: ", + is_port ? NM_SETTING_TEAM_PORT_SETTING_NAME : NM_SETTING_TEAM_SETTING_NAME, + property_name); +} + +static void +_team_setting_prefix_error(const NMTeamSetting *self, + const char * prop_name_master, + const char * prop_name_port, + GError ** error) +{ + _team_setting_ASSERT(self); + nm_assert(self->d.is_port ? (!!prop_name_port) : (!!prop_name_master)); + _team_setting_prefix_error_plain(self->d.is_port, + self->d.is_port ? prop_name_port : prop_name_master, + error); +} + +static gboolean +_team_setting_verify_properties(const NMTeamSetting *self, GError **error) +{ + const TeamAttrData *attr_data; + guint i; + + for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; + attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) { + if (!_team_attr_data_is_relevant(attr_data, self->d.is_port)) + continue; + if (!_team_setting_has_field(self, attr_data)) + continue; + + if (attr_data->has_range) { + gconstpointer p_field; + + p_field = _team_setting_get_field(self, attr_data); + if (attr_data->value_type == NM_VALUE_TYPE_INT32) { + gint32 v = *((const gint32 *) p_field); + + if (v < attr_data->range.r_int32.min || v > attr_data->range.r_int32.max) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("value out or range")); + _team_setting_prefix_error_plain(self->d.is_port, + attr_data->property_name, + error); + return FALSE; + } + } else if (attr_data->value_type == NM_VALUE_TYPE_STRING) { + const char *v = *((const char *const *) p_field); + + if (nm_utils_strv_find_first((char **) attr_data->range.r_string.valid_names, -1, v) + < 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("invalid value")); + _team_setting_prefix_error_plain(self->d.is_port, + attr_data->property_name, + error); + return FALSE; + } + } else + nm_assert_not_reached(); + } + + if (!self->d.is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { + if (self->d.master.runner_tx_hash) { + for (i = 0; i < self->d.master.runner_tx_hash->len; i++) { + const char *val = self->d.master.runner_tx_hash->pdata[i]; + + if (!val + || (nm_utils_strv_find_first((char **) _valid_names_runner_tx_hash, -1, val) + < 0)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("invalid runner-tx-hash")); + _team_setting_prefix_error_plain(self->d.is_port, + NM_SETTING_TEAM_RUNNER_TX_HASH, + error); + return FALSE; + } + } + } + } + } + + if (!self->d.is_port) { + for (i = 0; i < G_N_ELEMENTS(_runner_compat_lst); i++) { + const RunnerCompatElem *e = &_runner_compat_lst[i]; + + nm_assert(NM_PTRARRAY_LEN(e->valid_runners) > 0); + + attr_data = _team_attr_data_get(FALSE, e->team_attr); + + if (!_team_setting_has_field(self, attr_data)) + continue; + if (self->d.master.runner + && (nm_utils_strv_find_first((char **) e->valid_runners, -1, self->d.master.runner) + >= 0)) + continue; + if (e->valid_runners[1] == NULL) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("%s is only allowed for runner %s"), + attr_data->property_name, + e->valid_runners[0]); + } else { + gs_free char *s = NULL; + + s = g_strjoinv(",", (char **) e->valid_runners); + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("%s is only allowed for runners %s"), + attr_data->property_name, + s); + } + _team_setting_prefix_error_plain(self->d.is_port, NM_SETTING_TEAM_RUNNER, error); + return FALSE; + } + } else { + gboolean has_lacp_attrs; + gboolean has_activebackup_attrs; + + has_lacp_attrs = _team_setting_has_fields_any(self, + NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO, + NM_TEAM_ATTRIBUTE_PORT_LACP_KEY); + has_activebackup_attrs = _team_setting_has_fields_any(self, + NM_TEAM_ATTRIBUTE_PORT_PRIO, + NM_TEAM_ATTRIBUTE_PORT_STICKY); + if (has_lacp_attrs && has_activebackup_attrs) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("cannot set parameters for lacp and activebackup runners together")); + _team_setting_prefix_error(self, + NM_SETTING_TEAM_LINK_WATCHERS, + NM_SETTING_TEAM_PORT_LINK_WATCHERS, + error); + return FALSE; + } + } + + for (i = 0; i < self->d.link_watchers->len; i++) { + if (!self->d.link_watchers->pdata[i]) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_SETTING, + _("missing link watcher")); + _team_setting_prefix_error(self, + NM_SETTING_TEAM_LINK_WATCHERS, + NM_SETTING_TEAM_PORT_LINK_WATCHERS, + error); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +_team_setting_verify_config(const NMTeamSetting *self, GError **error) +{ + const char *js_str; + + /* we always materialize the JSON string. That is because we want to validate the + * string length of the resulting JSON. */ + js_str = nm_team_setting_config_get(self); + + if (js_str) { + if (strlen(js_str) > 1 * 1024 * 1024) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("team config exceeds size limit")); + _team_setting_prefix_error(self, + NM_SETTING_TEAM_CONFIG, + NM_SETTING_TEAM_PORT_CONFIG, + error); + return FALSE; + } + if (!g_utf8_validate(js_str, -1, NULL)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("team config is not valid UTF-8")); + _team_setting_prefix_error(self, + NM_SETTING_TEAM_CONFIG, + NM_SETTING_TEAM_PORT_CONFIG, + error); + return FALSE; + } + if (self->d.js_str_invalid) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid json")); + _team_setting_prefix_error(self, + NM_SETTING_TEAM_CONFIG, + NM_SETTING_TEAM_PORT_CONFIG, + error); + return FALSE; + } + } + + return TRUE; +} + +gboolean +nm_team_setting_verify(const NMTeamSetting *self, GError **error) +{ + if (self->d.strict_validated) { + if (!_team_setting_verify_properties(self, error)) + return FALSE; + } + return _team_setting_verify_config(self, error); +} + +/*****************************************************************************/ + +int +nm_team_setting_cmp(const NMTeamSetting *self_a, + const NMTeamSetting *self_b, + gboolean ignore_js_str) +{ + const TeamAttrData *attr_data; + + NM_CMP_SELF(self_a, self_b); + + NM_CMP_FIELD_UNSAFE(self_a, self_b, d.is_port); + + for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; + attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) { + if (!_team_attr_data_is_relevant(attr_data, self_a->d.is_port)) + continue; + + NM_CMP_RETURN(_team_attr_data_cmp(attr_data, + self_a->d.is_port, + _team_setting_get_field(self_a, attr_data), + _team_setting_get_field(self_b, attr_data))); + } + + if (!ignore_js_str) { + NM_CMP_DIRECT_STRCMP0(nm_team_setting_config_get(self_a), + nm_team_setting_config_get(self_b)); + } + + return 0; +} + +guint32 +nm_team_setting_reset(NMTeamSetting *self, const NMTeamSetting *src) +{ + const TeamAttrData *attr_data; + guint32 changed_flags; + + _team_setting_ASSERT(self); + _team_setting_ASSERT(src); + nm_assert(self->d.is_port == src->d.is_port); + + if (self == src) + return 0; + + changed_flags = 0; + + for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; + attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) { + if (!_team_attr_data_is_relevant(attr_data, self->d.is_port)) + continue; + if (_team_attr_data_equal(attr_data, + self->d.is_port, + _team_setting_get_field(self, attr_data), + _team_setting_get_field(src, attr_data))) + continue; + _team_attr_data_copy(attr_data, + self->d.is_port, + _team_setting_get_field(self, attr_data), + _team_setting_get_field(src, attr_data)); + changed_flags |= nm_team_attribute_to_flags(attr_data->team_attr); + } + + self->_data_priv.has_fields_mask = src->d.has_fields_mask; + + if (!nm_streq0(self->d._js_str, src->d._js_str)) { + g_free((char *) self->_data_priv._js_str); + self->_data_priv._js_str = g_strdup(src->d._js_str); + changed_flags |= nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); + } else if (changed_flags != 0) + changed_flags |= nm_team_attribute_to_flags(NM_TEAM_ATTRIBUTE_CONFIG); + + self->_data_priv._js_str_need_synthetize = src->d._js_str_need_synthetize; + self->_data_priv.strict_validated = src->d.strict_validated; + self->_data_priv.js_str_invalid = src->d.js_str_invalid; + + return changed_flags; +} + +static void +_variants_list_team_unref_auto(GVariant *(*p_variants)[]) +{ + int i; + + for (i = 0; i < _NM_TEAM_ATTRIBUTE_NUM; i++) + nm_g_variant_unref((*p_variants)[i]); +} + +gboolean +nm_team_setting_reset_from_dbus(NMTeamSetting * self, + GVariant * setting_dict, + GHashTable * keys, + guint32 * out_changed, + guint /* NMSettingParseFlags */ parse_flags, + GError ** error) +{ + nm_auto(_variants_list_team_unref_auto) GVariant *variants[_NM_TEAM_ATTRIBUTE_NUM] = { + NULL, + }; + gs_unref_ptrarray GPtrArray *v_link_watchers = NULL; + const TeamAttrData * attr_data; + GVariantIter iter; + const char * v_key; + GVariant * v_val; + const NMJsonVt * vt; + + *out_changed = 0; + + g_variant_iter_init(&iter, setting_dict); + while (g_variant_iter_next(&iter, "{&sv}", &v_key, &v_val)) { + _nm_unused gs_unref_variant GVariant *v_val_free = v_val; + const GVariantType * variant_type = NULL; + + attr_data = _team_attr_data_find_for_property_name(self->d.is_port, v_key); + if (!attr_data) { + /* _nm_setting_new_from_dbus() already checks for unknown keys. Don't + * do that here. */ + continue; + } + + if (keys) + g_hash_table_remove(keys, v_key); + + if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) + variant_type = nm_value_type_get_variant_type(attr_data->value_type); + else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_CONFIG) + variant_type = G_VARIANT_TYPE_STRING; + else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) + variant_type = G_VARIANT_TYPE("aa{sv}"); + else if (!self->d.is_port + && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) + variant_type = G_VARIANT_TYPE_STRING_ARRAY; + else + nm_assert_not_reached(); + + if (!g_variant_is_of_type(v_val, variant_type)) { + if (NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid D-Bus type \"%s\""), + g_variant_get_type_string(v_val)); + _team_setting_prefix_error_plain(self->d.is_port, attr_data->property_name, error); + return FALSE; + } + continue; + } + + /* _nm_setting_new_from_dbus() already checks for duplicate keys. Don't + * do that here. */ + nm_g_variant_unref(variants[attr_data->team_attr]); + variants[attr_data->team_attr] = g_steal_pointer(&v_val_free); + } + + vt = nm_json_vt(); + + if (variants[NM_TEAM_ATTRIBUTE_LINK_WATCHERS]) { + if (variants[NM_TEAM_ATTRIBUTE_CONFIG] && vt + && !NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + /* we don't require the content of the "link-watchers" and we also + * don't perform strict validation. No need to parse it. */ + } else { + gs_free_error GError *local = NULL; + + /* We might need the parsed v_link_watchers array below (because there is no JSON + * "config" present or because we don't have json support). + * + * Or we might run with NM_SETTING_PARSE_FLAGS_STRICT. In that mode, we may not necessarily + * require that the entire setting as a whole validates (if a JSON config is present and + * we are not "strict_validated") , but we require that we can at least parse the link watchers + * on their own. */ + v_link_watchers = _nm_utils_team_link_watchers_from_variant( + variants[NM_TEAM_ATTRIBUTE_LINK_WATCHERS], + NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT), + &local); + if (local && NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid link-watchers: %s"), + local->message); + _team_setting_prefix_error(self, + NM_SETTING_TEAM_LINK_WATCHERS, + NM_SETTING_TEAM_PORT_LINK_WATCHERS, + error); + return FALSE; + } + } + } + + *out_changed |= nm_team_setting_config_set( + self, + variants[NM_TEAM_ATTRIBUTE_CONFIG] + ? g_variant_get_string(variants[NM_TEAM_ATTRIBUTE_CONFIG], NULL) + : NULL); + + if (vt && variants[NM_TEAM_ATTRIBUTE_CONFIG]) { + /* for team settings, the JSON must be able to express all possible options. That means, + * if the GVariant contains both the JSON "config" and other options, then the other options + * are silently ignored. */ + } else { + guint32 extra_changed = 0u; + + for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; + attr_data < &team_attr_datas[G_N_ELEMENTS(team_attr_datas)]; + attr_data++) { + NMValueTypUnion val; + guint32 changed_flags = 0u; + + if (!_team_attr_data_is_relevant(attr_data, self->d.is_port)) + continue; + if (!variants[attr_data->team_attr]) + continue; + + if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) { + nm_value_type_get_from_variant(attr_data->value_type, + &val, + variants[attr_data->team_attr], + FALSE); + changed_flags = _team_setting_value_set(self, + attr_data, + &val, + SET_FIELD_MODE_SET, + RESET_JSON_NO); + } else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) { + changed_flags = _team_setting_value_link_watchers_set_list( + self, + v_link_watchers ? (const NMTeamLinkWatcher *const *) v_link_watchers->pdata + : NULL, + v_link_watchers ? v_link_watchers->len : 0u, + SET_FIELD_MODE_SET, + RESET_JSON_NO); + } else if (!self->d.is_port + && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { + gs_free const char **strv = NULL; + gsize len; + + strv = g_variant_get_strv(variants[attr_data->team_attr], &len); + changed_flags = _team_setting_value_master_runner_tx_hash_set_list( + self, + strv, + NM_MIN(len, (gsize) G_MAXUINT), + SET_FIELD_MODE_SET, + RESET_JSON_NO); + } else + nm_assert_not_reached(); + + extra_changed |= changed_flags; + } + + if (!variants[NM_TEAM_ATTRIBUTE_CONFIG]) { + /* clear the JSON string so it can be regenerated. But only if we didn't set + * it above. */ + self->_data_priv.strict_validated = TRUE; + self->_data_priv._js_str_need_synthetize = TRUE; + } + + *out_changed |= extra_changed; + } + + return TRUE; +} + +/*****************************************************************************/ + +gboolean +nm_team_setting_maybe_changed(NMSetting * source, + const GParamSpec *const *obj_properties, + guint32 changed_flags) +{ + NMTeamAttribute team_attr; + int count_flags; + guint32 ch; + + if (changed_flags == 0u) + return FALSE; + + count_flags = 0; + for (ch = changed_flags; ch != 0u; ch >>= 1) { + if (NM_FLAGS_HAS(ch, 0x1u)) + count_flags++; + } + + if (count_flags > 1) + g_object_freeze_notify(G_OBJECT(source)); + + ch = changed_flags; + for (team_attr = 0; team_attr < _NM_TEAM_ATTRIBUTE_NUM; team_attr++) { + if (!NM_FLAGS_ANY(ch, nm_team_attribute_to_flags(team_attr))) + continue; + g_object_notify_by_pspec(G_OBJECT(source), (GParamSpec *) obj_properties[team_attr]); + ch &= ~nm_team_attribute_to_flags(team_attr); + if (ch == 0) + break; + } + + if (count_flags > 1) + g_object_thaw_notify(G_OBJECT(source)); + + return TRUE; +} + +/*****************************************************************************/ + +NMTeamSetting * +_nm_setting_get_team_setting(struct _NMSetting *setting) +{ + if (NM_IS_SETTING_TEAM(setting)) + return _nm_setting_team_get_team_setting(NM_SETTING_TEAM(setting)); + return _nm_setting_team_port_get_team_setting(NM_SETTING_TEAM_PORT(setting)); +} + +static GVariant * +_nm_team_settings_property_to_dbus(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + NMTeamSetting * self = _nm_setting_get_team_setting(setting); + const TeamAttrData *attr_data = + _team_attr_data_get(self->d.is_port, + sett_info->property_infos[property_idx].param_spec->param_id); + + if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_CONFIG) { + const char *config; + + if (self->d.strict_validated && !_nm_utils_is_manager_process) { + /* if we are in strict validating mode on the client side, the JSON is generated + * artificially. In this case, don't send the config via D-Bus to the server. + * + * This also will cause NetworkManager to strictly validate the settings. + * If a JSON "config" is present, strict validation won't be performed. */ + return NULL; + } + + config = nm_team_setting_config_get(self); + return config ? g_variant_new_string(config) : NULL; + } + + if (!_team_setting_has_field(self, attr_data)) + return NULL; + + if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) { + return nm_value_type_to_variant(attr_data->value_type, + _team_setting_get_field(self, attr_data)); + } + if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) + return _nm_utils_team_link_watchers_to_variant(self->d.link_watchers); + if (!self->d.is_port && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) { + return g_variant_new_strv(self->d.master.runner_tx_hash + ? (const char *const *) self->d.master.runner_tx_hash->pdata + : NULL, + self->d.master.runner_tx_hash ? self->d.master.runner_tx_hash->len + : 0u); + } + + nm_assert_not_reached(); + return NULL; +} + +static void +_nm_team_settings_property_from_dbus_link_watchers(GVariant *dbus_value, GValue *prop_value) +{ + g_value_take_boxed(prop_value, + _nm_utils_team_link_watchers_from_variant(dbus_value, FALSE, NULL)); +} + +const NMSettInfoPropertType nm_sett_info_propert_type_team_b = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_BOOLEAN, + .to_dbus_fcn = _nm_team_settings_property_to_dbus, ); + +const NMSettInfoPropertType nm_sett_info_propert_type_team_i = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_INT32, + .to_dbus_fcn = _nm_team_settings_property_to_dbus, ); + +const NMSettInfoPropertType nm_sett_info_propert_type_team_s = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_STRING, + .to_dbus_fcn = _nm_team_settings_property_to_dbus, ); + +const NMSettInfoPropertType nm_sett_info_propert_type_team_as = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(NM_G_VARIANT_TYPE("as"), + .to_dbus_fcn = _nm_team_settings_property_to_dbus, ); + +const NMSettInfoPropertType nm_sett_info_propert_type_team_link_watchers = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = _nm_team_settings_property_to_dbus, + .gprop_from_dbus_fcn = + _nm_team_settings_property_from_dbus_link_watchers, ); + +/*****************************************************************************/ + +NMTeamSetting * +nm_team_setting_new(gboolean is_port, const char *js_str) +{ + NMTeamSetting *self; + gsize l; + + G_STATIC_ASSERT_EXPR(sizeof(*self) == sizeof(self->_data_priv)); + G_STATIC_ASSERT_EXPR(sizeof(*self) + == NM_CONST_MAX(nm_offsetofend(NMTeamSetting, d.master), + nm_offsetofend(NMTeamSetting, d.port))); + + l = is_port ? nm_offsetofend(NMTeamSetting, d.port) : nm_offsetofend(NMTeamSetting, d.master); + + self = g_malloc0(l); + + self->_data_priv.is_port = is_port; + self->_data_priv.strict_validated = TRUE; + self->_data_priv._js_str_need_synthetize = FALSE; + self->_data_priv.link_watchers = + g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); + + _team_setting_ASSERT(self); + + nm_team_setting_config_set(self, js_str); + + _team_setting_ASSERT(self); + + return self; +} + +void +nm_team_setting_free(NMTeamSetting *self) +{ + if (!self) + return; + + _team_setting_ASSERT(self); + + if (!self->d.is_port) { + nm_clear_pointer(((GPtrArray **) &self->_data_priv.master.runner_tx_hash), + g_ptr_array_unref); + g_free((char *) self->_data_priv.master.runner); + g_free((char *) self->_data_priv.master.runner_hwaddr_policy); + g_free((char *) self->_data_priv.master.runner_tx_balancer); + g_free((char *) self->_data_priv.master.runner_agg_select_policy); + } + g_ptr_array_unref((GPtrArray *) self->_data_priv.link_watchers); + g_free((char *) self->_data_priv._js_str); + g_free(self); +} diff --git a/src/libnm-core-impl/nm-team-utils.h b/src/libnm-core-impl/nm-team-utils.h new file mode 100644 index 00000000..2039c16e --- /dev/null +++ b/src/libnm-core-impl/nm-team-utils.h @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2019 Red Hat, Inc. + */ + +#ifndef __NM_TEAM_UITLS_H__ +#define __NM_TEAM_UITLS_H__ + +#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE) + #error Cannot use this header. +#endif + +#include "libnm-glib-aux/nm-value-type.h" +#include "libnm-core-intern/nm-core-internal.h" + +struct _NMSetting; + +struct NMTeamLinkWatcher; + +typedef enum { + + _NM_TEAM_ATTRIBUTE_0 = 0, + NM_TEAM_ATTRIBUTE_CONFIG = 1, + NM_TEAM_ATTRIBUTE_LINK_WATCHERS = 2, + + _NM_TEAM_ATTRIBUTE_START = 3, + + NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT = _NM_TEAM_ATTRIBUTE_START, + NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL, + NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT, + NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS, + NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY, + _NM_TEAM_ATTRIBUTE_MASTER_NUM, + + NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID = _NM_TEAM_ATTRIBUTE_START, + NM_TEAM_ATTRIBUTE_PORT_PRIO, + NM_TEAM_ATTRIBUTE_PORT_STICKY, + NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO, + NM_TEAM_ATTRIBUTE_PORT_LACP_KEY, + _NM_TEAM_ATTRIBUTE_PORT_NUM, + + _NM_TEAM_ATTRIBUTE_NUM = MAX(_NM_TEAM_ATTRIBUTE_MASTER_NUM, _NM_TEAM_ATTRIBUTE_PORT_NUM), + +} NMTeamAttribute; + +static inline guint32 +nm_team_attribute_to_flags(NMTeamAttribute team_attr) +{ + nm_assert(_NM_INT_NOT_NEGATIVE(team_attr)); + nm_assert(team_attr < _NM_TEAM_ATTRIBUTE_NUM); + G_STATIC_ASSERT_EXPR(_NM_TEAM_ATTRIBUTE_NUM < 32); + + return ((guint32) 1) << team_attr; +} + +struct _NMTeamSettingData { + const char *_js_str; + + const GPtrArray *link_watchers; + + /* this means that @_js_str is unset and needs to be created by + * converting the properties to JSON. This flag indicates that + * we need to re-generate the JSON string on-demand (lazily). */ + bool _js_str_need_synthetize; + + bool strict_validated : 1; + + /* indicates tha the JSON is invalid. Usually, we do a very relaxed validation of + * the JSON config, in case !@strict_validated and accept all unknown fields. This + * flag indicates that the JSON value is not even parsable as JSON. nm_connection_verify() + * would reject such a setting. */ + bool js_str_invalid : 1; + + bool is_port : 1; + + guint32 has_fields_mask; + + union { + struct { + const GPtrArray *runner_tx_hash; + const char * runner; + const char * runner_hwaddr_policy; + const char * runner_tx_balancer; + const char * runner_agg_select_policy; + gint32 notify_peers_count; + gint32 notify_peers_interval; + gint32 mcast_rejoin_count; + gint32 mcast_rejoin_interval; + gint32 runner_sys_prio; + gint32 runner_min_ports; + gint32 runner_tx_balancer_interval; + bool runner_active; + bool runner_fast_rate; + } master; + struct { + gint32 queue_id; + gint32 prio; + gint32 lacp_prio; + gint32 lacp_key; + bool sticky; + } port; + }; +}; + +/*****************************************************************************/ + +typedef struct { + union { + const struct _NMTeamSettingData d; + + struct _NMTeamSettingData _data_priv; + }; +} NMTeamSetting; + +NMTeamSetting *nm_team_setting_new(gboolean is_port, const char *js_str); + +void nm_team_setting_free(NMTeamSetting *self); + +NM_AUTO_DEFINE_FCN0(NMTeamSetting *, _nm_auto_free_team_setting, nm_team_setting_free); +#define nm_auto_free_team_setting nm_auto(_nm_auto_free_team_setting) + +/*****************************************************************************/ + +const char *nm_team_setting_config_get(const NMTeamSetting *self); + +guint32 nm_team_setting_config_set(NMTeamSetting *self, const char *js_str); + +/*****************************************************************************/ + +gconstpointer _nm_team_setting_value_get(const NMTeamSetting *self, + NMTeamAttribute team_attr, + NMValueType value_type); + +static inline gboolean +nm_team_setting_value_get_bool(const NMTeamSetting *self, NMTeamAttribute team_attr) +{ + const bool *p; + + p = _nm_team_setting_value_get(self, team_attr, NM_VALUE_TYPE_BOOL); + return p ? *p : 0; +} + +static inline gint32 +nm_team_setting_value_get_int32(const NMTeamSetting *self, NMTeamAttribute team_attr) +{ + const gint32 *p; + + p = _nm_team_setting_value_get(self, team_attr, NM_VALUE_TYPE_INT32); + return p ? *p : 0; +} + +static inline const char * +nm_team_setting_value_get_string(const NMTeamSetting *self, NMTeamAttribute team_attr) +{ + const char *const *p; + + p = _nm_team_setting_value_get(self, team_attr, NM_VALUE_TYPE_STRING); + return p ? *p : NULL; +} + +/*****************************************************************************/ + +guint32 nm_team_setting_value_reset(NMTeamSetting * self, + NMTeamAttribute team_attr, + gboolean to_default /* or else unset */); + +guint32 _nm_team_setting_value_set(NMTeamSetting * self, + NMTeamAttribute team_attr, + NMValueType value_type, + gconstpointer val); + +static inline guint32 +nm_team_setting_value_set_bool(NMTeamSetting *self, NMTeamAttribute team_attr, gboolean val) +{ + const bool bool_val = val; + + return _nm_team_setting_value_set(self, team_attr, NM_VALUE_TYPE_BOOL, &bool_val); +} + +static inline guint32 +nm_team_setting_value_set_int32(NMTeamSetting *self, NMTeamAttribute team_attr, gint32 val) +{ + return _nm_team_setting_value_set(self, team_attr, NM_VALUE_TYPE_INT32, &val); +} + +static inline guint32 +nm_team_setting_value_set_string(NMTeamSetting *self, NMTeamAttribute team_attr, const char *arg) +{ + return _nm_team_setting_value_set(self, team_attr, NM_VALUE_TYPE_STRING, &arg); +} + +/*****************************************************************************/ + +guint32 nm_team_setting_value_link_watchers_add(NMTeamSetting * self, + const struct NMTeamLinkWatcher *link_watcher); + +guint32 nm_team_setting_value_link_watchers_remove(NMTeamSetting *self, guint idx); + +guint32 +nm_team_setting_value_link_watchers_remove_by_value(NMTeamSetting * self, + const struct NMTeamLinkWatcher *link_watcher); + +guint32 nm_team_setting_value_link_watchers_set_list(NMTeamSetting * self, + const struct NMTeamLinkWatcher *const *arr, + guint len); + +/*****************************************************************************/ + +guint32 nm_team_setting_value_master_runner_tx_hash_add(NMTeamSetting *self, const char *txhash); + +guint32 nm_team_setting_value_master_runner_tx_hash_remove(NMTeamSetting *self, guint idx); + +guint32 nm_team_setting_value_master_runner_tx_hash_set_list(NMTeamSetting * self, + const char *const *arr, + guint len); + +/*****************************************************************************/ + +gboolean nm_team_setting_verify(const NMTeamSetting *self, GError **error); + +/*****************************************************************************/ + +int nm_team_setting_cmp(const NMTeamSetting *self_a, + const NMTeamSetting *self_b, + gboolean ignore_js_str); + +guint32 nm_team_setting_reset(NMTeamSetting *self, const NMTeamSetting *src); + +gboolean nm_team_setting_reset_from_dbus(NMTeamSetting * self, + GVariant * setting_dict, + GHashTable * keys, + guint32 * out_changed, + guint /* NMSettingParseFlags */ parse_flags, + GError ** error); + +/*****************************************************************************/ + +GPtrArray * +_nm_utils_team_link_watchers_from_variant(GVariant *value, gboolean strict_parsing, GError **error); +GVariant *_nm_utils_team_link_watchers_to_variant(const GPtrArray *link_watchers); + +/*****************************************************************************/ + +gboolean nm_team_setting_maybe_changed(struct _NMSetting * source, + const GParamSpec *const *obj_properties, + guint32 changed); + +struct _NMSettingTeam; +struct _NMSettingTeamPort; +NMTeamSetting *_nm_setting_team_get_team_setting(struct _NMSettingTeam *setting); +NMTeamSetting *_nm_setting_team_port_get_team_setting(struct _NMSettingTeamPort *setting); +NMTeamSetting *_nm_setting_get_team_setting(struct _NMSetting *setting); + +/*****************************************************************************/ + +extern const NMSettInfoPropertType nm_sett_info_propert_type_team_b; +extern const NMSettInfoPropertType nm_sett_info_propert_type_team_i; +extern const NMSettInfoPropertType nm_sett_info_propert_type_team_s; +extern const NMSettInfoPropertType nm_sett_info_propert_type_team_as; +extern const NMSettInfoPropertType nm_sett_info_propert_type_team_link_watchers; + +#endif /* __NM_TEAM_UITLS_H__ */ diff --git a/src/libnm-core-impl/nm-utils-private.h b/src/libnm-core-impl/nm-utils-private.h new file mode 100644 index 00000000..eb545d0f --- /dev/null +++ b/src/libnm-core-impl/nm-utils-private.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2005 - 2017 Red Hat, Inc. + */ + +#ifndef __NM_UTILS_PRIVATE_H__ +#define __NM_UTILS_PRIVATE_H__ + +#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE) + #error Cannot use this header. +#endif + +#include "nm-setting-private.h" +#include "nm-setting-ip-config.h" + +#define NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(_name, _type, ...) \ + (&((const NMVariantAttributeSpec){.name = _name, .type = _type, __VA_ARGS__})) + +gboolean _nm_utils_string_slist_validate(GSList *list, const char **valid_values); + +gboolean _nm_utils_secret_flags_validate(NMSettingSecretFlags secret_flags, + const char * setting_name, + const char * property_name, + NMSettingSecretFlags disallowed_flags, + GError ** error); + +gboolean _nm_utils_wps_method_validate(NMSettingWirelessSecurityWpsMethod wps_method, + const char * setting_name, + const char * property_name, + gboolean wps_required, + GError ** error); + +/* D-Bus transform funcs */ + +extern const NMSettInfoPropertType nm_sett_info_propert_type_strdict; + +extern const NMSettInfoPropertType nm_sett_info_propert_type_mac_address; + +extern const NMSettInfoPropertType nm_sett_info_propert_type_cloned_mac_address; + +extern const NMSettInfoPropertType nm_sett_info_propert_type_assigned_mac_address; + +extern const NMSettInfoPropertType nm_sett_info_propert_type_bridge_vlans; + +void _nm_utils_strdict_from_dbus(GVariant *dbus_value, GValue *prop_value); + +void _nm_utils_bytes_from_dbus(GVariant *dbus_value, GValue *prop_value); + +char *_nm_utils_hwaddr_canonical_or_invalid(const char *mac, gssize length); + +gboolean _nm_utils_hwaddr_link_local_valid(const char *mac); + +gboolean _nm_sriov_vf_parse_vlans(NMSriovVF *vf, const char *str, GError **error); + +gboolean _nm_utils_bridge_vlan_verify_list(GPtrArray * vlans, + gboolean check_normalizable, + GError ** error, + const char *setting, + const char *property); + +#endif diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c new file mode 100644 index 00000000..12e3bd40 --- /dev/null +++ b/src/libnm-core-impl/nm-utils.c @@ -0,0 +1,5698 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2005 - 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libnm-glib-aux/nm-uuid.h" +#include "libnm-glib-aux/nm-json-aux.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-enum-utils.h" +#include "libnm-glib-aux/nm-time-utils.h" +#include "libnm-glib-aux/nm-secret-utils.h" +#include "libnm-systemd-shared/nm-sd-utils-shared.h" +#include "libnm-core-aux-intern/nm-common-macros.h" +#include "nm-utils-private.h" +#include "nm-setting-private.h" +#include "nm-crypto.h" +#include "nm-setting-bond.h" +#include "nm-setting-bridge.h" +#include "nm-setting-bridge-port.h" +#include "nm-setting-infiniband.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-team.h" +#include "nm-setting-vlan.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" + +/** + * SECTION:nm-utils + * @short_description: Utility functions + * + * A collection of utility functions for working with SSIDs, IP addresses, Wi-Fi + * access points and devices, among other things. + */ + +/*****************************************************************************/ + +/** + * NMUtilsPredicateStr: + * @str: the name to check. + * + * This function takes a string argument and returns either %TRUE or %FALSE. + * It is a general purpose predicate, for example used by nm_setting_option_clear_by_name(). + * + * Returns: %TRUE if the predicate function matches. + * + * Since: 1.26 + */ + +/*****************************************************************************/ + +struct _NMSockAddrEndpoint { + const char *host; + guint16 port; + guint refcount; + char endpoint[]; +}; + +static gboolean +NM_IS_SOCK_ADDR_ENDPOINT(const NMSockAddrEndpoint *self) +{ + return self && self->refcount > 0; +} + +static const char * +_parse_endpoint(char *str, guint16 *out_port) +{ + char * s; + const char *s_port; + gint16 port; + + /* Like + * - https://git.zx2c4.com/WireGuard/tree/src/tools/config.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n192 + * - https://github.com/systemd/systemd/blob/911649fdd43f3a9158b847947724a772a5a45c34/src/network/netdev/wireguard.c#L614 + */ + + g_strstrip(str); + + if (!str[0]) + return NULL; + + if (str[0] == '[') { + str++; + s = strchr(str, ']'); + if (!s) + return NULL; + if (s == str) + return NULL; + if (s[1] != ':') + return NULL; + if (!s[2]) + return NULL; + *s = '\0'; + s_port = &s[2]; + } else { + s = strrchr(str, ':'); + if (!s) + return NULL; + if (s == str) + return NULL; + if (!s[1]) + return NULL; + *s = '\0'; + s_port = &s[1]; + } + + if (!NM_STRCHAR_ALL(s_port, ch, (ch >= '0' && ch <= '9'))) + return NULL; + + port = _nm_utils_ascii_str_to_int64(s_port, 10, 1, G_MAXUINT16, 0); + if (port == 0) + return NULL; + + *out_port = port; + return str; +} + +/** + * nm_sock_addr_endpoint_new: + * @endpoint: the endpoint string. + * + * This function cannot fail, even if the @endpoint is invalid. + * The reason is to allow NMSockAddrEndpoint also to be used + * for tracking invalid endpoints. Use nm_sock_addr_endpoint_get_host() + * to determine whether the endpoint is valid. + * + * Returns: (transfer full): the new #NMSockAddrEndpoint endpoint. + */ +NMSockAddrEndpoint * +nm_sock_addr_endpoint_new(const char *endpoint) +{ + NMSockAddrEndpoint *ep; + gsize l_endpoint; + gsize l_host = 0; + gsize i; + gs_free char * host_clone = NULL; + const char * host; + guint16 port; + + g_return_val_if_fail(endpoint, NULL); + + l_endpoint = strlen(endpoint) + 1; + + host = _parse_endpoint(nm_strndup_a(200, endpoint, l_endpoint - 1, &host_clone), &port); + + if (host) + l_host = strlen(host) + 1; + + ep = g_malloc(sizeof(NMSockAddrEndpoint) + l_endpoint + l_host); + ep->refcount = 1; + memcpy(ep->endpoint, endpoint, l_endpoint); + if (host) { + i = l_endpoint; + memcpy(&ep->endpoint[i], host, l_host); + ep->host = &ep->endpoint[i]; + ep->port = port; + } else { + ep->host = NULL; + ep->port = 0; + } + return ep; +} + +/** + * nm_sock_addr_endpoint_ref: + * @self: (allow-none): the #NMSockAddrEndpoint + */ +NMSockAddrEndpoint * +nm_sock_addr_endpoint_ref(NMSockAddrEndpoint *self) +{ + if (!self) + return NULL; + + g_return_val_if_fail(NM_IS_SOCK_ADDR_ENDPOINT(self), NULL); + + nm_assert(self->refcount < G_MAXUINT); + + self->refcount++; + return self; +} + +/** + * nm_sock_addr_endpoint_unref: + * @self: (allow-none): the #NMSockAddrEndpoint + */ +void +nm_sock_addr_endpoint_unref(NMSockAddrEndpoint *self) +{ + if (!self) + return; + + g_return_if_fail(NM_IS_SOCK_ADDR_ENDPOINT(self)); + + if (--self->refcount == 0) + g_free(self); +} + +/** + * nm_sock_addr_endpoint_get_endpoint: + * @self: the #NMSockAddrEndpoint + * + * Gives the endpoint string. Since #NMSockAddrEndpoint's only + * information is the endpoint string, this can be used for comparing + * to instances for equality and order them lexically. + * + * Returns: (transfer none): the endpoint. + */ +const char * +nm_sock_addr_endpoint_get_endpoint(NMSockAddrEndpoint *self) +{ + g_return_val_if_fail(NM_IS_SOCK_ADDR_ENDPOINT(self), NULL); + + return self->endpoint; +} + +/** + * nm_sock_addr_endpoint_get_host: + * @self: the #NMSockAddrEndpoint + * + * Returns: (transfer none): the parsed host part of the endpoint. + * If the endpoint is invalid, %NULL will be returned. + */ +const char * +nm_sock_addr_endpoint_get_host(NMSockAddrEndpoint *self) +{ + g_return_val_if_fail(NM_IS_SOCK_ADDR_ENDPOINT(self), NULL); + + return self->host; +} + +/** + * nm_sock_addr_endpoint_get_port: + * @self: the #NMSockAddrEndpoint + * + * Returns: the parsed port part of the endpoint (the service). + * If the endpoint is invalid, -1 will be returned. + */ +gint32 +nm_sock_addr_endpoint_get_port(NMSockAddrEndpoint *self) +{ + g_return_val_if_fail(NM_IS_SOCK_ADDR_ENDPOINT(self), -1); + + return self->host ? (int) self->port : -1; +} + +gboolean +nm_sock_addr_endpoint_get_fixed_sockaddr(NMSockAddrEndpoint *self, gpointer sockaddr) +{ + int addr_family; + NMIPAddr addrbin; + const char *s; + guint scope_id = 0; + + g_return_val_if_fail(NM_IS_SOCK_ADDR_ENDPOINT(self), FALSE); + g_return_val_if_fail(sockaddr, FALSE); + + if (!self->host) + return FALSE; + + if (nm_utils_parse_inaddr_bin(AF_UNSPEC, self->host, &addr_family, &addrbin)) + goto good; + + /* See if there is an IPv6 scope-id... + * + * Note that it does not make sense to persist connection profiles to disk, + * that refenrence a scope-id (because the interface's ifindex changes on + * reboot). However, we also support runtime only changes like `nmcli device modify` + * where nothing is persisted to disk. At least in that case, passing a scope-id + * might be reasonable. So, parse that too. */ + s = strchr(self->host, '%'); + if (!s) + return FALSE; + + if (s[1] == '\0' || !NM_STRCHAR_ALL(&s[1], ch, (ch >= '0' && ch <= '9'))) + return FALSE; + + scope_id = _nm_utils_ascii_str_to_int64(&s[1], 10, 0, G_MAXINT32, G_MAXUINT); + if (scope_id == G_MAXUINT && errno) + return FALSE; + + { + gs_free char *tmp_str = NULL; + const char * host_part; + + host_part = nm_strndup_a(200, self->host, s - self->host, &tmp_str); + if (nm_utils_parse_inaddr_bin(AF_INET6, host_part, &addr_family, &addrbin)) + goto good; + } + + return FALSE; + +good: + switch (addr_family) { + case AF_INET: + *((struct sockaddr_in *) sockaddr) = (struct sockaddr_in){ + .sin_family = AF_INET, + .sin_addr = addrbin.addr4_struct, + .sin_port = htons(self->port), + }; + return TRUE; + case AF_INET6: + *((struct sockaddr_in6 *) sockaddr) = (struct sockaddr_in6){ + .sin6_family = AF_INET6, + .sin6_addr = addrbin.addr6, + .sin6_port = htons(self->port), + .sin6_scope_id = scope_id, + .sin6_flowinfo = 0, + }; + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +struct IsoLangToEncodings { + const char * lang; + const char *const *encodings; +}; + +#define LANG_ENCODINGS(l, ...) \ + { \ + .lang = l, .encodings = NM_MAKE_STRV(__VA_ARGS__), \ + } + +/* 5-letter language codes */ +static const struct IsoLangToEncodings isoLangEntries5[] = { + /* Simplified Chinese */ + LANG_ENCODINGS("zh_cn", "euc-cn", "gb2312", "gb18030"), /* PRC */ + LANG_ENCODINGS("zh_sg", "euc-cn", "gb2312", "gb18030"), /* Singapore */ + + /* Traditional Chinese */ + LANG_ENCODINGS("zh_tw", "big5", "euc-tw"), /* Taiwan */ + LANG_ENCODINGS("zh_hk", "big5", "euc-tw", "big5-hkcs"), /* Hong Kong */ + LANG_ENCODINGS("zh_mo", "big5", "euc-tw"), /* Macau */ + + LANG_ENCODINGS(NULL, NULL)}; + +/* 2-letter language codes; we don't care about the other 3 in this table */ +static const struct IsoLangToEncodings isoLangEntries2[] = { + /* Japanese */ + LANG_ENCODINGS("ja", "euc-jp", "shift_jis", "iso-2022-jp"), + + /* Korean */ + LANG_ENCODINGS("ko", "euc-kr", "iso-2022-kr", "johab"), + + /* Thai */ + LANG_ENCODINGS("th", "iso-8859-11", "windows-874"), + + /* Central European */ + LANG_ENCODINGS("hu", "iso-8859-2", "windows-1250"), /* Hungarian */ + LANG_ENCODINGS("cs", "iso-8859-2", "windows-1250"), /* Czech */ + LANG_ENCODINGS("hr", "iso-8859-2", "windows-1250"), /* Croatian */ + LANG_ENCODINGS("pl", "iso-8859-2", "windows-1250"), /* Polish */ + LANG_ENCODINGS("ro", "iso-8859-2", "windows-1250"), /* Romanian */ + LANG_ENCODINGS("sk", "iso-8859-2", "windows-1250"), /* Slovakian */ + LANG_ENCODINGS("sl", "iso-8859-2", "windows-1250"), /* Slovenian */ + LANG_ENCODINGS("sh", "iso-8859-2", "windows-1250"), /* Serbo-Croatian */ + + /* Cyrillic */ + LANG_ENCODINGS("ru", "koi8-r", "windows-1251", "iso-8859-5"), /* Russian */ + LANG_ENCODINGS("be", "koi8-r", "windows-1251", "iso-8859-5"), /* Belorussian */ + LANG_ENCODINGS("bg", "windows-1251", "koi8-r", "iso-8859-5"), /* Bulgarian */ + LANG_ENCODINGS("mk", "koi8-r", "windows-1251", "iso-8859-5"), /* Macedonian */ + LANG_ENCODINGS("sr", "koi8-r", "windows-1251", "iso-8859-5"), /* Serbian */ + LANG_ENCODINGS("uk", "koi8-u", "koi8-r", "windows-1251"), /* Ukrainian */ + + /* Arabic */ + LANG_ENCODINGS("ar", "iso-8859-6", "windows-1256"), + + /* Baltic */ + LANG_ENCODINGS("et", "iso-8859-4", "windows-1257"), /* Estonian */ + LANG_ENCODINGS("lt", "iso-8859-4", "windows-1257"), /* Lithuanian */ + LANG_ENCODINGS("lv", "iso-8859-4", "windows-1257"), /* Latvian */ + + /* Greek */ + LANG_ENCODINGS("el", "iso-8859-7", "windows-1253"), + + /* Hebrew */ + LANG_ENCODINGS("he", "iso-8859-8", "windows-1255"), + LANG_ENCODINGS("iw", "iso-8859-8", "windows-1255"), + + /* Turkish */ + LANG_ENCODINGS("tr", "iso-8859-9", "windows-1254"), + + /* Table end */ + LANG_ENCODINGS(NULL, NULL)}; + +static GHashTable *langToEncodings5 = NULL; +static GHashTable *langToEncodings2 = NULL; + +static void +init_lang_to_encodings_hash(void) +{ + struct IsoLangToEncodings *enc; + + if (G_UNLIKELY(langToEncodings5 == NULL)) { + /* Five-letter codes */ + enc = (struct IsoLangToEncodings *) &isoLangEntries5[0]; + langToEncodings5 = g_hash_table_new(nm_str_hash, g_str_equal); + while (enc->lang) { + g_hash_table_insert(langToEncodings5, (gpointer) enc->lang, (gpointer) enc->encodings); + enc++; + } + } + + if (G_UNLIKELY(langToEncodings2 == NULL)) { + /* Two-letter codes */ + enc = (struct IsoLangToEncodings *) &isoLangEntries2[0]; + langToEncodings2 = g_hash_table_new(nm_str_hash, g_str_equal); + while (enc->lang) { + g_hash_table_insert(langToEncodings2, (gpointer) enc->lang, (gpointer) enc->encodings); + enc++; + } + } +} + +static gboolean +get_encodings_for_lang(const char *lang, const char *const **encodings) +{ + gs_free char *tmp_lang = NULL; + + g_return_val_if_fail(lang, FALSE); + g_return_val_if_fail(encodings, FALSE); + + init_lang_to_encodings_hash(); + + if ((*encodings = g_hash_table_lookup(langToEncodings5, lang))) + return TRUE; + + /* Truncate tmp_lang to length of 2 */ + if (strlen(lang) > 2) { + tmp_lang = g_strdup(lang); + tmp_lang[2] = '\0'; + if ((*encodings = g_hash_table_lookup(langToEncodings2, tmp_lang))) + return TRUE; + } + + return FALSE; +} + +static const char *const * +get_system_encodings(void) +{ + static const char *const *cached_encodings; + static char * default_encodings[4]; + const char *const * encodings = NULL; + char * lang; + + if (cached_encodings) + return cached_encodings; + + /* Use environment variables as encoding hint */ + lang = getenv("LC_ALL"); + if (!lang) + lang = getenv("LC_CTYPE"); + if (!lang) + lang = getenv("LANG"); + if (lang) { + char *dot; + + lang = g_ascii_strdown(lang, -1); + if ((dot = strchr(lang, '.'))) + *dot = '\0'; + + get_encodings_for_lang(lang, &encodings); + g_free(lang); + } + if (!encodings) { + g_get_charset((const char **) &default_encodings[0]); + default_encodings[1] = "iso-8859-1"; + default_encodings[2] = "windows-1251"; + default_encodings[3] = NULL; + encodings = (const char *const *) default_encodings; + } + + cached_encodings = encodings; + return cached_encodings; +} + +/*****************************************************************************/ + +static void __attribute__((constructor)) _nm_utils_init(void) +{ + static int initialized = 0; + + if (g_atomic_int_get(&initialized) != 0) + return; + + /* we don't expect this code to run multiple times, nor on multiple threads. + * + * In practice, it would not be a problem if two threads concurrently try to + * run the initialization code below, all code below itself is thread-safe, + * Hence, a poor-man guard "initialized" above is more than sufficient, + * although it does not guarantee that the code is not run concurrently. */ + + bindtextdomain(GETTEXT_PACKAGE, NMLOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + + _nm_dbus_errors_init(); + + g_atomic_int_set(&initialized, 1); +} + +/*****************************************************************************/ + +gboolean _nm_utils_is_manager_process; + +/* ssid helpers */ + +/** + * nm_utils_ssid_to_utf8: + * @ssid: (array length=len): pointer to a buffer containing the SSID data + * @len: length of the SSID data in @ssid + * + * Wi-Fi SSIDs are byte arrays, they are _not_ strings. Thus, an SSID may + * contain embedded NULLs and other unprintable characters. Often it is + * useful to print the SSID out for debugging purposes, but that should be the + * _only_ use of this function. Do not use this function for any persistent + * storage of the SSID, since the printable SSID returned from this function + * cannot be converted back into the real SSID of the access point. + * + * This function does almost everything humanly possible to convert the input + * into a printable UTF-8 string, using roughly the following procedure: + * + * 1) if the input data is already UTF-8 safe, no conversion is performed + * 2) attempts to get the current system language from the LANG environment + * variable, and depending on the language, uses a table of alternative + * encodings to try. For example, if LANG=hu_HU, the table may first try + * the ISO-8859-2 encoding, and if that fails, try the Windows-1250 encoding. + * If all fallback encodings fail, replaces non-UTF-8 characters with '?'. + * 3) If the system language was unable to be determined, falls back to the + * ISO-8859-1 encoding, then to the Windows-1251 encoding. + * 4) If step 3 fails, replaces non-UTF-8 characters with '?'. + * + * Again, this function should be used for debugging and display purposes + * _only_. + * + * Returns: (transfer full): an allocated string containing a UTF-8 + * representation of the SSID, which must be freed by the caller using g_free(). + * Returns %NULL on errors. + **/ +char * +nm_utils_ssid_to_utf8(const guint8 *ssid, gsize len) +{ + const char *const *encodings; + const char *const *e; + char * converted = NULL; + + g_return_val_if_fail(ssid != NULL, NULL); + + if (g_utf8_validate((const char *) ssid, len, NULL)) + return g_strndup((const char *) ssid, len); + + encodings = get_system_encodings(); + + for (e = encodings; *e; e++) { + converted = g_convert((const char *) ssid, len, "UTF-8", *e, NULL, NULL, NULL); + if (converted) + break; + } + + if (!converted) { + converted = g_convert_with_fallback((const char *) ssid, + len, + "UTF-8", + encodings[0], + "?", + NULL, + NULL, + NULL); + } + + if (!converted) { + /* If there is still no converted string, the SSID probably + * contains characters not valid in the current locale. Convert + * the string to ASCII instead. + */ + + /* Use the printable range of 0x20-0x7E */ + char *valid_chars = " !\"#$%&'()*+,-./0123456789:;<=>?@" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~"; + + converted = g_strndup((const char *) ssid, len); + g_strcanon(converted, valid_chars, '?'); + } + + return converted; +} + +char * +_nm_utils_ssid_to_utf8(GBytes *ssid) +{ + const guint8 *p; + gsize l; + + g_return_val_if_fail(ssid, NULL); + + p = g_bytes_get_data(ssid, &l); + return nm_utils_ssid_to_utf8(p, l); +} + +/* Shamelessly ripped from the Linux kernel ieee80211 stack */ +/** + * nm_utils_is_empty_ssid: + * @ssid: (array length=len): pointer to a buffer containing the SSID data + * @len: length of the SSID data in @ssid + * + * Different manufacturers use different mechanisms for not broadcasting the + * AP's SSID. This function attempts to detect blank/empty SSIDs using a + * number of known SSID-cloaking methods. + * + * Returns: %TRUE if the SSID is "empty", %FALSE if it is not + **/ +gboolean +nm_utils_is_empty_ssid(const guint8 *ssid, gsize len) +{ + return _nm_utils_is_empty_ssid_arr(ssid, len); +} + +/** + * nm_utils_escape_ssid: + * @ssid: (array length=len): pointer to a buffer containing the SSID data + * @len: length of the SSID data in @ssid + * + * This function does a quick printable character conversion of the SSID, simply + * replacing embedded NULLs and non-printable characters with the hexadecimal + * representation of that character. Intended for debugging only, should not + * be used for display of SSIDs. + * + * Returns: pointer to the escaped SSID, which uses an internal static buffer + * and will be overwritten by subsequent calls to this function + **/ +const char * +nm_utils_escape_ssid(const guint8 *ssid, gsize len) +{ + static char escaped[NM_IW_ESSID_MAX_SIZE * 2 + 1]; + const guint8 *s = ssid; + char * d = escaped; + + if (nm_utils_is_empty_ssid(ssid, len)) { + memcpy(escaped, "", sizeof("")); + return escaped; + } + + len = MIN(len, (guint32) NM_IW_ESSID_MAX_SIZE); + while (len--) { + if (*s == '\0') { + *d++ = '\\'; + *d++ = '0'; + s++; + } else { + *d++ = *s++; + } + } + *d = '\0'; + return escaped; +} + +/** + * nm_utils_same_ssid: + * @ssid1: (array length=len1): the first SSID to compare + * @len1: length of the SSID data in @ssid1 + * @ssid2: (array length=len2): the second SSID to compare + * @len2: length of the SSID data in @ssid2 + * @ignore_trailing_null: %TRUE to ignore one trailing NULL byte + * + * Earlier versions of the Linux kernel added a NULL byte to the end of the + * SSID to enable easy printing of the SSID on the console or in a terminal, + * but this behavior was problematic (SSIDs are simply byte arrays, not strings) + * and thus was changed. This function compensates for that behavior at the + * cost of some compatibility with odd SSIDs that may legitimately have trailing + * NULLs, even though that is functionally pointless. + * + * Returns: %TRUE if the SSIDs are the same, %FALSE if they are not + **/ +gboolean +nm_utils_same_ssid(const guint8 *ssid1, + gsize len1, + const guint8 *ssid2, + gsize len2, + gboolean ignore_trailing_null) +{ + g_return_val_if_fail(ssid1 != NULL || len1 == 0, FALSE); + g_return_val_if_fail(ssid2 != NULL || len2 == 0, FALSE); + + if (ssid1 == ssid2 && len1 == len2) + return TRUE; + if (!ssid1 || !ssid2) + return FALSE; + + if (ignore_trailing_null) { + if (len1 && ssid1[len1 - 1] == '\0') + len1--; + if (len2 && ssid2[len2 - 1] == '\0') + len2--; + } + + if (len1 != len2) + return FALSE; + + return memcmp(ssid1, ssid2, len1) == 0 ? TRUE : FALSE; +} + +gboolean +_nm_utils_string_slist_validate(GSList *list, const char **valid_values) +{ + GSList *iter; + + for (iter = list; iter; iter = iter->next) { + if (!g_strv_contains(valid_values, (char *) iter->data)) + return FALSE; + } + + return TRUE; +} + +/** + * _nm_utils_hash_values_to_slist: + * @hash: a #GHashTable + * + * Utility function to iterate over a hash table and return + * its values as a #GSList. + * + * Returns: (element-type gpointer) (transfer container): a newly allocated #GSList + * containing the values of the hash table. The caller must free the + * returned list with g_slist_free(). The hash values are not owned + * by the returned list. + **/ +GSList * +_nm_utils_hash_values_to_slist(GHashTable *hash) +{ + GSList * list = NULL; + GHashTableIter iter; + void * value; + + g_return_val_if_fail(hash, NULL); + + g_hash_table_iter_init(&iter, hash); + while (g_hash_table_iter_next(&iter, NULL, &value)) + list = g_slist_prepend(list, value); + + return list; +} + +void +_nm_utils_strdict_from_dbus(GVariant *dbus_value, GValue *prop_value) +{ + GVariantIter iter; + const char * key, *value; + GHashTable * hash; + + hash = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + g_variant_iter_init(&iter, dbus_value); + while (g_variant_iter_next(&iter, "{&s&s}", &key, &value)) + g_hash_table_insert(hash, g_strdup(key), g_strdup(value)); + + g_value_take_boxed(prop_value, hash); +} + +const NMSettInfoPropertType nm_sett_info_propert_type_strdict = + NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT(NM_G_VARIANT_TYPE("a{ss}"), + .gprop_from_dbus_fcn = _nm_utils_strdict_from_dbus, + .typdata_to_dbus.gprop_type = + NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_STRDICT); + +GHashTable * +_nm_utils_copy_strdict(GHashTable *strdict) +{ + GHashTable * copy; + GHashTableIter iter; + gpointer key, value; + + copy = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_free); + if (strdict) { + g_hash_table_iter_init(&iter, strdict); + while (g_hash_table_iter_next(&iter, &key, &value)) + g_hash_table_insert(copy, g_strdup(key), g_strdup(value)); + } + return copy; +} + +GPtrArray * +_nm_utils_copy_array(const GPtrArray *array, NMUtilsCopyFunc copy_func, GDestroyNotify free_func) +{ + GPtrArray *copy; + guint i; + + if (!array) + return g_ptr_array_new_with_free_func(free_func); + + copy = g_ptr_array_new_full(array->len, free_func); + for (i = 0; i < array->len; i++) + g_ptr_array_add(copy, copy_func(array->pdata[i])); + return copy; +} + +GPtrArray * +_nm_utils_copy_object_array(const GPtrArray *array) +{ + return _nm_utils_copy_array(array, g_object_ref, g_object_unref); +} + +void +_nm_utils_bytes_from_dbus(GVariant *dbus_value, GValue *prop_value) +{ + GBytes *bytes; + + if (g_variant_n_children(dbus_value)) { + gconstpointer data; + gsize length; + + data = g_variant_get_fixed_array(dbus_value, &length, 1); + bytes = g_bytes_new(data, length); + } else + bytes = NULL; + g_value_take_boxed(prop_value, bytes); +} + +/*****************************************************************************/ + +GSList * +_nm_utils_strv_to_slist(char **strv, gboolean deep_copy) +{ + GSList *list = NULL; + gsize i; + + if (!strv) + return NULL; + + if (deep_copy) { + for (i = 0; strv[i]; i++) + list = g_slist_prepend(list, g_strdup(strv[i])); + } else { + for (i = 0; strv[i]; i++) + list = g_slist_prepend(list, strv[i]); + } + return g_slist_reverse(list); +} + +char ** +_nm_utils_slist_to_strv(const GSList *slist, gboolean deep_copy) +{ + const GSList *iter; + char ** strv; + guint len, i; + + if (!slist) + return NULL; + + len = g_slist_length((GSList *) slist); + + strv = g_new(char *, len + 1); + + if (deep_copy) { + for (i = 0, iter = slist; iter; iter = iter->next, i++) { + nm_assert(iter->data); + strv[i] = g_strdup(iter->data); + } + } else { + for (i = 0, iter = slist; iter; iter = iter->next, i++) { + nm_assert(iter->data); + strv[i] = iter->data; + } + } + strv[i] = NULL; + + return strv; +} + +GPtrArray * +_nm_utils_strv_to_ptrarray(char **strv) +{ + GPtrArray *ptrarray; + gsize i, l; + + l = NM_PTRARRAY_LEN(strv); + + ptrarray = g_ptr_array_new_full(l, g_free); + + if (strv) { + for (i = 0; strv[i]; i++) + g_ptr_array_add(ptrarray, g_strdup(strv[i])); + } + + return ptrarray; +} + +char ** +_nm_utils_ptrarray_to_strv(const GPtrArray *ptrarray) +{ + char **strv; + guint i; + + if (!ptrarray) + return g_new0(char *, 1); + + strv = g_new(char *, ptrarray->len + 1); + + for (i = 0; i < ptrarray->len; i++) + strv[i] = g_strdup(ptrarray->pdata[i]); + strv[i] = NULL; + + return strv; +} + +/*****************************************************************************/ + +static gboolean +device_supports_ap_ciphers(guint32 dev_caps, guint32 ap_flags, gboolean static_wep) +{ + gboolean have_pair = FALSE; + gboolean have_group = FALSE; + /* Device needs to support at least one pairwise and one group cipher */ + + /* Pairwise */ + if (static_wep) { + /* Static WEP only uses group ciphers */ + have_pair = TRUE; + } else { + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_WEP40) + if (ap_flags & NM_802_11_AP_SEC_PAIR_WEP40) + have_pair = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_WEP104) + if (ap_flags & NM_802_11_AP_SEC_PAIR_WEP104) + have_pair = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP) + if (ap_flags & NM_802_11_AP_SEC_PAIR_TKIP) + have_pair = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP) + if (ap_flags & NM_802_11_AP_SEC_PAIR_CCMP) + have_pair = TRUE; + } + + /* Group */ + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_WEP40) + if (ap_flags & NM_802_11_AP_SEC_GROUP_WEP40) + have_group = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_WEP104) + if (ap_flags & NM_802_11_AP_SEC_GROUP_WEP104) + have_group = TRUE; + if (!static_wep) { + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP) + if (ap_flags & NM_802_11_AP_SEC_GROUP_TKIP) + have_group = TRUE; + if (dev_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP) + if (ap_flags & NM_802_11_AP_SEC_GROUP_CCMP) + have_group = TRUE; + } + + return (have_pair && have_group); +} + +/** + * nm_utils_ap_mode_security_valid: + * @type: the security type to check device capabilities against, + * e.g. #NMU_SEC_STATIC_WEP + * @wifi_caps: bitfield of the capabilities of the specific Wi-Fi device, e.g. + * #NM_WIFI_DEVICE_CAP_CIPHER_WEP40 + * + * Given a set of device capabilities, and a desired security type to check + * against, determines whether the combination of device capabilities and + * desired security type are valid for AP/Hotspot connections. + * + * Returns: %TRUE if the device capabilities are compatible with the desired + * @type, %FALSE if they are not. + **/ +gboolean +nm_utils_ap_mode_security_valid(NMUtilsSecurityType type, NMDeviceWifiCapabilities wifi_caps) +{ + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_AP)) + return FALSE; + + /* Return TRUE for any security that wpa_supplicant's lightweight AP + * mode can handle: which is open, WEP, and WPA/WPA2 PSK. + */ + switch (type) { + case NMU_SEC_NONE: + case NMU_SEC_STATIC_WEP: + case NMU_SEC_WPA_PSK: + case NMU_SEC_WPA2_PSK: + case NMU_SEC_SAE: + case NMU_SEC_OWE: + return TRUE; + case NMU_SEC_LEAP: + case NMU_SEC_DYNAMIC_WEP: + case NMU_SEC_WPA_ENTERPRISE: + case NMU_SEC_WPA2_ENTERPRISE: + case NMU_SEC_WPA3_SUITE_B_192: + return FALSE; + case NMU_SEC_INVALID: + break; + } + return FALSE; +} + +/** + * nm_utils_security_valid: + * @type: the security type to check AP flags and device capabilities against, + * e.g. #NMU_SEC_STATIC_WEP + * @wifi_caps: bitfield of the capabilities of the specific Wi-Fi device, e.g. + * #NM_WIFI_DEVICE_CAP_CIPHER_WEP40 + * @have_ap: whether the @ap_flags, @ap_wpa, and @ap_rsn arguments are valid + * @adhoc: whether the capabilities being tested are from an Ad-Hoc AP (IBSS) + * @ap_flags: bitfield of AP capabilities, e.g. #NM_802_11_AP_FLAGS_PRIVACY + * @ap_wpa: bitfield of AP capabilities derived from the AP's WPA beacon, + * e.g. (#NM_802_11_AP_SEC_PAIR_TKIP | #NM_802_11_AP_SEC_KEY_MGMT_PSK) + * @ap_rsn: bitfield of AP capabilities derived from the AP's RSN/WPA2 beacon, + * e.g. (#NM_802_11_AP_SEC_PAIR_CCMP | #NM_802_11_AP_SEC_PAIR_TKIP) + * + * Given a set of device capabilities, and a desired security type to check + * against, determines whether the combination of device, desired security + * type, and AP capabilities intersect. + * + * NOTE: this function cannot handle checking security for AP/Hotspot mode; + * use nm_utils_ap_mode_security_valid() instead. + * + * Returns: %TRUE if the device capabilities and AP capabilities intersect and are + * compatible with the desired @type, %FALSE if they are not + **/ +gboolean +nm_utils_security_valid(NMUtilsSecurityType type, + NMDeviceWifiCapabilities wifi_caps, + gboolean have_ap, + gboolean adhoc, + NM80211ApFlags ap_flags, + NM80211ApSecurityFlags ap_wpa, + NM80211ApSecurityFlags ap_rsn) +{ + switch (type) { + case NMU_SEC_NONE: + if (!have_ap) + return TRUE; + if (ap_flags & NM_802_11_AP_FLAGS_PRIVACY) + return FALSE; + if (ap_wpa || ap_rsn) + return FALSE; + return TRUE; + case NMU_SEC_LEAP: /* require PRIVACY bit for LEAP? */ + if (adhoc) + return FALSE; + /* fall-through */ + case NMU_SEC_STATIC_WEP: + if (!have_ap) { + if (wifi_caps & (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104)) + return TRUE; + return FALSE; + } + if (!(ap_flags & NM_802_11_AP_FLAGS_PRIVACY)) + return FALSE; + if (ap_wpa || ap_rsn) { + if (!device_supports_ap_ciphers(wifi_caps, ap_wpa, TRUE)) { + if (!device_supports_ap_ciphers(wifi_caps, ap_rsn, TRUE)) + return FALSE; + } + } + return TRUE; + case NMU_SEC_DYNAMIC_WEP: + if (adhoc) + return FALSE; + if (!have_ap) { + if (wifi_caps & (NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104)) + return TRUE; + return FALSE; + } + if (ap_rsn || !(ap_flags & NM_802_11_AP_FLAGS_PRIVACY)) + return FALSE; + /* Some APs broadcast minimal WPA-enabled beacons that must be handled */ + if (ap_wpa) { + if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return FALSE; + if (!device_supports_ap_ciphers(wifi_caps, ap_wpa, FALSE)) + return FALSE; + } + return TRUE; + case NMU_SEC_WPA_PSK: + if (adhoc) + return FALSE; + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_WPA)) + return FALSE; + if (!have_ap) + return TRUE; + if (ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_PSK) { + if ((ap_wpa & NM_802_11_AP_SEC_PAIR_TKIP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP)) + return TRUE; + if ((ap_wpa & NM_802_11_AP_SEC_PAIR_CCMP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + return TRUE; + } + return FALSE; + case NMU_SEC_WPA2_PSK: + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN)) + return FALSE; + if (!have_ap) + return TRUE; + if (adhoc) { + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_IBSS_RSN)) + return FALSE; + if ((ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + return TRUE; + return FALSE; + } + if (ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_PSK) { + if ((ap_rsn & NM_802_11_AP_SEC_PAIR_TKIP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_TKIP)) + return TRUE; + if ((ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + return TRUE; + } + return FALSE; + case NMU_SEC_WPA_ENTERPRISE: + if (adhoc) + return FALSE; + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_WPA)) + return FALSE; + if (!have_ap) + return TRUE; + if (!(ap_wpa & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return FALSE; + /* Ensure at least one WPA cipher is supported */ + if (!device_supports_ap_ciphers(wifi_caps, ap_wpa, FALSE)) + return FALSE; + return TRUE; + case NMU_SEC_WPA2_ENTERPRISE: + if (adhoc) + return FALSE; + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN)) + return FALSE; + if (!have_ap) + return TRUE; + if (!(ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) + return FALSE; + /* Ensure at least one WPA cipher is supported */ + if (!device_supports_ap_ciphers(wifi_caps, ap_rsn, FALSE)) + return FALSE; + return TRUE; + case NMU_SEC_SAE: + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN)) + return FALSE; + if (adhoc) + return FALSE; + if (!have_ap) + return TRUE; + if (ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_SAE) { + if ((ap_rsn & NM_802_11_AP_SEC_PAIR_CCMP) + && (wifi_caps & NM_WIFI_DEVICE_CAP_CIPHER_CCMP)) + return TRUE; + } + return FALSE; + case NMU_SEC_OWE: + if (adhoc) + return FALSE; + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN)) + return FALSE; + if (!have_ap) + return TRUE; + if (!NM_FLAGS_ANY(ap_rsn, NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) + return FALSE; + return TRUE; + case NMU_SEC_WPA3_SUITE_B_192: + if (adhoc) + return FALSE; + if (!(wifi_caps & NM_WIFI_DEVICE_CAP_RSN)) + return FALSE; + if (!have_ap) + return TRUE; + if (ap_rsn & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192) + return TRUE; + return FALSE; + case NMU_SEC_INVALID: + break; + } + + return FALSE; +} + +/** + * nm_utils_wep_key_valid: + * @key: a string that might be a WEP key + * @wep_type: the #NMWepKeyType type of the WEP key + * + * Checks if @key is a valid WEP key + * + * Returns: %TRUE if @key is a WEP key, %FALSE if not + */ +gboolean +nm_utils_wep_key_valid(const char *key, NMWepKeyType wep_type) +{ + gsize keylen; + gsize i; + + if (!key) + return FALSE; + + if (wep_type == NM_WEP_KEY_TYPE_UNKNOWN) { + return nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_KEY) + || nm_utils_wep_key_valid(key, NM_WEP_KEY_TYPE_PASSPHRASE); + } + + keylen = strlen(key); + if (wep_type == NM_WEP_KEY_TYPE_KEY) { + if (keylen == 10 || keylen == 26) { + /* Hex key */ + for (i = 0; i < keylen; i++) { + if (!g_ascii_isxdigit(key[i])) + return FALSE; + } + } else if (keylen == 5 || keylen == 13) { + /* ASCII key */ + for (i = 0; i < keylen; i++) { + if (!g_ascii_isprint(key[i])) + return FALSE; + } + } else + return FALSE; + } else if (wep_type == NM_WEP_KEY_TYPE_PASSPHRASE) { + if (!keylen || keylen > 64) + return FALSE; + } + + return TRUE; +} + +/** + * nm_utils_wpa_psk_valid: + * @psk: a string that might be a WPA PSK + * + * Checks if @psk is a valid WPA PSK + * + * Returns: %TRUE if @psk is a WPA PSK, %FALSE if not + */ +gboolean +nm_utils_wpa_psk_valid(const char *psk) +{ + gsize psklen; + gsize i; + + if (!psk) + return FALSE; + + psklen = strlen(psk); + if (psklen < 8 || psklen > 64) + return FALSE; + + if (psklen == 64) { + /* Hex PSK */ + for (i = 0; i < psklen; i++) { + if (!g_ascii_isxdigit(psk[i])) + return FALSE; + } + } + + return TRUE; +} + +/** + * nm_utils_ip4_dns_to_variant: + * @dns: (type utf8): an array of IP address strings + * + * Utility function to convert an array of IP address strings int a #GVariant of + * type 'au' representing an array of IPv4 addresses. + * + * Returns: (transfer none): a new floating #GVariant representing @dns. + **/ +GVariant * +nm_utils_ip4_dns_to_variant(char **dns) +{ + GVariantBuilder builder; + gsize i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("au")); + + if (dns) { + for (i = 0; dns[i]; i++) { + guint32 ip = 0; + + inet_pton(AF_INET, dns[i], &ip); + g_variant_builder_add(&builder, "u", ip); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * nm_utils_ip4_dns_from_variant: + * @value: a #GVariant of type 'au' + * + * Utility function to convert a #GVariant of type 'au' representing a list of + * IPv4 addresses into an array of IP address strings. + * + * Returns: (transfer full) (type utf8): a %NULL-terminated array of IP address strings. + **/ +char ** +nm_utils_ip4_dns_from_variant(GVariant *value) +{ + const guint32 *array; + gsize length; + char ** dns; + gsize i; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("au")), NULL); + + array = g_variant_get_fixed_array(value, &length, sizeof(guint32)); + dns = g_new(char *, length + 1u); + for (i = 0; i < length; i++) + dns[i] = nm_utils_inet4_ntop_dup(array[i]); + dns[i] = NULL; + + return dns; +} + +/** + * nm_utils_ip4_addresses_to_variant: + * @addresses: (element-type NMIPAddress): an array of #NMIPAddress objects + * @gateway: (allow-none): the gateway IP address + * + * Utility function to convert a #GPtrArray of #NMIPAddress objects representing + * IPv4 addresses into a #GVariant of type 'aau' representing an array of + * NetworkManager IPv4 addresses (which are tuples of address, prefix, and + * gateway). The "gateway" field of the first address will get the value of + * @gateway (if non-%NULL). In all of the other addresses, that field will be 0. + * + * Returns: (transfer none): a new floating #GVariant representing @addresses. + **/ +GVariant * +nm_utils_ip4_addresses_to_variant(GPtrArray *addresses, const char *gateway) +{ + GVariantBuilder builder; + guint i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aau")); + + if (addresses) { + for (i = 0; i < addresses->len; i++) { + NMIPAddress *addr = addresses->pdata[i]; + guint32 array[3]; + in_addr_t gw; + + if (nm_ip_address_get_family(addr) != AF_INET) + continue; + + gw = 0u; + if (gateway) { + in_addr_t a; + + if (inet_pton(AF_INET, gateway, &a) == 1) + gw = a; + gateway = NULL; + } + + nm_ip_address_get_address_binary(addr, &array[0]); + array[1] = nm_ip_address_get_prefix(addr); + array[2] = gw; + + g_variant_builder_add(&builder, "@au", nm_g_variant_new_au(array, 3)); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * nm_utils_ip4_addresses_from_variant: + * @value: a #GVariant of type 'aau' + * @out_gateway: (out) (allow-none) (transfer full): on return, will contain the IP gateway + * + * Utility function to convert a #GVariant of type 'aau' representing a list of + * NetworkManager IPv4 addresses (which are tuples of address, prefix, and + * gateway) into a #GPtrArray of #NMIPAddress objects. The "gateway" field of + * the first address (if set) will be returned in @out_gateway; the "gateway" fields + * of the other addresses are ignored. + * + * Returns: (transfer full) (element-type NMIPAddress): a newly allocated + * #GPtrArray of #NMIPAddress objects + **/ +GPtrArray * +nm_utils_ip4_addresses_from_variant(GVariant *value, char **out_gateway) +{ + GPtrArray * addresses; + GVariantIter iter; + GVariant * addr_var; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aau")), NULL); + + if (out_gateway) + *out_gateway = NULL; + + g_variant_iter_init(&iter, value); + addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref); + + while (g_variant_iter_next(&iter, "@au", &addr_var)) { + const guint32 *addr_array; + gsize length; + NMIPAddress * addr; + GError * error = NULL; + + addr_array = g_variant_get_fixed_array(addr_var, &length, sizeof(guint32)); + if (length < 3) { + g_warning("Ignoring invalid IP4 address"); + g_variant_unref(addr_var); + continue; + } + + addr = nm_ip_address_new_binary(AF_INET, &addr_array[0], addr_array[1], &error); + if (addr) { + g_ptr_array_add(addresses, addr); + + if (addr_array[2] && out_gateway && !*out_gateway) + *out_gateway = nm_utils_inet4_ntop_dup(addr_array[2]); + } else { + g_warning("Ignoring invalid IP4 address: %s", error->message); + g_clear_error(&error); + } + + g_variant_unref(addr_var); + } + + return addresses; +} + +/** + * nm_utils_ip4_routes_to_variant: + * @routes: (element-type NMIPRoute): an array of #NMIP4Route objects + * + * Utility function to convert a #GPtrArray of #NMIPRoute objects representing + * IPv4 routes into a #GVariant of type 'aau' representing an array of + * NetworkManager IPv4 routes (which are tuples of route, prefix, next hop, and + * metric). + * + * Returns: (transfer none): a new floating #GVariant representing @routes. + **/ +GVariant * +nm_utils_ip4_routes_to_variant(GPtrArray *routes) +{ + GVariantBuilder builder; + guint i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aau")); + + if (routes) { + for (i = 0; i < routes->len; i++) { + NMIPRoute *route = routes->pdata[i]; + guint32 array[4]; + + if (nm_ip_route_get_family(route) != AF_INET) + continue; + + nm_ip_route_get_dest_binary(route, &array[0]); + array[1] = nm_ip_route_get_prefix(route); + nm_ip_route_get_next_hop_binary(route, &array[2]); + /* The old routes format uses "0" for default, not "-1" */ + array[3] = MAX(0, nm_ip_route_get_metric(route)); + + g_variant_builder_add(&builder, "@au", nm_g_variant_new_au(array, 4)); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * nm_utils_ip4_routes_from_variant: + * @value: #GVariant of type 'aau' + * + * Utility function to convert a #GVariant of type 'aau' representing an array + * of NetworkManager IPv4 routes (which are tuples of route, prefix, next hop, + * and metric) into a #GPtrArray of #NMIPRoute objects. + * + * Returns: (transfer full) (element-type NMIPRoute): a newly allocated + * #GPtrArray of #NMIPRoute objects + **/ +GPtrArray * +nm_utils_ip4_routes_from_variant(GVariant *value) +{ + GVariantIter iter; + GVariant * route_var; + GPtrArray * routes; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aau")), NULL); + + g_variant_iter_init(&iter, value); + routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref); + + while (g_variant_iter_next(&iter, "@au", &route_var)) { + const guint32 *route_array; + gsize length; + NMIPRoute * route; + GError * error = NULL; + + route_array = g_variant_get_fixed_array(route_var, &length, sizeof(guint32)); + if (length < 4) { + g_warning("Ignoring invalid IP4 route"); + g_variant_unref(route_var); + continue; + } + + route = nm_ip_route_new_binary(AF_INET, + &route_array[0], + route_array[1], + &route_array[2], + /* The old routes format uses "0" for default, not "-1" */ + route_array[3] ? (gint64) route_array[3] : -1, + &error); + if (route) + g_ptr_array_add(routes, route); + else { + g_warning("Ignoring invalid IP4 route: %s", error->message); + g_clear_error(&error); + } + g_variant_unref(route_var); + } + + return routes; +} + +/** + * nm_utils_ip4_netmask_to_prefix: + * @netmask: an IPv4 netmask in network byte order + * + * Returns: the CIDR prefix represented by the netmask + **/ +guint32 +nm_utils_ip4_netmask_to_prefix(guint32 netmask) +{ + G_STATIC_ASSERT_EXPR(__SIZEOF_INT__ == 4); + G_STATIC_ASSERT_EXPR(sizeof(int) == 4); + G_STATIC_ASSERT_EXPR(sizeof(netmask) == 4); + + return ((netmask != 0u) ? (guint32) (32 - __builtin_ctz(ntohl(netmask))) : 0u); +} + +/** + * nm_utils_ip4_prefix_to_netmask: + * @prefix: a CIDR prefix + * + * Returns: the netmask represented by the prefix, in network byte order + **/ +guint32 +nm_utils_ip4_prefix_to_netmask(guint32 prefix) +{ + return _nm_utils_ip4_prefix_to_netmask(prefix); +} + +/** + * nm_utils_ip4_get_default_prefix: + * @ip: an IPv4 address (in network byte order) + * + * When the Internet was originally set up, various ranges of IP addresses were + * segmented into three network classes: A, B, and C. This function will return + * a prefix that is associated with the IP address specified defining where it + * falls in the predefined classes. + * + * Returns: the default class prefix for the given IP + **/ +/* The function is originally from ipcalc.c of Red Hat's initscripts. */ +guint32 +nm_utils_ip4_get_default_prefix(guint32 ip) +{ + return _nm_utils_ip4_get_default_prefix(ip); +} + +/** + * nm_utils_ip6_dns_to_variant: + * @dns: (type utf8): an array of IP address strings + * + * Utility function to convert an array of IP address strings int a #GVariant of + * type 'aay' representing an array of IPv6 addresses. + * + * If a string cannot be parsed, it will be silently ignored. + * + * Returns: (transfer none): a new floating #GVariant representing @dns. + **/ +GVariant * +nm_utils_ip6_dns_to_variant(char **dns) +{ + GVariantBuilder builder; + gsize i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aay")); + if (dns) { + for (i = 0; dns[i]; i++) { + struct in6_addr ip; + + if (inet_pton(AF_INET6, dns[i], &ip) != 1) + continue; + g_variant_builder_add(&builder, "@ay", nm_g_variant_new_ay_in6addr(&ip)); + } + } + return g_variant_builder_end(&builder); +} + +/** + * nm_utils_ip6_dns_from_variant: + * @value: a #GVariant of type 'aay' + * + * Utility function to convert a #GVariant of type 'aay' representing a list of + * IPv6 addresses into an array of IP address strings. Each "ay" entry must be + * a IPv6 address in binary form (16 bytes long). Invalid entries are silently + * ignored. + * + * Returns: (transfer full) (type utf8): a %NULL-terminated array of IP address strings. + **/ +char ** +nm_utils_ip6_dns_from_variant(GVariant *value) +{ + GVariantIter iter; + GVariant * ip_var; + char ** dns; + gsize i; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aay")), NULL); + + dns = g_new(char *, g_variant_n_children(value) + 1); + + g_variant_iter_init(&iter, value); + i = 0; + while (g_variant_iter_next(&iter, "@ay", &ip_var)) { + gsize length; + const struct in6_addr *ip = g_variant_get_fixed_array(ip_var, &length, 1); + + if (length == sizeof(struct in6_addr)) + dns[i++] = nm_utils_inet6_ntop_dup(ip); + + g_variant_unref(ip_var); + } + dns[i] = NULL; + + return dns; +} + +/** + * nm_utils_ip6_addresses_to_variant: + * @addresses: (element-type NMIPAddress): an array of #NMIPAddress objects + * @gateway: (allow-none): the gateway IP address + * + * Utility function to convert a #GPtrArray of #NMIPAddress objects representing + * IPv6 addresses into a #GVariant of type 'a(ayuay)' representing an array of + * NetworkManager IPv6 addresses (which are tuples of address, prefix, and + * gateway). The "gateway" field of the first address will get the value of + * @gateway (if non-%NULL). In all of the other addresses, that field will be + * all 0s. + * + * Returns: (transfer none): a new floating #GVariant representing @addresses. + **/ +GVariant * +nm_utils_ip6_addresses_to_variant(GPtrArray *addresses, const char *gateway) +{ + GVariantBuilder builder; + guint i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a(ayuay)")); + + if (addresses) { + for (i = 0; i < addresses->len; i++) { + NMIPAddress * addr = addresses->pdata[i]; + struct in6_addr address_bin; + struct in6_addr gateway_bin_data; + const struct in6_addr *gateway_bin; + + if (nm_ip_address_get_family(addr) != AF_INET6) + continue; + + nm_ip_address_get_address_binary(addr, &address_bin); + + gateway_bin = &in6addr_any; + if (gateway) { + if (inet_pton(AF_INET6, gateway, &gateway_bin_data) == 1) + gateway_bin = &gateway_bin_data; + gateway = NULL; + } + + g_variant_builder_add(&builder, + "(@ayu@ay)", + nm_g_variant_new_ay_in6addr(&address_bin), + (guint32) nm_ip_address_get_prefix(addr), + nm_g_variant_new_ay_in6addr(gateway_bin)); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * nm_utils_ip6_addresses_from_variant: + * @value: a #GVariant of type 'a(ayuay)' + * @out_gateway: (out) (allow-none) (transfer full): on return, will contain the IP gateway + * + * Utility function to convert a #GVariant of type 'a(ayuay)' representing a + * list of NetworkManager IPv6 addresses (which are tuples of address, prefix, + * and gateway) into a #GPtrArray of #NMIPAddress objects. The "gateway" field + * of the first address (if set) will be returned in @out_gateway; the "gateway" + * fields of the other addresses are ignored. + * + * Returns: (transfer full) (element-type NMIPAddress): a newly allocated + * #GPtrArray of #NMIPAddress objects + **/ +GPtrArray * +nm_utils_ip6_addresses_from_variant(GVariant *value, char **out_gateway) +{ + GVariantIter iter; + GVariant * addr_var, *gateway_var; + guint32 prefix; + GPtrArray * addresses; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("a(ayuay)")), NULL); + + if (out_gateway) + *out_gateway = NULL; + + g_variant_iter_init(&iter, value); + addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref); + + while (g_variant_iter_next(&iter, "(@ayu@ay)", &addr_var, &prefix, &gateway_var)) { + NMIPAddress * addr; + const struct in6_addr *addr_bytes, *gateway_bytes; + gsize addr_len, gateway_len; + GError * error = NULL; + + if (!g_variant_is_of_type(addr_var, G_VARIANT_TYPE_BYTESTRING) + || !g_variant_is_of_type(gateway_var, G_VARIANT_TYPE_BYTESTRING)) { + g_warning("%s: ignoring invalid IP6 address structure", __func__); + goto next; + } + + addr_bytes = g_variant_get_fixed_array(addr_var, &addr_len, 1); + if (addr_len != 16) { + g_warning("%s: ignoring invalid IP6 address of length %d", __func__, (int) addr_len); + goto next; + } + + addr = nm_ip_address_new_binary(AF_INET6, addr_bytes, prefix, &error); + if (addr) { + g_ptr_array_add(addresses, addr); + + if (out_gateway && !*out_gateway) { + gateway_bytes = g_variant_get_fixed_array(gateway_var, &gateway_len, 1); + if (gateway_len != 16) { + g_warning("%s: ignoring invalid IP6 address of length %d", + __func__, + (int) gateway_len); + goto next; + } + if (!IN6_IS_ADDR_UNSPECIFIED(gateway_bytes)) + *out_gateway = nm_utils_inet6_ntop_dup(gateway_bytes); + } + } else { + g_warning("Ignoring invalid IP6 address: %s", error->message); + g_clear_error(&error); + } + +next: + g_variant_unref(addr_var); + g_variant_unref(gateway_var); + } + + return addresses; +} + +/** + * nm_utils_ip6_routes_to_variant: + * @routes: (element-type NMIPRoute): an array of #NMIPRoute objects + * + * Utility function to convert a #GPtrArray of #NMIPRoute objects representing + * IPv6 routes into a #GVariant of type 'a(ayuayu)' representing an array of + * NetworkManager IPv6 routes (which are tuples of route, prefix, next hop, and + * metric). + * + * Returns: (transfer none): a new floating #GVariant representing @routes. + **/ +GVariant * +nm_utils_ip6_routes_to_variant(GPtrArray *routes) +{ + GVariantBuilder builder; + guint i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a(ayuayu)")); + + if (routes) { + for (i = 0; i < routes->len; i++) { + NMIPRoute * route = routes->pdata[i]; + struct in6_addr dest_bytes; + struct in6_addr next_hop_bytes; + guint32 metric; + + if (nm_ip_route_get_family(route) != AF_INET6) + continue; + + nm_ip_route_get_dest_binary(route, &dest_bytes); + nm_ip_route_get_next_hop_binary(route, &next_hop_bytes); + + /* The old routes format uses "0" for default, not "-1" */ + metric = NM_MAX(0, nm_ip_route_get_metric(route)); + + g_variant_builder_add(&builder, + "(@ayu@ayu)", + nm_g_variant_new_ay_in6addr(&dest_bytes), + (guint32) nm_ip_route_get_prefix(route), + nm_g_variant_new_ay_in6addr(&next_hop_bytes), + metric); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * nm_utils_ip6_routes_from_variant: + * @value: #GVariant of type 'a(ayuayu)' + * + * Utility function to convert a #GVariant of type 'a(ayuayu)' representing an + * array of NetworkManager IPv6 routes (which are tuples of route, prefix, next + * hop, and metric) into a #GPtrArray of #NMIPRoute objects. + * + * Returns: (transfer full) (element-type NMIPRoute): a newly allocated + * #GPtrArray of #NMIPRoute objects + **/ +GPtrArray * +nm_utils_ip6_routes_from_variant(GVariant *value) +{ + GPtrArray * routes; + GVariantIter iter; + GVariant * dest_var, *next_hop_var; + const struct in6_addr *dest, *next_hop; + gsize dest_len, next_hop_len; + guint32 prefix, metric; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("a(ayuayu)")), NULL); + + routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref); + + g_variant_iter_init(&iter, value); + while (g_variant_iter_next(&iter, "(@ayu@ayu)", &dest_var, &prefix, &next_hop_var, &metric)) { + NMIPRoute *route; + GError * error = NULL; + + if (!g_variant_is_of_type(dest_var, G_VARIANT_TYPE_BYTESTRING) + || !g_variant_is_of_type(next_hop_var, G_VARIANT_TYPE_BYTESTRING)) { + g_warning("%s: ignoring invalid IP6 address structure", __func__); + goto next; + } + + dest = g_variant_get_fixed_array(dest_var, &dest_len, 1); + if (dest_len != 16) { + g_warning("%s: ignoring invalid IP6 address of length %d", __func__, (int) dest_len); + goto next; + } + + next_hop = g_variant_get_fixed_array(next_hop_var, &next_hop_len, 1); + if (next_hop_len != 16) { + g_warning("%s: ignoring invalid IP6 address of length %d", + __func__, + (int) next_hop_len); + goto next; + } + + route = nm_ip_route_new_binary(AF_INET6, + dest, + prefix, + next_hop, + metric ? (gint64) metric : -1, + &error); + if (route) + g_ptr_array_add(routes, route); + else { + g_warning("Ignoring invalid IP6 route: %s", error->message); + g_clear_error(&error); + } + +next: + g_variant_unref(dest_var); + g_variant_unref(next_hop_var); + } + + return routes; +} + +/** + * nm_utils_ip_addresses_to_variant: + * @addresses: (element-type NMIPAddress): an array of #NMIPAddress objects + * + * Utility function to convert a #GPtrArray of #NMIPAddress objects representing + * IPv4 or IPv6 addresses into a #GVariant of type 'aa{sv}' representing an + * array of new-style NetworkManager IP addresses. All addresses will include + * "address" (an IP address string), and "prefix" (a uint). Some addresses may + * include additional attributes. + * + * Returns: (transfer none): a new floating #GVariant representing @addresses. + **/ +GVariant * +nm_utils_ip_addresses_to_variant(GPtrArray *addresses) +{ + GVariantBuilder builder; + guint i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + + if (addresses) { + for (i = 0; i < addresses->len; i++) { + NMIPAddress * addr = addresses->pdata[i]; + GVariantBuilder addr_builder; + gs_free const char **names = NULL; + guint j, len; + + g_variant_builder_init(&addr_builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&addr_builder, + "{sv}", + "address", + g_variant_new_string(nm_ip_address_get_address(addr))); + g_variant_builder_add(&addr_builder, + "{sv}", + "prefix", + g_variant_new_uint32(nm_ip_address_get_prefix(addr))); + + names = _nm_ip_address_get_attribute_names(addr, TRUE, &len); + for (j = 0; j < len; j++) { + g_variant_builder_add(&addr_builder, + "{sv}", + names[j], + nm_ip_address_get_attribute(addr, names[j])); + } + + g_variant_builder_add(&builder, "a{sv}", &addr_builder); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * nm_utils_ip_addresses_from_variant: + * @value: a #GVariant of type 'aa{sv}' + * @family: an IP address family + * + * Utility function to convert a #GVariant representing a list of new-style + * NetworkManager IPv4 or IPv6 addresses (as described in the documentation for + * nm_utils_ip_addresses_to_variant()) into a #GPtrArray of #NMIPAddress + * objects. + * + * Returns: (transfer full) (element-type NMIPAddress): a newly allocated + * #GPtrArray of #NMIPAddress objects + **/ +GPtrArray * +nm_utils_ip_addresses_from_variant(GVariant *value, int family) +{ + GPtrArray * addresses; + GVariantIter iter, attrs_iter; + GVariant * addr_var; + const char * ip; + guint32 prefix; + const char * attr_name; + GVariant * attr_val; + NMIPAddress *addr; + GError * error = NULL; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL); + + g_variant_iter_init(&iter, value); + addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref); + + while (g_variant_iter_next(&iter, "@a{sv}", &addr_var)) { + if (!g_variant_lookup(addr_var, "address", "&s", &ip) + || !g_variant_lookup(addr_var, "prefix", "u", &prefix)) { + g_warning("Ignoring invalid address"); + g_variant_unref(addr_var); + continue; + } + + addr = nm_ip_address_new(family, ip, prefix, &error); + if (!addr) { + g_warning("Ignoring invalid address: %s", error->message); + g_clear_error(&error); + g_variant_unref(addr_var); + continue; + } + + g_variant_iter_init(&attrs_iter, addr_var); + while (g_variant_iter_next(&attrs_iter, "{&sv}", &attr_name, &attr_val)) { + if (!NM_IN_STRSET(attr_name, "address", "prefix")) + nm_ip_address_set_attribute(addr, attr_name, attr_val); + g_variant_unref(attr_val); + } + + g_variant_unref(addr_var); + g_ptr_array_add(addresses, addr); + } + + return addresses; +} + +/** + * nm_utils_ip_routes_to_variant: + * @routes: (element-type NMIPRoute): an array of #NMIPRoute objects + * + * Utility function to convert a #GPtrArray of #NMIPRoute objects representing + * IPv4 or IPv6 routes into a #GVariant of type 'aa{sv}' representing an array + * of new-style NetworkManager IP routes (which are tuples of destination, + * prefix, next hop, metric, and additional attributes). + * + * Returns: (transfer none): a new floating #GVariant representing @routes. + **/ +GVariant * +nm_utils_ip_routes_to_variant(GPtrArray *routes) +{ + GVariantBuilder builder; + guint i; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + + if (routes) { + for (i = 0; i < routes->len; i++) { + NMIPRoute * route = routes->pdata[i]; + GVariantBuilder route_builder; + gs_free const char **names = NULL; + guint j, len; + + g_variant_builder_init(&route_builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&route_builder, + "{sv}", + "dest", + g_variant_new_string(nm_ip_route_get_dest(route))); + g_variant_builder_add(&route_builder, + "{sv}", + "prefix", + g_variant_new_uint32(nm_ip_route_get_prefix(route))); + if (nm_ip_route_get_next_hop(route)) { + g_variant_builder_add(&route_builder, + "{sv}", + "next-hop", + g_variant_new_string(nm_ip_route_get_next_hop(route))); + } + if (nm_ip_route_get_metric(route) != -1) { + g_variant_builder_add( + &route_builder, + "{sv}", + "metric", + g_variant_new_uint32((guint32) nm_ip_route_get_metric(route))); + } + + names = _nm_ip_route_get_attribute_names(route, TRUE, &len); + for (j = 0; j < len; j++) { + g_variant_builder_add(&route_builder, + "{sv}", + names[j], + nm_ip_route_get_attribute(route, names[j])); + } + + g_variant_builder_add(&builder, "a{sv}", &route_builder); + } + } + + return g_variant_builder_end(&builder); +} + +/** + * nm_utils_ip_routes_from_variant: + * @value: a #GVariant of type 'aa{sv}' + * @family: an IP address family + * + * Utility function to convert a #GVariant representing a list of new-style + * NetworkManager IPv4 or IPv6 addresses (which are tuples of destination, + * prefix, next hop, metric, and additional attributes) into a #GPtrArray of + * #NMIPRoute objects. + * + * Returns: (transfer full) (element-type NMIPRoute): a newly allocated + * #GPtrArray of #NMIPRoute objects + **/ +GPtrArray * +nm_utils_ip_routes_from_variant(GVariant *value, int family) +{ + GPtrArray * routes; + GVariantIter iter, attrs_iter; + GVariant * route_var; + const char * dest, *next_hop; + guint32 prefix, metric32; + gint64 metric; + const char * attr_name; + GVariant * attr_val; + NMIPRoute * route; + GError * error = NULL; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL); + + g_variant_iter_init(&iter, value); + routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref); + + while (g_variant_iter_next(&iter, "@a{sv}", &route_var)) { + if (!g_variant_lookup(route_var, "dest", "&s", &dest) + || !g_variant_lookup(route_var, "prefix", "u", &prefix)) { + g_warning("Ignoring invalid address"); + goto next; + } + if (!g_variant_lookup(route_var, "next-hop", "&s", &next_hop)) + next_hop = NULL; + if (g_variant_lookup(route_var, "metric", "u", &metric32)) + metric = metric32; + else + metric = -1; + + route = nm_ip_route_new(family, dest, prefix, next_hop, metric, &error); + if (!route) { + g_warning("Ignoring invalid route: %s", error->message); + g_clear_error(&error); + goto next; + } + + g_variant_iter_init(&attrs_iter, route_var); + while (g_variant_iter_next(&attrs_iter, "{&sv}", &attr_name, &attr_val)) { + if (!NM_IN_STRSET(attr_name, "dest", "prefix", "next-hop", "metric")) + nm_ip_route_set_attribute(route, attr_name, attr_val); + g_variant_unref(attr_val); + } + + g_ptr_array_add(routes, route); +next: + g_variant_unref(route_var); + } + + return routes; +} + +/*****************************************************************************/ + +static void +_string_append_tc_handle(GString *string, guint32 handle) +{ + g_string_append_printf(string, "%x:", TC_H_MAJ(handle) >> 16); + if (TC_H_MIN(handle) != TC_H_UNSPEC) + g_string_append_printf(string, "%x", TC_H_MIN(handle)); +} + +/** + * _nm_utils_string_append_tc_parent: + * @string: the string to write the parent handle to + * @prefix: optional prefix for the numeric handle + * @parent: the parent handle + * + * This is used to either write out the parent handle to the tc qdisc string + * or to pretty-format (use symbolic name for root) the key in keyfile. + * The presence of prefix determines which one is the case. + * + * Private API due to general ugliness and overall uselessness for anything + * sensible. + */ +void +_nm_utils_string_append_tc_parent(GString *string, const char *prefix, guint32 parent) +{ + if (parent == TC_H_ROOT) { + g_string_append(string, "root"); + } else { + if (prefix) { + if (parent == TC_H_INGRESS) + return; + g_string_append_printf(string, "%s ", prefix); + } + _string_append_tc_handle(string, parent); + } + + if (prefix) + g_string_append_c(string, ' '); +} + +/** + * _nm_utils_parse_tc_handle: + * @str: the string representation of a qdisc handle + * @error: location of the error + * + * Parses tc style handle number into a numeric representation. + * Don't use this, use nm_utils_tc_qdisc_from_str() instead. + */ +guint32 +_nm_utils_parse_tc_handle(const char *str, GError **error) +{ + gint64 maj; + gint64 min = 0; + const char *sep; + + nm_assert(str); + + maj = nm_g_ascii_strtoll(str, (char **) &sep, 0x10); + if (sep == str) + goto fail; + + sep = nm_str_skip_leading_spaces(sep); + + if (sep[0] == ':') { + const char *str2 = &sep[1]; + + min = nm_g_ascii_strtoll(str2, (char **) &sep, 0x10); + sep = nm_str_skip_leading_spaces(sep); + if (sep[0] != '\0') + goto fail; + } else if (sep[0] != '\0') + goto fail; + + if (maj <= 0 || maj > 0xffff || min < 0 || min > 0xffff + || !NM_STRCHAR_ALL(str, ch, (g_ascii_isxdigit(ch) || ch == ':' || g_ascii_isspace(ch)))) { + goto fail; + } + + return TC_H_MAKE(((guint32) maj) << 16, (guint32) min); +fail: + nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("'%s' is not a valid handle."), str); + return TC_H_UNSPEC; +} + +static const NMVariantAttributeSpec *const tc_object_attribute_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("root", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("parent", G_VARIANT_TYPE_STRING, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("handle", G_VARIANT_TYPE_STRING, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("kind", G_VARIANT_TYPE_STRING, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("", + G_VARIANT_TYPE_STRING, + .no_value = TRUE, + .consumes_rest = TRUE, ), + NULL, +}; + +static const NMVariantAttributeSpec *const tc_qdisc_sfq_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("quantum", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("perturb", G_VARIANT_TYPE_INT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("limit", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("divisor", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("flows", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("depth", G_VARIANT_TYPE_UINT32, ), + NULL, +}; + +static const NMVariantAttributeSpec *const tc_qdisc_tbf_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("rate", G_VARIANT_TYPE_UINT64, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("burst", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("limit", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("latency", G_VARIANT_TYPE_UINT32, ), + NULL, +}; + +static const NMVariantAttributeSpec *const tc_qdisc_fq_codel_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("limit", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("flows", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("target", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("interval", G_VARIANT_TYPE_UINT32, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("quantum", G_VARIANT_TYPE_UINT32, ), + + /* 0x83126E97u is not a valid value (it means "disabled"). We should reject that + * value. Or alternatively, reject all values >= MAX_INT(32). */ + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("ce_threshold", G_VARIANT_TYPE_UINT32, ), + + /* kernel clamps the value at 2^31. Possibly such values should be rejected from configuration + * as they cannot be configured. Leaving the attribute unspecified causes kernel to choose + * a default (currently 32MB). */ + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("memory_limit", G_VARIANT_TYPE_UINT32, ), + + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("ecn", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NULL, +}; + +typedef struct { + const char * kind; + const NMVariantAttributeSpec *const *attrs; +} NMQdiscAttributeSpec; + +static const NMQdiscAttributeSpec *const tc_qdisc_attribute_spec[] = { + &(const NMQdiscAttributeSpec){"fq_codel", tc_qdisc_fq_codel_spec}, + &(const NMQdiscAttributeSpec){"sfq", tc_qdisc_sfq_spec}, + &(const NMQdiscAttributeSpec){"tbf", tc_qdisc_tbf_spec}, + NULL, +}; + +/*****************************************************************************/ + +/** + * _nm_utils_string_append_tc_qdisc_rest: + * @string: the string to write the formatted qdisc to + * @qdisc: the %NMTCQdisc + * + * This formats the rest of the qdisc string but the parent. Useful to format + * the keyfile value and nowhere else. + * Use nm_utils_tc_qdisc_to_str() that also includes the parent instead. + */ +void +_nm_utils_string_append_tc_qdisc_rest(GString *string, NMTCQdisc *qdisc) +{ + guint32 handle = nm_tc_qdisc_get_handle(qdisc); + const char * kind = nm_tc_qdisc_get_kind(qdisc); + gs_free char *str = NULL; + + if (handle != TC_H_UNSPEC && !NM_IN_STRSET(kind, "ingress", "clsact")) { + g_string_append(string, "handle "); + _string_append_tc_handle(string, handle); + g_string_append_c(string, ' '); + } + + g_string_append(string, kind); + + str = nm_utils_format_variant_attributes(_nm_tc_qdisc_get_attributes(qdisc), ' ', ' '); + if (str) { + g_string_append_c(string, ' '); + g_string_append(string, str); + } +} + +/** + * nm_utils_tc_qdisc_to_str: + * @qdisc: the %NMTCQdisc + * @error: location of the error + * + * Turns the %NMTCQdisc into a tc style string representation of the queueing + * discipline. + * + * Returns: formatted string or %NULL + * + * Since: 1.12 + */ +char * +nm_utils_tc_qdisc_to_str(NMTCQdisc *qdisc, GError **error) +{ + GString *string; + + string = g_string_sized_new(60); + + _nm_utils_string_append_tc_parent(string, "parent", nm_tc_qdisc_get_parent(qdisc)); + _nm_utils_string_append_tc_qdisc_rest(string, qdisc); + + return g_string_free(string, FALSE); +} + +static gboolean +_tc_read_common_opts(const char *str, + guint32 * handle, + guint32 * parent, + char ** kind, + char ** rest, + GError ** error) +{ + gs_unref_hashtable GHashTable *ht = NULL; + GVariant * variant; + + ht = nm_utils_parse_variant_attributes(str, ' ', ' ', FALSE, tc_object_attribute_spec, error); + if (!ht) + return FALSE; + + if (g_hash_table_contains(ht, "root")) + *parent = TC_H_ROOT; + + variant = g_hash_table_lookup(ht, "parent"); + if (variant) { + if (*parent != TC_H_UNSPEC) { + g_set_error(error, + 1, + 0, + _("'%s' unexpected: parent already specified."), + g_variant_get_string(variant, NULL)); + return FALSE; + } + *parent = _nm_utils_parse_tc_handle(g_variant_get_string(variant, NULL), error); + if (*parent == TC_H_UNSPEC) + return FALSE; + } + + variant = g_hash_table_lookup(ht, "handle"); + if (variant) { + *handle = _nm_utils_parse_tc_handle(g_variant_get_string(variant, NULL), error); + if (*handle == TC_H_UNSPEC) + return FALSE; + if (TC_H_MIN(*handle)) { + g_set_error(error, + 1, + 0, + _("invalid handle: '%s'"), + g_variant_get_string(variant, NULL)); + return FALSE; + } + } + + variant = g_hash_table_lookup(ht, "kind"); + if (variant) { + *kind = g_variant_dup_string(variant, NULL); + if (NM_IN_STRSET(*kind, "ingress", "clsact")) { + if (*parent == TC_H_UNSPEC) + *parent = TC_H_INGRESS; + if (*handle == TC_H_UNSPEC) + *handle = TC_H_MAKE(TC_H_INGRESS, 0); + } + } + + if (*parent == TC_H_UNSPEC) { + if (*kind) { + g_free(*kind); + *kind = NULL; + } + g_set_error_literal(error, 1, 0, _("parent not specified.")); + return FALSE; + } + + variant = g_hash_table_lookup(ht, ""); + if (variant) + *rest = g_variant_dup_string(variant, NULL); + + return TRUE; +} + +/** + * nm_utils_tc_qdisc_from_str: + * @str: the string representation of a qdisc + * @error: location of the error + * + * Parses the tc style string qdisc representation of the queueing + * discipline to a %NMTCQdisc instance. Supports a subset of the tc language. + * + * Returns: the %NMTCQdisc or %NULL + * + * Since: 1.12 + */ +NMTCQdisc * +nm_utils_tc_qdisc_from_str(const char *str, GError **error) +{ + guint32 handle = TC_H_UNSPEC; + guint32 parent = TC_H_UNSPEC; + gs_free char * kind = NULL; + gs_free char * rest = NULL; + NMTCQdisc * qdisc = NULL; + gs_unref_hashtable GHashTable *options = NULL; + GHashTableIter iter; + gpointer key, value; + guint i; + + nm_assert(str); + nm_assert(!error || !*error); + + if (!_tc_read_common_opts(str, &handle, &parent, &kind, &rest, error)) + return NULL; + + for (i = 0; rest && tc_qdisc_attribute_spec[i]; i++) { + if (nm_streq(tc_qdisc_attribute_spec[i]->kind, kind)) { + options = nm_utils_parse_variant_attributes(rest, + ' ', + ' ', + FALSE, + tc_qdisc_attribute_spec[i]->attrs, + error); + if (!options) + return NULL; + break; + } + } + nm_clear_g_free(&rest); + + if (options) { + value = g_hash_table_lookup(options, ""); + if (value) + rest = g_variant_dup_string(value, NULL); + } + + if (rest) { + g_set_error(error, 1, 0, _("unsupported qdisc option: '%s'."), rest); + return NULL; + } + + qdisc = nm_tc_qdisc_new(kind, parent, error); + if (!qdisc) + return NULL; + + nm_tc_qdisc_set_handle(qdisc, handle); + + if (options) { + g_hash_table_iter_init(&iter, options); + while (g_hash_table_iter_next(&iter, &key, &value)) + nm_tc_qdisc_set_attribute(qdisc, key, g_variant_ref_sink(value)); + } + + return qdisc; +} + +/*****************************************************************************/ + +static const NMVariantAttributeSpec *const tc_action_simple_attribute_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("sdata", G_VARIANT_TYPE_BYTESTRING, ), + NULL, +}; + +static const NMVariantAttributeSpec *const tc_action_mirred_attribute_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("egress", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("ingress", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("mirror", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("redirect", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("dev", G_VARIANT_TYPE_STRING, ), + NULL, +}; + +static const NMVariantAttributeSpec *const tc_action_attribute_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("kind", G_VARIANT_TYPE_STRING, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("", + G_VARIANT_TYPE_STRING, + .no_value = TRUE, + .consumes_rest = TRUE, ), + NULL, +}; + +static gboolean +_string_append_tc_action(GString *string, NMTCAction *action, GError **error) +{ + const char * kind = nm_tc_action_get_kind(action); + gs_free char * str = NULL; + const NMVariantAttributeSpec *const *attrs; + + if (nm_streq(kind, "simple")) + attrs = tc_action_simple_attribute_spec; + else if (nm_streq(kind, "mirred")) + attrs = tc_action_mirred_attribute_spec; + else + attrs = NULL; + + g_string_append(string, kind); + + str = + _nm_utils_format_variant_attributes(_nm_tc_action_get_attributes(action), attrs, ' ', ' '); + if (str) { + g_string_append_c(string, ' '); + g_string_append(string, str); + } + + return TRUE; +} + +/** + * nm_utils_tc_action_to_str: + * @action: the %NMTCAction + * @error: location of the error + * + * Turns the %NMTCAction into a tc style string representation of the queueing + * discipline. + * + * Returns: formatted string or %NULL + * + * Since: 1.12 + */ +char * +nm_utils_tc_action_to_str(NMTCAction *action, GError **error) +{ + GString *string; + + string = g_string_sized_new(60); + if (!_string_append_tc_action(string, action, error)) { + g_string_free(string, TRUE); + return NULL; + } + + return g_string_free(string, FALSE); +} + +/** + * nm_utils_tc_action_from_str: + * @str: the string representation of a action + * @error: location of the error + * + * Parses the tc style string action representation of the queueing + * discipline to a %NMTCAction instance. Supports a subset of the tc language. + * + * Returns: the %NMTCAction or %NULL + * + * Since: 1.12 + */ +NMTCAction * +nm_utils_tc_action_from_str(const char *str, GError **error) +{ + const char * kind = NULL; + const char * rest = NULL; + nm_auto_unref_tc_action NMTCAction *action = NULL; + gs_unref_hashtable GHashTable *ht = NULL; + gs_unref_hashtable GHashTable * options = NULL; + GVariant * variant; + const NMVariantAttributeSpec *const *attrs; + + nm_assert(str); + nm_assert(!error || !*error); + + ht = nm_utils_parse_variant_attributes(str, ' ', ' ', FALSE, tc_action_attribute_spec, error); + if (!ht) + return FALSE; + + variant = g_hash_table_lookup(ht, "kind"); + if (variant) { + kind = g_variant_get_string(variant, NULL); + } else { + g_set_error_literal(error, 1, 0, _("action name missing.")); + return NULL; + } + + kind = g_variant_get_string(variant, NULL); + if (nm_streq(kind, "simple")) + attrs = tc_action_simple_attribute_spec; + else if (nm_streq(kind, "mirred")) + attrs = tc_action_mirred_attribute_spec; + else + attrs = NULL; + + variant = g_hash_table_lookup(ht, ""); + if (variant) + rest = g_variant_get_string(variant, NULL); + + action = nm_tc_action_new(kind, error); + if (!action) + return NULL; + + if (rest) { + GHashTableIter iter; + gpointer key, value; + + if (!attrs) { + g_set_error(error, 1, 0, _("unsupported action option: '%s'."), rest); + return NULL; + } + + options = nm_utils_parse_variant_attributes(rest, ' ', ' ', FALSE, attrs, error); + if (!options) + return NULL; + + g_hash_table_iter_init(&iter, options); + while (g_hash_table_iter_next(&iter, &key, &value)) + nm_tc_action_set_attribute(action, key, g_variant_ref_sink(value)); + } + + return g_steal_pointer(&action); +} + +/*****************************************************************************/ + +/** + * _nm_utils_string_append_tc_tfilter_rest: + * @string: the string to write the formatted tfilter to + * @tfilter: the %NMTCTfilter + * + * This formats the rest of the tfilter string but the parent. Useful to format + * the keyfile value and nowhere else. + * Use nm_utils_tc_tfilter_to_str() that also includes the parent instead. + */ +gboolean +_nm_utils_string_append_tc_tfilter_rest(GString *string, NMTCTfilter *tfilter, GError **error) +{ + guint32 handle = nm_tc_tfilter_get_handle(tfilter); + const char *kind = nm_tc_tfilter_get_kind(tfilter); + NMTCAction *action; + + if (handle != TC_H_UNSPEC) { + g_string_append(string, "handle "); + _string_append_tc_handle(string, handle); + g_string_append_c(string, ' '); + } + + g_string_append(string, kind); + + action = nm_tc_tfilter_get_action(tfilter); + if (action) { + g_string_append(string, " action "); + if (!_string_append_tc_action(string, action, error)) + return FALSE; + } + + return TRUE; +} + +/** + * nm_utils_tc_tfilter_to_str: + * @tfilter: the %NMTCTfilter + * @error: location of the error + * + * Turns the %NMTCTfilter into a tc style string representation of the queueing + * discipline. + * + * Returns: formatted string or %NULL + * + * Since: 1.12 + */ +char * +nm_utils_tc_tfilter_to_str(NMTCTfilter *tfilter, GError **error) +{ + GString *string; + + string = g_string_sized_new(60); + + _nm_utils_string_append_tc_parent(string, "parent", nm_tc_tfilter_get_parent(tfilter)); + if (!_nm_utils_string_append_tc_tfilter_rest(string, tfilter, error)) { + g_string_free(string, TRUE); + return NULL; + } + + return g_string_free(string, FALSE); +} + +static const NMVariantAttributeSpec *const tc_tfilter_attribute_spec[] = { + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("action", G_VARIANT_TYPE_BOOLEAN, .no_value = TRUE, ), + NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("", + G_VARIANT_TYPE_STRING, + .no_value = TRUE, + .consumes_rest = TRUE, ), + NULL, +}; + +/** + * nm_utils_tc_tfilter_from_str: + * @str: the string representation of a tfilter + * @error: location of the error + * + * Parses the tc style string tfilter representation of the queueing + * discipline to a %NMTCTfilter instance. Supports a subset of the tc language. + * + * Returns: the %NMTCTfilter or %NULL + * + * Since: 1.12 + */ +NMTCTfilter * +nm_utils_tc_tfilter_from_str(const char *str, GError **error) +{ + guint32 handle = TC_H_UNSPEC; + guint32 parent = TC_H_UNSPEC; + gs_free char * kind = NULL; + gs_free char * rest = NULL; + nm_auto_unref_tc_action NMTCAction *action = NULL; + const char * extra_opts = NULL; + NMTCTfilter * tfilter = NULL; + gs_unref_hashtable GHashTable *ht = NULL; + GVariant * variant; + + nm_assert(str); + nm_assert(!error || !*error); + + if (!_tc_read_common_opts(str, &handle, &parent, &kind, &rest, error)) + return NULL; + + if (rest) { + ht = nm_utils_parse_variant_attributes(rest, + ' ', + ' ', + FALSE, + tc_tfilter_attribute_spec, + error); + if (!ht) + return NULL; + + variant = g_hash_table_lookup(ht, ""); + if (variant) + extra_opts = g_variant_get_string(variant, NULL); + + if (g_hash_table_contains(ht, "action")) { + action = nm_utils_tc_action_from_str(extra_opts, error); + if (!action) { + g_prefix_error(error, _("invalid action: ")); + return NULL; + } + } else { + g_set_error(error, 1, 0, _("unsupported tfilter option: '%s'."), rest); + return NULL; + } + } + + tfilter = nm_tc_tfilter_new(kind, parent, error); + if (!tfilter) + return NULL; + + nm_tc_tfilter_set_handle(tfilter, handle); + if (action) + nm_tc_tfilter_set_action(tfilter, action); + + return tfilter; +} + +/*****************************************************************************/ + +extern const NMVariantAttributeSpec *const _nm_sriov_vf_attribute_spec[]; + +/** + * nm_utils_sriov_vf_to_str: + * @vf: the %NMSriovVF + * @omit_index: if %TRUE, the VF index will be omitted from output string + * @error: (out) (allow-none): location to store the error on failure + * + * Converts a SR-IOV virtual function object to its string representation. + * + * Returns: a newly allocated string or %NULL on error + * + * Since: 1.14 + */ +char * +nm_utils_sriov_vf_to_str(const NMSriovVF *vf, gboolean omit_index, GError **error) +{ + gs_free NMUtilsNamedValue *values = NULL; + gs_free const char ** names = NULL; + const guint * vlan_ids; + guint num_vlans, num_attrs; + guint i; + GString * str; + + str = g_string_new(""); + if (!omit_index) + g_string_append_printf(str, "%u", nm_sriov_vf_get_index(vf)); + + names = nm_sriov_vf_get_attribute_names(vf); + num_attrs = names ? g_strv_length((char **) names) : 0; + values = g_new0(NMUtilsNamedValue, num_attrs); + + for (i = 0; i < num_attrs; i++) { + values[i].name = names[i]; + values[i].value_ptr = nm_sriov_vf_get_attribute(vf, names[i]); + } + + if (num_attrs > 0) { + if (!omit_index) + g_string_append_c(str, ' '); + _nm_utils_format_variant_attributes_full(str, values, num_attrs, NULL, ' ', '='); + } + + vlan_ids = nm_sriov_vf_get_vlan_ids(vf, &num_vlans); + if (num_vlans != 0) { + g_string_append(str, " vlans"); + for (i = 0; i < num_vlans; i++) { + guint32 qos; + NMSriovVFVlanProtocol protocol; + + qos = nm_sriov_vf_get_vlan_qos(vf, vlan_ids[i]); + protocol = nm_sriov_vf_get_vlan_protocol(vf, vlan_ids[i]); + + g_string_append_c(str, i == 0 ? '=' : ';'); + + g_string_append_printf(str, "%u", vlan_ids[i]); + + if (qos != 0 || protocol != NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q) { + g_string_append_printf(str, + ".%u%s", + (unsigned) qos, + protocol == NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q ? "" : ".ad"); + } + } + } + + return g_string_free(str, FALSE); +} + +gboolean +_nm_sriov_vf_parse_vlans(NMSriovVF *vf, const char *str, GError **error) +{ + gs_free const char **vlans = NULL; + guint i; + + vlans = nm_utils_strsplit_set(str, ";"); + if (!vlans) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "empty VF VLAN"); + return FALSE; + } + + for (i = 0; vlans[i]; i++) { + gs_strfreev char **params = NULL; + guint id = G_MAXUINT; + gint64 qos = -1; + + /* we accept leading/trailing whitespace around vlans[1]. Hence + * the nm_str_skip_leading_spaces() and g_strchomp() below. + * + * However, we don't accept any whitespace inside the specifier. + * Hence the NM_STRCHAR_ALL() checks. */ + + params = g_strsplit(nm_str_skip_leading_spaces(vlans[i]), ".", 3); + if (!params || !params[0] || *params[0] == '\0') { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "empty VF VLAN"); + return FALSE; + } + + if (!params[1]) + g_strchomp(params[0]); + if (NM_STRCHAR_ALL(params[0], ch, ch == 'x' || g_ascii_isdigit(ch))) + id = _nm_utils_ascii_str_to_int64(params[0], 0, 0, 4095, G_MAXUINT); + if (id == G_MAXUINT) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VF VLAN id '%s'", + params[0]); + return FALSE; + } + if (!nm_sriov_vf_add_vlan(vf, id)) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "duplicate VLAN id %u", + id); + return FALSE; + } + + if (!params[1]) + continue; + + if (!params[2]) + g_strchomp(params[1]); + if (NM_STRCHAR_ALL(params[1], ch, ch == 'x' || g_ascii_isdigit(ch))) + qos = _nm_utils_ascii_str_to_int64(params[1], 0, 0, G_MAXUINT32, -1); + if (qos == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VF VLAN QoS '%s'", + params[1]); + return FALSE; + } + nm_sriov_vf_set_vlan_qos(vf, id, qos); + + if (!params[2]) + continue; + + g_strchomp(params[2]); + + if (nm_streq(params[2], "ad")) + nm_sriov_vf_set_vlan_protocol(vf, id, NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD); + else if (nm_streq(params[2], "q")) + nm_sriov_vf_set_vlan_protocol(vf, id, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + else { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid VF VLAN protocol '%s'", + params[2]); + return FALSE; + } + } + + return TRUE; +} + +/** + * nm_utils_sriov_vf_from_str: + * @str: the input string + * @error: (out) (allow-none): location to store the error on failure + * + * Converts a string to a SR-IOV virtual function object. + * + * Returns: (transfer full): the virtual function object + * + * Since: 1.14 + */ +NMSriovVF * +nm_utils_sriov_vf_from_str(const char *str, GError **error) +{ + gs_free char *index_free = NULL; + const char * detail; + + g_return_val_if_fail(str, NULL); + g_return_val_if_fail(!error || !*error, NULL); + + while (*str == ' ') + str++; + + detail = strchr(str, ' '); + if (detail) { + str = nm_strndup_a(200, str, detail - str, &index_free); + detail++; + } + + return _nm_utils_sriov_vf_from_strparts(str, detail, FALSE, error); +} + +NMSriovVF * +_nm_utils_sriov_vf_from_strparts(const char *index, + const char *detail, + gboolean ignore_unknown, + GError ** error) +{ + NMSriovVF * vf; + guint32 n_index; + GHashTableIter iter; + char * key; + GVariant * variant; + gs_unref_hashtable GHashTable *ht = NULL; + + n_index = _nm_utils_ascii_str_to_int64(index, 10, 0, G_MAXUINT32, 0); + if (errno) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + "invalid index"); + return NULL; + } + + vf = nm_sriov_vf_new(n_index); + if (detail) { + ht = nm_utils_parse_variant_attributes(detail, + ' ', + '=', + ignore_unknown, + _nm_sriov_vf_attribute_spec, + error); + if (!ht) { + nm_sriov_vf_unref(vf); + return NULL; + } + + if ((variant = g_hash_table_lookup(ht, "vlans"))) { + if (!_nm_sriov_vf_parse_vlans(vf, g_variant_get_string(variant, NULL), error)) { + nm_sriov_vf_unref(vf); + return NULL; + } + g_hash_table_remove(ht, "vlans"); + } + + g_hash_table_iter_init(&iter, ht); + while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &variant)) + nm_sriov_vf_set_attribute(vf, key, g_variant_ref_sink(variant)); + } + + return vf; +} + +/*****************************************************************************/ + +/** + * nm_utils_uuid_generate: + * + * Returns: a newly allocated UUID suitable for use as the #NMSettingConnection + * object's #NMSettingConnection:id: property. Should be freed with g_free() + **/ +char * +nm_utils_uuid_generate(void) +{ + return nm_uuid_generate_random_str_malloc(); +} + +/*****************************************************************************/ + +static gboolean +file_has_extension(const char *filename, const char *extensions[]) +{ + const char *ext; + gsize i; + + ext = strrchr(filename, '.'); + if (!ext) + return FALSE; + + for (i = 0; extensions[i]; i++) { + if (!g_ascii_strcasecmp(ext, extensions[i])) + return TRUE; + } + + return FALSE; +} + +/** + * nm_utils_file_is_certificate: + * @filename: name of the file to test + * + * Tests if @filename has a valid extension for an X.509 certificate file + * (".cer", ".crt", ".der", or ".pem"), and contains a certificate in a format + * recognized by NetworkManager. + * + * Returns: %TRUE if the file is a certificate, %FALSE if it is not + **/ +gboolean +nm_utils_file_is_certificate(const char *filename) +{ + const char * extensions[] = {".der", ".pem", ".crt", ".cer", NULL}; + NMCryptoFileFormat file_format; + + g_return_val_if_fail(filename != NULL, FALSE); + + if (!file_has_extension(filename, extensions)) + return FALSE; + + if (!nm_crypto_load_and_verify_certificate(filename, &file_format, NULL, NULL)) + return FALSE; + return file_format = NM_CRYPTO_FILE_FORMAT_X509; +} + +/** + * nm_utils_file_is_private_key: + * @filename: name of the file to test + * @out_encrypted: (out): on return, whether the file is encrypted + * + * Tests if @filename has a valid extension for an X.509 private key file + * (".der", ".key", ".pem", or ".p12"), and contains a private key in a format + * recognized by NetworkManager. + * + * Returns: %TRUE if the file is a private key, %FALSE if it is not + **/ +gboolean +nm_utils_file_is_private_key(const char *filename, gboolean *out_encrypted) +{ + const char *extensions[] = {".der", ".pem", ".p12", ".key", NULL}; + + g_return_val_if_fail(filename != NULL, FALSE); + + NM_SET_OUT(out_encrypted, FALSE); + if (!file_has_extension(filename, extensions)) + return FALSE; + + return nm_crypto_verify_private_key(filename, NULL, out_encrypted, NULL) + != NM_CRYPTO_FILE_FORMAT_UNKNOWN; +} + +/** + * nm_utils_file_is_pkcs12: + * @filename: name of the file to test + * + * Tests if @filename is a PKCS#12 file. + * + * Returns: %TRUE if the file is PKCS#12, %FALSE if it is not + **/ +gboolean +nm_utils_file_is_pkcs12(const char *filename) +{ + g_return_val_if_fail(filename != NULL, FALSE); + + return nm_crypto_is_pkcs12_file(filename, NULL); +} + +/*****************************************************************************/ + +gboolean +_nm_utils_check_file(const char * filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + struct stat * out_st, + GError ** error) +{ + struct stat st_backup; + + if (!out_st) + out_st = &st_backup; + + if (stat(filename, out_st) != 0) { + int errsv = errno; + + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("failed stat file %s: %s"), + filename, + nm_strerror_native(errsv)); + return FALSE; + } + + /* ignore non-files. */ + if (!S_ISREG(out_st->st_mode)) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("not a file (%s)"), + filename); + return FALSE; + } + + /* with check_owner enabled, check that the file belongs to the + * owner or root. */ + if (check_owner >= 0 && (out_st->st_uid != 0 && (gint64) out_st->st_uid != check_owner)) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("invalid file owner %d for %s"), + out_st->st_uid, + filename); + return FALSE; + } + + /* with check_owner enabled, check that the file cannot be modified + * by other users (except root). */ + if (check_owner >= 0 && NM_FLAGS_ANY(out_st->st_mode, S_IWGRP | S_IWOTH | S_ISUID)) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("file permissions for %s"), + filename); + return FALSE; + } + + if (check_file && !check_file(filename, out_st, user_data, error)) { + if (error && !*error) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("reject %s"), + filename); + } + return FALSE; + } + + return TRUE; +} + +gboolean +_nm_utils_check_module_file(const char * name, + int check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError ** error) +{ + if (!g_path_is_absolute(name)) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("path is not absolute (%s)"), + name); + return FALSE; + } + + /* Set special error code if the file doesn't exist. + * The VPN package might be split into separate packages, + * so it could be correct that the plugin file is missing. + * + * Note that nm-applet checks for this error code to fail + * gracefully. */ + if (!g_file_test(name, G_FILE_TEST_EXISTS)) { + g_set_error(error, + G_FILE_ERROR, + G_FILE_ERROR_NOENT, + _("Plugin file does not exist (%s)"), + name); + return FALSE; + } + + if (!g_file_test(name, G_FILE_TEST_IS_REGULAR)) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("Plugin is not a valid file (%s)"), + name); + return FALSE; + } + + if (g_str_has_suffix(name, ".la")) { + /* g_module_open() treats files that end with .la special. + * We don't want to parse the libtool archive. Just error out. */ + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("libtool archives are not supported (%s)"), + name); + return FALSE; + } + + return _nm_utils_check_file(name, check_owner, check_file, user_data, NULL, error); +} + +/*****************************************************************************/ + +/** + * nm_utils_file_search_in_paths: + * @progname: the helper program name, like "iptables" + * Must be a non-empty string, without path separator (/). + * @try_first: (allow-none): a custom path to try first before searching. + * It is silently ignored if it is empty or not an absolute path. + * @paths: (allow-none): a %NULL terminated list of search paths. + * Can be empty or %NULL, in which case only @try_first is checked. + * @file_test_flags: the flags passed to g_file_test() when searching + * for @progname. Set it to 0 to skip the g_file_test(). + * @predicate: (scope call): if given, pass the file name to this function + * for additional checks. This check is performed after the check for + * @file_test_flags. You cannot omit both @file_test_flags and @predicate. + * @user_data: (closure) (allow-none): user data for @predicate function. + * @error: (allow-none): on failure, set a "not found" error %G_IO_ERROR %G_IO_ERROR_NOT_FOUND. + * + * Searches for a @progname file in a list of search @paths. + * + * Returns: (transfer none): the full path to the helper, if found, or %NULL if not found. + * The returned string is not owned by the caller, but later + * invocations of the function might overwrite it. + */ +const char * +nm_utils_file_search_in_paths(const char * progname, + const char * try_first, + const char *const * paths, + GFileTest file_test_flags, + NMUtilsFileSearchInPathsPredicate predicate, + gpointer user_data, + GError ** error) +{ + g_return_val_if_fail(!error || !*error, NULL); + g_return_val_if_fail(progname && progname[0] && !strchr(progname, '/'), NULL); + g_return_val_if_fail(file_test_flags || predicate, NULL); + + /* Only consider @try_first if it is a valid, absolute path. This makes + * it simpler to pass in a path from configure checks. */ + if (try_first && try_first[0] == '/' + && (file_test_flags == 0 || g_file_test(try_first, file_test_flags)) + && (!predicate || predicate(try_first, user_data))) + return g_intern_string(try_first); + + if (paths && paths[0]) { + nm_auto_str_buf NMStrBuf strbuf = + NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE); + + for (; *paths; paths++) { + const char *path = *paths; + const char *s; + + if (!path[0]) + continue; + + nm_str_buf_reset(&strbuf); + nm_str_buf_append(&strbuf, path); + nm_str_buf_ensure_trailing_c(&strbuf, '/'); + s = nm_str_buf_append0(&strbuf, progname); + + if ((file_test_flags == 0 || g_file_test(s, file_test_flags)) + && (!predicate || predicate(s, user_data))) + return g_intern_string(s); + } + } + + g_set_error(error, + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("Could not find \"%s\" binary"), + progname); + return NULL; +} + +/*****************************************************************************/ + +/* Band, channel/frequency stuff for wireless */ +struct cf_pair { + guint32 chan; + guint32 freq; +}; + +static const struct cf_pair a_table[] = { + /* A band */ + {7, 5035}, {8, 5040}, {9, 5045}, {11, 5055}, {12, 5060}, {16, 5080}, {34, 5170}, + {36, 5180}, {38, 5190}, {40, 5200}, {42, 5210}, {44, 5220}, {46, 5230}, {48, 5240}, + {50, 5250}, {52, 5260}, {56, 5280}, {58, 5290}, {60, 5300}, {64, 5320}, {100, 5500}, + {104, 5520}, {108, 5540}, {112, 5560}, {116, 5580}, {120, 5600}, {124, 5620}, {128, 5640}, + {132, 5660}, {136, 5680}, {140, 5700}, {149, 5745}, {152, 5760}, {153, 5765}, {157, 5785}, + {160, 5800}, {161, 5805}, {165, 5825}, {183, 4915}, {184, 4920}, {185, 4925}, {187, 4935}, + {188, 4945}, {192, 4960}, {196, 4980}, {0, 0}}; + +static const guint a_table_freqs[G_N_ELEMENTS(a_table)] = { + /* A band */ + 5035, 5040, 5045, 5055, 5060, 5080, 5170, 5180, 5190, 5200, 5210, 5220, 5230, 5240, 5250, 5260, + 5280, 5290, 5300, 5320, 5500, 5520, 5540, 5560, 5580, 5600, 5620, 5640, 5660, 5680, 5700, 5745, + 5760, 5765, 5785, 5800, 5805, 5825, 4915, 4920, 4925, 4935, 4945, 4960, 4980, 0, +}; + +static const struct cf_pair bg_table[] = { + /* B/G band */ + {1, 2412}, + {2, 2417}, + {3, 2422}, + {4, 2427}, + {5, 2432}, + {6, 2437}, + {7, 2442}, + {8, 2447}, + {9, 2452}, + {10, 2457}, + {11, 2462}, + {12, 2467}, + {13, 2472}, + {14, 2484}, + {0, 0}}; + +static const guint bg_table_freqs[G_N_ELEMENTS(bg_table)] = { + /* B/G band */ + 2412, + 2417, + 2422, + 2427, + 2432, + 2437, + 2442, + 2447, + 2452, + 2457, + 2462, + 2467, + 2472, + 2484, + 0, +}; + +/** + * nm_utils_wifi_freq_to_channel: + * @freq: frequency + * + * Utility function to translate a Wi-Fi frequency to its corresponding channel. + * + * Returns: the channel represented by the frequency or 0 + **/ +guint32 +nm_utils_wifi_freq_to_channel(guint32 freq) +{ + int i = 0; + + if (freq > 4900) { + while (a_table[i].freq && (a_table[i].freq != freq)) + i++; + return a_table[i].chan; + } + + while (bg_table[i].freq && (bg_table[i].freq != freq)) + i++; + return bg_table[i].chan; +} + +/** + * nm_utils_wifi_freq_to_band: + * @freq: frequency + * + * Utility function to translate a Wi-Fi frequency to its corresponding band. + * + * Returns: the band containing the frequency or NULL if freq is invalid + **/ +const char * +nm_utils_wifi_freq_to_band(guint32 freq) +{ + if (freq >= 4915 && freq <= 5825) + return "a"; + else if (freq >= 2412 && freq <= 2484) + return "bg"; + + return NULL; +} + +/** + * nm_utils_wifi_channel_to_freq: + * @channel: channel + * @band: frequency band for wireless ("a" or "bg") + * + * Utility function to translate a Wi-Fi channel to its corresponding frequency. + * + * Returns: the frequency represented by the channel of the band, + * or -1 when the freq is invalid, or 0 when the band + * is invalid + **/ +guint32 +nm_utils_wifi_channel_to_freq(guint32 channel, const char *band) +{ + int i; + + g_return_val_if_fail(band, 0); + + if (nm_streq(band, "a")) { + for (i = 0; a_table[i].chan; i++) { + if (a_table[i].chan == channel) + return a_table[i].freq; + } + return ((guint32) -1); + } + + if (nm_streq(band, "bg")) { + for (i = 0; bg_table[i].chan; i++) { + if (bg_table[i].chan == channel) + return bg_table[i].freq; + } + return ((guint32) -1); + } + + return 0; +} + +/** + * nm_utils_wifi_find_next_channel: + * @channel: current channel + * @direction: whether going downward (0 or less) or upward (1 or more) + * @band: frequency band for wireless ("a" or "bg") + * + * Utility function to find out next/previous Wi-Fi channel for a channel. + * + * Returns: the next channel in the specified direction or 0 + **/ +guint32 +nm_utils_wifi_find_next_channel(guint32 channel, int direction, char *band) +{ + size_t a_size = G_N_ELEMENTS(a_table); + size_t bg_size = G_N_ELEMENTS(bg_table); + const struct cf_pair *pair; + + if (nm_streq(band, "a")) { + if (channel < a_table[0].chan) + return a_table[0].chan; + if (channel > a_table[a_size - 2].chan) + return a_table[a_size - 2].chan; + pair = &a_table[0]; + } else if (nm_streq(band, "bg")) { + if (channel < bg_table[0].chan) + return bg_table[0].chan; + if (channel > bg_table[bg_size - 2].chan) + return bg_table[bg_size - 2].chan; + pair = &bg_table[0]; + } else + g_return_val_if_reached(0); + + while (pair->chan) { + if (channel == pair->chan) + return channel; + if ((channel < (pair + 1)->chan) && (channel > pair->chan)) { + if (direction > 0) + return (pair + 1)->chan; + else + return pair->chan; + } + pair++; + } + return 0; +} + +/** + * nm_utils_wifi_is_channel_valid: + * @channel: channel + * @band: frequency band for wireless ("a" or "bg") + * + * Utility function to verify Wi-Fi channel validity. + * + * Returns: %TRUE or %FALSE + **/ +gboolean +nm_utils_wifi_is_channel_valid(guint32 channel, const char *band) +{ + guint32 freq; + + freq = nm_utils_wifi_channel_to_freq(channel, band); + + return !NM_IN_SET(freq, 0u, (guint32) -1); +} + +#define _nm_assert_wifi_freqs(table, table_freqs) \ + G_STMT_START \ + { \ + if (NM_MORE_ASSERT_ONCE(5)) { \ + int i, j; \ + \ + G_STATIC_ASSERT(G_N_ELEMENTS(table) > 0); \ + G_STATIC_ASSERT(G_N_ELEMENTS(table) == G_N_ELEMENTS(table_freqs)); \ + \ + for (i = 0; i < (int) G_N_ELEMENTS(table); i++) { \ + nm_assert((i == G_N_ELEMENTS(table) - 1) == (table[i].chan == 0)); \ + nm_assert((i == G_N_ELEMENTS(table) - 1) == (table[i].freq == 0)); \ + nm_assert(table[i].freq == table_freqs[i]); \ + for (j = 0; j < i; j++) { \ + nm_assert(table[j].chan != table[i].chan); \ + nm_assert(table[j].freq != table[i].freq); \ + } \ + } \ + } \ + } \ + G_STMT_END + +/** + * nm_utils_wifi_2ghz_freqs: + * + * Utility function to return 2.4 GHz Wi-Fi frequencies (802.11bg band). + * + * Returns: zero-terminated array of frequencies numbers (in MHz) + * + * Since: 1.2 + **/ +const guint * +nm_utils_wifi_2ghz_freqs(void) +{ + _nm_assert_wifi_freqs(bg_table, bg_table_freqs); + return bg_table_freqs; +} + +/** + * nm_utils_wifi_5ghz_freqs: + * + * Utility function to return 5 GHz Wi-Fi frequencies (802.11a band). + * + * Returns: zero-terminated array of frequencies numbers (in MHz) + * + * Since: 1.2 + **/ +const guint * +nm_utils_wifi_5ghz_freqs(void) +{ + _nm_assert_wifi_freqs(a_table, a_table_freqs); + return a_table_freqs; +} + +/** + * nm_utils_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. + * + * Previous versions used to take a guess at the terminal type and possibly + * return a wide UTF-8 encoded string. Now it always returns a 7-bit + * clean strings of one to 0 to 4 asterisks. Users that actually need + * the functionality are encouraged to make their implementations instead. + * + * Returns: the graphical representation of the access point strength + */ +const char * +nm_utils_wifi_strength_bars(guint8 strength) +{ + if (strength > 80) + return "****"; + else if (strength > 55) + return "*** "; + else if (strength > 30) + return "** "; + else if (strength > 5) + return "* "; + else + return " "; +} + +/** + * nm_utils_hwaddr_len: + * @type: the type of address; either ARPHRD_ETHER or + * ARPHRD_INFINIBAND + * + * Returns the length in octets of a hardware address of type @type. + * + * Before 1.28, it was an error to call this function with any value other than + * ARPHRD_ETHER or ARPHRD_INFINIBAND. + * + * Return value: the length or zero if the type is unrecognized. + */ +gsize +nm_utils_hwaddr_len(int type) +{ + switch (type) { + case ARPHRD_ETHER: + return ETH_ALEN; + case ARPHRD_INFINIBAND: + return INFINIBAND_ALEN; + default: + return 0; + } +} + +/** + * nm_utils_hexstr2bin: + * @hex: a string of hexadecimal characters with optional ':' separators + * + * Converts a hexadecimal string @hex into an array of bytes. The optional + * separator ':' may be used between single or pairs of hexadecimal characters, + * eg "00:11" or "0:1". Any "0x" at the beginning of @hex is ignored. @hex + * may not start or end with ':'. + * + * Return value: (transfer full): the converted bytes, or %NULL on error + */ +GBytes * +nm_utils_hexstr2bin(const char *hex) +{ + guint8 *buffer; + gsize len; + + buffer = nm_utils_hexstr2bin_alloc(hex, TRUE, FALSE, ":", 0, &len); + if (!buffer) + return NULL; + buffer = g_realloc(buffer, len); + return g_bytes_new_take(buffer, len); +} + +/** + * nm_utils_hwaddr_atoba: + * @asc: the ASCII representation of a hardware address + * @length: the expected length in bytes of the result + * + * Parses @asc and converts it to binary form in a #GByteArray. See + * nm_utils_hwaddr_aton() if you don't want a #GByteArray. + * + * Return value: (transfer full): a new #GByteArray, or %NULL if @asc couldn't + * be parsed + */ +GByteArray * +nm_utils_hwaddr_atoba(const char *asc, gsize length) +{ + GByteArray *ba; + + g_return_val_if_fail(asc, NULL); + g_return_val_if_fail(length > 0 && length <= NM_UTILS_HWADDR_LEN_MAX, NULL); + + ba = g_byte_array_sized_new(length); + g_byte_array_set_size(ba, length); + if (!_nm_utils_hwaddr_aton_exact(asc, ba->data, length)) + goto fail; + + return ba; +fail: + g_byte_array_unref(ba); + return NULL; +} + +/** + * nm_utils_hwaddr_aton: + * @asc: the ASCII representation of a hardware address + * @buffer: (type guint8) (array length=length): buffer to store the result into + * @length: the expected length in bytes of the result and + * the size of the buffer in bytes. + * + * Parses @asc and converts it to binary form in @buffer. + * Bytes in @asc can be separated by colons (:), or hyphens (-), but not mixed. + * + * Return value: @buffer, or %NULL if @asc couldn't be parsed + * or would be shorter or longer than @length. + */ +guint8 * +nm_utils_hwaddr_aton(const char *asc, gpointer buffer, gsize length) +{ + g_return_val_if_fail(asc, NULL); + g_return_val_if_fail(buffer, NULL); + g_return_val_if_fail(length > 0 && length <= NM_UTILS_HWADDR_LEN_MAX, NULL); + + return _nm_utils_hwaddr_aton_exact(asc, buffer, length); +} + +/** + * nm_utils_bin2hexstr: + * @src: (type guint8) (array length=len): an array of bytes + * @len: the length of the @src array + * @final_len: an index where to cut off the returned string, or -1 + * + * Converts the byte array @src into a hexadecimal string. If @final_len is + * greater than -1, the returned string is terminated at that index + * (returned_string[final_len] == '\0'), + * + * Return value: (transfer full): the textual form of @bytes + */ +char * +nm_utils_bin2hexstr(gconstpointer src, gsize len, int final_len) +{ + char *result; + gsize buflen = (len * 2) + 1; + + g_return_val_if_fail(src != NULL, NULL); + g_return_val_if_fail(len > 0 && (buflen - 1) / 2 == len, NULL); + g_return_val_if_fail(final_len < 0 || (gsize) final_len < buflen, NULL); + + result = g_malloc(buflen); + + nm_utils_bin2hexstr_full(src, len, '\0', FALSE, result); + + /* Cut converted key off at the correct length for this cipher type */ + if (final_len >= 0 && (gsize) final_len < buflen) + result[final_len] = '\0'; + + return result; +} + +/** + * nm_utils_hwaddr_ntoa: + * @addr: (type guint8) (array length=length): a binary hardware address + * @length: the length of @addr + * + * Converts @addr to textual form. + * + * Return value: (transfer full): the textual form of @addr + */ +char * +nm_utils_hwaddr_ntoa(gconstpointer addr, gsize length) +{ + g_return_val_if_fail(addr, g_strdup("")); + g_return_val_if_fail(length > 0, g_strdup("")); + + return nm_utils_bin2hexstr_full(addr, length, ':', TRUE, NULL); +} + +/** + * nm_utils_hwaddr_valid: + * @asc: the ASCII representation of a hardware address + * @length: the length of address that @asc is expected to convert to + * (or -1 to accept any length up to %NM_UTILS_HWADDR_LEN_MAX) + * + * Parses @asc to see if it is a valid hardware address of the given + * length. + * + * Return value: %TRUE if @asc appears to be a valid hardware address + * of the indicated length, %FALSE if not. + */ +gboolean +nm_utils_hwaddr_valid(const char *asc, gssize length) +{ + guint8 buf[NM_UTILS_HWADDR_LEN_MAX]; + gsize l; + + g_return_val_if_fail(asc != NULL, FALSE); + g_return_val_if_fail(length >= -1 && length <= NM_UTILS_HWADDR_LEN_MAX, FALSE); + + if (length == 0) + return FALSE; + + if (!_nm_utils_hwaddr_aton(asc, buf, sizeof(buf), &l)) + return FALSE; + + return length == -1 || length == (gssize) l; +} + +/** + * nm_utils_hwaddr_canonical: + * @asc: the ASCII representation of a hardware address + * @length: the length of address that @asc is expected to convert to + * (or -1 to accept any length up to %NM_UTILS_HWADDR_LEN_MAX) + * + * Parses @asc to see if it is a valid hardware address of the given + * length, and if so, returns it in canonical form (uppercase, with + * leading 0s as needed, and with colons rather than hyphens). + * + * Return value: (transfer full): the canonicalized address if @asc appears to + * be a valid hardware address of the indicated length, %NULL if not. + */ +char * +nm_utils_hwaddr_canonical(const char *asc, gssize length) +{ + guint8 buf[NM_UTILS_HWADDR_LEN_MAX]; + gsize l; + + g_return_val_if_fail(asc, NULL); + g_return_val_if_fail(length == -1 || (length > 0 && length <= NM_UTILS_HWADDR_LEN_MAX), NULL); + + if (!_nm_utils_hwaddr_aton(asc, buf, sizeof(buf), &l)) + return NULL; + if (length != -1 && length != (gssize) l) + return NULL; + return nm_utils_hwaddr_ntoa(buf, l); +} + +/* This is used to possibly canonicalize values passed to MAC address property + * setters. Unlike nm_utils_hwaddr_canonical(), it accepts %NULL, and if you + * pass it an invalid MAC address, it just returns that string rather than + * returning %NULL (so that we can return a proper error from verify() later). + */ +char * +_nm_utils_hwaddr_canonical_or_invalid(const char *mac, gssize length) +{ + char *canonical; + + if (!mac) + return NULL; + + canonical = nm_utils_hwaddr_canonical(mac, length); + if (canonical) + return canonical; + else + return g_strdup(mac); +} + +/* + * Determine if given Ethernet address is link-local + * + * Return value: %TRUE if @mac is link local + * reserved addr (01:80:c2:00:00:0X) per IEEE 802.1Q 8.6.3 Frame filtering, %FALSE if not. + */ +gboolean +_nm_utils_hwaddr_link_local_valid(const char *mac) +{ + guint8 mac_net[ETH_ALEN]; + static const guint8 eth_reserved_addr_base[] = {0x01, 0x80, 0xc2, 0x00, 0x00}; + + if (!mac) + return FALSE; + + if (!nm_utils_hwaddr_aton(mac, mac_net, ETH_ALEN)) + return FALSE; + + if (memcmp(mac_net, eth_reserved_addr_base, ETH_ALEN - 1) || (mac_net[5] & 0xF0)) + return FALSE; + + if (mac_net[5] == 1 /* 802.3x Pause address */ + || mac_net[5] == 2 /* 802.3ad Slow protocols */ + || mac_net[5] == 3) /* 802.1X PAE address */ + return FALSE; + + return TRUE; +} + +/** + * nm_utils_hwaddr_matches: + * @hwaddr1: (nullable): pointer to a binary or ASCII hardware address, or %NULL + * @hwaddr1_len: size of @hwaddr1, or -1 if @hwaddr1 is ASCII + * @hwaddr2: (nullable): pointer to a binary or ASCII hardware address, or %NULL + * @hwaddr2_len: size of @hwaddr2, or -1 if @hwaddr2 is ASCII + * + * Generalized hardware address comparison function. Tests if @hwaddr1 and + * @hwaddr2 "equal" (or more precisely, "equivalent"), with several advantages + * over a simple memcmp(): + * + * 1. If @hwaddr1_len or @hwaddr2_len is -1, then the corresponding address is + * assumed to be ASCII rather than binary, and will be converted to binary + * before being compared. + * + * 2. If @hwaddr1 or @hwaddr2 is %NULL, it is treated instead as though it was + * a zero-filled buffer @hwaddr1_len or @hwaddr2_len bytes long. + * + * 3. If @hwaddr1 and @hwaddr2 are InfiniBand hardware addresses (that is, if + * they are INFINIBAND_ALEN bytes long in binary form) + * then only the last 8 bytes are compared, since those are the only bytes + * that actually identify the hardware. (The other 12 bytes will change + * depending on the configuration of the InfiniBand fabric that the device + * is connected to.) + * + * If a passed-in ASCII hardware address cannot be parsed, or would parse to an + * address larger than %NM_UTILS_HWADDR_LEN_MAX, then it will silently fail to + * match. (This means that externally-provided address strings do not need to be + * sanity-checked before comparing them against known good addresses; they are + * guaranteed to not match if they are invalid.) + * + * Return value: %TRUE if @hwaddr1 and @hwaddr2 are equivalent, %FALSE if they are + * different (or either of them is invalid). + */ +gboolean +nm_utils_hwaddr_matches(gconstpointer hwaddr1, + gssize hwaddr1_len, + gconstpointer hwaddr2, + gssize hwaddr2_len) +{ + guint8 buf1[NM_UTILS_HWADDR_LEN_MAX], buf2[NM_UTILS_HWADDR_LEN_MAX]; + gsize l; + + if (hwaddr1_len == -1) { + if (hwaddr1 == NULL) { + hwaddr1_len = 0; + } else if (_nm_utils_hwaddr_aton(hwaddr1, buf1, sizeof(buf1), &l)) { + hwaddr1 = buf1; + hwaddr1_len = l; + } else { + g_return_val_if_fail(hwaddr2_len == -1 + || (hwaddr2_len > 0 && hwaddr2_len <= NM_UTILS_HWADDR_LEN_MAX), + FALSE); + return FALSE; + } + } else { + g_return_val_if_fail(hwaddr1_len > 0 && hwaddr1_len <= NM_UTILS_HWADDR_LEN_MAX, FALSE); + + if (!hwaddr1) { + memset(buf1, 0, hwaddr1_len); + hwaddr1 = buf1; + } + } + + if (hwaddr2_len == -1) { + if (hwaddr2 == NULL) + l = 0; + else if (!_nm_utils_hwaddr_aton(hwaddr2, buf2, sizeof(buf2), &l)) + return FALSE; + if (l != hwaddr1_len) + return FALSE; + hwaddr2 = buf2; + } else { + g_return_val_if_fail(hwaddr2_len > 0 && hwaddr2_len <= NM_UTILS_HWADDR_LEN_MAX, FALSE); + + if (hwaddr2_len != hwaddr1_len) + return FALSE; + + if (!hwaddr2) { + memset(buf2, 0, hwaddr2_len); + hwaddr2 = buf2; + } + } + + if (G_UNLIKELY(hwaddr1_len <= 0 || hwaddr1_len > NM_UTILS_HWADDR_LEN_MAX)) { + /* Only valid addresses can compare equal. In particular, + * addresses that are too long or of zero bytes, never + * compare equal. */ + return FALSE; + } + + if (hwaddr1_len == INFINIBAND_ALEN) { + hwaddr1 = &((guint8 *) hwaddr1)[INFINIBAND_ALEN - 8]; + hwaddr2 = &((guint8 *) hwaddr2)[INFINIBAND_ALEN - 8]; + hwaddr1_len = 8; + } + + return !memcmp(hwaddr1, hwaddr2, hwaddr1_len); +} + +/*****************************************************************************/ + +GVariant * +nm_utils_hwaddr_to_dbus(const char *str) +{ + guint8 buf[NM_UTILS_HWADDR_LEN_MAX]; + gsize len; + + if (!str) + return NULL; + if (!_nm_utils_hwaddr_aton(str, buf, sizeof(buf), &len)) + return NULL; + + return nm_g_variant_new_ay(buf, len); +} + +static GVariant * +_nm_utils_hwaddr_cloned_get(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_free char *addr = NULL; + + nm_assert(nm_streq(sett_info->property_infos[property_idx].name, "cloned-mac-address")); + + g_object_get(setting, "cloned-mac-address", &addr, NULL); + return nm_utils_hwaddr_to_dbus(addr); +} + +static gboolean +_nm_utils_hwaddr_cloned_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + gsize length; + const guint8 *array; + char * str; + + nm_assert(nm_streq0(property, "cloned-mac-address")); + + if (!_nm_setting_use_legacy_property(setting, + connection_dict, + "cloned-mac-address", + "assigned-mac-address")) + return TRUE; + + length = 0; + array = g_variant_get_fixed_array(value, &length, 1); + + if (!length) + return TRUE; + + str = nm_utils_hwaddr_ntoa(array, length); + g_object_set(setting, "cloned-mac-address", str, NULL); + g_free(str); + return TRUE; +} + +static gboolean +_nm_utils_hwaddr_cloned_not_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + NMSettingParseFlags parse_flags, + GError ** error) +{ + nm_assert(nm_streq0(property, "cloned-mac-address")); + return TRUE; +} + +const NMSettInfoPropertType nm_sett_info_propert_type_cloned_mac_address = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_BYTESTRING, + .to_dbus_fcn = _nm_utils_hwaddr_cloned_get, + .from_dbus_fcn = _nm_utils_hwaddr_cloned_set, + .missing_from_dbus_fcn = _nm_utils_hwaddr_cloned_not_set, ); + +static GVariant * +_nm_utils_hwaddr_cloned_data_synth(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_free char *addr = NULL; + + if (!_nm_connection_serialize_non_secret(flags)) + return NULL; + + nm_assert(nm_streq0(sett_info->property_infos[property_idx].name, "assigned-mac-address")); + + g_object_get(setting, "cloned-mac-address", &addr, NULL); + + /* Before introducing the extended "cloned-mac-address" (and its D-Bus + * field "assigned-mac-address"), libnm's nm_utils_hwaddr_to_dbus() + * would drop invalid values as it was unable to serialize them. + * + * Now, we would like to send invalid values as "assigned-mac-address" + * over D-Bus and let the server reject them. + * + * However, clients used to set the cloned-mac-address property + * to "" and it just worked as the value was not serialized in + * an ill form. + * + * To preserve that behavior, serialize "" as NULL. + */ + + return addr && addr[0] ? g_variant_new_take_string(g_steal_pointer(&addr)) : NULL; +} + +static gboolean +_nm_utils_hwaddr_cloned_data_set(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + nm_assert(nm_streq0(property, "assigned-mac-address")); + + if (_nm_setting_use_legacy_property(setting, + connection_dict, + "cloned-mac-address", + "assigned-mac-address")) + return TRUE; + + g_object_set(setting, + "cloned-mac-address", + nm_str_not_empty(g_variant_get_string(value, NULL)), + NULL); + return TRUE; +} + +const NMSettInfoPropertType nm_sett_info_propert_type_assigned_mac_address = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_STRING, + .to_dbus_fcn = _nm_utils_hwaddr_cloned_data_synth, + .from_dbus_fcn = _nm_utils_hwaddr_cloned_data_set, ); + +static void +_nm_utils_hwaddr_from_dbus(GVariant *dbus_value, GValue *prop_value) +{ + gsize length = 0; + const guint8 *array = g_variant_get_fixed_array(dbus_value, &length, 1); + char * str; + + str = length ? nm_utils_hwaddr_ntoa(array, length) : NULL; + g_value_take_string(prop_value, str); +} + +const NMSettInfoPropertType nm_sett_info_propert_type_mac_address = + NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT( + G_VARIANT_TYPE_BYTESTRING, + .gprop_from_dbus_fcn = _nm_utils_hwaddr_from_dbus, + .typdata_to_dbus.gprop_type = NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_MAC_ADDRESS); + +/*****************************************************************************/ + +/* Validate secret-flags. Most settings don't validate them, which is a bug. + * But we possibly cannot enforce a strict validation now. + * + * For new settings, they shall validate the secret-flags strictly. */ +gboolean +_nm_utils_secret_flags_validate(NMSettingSecretFlags secret_flags, + const char * setting_name, + const char * property_name, + NMSettingSecretFlags disallowed_flags, + GError ** error) +{ + if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) + return TRUE; + + if (NM_FLAGS_ANY(secret_flags, ~NM_SETTING_SECRET_FLAG_ALL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unknown secret flags")); + if (setting_name) + g_prefix_error(error, "%s.%s: ", setting_name, property_name); + return FALSE; + } + + if (!nm_utils_is_power_of_two(secret_flags)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("conflicting secret flags")); + if (setting_name) + g_prefix_error(error, "%s.%s: ", setting_name, property_name); + return FALSE; + } + + if (NM_FLAGS_ANY(secret_flags, disallowed_flags)) { + if (NM_FLAGS_HAS(secret_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("secret flags must not be \"not-required\"")); + if (setting_name) + g_prefix_error(error, "%s.%s: ", setting_name, property_name); + return FALSE; + } + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unsupported secret flags")); + if (setting_name) + g_prefix_error(error, "%s.%s: ", setting_name, property_name); + return FALSE; + } + + return TRUE; +} + +gboolean +_nm_utils_wps_method_validate(NMSettingWirelessSecurityWpsMethod wps_method, + const char * setting_name, + const char * property_name, + gboolean wps_required, + GError ** error) +{ + if (wps_method > NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("property is invalid")); + g_prefix_error(error, "%s.%s: ", setting_name, property_name); + return FALSE; + } + + if (NM_FLAGS_HAS(wps_method, NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED)) { + if (wps_method != NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("can't be simultaneously disabled and enabled")); + g_prefix_error(error, "%s.%s: ", setting_name, property_name); + return FALSE; + } + if (wps_required) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("WPS is required")); + g_prefix_error(error, "%s.%s: ", setting_name, property_name); + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + +static char * +_split_word(char *s) +{ + /* takes @s and truncates the string on the first white-space. + * then it returns the first word afterwards (again seeking + * over leading white-space). */ + for (; s[0]; s++) { + if (g_ascii_isspace(s[0])) { + s[0] = '\0'; + s++; + while (g_ascii_isspace(s[0])) + s++; + return s; + } + } + return s; +} + +gboolean +_nm_utils_generate_mac_address_mask_parse(const char * value, + struct ether_addr * out_mask, + struct ether_addr **out_ouis, + gsize * out_ouis_len, + GError ** error) +{ + gs_free char * s_free = NULL; + char * s, *s_next; + struct ether_addr mask; + gs_unref_array GArray *ouis = NULL; + + g_return_val_if_fail(!error || !*error, FALSE); + + if (!value || !*value) { + /* NULL and "" are valid values and both mean the default + * "q */ + if (out_mask) { + memset(out_mask, 0, sizeof(*out_mask)); + out_mask->ether_addr_octet[0] |= 0x02; + } + NM_SET_OUT(out_ouis, NULL); + NM_SET_OUT(out_ouis_len, 0); + return TRUE; + } + + s_free = g_strdup(value); + s = s_free; + + /* skip over leading whitespace */ + while (g_ascii_isspace(s[0])) + s++; + + /* parse the first mask */ + s_next = _split_word(s); + if (!nm_utils_hwaddr_aton(s, &mask, ETH_ALEN)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("not a valid ethernet MAC address for mask at position %lld"), + (long long) (s - s_free)); + return FALSE; + } + + if (s_next[0]) { + ouis = g_array_sized_new(FALSE, FALSE, sizeof(struct ether_addr), 4); + + do { + s = s_next; + s_next = _split_word(s); + + g_array_set_size(ouis, ouis->len + 1); + if (!nm_utils_hwaddr_aton(s, + &g_array_index(ouis, struct ether_addr, ouis->len - 1), + ETH_ALEN)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + _("not a valid ethernet MAC address #%u at position %lld"), + ouis->len, + (long long) (s - s_free)); + return FALSE; + } + } while (s_next[0]); + } + + NM_SET_OUT(out_mask, mask); + NM_SET_OUT(out_ouis_len, ouis ? ouis->len : 0); + NM_SET_OUT(out_ouis, + ouis ? ((struct ether_addr *) g_array_free(g_steal_pointer(&ouis), FALSE)) : NULL); + return TRUE; +} + +/*****************************************************************************/ + +gboolean +nm_utils_is_valid_iface_name_utf8safe(const char *utf8safe_name) +{ + gs_free gpointer bin_to_free = NULL; + gconstpointer bin; + gsize len; + + g_return_val_if_fail(utf8safe_name, FALSE); + + bin = nm_utils_buf_utf8safe_unescape(utf8safe_name, + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, + &len, + &bin_to_free); + + if (bin_to_free) { + /* some unescaping happened... */ + + if (len != strlen(bin)) { + /* there are embedded NUL chars. Invalid. */ + return FALSE; + } + } + + return nm_utils_ifname_valid_kernel(bin, NULL); +} + +/** + * nm_utils_is_valid_iface_name: + * @name: (allow-none): Name of interface + * @error: location to store the error occurring, or %NULL to ignore + * + * Validate the network interface name. + * + * This function is a 1:1 copy of the kernel's interface validation + * function in net/core/dev.c. + * + * Returns: %TRUE if interface name is valid, otherwise %FALSE is returned. + * + * Before 1.20, this function did not accept %NULL as @name argument. If you + * want to run against older versions of libnm, don't pass %NULL. + */ +gboolean +nm_utils_is_valid_iface_name(const char *name, GError **error) +{ + g_return_val_if_fail(!error || !*error, FALSE); + + return nm_utils_ifname_valid_kernel(name, error); +} + +/** + * nm_utils_iface_valid_name: + * @name: (allow-none): Name of interface + * + * Validate the network interface name. + * + * Deprecated: 1.6: Use nm_utils_is_valid_iface_name() instead, with better error reporting. + * + * Returns: %TRUE if interface name is valid, otherwise %FALSE is returned. + * + * Before 1.20, this function did not accept %NULL as @name argument. If you + * want to run against older versions of libnm, don't pass %NULL. + */ +gboolean +nm_utils_iface_valid_name(const char *name) +{ + return nm_utils_is_valid_iface_name(name, NULL); +} + +/** + * nm_utils_is_uuid: + * @str: (allow-none): a string that might be a UUID + * + * Checks if @str is a UUID + * + * Returns: %TRUE if @str is a UUID, %FALSE if not + * + * In older versions, nm_utils_is_uuid() did not accept %NULL as @str + * argument. Don't pass %NULL if you run against older versions of libnm. + * + * Deprecated: 1.32: older versions of NetworkManager had a wrong + * understanding of what makes a valid UUID. This function can thus + * accept some inputs as valid, which in fact are not valid UUIDs. + */ +gboolean +nm_utils_is_uuid(const char *str) +{ + return nm_uuid_is_valid_nmlegacy(str); +} + +static _nm_thread_local char _nm_utils_inet_ntop_buffer[NM_UTILS_INET_ADDRSTRLEN]; + +/** + * nm_utils_inet4_ntop: (skip) + * @inaddr: the address that should be converted to string. + * @dst: the destination buffer, it must contain at least + * INET_ADDRSTRLEN or %NM_UTILS_INET_ADDRSTRLEN + * characters. If set to %NULL, it will return a pointer to an internal, static + * buffer (shared with nm_utils_inet6_ntop()). Beware, that the internal + * buffer will be overwritten with ever new call of nm_utils_inet4_ntop() or + * nm_utils_inet6_ntop() that does not provide its own @dst buffer. Since + * 1.28, the internal buffer is thread local and thus thread safe. Before + * it was not thread safe. When in doubt, pass your own + * @dst buffer to avoid these issues. + * + * Wrapper for inet_ntop. + * + * Returns: the input buffer @dst, or a pointer to an + * internal, static buffer. This function cannot fail. + **/ +const char * +nm_utils_inet4_ntop(in_addr_t inaddr, char *dst) +{ + /* relying on the static buffer (by leaving @dst as %NULL) is discouraged. + * Don't do that! + * + * However, still support it to be lenient against mistakes and because + * this is public API of libnm. */ + return _nm_utils_inet4_ntop(inaddr, dst ?: _nm_utils_inet_ntop_buffer); +} + +/** + * nm_utils_inet6_ntop: (skip) + * @in6addr: the address that should be converted to string. + * @dst: the destination buffer, it must contain at least + * INET6_ADDRSTRLEN or %NM_UTILS_INET_ADDRSTRLEN + * characters. If set to %NULL, it will return a pointer to an internal, static + * buffer (shared with nm_utils_inet4_ntop()). Beware, that the internal + * buffer will be overwritten with ever new call of nm_utils_inet4_ntop() or + * nm_utils_inet6_ntop() that does not provide its own @dst buffer. Since + * 1.28, the internal buffer is thread local and thus thread safe. Before + * it was not thread safe. When in doubt, pass your own + * @dst buffer to avoid these issues. + * + * Wrapper for inet_ntop. + * + * Returns: the input buffer @dst, or a pointer to an + * internal, static buffer. %NULL is not allowed as @in6addr, + * otherwise, this function cannot fail. + **/ +const char * +nm_utils_inet6_ntop(const struct in6_addr *in6addr, char *dst) +{ + /* relying on the static buffer (by leaving @dst as %NULL) is discouraged. + * Don't do that! + * + * However, still support it to be lenient against mistakes and because + * this is public API of libnm. */ + g_return_val_if_fail(in6addr, NULL); + return _nm_utils_inet6_ntop(in6addr, dst ?: _nm_utils_inet_ntop_buffer); +} + +/** + * nm_utils_ipaddr_valid: + * @family: AF_INET or AF_INET6, or + * AF_UNSPEC to accept either + * @ip: an IP address + * + * Checks if @ip contains a valid IP address of the given family. + * + * Return value: %TRUE or %FALSE + */ +gboolean +nm_utils_ipaddr_valid(int family, const char *ip) +{ + g_return_val_if_fail(family == AF_INET || family == AF_INET6 || family == AF_UNSPEC, FALSE); + + return nm_utils_ipaddr_is_valid(family, ip); +} + +/** + * _nm_utils_dhcp_duid_valid: + * @duid: the candidate DUID + * + * Checks if @duid string contains either a special duid value ("ll", + * "llt", "lease" or the "stable" variants) or a valid hex DUID. + * + * Return value: %TRUE or %FALSE + */ +gboolean +_nm_utils_dhcp_duid_valid(const char *duid, GBytes **out_duid_bin) +{ + guint8 duid_arr[128 + 2]; + gsize duid_len; + + NM_SET_OUT(out_duid_bin, NULL); + + if (!duid) + return FALSE; + + if (NM_IN_STRSET(duid, "lease", "llt", "ll", "stable-llt", "stable-ll", "stable-uuid")) { + return TRUE; + } + + if (nm_utils_hexstr2bin_full(duid, + FALSE, + FALSE, + FALSE, + ":", + 0, + duid_arr, + sizeof(duid_arr), + &duid_len)) { + /* MAX DUID length is 128 octects + the type code (2 octects). */ + if (duid_len > 2 && duid_len <= (128 + 2)) { + NM_SET_OUT(out_duid_bin, g_bytes_new(duid_arr, duid_len)); + return TRUE; + } + } + + return FALSE; +} + +/** + * nm_utils_check_virtual_device_compatibility: + * @virtual_type: a virtual connection type + * @other_type: a connection type to test against @virtual_type + * + * Determines if a connection of type @virtual_type can (in the + * general case) work with connections of type @other_type. + * + * If @virtual_type is %NM_TYPE_SETTING_VLAN, then this checks if + * @other_type is a valid type for the parent of a VLAN. + * + * If @virtual_type is a "master" type (eg, %NM_TYPE_SETTING_BRIDGE), + * then this checks if @other_type is a valid type for a slave of that + * master. + * + * Note that even if this returns %TRUE it is not guaranteed that + * every connection of type @other_type is + * compatible with @virtual_type; it may depend on the exact + * configuration of the two connections, or on the capabilities of an + * underlying device driver. + * + * Returns: %TRUE or %FALSE + */ +gboolean +nm_utils_check_virtual_device_compatibility(GType virtual_type, GType other_type) +{ + g_return_val_if_fail(_nm_setting_type_get_base_type_priority(virtual_type) + != NM_SETTING_PRIORITY_INVALID, + FALSE); + g_return_val_if_fail(_nm_setting_type_get_base_type_priority(other_type) + != NM_SETTING_PRIORITY_INVALID, + FALSE); + + if (virtual_type == NM_TYPE_SETTING_BOND) { + return (other_type == NM_TYPE_SETTING_INFINIBAND || other_type == NM_TYPE_SETTING_WIRED + || other_type == NM_TYPE_SETTING_BRIDGE || other_type == NM_TYPE_SETTING_BOND + || other_type == NM_TYPE_SETTING_TEAM || other_type == NM_TYPE_SETTING_VLAN); + } else if (virtual_type == NM_TYPE_SETTING_BRIDGE) { + return (other_type == NM_TYPE_SETTING_WIRED || other_type == NM_TYPE_SETTING_BOND + || other_type == NM_TYPE_SETTING_TEAM || other_type == NM_TYPE_SETTING_VLAN); + } else if (virtual_type == NM_TYPE_SETTING_TEAM) { + return (other_type == NM_TYPE_SETTING_WIRED || other_type == NM_TYPE_SETTING_BRIDGE + || other_type == NM_TYPE_SETTING_BOND || other_type == NM_TYPE_SETTING_TEAM + || other_type == NM_TYPE_SETTING_VLAN); + } else if (virtual_type == NM_TYPE_SETTING_VLAN) { + return (other_type == NM_TYPE_SETTING_WIRED || other_type == NM_TYPE_SETTING_WIRELESS + || other_type == NM_TYPE_SETTING_BRIDGE || other_type == NM_TYPE_SETTING_BOND + || other_type == NM_TYPE_SETTING_TEAM || other_type == NM_TYPE_SETTING_VLAN); + } else { + g_warn_if_reached(); + return FALSE; + } +} + +/*****************************************************************************/ + +/** + * nm_utils_bond_mode_int_to_string: + * @mode: bonding mode as a numeric value + * + * Convert bonding mode from integer value to descriptive name. + * See https://www.kernel.org/doc/Documentation/networking/bonding.txt for + * available modes. + * + * Returns: bonding mode string, or NULL on error + * + * Since: 1.2 + */ +const char * +nm_utils_bond_mode_int_to_string(int mode) +{ + return _nm_setting_bond_mode_to_string(mode); +} + +/** + * nm_utils_bond_mode_string_to_int: + * @mode: bonding mode as string + * + * Convert bonding mode from string representation to numeric value. + * See https://www.kernel.org/doc/Documentation/networking/bonding.txt for + * available modes. + * The @mode string can be either a descriptive name or a number (as string). + * + * Returns: numeric bond mode, or -1 on error + * + * Since: 1.2 + */ +int +nm_utils_bond_mode_string_to_int(const char *mode) +{ + return _nm_setting_bond_mode_from_string(mode); +} + +/*****************************************************************************/ + +#define STRSTRDICTKEY_V1_SET 0x01 +#define STRSTRDICTKEY_V2_SET 0x02 +#define STRSTRDICTKEY_ALL_SET 0x03 + +struct _NMUtilsStrStrDictKey { + char type; + char data[1]; +}; + +guint +_nm_utils_strstrdictkey_hash(gconstpointer a) +{ + const NMUtilsStrStrDictKey *k = a; + const char * p; + NMHashState h; + + nm_hash_init(&h, 76642997u); + if (k) { + if (((int) k->type) & ~STRSTRDICTKEY_ALL_SET) + g_return_val_if_reached(0); + + nm_hash_update_val(&h, k->type); + if (k->type & STRSTRDICTKEY_ALL_SET) { + p = strchr(k->data, '\0'); + if (k->type == STRSTRDICTKEY_ALL_SET) { + /* the key contains two strings. Continue... */ + p = strchr(p + 1, '\0'); + } + if (p != k->data) + nm_hash_update(&h, k->data, p - k->data); + } + } + return nm_hash_complete(&h); +} + +gboolean +_nm_utils_strstrdictkey_equal(gconstpointer a, gconstpointer b) +{ + const NMUtilsStrStrDictKey *k1 = a; + const NMUtilsStrStrDictKey *k2 = b; + + if (k1 == k2) + return TRUE; + if (!k1 || !k2) + return FALSE; + + if (k1->type != k2->type) + return FALSE; + + if (k1->type & STRSTRDICTKEY_ALL_SET) { + if (!nm_streq(k1->data, k2->data)) + return FALSE; + + if (k1->type == STRSTRDICTKEY_ALL_SET) { + gsize l = strlen(k1->data) + 1; + + return nm_streq(&k1->data[l], &k2->data[l]); + } + } + + return TRUE; +} + +NMUtilsStrStrDictKey * +_nm_utils_strstrdictkey_create(const char *v1, const char *v2) +{ + char type = 0; + gsize l1 = 0, l2 = 0; + NMUtilsStrStrDictKey *k; + + if (!v1 && !v2) + return g_malloc0(1); + + /* we need to distinguish between ("",NULL) and (NULL,""). + * Thus, in @type we encode which strings we have present + * as not-NULL. */ + if (v1) { + type |= STRSTRDICTKEY_V1_SET; + l1 = strlen(v1) + 1; + } + if (v2) { + type |= STRSTRDICTKEY_V2_SET; + l2 = strlen(v2) + 1; + } + + k = g_malloc(G_STRUCT_OFFSET(NMUtilsStrStrDictKey, data) + l1 + l2); + k->type = type; + if (v1) + memcpy(&k->data[0], v1, l1); + if (v2) + memcpy(&k->data[l1], v2, l2); + + return k; +} + +static gboolean +validate_dns_option(const char * name, + gboolean numeric, + gboolean ipv6, + const NMUtilsDNSOptionDesc *option_descs) +{ + const NMUtilsDNSOptionDesc *desc; + + if (!option_descs) + return !!*name; + + for (desc = option_descs; desc->name; desc++) { + if (nm_streq(name, desc->name) && numeric == desc->numeric && (!desc->ipv6_only || ipv6)) + return TRUE; + } + + return FALSE; +} + +/** + * _nm_utils_dns_option_validate: + * @option: option string + * @out_name: (out) (allow-none): the option name + * @out_value: (out) (allow-none): the option value + * @ipv6: whether the option refers to a IPv6 configuration + * @option_descs: (allow-none): an array of NMUtilsDNSOptionDesc which describes the + * valid options + * + * Parses a DNS option in the form "name" or "name:number" and, if + * @option_descs is not NULL, checks that the option conforms to one + * of the provided descriptors. If @option_descs is NULL @ipv6 is + * not considered. + * + * Returns: %TRUE when the parsing was successful and the option is valid, + * %FALSE otherwise + */ +gboolean +_nm_utils_dns_option_validate(const char * option, + char ** out_name, + long * out_value, + gboolean ipv6, + const NMUtilsDNSOptionDesc *option_descs) +{ + gs_free char *option0_free = NULL; + const char * option0; + const char * option1; + const char * delim; + long option1_num; + + g_return_val_if_fail(option != NULL, FALSE); + + NM_SET_OUT(out_name, NULL); + NM_SET_OUT(out_value, -1); + + if (!option[0]) + return FALSE; + + delim = strchr(option, ':'); + if (!delim) { + if (!validate_dns_option(option, FALSE, ipv6, option_descs)) + return FALSE; + NM_SET_OUT(out_name, g_strdup(option)); + return TRUE; + } + + option1 = &delim[1]; + + if (!option1[0]) + return FALSE; + if (!NM_STRCHAR_ALL(option1, ch, g_ascii_isdigit(ch))) + return FALSE; + + option0 = nm_strndup_a(300, option, delim - option, &option0_free); + + if (!validate_dns_option(option0, TRUE, ipv6, option_descs)) + return FALSE; + + option1_num = _nm_utils_ascii_str_to_int64(option1, 10, 0, G_MAXINT32, -1); + if (option1_num == -1) + return FALSE; + + NM_SET_OUT(out_name, g_steal_pointer(&option0_free) ?: g_strdup(option0)); + NM_SET_OUT(out_value, option1_num); + return TRUE; +} + +/** + * _nm_utils_dns_option_find_idx: + * @array: an array of strings + * @option: a dns option string + * + * Searches for an option in an array of strings. The match is + * performed only the option name; the option value is ignored. + * + * Returns: the index of the option in the array or -1 if was not + * found. + */ +gssize +_nm_utils_dns_option_find_idx(GPtrArray *array, const char *option) +{ + gs_free char *option_name = NULL; + guint i; + + if (!_nm_utils_dns_option_validate(option, &option_name, NULL, FALSE, NULL)) + return -1; + + for (i = 0; i < array->len; i++) { + gs_free char *tmp_name = NULL; + + if (_nm_utils_dns_option_validate(array->pdata[i], &tmp_name, NULL, FALSE, NULL)) { + if (nm_streq(tmp_name, option_name)) + return i; + } + } + + return -1; +} + +/*****************************************************************************/ + +/** + * nm_utils_enum_to_str: + * @type: the %GType of the enum + * @value: the value to be translated + * + * Converts an enum value to its string representation. If the enum is a + * %G_TYPE_FLAGS the function returns a comma-separated list of matching values. + * If the value has no corresponding string representation, it is converted + * to a number. For enums it is converted to a decimal number, for flags + * to an (unsigned) hex number. + * + * Returns: a newly allocated string or %NULL + * + * Since: 1.2 + */ +char * +nm_utils_enum_to_str(GType type, int value) +{ + return _nm_utils_enum_to_str_full(type, value, ", ", NULL); +} + +/** + * nm_utils_enum_from_str: + * @type: the %GType of the enum + * @str: the input string + * @out_value: (out) (allow-none): the output value + * @err_token: (out) (allow-none) (transfer full): location to store the first unrecognized token + * + * Converts a string to the matching enum value. + * + * If the enum is a %G_TYPE_FLAGS the function returns the logical OR of values + * matching the comma-separated tokens in the string; if an unknown token is found + * the function returns %FALSE and stores a pointer to a newly allocated string + * containing the unrecognized token in @err_token. + * + * Returns: %TRUE if the conversion was successful, %FALSE otherwise + * + * Since: 1.2 + */ +gboolean +nm_utils_enum_from_str(GType type, const char *str, int *out_value, char **err_token) +{ + return _nm_utils_enum_from_str_full(type, str, out_value, err_token, NULL); +} + +/** + * nm_utils_enum_get_values: + * @type: the %GType of the enum + * @from: the first element to be returned + * @to: the last element to be returned + * + * Returns the list of possible values for a given enum. + * + * Returns: (transfer container): a NULL-terminated dynamically-allocated array of static strings + * or %NULL on error + * + * Since: 1.2 + */ +const char ** +nm_utils_enum_get_values(GType type, int from, int to) +{ + return _nm_utils_enum_get_values(type, from, to); +} + +/*****************************************************************************/ + +static gboolean +_nm_utils_is_json_object_no_validation(const char *str, GError **error) +{ + nm_assert(str); + + /* libjansson also requires only utf-8 encoding. */ + if (!g_utf8_validate(str, -1, NULL)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("not valid utf-8")); + return FALSE; + } + while (g_ascii_isspace(str[0])) + str++; + + /* do some very basic validation to see if this might be a JSON object. */ + if (str[0] == '{') { + gsize l; + + l = strlen(str) - 1; + while (l > 0 && g_ascii_isspace(str[l])) + l--; + + if (str[l] == '}') + return TRUE; + } + + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is not a JSON object")); + return FALSE; +} + +/** + * nm_utils_is_json_object: + * @str: the JSON string to test + * @error: optional error reason + * + * Returns: whether the passed string is valid JSON. + * If libnm is not compiled with libjansson support, this check will + * also return %TRUE for possibly invalid inputs. If that is a problem + * for you, you must validate the JSON yourself. + * + * Since: 1.6 + */ +gboolean +nm_utils_is_json_object(const char *str, GError **error) +{ + nm_auto_decref_json nm_json_t *json = NULL; + const NMJsonVt * vt; + nm_json_error_t jerror; + + g_return_val_if_fail(!error || !*error, FALSE); + + if (!str || !str[0]) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + str ? _("value is NULL") : _("value is empty")); + return FALSE; + } + + if (!(vt = nm_json_vt())) + return _nm_utils_is_json_object_no_validation(str, error); + + json = vt->nm_json_loads(str, NM_JSON_REJECT_DUPLICATES, &jerror); + if (!json) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("invalid JSON at position %d (%s)"), + jerror.position, + jerror.text); + return FALSE; + } + + /* valid JSON (depending on the definition) can also be a literal. + * Here we only allow objects. */ + if (!nm_json_is_object(json)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("is not a JSON object")); + return FALSE; + } + + return TRUE; +} + +static char * +attribute_unescape(const char *start, const char *end) +{ + char *ret, *dest; + + nm_assert(start <= end); + dest = ret = g_malloc(end - start + 1); + + for (; start < end && *start; start++) { + if (*start == '\\') { + start++; + if (!*start) + break; + } + *dest++ = *start; + } + *dest = '\0'; + + return ret; +} + +gboolean +_nmtst_variant_attribute_spec_assert_sorted(const NMVariantAttributeSpec *const *array, gsize len) +{ + gsize i; + + g_assert(array); + g_assert(len > 0); + g_assert_cmpint(len, ==, NM_PTRARRAY_LEN(array)); + + for (i = 0; i < len; i++) { + nm_assert(array[i]->name); + nm_assert(array[i]->name[0]); + if (i > 0) + nm_assert(strcmp(array[i - 1]->name, array[i]->name) < 0); + } + nm_assert(!array[i]); + + return TRUE; +} + +const NMVariantAttributeSpec * +_nm_variant_attribute_spec_find_binary_search(const NMVariantAttributeSpec *const *array, + gsize len, + const char * name) +{ + gssize idx; + + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMVariantAttributeSpec, name) == 0); + + idx = nm_utils_ptrarray_find_binary_search((gconstpointer *) array, + len, + &name, + nm_strcmp_p_with_data, + NULL); + if (idx < 0) + return NULL; + return array[idx]; +} + +/** + * nm_utils_parse_variant_attributes: + * @string: the input string + * @attr_separator: the attribute separator character + * @key_value_separator: character separating key and values + * @ignore_unknown: whether unknown attributes should be ignored + * @spec: the attribute format specifiers + * @error: (out) (allow-none): location to store the error on failure + * + * Parse attributes from a string. + * + * Returns: (transfer full) (element-type utf8 GVariant): a #GHashTable mapping + * attribute names to #GVariant values. Warning: the variant are still floating + * references, owned by the hash table. If you take a reference, ensure to sink + * the one of the hash table first. + * + * Since: 1.8 + */ +GHashTable * +nm_utils_parse_variant_attributes(const char * string, + char attr_separator, + char key_value_separator, + gboolean ignore_unknown, + const NMVariantAttributeSpec *const *spec, + GError ** error) +{ + gs_unref_hashtable GHashTable * ht = NULL; + const char * ptr = string, *start = NULL, *sep; + GVariant * variant; + const NMVariantAttributeSpec *const *s; + + g_return_val_if_fail(string, NULL); + g_return_val_if_fail(attr_separator, NULL); + g_return_val_if_fail(key_value_separator, NULL); + g_return_val_if_fail(!error || !*error, NULL); + + ht = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref); + + while (TRUE) { + gs_free char *name = NULL, *value = NULL; + + if (!start) + start = ptr; + if (*ptr == '\\') { + ptr++; + if (!*ptr) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unterminated escape sequence")); + return NULL; + } + goto next; + } + if (*ptr == attr_separator || *ptr == '\0') { + if (ptr == start) { + /* multiple separators */ + start = NULL; + goto next; + } + + /* Find the key-value separator */ + for (sep = start; sep != ptr; sep++) { + if (*sep == '\\') { + sep++; + if (!*sep) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unterminated escape sequence")); + return NULL; + } + } + if (*sep == key_value_separator) + break; + } + + name = attribute_unescape(start, sep); + + for (s = spec; *s; s++) { + if (g_hash_table_contains(ht, (*s)->name)) + continue; + if (nm_streq(name, (*s)->name)) + break; + if ((*s)->no_value && g_variant_type_equal((*s)->type, G_VARIANT_TYPE_STRING)) + break; + } + + if (!*s) { + if (ignore_unknown) + goto next; + else { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unknown attribute '%s'"), + name); + return NULL; + } + } + + if ((*s)->no_value) { + if ((*s)->consumes_rest) { + value = g_strdup(start); + ptr = strchr(start, '\0'); + } else { + value = g_steal_pointer(&name); + } + } else { + if (*sep != key_value_separator) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("missing key-value separator '%c' after '%s'"), + key_value_separator, + name); + return NULL; + } + + /* The attribute and key/value separators are the same. Look for the next one. */ + if (ptr == sep) + goto next; + + value = attribute_unescape(sep + 1, ptr); + } + + if (g_variant_type_equal((*s)->type, G_VARIANT_TYPE_UINT32)) { + gint64 num = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXUINT32, -1); + + if (num == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid uint32 value '%s' for attribute '%s'"), + value, + (*s)->name); + return NULL; + } + variant = g_variant_new_uint32(num); + } else if (g_variant_type_equal((*s)->type, G_VARIANT_TYPE_INT32)) { + gint64 num = + _nm_utils_ascii_str_to_int64(value, 10, G_MININT32, G_MAXINT32, G_MAXINT64); + + if (num == G_MAXINT64) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid int32 value '%s' for attribute '%s'"), + value, + (*s)->name); + return NULL; + } + variant = g_variant_new_int32(num); + } else if (g_variant_type_equal((*s)->type, G_VARIANT_TYPE_UINT64)) { + guint64 num = _nm_utils_ascii_str_to_uint64(value, 10, 0, G_MAXUINT64, G_MAXUINT64); + + if (num == G_MAXUINT64 && errno != 0) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid uint64 value '%s' for attribute '%s'"), + value, + (*s)->name); + return NULL; + } + variant = g_variant_new_uint64(num); + } else if (g_variant_type_equal((*s)->type, G_VARIANT_TYPE_BYTE)) { + gint64 num = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXUINT8, -1); + + if (num == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid uint8 value '%s' for attribute '%s'"), + value, + (*s)->name); + return NULL; + } + variant = g_variant_new_byte((guchar) num); + } else if (g_variant_type_equal((*s)->type, G_VARIANT_TYPE_BOOLEAN)) { + int b; + + b = (*s)->no_value ? TRUE : _nm_utils_ascii_str_to_bool(value, -1); + if (b == -1) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("invalid boolean value '%s' for attribute '%s'"), + value, + (*s)->name); + return NULL; + } + variant = g_variant_new_boolean(b); + } else if (g_variant_type_equal((*s)->type, G_VARIANT_TYPE_STRING)) { + variant = g_variant_new_take_string(g_steal_pointer(&value)); + } else if (g_variant_type_equal((*s)->type, G_VARIANT_TYPE_BYTESTRING)) { + variant = g_variant_new_bytestring(value); + } else { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_FAILED, + _("unsupported attribute '%s' of type '%s'"), + (*s)->name, + (char *) (*s)->type); + return NULL; + } + + g_hash_table_insert(ht, g_strdup((*s)->name), variant); + start = NULL; + } +next: + if (*ptr == '\0') + break; + ptr++; + } + + return g_steal_pointer(&ht); +} + +/* + * nm_utils_format_variant_attributes: + * @attributes: (element-type utf8 GVariant): a #GHashTable mapping attribute names to #GVariant values + * @attr_separator: the attribute separator character + * @key_value_separator: character separating key and values + * + * Format attributes to a string. + * + * Returns: (transfer full): the string representing attributes, or %NULL + * in case there are no attributes + * + * Since: 1.8 + */ +char * +nm_utils_format_variant_attributes(GHashTable *attributes, + char attr_separator, + char key_value_separator) +{ + return _nm_utils_format_variant_attributes(attributes, + NULL, + attr_separator, + key_value_separator); +} + +/*****************************************************************************/ + +/* + * nm_utils_get_timestamp_msec(): + * + * Gets current time in milliseconds of CLOCK_BOOTTIME. + * + * Returns: time in milliseconds + * + * Since: 1.12 + */ +gint64 +nm_utils_get_timestamp_msec(void) +{ + gint64 ts; + + ts = nm_utils_clock_gettime_msec(CLOCK_BOOTTIME); + if (ts >= 0) + return ts; + + if (ts == -EINVAL) { + /* The fallback to CLOCK_MONOTONIC is taken only if we're running on a + * criminally old kernel, prior to 2.6.39 (released on 18 May, 2011). + * That happens during buildcheck on old builders, we don't expect to + * be actually runs on kernels that old. */ + ts = nm_utils_clock_gettime_msec(CLOCK_MONOTONIC); + if (ts >= 0) + return ts; + } + + g_return_val_if_reached(-1); +} + +/*****************************************************************************/ + +/** + * nm_utils_version: + * + * Returns: the version ID of the libnm version. That is, the %NM_VERSION + * at runtime. + * + * Since: 1.6.0 + */ +guint +nm_utils_version(void) +{ + return NM_VERSION; +} + +/*****************************************************************************/ + +NM_UTILS_FLAGS2STR_DEFINE(nm_bluetooth_capability_to_string, + NMBluetoothCapabilities, + NM_UTILS_FLAGS2STR(NM_BT_CAPABILITY_NONE, "NONE"), + NM_UTILS_FLAGS2STR(NM_BT_CAPABILITY_DUN, "DUN"), + NM_UTILS_FLAGS2STR(NM_BT_CAPABILITY_NAP, "NAP"), ); + +/*****************************************************************************/ + +/** + * nm_utils_base64secret_decode: + * @base64_key: the (possibly invalid) base64 encode key. + * @required_key_len: the expected (binary) length of the key after + * decoding. If the length does not match, the validation fails. + * @out_key: (allow-none): (out): an optional output buffer for the binary + * key. If given, it will be filled with exactly @required_key_len + * bytes. + * + * Returns: %TRUE if the input key is a valid base64 encoded key + * with @required_key_len bytes. + * + * Since: 1.16 + */ +gboolean +nm_utils_base64secret_decode(const char *base64_key, gsize required_key_len, guint8 *out_key) +{ + nm_auto_free guint8 *bin_arr = NULL; + gsize base64_key_len; + gsize bin_len; + int r; + + if (!base64_key) + return FALSE; + + base64_key_len = strlen(base64_key); + + r = nm_sd_utils_unbase64mem(base64_key, base64_key_len, TRUE, &bin_arr, &bin_len); + if (r < 0) + return FALSE; + if (bin_len != required_key_len) { + nm_explicit_bzero(bin_arr, bin_len); + return FALSE; + } + + if (out_key) + memcpy(out_key, bin_arr, required_key_len); + + nm_explicit_bzero(bin_arr, bin_len); + return TRUE; +} + +gboolean +nm_utils_base64secret_normalize(const char *base64_key, + gsize required_key_len, + char ** out_base64_key_norm) +{ + gs_free guint8 *buf_free = NULL; + guint8 buf_static[200]; + guint8 * buf; + + if (required_key_len > sizeof(buf_static)) { + buf_free = g_new(guint8, required_key_len); + buf = buf_free; + } else + buf = buf_static; + + if (!nm_utils_base64secret_decode(base64_key, required_key_len, buf)) { + NM_SET_OUT(out_base64_key_norm, NULL); + return FALSE; + } + + NM_SET_OUT(out_base64_key_norm, g_base64_encode(buf, required_key_len)); + nm_explicit_bzero(buf, required_key_len); + return TRUE; +} + +static GVariant * +_nm_utils_bridge_vlans_to_dbus(const NMSettInfoSetting * sett_info, + guint property_idx, + NMConnection * connection, + NMSetting * setting, + NMConnectionSerializationFlags flags, + const NMConnectionSerializationOptions *options) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + GVariantBuilder builder; + guint i; + const char * property_name = sett_info->property_infos[property_idx].name; + + nm_assert(property_name); + + g_object_get(setting, property_name, &vlans, NULL); + g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}")); + + if (vlans) { + for (i = 0; i < vlans->len; i++) { + NMBridgeVlan * vlan = vlans->pdata[i]; + GVariantBuilder vlan_builder; + guint16 vid_start, vid_end; + + nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end); + + g_variant_builder_init(&vlan_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&vlan_builder, + "{sv}", + "vid-start", + g_variant_new_uint16(vid_start)); + g_variant_builder_add(&vlan_builder, "{sv}", "vid-end", g_variant_new_uint16(vid_end)); + g_variant_builder_add(&vlan_builder, + "{sv}", + "pvid", + g_variant_new_boolean(nm_bridge_vlan_is_pvid(vlan))); + g_variant_builder_add(&vlan_builder, + "{sv}", + "untagged", + g_variant_new_boolean(nm_bridge_vlan_is_untagged(vlan))); + g_variant_builder_add(&builder, "a{sv}", &vlan_builder); + } + } + + return g_variant_builder_end(&builder); +} + +static gboolean +_nm_utils_bridge_vlans_from_dbus(NMSetting * setting, + GVariant * connection_dict, + const char * property, + GVariant * value, + NMSettingParseFlags parse_flags, + GError ** error) +{ + gs_unref_ptrarray GPtrArray *vlans = NULL; + GVariantIter vlan_iter; + GVariant * vlan_var; + + g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), FALSE); + + vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref); + g_variant_iter_init(&vlan_iter, value); + while (g_variant_iter_next(&vlan_iter, "@a{sv}", &vlan_var)) { + _nm_unused gs_unref_variant GVariant *var_unref = vlan_var; + NMBridgeVlan * vlan; + guint16 vid_start, vid_end; + gboolean pvid = FALSE, untagged = FALSE; + + if (!g_variant_lookup(vlan_var, "vid-start", "q", &vid_start)) + continue; + if (vid_start < NM_BRIDGE_VLAN_VID_MIN || vid_start > NM_BRIDGE_VLAN_VID_MAX) + continue; + + if (!g_variant_lookup(vlan_var, "vid-end", "q", &vid_end)) + continue; + if (vid_end < NM_BRIDGE_VLAN_VID_MIN || vid_end > NM_BRIDGE_VLAN_VID_MAX) + continue; + if (vid_start > vid_end) + continue; + + if (!g_variant_lookup(vlan_var, "pvid", "b", &pvid)) + pvid = FALSE; + if (pvid && vid_start != vid_end) + continue; + if (!g_variant_lookup(vlan_var, "untagged", "b", &untagged)) + untagged = FALSE; + + vlan = nm_bridge_vlan_new(vid_start, vid_end); + nm_bridge_vlan_set_untagged(vlan, untagged); + nm_bridge_vlan_set_pvid(vlan, pvid); + g_ptr_array_add(vlans, vlan); + } + + g_object_set(setting, property, vlans, NULL); + + return TRUE; +} + +const NMSettInfoPropertType nm_sett_info_propert_type_bridge_vlans = + NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(NM_G_VARIANT_TYPE("aa{sv}"), + .to_dbus_fcn = _nm_utils_bridge_vlans_to_dbus, + .from_dbus_fcn = _nm_utils_bridge_vlans_from_dbus, ); + +gboolean +_nm_utils_bridge_vlan_verify_list(GPtrArray * vlans, + gboolean check_normalizable, + GError ** error, + const char *setting, + const char *property) +{ + guint i; + gs_unref_hashtable GHashTable *h = NULL; + gboolean pvid_found = FALSE; + + if (!vlans || vlans->len <= 1) + return TRUE; + + if (check_normalizable) { + guint16 vid_prev_end, vid_start, vid_end; + + nm_assert(_nm_utils_bridge_vlan_verify_list(vlans, FALSE, NULL, setting, property)); + + nm_bridge_vlan_get_vid_range(vlans->pdata[0], NULL, &vid_prev_end); + for (i = 1; i < vlans->len; i++) { + const NMBridgeVlan *vlan = vlans->pdata[i]; + + nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end); + + if (vid_prev_end > vid_start) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("Bridge VLANs %d and %d are not sorted by ascending vid"), + vid_prev_end, + vid_start); + g_prefix_error(error, "%s.%s: ", setting, property); + return FALSE; + } + + vid_prev_end = vid_end; + } + return TRUE; + } + + h = g_hash_table_new(nm_direct_hash, NULL); + for (i = 0; i < vlans->len; i++) { + NMBridgeVlan *vlan = vlans->pdata[i]; + guint16 v, vid_start, vid_end; + + nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end); + + for (v = vid_start; v <= vid_end; v++) { + if (!nm_g_hash_table_add(h, GUINT_TO_POINTER(v))) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("duplicate bridge VLAN vid %u"), + v); + g_prefix_error(error, "%s.%s: ", setting, property); + return FALSE; + } + } + + if (nm_bridge_vlan_is_pvid(vlan)) { + if (vid_start != vid_end || pvid_found) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("only one VLAN can be the PVID")); + g_prefix_error(error, "%s.%s: ", setting, property); + return FALSE; + } + pvid_found = TRUE; + } + } + + return TRUE; +} + +gboolean +_nm_utils_iaid_verify(const char *str, gint64 *out_value) +{ + gint64 iaid; + + NM_SET_OUT(out_value, -1); + + if (!str || !str[0]) + return FALSE; + + if (NM_IAID_IS_SPECIAL(str)) + return TRUE; + + if (NM_STRCHAR_ALL(str, ch, ch >= '0' && ch <= '9') && (str[0] != '0' || str[1] == '\0') + && (iaid = _nm_utils_ascii_str_to_int64(str, 10, 0, G_MAXUINT32, -1)) != -1) { + NM_SET_OUT(out_value, iaid); + return TRUE; + } + + return FALSE; +} + +gboolean +_nm_utils_validate_dhcp_hostname_flags(NMDhcpHostnameFlags flags, int addr_family, GError **error) +{ + NMDhcpHostnameFlags unknown; + + unknown = flags; + unknown &= ~(NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED | NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE + | NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE | NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS); + if (unknown) { + g_set_error(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("unknown flags 0x%x"), + (guint) unknown); + return FALSE; + } + + if (NM_FLAGS_ALL(flags, + NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE + | NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE)) { + g_set_error_literal( + error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'fqdn-no-update' and 'fqdn-serv-update' flags cannot be set at the same time")); + return FALSE; + } + + if (NM_FLAGS_HAS(flags, NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS) + && NM_FLAGS_ANY(flags, + NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE | NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED + | NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("'fqdn-clear-flags' flag is incompatible with other FQDN flags")); + return FALSE; + } + + if (addr_family == AF_INET6 && (flags & NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED)) { + g_set_error_literal(error, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY, + _("DHCPv6 does not support the E (encoded) FQDN flag")); + return FALSE; + } + + return TRUE; +} diff --git a/src/libnm-core-impl/nm-vpn-editor-plugin.c b/src/libnm-core-impl/nm-vpn-editor-plugin.c new file mode 100644 index 00000000..468ddcea --- /dev/null +++ b/src/libnm-core-impl/nm-vpn-editor-plugin.c @@ -0,0 +1,518 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2008 Novell, Inc. + * Copyright (C) 2008 - 2010 Red Hat, Inc. + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-vpn-editor-plugin.h" + +#include +#include + +#include "libnm-core-intern/nm-core-internal.h" + +/*****************************************************************************/ + +static void nm_vpn_editor_plugin_default_init(NMVpnEditorPluginInterface *iface); + +G_DEFINE_INTERFACE(NMVpnEditorPlugin, nm_vpn_editor_plugin, G_TYPE_OBJECT) + +static void +nm_vpn_editor_plugin_default_init(NMVpnEditorPluginInterface *iface) +{ + /** + * NMVpnEditorPlugin:name: + * + * Short display name of the VPN plugin. + */ + g_object_interface_install_property( + iface, + g_param_spec_string(NM_VPN_EDITOR_PLUGIN_NAME, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * NMVpnEditorPlugin:description: + * + * Longer description of the VPN plugin. + */ + g_object_interface_install_property( + iface, + g_param_spec_string(NM_VPN_EDITOR_PLUGIN_DESCRIPTION, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * NMVpnEditorPlugin:service: + * + * D-Bus service name of the plugin's VPN service. + */ + g_object_interface_install_property( + iface, + g_param_spec_string(NM_VPN_EDITOR_PLUGIN_SERVICE, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); +} + +/*****************************************************************************/ + +typedef struct { + NMVpnPluginInfo *plugin_info; +} NMVpnEditorPluginPrivate; + +static void +_private_destroy(gpointer data) +{ + NMVpnEditorPluginPrivate *priv = data; + + if (priv->plugin_info) + g_object_remove_weak_pointer((GObject *) priv->plugin_info, + (gpointer *) &priv->plugin_info); + + g_slice_free(NMVpnEditorPluginPrivate, priv); +} + +static NMVpnEditorPluginPrivate * +_private_get(NMVpnEditorPlugin *plugin, gboolean create) +{ + static GQuark quark = 0; + NMVpnEditorPluginPrivate *priv; + + nm_assert(NM_IS_VPN_EDITOR_PLUGIN(plugin)); + + if (G_UNLIKELY(quark == 0)) + quark = g_quark_from_string("nm-vpn-editor-plugin-private"); + + priv = g_object_get_qdata((GObject *) plugin, quark); + if (G_LIKELY(priv)) + return priv; + if (!create) + return NULL; + priv = g_slice_new0(NMVpnEditorPluginPrivate); + g_object_set_qdata_full((GObject *) plugin, quark, priv, _private_destroy); + return priv; +} + +#define NM_VPN_EDITOR_PLUGIN_GET_PRIVATE(plugin) _private_get(plugin, TRUE) +#define NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE(plugin) _private_get(plugin, FALSE) + +/*****************************************************************************/ + +/** + * nm_vpn_editor_plugin_get_plugin_info: + * @plugin: the #NMVpnEditorPlugin instance + * + * Returns: (transfer none): if set, return the #NMVpnPluginInfo instance. + * + * Since: 1.4 + */ +NMVpnPluginInfo * +nm_vpn_editor_plugin_get_plugin_info(NMVpnEditorPlugin *plugin) +{ + NMVpnEditorPluginPrivate *priv; + + g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), NULL); + + priv = NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE(plugin); + return priv ? priv->plugin_info : NULL; +} + +/** + * nm_vpn_editor_plugin_set_plugin_info: + * @plugin: the #NMVpnEditorPlugin instance + * @plugin_info: (allow-none): a #NMVpnPluginInfo instance or %NULL + * + * Set or clear the plugin-info instance. + * This takes a weak reference on @plugin_info, to avoid circular + * reference as the plugin-info might also reference the editor-plugin. + * + * Since: 1.4 + */ +void +nm_vpn_editor_plugin_set_plugin_info(NMVpnEditorPlugin *plugin, NMVpnPluginInfo *plugin_info) +{ + NMVpnEditorPluginInterface *interface; + NMVpnEditorPluginPrivate * priv; + + g_return_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin)); + + if (!plugin_info) { + priv = NM_VPN_EDITOR_PLUGIN_TRY_GET_PRIVATE(plugin); + if (!priv) + return; + } else { + g_return_if_fail(NM_IS_VPN_PLUGIN_INFO(plugin_info)); + priv = NM_VPN_EDITOR_PLUGIN_GET_PRIVATE(plugin); + } + + if (priv->plugin_info == plugin_info) + return; + if (priv->plugin_info) + g_object_remove_weak_pointer((GObject *) priv->plugin_info, + (gpointer *) &priv->plugin_info); + priv->plugin_info = plugin_info; + if (priv->plugin_info) + g_object_add_weak_pointer((GObject *) priv->plugin_info, (gpointer *) &priv->plugin_info); + + if (plugin_info) { + interface = NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin); + if (interface->notify_plugin_info_set) + interface->notify_plugin_info_set(plugin, plugin_info); + } +} + +/*****************************************************************************/ + +/** + * nm_vpn_editor_plugin_get_vt: + * @plugin: the #NMVpnEditorPlugin + * @vt: (out): buffer to be filled with the VT table of the plugin + * @vt_size: the size of the buffer. Can be 0 to only query the + * size of plugin's VT. + * + * Returns an opaque VT function table for the plugin to extend + * functionality. The actual meaning of NMVpnEditorPluginVT is not + * defined in public API of libnm, instead it must be agreed by + * both the plugin and the caller. See the header-only file + * 'nm-vpn-editor-plugin-call.h' which defines the meaning. + * + * Returns: the actual size of the @plugin's virtual function table. + * + * Since: 1.4 + **/ +gsize +nm_vpn_editor_plugin_get_vt(NMVpnEditorPlugin *plugin, NMVpnEditorPluginVT *vt, gsize vt_size) +{ + const NMVpnEditorPluginVT * p_vt = NULL; + gsize p_vt_size = 0; + NMVpnEditorPluginInterface *interface; + + g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), 0); + + if (vt_size) { + g_return_val_if_fail(vt, 0); + memset(vt, 0, vt_size); + } + + interface = NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin); + if (interface->get_vt) { + p_vt = interface->get_vt(plugin, &p_vt_size); + if (!p_vt) + p_vt_size = 0; + g_return_val_if_fail(p_vt_size, 0); + memcpy(vt, p_vt, MIN(vt_size, p_vt_size)); + } + + return p_vt_size; +} + +/*****************************************************************************/ + +static NMVpnEditorPlugin * +_nm_vpn_editor_plugin_load(const char * plugin_name, + gboolean do_file_checks, + const char * check_service, + int check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError ** error) +{ + void * dl_module = NULL; + gboolean loaded_before; + NMVpnEditorPluginFactory factory = NULL; + gs_unref_object NMVpnEditorPlugin *editor_plugin = NULL; + gs_free char * plugin_filename_free = NULL; + const char * plugin_filename; + gs_free_error GError *factory_error = NULL; + gs_free char * plug_name = NULL; + gs_free char * plug_service = NULL; + + g_return_val_if_fail(plugin_name && *plugin_name, NULL); + + /* if @do_file_checks is FALSE, we pass plugin_name directly to + * g_module_open(). + * + * Otherwise, we allow for library names without path component. + * In which case, we prepend the plugin directory and form an + * absolute path. In that case, we perform checks on the file. + * + * One exception is that we don't allow for the "la" suffix. The + * reason is that g_module_open() interprets files with this extension + * special and we don't want that. */ + plugin_filename = plugin_name; + if (do_file_checks) { + if (!strchr(plugin_name, '/') && !g_str_has_suffix(plugin_name, ".la")) { + plugin_filename_free = g_module_build_path(NMVPNDIR, plugin_name); + plugin_filename = plugin_filename_free; + } + } + + dl_module = dlopen(plugin_filename, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (!dl_module && do_file_checks) { + /* If the module is already loaded, we skip the file checks. + * + * _nm_utils_check_module_file() fails with ENOENT if the plugin file + * does not exist. That is relevant, because nm-applet checks for that. */ + if (!_nm_utils_check_module_file(plugin_filename, + check_owner, + check_file, + user_data, + error)) + return NULL; + } + + if (dl_module) { + loaded_before = TRUE; + } else { + loaded_before = FALSE; + dl_module = dlopen(plugin_filename, RTLD_LAZY | RTLD_LOCAL); + } + + if (!dl_module) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("cannot load plugin \"%s\": %s"), + plugin_name, + dlerror() ?: "unknown reason"); + return NULL; + } + + factory = dlsym(dl_module, "nm_vpn_editor_plugin_factory"); + if (!factory) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("failed to load nm_vpn_editor_plugin_factory() from %s (%s)"), + plugin_name, + dlerror()); + dlclose(dl_module); + return NULL; + } + + editor_plugin = factory(&factory_error); + + if (loaded_before) { + /* we want to leak the library, because the factory will register glib + * types, which cannot be unregistered. + * + * However, if the library was already loaded before, we want to return + * our part of the reference count. */ + dlclose(dl_module); + } + + if (!editor_plugin) { + if (factory_error) { + g_propagate_error(error, factory_error); + factory_error = NULL; + } else { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("unknown error initializing plugin %s"), + plugin_name); + } + return NULL; + } + + g_return_val_if_fail(G_IS_OBJECT(editor_plugin), NULL); + + /* Validate plugin properties */ + g_object_get(G_OBJECT(editor_plugin), + NM_VPN_EDITOR_PLUGIN_NAME, + &plug_name, + NM_VPN_EDITOR_PLUGIN_SERVICE, + &plug_service, + NULL); + + if (!plug_name || !*plug_name) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("cannot load VPN plugin in '%s': missing plugin name"), + plugin_name); + return NULL; + } + if (check_service && g_strcmp0(plug_service, check_service) != 0) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("cannot load VPN plugin in '%s': invalid service name"), + plugin_name); + return NULL; + } + + return g_steal_pointer(&editor_plugin); +} + +/** + * nm_vpn_editor_plugin_load_from_file: + * @plugin_name: The path or name of the shared library to load. + * The path must either be an absolute filename to an existing file. + * Alternatively, it can be the name (without path) of a library in the + * plugin directory of NetworkManager. + * @check_service: if not-null, check that the loaded plugin advertises + * the given service. + * @check_owner: if non-negative, check whether the file is owned + * by UID @check_owner or by root. In this case also check that + * the file is not writable by anybody else. + * @check_file: (scope call): optional callback to validate the file prior to + * loading the shared library. + * @user_data: user data for @check_file + * @error: on failure the error reason. + * + * Load the shared library @plugin_name and create a new + * #NMVpnEditorPlugin instance via the #NMVpnEditorPluginFactory + * function. + * + * If @plugin_name is not an absolute path name, it assumes the file + * is in the plugin directory of NetworkManager. In any case, the call + * will do certain checks on the file before passing it to dlopen. + * A consequence for that is, that you cannot omit the ".so" suffix + * as you could for nm_vpn_editor_plugin_load(). + * + * Returns: (transfer full): a new plugin instance or %NULL on error. + * + * Since: 1.2 + */ +NMVpnEditorPlugin * +nm_vpn_editor_plugin_load_from_file(const char * plugin_name, + const char * check_service, + int check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError ** error) +{ + return _nm_vpn_editor_plugin_load(plugin_name, + TRUE, + check_service, + check_owner, + check_file, + user_data, + error); +} + +/** + * nm_vpn_editor_plugin_load: + * @plugin_name: The name of the shared library to load. + * This path will be directly passed to dlopen() without + * further checks. + * @check_service: if not-null, check that the loaded plugin advertises + * the given service. + * @error: on failure the error reason. + * + * Load the shared library @plugin_name and create a new + * #NMVpnEditorPlugin instance via the #NMVpnEditorPluginFactory + * function. + * + * This is similar to nm_vpn_editor_plugin_load_from_file(), but + * it does no validation of the plugin name, instead passes it directly + * to dlopen(). If you have the full path to a plugin file, + * nm_vpn_editor_plugin_load_from_file() is preferred. + * + * Returns: (transfer full): a new plugin instance or %NULL on error. + * + * Since: 1.4 + */ +NMVpnEditorPlugin * +nm_vpn_editor_plugin_load(const char *plugin_name, const char *check_service, GError **error) +{ + return _nm_vpn_editor_plugin_load(plugin_name, FALSE, check_service, -1, NULL, NULL, error); +} + +/*****************************************************************************/ + +/** + * nm_vpn_editor_plugin_get_editor: + * @plugin: the #NMVpnEditorPlugin + * @connection: the #NMConnection to be edited + * @error: on return, an error or %NULL + * + * Returns: (transfer full): a new #NMVpnEditor or %NULL on error + */ +NMVpnEditor * +nm_vpn_editor_plugin_get_editor(NMVpnEditorPlugin *plugin, NMConnection *connection, GError **error) +{ + g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), NULL); + + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin)->get_editor(plugin, connection, error); +} + +NMVpnEditorPluginCapability +nm_vpn_editor_plugin_get_capabilities(NMVpnEditorPlugin *plugin) +{ + g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), 0); + + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin)->get_capabilities(plugin); +} + +/** + * nm_vpn_editor_plugin_import: + * @plugin: the #NMVpnEditorPlugin + * @path: full path to the file to attempt to read into a new #NMConnection + * @error: on return, an error or %NULL + * + * Returns: (transfer full): a new #NMConnection imported from @path, or %NULL + * on error or if the file at @path was not recognized by this plugin + */ +NMConnection * +nm_vpn_editor_plugin_import(NMVpnEditorPlugin *plugin, const char *path, GError **error) +{ + g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), NULL); + + if (nm_vpn_editor_plugin_get_capabilities(plugin) & NM_VPN_EDITOR_PLUGIN_CAPABILITY_IMPORT) { + g_return_val_if_fail(NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin)->import_from_file != NULL, + NULL); + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin)->import_from_file(plugin, path, error); + } + + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("the plugin does not support import capability")); + return NULL; +} + +gboolean +nm_vpn_editor_plugin_export(NMVpnEditorPlugin *plugin, + const char * path, + NMConnection * connection, + GError ** error) +{ + g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), FALSE); + + if (nm_vpn_editor_plugin_get_capabilities(plugin) & NM_VPN_EDITOR_PLUGIN_CAPABILITY_EXPORT) { + g_return_val_if_fail(NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin)->export_to_file != NULL, + FALSE); + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin)->export_to_file(plugin, + path, + connection, + error); + } + + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("the plugin does not support export capability")); + return FALSE; +} + +char * +nm_vpn_editor_plugin_get_suggested_filename(NMVpnEditorPlugin *plugin, NMConnection *connection) +{ + g_return_val_if_fail(NM_IS_VPN_EDITOR_PLUGIN(plugin), NULL); + + if (NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin)->get_suggested_filename) + return NM_VPN_EDITOR_PLUGIN_GET_INTERFACE(plugin)->get_suggested_filename(plugin, + connection); + return NULL; +} diff --git a/src/libnm-core-impl/nm-vpn-plugin-info.c b/src/libnm-core-impl/nm-vpn-plugin-info.c new file mode 100644 index 00000000..dc77dfd0 --- /dev/null +++ b/src/libnm-core-impl/nm-vpn-plugin-info.c @@ -0,0 +1,1354 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-vpn-plugin-info.h" + +#include + +#include "nm-errors.h" +#include "libnm-core-intern/nm-core-internal.h" + +#define DEFAULT_DIR_ETC NMCONFDIR "/VPN" +#define DEFAULT_DIR_LIB NMLIBDIR "/VPN" + +enum { + PROP_0, + PROP_NAME, + PROP_FILENAME, + PROP_KEYFILE, + + LAST_PROP, +}; + +typedef struct { + char * filename; + char * name; + char * service; + char * auth_dialog; + char ** aliases; + GKeyFile *keyfile; + + /* It is convenient for nm_vpn_plugin_info_lookup_property() to return a const char *, + * contrary to what g_key_file_get_string() does. Hence we must cache the returned + * value somewhere... let's put it in an internal hash table. + * This contains a clone of all the strings in keyfile. */ + GHashTable *keys; + + gboolean editor_plugin_loaded; + NMVpnEditorPlugin *editor_plugin; +} NMVpnPluginInfoPrivate; + +/** + * NMVpnPluginInfo: + */ +struct _NMVpnPluginInfo { + GObject parent; + NMVpnPluginInfoPrivate _priv; +}; + +struct _NMVpnPluginInfoClass { + GObjectClass parent; +}; + +#define NM_VPN_PLUGIN_INFO_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMVpnPluginInfo, NM_IS_VPN_PLUGIN_INFO) + +static void nm_vpn_plugin_info_initable_iface_init(GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE(NMVpnPluginInfo, + nm_vpn_plugin_info, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(G_TYPE_INITABLE, + nm_vpn_plugin_info_initable_iface_init);) + +/*****************************************************************************/ + +static NMVpnPluginInfo *_list_find_by_service(GSList *list, const char *name, const char *service); + +/*****************************************************************************/ + +/** + * nm_vpn_plugin_info_validate_filename: + * @filename: the filename to check + * + * Regular name files have a certain pattern. That basically means + * they have the file extension "name". Check if @filename + * is valid according to that pattern. + * + * Since: 1.2 + */ +gboolean +nm_vpn_plugin_info_validate_filename(const char *filename) +{ + if (!filename || !g_str_has_suffix(filename, ".name")) + return FALSE; + + /* originally, we didn't do further checks... but here we go. */ + if (filename[0] == '.') { + /* this also rejects name ".name" alone. */ + return FALSE; + } + return TRUE; +} + +static gboolean +nm_vpn_plugin_info_check_file_full(const char * filename, + gboolean check_absolute, + gboolean do_validate_filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + struct stat * out_st, + GError ** error) +{ + if (!filename || !*filename) { + g_set_error(error, NM_VPN_PLUGIN_ERROR, NM_VPN_PLUGIN_ERROR_FAILED, _("missing filename")); + return FALSE; + } + + if (check_absolute && !g_path_is_absolute(filename)) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("filename must be an absolute path (%s)"), + filename); + return FALSE; + } + + if (do_validate_filename && !nm_vpn_plugin_info_validate_filename(filename)) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("filename has invalid format (%s)"), + filename); + return FALSE; + } + + return _nm_utils_check_file(filename, check_owner, check_file, user_data, out_st, error); +} + +/** + * _nm_vpn_plugin_info_check_file: + * @filename: the file to check + * @check_absolute: if %TRUE, only allow absolute path names. + * @do_validate_filename: if %TRUE, only accept the filename if + * nm_vpn_plugin_info_validate_filename() succeeds. + * @check_owner: if non-negative, only accept the file if the + * owner UID is equal to @check_owner or if the owner is 0. + * In this case, also check that the file is not writable by + * other users. + * @check_file: pass a callback to do your own validation. + * @user_data: user data for @check_file. + * @error: (allow-none) (out): the error reason if the check fails. + * + * Check whether the file exists and is a valid name file (in keyfile format). + * Additionally, also check for file permissions. + * + * Returns: %TRUE if a file @filename exists and has valid permissions. + * + * Since: 1.2 + */ +gboolean +_nm_vpn_plugin_info_check_file(const char * filename, + gboolean check_absolute, + gboolean do_validate_filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data, + GError ** error) +{ + return nm_vpn_plugin_info_check_file_full(filename, + check_absolute, + do_validate_filename, + check_owner, + check_file, + user_data, + NULL, + error); +} + +typedef struct { + NMVpnPluginInfo *plugin_info; + struct stat stat; +} LoadDirInfo; + +static int +_sort_files(LoadDirInfo *a, LoadDirInfo *b) +{ + time_t ta, tb; + + ta = MAX(a->stat.st_mtime, a->stat.st_ctime); + tb = MAX(b->stat.st_mtime, b->stat.st_ctime); + if (ta < tb) + return 1; + if (ta > tb) + return -1; + return g_strcmp0(nm_vpn_plugin_info_get_filename(a->plugin_info), + nm_vpn_plugin_info_get_filename(b->plugin_info)); +} + +/** + * _nm_vpn_plugin_info_get_default_dir_etc: + * + * Returns: (transfer none): compile time constant of the default + * VPN plugin directory. + */ +const char * +_nm_vpn_plugin_info_get_default_dir_etc() +{ + return DEFAULT_DIR_ETC; +} + +/** + * _nm_vpn_plugin_info_get_default_dir_lib: + * + * Returns: (transfer none): compile time constant of the default + * VPN plugin directory. + */ +const char * +_nm_vpn_plugin_info_get_default_dir_lib() +{ + return DEFAULT_DIR_LIB; +} + +/** + * _nm_vpn_plugin_info_get_default_dir_user: + * + * Returns: The user can specify a different directory for VPN plugins + * by setting NM_VPN_PLUGIN_DIR environment variable. Return + * that directory. + */ +const char * +_nm_vpn_plugin_info_get_default_dir_user() +{ + return nm_str_not_empty(g_getenv("NM_VPN_PLUGIN_DIR")); +} + +/** + * _nm_vpn_plugin_info_list_load_dir: + * @dirname: the name of the directory to load. + * @do_validate_filename: only consider filenames that have a certain + * pattern (i.e. end with ".name"). + * @check_owner: if set to a non-negative number, check that the file + * owner is either the same uid or 0. In that case, also check + * that the file is not writable by group or other. + * @check_file: (allow-none): callback to check whether the file is valid. + * @user_data: data for @check_file + * + * Iterate over the content of @dirname and load name files. + * + * Returns: (transfer full) (element-type NMVpnPluginInfo): list of loaded plugin infos. + */ +GSList * +_nm_vpn_plugin_info_list_load_dir(const char * dirname, + gboolean do_validate_filename, + gint64 check_owner, + NMUtilsCheckFilePredicate check_file, + gpointer user_data) +{ + GDir * dir; + const char *fn; + GArray * array; + GSList * res = NULL; + guint i; + + g_return_val_if_fail(dirname, NULL); + + if (!dirname[0]) + return NULL; + + dir = g_dir_open(dirname, 0, NULL); + if (!dir) + return NULL; + + array = g_array_new(FALSE, FALSE, sizeof(LoadDirInfo)); + + while ((fn = g_dir_read_name(dir))) { + gs_free char *filename = NULL; + LoadDirInfo info = {0}; + + filename = g_build_filename(dirname, fn, NULL); + if (nm_vpn_plugin_info_check_file_full(filename, + FALSE, + do_validate_filename, + check_owner, + check_file, + user_data, + &info.stat, + NULL)) { + info.plugin_info = nm_vpn_plugin_info_new_from_file(filename, NULL); + if (info.plugin_info) { + g_array_append_val(array, info); + continue; + } + } + } + g_dir_close(dir); + + /* sort the files so that we have a stable behavior. The directory might contain + * duplicate VPNs, so while nm_vpn_plugin_info_list_load() would load them all, the + * caller probably wants to reject duplicates. Having a stable order means we always + * reject the same files in face of duplicates. */ + g_array_sort(array, (GCompareFunc) _sort_files); + + for (i = 0; i < array->len; i++) + res = g_slist_prepend(res, g_array_index(array, LoadDirInfo, i).plugin_info); + + g_array_unref(array); + + return g_slist_reverse(res); +} + +/** + * nm_vpn_plugin_info_list_load: + * + * Returns: (element-type NMVpnPluginInfo) (transfer full): list of plugins + * loaded from the default directories rejecting duplicates. + * + * Since: 1.2 + */ +GSList * +nm_vpn_plugin_info_list_load() +{ + int i; + gint64 uid; + GSList * list = NULL; + GSList * infos, *info; + const char *const dir[] = { + /* We load plugins from NM_VPN_PLUGIN_DIR *and* DEFAULT_DIR*, with + * preference to the former. + * + * load user directory with highest priority. */ + _nm_vpn_plugin_info_get_default_dir_user(), + + /* lib directory has higher priority then etc. The reason is that + * etc is deprecated and used by old plugins. We expect newer plugins + * to install their file in lib, where they have higher priority. + * + * Optimally, there are no duplicates anyway, so it doesn't really matter. */ + _nm_vpn_plugin_info_get_default_dir_lib(), + _nm_vpn_plugin_info_get_default_dir_etc(), + }; + + uid = getuid(); + + for (i = 0; i < G_N_ELEMENTS(dir); i++) { + if (!dir[i] || nm_utils_strv_find_first((char **) dir, i, dir[i]) >= 0) + continue; + + infos = _nm_vpn_plugin_info_list_load_dir(dir[i], TRUE, uid, NULL, NULL); + + for (info = infos; info; info = info->next) + nm_vpn_plugin_info_list_add(&list, info->data, NULL); + + g_slist_free_full(infos, g_object_unref); + } + return list; +} + +/** + * nm_vpn_plugin_info_new_search_file: + * @name: (allow-none): the name to search for. Either @name or @service + * must be present. + * @service: (allow-none): the service to search for. Either @name or + * @service must be present. + * + * This has the same effect as doing a full nm_vpn_plugin_info_list_load() + * followed by a search for the first matching VPN plugin info that has the + * given @name and/or @service. + * + * Returns: (transfer full): a newly created instance of plugin info + * or %NULL if no matching value was found. + * + * Since: 1.4 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_new_search_file(const char *name, const char *service) +{ + NMVpnPluginInfo *info; + GSList * infos; + + if (!name && !service) + g_return_val_if_reached(NULL); + + infos = nm_vpn_plugin_info_list_load(); + info = nm_g_object_ref(_list_find_by_service(infos, name, service)); + g_slist_free_full(infos, g_object_unref); + return info; +} + +/*****************************************************************************/ + +static gboolean +_check_no_conflict(NMVpnPluginInfo *i1, NMVpnPluginInfo *i2, GError **error) +{ + NMVpnPluginInfoPrivate *priv1, *priv2; + uint i; + struct { + const char *group; + const char *key; + } check_list[] = { + {NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "service"}, + {NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM, "plugin"}, + {NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "properties"}, + }; + + priv1 = NM_VPN_PLUGIN_INFO_GET_PRIVATE(i1); + priv2 = NM_VPN_PLUGIN_INFO_GET_PRIVATE(i2); + + for (i = 0; i < G_N_ELEMENTS(check_list); i++) { + gs_free NMUtilsStrStrDictKey *k = NULL; + const char * s1, *s2; + + k = _nm_utils_strstrdictkey_create(check_list[i].group, check_list[i].key); + s1 = g_hash_table_lookup(priv1->keys, k); + if (!s1) + continue; + s2 = g_hash_table_lookup(priv2->keys, k); + if (!s2) + continue; + + if (strcmp(s1, s2) == 0) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("there exists a conflicting plugin (%s) that has the same %s.%s value"), + priv2->name, + check_list[i].group, + check_list[i].key); + return FALSE; + } + } + return TRUE; +} + +/** + * nm_vpn_plugin_info_list_add: + * @list: (element-type NMVpnPluginInfo): list of plugins + * @plugin_info: instance to add + * @error: failure reason + * + * Returns: %TRUE if the plugin was added to @list. This will fail + * to add duplicate plugins. + * + * Since: 1.2 + */ +gboolean +nm_vpn_plugin_info_list_add(GSList **list, NMVpnPluginInfo *plugin_info, GError **error) +{ + GSList * iter; + const char *name; + + g_return_val_if_fail(list, FALSE); + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(plugin_info), FALSE); + + name = nm_vpn_plugin_info_get_name(plugin_info); + for (iter = *list; iter; iter = iter->next) { + if (iter->data == plugin_info) + return TRUE; + + if (strcmp(nm_vpn_plugin_info_get_name(iter->data), name) == 0) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("there exists a conflicting plugin with the same name (%s)"), + name); + return FALSE; + } + + /* the plugin must have unique values for certain properties. E.g. two different + * plugins cannot share the same service type. */ + if (!_check_no_conflict(plugin_info, iter->data, error)) + return FALSE; + } + + *list = g_slist_append(*list, g_object_ref(plugin_info)); + return TRUE; +} + +/** + * nm_vpn_plugin_info_list_remove: + * @list: (element-type NMVpnPluginInfo): list of plugins + * @plugin_info: instance + * + * Remove @plugin_info from @list. + * + * Returns: %TRUE if @plugin_info was in @list and successfully removed. + * + * Since: 1.2 + */ +gboolean +nm_vpn_plugin_info_list_remove(GSList **list, NMVpnPluginInfo *plugin_info) +{ + g_return_val_if_fail(list, FALSE); + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(plugin_info), FALSE); + + if (!g_slist_find(*list, plugin_info)) + return FALSE; + + *list = g_slist_remove(*list, plugin_info); + g_object_unref(plugin_info); + return TRUE; +} + +/** + * nm_vpn_plugin_info_list_find_by_name: + * @list: (element-type NMVpnPluginInfo): list of plugins + * @name: name to search + * + * Returns: (transfer none): the first plugin with a matching @name (or %NULL). + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_list_find_by_name(GSList *list, const char *name) +{ + GSList *iter; + + if (!name) + g_return_val_if_reached(NULL); + + for (iter = list; iter; iter = iter->next) { + if (strcmp(nm_vpn_plugin_info_get_name(iter->data), name) == 0) + return iter->data; + } + return NULL; +} + +/** + * nm_vpn_plugin_info_list_find_by_filename: + * @list: (element-type NMVpnPluginInfo): list of plugins + * @filename: filename to search + * + * Returns: (transfer none): the first plugin with a matching @filename (or %NULL). + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_list_find_by_filename(GSList *list, const char *filename) +{ + GSList *iter; + + if (!filename) + g_return_val_if_reached(NULL); + + for (iter = list; iter; iter = iter->next) { + if (g_strcmp0(nm_vpn_plugin_info_get_filename(iter->data), filename) == 0) + return iter->data; + } + return NULL; +} + +static NMVpnPluginInfo * +_list_find_by_service(GSList *list, const char *name, const char *service) +{ + for (; list; list = list->next) { + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(list->data); + + if (name && !nm_streq(name, priv->name)) + continue; + if (service && !nm_streq(priv->service, service) + && (nm_utils_strv_find_first(priv->aliases, -1, service) < 0)) + continue; + + return list->data; + } + return NULL; +} + +/** + * nm_vpn_plugin_info_list_find_by_service: + * @list: (element-type NMVpnPluginInfo): list of plugins + * @service: service to search. This can be the main service-type + * or one of the provided aliases. + * + * Returns: (transfer none): the first plugin with a matching @service (or %NULL). + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_list_find_by_service(GSList *list, const char *service) +{ + if (!service) + g_return_val_if_reached(NULL); + return _list_find_by_service(list, NULL, service); +} + +/* known_names are well known short names for the service-type. They all implicitly + * have a prefix "org.freedesktop.NetworkManager." + known_name. */ +static const char *known_names[] = { + "openvpn", + "vpnc", + "pptp", + "openconnect", + "openswan", + "libreswan", + "strongswan", + "ssh", + "l2tp", + "iodine", + "fortisslvpn", +}; + +/** + * nm_vpn_plugin_info_list_find_service_type: + * @list: (element-type NMVpnPluginInfo): a possibly empty #GSList of #NMVpnPluginInfo instances + * @name: a name to lookup the service-type. + * + * A VPN plugin provides one or several service-types, like org.freedesktop.NetworkManager.libreswan + * Certain plugins provide more then one service type, via aliases (org.freedesktop.NetworkManager.openswan). + * This function looks up a service-type (or an alias) based on a name. + * + * Preferably, the name can be a full service-type/alias of an installed + * plugin. Otherwise, it can be the name of a VPN plugin (in which case, the + * primary, non-aliased service-type is returned). Otherwise, it can be + * one of several well known short-names (which is a hard-coded list of + * types in libnm). On success, this returns a full qualified service-type + * (or an alias). It doesn't say, that such an plugin is actually available, + * but it could be retrieved via nm_vpn_plugin_info_list_find_by_service(). + * + * Returns: (transfer full): the resolved service-type or %NULL on failure. + * + * Since: 1.4 + */ +char * +nm_vpn_plugin_info_list_find_service_type(GSList *list, const char *name) +{ + NMVpnPluginInfo *info; + char * n; + + if (!name) + g_return_val_if_reached(NULL); + if (!*name) + return NULL; + + /* First, try to interpret @name as a full service-type (or alias). */ + info = _list_find_by_service(list, NULL, name); + if (info) + return g_strdup(name); + + /* try to interpret @name as plugin name, in which case we return + * the main service-type (not an alias). */ + info = _list_find_by_service(list, name, NULL); + if (info) + return g_strdup(NM_VPN_PLUGIN_INFO_GET_PRIVATE(info)->service); + + /* check the hard-coded list of short-names. They all have the same + * well-known prefix org.freedesktop.NetworkManager and the name. */ + if (nm_utils_strv_find_first((char **) known_names, G_N_ELEMENTS(known_names), name) >= 0) + return g_strdup_printf("%s.%s", NM_DBUS_INTERFACE, name); + + /* try, if there exists a plugin with @name under org.freedesktop.NetworkManager. + * Allow this to be a valid abbreviation. */ + n = g_strdup_printf("%s.%s", NM_DBUS_INTERFACE, name); + if (_list_find_by_service(list, NULL, n)) + return n; + g_free(n); + + /* currently, VPN plugins have no way to define a short-name for their + * alias name, unless the alias name is prefixed by org.freedesktop.NetworkManager. */ + + return NULL; +} + +static const char * +_service_type_get_default_abbreviation(const char *service_type) +{ + if (!g_str_has_prefix(service_type, NM_DBUS_INTERFACE)) + return NULL; + service_type += NM_STRLEN(NM_DBUS_INTERFACE); + if (service_type[0] != '.') + return NULL; + service_type++; + if (!service_type[0]) + return NULL; + return service_type; +} + +/** + * nm_vpn_plugin_info_list_get_service_types: + * @list: (element-type NMVpnPluginInfo): a possibly empty #GSList of #NMVpnPluginInfo + * @only_existing: only include results that are actually in @list. + * Otherwise, the result is extended with a hard-code list or + * well-known plugins + * @with_abbreviations: if %FALSE, only full service types are returned. + * Otherwise, this also includes abbreviated names that can be used + * with nm_vpn_plugin_info_list_find_service_type(). + * + * Returns: (transfer full): a %NULL terminated strv list of strings. + * The list itself and the values must be freed with g_strfreev(). + * + * Since: 1.4 + */ +char ** +nm_vpn_plugin_info_list_get_service_types(GSList * list, + gboolean only_existing, + gboolean with_abbreviations) +{ + GSList * iter; + GPtrArray * l; + guint i, j; + const char *n; + + l = g_ptr_array_sized_new(20); + + for (iter = list; iter; iter = iter->next) { + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(iter->data); + + g_ptr_array_add(l, g_strdup(priv->service)); + if (priv->aliases) { + for (i = 0; priv->aliases[i]; i++) + g_ptr_array_add(l, g_strdup(priv->aliases[i])); + } + + if (with_abbreviations) { + g_ptr_array_add(l, g_strdup(priv->name)); + n = _service_type_get_default_abbreviation(priv->service); + if (n) + g_ptr_array_add(l, g_strdup(n)); + for (i = 0; priv->aliases && priv->aliases[i]; i++) { + n = _service_type_get_default_abbreviation(priv->aliases[i]); + if (n) + g_ptr_array_add(l, g_strdup(n)); + } + } + } + + if (!only_existing) { + for (i = 0; i < G_N_ELEMENTS(known_names); i++) { + g_ptr_array_add(l, g_strdup_printf("%s.%s", NM_DBUS_INTERFACE, known_names[i])); + if (with_abbreviations) + g_ptr_array_add(l, g_strdup(known_names[i])); + } + } + + if (l->len <= 0) { + g_ptr_array_free(l, TRUE); + return g_new0(char *, 1); + } + + /* sort the result and remove duplicates. */ + g_ptr_array_sort(l, nm_strcmp_p); + for (i = 1, j = 1; i < l->len; i++) { + if (nm_streq(l->pdata[j - 1], l->pdata[i])) + g_free(l->pdata[i]); + else + l->pdata[j++] = l->pdata[i]; + } + + if (j == l->len) + g_ptr_array_add(l, NULL); + else + l->pdata[j] = NULL; + return (char **) g_ptr_array_free(l, FALSE); +} + +/*****************************************************************************/ + +/** + * nm_vpn_plugin_info_get_filename: + * @self: plugin info instance + * + * Returns: (transfer none): the filename. Can be %NULL. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_get_filename(NMVpnPluginInfo *self) +{ + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + return NM_VPN_PLUGIN_INFO_GET_PRIVATE(self)->filename; +} + +/** + * nm_vpn_plugin_info_get_name: + * @self: plugin info instance + * + * Returns: (transfer none): the name. Cannot be %NULL. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_get_name(NMVpnPluginInfo *self) +{ + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + return NM_VPN_PLUGIN_INFO_GET_PRIVATE(self)->name; +} + +/** + * nm_vpn_plugin_info_get_service: + * @self: plugin info instance + * + * Returns: (transfer none): the service. Cannot be %NULL. + * + * Since: 1.4 + */ +const char * +nm_vpn_plugin_info_get_service(NMVpnPluginInfo *self) +{ + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + return NM_VPN_PLUGIN_INFO_GET_PRIVATE(self)->service; +} + +/** + * nm_vpn_plugin_info_get_auth_dialog: + * @self: plugin info instance + * + * Returns: the absolute path to the auth-dialog helper or %NULL. + * + * Since: 1.4 + **/ +const char * +nm_vpn_plugin_info_get_auth_dialog(NMVpnPluginInfo *self) +{ + NMVpnPluginInfoPrivate *priv; + + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); + + if (G_UNLIKELY(priv->auth_dialog == NULL)) { + const char *s; + + s = g_hash_table_lookup( + priv->keys, + _nm_utils_strstrdictkey_static(NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, "auth-dialog")); + if (!s || !s[0]) + priv->auth_dialog = g_strdup(""); + else if (g_path_is_absolute(s)) + priv->auth_dialog = g_strdup(s); + else { + /* for relative paths, we take the basename and assume it's in LIBEXECDIR. */ + gs_free char *prog_basename = g_path_get_basename(s); + + priv->auth_dialog = g_build_filename(LIBEXECDIR, prog_basename, NULL); + } + } + + return priv->auth_dialog[0] ? priv->auth_dialog : NULL; +} + +/** + * nm_vpn_plugin_info_supports_hints: + * @self: plugin info instance + * + * Returns: %TRUE if the supports hints for secret requests, otherwise %FALSE + * + * Since: 1.4 + */ +gboolean +nm_vpn_plugin_info_supports_hints(NMVpnPluginInfo *self) +{ + const char *s; + + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), FALSE); + + s = nm_vpn_plugin_info_lookup_property(self, + NM_VPN_PLUGIN_INFO_KF_GROUP_GNOME, + "supports-hints"); + return _nm_utils_ascii_str_to_bool(s, FALSE); +} + +/** + * nm_vpn_plugin_info_get_plugin: + * @self: plugin info instance + * + * Returns: (transfer none): the plugin. Can be %NULL. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_get_plugin(NMVpnPluginInfo *self) +{ + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + return g_hash_table_lookup( + NM_VPN_PLUGIN_INFO_GET_PRIVATE(self)->keys, + _nm_utils_strstrdictkey_static(NM_VPN_PLUGIN_INFO_KF_GROUP_LIBNM, "plugin")); +} + +/** + * nm_vpn_plugin_info_get_program: + * @self: plugin info instance + * + * Returns: (transfer none): the program. Can be %NULL. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_get_program(NMVpnPluginInfo *self) +{ + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + return g_hash_table_lookup( + NM_VPN_PLUGIN_INFO_GET_PRIVATE(self)->keys, + _nm_utils_strstrdictkey_static(NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "program")); +} + +/** + * nm_vpn_plugin_info_supports_multiple: + * @self: plugin info instance + * + * Returns: %TRUE if the service supports multiple instances with different bus names, otherwise %FALSE + * + * Since: 1.2 + */ +gboolean +nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self) +{ + const char *s; + + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), FALSE); + + s = nm_vpn_plugin_info_lookup_property(self, + NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, + "supports-multiple-connections"); + return _nm_utils_ascii_str_to_bool(s, FALSE); +} + +/** + * nm_vpn_plugin_info_get_aliases: + * @self: plugin info instance + * + * Returns: (array zero-terminated=1) (element-type utf8) (transfer none): + * the aliases from the name-file. + * + * Since: 1.4 + */ +const char *const * +nm_vpn_plugin_info_get_aliases(NMVpnPluginInfo *self) +{ + NMVpnPluginInfoPrivate *priv; + + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); + if (priv->aliases) + return (const char *const *) priv->aliases; + + /* For convenience, we always want to return non-NULL, even for empty + * aliases. Hack around that, by making a NULL terminated array using + * the NULL of priv->aliases. */ + return (const char *const *) &priv->aliases; +} + +/** + * nm_vpn_plugin_info_lookup_property: + * @self: plugin info instance + * @group: group name + * @key: name of the property + * + * Returns: (transfer none): #NMVpnPluginInfo is internally a #GKeyFile. Returns the matching + * property. + * + * Since: 1.2 + */ +const char * +nm_vpn_plugin_info_lookup_property(NMVpnPluginInfo *self, const char *group, const char *key) +{ + NMVpnPluginInfoPrivate *priv; + gs_free NMUtilsStrStrDictKey *k = NULL; + + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + g_return_val_if_fail(group, NULL); + g_return_val_if_fail(key, NULL); + + priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); + + k = _nm_utils_strstrdictkey_create(group, key); + return g_hash_table_lookup(priv->keys, k); +} + +/*****************************************************************************/ + +/** + * nm_vpn_plugin_info_get_editor_plugin: + * @self: plugin info instance + * + * Returns: (transfer none): the cached #NMVpnEditorPlugin instance. + * + * Since: 1.2 + */ +NMVpnEditorPlugin * +nm_vpn_plugin_info_get_editor_plugin(NMVpnPluginInfo *self) +{ + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + return NM_VPN_PLUGIN_INFO_GET_PRIVATE(self)->editor_plugin; +} + +/** + * nm_vpn_plugin_info_set_editor_plugin: + * @self: plugin info instance + * @plugin: (allow-none): plugin instance + * + * Set the internal plugin instance. If %NULL, only clear the previous instance. + * + * Since: 1.2 + */ +void +nm_vpn_plugin_info_set_editor_plugin(NMVpnPluginInfo *self, NMVpnEditorPlugin *plugin) +{ + NMVpnPluginInfoPrivate *priv; + NMVpnEditorPlugin * old; + + g_return_if_fail(NM_IS_VPN_PLUGIN_INFO(self)); + g_return_if_fail(!plugin || G_IS_OBJECT(plugin)); + + priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); + + if (!plugin) { + priv->editor_plugin_loaded = FALSE; + g_clear_object(&priv->editor_plugin); + } else { + old = priv->editor_plugin; + priv->editor_plugin = g_object_ref(plugin); + priv->editor_plugin_loaded = TRUE; + if (old) + g_object_unref(old); + } +} + +/** + * nm_vpn_plugin_info_load_editor_plugin: + * @self: plugin info instance + * @error: error reason on failure + * + * Returns: (transfer none): loads the plugin and returns the newly created + * instance. The plugin is owned by @self and can be later retrieved again + * via nm_vpn_plugin_info_get_editor_plugin(). You can load the + * plugin only once, unless you reset the state via + * nm_vpn_plugin_info_set_editor_plugin(). + * + * Since: 1.2 + */ +NMVpnEditorPlugin * +nm_vpn_plugin_info_load_editor_plugin(NMVpnPluginInfo *self, GError **error) +{ + NMVpnPluginInfoPrivate *priv; + const char * plugin_filename; + + g_return_val_if_fail(NM_IS_VPN_PLUGIN_INFO(self), NULL); + + priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); + + if (priv->editor_plugin) + return priv->editor_plugin; + + plugin_filename = nm_vpn_plugin_info_get_plugin(self); + if (!plugin_filename || !*plugin_filename) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("missing \"plugin\" setting")); + return NULL; + } + + /* We only try once to load the plugin. If we previously tried and it was + * unsuccessful, error out immediately. */ + if (priv->editor_plugin_loaded) { + g_set_error(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_FAILED, + _("%s: don't retry loading plugin which already failed previously"), + priv->name); + return NULL; + } + + priv->editor_plugin_loaded = TRUE; + priv->editor_plugin = nm_vpn_editor_plugin_load_from_file(plugin_filename, + nm_vpn_plugin_info_get_service(self), + getuid(), + NULL, + NULL, + error); + if (priv->editor_plugin) + nm_vpn_editor_plugin_set_plugin_info(priv->editor_plugin, self); + return priv->editor_plugin; +} + +/*****************************************************************************/ + +static void +get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) +{ + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string(value, priv->name); + break; + case PROP_FILENAME: + g_value_set_string(value, priv->filename); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) +{ + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(object); + + switch (prop_id) { + case PROP_FILENAME: + priv->filename = g_value_dup_string(value); + break; + case PROP_KEYFILE: + priv->keyfile = g_value_dup_boxed(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +/*****************************************************************************/ + +static void +nm_vpn_plugin_info_init(NMVpnPluginInfo *plugin) +{} + +static gboolean +init_sync(GInitable *initable, GCancellable *cancellable, GError **error) +{ + NMVpnPluginInfo * self = NM_VPN_PLUGIN_INFO(initable); + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); + gs_strfreev char ** groups = NULL; + guint i, j; + + if (!priv->keyfile) { + if (!priv->filename) { + g_set_error_literal(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("missing filename to load VPN plugin info")); + return FALSE; + } + priv->keyfile = g_key_file_new(); + if (!g_key_file_load_from_file(priv->keyfile, priv->filename, G_KEY_FILE_NONE, error)) + return FALSE; + } + + /* we reqire at least a "name" */ + priv->name = + g_key_file_get_string(priv->keyfile, NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, "name", NULL); + if (!priv->name || !priv->name[0]) { + g_set_error_literal(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("missing name for VPN plugin info")); + return FALSE; + } + + /* we also require "service", because that how we associate NMSettingVpn:service-type with the + * NMVpnPluginInfo. */ + priv->service = g_key_file_get_string(priv->keyfile, + NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, + "service", + NULL); + if (!priv->service || !*priv->service) { + g_set_error_literal(error, + NM_VPN_PLUGIN_ERROR, + NM_VPN_PLUGIN_ERROR_BAD_ARGUMENTS, + _("missing service for VPN plugin info")); + return FALSE; + } + + priv->aliases = g_key_file_get_string_list(priv->keyfile, + NM_VPN_PLUGIN_INFO_KF_GROUP_CONNECTION, + "aliases", + NULL, + NULL); + if (priv->aliases && !priv->aliases[0]) + nm_clear_g_free(&priv->aliases); + + priv->keys = g_hash_table_new_full(_nm_utils_strstrdictkey_hash, + _nm_utils_strstrdictkey_equal, + g_free, + g_free); + groups = g_key_file_get_groups(priv->keyfile, NULL); + for (i = 0; groups && groups[i]; i++) { + gs_strfreev char **keys = NULL; + + keys = g_key_file_get_keys(priv->keyfile, groups[i], NULL, NULL); + for (j = 0; keys && keys[j]; j++) { + char *s; + + /* Lookup the value via get_string(). We want that behavior for all our + * values. */ + s = g_key_file_get_string(priv->keyfile, groups[i], keys[j], NULL); + if (s) + g_hash_table_insert(priv->keys, + _nm_utils_strstrdictkey_create(groups[i], keys[j]), + s); + } + } + + nm_clear_pointer(&priv->keyfile, g_key_file_unref); + + return TRUE; +} + +/** + * nm_vpn_plugin_info_new_from_file: + * @filename: filename to read. + * @error: on failure, the error reason. + * + * Read the plugin info from file @filename. Does not do + * any further verification on the file. You might want to check + * file permissions and ownership of the file. + * + * Returns: %NULL if there is any error or a newly created + * #NMVpnPluginInfo instance. + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_new_from_file(const char *filename, GError **error) +{ + g_return_val_if_fail(filename, NULL); + + return NM_VPN_PLUGIN_INFO(g_initable_new(NM_TYPE_VPN_PLUGIN_INFO, + NULL, + error, + NM_VPN_PLUGIN_INFO_FILENAME, + filename, + NULL)); +} + +/** + * nm_vpn_plugin_info_new_with_data: + * @filename: optional filename. + * @keyfile: inject data for the plugin info instance. + * @error: construction may fail if the keyfile lacks mandatory fields. + * In this case, return the error reason. + * + * This constructor does not read any data from file but + * takes instead a @keyfile argument. + * + * Returns: new plugin info instance. + * + * Since: 1.2 + */ +NMVpnPluginInfo * +nm_vpn_plugin_info_new_with_data(const char *filename, GKeyFile *keyfile, GError **error) +{ + g_return_val_if_fail(keyfile, NULL); + + return NM_VPN_PLUGIN_INFO(g_initable_new(NM_TYPE_VPN_PLUGIN_INFO, + NULL, + error, + NM_VPN_PLUGIN_INFO_FILENAME, + filename, + NM_VPN_PLUGIN_INFO_KEYFILE, + keyfile, + NULL)); +} + +static void +dispose(GObject *object) +{ + NMVpnPluginInfo * self = NM_VPN_PLUGIN_INFO(object); + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); + + g_clear_object(&priv->editor_plugin); + + G_OBJECT_CLASS(nm_vpn_plugin_info_parent_class)->dispose(object); +} + +static void +finalize(GObject *object) +{ + NMVpnPluginInfo * self = NM_VPN_PLUGIN_INFO(object); + NMVpnPluginInfoPrivate *priv = NM_VPN_PLUGIN_INFO_GET_PRIVATE(self); + + g_free(priv->name); + g_free(priv->service); + g_free(priv->auth_dialog); + g_strfreev(priv->aliases); + g_free(priv->filename); + g_hash_table_unref(priv->keys); + + nm_clear_pointer(&priv->keyfile, g_key_file_unref); + + G_OBJECT_CLASS(nm_vpn_plugin_info_parent_class)->finalize(object); +} + +static void +nm_vpn_plugin_info_class_init(NMVpnPluginInfoClass *plugin_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(plugin_class); + + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + /** + * NMVpnPluginInfo:name: + * + * The name of the VPN plugin. + * + * Since: 1.2 + */ + g_object_class_install_property(object_class, + PROP_NAME, + g_param_spec_string(NM_VPN_PLUGIN_INFO_NAME, + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * NMVpnPluginInfo:filename: + * + * The filename from which the info was loaded. + * Can be %NULL if the instance was not loaded from + * a file (i.e. the keyfile instance was passed to the + * constructor). + * + * Since: 1.2 + */ + g_object_class_install_property( + object_class, + PROP_FILENAME, + g_param_spec_string(NM_VPN_PLUGIN_INFO_FILENAME, + "", + "", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * NMVpnPluginInfo:keyfile: + * + * Initialize the instance with a different keyfile instance. + * When passing a keyfile instance, the constructor will not + * try to read from filename. + * + * Since: 1.2 + */ + g_object_class_install_property( + object_class, + PROP_KEYFILE, + g_param_spec_boxed(NM_VPN_PLUGIN_INFO_KEYFILE, + "", + "", + G_TYPE_KEY_FILE, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +static void +nm_vpn_plugin_info_initable_iface_init(GInitableIface *iface) +{ + iface->init = init_sync; +} diff --git a/src/libnm-core-impl/tests/certs/ca-no-ending-newline.pem b/src/libnm-core-impl/tests/certs/ca-no-ending-newline.pem new file mode 100644 index 00000000..664e299a --- /dev/null +++ b/src/libnm-core-impl/tests/certs/ca-no-ending-newline.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv +bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv +b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH +iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS +r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 +04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r +GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 +3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P +lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/src/libnm-core-impl/tests/certs/pkcs8-decrypted.der b/src/libnm-core-impl/tests/certs/pkcs8-decrypted.der new file mode 100644 index 00000000..2cbdeb5e Binary files /dev/null and b/src/libnm-core-impl/tests/certs/pkcs8-decrypted.der differ diff --git a/src/libnm-core-impl/tests/certs/pkcs8-enc-key.pem b/src/libnm-core-impl/tests/certs/pkcs8-enc-key.pem new file mode 100644 index 00000000..0d08f2d2 --- /dev/null +++ b/src/libnm-core-impl/tests/certs/pkcs8-enc-key.pem @@ -0,0 +1,29 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIE6TAbBgkqhkiG9w0BBQMwDgQIwiGFT4Jz6RsCAggABIIEyJNMddDYofbhydUp +J3VyrYIjv3LziJ7dkTXE3+mEYRCQrGLgljWBbib2JOLVCFt8melL6Yv1RcoVR7X7 +vrRqyycu0DumI4f5+Bf4wc234JNVhSaLYsw244fFtcnK2Gyn4IaVmWmrNvrwfX/w +SKcVmO30D5C5PCKzv2bou5FmnJLKdDQV1t816cr9T8pTx7MHvBzSZXbh86334BhF +T3zNwo8j2/+Gq2NBWUn+2GTTV8/r26aIwPcFi4QH6I2ghBwFmFHqU3/PoRm6nkmg +CqJj2Dggy+8zE5qg0iId7lrio0OjCH+Qed6NGwIa2lgv/bhuJVP3FOk4gqamJWHi +WMaq9McmS+03q2iokYeSQGbx85x+I90RTFZKhFx4dkerf6oTC/YoL4F++ff0e91v +sOrQsBkgRhrRtFwa9OFCzbsknlixONdd+ITkyX490xz1wcZTDkKtMDRLIPWa2O0b +MEq75jPYThZ5pF1vc5r+rqPafN7SfI+DDmhzJYEQNRoCWA4pH9Gwv0ayKnOgoj4K +TuFhXvcyWzTnVXmcqEFyf3CRrB0Ti+Z61enupC+FCuYV5lGsx9kJaTumTk2UPD02 +9Ap3asDLozdEPSXBG3+oCM2s01/IJlxtR84C97r9rpmWTc9K6DCBScETe9KnIghW +PU7XFogueG5Gwpe+x+IlTDq+qiyUNVX1uMGDcIaCC3VsoWqZrpnGGBhsovwBaXKt +T9fT2nE27Fd6DRWso4fgos6PPx7RVveu17BTMVQeUq9L8GrV4JNrE3a9aoXdbUhc +6gMiyAqxh/HEyciYoXsR9oVNi+VM0y8q3hL5nIcgDrCZr/c9aQ8+fuQBDXRrmrQd +bR2iwNLCBnbmQmM/vM333VhJ4MSOKd3SGw/j41K+Nr3uP5KRZUwV+5yy3ef/hGxU +i9JjCmSUt2bfWRUFlNaf1hCTYaKD0xnVr1SLFU4snIgh2qKawyqVc9EE2f+FcOM5 +0RtwQ3ku6FOk3cy6/xeKpResCHbWDS6nQaIKYyLukV+gm5MJIhOMkj2z4T1eXGUr +Nu/L7Gz+ps7ct0lM8W82n5lzSEa5/l1eNGM0wtQoAwutFEZp7Nx/IBKK87jVttr6 +82UVJeRk7rO2Mpobfw2LbKwga4rsuLrx3UwVDBWdLx7dNIc1rGoAxhsc72+skFgF +Uztwy4Yv1Uiji4T6v+mObPZD/HiIDL0vF02Pz08rNlgB0DgaTKrpql2FutIuQAdf +AciffQIoh9VGERlJoWuunG/UTxg2XRl2m1vCDrgBMInax+PXCv7/5Vh21AQc3fWP +uf4k6JSy46hYni7VTVKn6C/Di9z7oIrGl/jDkDsaenAbToyX9VWr3s7EBwnhTQ/I +OQ9bkWCagHIQlwJbu4M4/VAbiR26NrcR0C3JXBlPlT0qvFFB8gKbJAQEXtwIFS2h +m2fe0k6mQASMwdbJYXZ/wfsg5PPAWsKtny1aMvi0mTPSD5uRhIfEGEuR+AT4UbEW +BkEIE0lgGly4P1SpunKDQQE6m/e7h8Nl4pi8SMSme3YoX5MJwCP/CNkLBDVenAZI +oBrdoVox86SjwnUozVG192lcEAULlk+3ZGt6T9JXLBQl9hpNtyTC6SFh84R+5RoN +AevNl1bDfO+Vci0uJw== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/libnm-core-impl/tests/certs/pkcs8-noenc-key.pem b/src/libnm-core-impl/tests/certs/pkcs8-noenc-key.pem new file mode 100644 index 00000000..f73fb55a --- /dev/null +++ b/src/libnm-core-impl/tests/certs/pkcs8-noenc-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC80HGgFdlFIL2F +W5rHQ99xOkMrWcZ7an9YurDebAE3M0UwqxM24+5mWbxc8FJ8yYugdMzuI5Nq77IA +DwJpv9ZjMfnKC1VDMj3bmmMdPurfx8pLdLw/jhk3wBaYk6vMvh4z66Yvmb7valGq +Pxh6CRTnekNGI9XA7me4JNBlisl0Qasn9g4pl9PiGJAruEamS4Pk6dSWbfw58bs7 +/Yo5ejyt/Mn1n0Q/r3Gv/lAS0qRvmrW6GE1rMtANMfuGRNlAln21TzNcJzykm+sp +RWptxXEI0NY7u9+RP4M3C1mJxWir7AZDbtinpOZH6vF+92yMzgEYOLK/WZVdSPdW +tROv9xtPAgMBAAECggEBAJAfp+vjYZJjuGaYEuAxjdhW+biYcWn5U7V9484TsSXa +i+DnZOZMO8iCjMaAZuZ7zYmwPlE0dK12w29KBbSEy3eySRIRboa5TgBXq3pCcXRZ +g6/vLlZw+AzXIiha6BODt3g4UwUYnWcQx79lJCDa18sNR1a9ucbn8+Har/wiYT3M +JjTbUT6wR6rKEXchB58ZugYGhOTfugSDQg4U/dwEHPIaJ/wme++JUV5B/tjeGCG3 +F43o2Oos5vjfrDSpUKIYZn+2BdhP434jkwj22wQ2sy0ruU/kQx8nogMTRfP1v4GU +9QmNXj/DB24K388ZxcDmcxBJxrGAJ0MohYFo28DqRBECgYEA6hyKEqe2UbJx/+B6 +8mYgHb+pS2j0M4jPl11q9MMLVxLnDY9xZ85IEyWHQEC0GavPSAois0oiDeGAm32c +j6TFyV3/oPTmZSyV93/agWgnH9Xtc481pbNAb0GMfyotvRRE/+6ti9+Cl7oH9Qmm +ldMk7Hn6sK9t2mUOW8idPjKqlqcCgYEAzne25BryLJoIinbRMZg9KTfxfgUE6EKc +Tk5+9CFQn0/AItQJuKbIUyggYH4psWW5hWq6hFlmMYMR48FKv9ry7pZTB0djaoYD +lN+wSuhzUYWXedkAjvPmekITmf6rbnPfwOZvsr8CGMEUekqJPnPLzsQy+Ea2y/fb +QY4SHe7gExkCgYEAr+1scOJpZvFjK7ckjT3jipd6ADpJsORxo7zG4FImFnQU/6K4 +xRpGHWVJQyaccOIkrW04cGUYPDgmrjJx0ZwwKceijvEaphMgS1JgAHklVY4sl3ea +CAAxPqoSi4lFv94Yj/9rmT4IZD6fNivfbJ20FKUBl37tXX4tkRmr2I64lOcCgYEA +x3eqzrclrmdlxvfBZOuScwbkHP6WXhk0TwbQ6eRhsnfmxP8bITSoJoaGuRJKD2Oa +l0WkSobgDwd0uhecsrvBpTS/pDGY32n3fdWZyNTHzEOHMyWtv23tBcJek5ERaBU0 +X3WBBiw4x1eKBBeMfjR6+xhbsbcHlQiw36V05UxJWMkCgYEAhtcYvrfU4K48IJTU +qp03nvd+dMY3IUTdZNOCh8bswLKyn3aq3MfWF9Vp7kDAI3cfyMpSrAQnmg4nVcn6 +Gf3wakG8bpiSRbJnGN+iLm8JsD+3Vw9KzvKOOQVmpT7xt5Kupx1hWvLHQWvfYgOG +qEtTM8/+LD7W3I7midJNt50CD8A= +-----END PRIVATE KEY----- diff --git a/src/libnm-core-impl/tests/certs/test-aes-128-key.pem b/src/libnm-core-impl/tests/certs/test-aes-128-key.pem new file mode 100644 index 00000000..aab8f463 --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test-aes-128-key.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,800391B7DD72364B4C2562E0B6AEA000 + +E9dgNCIATOr4CN5c8o6B+8wdqq/I1BLmPmW2qT7YZepoF7E+RUb8ZLjo1VO2XsJw +Ir4EzuH7837zBD9dP5CjlY8cWfR63gZpEWRY5Jub8kzvqiL4UZ0Qr8IHOZxAkKEz +EL1Pn7e+tYt4kA372LPZHWO3vRCgmL1iSJj2/k3avPWAx7NUie4bzGI+00WNv699 +ClKzsJbWB1eiQvYgOr4aVV26oWfa896JkBoGhgZQ6ckqFpsdAos8m46iOSVZrwgq +Y2/d1CvfQod+87c8LRatwAjf+d6YAJJaeMyxjCGuAY6/JyDsorUkM2OlvbTt6WOA +gSPWO8I+Ov6THb4IuPhpIJ30Sl88tc6MlIByW49EWu2G1jPw3L8iqRzZ50Z85dyz +N9yFP91wEwi5F0Zed4iEpg3NVfklEe/VYqCldc5f9fZ84G5V98ZlAdNSqwd/UNBU +iPTflGqVpp1u+J2isOk+Agpj1MCxh2q0RNuvY9KHzOYBScirfbG4DKNbQgS/5Zw7 +3g9YL5Wbo7BczHLiXf/2adu6T8wI1LKRjkeLV9dK1Vw3ZaGy3mB9oFhCgjh9BNHw +wC78CVcUErjtOXdQagiCQn5k5EGeAB97QFROoAFjAmGvq3xCi4EHd9Sk6fcMm7Oi +1fuVR5EXUubF4Llq06lFzQp01s2F73noH49bs3qwdf0n8nrL2XhKB0XCOV/I3K5c +Y3W+YSl361QGjZ/NUcFLIIy+Uro90MmUBNk6af+wGHRJeflpVnK0ATX1PtpNHziz +jiMiIZicjgfVLxl1a5zTl6KUmGWKlZb533adQ0s6q9Qmi5Vk5L5W/GdMjcFtZbM6 +GU7EOkwihMLb3DVsNbm7vb8tUXqe/e5RmXtij8Yb+2a6/M9yaOHa0VjHzdiKHecr +eEYaKQDj7NCWLslNnRRh9GmgXcu2pqBhqRM3HAt3cEaHKKWHTcoWcz11pFwMHNtn +bU9GjGRVc3lCGhVNRiloO0zrKHmcBpDVLw2+ycXXpj/RaBW2fy0xRLKolyla+jEr +zdLzVI61O50ZMycOtPmE7DVZpkokn32hGer1eCOFnsN1lywi4cWLvU0jbQC+CJyw +T9vS8zB3WgOl8rC1AHsfQ4KYajlKleQm89deurQymnt/Qx49SiQA+TpwG9Xvx2TP +3Vc1NwZfM9ZZ1+6xit/rTuS3LhnmhEnGnV5ZyvAdmkCcV2iHjOnLnQWWQn24MHU3 +8Y9D4AdpI5V7Igwr3vH2NZMiw1W2Yc5EJuPAT8nIs5sgOYhXB/QLPJvulvOQhuNC +NPwJ6A505JrVNfHuEaoGUyA+mPeWuLwQo8y8cM6ZdFMG3RrwFNzuYTrc1Z/9GsmA +C0UfHf5dL0r7oWZ1SbpSvsmHYqc8sIypq0ohuLEbpegS/hWP8b2/XDRTjfTiJOrM +4LsUH9PMOJSxDlwS+7e3FdcGfgXfsMgB3aOjQvNpKEolOuv4A5LVFeMrrwtw4Xo1 +EuhstZwyarUTJenDUXzkakhA+8Yw/g2a7RsnANVTkeBuv2PbqFL4zdlsWvcpkz41 +ESxx1siSeU1E9beOII0zSi8vUD0IAevRHaWSlfU2po600IzX1FN97pa4DJV2ycgn +-----END RSA PRIVATE KEY----- diff --git a/src/libnm-core-impl/tests/certs/test-aes-256-key.pem b/src/libnm-core-impl/tests/certs/test-aes-256-key.pem new file mode 100644 index 00000000..e51bafd3 --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test-aes-256-key.pem @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,5FF6BD2D4E57E8933D4A6814DEF5305A + +9Br+xw6XOg7qUqfeE5PJ4g/PAm7eTcPMb4FzSKkaEosLo6oj4f37TwXuojJZeAmi +1EytpqM1vdYHCLdjg+qYaTIq6mzMZIyoaREokcOhcNrq5S0J39gJLVV9LjiXhCAH +GQgDBnbRT6HGz70AyTRLcW9aj6uBzTv/m92sLUw2txFeBXK8n2AA1oHJTgsFNYjf +/ZvTCE1VMQHDPx31Vn5WXSUHNc0hx4MTIwpHqWI17ohr8IiWCs5HXVfVaqrNeNEw +haD7fg8oNxjLs46/4dDWmfWXhDsMFSweZv03gZdyVjwn1IOqeVGmTdLpllfgOW7E ++XE8Y/d55s5nkOxu6eXNMtWgjclKBGr2iMxxnODmEsUt2WcV98cPS+25o3hOfy3s +NIcfxtWVRFUtjqf3ragyGLuXFqATkj1slj4LVMeewRJ1g+Z6ti0mwBN+ZrYtKdec +FRNb4zr5FW+3SqkIIJVfxJEYJDB4zODhMg8tySEHLKuT0uz42YQ4aoOHTzO5WDBY +2BI7TjRppXcExPnkAk5jqbKA6BjT9KcAVyypfxDKvCeXKdjDcL6ISOBSm6cQBh8D +HxsFzMy9PF6kKNeiNiEsVPnKYvhvs1hTBtp+IAgJ6KZnCDKplZFxo/mBAlV2KyCT +x+Mhmme3fXdLJkvxlVJAoAhwgXvomVCVTGI3JhcQIqVgxPIKYpqlHVFC7JjG+yQX +tvzCPtr9G9+Ofrm6zXjlDD7zNyl/KfFtEWhO2ePHkQlCEuKJnsnRIf/wQ0viG0yY +MH31Z/84o2pKLBKY5fq8+eYuYoP9Rk4W2LpjGMvdkKhEHL26kZofeFyqD+JcaxHc +kQh7/SbWAsREGb9Jp7I2q1mo749mse1oSFIQa5gN3jB0mgHZd6edRYeW2Up+rqEK +k6Xd6uqs7bZd5W9sP7Cf6yJOFEjqFVLQEVEXWSchgeta/JNrjGr3UzLFN2S+vhvX +XgDa41y2UdXHRqj2s864u0ZDPyGXYZnVbvQn/8xHQ7rvxHowpTn+XXUEf0AQnk3j +9h++3McwP8GuVxkwc6o9TfOL+ell5jup7F3SekwEiE3hqY8x87g6X2zD5VSnfCy3 +0t0LmPGI1b3LABeYjA1WEdhoTlHrNLkwOR4gsudrJ5nxIzfGy+IHaloXLJy4YKfX +pJ+qyGRUR42YD9IhiEmmmO1VoJgVEYfBiz50Jg8emddku6eKdmv9IKjiSb2pTbDS +4oUYKg109OOn+krk67dNXofAXrBa8v7QusC0yz9N25H05Xyou1iqpGk+uBrTqEO6 +lW9lWQo57BQU9og40xMKH/xQgIxfQRktUKsPizj8mKil4izo5KgjPSqBeEbj+Q3c +0FKlrpTXQlXfX5Z5esqMuCSiwQEzoJR+V+SUaSVcg1av0k/CJMin4Cr8roai+OjK +lhaQIvx35Bzd02yERYsfpDjmQCXmIeiDm8JtB6znbQPUJ4d8kzWR+5ACOZW/dUss +YhWJRkZpkIwTY+/sDU4mnP2R37MNo+OH4CwZyUDHjlkRPGW+6JBEpnnlI9a/1Vb1 +pjAGpi/8u/luvZGTzCzxQG2dZc5YQR869U+wFsFbLRiD0aP2SpdOH0QxxPOcdR8+ +HWyL01BJBKyK/wZWJhe+63zlk1L5CA0XYpoNkYpMlPNZkcqR7QzUOATfuBgI2aPM +AXaweaAWhpPCDsc2RypIs9DhTiCCkt8tq8Au15hVUKAoshLeewPtv0t75MEC0hVB +z6FVnNlqq0cqqcSVqvUG6JUGtFOGgG3ifEMXggq5k12+wGzY63DLR8dFPNpOL6/1 +nocOayHJIU9M8PP817PzhAUAePRRUKRg8kkbKKeZnCJxoF7O15AFVEJnl9Vyokkz +bULYhzYVx3xh8THMi+5jsnKWPJyMeYHbHH3C658SIw6Ff9fgEWscv5ZkGYdKMg+l +8hBn+++SoqIO+F3lOGco+s8qlYox106lUwJEtORXcBxmkaHSo/X2AVO8Owt4vYli +mjWnY6V9vooBgOuCMcY780pcoj2lSf9JPHDYK0j8t5VumDUSLyLt+tCj0yv/vl5L +9L++vbu2akZRC9ChijYpfhTvXoG36ePhoT7AGGnhpFjjw1VqG80GY4XSODKzH86w +kUcZoErb8swUPYOtsybtuPb+6c/YofQ8GfpVosPZgSRD4+U7v+zA3/z8xF2B0xt6 +uV8hXbropuni8KmbFuKrPZK3p2v2aZ8F0+GITwS75/hbT6D7ruUSr5q4V0VKeE8G +k3QSI0s6+74stPv3S/ByCxu8q51ffYqVw00wzPpEc4SmHEa0R7IczJKXupmDdZZM +1rASSBNzS5TZDBXP6S7npYQ8nHhgXTdCFO7eM3bp24B/i2o0s7+gkKrz0DkEbv9I +UrCJjTL8OIIP4qSLMILzZ8pB28c+zyM482ZqFY/2b7j6WlTiqa9P1adrD1gLxTQ0 +Sw9xY+sY3PAJqcnPA5NjDZL/h5plgHhCqDa9pEtdBVG2Mxcl9bXbphwD1MIzj4gr +xtlW1HUJ/iOhFcXldOJ1MCt++Bm5av4mL5adQ/oUnL5Q0oZZFwqT09k7xe7lZ98N +uj2Lfl8NN7N3ama9KatgbX5g6IALuk/rJN/4KEiiu24m+lR7c5L0pg/cG6LIFjmk +HlTsc0ANCgeZBhDJ8kvjcXDhFOqoYE/+D2VO6ZEHRsDibQ+kjpaH+DiD01/gh0N0 +HM6GGtm3GbOyZUhw5OFz04xzcyFYo2xaqzgaZieAOcrt2s6XyPVf1gww08/HtTMR +gLg14MUQvRXV6kPJfdu4OLZ//b6J0KnzVyLDRdOrWIj2raLWmKwQN9qv05/yskcD +Y6x7wq3v6iZpFjDc53sslhwp2XRsoWT9X5alVspz8WvP/kqgkTdzpPFdp1vIovOQ +kRXdzzKICDGDJUIcTL8cJ3Dv4XqNR/sVyuB4dfndzQQApbdYTDNpwX0VJDBjMkQy +Up6aiUknxa6Cbp7b1ZfUQY8yNBAIZL+R8dmobT3nAHW61DaASHSxn+elCD2Ja/6b +EiWikskyN6crMAv35ILr5ySsZK97ttNNmRoGFbt8bTjRd83Ie+UfH445kCKsY83x +aDCvWm+bbV6M9rSgjhJ3bWOudiw+EBMGvSamSnS7CYnRmwq4t+4bM2sh2nYKY0qw +-----END RSA PRIVATE KEY----- diff --git a/src/libnm-core-impl/tests/certs/test-ca-cert.pem b/src/libnm-core-impl/tests/certs/test-ca-cert.pem new file mode 100644 index 00000000..ef1be20d --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test-ca-cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCA3egAwIBAgIJAOvnZPt59yIZMA0GCSqGSIb3DQEBBQUAMIGLMQswCQYD +VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw +FQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UE +AxMEdGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAzMTAx +NTEyMTRaFw0xOTAzMDgxNTEyMTRaMIGLMQswCQYDVQQGEwJVUzESMBAGA1UECBMJ +QmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55 +IEx0ZDEQMA4GA1UECxMHVGVzdGluZzENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3 +DQEJARYNdGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAKot9j+/+CX1/gZLgJHIXCRgCItKLGnf7qGbgqB9T2ACBqR0jllKWwDKrcWU +xjXNIc+GF9Wnv+lX6G0Okn4Zt3/uRNobL+2b/yOF7M3Td3/9W873zdkQQX930YZc +Rr8uxdRPP5bxiCgtcw632y21sSEbG9mjccAUnV/0jdvfmMNj0i8gN6E0fMBiJ9S3 +FkxX/KFvt9JWE9CtoyL7ki7UIDq+6vj7Gd5N0B3dOa1y+rRHZzKlJPcSXQSEYUS4 +HmKDwiKSVahft8c4tDn7KPi0vex91hlgZVd3usL2E/Vq7o5D9FAZ5kZY0AdFXwdm +J4lO4Mj7ac7GE4vNERNcXVIX59sCAwEAAaOB8zCB8DAdBgNVHQ4EFgQUuDU3Mr7P +T3n1e3Sy8hBauoDFahAwgcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDF +ahChgZGkgY4wgYsxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO +BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQL +EwdUZXN0aW5nMQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRl +c3QuY29tggkA6+dk+3n3IhkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOC +AQEAVRG4aALIvCXCiKfe7K+iJxjBVRDFPEf7JWA9LGgbFOn6pNvbxonrR+0BETdc +JV1ET4ct2xsE7QNFIkp9GKRC+6J32zCo8qtLCD5+v436r8TUG2/t2JRMkb9I2XVT +p7RJoot6M0Ltf8KNQUPYh756xmKZ4USfQUwc58MOSDGY8VWEXJOYij9Pf0e0c52t +qiCEjXH7uXiS8Pgq9TYm7AkWSOrglYhSa83x0f8mtT8Q15nBESIHZ6o8FAS2bBgn +B0BkrKRjtBUkuJG3vTox+bYINh2Gxi1JZHWSV1tN5z3hd4VFcKqanW5OgQwToBqp +3nniskIjbH0xjgZf/nVMyLnjxg== +-----END CERTIFICATE----- diff --git a/src/libnm-core-impl/tests/certs/test-cert.p12 b/src/libnm-core-impl/tests/certs/test-cert.p12 new file mode 100644 index 00000000..ae4a6830 Binary files /dev/null and b/src/libnm-core-impl/tests/certs/test-cert.p12 differ diff --git a/src/libnm-core-impl/tests/certs/test-key-and-cert.pem b/src/libnm-core-impl/tests/certs/test-key-and-cert.pem new file mode 100644 index 00000000..dec9aa1b --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test-key-and-cert.pem @@ -0,0 +1,118 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,4DE0615F23D82107 + +QPNCO5Dobvz9dDhN32KkZRoEifW+HDm2PCbRQhKDiscGwB6LgypvVjHNsZiFKwzz +L4R51UqgQeJx7GSGJqE626e9z9J+UNBhop02aOO2X0eSPdvBzr/uJ6Umiyr1xqD7 +zWf7u9l5kXElDJRhK+87GMBewp4Ie9NeXDjhF8hzC5Kiulen4AH3AYnfH3S7DimU +h8GFMg8inrudrTbcjBhCdPeHG2jCygOxw3InRFz7uaN6LIhOaPQvmvpP4Cc1WRnW +ZPq9o+eU3fPWPD5t+Op/VzYLvKwgBy/yK1rQXUm6ZMO7MhhRJ94ZCsJv+nVWpJlv +QyBlxDKxwfkfYbDELdnnDQdHdMbKatLqa0KhSkgpp8LywBtanPz731tyT0r7b3na +eLdra59lRU7ZQLPEdS3lPZd2O/KQvWf8wbg7MjXS9LxQ7R5HOPu6DNJlwXVZBmmo +cAfu2q8ubU2IePvWLD1GOrBi6hE9TiGvFJkw+wBK+t72sz3njv9Xm/zlxruaEk5m +RW/kybU3FP4PtjriBbskz3/VZaaxuRN7OoOYTkmyHmG1ADgcRUV6fea19qqsBlN8 +xb+SRtoH28oT/JVWU5neE2dbNzk5LeVO+w70NNdR5s5xqkBhbGGaJxvXwNP4ltFr +T06SMh8znOLKwWB00aRtwfU7jOwR3mOleQO4ugIHmau3zp1TqzAHW8XtpuV7qVeI +ESZOZuf0vW43BtNzgLXt1+r+bmsMsRwhnyomL9M0TUyyBdVYY9GkzTG9pOESheRo +RSvAZ8qKGUliTpgBcbt2v1+NqkszcHa6FxuvS8YU4uo5/GqsgTxHTNIB232hIrrZ +EIm6QL9TC5oFXMjy6UNqoCm5Nb8DBJ6aErt7pt7aoktqUW3O3QIzQT3IbZ4nAcTt +lVF4d7j29I9t7bcC8GOVU1neilguZUss4ghJg9x4zI5UZdR7hZ8fbFT47TyxB+j5 +r0YdmjbjVTaSyaN2JGh1wvb4TzawGNVx/U2EJE16HigOtPfsfQRJ3x+FROKBdVa4 +aIFYXkRBeIPxX6n9pcw0lBCsnXo6/5iTjQSk2VqO3rHO/wyWiEjNczhL33dY2A8W +GG5ECMO5SqXZHQQzpABqK94dxe3UC8aEESO5NhEqDuV7qQGol0qPKrUA3wb0jb2e +DrejJ9HS2m1SUDmjpvvmEGy6GN7CRibbKt5rNZdJNNvWArOF5d0F6wkixQLl73oE +lq5gLQQk9n7ClleKLhlQpBCorxilBbzmSUekkJLi0eaZiBBFWBX9udqnUZloXTgO +8qwuO8K/GPR9Jy1/UH2Vh1H+wivaqKTVgEb0NotzgzECgTEFKJafl7rUNs1OZRZ3 +VBjevi6+iDpxVFgF71kXfdUC4ph0E1XDl0ja2rrKQGivMkUhWJ57+4EV5+hBkAnt +G0RV45NwHXLrK2bd8F9PlRk2XHW6mIcFRXsW1DjeBhk/sQjvlO9R01GRSgcXtekJ +tmX17FWrMrzXHpvy1IC3fk4RVnSjpzQ8O+17YE8/la9wVaeZZzHyYFmMT7VXjIhW +QozJQ0vJ2jxJRh5GYn3tpJzdaeRfvTBik0pChNdUTnWP+BJ35xoCTs8iwJbmgVZ1 +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=Testing, CN=test/emailAddress=test@test.com + Validity + Not Before: Mar 10 15:13:16 2009 GMT + Not After : Mar 8 15:13:16 2019 GMT + Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=Testing, CN=test1/emailAddress=test@test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:cd:34:b1:2e:b0:04:c6:f4:2b:a2:c0:a0:39:7a: + 82:ed:96:c4:f7:19:83:91:5c:b4:e7:9c:de:ec:48: + ec:2d:e4:51:08:26:42:ac:d3:98:26:7a:72:f7:49: + c2:9e:66:05:c6:47:29:fe:3b:ac:6b:af:6f:5e:a8: + 03:5a:73:33:ba:19:03:00:35:f5:00:bc:a8:be:14: + ce:46:69:e3:6d:ed:34:37:85:55:87:62:b3:b7:c9: + c0:cc:9a:aa:61:05:5b:cd:a2:17:42:d3:e5:6f:1c: + 60:8d:c2:15:41:46:f8:12:54:d0:38:57:e1:fd:8d: + 44:c8:fb:56:b3:b9:6c:e9:f8:9e:21:11:57:1b:8b: + f9:cf:e3:17:e7:d8:fd:ac:d1:01:c6:92:30:f3:2d: + c9:d6:c1:f0:3d:fd:ca:30:dd:75:74:e7:d1:6b:75: + d8:c5:4d:43:61:fe:f6:ad:7e:4c:63:7c:03:17:a2: + 06:8f:d0:8b:69:d3:7a:07:0f:0b:a2:cf:0c:70:38: + ba:cc:55:35:60:84:58:d8:d2:be:1f:ef:76:a9:ba: + ae:6a:dc:08:97:80:de:42:00:b7:d4:ce:9a:b0:36: + 2a:c7:6f:45:04:7c:ea:41:19:d8:b9:19:04:1f:11: + a9:22:80:bd:69:08:15:0d:3c:de:cd:7e:88:6c:0f: + a3:43 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + CE:03:7E:EF:E7:DE:C9:87:BF:DE:56:F4:C8:A3:40:F6:C8:6F:05:8C + X509v3 Authority Key Identifier: + keyid:B8:35:37:32:BE:CF:4F:79:F5:7B:74:B2:F2:10:5A:BA:80:C5:6A:10 + DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=Testing/CN=test/emailAddress=test@test.com + serial:EB:E7:64:FB:79:F7:22:19 + + Signature Algorithm: md5WithRSAEncryption + 7a:20:93:63:40:73:7d:33:01:2e:c0:13:52:a4:a7:e1:4d:82: + f4:fb:b2:7b:d0:2b:5a:3f:0e:3c:28:61:71:ab:01:4d:fe:89: + b5:cd:2f:97:59:93:53:9d:51:86:48:dd:b9:e4:73:5e:22:0b: + 12:0d:25:39:76:16:44:06:0c:40:45:21:6b:a6:b1:e0:bf:76: + 1b:36:f3:1e:41:82:57:d9:59:b7:60:40:43:1c:1d:79:f6:48: + 32:5c:4e:e2:06:89:96:41:d2:54:1f:4a:6f:f6:78:a5:3c:02: + 85:21:e2:65:e1:8a:6d:24:19:95:f8:c0:35:ab:bd:ff:3d:f1: + fb:50:2d:30:1e:67:a6:7c:50:f9:d5:77:66:77:5a:14:0f:5c: + cd:21:09:9b:a3:92:57:19:dd:01:a4:18:c5:f9:70:e4:17:43: + 8d:b1:e6:61:e9:50:89:83:4f:ce:a4:57:68:58:40:70:ae:71: + 1c:47:66:d2:30:54:50:ea:3a:87:32:64:3b:18:42:fe:5a:19: + 07:64:f7:f1:b1:10:07:fd:a7:d2:a7:a8:05:79:5b:25:ba:69: + 7b:1a:3e:b1:3e:e4:17:17:01:ba:eb:54:ae:83:00:ed:66:62: + 8d:c0:3e:8a:b4:27:5f:e9:01:ce:20:c3:34:a9:28:c0:6f:c7: + 3b:65:fe:f9 +-----BEGIN CERTIFICATE----- +MIIEojCCA4qgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBizELMAkGA1UEBhMCVVMx +EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO +TXkgQ29tcGFueSBMdGQxEDAOBgNVBAsTB1Rlc3RpbmcxDTALBgNVBAMTBHRlc3Qx +HDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMzEwMTUxMzE2WhcN +MTkwMzA4MTUxMzE2WjB6MQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJl +MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEQMA4GA1UECxMHVGVzdGluZzEOMAwG +A1UEAxMFdGVzdDExHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNNLEusATG9CuiwKA5eoLtlsT3GYOR +XLTnnN7sSOwt5FEIJkKs05gmenL3ScKeZgXGRyn+O6xrr29eqANaczO6GQMANfUA +vKi+FM5GaeNt7TQ3hVWHYrO3ycDMmqphBVvNohdC0+VvHGCNwhVBRvgSVNA4V+H9 +jUTI+1azuWzp+J4hEVcbi/nP4xfn2P2s0QHGkjDzLcnWwfA9/cow3XV059FrddjF +TUNh/vatfkxjfAMXogaP0Itp03oHDwuizwxwOLrMVTVghFjY0r4f73apuq5q3AiX +gN5CALfUzpqwNirHb0UEfOpBGdi5GQQfEakigL1pCBUNPN7NfohsD6NDAgMBAAGj +ggEfMIIBGzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVy +YXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzgN+7+feyYe/3lb0yKNA9shvBYww +gcAGA1UdIwSBuDCBtYAUuDU3Mr7PT3n1e3Sy8hBauoDFahChgZGkgY4wgYsxCzAJ +BgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcTB05ld2J1cnkx +FzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMRAwDgYDVQQLEwdUZXN0aW5nMQ0wCwYD +VQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA6+dk+3n3 +IhkwDQYJKoZIhvcNAQEEBQADggEBAHogk2NAc30zAS7AE1Kkp+FNgvT7snvQK1o/ +DjwoYXGrAU3+ibXNL5dZk1OdUYZI3bnkc14iCxINJTl2FkQGDEBFIWumseC/dhs2 +8x5BglfZWbdgQEMcHXn2SDJcTuIGiZZB0lQfSm/2eKU8AoUh4mXhim0kGZX4wDWr +vf898ftQLTAeZ6Z8UPnVd2Z3WhQPXM0hCZujklcZ3QGkGMX5cOQXQ42x5mHpUImD +T86kV2hYQHCucRxHZtIwVFDqOocyZDsYQv5aGQdk9/GxEAf9p9KnqAV5WyW6aXsa +PrE+5BcXAbrrVK6DAO1mYo3APoq0J1/pAc4gwzSpKMBvxztl/vk= +-----END CERTIFICATE----- diff --git a/src/libnm-core-impl/tests/certs/test-key-only-decrypted.der b/src/libnm-core-impl/tests/certs/test-key-only-decrypted.der new file mode 100644 index 00000000..c8652537 Binary files /dev/null and b/src/libnm-core-impl/tests/certs/test-key-only-decrypted.der differ diff --git a/src/libnm-core-impl/tests/certs/test-key-only-decrypted.pem b/src/libnm-core-impl/tests/certs/test-key-only-decrypted.pem new file mode 100644 index 00000000..1a25da5e --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test-key-only-decrypted.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAztLp+/SkzmaHzIst0sO4+r7qUwiVnidKBHgxoTbS5UL1dvck +cbjTEP23V4ZoKQIb6ZMrr8B+PVgWNBAlhX4oLUL8NH/WTwrntrlAUcjliWDNc1Fj +Z+FZNjFe0IdLXZeKQiLTQvKDYnmERVvYIJfi0TQjXuy/ikF8rYBQ85scUV3kKRzB +xnyhAHAP9nl3AyL1S6t2yiCr4U5TbIZo6dYPbpVLtbXP02zUc9vAPR0gIHKrPiW7 +P81fLXf91wuo6wxzwD90JxdWzzGaqe3BOSfHqul3SPRtVok1XHlb9i+Mn5O8ExzQ +dF8wGxVDcY7/Gt9lZWnCilqrBEqA3iApjUgcgwIDAQABAoIBAQC77HCmepf04ryp +Yhz00NoXG7mWUMqBtXlPrlaKAWKG5dxbiBwZKLK/UYlxgbLK8BuklBCx1Nnfq1Yh +YDp3nTWsSp0WpsF3eJUqNhD3Pu8xcD+9dEo6bUu+LgXUu9oC5Fq/1RzYS7qNk5RX +4Sf0FY4GQLWS3tL7MJ20mE71yg5k7qRbDydOyhTh1m1CpRYONWxSsWVLM8KGRDJp +iF0cdPfD5yoPFpgDk8gwzrnXJH2Wqzv8GvyYykRK5AW7tE50y0rTAM42WZ7YS6aZ +FNMmtYxeyzXLY3LgPP0uko5anz3OXRcqCfHfFANG0y7B9MehbrjMgI6S1BbzFl07 +V1zVQVqhAoGBAPIfrWMlh1fHlRh4iRGXK2Hvxyv2oNz9KvQ4qRaLV9ejRDzdt7Pi +/utbi5LGBuDw9016nXM3I77b6PukHzsHFnWGRnyRSbkMMt6mE17nggmNqcpFWfE8 +E7HcKMhk4gbuo5AHD3Ee1Gac2AUDnzYiE+sS6WI3O9d77cc5ych5jZ+nAoGBANqt +VZCttjY0E+hEyTP37eaE8u0jfdK8AflTJlNUHWKXglES525iu8I/Bso8KaQYsYF3 +SezGRd3KQepQhtMjFKfSpF+tbnboP57Y3XGo8xhbxqV4kRX+XGsVoW3Vg2nPKBrR ++dIY5nMwua+gkHWp9RDcemD2reiIZBVx95fK8dfFAoGAdCSH2pBs/MlrFqLzNTHr +iH4pb0hN39O9YAsx6POMfo79s6izbyHLIID4Ub6WHB7ashrIHjVr+yin+NXAeWMr +/dIcS7Kxx4c3e5/0mMi6kvSWZsWfoF0uIVo3YfEqjyK36OXKHXwpbNN9t+IF5ESy +g0e+FfPiy7nR1Ig+5+CO8+8CgYEAp/75+Ug677FaidouaMpT7gC9UAkwJLFjItQK +YYIBgTi8WOSY/2jCrhwVb1CA/RwrYjbuiARasGUt9oEe0x3kRHnC5e4rKxaJqdMZ +bLRK7a+0EHNrouXiwjG/7s1VQ/ht6wzdS9btVBlezdogoQSMzQNU0SExwa7mlMMV +X3v+B7ECgYAK+Yt4jnLH1LNWoDTU0Ug5hyXsQXV8lVBSnrkvS7GtP6ZA/4OqG7lJ +/bTYFZGoiAGzOnC+YlAqSIu45CEnpr2xBsThQiWUVxspmQD1lEWWFcd77DPUwn3C +59pLgx0AqJE3n6lBOwehiXbFKBdVzX8PfPZpuCK6qc/RiTILktwURA== +-----END RSA PRIVATE KEY----- diff --git a/src/libnm-core-impl/tests/certs/test-key-only.pem b/src/libnm-core-impl/tests/certs/test-key-only.pem new file mode 100644 index 00000000..5ef530e9 --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test-key-only.pem @@ -0,0 +1,92 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,B29FCA6ECB7C0C48 + +KuRV4sEVQkY0r0E+E81Se2qpm54HeboUMIJcMVqAt/HIeYwpjEzmXHUywXgayA5k +r5Uvru95vymFz80q/rl6i4Mt3ckEQ3/VHmBI4pPj/JeRbiDhqTyLqpB5zMgbuti+ +PsEemlbiaCMZFdJk8rJBWADTkirGbXs5ciLwTJyrJXhkBVTqoHtphSWMELxcrnQj +5bAXZ+rnApWA7LR3yCY1UE0Nj39Yu8lkzDJtbsSXPJVO//OQiGuQRcY6roAUzeS/ +amLdIYqoZm3OF83Ro88XZCIDCG6XjgKoqojtekClVIytB6mHWk+IHhI7DgU/pDBj +yOskzVRMja9ECnqaVX+bmlmJGZBgcd7+XiebCXNY9IAx/ZQDMbDJDoJusvH6npJR +PBgag8QBSzwpFyEP/1LrlyVyggZcGuXDWfUiM2rt2UvLm8QYeT5WGfUI+QwW6JRV +xEz2QwRiPsmj0uK2fXLKHbY0Pv79IMLqgesCAUbndWsi94kjAhpJGa/WfKVb8Dpg +T+mVa2OT0pgewPWJ3h5f47ag27WDJthsIUHAvNZ03uM23DIqDh6o03/B3/4r1Uab +HzsLWhUOzbLihO08qcPk/JqtDVCv579mpgvy7bL2ZsZUzefnmg+ah+1bhLguryZu +Zrt1hTu5WTz27UbYwWNtkxWnWcM9sSF9y+KVwumYhIUGTvg3Jds/jmp/Z5ad8tev +0RQuWmqnXVD51Y92tOXobJH7JwGbQ4GeRNG/UX7DS9Lvb6rPgmmxvGbXMcjEMapB +m5DLjCD7Lz4++sfzhCic3/nL8e8fjoYZmCbL/SpYjGjTrfoNaGYaT0De8MxCSjPf +rNT+ldWFD0oAOTmT0uqOFkeIx3vxczSQUyRfzGOLqtb9mAXk8lvdb8Myb5atfe8f +FNBL5ocz6Szv2AmKmjZE5P6NKSllyryCYl+H2QU0e3OyY8S9aG2aG8fqeVKaA01S +uOOjsK6IKMpkit+VFDP7RWvS8p1FTwTVRdbMZzASW8+D98OHsTlOENtQuuFyvNaN +vPHrbHltTkwDlYVab9UpFr/ZRW3cNJrmcl0hyeCJxk5u8B9UwW6yZmd24hxda2xt +1QjnxfWlX0i5KTmfWFyPmRYt1WH9rdVaH6wqRbGNbMRT7Lez0E/2+LXjFiiEe0Vh +ZuryhyhUXpHY8yyHxFY92XT504Z3wPUaJ1tQxGhdziZ7HcqFT8f4oTNJdBNtzqNw +l63gSTaKUkSB14CBOyR84cqpgSMwZvWZb3RQFYY4GSALWwhnn6PWeRhjWAXPms2v +tRmfEyDUCyPxDarezvS9kUir3XbvnCkoJOrK/lw4a8ygGBCBqRKOdI0SRUf1Nnjm +aZBrKrXHv5vP66fyC7cGMTq35+iiHbE//rRDGPrf9vpmw3ki3FlDMyAXOC5yWDco +0n+cNDyHJDdIW3+3nfZvSgPPMwmTdfNO90X3YczSmMTGjnIuhQMf7FMFmMZOSrqD +u234vYCjIIlph3aVpbzjOqKg3d4nuP9oouyAgRAr+vShgHRmq0jvsAiGH3Q6RqEj +DvkM1NgoloMy5RhKF4hIeaQM/UlgoXrcEx0RGCJHykQcQmVa5cf5EwjrO7b+w2Lg +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=test, CN=test/emailAddress=test@test.com + Validity + Not Before: Feb 2 02:22:25 2009 GMT + Not After : Jan 31 02:22:25 2019 GMT + Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=test-client, CN=test-client/emailAddress=test-client@test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ce:d2:e9:fb:f4:a4:ce:66:87:cc:8b:2d:d2:c3: + b8:fa:be:ea:53:08:95:9e:27:4a:04:78:31:a1:36: + d2:e5:42:f5:76:f7:24:71:b8:d3:10:fd:b7:57:86: + 68:29:02:1b:e9:93:2b:af:c0:7e:3d:58:16:34:10: + 25:85:7e:28:2d:42:fc:34:7f:d6:4f:0a:e7:b6:b9: + 40:51:c8:e5:89:60:cd:73:51:63:67:e1:59:36:31: + 5e:d0:87:4b:5d:97:8a:42:22:d3:42:f2:83:62:79: + 84:45:5b:d8:20:97:e2:d1:34:23:5e:ec:bf:8a:41: + 7c:ad:80:50:f3:9b:1c:51:5d:e4:29:1c:c1:c6:7c: + a1:00:70:0f:f6:79:77:03:22:f5:4b:ab:76:ca:20: + ab:e1:4e:53:6c:86:68:e9:d6:0f:6e:95:4b:b5:b5: + cf:d3:6c:d4:73:db:c0:3d:1d:20:20:72:ab:3e:25: + bb:3f:cd:5f:2d:77:fd:d7:0b:a8:eb:0c:73:c0:3f: + 74:27:17:56:cf:31:9a:a9:ed:c1:39:27:c7:aa:e9: + 77:48:f4:6d:56:89:35:5c:79:5b:f6:2f:8c:9f:93: + bc:13:1c:d0:74:5f:30:1b:15:43:71:8e:ff:1a:df: + 65:65:69:c2:8a:5a:ab:04:4a:80:de:20:29:8d:48: + 1c:83 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 34:E9:5C:D8:FC:C7:AF:F7:5D:F4:13:D3:82:1A:CD:52:0F:4A:BB:0B + X509v3 Authority Key Identifier: + keyid:52:33:2E:22:66:79:82:E5:41:2E:8D:3B:B2:E5:CC:FA:BE:99:33:8E + DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=test/CN=test/emailAddress=test@test.com + serial:E6:4C:5C:96:26:5E:D6:D0 + + Signature Algorithm: md5WithRSAEncryption + 88:c4:dd:c5:4a:a6:8e:1f:36:62:80:31:6b:2b:85:34:0d:6c: + a7:dc:e1:5a:3c:dd:74:57:db:59:be:8b:f2:79:40:2d:bd:9b: + e1:ab:0e:03:75:20:75:2f:ea:97:f9:d4:7e:15:75:c2:82:f5: + b1:37:e4:8b:24:d4:ef:02:3b:19:6a:56:bb:9f:c3:a5:b4:c4: + 39:a0:64:96:5b:c3:a8:19:74:ec:0b:cf:33:df:f3:12:f3:e2: + ab:1d:7d:de:21:64:cd:b1:a0:6f:7a:77:84:d4:62:8a:50:e6: + 76:5d:af:3a:ef:1a:87:0e:1a:38:c4:f9:0d:2f:14:e0:20:32: + 4c:2e:70:d2:71:82:d5:e5:c9:13:2c:4e:b4:c8:63:65:1d:48: + e8:c3:56:6d:88:84:57:65:13:1e:42:48:b4:03:25:71:4e:12: + 9d:1a:88:65:d5:71:21:3b:0e:be:37:1c:f0:88:6b:45:0c:0e: + 6c:e5:60:cb:86:bf:bf:40:30:c4:cf:94:f8:1c:60:d4:1b:df: + 88:02:7a:45:7e:d1:c6:f3:07:b9:b1:8f:2a:55:2f:be:77:9f: + d1:46:64:05:b7:67:c5:b2:ac:8c:42:8f:6a:51:a4:0d:2b:16: + a4:d6:39:a5:f8:25:ed:68:25:13:b5:19:ac:6c:e9:ec:ee:03: + 6b:80:38:6e + diff --git a/src/libnm-core-impl/tests/certs/test-tpm2wrapped-key.pem b/src/libnm-core-impl/tests/certs/test-tpm2wrapped-key.pem new file mode 100644 index 00000000..f3fd271c --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test-tpm2wrapped-key.pem @@ -0,0 +1,14 @@ +-----BEGIN TSS2 PRIVATE KEY----- +MIICEwYGZ4EFCgEDoAMBAQECBQCBAAABBIIBGAEWAAEACwACBEAAAAAQABAIAAAA +AAABAOJdEXw1LO6JWskHBSYQc1NfJAe9DOAyLA4XbXI5asQ8aNbmL51DP9mQQpqq +a1CSRZAIuuorMxyRBBAFpF4OZqjNd/Nskp3iMmifr5yqAYZ3M31MqlBFiiyctqKp +VwIChwsIbKelrsXbty1icP2CH+k4w/nPymPjnfYtgpMe8QW8n6U156ujIdJcISds +QFcl3nrDnD1IumX0/LfanQrRDVSI+m6szvTrdsPMtGeaNMnlz0gx74auo7/CgEjX +69xjPvQpNLHO/nV4EHSdfXH3LamcpWO8aEAVne3MFFA9V0bpv7uKoGhRjssD9kSr +PQzNXfjHpkcOLeH4pzxDMFXDIGUEgeAA3gAgrCL3RRcBryFXToo9ZN3/f4EeeEjK +58ejYomsxqvckhgAEMxbT26fo2h27b4KPlnUpoiL2JPLB0Xz6PJAF8n0YdJUO381 +xhzPTIQop81BxljTLV2C9WGns5bWDPW9ItEbv4UalEwFfxsW4Ma5smLWn3A1UwVN +Z1cW/oUx+3nOCF1TZgbMPszToqCPlpIHd9vO709qpSyULIUkZLHS6PUM7ESY5U81 +f4BITxJR+aYaqErni/FDLsDVP0MQ3CuFHeUDHI0uEzzbfurYFjpp9+caaoWuZzpg +VYV5pjPdgg== +-----END TSS2 PRIVATE KEY----- diff --git a/src/libnm-core-impl/tests/certs/test2-cert.p12 b/src/libnm-core-impl/tests/certs/test2-cert.p12 new file mode 100644 index 00000000..9d5732b0 Binary files /dev/null and b/src/libnm-core-impl/tests/certs/test2-cert.p12 differ diff --git a/src/libnm-core-impl/tests/certs/test2_ca_cert.pem b/src/libnm-core-impl/tests/certs/test2_ca_cert.pem new file mode 100644 index 00000000..9a487ca4 --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test2_ca_cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEpDCCA4ygAwIBAgIJANDnVhixAO1GMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYD +VQQGEwJVUzEWMBQGA1UECBMNTWFzc2FjaHVzZXR0czERMA8GA1UEBxMIV2VzdGZv +cmQxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xFDASBgNVBAsTC0VuZ2luZWVyaW5n +MRAwDgYDVQQDEwdlYXB0ZXN0MRgwFgYJKoZIhvcNAQkBFglpdEBpdC5jb20wHhcN +MDcxMTA5MTU0ODI1WhcNMTcxMTA2MTU0ODI1WjCBkjELMAkGA1UEBhMCVVMxFjAU +BgNVBAgTDU1hc3NhY2h1c2V0dHMxETAPBgNVBAcTCFdlc3Rmb3JkMRYwFAYDVQQK +Ew1SZWQgSGF0LCBJbmMuMRQwEgYDVQQLEwtFbmdpbmVlcmluZzEQMA4GA1UEAxMH +ZWFwdGVzdDEYMBYGCSqGSIb3DQEJARYJaXRAaXQuY29tMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAz9zRLSiQyQangDgEliEP8xSpnPJS7GjXzrkZS3sk +gZLuVuwoFeZRq3Hsrq/wGd/vM0KUFNmEaMc+47jnuv0UHQcQ45ZACO7s4/Aflhzj +lkmud/z06hVknIzjXmvS6q2ttCviHsXnfokl+wAxuUhsd+le0xjP9H1jXny4YBuS +jP+yGUz7PL4w1sFFghKIPrlB7m4GkFbQRqvH7FSJg86GWopPwJvNvIzhOZiO1a1D +CAAL4Ru3jxtNFxqWT87C/qUEe/2Qb7jtNyqFcKfwZyZh4u1bo0c8bjErlUZERbWz +zM3hTFypuw+i2v+0h3A8/Xb0hTjcHkUoJgfSdbsOLC5TOwIDAQABo4H6MIH3MB0G +A1UdDgQWBBR+UOaH4e8nrEuMcEXJl7UN5r/wDTCBxwYDVR0jBIG/MIG8gBR+UOaH +4e8nrEuMcEXJl7UN5r/wDaGBmKSBlTCBkjELMAkGA1UEBhMCVVMxFjAUBgNVBAgT +DU1hc3NhY2h1c2V0dHMxETAPBgNVBAcTCFdlc3Rmb3JkMRYwFAYDVQQKEw1SZWQg +SGF0LCBJbmMuMRQwEgYDVQQLEwtFbmdpbmVlcmluZzEQMA4GA1UEAxMHZWFwdGVz +dDEYMBYGCSqGSIb3DQEJARYJaXRAaXQuY29tggkA0OdWGLEA7UYwDAYDVR0TBAUw +AwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAmE2jqUymfxN2Vv7bPafoK/EpZwGPxu+z +phRFsgUgWVzidc/GtOxN81LduJ+ow8MEbQIabo4JV/MdKzuPuhAHToAQdeb0LIWa +p59vTIZiVhUt0cMAbQwKcTnfmDnXw9wytvtKgeAXJq0Jd6F+uNXTiR1btlYLZqmF +oSu54cHQlXpUT9z0BnQ8eXd7m0TwfzGQkTHQI7xBa87lZDAkJaTlhv7fR5vPmJYY +0LiXii71ce+4hxdlp7hQfwQ2sb8FPY3RlVboTRD0CvGaWypWhdSZnS790dBXgZOs +NCge6NGuHzW5LtiZE9ppuv8qJysVcIFdAqt8dkx58ksOqFcARCerXw== +-----END CERTIFICATE----- diff --git a/src/libnm-core-impl/tests/certs/test2_key_and_cert.pem b/src/libnm-core-impl/tests/certs/test2_key_and_cert.pem new file mode 100644 index 00000000..a668596e --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test2_key_and_cert.pem @@ -0,0 +1,119 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,5FA2D6D6242C26D0 + +dyNdbh115sczbUEhiaGYJ6fazyvJss5thPFEmkP6aftYlvXY6vPtc++xFCCktiSd +qFVEyi6oDyV4iGPmX7pCJ0e+pSI6uFNXKFtxh5/+/wXZcOEMCvfu7w2IrvYF2LHY +qJDljcISSRxeINuYO7TETD5fLLRKj2X9vwwkwVN02b2N5jsrm6Bt//WbatqSB3ln +FHyQhVKkvdl9Hr1XNmEfgGfZSxxDoPu1DjhtZ5ja2LZj64C2CXdI0oq2wcAVvQNn +rZeeg9sinQJkz9rwsNaWqlYw4X+YD2JRSwZuvwkWRydYMwgb1XS/jCxtuFF8NXWP +RBAOAZZUy7onzohsJHVVa05wCKQ4klo+PEfI3vn7BeuHyciCc0eFqGRvz8eFDybH +ZdPbU/3vGp+mOB7gd27TptttTCQQy9uM5CIyovNSYsAIw1Z583Ea4q8eXgzkgD6D +isCqkGXMfPbNXU3myQGDnQwWRi2CqX+rXM8PJUhdewLAlmHRz/aYSuql2BRixJKx +eASzmFBYdAjrvafda5D+xTyJwXEwdq/HlqMK9cY28ZbNrzA2Kor2X23EKC1+VG8k +B67OsfUhW27j4u6aV5JdLf87OtF3mHFRR+Lzs7i7LYvJ8ACE+jiIi7PboZjK5Oiv +JqTK0BwDaeNYjkd6jiJh8It/ReMbLk65J3eldOklN0VMPYiqcQnHvSPC2DD1YAy+ +Rv/JVj6TvzvgEAj+hgH6MAAF6u3ARj6+10DlvhUubkOC5RztLKReu8B+427TuuDb +T03gFpHD6X9IqSiq/QfYFyHFojCVSrv6wDZOcHc1s71kpJ8R14YIVe+DrrZN/0D4 +M631jdNg3JARMZXcXTHrghGIdPmOtrsRyTTRZuGoVup/DW9MRzOzCTMSNCX8T+eq +13HMSNQEO9lMwy0sYeO5c7sjHY4K1ubZuVE1mvXq4JLz3YxXJIvgp8TUvqDnAsK+ +Fv63bDoTg5Tq63XvnaKc7Lawneyg5ZAMzPN3nM0/1EZcn/2ICI5c4Yepc5t63EI5 +KytuXx86Mcx234enj3uMeuM22POQ1SnKOef6dFzK/CE8J8eUEY/aDhX4eBl/s3nd +U4+aaFKYz3HTazePayt2SC6rP/KKMmS14q59bOQA1DiWxCvmA2ypRyP87fV5DstH +I53RD5xp1P38iaO8U/divD0W2dkv748s9DQqYrHPtWALT9esxNU07CgB8Zt070si +7pzjQ8FDCZ8ygDmwWGNSBz1nA90Cpd6gAFDrep7HAtDE4qgNfokycpaJZkXBei/U +tC4tWYRbqDEsEbeBHvQRJzzqWzk9e/P4fQoelM3aryKzKLG5z7KvywVifKMLECQ+ +tIpzoRp06nuTA/O+iLFdkCy3JEWszfvvOwTwtIIV6+3s8TU0k9MmzEe3rGL+QqT/ +Tf+9/dN5LK+LWyc99BfmCOrBuFtQmHyEXkfe6EuFYEwj0B2ZfnLCon6cdRujjK7H +IJslC1B/cBVqG3KCrbBzjeygKfJ5Ijo72oXZJOCFTLeJefZKGGWJCp9nG9h9Wrcf +fEN/mj3wBvTa90/PYFj9NuaBtrvMF8Rn9XDeYPq2JGL8YkNdPuO8A+2Yko8wcvST +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Massachusetts, L=Westford, O=Red Hat, Inc., OU=Engineering, CN=eaptest/emailAddress=it@it.com + Validity + Not Before: Nov 9 15:50:14 2007 GMT + Not After : Nov 6 15:50:14 2017 GMT + Subject: C=US, ST=Massachusetts, O=Red Hat, Inc., OU=Engineering, CN=client/emailAddress=it@it.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:b0:8f:4f:1c:93:d4:43:e7:87:b7:22:33:55:a8: + 35:a1:c4:01:b0:f1:ed:26:23:96:ab:65:c2:c2:54: + db:79:22:03:ad:3f:6f:22:e3:63:3f:f4:21:6d:fa: + 88:c8:8f:1a:ce:55:49:7c:98:33:6a:67:8a:8d:d9: + 34:b0:c3:42:f4:72:a4:45:43:05:72:5d:0c:d3:42: + f8:9c:66:3b:b8:f8:77:ea:f6:b6:94:d7:cc:5d:62: + 34:2a:14:48:0a:bc:65:94:f5:7a:63:98:6c:88:4c: + 25:d8:95:f1:40:3d:00:d2:fb:43:28:fa:02:fb:2c: + 80:b3:e1:33:e7:8c:ce:8a:a0:1b:3d:04:4d:bc:a1: + b6:a2:42:8b:8e:f3:5b:4a:72:34:7d:8d:ba:d8:46: + 22:35:da:5c:f8:dd:fc:6d:9e:59:22:b7:6b:e7:78: + 56:54:9f:4c:d1:e2:4a:23:a3:bc:04:ea:46:6b:70: + 8a:fb:fe:8a:73:ca:36:d5:f3:e9:17:e3:22:d5:b3: + 70:05:e7:f7:37:b7:21:b5:90:53:27:27:ea:36:9b: + 00:ff:35:b0:66:3d:dc:a9:2f:95:d2:21:18:98:4f: + 28:07:09:70:20:a8:b1:82:aa:a5:df:ae:0f:e3:36: + be:68:8c:9e:80:d3:33:d0:f5:84:17:d9:0f:eb:9d: + af:0b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 71:AB:BB:91:B7:04:DE:43:35:36:07:8A:35:CA:BE:5C:3E:EB:B1:09 + X509v3 Authority Key Identifier: + keyid:7E:50:E6:87:E1:EF:27:AC:4B:8C:70:45:C9:97:B5:0D:E6:BF:F0:0D + DirName:/C=US/ST=Massachusetts/L=Westford/O=Red Hat, Inc./OU=Engineering/CN=eaptest/emailAddress=it@it.com + serial:D0:E7:56:18:B1:00:ED:46 + + Signature Algorithm: md5WithRSAEncryption + ce:43:6d:f7:f8:4a:66:fd:8a:2c:41:a6:e0:03:0e:60:30:d4: + 41:01:ba:46:ba:81:97:64:68:83:25:9c:e1:2c:03:8b:2d:ca: + 85:cf:bc:fa:ca:22:c4:59:28:23:8f:ff:50:94:60:1c:90:dd: + 75:f4:d4:ea:8c:fa:61:61:08:35:4a:8f:aa:a7:e9:3d:76:e9: + 08:28:55:01:c4:03:42:c7:ad:58:bb:ee:94:f7:09:b3:9a:9b: + 8b:d0:25:95:18:a6:22:d5:2c:fc:b7:bb:91:0c:7c:03:7f:9b: + 85:de:b0:e4:95:a8:73:94:27:0a:11:4e:e3:67:ae:2b:cc:e7: + 51:29:10:23:57:5c:3e:e7:ea:47:e0:f0:8f:5b:a2:9f:26:cf: + 7f:b5:7c:44:b1:7b:83:67:3c:41:ae:c6:66:64:e0:d2:ef:57: + a4:5c:1b:94:11:ce:28:e5:91:51:ef:e1:98:b7:3b:9a:cc:f7: + b9:85:76:eb:a8:2b:15:4a:cc:1a:a3:42:fa:be:1c:ce:b8:eb: + ee:12:d7:2f:e4:a8:cf:eb:2a:8f:78:e8:91:88:fa:c2:98:75: + 6a:4c:92:3f:2e:0d:e1:20:39:36:c6:2c:be:67:30:c3:f3:c3: + 65:81:ac:e3:3c:19:6a:21:ee:ea:f5:22:66:74:b2:07:53:7c: + 9a:0c:24:a6 +-----BEGIN CERTIFICATE----- +MIIEtDCCA5ygAwIBAgIBAjANBgkqhkiG9w0BAQQFADCBkjELMAkGA1UEBhMCVVMx +FjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxETAPBgNVBAcTCFdlc3Rmb3JkMRYwFAYD +VQQKEw1SZWQgSGF0LCBJbmMuMRQwEgYDVQQLEwtFbmdpbmVlcmluZzEQMA4GA1UE +AxMHZWFwdGVzdDEYMBYGCSqGSIb3DQEJARYJaXRAaXQuY29tMB4XDTA3MTEwOTE1 +NTAxNFoXDTE3MTEwNjE1NTAxNFowfjELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDU1h +c3NhY2h1c2V0dHMxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xFDASBgNVBAsTC0Vu +Z2luZWVyaW5nMQ8wDQYDVQQDEwZjbGllbnQxGDAWBgkqhkiG9w0BCQEWCWl0QGl0 +LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALCPTxyT1EPnh7ci +M1WoNaHEAbDx7SYjlqtlwsJU23kiA60/byLjYz/0IW36iMiPGs5VSXyYM2pnio3Z +NLDDQvRypEVDBXJdDNNC+JxmO7j4d+r2tpTXzF1iNCoUSAq8ZZT1emOYbIhMJdiV +8UA9ANL7Qyj6AvssgLPhM+eMzoqgGz0ETbyhtqJCi47zW0pyNH2NuthGIjXaXPjd +/G2eWSK3a+d4VlSfTNHiSiOjvATqRmtwivv+inPKNtXz6RfjItWzcAXn9ze3IbWQ +Uycn6jabAP81sGY93KkvldIhGJhPKAcJcCCosYKqpd+uD+M2vmiMnoDTM9D1hBfZ +D+udrwsCAwEAAaOCASYwggEiMAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9w +ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBRxq7uRtwTeQzU2 +B4o1yr5cPuuxCTCBxwYDVR0jBIG/MIG8gBR+UOaH4e8nrEuMcEXJl7UN5r/wDaGB +mKSBlTCBkjELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxETAP +BgNVBAcTCFdlc3Rmb3JkMRYwFAYDVQQKEw1SZWQgSGF0LCBJbmMuMRQwEgYDVQQL +EwtFbmdpbmVlcmluZzEQMA4GA1UEAxMHZWFwdGVzdDEYMBYGCSqGSIb3DQEJARYJ +aXRAaXQuY29tggkA0OdWGLEA7UYwDQYJKoZIhvcNAQEEBQADggEBAM5Dbff4Smb9 +iixBpuADDmAw1EEBuka6gZdkaIMlnOEsA4styoXPvPrKIsRZKCOP/1CUYByQ3XX0 +1OqM+mFhCDVKj6qn6T126QgoVQHEA0LHrVi77pT3CbOam4vQJZUYpiLVLPy3u5EM +fAN/m4XesOSVqHOUJwoRTuNnrivM51EpECNXXD7n6kfg8I9bop8mz3+1fESxe4Nn +PEGuxmZk4NLvV6RcG5QRzijlkVHv4Zi3O5rM97mFduuoKxVKzBqjQvq+HM646+4S +1y/kqM/rKo946JGI+sKYdWpMkj8uDeEgOTbGLL5nMMPzw2WBrOM8GWoh7ur1ImZ0 +sgdTfJoMJKY= +-----END CERTIFICATE----- diff --git a/src/libnm-core-impl/tests/certs/test_ca_cert.der b/src/libnm-core-impl/tests/certs/test_ca_cert.der new file mode 100644 index 00000000..e844f65b Binary files /dev/null and b/src/libnm-core-impl/tests/certs/test_ca_cert.der differ diff --git a/src/libnm-core-impl/tests/certs/test_ca_cert.pem b/src/libnm-core-impl/tests/certs/test_ca_cert.pem new file mode 100644 index 00000000..faa3fc29 --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test_ca_cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEhjCCA26gAwIBAgIJAOZMXJYmXtbQMA0GCSqGSIb3DQEBBQUAMIGIMQswCQYD +VQQGEwJVUzESMBAGA1UECBMJQmVya3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcw +FQYDVQQKEw5NeSBDb21wYW55IEx0ZDENMAsGA1UECxMEdGVzdDENMAsGA1UEAxME +dGVzdDEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wOTAyMDIwMjIx +NTJaFw0xOTAxMzEwMjIxNTJaMIGIMQswCQYDVQQGEwJVUzESMBAGA1UECBMJQmVy +a3NoaXJlMRAwDgYDVQQHEwdOZXdidXJ5MRcwFQYDVQQKEw5NeSBDb21wYW55IEx0 +ZDENMAsGA1UECxMEdGVzdDENMAsGA1UEAxMEdGVzdDEcMBoGCSqGSIb3DQEJARYN +dGVzdEB0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANZz +iHfFR6Gqdz5Gq1elSaf7J2tznfialPyMkRoqngSItrM3JWlkfBu3ZIpGSYG8+Z8H +JjaKh/zkCCH73HcjADCocm/XtgxlOlccDtBvfDqifb2dBikcmcfh0tX0o4JU9L+z +WP7sVcUkJG7C5811lA5pAsLVooJxrZnrta6XstGvFgTSxI2Y4Jowf0syM5b/AHIQ ++htsVhSyj1DWM9day3QsEMZl55SCxCLTB6ZIJLF6NmTYwdnzWWTQf4k7kcptZJU8 +Fqv60b/ATvvUf7S+ZMGLopFIIlmc1Kkn7VTseIKy4ycYuDpyimQy3Yw4flC00vhl +hTijH+5dlVOUFrRh0bsCAwEAAaOB8DCB7TAdBgNVHQ4EFgQUUjMuImZ5guVBLo07 +suXM+r6ZM44wgb0GA1UdIwSBtTCBsoAUUjMuImZ5guVBLo07suXM+r6ZM46hgY6k +gYswgYgxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAOBgNVBAcT +B05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMQ0wCwYDVQQLEwR0ZXN0 +MQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggkA +5kxcliZe1tAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAZsWzGp9D +10NWf8ts3A9mhcEb8oDgKNpCOZF9gqr2usZygJjY9MX2n0n81T8XaWDk1wZLlcDW +hzIalCY8rpE57Dj3MEmdCmYr3BrAy6GVlc/MO7ffTHxogl9ouScSYo8ETTtNMKCg +zwkDnXjGzmb1e59DIXMVHBBbTmJXJbM8XP9A2azJkVUyE2fSdWksXMP0XklGbW2p +DksJ+8G1IWkyRhnINV5GZLLNAT5gBA6sUv/iqQKwk8r2dsweMPtLoLWZPAaZErYh +sthGy00Q0GJkmkJpQl8QIlPq5vylNcMUhvc8tmzwk+rDVUj3jTJWqFwMtnHAaNyC +FXl9ynWpe5J28w== +-----END CERTIFICATE----- diff --git a/src/libnm-core-impl/tests/certs/test_key_and_cert.pem b/src/libnm-core-impl/tests/certs/test_key_and_cert.pem new file mode 100644 index 00000000..c00495d0 --- /dev/null +++ b/src/libnm-core-impl/tests/certs/test_key_and_cert.pem @@ -0,0 +1,119 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,B29FCA6ECB7C0C48 + +KuRV4sEVQkY0r0E+E81Se2qpm54HeboUMIJcMVqAt/HIeYwpjEzmXHUywXgayA5k +r5Uvru95vymFz80q/rl6i4Mt3ckEQ3/VHmBI4pPj/JeRbiDhqTyLqpB5zMgbuti+ +PsEemlbiaCMZFdJk8rJBWADTkirGbXs5ciLwTJyrJXhkBVTqoHtphSWMELxcrnQj +5bAXZ+rnApWA7LR3yCY1UE0Nj39Yu8lkzDJtbsSXPJVO//OQiGuQRcY6roAUzeS/ +amLdIYqoZm3OF83Ro88XZCIDCG6XjgKoqojtekClVIytB6mHWk+IHhI7DgU/pDBj +yOskzVRMja9ECnqaVX+bmlmJGZBgcd7+XiebCXNY9IAx/ZQDMbDJDoJusvH6npJR +PBgag8QBSzwpFyEP/1LrlyVyggZcGuXDWfUiM2rt2UvLm8QYeT5WGfUI+QwW6JRV +xEz2QwRiPsmj0uK2fXLKHbY0Pv79IMLqgesCAUbndWsi94kjAhpJGa/WfKVb8Dpg +T+mVa2OT0pgewPWJ3h5f47ag27WDJthsIUHAvNZ03uM23DIqDh6o03/B3/4r1Uab +HzsLWhUOzbLihO08qcPk/JqtDVCv579mpgvy7bL2ZsZUzefnmg+ah+1bhLguryZu +Zrt1hTu5WTz27UbYwWNtkxWnWcM9sSF9y+KVwumYhIUGTvg3Jds/jmp/Z5ad8tev +0RQuWmqnXVD51Y92tOXobJH7JwGbQ4GeRNG/UX7DS9Lvb6rPgmmxvGbXMcjEMapB +m5DLjCD7Lz4++sfzhCic3/nL8e8fjoYZmCbL/SpYjGjTrfoNaGYaT0De8MxCSjPf +rNT+ldWFD0oAOTmT0uqOFkeIx3vxczSQUyRfzGOLqtb9mAXk8lvdb8Myb5atfe8f +FNBL5ocz6Szv2AmKmjZE5P6NKSllyryCYl+H2QU0e3OyY8S9aG2aG8fqeVKaA01S +uOOjsK6IKMpkit+VFDP7RWvS8p1FTwTVRdbMZzASW8+D98OHsTlOENtQuuFyvNaN +vPHrbHltTkwDlYVab9UpFr/ZRW3cNJrmcl0hyeCJxk5u8B9UwW6yZmd24hxda2xt +1QjnxfWlX0i5KTmfWFyPmRYt1WH9rdVaH6wqRbGNbMRT7Lez0E/2+LXjFiiEe0Vh +ZuryhyhUXpHY8yyHxFY92XT504Z3wPUaJ1tQxGhdziZ7HcqFT8f4oTNJdBNtzqNw +l63gSTaKUkSB14CBOyR84cqpgSMwZvWZb3RQFYY4GSALWwhnn6PWeRhjWAXPms2v +tRmfEyDUCyPxDarezvS9kUir3XbvnCkoJOrK/lw4a8ygGBCBqRKOdI0SRUf1Nnjm +aZBrKrXHv5vP66fyC7cGMTq35+iiHbE//rRDGPrf9vpmw3ki3FlDMyAXOC5yWDco +0n+cNDyHJDdIW3+3nfZvSgPPMwmTdfNO90X3YczSmMTGjnIuhQMf7FMFmMZOSrqD +u234vYCjIIlph3aVpbzjOqKg3d4nuP9oouyAgRAr+vShgHRmq0jvsAiGH3Q6RqEj +DvkM1NgoloMy5RhKF4hIeaQM/UlgoXrcEx0RGCJHykQcQmVa5cf5EwjrO7b+w2Lg +-----END RSA PRIVATE KEY----- +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: md5WithRSAEncryption + Issuer: C=US, ST=Berkshire, L=Newbury, O=My Company Ltd, OU=test, CN=test/emailAddress=test@test.com + Validity + Not Before: Feb 2 02:22:25 2009 GMT + Not After : Jan 31 02:22:25 2019 GMT + Subject: C=US, ST=Berkshire, O=My Company Ltd, OU=test-client, CN=test-client/emailAddress=test-client@test.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public Key: (2048 bit) + Modulus (2048 bit): + 00:ce:d2:e9:fb:f4:a4:ce:66:87:cc:8b:2d:d2:c3: + b8:fa:be:ea:53:08:95:9e:27:4a:04:78:31:a1:36: + d2:e5:42:f5:76:f7:24:71:b8:d3:10:fd:b7:57:86: + 68:29:02:1b:e9:93:2b:af:c0:7e:3d:58:16:34:10: + 25:85:7e:28:2d:42:fc:34:7f:d6:4f:0a:e7:b6:b9: + 40:51:c8:e5:89:60:cd:73:51:63:67:e1:59:36:31: + 5e:d0:87:4b:5d:97:8a:42:22:d3:42:f2:83:62:79: + 84:45:5b:d8:20:97:e2:d1:34:23:5e:ec:bf:8a:41: + 7c:ad:80:50:f3:9b:1c:51:5d:e4:29:1c:c1:c6:7c: + a1:00:70:0f:f6:79:77:03:22:f5:4b:ab:76:ca:20: + ab:e1:4e:53:6c:86:68:e9:d6:0f:6e:95:4b:b5:b5: + cf:d3:6c:d4:73:db:c0:3d:1d:20:20:72:ab:3e:25: + bb:3f:cd:5f:2d:77:fd:d7:0b:a8:eb:0c:73:c0:3f: + 74:27:17:56:cf:31:9a:a9:ed:c1:39:27:c7:aa:e9: + 77:48:f4:6d:56:89:35:5c:79:5b:f6:2f:8c:9f:93: + bc:13:1c:d0:74:5f:30:1b:15:43:71:8e:ff:1a:df: + 65:65:69:c2:8a:5a:ab:04:4a:80:de:20:29:8d:48: + 1c:83 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 34:E9:5C:D8:FC:C7:AF:F7:5D:F4:13:D3:82:1A:CD:52:0F:4A:BB:0B + X509v3 Authority Key Identifier: + keyid:52:33:2E:22:66:79:82:E5:41:2E:8D:3B:B2:E5:CC:FA:BE:99:33:8E + DirName:/C=US/ST=Berkshire/L=Newbury/O=My Company Ltd/OU=test/CN=test/emailAddress=test@test.com + serial:E6:4C:5C:96:26:5E:D6:D0 + + Signature Algorithm: md5WithRSAEncryption + 88:c4:dd:c5:4a:a6:8e:1f:36:62:80:31:6b:2b:85:34:0d:6c: + a7:dc:e1:5a:3c:dd:74:57:db:59:be:8b:f2:79:40:2d:bd:9b: + e1:ab:0e:03:75:20:75:2f:ea:97:f9:d4:7e:15:75:c2:82:f5: + b1:37:e4:8b:24:d4:ef:02:3b:19:6a:56:bb:9f:c3:a5:b4:c4: + 39:a0:64:96:5b:c3:a8:19:74:ec:0b:cf:33:df:f3:12:f3:e2: + ab:1d:7d:de:21:64:cd:b1:a0:6f:7a:77:84:d4:62:8a:50:e6: + 76:5d:af:3a:ef:1a:87:0e:1a:38:c4:f9:0d:2f:14:e0:20:32: + 4c:2e:70:d2:71:82:d5:e5:c9:13:2c:4e:b4:c8:63:65:1d:48: + e8:c3:56:6d:88:84:57:65:13:1e:42:48:b4:03:25:71:4e:12: + 9d:1a:88:65:d5:71:21:3b:0e:be:37:1c:f0:88:6b:45:0c:0e: + 6c:e5:60:cb:86:bf:bf:40:30:c4:cf:94:f8:1c:60:d4:1b:df: + 88:02:7a:45:7e:d1:c6:f3:07:b9:b1:8f:2a:55:2f:be:77:9f: + d1:46:64:05:b7:67:c5:b2:ac:8c:42:8f:6a:51:a4:0d:2b:16: + a4:d6:39:a5:f8:25:ed:68:25:13:b5:19:ac:6c:e9:ec:ee:03: + 6b:80:38:6e +-----BEGIN CERTIFICATE----- +MIIErjCCA5agAwIBAgIBATANBgkqhkiG9w0BAQQFADCBiDELMAkGA1UEBhMCVVMx +EjAQBgNVBAgTCUJlcmtzaGlyZTEQMA4GA1UEBxMHTmV3YnVyeTEXMBUGA1UEChMO +TXkgQ29tcGFueSBMdGQxDTALBgNVBAsTBHRlc3QxDTALBgNVBAMTBHRlc3QxHDAa +BgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5jb20wHhcNMDkwMjAyMDIyMjI1WhcNMTkw +MTMxMDIyMjI1WjCBizELMAkGA1UEBhMCVVMxEjAQBgNVBAgTCUJlcmtzaGlyZTEX +MBUGA1UEChMOTXkgQ29tcGFueSBMdGQxFDASBgNVBAsTC3Rlc3QtY2xpZW50MRQw +EgYDVQQDEwt0ZXN0LWNsaWVudDEjMCEGCSqGSIb3DQEJARYUdGVzdC1jbGllbnRA +dGVzdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDO0un79KTO +ZofMiy3Sw7j6vupTCJWeJ0oEeDGhNtLlQvV29yRxuNMQ/bdXhmgpAhvpkyuvwH49 +WBY0ECWFfigtQvw0f9ZPCue2uUBRyOWJYM1zUWNn4Vk2MV7Qh0tdl4pCItNC8oNi +eYRFW9ggl+LRNCNe7L+KQXytgFDzmxxRXeQpHMHGfKEAcA/2eXcDIvVLq3bKIKvh +TlNshmjp1g9ulUu1tc/TbNRz28A9HSAgcqs+Jbs/zV8td/3XC6jrDHPAP3QnF1bP +MZqp7cE5J8eq6XdI9G1WiTVceVv2L4yfk7wTHNB0XzAbFUNxjv8a32VlacKKWqsE +SoDeICmNSByDAgMBAAGjggEcMIIBGDAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf +Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUNOlc2PzH +r/dd9BPTghrNUg9Kuwswgb0GA1UdIwSBtTCBsoAUUjMuImZ5guVBLo07suXM+r6Z +M46hgY6kgYswgYgxCzAJBgNVBAYTAlVTMRIwEAYDVQQIEwlCZXJrc2hpcmUxEDAO +BgNVBAcTB05ld2J1cnkxFzAVBgNVBAoTDk15IENvbXBhbnkgTHRkMQ0wCwYDVQQL +EwR0ZXN0MQ0wCwYDVQQDEwR0ZXN0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3Qu +Y29tggkA5kxcliZe1tAwDQYJKoZIhvcNAQEEBQADggEBAIjE3cVKpo4fNmKAMWsr +hTQNbKfc4Vo83XRX21m+i/J5QC29m+GrDgN1IHUv6pf51H4VdcKC9bE35Isk1O8C +OxlqVrufw6W0xDmgZJZbw6gZdOwLzzPf8xLz4qsdfd4hZM2xoG96d4TUYopQ5nZd +rzrvGocOGjjE+Q0vFOAgMkwucNJxgtXlyRMsTrTIY2UdSOjDVm2IhFdlEx5CSLQD +JXFOEp0aiGXVcSE7Dr43HPCIa0UMDmzlYMuGv79AMMTPlPgcYNQb34gCekV+0cbz +B7mxjypVL753n9FGZAW3Z8WyrIxCj2pRpA0rFqTWOaX4Je1oJRO1Gaxs6ezuA2uA +OG4= +-----END CERTIFICATE----- diff --git a/src/libnm-core-impl/tests/meson.build b/src/libnm-core-impl/tests/meson.build new file mode 100644 index 00000000..ca1d1c1a --- /dev/null +++ b/src/libnm-core-impl/tests/meson.build @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +enum_sources = gnome.mkenums_simple( + 'nm-core-tests-enum-types', + sources: 'test-general-enums.h', + identifier_prefix: nm_id_prefix, + body_prefix: '#include "libnm-core-impl/nm-default-libnm-core.h"', +) + +test_units = [ + 'test-compare', + 'test-crypto', + 'test-general', + 'test-keyfile', + 'test-secrets', + 'test-setting', + 'test-settings-defaults', +] + +foreach test_unit: test_units + exe = executable( + 'libnm-core-' + test_unit, + [test_unit + '.c'] + enum_sources, + include_directories: [ + libnm_core_impl_inc, + ], + dependencies: [ + libnm_core_public_dep, + ], + link_with: [ + libnm_core_aux_intern, + libnm_core_impl, + libnm_crypto, + libnm_base, + libnm_log_null, + libnm_systemd_shared, + libnm_glib_aux, + libnm_std_aux, + libc_siphash, + ], + ) + + test( + 'src/libnm-core-impl/tests/' + test_unit, + test_script, + args: test_args + [exe.full_path()], + timeout: default_test_timeout, + ) +endforeach diff --git a/src/libnm-core-impl/tests/nm-core-tests-enum-types.c.template b/src/libnm-core-impl/tests/nm-core-tests-enum-types.c.template new file mode 100644 index 00000000..1160be86 --- /dev/null +++ b/src/libnm-core-impl/tests/nm-core-tests-enum-types.c.template @@ -0,0 +1,35 @@ +/*** BEGIN file-header ***/ +#include "config.h" + +#include "nm-core-tests-enum-types.h" + +#include "test-general-enums.h" +/*** END file-header ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static volatile gsize g_define_type_id__volatile = 0; + + if (g_once_init_enter (&g_define_type_id__volatile)) + { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + GType g_define_type_id = + g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + g_once_init_leave (&g_define_type_id__volatile, g_define_type_id); + } + + return g_define_type_id__volatile; +} + +/*** END value-tail ***/ diff --git a/src/libnm-core-impl/tests/nm-core-tests-enum-types.h.template b/src/libnm-core-impl/tests/nm-core-tests-enum-types.h.template new file mode 100644 index 00000000..62e82b77 --- /dev/null +++ b/src/libnm-core-impl/tests/nm-core-tests-enum-types.h.template @@ -0,0 +1,21 @@ +/*** BEGIN file-header ***/ +#ifndef __NM_CORE_TESTS_ENUM_TYPES_H__ +#define __NM_CORE_TESTS_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN enumeration-production ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) + +/*** END enumeration-production ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __NM_CORE_TESTS_ENUM_TYPES_H__ */ +/*** END file-tail ***/ diff --git a/src/libnm-core-impl/tests/test-compare.c b/src/libnm-core-impl/tests/test-compare.c new file mode 100644 index 00000000..3716399a --- /dev/null +++ b/src/libnm-core-impl/tests/test-compare.c @@ -0,0 +1,231 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2007 - 2014 Red Hat, Inc. + * Copyright (C) 2007 - 2008 Novell, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include +#include + +#include "nm-property-compare.h" + +#include "libnm-glib-aux/nm-test-utils.h" + +static void +compare_ints(void) +{ + GVariant *value1, *value2; + + value1 = g_variant_new_int32(5); + value2 = g_variant_new_int32(5); + g_assert(nm_property_compare(value1, value2) == 0); + + g_variant_unref(value2); + value2 = g_variant_new_int32(10); + g_assert(nm_property_compare(value1, value2) < 0); + + g_variant_unref(value2); + value2 = g_variant_new_int32(-1); + g_assert(nm_property_compare(value1, value2) > 0); + + g_variant_unref(value1); + g_variant_unref(value2); +} + +static void +compare_strings(void) +{ + GVariant * value1, *value2; + const char *str1 = "hello"; + const char *str2 = "world"; + + value1 = g_variant_new_string(str1); + value2 = g_variant_new_string(str1); + g_assert(nm_property_compare(value1, value2) == 0); + + g_variant_unref(value2); + value2 = g_variant_new_string(str2); + g_assert(nm_property_compare(value1, value2) < 0); + + g_assert(nm_property_compare(value2, value1) > 0); + + g_variant_unref(value1); + g_variant_unref(value2); +} + +static void +compare_strv(void) +{ + GVariant * value1, *value2; + const char *const strv1[] = {"foo", "bar", "baz", NULL}; + const char *const strv2[] = {"foo", "bar", "bar", NULL}; + const char *const strv3[] = {"foo", "bar", NULL}; + const char *const strv4[] = {"foo", "bar", "baz", "bam", NULL}; + + value1 = g_variant_new_strv(strv1, -1); + value2 = g_variant_new_strv(strv1, -1); + g_assert(nm_property_compare(value1, value2) == 0); + + g_variant_unref(value2); + value2 = g_variant_new_strv(strv2, -1); + g_assert(nm_property_compare(value1, value2) != 0); + + g_variant_unref(value2); + value2 = g_variant_new_strv(strv3, -1); + g_assert(nm_property_compare(value1, value2) != 0); + + g_variant_unref(value2); + value2 = g_variant_new_strv(strv4, -1); + g_assert(nm_property_compare(value1, value2) != 0); + + g_variant_unref(value1); + g_variant_unref(value2); +} + +static void +compare_arrays(void) +{ + GVariant *value1, *value2; + guint32 array[] = {0, 1, 2, 3, 4}; + + value1 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + array, + G_N_ELEMENTS(array), + sizeof(guint32)); + value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + array, + G_N_ELEMENTS(array), + sizeof(guint32)); + + g_assert(nm_property_compare(value1, value2) == 0); + + g_variant_unref(value2); + value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + array + 1, + G_N_ELEMENTS(array) - 1, + sizeof(guint32)); + g_assert(nm_property_compare(value1, value2) != 0); + + array[0] = 7; + g_variant_unref(value2); + value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + array, + G_N_ELEMENTS(array), + sizeof(guint32)); + g_assert(nm_property_compare(value1, value2) != 0); + + g_variant_unref(value1); + g_variant_unref(value2); +} + +static void +compare_str_hash(void) +{ + GVariant * value1, *value2; + GVariantBuilder builder; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); + g_variant_builder_add(&builder, "{ss}", "key1", "hello"); + g_variant_builder_add(&builder, "{ss}", "key2", "world"); + g_variant_builder_add(&builder, "{ss}", "key3", "!"); + value1 = g_variant_builder_end(&builder); + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); + g_variant_builder_add(&builder, "{ss}", "key3", "!"); + g_variant_builder_add(&builder, "{ss}", "key2", "world"); + g_variant_builder_add(&builder, "{ss}", "key1", "hello"); + value2 = g_variant_builder_end(&builder); + + g_assert(nm_property_compare(value1, value2) == 0); + + g_variant_unref(value2); + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); + g_variant_builder_add(&builder, "{ss}", "key1", "hello"); + g_variant_builder_add(&builder, "{ss}", "key3", "!"); + value2 = g_variant_builder_end(&builder); + + g_assert(nm_property_compare(value1, value2) != 0); + g_assert(nm_property_compare(value2, value1) != 0); + + g_variant_unref(value2); + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}")); + g_variant_builder_add(&builder, "{ss}", "key1", "hello"); + g_variant_builder_add(&builder, "{ss}", "key2", "moon"); + g_variant_builder_add(&builder, "{ss}", "key3", "!"); + value2 = g_variant_builder_end(&builder); + + g_assert(nm_property_compare(value1, value2) != 0); + + g_variant_unref(value1); + g_variant_unref(value2); +} + +static void +compare_ip6_addresses(void) +{ + GVariant * value1, *value2; + struct in6_addr addr1; + struct in6_addr addr2; + struct in6_addr addr3; + guint32 prefix1 = 64; + guint32 prefix2 = 64; + guint32 prefix3 = 0; + + inet_pton(AF_INET6, "1:2:3:4:5:6:7:8", &addr1); + inet_pton(AF_INET6, "ffff:2:3:4:5:6:7:8", &addr2); + inet_pton(AF_INET6, "::", &addr3); + + value1 = g_variant_new( + "(@ayu@ay)", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr1.s6_addr, 16, 1), + prefix1, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1)); + + value2 = g_variant_new( + "(@ayu@ay)", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr1.s6_addr, 16, 1), + prefix1, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1)); + + g_assert(nm_property_compare(value1, value2) == 0); + + g_variant_unref(value2); + value2 = g_variant_new( + "(@ayu@ay)", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr2.s6_addr, 16, 1), + prefix2, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1)); + + g_assert(nm_property_compare(value1, value2) != 0); + + g_variant_unref(value2); + value2 = g_variant_new( + "(@ayu@ay)", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1), + prefix3, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1)); + + g_assert(nm_property_compare(value1, value2) != 0); + + g_variant_unref(value1); + g_variant_unref(value2); +} + +NMTST_DEFINE(); + +int +main(int argc, char *argv[]) +{ + nmtst_init(&argc, &argv, TRUE); + + g_test_add_func("/libnm/compare/ints", compare_ints); + g_test_add_func("/libnm/compare/strings", compare_strings); + g_test_add_func("/libnm/compare/strv", compare_strv); + g_test_add_func("/libnm/compare/arrays", compare_arrays); + g_test_add_func("/libnm/compare/str_hash", compare_str_hash); + g_test_add_func("/libnm/compare/ip6_addresses", compare_ip6_addresses); + + return g_test_run(); +} diff --git a/src/libnm-core-impl/tests/test-crypto.c b/src/libnm-core-impl/tests/test-crypto.c new file mode 100644 index 00000000..bdbcd0f3 --- /dev/null +++ b/src/libnm-core-impl/tests/test-crypto.c @@ -0,0 +1,455 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Dan Williams + * Copyright (C) 2007 - 2011 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include +#include +#include + +#include "nm-crypto-impl.h" +#include "nm-utils.h" +#include "nm-errors.h" +#include "libnm-core-intern/nm-core-internal.h" + +#include "libnm-glib-aux/nm-test-utils.h" + +#define TEST_CERT_DIR NM_BUILD_SRCDIR "/src/libnm-core-impl/tests/certs" + +#if 0 +static const char *pem_rsa_key_begin = "-----BEGIN RSA PRIVATE KEY-----"; +static const char *pem_rsa_key_end = "-----END RSA PRIVATE KEY-----"; + +static const char *pem_dsa_key_begin = "-----BEGIN DSA PRIVATE KEY-----"; +static const char *pem_dsa_key_end = "-----END DSA PRIVATE KEY-----"; + +static void +dump_key_to_pem (const char *key, gsize key_len, int key_type) +{ + char *b64 = NULL; + GString *str = NULL; + const char *start_tag; + const char *end_tag; + char *p; + + switch (key_type) { + case NM_CRYPTO_KEY_TYPE_RSA: + start_tag = pem_rsa_key_begin; + end_tag = pem_rsa_key_end; + break; + case NM_CRYPTO_KEY_TYPE_DSA: + start_tag = pem_dsa_key_begin; + end_tag = pem_dsa_key_end; + break; + default: + g_warning ("Unknown key type %d", key_type); + return; + } + + b64 = g_base64_encode ((const unsigned char *) key, key_len); + if (!b64) { + g_warning ("Couldn't base64 encode the key."); + goto out; + } + + str = g_string_new (NULL); + + g_string_append (str, start_tag); + g_string_append_c (str, '\n'); + + for (p = b64; p < (b64 + strlen (b64)); p += 64) { + g_string_append_len (str, p, strnlen (p, 64)); + g_string_append_c (str, '\n'); + } + + g_string_append (str, end_tag); + g_string_append_c (str, '\n'); + + g_message ("Decrypted private key:\n\n%s", str->str); + +out: + g_free (b64); + if (str) + g_string_free (str, TRUE); +} +#endif + +static void +test_cert(gconstpointer test_data) +{ + gs_free char * path = NULL; + gs_unref_bytes GBytes *cert = NULL; + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + GError * error = NULL; + gboolean success; + + path = g_build_filename(TEST_CERT_DIR, (const char *) test_data, NULL); + + success = nm_crypto_load_and_verify_certificate(path, &format, &cert, &error); + nmtst_assert_success(success, error); + g_assert_cmpint(format, ==, NM_CRYPTO_FILE_FORMAT_X509); + + g_assert(nm_utils_file_is_certificate(path)); +} + +static void +test_load_private_key(const char *path, + const char *password, + const char *decrypted_path, + int expected_error) +{ + NMCryptoKeyType key_type = NM_CRYPTO_KEY_TYPE_UNKNOWN; + gboolean is_encrypted = FALSE; + gs_unref_bytes GBytes *array = NULL; + GError * error = NULL; + + g_assert(nm_utils_file_is_private_key(path, &is_encrypted)); + g_assert(is_encrypted); + + array = nmtst_crypto_decrypt_openssl_private_key(path, password, &key_type, &error); + /* Even if the password is wrong, we should determine the key type */ + g_assert_cmpint(key_type, ==, NM_CRYPTO_KEY_TYPE_RSA); + + if (expected_error != -1) { + g_assert(array == NULL); + g_assert_error(error, NM_CRYPTO_ERROR, expected_error); + g_clear_error(&error); + return; + } + + if (password == NULL) { + g_assert(array == NULL); + g_assert_no_error(error); + return; + } + + g_assert(array != NULL); + + if (decrypted_path) { + gs_free char *contents = NULL; + gsize length; + + /* Compare the crypto decrypted key against a known-good decryption */ + if (!g_file_get_contents(decrypted_path, &contents, &length, NULL)) + g_assert_not_reached(); + g_assert(nm_utils_gbytes_equal_mem(array, contents, length)); + } +} + +static void +test_load_pkcs12(const char *path, const char *password, int expected_error) +{ + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + gboolean is_encrypted = FALSE; + GError * error = NULL; + + g_assert(nm_utils_file_is_private_key(path, NULL)); + + format = nm_crypto_verify_private_key(path, password, &is_encrypted, &error); + if (expected_error != -1) { + g_assert_error(error, NM_CRYPTO_ERROR, expected_error); + g_assert_cmpint(format, ==, NM_CRYPTO_FILE_FORMAT_UNKNOWN); + g_clear_error(&error); + } else { + g_assert_no_error(error); + g_assert_cmpint(format, ==, NM_CRYPTO_FILE_FORMAT_PKCS12); + g_assert(is_encrypted); + } +} + +static void +test_load_pkcs12_no_password(const char *path) +{ + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + gboolean is_encrypted = FALSE; + GError * error = NULL; + + g_assert(nm_utils_file_is_private_key(path, NULL)); + + /* We should still get a valid returned crypto file format */ + format = nm_crypto_verify_private_key(path, NULL, &is_encrypted, &error); + g_assert_no_error(error); + g_assert_cmpint(format, ==, NM_CRYPTO_FILE_FORMAT_PKCS12); + g_assert(is_encrypted); +} + +static void +test_is_pkcs12(const char *path, gboolean expect_fail) +{ + gboolean is_pkcs12; + GError * error = NULL; + + is_pkcs12 = nm_crypto_is_pkcs12_file(path, &error); + + if (expect_fail) { + g_assert_error(error, NM_CRYPTO_ERROR, NM_CRYPTO_ERROR_INVALID_DATA); + g_assert(!is_pkcs12); + g_clear_error(&error); + } else { + g_assert_no_error(error); + g_assert(is_pkcs12); + } +} + +static void +test_load_pkcs8(const char *path, const char *password, int expected_error) +{ + NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN; + gboolean is_encrypted = FALSE; + GError * error = NULL; + + g_assert(nm_utils_file_is_private_key(path, NULL)); + + format = nm_crypto_verify_private_key(path, password, &is_encrypted, &error); + if (expected_error != -1) { + g_assert_error(error, NM_CRYPTO_ERROR, expected_error); + g_assert_cmpint(format, ==, NM_CRYPTO_FILE_FORMAT_UNKNOWN); + g_clear_error(&error); + } else { + g_assert_no_error(error); + g_assert_cmpint(format, ==, NM_CRYPTO_FILE_FORMAT_RAW_KEY); + g_assert(is_encrypted); + } +} + +static void +test_encrypt_private_key(const char *path, const char *password) +{ + NMCryptoKeyType key_type = NM_CRYPTO_KEY_TYPE_UNKNOWN; + gs_unref_bytes GBytes *array = NULL; + gs_unref_bytes GBytes *encrypted = NULL; + gs_unref_bytes GBytes *re_decrypted = NULL; + GError * error = NULL; + + array = nmtst_crypto_decrypt_openssl_private_key(path, password, &key_type, &error); + nmtst_assert_success(array, error); + g_assert_cmpint(key_type, ==, NM_CRYPTO_KEY_TYPE_RSA); + + /* Now re-encrypt the private key */ + encrypted = nmtst_crypto_rsa_key_encrypt(g_bytes_get_data(array, NULL), + g_bytes_get_size(array), + password, + NULL, + &error); + nmtst_assert_success(encrypted, error); + + /* Then re-decrypt the private key */ + key_type = NM_CRYPTO_KEY_TYPE_UNKNOWN; + re_decrypted = nmtst_crypto_decrypt_openssl_private_key_data(g_bytes_get_data(encrypted, NULL), + g_bytes_get_size(encrypted), + password, + &key_type, + &error); + nmtst_assert_success(re_decrypted, error); + g_assert_cmpint(key_type, ==, NM_CRYPTO_KEY_TYPE_RSA); + + /* Compare the original decrypted key with the re-decrypted key */ + g_assert(g_bytes_equal(array, re_decrypted)); +} + +static void +test_key(gconstpointer test_data) +{ + char **parts, *path, *password, *decrypted_path; + int len; + + parts = g_strsplit((const char *) test_data, ", ", -1); + len = g_strv_length(parts); + if (len != 2 && len != 3) + g_error("wrong number of arguments (, , [])"); + + path = g_build_filename(TEST_CERT_DIR, parts[0], NULL); + password = parts[1]; + decrypted_path = parts[2] ? g_build_filename(TEST_CERT_DIR, parts[2], NULL) : NULL; + + test_is_pkcs12(path, TRUE); + test_load_private_key(path, password, decrypted_path, -1); + test_load_private_key(path, "blahblahblah", NULL, NM_CRYPTO_ERROR_DECRYPTION_FAILED); + test_load_private_key(path, NULL, NULL, -1); + test_encrypt_private_key(path, password); + + g_free(path); + g_free(decrypted_path); + g_strfreev(parts); +} + +static void +test_key_decrypted(gconstpointer test_data) +{ + const char *file = (const char *) test_data; + gboolean is_encrypted = FALSE; + char * path; + + path = g_build_filename(TEST_CERT_DIR, file, NULL); + + g_assert(nm_utils_file_is_private_key(path, &is_encrypted)); + g_assert(!is_encrypted); + + g_free(path); +} + +static void +test_pkcs12(gconstpointer test_data) +{ + char **parts, *path, *password; + + parts = g_strsplit((const char *) test_data, ", ", -1); + if (g_strv_length(parts) != 2) + g_error("wrong number of arguments (, )"); + + path = g_build_filename(TEST_CERT_DIR, parts[0], NULL); + password = parts[1]; + + test_is_pkcs12(path, FALSE); + test_load_pkcs12(path, password, -1); + test_load_pkcs12(path, "blahblahblah", NM_CRYPTO_ERROR_DECRYPTION_FAILED); + test_load_pkcs12_no_password(path); + + g_free(path); + g_strfreev(parts); +} + +static void +test_pkcs8(gconstpointer test_data) +{ + char **parts, *path, *password; + + parts = g_strsplit((const char *) test_data, ", ", -1); + if (g_strv_length(parts) != 2) + g_error("wrong number of arguments (, )"); + + path = g_build_filename(TEST_CERT_DIR, parts[0], NULL); + password = parts[1]; + + test_is_pkcs12(path, TRUE); + /* Note: NSS and gnutls < 3.5.4 don't support all the ciphers that openssl + * can use with PKCS#8 and thus the password can't be actually verified with + * such libraries. + */ + test_load_pkcs8(path, password, -1); + + g_free(path); + g_strfreev(parts); +} + +#define SALT "sodium chloride" +#define SHORT_PASSWORD "short" +#define LONG_PASSWORD "this is a longer password than the short one" +#define SHORT_DIGEST 16 +#define LONG_DIGEST 57 + +struct { + const char *salt, *password; + gsize digest_size; + const char *result; +} md5_tests[] = { + {NULL, SHORT_PASSWORD, SHORT_DIGEST, "4f09daa9d95bcb166a302407a0e0babe"}, + {NULL, + SHORT_PASSWORD, + LONG_DIGEST, + "4f09daa9d95bcb166a302407a0e0babeb7d62e5baf706830d007c253f0fe7584ad7e92dc00a599ec277293c298ae7" + "0ee3904c348e23be61c91"}, + {SALT, SHORT_PASSWORD, SHORT_DIGEST, "774771f7292210233b5724991d1f9894"}, + {SALT, + SHORT_PASSWORD, + LONG_DIGEST, + "774771f7292210233b5724991d1f98941a6ffdb45e4dc7fa04b1fa6aceed379c1ade0577bc8f261d109942ed57369" + "21c052664d72e0d5bade9"}, + {NULL, LONG_PASSWORD, SHORT_DIGEST, "e9c03517f81ff29bb777dac21fb1699c"}, + {NULL, + LONG_PASSWORD, + LONG_DIGEST, + "e9c03517f81ff29bb777dac21fb1699c50968c7ccd8db4f0a59d00ffd87b05876d45f25a927d51a8400c35af60fbd" + "64584349a8b7435d62fd9"}, + {SALT, LONG_PASSWORD, SHORT_DIGEST, "4e5c076e2f85f5e03994acbf3a9e10d6"}, + {SALT, + LONG_PASSWORD, + LONG_DIGEST, + "4e5c076e2f85f5e03994acbf3a9e10d61a6969c9fdf47ae8b1f7e2725b3767b05cc974bfcb5344b630c91761e015e" + "09d7794b5065662533bc9"}, + {NULL, "", SHORT_DIGEST, "d41d8cd98f00b204e9800998ecf8427e"}, + {SALT, "", SHORT_DIGEST, "7df1e0494c977195005d82a1809685e4"}, +}; + +static void +test_md5(void) +{ + char digest[LONG_DIGEST], *hex; + int i; + + for (i = 0; i < G_N_ELEMENTS(md5_tests); i++) { + memset(digest, 0, sizeof(digest)); + nm_crypto_md5_hash((const guint8 *) md5_tests[i].salt, + /* nm_crypto_md5_hash() used to clamp salt_len to 8. It + * doesn't any more, so we need to do it here now to + * get output that matches md5_tests[i].result. + */ + md5_tests[i].salt ? 8 : 0, + (const guint8 *) md5_tests[i].password, + strlen(md5_tests[i].password), + (guint8 *) digest, + md5_tests[i].digest_size); + + hex = nm_utils_bin2hexstr(digest, md5_tests[i].digest_size, -1); + g_assert_cmpstr(hex, ==, md5_tests[i].result); + g_free(hex); + } +} + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + GError * error = NULL; + gboolean success; + int ret; + + nmtst_init(&argc, &argv, TRUE); + + success = _nm_crypto_init(&error); + g_assert_no_error(error); + g_assert(success); + + g_test_add_data_func("/libnm/crypto/cert/pem", "test_ca_cert.pem", test_cert); + g_test_add_data_func("/libnm/crypto/cert/pem-2", "test2_ca_cert.pem", test_cert); + g_test_add_data_func("/libnm/crypto/cert/der", "test_ca_cert.der", test_cert); + g_test_add_data_func("/libnm/crypto/cert/pem-no-ending-newline", + "ca-no-ending-newline.pem", + test_cert); + g_test_add_data_func("/libnm/crypto/cert/pem-combined", "test_key_and_cert.pem", test_cert); + g_test_add_data_func("/libnm/crypto/cert/pem-combined-2", "test2_key_and_cert.pem", test_cert); + + g_test_add_data_func("/libnm/crypto/key/padding-6", + "test_key_and_cert.pem, test, test-key-only-decrypted.der", + test_key); + g_test_add_data_func("/libnm/crypto/key/key-only", + "test-key-only.pem, test, test-key-only-decrypted.der", + test_key); + g_test_add_data_func("/libnm/crypto/key/padding-8", + "test2_key_and_cert.pem, 12345testing", + test_key); + g_test_add_data_func("/libnm/crypto/key/aes-128", + "test-aes-128-key.pem, test-aes-password", + test_key); + g_test_add_data_func("/libnm/crypto/key/aes-256", + "test-aes-256-key.pem, test-aes-password", + test_key); + g_test_add_data_func("/libnm/crypto/key/decrypted", + "test-key-only-decrypted.pem", + test_key_decrypted); + + g_test_add_data_func("/libnm/crypto/PKCS#12/1", "test-cert.p12, test", test_pkcs12); + g_test_add_data_func("/libnm/crypto/PKCS#12/2", "test2-cert.p12, 12345testing", test_pkcs12); + + g_test_add_data_func("/libnm/crypto/PKCS#8", "pkcs8-enc-key.pem, 1234567890", test_pkcs8); + + g_test_add_func("/libnm/crypto/md5", test_md5); + + ret = g_test_run(); + + return ret; +} diff --git a/src/libnm-core-impl/tests/test-general-enums.h b/src/libnm-core-impl/tests/test-general-enums.h new file mode 100644 index 00000000..b3163e23 --- /dev/null +++ b/src/libnm-core-impl/tests/test-general-enums.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#ifndef _NM_TEST_GENERAL_ENUMS_H_ +#define _NM_TEST_GENERAL_ENUMS_H_ + +typedef enum { + NM_TEST_GENERAL_BOOL_ENUM_NO = 0, + NM_TEST_GENERAL_BOOL_ENUM_YES = 1, + NM_TEST_GENERAL_BOOL_ENUM_MAYBE = 2, + NM_TEST_GENERAL_BOOL_ENUM_UNKNOWN = 3, + NM_TEST_GENERAL_BOOL_ENUM_INVALID = 4, /*< skip >*/ + NM_TEST_GENERAL_BOOL_ENUM_67 = 67, + NM_TEST_GENERAL_BOOL_ENUM_46 = 64, +} NMTestGeneralBoolEnum; + +typedef enum { + NM_TEST_GENERAL_META_FLAGS_NONE = 0, + NM_TEST_GENERAL_META_FLAGS_FOO = (1 << 0), + NM_TEST_GENERAL_META_FLAGS_BAR = (1 << 1), + NM_TEST_GENERAL_META_FLAGS_BAZ = (1 << 2), + NM_TEST_GENERAL_META_FLAGS_0x8 = (1 << 3), + NM_TEST_GENERAL_META_FLAGS_0x4 = (1 << 4), +} NMTestGeneralMetaFlags; + +typedef enum { /*< flags >*/ + NM_TEST_GENERAL_COLOR_FLAGS_WHITE = 1, /*< skip >*/ + NM_TEST_GENERAL_COLOR_FLAGS_BLUE = 2, + NM_TEST_GENERAL_COLOR_FLAGS_RED = 4, + NM_TEST_GENERAL_COLOR_FLAGS_GREEN = 8, +} NMTestGeneralColorFlags; + +#endif /* _NM_TEST_GENERAL_ENUMS_H_ */ diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c new file mode 100644 index 00000000..f9cc5e54 --- /dev/null +++ b/src/libnm-core-impl/tests/test-general.c @@ -0,0 +1,10840 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2018 Red Hat, Inc. + */ + +#define NM_GLIB_COMPAT_H_TEST + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include +#include +#include +#include +#include + +#include "libnm-std-aux/c-list-util.h" +#include "libnm-glib-aux/nm-uuid.h" +#include "libnm-glib-aux/nm-enum-utils.h" +#include "libnm-glib-aux/nm-str-buf.h" +#include "libnm-glib-aux/nm-json-aux.h" +#include "libnm-base/nm-base.h" +#include "libnm-systemd-shared/nm-sd-utils-shared.h" + +#include "nm-utils.h" +#include "nm-setting-private.h" +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "libnm-core-intern/nm-core-internal.h" +#include "nm-core-tests-enum-types.h" +#include "nm-team-utils.h" + +#include "nm-setting-8021x.h" +#include "nm-setting-adsl.h" +#include "nm-setting-bluetooth.h" +#include "nm-setting-bond.h" +#include "nm-setting-bridge.h" +#include "nm-setting-bridge-port.h" +#include "nm-setting-cdma.h" +#include "nm-setting-connection.h" +#include "nm-setting-ethtool.h" +#include "nm-setting-generic.h" +#include "nm-setting-gsm.h" +#include "nm-setting-infiniband.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-olpc-mesh.h" +#include "nm-setting-ppp.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-serial.h" +#include "nm-setting-team.h" +#include "nm-setting-team-port.h" +#include "nm-setting-user.h" +#include "nm-setting-vlan.h" +#include "nm-setting-vpn.h" +#include "nm-setting-wimax.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-wpan.h" +#include "nm-simple-connection.h" +#include "libnm-core-intern/nm-keyfile-internal.h" +#include "libnm-glib-aux/nm-dedup-multi.h" +#include "libnm-base/nm-ethtool-base.h" +#include "libnm-base/nm-ethtool-utils-base.h" + +#include "test-general-enums.h" + +#include "libnm-glib-aux/nm-test-utils.h" + +/* When passing a "bool" typed argument to a variadic function that + * expects a gboolean, the compiler will promote the integer type + * to have at least size (int). That way: + * g_object_set (obj, PROP_BOOL, bool_val, NULL); + * will just work correctly. */ +G_STATIC_ASSERT(sizeof(gboolean) == sizeof(int)); +G_STATIC_ASSERT(sizeof(bool) <= sizeof(int)); + +/*****************************************************************************/ + +/* NM_UTILS_HWADDR_LEN_MAX is public API of libnm(-core) and _NM_UTILS_HWADDR_LEN_MAX + * is internal API. They are the same, but the latter can be used without including libnm-core. */ +G_STATIC_ASSERT(NM_UTILS_HWADDR_LEN_MAX == _NM_UTILS_HWADDR_LEN_MAX); + +/*****************************************************************************/ + +static void +test_nm_ascii_spaces(void) +{ + int i; + const char *const S = NM_ASCII_SPACES; + + for (i = 0; S[i]; i++) + g_assert(!strchr(&S[i + 1], S[i])); + + for (i = 0; S[i] != '\0'; i++) + g_assert(g_ascii_isspace(S[i])); + + g_assert(!g_ascii_isspace((char) 0)); + for (i = 1; i < 0x100; i++) { + if (g_ascii_isspace((char) i)) + g_assert(strchr(S, (char) i)); + else + g_assert(!strchr(S, (char) i)); + } +} + +/*****************************************************************************/ + +static void +test_wired_wake_on_lan_enum(void) +{ + nm_auto_unref_gtypeclass GFlagsClass *flags_class = NULL; + gs_unref_hashtable GHashTable *vals = g_hash_table_new(nm_direct_hash, NULL); + guint i; + + G_STATIC_ASSERT_EXPR(sizeof(NMSettingWiredWakeOnLan) == sizeof(_NMSettingWiredWakeOnLan)); + G_STATIC_ASSERT_EXPR(sizeof(NMSettingWiredWakeOnLan) < sizeof(gint64)); + + G_STATIC_ASSERT_EXPR(sizeof(NMSettingWiredWakeOnLan) < sizeof(gint64)); + g_assert((((gint64) ((NMSettingWiredWakeOnLan) -1)) < 0) + == (((gint64) ((_NMSettingWiredWakeOnLan) -1)) < 0)); + +#define _E(n) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT_EXPR(n == (gint64) _##n); \ + G_STATIC_ASSERT_EXPR(_##n == (gint64) n); \ + g_assert(_##n == _NM_SETTING_WIRED_WAKE_ON_LAN_CAST(n)); \ + if (!g_hash_table_add(vals, GUINT_TO_POINTER(n))) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + _E(NM_SETTING_WIRED_WAKE_ON_LAN_NONE); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_PHY); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_ARP); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_ALL); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE); + _E(NM_SETTING_WIRED_WAKE_ON_LAN_EXCLUSIVE_FLAGS); +#undef _E + + flags_class = G_FLAGS_CLASS(g_type_class_ref(NM_TYPE_SETTING_WIRED_WAKE_ON_LAN)); + for (i = 0; i < flags_class->n_values; i++) { + const GFlagsValue *value = &flags_class->values[i]; + + if (!g_hash_table_contains(vals, GUINT_TO_POINTER(value->value))) { + g_error("The enum value %s from NMSettingWiredWakeOnLan is not checked for " + "_NMSettingWiredWakeOnLan", + value->value_name); + } + } +} + +/*****************************************************************************/ + +static void +test_wireless_wake_on_wlan_enum(void) +{ + nm_auto_unref_gtypeclass GFlagsClass *flags_class = NULL; + gs_unref_hashtable GHashTable *vals = g_hash_table_new(nm_direct_hash, NULL); + guint i; + + G_STATIC_ASSERT_EXPR(sizeof(NMSettingWirelessWakeOnWLan) + == sizeof(_NMSettingWirelessWakeOnWLan)); + G_STATIC_ASSERT_EXPR(sizeof(NMSettingWirelessWakeOnWLan) < sizeof(gint64)); + + G_STATIC_ASSERT_EXPR(sizeof(NMSettingWirelessWakeOnWLan) < sizeof(gint64)); + g_assert((((gint64) ((NMSettingWirelessWakeOnWLan) -1)) < 0) + == (((gint64) ((_NMSettingWirelessWakeOnWLan) -1)) < 0)); + +#define _E(n) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT_EXPR(n == (gint64) _##n); \ + G_STATIC_ASSERT_EXPR(_##n == (gint64) n); \ + g_assert(_##n == _NM_SETTING_WIRELESS_WAKE_ON_WLAN_CAST(n)); \ + if (!g_hash_table_add(vals, GUINT_TO_POINTER(n))) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_NONE); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_ALL); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE); + _E(NM_SETTING_WIRELESS_WAKE_ON_WLAN_EXCLUSIVE_FLAGS); +#undef _E + + flags_class = G_FLAGS_CLASS(g_type_class_ref(NM_TYPE_SETTING_WIRELESS_WAKE_ON_WLAN)); + for (i = 0; i < flags_class->n_values; i++) { + const GFlagsValue *value = &flags_class->values[i]; + + if (!g_hash_table_contains(vals, GUINT_TO_POINTER(value->value))) { + g_error("The enum value %s from NMSettingWirelessWakeOnWLan is not checked for " + "_NMSettingWirelessWakeOnWLan", + value->value_name); + } + } +} + +/*****************************************************************************/ + +static void +test_device_wifi_capabilities(void) +{ + nm_auto_unref_gtypeclass GFlagsClass *flags_class = NULL; + gs_unref_hashtable GHashTable *vals = g_hash_table_new(nm_direct_hash, NULL); + guint i; + + G_STATIC_ASSERT_EXPR(sizeof(NMDeviceWifiCapabilities) == sizeof(_NMDeviceWifiCapabilities)); + G_STATIC_ASSERT_EXPR(sizeof(NMDeviceWifiCapabilities) < sizeof(gint64)); + + G_STATIC_ASSERT_EXPR(sizeof(NMDeviceWifiCapabilities) < sizeof(gint64)); + g_assert((((gint64) ((NMDeviceWifiCapabilities) -1)) < 0) + == (((gint64) ((_NMDeviceWifiCapabilities) -1)) < 0)); + +#define _E(n) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT_EXPR(n == (gint64) _##n); \ + G_STATIC_ASSERT_EXPR(_##n == (gint64) n); \ + if (!g_hash_table_add(vals, GUINT_TO_POINTER(n))) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + _E(NM_WIFI_DEVICE_CAP_NONE); + _E(NM_WIFI_DEVICE_CAP_CIPHER_WEP40); + _E(NM_WIFI_DEVICE_CAP_CIPHER_WEP104); + _E(NM_WIFI_DEVICE_CAP_CIPHER_TKIP); + _E(NM_WIFI_DEVICE_CAP_CIPHER_CCMP); + _E(NM_WIFI_DEVICE_CAP_WPA); + _E(NM_WIFI_DEVICE_CAP_RSN); + _E(NM_WIFI_DEVICE_CAP_AP); + _E(NM_WIFI_DEVICE_CAP_ADHOC); + _E(NM_WIFI_DEVICE_CAP_FREQ_VALID); + _E(NM_WIFI_DEVICE_CAP_FREQ_2GHZ); + _E(NM_WIFI_DEVICE_CAP_FREQ_5GHZ); + _E(NM_WIFI_DEVICE_CAP_MESH); + _E(NM_WIFI_DEVICE_CAP_IBSS_RSN); +#undef _E + + flags_class = G_FLAGS_CLASS(g_type_class_ref(NM_TYPE_DEVICE_WIFI_CAPABILITIES)); + for (i = 0; i < flags_class->n_values; i++) { + const GFlagsValue *value = &flags_class->values[i]; + + if (!g_hash_table_contains(vals, GUINT_TO_POINTER(value->value))) { + g_error("The enum value %s from NMDeviceWifiCapabilities is not checked for " + "_NMDeviceWifiCapabilities", + value->value_name); + } + } +} + +/*****************************************************************************/ + +static void +test_80211_mode(void) +{ + nm_auto_unref_gtypeclass GEnumClass *enum_class = NULL; + gs_unref_hashtable GHashTable *vals = g_hash_table_new(nm_direct_hash, NULL); + guint i; + + G_STATIC_ASSERT_EXPR(sizeof(NM80211Mode) == sizeof(_NM80211Mode)); + G_STATIC_ASSERT_EXPR(sizeof(NM80211Mode) < sizeof(gint64)); + + G_STATIC_ASSERT_EXPR(sizeof(NM80211Mode) < sizeof(gint64)); + g_assert((((gint64) ((NM80211Mode) -1)) < 0) == (((gint64) ((_NM80211Mode) -1)) < 0)); + +#define _E(n) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT_EXPR(n == (gint64) _##n); \ + G_STATIC_ASSERT_EXPR(_##n == (gint64) n); \ + g_assert(n == NM_802_11_MODE_CAST(_##n)); \ + if (!g_hash_table_add(vals, GINT_TO_POINTER(n))) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + _E(NM_802_11_MODE_UNKNOWN); + _E(NM_802_11_MODE_ADHOC); + _E(NM_802_11_MODE_INFRA); + _E(NM_802_11_MODE_AP); + _E(NM_802_11_MODE_MESH); +#undef _E + + enum_class = G_ENUM_CLASS(g_type_class_ref(NM_TYPE_802_11_MODE)); + for (i = 0; i < enum_class->n_values; i++) { + const GEnumValue *value = &enum_class->values[i]; + + if (!g_hash_table_contains(vals, GINT_TO_POINTER(value->value))) { + g_error("The enum value %s from NM80211Mode is not checked for " + "_NM80211Mode", + value->value_name); + } + } +} + +/*****************************************************************************/ + +static void +test_vlan_flags(void) +{ + nm_auto_unref_gtypeclass GFlagsClass *flags_class = NULL; + gs_unref_hashtable GHashTable *vals = g_hash_table_new(nm_direct_hash, NULL); + guint i; + + G_STATIC_ASSERT_EXPR(sizeof(NMVlanFlags) == sizeof(_NMVlanFlags)); + G_STATIC_ASSERT_EXPR(sizeof(NMVlanFlags) < sizeof(gint64)); + + G_STATIC_ASSERT_EXPR(sizeof(NMVlanFlags) < sizeof(gint64)); + g_assert((((gint64) ((NMVlanFlags) -1)) < 0) == (((gint64) ((_NMVlanFlags) -1)) < 0)); + +#define _E(n) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT_EXPR(n == (gint64) _##n); \ + G_STATIC_ASSERT_EXPR(_##n == (gint64) n); \ + g_assert(n == NM_VLAN_FLAGS_CAST(_##n)); \ + if (!g_hash_table_add(vals, GUINT_TO_POINTER(n))) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + _E(NM_VLAN_FLAG_REORDER_HEADERS); + _E(NM_VLAN_FLAG_GVRP); + _E(NM_VLAN_FLAG_LOOSE_BINDING); + _E(NM_VLAN_FLAG_MVRP); + _E(NM_VLAN_FLAGS_ALL); +#undef _E + + flags_class = G_FLAGS_CLASS(g_type_class_ref(NM_TYPE_VLAN_FLAGS)); + for (i = 0; i < flags_class->n_values; i++) { + const GFlagsValue *value = &flags_class->values[i]; + + if (!g_hash_table_contains(vals, GUINT_TO_POINTER(value->value))) { + g_error("The enum value %s from NMVlanFlags is not checked for " + "_NMVlanFlags", + value->value_name); + } + } +} + +/*****************************************************************************/ + +typedef struct _nm_packed { + int v0; + char v1; + double v2; + guint8 v3; +} TestHashStruct; + +static void +_test_hash_struct(int v0, char v1, double v2, guint8 v3) +{ + const TestHashStruct s = { + .v0 = v0, + .v1 = v1, + .v2 = v2, + .v3 = v3, + }; + NMHashState h; + guint hh; + + nm_hash_init(&h, 100); + nm_hash_update(&h, &s, sizeof(s)); + hh = nm_hash_complete(&h); + + nm_hash_init(&h, 100); + nm_hash_update_val(&h, v0); + nm_hash_update_val(&h, v1); + nm_hash_update_val(&h, v2); + nm_hash_update_val(&h, v3); + g_assert_cmpint(hh, ==, nm_hash_complete(&h)); + + nm_hash_init(&h, 100); + nm_hash_update_vals(&h, v0, v1, v2, v3); + g_assert_cmpint(hh, ==, nm_hash_complete(&h)); +} + +static guint +_test_hash_str(const char *str) +{ + NMHashState h; + guint v, v2; + const guint SEED = 10; + + nm_hash_init(&h, SEED); + nm_hash_update_str0(&h, str); + v = nm_hash_complete(&h); + + /* assert that hashing a string and a buffer yields the + * same result. + * + * I think that is a desirable property. */ + nm_hash_init(&h, SEED); + nm_hash_update_mem(&h, str, strlen(str)); + v2 = nm_hash_complete(&h); + + g_assert(v == v2); + return v; +} + +#define _test_hash_vals(type, ...) \ + G_STMT_START \ + { \ + NMHashState h0, h1, h2, h3; \ + const type v[] = {__VA_ARGS__}; \ + guint h; \ + guint i; \ + \ + nm_hash_init(&h0, 10); \ + nm_hash_init(&h1, 10); \ + nm_hash_init(&h2, 10); \ + nm_hash_init(&h3, 10); \ + \ + /* assert that it doesn't matter, whether we hash the values individually, + * or all at once, or via the convenience macros nm_hash_update_val() + * and nm_hash_update_vals(). */ \ + for (i = 0; i < G_N_ELEMENTS(v); i++) { \ + nm_hash_update(&h0, &v[i], sizeof(type)); \ + nm_hash_update_val(&h1, v[i]); \ + } \ + nm_hash_update_vals(&h2, __VA_ARGS__); \ + nm_hash_update(&h3, v, sizeof(v)); \ + \ + h = nm_hash_complete(&h0); \ + g_assert_cmpint(h, ==, nm_hash_complete(&h1)); \ + g_assert_cmpint(h, ==, nm_hash_complete(&h2)); \ + g_assert_cmpint(h, ==, nm_hash_complete(&h3)); \ + } \ + G_STMT_END + +static void +test_nm_hash(void) +{ + g_assert(nm_hash_static(0)); + g_assert(nm_hash_static(777)); + + g_assert(nm_hash_str(NULL)); + g_assert(nm_hash_str("")); + g_assert(nm_hash_str("a")); + + g_assert(nm_hash_ptr(NULL)); + g_assert(nm_hash_ptr("")); + g_assert(nm_hash_ptr("a")); + + _test_hash_str(""); + _test_hash_str("a"); + _test_hash_str("aa"); + _test_hash_str("diceros bicornis longipes"); + + /* assert that nm_hash_update_vals() is the same as calling nm_hash_update_val() multiple times. */ + _test_hash_vals(int, 1); + _test_hash_vals(int, 1, 2); + _test_hash_vals(int, 1, 2, 3); + _test_hash_vals(int, 1, 2, 3, 4); + _test_hash_vals(long, 1l); + _test_hash_vals(long, 1l, 2l, 3l, 4l, 5l); + + _test_hash_struct(10, 'a', 5.4, 7); + _test_hash_struct(-10, '\0', -5.4e49, 255); + + g_assert_cmpint(NM_HASH_COMBINE_BOOLS(guint8, 1, 0), ==, 0x002); + g_assert_cmpint(NM_HASH_COMBINE_BOOLS(guint8, 1, 1), ==, 0x003); + g_assert_cmpint(NM_HASH_COMBINE_BOOLS(guint8, 1, 1, 0, 0, 0, 0), ==, 0x030); + g_assert_cmpint(NM_HASH_COMBINE_BOOLS(guint8, 1, 1, 0, 0, 0, 1), ==, 0x031); + g_assert_cmpint(NM_HASH_COMBINE_BOOLS(guint8, 0, 0, 1, 1, 0, 0, 0, 1), ==, 0x031); + g_assert_cmpint(NM_HASH_COMBINE_BOOLS(guint16, 0, 0, 1, 1, 0, 0, 0, 1), ==, 0x031); + g_assert_cmpint(NM_HASH_COMBINE_BOOLS(guint16, 0, 0, 0, 1, 1, 0, 0, 0, 1), ==, 0x031); + g_assert_cmpint(NM_HASH_COMBINE_BOOLS(guint16, 1, 0, 0, 1, 1, 0, 0, 0, 1), ==, 0x131); +} + +/*****************************************************************************/ + +static void +test_nm_g_slice_free_fcn(void) +{ + gpointer p; + struct { + char a1; + char a2; + } xx; + + p = g_slice_new(gint64); + (nm_g_slice_free_fcn(gint64))(p); + + p = g_slice_new(gint32); + (nm_g_slice_free_fcn(gint32))(p); + + p = g_slice_new(int); + (nm_g_slice_free_fcn(int))(p); + + p = g_slice_new(gint64); + nm_g_slice_free_fcn_gint64(p); + + p = g_slice_alloc(sizeof(xx)); + (nm_g_slice_free_fcn(xx))(p); +} + +/*****************************************************************************/ + +static void +_do_test_nm_utils_strsplit_set_f_one(NMUtilsStrsplitSetFlags flags, + const char * str, + gsize words_len, + const char *const * exp_words) +{ +#define DELIMITERS " \n" +#define DELIMITERS_C ' ', '\n' + + gs_free const char **words = NULL; + gsize i, j, k; + const gboolean f_allow_escaping = + NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING); + const gboolean f_preserve_empty = + NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY); + const char * s1; + gsize initial_offset; + gs_strfreev char **words_g = NULL; + + g_assert(!NM_FLAGS_ANY(flags, + ~(NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING + | NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY))); + + /* assert that the expected words are valid (and don't contain unescaped delimiters). */ + for (i = 0; i < words_len; i++) { + const char *w = exp_words[i]; + + g_assert(w); + if (!f_preserve_empty) + g_assert(w[0]); + for (k = 0; w[k];) { + if (f_allow_escaping && w[k] == '\\') { + k++; + if (w[k] == '\0') + break; + k++; + continue; + } + g_assert(!NM_IN_SET(w[k], DELIMITERS_C)); + k++; + } + if (!f_allow_escaping) + g_assert(!NM_STRCHAR_ANY(w, ch, NM_IN_SET(ch, DELIMITERS_C))); + } + + initial_offset = (f_preserve_empty || !str) ? 0u : strspn(str, DELIMITERS); + + /* first compare our expected values with what g_strsplit_set() would + * do. */ + words_g = str ? g_strsplit_set(str, DELIMITERS, -1) : NULL; + if (str == NULL) { + g_assert_cmpint(words_len, ==, 0); + g_assert(!words_g); + } else if (nm_streq0(str, "")) { + g_assert_cmpint(words_len, ==, 0); + g_assert(words_g); + g_assert(!words_g[0]); + } else { + g_assert(words_g); + g_assert(words_g[0]); + if (!f_allow_escaping) { + if (!f_preserve_empty) { + for (i = 0, j = 0; words_g[i]; i++) { + if (words_g[i][0] == '\0') + g_free(words_g[i]); + else + words_g[j++] = words_g[i]; + } + words_g[j] = NULL; + } + if (f_preserve_empty) + g_assert_cmpint(words_len, >, 0); + for (i = 0; i < words_len; i++) { + g_assert(exp_words[i]); + g_assert_cmpstr(exp_words[i], ==, words_g[i]); + } + g_assert(words_g[words_len] == NULL); + g_assert_cmpint(NM_PTRARRAY_LEN(words_g), ==, words_len); + g_assert(nm_utils_strv_cmp_n(exp_words, words_len, words_g, -1) == 0); + } + } + + if (flags == NM_UTILS_STRSPLIT_SET_FLAGS_NONE && nmtst_get_rand_bool()) + words = nm_utils_strsplit_set(str, DELIMITERS); + else if (flags == NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY && nmtst_get_rand_bool()) + words = nm_utils_strsplit_set_with_empty(str, DELIMITERS); + else + words = nm_utils_strsplit_set_full(str, DELIMITERS, flags); + + g_assert_cmpint(NM_PTRARRAY_LEN(words), ==, words_len); + + if (words_len == 0) { + g_assert(!words); + g_assert(!str || NM_STRCHAR_ALL(str, ch, NM_IN_SET(ch, DELIMITERS_C))); + return; + } + + g_assert(words); + for (i = 0; i < words_len; i++) + g_assert_cmpstr(exp_words[i], ==, words[i]); + g_assert(words[words_len] == NULL); + + g_assert(nm_utils_strv_cmp_n(exp_words, words_len, words, -1) == 0); + + s1 = words[0]; + g_assert(s1 >= (char *) &words[words_len + 1]); + s1 = &s1[strlen(str)]; + for (i = 1; i < words_len; i++) { + g_assert(&(words[i - 1])[strlen(words[i - 1])] < words[i]); + g_assert(words[i] <= s1); + } + + /* while strsplit removes all delimiters, we can relatively easily find them + * in the original string. Assert that the original string and the pointer offsets + * of words correspond. In particular, find idx_delim_after and idx_delim_before + * to determine which delimiter was after/before a word. */ + { + gsize idx_word_start; + gsize idx_delim_after_old = G_MAXSIZE; + + idx_word_start = initial_offset; + for (i = 0; i < words_len; i++) { + const gsize l_i = strlen(words[i]); + gsize idx_delim_after; + gsize idx_delim_before; + + /* find the delimiter *after* words[i]. We can do that by looking at the next + * word and calculating the pointer difference. + * + * The delimiter after the very last word is '\0' and requires strlen() to find. */ + idx_delim_after = initial_offset + ((words[i] - words[0]) + l_i); + if (idx_delim_after != idx_word_start + l_i) { + g_assert(!f_preserve_empty); + g_assert_cmpint(idx_word_start + l_i, <, idx_delim_after); + idx_word_start = idx_delim_after - l_i; + } + if (i + 1 < words_len) { + gsize x = initial_offset + ((words[i + 1] - words[0]) - 1); + + if (idx_delim_after != x) { + g_assert(!f_preserve_empty); + g_assert_cmpint(idx_delim_after, <, x); + for (k = idx_delim_after; k <= x; k++) + g_assert(NM_IN_SET(str[k], DELIMITERS_C)); + } + g_assert(NM_IN_SET(str[idx_delim_after], DELIMITERS_C)); + } else { + if (f_preserve_empty) + g_assert(NM_IN_SET(str[idx_delim_after], '\0')); + else + g_assert(NM_IN_SET(str[idx_delim_after], '\0', DELIMITERS_C)); + } + + /* find the delimiter *before* words[i]. */ + if (i == 0) { + /* there is only a delimiter *before*, with !f_preserve_empty and leading + * delimiters. */ + idx_delim_before = G_MAXSIZE; + if (initial_offset > 0) { + g_assert(!f_preserve_empty); + idx_delim_before = initial_offset - 1; + } + } else + idx_delim_before = initial_offset + (words[i] - words[0]) - 1; + if (idx_delim_before != G_MAXSIZE) + g_assert(NM_IN_SET(str[idx_delim_before], DELIMITERS_C)); + if (idx_delim_after_old != idx_delim_before) { + g_assert(!f_preserve_empty); + if (i == 0) { + g_assert_cmpint(initial_offset, >, 0); + g_assert_cmpint(idx_delim_before, !=, G_MAXSIZE); + g_assert_cmpint(idx_delim_before, ==, initial_offset - 1); + } else { + g_assert_cmpint(idx_delim_after_old, !=, G_MAXSIZE); + g_assert_cmpint(idx_delim_before, !=, G_MAXSIZE); + g_assert_cmpint(idx_delim_after_old, <, idx_delim_before); + for (k = idx_delim_after_old; k <= idx_delim_before; k++) + g_assert(NM_IN_SET(str[k], DELIMITERS_C)); + } + } + + for (k = 0; k < l_i;) { + if (f_allow_escaping && str[idx_word_start + k] == '\\') { + k++; + if (k >= l_i) + break; + k++; + continue; + } + g_assert(!NM_IN_SET(str[idx_word_start + k], DELIMITERS_C)); + k++; + } + g_assert(strncmp(words[i], &str[idx_word_start], l_i) == 0); + + if (i > 0) { + const char *s = &(words[i - 1])[strlen(words[i - 1]) + 1]; + + if (s != words[i]) { + g_assert(!f_preserve_empty); + g_assert(s < words[i]); + } + } + + idx_word_start += l_i + 1; + idx_delim_after_old = idx_delim_after; + } + } +} + +static void +_do_test_nm_utils_strsplit_set_f(NMUtilsStrsplitSetFlags flags, + const char * str, + gsize words_len, + const char *const * exp_words) +{ + _do_test_nm_utils_strsplit_set_f_one(flags, str, words_len, exp_words); + + if (NM_FLAGS_HAS(flags, NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY)) { + gs_unref_ptrarray GPtrArray *exp_words2 = NULL; + gsize k; + + exp_words2 = g_ptr_array_new(); + for (k = 0; k < words_len; k++) { + if (exp_words[k][0] != '\0') + g_ptr_array_add(exp_words2, (gpointer) exp_words[k]); + } + + _do_test_nm_utils_strsplit_set_f_one(flags & (~NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY), + str, + exp_words2->len, + (const char *const *) exp_words2->pdata); + } +} + +#define do_test_nm_utils_strsplit_set_f(flags, str, ...) \ + _do_test_nm_utils_strsplit_set_f(flags, str, NM_NARG(__VA_ARGS__), NM_MAKE_STRV(__VA_ARGS__)) + +#define do_test_nm_utils_strsplit_set(allow_escaping, str, ...) \ + do_test_nm_utils_strsplit_set_f((allow_escaping) ? NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING \ + : NM_UTILS_STRSPLIT_SET_FLAGS_NONE, \ + str, \ + ##__VA_ARGS__) + +static void +_do_test_nm_utils_strsplit_set_simple(NMUtilsStrsplitSetFlags flags, + const char * str, + gsize words_len, + const char *const * exp_words) +{ + gs_free const char **tokens = NULL; + gsize n_tokens; + + tokens = nm_utils_strsplit_set_full(str, DELIMITERS, flags); + + if (!tokens) { + g_assert_cmpint(words_len, ==, 0); + return; + } + + g_assert(str && str[0]); + g_assert_cmpint(words_len, >, 0); + n_tokens = NM_PTRARRAY_LEN(tokens); + + if (nm_utils_strv_cmp_n(exp_words, words_len, tokens, -1) != 0) { + gsize i; + + g_print(">>> split \"%s\" (flags %x) got %zu tokens (%zu expected)\n", + str, + (guint) flags, + n_tokens, + words_len); + for (i = 0; i < NM_MAX(n_tokens, words_len); i++) { + const char *s1 = i < n_tokens ? tokens[i] : NULL; + const char *s2 = i < words_len ? exp_words[i] : NULL; + + g_print(">>> [%zu]: %s - %s%s%s vs. %s%s%s\n", + i, + nm_streq0(s1, s2) ? "same" : "diff", + NM_PRINT_FMT_QUOTE_STRING(s1), + NM_PRINT_FMT_QUOTE_STRING(s2)); + } + g_assert_not_reached(); + } + g_assert_cmpint(words_len, ==, NM_PTRARRAY_LEN(tokens)); +} +#define do_test_nm_utils_strsplit_set_simple(flags, str, ...) \ + _do_test_nm_utils_strsplit_set_simple((flags), \ + (str), \ + NM_NARG(__VA_ARGS__), \ + NM_MAKE_STRV(__VA_ARGS__)) + +static void +test_nm_utils_strsplit_set(void) +{ + gs_unref_ptrarray GPtrArray *words_exp = NULL; + guint test_run; + + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_NONE, NULL); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_NONE, ""); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_NONE, " "); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_NONE, "a b", "a", "b"); + + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, NULL); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, ""); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, " ", "", ""); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, " ", "", "", ""); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, "a ", "a", "", ""); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + "a b", + "a", + "", + "b"); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + " ab b", + "", + "ab", + "", + "b"); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + "ab b", + "ab", + "", + "b"); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, "abb", "abb"); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + "abb bb ", + "abb", + "", + "bb", + ""); + do_test_nm_utils_strsplit_set_f(NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + "abb bcb ", + "abb", + "bcb", + ""); + + do_test_nm_utils_strsplit_set(FALSE, NULL); + do_test_nm_utils_strsplit_set(FALSE, ""); + do_test_nm_utils_strsplit_set(FALSE, "\n"); + do_test_nm_utils_strsplit_set(TRUE, " \t\n", "\t"); + do_test_nm_utils_strsplit_set(FALSE, "a", "a"); + do_test_nm_utils_strsplit_set(FALSE, "a b", "a", "b"); + do_test_nm_utils_strsplit_set(FALSE, "a\rb", "a\rb"); + do_test_nm_utils_strsplit_set(FALSE, " a\rb ", "a\rb"); + do_test_nm_utils_strsplit_set(FALSE, " a bbbd afds ere", "a", "bbbd", "afds", "ere"); + do_test_nm_utils_strsplit_set(FALSE, + "1 2 3 4 5 6 7 8 9 0 " + "1 2 3 4 5 6 7 8 9 0 " + "1 2 3 4 5 6 7 8 9 0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0"); + do_test_nm_utils_strsplit_set(TRUE, "\\", "\\"); + do_test_nm_utils_strsplit_set(TRUE, "\\ ", "\\ "); + do_test_nm_utils_strsplit_set(TRUE, "\\\\", "\\\\"); + do_test_nm_utils_strsplit_set(TRUE, "\\\t", "\\\t"); + do_test_nm_utils_strsplit_set(TRUE, "foo\\", "foo\\"); + do_test_nm_utils_strsplit_set(TRUE, "bar foo\\", "bar", "foo\\"); + do_test_nm_utils_strsplit_set(TRUE, "\\ a b\\ \\ c", "\\ a", "b\\ \\ ", "c"); + + words_exp = g_ptr_array_new_with_free_func(g_free); + for (test_run = 0; test_run < 100; test_run++) { + gboolean f_allow_escaping = nmtst_get_rand_bool(); + guint words_len = nmtst_get_rand_uint32() % 100; + gs_free char *str = NULL; + guint i; + + g_ptr_array_set_size(words_exp, 0); + for (i = 0; i < words_len; i++) { + guint word_len; + char *word; + guint j; + + word_len = nmtst_get_rand_uint32(); + if ((word_len % 100) < 30) + word_len = 0; + else + word_len = (word_len >> 10) % 100; + word = g_new(char, word_len + 3); + for (j = 0; j < word_len;) { + guint32 p = nmtst_get_rand_uint32(); + static const char delimiters_arr[] = {DELIMITERS_C}; + static const char regular_chars[] = "abcdefghijklmnopqrstuvwxyz"; + + if (!f_allow_escaping || (p % 1000) < 700) { + if (((p >> 20) % 100) < 20) + word[j++] = '\\'; + word[j++] = regular_chars[(p >> 11) % (G_N_ELEMENTS(regular_chars) - 1)]; + continue; + } + word[j++] = '\\'; + word[j++] = delimiters_arr[(p >> 11) % G_N_ELEMENTS(delimiters_arr)]; + } + word[j] = '\0'; + g_ptr_array_add(words_exp, word); + } + g_ptr_array_add(words_exp, NULL); + + str = g_strjoinv(" ", (char **) words_exp->pdata); + + if (str[0] == '\0' && words_len > 0) { + g_assert(words_len == 1); + g_assert_cmpstr(words_exp->pdata[0], ==, ""); + words_len = 0; + } + + _do_test_nm_utils_strsplit_set_f((f_allow_escaping + ? NM_UTILS_STRSPLIT_SET_FLAGS_ALLOW_ESCAPING + : NM_UTILS_STRSPLIT_SET_FLAGS_NONE) + | NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + str, + words_len, + (const char *const *) words_exp->pdata); + } + + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, "\t", "\t"); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED + | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, + "\t"); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED + | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP + | NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + "\t", + ""); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED + | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP + | NM_UTILS_STRSPLIT_SET_FLAGS_PRESERVE_EMPTY, + "\t\\\t\t\t\\\t", + "\t\t\t\t"); + + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, "\ta", "\ta"); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED + | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, + "\ta", + "a"); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, + "\ta\\ b\t\\ ", + "\ta b\t "); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED + | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, + "\ta\\ b\t\\ \t", + "a b\t "); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, "a\\ b", "a ", "b"); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED, + "\ta\\ b", + "\ta ", + "b"); + do_test_nm_utils_strsplit_set_simple(NM_UTILS_STRSPLIT_SET_FLAGS_ESCAPED + | NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP, + "\ta\\ b", + "a ", + "b"); +} + +/*****************************************************************************/ + +static char * +_escaped_tokens_create_random_word_full(const char *const *tokens, gsize n_tokens, gsize len) +{ + GString *gstr = g_string_new(NULL); + gsize i; + char random_token[2] = {0}; + + for (i = 0; i < len; i++) { + const char *token = tokens[nmtst_get_rand_uint32() % n_tokens]; + + if (!token[0]) { + do { + random_token[0] = nmtst_get_rand_uint32(); + } while (random_token[0] == '\0'); + token = random_token; + } + g_string_append(gstr, token); + } + + /* reallocate the string, so that we don't have any excess memory from + * the GString buffer. This is so that valgrind may better detect an out + * or range access. */ + return nm_str_realloc(g_string_free(gstr, FALSE)); +} + +/* set to 1 to exclude characters that are annoying to see in the debugger + * and printf() output. */ +#define ESCAPED_TOKENS_ONLY_NICE_CHARS 0 + +static char * +_escaped_tokens_create_random_whitespace(void) +{ + static const char *tokens[] = { + " ", +#if !ESCAPED_TOKENS_ONLY_NICE_CHARS + "\n", + "\t", + "\r", + "\f", +#endif + }; + + return _escaped_tokens_create_random_word_full(tokens, + G_N_ELEMENTS(tokens), + nmtst_get_rand_word_length(NULL) / 4u); +} + +static char * +_escaped_tokens_create_random_word(void) +{ + static const char *tokens[] = { + "a", + "b", + "c", + " ", + ",", + "=", + "\\", +#if !ESCAPED_TOKENS_ONLY_NICE_CHARS + "\n", + "\f", + ":", + "", +#endif + }; + + return _escaped_tokens_create_random_word_full(tokens, + G_N_ELEMENTS(tokens), + nmtst_get_rand_word_length(NULL)); +} + +static void +_escaped_tokens_str_append_delimiter(GString *str, gboolean strict, gboolean needs_delimiter) +{ + guint len = nmtst_get_rand_word_length(NULL) / 10u; + char *s; + +again: + if (!strict) { + g_string_append(str, (s = _escaped_tokens_create_random_whitespace())); + nm_clear_g_free(&s); + } + + if (needs_delimiter) + g_string_append_c(str, ','); + + if (!strict) { + g_string_append(str, (s = _escaped_tokens_create_random_whitespace())); + nm_clear_g_free(&s); + if (len-- > 0) { + needs_delimiter = TRUE; + goto again; + } + } +} + +static void +_escaped_tokens_split(char *str, const char **out_key, const char **out_val) +{ + const char *key; + const char *val; + gsize len = strlen(str); + + g_assert(str); + + nm_utils_escaped_tokens_options_split(str, &key, &val); + g_assert(key); + g_assert(key == str); + if (val) { + g_assert(val > str); + g_assert(val > key); + g_assert(val <= &str[len]); + } + NM_SET_OUT(out_key, key); + NM_SET_OUT(out_val, val); +} + +static void +_escaped_tokens_combine(GString * combined, + const char *key, + const char *val, + gboolean strict, + gboolean allow_append_delimiter_before, + gboolean needs_delimiter_after) +{ + gs_free char *escaped_key = NULL; + gs_free char *escaped_val = NULL; + + if (allow_append_delimiter_before) + _escaped_tokens_str_append_delimiter(combined, strict, FALSE); + g_string_append(combined, nm_utils_escaped_tokens_options_escape_key(key, &escaped_key)); + if (val) { + char *s; + + if (!strict) { + g_string_append(combined, (s = _escaped_tokens_create_random_whitespace())); + nm_clear_g_free(&s); + } + g_string_append_c(combined, '='); + if (!strict) { + g_string_append(combined, (s = _escaped_tokens_create_random_whitespace())); + nm_clear_g_free(&s); + } + g_string_append(combined, nm_utils_escaped_tokens_options_escape_val(val, &escaped_val)); + } + _escaped_tokens_str_append_delimiter(combined, strict, needs_delimiter_after); +} + +static void +_escaped_tokens_check_one_impl(const char * expected_key, + const char * expected_val, + const char * expected_combination, + const char *const *other, + gsize n_other) +{ + nm_auto_free_gstring GString *combined = g_string_new(NULL); + gsize i; + + g_assert(expected_key); + g_assert(expected_combination); + g_assert(other); + + _escaped_tokens_combine(combined, expected_key, expected_val, TRUE, TRUE, FALSE); + + g_assert_cmpstr(combined->str, ==, expected_combination); + + for (i = 0; i < n_other + 2u; i++) { + nm_auto_free_gstring GString *str0 = NULL; + gs_free const char ** strv_split = NULL; + gs_free char * strv_split0 = NULL; + const char * comb; + const char * key; + const char * val; + + if (i == 0) + comb = expected_combination; + else if (i == 1) { + _escaped_tokens_combine(nm_gstring_prepare(&str0), + expected_key, + expected_val, + FALSE, + TRUE, + FALSE); + comb = str0->str; + } else + comb = other[i - 2]; + + strv_split = nm_utils_escaped_tokens_options_split_list(comb); + if (!strv_split) { + g_assert_cmpstr(expected_key, ==, ""); + g_assert_cmpstr(expected_val, ==, NULL); + continue; + } + g_assert(expected_val || expected_key[0]); + + g_assert_cmpuint(NM_PTRARRAY_LEN(strv_split), ==, 1u); + + strv_split0 = g_strdup(strv_split[0]); + + _escaped_tokens_split(strv_split0, &key, &val); + g_assert_cmpstr(key, ==, expected_key); + g_assert_cmpstr(val, ==, expected_val); + } +} + +#define _escaped_tokens_check_one(expected_key, expected_val, expected_combination, ...) \ + _escaped_tokens_check_one_impl(expected_key, \ + expected_val, \ + expected_combination, \ + NM_MAKE_STRV(__VA_ARGS__), \ + NM_NARG(__VA_ARGS__)) + +static void +test_nm_utils_escaped_tokens(void) +{ + int i_run; + + for (i_run = 0; i_run < 1000; i_run++) { + const guint num_options = nmtst_get_rand_word_length(NULL); + gs_unref_ptrarray GPtrArray *options = g_ptr_array_new_with_free_func(g_free); + nm_auto_free_gstring GString *combined = g_string_new(NULL); + gs_free const char ** strv_split = NULL; + guint i_option; + guint i; + + /* Generate a list of random words for option key-value pairs. */ + for (i_option = 0; i_option < 2u * num_options; i_option++) { + char *word = NULL; + + if (i_option % 2u == 1 && nmtst_get_rand_uint32() % 5 == 0 + && strlen(options->pdata[options->len - 1]) > 0u) { + /* For some options, leave the value unset and only generate a key. + * + * If key is "", then we cannot do that, because the test below would try + * to append "" to the combined list, which the parser then would drop. + * Only test omitting the value, if strlen() of the key is positive. */ + } else + word = _escaped_tokens_create_random_word(); + g_ptr_array_add(options, word); + } + + /* Combine the options in one comma separated list, with proper escaping. */ + for (i_option = 0; i_option < num_options; i_option++) { + _escaped_tokens_combine(combined, + options->pdata[2u * i_option + 0u], + options->pdata[2u * i_option + 1u], + FALSE, + i_option == 0, + i_option != num_options - 1); + } + + /* ensure that we can split and parse the options without difference. */ + strv_split = nm_utils_escaped_tokens_options_split_list(combined->str); + for (i_option = 0; i_option < num_options; i_option++) { + const char * expected_key = options->pdata[2u * i_option + 0u]; + const char * expected_val = options->pdata[2u * i_option + 1u]; + gs_free char *s_split = + i_option < NM_PTRARRAY_LEN(strv_split) ? g_strdup(strv_split[i_option]) : NULL; + const char *key = NULL; + const char *val = NULL; + + if (s_split) + _escaped_tokens_split(s_split, &key, &val); + + if (!nm_streq0(key, expected_key) || !nm_streq0(val, expected_val)) { + g_print(">>> ASSERTION IS ABOUT TO FAIL for item %5d of %5d\n", + i_option, + num_options); + g_print(">>> combined = \"%s\"\n", combined->str); + g_print(">>> %c parsed[%5d].key = \"%s\"\n", + nm_streq0(key, expected_key) ? ' ' : 'X', + i_option, + key); + g_print(">>> %c parsed[%5d].val = %s%s%s\n", + nm_streq0(val, expected_val) ? ' ' : 'X', + i_option, + NM_PRINT_FMT_QUOTE_STRING(val)); + for (i = 0; i < num_options; i++) { + g_print(">>> %c original[%5d].key = \"%s\"\n", + i == i_option ? '*' : ' ', + i, + (char *) options->pdata[2u * i + 0u]); + g_print(">>> %c original[%5d].val = %s%s%s\n", + i == i_option ? '*' : ' ', + i, + NM_PRINT_FMT_QUOTE_STRING((char *) options->pdata[2u * i + 1u])); + } + for (i = 0; i < NM_PTRARRAY_LEN(strv_split); i++) + g_print(">>> split[%5d] = \"%s\"\n", i, strv_split[i]); + } + + g_assert_cmpstr(key, ==, expected_key); + g_assert_cmpstr(val, ==, expected_val); + } + g_assert_cmpint(NM_PTRARRAY_LEN(strv_split), ==, num_options); + + /* Above we show a full round-trip of random option key-value pairs, that they can + * without loss escape, concatenate, split-list, and split. This proofed that every + * option key-value pair can be represented as a combined string and parsed back. + * + * Now, just check that we can also parse arbitrary random words in nm_utils_escaped_tokens_options_split(). + * split() is a non-injective surjective function. As we check the round-trip above for random words, where + * options-split() is the last step, we show that every random word can be the output of the function + * (which shows, the surjective part). + * + * But multiple random input arguments, may map to the same output argument (non-injective). + * Just test whether we can handle random input words without crashing. For that, just use the + * above generate list of random words. + */ + for (i = 0; i < 1u + 2u * i_option; i++) { + gs_free char *str = NULL; + const char * cstr; + + if (i == 0) + cstr = combined->str; + else + cstr = options->pdata[i - 1u]; + if (!cstr) + continue; + + str = g_strdup(cstr); + _escaped_tokens_split(str, NULL, NULL); + } + } + + _escaped_tokens_check_one("", NULL, ""); + _escaped_tokens_check_one("", "", "=", " ="); + _escaped_tokens_check_one("a", "b", "a=b", "a = b"); + _escaped_tokens_check_one("a\\=", "b\\=", "a\\\\\\==b\\\\=", "a\\\\\\==b\\\\\\="); + _escaped_tokens_check_one("\\=", "\\=", "\\\\\\==\\\\=", "\\\\\\==\\\\\\="); + _escaped_tokens_check_one(" ", "bb=", "\\ =bb=", "\\ =bb\\="); + _escaped_tokens_check_one(" ", "bb\\=", "\\ =bb\\\\=", "\\ =bb\\\\\\="); + _escaped_tokens_check_one("a b", "a b", "a b=a b"); + _escaped_tokens_check_one("a b", "a b", "a b=a b"); + _escaped_tokens_check_one("a = b", "a = b", "a \\= b=a = b", "a \\= b=a \\= b"); +} + +/*****************************************************************************/ + +typedef struct { + int val; + CList lst; +} CListSort; + +static int +_c_list_sort_cmp(const CList *lst_a, const CList *lst_b, const void *user_data) +{ + const CListSort *a, *b; + + g_assert(lst_a); + g_assert(lst_b); + g_assert(lst_a != lst_b); + + a = c_list_entry(lst_a, CListSort, lst); + b = c_list_entry(lst_b, CListSort, lst); + + if (a->val < b->val) + return -1; + if (a->val > b->val) + return 1; + return 0; +} + +static void +_do_test_c_list_sort(CListSort *elements, guint n_list, gboolean headless) +{ + CList head, *iter, *iter_prev, *lst; + guint i; + const CListSort *el_prev; + CListSort * el; + + c_list_init(&head); + for (i = 0; i < n_list; i++) { + el = &elements[i]; + el->val = nmtst_get_rand_uint32() % (2 * n_list); + c_list_link_tail(&head, &el->lst); + } + + if (headless) { + lst = head.next; + c_list_unlink_stale(&head); + lst = c_list_sort_headless(lst, _c_list_sort_cmp, NULL); + g_assert(lst); + g_assert(lst->next); + g_assert(lst->prev); + g_assert(c_list_length(lst) == n_list - 1); + iter_prev = lst->prev; + for (iter = lst; iter != lst; iter = iter->next) { + g_assert(iter); + g_assert(iter->next); + g_assert(iter->prev == iter_prev); + } + c_list_link_before(lst, &head); + } else + c_list_sort(&head, _c_list_sort_cmp, NULL); + + g_assert(!c_list_is_empty(&head)); + g_assert(c_list_length(&head) == n_list); + + el_prev = NULL; + c_list_for_each (iter, &head) { + el = c_list_entry(iter, CListSort, lst); + g_assert(el >= elements && el < &elements[n_list]); + if (el_prev) { + if (el_prev->val == el->val) + g_assert(el_prev < el); + else + g_assert(el_prev->val < el->val); + g_assert(iter->prev == &el_prev->lst); + g_assert(el_prev->lst.next == iter); + } + el_prev = el; + } + g_assert(head.prev == &el_prev->lst); +} + +static void +test_c_list_sort(void) +{ + const guint N_ELEMENTS = 10000; + guint n_list, repeat; + gs_free CListSort *elements = NULL; + + { + CList head; + + c_list_init(&head); + c_list_sort(&head, _c_list_sort_cmp, NULL); + g_assert(c_list_length(&head) == 0); + g_assert(c_list_is_empty(&head)); + } + + elements = g_new0(CListSort, N_ELEMENTS); + for (n_list = 1; n_list < N_ELEMENTS; n_list++) { + if (n_list > 150) { + n_list += nmtst_get_rand_uint32() % n_list; + if (n_list >= N_ELEMENTS) + break; + } + { + const guint N_REPEAT = n_list > 50 ? 1 : 5; + + for (repeat = 0; repeat < N_REPEAT; repeat++) + _do_test_c_list_sort(elements, n_list, nmtst_get_rand_uint32() % 2); + } + } +} + +/*****************************************************************************/ + +typedef struct { + NMDedupMultiObj parent; + guint val; + guint other; +} DedupObj; + +static const NMDedupMultiObjClass dedup_obj_class; + +static DedupObj * +_dedup_obj_assert(const NMDedupMultiObj *obj) +{ + DedupObj *o; + + g_assert(obj); + o = (DedupObj *) obj; + g_assert(o->parent.klass == &dedup_obj_class); + g_assert(o->parent._ref_count > 0); + g_assert(o->val > 0); + return o; +} + +static const NMDedupMultiObj * +_dedup_obj_clone(const NMDedupMultiObj *obj) +{ + DedupObj *o, *o2; + + o = _dedup_obj_assert(obj); + o2 = g_slice_new0(DedupObj); + o2->parent.klass = &dedup_obj_class; + o2->parent._ref_count = 1; + o2->val = o->val; + o2->other = o->other; + return (NMDedupMultiObj *) o2; +} + +static void +_dedup_obj_destroy(NMDedupMultiObj *obj) +{ + DedupObj *o = (DedupObj *) obj; + + g_assert(o->parent._ref_count == 0); + o->parent._ref_count = 1; + o = _dedup_obj_assert(obj); + g_slice_free(DedupObj, o); +} + +static void +_dedup_obj_full_hash_update(const NMDedupMultiObj *obj, NMHashState *h) +{ + const DedupObj *o; + + o = _dedup_obj_assert(obj); + nm_hash_update_vals(h, o->val, o->other); +} + +static gboolean +_dedup_obj_full_equal(const NMDedupMultiObj *obj_a, const NMDedupMultiObj *obj_b) +{ + const DedupObj *o_a = _dedup_obj_assert(obj_a); + const DedupObj *o_b = _dedup_obj_assert(obj_b); + + return o_a->val == o_b->val && o_a->other == o_b->other; +} + +static const NMDedupMultiObjClass dedup_obj_class = { + .obj_clone = _dedup_obj_clone, + .obj_destroy = _dedup_obj_destroy, + .obj_full_hash_update = _dedup_obj_full_hash_update, + .obj_full_equal = _dedup_obj_full_equal, +}; + +#define DEDUP_OBJ_INIT(val_val, other_other) \ + (&((DedupObj){ \ + .parent = \ + { \ + .klass = &dedup_obj_class, \ + ._ref_count = NM_OBJ_REF_COUNT_STACKINIT, \ + }, \ + .val = (val_val), \ + .other = (other_other), \ + })) + +typedef struct { + NMDedupMultiIdxType parent; + guint partition_size; + guint val_mod; +} DedupIdxType; + +static const NMDedupMultiIdxTypeClass dedup_idx_type_class; + +static const DedupIdxType * +_dedup_idx_assert(const NMDedupMultiIdxType *idx_type) +{ + DedupIdxType *t; + + g_assert(idx_type); + t = (DedupIdxType *) idx_type; + g_assert(t->parent.klass == &dedup_idx_type_class); + g_assert(t->partition_size > 0); + g_assert(t->val_mod > 0); + return t; +} + +static void +_dedup_idx_obj_id_hash_update(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj, + NMHashState * h) +{ + const DedupIdxType *t; + const DedupObj * o; + + t = _dedup_idx_assert(idx_type); + o = _dedup_obj_assert(obj); + + nm_hash_update_val(h, o->val / t->partition_size); + nm_hash_update_val(h, o->val % t->val_mod); +} + +static gboolean +_dedup_idx_obj_id_equal(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj_a, + const NMDedupMultiObj * obj_b) +{ + const DedupIdxType *t; + const DedupObj * o_a; + const DedupObj * o_b; + + t = _dedup_idx_assert(idx_type); + o_a = _dedup_obj_assert(obj_a); + o_b = _dedup_obj_assert(obj_b); + + return (o_a->val / t->partition_size) == (o_b->val / t->partition_size) + && (o_a->val % t->val_mod) == (o_b->val % t->val_mod); +} + +static void +_dedup_idx_obj_partition_hash_update(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj, + NMHashState * h) +{ + const DedupIdxType *t; + const DedupObj * o; + + t = _dedup_idx_assert(idx_type); + o = _dedup_obj_assert(obj); + + nm_hash_update_val(h, o->val / t->partition_size); +} + +static gboolean +_dedup_idx_obj_partition_equal(const NMDedupMultiIdxType *idx_type, + const NMDedupMultiObj * obj_a, + const NMDedupMultiObj * obj_b) +{ + const DedupIdxType *t; + const DedupObj * o_a; + const DedupObj * o_b; + + t = _dedup_idx_assert(idx_type); + o_a = _dedup_obj_assert(obj_a); + o_b = _dedup_obj_assert(obj_b); + + return (o_a->val / t->partition_size) == (o_b->val / t->partition_size); +} + +static const NMDedupMultiIdxTypeClass dedup_idx_type_class = { + .idx_obj_id_hash_update = _dedup_idx_obj_id_hash_update, + .idx_obj_id_equal = _dedup_idx_obj_id_equal, + .idx_obj_partition_hash_update = _dedup_idx_obj_partition_hash_update, + .idx_obj_partition_equal = _dedup_idx_obj_partition_equal, +}; + +static const DedupIdxType * +DEDUP_IDX_TYPE_INIT(DedupIdxType *idx_type, guint partition_size, guint val_mod) +{ + nm_dedup_multi_idx_type_init((NMDedupMultiIdxType *) idx_type, &dedup_idx_type_class); + idx_type->val_mod = val_mod; + idx_type->partition_size = partition_size; + return idx_type; +} + +static gboolean +_dedup_idx_add(NMDedupMultiIndex * idx, + const DedupIdxType * idx_type, + const DedupObj * obj, + NMDedupMultiIdxMode mode, + const NMDedupMultiEntry **out_entry) +{ + g_assert(idx); + _dedup_idx_assert((NMDedupMultiIdxType *) idx_type); + if (obj) + _dedup_obj_assert((NMDedupMultiObj *) obj); + return nm_dedup_multi_index_add(idx, + (NMDedupMultiIdxType *) idx_type, + obj, + mode, + out_entry, + NULL); +} + +static void +_dedup_head_entry_assert(const NMDedupMultiHeadEntry *entry) +{ + g_assert(entry); + g_assert(entry->len > 0); + g_assert(entry->len == c_list_length(&entry->lst_entries_head)); + g_assert(entry->idx_type); + g_assert(entry->is_head); +} + +static const DedupObj * +_dedup_entry_assert(const NMDedupMultiEntry *entry) +{ + g_assert(entry); + g_assert(!c_list_is_empty(&entry->lst_entries)); + g_assert(entry->head); + g_assert(!entry->is_head); + g_assert(entry->head != (gpointer) entry); + _dedup_head_entry_assert(entry->head); + return _dedup_obj_assert(entry->obj); +} + +static const DedupIdxType * +_dedup_entry_get_idx_type(const NMDedupMultiEntry *entry) +{ + _dedup_entry_assert(entry); + + g_assert(entry->head); + g_assert(entry->head->idx_type); + return _dedup_idx_assert(entry->head->idx_type); +} + +static void +_dedup_entry_assert_all(const NMDedupMultiEntry *entry, + gssize expected_idx, + const DedupObj *const * expected_obj) +{ + gsize n, i; + CList *iter; + + g_assert(entry); + _dedup_entry_assert(entry); + + g_assert(expected_obj); + n = NM_PTRARRAY_LEN(expected_obj); + + g_assert(n == c_list_length(&entry->lst_entries)); + + g_assert(expected_idx >= -1 && expected_idx < n); + g_assert(entry->head); + if (expected_idx == -1) + g_assert(entry->head == (gpointer) entry); + else + g_assert(entry->head != (gpointer) entry); + + i = 0; + c_list_for_each (iter, &entry->head->lst_entries_head) { + const NMDedupMultiEntry *entry_current = c_list_entry(iter, NMDedupMultiEntry, lst_entries); + const DedupObj * obj_current; + const DedupIdxType * idx_type = _dedup_entry_get_idx_type(entry_current); + + obj_current = _dedup_entry_assert(entry_current); + g_assert(obj_current); + g_assert(i < n); + if (expected_idx == i) + g_assert(entry_current == entry); + g_assert(idx_type->parent.klass->idx_obj_partition_equal( + &idx_type->parent, + entry_current->obj, + c_list_entry(entry->head->lst_entries_head.next, NMDedupMultiEntry, lst_entries)->obj)); + i++; + } +} +#define _dedup_entry_assert_all(entry, expected_idx, ...) \ + _dedup_entry_assert_all(entry, expected_idx, (const DedupObj *const[]){__VA_ARGS__, NULL}) + +static void +test_dedup_multi(void) +{ + NMDedupMultiIndex * idx; + DedupIdxType IDX_20_3_a_stack; + const DedupIdxType *const IDX_20_3_a = DEDUP_IDX_TYPE_INIT(&IDX_20_3_a_stack, 20, 3); + const NMDedupMultiEntry * entry1; + + idx = nm_dedup_multi_index_new(); + + g_assert(_dedup_idx_add(idx, + IDX_20_3_a, + DEDUP_OBJ_INIT(1, 1), + NM_DEDUP_MULTI_IDX_MODE_APPEND, + &entry1)); + _dedup_entry_assert_all(entry1, 0, DEDUP_OBJ_INIT(1, 1)); + + g_assert(nm_dedup_multi_index_obj_find(idx, (NMDedupMultiObj *) DEDUP_OBJ_INIT(1, 1))); + g_assert(!nm_dedup_multi_index_obj_find(idx, (NMDedupMultiObj *) DEDUP_OBJ_INIT(1, 2))); + + g_assert(_dedup_idx_add(idx, + IDX_20_3_a, + DEDUP_OBJ_INIT(1, 2), + NM_DEDUP_MULTI_IDX_MODE_APPEND, + &entry1)); + _dedup_entry_assert_all(entry1, 0, DEDUP_OBJ_INIT(1, 2)); + + g_assert(!nm_dedup_multi_index_obj_find(idx, (NMDedupMultiObj *) DEDUP_OBJ_INIT(1, 1))); + g_assert(nm_dedup_multi_index_obj_find(idx, (NMDedupMultiObj *) DEDUP_OBJ_INIT(1, 2))); + + g_assert(_dedup_idx_add(idx, + IDX_20_3_a, + DEDUP_OBJ_INIT(2, 2), + NM_DEDUP_MULTI_IDX_MODE_APPEND, + &entry1)); + _dedup_entry_assert_all(entry1, 1, DEDUP_OBJ_INIT(1, 2), DEDUP_OBJ_INIT(2, 2)); + + nm_dedup_multi_index_unref(idx); +} + +/*****************************************************************************/ + +static NMConnection * +_connection_new_from_dbus(GVariant *dict, GError **error) +{ + return _nm_simple_connection_new_from_dbus(dict, NM_SETTING_PARSE_FLAGS_NORMALIZE, error); +} + +static void +vpn_check_func(const char *key, const char *value, gpointer user_data) +{ + if (!strcmp(key, "foobar1")) { + g_assert_cmpstr(value, ==, "blahblah1"); + return; + } + + if (!strcmp(key, "foobar2")) { + g_assert_cmpstr(value, ==, "blahblah2"); + return; + } + + if (!strcmp(key, "foobar3")) { + g_assert_cmpstr(value, ==, "blahblah3"); + return; + } + + if (!strcmp(key, "foobar4")) { + g_assert_cmpstr(value, ==, "blahblah4"); + return; + } + + g_assert_not_reached(); +} + +static void +vpn_check_empty_func(const char *key, const char *value, gpointer user_data) +{ + g_assert_not_reached(); +} + +static void +test_setting_vpn_items(void) +{ + gs_unref_object NMConnection *connection = NULL; + NMSettingVpn * s_vpn; + + connection = + nmtst_create_minimal_connection("vpn-items", NULL, NM_SETTING_VPN_SETTING_NAME, NULL); + + s_vpn = nm_connection_get_setting_vpn(connection); + + nm_setting_vpn_add_data_item(s_vpn, "foobar1", "blahblah1"); + nm_setting_vpn_add_data_item(s_vpn, "foobar2", "blahblah2"); + nm_setting_vpn_add_data_item(s_vpn, "foobar3", "blahblah3"); + nm_setting_vpn_add_data_item(s_vpn, "foobar4", "blahblah4"); + + /* Ensure that added values are all present */ + nm_setting_vpn_foreach_data_item(s_vpn, vpn_check_func, NULL); + nm_setting_vpn_remove_data_item(s_vpn, "foobar1"); + nm_setting_vpn_remove_data_item(s_vpn, "foobar2"); + nm_setting_vpn_remove_data_item(s_vpn, "foobar3"); + nm_setting_vpn_remove_data_item(s_vpn, "foobar4"); + + g_assert(!_nm_connection_aggregate(connection, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)); + g_assert(!_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + nm_setting_vpn_add_secret(s_vpn, "foobar1", "blahblah1"); + + g_assert(_nm_connection_aggregate(connection, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)); + g_assert(_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + nm_setting_vpn_add_secret(s_vpn, "foobar2", "blahblah2"); + nm_setting_vpn_add_secret(s_vpn, "foobar3", "blahblah3"); + nm_setting_vpn_add_secret(s_vpn, "foobar4", "blahblah4"); + + /* Ensure that added values are all present */ + nm_setting_vpn_foreach_secret(s_vpn, vpn_check_func, NULL); + nm_setting_vpn_remove_secret(s_vpn, "foobar1"); + nm_setting_vpn_remove_secret(s_vpn, "foobar2"); + nm_setting_vpn_remove_secret(s_vpn, "foobar3"); + + g_assert(_nm_connection_aggregate(connection, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)); + g_assert(_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + nm_setting_vpn_add_data_item(s_vpn, "foobar4-flags", "blahblah4"); + + g_assert(_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + nm_setting_vpn_add_data_item(s_vpn, "foobar4-flags", "2"); + + g_assert(!_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + nm_setting_vpn_remove_secret(s_vpn, "foobar4"); + + g_assert(!_nm_connection_aggregate(connection, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)); + g_assert(!_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + nm_setting_vpn_remove_data_item(s_vpn, "foobar4-flags"); + + /* Try to add some blank values and make sure they are rejected */ + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(key && key[0])); + nm_setting_vpn_add_data_item(s_vpn, NULL, NULL); + g_test_assert_expected_messages(); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(key && key[0])); + nm_setting_vpn_add_data_item(s_vpn, "", ""); + g_test_assert_expected_messages(); + + nm_setting_vpn_add_data_item(s_vpn, "foobar1", ""); + g_assert_cmpstr(nm_setting_vpn_get_data_item(s_vpn, "foobar1"), ==, ""); + + nm_setting_vpn_add_data_item(s_vpn, "foobar1", NULL); + g_assert_cmpstr(nm_setting_vpn_get_data_item(s_vpn, "foobar1"), ==, NULL); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(key && key[0])); + nm_setting_vpn_add_data_item(s_vpn, NULL, "blahblah1"); + g_test_assert_expected_messages(); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(key && key[0])); + nm_setting_vpn_add_data_item(s_vpn, "", "blahblah1"); + g_test_assert_expected_messages(); + + nm_setting_vpn_foreach_data_item(s_vpn, vpn_check_empty_func, NULL); + + /* Try to add some blank secrets and make sure they are rejected */ + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(key && key[0])); + nm_setting_vpn_add_secret(s_vpn, NULL, NULL); + g_test_assert_expected_messages(); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(key && key[0])); + nm_setting_vpn_add_secret(s_vpn, "", ""); + g_test_assert_expected_messages(); + + nm_setting_vpn_add_secret(s_vpn, "foobar1", ""); + + nm_setting_vpn_add_secret(s_vpn, "foobar1", NULL); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(key && key[0])); + nm_setting_vpn_add_secret(s_vpn, NULL, "blahblah1"); + g_test_assert_expected_messages(); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(key && key[0])); + nm_setting_vpn_add_secret(s_vpn, "", "blahblah1"); + g_test_assert_expected_messages(); + + nm_setting_vpn_foreach_secret(s_vpn, vpn_check_empty_func, NULL); +} + +static void +test_setting_vpn_update_secrets(void) +{ + NMConnection * connection; + NMSettingVpn * s_vpn; + GVariantBuilder settings_builder, vpn_builder, secrets_builder; + GVariant * settings; + gboolean success; + GError * error = NULL; + const char * tmp; + const char * key1 = "foobar"; + const char * key2 = "blahblah"; + const char * val1 = "value1"; + const char * val2 = "value2"; + + connection = nm_simple_connection_new(); + s_vpn = (NMSettingVpn *) nm_setting_vpn_new(); + nm_connection_add_setting(connection, NM_SETTING(s_vpn)); + + g_variant_builder_init(&settings_builder, NM_VARIANT_TYPE_CONNECTION); + g_variant_builder_init(&vpn_builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_init(&secrets_builder, G_VARIANT_TYPE("a{ss}")); + + g_variant_builder_add(&secrets_builder, "{ss}", key1, val1); + g_variant_builder_add(&secrets_builder, "{ss}", key2, val2); + + g_variant_builder_add(&vpn_builder, + "{sv}", + NM_SETTING_VPN_SECRETS, + g_variant_builder_end(&secrets_builder)); + g_variant_builder_add(&settings_builder, "{sa{sv}}", NM_SETTING_VPN_SETTING_NAME, &vpn_builder); + settings = g_variant_builder_end(&settings_builder); + + success = + nm_connection_update_secrets(connection, NM_SETTING_VPN_SETTING_NAME, settings, &error); + g_assert_no_error(error); + g_assert(success); + + /* Read the secrets back out */ + tmp = nm_setting_vpn_get_secret(s_vpn, key1); + g_assert(tmp); + g_assert_cmpstr(tmp, ==, val1); + + tmp = nm_setting_vpn_get_secret(s_vpn, key2); + g_assert(tmp); + g_assert_cmpstr(tmp, ==, val2); + + g_variant_unref(settings); + g_object_unref(connection); +} + +#define TO_DEL_NUM 50 +typedef struct { + NMSettingVpn *s_vpn; + char * to_del[TO_DEL_NUM]; + guint called; +} IterInfo; + +static void +del_iter_func(const char *key, const char *value, gpointer user_data) +{ + IterInfo *info = user_data; + int i; + + /* Record how many times this function gets called; it should get called + * exactly as many times as there are keys in the hash table, regardless + * of what keys we delete from the table. + */ + info->called++; + + /* During the iteration, remove a bunch of stuff from the table */ + if (info->called == 1) { + for (i = 0; i < TO_DEL_NUM; i++) + nm_setting_vpn_remove_data_item(info->s_vpn, info->to_del[i]); + } +} + +static void +test_setting_vpn_modify_during_foreach(void) +{ + NMSettingVpn *s_vpn; + IterInfo info; + char * key, *val; + int i, u = 0; + + s_vpn = (NMSettingVpn *) nm_setting_vpn_new(); + g_assert(s_vpn); + + for (i = 0; i < TO_DEL_NUM * 2; i++) { + key = g_strdup_printf("adsfasdfadf%d", i); + val = g_strdup_printf("42263236236awt%d", i); + nm_setting_vpn_add_data_item(s_vpn, key, val); + + /* Cache some keys to delete */ + if (i % 2) + info.to_del[u++] = g_strdup(key); + + g_free(key); + g_free(val); + } + + /* Iterate over current table keys */ + info.s_vpn = s_vpn; + info.called = 0; + nm_setting_vpn_foreach_data_item(s_vpn, del_iter_func, &info); + + /* Make sure all the things we removed during iteration are really gone */ + for (i = 0; i < TO_DEL_NUM; i++) { + g_assert_cmpstr(nm_setting_vpn_get_data_item(s_vpn, info.to_del[i]), ==, NULL); + g_free(info.to_del[i]); + } + + /* And make sure the foreach callback was called the same number of times + * as there were keys in the table at the beginning of the foreach. + */ + g_assert_cmpint(info.called, ==, TO_DEL_NUM * 2); + + g_object_unref(s_vpn); +} + +static void +test_setting_ip4_config_labels(void) +{ + NMSettingIPConfig *s_ip4; + NMIPAddress * addr; + GVariant * label; + GPtrArray * addrs; + char ** labels; + NMConnection * conn; + GVariant * dict, *dict2, *setting_dict, *value; + GError * error = NULL; + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + + /* addr 1 */ + addr = nm_ip_address_new(AF_INET, "1.2.3.4", 24, &error); + g_assert_no_error(error); + + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + nmtst_assert_setting_verifies(NM_SETTING(s_ip4)); + + addr = nm_setting_ip_config_get_address(s_ip4, 0); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label == NULL); + + /* The 'address-labels' property should be omitted from the serialization if + * there are no non-NULL labels. + */ + conn = nmtst_create_minimal_connection("label test", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + nm_connection_add_setting(conn, nm_setting_duplicate(NM_SETTING(s_ip4))); + dict = nm_connection_to_dbus(conn, NM_CONNECTION_SERIALIZE_ALL); + g_object_unref(conn); + + setting_dict = + g_variant_lookup_value(dict, NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_VARIANT_TYPE_SETTING); + g_assert(setting_dict != NULL); + + value = g_variant_lookup_value(setting_dict, "address-labels", NULL); + g_assert(value == NULL); + + g_variant_unref(setting_dict); + g_variant_unref(dict); + + /* Now back to constructing the original s_ip4... */ + + /* addr 2 */ + addr = nm_ip_address_new(AF_INET, "2.3.4.5", 24, &error); + g_assert_no_error(error); + nm_ip_address_set_attribute(addr, + NM_IP_ADDRESS_ATTRIBUTE_LABEL, + g_variant_new_string("eth0:1")); + + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + nmtst_assert_setting_verifies(NM_SETTING(s_ip4)); + + addr = nm_setting_ip_config_get_address(s_ip4, 1); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label != NULL); + g_assert_cmpstr(g_variant_get_string(label, NULL), ==, "eth0:1"); + + /* addr 3 */ + addr = nm_ip_address_new(AF_INET, "3.4.5.6", 24, &error); + g_assert_no_error(error); + nm_ip_address_set_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL, NULL); + + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + nmtst_assert_setting_verifies(NM_SETTING(s_ip4)); + + addr = nm_setting_ip_config_get_address(s_ip4, 2); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label == NULL); + + /* Remove addr 1 and re-verify remaining addresses */ + nm_setting_ip_config_remove_address(s_ip4, 0); + nmtst_assert_setting_verifies(NM_SETTING(s_ip4)); + + addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "2.3.4.5"); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label != NULL); + g_assert_cmpstr(g_variant_get_string(label, NULL), ==, "eth0:1"); + + addr = nm_setting_ip_config_get_address(s_ip4, 1); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "3.4.5.6"); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label == NULL); + + /* If we serialize as the daemon, the labels should appear in the D-Bus + * serialization under both 'address-labels' and 'address-data'. + */ + conn = nmtst_create_minimal_connection("label test", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + _nm_utils_is_manager_process = TRUE; + dict = nm_connection_to_dbus(conn, NM_CONNECTION_SERIALIZE_ALL); + _nm_utils_is_manager_process = FALSE; + g_object_unref(conn); + + setting_dict = + g_variant_lookup_value(dict, NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_VARIANT_TYPE_SETTING); + g_assert(setting_dict != NULL); + + value = g_variant_lookup_value(setting_dict, "address-labels", G_VARIANT_TYPE_STRING_ARRAY); + g_assert(value != NULL); + g_variant_get(value, "^as", &labels); + g_assert_cmpint(g_strv_length(labels), ==, 2); + g_assert_cmpstr(labels[0], ==, "eth0:1"); + g_assert_cmpstr(labels[1], ==, ""); + g_variant_unref(value); + g_strfreev(labels); + + value = g_variant_lookup_value(setting_dict, "address-data", G_VARIANT_TYPE("aa{sv}")); + addrs = nm_utils_ip_addresses_from_variant(value, AF_INET); + g_variant_unref(value); + g_assert(addrs != NULL); + g_assert_cmpint(addrs->len, ==, 2); + addr = addrs->pdata[0]; + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label != NULL); + g_assert_cmpstr(g_variant_get_string(label, NULL), ==, "eth0:1"); + addr = addrs->pdata[1]; + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label == NULL); + g_ptr_array_unref(addrs); + + g_variant_unref(setting_dict); + + /* We should be able to deserialize the labels from either 'address-labels' + * or 'address-data'. + */ + dict2 = g_variant_ref(dict); + + NMTST_VARIANT_EDITOR( + dict, NMTST_VARIANT_DROP_PROPERTY(NM_SETTING_IP4_CONFIG_SETTING_NAME, "address-data");); + conn = _connection_new_from_dbus(dict, &error); + g_assert_no_error(error); + g_variant_unref(dict); + + s_ip4 = nm_connection_get_setting_ip4_config(conn); + + addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "2.3.4.5"); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label != NULL); + g_assert_cmpstr(g_variant_get_string(label, NULL), ==, "eth0:1"); + + addr = nm_setting_ip_config_get_address(s_ip4, 1); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "3.4.5.6"); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label == NULL); + + g_object_unref(conn); + + NMTST_VARIANT_EDITOR( + dict2, NMTST_VARIANT_DROP_PROPERTY(NM_SETTING_IP4_CONFIG_SETTING_NAME, "address-labels");); + conn = _connection_new_from_dbus(dict2, &error); + g_assert_no_error(error); + g_variant_unref(dict2); + + s_ip4 = nm_connection_get_setting_ip4_config(conn); + + addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "2.3.4.5"); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert_cmpstr(g_variant_get_string(label, NULL), ==, "eth0:1"); + + addr = nm_setting_ip_config_get_address(s_ip4, 1); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "3.4.5.6"); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label == NULL); + + /* Test explicit property assignment */ + g_object_get(G_OBJECT(s_ip4), NM_SETTING_IP_CONFIG_ADDRESSES, &addrs, NULL); + + nm_setting_ip_config_clear_addresses(s_ip4); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 0); + + g_object_set(G_OBJECT(s_ip4), NM_SETTING_IP_CONFIG_ADDRESSES, addrs, NULL); + g_ptr_array_unref(addrs); + nmtst_assert_setting_verifies(NM_SETTING(s_ip4)); + g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 2); + + addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "2.3.4.5"); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label != NULL); + g_assert_cmpstr(g_variant_get_string(label, NULL), ==, "eth0:1"); + + addr = nm_setting_ip_config_get_address(s_ip4, 1); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "3.4.5.6"); + label = nm_ip_address_get_attribute(addr, NM_IP_ADDRESS_ATTRIBUTE_LABEL); + g_assert(label == NULL); + + g_object_unref(conn); +} + +static void +test_setting_ip4_config_address_data(void) +{ + NMSettingIPConfig *s_ip4; + NMIPAddress * addr; + GPtrArray * addrs; + NMConnection * conn; + GVariant * dict, *setting_dict, *value; + GError * error = NULL; + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + + /* addr 1 */ + addr = nm_ip_address_new(AF_INET, "1.2.3.4", 24, &error); + g_assert_no_error(error); + nm_ip_address_set_attribute(addr, "one", g_variant_new_string("foo")); + nm_ip_address_set_attribute(addr, "two", g_variant_new_int32(42)); + + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + nmtst_assert_setting_verifies(NM_SETTING(s_ip4)); + + /* addr 2 */ + addr = nm_ip_address_new(AF_INET, "2.3.4.5", 24, &error); + g_assert_no_error(error); + + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + nmtst_assert_setting_verifies(NM_SETTING(s_ip4)); + + /* The client-side D-Bus serialization should include the attributes in + * "address-data", and should not have an "addresses" property. + */ + conn = nmtst_create_minimal_connection("address-data test", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + dict = nm_connection_to_dbus(conn, NM_CONNECTION_SERIALIZE_ALL); + + setting_dict = + g_variant_lookup_value(dict, NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_VARIANT_TYPE_SETTING); + g_assert(setting_dict != NULL); + + value = g_variant_lookup_value(setting_dict, "addresses", NULL); + g_assert(value == NULL); + + value = g_variant_lookup_value(setting_dict, "address-data", G_VARIANT_TYPE("aa{sv}")); + addrs = nm_utils_ip_addresses_from_variant(value, AF_INET); + g_variant_unref(value); + g_assert(addrs != NULL); + g_assert_cmpint(addrs->len, ==, 2); + + addr = addrs->pdata[0]; + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "1.2.3.4"); + value = nm_ip_address_get_attribute(addr, "one"); + g_assert(value != NULL); + g_assert_cmpstr(g_variant_get_string(value, NULL), ==, "foo"); + value = nm_ip_address_get_attribute(addr, "two"); + g_assert(value != NULL); + g_assert_cmpint(g_variant_get_int32(value), ==, 42); + + g_ptr_array_unref(addrs); + g_variant_unref(setting_dict); + g_variant_unref(dict); + + /* The daemon-side serialization should include both 'addresses' and 'address-data' */ + _nm_utils_is_manager_process = TRUE; + dict = nm_connection_to_dbus(conn, NM_CONNECTION_SERIALIZE_ALL); + _nm_utils_is_manager_process = FALSE; + + setting_dict = + g_variant_lookup_value(dict, NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_VARIANT_TYPE_SETTING); + g_assert(setting_dict != NULL); + + value = g_variant_lookup_value(setting_dict, "addresses", G_VARIANT_TYPE("aau")); + g_assert(value != NULL); + g_variant_unref(value); + + value = g_variant_lookup_value(setting_dict, "address-data", G_VARIANT_TYPE("aa{sv}")); + g_assert(value != NULL); + g_variant_unref(value); + + g_variant_unref(setting_dict); + g_object_unref(conn); + + /* When we reserialize that dictionary as a client, 'address-data' will be preferred. */ + conn = _connection_new_from_dbus(dict, &error); + g_assert_no_error(error); + + s_ip4 = nm_connection_get_setting_ip4_config(conn); + + addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "1.2.3.4"); + value = nm_ip_address_get_attribute(addr, "one"); + g_assert(value != NULL); + g_assert_cmpstr(g_variant_get_string(value, NULL), ==, "foo"); + value = nm_ip_address_get_attribute(addr, "two"); + g_assert(value != NULL); + g_assert_cmpint(g_variant_get_int32(value), ==, 42); + + /* But on the server side, 'addresses' will have precedence. */ + _nm_utils_is_manager_process = TRUE; + conn = _connection_new_from_dbus(dict, &error); + _nm_utils_is_manager_process = FALSE; + g_assert_no_error(error); + g_variant_unref(dict); + + s_ip4 = nm_connection_get_setting_ip4_config(conn); + + addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "1.2.3.4"); + value = nm_ip_address_get_attribute(addr, "one"); + g_assert(value == NULL); + value = nm_ip_address_get_attribute(addr, "two"); + g_assert(value == NULL); + + g_object_unref(conn); +} + +static void +test_setting_ip_route_attributes(void) +{ + GVariant *variant; + gboolean res, known; + +#define TEST_ATTR(name, type, value, family, exp_res, exp_known) \ + variant = g_variant_new_##type(value); \ + res = nm_ip_route_attribute_validate(name, variant, family, &known, NULL); \ + g_assert(res == exp_res); \ + g_assert(known == exp_known); \ + g_variant_unref(variant); + + TEST_ATTR("foo", uint32, 12, AF_INET, FALSE, FALSE); + + TEST_ATTR("tos", byte, 127, AF_INET, TRUE, TRUE); + TEST_ATTR("tos", string, "0x28", AF_INET, FALSE, TRUE); + + TEST_ATTR("cwnd", uint32, 10, AF_INET, TRUE, TRUE); + TEST_ATTR("cwnd", string, "11", AF_INET, FALSE, TRUE); + + TEST_ATTR("lock-mtu", boolean, TRUE, AF_INET, TRUE, TRUE); + TEST_ATTR("lock-mtu", uint32, 1, AF_INET, FALSE, TRUE); + + TEST_ATTR("from", string, "fd01::1", AF_INET6, TRUE, TRUE); + TEST_ATTR("from", string, "fd01::1/64", AF_INET6, TRUE, TRUE); + TEST_ATTR("from", string, "fd01::1/128", AF_INET6, TRUE, TRUE); + TEST_ATTR("from", string, "fd01::1/129", AF_INET6, FALSE, TRUE); + TEST_ATTR("from", string, "fd01::1/a", AF_INET6, FALSE, TRUE); + TEST_ATTR("from", string, "abc/64", AF_INET6, FALSE, TRUE); + TEST_ATTR("from", string, "1.2.3.4", AF_INET, FALSE, TRUE); + TEST_ATTR("from", string, "1.2.3.4", AF_INET6, FALSE, TRUE); + + TEST_ATTR("src", string, "1.2.3.4", AF_INET, TRUE, TRUE); + TEST_ATTR("src", string, "1.2.3.4", AF_INET6, FALSE, TRUE); + TEST_ATTR("src", string, "1.2.3.0/24", AF_INET, FALSE, TRUE); + TEST_ATTR("src", string, "fd01::12", AF_INET6, TRUE, TRUE); + + TEST_ATTR("type", string, "local", AF_INET, TRUE, TRUE); + TEST_ATTR("type", string, "local", AF_INET6, TRUE, TRUE); + TEST_ATTR("type", string, "unicast", AF_INET, TRUE, TRUE); + TEST_ATTR("type", string, "unicast", AF_INET6, TRUE, TRUE); + +#undef TEST_ATTR +} + +static void +test_setting_gsm_apn_spaces(void) +{ + gs_unref_object NMSettingGsm *s_gsm = NULL; + const char * tmp; + + s_gsm = (NMSettingGsm *) nm_setting_gsm_new(); + g_assert(s_gsm); + + /* Trailing space */ + g_object_set(s_gsm, NM_SETTING_GSM_APN, "foobar ", NULL); + tmp = nm_setting_gsm_get_apn(s_gsm); + g_assert_cmpstr(tmp, ==, "foobar"); + + /* Leading space */ + g_object_set(s_gsm, NM_SETTING_GSM_APN, " foobar", NULL); + tmp = nm_setting_gsm_get_apn(s_gsm); + g_assert_cmpstr(tmp, ==, "foobar"); +} + +static void +test_setting_gsm_apn_bad_chars(void) +{ + gs_unref_object NMSettingGsm *s_gsm = NULL; + + s_gsm = (NMSettingGsm *) nm_setting_gsm_new(); + g_assert(s_gsm); + + /* Make sure a valid APN works */ + g_object_set(s_gsm, NM_SETTING_GSM_APN, "foobar123.-baz", NULL); + g_assert(nm_setting_verify(NM_SETTING(s_gsm), NULL, NULL)); + + /* Random invalid chars */ + g_object_set(s_gsm, NM_SETTING_GSM_APN, "@#%$@#%@#%", NULL); + g_assert(!nm_setting_verify(NM_SETTING(s_gsm), NULL, NULL)); + + /* Spaces */ + g_object_set(s_gsm, NM_SETTING_GSM_APN, "foobar baz", NULL); + g_assert(!nm_setting_verify(NM_SETTING(s_gsm), NULL, NULL)); + + /* 0 characters long */ + g_object_set(s_gsm, NM_SETTING_GSM_APN, "", NULL); + g_assert(nm_setting_verify(NM_SETTING(s_gsm), NULL, NULL)); + + /* 65-character long */ + g_object_set(s_gsm, + NM_SETTING_GSM_APN, + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl1", + NULL); + g_assert(!nm_setting_verify(NM_SETTING(s_gsm), NULL, NULL)); +} + +static void +test_setting_gsm_apn_underscore(void) +{ + gs_unref_object NMSettingGsm *s_gsm = NULL; + + s_gsm = (NMSettingGsm *) nm_setting_gsm_new(); + g_assert(s_gsm); + + /* 65-character long */ + g_object_set(s_gsm, NM_SETTING_GSM_APN, "foobar_baz", NULL); + nmtst_assert_setting_verifies(NM_SETTING(s_gsm)); +} + +static void +test_setting_gsm_without_number(void) +{ + gs_unref_object NMSettingGsm *s_gsm = NULL; + + s_gsm = (NMSettingGsm *) nm_setting_gsm_new(); + g_assert(s_gsm); + + g_object_set(s_gsm, NM_SETTING_GSM_NUMBER, NULL, NULL); + nmtst_assert_setting_verifies(NM_SETTING(s_gsm)); + + g_object_set(s_gsm, NM_SETTING_GSM_NUMBER, "", NULL); + nmtst_assert_setting_verify_fails(NM_SETTING(s_gsm), + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); +} + +static void +test_setting_gsm_sim_operator_id(void) +{ + gs_unref_object NMSettingGsm *s_gsm = NULL; + + s_gsm = (NMSettingGsm *) nm_setting_gsm_new(); + g_assert(s_gsm); + + /* Valid */ + g_object_set(s_gsm, NM_SETTING_GSM_SIM_OPERATOR_ID, "12345", NULL); + nmtst_assert_setting_verifies(NM_SETTING(s_gsm)); + + g_object_set(s_gsm, NM_SETTING_GSM_SIM_OPERATOR_ID, "123456", NULL); + nmtst_assert_setting_verifies(NM_SETTING(s_gsm)); + + /* Invalid */ + g_object_set(s_gsm, NM_SETTING_GSM_SIM_OPERATOR_ID, "", NULL); + nmtst_assert_setting_verify_fails(NM_SETTING(s_gsm), + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + + g_object_set(s_gsm, NM_SETTING_GSM_SIM_OPERATOR_ID, " ", NULL); + nmtst_assert_setting_verify_fails(NM_SETTING(s_gsm), + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + + g_object_set(s_gsm, NM_SETTING_GSM_SIM_OPERATOR_ID, "abcdef", NULL); + nmtst_assert_setting_verify_fails(NM_SETTING(s_gsm), + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); +} + +static NMSettingWirelessSecurity * +make_test_wsec_setting(const char *detail) +{ + NMSettingWirelessSecurity *s_wsec; + + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + g_assert(s_wsec); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, + "foobarbaz", + NM_SETTING_WIRELESS_SECURITY_PSK, + "random psk", + NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + "aaaaaaaaaa", + NULL); + return s_wsec; +} + +static gboolean +_variant_contains(GVariant *vardict, const char *key) +{ + gs_unref_variant GVariant *value = NULL; + + value = g_variant_lookup_value(vardict, key, NULL); + return !!value; +} + +static void +test_setting_to_dbus_all(void) +{ + NMSettingWirelessSecurity *s_wsec; + GVariant * dict; + + s_wsec = make_test_wsec_setting("setting-to-dbus-all"); + + dict = _nm_setting_to_dbus(NM_SETTING(s_wsec), NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + + /* Make sure all keys are there */ + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT)); + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME)); + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_PSK)); + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_WEP_KEY0)); + + g_variant_unref(dict); + g_object_unref(s_wsec); +} + +static void +test_setting_to_dbus_no_secrets(void) +{ + NMSettingWirelessSecurity *s_wsec; + GVariant * dict; + + s_wsec = make_test_wsec_setting("setting-to-dbus-no-secrets"); + + dict = _nm_setting_to_dbus(NM_SETTING(s_wsec), + NULL, + NM_CONNECTION_SERIALIZE_WITH_NON_SECRET, + NULL); + + /* Make sure non-secret keys are there */ + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT)); + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME)); + + /* Make sure secrets are not there */ + g_assert(!_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_PSK)); + g_assert(!_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_WEP_KEY0)); + + g_variant_unref(dict); + g_object_unref(s_wsec); +} + +static void +test_setting_to_dbus_only_secrets(void) +{ + NMSettingWirelessSecurity *s_wsec; + GVariant * dict; + + s_wsec = make_test_wsec_setting("setting-to-dbus-only-secrets"); + + dict = + _nm_setting_to_dbus(NM_SETTING(s_wsec), NULL, NM_CONNECTION_SERIALIZE_WITH_SECRETS, NULL); + + /* Make sure non-secret keys are not there */ + g_assert(!_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT)); + g_assert(!_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME)); + + /* Make sure secrets are there */ + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_PSK)); + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_WEP_KEY0)); + + g_variant_unref(dict); + g_object_unref(s_wsec); +} + +static void +test_setting_to_dbus_transform(void) +{ + NMSetting * s_wired; + GVariant * dict, *val; + const char * test_mac_address = "11:22:33:44:55:66"; + const guint8 *dbus_mac_address; + guint8 cmp_mac_address[ETH_ALEN]; + gsize len; + + s_wired = nm_setting_wired_new(); + g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, test_mac_address, NULL); + + g_assert_cmpstr(nm_setting_wired_get_mac_address(NM_SETTING_WIRED(s_wired)), + ==, + test_mac_address); + + dict = _nm_setting_to_dbus(s_wired, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + g_assert(dict != NULL); + + val = g_variant_lookup_value(dict, NM_SETTING_WIRED_MAC_ADDRESS, G_VARIANT_TYPE_BYTESTRING); + g_assert(val != NULL); + + dbus_mac_address = g_variant_get_fixed_array(val, &len, 1); + g_assert_cmpint(len, ==, ETH_ALEN); + + nm_utils_hwaddr_aton(test_mac_address, cmp_mac_address, ETH_ALEN); + g_assert(memcmp(dbus_mac_address, cmp_mac_address, ETH_ALEN) == 0); + + g_variant_unref(val); + g_variant_unref(dict); + g_object_unref(s_wired); +} + +static void +test_setting_to_dbus_enum(void) +{ + NMSetting *s_ip6, *s_wsec, *s_serial; + GVariant * dict, *val; + + /* enum */ + s_ip6 = nm_setting_ip6_config_new(); + g_object_set(s_ip6, + NM_SETTING_IP6_CONFIG_IP6_PRIVACY, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR, + NULL); + + dict = _nm_setting_to_dbus(s_ip6, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + g_assert(dict != NULL); + + val = g_variant_lookup_value(dict, NM_SETTING_IP6_CONFIG_IP6_PRIVACY, G_VARIANT_TYPE_INT32); + g_assert(val != NULL); + g_assert_cmpint(g_variant_get_int32(val), ==, NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR); + g_variant_unref(val); + + g_variant_unref(dict); + g_object_unref(s_ip6); + + /* flags (and a transformed enum) */ + s_wsec = nm_setting_wireless_security_new(); + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + NM_WEP_KEY_TYPE_KEY, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, + (NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED), + NULL); + + dict = _nm_setting_to_dbus(s_wsec, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + g_assert(dict != NULL); + + val = g_variant_lookup_value(dict, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + G_VARIANT_TYPE_UINT32); + g_assert(val != NULL); + g_assert_cmpint(g_variant_get_uint32(val), ==, NM_WEP_KEY_TYPE_KEY); + g_variant_unref(val); + + val = g_variant_lookup_value(dict, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, + G_VARIANT_TYPE_UINT32); + g_assert(val != NULL); + g_assert_cmpint(g_variant_get_uint32(val), + ==, + (NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED)); + g_variant_unref(val); + + g_variant_unref(dict); + g_object_unref(s_wsec); + + /* another transformed enum */ + s_serial = nm_setting_serial_new(); + g_object_set(s_serial, NM_SETTING_SERIAL_PARITY, NM_SETTING_SERIAL_PARITY_ODD, NULL); + + dict = _nm_setting_to_dbus(s_serial, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + g_assert(dict != NULL); + + val = g_variant_lookup_value(dict, NM_SETTING_SERIAL_PARITY, G_VARIANT_TYPE_BYTE); + g_assert(val != NULL); + g_assert_cmpint(g_variant_get_byte(val), ==, 'o'); + g_variant_unref(val); + + g_variant_unref(dict); + g_object_unref(s_serial); +} + +static void +test_connection_to_dbus_setting_name(void) +{ + NMConnection * connection; + NMSettingWirelessSecurity *s_wsec; + GVariant * dict; + + connection = nm_simple_connection_new(); + s_wsec = make_test_wsec_setting("connection-to-dbus-setting-name"); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + g_assert(_nm_connection_aggregate(connection, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)); + g_assert(_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NULL); + + g_assert(_nm_connection_aggregate(connection, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)); + g_assert(!_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + g_object_set(s_wsec, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_WIRELESS_SECURITY_LEAP_PASSWORD_FLAGS, + NM_SETTING_SECRET_FLAG_NONE, + NULL); + + g_assert(_nm_connection_aggregate(connection, NM_CONNECTION_AGGREGATE_ANY_SECRETS, NULL)); + g_assert(_nm_connection_aggregate(connection, + NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS, + NULL)); + + dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); + + /* Make sure the keys of the first level dict are setting names, not + * the GType name of the setting objects. + */ + g_assert(_variant_contains(dict, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME)); + + g_variant_unref(dict); + g_object_unref(connection); +} + +static void +test_connection_to_dbus_deprecated_props(void) +{ + NMConnection * connection; + NMSetting * s_wireless; + GBytes * ssid; + NMSettingWirelessSecurity *s_wsec; + GVariant * dict, *wireless_dict, *sec_val; + + connection = nmtst_create_minimal_connection("test-connection-to-dbus-deprecated-props", + NULL, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + + s_wireless = nm_setting_wireless_new(); + ssid = g_bytes_new("1234567", 7); + g_object_set(s_wireless, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + nm_connection_add_setting(connection, s_wireless); + + /* Serialization should not have an 802-11-wireless.security property */ + dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); + g_assert(dict != NULL); + + wireless_dict = + g_variant_lookup_value(dict, NM_SETTING_WIRELESS_SETTING_NAME, NM_VARIANT_TYPE_SETTING); + g_assert(wireless_dict != NULL); + + sec_val = g_variant_lookup_value(wireless_dict, "security", NULL); + g_assert(sec_val == NULL); + + g_variant_unref(wireless_dict); + g_variant_unref(dict); + + /* Now add an NMSettingWirelessSecurity and try again */ + s_wsec = make_test_wsec_setting("test-connection-to-dbus-deprecated-props"); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + dict = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); + g_assert(dict != NULL); + + wireless_dict = + g_variant_lookup_value(dict, NM_SETTING_WIRELESS_SETTING_NAME, NM_VARIANT_TYPE_SETTING); + g_assert(wireless_dict != NULL); + + sec_val = g_variant_lookup_value(wireless_dict, "security", NULL); + g_assert(g_variant_is_of_type(sec_val, G_VARIANT_TYPE_STRING)); + g_assert_cmpstr(g_variant_get_string(sec_val, NULL), + ==, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); + + g_variant_unref(sec_val); + g_variant_unref(wireless_dict); + g_variant_unref(dict); + g_object_unref(connection); +} + +static void +test_setting_new_from_dbus(void) +{ + NMSettingWirelessSecurity *s_wsec; + GVariant * dict; + + s_wsec = make_test_wsec_setting("setting-new-from-dbus"); + dict = _nm_setting_to_dbus(NM_SETTING(s_wsec), NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + g_object_unref(s_wsec); + + s_wsec = + (NMSettingWirelessSecurity *) _nm_setting_new_from_dbus(NM_TYPE_SETTING_WIRELESS_SECURITY, + dict, + NULL, + NM_SETTING_PARSE_FLAGS_NONE, + NULL); + g_variant_unref(dict); + + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk"); + g_assert_cmpstr(nm_setting_wireless_security_get_leap_username(s_wsec), ==, "foobarbaz"); + g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "random psk"); + g_object_unref(s_wsec); +} + +static void +test_setting_new_from_dbus_transform(void) +{ + NMSetting * s_wired; + GVariant * dict; + GVariantBuilder builder; + const char * test_mac_address = "11:22:33:44:55:66"; + guint8 dbus_mac_address[ETH_ALEN]; + GError * error = NULL; + + nm_utils_hwaddr_aton(test_mac_address, dbus_mac_address, ETH_ALEN); + + g_variant_builder_init(&builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_add( + &builder, + "{sv}", + NM_SETTING_WIRED_MAC_ADDRESS, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, dbus_mac_address, ETH_ALEN, 1)); + dict = g_variant_builder_end(&builder); + + s_wired = _nm_setting_new_from_dbus(NM_TYPE_SETTING_WIRED, + dict, + NULL, + NM_SETTING_PARSE_FLAGS_NONE, + &error); + g_assert_no_error(error); + + g_assert_cmpstr(nm_setting_wired_get_mac_address(NM_SETTING_WIRED(s_wired)), + ==, + test_mac_address); + + g_variant_unref(dict); + g_object_unref(s_wired); +} + +static void +test_setting_new_from_dbus_enum(void) +{ + NMSettingIP6Config * s_ip6; + NMSettingWirelessSecurity *s_wsec; + NMSettingSerial * s_serial; + GVariant * dict; + GVariantBuilder builder; + GError * error = NULL; + + /* enum */ + g_variant_builder_init(&builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_add(&builder, + "{sv}", + NM_SETTING_IP6_CONFIG_IP6_PRIVACY, + g_variant_new_int32(NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR)); + dict = g_variant_builder_end(&builder); + + s_ip6 = (NMSettingIP6Config *) _nm_setting_new_from_dbus(NM_TYPE_SETTING_IP6_CONFIG, + dict, + NULL, + NM_SETTING_PARSE_FLAGS_NONE, + &error); + g_assert_no_error(error); + + g_assert_cmpint(nm_setting_ip6_config_get_ip6_privacy(s_ip6), + ==, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR); + + g_variant_unref(dict); + g_object_unref(s_ip6); + + /* flags (and a transformed enum) */ + g_variant_builder_init(&builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_add(&builder, + "{sv}", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + g_variant_new_uint32(NM_WEP_KEY_TYPE_KEY)); + g_variant_builder_add(&builder, + "{sv}", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_FLAGS, + g_variant_new_uint32(NM_SETTING_SECRET_FLAG_AGENT_OWNED + | NM_SETTING_SECRET_FLAG_NOT_SAVED)); + dict = g_variant_builder_end(&builder); + + s_wsec = + (NMSettingWirelessSecurity *) _nm_setting_new_from_dbus(NM_TYPE_SETTING_WIRELESS_SECURITY, + dict, + NULL, + NM_SETTING_PARSE_FLAGS_NONE, + &error); + g_assert_no_error(error); + + g_assert_cmpint(nm_setting_wireless_security_get_wep_key_type(s_wsec), ==, NM_WEP_KEY_TYPE_KEY); + g_assert_cmpint(nm_setting_wireless_security_get_wep_key_flags(s_wsec), + ==, + (NM_SETTING_SECRET_FLAG_AGENT_OWNED | NM_SETTING_SECRET_FLAG_NOT_SAVED)); + + g_variant_unref(dict); + g_object_unref(s_wsec); + + /* another transformed enum */ + g_variant_builder_init(&builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_add(&builder, "{sv}", NM_SETTING_SERIAL_PARITY, g_variant_new_byte('E')); + dict = g_variant_builder_end(&builder); + + s_serial = (NMSettingSerial *) _nm_setting_new_from_dbus(NM_TYPE_SETTING_SERIAL, + dict, + NULL, + NM_SETTING_PARSE_FLAGS_NONE, + &error); + g_assert_no_error(error); + + g_assert_cmpint(nm_setting_serial_get_parity(s_serial), ==, NM_SETTING_SERIAL_PARITY_EVEN); + + g_variant_unref(dict); + g_object_unref(s_serial); +} + +static void +test_setting_new_from_dbus_bad(void) +{ + NMSetting * setting; + NMConnection *conn; + GBytes * ssid; + GPtrArray * addrs; + GVariant * orig_dict, *dict; + GError * error = NULL; + + /* We want to test: + * - ordinary scalar properties + * - string properties + * - GBytes-valued properties (which are handled specially by set_property_from_dbus()) + * - enum/flags-valued properties + * - overridden properties + * - transformed properties + * + * No single setting class has examples of all of these, so we need two settings. + */ + + conn = nm_simple_connection_new(); + + setting = nm_setting_connection_new(); + g_object_set(setting, + NM_SETTING_CONNECTION_ID, + "test", + NM_SETTING_CONNECTION_UUID, + "83c5a841-1759-4cdb-bfce-8d4087956497", + NULL); + nm_connection_add_setting(conn, setting); + + setting = nm_setting_wireless_new(); + ssid = g_bytes_new("my-ssid", 7); + g_object_set(setting, + /* scalar */ + NM_SETTING_WIRELESS_RATE, + 100, + /* string */ + NM_SETTING_WIRELESS_MODE, + NM_SETTING_WIRELESS_MODE_INFRA, + /* GBytes */ + NM_SETTING_WIRELESS_SSID, + ssid, + /* transformed */ + NM_SETTING_WIRELESS_BSSID, + "00:11:22:33:44:55", + NULL); + g_bytes_unref(ssid); + nm_connection_add_setting(conn, setting); + + setting = nm_setting_ip6_config_new(); + addrs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref); + g_ptr_array_add(addrs, nm_ip_address_new(AF_INET6, "1234::5678", 64, NULL)); + g_object_set(setting, + /* enum */ + NM_SETTING_IP6_CONFIG_IP6_PRIVACY, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR, + /* overridden */ + NM_SETTING_IP_CONFIG_ADDRESSES, + addrs, + /* (needed in order to verify()) */ + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NULL); + g_ptr_array_unref(addrs); + nm_connection_add_setting(conn, setting); + + orig_dict = nm_connection_to_dbus(conn, NM_CONNECTION_SERIALIZE_ALL); + g_object_unref(conn); + + /* sanity-check */ + conn = _connection_new_from_dbus(orig_dict, &error); + g_assert_no_error(error); + g_assert(conn); + g_object_unref(conn); + + /* Compatible mismatches */ + + dict = g_variant_ref(orig_dict); + NMTST_VARIANT_EDITOR(dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_RATE, + "i", + 10);); + conn = _connection_new_from_dbus(dict, &error); + g_assert(conn); + g_assert_no_error(error); + setting = nm_connection_get_setting(conn, NM_TYPE_SETTING_WIRELESS); + g_assert(setting); + g_assert_cmpint(nm_setting_wireless_get_rate(NM_SETTING_WIRELESS(setting)), ==, 10); + g_object_unref(conn); + g_variant_unref(dict); + + dict = g_variant_ref(orig_dict); + NMTST_VARIANT_EDITOR( + dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_IP6_PRIVACY, + "i", + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR);); + conn = _connection_new_from_dbus(dict, &error); + g_assert(conn); + g_assert_no_error(error); + setting = nm_connection_get_setting(conn, NM_TYPE_SETTING_IP6_CONFIG); + g_assert(setting); + g_assert_cmpint(nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(setting)), + ==, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR); + g_object_unref(conn); + g_variant_unref(dict); + + /* Incompatible mismatches */ + + dict = g_variant_ref(orig_dict); + NMTST_VARIANT_EDITOR(dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_RATE, + "s", + "ten");); + conn = _connection_new_from_dbus(dict, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "802-11-wireless.rate:")); + g_clear_error(&error); + g_variant_unref(dict); + + dict = g_variant_ref(orig_dict); + NMTST_VARIANT_EDITOR(dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_MODE, + "b", + FALSE);); + conn = _connection_new_from_dbus(dict, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "802-11-wireless.mode:")); + g_clear_error(&error); + g_variant_unref(dict); + + dict = g_variant_ref(orig_dict); + NMTST_VARIANT_EDITOR(dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_SSID, + "s", + "fred");); + conn = _connection_new_from_dbus(dict, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "802-11-wireless.ssid:")); + g_clear_error(&error); + g_variant_unref(dict); + + dict = g_variant_ref(orig_dict); + NMTST_VARIANT_EDITOR(dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_WIRELESS_SETTING_NAME, + NM_SETTING_WIRELESS_BSSID, + "i", + 42);); + conn = _connection_new_from_dbus(dict, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "802-11-wireless.bssid:")); + g_clear_error(&error); + g_variant_unref(dict); + + dict = g_variant_ref(orig_dict); + NMTST_VARIANT_EDITOR(dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_IP6_PRIVACY, + "s", + "private");); + conn = _connection_new_from_dbus(dict, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "ipv6.ip6-privacy:")); + g_clear_error(&error); + g_variant_unref(dict); + + dict = g_variant_ref(orig_dict); + NMTST_VARIANT_EDITOR(dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_ADDRESSES, + "s", + "1234::5678");); + conn = _connection_new_from_dbus(dict, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "ipv6.addresses:")); + g_clear_error(&error); + g_variant_unref(dict); + + g_variant_unref(orig_dict); +} + +static NMConnection * +new_test_connection(void) +{ + NMConnection *connection; + NMSetting * setting; + char * uuid; + guint64 timestamp = time(NULL); + + connection = nm_simple_connection_new(); + + setting = nm_setting_connection_new(); + uuid = nm_utils_uuid_generate(); + g_object_set(G_OBJECT(setting), + NM_SETTING_CONNECTION_ID, + "foobar", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_CONNECTION_TIMESTAMP, + timestamp, + NULL); + g_free(uuid); + nm_connection_add_setting(connection, setting); + + setting = nm_setting_wired_new(); + g_object_set(G_OBJECT(setting), NM_SETTING_WIRED_MTU, 1592, NULL); + nm_connection_add_setting(connection, setting); + + setting = nm_setting_ip4_config_new(); + g_object_set(G_OBJECT(setting), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, + "eyeofthetiger", + NULL); + nm_connection_add_setting(connection, setting); + + return connection; +} + +static GVariant * +new_connection_dict(char ** out_uuid, + const char **out_expected_id, + const char **out_expected_ip6_method) +{ + GVariantBuilder conn_builder, setting_builder; + + g_variant_builder_init(&conn_builder, NM_VARIANT_TYPE_CONNECTION); + + *out_uuid = nm_utils_uuid_generate(); + *out_expected_id = "My happy connection"; + *out_expected_ip6_method = NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL; + + /* Connection setting */ + g_variant_builder_init(&setting_builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_add(&setting_builder, + "{sv}", + NM_SETTING_NAME, + g_variant_new_string(NM_SETTING_CONNECTION_SETTING_NAME)); + g_variant_builder_add(&setting_builder, + "{sv}", + NM_SETTING_CONNECTION_ID, + g_variant_new_string(*out_expected_id)); + g_variant_builder_add(&setting_builder, + "{sv}", + NM_SETTING_CONNECTION_UUID, + g_variant_new_string(*out_uuid)); + g_variant_builder_add(&setting_builder, + "{sv}", + NM_SETTING_CONNECTION_TYPE, + g_variant_new_string(NM_SETTING_WIRED_SETTING_NAME)); + + g_variant_builder_add(&conn_builder, + "{sa{sv}}", + NM_SETTING_CONNECTION_SETTING_NAME, + &setting_builder); + + /* Wired setting */ + g_variant_builder_init(&setting_builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_add(&conn_builder, + "{sa{sv}}", + NM_SETTING_WIRED_SETTING_NAME, + &setting_builder); + + /* IP6 */ + g_variant_builder_init(&setting_builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_add(&setting_builder, + "{sv}", + NM_SETTING_IP_CONFIG_METHOD, + g_variant_new_string(*out_expected_ip6_method)); + g_variant_builder_add(&conn_builder, + "{sa{sv}}", + NM_SETTING_IP6_CONFIG_SETTING_NAME, + &setting_builder); + + return g_variant_builder_end(&conn_builder); +} + +static void +test_connection_replace_settings(void) +{ + NMConnection * connection; + GVariant * new_settings; + GError * error = NULL; + gboolean success; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip6; + char * uuid = NULL; + const char * expected_id = NULL, *expected_method = NULL; + + connection = new_test_connection(); + + new_settings = new_connection_dict(&uuid, &expected_id, &expected_method); + g_assert(new_settings); + + /* Replace settings and test */ + success = nm_connection_replace_settings(connection, new_settings, &error); + g_assert_no_error(error); + g_assert(success); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, uuid); + + g_assert(nm_connection_get_setting_wired(connection)); + g_assert(!nm_connection_get_setting_ip4_config(connection)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, expected_method); + + g_free(uuid); + g_variant_unref(new_settings); + g_object_unref(connection); +} + +static void +test_connection_replace_settings_from_connection(void) +{ + NMConnection * connection, *replacement; + NMSettingConnection *s_con; + NMSetting * setting; + GBytes * ssid; + char * uuid = NULL; + const char * expected_id = "Awesome connection"; + + connection = new_test_connection(); + g_assert(connection); + + replacement = nm_simple_connection_new(); + g_assert(replacement); + + /* New connection setting */ + setting = nm_setting_connection_new(); + g_assert(setting); + + uuid = nm_utils_uuid_generate(); + g_object_set(setting, + NM_SETTING_CONNECTION_ID, + expected_id, + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + nm_connection_add_setting(replacement, setting); + + /* New wifi setting */ + setting = nm_setting_wireless_new(); + g_assert(setting); + + ssid = g_bytes_new("1234567", 7); + g_object_set(setting, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + g_bytes_unref(ssid); + nm_connection_add_setting(replacement, setting); + + /* Replace settings and test */ + nm_connection_replace_settings_from_connection(connection, replacement); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, uuid); + + g_assert(!nm_connection_get_setting_wired(connection)); + g_assert(!nm_connection_get_setting_ip6_config(connection)); + g_assert(nm_connection_get_setting_wireless(connection)); + + g_free(uuid); + g_object_unref(replacement); + g_object_unref(connection); +} + +static void +test_connection_replace_settings_bad(void) +{ + NMConnection * connection, *new_connection; + GVariant * new_settings; + GVariantBuilder builder, setting_builder; + GError * error = NULL; + gboolean success; + NMSettingConnection *s_con; + + new_connection = new_test_connection(); + g_assert(nm_connection_verify(new_connection, NULL)); + s_con = nm_connection_get_setting_connection(new_connection); + g_object_set(s_con, + NM_SETTING_CONNECTION_UUID, + NULL, + NM_SETTING_CONNECTION_ID, + "bad-connection", + NULL); + g_assert(!nm_connection_verify(new_connection, NULL)); + + /* nm_connection_replace_settings_from_connection() should succeed */ + connection = new_test_connection(); + nm_connection_replace_settings_from_connection(connection, new_connection); + g_assert_cmpstr(nm_connection_get_id(connection), ==, "bad-connection"); + g_assert(!nm_connection_verify(connection, NULL)); + g_object_unref(connection); + + /* nm_connection_replace_settings() should succeed */ + new_settings = nm_connection_to_dbus(new_connection, NM_CONNECTION_SERIALIZE_ALL); + g_assert(new_settings != NULL); + + connection = new_test_connection(); + success = nm_connection_replace_settings(connection, new_settings, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpstr(nm_connection_get_id(connection), ==, "bad-connection"); + g_assert(!nm_connection_verify(connection, NULL)); + g_object_unref(connection); + g_variant_unref(new_settings); + + /* But given an invalid dict, it should fail */ + g_variant_builder_init(&builder, NM_VARIANT_TYPE_CONNECTION); + g_variant_builder_init(&setting_builder, NM_VARIANT_TYPE_SETTING); + g_variant_builder_add(&builder, "{sa{sv}}", "ip-over-avian-carrier", &setting_builder); + new_settings = g_variant_builder_end(&builder); + + connection = new_test_connection(); + success = nm_connection_replace_settings(connection, new_settings, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING); + g_clear_error(&error); + g_assert(!success); + + g_assert(nm_connection_verify(connection, NULL)); + g_object_unref(connection); + + g_variant_unref(new_settings); + g_object_unref(new_connection); +} + +static void +test_connection_new_from_dbus(void) +{ + NMConnection * connection; + GVariant * new_settings; + GError * error = NULL; + NMSettingConnection *s_con; + NMSettingIPConfig * s_ip6; + char * uuid = NULL; + const char * expected_id = NULL, *expected_method = NULL; + + new_settings = new_connection_dict(&uuid, &expected_id, &expected_method); + g_assert(new_settings); + + /* Replace settings and test */ + connection = _connection_new_from_dbus(new_settings, &error); + g_assert_no_error(error); + g_assert(connection); + + s_con = nm_connection_get_setting_connection(connection); + g_assert(s_con); + g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id); + g_assert_cmpstr(nm_setting_connection_get_uuid(s_con), ==, uuid); + + g_assert(nm_connection_get_setting_wired(connection)); + g_assert(nm_connection_get_setting_ip4_config(connection)); + + s_ip6 = nm_connection_get_setting_ip6_config(connection); + g_assert(s_ip6); + g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, expected_method); + + g_free(uuid); + g_variant_unref(new_settings); + g_object_unref(connection); +} + +static void +check_permission(NMSettingConnection *s_con, guint32 idx, const char *expected_uname) +{ + gboolean success; + const char *ptype = NULL, *pitem = NULL, *detail = NULL; + + success = nm_setting_connection_get_permission(s_con, idx, &ptype, &pitem, &detail); + g_assert(success); + + g_assert_cmpstr(ptype, ==, "user"); + + g_assert(pitem); + g_assert_cmpstr(pitem, ==, expected_uname); + + g_assert(!detail); +} + +#define TEST_UNAME "asdfasfasdf" + +static void +test_setting_connection_permissions_helpers(void) +{ + NMSettingConnection *s_con; + gboolean success; + char buf[9] = {0x61, 0x62, 0x63, 0xff, 0xfe, 0xfd, 0x23, 0x01, 0x00}; + char ** perms; + const char * expected_perm = "user:" TEST_UNAME ":"; + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + + /* Ensure a bad [type] is rejected */ + success = nm_setting_connection_add_permission(s_con, "foobar", "blah", NULL); + g_assert(!success); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 0); + + /* Ensure a bad [type] is rejected */ + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(ptype)); + success = nm_setting_connection_add_permission(s_con, NULL, "blah", NULL); + g_test_assert_expected_messages(); + g_assert(!success); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 0); + + /* Ensure a bad [item] is rejected */ + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(pitem)); + success = nm_setting_connection_add_permission(s_con, "user", NULL, NULL); + g_test_assert_expected_messages(); + g_assert(!success); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 0); + + /* Ensure a bad [item] is rejected */ + success = nm_setting_connection_add_permission(s_con, "user", "", NULL); + g_assert(!success); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 0); + + /* Ensure an [item] with ':' is rejected */ + success = nm_setting_connection_add_permission(s_con, "user", "ad:asdf", NULL); + g_assert(!success); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 0); + + /* Ensure a non-UTF-8 [item] is rejected */ + success = nm_setting_connection_add_permission(s_con, "user", buf, NULL); + g_assert(!success); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 0); + + /* Ensure a non-NULL [detail] is rejected */ + success = nm_setting_connection_add_permission(s_con, "user", "dafasdf", "asdf"); + g_assert(!success); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 0); + + /* Ensure a valid call results in success */ + success = nm_setting_connection_add_permission(s_con, "user", TEST_UNAME, NULL); + g_assert(success); + + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + + check_permission(s_con, 0, TEST_UNAME); + + /* Check the actual GObject property just to be paranoid */ + g_object_get(G_OBJECT(s_con), NM_SETTING_CONNECTION_PERMISSIONS, &perms, NULL); + g_assert(perms); + g_assert_cmpint(g_strv_length(perms), ==, 1); + g_assert_cmpstr(perms[0], ==, expected_perm); + g_strfreev(perms); + + /* Now remove that permission and ensure we have 0 permissions */ + nm_setting_connection_remove_permission(s_con, 0); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 0); + + g_object_unref(s_con); +} + +static void +add_permission_property(NMSettingConnection *s_con, + const char * ptype, + const char * pitem, + int pitem_len, + const char * detail) +{ + GString *str; + char * perms[2]; + + str = g_string_sized_new(50); + if (ptype) + g_string_append(str, ptype); + g_string_append_c(str, ':'); + + if (pitem) { + if (pitem_len >= 0) + g_string_append_len(str, pitem, pitem_len); + else + g_string_append(str, pitem); + } + + g_string_append_c(str, ':'); + + if (detail) + g_string_append(str, detail); + + perms[0] = str->str; + perms[1] = NULL; + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_PERMISSIONS, perms, NULL); + + g_string_free(str, TRUE); +} + +static void +test_setting_connection_permissions_property(void) +{ + gs_unref_object NMSettingConnection *s_con = NULL; + gboolean success; + char buf[9] = {0x61, 0x62, 0x63, 0xff, 0xfe, 0xfd, 0x23, 0x01, 0x00}; + + s_con = NM_SETTING_CONNECTION(nm_setting_connection_new()); + +#define _assert_permission_invalid_at_idx(s_con, idx, expected_item) \ + G_STMT_START \ + { \ + NMSettingConnection *_s_con = (s_con); \ + guint _idx = (idx); \ + const char * _ptype; \ + const char * _pitem; \ + const char * _detail; \ + const char ** _p_ptype = nmtst_get_rand_bool() ? &_ptype : NULL; \ + const char ** _p_pitem = nmtst_get_rand_bool() ? &_pitem : NULL; \ + const char ** _p_detail = nmtst_get_rand_bool() ? &_detail : NULL; \ + \ + g_assert_cmpint(_idx, <, nm_setting_connection_get_num_permissions(_s_con)); \ + g_assert( \ + nm_setting_connection_get_permission(_s_con, _idx, _p_ptype, _p_pitem, _p_detail)); \ + if (_p_ptype) \ + g_assert_cmpstr(_ptype, ==, "invalid"); \ + if (_p_pitem) { \ + const char *_expected_item = (expected_item); \ + \ + if (!_expected_item) \ + g_assert_cmpstr(_pitem, !=, NULL); \ + else \ + g_assert_cmpstr(_pitem, ==, _expected_item); \ + } \ + if (_p_detail) \ + g_assert_cmpstr(_detail, ==, NULL); \ + } \ + G_STMT_END + + /* Ensure a bad [type] is rejected */ + add_permission_property(s_con, "foobar", "blah", -1, NULL); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + _assert_permission_invalid_at_idx(s_con, 0, "foobar:blah:"); + + /* Ensure a bad [type] is rejected */ + add_permission_property(s_con, NULL, "blah", -1, NULL); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + _assert_permission_invalid_at_idx(s_con, 0, ":blah:"); + + /* Ensure a bad [item] is rejected */ + add_permission_property(s_con, "user", NULL, -1, NULL); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + _assert_permission_invalid_at_idx(s_con, 0, "user::"); + + /* Ensure a bad [item] is rejected */ + add_permission_property(s_con, "user", "", -1, NULL); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + _assert_permission_invalid_at_idx(s_con, 0, "user::"); + + /* Ensure an [item] with ':' in the middle is rejected */ + add_permission_property(s_con, "user", "ad:asdf", -1, NULL); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + _assert_permission_invalid_at_idx(s_con, 0, "user:ad:asdf:"); + + /* Ensure an [item] with ':' at the end is rejected */ + add_permission_property(s_con, "user", "adasdfaf:", -1, NULL); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + _assert_permission_invalid_at_idx(s_con, 0, "user:adasdfaf::"); + + /* Ensure a non-UTF-8 [item] is rejected */ + add_permission_property(s_con, "user", buf, (int) sizeof(buf), NULL); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + _assert_permission_invalid_at_idx(s_con, 0, NULL); + + /* Ensure a non-NULL [detail] is rejected */ + add_permission_property(s_con, "user", "dafasdf", -1, "asdf"); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); + _assert_permission_invalid_at_idx(s_con, 0, "user:dafasdf:asdf"); + + /* Ensure a valid call results in success */ + success = nm_setting_connection_add_permission(s_con, "user", TEST_UNAME, NULL); + g_assert(success); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 2); + _assert_permission_invalid_at_idx(s_con, 0, "user:dafasdf:asdf"); + check_permission(s_con, 1, TEST_UNAME); + + /* Now remove that permission and ensure we have 0 permissions */ + nm_setting_connection_remove_permission(s_con, 0); + g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1); +} + +static void +test_connection_compare_same(void) +{ + NMConnection *a, *b; + + a = new_test_connection(); + b = nm_simple_connection_new_clone(a); + g_assert(nm_connection_compare(a, b, NM_SETTING_COMPARE_FLAG_EXACT)); + g_object_unref(a); + g_object_unref(b); +} + +static void +test_connection_compare_key_only_in_a(void) +{ + NMConnection * a, *b; + NMSettingConnection *s_con; + + a = new_test_connection(); + b = nm_simple_connection_new_clone(a); + s_con = (NMSettingConnection *) nm_connection_get_setting(b, NM_TYPE_SETTING_CONNECTION); + g_assert(s_con); + g_object_set(s_con, NM_SETTING_CONNECTION_TIMESTAMP, (guint64) 0, NULL); + + g_assert(!nm_connection_compare(a, b, NM_SETTING_COMPARE_FLAG_EXACT)); + g_object_unref(a); + g_object_unref(b); +} + +static void +test_connection_compare_setting_only_in_a(void) +{ + NMConnection *a, *b; + + a = new_test_connection(); + b = nm_simple_connection_new_clone(a); + nm_connection_remove_setting(b, NM_TYPE_SETTING_IP4_CONFIG); + g_assert(!nm_connection_compare(a, b, NM_SETTING_COMPARE_FLAG_EXACT)); + g_object_unref(a); + g_object_unref(b); +} + +static void +test_connection_compare_key_only_in_b(void) +{ + NMConnection * a, *b; + NMSettingConnection *s_con; + + a = new_test_connection(); + b = nm_simple_connection_new_clone(a); + s_con = (NMSettingConnection *) nm_connection_get_setting(b, NM_TYPE_SETTING_CONNECTION); + g_assert(s_con); + g_object_set(s_con, NM_SETTING_CONNECTION_TIMESTAMP, (guint64) 0, NULL); + + g_assert(!nm_connection_compare(a, b, NM_SETTING_COMPARE_FLAG_EXACT)); + g_object_unref(a); + g_object_unref(b); +} + +static void +test_connection_compare_setting_only_in_b(void) +{ + NMConnection *a, *b; + + a = new_test_connection(); + b = nm_simple_connection_new_clone(a); + nm_connection_remove_setting(a, NM_TYPE_SETTING_IP4_CONFIG); + g_assert(!nm_connection_compare(a, b, NM_SETTING_COMPARE_FLAG_EXACT)); + g_object_unref(a); + g_object_unref(b); +} + +typedef struct { + const char *key_name; + guint32 result; +} DiffKey; + +typedef struct { + const char *name; + DiffKey keys[30]; +} DiffSetting; + +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) + +static void +ensure_diffs(GHashTable *diffs, const DiffSetting *check, gsize n_check) +{ + guint i; + + g_assert(g_hash_table_size(diffs) == n_check); + + /* Loop through the settings */ + for (i = 0; i < n_check; i++) { + GHashTable *setting_hash; + guint z = 0; + + setting_hash = g_hash_table_lookup(diffs, check[i].name); + g_assert(setting_hash); + + /* Get the number of keys to check */ + while (check[i].keys[z].key_name) + z++; + g_assert(g_hash_table_size(setting_hash) == z); + + /* Now compare the actual keys */ + for (z = 0; check[i].keys[z].key_name; z++) { + NMSettingDiffResult result; + + result = GPOINTER_TO_UINT(g_hash_table_lookup(setting_hash, check[i].keys[z].key_name)); + g_assert(result == check[i].keys[z].result); + } + } +} + +static void +test_connection_diff_a_only(void) +{ + NMConnection * connection; + GHashTable * out_diffs = NULL; + gboolean same; + const DiffSetting settings[] = { + {NM_SETTING_CONNECTION_SETTING_NAME, + {{NM_SETTING_CONNECTION_ID, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_UUID, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_STABLE_ID, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_INTERFACE_NAME, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_TYPE, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_TIMESTAMP, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_AUTOCONNECT, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_AUTOCONNECT_PRIORITY, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_AUTOCONNECT_RETRIES, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_MULTI_CONNECT, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_READ_ONLY, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_PERMISSIONS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_ZONE, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_MASTER, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_SECONDARIES, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_METERED, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_LLDP, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_AUTH_RETRIES, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_MDNS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_LLMNR, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_MUD_URL, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A}, + {NULL, NM_SETTING_DIFF_RESULT_UNKNOWN}}}, + {NM_SETTING_WIRED_SETTING_NAME, + { + {NM_SETTING_WIRED_PORT, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_SPEED, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_DUPLEX, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_AUTO_NEGOTIATE, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_MAC_ADDRESS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_CLONED_MAC_ADDRESS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_GENERATE_MAC_ADDRESS_MASK, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_MTU, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_S390_SUBCHANNELS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_S390_NETTYPE, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_S390_OPTIONS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_WAKE_ON_LAN, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_WIRED_ACCEPT_ALL_MAC_ADDRESSES, NM_SETTING_DIFF_RESULT_IN_A}, + {NULL, NM_SETTING_DIFF_RESULT_UNKNOWN}, + }}, + {NM_SETTING_IP4_CONFIG_SETTING_NAME, + { + {NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DNS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DNS_SEARCH, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DNS_OPTIONS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_ADDRESSES, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_GATEWAY, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_ROUTES, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_ROUTE_METRIC, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_ROUTE_TABLE, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_ROUTING_RULES, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_IGNORE_AUTO_ROUTES, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP4_CONFIG_DHCP_FQDN, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_NEVER_DEFAULT, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_MAY_FAIL, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DAD_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DNS_PRIORITY, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DHCP_IAID, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER, NM_SETTING_DIFF_RESULT_IN_A}, + {NM_SETTING_IP_CONFIG_DHCP_REJECT_SERVERS, NM_SETTING_DIFF_RESULT_IN_A}, + {NULL, NM_SETTING_DIFF_RESULT_UNKNOWN}, + }}, + }; + + connection = new_test_connection(); + + same = nm_connection_diff(connection, NULL, NM_SETTING_COMPARE_FLAG_EXACT, &out_diffs); + g_assert(same == FALSE); + g_assert(out_diffs != NULL); + g_assert(g_hash_table_size(out_diffs) > 0); + + ensure_diffs(out_diffs, settings, ARRAY_LEN(settings)); + + g_hash_table_destroy(out_diffs); + g_object_unref(connection); +} + +static void +test_connection_diff_same(void) +{ + NMConnection *a, *b; + GHashTable * out_diffs = NULL; + gboolean same; + + a = new_test_connection(); + b = nm_simple_connection_new_clone(a); + + same = nm_connection_diff(a, b, NM_SETTING_COMPARE_FLAG_EXACT, &out_diffs); + g_assert(same == TRUE); + g_assert(out_diffs == NULL); + g_object_unref(a); + g_object_unref(b); +} + +static void +test_connection_diff_different(void) +{ + NMConnection * a, *b; + GHashTable * out_diffs = NULL; + NMSettingIPConfig *s_ip4; + gboolean same; + const DiffSetting settings[] = { + {NM_SETTING_IP4_CONFIG_SETTING_NAME, + { + {NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_DIFF_RESULT_IN_A | NM_SETTING_DIFF_RESULT_IN_B}, + {NULL, NM_SETTING_DIFF_RESULT_UNKNOWN}, + }}, + }; + + a = new_test_connection(); + b = nm_simple_connection_new_clone(a); + s_ip4 = nm_connection_get_setting_ip4_config(a); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + + same = nm_connection_diff(a, b, NM_SETTING_COMPARE_FLAG_EXACT, &out_diffs); + g_assert(same == FALSE); + g_assert(out_diffs != NULL); + g_assert(g_hash_table_size(out_diffs) > 0); + + ensure_diffs(out_diffs, settings, ARRAY_LEN(settings)); + + g_hash_table_destroy(out_diffs); + g_object_unref(a); + g_object_unref(b); +} + +static void +test_connection_diff_no_secrets(void) +{ + NMConnection * a, *b; + GHashTable * out_diffs = NULL; + NMSetting * s_pppoe; + gboolean same; + const DiffSetting settings[] = { + {NM_SETTING_PPPOE_SETTING_NAME, + { + {NM_SETTING_PPPOE_PASSWORD, NM_SETTING_DIFF_RESULT_IN_B}, + {NULL, NM_SETTING_DIFF_RESULT_UNKNOWN}, + }}, + }; + + a = new_test_connection(); + s_pppoe = nm_setting_pppoe_new(); + g_object_set(G_OBJECT(s_pppoe), NM_SETTING_PPPOE_USERNAME, "thomas", NULL); + nm_connection_add_setting(a, s_pppoe); + + b = nm_simple_connection_new_clone(a); + + /* Add a secret to B */ + s_pppoe = NM_SETTING(nm_connection_get_setting_pppoe(b)); + g_assert(s_pppoe); + g_object_set(G_OBJECT(s_pppoe), NM_SETTING_PPPOE_PASSWORD, "secretpassword", NULL); + + /* Make sure the diff returns no results as secrets are ignored */ + same = nm_connection_diff(a, b, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS, &out_diffs); + g_assert(same == TRUE); + g_assert(out_diffs == NULL); + + /* Now make sure the diff returns results if secrets are not ignored */ + same = nm_connection_diff(a, b, NM_SETTING_COMPARE_FLAG_EXACT, &out_diffs); + g_assert(same == FALSE); + g_assert(out_diffs != NULL); + g_assert(g_hash_table_size(out_diffs) > 0); + + ensure_diffs(out_diffs, settings, ARRAY_LEN(settings)); + + g_hash_table_destroy(out_diffs); + g_object_unref(a); + g_object_unref(b); +} + +static void +test_connection_diff_inferrable(void) +{ + NMConnection * a, *b; + GHashTable * out_diffs = NULL; + gboolean same; + NMSettingConnection *s_con; + NMSettingWired * s_wired; + NMSettingIPConfig * s_ip4; + char * uuid; + const DiffSetting settings[] = { + {NM_SETTING_CONNECTION_SETTING_NAME, + { + {NM_SETTING_CONNECTION_INTERFACE_NAME, NM_SETTING_DIFF_RESULT_IN_A}, + {NULL, NM_SETTING_DIFF_RESULT_UNKNOWN}, + }}, + }; + + a = new_test_connection(); + b = nm_simple_connection_new_clone(a); + + /* Change the UUID, wired MTU, and set ignore-auto-dns */ + s_con = nm_connection_get_setting_connection(a); + g_assert(s_con); + uuid = nm_utils_uuid_generate(); + g_object_set(G_OBJECT(s_con), + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_ID, + "really neat connection", + NULL); + g_free(uuid); + + s_wired = nm_connection_get_setting_wired(a); + g_assert(s_wired); + g_object_set(G_OBJECT(s_wired), NM_SETTING_WIRED_MTU, 300, NULL); + + s_ip4 = nm_connection_get_setting_ip4_config(a); + g_assert(s_ip4); + g_object_set(G_OBJECT(s_ip4), NM_SETTING_IP_CONFIG_IGNORE_AUTO_DNS, TRUE, NULL); + + /* Make sure the diff returns no results as secrets are ignored */ + same = nm_connection_diff(a, b, NM_SETTING_COMPARE_FLAG_INFERRABLE, &out_diffs); + g_assert(same == TRUE); + g_assert(out_diffs == NULL); + + /* And change a INFERRABLE property to ensure that it shows up in the diff results */ + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, "usb0", NULL); + + /* Make sure the diff returns no results as secrets are ignored */ + same = nm_connection_diff(a, b, NM_SETTING_COMPARE_FLAG_INFERRABLE, &out_diffs); + g_assert(same == FALSE); + g_assert(out_diffs != NULL); + g_assert(g_hash_table_size(out_diffs) > 0); + + ensure_diffs(out_diffs, settings, ARRAY_LEN(settings)); + + g_hash_table_destroy(out_diffs); + g_object_unref(a); + g_object_unref(b); +} + +static void +add_generic_settings(NMConnection *connection, const char *ctype) +{ + NMSetting *setting; + char * uuid; + + uuid = nm_utils_uuid_generate(); + + setting = nm_setting_connection_new(); + g_object_set(setting, + NM_SETTING_CONNECTION_ID, + "asdfasdfadf", + NM_SETTING_CONNECTION_TYPE, + ctype, + NM_SETTING_CONNECTION_UUID, + uuid, + NULL); + nm_connection_add_setting(connection, setting); + + g_free(uuid); + + setting = nm_setting_ip4_config_new(); + g_object_set(setting, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + nm_connection_add_setting(connection, setting); + + setting = nm_setting_ip6_config_new(); + g_object_set(setting, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + nm_connection_add_setting(connection, setting); +} + +static void +test_connection_good_base_types(void) +{ + NMConnection *connection; + NMSetting * setting; + gboolean success; + GError * error = NULL; + GBytes * ssid; + const char * bdaddr = "11:22:33:44:55:66"; + + /* Try a basic wired connection */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_WIRED_SETTING_NAME); + setting = nm_setting_wired_new(); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + g_object_unref(connection); + + /* Try a wired PPPoE connection */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_PPPOE_SETTING_NAME); + setting = nm_setting_pppoe_new(); + g_object_set(setting, NM_SETTING_PPPOE_USERNAME, "bob smith", NULL); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + g_object_unref(connection); + + /* Wifi connection */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_WIRELESS_SETTING_NAME); + + setting = nm_setting_wireless_new(); + ssid = g_bytes_new("1234567", 7); + g_object_set(setting, + NM_SETTING_WIRELESS_SSID, + ssid, + NM_SETTING_WIRELESS_MODE, + "infrastructure", + NULL); + g_bytes_unref(ssid); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + g_object_unref(connection); + + /* Bluetooth connection */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_BLUETOOTH_SETTING_NAME); + + setting = nm_setting_bluetooth_new(); + g_object_set(setting, + NM_SETTING_BLUETOOTH_BDADDR, + bdaddr, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_BLUETOOTH_TYPE_PANU, + NULL); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + g_object_unref(connection); + + /* WiMAX connection */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_WIMAX_SETTING_NAME); + setting = nm_setting_wimax_new(); + g_object_set(setting, NM_SETTING_WIMAX_NETWORK_NAME, "CLEAR", NULL); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + g_object_unref(connection); + + /* GSM connection */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_GSM_SETTING_NAME); + + setting = nm_setting_gsm_new(); + g_object_set(setting, NM_SETTING_GSM_APN, "metered.billing.sucks", NULL); + nm_connection_add_setting(connection, setting); + + /* CDMA connection */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_CDMA_SETTING_NAME); + + setting = nm_setting_cdma_new(); + g_object_set(setting, + NM_SETTING_CDMA_NUMBER, + "#777", + NM_SETTING_CDMA_USERNAME, + "foobar@vzw.com", + NULL); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_no_error(error); + g_assert(success); + g_object_unref(connection); +} + +static void +test_connection_bad_base_types(void) +{ + NMConnection *connection; + NMSetting * setting; + gboolean success; + GError * error = NULL; + + /* Test various non-base connection types to make sure they are rejected; + * using a fake 'wired' connection so the rest of it verifies + */ + + /* Connection setting */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_CONNECTION_SETTING_NAME); + setting = nm_setting_wired_new(); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "connection.type: ")); + g_assert(success == FALSE); + g_object_unref(connection); + g_clear_error(&error); + + /* PPP setting */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_PPP_SETTING_NAME); + setting = nm_setting_wired_new(); + nm_connection_add_setting(connection, setting); + setting = nm_setting_ppp_new(); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "connection.type: ")); + g_assert(success == FALSE); + g_object_unref(connection); + g_clear_error(&error); + + /* Serial setting */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_SERIAL_SETTING_NAME); + setting = nm_setting_wired_new(); + nm_connection_add_setting(connection, setting); + setting = nm_setting_serial_new(); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "connection.type: ")); + g_assert(success == FALSE); + g_object_unref(connection); + g_clear_error(&error); + + /* IP4 setting */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_IP4_CONFIG_SETTING_NAME); + setting = nm_setting_wired_new(); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "connection.type: ")); + g_assert(success == FALSE); + g_object_unref(connection); + g_clear_error(&error); + + /* IP6 setting */ + connection = nm_simple_connection_new(); + add_generic_settings(connection, NM_SETTING_IP6_CONFIG_SETTING_NAME); + setting = nm_setting_wired_new(); + nm_connection_add_setting(connection, setting); + + success = nm_connection_verify(connection, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(g_str_has_prefix(error->message, "connection.type: ")); + g_assert(success == FALSE); + g_object_unref(connection); + g_clear_error(&error); +} + +static void +test_setting_compare_id(void) +{ + gs_unref_object NMSetting *old = NULL, *new = NULL; + gboolean success; + + old = nm_setting_connection_new(); + g_object_set(old, + NM_SETTING_CONNECTION_ID, + "really awesome cool connection", + NM_SETTING_CONNECTION_UUID, + "fbbd59d5-acab-4e30-8f86-258d272617e7", + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NULL); + + new = nm_setting_duplicate(old); + g_object_set(new, NM_SETTING_CONNECTION_ID, "some different connection id", NULL); + + /* First make sure they are different */ + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(success == FALSE); + + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_IGNORE_ID); + g_assert(success); +} + +static void +test_setting_compare_addresses(void) +{ + gs_unref_object NMSetting *s1 = NULL, *s2 = NULL; + gboolean success; + NMIPAddress * a; + GHashTable * result = NULL; + + s1 = nm_setting_ip4_config_new(); + s2 = nm_setting_ip4_config_new(); + + a = nm_ip_address_new(AF_INET, "192.168.7.5", 24, NULL); + + nm_ip_address_set_attribute(a, NM_IP_ADDRESS_ATTRIBUTE_LABEL, g_variant_new_string("xoxoxo")); + nm_setting_ip_config_add_address((NMSettingIPConfig *) s1, a); + + nm_ip_address_set_attribute(a, NM_IP_ADDRESS_ATTRIBUTE_LABEL, g_variant_new_string("hello")); + nm_setting_ip_config_add_address((NMSettingIPConfig *) s2, a); + + nm_ip_address_unref(a); + + if (nmtst_get_rand_uint32() % 2) + NM_SWAP(&s1, &s2); + + success = nm_setting_compare(s1, s2, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(!success); + + success = nm_setting_diff(s1, s2, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, &result); + g_assert(!success); + nm_clear_pointer(&result, g_hash_table_unref); +} + +static void +test_setting_compare_routes(void) +{ + gs_unref_object NMSetting *s1 = NULL, *s2 = NULL; + gboolean success; + NMIPRoute * r; + GHashTable * result = NULL; + + s1 = nm_setting_ip4_config_new(); + s2 = nm_setting_ip4_config_new(); + + r = nm_ip_route_new(AF_INET, "192.168.12.0", 24, "192.168.11.1", 473, NULL); + + nm_ip_route_set_attribute(r, NM_IP_ADDRESS_ATTRIBUTE_LABEL, g_variant_new_string("xoxoxo")); + nm_setting_ip_config_add_route((NMSettingIPConfig *) s1, r); + + nm_ip_route_set_attribute(r, NM_IP_ADDRESS_ATTRIBUTE_LABEL, g_variant_new_string("hello")); + nm_setting_ip_config_add_route((NMSettingIPConfig *) s2, r); + + nm_ip_route_unref(r); + + if (nmtst_get_rand_uint32() % 2) + NM_SWAP(&s1, &s2); + + success = nm_setting_compare(s1, s2, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(!success); + + success = nm_setting_diff(s1, s2, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, &result); + g_assert(!success); + nm_clear_pointer(&result, g_hash_table_unref); +} + +static void +test_setting_compare_wired_cloned_mac_address(void) +{ + gs_unref_object NMSetting *old = NULL, *new = NULL; + gboolean success; + gs_free char * str1 = NULL; + + old = nm_setting_wired_new(); + g_object_set(old, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "stable", NULL); + + g_assert_cmpstr("stable", ==, nm_setting_wired_get_cloned_mac_address((NMSettingWired *) old)); + g_object_get(old, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL); + g_assert_cmpstr("stable", ==, str1); + nm_clear_g_free(&str1); + + new = nm_setting_duplicate(old); + g_object_set(new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "11:22:33:44:55:66", NULL); + + g_assert_cmpstr("11:22:33:44:55:66", + ==, + nm_setting_wired_get_cloned_mac_address((NMSettingWired *) new)); + g_object_get(new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL); + g_assert_cmpstr("11:22:33:44:55:66", ==, str1); + nm_clear_g_free(&str1); + + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(!success); + g_clear_object(&new); + + new = nm_setting_duplicate(old); + g_object_set(new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, "stable-bia", NULL); + + g_assert_cmpstr("stable-bia", + ==, + nm_setting_wired_get_cloned_mac_address((NMSettingWired *) new)); + g_object_get(new, NM_SETTING_WIRED_CLONED_MAC_ADDRESS, &str1, NULL); + g_assert_cmpstr("stable-bia", ==, str1); + nm_clear_g_free(&str1); + + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(!success); + g_clear_object(&new); +} + +static void +test_setting_compare_wireless_cloned_mac_address(void) +{ + gs_unref_object NMSetting *old = NULL, *new = NULL; + gboolean success; + gs_free char * str1 = NULL; + + old = nm_setting_wireless_new(); + g_object_set(old, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, "stable", NULL); + + g_assert_cmpstr("stable", + ==, + nm_setting_wireless_get_cloned_mac_address((NMSettingWireless *) old)); + g_object_get(old, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL); + g_assert_cmpstr("stable", ==, str1); + nm_clear_g_free(&str1); + + new = nm_setting_duplicate(old); + g_object_set(new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, "11:22:33:44:55:66", NULL); + + g_assert_cmpstr("11:22:33:44:55:66", + ==, + nm_setting_wireless_get_cloned_mac_address((NMSettingWireless *) new)); + g_object_get(new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL); + g_assert_cmpstr("11:22:33:44:55:66", ==, str1); + nm_clear_g_free(&str1); + + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(!success); + g_clear_object(&new); + + new = nm_setting_duplicate(old); + g_object_set(new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, "stable-bia", NULL); + + g_assert_cmpstr("stable-bia", + ==, + nm_setting_wireless_get_cloned_mac_address((NMSettingWireless *) new)); + g_object_get(new, NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS, &str1, NULL); + g_assert_cmpstr("stable-bia", ==, str1); + nm_clear_g_free(&str1); + + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(!success); + g_clear_object(&new); +} + +static void +test_setting_compare_timestamp(void) +{ + gs_unref_object NMSetting *old = NULL, *new = NULL; + gboolean success; + + old = nm_setting_connection_new(); + g_object_set(old, + NM_SETTING_CONNECTION_ID, + "ignore timestamp connection", + NM_SETTING_CONNECTION_UUID, + "b047a198-0e0a-4f0e-a653-eea09bb35e40", + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TIMESTAMP, + (guint64) 1234567890, + NULL); + + new = nm_setting_duplicate(old); + g_object_set(new, NM_SETTING_CONNECTION_TIMESTAMP, (guint64) 1416316539, NULL); + + /* First make sure they are different */ + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(success == FALSE); + + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP); + g_assert(success); +} + +typedef struct { + NMSettingSecretFlags secret_flags; + NMSettingCompareFlags comp_flags; + gboolean remove_secret; +} TestDataCompareSecrets; + +static TestDataCompareSecrets * +test_data_compare_secrets_new(NMSettingSecretFlags secret_flags, + NMSettingCompareFlags comp_flags, + gboolean remove_secret) +{ + TestDataCompareSecrets *data = g_new0(TestDataCompareSecrets, 1); + + data->secret_flags = secret_flags; + data->comp_flags = comp_flags; + data->remove_secret = remove_secret; + return data; +} + +static void +_test_compare_secrets_check_diff(NMSetting * a, + NMSetting * b, + NMSettingCompareFlags flags, + gboolean exp_same_psk, + gboolean exp_same_psk_flags) +{ + gs_unref_hashtable GHashTable *h = NULL; + NMSettingDiffResult _RESULT_IN_A = NM_SETTING_DIFF_RESULT_IN_A; + NMSettingDiffResult _RESULT_IN_B = NM_SETTING_DIFF_RESULT_IN_B; + gboolean invert_results; + gboolean diff_result; + NMSettingSecretFlags a_psk_flags = + nm_setting_wireless_security_get_psk_flags(NM_SETTING_WIRELESS_SECURITY(a)); + NMSettingSecretFlags b_psk_flags = + nm_setting_wireless_security_get_psk_flags(NM_SETTING_WIRELESS_SECURITY(b)); + const char *a_psk = nm_setting_wireless_security_get_psk(NM_SETTING_WIRELESS_SECURITY(a)); + const char *b_psk = nm_setting_wireless_security_get_psk(NM_SETTING_WIRELESS_SECURITY(b)); + + g_assert(NM_IS_SETTING_WIRELESS_SECURITY(a)); + g_assert(NM_IS_SETTING_WIRELESS_SECURITY(b)); + + invert_results = nmtst_get_rand_bool(); + if (invert_results) { + _RESULT_IN_A = NM_SETTING_DIFF_RESULT_IN_B; + _RESULT_IN_B = NM_SETTING_DIFF_RESULT_IN_A; + } + + diff_result = nm_setting_diff(a, b, flags, invert_results, &h); + + g_assert(exp_same_psk_flags == (a_psk_flags == b_psk_flags)); + + if (nm_streq0(a_psk, b_psk)) + g_assert(exp_same_psk); + else { + if (flags == NM_SETTING_COMPARE_FLAG_EXACT) + g_assert(!exp_same_psk); + else if (flags == NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS) { + if (!NM_FLAGS_HAS(a_psk_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED) + && !NM_FLAGS_HAS(b_psk_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)) + g_assert(!exp_same_psk); + else if (!NM_FLAGS_HAS(a_psk_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED) + && NM_FLAGS_HAS(b_psk_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)) + g_assert(!exp_same_psk); + else + g_assert(exp_same_psk); + } else if (flags == NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS) { + if (!NM_FLAGS_HAS(a_psk_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED) + && !NM_FLAGS_HAS(b_psk_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)) + g_assert(!exp_same_psk); + else if (!NM_FLAGS_HAS(a_psk_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED) + && NM_FLAGS_HAS(b_psk_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)) + g_assert(!exp_same_psk); + else + g_assert(exp_same_psk); + } else if (flags == NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS) + g_assert(exp_same_psk); + else + g_assert_not_reached(); + } + + g_assert(diff_result == (exp_same_psk && exp_same_psk_flags)); + g_assert(diff_result == (!h)); + + if (!diff_result) { + if (flags == NM_SETTING_COMPARE_FLAG_EXACT) + g_assert(!exp_same_psk); + else if (NM_IN_SET(flags, + NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS, + NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS) + && (a_psk_flags != b_psk_flags) + && nm_setting_wireless_security_get_psk_flags(NM_SETTING_WIRELESS_SECURITY(a)) + == NM_SETTING_SECRET_FLAG_NONE) + g_assert(!exp_same_psk); + else + g_assert(exp_same_psk); + + g_assert((!exp_same_psk) == g_hash_table_contains(h, NM_SETTING_WIRELESS_SECURITY_PSK)); + if (!exp_same_psk) { + if (nm_setting_wireless_security_get_psk(NM_SETTING_WIRELESS_SECURITY(a))) + g_assert_cmpint( + GPOINTER_TO_UINT(g_hash_table_lookup(h, NM_SETTING_WIRELESS_SECURITY_PSK)), + ==, + _RESULT_IN_A); + else + g_assert_cmpint( + GPOINTER_TO_UINT(g_hash_table_lookup(h, NM_SETTING_WIRELESS_SECURITY_PSK)), + ==, + _RESULT_IN_B); + } + + g_assert((!exp_same_psk_flags) + == g_hash_table_contains(h, NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS)); + if (!exp_same_psk_flags) { + if (nm_setting_wireless_security_get_psk_flags(NM_SETTING_WIRELESS_SECURITY(a)) + != NM_SETTING_SECRET_FLAG_NONE) + g_assert_cmpint(GPOINTER_TO_UINT( + g_hash_table_lookup(h, NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS)), + ==, + _RESULT_IN_A); + else + g_assert_cmpint(GPOINTER_TO_UINT( + g_hash_table_lookup(h, NM_SETTING_WIRELESS_SECURITY_PSK_FLAGS)), + ==, + _RESULT_IN_B); + } + + g_assert_cmpint(g_hash_table_size(h), ==, (!exp_same_psk) + (!exp_same_psk_flags)); + } + + g_assert(diff_result == nm_setting_compare(a, b, flags)); + g_assert(diff_result == nm_setting_compare(b, a, flags)); +} + +static void +test_setting_compare_secrets(gconstpointer test_data) +{ + const TestDataCompareSecrets *data = test_data; + gs_unref_object NMConnection *conn_old = NULL; + gs_unref_object NMConnection *conn_new = NULL; + gs_unref_object NMSetting *old = NULL; + gs_unref_object NMSetting *new = NULL; + + /* Make sure that a connection with transient/unsaved secrets compares + * successfully to the same connection without those secrets. + */ + + old = nm_setting_wireless_security_new(); + g_object_set(old, + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + "wpa-psk", + NM_SETTING_WIRELESS_SECURITY_PSK, + "really cool psk", + NULL); + nm_setting_set_secret_flags(old, NM_SETTING_WIRELESS_SECURITY_PSK, data->secret_flags, NULL); + + new = nm_setting_duplicate(old); + if (data->remove_secret) + g_object_set(new, NM_SETTING_WIRELESS_SECURITY_PSK, NULL, NULL); + + g_assert((!data->remove_secret) == nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_EXACT)); + g_assert((!data->remove_secret) == nm_setting_compare(new, old, NM_SETTING_COMPARE_FLAG_EXACT)); + + _test_compare_secrets_check_diff(old, + new, + NM_SETTING_COMPARE_FLAG_EXACT, + !data->remove_secret, + TRUE); + _test_compare_secrets_check_diff(new, + old, + NM_SETTING_COMPARE_FLAG_EXACT, + !data->remove_secret, + TRUE); + + g_assert(nm_setting_compare(old, new, data->comp_flags)); + g_assert(nm_setting_compare(new, old, data->comp_flags)); + + _test_compare_secrets_check_diff(old, new, data->comp_flags, TRUE, TRUE); + _test_compare_secrets_check_diff(new, old, data->comp_flags, TRUE, TRUE); + + /* OK. Try again, but this time not only change the secret, also let the secret flags differ... */ + if (data->secret_flags != NM_SETTING_SECRET_FLAG_NONE) { + nm_setting_set_secret_flags(new, + NM_SETTING_WIRELESS_SECURITY_PSK, + NM_SETTING_SECRET_FLAG_NONE, + NULL); + + _test_compare_secrets_check_diff(old, new, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, FALSE); + _test_compare_secrets_check_diff(new, old, NM_SETTING_COMPARE_FLAG_EXACT, FALSE, FALSE); + + _test_compare_secrets_check_diff(old, new, data->comp_flags, TRUE, FALSE); + _test_compare_secrets_check_diff(new, old, data->comp_flags, FALSE, FALSE); + + nm_setting_set_secret_flags(new, + NM_SETTING_WIRELESS_SECURITY_PSK, + data->secret_flags, + NULL); + } + + conn_old = nmtst_create_minimal_connection("test-compare-secrets", + NULL, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + nm_connection_add_setting(conn_old, nm_setting_duplicate(old)); + conn_new = nm_simple_connection_new_clone(conn_old); + nm_connection_add_setting(conn_new, nm_setting_duplicate(new)); + + g_assert((!data->remove_secret) + == nm_connection_compare(conn_old, conn_new, NM_SETTING_COMPARE_FLAG_EXACT)); + g_assert((!data->remove_secret) + == nm_connection_compare(conn_new, conn_old, NM_SETTING_COMPARE_FLAG_EXACT)); + + g_assert(nm_connection_compare(conn_old, conn_new, data->comp_flags)); + g_assert(nm_connection_compare(conn_new, conn_old, data->comp_flags)); +} + +static void +test_setting_compare_vpn_secrets(gconstpointer test_data) +{ + const TestDataCompareSecrets *data = test_data; + gs_unref_object NMSetting *old = NULL, *new = NULL; + gboolean success; + + /* Make sure that a connection with transient/unsaved secrets compares + * successfully to the same connection without those secrets. + */ + + old = nm_setting_vpn_new(); + nm_setting_vpn_add_secret(NM_SETTING_VPN(old), "foobarbaz", "really secret password"); + nm_setting_vpn_add_secret(NM_SETTING_VPN(old), "asdfasdfasdf", "really adfasdfasdfasdf"); + nm_setting_vpn_add_secret(NM_SETTING_VPN(old), "0123456778", "abcdefghijklmnpqrstuvqxyz"); + nm_setting_vpn_add_secret(NM_SETTING_VPN(old), + "borkbork", + "yet another really secret password"); + nm_setting_set_secret_flags(old, "borkbork", data->secret_flags, NULL); + + /* Clear "borkbork" from the duplicated setting */ + new = nm_setting_duplicate(old); + if (data->remove_secret) { + nm_setting_vpn_remove_secret(NM_SETTING_VPN(new), "borkbork"); + + /* First make sure they are different */ + success = nm_setting_compare(old, new, NM_SETTING_COMPARE_FLAG_EXACT); + g_assert(success == FALSE); + } + + success = nm_setting_compare(old, new, data->comp_flags); + g_assert(success); +} + +static void +test_hwaddr_aton_ether_normal(void) +{ + guint8 buf[100]; + guint8 expected[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + + g_assert(nm_utils_hwaddr_aton("00:11:22:33:44:55", buf, ETH_ALEN) != NULL); + g_assert(memcmp(buf, expected, sizeof(expected)) == 0); +} + +static void +test_hwaddr_aton_ib_normal(void) +{ + guint8 buf[100]; + const char *source = "00:11:22:33:44:55:66:77:88:99:01:12:23:34:45:56:67:78:89:90"; + guint8 expected[INFINIBAND_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90}; + + g_assert(nm_utils_hwaddr_aton(source, buf, INFINIBAND_ALEN) != NULL); + g_assert(memcmp(buf, expected, sizeof(expected)) == 0); +} + +static void +test_hwaddr_aton_no_leading_zeros(void) +{ + guint8 buf[100]; + guint8 expected[ETH_ALEN] = {0x00, 0x1A, 0x2B, 0x03, 0x44, 0x05}; + + g_assert(nm_utils_hwaddr_aton("0:1a:2B:3:44:5", buf, ETH_ALEN) != NULL); + g_assert(memcmp(buf, expected, sizeof(expected)) == 0); +} + +static void +test_hwaddr_aton_malformed(void) +{ + guint8 buf[100]; + + g_assert(nm_utils_hwaddr_aton("0:1a:2B:3:a@%%", buf, ETH_ALEN) == NULL); +} + +static void +test_hwaddr_equal(void) +{ + const char * string = "00:1a:2b:03:44:05"; + const char * upper_string = "00:1A:2B:03:44:05"; + const char * bad_string = "0:1a:2b:3:44:5"; + const guint8 binary[ETH_ALEN] = {0x00, 0x1A, 0x2B, 0x03, 0x44, 0x05}; + const char * other_string = "1a:2b:03:44:05:00"; + const guint8 other_binary[ETH_ALEN] = {0x1A, 0x2B, 0x03, 0x44, 0x05, 0x00}; + const char * long_string = "00:1a:2b:03:44:05:06:07"; + const guint8 long_binary[8] = {0x00, 0x1A, 0x2B, 0x03, 0x44, 0x05, 0x06, 0x07}; + const char * null_string = "00:00:00:00:00:00"; + const guint8 null_binary[ETH_ALEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + g_assert(nm_utils_hwaddr_matches(string, -1, string, -1)); + g_assert(nm_utils_hwaddr_matches(string, -1, upper_string, -1)); + g_assert(nm_utils_hwaddr_matches(string, -1, bad_string, -1)); + g_assert(nm_utils_hwaddr_matches(string, -1, binary, sizeof(binary))); + g_assert(!nm_utils_hwaddr_matches(string, -1, other_string, -1)); + g_assert(!nm_utils_hwaddr_matches(string, -1, other_binary, sizeof(other_binary))); + g_assert(!nm_utils_hwaddr_matches(string, -1, long_string, -1)); + g_assert(!nm_utils_hwaddr_matches(string, -1, long_binary, sizeof(long_binary))); + g_assert(!nm_utils_hwaddr_matches(string, -1, null_string, -1)); + g_assert(!nm_utils_hwaddr_matches(string, -1, null_binary, sizeof(null_binary))); + g_assert(!nm_utils_hwaddr_matches(string, -1, NULL, ETH_ALEN)); + + g_assert(nm_utils_hwaddr_matches(binary, sizeof(binary), string, -1)); + g_assert(nm_utils_hwaddr_matches(binary, sizeof(binary), upper_string, -1)); + g_assert(nm_utils_hwaddr_matches(binary, sizeof(binary), bad_string, -1)); + g_assert(nm_utils_hwaddr_matches(binary, sizeof(binary), binary, sizeof(binary))); + g_assert(!nm_utils_hwaddr_matches(binary, sizeof(binary), other_string, -1)); + g_assert(!nm_utils_hwaddr_matches(binary, sizeof(binary), other_binary, sizeof(other_binary))); + g_assert(!nm_utils_hwaddr_matches(binary, sizeof(binary), long_string, -1)); + g_assert(!nm_utils_hwaddr_matches(binary, sizeof(binary), long_binary, sizeof(long_binary))); + g_assert(!nm_utils_hwaddr_matches(binary, sizeof(binary), null_string, -1)); + g_assert(!nm_utils_hwaddr_matches(binary, sizeof(binary), null_binary, sizeof(null_binary))); + g_assert(!nm_utils_hwaddr_matches(binary, sizeof(binary), NULL, ETH_ALEN)); + + g_assert(!nm_utils_hwaddr_matches(null_string, -1, string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_string, -1, upper_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_string, -1, bad_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_string, -1, binary, sizeof(binary))); + g_assert(!nm_utils_hwaddr_matches(null_string, -1, other_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_string, -1, other_binary, sizeof(other_binary))); + g_assert(!nm_utils_hwaddr_matches(null_string, -1, long_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_string, -1, long_binary, sizeof(long_binary))); + g_assert(nm_utils_hwaddr_matches(null_string, -1, null_string, -1)); + g_assert(nm_utils_hwaddr_matches(null_string, -1, null_binary, sizeof(null_binary))); + g_assert(nm_utils_hwaddr_matches(null_string, -1, NULL, ETH_ALEN)); + + g_assert(!nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), upper_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), bad_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), binary, sizeof(binary))); + g_assert(!nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), other_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_binary, + sizeof(null_binary), + other_binary, + sizeof(other_binary))); + g_assert(!nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), long_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_binary, + sizeof(null_binary), + long_binary, + sizeof(long_binary))); + g_assert(nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), null_string, -1)); + g_assert(nm_utils_hwaddr_matches(null_binary, + sizeof(null_binary), + null_binary, + sizeof(null_binary))); + g_assert(nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), NULL, ETH_ALEN)); + + g_assert(!nm_utils_hwaddr_matches(NULL, -1, NULL, -1)); + g_assert(!nm_utils_hwaddr_matches(NULL, -1, string, -1)); + g_assert(!nm_utils_hwaddr_matches(string, -1, NULL, -1)); + g_assert(!nm_utils_hwaddr_matches(NULL, -1, null_string, -1)); + g_assert(!nm_utils_hwaddr_matches(null_string, -1, NULL, -1)); + g_assert(!nm_utils_hwaddr_matches(NULL, -1, binary, sizeof(binary))); + g_assert(!nm_utils_hwaddr_matches(binary, sizeof(binary), NULL, -1)); + g_assert(!nm_utils_hwaddr_matches(NULL, -1, null_binary, sizeof(null_binary))); + g_assert(!nm_utils_hwaddr_matches(null_binary, sizeof(null_binary), NULL, -1)); +} + +static void +test_hwaddr_canonical(void) +{ + const char *string = "00:1A:2B:03:44:05"; + const char *lower_string = "00:1a:2b:03:44:05"; + const char *short_string = "0:1a:2b:3:44:5"; + const char *hyphen_string = "00-1a-2b-03-44-05"; + const char *invalid_string = "00:1A:2B"; + char * canonical; + + canonical = nm_utils_hwaddr_canonical(string, ETH_ALEN); + g_assert_cmpstr(canonical, ==, string); + g_free(canonical); + + canonical = nm_utils_hwaddr_canonical(lower_string, ETH_ALEN); + g_assert_cmpstr(canonical, ==, string); + g_free(canonical); + + canonical = nm_utils_hwaddr_canonical(short_string, ETH_ALEN); + g_assert_cmpstr(canonical, ==, string); + g_free(canonical); + + canonical = nm_utils_hwaddr_canonical(hyphen_string, ETH_ALEN); + g_assert_cmpstr(canonical, ==, string); + g_free(canonical); + + canonical = nm_utils_hwaddr_canonical(invalid_string, ETH_ALEN); + g_assert_cmpstr(canonical, ==, NULL); + + canonical = nm_utils_hwaddr_canonical(invalid_string, -1); + g_assert_cmpstr(canonical, ==, invalid_string); + g_free(canonical); +} + +static void +test_connection_changed_cb(NMConnection *connection, gboolean *data) +{ + *data = TRUE; +} + +static guint32 +_netmask_to_prefix(guint32 netmask) +{ + guint32 prefix; + guint8 v; + const guint8 *p = (guint8 *) &netmask; + + if (p[3]) { + prefix = 24; + v = p[3]; + } else if (p[2]) { + prefix = 16; + v = p[2]; + } else if (p[1]) { + prefix = 8; + v = p[1]; + } else { + prefix = 0; + v = p[0]; + } + + while (v) { + prefix++; + v <<= 1; + } + + g_assert_cmpint(prefix, <=, 32); + + /* we re-implemented the netmask-to-prefix code differently. Check + * that they agree. */ + g_assert_cmpint(prefix, ==, nm_utils_ip4_netmask_to_prefix(netmask)); + + return prefix; +} + +static void +test_ip4_prefix_to_netmask(void) +{ + int i; + + for (i = 0; i <= 32; i++) { + guint32 netmask = _nm_utils_ip4_prefix_to_netmask(i); + int plen = _netmask_to_prefix(netmask); + + g_assert_cmpint(i, ==, plen); + { + guint32 msk = 0x80000000; + guint32 netmask2 = 0; + guint32 prefix = i; + while (prefix > 0) { + netmask2 |= msk; + msk >>= 1; + prefix--; + } + g_assert_cmpint(netmask, ==, (guint32) htonl(netmask2)); + } + } +} + +static void +test_ip4_netmask_to_prefix(void) +{ + int i, j; + + GRand *rand = g_rand_new(); + + g_rand_set_seed(rand, 1); + + for (i = 2; i <= 32; i++) { + guint32 netmask = _nm_utils_ip4_prefix_to_netmask(i); + guint32 netmask_lowest_bit = netmask & ~_nm_utils_ip4_prefix_to_netmask(i - 1); + + g_assert_cmpint(i, ==, _netmask_to_prefix(netmask)); + + for (j = 0; j < 2 * i; j++) { + guint32 r = g_rand_int(rand); + guint32 netmask_holey; + guint32 prefix_holey; + + netmask_holey = (netmask & r) | netmask_lowest_bit; + + if (netmask_holey == netmask) + continue; + + /* create an invalid netmask with holes and check that the function + * returns the longest prefix. */ + prefix_holey = _netmask_to_prefix(netmask_holey); + + g_assert_cmpint(i, ==, prefix_holey); + } + } + + g_rand_free(rand); +} + +#define ASSERT_CHANGED(statement) \ + G_STMT_START \ + { \ + changed = FALSE; \ + statement; \ + g_assert(changed); \ + } \ + G_STMT_END + +#define ASSERT_UNCHANGED(statement) \ + G_STMT_START \ + { \ + changed = FALSE; \ + statement; \ + g_assert(!changed); \ + } \ + G_STMT_END + +static void +test_connection_changed_signal(void) +{ + NMConnection *connection; + gboolean changed = FALSE; + + connection = new_test_connection(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + /* Add new setting */ + ASSERT_CHANGED(nm_connection_add_setting(connection, nm_setting_vlan_new())); + + /* Remove existing setting */ + ASSERT_CHANGED(nm_connection_remove_setting(connection, NM_TYPE_SETTING_VLAN)); + + /* Remove non-existing setting */ + ASSERT_UNCHANGED(nm_connection_remove_setting(connection, NM_TYPE_SETTING_VLAN)); + + g_object_unref(connection); +} + +static void +test_setting_connection_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSettingConnection *s_con; + gs_free char * uuid = NULL; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + ASSERT_CHANGED(g_object_set(s_con, NM_SETTING_CONNECTION_ID, "adfadfasdfaf", NULL)); + + ASSERT_CHANGED(nm_setting_connection_add_permission(s_con, "user", "billsmith", NULL)); + ASSERT_CHANGED(nm_setting_connection_remove_permission(s_con, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx < nm_g_array_len(priv->permissions))); + ASSERT_UNCHANGED(nm_setting_connection_remove_permission(s_con, 1)); + g_test_assert_expected_messages(); + + uuid = nm_utils_uuid_generate(); + ASSERT_CHANGED(nm_setting_connection_add_secondary(s_con, uuid)); + ASSERT_CHANGED(nm_setting_connection_remove_secondary(s_con, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx < nm_g_array_len(priv->secondaries))); + ASSERT_UNCHANGED(nm_setting_connection_remove_secondary(s_con, 1)); + g_test_assert_expected_messages(); + + g_object_unref(connection); +} + +static void +test_setting_bond_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSettingBond *s_bond; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_bond = (NMSettingBond *) nm_setting_bond_new(); + nm_connection_add_setting(connection, NM_SETTING(s_bond)); + + ASSERT_CHANGED(nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY, "10")); + ASSERT_CHANGED(nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY)); + ASSERT_UNCHANGED(nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_UPDELAY)); + + g_object_unref(connection); +} + +static void +test_setting_ip4_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSettingIPConfig *s_ip4; + NMIPAddress * addr; + NMIPRoute * route; + GError * error = NULL; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + ASSERT_CHANGED(nm_setting_ip_config_add_dns(s_ip4, "11.22.0.0")); + ASSERT_CHANGED(nm_setting_ip_config_remove_dns(s_ip4, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->dns->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_dns(s_ip4, 1)); + g_test_assert_expected_messages(); + + nm_setting_ip_config_add_dns(s_ip4, "33.44.0.0"); + ASSERT_CHANGED(nm_setting_ip_config_clear_dns(s_ip4)); + + ASSERT_CHANGED(nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com")); + ASSERT_CHANGED(nm_setting_ip_config_remove_dns_search(s_ip4, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->dns_search->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_dns_search(s_ip4, 1)); + g_test_assert_expected_messages(); + + ASSERT_CHANGED(nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com")); + ASSERT_CHANGED(nm_setting_ip_config_clear_dns_searches(s_ip4)); + + addr = nm_ip_address_new(AF_INET, "22.33.0.0", 24, &error); + g_assert_no_error(error); + ASSERT_CHANGED(nm_setting_ip_config_add_address(s_ip4, addr)); + ASSERT_CHANGED(nm_setting_ip_config_remove_address(s_ip4, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->addresses->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_address(s_ip4, 1)); + g_test_assert_expected_messages(); + + nm_setting_ip_config_add_address(s_ip4, addr); + ASSERT_CHANGED(nm_setting_ip_config_clear_addresses(s_ip4)); + + route = nm_ip_route_new(AF_INET, "22.33.0.0", 24, NULL, 0, &error); + g_assert_no_error(error); + + ASSERT_CHANGED(nm_setting_ip_config_add_route(s_ip4, route)); + ASSERT_CHANGED(nm_setting_ip_config_remove_route(s_ip4, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->routes->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_route(s_ip4, 1)); + g_test_assert_expected_messages(); + + nm_setting_ip_config_add_route(s_ip4, route); + ASSERT_CHANGED(nm_setting_ip_config_clear_routes(s_ip4)); + + ASSERT_CHANGED(nm_setting_ip_config_add_dns_option(s_ip4, "debug")); + ASSERT_CHANGED(nm_setting_ip_config_remove_dns_option(s_ip4, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->dns_options->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_dns_option(s_ip4, 1)); + g_test_assert_expected_messages(); + + nm_ip_address_unref(addr); + nm_ip_route_unref(route); + g_object_unref(connection); +} + +static void +test_setting_ip6_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSettingIPConfig *s_ip6; + NMIPAddress * addr; + NMIPRoute * route; + GError * error = NULL; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip6)); + + ASSERT_CHANGED(nm_setting_ip_config_add_dns(s_ip6, "1:2:3::4:5:6")); + ASSERT_CHANGED(nm_setting_ip_config_remove_dns(s_ip6, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->dns->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_dns(s_ip6, 1)); + g_test_assert_expected_messages(); + + nm_setting_ip_config_add_dns(s_ip6, "1:2:3::4:5:6"); + ASSERT_CHANGED(nm_setting_ip_config_clear_dns(s_ip6)); + + ASSERT_CHANGED(nm_setting_ip_config_add_dns_search(s_ip6, "foobar.com")); + ASSERT_CHANGED(nm_setting_ip_config_remove_dns_search(s_ip6, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->dns_search->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_dns_search(s_ip6, 1)); + g_test_assert_expected_messages(); + + nm_setting_ip_config_add_dns_search(s_ip6, "foobar.com"); + ASSERT_CHANGED(nm_setting_ip_config_clear_dns_searches(s_ip6)); + + addr = nm_ip_address_new(AF_INET6, "1:2:3::4:5:6", 64, &error); + g_assert_no_error(error); + + ASSERT_CHANGED(nm_setting_ip_config_add_address(s_ip6, addr)); + ASSERT_CHANGED(nm_setting_ip_config_remove_address(s_ip6, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->addresses->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_address(s_ip6, 1)); + g_test_assert_expected_messages(); + + nm_setting_ip_config_add_address(s_ip6, addr); + ASSERT_CHANGED(nm_setting_ip_config_clear_addresses(s_ip6)); + + route = nm_ip_route_new(AF_INET6, "1:2:3::4:5:6", 128, NULL, 0, &error); + g_assert_no_error(error); + + ASSERT_CHANGED(nm_setting_ip_config_add_route(s_ip6, route)); + ASSERT_CHANGED(nm_setting_ip_config_remove_route(s_ip6, 0)); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->routes->len)); + ASSERT_UNCHANGED(nm_setting_ip_config_remove_route(s_ip6, 1)); + g_test_assert_expected_messages(); + + nm_setting_ip_config_add_route(s_ip6, route); + ASSERT_CHANGED(nm_setting_ip_config_clear_routes(s_ip6)); + + nm_ip_address_unref(addr); + nm_ip_route_unref(route); + g_object_unref(connection); +} + +static void +test_setting_vlan_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSettingVlan *s_vlan; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_vlan = (NMSettingVlan *) nm_setting_vlan_new(); + nm_connection_add_setting(connection, NM_SETTING(s_vlan)); + + ASSERT_CHANGED(nm_setting_vlan_add_priority(s_vlan, NM_VLAN_INGRESS_MAP, 1, 3)); + ASSERT_CHANGED(nm_setting_vlan_remove_priority(s_vlan, NM_VLAN_INGRESS_MAP, 0)); + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx < g_slist_length(list))); + ASSERT_UNCHANGED(nm_setting_vlan_remove_priority(s_vlan, NM_VLAN_INGRESS_MAP, 1)); + g_test_assert_expected_messages(); + ASSERT_CHANGED(nm_setting_vlan_add_priority_str(s_vlan, NM_VLAN_INGRESS_MAP, "1:3")); + ASSERT_CHANGED(nm_setting_vlan_clear_priorities(s_vlan, NM_VLAN_INGRESS_MAP)); + + ASSERT_CHANGED(nm_setting_vlan_add_priority(s_vlan, NM_VLAN_EGRESS_MAP, 1, 3)); + ASSERT_CHANGED(nm_setting_vlan_remove_priority(s_vlan, NM_VLAN_EGRESS_MAP, 0)); + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx < g_slist_length(list))); + ASSERT_UNCHANGED(nm_setting_vlan_remove_priority(s_vlan, NM_VLAN_EGRESS_MAP, 1)); + g_test_assert_expected_messages(); + ASSERT_CHANGED(nm_setting_vlan_add_priority_str(s_vlan, NM_VLAN_EGRESS_MAP, "1:3")); + ASSERT_CHANGED(nm_setting_vlan_clear_priorities(s_vlan, NM_VLAN_EGRESS_MAP)); + + g_object_unref(connection); +} + +static void +test_setting_vpn_changed_signal(void) +{ + NMConnection *connection; + gboolean changed = FALSE; + NMSettingVpn *s_vpn; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_vpn = (NMSettingVpn *) nm_setting_vpn_new(); + nm_connection_add_setting(connection, NM_SETTING(s_vpn)); + + ASSERT_CHANGED(nm_setting_vpn_add_data_item(s_vpn, "foobar", "baz")); + ASSERT_CHANGED(nm_setting_vpn_remove_data_item(s_vpn, "foobar")); + ASSERT_UNCHANGED(nm_setting_vpn_remove_data_item(s_vpn, "not added")); + + ASSERT_CHANGED(nm_setting_vpn_add_secret(s_vpn, "foobar", "baz")); + ASSERT_CHANGED(nm_setting_vpn_remove_secret(s_vpn, "foobar")); + ASSERT_UNCHANGED(nm_setting_vpn_remove_secret(s_vpn, "not added")); + + g_object_unref(connection); +} + +static void +test_setting_wired_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSettingWired *s_wired; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + ASSERT_CHANGED(nm_setting_wired_add_s390_option(s_wired, "portno", "1")); + ASSERT_CHANGED(nm_setting_wired_remove_s390_option(s_wired, "portno")); + ASSERT_UNCHANGED(nm_setting_wired_remove_s390_option(s_wired, "layer2")); + + g_object_unref(connection); +} + +static void +test_setting_wireless_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSettingWireless *s_wifi; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + ASSERT_CHANGED(nm_setting_wireless_add_seen_bssid(s_wifi, "00:11:22:33:44:55")); + + g_object_unref(connection); +} + +static void +test_setting_wireless_security_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSettingWirelessSecurity *s_wsec; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + /* Protos */ + ASSERT_CHANGED(nm_setting_wireless_security_add_proto(s_wsec, "wpa")); + ASSERT_CHANGED(nm_setting_wireless_security_remove_proto(s_wsec, 0)); + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(elt != NULL)); + ASSERT_UNCHANGED(nm_setting_wireless_security_remove_proto(s_wsec, 1)); + g_test_assert_expected_messages(); + + nm_setting_wireless_security_add_proto(s_wsec, "wep"); + ASSERT_CHANGED(nm_setting_wireless_security_clear_protos(s_wsec)); + + /* Pairwise ciphers */ + ASSERT_CHANGED(nm_setting_wireless_security_add_pairwise(s_wsec, "tkip")); + ASSERT_CHANGED(nm_setting_wireless_security_remove_pairwise(s_wsec, 0)); + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(elt != NULL)); + ASSERT_UNCHANGED(nm_setting_wireless_security_remove_pairwise(s_wsec, 1)); + g_test_assert_expected_messages(); + + nm_setting_wireless_security_add_pairwise(s_wsec, "tkip"); + ASSERT_CHANGED(nm_setting_wireless_security_clear_pairwise(s_wsec)); + + /* Group ciphers */ + ASSERT_CHANGED(nm_setting_wireless_security_add_group(s_wsec, "ccmp")); + ASSERT_CHANGED(nm_setting_wireless_security_remove_group(s_wsec, 0)); + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(elt != NULL)); + ASSERT_UNCHANGED(nm_setting_wireless_security_remove_group(s_wsec, 1)); + g_test_assert_expected_messages(); + + nm_setting_wireless_security_add_group(s_wsec, "tkip"); + ASSERT_CHANGED(nm_setting_wireless_security_clear_groups(s_wsec)); + + /* WEP key secret flags */ + ASSERT_CHANGED(g_assert(nm_setting_set_secret_flags(NM_SETTING(s_wsec), + "wep-key0", + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NULL))); + ASSERT_CHANGED(g_assert(nm_setting_set_secret_flags(NM_SETTING(s_wsec), + "wep-key1", + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NULL))); + ASSERT_CHANGED(g_assert(nm_setting_set_secret_flags(NM_SETTING(s_wsec), + "wep-key2", + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NULL))); + ASSERT_CHANGED(g_assert(nm_setting_set_secret_flags(NM_SETTING(s_wsec), + "wep-key3", + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NULL))); + + g_object_unref(connection); +} + +static void +test_setting_802_1x_changed_signal(void) +{ + NMConnection * connection; + gboolean changed = FALSE; + NMSetting8021x *s_8021x; + + connection = nm_simple_connection_new(); + g_signal_connect(connection, + NM_CONNECTION_CHANGED, + (GCallback) test_connection_changed_cb, + &changed); + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + /* EAP methods */ + ASSERT_CHANGED(nm_setting_802_1x_add_eap_method(s_8021x, "tls")); + ASSERT_CHANGED(nm_setting_802_1x_remove_eap_method(s_8021x, 0)); + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(elt != NULL)); + ASSERT_UNCHANGED(nm_setting_802_1x_remove_eap_method(s_8021x, 1)); + g_test_assert_expected_messages(); + + nm_setting_802_1x_add_eap_method(s_8021x, "ttls"); + ASSERT_CHANGED(nm_setting_802_1x_clear_eap_methods(s_8021x)); + + /* alternate subject matches */ + ASSERT_CHANGED(nm_setting_802_1x_add_altsubject_match(s_8021x, "EMAIL:server@example.com")); + ASSERT_CHANGED(nm_setting_802_1x_remove_altsubject_match(s_8021x, 0)); + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(elt != NULL)); + ASSERT_UNCHANGED(nm_setting_802_1x_remove_altsubject_match(s_8021x, 1)); + g_test_assert_expected_messages(); + + nm_setting_802_1x_add_altsubject_match(s_8021x, "EMAIL:server@example.com"); + ASSERT_CHANGED(nm_setting_802_1x_clear_altsubject_matches(s_8021x)); + + /* phase2 alternate subject matches */ + ASSERT_CHANGED( + nm_setting_802_1x_add_phase2_altsubject_match(s_8021x, "EMAIL:server@example.com")); + ASSERT_CHANGED(nm_setting_802_1x_remove_phase2_altsubject_match(s_8021x, 0)); + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(elt != NULL)); + ASSERT_UNCHANGED(nm_setting_802_1x_remove_phase2_altsubject_match(s_8021x, 1)); + g_test_assert_expected_messages(); + + nm_setting_802_1x_add_phase2_altsubject_match(s_8021x, "EMAIL:server@example.com"); + ASSERT_CHANGED(nm_setting_802_1x_clear_phase2_altsubject_matches(s_8021x)); + + g_object_unref(connection); +} + +static void +test_setting_old_uuid(void) +{ + gs_unref_object NMSetting *setting = NULL; + + /* NetworkManager-0.9.4.0 generated 40-character UUIDs with no dashes, + * like this one. Test that we maintain compatibility. */ + const char *uuid = "f43bec2cdd60e5da381ebb1eb1fa39f3cc52660c"; + + setting = nm_setting_connection_new(); + g_object_set(G_OBJECT(setting), + NM_SETTING_CONNECTION_ID, + "uuidtest", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + nmtst_assert_setting_verifies(NM_SETTING(setting)); +} + +/*****************************************************************************/ + +static void +test_connection_normalize_uuid(void) +{ + gs_unref_object NMConnection *con = NULL; + + con = nmtst_create_minimal_connection("test1", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + + nmtst_assert_connection_verifies_and_normalizable(con); + + g_object_set(nm_connection_get_setting_connection(con), NM_SETTING_CONNECTION_UUID, NULL, NULL); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); +} + +/*****************************************************************************/ + +/* + * Test normalization of interface-name + */ +static void +test_connection_normalize_virtual_iface_name(void) +{ + NMConnection * con = NULL; + NMSettingConnection *s_con; + NMSettingVlan * s_vlan; + GVariant * connection_dict, *setting_dict, *var; + GError * error = NULL; + const char * IFACE_NAME = "iface"; + const char * IFACE_VIRT = "iface-X"; + + con = nmtst_create_minimal_connection("test1", + "22001632-bbb4-4616-b277-363dce3dfb5b", + NM_SETTING_VLAN_SETTING_NAME, + &s_con); + + nm_connection_add_setting(con, + g_object_new(NM_TYPE_SETTING_IP4_CONFIG, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL)); + + nm_connection_add_setting(con, + g_object_new(NM_TYPE_SETTING_IP6_CONFIG, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NULL)); + + s_vlan = nm_connection_get_setting_vlan(con); + + g_object_set(G_OBJECT(s_vlan), NM_SETTING_VLAN_PARENT, "eth0", NULL); + + g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, IFACE_NAME, NULL); + + g_assert_cmpstr(nm_connection_get_interface_name(con), ==, IFACE_NAME); + + connection_dict = nm_connection_to_dbus(con, NM_CONNECTION_SERIALIZE_ALL); + g_object_unref(con); + + /* Serialized form should include vlan.interface-name as well. */ + setting_dict = g_variant_lookup_value(connection_dict, + NM_SETTING_VLAN_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + g_assert(setting_dict != NULL); + var = g_variant_lookup_value(setting_dict, "interface-name", NULL); + g_assert(var != NULL); + g_assert(g_variant_is_of_type(var, G_VARIANT_TYPE_STRING)); + g_assert_cmpstr(g_variant_get_string(var, NULL), ==, IFACE_NAME); + + g_variant_unref(setting_dict); + g_variant_unref(var); + + /* If vlan.interface-name will be ignored. */ + NMTST_VARIANT_EDITOR( + connection_dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_VLAN_SETTING_NAME, + "interface-name", + "s", + ":::this-is-not-a-valid-interface-name:::");); + + con = _connection_new_from_dbus(connection_dict, &error); + nmtst_assert_success(con, error); + g_assert_cmpstr(nm_connection_get_interface_name(con), ==, IFACE_NAME); + g_clear_object(&con); + + /* If vlan.interface-name is valid, but doesn't match, it will be ignored. */ + NMTST_VARIANT_EDITOR(connection_dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_VLAN_SETTING_NAME, + "interface-name", + "s", + IFACE_VIRT);); + + con = _connection_new_from_dbus(connection_dict, &error); + g_assert_no_error(error); + + g_assert_cmpstr(nm_connection_get_interface_name(con), ==, IFACE_NAME); + s_con = nm_connection_get_setting_connection(con); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, IFACE_NAME); + g_object_unref(con); + + /* But removing connection.interface-name should result in vlan.connection-name + * being "promoted". + */ + NMTST_VARIANT_EDITOR(connection_dict, + NMTST_VARIANT_DROP_PROPERTY(NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME);); + + con = _connection_new_from_dbus(connection_dict, &error); + g_assert_no_error(error); + + g_assert_cmpstr(nm_connection_get_interface_name(con), ==, IFACE_VIRT); + s_con = nm_connection_get_setting_connection(con); + g_assert_cmpstr(nm_setting_connection_get_interface_name(s_con), ==, IFACE_VIRT); + g_object_unref(con); + + g_variant_unref(connection_dict); +} + +static void +_test_connection_normalize_type_normalizable_setting( + const char *type, + void (*prepare_normalizable_fcn)(NMConnection *con)) +{ + NMSettingConnection *s_con; + NMSetting * s_base; + GType base_type; + gs_unref_object NMConnection *con = NULL; + gs_free char * id = g_strdup_printf("%s[%s]", G_STRFUNC, type); + + base_type = nm_setting_lookup_type(type); + g_assert(base_type != G_TYPE_INVALID); + g_assert(_nm_setting_type_get_base_type_priority(base_type) != NM_SETTING_PRIORITY_INVALID); + + con = nmtst_create_minimal_connection(id, NULL, NULL, &s_con); + + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + + g_object_set(s_con, NM_SETTING_CONNECTION_TYPE, type, NULL); + + if (prepare_normalizable_fcn) + prepare_normalizable_fcn(con); + + g_assert(!nm_connection_get_setting_by_name(con, type)); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING); + nmtst_connection_normalize(con); + + s_base = nm_connection_get_setting_by_name(con, type); + g_assert(s_base); + g_assert(G_OBJECT_TYPE(s_base) == base_type); +} + +static void +_test_connection_normalize_type_unnormalizable_setting(const char *type) +{ + NMSettingConnection *s_con; + GType base_type; + gs_unref_object NMConnection *con = NULL; + gs_free char * id = g_strdup_printf("%s[%s]", G_STRFUNC, type); + + base_type = nm_setting_lookup_type(type); + g_assert(base_type != G_TYPE_INVALID); + g_assert(_nm_setting_type_get_base_type_priority(base_type) != NM_SETTING_PRIORITY_INVALID); + + con = nmtst_create_minimal_connection(id, NULL, NULL, &s_con); + + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + + g_object_set(s_con, NM_SETTING_CONNECTION_TYPE, type, NULL); + + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING); +} + +static void +_test_connection_normalize_type_normalizable_type(const char *type, + NMSetting *(*add_setting_fcn)(NMConnection *con)) +{ + NMSettingConnection *s_con; + NMSetting * s_base; + GType base_type; + gs_unref_object NMConnection *con = NULL; + gs_free char * id = g_strdup_printf("%s[%s]", G_STRFUNC, type); + + base_type = nm_setting_lookup_type(type); + g_assert(base_type != G_TYPE_INVALID); + g_assert(_nm_setting_type_get_base_type_priority(base_type) != NM_SETTING_PRIORITY_INVALID); + + con = nmtst_create_minimal_connection(id, NULL, NULL, &s_con); + + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + + if (add_setting_fcn) + s_base = add_setting_fcn(con); + else { + s_base = g_object_new(base_type, NULL); + nm_connection_add_setting(con, s_base); + } + + g_assert(!nm_setting_connection_get_connection_type(s_con)); + g_assert(nm_connection_get_setting_by_name(con, type) == s_base); + + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + nmtst_connection_normalize(con); + + g_assert_cmpstr(nm_connection_get_connection_type(con), ==, type); + g_assert(nm_connection_get_setting_by_name(con, type) == s_base); +} + +static NMSetting * +_add_setting_fcn_adsl(NMConnection *con) +{ + NMSetting *setting; + + setting = g_object_new(NM_TYPE_SETTING_ADSL, + NM_SETTING_ADSL_USERNAME, + "test-user", + NM_SETTING_ADSL_PROTOCOL, + NM_SETTING_ADSL_PROTOCOL_PPPOA, + NM_SETTING_ADSL_ENCAPSULATION, + NM_SETTING_ADSL_ENCAPSULATION_VCMUX, + NULL); + + nm_connection_add_setting(con, setting); + return setting; +} + +static NMSetting * +_add_setting_fcn_bluetooth(NMConnection *con) +{ + NMSetting *setting; + + setting = g_object_new(NM_TYPE_SETTING_BLUETOOTH, + NM_SETTING_BLUETOOTH_BDADDR, + "11:22:33:44:55:66", + NM_SETTING_BLUETOOTH_TYPE, + NM_SETTING_BLUETOOTH_TYPE_PANU, + NULL); + + nm_connection_add_setting(con, setting); + return setting; +} + +static NMSetting * +_add_setting_fcn_bond(NMConnection *con) +{ + NMSetting * setting; + NMSettingConnection *s_con; + + setting = g_object_new(NM_TYPE_SETTING_BOND, NULL); + + nm_connection_add_setting(con, setting); + + s_con = nm_connection_get_setting_connection(con); + + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "test-bond", NULL); + + return setting; +} + +static NMSetting * +_add_setting_fcn_bridge(NMConnection *con) +{ + NMSetting * setting; + NMSettingConnection *s_con; + + setting = g_object_new(NM_TYPE_SETTING_BRIDGE, NULL); + + nm_connection_add_setting(con, setting); + + s_con = nm_connection_get_setting_connection(con); + + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "test-bridge", NULL); + + return setting; +} + +static NMSetting * +_add_setting_fcn_cdma(NMConnection *con) +{ + NMSetting *setting; + + setting = g_object_new(NM_TYPE_SETTING_CDMA, NM_SETTING_CDMA_NUMBER, "test-number", NULL); + + nm_connection_add_setting(con, setting); + return setting; +} + +static NMSetting * +_add_setting_fcn_infiniband(NMConnection *con) +{ + NMSetting *setting; + + setting = g_object_new(NM_TYPE_SETTING_INFINIBAND, + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "connected", + NULL); + + nm_connection_add_setting(con, setting); + return setting; +} + +static NMSetting * +_add_setting_fcn_olpc_mesh(NMConnection *con) +{ + NMSetting * setting; + const char *ssid_data = "ssid-test"; + GBytes * ssid; + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + setting = g_object_new(NM_TYPE_SETTING_OLPC_MESH, + NM_SETTING_OLPC_MESH_SSID, + ssid, + NM_SETTING_OLPC_MESH_CHANNEL, + 1, + NULL); + g_bytes_unref(ssid); + + nm_connection_add_setting(con, setting); + return setting; +} + +static NMSetting * +_add_setting_fcn_team(NMConnection *con) +{ + NMSetting * setting; + NMSettingConnection *s_con; + + setting = g_object_new(NM_TYPE_SETTING_TEAM, NULL); + + nm_connection_add_setting(con, setting); + + s_con = nm_connection_get_setting_connection(con); + + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "test-team", NULL); + + return setting; +} + +static NMSetting * +_add_setting_fcn_vlan(NMConnection *con) +{ + NMSetting *setting; + + setting = g_object_new(NM_TYPE_SETTING_VLAN, NM_SETTING_VLAN_PARENT, "test-parent", NULL); + + nm_connection_add_setting(con, setting); + return setting; +} + +static NMSetting * +_add_setting_fcn_vpn(NMConnection *con) +{ + NMSetting *setting; + + setting = g_object_new(NM_TYPE_SETTING_VPN, + NM_SETTING_VPN_SERVICE_TYPE, + "test-vpn-service-type", + NULL); + + nm_connection_add_setting(con, setting); + return setting; +} + +static NMSetting * +_add_setting_fcn_wimax(NMConnection *con) +{ + NMSetting *setting; + + setting = + g_object_new(NM_TYPE_SETTING_WIMAX, NM_SETTING_WIMAX_NETWORK_NAME, "test-network", NULL); + + nm_connection_add_setting(con, setting); + return setting; +} + +static NMSetting * +_add_setting_fcn_wireless(NMConnection *con) +{ + NMSetting * setting; + const char *ssid_data = "ssid-test"; + GBytes * ssid; + + ssid = g_bytes_new(ssid_data, strlen(ssid_data)); + setting = g_object_new(NM_TYPE_SETTING_WIRELESS, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + + nm_connection_add_setting(con, setting); + return setting; +} + +static void +_prepare_normalizable_fcn_vlan(NMConnection *con) +{ + nm_connection_add_setting(con, + g_object_new(NM_TYPE_SETTING_WIRED, + NM_SETTING_WIRED_MAC_ADDRESS, + "11:22:33:44:55:66", + NULL)); +} + +static void +test_connection_normalize_type(void) +{ + guint i; + struct { + const char *type; + gboolean normalizable; + NMSetting *(*add_setting_fcn)(NMConnection *con); + void (*prepare_normalizable_fcn)(NMConnection *con); + } types[] = { + {NM_SETTING_GENERIC_SETTING_NAME, TRUE}, + {NM_SETTING_GSM_SETTING_NAME, TRUE}, + {NM_SETTING_WIRED_SETTING_NAME, TRUE}, + {NM_SETTING_VLAN_SETTING_NAME, TRUE, _add_setting_fcn_vlan, _prepare_normalizable_fcn_vlan}, + + {NM_SETTING_ADSL_SETTING_NAME, FALSE, _add_setting_fcn_adsl}, + {NM_SETTING_BLUETOOTH_SETTING_NAME, FALSE, _add_setting_fcn_bluetooth}, + {NM_SETTING_BOND_SETTING_NAME, FALSE, _add_setting_fcn_bond}, + {NM_SETTING_BRIDGE_SETTING_NAME, FALSE, _add_setting_fcn_bridge}, + {NM_SETTING_CDMA_SETTING_NAME, FALSE, _add_setting_fcn_cdma}, + {NM_SETTING_INFINIBAND_SETTING_NAME, FALSE, _add_setting_fcn_infiniband}, + {NM_SETTING_OLPC_MESH_SETTING_NAME, FALSE, _add_setting_fcn_olpc_mesh}, + {NM_SETTING_TEAM_SETTING_NAME, FALSE, _add_setting_fcn_team}, + {NM_SETTING_VLAN_SETTING_NAME, FALSE, _add_setting_fcn_vlan}, + {NM_SETTING_VPN_SETTING_NAME, FALSE, _add_setting_fcn_vpn}, + {NM_SETTING_WIMAX_SETTING_NAME, FALSE, _add_setting_fcn_wimax}, + {NM_SETTING_WIRELESS_SETTING_NAME, FALSE, _add_setting_fcn_wireless}, + {0}, + }; + + for (i = 0; types[i].type; i++) { + const char *type = types[i].type; + + if (types[i].normalizable) + _test_connection_normalize_type_normalizable_setting(type, + types[i].prepare_normalizable_fcn); + else + _test_connection_normalize_type_unnormalizable_setting(type); + _test_connection_normalize_type_normalizable_type(type, types[i].add_setting_fcn); + } +} + +static void +test_connection_normalize_slave_type_1(void) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingConnection * s_con; + + con = nmtst_create_minimal_connection("test_connection_normalize_slave_type_1", + "cc4cd5df-45dc-483e-b291-6b76c2338ecb", + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + "invalid-type", + NULL); + + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(!nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME)); + + g_object_set(s_con, NM_SETTING_CONNECTION_SLAVE_TYPE, "bridge", NULL); + + g_assert(!nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME)); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING); + nmtst_connection_normalize(con); + g_assert(nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); +} + +static void +test_connection_normalize_slave_type_2(void) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingConnection * s_con; + + con = nmtst_create_minimal_connection("test_connection_normalize_slave_type_2", + "40bea008-ca72-439a-946b-e65f827656f9", + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + "invalid-type", + NULL); + + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(!nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME)); + + g_object_set(s_con, NM_SETTING_CONNECTION_SLAVE_TYPE, NULL, NULL); + nm_connection_add_setting(con, nm_setting_bridge_port_new()); + + g_assert(nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NULL); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + nmtst_connection_normalize(con); + g_assert(nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_BRIDGE_SETTING_NAME); +} + +static void +test_connection_normalize_infiniband_mtu(void) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingInfiniband * s_infini; + guint mtu_regular = nmtst_rand_select(2044, 2045, 65520); + + con = nmtst_create_minimal_connection("test_connection_normalize_infiniband_mtu", + NULL, + NM_SETTING_INFINIBAND_SETTING_NAME, + NULL); + + s_infini = nm_connection_get_setting_infiniband(con); + g_object_set(s_infini, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "connected", NULL); + nmtst_assert_connection_verifies_and_normalizable(con); + + g_object_set(s_infini, + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "datagram", + NM_SETTING_INFINIBAND_MTU, + (guint) mtu_regular, + NULL); + nmtst_assert_connection_verifies_and_normalizable(con); + nmtst_connection_normalize(con); + g_assert_cmpint(mtu_regular, ==, nm_setting_infiniband_get_mtu(s_infini)); + + g_object_set(s_infini, + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "datagram", + NM_SETTING_INFINIBAND_MTU, + (guint) 65521, + NULL); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + nmtst_connection_normalize(con); + g_assert_cmpint(65520, ==, nm_setting_infiniband_get_mtu(s_infini)); + + g_object_set(s_infini, + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "connected", + NM_SETTING_INFINIBAND_MTU, + (guint) mtu_regular, + NULL); + nmtst_assert_connection_verifies_without_normalization(con); + g_assert_cmpint(mtu_regular, ==, nm_setting_infiniband_get_mtu(s_infini)); + + g_object_set(s_infini, + NM_SETTING_INFINIBAND_TRANSPORT_MODE, + "connected", + NM_SETTING_INFINIBAND_MTU, + (guint) 65521, + NULL); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + nmtst_connection_normalize(con); + g_assert_cmpint(65520, ==, nm_setting_infiniband_get_mtu(s_infini)); +} + +static void +test_connection_normalize_gateway_never_default(void) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingIPConfig * s_ip4, *s_ip6; + NMIPAddress * addr; + gs_free_error GError *error = NULL; + + con = nmtst_create_minimal_connection("test1", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + nmtst_assert_connection_verifies_and_normalizable(con); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.1", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_GATEWAY, + "1.1.1.254", + NM_SETTING_IP_CONFIG_NEVER_DEFAULT, + FALSE, + NULL); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + + nm_connection_add_setting(con, (NMSetting *) s_ip4); + nm_connection_add_setting(con, (NMSetting *) s_ip6); + nm_connection_add_setting(con, nm_setting_proxy_new()); + + nmtst_assert_connection_verifies_without_normalization(con); + g_assert_cmpstr("1.1.1.254", ==, nm_setting_ip_config_get_gateway(s_ip4)); + + /* Now set never-default to TRUE and check that the gateway is + * removed during normalization + * */ + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_NEVER_DEFAULT, TRUE, NULL); + + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + nmtst_connection_normalize(con); + g_assert_cmpstr(NULL, ==, nm_setting_ip_config_get_gateway(s_ip4)); +} + +static void +test_connection_normalize_may_fail(void) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingIPConfig * s_ip4, *s_ip6; + + con = nmtst_create_minimal_connection("test2", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + nmtst_assert_connection_verifies_and_normalizable(con); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_MAY_FAIL, + FALSE, + NULL); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_AUTO, + NM_SETTING_IP_CONFIG_MAY_FAIL, + FALSE, + NULL); + + nm_connection_add_setting(con, (NMSetting *) s_ip4); + nm_connection_add_setting(con, (NMSetting *) s_ip6); + + nmtst_assert_connection_verifies_and_normalizable(con); + + /* Now set method=disabled/ignore and check that may-fail becomes TRUE + * after normalization + * */ + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL); + + nmtst_assert_connection_verifies(con); + nmtst_connection_normalize(con); + g_assert_cmpint(nm_setting_ip_config_get_may_fail(s_ip4), ==, TRUE); + g_assert_cmpint(nm_setting_ip_config_get_may_fail(s_ip6), ==, TRUE); +} + +static void +test_connection_normalize_shared_addresses(void) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingIPConfig * s_ip4, *s_ip6; + NMIPAddress * addr; + gs_free_error GError *error = NULL; + + con = nmtst_create_minimal_connection("test1", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + nmtst_assert_connection_verifies_and_normalizable(con); + + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(G_OBJECT(s_ip4), + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_SHARED, + NULL); + + addr = nm_ip_address_new(AF_INET, "1.1.1.1", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); + + nm_connection_add_setting(con, (NMSetting *) s_ip4); + nm_connection_add_setting(con, (NMSetting *) s_ip6); + + nmtst_assert_connection_verifies_and_normalizable(con); + + /* Now we add other addresses and check that they are + * removed during normalization + * */ + addr = nm_ip_address_new(AF_INET, "2.2.2.2", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + addr = nm_ip_address_new(AF_INET, "3.3.3.3", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + nmtst_connection_normalize(con); + g_assert_cmpuint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1); + addr = nm_setting_ip_config_get_address(s_ip4, 0); + g_assert_cmpstr(nm_ip_address_get_address(addr), ==, "1.1.1.1"); +} + +static void +test_connection_normalize_ovs_interface_type_system(gconstpointer test_data) +{ + const guint TEST_CASE = GPOINTER_TO_UINT(test_data); + gs_unref_object NMConnection *con = NULL; + NMSettingConnection * s_con; + NMSettingOvsInterface * s_ovs_if; + + con = nmtst_create_minimal_connection("test_connection_normalize_ovs_interface_type_system", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + + switch (TEST_CASE) { + case 1: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING); + + nmtst_connection_normalize(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + s_ovs_if = nm_connection_get_setting_ovs_interface(con); + g_assert(s_ovs_if); + g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "system"); + break; + case 2: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + + s_ovs_if = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new()); + nm_connection_add_setting(con, NM_SETTING(s_ovs_if)); + + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + + nmtst_connection_normalize(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con)); + g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "system"); + break; + case 3: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + + s_ovs_if = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new()); + nm_connection_add_setting(con, NM_SETTING(s_ovs_if)); + + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "system", NULL); + nmtst_assert_connection_verifies_without_normalization(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + break; + case 4: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + + s_ovs_if = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new()); + nm_connection_add_setting(con, NM_SETTING(s_ovs_if)); + + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "internal", NULL); + /* the setting doesn't verify, because the interface-type must be "system". */ + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + break; + case 5: + g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, "master0", NULL); + + s_ovs_if = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new()); + nm_connection_add_setting(con, NM_SETTING(s_ovs_if)); + + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "system", NULL); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + nmtst_connection_normalize(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_WIRED_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_assert(s_con == nm_connection_get_setting_connection(con)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_OVS_PORT_SETTING_NAME); + break; + case 6: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BRIDGE_SETTING_NAME, + NULL); + + s_ovs_if = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new()); + nm_connection_add_setting(con, NM_SETTING(s_ovs_if)); + + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "system", NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + break; + case 7: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_BRIDGE_SETTING_NAME, + NULL); + + nm_connection_add_setting(con, nm_setting_bridge_port_new()); + + s_ovs_if = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new()); + nm_connection_add_setting(con, NM_SETTING(s_ovs_if)); + + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "system", NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + break; + default: + g_assert_not_reached(); + break; + } +} + +static void +test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_data) +{ + const guint TEST_CASE = GPOINTER_TO_UINT(test_data); + gs_unref_object NMConnection *con = NULL; + NMSettingConnection * s_con; + NMSettingOvsInterface * s_ovs_if; + NMSettingOvsPatch * s_ovs_patch; + NMSettingIP4Config * s_ip4; + NMSettingIP6Config * s_ip6; + + con = nmtst_create_minimal_connection( + "test_connection_normalize_ovs_interface_type_ovs_interface", + NULL, + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + &s_con); + s_ovs_if = nm_connection_get_setting_ovs_interface(con); + g_assert(s_ovs_if); + + switch (TEST_CASE) { + case 1: + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + break; + case 2: + g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, "master0", NULL); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + nmtst_connection_normalize(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_assert(s_con == nm_connection_get_setting_connection(con)); + g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_OVS_PORT_SETTING_NAME); + g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "internal"); + break; + case 3: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + nmtst_connection_normalize(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_assert(s_con == nm_connection_get_setting_connection(con)); + g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_OVS_PORT_SETTING_NAME); + g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "internal"); + break; + case 4: + g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, "master0", NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "internal", NULL); + nmtst_assert_connection_verifies_after_normalization(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + nmtst_connection_normalize(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_assert(s_con == nm_connection_get_setting_connection(con)); + g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_OVS_PORT_SETTING_NAME); + g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "internal"); + break; + case 5: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "internal", NULL); + nm_connection_add_setting(con, nm_setting_ip4_config_new()); + nm_connection_add_setting(con, nm_setting_ip6_config_new()); + nm_connection_add_setting(con, nm_setting_proxy_new()); + s_ip4 = NM_SETTING_IP4_CONFIG(nm_connection_get_setting_ip4_config(con)); + s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(con)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, "auto", NULL); + g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, "auto", NULL); + nmtst_assert_connection_verifies_without_normalization(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + break; + case 6: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "internal", NULL); + nmtst_assert_connection_verifies_and_normalizable(con); + nmtst_connection_normalize(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME); + g_assert(s_con == nm_connection_get_setting_connection(con)); + g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_OVS_PORT_SETTING_NAME); + g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "internal"); + break; + case 7: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "system", NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + break; + case 8: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "bogus", NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + break; + case 9: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "patch", NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_SETTING); + break; + case 10: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "patch", NULL); + nm_connection_add_setting(con, nm_setting_ovs_patch_new()); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + break; + case 11: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "adsf", + NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "patch", NULL); + nm_connection_add_setting(con, nm_setting_ovs_patch_new()); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_MISSING_PROPERTY); + break; + case 12: + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "adsf", + NULL); + g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "patch", NULL); + s_ovs_patch = NM_SETTING_OVS_PATCH(nm_setting_ovs_patch_new()); + nm_connection_add_setting(con, NM_SETTING(s_ovs_patch)); + g_object_set(s_ovs_patch, NM_SETTING_OVS_PATCH_PEER, "1.2.3.4", NULL); + nmtst_assert_connection_verifies_and_normalizable(con); + nmtst_connection_normalize(con); + nmtst_assert_connection_has_settings(con, + NM_SETTING_CONNECTION_SETTING_NAME, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_PROXY_SETTING_NAME, + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + NM_SETTING_OVS_PATCH_SETTING_NAME); + g_assert(s_con == nm_connection_get_setting_connection(con)); + g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con)); + g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), + ==, + NM_SETTING_OVS_PORT_SETTING_NAME); + g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "patch"); + break; + default: + g_assert_not_reached(); + } +} + +static void +test_setting_ip4_gateway(void) +{ + NMConnection * conn; + NMSettingIPConfig *s_ip4; + NMIPAddress * addr; + GVariant * conn_dict, *ip4_dict, *value; + GVariantIter iter; + GVariant * addr_var; + guint32 addr_vals_0[] = {htonl(0xc0a8010a), 0x00000018, htonl(0x00000000)}; + guint32 addr_vals_1[] = {htonl(0xc0a8010b), 0x00000018, htonl(0xc0a80101)}; + GVariantBuilder addrs_builder; + GError * error = NULL; + + nmtst_assert_ip4_address(addr_vals_0[0], "192.168.1.10"); + + /* When serializing on the daemon side, ipv4.gateway is copied to the first + * entry of ipv4.addresses + */ + conn = nmtst_create_minimal_connection("test_setting_ip4_gateway", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new(); + g_object_set(s_ip4, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP4_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "192.168.1.1", + NULL); + nm_connection_add_setting(conn, NM_SETTING(s_ip4)); + + addr = nm_ip_address_new(AF_INET, "192.168.1.10", 24, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip4, addr); + nm_ip_address_unref(addr); + + _nm_utils_is_manager_process = TRUE; + conn_dict = nm_connection_to_dbus(conn, NM_CONNECTION_SERIALIZE_ALL); + _nm_utils_is_manager_process = FALSE; + g_object_unref(conn); + + ip4_dict = g_variant_lookup_value(conn_dict, + NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + g_assert(ip4_dict != NULL); + + value = g_variant_lookup_value(ip4_dict, NM_SETTING_IP_CONFIG_GATEWAY, G_VARIANT_TYPE_STRING); + g_assert(value != NULL); + g_assert_cmpstr(g_variant_get_string(value, NULL), ==, "192.168.1.1"); + g_variant_unref(value); + + value = g_variant_lookup_value(ip4_dict, NM_SETTING_IP_CONFIG_ADDRESSES, G_VARIANT_TYPE("aau")); + g_assert(value != NULL); + + g_variant_iter_init(&iter, value); + while (g_variant_iter_next(&iter, "@au", &addr_var)) { + const guint32 *addr_array; + gsize length; + + addr_array = g_variant_get_fixed_array(addr_var, &length, sizeof(guint32)); + g_assert_cmpint(length, ==, 3); + nmtst_assert_ip4_address(addr_array[2], "192.168.1.1"); + g_variant_unref(addr_var); + } + g_variant_unref(value); + + g_variant_unref(ip4_dict); + + /* When deserializing an old-style connection, the first non-0 gateway in + * ipv4.addresses is copied to :gateway. + */ + NMTST_VARIANT_EDITOR( + conn_dict, + NMTST_VARIANT_DROP_PROPERTY(NM_SETTING_IP4_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_GATEWAY); + NMTST_VARIANT_DROP_PROPERTY(NM_SETTING_IP4_CONFIG_SETTING_NAME, "address-data");); + + conn = _connection_new_from_dbus(conn_dict, &error); + g_assert_no_error(error); + + s_ip4 = (NMSettingIPConfig *) nm_connection_get_setting_ip4_config(conn); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1"); + + g_object_unref(conn); + + /* Try again with the gateway in the second address. */ + g_variant_builder_init(&addrs_builder, G_VARIANT_TYPE("aau")); + g_variant_builder_add(&addrs_builder, + "@au", + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, addr_vals_0, 3, 4)); + g_variant_builder_add(&addrs_builder, + "@au", + g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, addr_vals_1, 3, 4)); + + NMTST_VARIANT_EDITOR(conn_dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_IP4_CONFIG_SETTING_NAME, + "addresses", + "aau", + &addrs_builder);); + + conn = _connection_new_from_dbus(conn_dict, &error); + g_assert_no_error(error); + g_variant_unref(conn_dict); + + s_ip4 = (NMSettingIPConfig *) nm_connection_get_setting_ip4_config(conn); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1"); + + g_object_unref(conn); +} + +static void +test_setting_ip6_gateway(void) +{ + NMConnection * conn; + NMSettingIPConfig *s_ip6; + NMIPAddress * addr; + GVariant * conn_dict, *ip6_dict, *value; + GVariantIter iter; + GVariant * gateway_var; + GVariantBuilder addrs_builder; + guint8 addr_bytes_0[] = {0xab, + 0xcd, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0a}; + guint8 addr_bytes_1[] = {0xab, + 0xcd, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0b}; + guint8 gateway_bytes_1[] = {0xab, + 0xcd, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01}; + GError * error = NULL; + + /* When serializing on the daemon side, ipv6.gateway is copied to the first + * entry of ipv6.addresses + */ + conn = nmtst_create_minimal_connection("test_setting_ip6_gateway", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new(); + g_object_set(s_ip6, + NM_SETTING_IP_CONFIG_METHOD, + NM_SETTING_IP6_CONFIG_METHOD_MANUAL, + NM_SETTING_IP_CONFIG_GATEWAY, + "abcd::1", + NULL); + nm_connection_add_setting(conn, NM_SETTING(s_ip6)); + + addr = nm_ip_address_new(AF_INET6, "abcd::10", 64, &error); + g_assert_no_error(error); + nm_setting_ip_config_add_address(s_ip6, addr); + nm_ip_address_unref(addr); + + _nm_utils_is_manager_process = TRUE; + conn_dict = nm_connection_to_dbus(conn, NM_CONNECTION_SERIALIZE_ALL); + _nm_utils_is_manager_process = FALSE; + g_object_unref(conn); + + ip6_dict = g_variant_lookup_value(conn_dict, + NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_VARIANT_TYPE_SETTING); + g_assert(ip6_dict != NULL); + + value = g_variant_lookup_value(ip6_dict, NM_SETTING_IP_CONFIG_GATEWAY, G_VARIANT_TYPE_STRING); + g_assert(value != NULL); + g_assert_cmpstr(g_variant_get_string(value, NULL), ==, "abcd::1"); + g_variant_unref(value); + + value = g_variant_lookup_value(ip6_dict, + NM_SETTING_IP_CONFIG_ADDRESSES, + G_VARIANT_TYPE("a(ayuay)")); + g_assert(value != NULL); + + g_variant_iter_init(&iter, value); + while (g_variant_iter_next(&iter, "(@ayu@ay)", NULL, NULL, &gateway_var)) { + const guint8 *gateway_bytes; + gsize length; + + gateway_bytes = g_variant_get_fixed_array(gateway_var, &length, 1); + g_assert_cmpint(length, ==, 16); + nmtst_assert_ip6_address((struct in6_addr *) gateway_bytes, "abcd::1"); + g_variant_unref(gateway_var); + } + g_variant_unref(value); + + g_variant_unref(ip6_dict); + + /* When deserializing an old-style connection, the first non-0 gateway in + * ipv6.addresses is copied to :gateway. + */ + NMTST_VARIANT_EDITOR( + conn_dict, + NMTST_VARIANT_DROP_PROPERTY(NM_SETTING_IP6_CONFIG_SETTING_NAME, + NM_SETTING_IP_CONFIG_GATEWAY); + NMTST_VARIANT_DROP_PROPERTY(NM_SETTING_IP6_CONFIG_SETTING_NAME, "address-data");); + + conn = _connection_new_from_dbus(conn_dict, &error); + g_assert_no_error(error); + + s_ip6 = (NMSettingIPConfig *) nm_connection_get_setting_ip6_config(conn); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "abcd::1"); + + g_object_unref(conn); + + /* Try again with the gateway in the second address. */ + g_variant_builder_init(&addrs_builder, G_VARIANT_TYPE("a(ayuay)")); + g_variant_builder_add(&addrs_builder, + "(@ayu@ay)", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, addr_bytes_0, 16, 1), + 64, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, &in6addr_any, 16, 1)); + g_variant_builder_add(&addrs_builder, + "(@ayu@ay)", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, addr_bytes_1, 16, 1), + 64, + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, gateway_bytes_1, 16, 1)); + + NMTST_VARIANT_EDITOR(conn_dict, + NMTST_VARIANT_CHANGE_PROPERTY(NM_SETTING_IP6_CONFIG_SETTING_NAME, + "addresses", + "a(ayuay)", + &addrs_builder);); + + conn = _connection_new_from_dbus(conn_dict, &error); + g_assert_no_error(error); + g_variant_unref(conn_dict); + + s_ip6 = (NMSettingIPConfig *) nm_connection_get_setting_ip6_config(conn); + g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "abcd::1"); + + g_object_unref(conn); +} + +typedef struct { + const char * str; + const guint8 expected[20]; + const guint expected_len; +} HexItem; + +static void +test_setting_compare_default_strv(void) +{ + gs_unref_object NMConnection *c1 = NULL, *c2 = NULL; + char ** strv; + NMSettingIPConfig * s_ip2, *s_ip1; + gboolean compare; + GHashTable * out_settings = NULL; + + c1 = nmtst_create_minimal_connection("test_compare_default_strv", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + nmtst_assert_connection_verifies_and_normalizable(c1); + nmtst_connection_normalize(c1); + + c2 = nm_simple_connection_new_clone(c1); + nmtst_assert_connection_verifies_without_normalization(c2); + + nmtst_assert_connection_equals(c1, FALSE, c2, FALSE); + + s_ip1 = nm_connection_get_setting_ip4_config(c1); + s_ip2 = nm_connection_get_setting_ip4_config(c2); + + nm_setting_ip_config_clear_dns_options(s_ip2, FALSE); + g_object_get(G_OBJECT(s_ip2), NM_SETTING_IP_CONFIG_DNS_OPTIONS, &strv, NULL); + g_assert(!strv); + nmtst_assert_connection_equals(c1, FALSE, c2, FALSE); + + nm_setting_ip_config_clear_dns_options(s_ip2, TRUE); + g_object_get(G_OBJECT(s_ip2), NM_SETTING_IP_CONFIG_DNS_OPTIONS, &strv, NULL); + g_assert(strv && !strv[0]); + g_strfreev(strv); + + compare = nm_setting_diff((NMSetting *) s_ip1, + (NMSetting *) s_ip2, + NM_SETTING_COMPARE_FLAG_EXACT, + FALSE, + &out_settings); + g_assert(!compare); + g_assert(out_settings); + g_assert(g_hash_table_contains(out_settings, NM_SETTING_IP_CONFIG_DNS_OPTIONS)); + g_hash_table_unref(out_settings); + out_settings = NULL; + + compare = nm_connection_diff(c1, c2, NM_SETTING_COMPARE_FLAG_EXACT, &out_settings); + g_assert(!compare); + g_assert(out_settings); + g_hash_table_unref(out_settings); + out_settings = NULL; +} + +/*****************************************************************************/ + +static void +test_setting_user_data(void) +{ + gs_unref_object NMSettingUser *s_user = NULL; + + s_user = NM_SETTING_USER(nm_setting_user_new()); +} + +/*****************************************************************************/ + +typedef union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; +} SockAddrUnion; + +static void +_sock_addr_endpoint(const char *endpoint, const char *host, gint32 port) +{ + nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL; + const char * s_endpoint; + const char * s_host; + gint32 s_port; + SockAddrUnion sockaddr = {}; + + g_assert(endpoint); + g_assert((!host) == (port == -1)); + g_assert(port >= -1 && port <= G_MAXUINT16); + + ep = nm_sock_addr_endpoint_new(endpoint); + g_assert(ep); + + s_endpoint = nm_sock_addr_endpoint_get_endpoint(ep); + s_host = nm_sock_addr_endpoint_get_host(ep); + s_port = nm_sock_addr_endpoint_get_port(ep); + g_assert_cmpstr(endpoint, ==, s_endpoint); + g_assert_cmpstr(host, ==, s_host); + g_assert_cmpint(port, ==, s_port); + + g_assert(!nm_sock_addr_endpoint_get_fixed_sockaddr(ep, &sockaddr)); + + if (endpoint[0] != ' ') { + gs_free char *endpoint2 = NULL; + + /* also test with a leading space */ + endpoint2 = g_strdup_printf(" %s", endpoint); + _sock_addr_endpoint(endpoint2, host, port); + } + + if (endpoint[0] && endpoint[strlen(endpoint) - 1] != ' ') { + gs_free char *endpoint2 = NULL; + + /* also test with a trailing space */ + endpoint2 = g_strdup_printf("%s ", endpoint); + _sock_addr_endpoint(endpoint2, host, port); + } +} + +static void +_sock_addr_endpoint_fixed(const char *endpoint, const char *host, guint16 port, guint scope_id) +{ + nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL; + const char * s_endpoint; + const char * s_host; + gint32 s_port; + int addr_family; + NMIPAddr addrbin; + SockAddrUnion sockaddr = {}; + + g_assert(endpoint); + g_assert(host); + g_assert(port > 0); + + if (!nm_utils_parse_inaddr_bin(AF_UNSPEC, host, &addr_family, &addrbin)) + g_assert_not_reached(); + + ep = nm_sock_addr_endpoint_new(endpoint); + g_assert(ep); + + s_endpoint = nm_sock_addr_endpoint_get_endpoint(ep); + s_host = nm_sock_addr_endpoint_get_host(ep); + s_port = nm_sock_addr_endpoint_get_port(ep); + g_assert_cmpstr(endpoint, ==, s_endpoint); + g_assert_cmpstr(NULL, !=, s_host); + g_assert_cmpint(port, ==, s_port); + + if (!nm_sock_addr_endpoint_get_fixed_sockaddr(ep, &sockaddr)) + g_assert_not_reached(); + + g_assert_cmpint(sockaddr.sa.sa_family, ==, addr_family); + if (addr_family == AF_INET) { + const SockAddrUnion s = { + .in = + { + .sin_family = AF_INET, + .sin_addr = addrbin.addr4_struct, + .sin_port = htons(port), + }, + }; + + g_assert_cmpint(sockaddr.in.sin_addr.s_addr, ==, addrbin.addr4); + g_assert_cmpint(sockaddr.in.sin_port, ==, htons(port)); + g_assert(memcmp(&s, &sockaddr, sizeof(s.in)) == 0); + } else if (addr_family == AF_INET6) { + const SockAddrUnion s = { + .in6 = + { + .sin6_family = AF_INET6, + .sin6_addr = addrbin.addr6, + .sin6_scope_id = scope_id, + .sin6_port = htons(port), + }, + }; + + g_assert(memcmp(&sockaddr.in6.sin6_addr, &addrbin, sizeof(addrbin.addr6)) == 0); + g_assert_cmpint(sockaddr.in6.sin6_port, ==, htons(port)); + g_assert_cmpint(sockaddr.in6.sin6_scope_id, ==, scope_id); + g_assert_cmpint(sockaddr.in6.sin6_flowinfo, ==, 0); + g_assert(memcmp(&s, &sockaddr, sizeof(s.in6)) == 0); + } else + g_assert_not_reached(); +} + +static void +test_sock_addr_endpoint(void) +{ + _sock_addr_endpoint("", NULL, -1); + _sock_addr_endpoint(":", NULL, -1); + _sock_addr_endpoint("a", NULL, -1); + _sock_addr_endpoint("a:", NULL, -1); + _sock_addr_endpoint(":a", NULL, -1); + _sock_addr_endpoint("[]:a", NULL, -1); + _sock_addr_endpoint("[]a", NULL, -1); + _sock_addr_endpoint("[]:", NULL, -1); + _sock_addr_endpoint("[a]b", NULL, -1); + _sock_addr_endpoint("[a:b", NULL, -1); + _sock_addr_endpoint("[a[:b", NULL, -1); + _sock_addr_endpoint("a:6", "a", 6); + _sock_addr_endpoint("a:6", "a", 6); + _sock_addr_endpoint("[a]:6", "a", 6); + _sock_addr_endpoint("[a]:6", "a", 6); + _sock_addr_endpoint("[a]:655", "a", 655); + _sock_addr_endpoint("[ab]:][6", NULL, -1); + _sock_addr_endpoint("[ab]:]:[6", NULL, -1); + _sock_addr_endpoint("[a[]:b", NULL, -1); + _sock_addr_endpoint("[192.169.6.x]:6", "192.169.6.x", 6); + _sock_addr_endpoint("[192.169.6.x]:0", NULL, -1); + _sock_addr_endpoint("192.169.6.7:0", NULL, -1); + + _sock_addr_endpoint_fixed("192.169.6.7:6", "192.169.6.7", 6, 0); + _sock_addr_endpoint_fixed("[192.169.6.7]:6", "192.169.6.7", 6, 0); + _sock_addr_endpoint_fixed("[a:b::]:6", "a:b::", 6, 0); + _sock_addr_endpoint_fixed("[a:b::%7]:6", "a:b::", 6, 7); + _sock_addr_endpoint_fixed("a:b::1%75:6", "a:b::1", 6, 75); + _sock_addr_endpoint_fixed("a:b::1%0:64", "a:b::1", 64, 0); +} + +/*****************************************************************************/ + +static void +test_hexstr2bin(void) +{ + static const HexItem items[] = { + {"aaBBCCddDD10496a", {0xaa, 0xbb, 0xcc, 0xdd, 0xdd, 0x10, 0x49, 0x6a}, 8}, + {"aa:bb:cc:dd:10:49:6a", {0xaa, 0xbb, 0xcc, 0xdd, 0x10, 0x49, 0x6a}, 7}, + {"0xccddeeff", {0xcc, 0xdd, 0xee, 0xff}, 4}, + {"1:2:66:77:80", {0x01, 0x02, 0x66, 0x77, 0x80}, 5}, + {"e", {0x0e}, 1}, + {"ef", {0xef}, 1}, + {"efa"}, + {"efad", {0xef, 0xad}, 2}, + {"ef:a", {0xef, 0x0a}, 2}, + {"aabb1199:"}, + {":aabb1199"}, + {"aabb$$dd"}, + {"aab:ccc:ddd"}, + {"aab::ccc:ddd"}, + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS(items); i++) { + gs_unref_bytes GBytes *b = NULL; + + b = nm_utils_hexstr2bin(items[i].str); + if (items[i].expected_len) + g_assert(b); + else + g_assert(!b); + g_assert(nm_utils_gbytes_equal_mem(b, items[i].expected, items[i].expected_len)); + } +} + +/*****************************************************************************/ + +static void +_do_strquote(const char *str, gsize buf_len, const char *expected) +{ + char canary = (char) nmtst_get_rand_uint32(); + gs_free char *buf_full = g_malloc(buf_len + 2); + char * buf = &buf_full[1]; + const char * b; + + buf[-1] = canary; + buf[buf_len] = canary; + + if (buf_len == 0) { + b = nm_strquote(NULL, 0, str); + g_assert(b == NULL); + g_assert(expected == NULL); + b = nm_strquote(buf, 0, str); + g_assert(b == buf); + } else { + b = nm_strquote(buf, buf_len, str); + g_assert(b == buf); + g_assert(strlen(b) < buf_len); + g_assert_cmpstr(expected, ==, b); + } + + g_assert(buf[-1] == canary); + g_assert(buf[buf_len] == canary); +} + +static void +test_nm_strquote(void) +{ + _do_strquote(NULL, 0, NULL); + _do_strquote("", 0, NULL); + _do_strquote("a", 0, NULL); + _do_strquote("ab", 0, NULL); + + _do_strquote(NULL, 1, ""); + _do_strquote(NULL, 2, "("); + _do_strquote(NULL, 3, "(n"); + _do_strquote(NULL, 4, "(nu"); + _do_strquote(NULL, 5, "(nul"); + _do_strquote(NULL, 6, "(null"); + _do_strquote(NULL, 7, "(null)"); + _do_strquote(NULL, 8, "(null)"); + _do_strquote(NULL, 100, "(null)"); + + _do_strquote("", 1, ""); + _do_strquote("", 2, "^"); + _do_strquote("", 3, "\"\""); + _do_strquote("", 4, "\"\""); + _do_strquote("", 5, "\"\""); + _do_strquote("", 100, "\"\""); + + _do_strquote("a", 1, ""); + _do_strquote("a", 2, "^"); + _do_strquote("a", 3, "\"^"); + _do_strquote("a", 4, "\"a\""); + _do_strquote("a", 5, "\"a\""); + _do_strquote("a", 6, "\"a\""); + _do_strquote("a", 100, "\"a\""); + + _do_strquote("ab", 1, ""); + _do_strquote("ab", 2, "^"); + _do_strquote("ab", 3, "\"^"); + _do_strquote("ab", 4, "\"a^"); + _do_strquote("ab", 5, "\"ab\""); + _do_strquote("ab", 6, "\"ab\""); + _do_strquote("ab", 7, "\"ab\""); + _do_strquote("ab", 100, "\"ab\""); + + _do_strquote("abc", 1, ""); + _do_strquote("abc", 2, "^"); + _do_strquote("abc", 3, "\"^"); + _do_strquote("abc", 4, "\"a^"); + _do_strquote("abc", 5, "\"ab^"); + _do_strquote("abc", 6, "\"abc\""); + _do_strquote("abc", 7, "\"abc\""); + _do_strquote("abc", 100, "\"abc\""); +} + +/*****************************************************************************/ + +#define UUID_NS_ZERO "00000000-0000-0000-0000-000000000000" +#define UUID_NS_DNS "6ba7b810-9dad-11d1-80b4-00c04fd430c8" +#define UUID_NS_URL "6ba7b811-9dad-11d1-80b4-00c04fd430c8" +#define UUID_NS_OID "6ba7b812-9dad-11d1-80b4-00c04fd430c8" +#define UUID_NS_X500 "6ba7b814-9dad-11d1-80b4-00c04fd430c8" + +static const NMUuid * +_uuid(const char *str) +{ + static NMUuid u; + + g_assert(str); + g_assert(nm_uuid_parse(str, &u)); + return &u; +} + +static void +_test_uuid(int uuid_type, + const char *expected_uuid, + const char *str, + gssize slen, + gpointer type_args) +{ + gs_free char *uuid_test = NULL; + + uuid_test = nm_uuid_generate_from_string_str(str, slen, uuid_type, type_args); + + g_assert(uuid_test); + g_assert(nm_utils_is_uuid(uuid_test)); + + if (!nm_streq(uuid_test, expected_uuid)) { + g_error("UUID test failed: type=%d; text=%s, len=%lld, ns=%s, uuid=%s, expected=%s", + uuid_type, + str, + (long long) slen, + NM_IN_SET(uuid_type, NM_UUID_TYPE_VERSION3, NM_UUID_TYPE_VERSION5) + ? (((const char *) type_args) ?: "(all-zero)") + : (type_args ? "(unknown)" : "(null)"), + uuid_test, + expected_uuid); + } + + if (slen < 0) { + /* also test that passing slen==-1 yields the same result as passing strlen(str). */ + _test_uuid(uuid_type, expected_uuid, str, strlen(str), type_args); + } else if (str && slen == 0) { + /* also test if we accept NULL for slen==0 */ + _test_uuid(uuid_type, expected_uuid, NULL, 0, type_args); + } + + if (NM_IN_SET(uuid_type, NM_UUID_TYPE_VERSION3, NM_UUID_TYPE_VERSION5) && !type_args) { + /* For version3 and version5, a missing @type_args is equal to UUID_NS_ZERO */ + _test_uuid(uuid_type, expected_uuid, str, slen, UUID_NS_ZERO); + } +} + +typedef struct { + const char *uuid3; + const char *uuid5; +} ExpectedUuids; + +static void +test_nm_utils_uuid_generate_from_string(void) +{ + const ExpectedUuids zero_uuids[] = { + { + .uuid3 = "19826852-5007-3022-a72a-212f66e9fac3", + .uuid5 = "b6c54489-38a0-5f50-a60a-fd8d76219cae", + }, + { + .uuid3 = "9153af2e-fc8e-34f3-9e8b-81f73b33d0cb", + .uuid5 = "11116e73-1c03-5de6-9130-5f9925ae8ab4", + }, + { + .uuid3 = "2f06a3ae-d78d-30d7-b898-088a0e0b76f6", + .uuid5 = "1087ebe8-1ef8-5d97-8873-735b4949004d", + }, + { + .uuid3 = "aca948e0-1468-3a51-9f2e-c688a484efd7", + .uuid5 = "7e57d004-2b97-5e7a-b45f-5387367791cd", + }, + { + .uuid3 = "b74e537a-53e8-3808-9abd-58546a6542bd", + .uuid5 = "1dd80df1-492c-5dc5-aec2-6bf0e104f923", + }, + { + .uuid3 = "1b00958a-7d76-3d08-8aba-c66c5828658c", + .uuid5 = "f797f61e-a392-5acf-af25-b46057f1c8e8", + }, + { + .uuid3 = "7ba18f7d-c9cf-3b48-a89e-ad79243135cc", + .uuid5 = "e02c9780-2fc5-5d57-b92f-4cc3a64bff16", + }, + { + .uuid3 = "9baf0978-1a60-35c5-9e9b-bec8d259fd4e", + .uuid5 = "94167980-f909-527e-a4af-bc3155f586d3", + }, + { + .uuid3 = "588668c0-7631-39c7-9976-c7d414adf7ba", + .uuid5 = "9e3eefda-b56e-56bd-8a3a-0b8009d4a536", + }, + { + .uuid3 = "8edb3613-9612-3b32-9dd7-0a01aa8ed453", + .uuid5 = "9b75648e-d38c-54e8-adee-1fb295a079c9", + }, + { + .uuid3 = "f3b34394-63a5-3773-9014-1f8a50d765b8", + .uuid5 = "dd56b598-9e74-58c3-b3e8-2c623780b8ed", + }, + { + .uuid3 = "0572965f-05b8-342b-b225-d5c29d449eee", + .uuid5 = "5666449a-fb7e-55b7-ae9f-0552e6513a10", + }, + { + .uuid3 = "6f7177c3-77b0-3f42-82a8-7031e25fcccf", + .uuid5 = "10b38db9-82fc-528e-9ddb-1f09b7dbf907", + }, + { + .uuid3 = "d1e0f845-bc1b-368c-b8c8-49ab0b9e486b", + .uuid5 = "85492596-9468-5845-9c7f-d4ae999cb751", + }, + { + .uuid3 = "46371ea3-c8a3-34d8-b2cf-2fa90bda4378", + .uuid5 = "22b1c0dd-aa5d-54a4-8768-5adfd0d112bd", + }, + { + .uuid3 = "f1e6b499-9b68-343b-a5c5-ece7acc49a68", + .uuid5 = "9cc429f8-200e-52a3-9e3b-ef134afa1e29", + }, + { + .uuid3 = "9ed06458-c712-31dd-aba5-6cf79879fabe", + .uuid5 = "3949f95c-5d76-5ee2-af60-8e2d8fcf649d", + }, + { + .uuid3 = "4ddd5cd7-bc83-36aa-909c-4e660f57c830", + .uuid5 = "0e994a02-069b-58fb-b3a4-d7dc94e90fca", + }, + { + .uuid3 = "335fa537-0909-395d-a696-6f41827dcbeb", + .uuid5 = "17db3a41-de9b-5c6b-904d-833943209b3c", + }, + { + .uuid3 = "dbd58444-05ad-3edd-adc7-4393ecbcb43c", + .uuid5 = "1bd906f2-05f9-5ab5-a39a-4c17a188f886", + }, + { + .uuid3 = "a1c62d82-d13c-361b-8f4e-ca91bc2f7fc5", + .uuid5 = "ce6550fd-95b7-57e4-9aa7-461522666be4", + }, + { + .uuid3 = "e943d83e-3f82-307f-81ed-b7a7bcd0743e", + .uuid5 = "04aa09ee-b420-57ac-8a23-5d99907fb0a1", + }, + { + .uuid3 = "cabf46dd-9f09-375c-8f6e-f2a8cf114709", + .uuid5 = "8ece2c62-0c31-5c55-b7c6-155381e3780e", + }, + { + .uuid3 = "19beddf3-f2fb-340f-96ac-4f394960b7a7", + .uuid5 = "5762a9f9-9a21-59ab-b0d2-2cb90027ef7f", + }, + { + .uuid3 = "08d835c2-f4ca-394c-ba7f-2494d8b60c6c", + .uuid5 = "23c8409d-4b5f-5b6a-b946-41e49bad6c78", + }, + { + .uuid3 = "3b8c6847-5331-35bf-9cd9-ced50e53cd7c", + .uuid5 = "e8e396be-95d5-5569-8edc-e0b64c2b7613", + }, + { + .uuid3 = "e601f160-484b-3254-8f3b-0a25c7203d8a", + .uuid5 = "bc8b3cbc-ad5b-5808-a1b0-e0f7a1ad68a3", + }, + { + .uuid3 = "e5e492ed-5349-379d-b7de-a370a51e44a3", + .uuid5 = "62c5ed3f-9afa-59ad-874f-a9dd8afc69d4", + }, + { + .uuid3 = "c40111f6-fe97-305e-bfce-7db730c3d2ec", + .uuid5 = "66877a72-7243-59ed-b9e3-b5023b6da9c2", + }, + { + .uuid3 = "21e18ea8-95c2-362b-9ca9-25d6a0ff2dff", + .uuid5 = "49a49eee-7e86-5d66-837a-8a8810cb5562", + }, + { + .uuid3 = "adab623b-1343-307f-80d8-58d005376ad9", + .uuid5 = "e4a2a7ed-3bf3-53cf-a2bb-154dbb39a38c", + }, + { + .uuid3 = "67e9fc7c-dafe-356d-ac1a-a63ce3f44813", + .uuid5 = "50cacfc9-f5d2-52dd-897c-a25a0927b816", + }, + { + .uuid3 = "36cc7f20-126c-3e40-94e7-737ac7486547", + .uuid5 = "ca629991-3f2b-5e86-9bb7-37a335f7d809", + }, + { + .uuid3 = "fe282996-ac5e-3d13-b478-5def30007a8e", + .uuid5 = "c1adf8a7-f72a-58ae-82d5-d18807f12e2e", + }, + { + .uuid3 = "3bfe339c-05ae-3233-a1a5-ebf1ead589db", + .uuid5 = "6120c3cd-24e1-5ce4-987b-f8bfee2e4633", + }, + { + .uuid3 = "d1d90bc7-da4a-3cd7-a7c8-a1a89765d8ee", + .uuid5 = "433d6a26-c319-5fcf-9a30-5ec6ad59d109", + }, + { + .uuid3 = "10b88a02-0102-359b-81e9-7e3b0ff7d25e", + .uuid5 = "77d228d9-1b96-59e2-a07e-a8fdd4f62884", + }, + { + .uuid3 = "7da5e4f2-6df0-3aca-a1b0-b7f8b1340e1d", + .uuid5 = "698259bf-a32b-5e00-9ec6-88b12278c4ad", + }, + { + .uuid3 = "cbe24d98-ca20-3058-86b6-24a6b36ceff0", + .uuid5 = "60dbca63-704f-5666-9f64-f4e1a630c4aa", + }, + { + .uuid3 = "04d84e6a-b793-3993-afbf-bae7cfc42b49", + .uuid5 = "79d63ec0-a39d-557d-8299-f4c97acfadc3", + }, + { + .uuid3 = "fdd157d8-a537-350a-9cc9-1930e8666c63", + .uuid5 = "7df7f75e-a146-5a76-828b-bac052db312b", + }, + { + .uuid3 = "0bea36bb-24a7-3ee6-a98d-116433c14cd4", + .uuid5 = "2bcca2e9-2879-53e3-b09d-cbbfd58771b2", + }, + { + .uuid3 = "52b040a4-1b84-32d2-b758-f82386f7e0f0", + .uuid5 = "cb7bdca3-e9f7-50cd-b72e-73cb9ff24f62", + }, + { + .uuid3 = "0f0a4e26-e034-3021-acf2-4e886af43092", + .uuid5 = "8e428e2b-5da3-5368-b760-5ca07ccbd819", + }, + { + .uuid3 = "819d3cd1-afe5-3e4a-9f0c-945e25d09879", + .uuid5 = "f340ef4d-139c-567a-b0fc-7c495336674e", + }, + { + .uuid3 = "e7df1a3b-c9f8-3e5a-88d6-ba72b2a0f27b", + .uuid5 = "7e3f5fd2-3c93-58d6-9f35-6e0192445b11", + }, + { + .uuid3 = "0854bedf-74ba-3f2b-b823-dc2c90d27c76", + .uuid5 = "bc112b6b-c5de-5ee9-b816-808792743a20", + }, + { + .uuid3 = "a1b8c3ba-f821-32ef-a3fd-b97b3855efa8", + .uuid5 = "47f8f82d-9fcd-553c-90c5-3f3cb3ad00ad", + }, + { + .uuid3 = "9458f819-079b-3033-9430-ba10f576c067", + .uuid5 = "bee5c091-5f01-51fa-86bb-e9488fd3b4da", + }, + { + .uuid3 = "8e1f240a-e386-3e00-866a-6f9da1e3503f", + .uuid5 = "8ea92cea-d741-566f-a44a-d51e65b4c5e4", + }, + }; + const ExpectedUuids dns_uuids[] = { + { + .uuid3 = "4385125b-dd1e-3025-880f-3311517cc8d5", + .uuid5 = "6af613b6-569c-5c22-9c37-2ed93f31d3af", + }, + { + .uuid3 = "afd0b036-625a-3aa8-b639-9dc8c8fff0ff", + .uuid5 = "b04965e6-a9bb-591f-8f8a-1adcb2c8dc39", + }, + { + .uuid3 = "9c45c2f1-1761-3daa-ad31-1ff8703ae846", + .uuid5 = "4b166dbe-d99d-5091-abdd-95b83330ed3a", + }, + { + .uuid3 = "15e0ba07-10e4-3d7f-aaff-c00fed873c88", + .uuid5 = "98123fde-012f-5ff3-8b50-881449dac91a", + }, + { + .uuid3 = "bc27b4db-bc0f-34f9-ae8e-4b72f2d51b60", + .uuid5 = "6ed955c6-506a-5343-9be4-2c0afae02eef", + }, + { + .uuid3 = "7586bfed-b8b8-3bb3-9c95-09a4a79dc0f7", + .uuid5 = "c8691da2-158a-5ed6-8537-0e6f140801f2", + }, + { + .uuid3 = "881430b6-8d28-3175-b87d-e81f2f5978c6", + .uuid5 = "a6c4fc8f-6950-51de-a9ae-2c519c465071", + }, + { + .uuid3 = "24075675-98ae-354e-89ca-0126a9ad36e3", + .uuid5 = "a9f96b98-dd44-5216-ab0d-dbfc6b262edf", + }, + { + .uuid3 = "2c269ea4-dbfd-32dd-9bd7-a5c22677d18b", + .uuid5 = "e99caacd-6c45-5906-bd9f-b79e62f25963", + }, + { + .uuid3 = "44eb0948-118f-3f28-87e4-f61c8f889aba", + .uuid5 = "e4d80b30-151e-51b5-9f4f-18a3b82718e6", + }, + { + .uuid3 = "fc72beeb-f790-36ee-a73d-33888c9d8880", + .uuid5 = "0159d6c7-973f-5e7a-a9a0-d195d0ea6fe2", + }, + { + .uuid3 = "1e46afa2-6176-3cd3-9750-3015846723df", + .uuid5 = "7fef88f7-411d-5669-b42d-bf5fc7f9b58b", + }, + { + .uuid3 = "0042b01d-95bd-343f-bd9f-3186bfd63508", + .uuid5 = "52524d6e-10dc-5261-aa36-8b2efcbaa5f0", + }, + { + .uuid3 = "115ff52f-d605-3b4b-98fe-c0ea57f4930c", + .uuid5 = "91c274f2-9a0d-5ce6-ac3d-7529f452df21", + }, + { + .uuid3 = "ed0221e8-ac7d-393b-821d-25183567885b", + .uuid5 = "0ff1e264-520d-543a-87dd-181a491e667e", + }, + { + .uuid3 = "508ef333-85a6-314c-bcf3-17ddc32b2216", + .uuid5 = "23986425-d3a5-5e13-8bab-299745777a8d", + }, + { + .uuid3 = "a4715ee0-524a-37cc-beb2-a0b5030757b7", + .uuid5 = "c15b38c9-9a3e-543c-a703-dd742f25b4d5", + }, + { + .uuid3 = "d1c72756-aaec-3470-a2f2-97415f44d72f", + .uuid5 = "db680066-c83d-5ed7-89a4-1d79466ea62d", + }, + { + .uuid3 = "7aec2f01-586e-3d53-b8f3-6cf7e6b649a4", + .uuid5 = "cadb7952-2bba-5609-88d4-8e47ec4e7920", + }, + { + .uuid3 = "3d234b88-8d6f-319a-91ea-edb6059fc825", + .uuid5 = "35140057-a2a4-5adb-a500-46f8ed8b66a9", + }, + { + .uuid3 = "d2568554-93ec-30c7-9e15-f383be19e5bb", + .uuid5 = "66e549b7-01e2-5d07-98d5-430f74d8d3b2", + }, + { + .uuid3 = "800e59a7-dd0f-3114-8e58-ab7e213895ca", + .uuid5 = "292c8e99-2378-55aa-83d8-350e0ac3f1cc", + }, + { + .uuid3 = "3b7d03f0-e067-3d72-84f4-e410ac36ef57", + .uuid5 = "0e3b230a-0509-55d8-96a0-9875f387a2be", + }, + { + .uuid3 = "8762be68-de95-391a-94a0-c5fd0446e037", + .uuid5 = "4c507660-a83b-55c0-9b2b-83eccb07723d", + }, + { + .uuid3 = "2bd8b4c9-01af-3cd0-aced-94ee6e2004b8", + .uuid5 = "a1b9b633-da11-58be-b1a9-5cfa2848f186", + }, + { + .uuid3 = "a627d6a4-394a-33f5-b68e-22bfb6488d01", + .uuid5 = "c2708a8b-120a-56f5-a30d-990048af87cc", + }, + { + .uuid3 = "6a592510-17d9-3925-b321-4a8d4927f8d0", + .uuid5 = "e7263999-68b6-5a23-b530-af25b7efd632", + }, + { + .uuid3 = "9ee72491-59c4-333c-bb93-fe733a842fdb", + .uuid5 = "ce1ae2d5-3454-5952-97ff-36ff935bcfe9", + }, + { + .uuid3 = "2591c62c-0a9d-3c28-97bc-fa0401556a3c", + .uuid5 = "33677b87-bc8d-5ff6-9a25-fe60225e4bf0", + }, + { + .uuid3 = "7912be1e-4562-373b-92e2-3d6d2123bc8e", + .uuid5 = "ed2305ae-e8f9-5387-b860-3d80ae6c02f7", + }, + { + .uuid3 = "09370cda-89a4-3a48-b592-9c0486e0d5e4", + .uuid5 = "604ed872-ae2d-5d91-8e3e-572f3a3aaaa5", + }, + { + .uuid3 = "de5980d3-a137-373c-850b-ca3e5f100779", + .uuid5 = "8f8173d9-2f8d-5636-a693-24d9f79ba651", + }, + { + .uuid3 = "9441501d-f633-365a-8955-9df443edc762", + .uuid5 = "36eb8d4d-b854-51f1-9fdf-3735964225d5", + }, + { + .uuid3 = "434ada18-13ce-3c08-8b40-a1a1ae030569", + .uuid5 = "3493b6ca-f84b-56a9-97cc-c0bd1c46c4c0", + }, + { + .uuid3 = "a13b6160-bd23-3710-a150-41d800dd30b4", + .uuid5 = "f413ea13-fcd9-5b44-9d22-1fa1f7b063a5", + }, + { + .uuid3 = "73a67c12-c5f0-3288-ad6a-c78aea0917b0", + .uuid5 = "f468d924-d23b-56c2-b90f-3d1cf4b45337", + }, + { + .uuid3 = "a126ee4f-a222-357d-b71b-7d3f226c559f", + .uuid5 = "8828c9d6-ed76-5c09-bf64-ba9e9cd90896", + }, + { + .uuid3 = "48f4f36b-b015-3137-9b6e-351bb175c7f7", + .uuid5 = "facb7618-55ca-5c30-9cba-fd567b6c0611", + }, + { + .uuid3 = "3fe8f6a3-fe4a-3487-89d6-dd06c6ad02e3", + .uuid5 = "96f3de0e-6412-5434-b406-67ef3352ab85", + }, + { + .uuid3 = "d68fa2d4-adc9-3b20-ac77-42585cd1d59f", + .uuid5 = "9ebacb89-40ab-52b3-93a2-9054611d8f55", + }, + { + .uuid3 = "819f86a3-31d5-3e72-a83e-142c3a3e4832", + .uuid5 = "681046ff-9129-5ade-b11c-769864e02184", + }, + { + .uuid3 = "9957b433-ddc8-3113-a3e6-5512cf13dab1", + .uuid5 = "c13d0b5d-1ca3-57b6-a23f-8586bca44928", + }, + { + .uuid3 = "5aab6e0c-b7d3-379c-92e3-2bfbb5572511", + .uuid5 = "7c411b5e-9d3f-50b5-9c28-62096e41c4ed", + }, + { + .uuid3 = "11c8ff30-3a7d-3547-80a7-d61b8abeeda8", + .uuid5 = "f825aafe-6696-5121-b263-6b2c408b7f43", + }, + { + .uuid3 = "98799b9f-1c5e-30b3-930f-e412b862cbe4", + .uuid5 = "f2b4caea-61c3-5bed-8ce7-d8b9d16e129e", + }, + { + .uuid3 = "9bdf2544-31d8-3555-94b0-6a749118a996", + .uuid5 = "3593855a-6557-5736-8cab-172c6987f949", + }, + { + .uuid3 = "ddcfb9b3-e990-3985-9021-546a2711e7e5", + .uuid5 = "36392431-d554-5385-b876-7bc6e1cb26b3", + }, + { + .uuid3 = "190d7a78-1484-3136-80a6-40f28852785c", + .uuid5 = "7e645493-0898-5501-8155-e8578b4f5224", + }, + { + .uuid3 = "6ed693e4-7dc0-3210-856b-a6eb4cc73e13", + .uuid5 = "14dc6a81-0491-5683-baaf-7582a61c5798", + }, + { + .uuid3 = "b6a14b21-e73a-3ce2-9076-a804c434f5c6", + .uuid5 = "883e0a9c-e3b3-5f9c-8073-2913cbbb99ec", + }, + }; + char i_str[30]; + guint i; + + _test_uuid(NM_UUID_TYPE_LEGACY, "d41d8cd9-8f00-b204-e980-0998ecf8427e", "", -1, NULL); + _test_uuid(NM_UUID_TYPE_LEGACY, "0cc175b9-c0f1-b6a8-31c3-99e269772661", "a", -1, NULL); + _test_uuid(NM_UUID_TYPE_LEGACY, "098f6bcd-4621-d373-cade-4e832627b4f6", "test", -1, NULL); + _test_uuid(NM_UUID_TYPE_LEGACY, "70350f60-27bc-e371-3f6b-76473084309b", "a\0b", 3, NULL); + _test_uuid(NM_UUID_TYPE_LEGACY, + "59c0547b-7fe2-1c15-2cce-e328e8bf6742", + "/etc/NetworkManager/system-connections/em1", + -1, + NULL); + + _test_uuid(NM_UUID_TYPE_VERSION3, "4ae71336-e44b-39bf-b9d2-752e234818a5", "", -1, NULL); + _test_uuid(NM_UUID_TYPE_VERSION3, "0531103a-d8fc-3dd4-b972-d98e4750994e", "a", -1, NULL); + _test_uuid(NM_UUID_TYPE_VERSION3, "96e17d7a-ac89-38cf-95e1-bf5098da34e1", "test", -1, NULL); + _test_uuid(NM_UUID_TYPE_VERSION3, "8156568e-4ae6-3f34-a93e-18e2c6cbbf78", "a\0b", 3, NULL); + + _test_uuid(NM_UUID_TYPE_VERSION3, "c87ee674-4ddc-3efe-a74e-dfe25da5d7b3", "", -1, UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION3, "4c104dd0-4821-30d5-9ce3-0e7a1f8b7c0d", "a", -1, UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION3, + "45a113ac-c7f2-30b0-90a5-a399ab912716", + "test", + -1, + UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION3, + "002a0ada-f547-375a-bab5-896a11d1927e", + "a\0b", + 3, + UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION3, + "9a75f5f2-195e-31a9-9d07-8c18b5d3b285", + "test123", + -1, + UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION3, "ec794efe-a384-3b11-a0b6-ec8995bc6acc", "x", -1, UUID_NS_DNS); + + _test_uuid(NM_UUID_TYPE_VERSION5, "a7650b9f-f19f-5300-8a13-91160ea8de2c", "a\0b", 3, NULL); + _test_uuid(NM_UUID_TYPE_VERSION5, "4f3f2898-69e3-5a0d-820a-c4e87987dbce", "a", -1, UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION5, "05b16a01-46c6-56dd-bd6e-c6dfb4a1427a", "x", -1, UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION5, + "c9ed566a-6b79-5d3a-b2b7-96a936b48cf3", + "test123", + -1, + UUID_NS_DNS); + + for (i = 0; i < G_N_ELEMENTS(zero_uuids); i++) { + nm_sprintf_buf(i_str, "%u", i), + _test_uuid(NM_UUID_TYPE_VERSION3, zero_uuids[i].uuid3, i_str, -1, NULL); + _test_uuid(NM_UUID_TYPE_VERSION5, zero_uuids[i].uuid5, i_str, -1, NULL); + } + for (i = 0; i < G_N_ELEMENTS(dns_uuids); i++) { + nm_sprintf_buf(i_str, "%u", i), + _test_uuid(NM_UUID_TYPE_VERSION3, dns_uuids[i].uuid3, i_str, -1, UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION5, dns_uuids[i].uuid5, i_str, -1, UUID_NS_DNS); + } + + /* examples from cpython unit tests: */ + _test_uuid(NM_UUID_TYPE_VERSION3, + "6fa459ea-ee8a-3ca4-894e-db77e160355e", + "python.org", + -1, + UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION5, + "886313e1-3b8a-5372-9b90-0c9aee199e5d", + "python.org", + -1, + UUID_NS_DNS); + _test_uuid(NM_UUID_TYPE_VERSION3, + "9fe8e8c4-aaa8-32a9-a55c-4535a88b748d", + "http://python.org/", + -1, + UUID_NS_URL); + _test_uuid(NM_UUID_TYPE_VERSION5, + "4c565f0d-3f5a-5890-b41b-20cf47701c5e", + "http://python.org/", + -1, + UUID_NS_URL); + _test_uuid(NM_UUID_TYPE_VERSION3, + "dd1a1cef-13d5-368a-ad82-eca71acd4cd1", + "1.3.6.1", + -1, + UUID_NS_OID); + _test_uuid(NM_UUID_TYPE_VERSION5, + "1447fa61-5277-5fef-a9b3-fbc6e44f4af3", + "1.3.6.1", + -1, + UUID_NS_OID); + _test_uuid(NM_UUID_TYPE_VERSION3, + "658d3002-db6b-3040-a1d1-8ddd7d189a4d", + "c=ca", + -1, + UUID_NS_X500); + _test_uuid(NM_UUID_TYPE_VERSION5, + "cc957dd1-a972-5349-98cd-874190002798", + "c=ca", + -1, + UUID_NS_X500); + + _test_uuid(NM_UUID_TYPE_VERSION5, + "74738ff5-5367-5958-9aee-98fffdcd1876", + "www.example.org", + -1, + UUID_NS_DNS); +} + +/*****************************************************************************/ + +static void +__test_uuid(const char *expected_uuid, const char *str, gssize slen, char *uuid_test) +{ + g_assert(uuid_test); + g_assert(nm_uuid_is_normalized(uuid_test)); + + if (strcmp(uuid_test, expected_uuid)) { + g_error("UUID test failed (1): text=%s, len=%lld, expected=%s, uuid_test=%s", + str, + (long long) slen, + expected_uuid, + uuid_test); + } + g_free(uuid_test); + + uuid_test = nm_uuid_generate_from_string_str(str, slen, NM_UUID_TYPE_VERSION3, NM_UUID_NS1); + + g_assert(uuid_test); + g_assert(nm_utils_is_uuid(uuid_test)); + + if (strcmp(uuid_test, expected_uuid)) { + g_error("UUID test failed (2): text=%s; len=%lld, expected=%s, uuid2=%s", + str, + (long long) slen, + expected_uuid, + uuid_test); + } + g_free(uuid_test); +} + +#define _test_uuid(expected_uuid, str, strlen, ...) \ + __test_uuid(expected_uuid, str, strlen, nm_uuid_generate_from_strings(__VA_ARGS__, NULL)) + +static void +test_nm_utils_uuid_generate_from_strings(void) +{ + const NMUuid uuid0 = {}; + + g_assert_cmpmem(&uuid0, sizeof(uuid0), _uuid("00000000-0000-0000-0000-000000000000"), 16); + + g_assert(nm_uuid_is_null(NULL)); + g_assert(nm_uuid_is_null(&uuid0)); + g_assert(nm_uuid_is_null(_uuid("00000000-0000-0000-0000-000000000000"))); + g_assert(!nm_uuid_is_null(_uuid("10000000-0000-0000-0000-000000000000"))); + + _test_uuid("b07c334a-399b-32de-8d50-58e4e08f98e3", "", 0, NULL); + _test_uuid("b8a426cb-bcb5-30a3-bd8f-6786fea72df9", "\0", 1, ""); + _test_uuid("12a4a982-7aae-39e1-951e-41aeb1250959", "a\0", 2, "a"); + _test_uuid("69e22c7e-f89f-3a43-b239-1cb52ed8db69", "aa\0", 3, "aa"); + _test_uuid("59829fd3-5ad5-3d90-a7b0-4911747e4088", "\0\0", 2, "", ""); + _test_uuid("01ad0e06-6c50-3384-8d86-ddab81421425", "a\0\0", 3, "a", ""); + _test_uuid("e1ed8647-9ed3-3ec8-8c6d-e8204524d71d", "aa\0\0", 4, "aa", ""); + _test_uuid("fb1c7cd6-275c-3489-9382-83b900da8af0", "\0a\0", 3, "", "a"); + _test_uuid("5d79494e-c4ba-31a6-80a2-d6016ccd7e17", "a\0a\0", 4, "a", "a"); + _test_uuid("fd698d86-1b60-3ebe-855f-7aada9950a8d", "aa\0a\0", 5, "aa", "a"); + _test_uuid("8c573b48-0f01-30ba-bb94-c5f59f4fe517", "\0aa\0", 4, "", "aa"); + _test_uuid("2bdd3d46-eb83-3c53-a41b-a724d04b5544", "a\0aa\0", 5, "a", "aa"); + _test_uuid("13d4b780-07c1-3ba7-b449-81c4844ef039", "aa\0aa\0", 6, "aa", "aa"); + _test_uuid("dd265bf7-c05a-3037-9939-b9629858a477", "a\0b\0", 4, "a", "b"); +} + +/*****************************************************************************/ + +static void +test_nm_utils_ascii_str_to_int64_check(const char *str, + guint base, + gint64 min, + gint64 max, + gint64 fallback, + int exp_errno, + gint64 exp_val) +{ + gint64 v; + + errno = 1; + v = _nm_utils_ascii_str_to_int64(str, base, min, max, fallback); + g_assert_cmpint(errno, ==, exp_errno); + g_assert_cmpint(v, ==, exp_val); +} + +static void +test_nm_utils_ascii_str_to_int64_do(const char *str, + guint base, + gint64 min, + gint64 max, + gint64 fallback, + int exp_errno, + gint64 exp_val) +{ + const char * sign = ""; + const char * val; + static const char *whitespaces[] = { + "", + " ", + "\r\n\t", + " \r\n\t ", + " \r\n\t \t\r\n\t", + NULL, + }; + static const char *nulls[] = { + "", + "0", + "00", + "0000", + "0000000000000000", + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000", + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000", + NULL, + }; + const char **ws_pre, **ws_post, **null; + guint i; + + if (str == NULL || exp_errno != 0) { + test_nm_utils_ascii_str_to_int64_check(str, base, min, max, fallback, exp_errno, exp_val); + return; + } + + if (strncmp(str, "-", 1) == 0) + sign = "-"; + + val = str + strlen(sign); + + for (ws_pre = whitespaces; *ws_pre; ws_pre++) { + for (ws_post = whitespaces; *ws_post; ws_post++) { + for (null = nulls; *null; null++) { + for (i = 0;; i++) { + char * s; + const char *str_base = ""; + + if (base == 16) { + if (i == 1) + str_base = "0x"; + else if (i > 1) + break; + } else if (base == 8) { + if (i == 1) + str_base = "0"; + else if (i > 1) + break; + } else if (base == 0) { + if (i > 0) + break; + /* with base==0, a leading zero would be interpreted as octal. Only test without *null */ + if ((*null)[0]) + break; + } else { + if (i > 0) + break; + } + + s = g_strdup_printf("%s%s%s%s%s%s", + *ws_pre, + sign, + str_base, + *null, + val, + *ws_post); + + test_nm_utils_ascii_str_to_int64_check(s, + base, + min, + max, + fallback, + exp_errno, + exp_val); + g_free(s); + } + } + } + } +} + +static void +test_nm_utils_ascii_str_to_int64(void) +{ + test_nm_utils_ascii_str_to_int64_do(NULL, 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do("", 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do("1x", 10, 0, 10000, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do("4711", 10, 0, 10000, -1, 0, 4711); + test_nm_utils_ascii_str_to_int64_do("10000", 10, 0, 10000, -1, 0, 10000); + test_nm_utils_ascii_str_to_int64_do("10001", 10, 0, 10000, -1, ERANGE, -1); + test_nm_utils_ascii_str_to_int64_do("FF", 16, 0, 10000, -1, 0, 255); + test_nm_utils_ascii_str_to_int64_do("FF", 10, 0, 10000, -2, EINVAL, -2); + test_nm_utils_ascii_str_to_int64_do("9223372036854775807", + 10, + 0, + G_MAXINT64, + -2, + 0, + G_MAXINT64); + test_nm_utils_ascii_str_to_int64_do("7FFFFFFFFFFFFFFF", 16, 0, G_MAXINT64, -2, 0, G_MAXINT64); + test_nm_utils_ascii_str_to_int64_do("9223372036854775808", 10, 0, G_MAXINT64, -2, ERANGE, -2); + test_nm_utils_ascii_str_to_int64_do("-9223372036854775808", + 10, + G_MININT64, + 0, + -2, + 0, + G_MININT64); + test_nm_utils_ascii_str_to_int64_do("-9223372036854775808", + 10, + G_MININT64 + 1, + 0, + -2, + ERANGE, + -2); + test_nm_utils_ascii_str_to_int64_do("-9223372036854775809", 10, G_MININT64, 0, -2, ERANGE, -2); + test_nm_utils_ascii_str_to_int64_do("1.0", 10, 1, 1, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do("1x0", 16, -10, 10, -100, EINVAL, -100); + test_nm_utils_ascii_str_to_int64_do("0", 16, -10, 10, -100, 0, 0); + test_nm_utils_ascii_str_to_int64_do("10001111", 2, -1000, 1000, -100000, 0, 0x8F); + test_nm_utils_ascii_str_to_int64_do("-10001111", 2, -1000, 1000, -100000, 0, -0x8F); + test_nm_utils_ascii_str_to_int64_do("1111111", 2, G_MININT64, G_MAXINT64, -1, 0, 0x7F); + test_nm_utils_ascii_str_to_int64_do("111111111111111", + 2, + G_MININT64, + G_MAXINT64, + -1, + 0, + 0x7FFF); + test_nm_utils_ascii_str_to_int64_do("11111111111111111111111111111111111111111111111", + 2, + G_MININT64, + G_MAXINT64, + -1, + 0, + 0x7FFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do( + "111111111111111111111111111111111111111111111111111111111111111", + 2, + G_MININT64, + G_MAXINT64, + -1, + 0, + 0x7FFFFFFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do( + "100000000000000000000000000000000000000000000000000000000000000", + 2, + G_MININT64, + G_MAXINT64, + -1, + 0, + 0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do( + "1000000000000000000000000000000000000000000000000000000000000000", + 2, + G_MININT64, + G_MAXINT64, + -1, + ERANGE, + -1); + test_nm_utils_ascii_str_to_int64_do( + "-100000000000000000000000000000000000000000000000000000000000000", + 2, + G_MININT64, + G_MAXINT64, + -1, + 0, + -0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do( + "111111111111111111111111111111111111111111111111111111111111111", + 2, + G_MININT64, + G_MAXINT64, + -1, + 0, + 0x7FFFFFFFFFFFFFFF); + test_nm_utils_ascii_str_to_int64_do( + "-100000000000000000000000000000000000000000000000000000000000000", + 2, + G_MININT64, + G_MAXINT64, + -1, + 0, + -0x4000000000000000); + test_nm_utils_ascii_str_to_int64_do("0x70", 10, G_MININT64, G_MAXINT64, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do("4711", 0, G_MININT64, G_MAXINT64, -1, 0, 4711); + test_nm_utils_ascii_str_to_int64_do("04711", 0, G_MININT64, G_MAXINT64, -1, 0, 04711); + test_nm_utils_ascii_str_to_int64_do("0x4711", 0, G_MININT64, G_MAXINT64, -1, 0, 0x4711); + test_nm_utils_ascii_str_to_int64_do("080", 0, G_MININT64, G_MAXINT64, -1, EINVAL, -1); + test_nm_utils_ascii_str_to_int64_do("070", 0, G_MININT64, G_MAXINT64, -1, 0, 7 * 8); + test_nm_utils_ascii_str_to_int64_do("0x70", 0, G_MININT64, G_MAXINT64, -1, 0, 0x70); + + g_assert_cmpint(21, ==, _nm_utils_ascii_str_to_int64("025", 0, 0, 1000, -1)); + g_assert_cmpint(21, ==, _nm_utils_ascii_str_to_int64("0025", 0, 0, 1000, -1)); + g_assert_cmpint(25, ==, _nm_utils_ascii_str_to_int64("025", 10, 0, 1000, -1)); + g_assert_cmpint(25, ==, _nm_utils_ascii_str_to_int64("0025", 10, 0, 1000, -1)); +} + +/*****************************************************************************/ + +static void +test_nm_utils_strstrdictkey(void) +{ +#define _VALUES_STATIC(_v1, _v2) \ + { \ + .v1 = _v1, .v2 = _v2, .v_static = _nm_utils_strstrdictkey_static(_v1, _v2), \ + } + const struct { + const char * v1; + const char * v2; + NMUtilsStrStrDictKey *v_static; + } * val1, *val2, + values[] = { + {NULL, NULL}, + {"", NULL}, + {NULL, ""}, + {"a", NULL}, + {NULL, "a"}, + _VALUES_STATIC("", ""), + _VALUES_STATIC("a", ""), + _VALUES_STATIC("", "a"), + _VALUES_STATIC("a", "b"), + }; + guint i, j; + + for (i = 0; i < G_N_ELEMENTS(values); i++) { + gs_free NMUtilsStrStrDictKey *key1 = NULL; + + val1 = &values[i]; + + key1 = _nm_utils_strstrdictkey_create(val1->v1, val1->v2); + if (val1->v_static) { + g_assert(_nm_utils_strstrdictkey_equal(key1, val1->v_static)); + g_assert(_nm_utils_strstrdictkey_equal(val1->v_static, key1)); + g_assert_cmpint(_nm_utils_strstrdictkey_hash(key1), + ==, + _nm_utils_strstrdictkey_hash(val1->v_static)); + } + + for (j = 0; j < G_N_ELEMENTS(values); j++) { + gs_free NMUtilsStrStrDictKey *key2 = NULL; + + val2 = &values[j]; + key2 = _nm_utils_strstrdictkey_create(val2->v1, val2->v2); + if (i != j) { + g_assert(!_nm_utils_strstrdictkey_equal(key1, key2)); + g_assert(!_nm_utils_strstrdictkey_equal(key2, key1)); + } + } + } +} + +/*****************************************************************************/ + +static guint +_g_strv_length(gconstpointer arr) +{ + return arr ? g_strv_length((char **) arr) : 0; +} + +static void +test_nm_ptrarray_len(void) +{ +#define _PTRARRAY_cmp(len, arr) \ + G_STMT_START \ + { \ + g_assert_cmpint(len, ==, NM_PTRARRAY_LEN(arr)); \ + g_assert_cmpint(len, ==, _g_strv_length(arr)); \ + } \ + G_STMT_END +#define _PTRARRAY_LEN0(T) \ + G_STMT_START \ + { \ + T ** vnull = NULL; \ + T *const * vnull1 = NULL; \ + T *const *const vnull2 = NULL; \ + T * v0[] = {NULL}; \ + T *const * v01 = v0; \ + T *const *const v02 = v0; \ + T **const v03 = v0; \ + \ + _PTRARRAY_cmp(0, vnull); \ + _PTRARRAY_cmp(0, vnull1); \ + _PTRARRAY_cmp(0, vnull2); \ + _PTRARRAY_cmp(0, v0); \ + _PTRARRAY_cmp(0, v01); \ + _PTRARRAY_cmp(0, v02); \ + _PTRARRAY_cmp(0, v03); \ + } \ + G_STMT_END + + _PTRARRAY_LEN0(char); + _PTRARRAY_LEN0(const char); + _PTRARRAY_LEN0(int); + _PTRARRAY_LEN0(const int); + _PTRARRAY_LEN0(void *); + _PTRARRAY_LEN0(void); + _PTRARRAY_LEN0(const void); + +#define _PTRARRAY_LENn(T) \ + G_STMT_START \ + { \ + T x[5] = {0}; \ + \ + T * v1[] = {&x[0], NULL}; \ + T *const * v11 = v1; \ + T *const *const v12 = v1; \ + T **const v13 = v1; \ + \ + T * v2[] = {&x[0], &x[1], NULL}; \ + T *const * v21 = v2; \ + T *const *const v22 = v2; \ + T **const v23 = v2; \ + \ + _PTRARRAY_cmp(1, v1); \ + _PTRARRAY_cmp(1, v11); \ + _PTRARRAY_cmp(1, v12); \ + _PTRARRAY_cmp(1, v13); \ + \ + _PTRARRAY_cmp(2, v2); \ + _PTRARRAY_cmp(2, v21); \ + _PTRARRAY_cmp(2, v22); \ + _PTRARRAY_cmp(2, v23); \ + } \ + G_STMT_END + + _PTRARRAY_LENn(char); + _PTRARRAY_LENn(const char); + _PTRARRAY_LENn(int); + _PTRARRAY_LENn(const int); + _PTRARRAY_LENn(void *); +} + +/*****************************************************************************/ + +static void +test_nm_utils_dns_option_validate_do(char * option, + gboolean ipv6, + const NMUtilsDNSOptionDesc *descs, + gboolean exp_result, + char * exp_name, + gboolean exp_value) +{ + char * name; + long value = 0; + gboolean result; + + result = _nm_utils_dns_option_validate(option, &name, &value, ipv6, descs); + + g_assert(result == exp_result); + g_assert_cmpstr(name, ==, exp_name); + g_assert(value == exp_value); + + g_free(name); +} + +static const NMUtilsDNSOptionDesc opt_descs[] = { + /* name num ipv6 */ + {"opt1", FALSE, FALSE}, + {"opt2", TRUE, FALSE}, + {"opt3", FALSE, TRUE}, + {"opt4", TRUE, TRUE}, + {NULL, FALSE, FALSE}}; + +static void +test_nm_utils_dns_option_validate(void) +{ + /* opt ipv6 descs result name value */ + test_nm_utils_dns_option_validate_do("", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do(":", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do(":1", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do(":val", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt", FALSE, NULL, TRUE, "opt", -1); + test_nm_utils_dns_option_validate_do("opt:", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt:12", FALSE, NULL, TRUE, "opt", 12); + test_nm_utils_dns_option_validate_do("opt:12 ", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt:val", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt:2val", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt:2:3", FALSE, NULL, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt-6", FALSE, NULL, TRUE, "opt-6", -1); + + test_nm_utils_dns_option_validate_do("opt1", FALSE, opt_descs, TRUE, "opt1", -1); + test_nm_utils_dns_option_validate_do("opt1", TRUE, opt_descs, TRUE, "opt1", -1); + test_nm_utils_dns_option_validate_do("opt1:3", FALSE, opt_descs, FALSE, NULL, -1); + + test_nm_utils_dns_option_validate_do("opt2", FALSE, opt_descs, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt2:5", FALSE, opt_descs, TRUE, "opt2", 5); + + test_nm_utils_dns_option_validate_do("opt3", FALSE, opt_descs, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt3", TRUE, opt_descs, TRUE, "opt3", -1); + + test_nm_utils_dns_option_validate_do("opt4", FALSE, opt_descs, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt4", TRUE, opt_descs, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt4:40", FALSE, opt_descs, FALSE, NULL, -1); + test_nm_utils_dns_option_validate_do("opt4:40", TRUE, opt_descs, TRUE, "opt4", 40); +} + +static void +test_nm_utils_dns_option_find_idx(void) +{ + GPtrArray *options; + + options = g_ptr_array_new(); + + g_ptr_array_add(options, "debug"); + g_ptr_array_add(options, "timeout:5"); + g_ptr_array_add(options, "edns0"); + + g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "debug"), ==, 0); + g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "debug:1"), ==, 0); + g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "timeout"), ==, 1); + g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "timeout:5"), ==, 1); + g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "timeout:2"), ==, 1); + g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "edns0"), ==, 2); + g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "rotate"), ==, -1); + g_assert_cmpint(_nm_utils_dns_option_find_idx(options, ""), ==, -1); + + g_ptr_array_free(options, TRUE); +} + +/*****************************************************************************/ + +static void +_json_config_check_valid(const char *conf, gboolean expected) +{ + gs_free_error GError *error = NULL; + gboolean res; + + res = nm_utils_is_json_object(conf, &error); + g_assert_cmpint(res, ==, expected); + g_assert(res || error); +} + +static void +test_nm_utils_check_valid_json(void) +{ + _json_config_check_valid(NULL, FALSE); + _json_config_check_valid("", FALSE); + + /* Without JSON library everything except empty string is considered valid */ + nmtst_json_vt_reset(FALSE); + _json_config_check_valid("{ }", TRUE); + _json_config_check_valid("{'%!-a1} ", TRUE); + _json_config_check_valid(" {'%!-a1}", TRUE); + _json_config_check_valid("{'%!-a1", FALSE); + + if (nmtst_json_vt_reset(TRUE)) { + _json_config_check_valid("{ }", TRUE); + _json_config_check_valid("{ \"a\" : 1 }", TRUE); + _json_config_check_valid("{ \"a\" : }", FALSE); + } +} + +static void +_team_config_equal_check(const char *conf1, + const char *conf2, + gboolean port_config, + gboolean expected) +{ + nm_auto_free_team_setting NMTeamSetting *team_a = NULL; + nm_auto_free_team_setting NMTeamSetting *team_b = NULL; + gboolean is_same; + + if (nmtst_get_rand_bool()) + NM_SWAP(&conf1, &conf2); + + if (!nm_streq0(conf1, conf2)) { + _team_config_equal_check(conf1, conf1, port_config, TRUE); + _team_config_equal_check(conf2, conf2, port_config, TRUE); + } + + team_a = nm_team_setting_new(port_config, conf1); + team_b = nm_team_setting_new(port_config, conf2); + + is_same = (nm_team_setting_cmp(team_a, team_b, TRUE) == 0); + g_assert_cmpint(is_same, ==, expected); + + if (nm_streq0(conf1, conf2)) { + g_assert_cmpint(nm_team_setting_cmp(team_a, team_b, FALSE), ==, 0); + g_assert(expected); + } else + g_assert_cmpint(nm_team_setting_cmp(team_a, team_b, FALSE), !=, 0); +} + +static void +test_nm_utils_team_config_equal(void) +{ + int with_json_vt; + + for (with_json_vt = 0; with_json_vt < 2; with_json_vt++) { + const NMJsonVt *vt; + + vt = nmtst_json_vt_reset(!!with_json_vt); + + _team_config_equal_check("", "", TRUE, TRUE); + _team_config_equal_check("", " ", TRUE, TRUE); + _team_config_equal_check("{}", "{ }", TRUE, TRUE); + _team_config_equal_check("{}", "{", TRUE, TRUE); + _team_config_equal_check("{ \"a\": 1 }", "{ \"a\": 1 }", TRUE, TRUE); + _team_config_equal_check("{ \"a\": 1 }", "{ \"a\": 1 }", TRUE, TRUE); + + /* team config */ + _team_config_equal_check("{ }", "{ \"runner\" : { \"name\" : \"random\"} }", FALSE, !vt); + _team_config_equal_check("{ \"runner\" : { \"name\" : \"roundrobin\"} }", + "{ \"runner\" : { \"name\" : \"random\"} }", + FALSE, + !vt); + _team_config_equal_check("{ \"runner\" : { \"name\" : \"random\"} }", + "{ \"runner\" : { \"name\" : \"random\"} }", + FALSE, + TRUE); + _team_config_equal_check("{ \"runner\" : { \"name\" : \"loadbalance\"} }", + "{ \"runner\" : { \"name\" : \"loadbalance\"} }", + FALSE, + TRUE); + _team_config_equal_check( + "{ \"runner\" : { \"name\" : \"random\"}, \"ports\" : { \"eth0\" : {} } }", + "{ \"runner\" : { \"name\" : \"random\"}, \"ports\" : { \"eth1\" : {} } }", + FALSE, + TRUE); + _team_config_equal_check("{ \"runner\" : { \"name\" : \"lacp\"} }", + "{ \"runner\" : { \"name\" : \"lacp\", \"tx_hash\" : [ \"eth\", " + "\"ipv4\", \"ipv6\" ] } }", + FALSE, + !vt); + _team_config_equal_check("{ \"runner\" : { \"name\" : \"roundrobin\"} }", + "{ \"runner\" : { \"name\" : \"roundrobin\", \"tx_hash\" : [ " + "\"eth\", \"ipv4\", \"ipv6\" ] } }", + FALSE, + !vt); + _team_config_equal_check( + "{ \"runner\" : { \"name\" : \"lacp\"} }", + "{ \"runner\" : { \"name\" : \"lacp\", \"tx_hash\" : [ \"eth\" ] } }", + FALSE, + !vt); + + /* team port config */ + _team_config_equal_check("{ }", + "{ \"link_watch\" : { \"name\" : \"ethtool\"} }", + TRUE, + !vt); + _team_config_equal_check("{ }", + "{ \"link_watch\" : { \"name\" : \"arp_ping\"} }", + TRUE, + TRUE); + _team_config_equal_check("{ \"link_watch\" : { \"name\" : \"ethtool\"} }", + "{ \"link_watch\" : { \"name\" : \"arp_ping\"} }", + TRUE, + !vt); + _team_config_equal_check("{ \"link_watch\" : { \"name\" : \"arp_ping\"} }", + "{ \"link_watch\" : { \"name\" : \"arp_ping\"} }", + TRUE, + TRUE); + _team_config_equal_check( + "{ \"link_watch\" : { \"name\" : \"arp_ping\"}, \"ports\" : { \"eth0\" : {} } }", + "{ \"link_watch\" : { \"name\" : \"arp_ping\"}, \"ports\" : { \"eth1\" : {} } }", + TRUE, + TRUE); + } + + nmtst_json_vt_reset(TRUE); +} + +/*****************************************************************************/ + +enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED { + _DUMMY_1 = -1, +}; + +enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED { + _DUMMY_2, +}; + +enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED_64 { + _DUMMY_3 = (1LL << 40), +}; + +enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED_64 { + _DUMMY_4a = -1, + _DUMMY_4b = (1LL << 40), +}; + +#define test_nm_utils_is_power_of_two_do(type, x, expect) \ + G_STMT_START \ + { \ + typeof(x) x1 = (x); \ + type x2 = (type) x1; \ + gboolean val; \ + \ + val = nm_utils_is_power_of_two(x1); \ + g_assert_cmpint(expect, ==, val); \ + if (x1 != 0) \ + g_assert_cmpint(val, ==, nm_utils_is_power_of_two_or_zero(x1)); \ + else { \ + g_assert(nm_utils_is_power_of_two_or_zero(x1)); \ + g_assert(!val); \ + } \ + if (((typeof(x1)) x2) == x1 && ((typeof(x2)) x1) == x2 && x2 > 0) { \ + /* x2 equals @x, and is positive. Compare to @expect */ \ + g_assert_cmpint(expect, ==, nm_utils_is_power_of_two(x2)); \ + } else if (!(x2 > 0)) { \ + /* a non positive value is always FALSE. */ \ + g_assert_cmpint(FALSE, ==, nm_utils_is_power_of_two(x2)); \ + } \ + if (x2) { \ + x2 = -x2; \ + if (!(x2 > 0)) { \ + /* for negative values, we return FALSE. */ \ + g_assert_cmpint(FALSE, ==, nm_utils_is_power_of_two(x2)); \ + } \ + } \ + } \ + G_STMT_END + +static void +test_nm_utils_is_power_of_two(void) +{ + guint64 xyes, xno; + int i, j; + GRand * rand = nmtst_get_rand(); + int numbits; + + g_assert(!nm_utils_is_power_of_two(0)); + g_assert(nm_utils_is_power_of_two_or_zero(0)); + + for (i = -1; i < 64; i++) { + /* find a (positive) x which is a power of two. */ + if (i == -1) + xyes = 0; + else { + xyes = (((guint64) 1) << i); + g_assert(xyes != 0); + } + + xno = xyes; + if (xyes != 0) { +again: + /* Find another @xno, that is not a power of two. Do that, + * by randomly setting bits. */ + numbits = g_rand_int_range(rand, 1, 65); + while (xno != ~((guint64) 0) && numbits > 0) { + guint64 v = (((guint64) 1) << g_rand_int_range(rand, 0, 64)); + + if ((xno | v) != xno) { + xno |= v; + --numbits; + } + } + if (xno == xyes) + goto again; + } + + for (j = 0; j < 2; j++) { + gboolean expect = j == 0; + guint64 x = expect ? xyes : xno; + + if (expect && xyes == 0) + continue; + + /* check if @x is as @expect, when casted to a certain data type. */ + test_nm_utils_is_power_of_two_do(gint8, x, expect); + test_nm_utils_is_power_of_two_do(guint8, x, expect); + test_nm_utils_is_power_of_two_do(gint16, x, expect); + test_nm_utils_is_power_of_two_do(guint16, x, expect); + test_nm_utils_is_power_of_two_do(gint32, x, expect); + test_nm_utils_is_power_of_two_do(guint32, x, expect); + test_nm_utils_is_power_of_two_do(gint64, x, expect); + test_nm_utils_is_power_of_two_do(guint64, x, expect); + test_nm_utils_is_power_of_two_do(char, x, expect); + test_nm_utils_is_power_of_two_do(unsigned char, x, expect); + test_nm_utils_is_power_of_two_do(signed char, x, expect); + test_nm_utils_is_power_of_two_do(enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED, x, expect); + test_nm_utils_is_power_of_two_do(enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED, x, expect); + test_nm_utils_is_power_of_two_do(enum TEST_IS_POWER_OF_TWP_ENUM_SIGNED_64, x, expect); + test_nm_utils_is_power_of_two_do(enum TEST_IS_POWER_OF_TWP_ENUM_UNSIGNED_64, x, expect); + } + } +} + +/*****************************************************************************/ + +static int +_test_find_binary_search_cmp(gconstpointer a, gconstpointer b, gpointer dummy) +{ + int ia, ib; + + ia = GPOINTER_TO_INT(a); + ib = GPOINTER_TO_INT(b); + + if (ia == ib) + return 0; + if (ia < ib) + return -1; + return 1; +} + +static void +_test_find_binary_search_do(const int *array, gsize len) +{ + gsize i; + gssize idx, idx2, idx_first, idx_last; + gs_free gconstpointer *parray = g_new(gconstpointer, len); + const int NEEDLE = 0; + gconstpointer pneedle = GINT_TO_POINTER(NEEDLE); + gssize expected_result; + + for (i = 0; i < len; i++) + parray[i] = GINT_TO_POINTER(array[i]); + + expected_result = nm_utils_ptrarray_find_first(parray, len, pneedle); + + idx = nm_utils_ptrarray_find_binary_search_range(parray, + len, + pneedle, + _test_find_binary_search_cmp, + NULL, + &idx_first, + &idx_last); + + idx2 = nm_utils_ptrarray_find_binary_search(parray, + len, + pneedle, + _test_find_binary_search_cmp, + NULL); + g_assert_cmpint(idx, ==, idx2); + + if (expected_result >= 0) { + g_assert_cmpint(expected_result, ==, idx); + } else { + idx2 = ~idx; + + g_assert_cmpint(idx, <, 0); + + g_assert(idx2 >= 0); + g_assert(idx2 <= len); + g_assert(idx2 - 1 < 0 || _test_find_binary_search_cmp(parray[idx2 - 1], pneedle, NULL) < 0); + g_assert(idx2 >= len || _test_find_binary_search_cmp(parray[idx2], pneedle, NULL) > 0); + } + g_assert_cmpint(idx, ==, idx_first); + g_assert_cmpint(idx, ==, idx_last); + for (i = 0; i < len; i++) { + int cmp; + + cmp = _test_find_binary_search_cmp(parray[i], pneedle, NULL); + if (cmp == 0) { + g_assert(pneedle == parray[i]); + g_assert(idx >= 0); + g_assert(i == idx); + } else { + g_assert(pneedle != parray[i]); + if (cmp < 0) { + if (idx < 0) + g_assert(i < ~idx); + else + g_assert(i < idx); + } else { + if (idx < 0) + g_assert(i >= ~idx); + else + g_assert(i >= idx); + } + } + } +} + +static void +_test_find_binary_search_do_uint32(const int *int_array, gsize len) +{ + gssize idx; + const int OFFSET = 100; + const int NEEDLE = 0 + OFFSET; + gssize expected_result = -1; + guint32 array[30]; + + g_assert(len <= G_N_ELEMENTS(array)); + + /* the test data has negative values. Shift them... */ + for (idx = 0; idx < len; idx++) { + int v = int_array[idx]; + + g_assert(v > -OFFSET); + g_assert(v < OFFSET); + g_assert(idx == 0 || v > int_array[idx - 1]); + array[idx] = (guint32) (int_array[idx] + OFFSET); + if (array[idx] == NEEDLE) + expected_result = idx; + } + + idx = nm_utils_array_find_binary_search(array, + sizeof(guint32), + len, + &NEEDLE, + nm_cmp_uint32_p_with_data, + NULL); + if (expected_result >= 0) + g_assert_cmpint(expected_result, ==, idx); + else { + gssize idx2 = ~idx; + g_assert_cmpint(idx, <, 0); + + g_assert(idx2 >= 0); + g_assert(idx2 <= len); + g_assert(idx2 - 1 < 0 || array[idx2 - 1] < NEEDLE); + g_assert(idx2 >= len || array[idx2] > NEEDLE); + } +} +#define test_find_binary_search_do(...) \ + G_STMT_START \ + { \ + const int _array[] = {__VA_ARGS__}; \ + _test_find_binary_search_do(_array, G_N_ELEMENTS(_array)); \ + _test_find_binary_search_do_uint32(_array, G_N_ELEMENTS(_array)); \ + } \ + G_STMT_END + +static void +test_nm_utils_ptrarray_find_binary_search(void) +{ + test_find_binary_search_do(0); + test_find_binary_search_do(-1, 0); + test_find_binary_search_do(-2, -1, 0); + test_find_binary_search_do(-3, -2, -1, 0); + test_find_binary_search_do(0, 1); + test_find_binary_search_do(0, 1, 2); + test_find_binary_search_do(-1, 0, 1, 2); + test_find_binary_search_do(-2, -1, 0, 1, 2); + test_find_binary_search_do(-3, -2, -1, 0, 1, 2); + test_find_binary_search_do(-3, -2, -1, 0, 1, 2); + test_find_binary_search_do(-3, -2, -1, 0, 1, 2, 3); + test_find_binary_search_do(-3, -2, -1, 0, 1, 2, 3, 4); + + test_find_binary_search_do(-1); + test_find_binary_search_do(-2, -1); + test_find_binary_search_do(-3, -2, -1); + test_find_binary_search_do(1); + test_find_binary_search_do(1, 2); + test_find_binary_search_do(-1, 1, 2); + test_find_binary_search_do(-2, -1, 1, 2); + test_find_binary_search_do(-3, -2, -1, 1, 2); + test_find_binary_search_do(-3, -2, -1, 1, 2); + test_find_binary_search_do(-3, -2, -1, 1, 2, 3); + test_find_binary_search_do(-3, -2, -1, 1, 2, 3, 4); +} + +/*****************************************************************************/ + +#define BIN_SEARCH_W_DUPS_LEN 100 +#define BIN_SEARCH_W_DUPS_JITTER 10 + +static int +_test_bin_search2_cmp(gconstpointer pa, gconstpointer pb, gpointer user_data) +{ + int a = GPOINTER_TO_INT(pa); + int b = GPOINTER_TO_INT(pb); + + g_assert(a >= 0 && a <= BIN_SEARCH_W_DUPS_LEN + BIN_SEARCH_W_DUPS_JITTER); + g_assert(b >= 0 && b <= BIN_SEARCH_W_DUPS_LEN + BIN_SEARCH_W_DUPS_JITTER); + NM_CMP_DIRECT(a, b); + return 0; +} + +static int +_test_bin_search2_cmp_p(gconstpointer pa, gconstpointer pb, gpointer user_data) +{ + return _test_bin_search2_cmp(*((gpointer *) pa), *((gpointer *) pb), NULL); +} + +static void +test_nm_utils_ptrarray_find_binary_search_with_duplicates(void) +{ + gssize idx, idx2, idx_first2, idx_first, idx_last; + int i_test, i_len, i; + gssize j; + gconstpointer arr[BIN_SEARCH_W_DUPS_LEN]; + const int N_TEST = 10; + + for (i_test = 0; i_test < N_TEST; i_test++) { + for (i_len = 0; i_len < BIN_SEARCH_W_DUPS_LEN; i_len++) { + /* fill with random numbers... surely there are some duplicates + * there... or maybe even there are none... */ + for (i = 0; i < i_len; i++) + arr[i] = + GINT_TO_POINTER(nmtst_get_rand_uint32() % (i_len + BIN_SEARCH_W_DUPS_JITTER)); + g_qsort_with_data(arr, i_len, sizeof(gpointer), _test_bin_search2_cmp_p, NULL); + for (i = 0; i < i_len + BIN_SEARCH_W_DUPS_JITTER; i++) { + gconstpointer p = GINT_TO_POINTER(i); + + idx = nm_utils_ptrarray_find_binary_search_range(arr, + i_len, + p, + _test_bin_search2_cmp, + NULL, + &idx_first, + &idx_last); + + idx_first2 = nm_utils_ptrarray_find_first(arr, i_len, p); + + idx2 = nm_utils_array_find_binary_search(arr, + sizeof(gpointer), + i_len, + &p, + _test_bin_search2_cmp_p, + NULL); + g_assert_cmpint(idx, ==, idx2); + + idx2 = nm_utils_ptrarray_find_binary_search(arr, + i_len, + p, + _test_bin_search2_cmp, + NULL); + g_assert_cmpint(idx, ==, idx2); + + if (idx_first2 < 0) { + g_assert_cmpint(idx, <, 0); + g_assert_cmpint(idx, ==, idx_first); + g_assert_cmpint(idx, ==, idx_last); + idx = ~idx; + g_assert_cmpint(idx, >=, 0); + g_assert_cmpint(idx, <=, i_len); + if (i_len == 0) + g_assert_cmpint(idx, ==, 0); + else { + g_assert(idx == i_len || GPOINTER_TO_INT(arr[idx]) > i); + g_assert(idx == 0 || GPOINTER_TO_INT(arr[idx - 1]) < i); + } + } else { + g_assert_cmpint(idx_first, ==, idx_first2); + g_assert_cmpint(idx_first, >=, 0); + g_assert_cmpint(idx_last, <, i_len); + g_assert_cmpint(idx_first, <=, idx_last); + g_assert_cmpint(idx, >=, idx_first); + g_assert_cmpint(idx, <=, idx_last); + for (j = idx_first; j < idx_last; j++) + g_assert(GPOINTER_TO_INT(arr[j]) == i); + g_assert(idx_first == 0 || GPOINTER_TO_INT(arr[idx_first - 1]) < i); + g_assert(idx_last == i_len - 1 || GPOINTER_TO_INT(arr[idx_last + 1]) > i); + } + } + } + } +} + +/*****************************************************************************/ + +static void +_test_nm_utils_enum_to_str_do_full(GType type, + int flags, + const char * exp_str, + const NMUtilsEnumValueInfo *value_infos) +{ + gs_free char *str = NULL; + int flags2; + gs_free char *err_token = NULL; + gboolean result; + + g_assert(exp_str); + + str = _nm_utils_enum_to_str_full(type, flags, ", ", value_infos); + g_assert_cmpstr(str, ==, exp_str); + + if (!value_infos) { + gs_free char *str2 = NULL; + + str2 = nm_utils_enum_to_str(type, flags); + g_assert_cmpstr(str2, ==, exp_str); + } + + result = _nm_utils_enum_from_str_full(type, str, &flags2, &err_token, value_infos); + g_assert(result == TRUE); + g_assert_cmpint(flags2, ==, flags); + g_assert_cmpstr(err_token, ==, NULL); +} + +#define _test_nm_utils_enum_to_str_do(...) _test_nm_utils_enum_to_str_do_full(__VA_ARGS__, NULL) + +static void +_test_nm_utils_enum_from_str_do_full(GType type, + const char * str, + gboolean exp_result, + int exp_flags, + const char * exp_err_token, + const NMUtilsEnumValueInfo *value_infos) +{ + int flags; + gs_free char *err_token = NULL; + gboolean result; + + result = _nm_utils_enum_from_str_full(type, str, &flags, &err_token, value_infos); + + g_assert(result == exp_result); + g_assert_cmpint(flags, ==, exp_flags); + g_assert_cmpstr(err_token, ==, exp_err_token); + + if (!value_infos) { + int flags2; + gs_free char *err_token2 = NULL; + gboolean result2; + + result2 = nm_utils_enum_from_str(type, str, &flags2, &err_token2); + g_assert(result2 == exp_result); + g_assert_cmpint(flags2, ==, exp_flags); + g_assert_cmpstr(err_token2, ==, exp_err_token); + } + + if (result) { + int flags2; + gs_free char *str2 = NULL; + gs_free char *err_token2 = NULL; + + str2 = _nm_utils_enum_to_str_full(type, flags, ", ", value_infos); + g_assert(str2); + + result = _nm_utils_enum_from_str_full(type, str2, &flags2, &err_token2, value_infos); + g_assert(result == TRUE); + g_assert_cmpint(flags2, ==, flags); + g_assert_cmpstr(err_token, ==, NULL); + } +} + +#define _test_nm_utils_enum_from_str_do(...) _test_nm_utils_enum_from_str_do_full(__VA_ARGS__, NULL) + +static void +_test_nm_utils_enum_get_values_do(GType type, int from, int to, const char *exp_str) +{ + gs_free const char **strv = NULL; + gs_free char * str = NULL; + + g_assert(exp_str); + + strv = nm_utils_enum_get_values(type, from, to); + g_assert(strv); + str = g_strjoinv(",", (char **) strv); + g_assert_cmpstr(str, ==, exp_str); +} + +static void +test_nm_utils_enum(void) +{ + GType bool_enum = nm_test_general_bool_enum_get_type(); + GType meta_flags = nm_test_general_meta_flags_get_type(); + GType color_flags = nm_test_general_color_flags_get_type(); + static const NMUtilsEnumValueInfo color_value_infos[] = { + { + .nick = "nick-4d", + .value = 0x4D, + }, + { + .nick = "nick-5", + .value = 5, + }, + { + .nick = "nick-red", + .value = NM_TEST_GENERAL_COLOR_FLAGS_RED, + }, + {0}, + }; + + _test_nm_utils_enum_to_str_do(bool_enum, NM_TEST_GENERAL_BOOL_ENUM_YES, "yes"); + _test_nm_utils_enum_to_str_do(bool_enum, NM_TEST_GENERAL_BOOL_ENUM_UNKNOWN, "unknown"); + _test_nm_utils_enum_to_str_do(bool_enum, NM_TEST_GENERAL_BOOL_ENUM_INVALID, "4"); + _test_nm_utils_enum_to_str_do(bool_enum, NM_TEST_GENERAL_BOOL_ENUM_67, "67"); + _test_nm_utils_enum_to_str_do(bool_enum, NM_TEST_GENERAL_BOOL_ENUM_46, "64"); + + _test_nm_utils_enum_to_str_do(meta_flags, NM_TEST_GENERAL_META_FLAGS_NONE, "none"); + _test_nm_utils_enum_to_str_do(meta_flags, NM_TEST_GENERAL_META_FLAGS_BAZ, "baz"); + _test_nm_utils_enum_to_str_do(meta_flags, + NM_TEST_GENERAL_META_FLAGS_FOO | NM_TEST_GENERAL_META_FLAGS_BAR + | NM_TEST_GENERAL_META_FLAGS_BAZ, + "foo, bar, baz"); + _test_nm_utils_enum_to_str_do(meta_flags, 0xFF, "foo, bar, baz, 0xf8"); + _test_nm_utils_enum_to_str_do(meta_flags, NM_TEST_GENERAL_META_FLAGS_0x8, "0x8"); + _test_nm_utils_enum_to_str_do(meta_flags, NM_TEST_GENERAL_META_FLAGS_0x4, "0x10"); + + _test_nm_utils_enum_to_str_do(color_flags, NM_TEST_GENERAL_COLOR_FLAGS_RED, "red"); + _test_nm_utils_enum_to_str_do(color_flags, NM_TEST_GENERAL_COLOR_FLAGS_WHITE, "0x1"); + _test_nm_utils_enum_to_str_do(color_flags, + NM_TEST_GENERAL_COLOR_FLAGS_RED + | NM_TEST_GENERAL_COLOR_FLAGS_GREEN, + "red, green"); + + _test_nm_utils_enum_to_str_do_full(color_flags, + NM_TEST_GENERAL_COLOR_FLAGS_RED + | NM_TEST_GENERAL_COLOR_FLAGS_GREEN, + "nick-red, green", + color_value_infos); + + _test_nm_utils_enum_to_str_do_full(color_flags, + 0x4D | NM_TEST_GENERAL_COLOR_FLAGS_RED + | NM_TEST_GENERAL_COLOR_FLAGS_GREEN, + "nick-4d", + color_value_infos); + + _test_nm_utils_enum_to_str_do_full(color_flags, + 5 | NM_TEST_GENERAL_COLOR_FLAGS_GREEN, + "nick-5, green", + color_value_infos); + + _test_nm_utils_enum_from_str_do(bool_enum, "", FALSE, 0, NULL); + _test_nm_utils_enum_from_str_do(bool_enum, " ", FALSE, 0, NULL); + _test_nm_utils_enum_from_str_do(bool_enum, "invalid", FALSE, 0, "invalid"); + _test_nm_utils_enum_from_str_do(bool_enum, "yes", TRUE, NM_TEST_GENERAL_BOOL_ENUM_YES, NULL); + _test_nm_utils_enum_from_str_do(bool_enum, "no", TRUE, NM_TEST_GENERAL_BOOL_ENUM_NO, NULL); + _test_nm_utils_enum_from_str_do(bool_enum, "yes,no", FALSE, 0, "yes,no"); + + _test_nm_utils_enum_from_str_do(meta_flags, "", TRUE, 0, NULL); + _test_nm_utils_enum_from_str_do(meta_flags, " ", TRUE, 0, NULL); + _test_nm_utils_enum_from_str_do(meta_flags, "foo", TRUE, NM_TEST_GENERAL_META_FLAGS_FOO, NULL); + _test_nm_utils_enum_from_str_do(meta_flags, + "foo,baz", + TRUE, + NM_TEST_GENERAL_META_FLAGS_FOO | NM_TEST_GENERAL_META_FLAGS_BAZ, + NULL); + _test_nm_utils_enum_from_str_do(meta_flags, + "foo, baz", + TRUE, + NM_TEST_GENERAL_META_FLAGS_FOO | NM_TEST_GENERAL_META_FLAGS_BAZ, + NULL); + _test_nm_utils_enum_from_str_do(meta_flags, + "foo,,bar", + TRUE, + NM_TEST_GENERAL_META_FLAGS_FOO | NM_TEST_GENERAL_META_FLAGS_BAR, + NULL); + _test_nm_utils_enum_from_str_do(meta_flags, "foo,baz,quux,bar", FALSE, 0, "quux"); + _test_nm_utils_enum_from_str_do(meta_flags, + "foo,0x6", + TRUE, + NM_TEST_GENERAL_META_FLAGS_FOO | 0x6, + NULL); + _test_nm_utils_enum_from_str_do(meta_flags, "0x30,0x08,foo", TRUE, 0x39, NULL); + + _test_nm_utils_enum_from_str_do(color_flags, + "green", + TRUE, + NM_TEST_GENERAL_COLOR_FLAGS_GREEN, + NULL); + _test_nm_utils_enum_from_str_do(color_flags, + "blue,red", + TRUE, + NM_TEST_GENERAL_COLOR_FLAGS_BLUE + | NM_TEST_GENERAL_COLOR_FLAGS_RED, + NULL); + _test_nm_utils_enum_from_str_do(color_flags, "blue,white", FALSE, 0, "white"); + + _test_nm_utils_enum_from_str_do_full(color_flags, + "nick-red", + TRUE, + NM_TEST_GENERAL_COLOR_FLAGS_RED, + NULL, + color_value_infos); + + _test_nm_utils_enum_from_str_do_full(color_flags, "0x4D", TRUE, 0x4D, NULL, color_value_infos); + + _test_nm_utils_enum_from_str_do_full(color_flags, + "green,nick-4d", + TRUE, + 0x4D | NM_TEST_GENERAL_COLOR_FLAGS_GREEN, + NULL, + color_value_infos); + + _test_nm_utils_enum_from_str_do_full(color_flags, + "nick-4d,nick-red,nick-5,green,nick-red", + TRUE, + 0x4D | NM_TEST_GENERAL_COLOR_FLAGS_GREEN, + NULL, + color_value_infos); + + _test_nm_utils_enum_from_str_do_full(NM_TYPE_SETTING_CONNECTION_LLMNR, + "-1", + TRUE, + -1, + NULL, + NULL); + + _test_nm_utils_enum_from_str_do_full(NM_TYPE_SETTING_CONNECTION_LLMNR, + "-0x1", + TRUE, + -1, + NULL, + NULL); + + _test_nm_utils_enum_get_values_do(bool_enum, 0, G_MAXINT, "no,yes,maybe,unknown,67,64"); + _test_nm_utils_enum_get_values_do(bool_enum, + NM_TEST_GENERAL_BOOL_ENUM_YES, + NM_TEST_GENERAL_BOOL_ENUM_MAYBE, + "yes,maybe"); + _test_nm_utils_enum_get_values_do(meta_flags, 0, G_MAXINT, "none,foo,bar,baz,0x8,0x10"); + _test_nm_utils_enum_get_values_do(color_flags, 0, G_MAXINT, "blue,red,green"); +} + +/*****************************************************************************/ + +static void +_do_test_utils_str_utf8safe_unescape(const char *str, const char *expected, gsize expected_len) +{ + gsize l; + const char * s; + gs_free gpointer buf_free_1 = NULL; + gs_free char * str_free_1 = NULL; + + s = nm_utils_buf_utf8safe_unescape(str, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &l, &buf_free_1); + g_assert_cmpint(expected_len, ==, l); + g_assert_cmpstr(s, ==, expected); + + if (str == NULL) { + g_assert(!s); + g_assert(!buf_free_1); + g_assert_cmpint(l, ==, 0); + } else { + g_assert(s); + if (!strchr(str, '\\')) { + g_assert(!buf_free_1); + g_assert(s == str); + g_assert_cmpint(l, ==, strlen(str)); + } else { + g_assert(buf_free_1); + g_assert(s == buf_free_1); + g_assert(memcmp(s, expected, expected_len) == 0); + } + } + + if (expected && l == strlen(expected)) { + /* there are no embedded NULs. Check that nm_utils_str_utf8safe_unescape() yields the same result. */ + s = nm_utils_str_utf8safe_unescape(str, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &str_free_1); + g_assert_cmpstr(s, ==, expected); + g_assert(str); + if (strchr(str, '\\')) { + g_assert(str_free_1 != str); + g_assert(s == str_free_1); + } else + g_assert(s == str); + } +} + +#define do_test_utils_str_utf8safe_unescape(str, expected) \ + _do_test_utils_str_utf8safe_unescape("" str "", expected, NM_STRLEN(expected)) + +static void +_do_test_utils_str_utf8safe(const char * str, + gsize str_len, + const char * expected, + NMUtilsStrUtf8SafeFlags flags) +{ + const char * str_safe; + const char * buf_safe; + const char * s; + gs_free char *str_free_1 = NULL; + gs_free char *str_free_2 = NULL; + gs_free char *str_free_3 = NULL; + gs_free char *str_free_4 = NULL; + gs_free char *str_free_5 = NULL; + gs_free char *str_free_6 = NULL; + gs_free char *str_free_7 = NULL; + gs_free char *str_free_8 = NULL; + gboolean str_has_nul = FALSE; +#define RND_FLAG \ + ((nmtst_get_rand_bool()) ? NM_UTILS_STR_UTF8_SAFE_FLAG_NONE \ + : NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET) + + buf_safe = nm_utils_buf_utf8safe_escape(str, str_len, flags | RND_FLAG, &str_free_1); + + str_safe = nm_utils_str_utf8safe_escape(str, flags | RND_FLAG, &str_free_2); + + if (str_len == 0) { + g_assert(buf_safe == NULL); + g_assert(str_free_1 == NULL); + g_assert(str_safe == str); + g_assert(str == NULL || str[0] == '\0'); + g_assert(str_free_2 == NULL); + } else if (str_len == strlen(str)) { + g_assert(buf_safe); + g_assert_cmpstr(buf_safe, ==, str_safe); + + /* nm_utils_buf_utf8safe_escape() can only return a pointer equal to the input string, + * if and only if str_len is negative. Otherwise, the input str won't be NUL terminated + * and cannot be returned. */ + g_assert(buf_safe != str); + g_assert(buf_safe == str_free_1); + } else + str_has_nul = TRUE; + + str_free_3 = nm_utils_str_utf8safe_escape_cp(str, flags | RND_FLAG); + g_assert_cmpstr(str_free_3, ==, str_safe); + g_assert((!str && !str_free_3) || (str != str_free_3)); + + if (str_len > 0) + _do_test_utils_str_utf8safe_unescape(buf_safe, str, str_len); + + if (expected == NULL) { + g_assert(!str_has_nul); + + g_assert(str_safe == str); + g_assert(!str_free_2); + if (str) { + g_assert(!strchr(str, '\\')); + g_assert(g_utf8_validate(str, -1, NULL)); + } + + g_assert(str + == nm_utils_str_utf8safe_unescape(str_safe, + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, + &str_free_4)); + g_assert(!str_free_4); + + str_free_5 = nm_utils_str_utf8safe_unescape_cp(str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + if (str) { + g_assert(str_free_5 != str); + g_assert_cmpstr(str_free_5, ==, str); + } else + g_assert(!str_free_5); + return; + } + + if (!str_has_nul) { + g_assert(str); + g_assert(str_safe != str); + g_assert(str_safe == str_free_2); + g_assert(strchr(str, '\\') || !g_utf8_validate(str, -1, NULL) + || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII) + && NM_STRCHAR_ANY(str, ch, (guchar) ch >= 127)) + || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL) + && NM_STRCHAR_ANY(str, ch, (guchar) ch < ' '))); + g_assert(g_utf8_validate(str_safe, -1, NULL)); + + str_free_6 = g_strcompress(str_safe); + g_assert_cmpstr(str, ==, str_free_6); + + str_free_7 = nm_utils_str_utf8safe_unescape_cp(str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + g_assert(str_free_7 != str); + g_assert_cmpstr(str_free_7, ==, str); + + s = nm_utils_str_utf8safe_unescape(str_safe, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE, &str_free_8); + g_assert(str_free_8 != str); + g_assert(s == str_free_8); + g_assert_cmpstr(str_free_8, ==, str); + + g_assert_cmpstr(str_safe, ==, expected); + + return; + } + + g_assert_cmpstr(buf_safe, ==, expected); +} +#define do_test_utils_str_utf8safe(str, expected, flags) \ + _do_test_utils_str_utf8safe("" str "", NM_STRLEN(str), expected, flags) + +static void +test_utils_str_utf8safe(void) +{ + _do_test_utils_str_utf8safe(NULL, 0, NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + + do_test_utils_str_utf8safe("", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\\", "\\\\", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\\a", "\\\\a", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\314", "\\314", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\314\315x\315\315x", + "\\314\\315x\\315\\315x", + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\314\315xx", "\\314\\315xx", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\314xx", "\\314xx", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\xa0", "\\240", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\xe2\x91\xa0", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\xe2\xe2\x91\xa0", + "\\342\xe2\x91\xa0", + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\xe2\xe2\x91\xa0\xa0", + "\\342\xe2\x91\xa0\\240", + NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("a", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("ab", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("ab\314", "ab\\314", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("ab\314adsf", "ab\\314adsf", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("abadsf", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("abäb", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("x\xa0", "x\\240", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("Ä\304ab\\äb", "Ä\\304ab\\\\äb", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("Äab\\äb", "Äab\\\\äb", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("ÄÄab\\äb", "ÄÄab\\\\äb", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("㈞abä㈞b", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("abäb", + "ab\\303\\244b", + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII); + do_test_utils_str_utf8safe("ab\ab", "ab\\007b", NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL); + + do_test_utils_str_utf8safe("\0", "\\000", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\0a\0", "\\000a\\000", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\\\0", "\\\\\\000", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\n\0", "\n\\000", NM_UTILS_STR_UTF8_SAFE_FLAG_NONE); + do_test_utils_str_utf8safe("\n\0", "\\012\\000", NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL); + + do_test_utils_str_utf8safe_unescape("\n\\0", "\n\0"); + do_test_utils_str_utf8safe_unescape("\n\\01", "\n\01"); + do_test_utils_str_utf8safe_unescape("\n\\012", "\n\012"); + do_test_utils_str_utf8safe_unescape("\n\\.", "\n."); + do_test_utils_str_utf8safe_unescape("\\n\\.3\\r", "\n.3\r"); +} + +/*****************************************************************************/ + +static int +_test_nm_in_set_get(int *call_counter, gboolean allow_called, int value) +{ + g_assert(call_counter); + *call_counter += 1; + if (!allow_called) + g_assert_not_reached(); + return value; +} + +static void +_test_nm_in_set_assert(int *call_counter, int expected) +{ + g_assert(call_counter); + g_assert_cmpint(expected, ==, *call_counter); + *call_counter = 0; +} + +static void +test_nm_in_set(void) +{ + int call_counter = 0; + +#define G(x) _test_nm_in_set_get(&call_counter, TRUE, x) +#define N(x) _test_nm_in_set_get(&call_counter, FALSE, x) +#define _ASSERT(expected, expr) \ + G_STMT_START \ + { \ + _test_nm_in_set_assert(&call_counter, 0); \ + g_assert(expr); \ + _test_nm_in_set_assert(&call_counter, (expected)); \ + } \ + G_STMT_END + _ASSERT(1, !NM_IN_SET(-1, G(1))); + _ASSERT(1, NM_IN_SET(-1, G(-1))); + + _ASSERT(2, !NM_IN_SET(-1, G(1), G(2))); + _ASSERT(1, NM_IN_SET(-1, G(-1), N(2))); + _ASSERT(2, NM_IN_SET(-1, G(1), G(-1))); + _ASSERT(1, NM_IN_SET(-1, G(-1), N(-1))); + + _ASSERT(3, !NM_IN_SET(-1, G(1), G(2), G(3))); + _ASSERT(1, NM_IN_SET(-1, G(-1), N(2), N(3))); + _ASSERT(2, NM_IN_SET(-1, G(1), G(-1), N(3))); + _ASSERT(3, NM_IN_SET(-1, G(1), G(2), G(-1))); + _ASSERT(2, NM_IN_SET(-1, G(1), G(-1), N(-1))); + _ASSERT(1, NM_IN_SET(-1, G(-1), N(2), N(-1))); + _ASSERT(1, NM_IN_SET(-1, G(-1), N(-1), N(3))); + _ASSERT(1, NM_IN_SET(-1, G(-1), N(-1), N(-1))); + + _ASSERT(4, !NM_IN_SET(-1, G(1), G(2), G(3), G(4))); + _ASSERT(1, NM_IN_SET(-1, G(-1), N(2), N(3), N(4))); + _ASSERT(2, NM_IN_SET(-1, G(1), G(-1), N(3), N(4))); + _ASSERT(3, NM_IN_SET(-1, G(1), G(2), G(-1), N(4))); + _ASSERT(4, NM_IN_SET(-1, G(1), G(2), G(3), G(-1))); + + _ASSERT(4, NM_IN_SET(-1, G(1), G(2), G(3), G(-1), G(5))); + _ASSERT(5, NM_IN_SET(-1, G(1), G(2), G(3), G(4), G(-1))); + _ASSERT(6, NM_IN_SET(-1, G(1), G(2), G(3), G(4), G(5), G(-1))); + + _ASSERT(1, !NM_IN_SET_SE(-1, G(1))); + _ASSERT(1, NM_IN_SET_SE(-1, G(-1))); + + _ASSERT(2, !NM_IN_SET_SE(-1, G(1), G(2))); + _ASSERT(2, NM_IN_SET_SE(-1, G(-1), G(2))); + _ASSERT(2, NM_IN_SET_SE(-1, G(1), G(-1))); + _ASSERT(2, NM_IN_SET_SE(-1, G(-1), G(-1))); + + _ASSERT(3, !NM_IN_SET_SE(-1, G(1), G(2), G(3))); + _ASSERT(3, NM_IN_SET_SE(-1, G(-1), G(2), G(3))); + _ASSERT(3, NM_IN_SET_SE(-1, G(1), G(-1), G(3))); + _ASSERT(3, NM_IN_SET_SE(-1, G(1), G(2), G(-1))); + _ASSERT(3, NM_IN_SET_SE(-1, G(1), G(-1), G(-1))); + _ASSERT(3, NM_IN_SET_SE(-1, G(-1), G(2), G(-1))); + _ASSERT(3, NM_IN_SET_SE(-1, G(-1), G(-1), G(3))); + _ASSERT(3, NM_IN_SET_SE(-1, G(-1), G(-1), G(-1))); + + _ASSERT(4, !NM_IN_SET_SE(-1, G(1), G(2), G(3), G(4))); + _ASSERT(4, NM_IN_SET_SE(-1, G(-1), G(2), G(3), G(4))); + _ASSERT(4, NM_IN_SET_SE(-1, G(1), G(-1), G(3), G(4))); + _ASSERT(4, NM_IN_SET_SE(-1, G(1), G(2), G(-1), G(4))); + _ASSERT(4, NM_IN_SET_SE(-1, G(1), G(2), G(3), G(-1))); + + _ASSERT(5, NM_IN_SET_SE(-1, G(1), G(2), G(3), G(-1), G(5))); + _ASSERT(6, NM_IN_SET_SE(-1, G(1), G(2), G(3), G(4), G(5), G(-1))); + + g_assert(!NM_IN_SET(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); +#undef G +#undef N +#undef _ASSERT +} + +/*****************************************************************************/ + +static const char * +_test_nm_in_set_getstr(int *call_counter, gboolean allow_called, const char *value) +{ + g_assert(call_counter); + *call_counter += 1; + if (!allow_called) + g_assert_not_reached(); + return value; +} + +static void +test_nm_in_strset(void) +{ + int call_counter = 0; + +#define G(x) _test_nm_in_set_getstr(&call_counter, TRUE, x) +#define N(x) _test_nm_in_set_getstr(&call_counter, FALSE, x) +#define _ASSERT(expected, expr) \ + G_STMT_START \ + { \ + _test_nm_in_set_assert(&call_counter, 0); \ + g_assert(expr); \ + _test_nm_in_set_assert(&call_counter, (expected)); \ + } \ + G_STMT_END + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL))); + _ASSERT(1, !NM_IN_STRSET("a", G(NULL))); + _ASSERT(1, !NM_IN_STRSET(NULL, G("a"))); + + _ASSERT(1, NM_IN_STRSET_SE(NULL, G(NULL))); + _ASSERT(1, !NM_IN_STRSET_SE("a", G(NULL))); + _ASSERT(1, !NM_IN_STRSET_SE(NULL, G("a"))); + + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N(NULL))); + _ASSERT(2, !NM_IN_STRSET("a", G(NULL), G(NULL))); + _ASSERT(2, NM_IN_STRSET(NULL, G("a"), G(NULL))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N("a"))); + _ASSERT(2, NM_IN_STRSET("a", G(NULL), G("a"))); + _ASSERT(2, !NM_IN_STRSET(NULL, G("a"), G("a"))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N("b"))); + _ASSERT(2, !NM_IN_STRSET("a", G(NULL), G("b"))); + _ASSERT(2, !NM_IN_STRSET(NULL, G("a"), G("b"))); + + _ASSERT(2, NM_IN_STRSET_SE(NULL, G(NULL), G(NULL))); + _ASSERT(2, !NM_IN_STRSET_SE("a", G(NULL), G(NULL))); + _ASSERT(2, NM_IN_STRSET_SE(NULL, G("a"), G(NULL))); + _ASSERT(2, NM_IN_STRSET_SE(NULL, G(NULL), G("a"))); + _ASSERT(2, NM_IN_STRSET_SE("a", G(NULL), G("a"))); + _ASSERT(2, !NM_IN_STRSET_SE(NULL, G("a"), G("a"))); + _ASSERT(2, NM_IN_STRSET_SE(NULL, G(NULL), G("b"))); + _ASSERT(2, !NM_IN_STRSET_SE("a", G(NULL), G("b"))); + _ASSERT(2, !NM_IN_STRSET_SE(NULL, G("a"), G("b"))); + + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N(NULL), N(NULL))); + _ASSERT(3, !NM_IN_STRSET("a", G(NULL), G(NULL), G(NULL))); + _ASSERT(2, NM_IN_STRSET(NULL, G("a"), G(NULL), N(NULL))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N("a"), N(NULL))); + _ASSERT(2, NM_IN_STRSET("a", G(NULL), G("a"), N(NULL))); + _ASSERT(3, NM_IN_STRSET(NULL, G("a"), G("a"), G(NULL))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N("b"), N(NULL))); + _ASSERT(3, !NM_IN_STRSET("a", G(NULL), G("b"), G(NULL))); + _ASSERT(3, NM_IN_STRSET(NULL, G("a"), G("b"), G(NULL))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N(NULL), N("a"))); + _ASSERT(3, NM_IN_STRSET("a", G(NULL), G(NULL), G("a"))); + _ASSERT(2, NM_IN_STRSET(NULL, G("a"), G(NULL), N("a"))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N("a"), N("a"))); + _ASSERT(2, NM_IN_STRSET("a", G(NULL), G("a"), N("a"))); + _ASSERT(3, !NM_IN_STRSET(NULL, G("a"), G("a"), G("a"))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N("b"), N("a"))); + _ASSERT(3, NM_IN_STRSET("a", G(NULL), G("b"), G("a"))); + _ASSERT(3, !NM_IN_STRSET(NULL, G("a"), G("b"), G("a"))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N(NULL), N("b"))); + _ASSERT(3, !NM_IN_STRSET("a", G(NULL), G(NULL), G("b"))); + _ASSERT(2, NM_IN_STRSET(NULL, G("a"), G(NULL), N("b"))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N("a"), N("b"))); + _ASSERT(2, NM_IN_STRSET("a", G(NULL), G("a"), N("b"))); + _ASSERT(3, !NM_IN_STRSET(NULL, G("a"), G("a"), G("b"))); + _ASSERT(1, NM_IN_STRSET(NULL, G(NULL), N("b"), N("b"))); + _ASSERT(3, !NM_IN_STRSET("a", G(NULL), G("b"), G("b"))); + _ASSERT(3, !NM_IN_STRSET(NULL, G("a"), G("b"), G("b"))); + + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G(NULL), G(NULL))); + _ASSERT(3, !NM_IN_STRSET_SE("a", G(NULL), G(NULL), G(NULL))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G("a"), G(NULL), G(NULL))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G("a"), G(NULL))); + _ASSERT(3, NM_IN_STRSET_SE("a", G(NULL), G("a"), G(NULL))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G("a"), G("a"), G(NULL))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G("b"), G(NULL))); + _ASSERT(3, !NM_IN_STRSET_SE("a", G(NULL), G("b"), G(NULL))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G("a"), G("b"), G(NULL))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G(NULL), G("a"))); + _ASSERT(3, NM_IN_STRSET_SE("a", G(NULL), G(NULL), G("a"))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G("a"), G(NULL), G("a"))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G("a"), G("a"))); + _ASSERT(3, NM_IN_STRSET_SE("a", G(NULL), G("a"), G("a"))); + _ASSERT(3, !NM_IN_STRSET_SE(NULL, G("a"), G("a"), G("a"))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G("b"), G("a"))); + _ASSERT(3, NM_IN_STRSET_SE("a", G(NULL), G("b"), G("a"))); + _ASSERT(3, !NM_IN_STRSET_SE(NULL, G("a"), G("b"), G("a"))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G(NULL), G("b"))); + _ASSERT(3, !NM_IN_STRSET_SE("a", G(NULL), G(NULL), G("b"))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G("a"), G(NULL), G("b"))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G("a"), G("b"))); + _ASSERT(3, NM_IN_STRSET_SE("a", G(NULL), G("a"), G("b"))); + _ASSERT(3, !NM_IN_STRSET_SE(NULL, G("a"), G("a"), G("b"))); + _ASSERT(3, NM_IN_STRSET_SE(NULL, G(NULL), G("b"), G("b"))); + _ASSERT(3, !NM_IN_STRSET_SE("a", G(NULL), G("b"), G("b"))); + _ASSERT(3, !NM_IN_STRSET_SE(NULL, G("a"), G("b"), G("b"))); + + _ASSERT(3, NM_IN_STRSET("a", G(NULL), G("b"), G("a"), N("a"))); + _ASSERT(4, NM_IN_STRSET("a", G(NULL), G("b"), G("c"), G("a"))); + _ASSERT(4, !NM_IN_STRSET("a", G(NULL), G("b"), G("c"), G("d"))); + + _ASSERT(4, NM_IN_STRSET("a", G(NULL), G("b"), G("c"), G("a"), N("a"))); + _ASSERT(5, NM_IN_STRSET("a", G(NULL), G("b"), G("c"), G("d"), G("a"))); + _ASSERT(5, !NM_IN_STRSET("a", G(NULL), G("b"), G("c"), G("d"), G("e"))); + + _ASSERT(5, NM_IN_STRSET("a", G(NULL), G("b"), G("c"), G("d"), G("a"), N("a"))); + _ASSERT(6, NM_IN_STRSET("a", G(NULL), G("b"), G("c"), G("d"), G("e"), G("a"))); + _ASSERT(6, !NM_IN_STRSET("a", G(NULL), G("b"), G("c"), G("d"), G("e"), G("f"))); + + g_assert(!NM_IN_STRSET(NULL, + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16")); + g_assert(!NM_IN_STRSET("_", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16")); + g_assert(NM_IN_STRSET("10", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16")); +#undef G +#undef N +#undef _ASSERT +} + +static void +test_route_attributes_parse(void) +{ + GHashTable *ht; + GError * error = NULL; + GVariant * variant; + + ht = nm_utils_parse_variant_attributes("mtu=1400 src=1.2.3.4 cwnd=14", + ' ', + '=', + FALSE, + nm_ip_route_get_variant_attribute_spec(), + &error); + g_assert_no_error(error); + g_assert(ht); + g_hash_table_unref(ht); + + ht = nm_utils_parse_variant_attributes("mtu=1400 src=1.2.3.4 cwnd=14 \\", + ' ', + '=', + FALSE, + nm_ip_route_get_variant_attribute_spec(), + &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_FAILED); + g_assert(!ht); + g_clear_error(&error); + + ht = nm_utils_parse_variant_attributes("mtu.1400 src.1\\.2\\.3\\.4 ", + ' ', + '.', + FALSE, + nm_ip_route_get_variant_attribute_spec(), + &error); + g_assert(ht); + g_assert_no_error(error); + variant = g_hash_table_lookup(ht, NM_IP_ROUTE_ATTRIBUTE_MTU); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)); + g_assert_cmpuint(g_variant_get_uint32(variant), ==, 1400); + + variant = g_hash_table_lookup(ht, NM_IP_ROUTE_ATTRIBUTE_SRC); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)); + g_assert_cmpstr(g_variant_get_string(variant, NULL), ==, "1.2.3.4"); + g_hash_table_unref(ht); + + ht = nm_utils_parse_variant_attributes("from:fd01\\:\\:42\\/64/initrwnd:21", + '/', + ':', + FALSE, + nm_ip_route_get_variant_attribute_spec(), + &error); + g_assert(ht); + g_assert_no_error(error); + variant = g_hash_table_lookup(ht, NM_IP_ROUTE_ATTRIBUTE_INITRWND); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)); + g_assert_cmpuint(g_variant_get_uint32(variant), ==, 21); + + variant = g_hash_table_lookup(ht, NM_IP_ROUTE_ATTRIBUTE_FROM); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)); + g_assert_cmpstr(g_variant_get_string(variant, NULL), ==, "fd01::42/64"); + g_hash_table_unref(ht); +} + +static void +test_route_attributes_format(void) +{ + gs_unref_hashtable GHashTable *ht = NULL; + char * str; + + ht = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, (GDestroyNotify) g_variant_unref); + + str = nm_utils_format_variant_attributes(NULL, ' ', '='); + g_assert_cmpstr(str, ==, NULL); + + str = nm_utils_format_variant_attributes(ht, ' ', '='); + g_assert_cmpstr(str, ==, NULL); + + g_hash_table_insert(ht, NM_IP_ROUTE_ATTRIBUTE_MTU, g_variant_new_uint32(5000)); + g_hash_table_insert(ht, NM_IP_ROUTE_ATTRIBUTE_INITRWND, g_variant_new_uint32(20)); + g_hash_table_insert(ht, NM_IP_ROUTE_ATTRIBUTE_LOCK_MTU, g_variant_new_boolean(TRUE)); + g_hash_table_insert(ht, NM_IP_ROUTE_ATTRIBUTE_SRC, g_variant_new_string("aaaa:bbbb::1")); + str = nm_utils_format_variant_attributes(ht, ' ', '='); + g_assert_cmpstr(str, ==, "initrwnd=20 lock-mtu=true mtu=5000 src=aaaa:bbbb::1"); + g_hash_table_remove_all(ht); + g_free(str); + + g_hash_table_insert(ht, NM_IP_ROUTE_ATTRIBUTE_WINDOW, g_variant_new_uint32(30000)); + g_hash_table_insert(ht, NM_IP_ROUTE_ATTRIBUTE_INITCWND, g_variant_new_uint32(21)); + g_hash_table_insert(ht, + NM_IP_ROUTE_ATTRIBUTE_FROM, + g_variant_new_string("aaaa:bbbb:cccc:dddd::/64")); + str = nm_utils_format_variant_attributes(ht, '/', ':'); + g_assert_cmpstr(str, ==, "from:aaaa\\:bbbb\\:cccc\\:dddd\\:\\:\\/64/initcwnd:21/window:30000"); + g_hash_table_remove_all(ht); + g_free(str); +} + +/*****************************************************************************/ + +static void +test_variant_attribute_spec(void) +{ + const NMVariantAttributeSpec *const *const specs_list[] = { + nm_ip_route_get_variant_attribute_spec(), + }; + int i_specs; + + for (i_specs = 0; i_specs < G_N_ELEMENTS(specs_list); i_specs++) { + const NMVariantAttributeSpec *const *const specs = specs_list[i_specs]; + gsize len; + gsize i; + + g_assert(specs); + + len = NM_PTRARRAY_LEN(specs); + g_assert_cmpint(len, >, 0u); + + _nmtst_variant_attribute_spec_assert_sorted(specs, len); + for (i = 0; i < len; i++) + g_assert(specs[i] + == _nm_variant_attribute_spec_find_binary_search(specs, len, specs[i]->name)); + g_assert(!_nm_variant_attribute_spec_find_binary_search(specs, len, "bogus")); + } +} + +/*****************************************************************************/ + +static gboolean +do_test_nm_set_out_called(int *call_count) +{ + (*call_count)++; + return TRUE; +} + +static void +test_nm_set_out(void) +{ + gboolean val; + gboolean *p_val; + int call_count; + + /* NM_SET_OUT() has an unexpected non-function like behavior + * wrt. side-effects of the value argument. Test it */ + + p_val = &val; + call_count = 0; + NM_SET_OUT(p_val, do_test_nm_set_out_called(&call_count)); + g_assert_cmpint(call_count, ==, 1); + + p_val = NULL; + call_count = 0; + NM_SET_OUT(p_val, do_test_nm_set_out_called(&call_count)); + g_assert_cmpint(call_count, ==, 0); + + /* test that we successfully re-defined _G_BOOLEAN_EXPR() */ +#define _T1(a) \ + ({ \ + g_assert(a > 2); \ + a; \ + }) + g_assert(_T1(3) > 1); +#undef _T1 +} + +/*****************************************************************************/ + +static void +test_get_start_time_for_pid(void) +{ + guint64 x_start_time; + char x_state; + pid_t x_ppid; + + x_start_time = nm_utils_get_start_time_for_pid(getpid(), &x_state, &x_ppid); + + g_assert(x_start_time > 0); + g_assert(x_ppid == getppid()); + g_assert(!NM_IN_SET(x_state, '\0', ' ')); +} + +/*****************************************************************************/ + +static void +test_nm_va_args_macros(void) +{ +#define GET_NARG_1(...) NM_NARG(__VA_ARGS__) + + g_assert_cmpint(0, ==, GET_NARG_1()); + g_assert_cmpint(1, ==, GET_NARG_1(x)); + g_assert_cmpint(2, ==, GET_NARG_1(, )); + g_assert_cmpint(2, ==, GET_NARG_1(, x)); + g_assert_cmpint(2, ==, GET_NARG_1(x, )); + g_assert_cmpint(2, ==, GET_NARG_1(x, x)); + g_assert_cmpint(3, ==, GET_NARG_1(, , )); + g_assert_cmpint(3, ==, GET_NARG_1(, , x)); + g_assert_cmpint(3, ==, GET_NARG_1(, x, )); + g_assert_cmpint(3, ==, GET_NARG_1(, x, x)); + g_assert_cmpint(3, ==, GET_NARG_1(x, , )); + g_assert_cmpint(3, ==, GET_NARG_1(x, , x)); + g_assert_cmpint(3, ==, GET_NARG_1(x, x, )); + g_assert_cmpint(3, ==, GET_NARG_1(x, x, x)); + g_assert_cmpint(4, ==, GET_NARG_1(, , , )); + g_assert_cmpint(4, ==, GET_NARG_1(, , , x)); + g_assert_cmpint(4, ==, GET_NARG_1(, , x, )); + g_assert_cmpint(4, ==, GET_NARG_1(, , x, x)); + g_assert_cmpint(4, ==, GET_NARG_1(, x, , )); + g_assert_cmpint(4, ==, GET_NARG_1(, x, , x)); + g_assert_cmpint(4, ==, GET_NARG_1(, x, x, )); + g_assert_cmpint(4, ==, GET_NARG_1(, x, x, x)); + g_assert_cmpint(4, ==, GET_NARG_1(x, , , )); + g_assert_cmpint(4, ==, GET_NARG_1(x, , , x)); + g_assert_cmpint(4, ==, GET_NARG_1(x, , x, )); + g_assert_cmpint(4, ==, GET_NARG_1(x, , x, x)); + g_assert_cmpint(4, ==, GET_NARG_1(x, x, , )); + g_assert_cmpint(4, ==, GET_NARG_1(x, x, , x)); + g_assert_cmpint(4, ==, GET_NARG_1(x, x, x, )); + g_assert_cmpint(4, ==, GET_NARG_1(x, x, x, x)); + + g_assert_cmpint(5, ==, GET_NARG_1(x, x, x, x, x)); + g_assert_cmpint(6, ==, GET_NARG_1(x, x, x, x, x, x)); + g_assert_cmpint(7, ==, GET_NARG_1(x, x, x, x, x, x, x)); + g_assert_cmpint(8, ==, GET_NARG_1(x, x, x, x, x, x, x, x)); + g_assert_cmpint(9, ==, GET_NARG_1(x, x, x, x, x, x, x, x, x)); + g_assert_cmpint(10, ==, GET_NARG_1(x, x, x, x, x, x, x, x, x, x)); + + G_STATIC_ASSERT_EXPR(0 == GET_NARG_1()); + G_STATIC_ASSERT_EXPR(1 == GET_NARG_1(x)); + G_STATIC_ASSERT_EXPR(2 == GET_NARG_1(x, x)); +} + +/*****************************************************************************/ + +static void +test_ethtool_offload(void) +{ + const NMEthtoolData *d; + + g_assert_cmpint(nm_ethtool_id_get_by_name("invalid"), ==, NM_ETHTOOL_ID_UNKNOWN); + g_assert_cmpint(nm_ethtool_id_get_by_name("feature-rx"), ==, NM_ETHTOOL_ID_FEATURE_RX); + + d = nm_ethtool_data_get_by_optname(NM_ETHTOOL_OPTNAME_FEATURE_RXHASH); + g_assert(d); + g_assert_cmpint(d->id, ==, NM_ETHTOOL_ID_FEATURE_RXHASH); + g_assert_cmpstr(d->optname, ==, NM_ETHTOOL_OPTNAME_FEATURE_RXHASH); + + /* these features are NETIF_F_NEVER_CHANGE: */ + g_assert(!nm_ethtool_data_get_by_optname("feature-netns-local")); + g_assert(!nm_ethtool_data_get_by_optname("feature-tx-lockless")); + g_assert(!nm_ethtool_data_get_by_optname("feature-vlan-challenged")); +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop * loop1; + GMainContext *c2; + GSource * extra_sources[2]; + bool got_signal[5]; + int fd_2; +} IntegData; + +static gboolean +_test_integrate_cb_handle(IntegData *d, int signal) +{ + int i; + + g_assert(d); + g_assert(signal >= 0); + g_assert(signal < G_N_ELEMENTS(d->got_signal)); + + g_assert(!d->got_signal[signal]); + d->got_signal[signal] = TRUE; + + for (i = 0; i < G_N_ELEMENTS(d->got_signal); i++) { + if (!d->got_signal[i]) + break; + } + if (i == G_N_ELEMENTS(d->got_signal)) + g_main_loop_quit(d->loop1); + return G_SOURCE_REMOVE; +} + +static gboolean +_test_integrate_cb_timeout_1(gpointer user_data) +{ + return _test_integrate_cb_handle(user_data, 0); +} + +static gboolean +_test_integrate_cb_fd_2(int fd, GIOCondition condition, gpointer user_data) + +{ + IntegData *d = user_data; + + g_assert(d->got_signal[1]); + g_assert(d->got_signal[2]); + g_assert(d->got_signal[3]); + g_assert(d->extra_sources[0]); + g_assert(d->extra_sources[1]); + + return _test_integrate_cb_handle(d, 4); +} + +static gboolean +_test_integrate_cb_idle_2(gpointer user_data) +{ + IntegData *d = user_data; + GSource * extra_source; + + g_assert(d->got_signal[1]); + g_assert(d->got_signal[2]); + g_assert(d->extra_sources[0]); + g_assert(!d->extra_sources[1]); + + extra_source = nm_g_unix_fd_source_new(d->fd_2, + G_IO_IN, + G_PRIORITY_DEFAULT, + _test_integrate_cb_fd_2, + d, + NULL); + g_source_attach(extra_source, d->c2); + + d->extra_sources[1] = extra_source; + + return _test_integrate_cb_handle(d, 3); +} + +static gboolean +_test_integrate_cb_idle_1(gpointer user_data) +{ + IntegData *d = user_data; + GSource * extra_source; + + g_assert(d->got_signal[2]); + g_assert(!d->extra_sources[0]); + + extra_source = g_idle_source_new(); + g_source_set_callback(extra_source, _test_integrate_cb_idle_2, d, NULL); + g_source_attach(extra_source, d->c2); + + d->extra_sources[0] = extra_source; + + return _test_integrate_cb_handle(d, 1); +} + +static gboolean +_test_integrate_cb_fd_1(int fd, GIOCondition condition, gpointer user_data) + +{ + IntegData *d = user_data; + + g_assert(!d->got_signal[1]); + return _test_integrate_cb_handle(d, 2); +} + +static gboolean +_test_integrate_maincontext_cb_idle1(gpointer user_data) +{ + guint32 *p_count = user_data; + + g_assert(*p_count < 5); + (*p_count)++; + return G_SOURCE_CONTINUE; +} + +static void +test_integrate_maincontext(gconstpointer test_data) +{ + const guint TEST_IDX = GPOINTER_TO_UINT(test_data); + GMainContext * c1 = g_main_context_default(); + nm_auto_unref_gmaincontext GMainContext *c2 = g_main_context_new(); + nm_auto_destroy_and_unref_gsource GSource *integ_source = NULL; + + integ_source = nm_utils_g_main_context_create_integrate_source(c2); + g_source_attach(integ_source, c1); + + if (TEST_IDX == 1) { + nm_auto_destroy_and_unref_gsource GSource *idle_source_1 = NULL; + guint32 count = 0; + + idle_source_1 = g_idle_source_new(); + g_source_set_callback(idle_source_1, _test_integrate_maincontext_cb_idle1, &count, NULL); + g_source_attach(idle_source_1, c2); + + nmtst_main_context_iterate_until_assert(c1, 2000, count == 5); + } + + if (TEST_IDX == 2) { + nm_auto_destroy_and_unref_gsource GSource *main_timeout_source = NULL; + nm_auto_destroy_and_unref_gsource GSource *timeout_source_1 = NULL; + nm_auto_destroy_and_unref_gsource GSource *idle_source_1 = NULL; + nm_auto_destroy_and_unref_gsource GSource *fd_source_1 = NULL; + nm_auto_unref_gmainloop GMainLoop *loop1 = NULL; + nm_auto_close int fd_1 = -1; + nm_auto_close int fd_2 = -1; + IntegData d; + int i; + + main_timeout_source = g_timeout_source_new(3000); + g_source_set_callback(main_timeout_source, nmtst_g_source_assert_not_called, NULL, NULL); + g_source_attach(main_timeout_source, c1); + + loop1 = g_main_loop_new(c1, FALSE); + + d = (IntegData){ + .loop1 = loop1, + .c2 = c2, + }; + + fd_1 = open("/dev/null", O_RDONLY | O_CLOEXEC); + g_assert(fd_1 >= 0); + fd_source_1 = nm_g_unix_fd_source_new(fd_1, + G_IO_IN, + G_PRIORITY_DEFAULT, + _test_integrate_cb_fd_1, + &d, + NULL); + g_source_attach(fd_source_1, c2); + + fd_2 = open("/dev/null", O_RDONLY | O_CLOEXEC); + g_assert(fd_2 >= 0); + d.fd_2 = fd_2; + + idle_source_1 = g_idle_source_new(); + g_source_set_callback(idle_source_1, _test_integrate_cb_idle_1, &d, NULL); + g_source_attach(idle_source_1, c2); + + timeout_source_1 = g_timeout_source_new(5); + g_source_set_callback(timeout_source_1, _test_integrate_cb_timeout_1, &d, NULL); + g_source_attach(timeout_source_1, c2); + + g_main_loop_run(loop1); + + for (i = 0; i < G_N_ELEMENTS(d.extra_sources); i++) { + g_assert(d.extra_sources[i]); + nm_clear_pointer(&d.extra_sources[i], nm_g_source_destroy_and_unref); + } + } +} + +/*****************************************************************************/ + +static void +test_nm_ip_addr_zero(void) +{ + in_addr_t a4 = nmtst_inet4_from_string("0.0.0.0"); + struct in6_addr a6 = *nmtst_inet6_from_string("::"); + char buf[NM_UTILS_INET_ADDRSTRLEN]; + NMIPAddr a = NM_IP_ADDR_INIT; + + g_assert(memcmp(&a, &nm_ip_addr_zero, sizeof(a)) == 0); + + g_assert(IN6_IS_ADDR_UNSPECIFIED(&nm_ip_addr_zero.addr6)); + g_assert(memcmp(&nm_ip_addr_zero.addr6, &in6addr_any, sizeof(in6addr_any)) == 0); + + g_assert(memcmp(&nm_ip_addr_zero, &a4, sizeof(a4)) == 0); + g_assert(memcmp(&nm_ip_addr_zero, &a6, sizeof(a6)) == 0); + + g_assert_cmpstr(_nm_utils_inet4_ntop(nm_ip_addr_zero.addr4, buf), ==, "0.0.0.0"); + g_assert_cmpstr(_nm_utils_inet6_ntop(&nm_ip_addr_zero.addr6, buf), ==, "::"); + + g_assert_cmpstr(nm_utils_inet_ntop(AF_INET, &nm_ip_addr_zero, buf), ==, "0.0.0.0"); + g_assert_cmpstr(nm_utils_inet_ntop(AF_INET6, &nm_ip_addr_zero, buf), ==, "::"); + + G_STATIC_ASSERT_EXPR(sizeof(a) == sizeof(a.array)); +} + +static void +test_connection_ovs_ifname(gconstpointer test_data) +{ + const guint TEST_CASE = GPOINTER_TO_UINT(test_data); + gs_unref_object NMConnection *con = NULL; + NMSettingConnection * s_con = NULL; + NMSettingOvsBridge * s_ovs_bridge = NULL; + NMSettingOvsPort * s_ovs_port = NULL; + NMSettingOvsInterface * s_ovs_iface = NULL; + NMSettingOvsPatch * s_ovs_patch = NULL; + const char * ovs_iface_type = NULL; + + switch (TEST_CASE) { + case 1: + con = nmtst_create_minimal_connection("test_connection_ovs_ifname_bridge", + NULL, + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + &s_con); + s_ovs_bridge = nm_connection_get_setting_ovs_bridge(con); + g_assert(s_ovs_bridge); + break; + case 2: + con = nmtst_create_minimal_connection("test_connection_ovs_ifname_port", + NULL, + NM_SETTING_OVS_PORT_SETTING_NAME, + &s_con); + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_BRIDGE_SETTING_NAME, + NULL); + + s_ovs_port = nm_connection_get_setting_ovs_port(con); + g_assert(s_ovs_port); + break; + case 3: + con = nmtst_create_minimal_connection("test_connection_ovs_ifname_interface_patch", + NULL, + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + &s_con); + s_ovs_iface = nm_connection_get_setting_ovs_interface(con); + g_assert(s_ovs_iface); + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + + g_object_set(s_ovs_iface, NM_SETTING_OVS_INTERFACE_TYPE, "patch", NULL); + + s_ovs_patch = NM_SETTING_OVS_PATCH(nm_setting_ovs_patch_new()); + g_assert(s_ovs_patch); + + g_object_set(s_ovs_patch, NM_SETTING_OVS_PATCH_PEER, "1.2.3.4", NULL); + + nm_connection_add_setting(con, NM_SETTING(s_ovs_patch)); + s_ovs_patch = nm_connection_get_setting_ovs_patch(con); + g_assert(s_ovs_patch); + ovs_iface_type = "patch"; + break; + case 4: + con = nmtst_create_minimal_connection("test_connection_ovs_ifname_interface_internal", + NULL, + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + &s_con); + s_ovs_iface = nm_connection_get_setting_ovs_interface(con); + g_assert(s_ovs_iface); + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + + g_object_set(s_ovs_iface, NM_SETTING_OVS_INTERFACE_TYPE, "internal", NULL); + ovs_iface_type = "internal"; + break; + case 5: + con = nmtst_create_minimal_connection("test_connection_ovs_ifname_interface_system", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + + s_ovs_iface = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new()); + g_assert(s_ovs_iface); + + g_object_set(s_ovs_iface, NM_SETTING_OVS_INTERFACE_TYPE, "system", NULL); + + nm_connection_add_setting(con, NM_SETTING(s_ovs_iface)); + s_ovs_iface = nm_connection_get_setting_ovs_interface(con); + g_assert(s_ovs_iface); + + ovs_iface_type = "system"; + break; + case 6: + con = nmtst_create_minimal_connection("test_connection_ovs_ifname_interface_dpdk", + NULL, + NM_SETTING_OVS_INTERFACE_SETTING_NAME, + &s_con); + s_ovs_iface = nm_connection_get_setting_ovs_interface(con); + g_assert(s_ovs_iface); + + g_object_set(s_con, + NM_SETTING_CONNECTION_MASTER, + "master0", + NM_SETTING_CONNECTION_SLAVE_TYPE, + NM_SETTING_OVS_PORT_SETTING_NAME, + NULL); + + g_object_set(s_ovs_iface, NM_SETTING_OVS_INTERFACE_TYPE, "dpdk", NULL); + ovs_iface_type = "dpdk"; + break; + } + + if (!nm_streq0(ovs_iface_type, "system")) { + /* wrong: contains backward slash */ + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "ovs\\0", NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + + /* wrong: contains forward slash */ + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "ovs/0", NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + } + + /* wrong: contains space */ + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "ovs 0", NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + + /* good */ + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "ovs0", NULL); + nmtst_assert_connection_verifies(con); + + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "ovs-br0", NULL); + nmtst_assert_connection_verifies(con); + + /* good if bridge, port, or patch interface */ + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "ovs123123123123130123123", NULL); + + if (!ovs_iface_type || nm_streq(ovs_iface_type, "patch")) + nmtst_assert_connection_verifies(con); + else { + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + } +} + +/*****************************************************************************/ + +static gboolean +_strsplit_quoted_char_needs_escaping(char ch) +{ + return NM_IN_SET(ch, '\'', '\"', '\\') || strchr(NM_ASCII_WHITESPACES, ch); +} + +static char * +_strsplit_quoted_create_str_rand(gssize len) +{ + NMStrBuf strbuf = NM_STR_BUF_INIT(nmtst_get_rand_uint32() % 200, nmtst_get_rand_bool()); + + g_assert(len >= -1); + + if (len == -1) + len = nmtst_get_rand_word_length(NULL); + + while (len-- > 0) { + char ch; + + ch = nmtst_rand_select('a', ' ', '\\', '"', '\'', nmtst_get_rand_uint32() % 255 + 1); + g_assert(ch); + nm_str_buf_append_c(&strbuf, ch); + } + + if (!strbuf.allocated) + nm_str_buf_maybe_expand(&strbuf, 1, nmtst_get_rand_bool()); + return nm_str_buf_finalize(&strbuf, NULL); +} + +static char ** +_strsplit_quoted_create_strv_rand(void) +{ + guint len = nmtst_get_rand_word_length(NULL); + char **ptr; + guint i; + + ptr = g_new(char *, len + 1); + for (i = 0; i < len; i++) + ptr[i] = _strsplit_quoted_create_str_rand(-1); + ptr[i] = NULL; + return ptr; +} + +static char * +_strsplit_quoted_join_strv_rand(const char *const *strv) +{ + NMStrBuf strbuf = NM_STR_BUF_INIT(nmtst_get_rand_uint32() % 200, nmtst_get_rand_bool()); + char * result; + gsize l; + gsize l2; + gsize * p_l2 = nmtst_get_rand_bool() ? &l2 : NULL; + gsize i; + + g_assert(strv); + + nm_str_buf_append_c_repeated(&strbuf, ' ', nmtst_get_rand_word_length(NULL) / 4); + for (i = 0; strv[i]; i++) { + const char *s = strv[i]; + gsize j; + char quote; + + nm_str_buf_append_c_repeated(&strbuf, ' ', 1 + nmtst_get_rand_word_length(NULL) / 4); + + j = 0; + quote = '\0'; + while (TRUE) { + char ch = s[j++]; + + /* extract_first_word*/ + if (quote != '\0') { + if (ch == '\0') { + nm_str_buf_append_c(&strbuf, quote); + break; + } + if (ch == quote || ch == '\\' || nmtst_get_rand_uint32() % 5 == 0) + nm_str_buf_append_c(&strbuf, '\\'); + nm_str_buf_append_c(&strbuf, ch); + if (nmtst_get_rand_uint32() % 3 == 0) { + nm_str_buf_append_c(&strbuf, quote); + quote = '\0'; + goto next_maybe_quote; + } + continue; + } + + if (ch == '\0') { + if (s == strv[i]) { + quote = nmtst_rand_select('\'', '"'); + nm_str_buf_append_c_repeated(&strbuf, quote, 2); + } + break; + } + + if (_strsplit_quoted_char_needs_escaping(ch) || nmtst_get_rand_uint32() % 5 == 0) + nm_str_buf_append_c(&strbuf, '\\'); + + nm_str_buf_append_c(&strbuf, ch); + +next_maybe_quote: + if (nmtst_get_rand_uint32() % 5 == 0) { + quote = nmtst_rand_select('\'', '\"'); + nm_str_buf_append_c(&strbuf, quote); + if (nmtst_get_rand_uint32() % 5 == 0) { + nm_str_buf_append_c(&strbuf, quote); + quote = '\0'; + } + } + } + } + nm_str_buf_append_c_repeated(&strbuf, ' ', nmtst_get_rand_word_length(NULL) / 4); + + nm_str_buf_maybe_expand(&strbuf, 1, nmtst_get_rand_bool()); + + l = strbuf.len; + result = nm_str_buf_finalize(&strbuf, p_l2); + g_assert(!p_l2 || l == *p_l2); + g_assert(strlen(result) == l); + return result; +} + +static void +_strsplit_quoted_assert_strv(const char * topic, + const char * str, + const char *const *strv1, + const char *const *strv2) +{ + nm_auto_str_buf NMStrBuf s1 = {}; + nm_auto_str_buf NMStrBuf s2 = {}; + gs_free char * str_escaped = NULL; + int i; + + g_assert(str); + g_assert(strv1); + g_assert(strv2); + + if (nm_utils_strv_equal(strv1, strv2)) + return; + + for (i = 0; strv1[i]; i++) { + gs_free char *s = g_strescape(strv1[i], NULL); + + g_print(">>> [%s] strv1[%d] = \"%s\"\n", topic, i, s); + if (i > 0) + nm_str_buf_append_c(&s1, ' '); + nm_str_buf_append_printf(&s1, "\"%s\"", s); + } + + for (i = 0; strv2[i]; i++) { + gs_free char *s = g_strescape(strv2[i], NULL); + + g_print(">>> [%s] strv2[%d] = \"%s\"\n", topic, i, s); + if (i > 0) + nm_str_buf_append_c(&s2, ' '); + nm_str_buf_append_printf(&s2, "\"%s\"", s); + } + + nm_str_buf_maybe_expand(&s1, 1, FALSE); + nm_str_buf_maybe_expand(&s2, 1, FALSE); + + str_escaped = g_strescape(str, NULL); + g_error("compared words differs: [%s] str=\"%s\"; strv1=%s; strv2=%s", + topic, + str_escaped, + nm_str_buf_get_str(&s1), + nm_str_buf_get_str(&s2)); +} + +static void +_strsplit_quoted_test(const char *str, const char *const *strv_expected) +{ + gs_strfreev char **strv_systemd = NULL; + gs_strfreev char **strv_nm = NULL; + int r; + + g_assert(str); + + r = nmtst_systemd_extract_first_word_all(str, &strv_systemd); + g_assert_cmpint(r, ==, 1); + g_assert(strv_systemd); + + if (!strv_expected) + strv_expected = (const char *const *) strv_systemd; + + _strsplit_quoted_assert_strv("systemd", str, strv_expected, (const char *const *) strv_systemd); + + strv_nm = nm_utils_strsplit_quoted(str); + g_assert(strv_nm); + _strsplit_quoted_assert_strv("nm", str, strv_expected, (const char *const *) strv_nm); +} + +static void +test_strsplit_quoted(void) +{ + int i_run; + + _strsplit_quoted_test("", NM_MAKE_STRV()); + _strsplit_quoted_test(" ", NM_MAKE_STRV()); + _strsplit_quoted_test(" ", NM_MAKE_STRV()); + _strsplit_quoted_test(" \t", NM_MAKE_STRV()); + _strsplit_quoted_test("a b", NM_MAKE_STRV("a", "b")); + _strsplit_quoted_test("a\\ b", NM_MAKE_STRV("a b")); + _strsplit_quoted_test(" a\\ \"b\"", NM_MAKE_STRV("a b")); + _strsplit_quoted_test(" a\\ \"b\" c \n", NM_MAKE_STRV("a b", "c")); + + for (i_run = 0; i_run < 1000; i_run++) { + gs_strfreev char **strv = NULL; + gs_free char * str = NULL; + + /* create random strv array and join them carefully so that splitting + * them will yield the original value. */ + strv = _strsplit_quoted_create_strv_rand(); + str = _strsplit_quoted_join_strv_rand((const char *const *) strv); + _strsplit_quoted_test(str, (const char *const *) strv); + } + + /* Create random words and assert that systemd and our implementation can + * both split them (and in the exact same way). */ + for (i_run = 0; i_run < 1000; i_run++) { + gs_free char *s = _strsplit_quoted_create_str_rand(nmtst_get_rand_uint32() % 150); + + _strsplit_quoted_test(s, NULL); + } +} + +/*****************************************************************************/ + +static void +_do_wifi_ghz_freqs(const guint *freqs, const char *band) +{ + int len; + int j; + int i; + + g_assert(NM_IN_STRSET(band, "a", "bg")); + g_assert(freqs); + g_assert(freqs[0] != 0); + + for (i = 0; freqs[i]; i++) { + for (j = 0; j < i; j++) + g_assert(freqs[i] != freqs[j]); + } + len = i; + + g_assert(nm_utils_wifi_freq_to_channel(0) == 0); + g_assert(nm_utils_wifi_channel_to_freq(0, "bg") == -1); + g_assert(nm_utils_wifi_channel_to_freq(0, "foo") == 0); + g_assert(!nm_utils_wifi_is_channel_valid(0, "bg")); + g_assert(!nm_utils_wifi_is_channel_valid(0, "foo")); + + for (i = 0; i < len; i++) { + guint freq = freqs[i]; + guint32 chan; + guint32 freq2; + + chan = nm_utils_wifi_freq_to_channel(freq); + g_assert(chan != 0); + + freq2 = nm_utils_wifi_channel_to_freq(chan, band); + g_assert(freq2 == freq); + + g_assert(nm_utils_wifi_is_channel_valid(chan, band)); + } + + g_assert(freqs[len] == 0); +} + +static void +test_nm_utils_wifi_ghz_freqs(void) +{ + _do_wifi_ghz_freqs(nm_utils_wifi_2ghz_freqs(), "bg"); + _do_wifi_ghz_freqs(nm_utils_wifi_5ghz_freqs(), "a"); +} + +/*****************************************************************************/ + +static void +test_vpn_connection_state_reason(void) +{ +#define ASSERT(v1, v2) \ + G_STMT_START \ + { \ + G_STATIC_ASSERT((gint64) (v1) == v2); \ + G_STATIC_ASSERT((gint64) (v2) == v1); \ + \ + nm_assert(((NMActiveConnectionStateReason) (int) (v1)) == v2); \ + nm_assert(((NMVpnConnectionStateReason) (int) (v2)) == v1); \ + } \ + G_STMT_END + + ASSERT(NM_VPN_CONNECTION_STATE_REASON_UNKNOWN, NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_NONE, NM_ACTIVE_CONNECTION_STATE_REASON_NONE); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED, + NM_ACTIVE_CONNECTION_STATE_REASON_USER_DISCONNECTED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED, + NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_STOPPED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_IP_CONFIG_INVALID, + NM_ACTIVE_CONNECTION_STATE_REASON_IP_CONFIG_INVALID); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_CONNECT_TIMEOUT, + NM_ACTIVE_CONNECTION_STATE_REASON_CONNECT_TIMEOUT); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_SERVICE_START_FAILED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_NO_SECRETS, NM_ACTIVE_CONNECTION_STATE_REASON_NO_SECRETS); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_LOGIN_FAILED, + NM_ACTIVE_CONNECTION_STATE_REASON_LOGIN_FAILED); + ASSERT(NM_VPN_CONNECTION_STATE_REASON_CONNECTION_REMOVED, + NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init(&argc, &argv, TRUE); + + g_test_add_func("/core/general/test_nm_ascii_spaces", test_nm_ascii_spaces); + g_test_add_func("/core/general/test_wired_wake_on_lan_enum", test_wired_wake_on_lan_enum); + g_test_add_func("/core/general/test_wireless_wake_on_wlan_enum", + test_wireless_wake_on_wlan_enum); + g_test_add_func("/core/general/test_device_wifi_capabilities", test_device_wifi_capabilities); + g_test_add_func("/core/general/test_80211_mode", test_80211_mode); + g_test_add_func("/core/general/test_vlan_flags", test_vlan_flags); + g_test_add_func("/core/general/test_nm_hash", test_nm_hash); + g_test_add_func("/core/general/test_nm_g_slice_free_fcn", test_nm_g_slice_free_fcn); + g_test_add_func("/core/general/test_c_list_sort", test_c_list_sort); + g_test_add_func("/core/general/test_dedup_multi", test_dedup_multi); + g_test_add_func("/core/general/test_utils_str_utf8safe", test_utils_str_utf8safe); + g_test_add_func("/core/general/test_nm_utils_strsplit_set", test_nm_utils_strsplit_set); + g_test_add_func("/core/general/test_nm_utils_escaped_tokens", test_nm_utils_escaped_tokens); + g_test_add_func("/core/general/test_nm_in_set", test_nm_in_set); + g_test_add_func("/core/general/test_nm_in_strset", test_nm_in_strset); + g_test_add_func("/core/general/test_setting_vpn_items", test_setting_vpn_items); + g_test_add_func("/core/general/test_setting_vpn_update_secrets", + test_setting_vpn_update_secrets); + g_test_add_func("/core/general/test_setting_vpn_modify_during_foreach", + test_setting_vpn_modify_during_foreach); + g_test_add_func("/core/general/test_setting_ip4_config_labels", test_setting_ip4_config_labels); + g_test_add_func("/core/general/test_setting_ip4_config_address_data", + test_setting_ip4_config_address_data); + g_test_add_func("/core/general/test_setting_ip_route_attributes", + test_setting_ip_route_attributes); + g_test_add_func("/core/general/test_setting_gsm_apn_spaces", test_setting_gsm_apn_spaces); + g_test_add_func("/core/general/test_setting_gsm_apn_bad_chars", test_setting_gsm_apn_bad_chars); + g_test_add_func("/core/general/test_setting_gsm_apn_underscore", + test_setting_gsm_apn_underscore); + g_test_add_func("/core/general/test_setting_gsm_without_number", + test_setting_gsm_without_number); + g_test_add_func("/core/general/test_setting_gsm_sim_operator_id", + test_setting_gsm_sim_operator_id); + g_test_add_func("/core/general/test_setting_to_dbus_all", test_setting_to_dbus_all); + g_test_add_func("/core/general/test_setting_to_dbus_no_secrets", + test_setting_to_dbus_no_secrets); + g_test_add_func("/core/general/test_setting_to_dbus_only_secrets", + test_setting_to_dbus_only_secrets); + g_test_add_func("/core/general/test_setting_to_dbus_transform", test_setting_to_dbus_transform); + g_test_add_func("/core/general/test_setting_to_dbus_enum", test_setting_to_dbus_enum); + g_test_add_func("/core/general/test_setting_compare_id", test_setting_compare_id); + g_test_add_func("/core/general/test_setting_compare_addresses", test_setting_compare_addresses); + g_test_add_func("/core/general/test_setting_compare_routes", test_setting_compare_routes); + g_test_add_func("/core/general/test_setting_compare_wired_cloned_mac_address", + test_setting_compare_wired_cloned_mac_address); + g_test_add_func("/core/general/test_setting_compare_wirless_cloned_mac_address", + test_setting_compare_wireless_cloned_mac_address); + g_test_add_func("/core/general/test_setting_compare_timestamp", test_setting_compare_timestamp); +#define ADD_FUNC(name, func, secret_flags, comp_flags, remove_secret) \ + g_test_add_data_func_full( \ + "/core/general/" G_STRINGIFY(func) "_" name, \ + test_data_compare_secrets_new(secret_flags, comp_flags, remove_secret), \ + func, \ + g_free) + ADD_FUNC("agent_owned", + test_setting_compare_secrets, + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS, + TRUE); + ADD_FUNC("not_saved", + test_setting_compare_secrets, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS, + TRUE); + ADD_FUNC("secrets", + test_setting_compare_secrets, + NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS, + TRUE); + ADD_FUNC("exact", + test_setting_compare_secrets, + NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_COMPARE_FLAG_EXACT, + FALSE); + ADD_FUNC("agent_owned", + test_setting_compare_vpn_secrets, + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS, + TRUE); + ADD_FUNC("not_saved", + test_setting_compare_vpn_secrets, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS, + TRUE); + ADD_FUNC("secrets", + test_setting_compare_vpn_secrets, + NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS, + TRUE); + ADD_FUNC("exact", + test_setting_compare_vpn_secrets, + NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_COMPARE_FLAG_EXACT, + FALSE); + g_test_add_func("/core/general/test_setting_old_uuid", test_setting_old_uuid); + + g_test_add_func("/core/general/test_connection_to_dbus_setting_name", + test_connection_to_dbus_setting_name); + g_test_add_func("/core/general/test_connection_to_dbus_deprecated_props", + test_connection_to_dbus_deprecated_props); + g_test_add_func("/core/general/test_setting_new_from_dbus", test_setting_new_from_dbus); + g_test_add_func("/core/general/test_setting_new_from_dbus_transform", + test_setting_new_from_dbus_transform); + g_test_add_func("/core/general/test_setting_new_from_dbus_enum", + test_setting_new_from_dbus_enum); + g_test_add_func("/core/general/test_setting_new_from_dbus_bad", test_setting_new_from_dbus_bad); + g_test_add_func("/core/general/test_connection_replace_settings", + test_connection_replace_settings); + g_test_add_func("/core/general/test_connection_replace_settings_from_connection", + test_connection_replace_settings_from_connection); + g_test_add_func("/core/general/test_connection_replace_settings_bad", + test_connection_replace_settings_bad); + g_test_add_func("/core/general/test_connection_new_from_dbus", test_connection_new_from_dbus); + g_test_add_func("/core/general/test_connection_normalize_virtual_iface_name", + test_connection_normalize_virtual_iface_name); + g_test_add_func("/core/general/test_connection_normalize_uuid", test_connection_normalize_uuid); + g_test_add_func("/core/general/test_connection_normalize_type", test_connection_normalize_type); + g_test_add_func("/core/general/test_connection_normalize_slave_type_1", + test_connection_normalize_slave_type_1); + g_test_add_func("/core/general/test_connection_normalize_slave_type_2", + test_connection_normalize_slave_type_2); + g_test_add_func("/core/general/test_connection_normalize_infiniband_mtu", + test_connection_normalize_infiniband_mtu); + g_test_add_func("/core/general/test_connection_normalize_gateway_never_default", + test_connection_normalize_gateway_never_default); + g_test_add_func("/core/general/test_connection_normalize_may_fail", + test_connection_normalize_may_fail); + g_test_add_func("/core/general/test_connection_normalize_shared_addresses", + test_connection_normalize_shared_addresses); + g_test_add_data_func("/core/general/test_connection_normalize_ovs_interface_type_system/1", + GUINT_TO_POINTER(1), + test_connection_normalize_ovs_interface_type_system); + g_test_add_data_func("/core/general/test_connection_normalize_ovs_interface_type_system/2", + GUINT_TO_POINTER(2), + test_connection_normalize_ovs_interface_type_system); + g_test_add_data_func("/core/general/test_connection_normalize_ovs_interface_type_system/3", + GUINT_TO_POINTER(3), + test_connection_normalize_ovs_interface_type_system); + g_test_add_data_func("/core/general/test_connection_normalize_ovs_interface_type_system/4", + GUINT_TO_POINTER(4), + test_connection_normalize_ovs_interface_type_system); + g_test_add_data_func("/core/general/test_connection_normalize_ovs_interface_type_system/5", + GUINT_TO_POINTER(5), + test_connection_normalize_ovs_interface_type_system); + g_test_add_data_func("/core/general/test_connection_normalize_ovs_interface_type_system/6", + GUINT_TO_POINTER(6), + test_connection_normalize_ovs_interface_type_system); + g_test_add_data_func("/core/general/test_connection_normalize_ovs_interface_type_system/7", + GUINT_TO_POINTER(7), + test_connection_normalize_ovs_interface_type_system); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/1", + GUINT_TO_POINTER(1), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/2", + GUINT_TO_POINTER(2), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/3", + GUINT_TO_POINTER(3), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/4", + GUINT_TO_POINTER(4), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/5", + GUINT_TO_POINTER(5), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/6", + GUINT_TO_POINTER(6), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/7", + GUINT_TO_POINTER(7), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/8", + GUINT_TO_POINTER(8), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/9", + GUINT_TO_POINTER(9), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/10", + GUINT_TO_POINTER(10), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/11", + GUINT_TO_POINTER(11), + test_connection_normalize_ovs_interface_type_ovs_interface); + g_test_add_data_func( + "/core/general/test_connection_normalize_ovs_interface_type_ovs_interface/12", + GUINT_TO_POINTER(12), + test_connection_normalize_ovs_interface_type_ovs_interface); + + g_test_add_data_func("/core/general/test_connection_ovs_ifname/1", + GUINT_TO_POINTER(1), + test_connection_ovs_ifname); + g_test_add_data_func("/core/general/test_connection_ovs_ifname/2", + GUINT_TO_POINTER(2), + test_connection_ovs_ifname); + g_test_add_data_func("/core/general/test_connection_ovs_ifname/3", + GUINT_TO_POINTER(3), + test_connection_ovs_ifname); + g_test_add_data_func("/core/general/test_connection_ovs_ifname/4", + GUINT_TO_POINTER(4), + test_connection_ovs_ifname); + g_test_add_data_func("/core/general/test_connection_ovs_ifname/5", + GUINT_TO_POINTER(5), + test_connection_ovs_ifname); + g_test_add_data_func("/core/general/test_connection_ovs_ifname/6", + GUINT_TO_POINTER(6), + test_connection_ovs_ifname); + + g_test_add_func("/core/general/test_setting_connection_permissions_helpers", + test_setting_connection_permissions_helpers); + g_test_add_func("/core/general/test_setting_connection_permissions_property", + test_setting_connection_permissions_property); + + g_test_add_func("/core/general/test_connection_compare_same", test_connection_compare_same); + g_test_add_func("/core/general/test_connection_compare_key_only_in_a", + test_connection_compare_key_only_in_a); + g_test_add_func("/core/general/test_connection_compare_setting_only_in_a", + test_connection_compare_setting_only_in_a); + g_test_add_func("/core/general/test_connection_compare_key_only_in_b", + test_connection_compare_key_only_in_b); + g_test_add_func("/core/general/test_connection_compare_setting_only_in_b", + test_connection_compare_setting_only_in_b); + + g_test_add_func("/core/general/test_connection_diff_a_only", test_connection_diff_a_only); + g_test_add_func("/core/general/test_connection_diff_same", test_connection_diff_same); + g_test_add_func("/core/general/test_connection_diff_different", test_connection_diff_different); + g_test_add_func("/core/general/test_connection_diff_no_secrets", + test_connection_diff_no_secrets); + g_test_add_func("/core/general/test_connection_diff_inferrable", + test_connection_diff_inferrable); + g_test_add_func("/core/general/test_connection_good_base_types", + test_connection_good_base_types); + g_test_add_func("/core/general/test_connection_bad_base_types", test_connection_bad_base_types); + + g_test_add_func("/core/general/test_hwaddr_aton_ether_normal", test_hwaddr_aton_ether_normal); + g_test_add_func("/core/general/test_hwaddr_aton_ib_normal", test_hwaddr_aton_ib_normal); + g_test_add_func("/core/general/test_hwaddr_aton_no_leading_zeros", + test_hwaddr_aton_no_leading_zeros); + g_test_add_func("/core/general/test_hwaddr_aton_malformed", test_hwaddr_aton_malformed); + g_test_add_func("/core/general/test_hwaddr_equal", test_hwaddr_equal); + g_test_add_func("/core/general/test_hwaddr_canonical", test_hwaddr_canonical); + + g_test_add_func("/core/general/test_ip4_prefix_to_netmask", test_ip4_prefix_to_netmask); + g_test_add_func("/core/general/test_ip4_netmask_to_prefix", test_ip4_netmask_to_prefix); + + g_test_add_func("/core/general/test_connection_changed_signal", test_connection_changed_signal); + g_test_add_func("/core/general/test_setting_connection_changed_signal", + test_setting_connection_changed_signal); + g_test_add_func("/core/general/test_setting_bond_changed_signal", + test_setting_bond_changed_signal); + g_test_add_func("/core/general/test_setting_ip4_changed_signal", + test_setting_ip4_changed_signal); + g_test_add_func("/core/general/test_setting_ip6_changed_signal", + test_setting_ip6_changed_signal); + g_test_add_func("/core/general/test_setting_vlan_changed_signal", + test_setting_vlan_changed_signal); + g_test_add_func("/core/general/test_setting_vpn_changed_signal", + test_setting_vpn_changed_signal); + g_test_add_func("/core/general/test_setting_wired_changed_signal", + test_setting_wired_changed_signal); + g_test_add_func("/core/general/test_setting_wireless_changed_signal", + test_setting_wireless_changed_signal); + g_test_add_func("/core/general/test_setting_wireless_security_changed_signal", + test_setting_wireless_security_changed_signal); + g_test_add_func("/core/general/test_setting_802_1x_changed_signal", + test_setting_802_1x_changed_signal); + g_test_add_func("/core/general/test_setting_ip4_gateway", test_setting_ip4_gateway); + g_test_add_func("/core/general/test_setting_ip6_gateway", test_setting_ip6_gateway); + g_test_add_func("/core/general/test_setting_compare_default_strv", + test_setting_compare_default_strv); + g_test_add_func("/core/general/test_setting_user_data", test_setting_user_data); + + g_test_add_func("/core/general/test_sock_addr_endpoint", test_sock_addr_endpoint); + + g_test_add_func("/core/general/hexstr2bin", test_hexstr2bin); + g_test_add_func("/core/general/nm_strquote", test_nm_strquote); + g_test_add_func("/core/general/test_nm_utils_uuid_generate_from_string", + test_nm_utils_uuid_generate_from_string); + g_test_add_func("/core/general/nm_uuid_generate_from_strings", + test_nm_utils_uuid_generate_from_strings); + + g_test_add_func("/core/general/_nm_utils_ascii_str_to_int64", test_nm_utils_ascii_str_to_int64); + g_test_add_func("/core/general/nm_utils_is_power_of_two", test_nm_utils_is_power_of_two); + g_test_add_func("/core/general/nm_utils_ptrarray_find_binary_search_range", + test_nm_utils_ptrarray_find_binary_search); + g_test_add_func("/core/general/nm_utils_ptrarray_find_binary_search_with_duplicates", + test_nm_utils_ptrarray_find_binary_search_with_duplicates); + g_test_add_func("/core/general/_nm_utils_strstrdictkey", test_nm_utils_strstrdictkey); + g_test_add_func("/core/general/nm_ptrarray_len", test_nm_ptrarray_len); + + g_test_add_func("/core/general/_nm_utils_dns_option_validate", + test_nm_utils_dns_option_validate); + g_test_add_func("/core/general/_nm_utils_dns_option_find_idx", + test_nm_utils_dns_option_find_idx); + g_test_add_func("/core/general/_nm_utils_validate_json", test_nm_utils_check_valid_json); + g_test_add_func("/core/general/_nm_utils_team_config_equal", test_nm_utils_team_config_equal); + g_test_add_func("/core/general/test_nm_utils_enum", test_nm_utils_enum); + g_test_add_func("/core/general/nm-set-out", test_nm_set_out); + g_test_add_func("/core/general/route_attributes/parse", test_route_attributes_parse); + g_test_add_func("/core/general/route_attributes/format", test_route_attributes_format); + g_test_add_func("/core/general/test_variant_attribute_spec", test_variant_attribute_spec); + + g_test_add_func("/core/general/get_start_time_for_pid", test_get_start_time_for_pid); + g_test_add_func("/core/general/test_nm_va_args_macros", test_nm_va_args_macros); + g_test_add_func("/core/general/test_ethtool_offload", test_ethtool_offload); + + g_test_add_data_func("/core/general/test_integrate_maincontext/1", + GUINT_TO_POINTER(1), + test_integrate_maincontext); + g_test_add_data_func("/core/general/test_integrate_maincontext/2", + GUINT_TO_POINTER(2), + test_integrate_maincontext); + + g_test_add_func("/core/general/test_nm_ip_addr_zero", test_nm_ip_addr_zero); + g_test_add_func("/core/general/test_nm_utils_wifi_ghz_freqs", test_nm_utils_wifi_ghz_freqs); + + g_test_add_func("/core/general/test_strsplit_quoted", test_strsplit_quoted); + g_test_add_func("/core/general/test_vpn_connection_state_reason", + test_vpn_connection_state_reason); + + return g_test_run(); +} diff --git a/src/libnm-core-impl/tests/test-keyfile.c b/src/libnm-core-impl/tests/test-keyfile.c new file mode 100644 index 00000000..ba7d783b --- /dev/null +++ b/src/libnm-core-impl/tests/test-keyfile.c @@ -0,0 +1,909 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "libnm-glib-aux/nm-json-aux.h" +#include "libnm-core-intern/nm-keyfile-utils.h" +#include "libnm-core-intern/nm-keyfile-internal.h" +#include "nm-simple-connection.h" +#include "nm-setting-connection.h" +#include "nm-setting-wired.h" +#include "nm-setting-8021x.h" +#include "nm-setting-team.h" +#include "nm-setting-user.h" +#include "nm-setting-proxy.h" + +#include "libnm-glib-aux/nm-test-utils.h" + +#define TEST_CERT_DIR NM_BUILD_SRCDIR "/src/libnm-core-impl/tests/certs" +#define TEST_WIRED_TLS_CA_CERT TEST_CERT_DIR "/test-ca-cert.pem" +#define TEST_WIRED_TLS_PRIVKEY TEST_CERT_DIR "/test-key-and-cert.pem" +#define TEST_WIRED_TLS_TPM2KEY TEST_CERT_DIR "/test-tpm2wrapped-key.pem" + +/*****************************************************************************/ + +static void +do_test_encode_key_full(GKeyFile * kf, + const char *name, + const char *key, + const char *key_decode_encode) +{ + gs_free char *to_free1 = NULL; + gs_free char *to_free2 = NULL; + const char * key2; + const char * name2; + + g_assert(key); + + if (name) { + key2 = nm_keyfile_key_encode(name, &to_free1); + g_assert(key2); + g_assert(NM_STRCHAR_ALL(key2, ch, (guchar) ch < 127)); + g_assert_cmpstr(key2, ==, key); + + /* try to add the encoded key to the keyfile. We expect + * no g_critical warning about invalid key. */ + g_key_file_set_value(kf, "group", key, "dummy"); + } + + name2 = nm_keyfile_key_decode(key, &to_free2); + if (name) + g_assert_cmpstr(name2, ==, name); + else { + key2 = nm_keyfile_key_encode(name2, &to_free1); + g_assert(key2); + g_assert(NM_STRCHAR_ALL(key2, ch, (guchar) ch < 127)); + if (key_decode_encode) + g_assert_cmpstr(key2, ==, key_decode_encode); + g_key_file_set_value(kf, "group", key2, "dummy"); + } +} + +#define do_test_encode_key_bijection(kf, name, key) \ + do_test_encode_key_full(kf, "" name, "" key, NULL) +#define do_test_encode_key_identity(kf, name) do_test_encode_key_full(kf, "" name, "" name, NULL) +#define do_test_encode_key_decode_surjection(kf, key, key_decode_encode) \ + do_test_encode_key_full(kf, NULL, "" key, "" key_decode_encode) + +static void +test_encode_key(void) +{ + nm_auto_unref_keyfile GKeyFile *kf = g_key_file_new(); + + do_test_encode_key_identity(kf, "a"); + do_test_encode_key_bijection(kf, "", "\\00"); + do_test_encode_key_bijection(kf, " ", "\\20"); + do_test_encode_key_bijection(kf, "\\ ", "\\\\20"); + do_test_encode_key_identity(kf, "\\0"); + do_test_encode_key_identity(kf, "\\a"); + do_test_encode_key_identity(kf, "\\0g"); + do_test_encode_key_bijection(kf, "\\0f", "\\5C0f"); + do_test_encode_key_bijection(kf, "\\0f ", "\\5C0f\\20"); + do_test_encode_key_bijection(kf, " \\0f ", "\\20\\5C0f\\20"); + do_test_encode_key_bijection(kf, "\xF5", "\\F5"); + do_test_encode_key_bijection(kf, "\x7F", "\\7F"); + do_test_encode_key_bijection(kf, "\x1f", "\\1F"); + do_test_encode_key_bijection(kf, " ", "\\20\\20"); + do_test_encode_key_bijection(kf, " ", "\\20 \\20"); + do_test_encode_key_decode_surjection(kf, "f\\20c", "f c"); + do_test_encode_key_decode_surjection(kf, "\\20\\20\\20", "\\20 \\20"); + + do_test_encode_key_bijection(kf, "\t", "\\09"); + do_test_encode_key_bijection(kf, "\t=x", "\\09\\3Dx"); + do_test_encode_key_bijection( + kf, + "(nm-openvpn-auth-dialog:10283): GdkPixbuf-DEBUG: \tCopy pixels == false", + "(nm-openvpn-auth-dialog:10283): GdkPixbuf-DEBUG: \\09Copy pixels \\3D\\3D false"); +} + +/*****************************************************************************/ + +#define CLEAR(con, keyfile) \ + G_STMT_START \ + { \ + NMConnection **_con = (con); \ + GKeyFile ** _keyfile = (keyfile); \ + \ + g_clear_object(_con); \ + nm_clear_pointer(_keyfile, g_key_file_unref); \ + } \ + G_STMT_END + +static void +_assert_gbytes(GBytes *bytes, gconstpointer data, gssize len) +{ + g_assert((data && len > 0) || !len || (data && len == -1)); + + if (len == -1) + len = strlen(data); + + if (!len) + g_assert(!bytes); + + g_assert(nm_utils_gbytes_equal_mem(bytes, data, len)); +} + +static GKeyFile * +_keyfile_load_from_data(const char *str) +{ + GError * error = NULL; + gboolean success; + GKeyFile *keyfile; + + g_assert(str); + + keyfile = g_key_file_new(); + success = g_key_file_load_from_data(keyfile, str, strlen(str), G_KEY_FILE_NONE, &error); + g_assert_no_error(error); + g_assert(success); + + return keyfile; +} + +static GKeyFile * +_nm_keyfile_write(NMConnection *connection, NMKeyfileWriteHandler handler, void *user_data) +{ + GError * error = NULL; + GKeyFile *kf; + + g_assert(NM_IS_CONNECTION(connection)); + + kf = nm_keyfile_write(connection, NM_KEYFILE_HANDLER_FLAGS_NONE, handler, user_data, &error); + g_assert_no_error(error); + g_assert(kf); + return kf; +} + +static NMConnection * +_nm_keyfile_read(GKeyFile * keyfile, + const char * keyfile_name, + NMKeyfileReadHandler read_handler, + void * read_data, + gboolean needs_normalization) +{ + GError * error = NULL; + NMConnection *con; + gs_free char *filename = NULL; + gs_free char *base_dir = NULL; + + g_assert(keyfile); + g_assert(!keyfile_name || (keyfile_name[0] == '/')); + + base_dir = g_path_get_dirname(keyfile_name); + filename = g_path_get_basename(keyfile_name); + + con = nm_keyfile_read(keyfile, + base_dir, + NM_KEYFILE_HANDLER_FLAGS_NONE, + read_handler, + read_data, + &error); + g_assert_no_error(error); + g_assert(NM_IS_CONNECTION(con)); + + nm_keyfile_read_ensure_id(con, filename); + nm_keyfile_read_ensure_uuid(con, keyfile_name); + + if (needs_normalization) { + nmtst_assert_connection_verifies_after_normalization(con, 0, 0); + nmtst_connection_normalize(con); + } else { + { + NMSettingConnection *s_con; + + /* a non-slave connection must have a proxy setting, but + * keyfile reader does not add that (unless a [proxy] section + * is present. */ + s_con = nm_connection_get_setting_connection(con); + if (s_con && !nm_setting_connection_get_master(s_con) + && !nm_connection_get_setting_proxy(con)) + nm_connection_add_setting(con, nm_setting_proxy_new()); + } + nmtst_assert_connection_verifies_without_normalization(con); + } + return con; +} + +static void +_keyfile_convert(NMConnection ** con, + GKeyFile ** keyfile, + const char * keyfile_name, + NMKeyfileReadHandler read_handler, + void * read_data, + NMKeyfileWriteHandler write_handler, + void * write_data, + gboolean needs_normalization) +{ + NMConnection * c0; + GKeyFile * k0; + gs_unref_object NMConnection *c0_k1_c2 = NULL, *k0_c1 = NULL, *k0_c1_k2_c3 = NULL; + nm_auto_unref_keyfile GKeyFile *k0_c1_k2 = NULL, *c0_k1 = NULL, *c0_k1_c2_k3 = NULL; + + /* convert from @con to @keyfile and check that we can make + * full round trips and obtaining the same result. */ + + g_assert(con); + g_assert(keyfile); + g_assert(*con || *keyfile); + + c0 = *con; + k0 = *keyfile; + + if (c0) { + c0_k1 = _nm_keyfile_write(c0, write_handler, write_data); + c0_k1_c2 = _nm_keyfile_read(c0_k1, keyfile_name, read_handler, read_data, FALSE); + c0_k1_c2_k3 = _nm_keyfile_write(c0_k1_c2, write_handler, write_data); + + g_assert(_nm_keyfile_equal(c0_k1, c0_k1_c2_k3, TRUE)); + } + if (k0) { + NMSetting8021x *s1, *s2; + + k0_c1 = _nm_keyfile_read(k0, keyfile_name, read_handler, read_data, needs_normalization); + k0_c1_k2 = _nm_keyfile_write(k0_c1, write_handler, write_data); + k0_c1_k2_c3 = _nm_keyfile_read(k0_c1_k2, keyfile_name, read_handler, read_data, FALSE); + + /* It is a expected behavior, that if @k0 contains a relative path ca-cert, @k0_c1 will + * contain that path as relative. But @k0_c1_k2 and @k0_c1_k2_c3 will have absolute paths. + * In this case, hack up @k0_c1_k2_c3 to contain the same relative path. */ + s1 = nm_connection_get_setting_802_1x(k0_c1); + s2 = nm_connection_get_setting_802_1x(k0_c1_k2_c3); + if (s1 || s2) { + g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s1), + ==, + nm_setting_802_1x_get_ca_cert_scheme(s2)); + switch (nm_setting_802_1x_get_ca_cert_scheme(s1)) { + case NM_SETTING_802_1X_CK_SCHEME_PATH: + { + const char *p1 = nm_setting_802_1x_get_ca_cert_path(s1); + const char *p2 = nm_setting_802_1x_get_ca_cert_path(s2); + + nmtst_assert_resolve_relative_path_equals(p1, p2); + if (strcmp(p1, p2) != 0) { + gs_free char * puri = NULL; + gs_unref_bytes GBytes *pfile = NULL; + + g_assert(p1[0] != '/' && p2[0] == '/'); + + /* one of the paths is a relative path and the other is absolute. This is an + * expected difference. + * Make the paths of s2 identical to s1... */ + puri = g_strconcat(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, p1, NULL); + pfile = g_bytes_new(puri, strlen(puri) + 1); + g_object_set(s2, NM_SETTING_802_1X_CA_CERT, pfile, NULL); + } + } break; + case NM_SETTING_802_1X_CK_SCHEME_BLOB: + { + GBytes *b1, *b2; + + b1 = nm_setting_802_1x_get_ca_cert_blob(s1); + b2 = nm_setting_802_1x_get_ca_cert_blob(s2); + g_assert(b1); + g_assert(b2); + g_assert(g_bytes_equal(b1, b2)); + break; + } + default: + break; + } + } + + nmtst_assert_connection_equals(k0_c1, FALSE, k0_c1_k2_c3, FALSE); + } + + if (!k0) + *keyfile = g_key_file_ref(c0_k1); + else if (!c0) + *con = g_object_ref(k0_c1); + else { + /* finally, if both a keyfile and a connection are given, assert that they are equal + * after a round of conversion. */ + g_assert(_nm_keyfile_equal(c0_k1, k0_c1_k2, TRUE)); + nmtst_assert_connection_equals(k0_c1, FALSE, c0_k1_c2, FALSE); + } +} + +/*****************************************************************************/ + +static void +_test_8021x_cert_check(NMConnection * con, + NMSetting8021xCKScheme expected_scheme, + const void * value, + gssize val_len) +{ + GKeyFile * keyfile = NULL; + NMSetting8021x *s_8021x; + gs_free char * kval = NULL; + + _keyfile_convert(&con, &keyfile, "/_test_8021x_cert_check/foo", NULL, NULL, NULL, NULL, FALSE); + + s_8021x = nm_connection_get_setting_802_1x(con); + + g_assert(nm_setting_802_1x_get_ca_cert_scheme(s_8021x) == expected_scheme); + + if (expected_scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + const char *path = nm_setting_802_1x_get_ca_cert_path(s_8021x); + + g_assert_cmpstr(path, ==, value); + g_assert(val_len == -1 || strlen(path) == val_len); + + kval = g_key_file_get_string(keyfile, "802-1x", "ca-cert", NULL); + g_assert(kval); + g_assert_cmpstr(kval, ==, value); + } else if (expected_scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + GBytes * blob = nm_setting_802_1x_get_ca_cert_blob(s_8021x); + gs_free char *file_blob = NULL; + + if (val_len == -1) { + gsize l; + gboolean success; + + success = g_file_get_contents(value, &file_blob, &l, NULL); + g_assert(success); + + value = file_blob; + val_len = l; + } + + g_assert(blob); + g_assert(nm_utils_gbytes_equal_mem(blob, value, val_len)); + + kval = g_key_file_get_string(keyfile, "802-1x", "ca-cert", NULL); + g_assert(kval); + g_assert(g_str_has_prefix(kval, NM_KEYFILE_CERT_SCHEME_PREFIX_BLOB)); + } + + g_key_file_unref(keyfile); +} + +static void +_test_8021x_cert_check_blob_full(NMConnection *con, const void *data, gsize len) +{ + GBytes * bytes; + NMSetting8021x *s_8021x = nm_connection_get_setting_802_1x(con); + + bytes = g_bytes_new(data, len); + g_object_set(s_8021x, NM_SETTING_802_1X_CA_CERT, bytes, NULL); + _test_8021x_cert_check(con, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + g_bytes_get_data(bytes, NULL), + g_bytes_get_size(bytes)); + g_bytes_unref(bytes); +} +#define _test_8021x_cert_check_blob(con, data) \ + _test_8021x_cert_check_blob_full(con, data, NM_STRLEN(data)) + +static void +_test_8021x_cert_from_files(const char *cert, const char *key) +{ + NMSetting8021x *s_8021x; + gs_unref_object NMConnection *con = + nmtst_create_minimal_connection("test-cert", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + GError * error = NULL; + gboolean success; + NMSetting8021xCKScheme scheme = NM_SETTING_802_1X_CK_SCHEME_PATH; + gs_free char *full_TEST_WIRED_TLS_CA_CERT = nmtst_file_resolve_relative_path(cert, NULL); + gs_free char *full_TEST_WIRED_TLS_PRIVKEY = nmtst_file_resolve_relative_path(key, NULL); + + /* test writing/reading of certificates of NMSetting8021x */ + + /* create a valid connection with NMSetting8021x */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_setting_802_1x_add_eap_method(s_8021x, "tls"); + g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL); + success = + nm_setting_802_1x_set_ca_cert(s_8021x, full_TEST_WIRED_TLS_CA_CERT, scheme, NULL, &error); + g_assert_no_error(error); + g_assert(success); + success = nm_setting_802_1x_set_client_cert(s_8021x, + full_TEST_WIRED_TLS_CA_CERT, + scheme, + NULL, + &error); + g_assert_no_error(error); + g_assert(success); + success = nm_setting_802_1x_set_private_key(s_8021x, + full_TEST_WIRED_TLS_PRIVKEY, + "test1", + scheme, + NULL, + &error); + g_assert_no_error(error); + g_assert(success); + + /* test resetting ca-cert to different values and see whether we can write/read. */ + + nm_connection_add_setting(con, NM_SETTING(s_8021x)); + nmtst_assert_connection_verifies_and_normalizable(con); + nmtst_connection_normalize(con); + + _test_8021x_cert_check(con, scheme, full_TEST_WIRED_TLS_CA_CERT, -1); + + scheme = NM_SETTING_802_1X_CK_SCHEME_BLOB; + success = + nm_setting_802_1x_set_ca_cert(s_8021x, full_TEST_WIRED_TLS_CA_CERT, scheme, NULL, &error); + g_assert_no_error(error); + g_assert(success); + _test_8021x_cert_check(con, scheme, full_TEST_WIRED_TLS_CA_CERT, -1); + + _test_8021x_cert_check_blob(con, "a"); + _test_8021x_cert_check_blob(con, "\0"); + _test_8021x_cert_check_blob(con, "10"); + _test_8021x_cert_check_blob(con, "data:;base64,a"); + _test_8021x_cert_check_blob_full(con, "data:;base64,a", NM_STRLEN("data:;base64,a") + 1); + _test_8021x_cert_check_blob(con, "data:;base64,file://a"); + _test_8021x_cert_check_blob(con, "123"); +} + +static void +test_8021x_cert(void) +{ + _test_8021x_cert_from_files(TEST_WIRED_TLS_CA_CERT, TEST_WIRED_TLS_PRIVKEY); +} + +static void +test_8021x_cert_tpm2key(void) +{ + _test_8021x_cert_from_files(TEST_WIRED_TLS_CA_CERT, TEST_WIRED_TLS_TPM2KEY); +} + +/*****************************************************************************/ + +static void +test_8021x_cert_read(void) +{ + GKeyFile * keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSetting8021x * s_8021x; + + con = nmtst_create_connection_from_keyfile("[connection]\n" + "type=ethernet", + "/test_8021x_cert_read/test0"); + CLEAR(&con, &keyfile); + + keyfile = _keyfile_load_from_data("[connection]\n" + "type=ethernet"); + _keyfile_convert(&con, &keyfile, "/test_8021x_cert_read/test1", NULL, NULL, NULL, NULL, TRUE); + CLEAR(&con, &keyfile); + + keyfile = _keyfile_load_from_data( + "[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + "ca-cert=48;130;2;52;48;130;1;161;2;16;2;173;102;126;78;69;254;94;87;111;60;152;25;94;221;" + "192;48;13;6;9;42;134;72;134;247;13;1;1;2;5;0;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;" + "48;30;6;3;85;4;10;19;23;82;83;65;32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;" + "73;110;99;46;49;46;48;44;6;3;85;4;11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;" + "114;32;67;101;114;116;105;102;105;99;97;116;105;111;110;32;65;117;116;104;111;114;105;116;" + "121;48;30;23;13;57;52;49;49;48;57;48;48;48;48;48;48;90;23;13;49;48;48;49;48;55;50;51;53;" + "57;53;57;90;48;95;49;11;48;9;6;3;85;4;6;19;2;85;83;49;32;48;30;6;3;85;4;10;19;23;82;83;65;" + "32;68;97;116;97;32;83;101;99;117;114;105;116;121;44;32;73;110;99;46;49;46;48;44;6;3;85;4;" + "11;19;37;83;101;99;117;114;101;32;83;101;114;118;101;114;32;67;101;114;116;105;102;105;99;" + "97;116;105;111;110;32;65;117;116;104;111;114;105;116;121;48;129;155;48;13;6;9;42;134;72;" + "134;247;13;1;1;1;5;0;3;129;137;0;48;129;133;2;126;0;146;206;122;193;174;131;62;90;170;137;" + "131;87;172;37;1;118;12;173;174;142;44;55;206;235;53;120;100;84;3;229;132;64;81;201;191;" + "143;8;226;138;130;8;210;22;134;55;85;233;177;33;2;173;118;104;129;154;5;162;75;201;75;37;" + "102;34;86;108;136;7;143;247;129;89;109;132;7;101;112;19;113;118;62;155;119;76;227;80;137;" + "86;152;72;185;29;167;41;26;19;46;74;17;89;156;30;21;213;73;84;44;115;58;105;130;177;151;" + "57;156;109;112;103;72;229;221;45;214;200;30;123;2;3;1;0;1;48;13;6;9;42;134;72;134;247;13;" + "1;1;2;5;0;3;126;0;101;221;126;225;178;236;176;226;58;224;236;113;70;154;25;17;184;211;199;" + "160;180;3;64;38;2;62;9;156;225;18;179;209;90;246;55;165;183;97;3;182;91;22;105;59;198;68;" + "8;12;136;83;12;107;151;73;199;62;53;220;108;185;187;170;223;92;187;58;47;147;96;182;169;" + "75;77;242;32;247;205;95;127;100;123;142;220;0;92;215;250;119;202;57;22;89;111;14;234;211;" + "181;131;127;77;77;66;86;118;180;201;95;4;248;56;248;235;210;95;117;95;205;123;252;229;142;" + "128;124;252;80;\n" + "client-cert=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;" + "107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;" + "109;0;\n" + "private-key=102;105;108;101;58;47;47;47;104;111;109;101;47;100;99;98;119;47;68;101;115;" + "107;116;111;112;47;99;101;114;116;105;110;102;114;97;47;99;108;105;101;110;116;46;112;101;" + "109;0;\n" + "private-key-password=12345testing\n"); + _keyfile_convert(&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, TRUE); + CLEAR(&con, &keyfile); + + keyfile = + _keyfile_load_from_data("[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + /* unqualified strings are only recognized as path up to 500 chars*/ + "ca-cert=" + "/11111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111" + "/11111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111" + "/11111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111" + "/11111111111111111111111111111111111111111111111111111111111111111" + "1111111111111111111111111111111111" + "/11111111111111111111111111111111111111111111111111111111111111111" + "111111111111111111111111111111111\n" + "client-cert=/" + "222222222222222222222222222222222222222222222222222222222222222222" + "222222222222222222222222222222221" + "/22222222222222222222222222222222222222222222222222222222222222222" + "2222222222222222222222222222222221" + "/22222222222222222222222222222222222222222222222222222222222222222" + "2222222222222222222222222222222221" + "/22222222222222222222222222222222222222222222222222222222222222222" + "2222222222222222222222222222222221" + "/22222222222222222222222222222222222222222222222222222222222222222" + "2222222222222222222222222222222222\n" + "private-key=file://" + "/33333333333333333333333333333333333333333333333333333333333333333" + "3333333333333333333333333333333331" + "/33333333333333333333333333333333333333333333333333333333333333333" + "3333333333333333333333333333333331" + "/33333333333333333333333333333333333333333333333333333333333333333" + "3333333333333333333333333333333331" + "/33333333333333333333333333333333333333333333333333333333333333333" + "3333333333333333333333333333333331" + "/33333333333333333333333333333333333333333333333333333333333333333" + "333333333333333333333333333333333111111\n" + "private-key-password=12345testing\n"); + _keyfile_convert(&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, TRUE); + s_8021x = nm_connection_get_setting_802_1x(con); + + g_assert(nm_setting_802_1x_get_ca_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert(g_str_has_prefix(nm_setting_802_1x_get_ca_cert_path(s_8021x), "/111111111111")); + g_assert_cmpint(strlen(nm_setting_802_1x_get_ca_cert_path(s_8021x)), ==, 499); + + g_assert(nm_setting_802_1x_get_client_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + g_assert( + g_str_has_prefix(g_bytes_get_data(nm_setting_802_1x_get_client_cert_blob(s_8021x), NULL), + "/2222222222")); + g_assert_cmpint(g_bytes_get_size(nm_setting_802_1x_get_client_cert_blob(s_8021x)), + ==, + 500 + 1 /* keyfile reader adds a trailing NUL */); + + g_assert(nm_setting_802_1x_get_private_key_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert(g_str_has_prefix(nm_setting_802_1x_get_private_key_path(s_8021x), "/333333333")); + g_assert_cmpint(strlen(nm_setting_802_1x_get_private_key_path(s_8021x)), ==, 505); + CLEAR(&con, &keyfile); + + keyfile = _keyfile_load_from_data("[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + "ca-cert=/\n" + "client-cert=a.pem\n" + "private-key=data:;base64,aGFsbG8=\n" // hallo + "private-key-password=12345testing\n"); + _keyfile_convert(&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, TRUE); + s_8021x = nm_connection_get_setting_802_1x(con); + + g_assert(nm_setting_802_1x_get_ca_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr(nm_setting_802_1x_get_ca_cert_path(s_8021x), ==, "/"); + + g_assert(nm_setting_802_1x_get_client_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr(nm_setting_802_1x_get_client_cert_path(s_8021x), + ==, + "/test_8021x_cert_read/a.pem"); + + g_assert(nm_setting_802_1x_get_private_key_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes(nm_setting_802_1x_get_private_key_blob(s_8021x), "hallo", -1); + CLEAR(&con, &keyfile); + + keyfile = _keyfile_load_from_data("[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + "ca-cert=file://data:;base64,x\n" + "client-cert=abc.der\n" + "private-key=abc.deR\n" + "private-key-password=12345testing\n"); + _keyfile_convert(&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, TRUE); + s_8021x = nm_connection_get_setting_802_1x(con); + + g_assert(nm_setting_802_1x_get_ca_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr(nm_setting_802_1x_get_ca_cert_path(s_8021x), ==, "data:;base64,x"); + + g_assert(nm_setting_802_1x_get_client_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_PATH); + g_assert_cmpstr(nm_setting_802_1x_get_client_cert_path(s_8021x), + ==, + "/test_8021x_cert_read/abc.der"); + + g_assert(nm_setting_802_1x_get_private_key_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes(nm_setting_802_1x_get_private_key_blob(s_8021x), "abc.deR\0", 8); + CLEAR(&con, &keyfile); + + keyfile = + _keyfile_load_from_data("[connection]\n" + "type=802-3-ethernet\n" + + "[802-1x]\n" + "eap=tls;\n" + "identity=Bill Smith\n" + "ca-cert=104;97;108;108;111;\n" /* "hallo" without trailing NUL */ + "client-cert=104;097;108;108;111;0;\n" + "private-key=hallo\n" + "private-key-password=12345testing\n"); + _keyfile_convert(&con, &keyfile, "/test_8021x_cert_read/test2", NULL, NULL, NULL, NULL, TRUE); + s_8021x = nm_connection_get_setting_802_1x(con); + + g_assert(nm_setting_802_1x_get_ca_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes(nm_setting_802_1x_get_ca_cert_blob(s_8021x), "hallo", 5); + + g_assert(nm_setting_802_1x_get_client_cert_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes(nm_setting_802_1x_get_client_cert_blob(s_8021x), "hallo\0", 6); + + g_assert(nm_setting_802_1x_get_private_key_scheme(s_8021x) == NM_SETTING_802_1X_CK_SCHEME_BLOB); + _assert_gbytes(nm_setting_802_1x_get_private_key_blob(s_8021x), "hallo\0", 6); + CLEAR(&con, &keyfile); +} + +static void +test_team_conf_read_valid(void) +{ + GKeyFile * keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSettingTeam * s_team; + + con = nmtst_create_connection_from_keyfile("[connection]\n" + "type=team\n" + "interface-name=nm-team1\n" + "[team]\n" + "config={\"foo\":\"bar\"}", + "/test_team_conf_read/valid"); + + g_assert(con); + s_team = nm_connection_get_setting_team(con); + g_assert(s_team); + g_assert_cmpstr(nm_setting_team_get_config(s_team), ==, "{\"foo\":\"bar\"}"); + + CLEAR(&con, &keyfile); +} + +static void +test_team_conf_read_invalid(void) +{ + GKeyFile * keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSettingTeam * s_team; + + if (!nm_json_vt()) { + g_test_skip("team test requires JSON validation"); + return; + } + + con = nmtst_create_connection_from_keyfile("[connection]\n" + "type=team\n" + "interface-name=nm-team1\n" + "[team]\n" + "config={foobar}", + "/test_team_conf_read/invalid"); + + g_assert(con); + s_team = nm_connection_get_setting_team(con); + g_assert(s_team); + g_assert(nm_setting_team_get_config(s_team) == NULL); + + CLEAR(&con, &keyfile); +} + +/*****************************************************************************/ + +static void +test_user_1(void) +{ + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSettingUser * s_user; + + con = nmtst_create_connection_from_keyfile("[connection]\n" + "id=t\n" + "type=ethernet\n" + "\n" + "[user]\n" + "my-value.x=value1\n" + "", + "/test_user_1/invalid"); + g_assert(con); + s_user = NM_SETTING_USER(nm_connection_get_setting(con, NM_TYPE_SETTING_USER)); + g_assert(s_user); + g_assert_cmpstr(nm_setting_user_get_data(s_user, "my-value.x"), ==, "value1"); + + CLEAR(&con, &keyfile); + + con = nmtst_create_minimal_connection("user-2", + "8b85fb8d-3070-48ba-93d9-53eee231d9a2", + NM_SETTING_WIRED_SETTING_NAME, + NULL); + s_user = NM_SETTING_USER(nm_setting_user_new()); + +#define _USER_SET_DATA(s_user, key, val) \ + G_STMT_START \ + { \ + GError * _error = NULL; \ + gboolean _success; \ + \ + _success = nm_setting_user_set_data((s_user), (key), (val), &_error); \ + nmtst_assert_success(_success, _error); \ + } \ + G_STMT_END + +#define _USER_SET_DATA_X(s_user, key) _USER_SET_DATA(s_user, key, "val=" key "") + + _USER_SET_DATA(s_user, "my.val1", ""); + _USER_SET_DATA_X(s_user, "my.val2"); + _USER_SET_DATA_X(s_user, "my.v__al3"); + _USER_SET_DATA_X(s_user, "my._v"); + _USER_SET_DATA_X(s_user, "my.v+"); + _USER_SET_DATA_X(s_user, "my.Av"); + _USER_SET_DATA_X(s_user, "MY.AV"); + _USER_SET_DATA_X(s_user, "MY.8V"); + _USER_SET_DATA_X(s_user, "MY.8-V"); + _USER_SET_DATA_X(s_user, "MY.8_V"); + _USER_SET_DATA_X(s_user, "MY.8+V"); + _USER_SET_DATA_X(s_user, "MY.8/V"); + _USER_SET_DATA_X(s_user, "MY.8=V"); + _USER_SET_DATA_X(s_user, "MY.-"); + _USER_SET_DATA_X(s_user, "MY._"); + _USER_SET_DATA_X(s_user, "MY.+"); + _USER_SET_DATA_X(s_user, "MY./"); + _USER_SET_DATA_X(s_user, "MY.="); + _USER_SET_DATA_X(s_user, "my.keys.1"); + _USER_SET_DATA_X(s_user, "my.other.KEY.42"); + + nm_connection_add_setting(con, NM_SETTING(s_user)); + nmtst_connection_normalize(con); + + _keyfile_convert(&con, &keyfile, "/test_user_1/foo", NULL, NULL, NULL, NULL, FALSE); +} + +/*****************************************************************************/ + +static void +test_vpn_1(void) +{ + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSettingVpn * s_vpn; + + con = nmtst_create_connection_from_keyfile("[connection]\n" + "id=t\n" + "type=vpn\n" + "\n" + "[vpn]\n" + "service-type=a.b.c\n" + "vpn-key-1=value1\n" + "", + "/test_vpn_1/invalid"); + g_assert(con); + s_vpn = NM_SETTING_VPN(nm_connection_get_setting(con, NM_TYPE_SETTING_VPN)); + g_assert(s_vpn); + g_assert_cmpstr(nm_setting_vpn_get_data_item(s_vpn, "vpn-key-1"), ==, "value1"); + + CLEAR(&con, &keyfile); +} + +/*****************************************************************************/ + +static void +test_bridge_vlans(void) +{ + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSettingBridge * s_bridge; + NMBridgeVlan * vlan; + guint16 vid, vid_end; + + con = nmtst_create_connection_from_keyfile("[connection]\n" + "id=t\n" + "type=bridge\n" + "interface-name=br4\n" + "\n" + "[bridge]\n" + "vlans=900 , 1 pvid untagged, 100-123 untagged\n" + "", + "/test_bridge_port/vlans"); + s_bridge = NM_SETTING_BRIDGE(nm_connection_get_setting(con, NM_TYPE_SETTING_BRIDGE)); + g_assert(s_bridge); + g_assert_cmpuint(nm_setting_bridge_get_num_vlans(s_bridge), ==, 3); + + vlan = nm_setting_bridge_get_vlan(s_bridge, 0); + g_assert(vlan); + nm_bridge_vlan_get_vid_range(vlan, &vid, &vid_end); + g_assert_cmpuint(vid, ==, 1); + g_assert_cmpuint(vid_end, ==, 1); + g_assert_cmpint(nm_bridge_vlan_is_pvid(vlan), ==, TRUE); + g_assert_cmpint(nm_bridge_vlan_is_untagged(vlan), ==, TRUE); + + vlan = nm_setting_bridge_get_vlan(s_bridge, 1); + g_assert(vlan); + nm_bridge_vlan_get_vid_range(vlan, &vid, &vid_end); + g_assert_cmpuint(vid, ==, 100); + g_assert_cmpuint(vid_end, ==, 123); + g_assert_cmpint(nm_bridge_vlan_is_pvid(vlan), ==, FALSE); + g_assert_cmpint(nm_bridge_vlan_is_untagged(vlan), ==, TRUE); + + vlan = nm_setting_bridge_get_vlan(s_bridge, 2); + g_assert(vlan); + nm_bridge_vlan_get_vid_range(vlan, &vid, &vid_end); + g_assert_cmpuint(vid, ==, 900); + g_assert_cmpuint(vid_end, ==, 900); + g_assert_cmpint(nm_bridge_vlan_is_pvid(vlan), ==, FALSE); + g_assert_cmpint(nm_bridge_vlan_is_untagged(vlan), ==, FALSE); + + CLEAR(&con, &keyfile); +} + +static void +test_bridge_port_vlans(void) +{ + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + gs_unref_object NMConnection *con = NULL; + NMSettingBridgePort * s_port; + NMBridgeVlan * vlan; + guint16 vid_start, vid_end; + + con = nmtst_create_connection_from_keyfile("[connection]\n" + "id=t\n" + "type=dummy\n" + "interface-name=dummy1\n" + "master=br0\n" + "slave-type=bridge\n" + "\n" + "[bridge-port]\n" + "vlans=4094 pvid , 10-20 untagged\n" + "", + "/test_bridge_port/vlans"); + s_port = NM_SETTING_BRIDGE_PORT(nm_connection_get_setting(con, NM_TYPE_SETTING_BRIDGE_PORT)); + g_assert(s_port); + g_assert_cmpuint(nm_setting_bridge_port_get_num_vlans(s_port), ==, 2); + + vlan = nm_setting_bridge_port_get_vlan(s_port, 0); + g_assert(vlan); + nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end); + g_assert_cmpuint(vid_start, ==, 10); + g_assert_cmpuint(vid_end, ==, 20); + g_assert_cmpint(nm_bridge_vlan_is_pvid(vlan), ==, FALSE); + g_assert_cmpint(nm_bridge_vlan_is_untagged(vlan), ==, TRUE); + + vlan = nm_setting_bridge_port_get_vlan(s_port, 1); + g_assert(vlan); + nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end); + g_assert_cmpuint(vid_start, ==, 4094); + g_assert_cmpuint(vid_end, ==, 4094); + g_assert_cmpint(nm_bridge_vlan_is_pvid(vlan), ==, TRUE); + g_assert_cmpint(nm_bridge_vlan_is_untagged(vlan), ==, FALSE); + + CLEAR(&con, &keyfile); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init(&argc, &argv, TRUE); + + g_test_add_func("/core/keyfile/encode_key", test_encode_key); + g_test_add_func("/core/keyfile/test_8021x_cert", test_8021x_cert); + g_test_add_func("/core/keyfile/test_8021x_cert_tpm2key", test_8021x_cert_tpm2key); + g_test_add_func("/core/keyfile/test_8021x_cert_read", test_8021x_cert_read); + g_test_add_func("/core/keyfile/test_team_conf_read/valid", test_team_conf_read_valid); + g_test_add_func("/core/keyfile/test_team_conf_read/invalid", test_team_conf_read_invalid); + g_test_add_func("/core/keyfile/test_user/1", test_user_1); + g_test_add_func("/core/keyfile/test_vpn/1", test_vpn_1); + g_test_add_func("/core/keyfile/bridge/vlans", test_bridge_vlans); + g_test_add_func("/core/keyfile/bridge-port/vlans", test_bridge_port_vlans); + + return g_test_run(); +} diff --git a/src/libnm-core-impl/tests/test-secrets.c b/src/libnm-core-impl/tests/test-secrets.c new file mode 100644 index 00000000..41dcc4b3 --- /dev/null +++ b/src/libnm-core-impl/tests/test-secrets.c @@ -0,0 +1,686 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-setting-8021x.h" +#include "nm-setting-cdma.h" +#include "nm-setting-connection.h" +#include "nm-setting-gsm.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ppp.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-vpn.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless-security.h" +#include "nm-setting-wireless.h" +#include "nm-simple-connection.h" +#include "nm-utils.h" +#include "libnm-core-intern/nm-core-internal.h" + +#include "libnm-glib-aux/nm-test-utils.h" + +#define TEST_CERT_DIR NM_BUILD_SRCDIR "/src/libnm-core-impl/tests/certs" +#define TEST_NEED_SECRETS_EAP_TLS_CA_CERT TEST_CERT_DIR "/test_ca_cert.pem" +#define TEST_NEED_SECRETS_EAP_TLS_CLIENT_CERT TEST_CERT_DIR "/test_key_and_cert.pem" +#define TEST_NEED_SECRETS_EAP_TLS_PRIVATE_KEY TEST_CERT_DIR "/test_key_and_cert.pem" + +static void +_assert_hints_has(GPtrArray *hints, const char *item) +{ + guint i; + guint found = 0; + + g_assert(hints); + g_assert(item); + for (i = 0; i < hints->len; i++) { + g_assert(hints->pdata[i]); + if (strcmp(hints->pdata[i], item) == 0) + found++; + } + g_assert_cmpint(found, ==, 1); +} + +static NMConnection * +make_tls_connection(const char *detail, NMSetting8021xCKScheme scheme) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSetting8021x * s_8021x; + NMSettingWired * s_wired; + NMSettingIP4Config * s_ip4; + char * uuid; + gboolean success; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + uuid = nm_utils_uuid_generate(); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Need TLS Secrets", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + g_free(uuid); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* Wireless security setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL); + + nm_setting_802_1x_add_eap_method(s_8021x, "tls"); + + success = nm_setting_802_1x_set_ca_cert(s_8021x, + TEST_NEED_SECRETS_EAP_TLS_CA_CERT, + scheme, + NULL, + &error); + nmtst_assert_success(success, error); + + success = nm_setting_802_1x_set_client_cert(s_8021x, + TEST_NEED_SECRETS_EAP_TLS_CLIENT_CERT, + scheme, + NULL, + &error); + nmtst_assert_success(success, error); + + success = nm_setting_802_1x_set_private_key(s_8021x, + TEST_NEED_SECRETS_EAP_TLS_PRIVATE_KEY, + "test", + scheme, + NULL, + &error); + nmtst_assert_success(success, error); + + success = nm_setting_set_secret_flags(NM_SETTING(s_8021x), + NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + &error); + nmtst_assert_success(success, error); + + /* IP4 setting */ + s_ip4 = (NMSettingIP4Config *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + nmtst_assert_connection_verifies_and_normalizable(connection); + + return connection; +} + +static void +test_need_tls_secrets_path(void) +{ + NMConnection *connection; + const char * setting_name; + GPtrArray * hints = NULL; + + connection = make_tls_connection("need-tls-secrets-path-key", NM_SETTING_802_1X_CK_SCHEME_PATH); + + /* Ensure we don't need any secrets since we just set up the connection */ + setting_name = nm_connection_need_secrets(connection, &hints); + g_assert(!setting_name); + g_assert(!hints); + + /* Connection is good; clear secrets and ensure private key password is then required */ + nm_connection_clear_secrets(connection); + + hints = NULL; + setting_name = nm_connection_need_secrets(connection, &hints); + g_assert_cmpstr(setting_name, ==, NM_SETTING_802_1X_SETTING_NAME); + _assert_hints_has(hints, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD); + + g_ptr_array_free(hints, TRUE); + g_object_unref(connection); +} + +static void +test_need_tls_secrets_blob(void) +{ + NMConnection *connection; + const char * setting_name; + GPtrArray * hints = NULL; + + connection = make_tls_connection("need-tls-secrets-blob-key", NM_SETTING_802_1X_CK_SCHEME_BLOB); + + /* Ensure we don't need any secrets since we just set up the connection */ + setting_name = nm_connection_need_secrets(connection, &hints); + g_assert(!setting_name); + g_assert(!hints); + + /* Clear secrets and ensure password is again required */ + nm_connection_clear_secrets(connection); + + hints = NULL; + setting_name = nm_connection_need_secrets(connection, &hints); + g_assert_cmpstr(setting_name, ==, NM_SETTING_802_1X_SETTING_NAME); + _assert_hints_has(hints, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD); + + g_ptr_array_free(hints, TRUE); + g_object_unref(connection); +} + +static NMConnection * +make_tls_phase2_connection(const char *detail, NMSetting8021xCKScheme scheme) +{ + NMConnection * connection; + NMSettingConnection *s_con; + NMSetting8021x * s_8021x; + NMSettingWired * s_wired; + NMSettingIP4Config * s_ip4; + char * uuid; + gboolean success; + GError * error = NULL; + + connection = nm_simple_connection_new(); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + uuid = nm_utils_uuid_generate(); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Need TLS Secrets", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + g_free(uuid); + + /* Wired setting */ + s_wired = (NMSettingWired *) nm_setting_wired_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wired)); + + /* Wireless security setting */ + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + nm_connection_add_setting(connection, NM_SETTING(s_8021x)); + + g_object_set(s_8021x, NM_SETTING_802_1X_ANONYMOUS_IDENTITY, "blahblah", NULL); + g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL); + + nm_setting_802_1x_add_eap_method(s_8021x, "ttls"); + g_object_set(s_8021x, NM_SETTING_802_1X_PHASE2_AUTH, "tls", NULL); + + success = nm_setting_802_1x_set_phase2_ca_cert(s_8021x, + TEST_NEED_SECRETS_EAP_TLS_CA_CERT, + scheme, + NULL, + &error); + nmtst_assert_success(success, error); + + success = nm_setting_802_1x_set_phase2_client_cert(s_8021x, + TEST_NEED_SECRETS_EAP_TLS_CLIENT_CERT, + scheme, + NULL, + &error); + nmtst_assert_success(success, error); + + success = nm_setting_802_1x_set_phase2_private_key(s_8021x, + TEST_NEED_SECRETS_EAP_TLS_PRIVATE_KEY, + "test", + scheme, + NULL, + &error); + nmtst_assert_success(success, error); + + success = nm_setting_set_secret_flags(NM_SETTING(s_8021x), + NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD, + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + &error); + nmtst_assert_success(success, error); + + /* IP4 setting */ + s_ip4 = (NMSettingIP4Config *) nm_setting_ip4_config_new(); + nm_connection_add_setting(connection, NM_SETTING(s_ip4)); + + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); + + nmtst_assert_connection_verifies_and_normalizable(connection); + return connection; +} + +static void +test_need_tls_phase2_secrets_path(void) +{ + NMConnection *connection; + const char * setting_name; + GPtrArray * hints = NULL; + + connection = make_tls_phase2_connection("need-tls-phase2-secrets-path-key", + NM_SETTING_802_1X_CK_SCHEME_PATH); + + /* Ensure we don't need any secrets since we just set up the connection */ + setting_name = nm_connection_need_secrets(connection, &hints); + g_assert(!setting_name); + g_assert(!hints); + + /* Connection is good; clear secrets and ensure private key password is then required */ + nm_connection_clear_secrets(connection); + + hints = NULL; + setting_name = nm_connection_need_secrets(connection, &hints); + g_assert_cmpstr(setting_name, ==, NM_SETTING_802_1X_SETTING_NAME); + _assert_hints_has(hints, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD); + + g_ptr_array_free(hints, TRUE); + g_object_unref(connection); +} + +static void +test_need_tls_phase2_secrets_blob(void) +{ + NMConnection *connection; + const char * setting_name; + GPtrArray * hints = NULL; + + connection = make_tls_phase2_connection("need-tls-phase2-secrets-blob-key", + NM_SETTING_802_1X_CK_SCHEME_BLOB); + + /* Ensure we don't need any secrets since we just set up the connection */ + setting_name = nm_connection_need_secrets(connection, &hints); + g_assert(!setting_name); + g_assert(!hints); + + /* Connection is good; clear secrets and ensure private key password is then required */ + nm_connection_clear_secrets(connection); + + hints = NULL; + setting_name = nm_connection_need_secrets(connection, &hints); + g_assert_cmpstr(setting_name, ==, NM_SETTING_802_1X_SETTING_NAME); + _assert_hints_has(hints, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY_PASSWORD); + + g_ptr_array_free(hints, TRUE); + g_object_unref(connection); +} + +static NMConnection * +wifi_connection_new(void) +{ + NMConnection * connection; + NMSettingConnection * s_con; + NMSettingWireless * s_wifi; + NMSettingWirelessSecurity *s_wsec; + unsigned char tmpssid[] = {0x31, 0x33, 0x33, 0x37}; + char * uuid; + GBytes * ssid; + + connection = nm_simple_connection_new(); + g_assert(connection); + + /* Connection setting */ + s_con = (NMSettingConnection *) nm_setting_connection_new(); + g_assert(s_con); + + uuid = nm_utils_uuid_generate(); + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test Wireless", + NM_SETTING_CONNECTION_UUID, + uuid, + NM_SETTING_CONNECTION_AUTOCONNECT, + FALSE, + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRELESS_SETTING_NAME, + NULL); + g_free(uuid); + nm_connection_add_setting(connection, NM_SETTING(s_con)); + + /* Wireless setting */ + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + g_assert(s_wifi); + + ssid = g_bytes_new(tmpssid, sizeof(tmpssid)); + g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NULL); + g_bytes_unref(ssid); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + + /* Wifi security */ + s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new(); + g_assert(s_wsec); + + g_object_set(G_OBJECT(s_wsec), NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL); + nm_connection_add_setting(connection, NM_SETTING(s_wsec)); + + return connection; +} + +static GVariant * +build_wep_secrets(const char *wepkey) +{ + GVariantBuilder builder; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(&builder, + "{sv}", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + g_variant_new_string(wepkey)); + g_variant_builder_add(&builder, + "{sv}", + NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, + g_variant_new_uint32(NM_WEP_KEY_TYPE_KEY)); + + return g_variant_builder_end(&builder); +} + +static void +test_update_secrets_wifi_single_setting(void) +{ + NMConnection * connection; + NMSettingWirelessSecurity *s_wsec; + GVariant * secrets; + GError * error = NULL; + gboolean success; + const char * wepkey = "11111111111111111111111111"; + const char * tmp; + + /* Test update with a hashed setting of 802-11-wireless secrets */ + + connection = wifi_connection_new(); + + secrets = build_wep_secrets(wepkey); + success = nm_connection_update_secrets(connection, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + secrets, + &error); + g_assert_no_error(error); + g_assert(success); + + g_variant_unref(secrets); + + /* Make sure the secret is now in the connection */ + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + tmp = nm_setting_wireless_security_get_wep_key(s_wsec, 0); + g_assert_cmpstr(tmp, ==, wepkey); + + g_object_unref(connection); +} + +static void +test_update_secrets_wifi_full_hash(void) +{ + NMConnection * connection; + NMSettingWirelessSecurity *s_wsec; + GVariantBuilder builder; + GVariant * all; + GError * error = NULL; + gboolean success; + const char * wepkey = "11111111111111111111111111"; + const char * tmp; + + /* Test update with a hashed connection containing only 802-11-wireless + * setting and secrets. + */ + + connection = wifi_connection_new(); + + g_variant_builder_init(&builder, NM_VARIANT_TYPE_CONNECTION); + g_variant_builder_add(&builder, + "{s@a{sv}}", + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + build_wep_secrets(wepkey)); + all = g_variant_builder_end(&builder); + + success = nm_connection_update_secrets(connection, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + all, + &error); + g_assert_no_error(error); + g_assert(success); + + g_variant_unref(all); + + /* Make sure the secret is now in the connection */ + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + tmp = nm_setting_wireless_security_get_wep_key(s_wsec, 0); + g_assert_cmpstr(tmp, ==, wepkey); + + g_object_unref(connection); +} + +static void +test_update_secrets_wifi_bad_setting_name(void) +{ + NMConnection *connection; + GVariant * secrets; + GError * error = NULL; + gboolean success; + const char * wepkey = "11111111111111111111111111"; + + /* Test that passing an invalid setting name to + * nm_connection_update_secrets() fails with the correct error. + */ + + connection = wifi_connection_new(); + + secrets = build_wep_secrets(wepkey); + + success = nm_connection_update_secrets(connection, "asdfasdfasdfasf", secrets, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_SETTING_NOT_FOUND); + g_assert(success == FALSE); + + g_clear_error(&error); + g_variant_unref(secrets); + g_object_unref(connection); +} + +static void +test_update_secrets_whole_connection(void) +{ + NMConnection * connection; + NMSettingWirelessSecurity *s_wsec; + GVariant * secrets; + GError * error = NULL; + gboolean success; + const char * wepkey = "11111111111111111111111111"; + + /* Test calling nm_connection_update_secrets() with an entire hashed + * connection including non-secrets. + */ + + connection = wifi_connection_new(); + + /* Build up the secrets dictionary */ + secrets = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); + NMTST_VARIANT_EDITOR(secrets, + NMTST_VARIANT_ADD_PROPERTY(NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, + "s", + wepkey);); + + success = nm_connection_update_secrets(connection, NULL, secrets, &error); + g_assert_no_error(error); + g_assert(success == TRUE); + + g_variant_unref(secrets); + + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec); + g_assert_cmpstr(nm_setting_wireless_security_get_wep_key(s_wsec, 0), ==, wepkey); + + g_object_unref(connection); +} + +static void +test_update_secrets_whole_connection_empty_hash(void) +{ + gs_unref_object NMConnection *connection = NULL; + GVariant * secrets; + GError * error = NULL; + gboolean success; + + /* Test that updating secrets with an empty connection hash returns success */ + + connection = wifi_connection_new(); + secrets = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0); + success = nm_connection_update_secrets(connection, NULL, secrets, &error); + nmtst_assert_success(success, error); + g_variant_unref(secrets); + + g_clear_object(&connection); + + connection = wifi_connection_new(); + secrets = nm_g_variant_singleton_aLsaLsvII(); + success = nm_connection_update_secrets(connection, NULL, secrets, &error); + nmtst_assert_success(success, error); +} + +static void +test_update_secrets_whole_connection_bad_setting(void) +{ + NMConnection * connection; + NMSettingWirelessSecurity *s_wsec; + GVariant * secrets, *copy, *setting_hash; + const char * setting_name; + GVariantBuilder conn_builder; + GVariantIter conn_iter; + GError * error = NULL; + gboolean success; + const char * wepkey = "11111111111111111111111111"; + + /* Test that sending a hashed connection containing an invalid setting + * name fails with the right error. + */ + + connection = wifi_connection_new(); + s_wsec = nm_connection_get_setting_wireless_security(connection); + g_assert(s_wsec != NULL); + g_object_set(G_OBJECT(s_wsec), NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, wepkey, NULL); + + /* Build up the secrets hash */ + secrets = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_ALL); + + /* Copy the dict, renaming the wireless-security setting in the process + * (so we ensure libnm is returning the right error when it finds an entry + * in the connection hash that doesn't match any setting in the connection). + */ + g_variant_builder_init(&conn_builder, NM_VARIANT_TYPE_CONNECTION); + g_variant_iter_init(&conn_iter, secrets); + while (g_variant_iter_next(&conn_iter, "{&s@a{sv}}", &setting_name, &setting_hash)) { + if (strcmp(setting_name, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME) == 0) + setting_name = "asdfasdfasdfasdf"; + + g_variant_builder_add(&conn_builder, "{s@a{sv}}", setting_name, setting_hash); + g_variant_unref(setting_hash); + } + copy = g_variant_builder_end(&conn_builder); + g_variant_unref(secrets); + + success = nm_connection_update_secrets(connection, NULL, copy, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_SETTING_NOT_FOUND); + g_assert(success == FALSE); + + g_clear_error(&error); + g_variant_unref(copy); + g_object_unref(connection); +} + +static void +test_update_secrets_whole_connection_empty_base_setting(void) +{ + NMConnection *connection; + GVariant * secrets, *setting; + GError * error = NULL; + gboolean success; + + /* Test that a hashed connection which does not have any hashed secrets + * for the requested setting returns success. + */ + + connection = wifi_connection_new(); + secrets = nm_connection_to_dbus(connection, NM_CONNECTION_SERIALIZE_WITH_SECRETS); + g_assert_cmpint(g_variant_n_children(secrets), ==, 3); + + setting = g_variant_lookup_value(secrets, NM_SETTING_WIRELESS_SETTING_NAME, NULL); + g_assert(setting != NULL); + g_variant_unref(setting); + + success = nm_connection_update_secrets(connection, + NM_SETTING_WIRELESS_SECURITY_SETTING_NAME, + secrets, + &error); + g_assert_no_error(error); + g_assert(success); + + g_variant_unref(secrets); + g_object_unref(connection); +} + +static void +test_update_secrets_null_setting_name_with_setting_hash(void) +{ + NMConnection *connection; + GVariant * secrets; + GError * error = NULL; + gboolean success; + const char * wepkey = "11111111111111111111111111"; + + /* Ensure that a NULL setting name and only a hashed setting fails */ + + connection = wifi_connection_new(); + + secrets = build_wep_secrets(wepkey); + + NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(setting_name || full_connection)); + success = nm_connection_update_secrets(connection, NULL, secrets, &error); + g_test_assert_expected_messages(); + g_assert_no_error(error); + g_assert(!success); + + g_variant_unref(secrets); + g_object_unref(connection); +} + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init(&argc, &argv, TRUE); + + /* The tests */ + g_test_add_func("/libnm/need_tls_secrets_path", test_need_tls_secrets_path); + g_test_add_func("/libnm/need_tls_secrets_blob", test_need_tls_secrets_blob); + g_test_add_func("/libnm/need_tls_phase2_secrets_path", test_need_tls_phase2_secrets_path); + g_test_add_func("/libnm/need_tls_phase2_secrets_blob", test_need_tls_phase2_secrets_blob); + + g_test_add_func("/libnm/update_secrets_wifi_single_setting", + test_update_secrets_wifi_single_setting); + g_test_add_func("/libnm/update_secrets_wifi_full_hash", test_update_secrets_wifi_full_hash); + g_test_add_func("/libnm/update_secrets_wifi_bad_setting_name", + test_update_secrets_wifi_bad_setting_name); + + g_test_add_func("/libnm/update_secrets_whole_connection", test_update_secrets_whole_connection); + g_test_add_func("/libnm/update_secrets_whole_connection_empty_hash", + test_update_secrets_whole_connection_empty_hash); + g_test_add_func("/libnm/update_secrets_whole_connection_bad_setting", + test_update_secrets_whole_connection_bad_setting); + g_test_add_func("/libnm/update_secrets_whole_connection_empty_base_setting", + test_update_secrets_whole_connection_empty_base_setting); + g_test_add_func("/libnm/update_secrets_null_setting_name_with_setting_hash", + test_update_secrets_null_setting_name_with_setting_hash); + + return g_test_run(); +} diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c new file mode 100644 index 00000000..6b65e98d --- /dev/null +++ b/src/libnm-core-impl/tests/test-setting.c @@ -0,0 +1,4830 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2017 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include +#include + +#include "libnm-glib-aux/nm-uuid.h" +#include "libnm-glib-aux/nm-json-aux.h" +#include "libnm-base/nm-ethtool-utils-base.h" +#include "libnm-core-intern/nm-core-internal.h" +#include "nm-utils.h" +#include "nm-utils-private.h" +#include "libnm-core-intern/nm-core-internal.h" +#include "nm-setting-8021x.h" +#include "nm-setting-bond.h" +#include "nm-setting-dcb.h" +#include "nm-setting-ethtool.h" +#include "nm-setting-team.h" +#include "nm-setting-team-port.h" +#include "nm-setting-tc-config.h" +#include "nm-setting-dummy.h" +#include "nm-connection.h" +#include "nm-simple-connection.h" +#include "nm-setting-connection.h" +#include "nm-errors.h" +#include "libnm-core-intern/nm-keyfile-internal.h" + +#include "libnm-glib-aux/nm-test-utils.h" + +#define TEST_CERT_DIR NM_BUILD_SRCDIR "/src/libnm-core-impl/tests/certs" + +/*****************************************************************************/ + +/* converts @dict to a connection. In this case, @dict must be good, without warnings, so that + * NM_SETTING_PARSE_FLAGS_STRICT and NM_SETTING_PARSE_FLAGS_BEST_EFFORT yield the exact same results. */ +static NMConnection * +_connection_new_from_dbus_strict(GVariant *dict, gboolean normalize) +{ + gs_unref_object NMConnection *con_x_0 = NULL; + gs_unref_object NMConnection *con_x_s = NULL; + gs_unref_object NMConnection *con_x_e = NULL; + gs_unref_object NMConnection *con_n_0 = NULL; + gs_unref_object NMConnection *con_n_s = NULL; + gs_unref_object NMConnection *con_n_e = NULL; + gs_free_error GError *error = NULL; + guint i; + + g_assert(g_variant_is_of_type(dict, NM_VARIANT_TYPE_CONNECTION)); + + con_x_0 = _nm_simple_connection_new_from_dbus(dict, NM_SETTING_PARSE_FLAGS_NONE, &error); + nmtst_assert_success(NM_IS_CONNECTION(con_x_0), error); + + con_x_s = _nm_simple_connection_new_from_dbus(dict, NM_SETTING_PARSE_FLAGS_STRICT, &error); + nmtst_assert_success(NM_IS_CONNECTION(con_x_s), error); + + con_x_e = _nm_simple_connection_new_from_dbus(dict, NM_SETTING_PARSE_FLAGS_BEST_EFFORT, &error); + nmtst_assert_success(NM_IS_CONNECTION(con_x_e), error); + + con_n_0 = _nm_simple_connection_new_from_dbus(dict, NM_SETTING_PARSE_FLAGS_NORMALIZE, &error); + nmtst_assert_success(NM_IS_CONNECTION(con_n_0), error); + + con_n_s = _nm_simple_connection_new_from_dbus(dict, + NM_SETTING_PARSE_FLAGS_STRICT + | NM_SETTING_PARSE_FLAGS_NORMALIZE, + &error); + nmtst_assert_success(NM_IS_CONNECTION(con_n_s), error); + + con_n_e = _nm_simple_connection_new_from_dbus(dict, + NM_SETTING_PARSE_FLAGS_BEST_EFFORT + | NM_SETTING_PARSE_FLAGS_NORMALIZE, + &error); + nmtst_assert_success(NM_IS_CONNECTION(con_n_e), error); + + nmtst_assert_connection_verifies(con_x_0); + nmtst_assert_connection_verifies(con_x_e); + nmtst_assert_connection_verifies(con_x_s); + + nmtst_assert_connection_verifies_without_normalization(con_n_0); + nmtst_assert_connection_verifies_without_normalization(con_n_e); + nmtst_assert_connection_verifies_without_normalization(con_n_s); + + /* randomly compare some pairs that we created. They must all be equal, + * after accounting for normalization. */ + for (i = 0; i < 10; i++) { + NMConnection *cons[] = {con_x_0, con_x_s, con_x_e, con_n_0, con_n_s, con_n_e}; + guint idx_a = (nmtst_get_rand_uint32() % G_N_ELEMENTS(cons)); + guint idx_b = (nmtst_get_rand_uint32() % G_N_ELEMENTS(cons)); + gboolean normalize_a, normalize_b; + + if (idx_a <= 2 && idx_b <= 2) { + normalize_a = nmtst_get_rand_bool(); + normalize_b = normalize_a; + } else if (idx_a > 2 && idx_b > 2) { + normalize_a = nmtst_get_rand_bool(); + normalize_b = nmtst_get_rand_bool(); + } else { + normalize_a = (idx_a <= 2) ? TRUE : nmtst_get_rand_bool(); + normalize_b = (idx_b <= 2) ? TRUE : nmtst_get_rand_bool(); + } + nmtst_assert_connection_equals(cons[idx_a], normalize_a, cons[idx_b], normalize_b); + } + + return (normalize) ? g_steal_pointer(&con_x_0) : g_steal_pointer(&con_n_0); +} + +/*****************************************************************************/ + +static void +test_nm_meta_setting_types_by_priority(void) +{ + gs_unref_ptrarray GPtrArray *arr = NULL; + int i; + int j; + + G_STATIC_ASSERT_EXPR(_NM_META_SETTING_TYPE_NUM + == G_N_ELEMENTS(nm_meta_setting_types_by_priority)); + + G_STATIC_ASSERT_EXPR(_NM_META_SETTING_TYPE_NUM == 51); + + arr = g_ptr_array_new_with_free_func(g_object_unref); + + for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) { + const NMMetaSettingType meta_type = nm_meta_setting_types_by_priority[i]; + const NMMetaSettingInfo *setting_info; + NMSetting * setting; + + g_assert(_NM_INT_NOT_NEGATIVE(meta_type)); + g_assert(meta_type < _NM_META_SETTING_TYPE_NUM); + + setting_info = &nm_meta_setting_infos[meta_type]; + g_assert(setting_info); + _nm_assert_setting_info(setting_info, 0); + + for (j = 0; j < i; j++) + g_assert_cmpint(nm_meta_setting_types_by_priority[j], !=, meta_type); + + setting = g_object_new(setting_info->get_setting_gtype(), NULL); + g_assert(NM_IS_SETTING(setting)); + + g_ptr_array_add(arr, setting); + } + + for (i = 1; i < _NM_META_SETTING_TYPE_NUM; i++) { + NMSetting *setting = arr->pdata[i]; + + for (j = 0; j < i; j++) { + NMSetting *other = arr->pdata[j]; + + if (_nmtst_nm_setting_sort(other, setting) >= 0) { + g_error("sort order for nm_meta_setting_types_by_priority[%d vs %d] is wrong: %s " + "should be before %s", + j, + i, + nm_setting_get_name(setting), + nm_setting_get_name(other)); + } + } + } +} + +/*****************************************************************************/ + +static char * +_create_random_ipaddr(int addr_family, gboolean as_service) +{ + char delimiter = as_service ? ':' : '/'; + int num; + + if (addr_family == AF_UNSPEC) + addr_family = nmtst_rand_select(AF_INET, AF_INET6); + + g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + if (as_service) + num = (nmtst_get_rand_uint32() % 1000) + 30000; + else + num = addr_family == AF_INET ? 32 : 128; + + if (addr_family == AF_INET) + return g_strdup_printf("192.168.%u.%u%c%d", + nmtst_get_rand_uint32() % 256, + nmtst_get_rand_uint32() % 256, + delimiter, + num); + else + return g_strdup_printf("a:b:c::%02x:%02x%c%d", + nmtst_get_rand_uint32() % 256, + nmtst_get_rand_uint32() % 256, + delimiter, + num); +} + +/*****************************************************************************/ + +static void +compare_blob_data(const char *test, const char *key_path, GBytes *key) +{ + gs_free char *contents = NULL; + gsize len = 0; + GError * error = NULL; + gboolean success; + + g_assert(key && g_bytes_get_size(key) > 0); + + success = g_file_get_contents(key_path, &contents, &len, &error); + nmtst_assert_success(success, error); + + g_assert_cmpmem(contents, len, g_bytes_get_data(key, NULL), g_bytes_get_size(key)); +} + +static void +check_scheme_path(GBytes *value, const char *path) +{ + const guint8 *p; + gsize l; + + g_assert(value); + + p = g_bytes_get_data(value, &l); + g_assert_cmpint(l, ==, strlen(path) + NM_STRLEN(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH) + 1); + g_assert(memcmp(p, + NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH, + strlen(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH)) + == 0); + p += strlen(NM_SETTING_802_1X_CERT_SCHEME_PREFIX_PATH); + g_assert(memcmp(p, path, strlen(path)) == 0); + p += strlen(path); + g_assert(*p == '\0'); +} + +static void +test_private_key_import(const char *path, const char *password, NMSetting8021xCKScheme scheme) +{ + NMSetting8021x * s_8021x; + gboolean success; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + NMSetting8021xCKFormat tmp_fmt; + GError * error = NULL; + GBytes * tmp_key = NULL, *client_cert = NULL; + const char * pw; + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + g_assert(s_8021x); + + success = nm_setting_802_1x_set_private_key(s_8021x, path, password, scheme, &format, &error); + nmtst_assert_success(success, error); + g_assert(format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + tmp_fmt = nm_setting_802_1x_get_private_key_format(s_8021x); + g_assert(tmp_fmt == format); + + /* Make sure the password is what we expect */ + pw = nm_setting_802_1x_get_private_key_password(s_8021x); + g_assert(pw != NULL); + g_assert_cmpstr(pw, ==, password); + + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + tmp_key = nm_setting_802_1x_get_private_key_blob(s_8021x); + compare_blob_data("private-key-import", path, tmp_key); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + g_object_get(s_8021x, NM_SETTING_802_1X_PRIVATE_KEY, &tmp_key, NULL); + check_scheme_path(tmp_key, path); + g_bytes_unref(tmp_key); + } else + g_assert_not_reached(); + + /* If it's PKCS#12 ensure the client cert is the same value */ + if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + g_object_get(s_8021x, NM_SETTING_802_1X_PRIVATE_KEY, &tmp_key, NULL); + g_assert(tmp_key); + + g_object_get(s_8021x, NM_SETTING_802_1X_CLIENT_CERT, &client_cert, NULL); + g_assert(client_cert); + + /* make sure they are the same */ + g_assert(g_bytes_equal(tmp_key, client_cert)); + + g_bytes_unref(tmp_key); + g_bytes_unref(client_cert); + } + + g_object_unref(s_8021x); +} + +/*****************************************************************************/ + +static void +_do_test_connection_uuid(NMConnection *con, const char *uuid, const char *expected_uuid) +{ + NMSettingConnection *s_con; + gs_free char * uuid_old = NULL; + gboolean success; + gboolean is_normalized; + char uuid_normalized[37]; + + nmtst_assert_connection_verifies_without_normalization(con); + + s_con = NM_SETTING_CONNECTION(nm_connection_get_setting(con, NM_TYPE_SETTING_CONNECTION)); + g_assert(NM_IS_SETTING_CONNECTION(s_con)); + + g_assert(uuid); + + uuid_old = g_strdup(nm_setting_connection_get_uuid(s_con)); + + g_assert(nm_utils_is_uuid(uuid_old)); + + g_object_set(s_con, NM_SETTING_CONNECTION_UUID, uuid, NULL); + + g_assert_cmpstr(uuid, ==, nm_setting_connection_get_uuid(s_con)); + + if (nm_streq0(uuid, expected_uuid)) { + nmtst_assert_connection_verifies_without_normalization(con); + g_assert(nm_utils_is_uuid(uuid)); + g_assert(nm_uuid_is_valid(uuid)); + g_assert(nm_uuid_is_valid_nm(uuid, &is_normalized, NULL)); + g_assert(!is_normalized); + } else if (!expected_uuid) { + gs_free_error GError *error = NULL; + + success = nm_connection_verify(con, &error); + nmtst_assert_no_success(success, error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + + g_assert(!nm_utils_is_uuid(uuid)); + g_assert(!nm_uuid_is_valid(uuid)); + g_assert(!nm_uuid_is_valid_nmlegacy(uuid)); + g_assert(!nm_uuid_is_valid_nm(uuid, NULL, NULL)); + } else { + gs_free_error GError *error = NULL; + + nmtst_assert_connection_verifies_and_normalizable(con); + + success = nm_connection_verify(con, &error); + nmtst_assert_success(success, error); + + if (!nmtst_connection_normalize(con)) + g_assert_not_reached(); + + g_assert_cmpstr(expected_uuid, ==, nm_setting_connection_get_uuid(s_con)); + g_assert(nm_uuid_is_valid(expected_uuid)); + + g_assert(nm_utils_is_uuid(uuid)); + g_assert(nm_uuid_is_valid_nmlegacy(uuid)); + g_assert(nm_uuid_is_valid_nm(uuid, &is_normalized, uuid_normalized)); + + g_assert_cmpstr(expected_uuid, ==, uuid_normalized); + } + + g_object_set(s_con, NM_SETTING_CONNECTION_UUID, uuid_old, NULL); + nmtst_assert_connection_verifies_without_normalization(con); + + if (expected_uuid && !nm_streq(expected_uuid, uuid)) + _do_test_connection_uuid(con, expected_uuid, expected_uuid); +} + +static void +test_connection_uuid(void) +{ + gs_unref_object NMConnection *con = NULL; + + con = nmtst_create_minimal_connection("test-uuid", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL); + + nmtst_connection_normalize(con); + +#define _do_test_connection_uuid_bad(con, uuid) _do_test_connection_uuid((con), "" uuid "", NULL) + +#define _do_test_connection_uuid_good(con, uuid) \ + _do_test_connection_uuid((con), "" uuid "", "" uuid "") + +#define _do_test_connection_uuid_norm(con, uuid, expected_uuid) \ + _do_test_connection_uuid((con), "" uuid "", "" expected_uuid "") + + _do_test_connection_uuid_bad(con, "x1e775e3-a316-4eb2-b4d8-4b0f2bcaea53"); + _do_test_connection_uuid_bad(con, "1e775e3aa3164eb2b4d84b0f2bcaea53abcdabc"); + _do_test_connection_uuid_bad(con, "1e775e3aa3164eb2b4d84b0f2bcaea53abcdabcdd"); + + _do_test_connection_uuid_good(con, "a1e775e3-a316-4eb2-b4d8-4b0f2bcaea53"); + + _do_test_connection_uuid_norm(con, + "A1E775e3-a316-4eb2-b4d8-4b0f2bcaea53", + "a1e775e3-a316-4eb2-b4d8-4b0f2bcaea53"); + _do_test_connection_uuid_norm(con, + "A1E775E3-A316-4EB2-B4D8-4B0F2BCAEA53", + "a1e775e3-a316-4eb2-b4d8-4b0f2bcaea53"); + _do_test_connection_uuid_norm(con, + "-1e775e3aa316-4eb2-b4d8-4b0f2bcaea53", + "bdd73688-5c87-5454-917d-f5c3faed39c0"); + _do_test_connection_uuid_norm(con, + "----1e775e3aa3164eb2b4d84b0f2bcaea53", + "8a232814-c6cf-54c9-9384-71a60011d0b2"); + _do_test_connection_uuid_norm(con, + "1e775e3aa3164eb2b4d84b0f2bcaea53abcdabcd", + "ae35a4a8-4029-5770-9fa4-d79a672874c3"); + _do_test_connection_uuid_norm(con, + "1e775e3Aa3164eb2b4d84b0f2bcaea53abcdabcd", + "ae35a4a8-4029-5770-9fa4-d79a672874c3"); + _do_test_connection_uuid_norm(con, + "1E775E3AA3164EB2B4D84B0F2BCAEA53ABCDABCD", + "ae35a4a8-4029-5770-9fa4-d79a672874c3"); +} + +/*****************************************************************************/ + +static void +test_phase2_private_key_import(const char * path, + const char * password, + NMSetting8021xCKScheme scheme) +{ + NMSetting8021x * s_8021x; + gboolean success; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + NMSetting8021xCKFormat tmp_fmt; + GError * error = NULL; + GBytes * tmp_key = NULL, *client_cert = NULL; + const char * pw; + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + g_assert(s_8021x); + + success = + nm_setting_802_1x_set_phase2_private_key(s_8021x, path, password, scheme, &format, &error); + nmtst_assert_success(success, error); + g_assert(format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + tmp_fmt = nm_setting_802_1x_get_phase2_private_key_format(s_8021x); + g_assert(tmp_fmt == format); + + /* Make sure the password is what we expect */ + pw = nm_setting_802_1x_get_phase2_private_key_password(s_8021x); + g_assert(pw); + g_assert_cmpstr(pw, ==, password); + + if (scheme == NM_SETTING_802_1X_CK_SCHEME_BLOB) { + tmp_key = nm_setting_802_1x_get_phase2_private_key_blob(s_8021x); + compare_blob_data("phase2-private-key-import", path, tmp_key); + } else if (scheme == NM_SETTING_802_1X_CK_SCHEME_PATH) { + g_object_get(s_8021x, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, &tmp_key, NULL); + check_scheme_path(tmp_key, path); + g_bytes_unref(tmp_key); + } else + g_assert_not_reached(); + + /* If it's PKCS#12 ensure the client cert is the same value */ + if (format == NM_SETTING_802_1X_CK_FORMAT_PKCS12) { + g_object_get(s_8021x, NM_SETTING_802_1X_PHASE2_PRIVATE_KEY, &tmp_key, NULL); + g_assert(tmp_key); + + g_object_get(s_8021x, NM_SETTING_802_1X_PHASE2_CLIENT_CERT, &client_cert, NULL); + g_assert(client_cert); + + /* make sure they are the same */ + g_assert(g_bytes_equal(tmp_key, client_cert)); + + g_bytes_unref(tmp_key); + g_bytes_unref(client_cert); + } + + g_object_unref(s_8021x); +} + +static void +test_wrong_password_keeps_data(const char *path, const char *password) +{ + NMSetting8021x * s_8021x; + gboolean success; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + GError * error = NULL; + const char * pw; + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + g_assert(s_8021x); + + success = nm_setting_802_1x_set_private_key(s_8021x, + path, + password, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + &format, + &error); + nmtst_assert_success(success, error); + g_assert(format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + + /* Now try to set it to something that's not a certificate */ + format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + success = nm_setting_802_1x_set_private_key(s_8021x, + "Makefile.am", + password, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + &format, + &error); + nmtst_assert_no_success(success, error); + g_assert(format == NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + g_clear_error(&error); + + /* Make sure the password hasn't changed */ + pw = nm_setting_802_1x_get_private_key_password(s_8021x); + g_assert(pw); + g_assert_cmpstr(pw, ==, password); + + g_object_unref(s_8021x); +} + +static void +test_clear_private_key(const char *path, const char *password) +{ + NMSetting8021x * s_8021x; + gboolean success; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + GError * error = NULL; + const char * pw; + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + g_assert(s_8021x); + + success = nm_setting_802_1x_set_private_key(s_8021x, + path, + password, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + &format, + &error); + nmtst_assert_success(success, error); + g_assert(format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + + /* Make sure the password is what we expect */ + pw = nm_setting_802_1x_get_private_key_password(s_8021x); + g_assert(pw); + g_assert_cmpstr(pw, ==, password); + + /* Now clear it */ + success = nm_setting_802_1x_set_private_key(s_8021x, + NULL, + NULL, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + NULL, + &error); + nmtst_assert_success(success, error); + + /* Ensure the password is also now clear */ + g_assert(!nm_setting_802_1x_get_private_key_password(s_8021x)); + + g_object_unref(s_8021x); +} + +static void +test_wrong_phase2_password_keeps_data(const char *path, const char *password) +{ + NMSetting8021x * s_8021x; + gboolean success; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + GError * error = NULL; + const char * pw; + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + g_assert(s_8021x); + + success = nm_setting_802_1x_set_phase2_private_key(s_8021x, + path, + password, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + &format, + &error); + nmtst_assert_success(success, error); + g_assert(format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + + /* Now try to set it to something that's not a certificate */ + format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + success = nm_setting_802_1x_set_phase2_private_key(s_8021x, + "Makefile.am", + password, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + &format, + &error); + nmtst_assert_no_success(success, error); + g_assert(format == NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + g_clear_error(&error); + + /* Make sure the password hasn't changed */ + pw = nm_setting_802_1x_get_phase2_private_key_password(s_8021x); + g_assert(pw); + g_assert_cmpstr(pw, ==, password); + + g_object_unref(s_8021x); +} + +static void +test_clear_phase2_private_key(const char *path, const char *password) +{ + NMSetting8021x * s_8021x; + gboolean success; + NMSetting8021xCKFormat format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN; + GError * error = NULL; + const char * pw; + + s_8021x = (NMSetting8021x *) nm_setting_802_1x_new(); + g_assert(s_8021x); + + success = nm_setting_802_1x_set_phase2_private_key(s_8021x, + path, + password, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + &format, + &error); + nmtst_assert_success(success, error); + g_assert(format != NM_SETTING_802_1X_CK_FORMAT_UNKNOWN); + + /* Make sure the password is what we expect */ + pw = nm_setting_802_1x_get_phase2_private_key_password(s_8021x); + g_assert(pw); + g_assert_cmpstr(pw, ==, password); + + /* Now clear it */ + success = nm_setting_802_1x_set_phase2_private_key(s_8021x, + NULL, + NULL, + NM_SETTING_802_1X_CK_SCHEME_BLOB, + NULL, + &error); + nmtst_assert_success(success, error); + + /* Ensure the password is also now clear */ + g_assert(!nm_setting_802_1x_get_phase2_private_key_password(s_8021x)); + + g_object_unref(s_8021x); +} + +static void +test_8021x(gconstpointer test_data) +{ + char **parts, *path, *password; + + parts = g_strsplit((const char *) test_data, ", ", -1); + g_assert_cmpint(g_strv_length(parts), ==, 2); + + path = g_build_filename(TEST_CERT_DIR, parts[0], NULL); + password = parts[1]; + + /* Test phase1 and phase2 path scheme */ + test_private_key_import(path, password, NM_SETTING_802_1X_CK_SCHEME_PATH); + test_phase2_private_key_import(path, password, NM_SETTING_802_1X_CK_SCHEME_PATH); + + /* Test phase1 and phase2 blob scheme */ + test_private_key_import(path, password, NM_SETTING_802_1X_CK_SCHEME_BLOB); + test_phase2_private_key_import(path, password, NM_SETTING_802_1X_CK_SCHEME_BLOB); + + /* Test that using a wrong password does not change existing data */ + test_wrong_password_keeps_data(path, password); + test_wrong_phase2_password_keeps_data(path, password); + + /* Test clearing the private key */ + test_clear_private_key(path, password); + test_clear_phase2_private_key(path, password); + + g_free(path); + g_strfreev(parts); +} + +/*****************************************************************************/ + +static void +create_bond_connection(NMConnection **con, NMSettingBond **s_bond) +{ + NMSettingConnection *s_con; + + g_assert(con); + g_assert(s_bond); + + *con = nmtst_create_minimal_connection("bond", NULL, NM_SETTING_BOND_SETTING_NAME, &s_con); + + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "bond0", NULL); + + *s_bond = (NMSettingBond *) nm_setting_bond_new(); + g_assert(*s_bond); + + nm_connection_add_setting(*con, NM_SETTING(*s_bond)); +} + +#define test_verify_options(exp, ...) _test_verify_options(exp, NM_MAKE_STRV(__VA_ARGS__)) + +static void +_test_verify_options(gboolean expected_result, const char *const *options) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingBond * s_bond; + const char *const * option; + + g_assert(NM_PTRARRAY_LEN(options) % 2 == 0); + + create_bond_connection(&con, &s_bond); + + for (option = options; option[0]; option += 2) + g_assert(nm_setting_bond_add_option(s_bond, option[0], option[1])); + + if (expected_result) { + nmtst_assert_connection_verifies_and_normalizable(con); + } else { + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + } +} + +static void +test_bond_verify(void) +{ + test_verify_options(TRUE, "mode", "3", "arp_interval", "0"); + test_verify_options(FALSE, + /* arp_interval not supported in balance-alb mode */ + "mode", + "balance-alb", + "arp_interval", + "1", + "arp_ip_target", + "1.2.3.4"); + test_verify_options(FALSE, + /* arp_ip_target requires arp_interval */ + "mode", + "balance-rr", + "arp_ip_target", + "1.2.3.4"); + test_verify_options(TRUE, + "mode", + "balance-rr", + "arp_interval", + "1", + "arp_ip_target", + "1.2.3.4"); + test_verify_options(FALSE, + /* num_grat_arp, num_unsol_na cannot be different */ + "mode", + "balance-rr", + "num_grat_arp", + "3", + "num_unsol_na", + "4"); + test_verify_options(TRUE, "mode", "balance-rr", "num_grat_arp", "5", "num_unsol_na", "5"); + test_verify_options(TRUE, "mode", "active-backup", "primary", "eth0"); + test_verify_options(FALSE, + /* primary requires mode=active-backup */ + "mode", + "802.3ad", + "primary", + "eth0"); + test_verify_options(TRUE, "mode", "802.3ad", "lacp_rate", "fast"); + test_verify_options(FALSE, + /* lacp_rate=fast requires mode=802.3ad */ + "mode", + "balance-rr", + "lacp_rate", + "fast"); + test_verify_options(TRUE, "mode", "802.3ad", "ad_actor_system", "ae:00:11:33:44:55"); + test_verify_options(TRUE, "mode", "0", "miimon", "0", "updelay", "0", "downdelay", "0"); + test_verify_options(TRUE, "mode", "0", "downdelay", "0", "updelay", "0"); + test_verify_options(TRUE, + "mode", + "0", + "miimon", + "100", + "arp_ip_target", + "1.1.1.1", + "arp_interval", + "200"); + test_verify_options(TRUE, + "mode", + "0", + "downdelay", + "100", + "arp_ip_target", + "1.1.1.1", + "arp_interval", + "200"); +} + +static void +test_bond_compare_options(gboolean exp_res, const char **opts1, const char **opts2) +{ + gs_unref_object NMSettingBond *s_bond1 = NULL, *s_bond2 = NULL; + const char ** p; + + s_bond1 = (NMSettingBond *) nm_setting_bond_new(); + g_assert(s_bond1); + s_bond2 = (NMSettingBond *) nm_setting_bond_new(); + g_assert(s_bond2); + + for (p = opts1; p[0] && p[1]; p += 2) + g_assert(nm_setting_bond_add_option(s_bond1, p[0], p[1])); + + for (p = opts2; p[0] && p[1]; p += 2) + g_assert(nm_setting_bond_add_option(s_bond2, p[0], p[1])); + + g_assert_cmpint(nm_setting_compare((NMSetting *) s_bond1, + (NMSetting *) s_bond2, + NM_SETTING_COMPARE_FLAG_EXACT), + ==, + exp_res); +} + +static void +test_bond_compare(void) +{ + test_bond_compare_options(TRUE, + ((const char *[]){"mode", "balance-rr", "miimon", "1", NULL}), + ((const char *[]){"mode", "balance-rr", "miimon", "1", NULL})); + test_bond_compare_options(FALSE, + ((const char *[]){"mode", "balance-rr", "miimon", "1", NULL}), + ((const char *[]){"mode", "balance-rr", "miimon", "2", NULL})); + + test_bond_compare_options(FALSE, + ((const char *[]){"miimon", "1", NULL}), + ((const char *[]){"miimon", "1", "updelay", "0", NULL})); + + test_bond_compare_options(FALSE, + ((const char *[]){"num_grat_arp", "2", NULL}), + ((const char *[]){"num_grat_arp", "1", NULL})); + test_bond_compare_options(FALSE, + ((const char *[]){"num_grat_arp", "3", NULL}), + ((const char *[]){"num_unsol_na", "3", NULL})); + test_bond_compare_options(FALSE, + ((const char *[]){"num_grat_arp", "4", NULL}), + ((const char *[]){"num_unsol_na", "4", "num_grat_arp", "4", NULL})); + + test_bond_compare_options(FALSE, + ((const char *[]){"mode", "balance-rr", "miimon", "100", NULL}), + ((const char *[]){"mode", "balance-rr", NULL})); +} + +static void +test_bond_normalize_options(const char **opts1, const char **opts2) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingBond * s_bond; + GError * error = NULL; + gboolean success; + const char ** p; + int num = 0; + + create_bond_connection(&con, &s_bond); + + for (p = opts1; p[0] && p[1]; p += 2) + g_assert(nm_setting_bond_add_option(s_bond, p[0], p[1])); + + nmtst_assert_connection_verifies_and_normalizable(con); + nmtst_connection_normalize(con); + success = nm_setting_verify((NMSetting *) s_bond, con, &error); + nmtst_assert_success(success, error); + + for (p = opts2; p[0] && p[1]; p += 2) { + g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, p[0]), ==, p[1]); + num++; + } + + g_assert_cmpint(num, ==, nm_setting_bond_get_num_options(s_bond)); +} + +static void +test_bond_normalize(void) +{ + test_bond_normalize_options( + ((const char *[]){"mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL}), + ((const char *[]){"mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL})); + test_bond_normalize_options(((const char *[]){"mode", "1", "miimon", "1", NULL}), + ((const char *[]){"mode", "active-backup", "miimon", "1", NULL})); + test_bond_normalize_options( + ((const char *[]){"mode", "balance-alb", "tlb_dynamic_lb", "1", NULL}), + ((const char *[]){"mode", "balance-alb", "tlb_dynamic_lb", "1", NULL})); + test_bond_normalize_options( + ((const char *[]){"mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL}), + ((const char *[]){"mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL})); + test_bond_normalize_options( + ((const char + *[]){"mode", "balance-rr", "ad_actor_sys_prio", "4", "packets_per_slave", "3", NULL}), + ((const char *[]){"mode", "balance-rr", "packets_per_slave", "3", NULL})); +} + +/*****************************************************************************/ + +#define DCB_FLAGS_ALL \ + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING) + +static void +test_dcb_flags_valid(void) +{ + gs_unref_object NMSettingDcb *s_dcb = NULL; + GError * error = NULL; + gboolean success; + guint i; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_assert(s_dcb); + + g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, 0); + g_assert_cmpint(nm_setting_dcb_get_app_iscsi_flags(s_dcb), ==, 0); + g_assert_cmpint(nm_setting_dcb_get_app_fip_flags(s_dcb), ==, 0); + g_assert_cmpint(nm_setting_dcb_get_priority_flow_control_flags(s_dcb), ==, 0); + g_assert_cmpint(nm_setting_dcb_get_priority_group_flags(s_dcb), ==, 0); + + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_APP_FCOE_FLAGS, + DCB_FLAGS_ALL, + NM_SETTING_DCB_APP_ISCSI_FLAGS, + DCB_FLAGS_ALL, + NM_SETTING_DCB_APP_FIP_FLAGS, + DCB_FLAGS_ALL, + NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, + DCB_FLAGS_ALL, + NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, + DCB_FLAGS_ALL, + NULL); + /* Priority Group Bandwidth must total 100% */ + for (i = 0; i < 7; i++) + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, i, 12); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 7, 16); + + success = nm_setting_verify(NM_SETTING(s_dcb), NULL, &error); + g_assert_no_error(error); + g_assert(success); + + g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, DCB_FLAGS_ALL); + g_assert_cmpint(nm_setting_dcb_get_app_iscsi_flags(s_dcb), ==, DCB_FLAGS_ALL); + g_assert_cmpint(nm_setting_dcb_get_app_fip_flags(s_dcb), ==, DCB_FLAGS_ALL); + g_assert_cmpint(nm_setting_dcb_get_priority_flow_control_flags(s_dcb), ==, DCB_FLAGS_ALL); + g_assert_cmpint(nm_setting_dcb_get_priority_group_flags(s_dcb), ==, DCB_FLAGS_ALL); +} + +#define TEST_FLAG(p, f, v) \ + { \ + /* GObject property min/max should ensure the property does not get set to \ + * the invalid value, so we ensure the value we just tried to set is 0 and \ + * that verify is successful since the property never got set. \ + */ \ + g_object_set(G_OBJECT(s_dcb), p, v, NULL); \ + g_assert_cmpint(f(s_dcb), ==, 0); \ + success = nm_setting_verify(NM_SETTING(s_dcb), NULL, &error); \ + g_assert_no_error(error); \ + g_assert(success); \ + } + +static void +test_dcb_flags_invalid(void) +{ + gs_unref_object NMSettingDcb *s_dcb = NULL; + GError * error = NULL; + gboolean success; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_assert(s_dcb); + + NMTST_EXPECT("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*"); + TEST_FLAG(NM_SETTING_DCB_APP_FCOE_FLAGS, nm_setting_dcb_get_app_fcoe_flags, 0x332523); + g_test_assert_expected_messages(); + + NMTST_EXPECT("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*"); + TEST_FLAG(NM_SETTING_DCB_APP_ISCSI_FLAGS, nm_setting_dcb_get_app_iscsi_flags, 0xFF); + g_test_assert_expected_messages(); + + NMTST_EXPECT("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*"); + TEST_FLAG(NM_SETTING_DCB_APP_FIP_FLAGS, nm_setting_dcb_get_app_fip_flags, 0x1111); + g_test_assert_expected_messages(); + + NMTST_EXPECT("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*"); + TEST_FLAG(NM_SETTING_DCB_PRIORITY_FLOW_CONTROL_FLAGS, + nm_setting_dcb_get_priority_flow_control_flags, + G_MAXUINT32); + g_test_assert_expected_messages(); + + NMTST_EXPECT("GLib-GObject", G_LOG_LEVEL_WARNING, "*invalid or out of range*"); + TEST_FLAG( + NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, + nm_setting_dcb_get_priority_group_flags, + (NM_SETTING_DCB_FLAG_ENABLE | NM_SETTING_DCB_FLAG_ADVERTISE | NM_SETTING_DCB_FLAG_WILLING) + + 1); + g_test_assert_expected_messages(); +} + +#define TEST_APP_PRIORITY(lcprop, ucprop, v) \ + { \ + g_object_set(G_OBJECT(s_dcb), \ + NM_SETTING_DCB_APP_##ucprop##_FLAGS, \ + NM_SETTING_DCB_FLAG_NONE, \ + NULL); \ + \ + g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_APP_##ucprop##_PRIORITY, v, NULL); \ + g_assert_cmpint(nm_setting_dcb_get_app_##lcprop##_priority(s_dcb), ==, v); \ + \ + /* Assert that the setting is invalid while the app is disabled unless v is default */ \ + success = nm_setting_verify(NM_SETTING(s_dcb), NULL, &error); \ + if (v >= 0) { \ + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); \ + g_assert(success == FALSE); \ + } else { \ + g_assert_no_error(error); \ + g_assert(success); \ + } \ + g_clear_error(&error); \ + \ + /* Set the enable flag and re-verify, this time it should be valid */ \ + g_object_set(G_OBJECT(s_dcb), \ + NM_SETTING_DCB_APP_##ucprop##_FLAGS, \ + NM_SETTING_DCB_FLAG_ENABLE, \ + NULL); \ + success = nm_setting_verify(NM_SETTING(s_dcb), NULL, &error); \ + g_assert_no_error(error); \ + g_assert(success); \ + \ + g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_APP_##ucprop##_PRIORITY, 0, NULL); \ + } + +static void +test_dcb_app_priorities(void) +{ + gs_unref_object NMSettingDcb *s_dcb = NULL; + GError * error = NULL; + gboolean success; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_assert(s_dcb); + + /* Defaults */ + g_assert_cmpint(nm_setting_dcb_get_app_fcoe_priority(s_dcb), ==, -1); + g_assert_cmpint(nm_setting_dcb_get_app_iscsi_priority(s_dcb), ==, -1); + g_assert_cmpint(nm_setting_dcb_get_app_fip_priority(s_dcb), ==, -1); + + TEST_APP_PRIORITY(fcoe, FCOE, 6); + TEST_APP_PRIORITY(iscsi, ISCSI, 5); + TEST_APP_PRIORITY(fip, FIP, 4); + + TEST_APP_PRIORITY(fcoe, FCOE, -1); + TEST_APP_PRIORITY(iscsi, ISCSI, -1); + TEST_APP_PRIORITY(fip, FIP, -1); +} + +#define TEST_PRIORITY_VALID(fn, id, val, flagsprop, verify) \ + { \ + /* Assert that setting the value gets the same value back out */ \ + nm_setting_dcb_set_priority_##fn(s_dcb, id, val); \ + g_assert_cmpint(nm_setting_dcb_get_priority_##fn(s_dcb, id), ==, val); \ + \ + if (verify) { \ + if (val != 0) { \ + /* Assert that verify fails because the flags do not include 'enabled' \ + * and a value has been set. \ + */ \ + success = nm_setting_verify(NM_SETTING(s_dcb), NULL, &error); \ + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); \ + g_assert(success == FALSE); \ + g_clear_error(&error); \ + } \ + \ + /* Assert that adding the 'enabled' flag verifies the setting */ \ + g_object_set(G_OBJECT(s_dcb), \ + NM_SETTING_DCB_PRIORITY_##flagsprop##_FLAGS, \ + NM_SETTING_DCB_FLAG_ENABLE, \ + NULL); \ + success = nm_setting_verify(NM_SETTING(s_dcb), NULL, &error); \ + g_assert_no_error(error); \ + g_assert(success); \ + } \ + \ + /* Reset everything */ \ + g_object_set(G_OBJECT(s_dcb), \ + NM_SETTING_DCB_PRIORITY_##flagsprop##_FLAGS, \ + NM_SETTING_DCB_FLAG_NONE, \ + NULL); \ + nm_setting_dcb_set_priority_##fn(s_dcb, id, 0); \ + } + +/* If Priority Groups are enabled, PG bandwidth must equal 100% */ +#define SET_VALID_PRIORITY_GROUP_BANDWIDTH \ + { \ + guint x; \ + for (x = 0; x < 7; x++) \ + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, x, 12); \ + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 7, 16); \ + } + +static void +test_dcb_priorities_valid(void) +{ + gs_unref_object NMSettingDcb *s_dcb = NULL; + GError * error = NULL; + gboolean success; + guint i; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_assert(s_dcb); + + for (i = 0; i < 8; i++) + TEST_PRIORITY_VALID(flow_control, i, TRUE, FLOW_CONTROL, TRUE); + + SET_VALID_PRIORITY_GROUP_BANDWIDTH + for (i = 0; i < 8; i++) { + TEST_PRIORITY_VALID(group_id, i, i, GROUP, TRUE); + TEST_PRIORITY_VALID(group_id, i, 7 - i, GROUP, TRUE); + } + + /* Clear PG bandwidth from earlier tests */ + for (i = 0; i < 8; i++) + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, i, 0); + + /* Priority Group Bandwidth must add up to 100% if enabled, which requires + * some dancing for verifying individual values here. + */ + for (i = 0; i < 8; i++) { + guint other = 7 - (i % 8); + + /* Set another priority group to the remaining bandwidth */ + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, other, 100 - i); + TEST_PRIORITY_VALID(group_bandwidth, i, i, GROUP, TRUE); + + /* Set another priority group to the remaining bandwidth */ + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, other, 100 - (7 - i)); + TEST_PRIORITY_VALID(group_bandwidth, i, 7 - i, GROUP, TRUE); + + /* Clear remaining bandwidth */ + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, other, 0); + } + + SET_VALID_PRIORITY_GROUP_BANDWIDTH + for (i = 0; i < 8; i++) { + TEST_PRIORITY_VALID(bandwidth, i, i, GROUP, TRUE); + TEST_PRIORITY_VALID(bandwidth, i, 7 - i, GROUP, TRUE); + } + + SET_VALID_PRIORITY_GROUP_BANDWIDTH + for (i = 0; i < 8; i++) + TEST_PRIORITY_VALID(strict_bandwidth, i, TRUE, GROUP, TRUE); + + SET_VALID_PRIORITY_GROUP_BANDWIDTH + for (i = 0; i < 8; i++) { + TEST_PRIORITY_VALID(traffic_class, i, i, GROUP, TRUE); + TEST_PRIORITY_VALID(traffic_class, i, 7 - i, GROUP, TRUE); + } +} + +static void +test_dcb_bandwidth_sums(void) +{ + gs_unref_object NMSettingDcb *s_dcb = NULL; + GError * error = NULL; + gboolean success; + + s_dcb = (NMSettingDcb *) nm_setting_dcb_new(); + g_assert(s_dcb); + + /* Assert that setting the value gets the same value back out */ + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 0, 9); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 1, 10); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 2, 11); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 3, 12); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 4, 13); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 5, 14); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 6, 15); + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 7, 16); + + /* Assert verify success when sums total 100% */ + g_object_set(G_OBJECT(s_dcb), + NM_SETTING_DCB_PRIORITY_GROUP_FLAGS, + NM_SETTING_DCB_FLAG_ENABLE, + NULL); + success = nm_setting_verify(NM_SETTING(s_dcb), NULL, &error); + g_assert_no_error(error); + g_assert(success); + + /* Assert verify fails when sums do not total 100% */ + nm_setting_dcb_set_priority_group_bandwidth(s_dcb, 4, 20); + success = nm_setting_verify(NM_SETTING(s_dcb), NULL, &error); + g_assert_error(error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY); + g_assert(success == FALSE); + g_clear_error(&error); +} + +/*****************************************************************************/ + +static void +test_nm_json(void) +{ + g_assert(NM_IN_SET(WITH_JANSSON, 0, 1)); + +#if WITH_JANSSON + g_assert(nm_json_vt()); +#else + g_assert(!nm_json_vt()); +#endif + +#if WITH_JANSSON != defined(JANSSON_SONAME) + #error "WITH_JANSON and JANSSON_SONAME are defined inconsistently." +#endif +} + +/*****************************************************************************/ + +static void +_test_team_config_sync(const char *team_config, + int notify_peer_count, + int notify_peers_interval, + int mcast_rejoin_count, + int mcast_rejoin_interval, + char * runner, + char * runner_hwaddr_policy, /* activebackup */ + GPtrArray * runner_tx_hash, /* lacp, loadbalance */ + char * runner_tx_balancer, /* lacp, loadbalance */ + int runner_tx_balancer_interval, /* lacp, loadbalance */ + gboolean runner_active, /* lacp */ + gboolean runner_fast_rate, /* lacp */ + int runner_sys_prio, /* lacp */ + int runner_min_ports, /* lacp */ + char * runner_agg_select_policy, /* lacp */ + GPtrArray * link_watchers) +{ + gs_unref_object NMSettingTeam *s_team = NULL; + guint i, j; + gboolean found; + + if (!nm_json_vt()) { + g_test_skip("team test requires JSON validation"); + return; + } + + s_team = (NMSettingTeam *) nm_setting_team_new(); + g_assert(s_team); + + g_object_set(s_team, NM_SETTING_TEAM_CONFIG, team_config, NULL); + g_assert_cmpint(nm_setting_team_get_notify_peers_count(s_team), ==, notify_peer_count); + g_assert_cmpint(nm_setting_team_get_notify_peers_interval(s_team), ==, notify_peers_interval); + g_assert_cmpint(nm_setting_team_get_mcast_rejoin_count(s_team), ==, mcast_rejoin_count); + g_assert_cmpint(nm_setting_team_get_mcast_rejoin_interval(s_team), ==, mcast_rejoin_interval); + g_assert_cmpint(nm_setting_team_get_runner_tx_balancer_interval(s_team), + ==, + runner_tx_balancer_interval); + g_assert_cmpint(nm_setting_team_get_runner_active(s_team), ==, runner_active); + g_assert_cmpint(nm_setting_team_get_runner_fast_rate(s_team), ==, runner_fast_rate); + g_assert_cmpint(nm_setting_team_get_runner_sys_prio(s_team), ==, runner_sys_prio); + g_assert_cmpint(nm_setting_team_get_runner_min_ports(s_team), ==, runner_min_ports); + g_assert_cmpstr(nm_setting_team_get_runner(s_team), ==, runner); + g_assert_cmpstr(nm_setting_team_get_runner_hwaddr_policy(s_team), ==, runner_hwaddr_policy); + g_assert_cmpstr(nm_setting_team_get_runner_tx_balancer(s_team), ==, runner_tx_balancer); + g_assert_cmpstr(nm_setting_team_get_runner_agg_select_policy(s_team), + ==, + runner_agg_select_policy); + + if (runner_tx_hash) { + g_assert_cmpint(runner_tx_hash->len, ==, nm_setting_team_get_num_runner_tx_hash(s_team)); + for (i = 0; i < runner_tx_hash->len; i++) { + found = FALSE; + for (j = 0; j < nm_setting_team_get_num_runner_tx_hash(s_team); j++) { + if (nm_streq0(nm_setting_team_get_runner_tx_hash(s_team, j), + runner_tx_hash->pdata[i])) { + found = TRUE; + break; + } + } + g_assert(found); + } + } + + if (link_watchers) { + g_assert_cmpint(link_watchers->len, ==, nm_setting_team_get_num_link_watchers(s_team)); + for (i = 0; i < link_watchers->len; i++) { + found = FALSE; + for (j = 0; j < nm_setting_team_get_num_link_watchers(s_team); j++) { + if (nm_team_link_watcher_equal(link_watchers->pdata[i], + nm_setting_team_get_link_watcher(s_team, j))) { + found = TRUE; + break; + } + } + g_assert(found); + } + } + + g_assert(nm_setting_verify((NMSetting *) s_team, NULL, NULL)); +} + +static void +test_runner_roundrobin_sync_from_config(void) +{ + _test_team_config_sync("", + -1, + -1, + -1, + -1, + NULL, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + NULL); +} + +static void +test_runner_broadcast_sync_from_config(void) +{ + _test_team_config_sync("{\"runner\": {\"name\": \"broadcast\"}}", + -1, + -1, + -1, + -1, + NM_SETTING_TEAM_RUNNER_BROADCAST, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + NULL); +} + +static void +test_runner_random_sync_from_config(void) +{ + _test_team_config_sync("{\"runner\": {\"name\": \"random\"}}", + -1, + -1, + -1, + -1, + NM_SETTING_TEAM_RUNNER_RANDOM, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + NULL); +} + +static void +test_runner_activebackup_sync_from_config(void) +{ + _test_team_config_sync("{\"runner\": {\"name\": \"activebackup\"}}", + -1, + -1, + -1, + -1, + NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + NULL); +} + +static void +test_runner_loadbalance_sync_from_config(void) +{ + gs_unref_ptrarray GPtrArray *tx_hash = NULL; + + tx_hash = g_ptr_array_new_with_free_func(g_free); + g_ptr_array_add(tx_hash, g_strdup("eth")); + g_ptr_array_add(tx_hash, g_strdup("ipv4")); + g_ptr_array_add(tx_hash, g_strdup("ipv6")); + + _test_team_config_sync("{\"runner\": {\"name\": \"loadbalance\"}}", + -1, + -1, + -1, + -1, + NM_SETTING_TEAM_RUNNER_LOADBALANCE, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + NULL); + + _test_team_config_sync("{\"runner\": {\"name\": \"loadbalance\", " + "\"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"]}}", + -1, + -1, + -1, + -1, + NM_SETTING_TEAM_RUNNER_LOADBALANCE, + NULL, + tx_hash, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + NULL); + + _test_team_config_sync( + "{\"runner\": {\"name\": \"loadbalance\", \"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"], " + "\"tx_balancer\": {\"name\": \"basic\", \"balancing_interval\": 30}}}", + -1, + -1, + -1, + -1, + NM_SETTING_TEAM_RUNNER_LOADBALANCE, + NULL, + tx_hash, + "basic", + 30, + TRUE, + FALSE, + -1, + -1, + NULL, + NULL); +} + +static void +test_runner_lacp_sync_from_config(void) +{ + gs_unref_ptrarray GPtrArray *tx_hash = NULL; + + tx_hash = g_ptr_array_new_with_free_func(g_free); + g_ptr_array_add(tx_hash, g_strdup("eth")); + g_ptr_array_add(tx_hash, g_strdup("ipv4")); + g_ptr_array_add(tx_hash, g_strdup("ipv6")); + + _test_team_config_sync( + "{\"runner\": {\"name\": \"lacp\", \"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"]}}", + -1, + -1, + -1, + -1, + NM_SETTING_TEAM_RUNNER_LACP, + NULL, + tx_hash, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + NULL); + + _test_team_config_sync( + "{\"runner\": {\"name\": \"lacp\", \"tx_hash\": [\"eth\", \"ipv4\", \"ipv6\"], " + "\"active\": false, \"fast_rate\": true, \"sys_prio\": 10, \"min_ports\": 5, " + "\"agg_select_policy\": \"port_config\"}}", + -1, + -1, + -1, + -1, + NM_SETTING_TEAM_RUNNER_LACP, + NULL, + tx_hash, + NULL, + -1, + FALSE, + TRUE, + 10, + 5, + "port_config", + NULL); +} + +static void +test_watcher_ethtool_sync_from_config(void) +{ + gs_unref_ptrarray GPtrArray *link_watchers = NULL; + + link_watchers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); + g_ptr_array_add(link_watchers, nm_team_link_watcher_new_ethtool(0, 0, NULL)); + _test_team_config_sync("{\"link_watch\": {\"name\": \"ethtool\"}}", + -1, + -1, + -1, + -1, + NULL, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + link_watchers); +} + +static void +test_watcher_nsna_ping_sync_from_config(void) +{ + gs_unref_ptrarray GPtrArray *link_watchers = NULL; + + link_watchers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); + g_ptr_array_add(link_watchers, + nm_team_link_watcher_new_nsna_ping(0, 0, 3, "target.host", NULL)); + _test_team_config_sync( + "{\"link_watch\": {\"name\": \"nsna_ping\", \"target_host\": \"target.host\"}}", + -1, + -1, + -1, + -1, + NULL, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + link_watchers); +} + +static void +test_watcher_arp_ping_sync_from_config(void) +{ + gs_unref_ptrarray GPtrArray *link_watchers = NULL; + + link_watchers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); + g_ptr_array_add( + link_watchers, + nm_team_link_watcher_new_arp_ping(0, 0, 3, "target.host", "source.host", 0, NULL)); + _test_team_config_sync( + "{\"link_watch\": {\"name\": \"arp_ping\", \"target_host\": \"target.host\", " + "\"source_host\": \"source.host\"}}", + -1, + -1, + -1, + -1, + NULL, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + link_watchers); +} + +static void +test_multiple_watchers_sync_from_config(void) +{ + gs_unref_ptrarray GPtrArray *link_watchers = NULL; + + link_watchers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); + g_ptr_array_add(link_watchers, nm_team_link_watcher_new_ethtool(2, 4, NULL)); + g_ptr_array_add(link_watchers, + nm_team_link_watcher_new_nsna_ping(3, 6, 9, "target.host", NULL)); + g_ptr_array_add( + link_watchers, + nm_team_link_watcher_new_arp_ping(5, + 10, + 15, + "target.host", + "source.host", + NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE + | NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE + | NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS, + NULL)); + _test_team_config_sync( + "{\"link_watch\": [" + "{\"name\": \"ethtool\", \"delay_up\": 2, \"delay_down\": 4}, " + "{\"name\": \"arp_ping\", \"init_wait\": 5, \"interval\": 10, \"missed_max\": 15, " + "\"target_host\": \"target.host\", \"source_host\": \"source.host\", " + "\"validate_active\": true, \"validate_inactive\": true, \"send_always\": true}, " + "{\"name\": \"nsna_ping\", \"init_wait\": 3, \"interval\": 6, \"missed_max\": 9, " + "\"target_host\": \"target.host\"}]}", + -1, + -1, + -1, + -1, + NULL, + NULL, + NULL, + NULL, + -1, + TRUE, + FALSE, + -1, + -1, + NULL, + link_watchers); +} + +/*****************************************************************************/ + +static void +_test_team_port_config_sync(const char *team_port_config, + int queue_id, + int prio, + gboolean sticky, + int lacp_prio, + int lacp_key, + GPtrArray * link_watchers) +{ + gs_unref_object NMSettingTeamPort *s_team_port = NULL; + guint i, j; + gboolean found; + + if (!nm_json_vt()) { + g_test_skip("team test requires JSON validation"); + return; + } + + s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new(); + g_assert(s_team_port); + + g_object_set(s_team_port, NM_SETTING_TEAM_CONFIG, team_port_config, NULL); + g_assert(nm_setting_team_port_get_queue_id(s_team_port) == queue_id); + g_assert(nm_setting_team_port_get_prio(s_team_port) == prio); + g_assert(nm_setting_team_port_get_sticky(s_team_port) == sticky); + g_assert(nm_setting_team_port_get_lacp_prio(s_team_port) == lacp_prio); + g_assert(nm_setting_team_port_get_lacp_key(s_team_port) == lacp_key); + + if (link_watchers) { + g_assert(link_watchers->len == nm_setting_team_port_get_num_link_watchers(s_team_port)); + for (i = 0; i < link_watchers->len; i++) { + found = FALSE; + for (j = 0; j < nm_setting_team_port_get_num_link_watchers(s_team_port); j++) { + if (nm_team_link_watcher_equal( + link_watchers->pdata[i], + nm_setting_team_port_get_link_watcher(s_team_port, j))) { + found = TRUE; + break; + } + } + g_assert(found); + } + } + + g_assert(nm_setting_verify((NMSetting *) s_team_port, NULL, NULL)); +} + +static void +test_team_port_default(void) +{ + _test_team_port_config_sync("", -1, 0, FALSE, -1, -1, NULL); +} + +static void +test_team_port_queue_id(void) +{ + _test_team_port_config_sync("{\"queue_id\": 3}", 3, 0, FALSE, -1, -1, NULL); + _test_team_port_config_sync("{\"queue_id\": 0}", 0, 0, FALSE, -1, -1, NULL); +} + +static void +test_team_port_prio(void) +{ + _test_team_port_config_sync("{\"prio\": 6}", -1, 6, FALSE, -1, -1, NULL); + _test_team_port_config_sync("{\"prio\": 0}", -1, 0, FALSE, -1, -1, NULL); +} + +static void +test_team_port_sticky(void) +{ + _test_team_port_config_sync("{\"sticky\": true}", -1, 0, TRUE, -1, -1, NULL); + _test_team_port_config_sync("{\"sticky\": false}", -1, 0, FALSE, -1, -1, NULL); +} + +static void +test_team_port_lacp_prio(void) +{ + _test_team_port_config_sync("{\"lacp_prio\": 9}", -1, 0, FALSE, 9, -1, NULL); + _test_team_port_config_sync("{\"lacp_prio\": 0}", -1, 0, FALSE, 0, -1, NULL); +} + +static void +test_team_port_lacp_key(void) +{ + _test_team_port_config_sync("{\"lacp_key\": 12}", -1, 0, FALSE, -1, 12, NULL); + _test_team_port_config_sync("{\"lacp_key\": 0}", -1, 0, FALSE, -1, 0, NULL); +} + +static void +test_team_port_full_config(void) +{ + gs_unref_ptrarray GPtrArray *link_watchers = NULL; + + link_watchers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_team_link_watcher_unref); + g_ptr_array_add( + link_watchers, + nm_team_link_watcher_new_arp_ping(0, + 3, + 3, + "1.2.3.2", + "1.2.3.1", + NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE, + NULL)); + g_ptr_array_add( + link_watchers, + nm_team_link_watcher_new_arp_ping(1, + 1, + 0, + "1.2.3.4", + "1.2.3.1", + NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS, + NULL)); + + _test_team_port_config_sync( + "{\"queue_id\": 10, \"prio\": 20, \"sticky\": true, \"lacp_prio\": 30, " + "\"lacp_key\": 40, \"link_watch\": [" + "{\"name\": \"arp_ping\", \"interval\": 3, \"target_host\": \"1.2.3.2\", " + "\"source_host\": \"1.2.3.1\", \"validate_inactive\": true}, " + "{\"name\": \"arp_ping\", \"init_wait\": 1, \"interval\": 1, " + "\"target_host\": \"1.2.3.4\", \"source_host\": \"1.2.3.1\", " + "\"send_always\": true}]}", + 10, + 20, + true, + 30, + 40, + NULL); +} + +/*****************************************************************************/ + +static void +_check_team_setting(NMSetting *setting) +{ + gs_unref_object NMSetting *setting2 = NULL; + gs_unref_object NMSetting *setting_clone = NULL; + gboolean is_port = NM_IS_SETTING_TEAM_PORT(setting); + gs_unref_variant GVariant *variant2 = NULL; + gs_unref_variant GVariant *variant3 = NULL; + + g_assert(NM_IS_SETTING_TEAM(setting) || is_port); + + setting2 = g_object_new(G_OBJECT_TYPE(setting), + is_port ? NM_SETTING_TEAM_PORT_CONFIG : NM_SETTING_TEAM_CONFIG, + is_port ? nm_setting_team_port_get_config(NM_SETTING_TEAM_PORT(setting)) + : nm_setting_team_get_config(NM_SETTING_TEAM(setting)), + NULL); + + if (nm_json_vt()) + nmtst_assert_setting_is_equal(setting, setting2, NM_SETTING_COMPARE_FLAG_EXACT); + + g_clear_object(&setting2); + + nmtst_assert_setting_dbus_roundtrip(setting); + + /* OK, now parse the setting only from the D-Bus variant, but removing the JSON config. + * For that, we have to "drop" the JSON and we do that by resetting the property. + * This causes JSON to be regenerated and it's in a normalized form that will compare + * equal. */ + setting_clone = nm_setting_duplicate(setting); + setting = setting_clone; + if (is_port) { + g_object_set(setting, + NM_SETTING_TEAM_PORT_STICKY, + nm_setting_team_port_get_sticky(NM_SETTING_TEAM_PORT(setting)), + NULL); + } else { + g_object_set(setting, + NM_SETTING_TEAM_RUNNER_SYS_PRIO, + nm_setting_team_get_runner_sys_prio(NM_SETTING_TEAM(setting)), + NULL); + } + variant2 = _nm_setting_to_dbus(setting, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL); + variant3 = nm_utils_gvariant_vardict_filter_drop_one(variant2, "config"); + setting2 = nmtst_assert_setting_dbus_new(G_OBJECT_TYPE(setting), variant3); + nmtst_assert_setting_is_equal(setting, setting2, NM_SETTING_COMPARE_FLAG_EXACT); +} + +static void +test_team_setting(void) +{ + gs_unref_variant GVariant *variant = nmtst_variant_from_string( + G_VARIANT_TYPE_VARDICT, + "{'config': <'{\"link_watch\": {\"name\": \"ethtool\"}}'>, 'interface-name': <'nm-team'>, " + "'link-watchers': <[{'name': <'ethtool'>}]>}"); + gs_free_error GError *error = NULL; + gs_unref_object NMSetting * setting = NULL; + nm_auto_unref_team_link_watcher NMTeamLinkWatcher *watcher1 = + nm_team_link_watcher_new_nsna_ping(1, 3, 4, "bbb", NULL); + nm_auto_unref_team_link_watcher NMTeamLinkWatcher *watcher2 = + nm_team_link_watcher_new_arp_ping2(1, 3, 4, -1, "ccc", "ddd", 0, NULL); + + g_assert(watcher1); + g_assert(watcher2); + + setting = _nm_setting_new_from_dbus(NM_TYPE_SETTING_TEAM, + variant, + NULL, + NM_SETTING_PARSE_FLAGS_STRICT, + &error); + nmtst_assert_success(setting, error); + _check_team_setting(setting); + + g_assert_cmpstr(nm_setting_team_get_config(NM_SETTING_TEAM(setting)), + ==, + "{\"link_watch\": {\"name\": \"ethtool\"}}"); + g_assert_cmpint(nm_setting_team_get_num_link_watchers(NM_SETTING_TEAM(setting)), ==, 1); + + g_object_set(setting, NM_SETTING_TEAM_RUNNER_SYS_PRIO, (int) 10, NULL); + + _check_team_setting(setting); + g_assert_cmpint(nm_setting_team_get_num_link_watchers(NM_SETTING_TEAM(setting)), ==, 1); + g_assert_cmpstr( + nm_setting_team_get_config(NM_SETTING_TEAM(setting)), + ==, + "{ \"runner\": { \"sys_prio\": 10 }, \"link_watch\": { \"name\": \"ethtool\" } }"); + + nm_setting_team_remove_link_watcher(NM_SETTING_TEAM(setting), 0); + + _check_team_setting(setting); + g_assert_cmpint(nm_setting_team_get_num_link_watchers(NM_SETTING_TEAM(setting)), ==, 0); + g_assert_cmpstr(nm_setting_team_get_config(NM_SETTING_TEAM(setting)), + ==, + "{ \"runner\": { \"sys_prio\": 10 } }"); + + nm_setting_team_add_link_watcher(NM_SETTING_TEAM(setting), watcher1); + _check_team_setting(setting); + g_assert_cmpstr( + nm_setting_team_get_config(NM_SETTING_TEAM(setting)), + ==, + "{ \"runner\": { \"sys_prio\": 10 }, \"link_watch\": { \"name\": \"nsna_ping\", " + "\"interval\": 3, \"init_wait\": 1, \"missed_max\": 4, \"target_host\": \"bbb\" } }"); + + nm_setting_team_add_link_watcher(NM_SETTING_TEAM(setting), watcher2); + _check_team_setting(setting); + g_assert_cmpstr( + nm_setting_team_get_config(NM_SETTING_TEAM(setting)), + ==, + "{ \"runner\": { \"sys_prio\": 10 }, \"link_watch\": [ { \"name\": \"nsna_ping\", " + "\"interval\": 3, \"init_wait\": 1, \"missed_max\": 4, \"target_host\": \"bbb\" }, { " + "\"name\": \"arp_ping\", \"interval\": 3, \"init_wait\": 1, \"missed_max\": 4, " + "\"source_host\": \"ddd\", \"target_host\": \"ccc\" } ] }"); + + nm_setting_team_remove_link_watcher(NM_SETTING_TEAM(setting), 0); + nm_setting_team_remove_link_watcher(NM_SETTING_TEAM(setting), 0); + g_object_set(setting, NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, (int) 5, NULL); + g_assert_cmpstr( + nm_setting_team_get_config(NM_SETTING_TEAM(setting)), + ==, + "{ \"runner\": { \"tx_balancer\": { \"balancing_interval\": 5 }, \"sys_prio\": 10 } }"); + + g_object_set(setting, NM_SETTING_TEAM_RUNNER, NULL, NULL); + _check_team_setting(setting); + g_assert_cmpstr( + nm_setting_team_get_config(NM_SETTING_TEAM(setting)), + ==, + "{ \"runner\": { \"tx_balancer\": { \"balancing_interval\": 5 }, \"sys_prio\": 10 } }"); + + g_object_set(setting, + NM_SETTING_TEAM_CONFIG, + "{ \"runner\": { \"tx_hash\": [ \"eth\", \"l3\" ] } }", + NULL); + _check_team_setting(setting); +} + +/*****************************************************************************/ + +static void +_setting_ethtool_set_feature(NMSettingEthtool *s_ethtool, const char *opt_name, NMTernary value) +{ + g_assert(NM_IS_SETTING_ETHTOOL(s_ethtool)); + + if (nmtst_get_rand_bool()) { + nm_setting_ethtool_set_feature(s_ethtool, opt_name, value); + return; + } + + if (value == NM_TERNARY_DEFAULT) { + nm_setting_option_set(NM_SETTING(s_ethtool), opt_name, NULL); + return; + } + + if (nmtst_get_rand_bool()) + nm_setting_option_set_boolean(NM_SETTING(s_ethtool), opt_name, value); + else + nm_setting_option_set(NM_SETTING(s_ethtool), opt_name, g_variant_new_boolean(value)); +} + +static NMTernary +_setting_ethtool_get_feature(NMSettingEthtool *s_ethtool, const char *opt_name) +{ + GVariant *v; + gboolean b; + + switch (nmtst_get_rand_uint32() % 3) { + case 0: + return nm_setting_ethtool_get_feature(s_ethtool, opt_name); + case 1: + if (!nm_setting_option_get_boolean(NM_SETTING(s_ethtool), opt_name, &b)) + return NM_TERNARY_DEFAULT; + return b; + default: + v = nm_setting_option_get(NM_SETTING(s_ethtool), opt_name); + if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) + return NM_TERNARY_DEFAULT; + return g_variant_get_boolean(v); + } +} + +static void +test_ethtool_features(void) +{ + gs_unref_object NMConnection *con = NULL; + gs_unref_object NMConnection *con2 = NULL; + gs_unref_object NMConnection *con3 = NULL; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + NMSettingConnection * s_con; + NMSettingEthtool * s_ethtool; + NMSettingEthtool * s_ethtool2; + NMSettingEthtool * s_ethtool3; + + con = nmtst_create_minimal_connection("ethtool-1", NULL, NM_SETTING_WIRED_SETTING_NAME, &s_con); + s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new()); + nm_connection_add_setting(con, NM_SETTING(s_ethtool)); + + _setting_ethtool_set_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RX, NM_TERNARY_TRUE); + _setting_ethtool_set_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_LRO, NM_TERNARY_FALSE); + + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RX), + ==, + NM_TERNARY_TRUE); + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_LRO), + ==, + NM_TERNARY_FALSE); + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_SG), + ==, + NM_TERNARY_DEFAULT); + + nmtst_connection_normalize(con); + + variant = nm_connection_to_dbus(con, NM_CONNECTION_SERIALIZE_ALL); + + con2 = nm_simple_connection_new_from_dbus(variant, &error); + nmtst_assert_success(con2, error); + + s_ethtool2 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con2, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_RX), + ==, + NM_TERNARY_TRUE); + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_LRO), + ==, + NM_TERNARY_FALSE); + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool2, NM_ETHTOOL_OPTNAME_FEATURE_SG), + ==, + NM_TERNARY_DEFAULT); + + nmtst_assert_connection_verifies_without_normalization(con2); + + nmtst_assert_connection_equals(con, FALSE, con2, FALSE); + + keyfile = nm_keyfile_write(con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success(keyfile, error); + + con3 = nm_keyfile_read(keyfile, + "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, + NULL, + NULL, + &error); + nmtst_assert_success(con3, error); + + nm_keyfile_read_ensure_id(con3, "unused-because-already-has-id"); + nm_keyfile_read_ensure_uuid(con3, "unused-because-already-has-uuid"); + + nmtst_connection_normalize(con3); + + nmtst_assert_connection_equals(con, FALSE, con3, FALSE); + + s_ethtool3 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con3, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_RX), + ==, + NM_TERNARY_TRUE); + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_LRO), + ==, + NM_TERNARY_FALSE); + g_assert_cmpint(_setting_ethtool_get_feature(s_ethtool3, NM_ETHTOOL_OPTNAME_FEATURE_SG), + ==, + NM_TERNARY_DEFAULT); +} + +static void +test_ethtool_coalesce(void) +{ + gs_unref_object NMConnection *con = NULL; + gs_unref_object NMConnection *con2 = NULL; + gs_unref_object NMConnection *con3 = NULL; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + NMSettingConnection * s_con; + NMSettingEthtool * s_ethtool; + NMSettingEthtool * s_ethtool2; + NMSettingEthtool * s_ethtool3; + guint32 u32; + + con = nmtst_create_minimal_connection("ethtool-coalesce", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new()); + nm_connection_add_setting(con, NM_SETTING(s_ethtool)); + + nm_setting_option_set_uint32(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, 4); + + g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, + &u32)); + g_assert_cmpuint(u32, ==, 4); + + nmtst_connection_normalize(con); + + variant = nm_connection_to_dbus(con, NM_CONNECTION_SERIALIZE_ALL); + + con2 = nm_simple_connection_new_from_dbus(variant, &error); + nmtst_assert_success(con2, error); + + s_ethtool2 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con2, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool2), + NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, + &u32)); + g_assert_cmpuint(u32, ==, 4); + + nmtst_assert_connection_verifies_without_normalization(con2); + + nmtst_assert_connection_equals(con, FALSE, con2, FALSE); + + keyfile = nm_keyfile_write(con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success(keyfile, error); + + con3 = nm_keyfile_read(keyfile, + "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, + NULL, + NULL, + &error); + nmtst_assert_success(con3, error); + + nm_keyfile_read_ensure_id(con3, "unused-because-already-has-id"); + nm_keyfile_read_ensure_uuid(con3, "unused-because-already-has-uuid"); + + nmtst_connection_normalize(con3); + + nmtst_assert_connection_equals(con, FALSE, con3, FALSE); + + s_ethtool3 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con3, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool3), + NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, + &u32)); + g_assert_cmpuint(u32, ==, 4); + + nm_setting_option_set(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, NULL); + g_assert_false(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, + NULL)); + + nm_setting_option_set_uint32(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES, 8); + + g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES, + &u32)); + g_assert_cmpuint(u32, ==, 8); + + nm_setting_option_clear_by_name(NM_SETTING(s_ethtool), nm_ethtool_optname_is_coalesce); + g_assert_false(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_COALESCE_RX_FRAMES, + NULL)); + g_assert_false(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_COALESCE_TX_FRAMES, + NULL)); + g_assert_false(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_COALESCE_TX_USECS, + NULL)); +} + +static void +test_ethtool_ring(void) +{ + gs_unref_object NMConnection *con = NULL; + gs_unref_object NMConnection *con2 = NULL; + gs_unref_object NMConnection *con3 = NULL; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + NMSettingConnection * s_con; + NMSettingEthtool * s_ethtool; + NMSettingEthtool * s_ethtool2; + NMSettingEthtool * s_ethtool3; + guint32 out_value; + + con = nmtst_create_minimal_connection("ethtool-ring", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new()); + nm_connection_add_setting(con, NM_SETTING(s_ethtool)); + + nm_setting_option_set_uint32(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, 4); + + g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, + &out_value)); + g_assert_cmpuint(out_value, ==, 4); + + nmtst_connection_normalize(con); + + variant = nm_connection_to_dbus(con, NM_CONNECTION_SERIALIZE_ALL); + + con2 = nm_simple_connection_new_from_dbus(variant, &error); + nmtst_assert_success(con2, error); + + s_ethtool2 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con2, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool2), + NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, + &out_value)); + g_assert_cmpuint(out_value, ==, 4); + + nmtst_assert_connection_verifies_without_normalization(con2); + + nmtst_assert_connection_equals(con, FALSE, con2, FALSE); + + keyfile = nm_keyfile_write(con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success(keyfile, error); + + con3 = nm_keyfile_read(keyfile, + "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, + NULL, + NULL, + &error); + nmtst_assert_success(con3, error); + + nm_keyfile_read_ensure_id(con3, "unused-because-already-has-id"); + nm_keyfile_read_ensure_uuid(con3, "unused-because-already-has-uuid"); + + nmtst_connection_normalize(con3); + + nmtst_assert_connection_equals(con, FALSE, con3, FALSE); + + s_ethtool3 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con3, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool3), + NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, + &out_value)); + g_assert_cmpuint(out_value, ==, 4); + + nm_setting_option_set(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, NULL); + g_assert_false(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, + NULL)); + + nm_setting_option_set_uint32(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, 8); + + g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, + &out_value)); + g_assert_cmpuint(out_value, ==, 8); + + nm_setting_option_clear_by_name(NM_SETTING(s_ethtool), nm_ethtool_optname_is_ring); + g_assert_false(nm_setting_option_get_uint32(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_RING_RX_JUMBO, + NULL)); + g_assert_false( + nm_setting_option_get_uint32(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_RING_RX, NULL)); + g_assert_false( + nm_setting_option_get_uint32(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_RING_TX, NULL)); +} + +static void +test_ethtool_pause(void) +{ + gs_unref_object NMConnection *con = NULL; + gs_unref_object NMConnection *con2 = NULL; + gs_unref_object NMConnection *con3 = NULL; + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + nm_auto_unref_keyfile GKeyFile *keyfile = NULL; + NMSettingConnection * s_con; + NMSettingEthtool * s_ethtool; + NMSettingEthtool * s_ethtool2; + NMSettingEthtool * s_ethtool3; + gboolean out_value; + + con = nmtst_create_minimal_connection("ethtool-pause", + NULL, + NM_SETTING_WIRED_SETTING_NAME, + &s_con); + s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new()); + nm_connection_add_setting(con, NM_SETTING(s_ethtool)); + + nm_setting_option_set_boolean(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG, FALSE); + nm_setting_option_set_boolean(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_PAUSE_RX, TRUE); + nm_setting_option_set_boolean(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_PAUSE_TX, TRUE); + + g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG, + &out_value)); + g_assert_true(!out_value); + g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_PAUSE_RX, + &out_value)); + g_assert_true(out_value); + g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool), + NM_ETHTOOL_OPTNAME_PAUSE_TX, + &out_value)); + g_assert_true(out_value); + + nmtst_connection_normalize(con); + + variant = nm_connection_to_dbus(con, NM_CONNECTION_SERIALIZE_ALL); + + con2 = nm_simple_connection_new_from_dbus(variant, &error); + nmtst_assert_success(con2, error); + + s_ethtool2 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con2, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool2), + NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG, + &out_value)); + g_assert_true(!out_value); + + nmtst_assert_connection_verifies_without_normalization(con2); + + nmtst_assert_connection_equals(con, FALSE, con2, FALSE); + + con2 = nm_simple_connection_new_from_dbus(variant, &error); + nmtst_assert_success(con2, error); + + keyfile = nm_keyfile_write(con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success(keyfile, error); + + con3 = nm_keyfile_read(keyfile, + "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, + NULL, + NULL, + &error); + nmtst_assert_success(con3, error); + + nm_keyfile_read_ensure_id(con3, "unused-because-already-has-id"); + nm_keyfile_read_ensure_uuid(con3, "unused-because-already-has-uuid"); + + nmtst_connection_normalize(con3); + + nmtst_assert_connection_equals(con, FALSE, con3, FALSE); + + s_ethtool3 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con3, NM_TYPE_SETTING_ETHTOOL)); + + g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool3), + NM_ETHTOOL_OPTNAME_PAUSE_AUTONEG, + &out_value)); + g_assert_true(!out_value); + g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool3), + NM_ETHTOOL_OPTNAME_PAUSE_RX, + &out_value)); + g_assert_true(out_value); + g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool3), + NM_ETHTOOL_OPTNAME_PAUSE_TX, + &out_value)); + g_assert_true(out_value); +} + +/*****************************************************************************/ + +static void +test_sriov_vf(void) +{ + NMSriovVF *vf1, *vf2; + GError * error = NULL; + char * str; + + vf1 = nm_sriov_vf_new(1); + nm_sriov_vf_set_attribute(vf1, + NM_SRIOV_VF_ATTRIBUTE_MAC, + g_variant_new_string("00:11:22:33:44:55")); + nm_sriov_vf_set_attribute(vf1, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK, g_variant_new_boolean(TRUE)); + nm_sriov_vf_set_attribute(vf1, NM_SRIOV_VF_ATTRIBUTE_TRUST, g_variant_new_boolean(FALSE)); + nm_sriov_vf_set_attribute(vf1, NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE, g_variant_new_uint32(100)); + nm_sriov_vf_set_attribute(vf1, NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE, g_variant_new_uint32(500)); + + str = nm_utils_sriov_vf_to_str(vf1, FALSE, &error); + g_assert_no_error(error); + g_assert_cmpstr( + str, + ==, + "1 mac=00:11:22:33:44:55 max-tx-rate=500 min-tx-rate=100 spoof-check=true trust=false"); + g_free(str); + + vf2 = nm_utils_sriov_vf_from_str(" 1 mac=00:11:22:33:44:55 max-tx-rate=500 min-tx-rate=100", + &error); + nmtst_assert_success(vf2, error); + nm_sriov_vf_set_attribute(vf2, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK, g_variant_new_boolean(FALSE)); + nm_sriov_vf_set_attribute(vf2, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK, g_variant_new_boolean(TRUE)); + nm_sriov_vf_set_attribute(vf2, NM_SRIOV_VF_ATTRIBUTE_TRUST, g_variant_new_boolean(TRUE)); + nm_sriov_vf_set_attribute(vf2, NM_SRIOV_VF_ATTRIBUTE_TRUST, NULL); + nm_sriov_vf_set_attribute(vf2, NM_SRIOV_VF_ATTRIBUTE_TRUST, g_variant_new_boolean(FALSE)); + + g_assert(nm_sriov_vf_equal(vf1, vf2)); + + nm_sriov_vf_unref(vf1); + nm_sriov_vf_unref(vf2); +} + +static void +test_sriov_vf_dup(void) +{ + NMSriovVF *vf1, *vf2; + + vf1 = nm_sriov_vf_new(1); + nm_sriov_vf_set_attribute(vf1, NM_SRIOV_VF_ATTRIBUTE_MAC, g_variant_new_string("foobar")); + nm_sriov_vf_set_attribute(vf1, NM_SRIOV_VF_ATTRIBUTE_TRUST, g_variant_new_boolean(FALSE)); + nm_sriov_vf_set_attribute(vf1, NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE, g_variant_new_uint32(10)); + nm_sriov_vf_set_attribute(vf1, NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE, g_variant_new_uint32(1000)); + nm_sriov_vf_add_vlan(vf1, 80); + nm_sriov_vf_set_vlan_qos(vf1, 80, NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD); + + vf2 = nm_sriov_vf_dup(vf1); + g_assert(nm_sriov_vf_equal(vf1, vf2)); + + nm_sriov_vf_unref(vf1); + nm_sriov_vf_unref(vf2); +} + +static void +test_sriov_vf_vlan(void) +{ + NMSriovVF * vf; + const guint * vlan_ids; + guint num; + GError * error = NULL; + gs_free char *str = NULL; + + vf = nm_sriov_vf_new(19); + nm_sriov_vf_set_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MAC, g_variant_new_string("00:11:22")); + g_assert(nm_sriov_vf_add_vlan(vf, 80)); + g_assert(!nm_sriov_vf_add_vlan(vf, 80)); + g_assert(nm_sriov_vf_add_vlan(vf, 82)); + g_assert(nm_sriov_vf_add_vlan(vf, 83)); + g_assert(nm_sriov_vf_add_vlan(vf, 81)); + g_assert(!nm_sriov_vf_remove_vlan(vf, 100)); + g_assert(nm_sriov_vf_remove_vlan(vf, 82)); + nm_sriov_vf_set_vlan_qos(vf, 81, 0xabba); + nm_sriov_vf_set_vlan_protocol(vf, 81, NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD); + + vlan_ids = nm_sriov_vf_get_vlan_ids(vf, &num); + g_assert(vlan_ids); + g_assert_cmpint(num, ==, 3); + g_assert_cmpint(vlan_ids[0], ==, 80); + g_assert_cmpint(vlan_ids[1], ==, 81); + g_assert_cmpint(vlan_ids[2], ==, 83); + g_assert_cmpint(nm_sriov_vf_get_vlan_qos(vf, 80), ==, 0x0); + g_assert_cmpint(nm_sriov_vf_get_vlan_protocol(vf, 80), ==, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + g_assert_cmpint(nm_sriov_vf_get_vlan_qos(vf, 81), ==, 0xabba); + g_assert_cmpint(nm_sriov_vf_get_vlan_protocol(vf, 81), ==, NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD); + + nm_sriov_vf_unref(vf); + + vf = nm_utils_sriov_vf_from_str("20 spoof-check=false vlans=85.0.q;4000.0x20.ad;81.10;83", + &error); + nmtst_assert_success(vf, error); + vlan_ids = nm_sriov_vf_get_vlan_ids(vf, &num); + g_assert(vlan_ids); + g_assert_cmpint(num, ==, 4); + g_assert_cmpint(vlan_ids[0], ==, 81); + g_assert_cmpint(nm_sriov_vf_get_vlan_qos(vf, 81), ==, 10); + g_assert_cmpint(nm_sriov_vf_get_vlan_protocol(vf, 81), ==, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + g_assert_cmpint(vlan_ids[1], ==, 83); + g_assert_cmpint(nm_sriov_vf_get_vlan_qos(vf, 83), ==, 0); + g_assert_cmpint(nm_sriov_vf_get_vlan_protocol(vf, 83), ==, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + g_assert_cmpint(vlan_ids[2], ==, 85); + g_assert_cmpint(nm_sriov_vf_get_vlan_qos(vf, 85), ==, 0); + g_assert_cmpint(nm_sriov_vf_get_vlan_protocol(vf, 85), ==, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + g_assert_cmpint(vlan_ids[3], ==, 4000); + g_assert_cmpint(nm_sriov_vf_get_vlan_qos(vf, 4000), ==, 0x20); + g_assert_cmpint(nm_sriov_vf_get_vlan_protocol(vf, 4000), ==, NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD); + + str = nm_utils_sriov_vf_to_str(vf, FALSE, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "20 spoof-check=false vlans=81.10;83;85;4000.32.ad"); + + nm_sriov_vf_unref(vf); +} + +static void +test_sriov_setting(void) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingConnection * s_con; + NMSettingSriov * s_sriov = NULL; + NMSriovVF * vf1, *vf2, *vf3; + GError * error = NULL; + gboolean success; + + con = nm_simple_connection_new(); + + s_con = (NMSettingConnection *) nm_setting_connection_new(); + nm_connection_add_setting(con, NM_SETTING(s_con)); + + g_object_set(s_con, + NM_SETTING_CONNECTION_ID, + "Test SR-IOV connection", + NM_SETTING_CONNECTION_UUID, + nm_uuid_generate_random_str_a(), + NM_SETTING_CONNECTION_AUTOCONNECT, + TRUE, + NM_SETTING_CONNECTION_INTERFACE_NAME, + "eth0", + NM_SETTING_CONNECTION_TYPE, + NM_SETTING_WIRED_SETTING_NAME, + NULL); + + nm_connection_add_setting(con, nm_setting_wired_new()); + + s_sriov = (NMSettingSriov *) nm_setting_sriov_new(); + nm_connection_add_setting(con, NM_SETTING(s_sriov)); + + g_object_set(s_sriov, NM_SETTING_SRIOV_TOTAL_VFS, 16, NULL); + nm_setting_sriov_add_vf(s_sriov, (vf1 = nm_sriov_vf_new(0))); + nm_setting_sriov_add_vf(s_sriov, (vf2 = nm_sriov_vf_new(4))); + nm_setting_sriov_add_vf(s_sriov, (vf3 = nm_sriov_vf_new(10))); + g_assert(nm_setting_sriov_remove_vf_by_index(s_sriov, 4)); + nm_sriov_vf_unref(vf2); + nm_setting_sriov_add_vf(s_sriov, (vf2 = nm_sriov_vf_new(2))); + + nmtst_assert_connection_verifies_and_normalizable(con); + nmtst_connection_normalize(con); + success = nm_setting_verify((NMSetting *) s_sriov, con, &error); + nmtst_assert_success(success, error); + + g_assert_cmpint(nm_setting_sriov_get_num_vfs(s_sriov), ==, 3); + g_assert_cmpint(nm_sriov_vf_get_index(nm_setting_sriov_get_vf(s_sriov, 0)), ==, 0); + g_assert_cmpint(nm_sriov_vf_get_index(nm_setting_sriov_get_vf(s_sriov, 1)), ==, 2); + g_assert_cmpint(nm_sriov_vf_get_index(nm_setting_sriov_get_vf(s_sriov, 2)), ==, 10); + + nm_sriov_vf_unref(vf1); + nm_sriov_vf_unref(vf2); + nm_sriov_vf_unref(vf3); +} + +typedef struct { + guint id; + guint qos; + bool proto_ad; +} VlanData; + +static void +_test_sriov_parse_vlan_one(const char *string, gboolean exp_res, VlanData *data, guint data_length) +{ + NMSriovVF * vf; + gboolean res; + guint i, num_vlans; + const guint *vlan_ids; + + vf = nm_sriov_vf_new(1); + g_assert(vf); + + res = _nm_sriov_vf_parse_vlans(vf, string, NULL); + g_assert_cmpint(res, ==, exp_res); + + if (exp_res) { + vlan_ids = nm_sriov_vf_get_vlan_ids(vf, &num_vlans); + g_assert_cmpint(num_vlans, ==, data_length); + for (i = 0; i < num_vlans; i++) { + g_assert_cmpint(vlan_ids[i], ==, data[i].id); + g_assert_cmpint(nm_sriov_vf_get_vlan_qos(vf, vlan_ids[i]), ==, data[i].qos); + g_assert_cmpint(nm_sriov_vf_get_vlan_protocol(vf, vlan_ids[i]), + ==, + data[i].proto_ad ? NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD + : NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q); + } + } + + nm_sriov_vf_unref(vf); +} + +#define test_sriov_parse_vlan_one(string, result, ...) \ + { \ + VlanData _data[] = {__VA_ARGS__}; \ + guint _length = G_N_ELEMENTS(_data); \ + \ + _test_sriov_parse_vlan_one(string, result, _data, _length); \ + } + +static void +test_sriov_parse_vlans(void) +{ + test_sriov_parse_vlan_one("", FALSE, {}); + test_sriov_parse_vlan_one("1", TRUE, {1, 0, 0}); + test_sriov_parse_vlan_one("1;2", TRUE, {1, 0, 0}, {2, 0, 0}); + test_sriov_parse_vlan_one("4095;;2", TRUE, {2, 0, 0}, {4095, 0, 0}); + test_sriov_parse_vlan_one("1 2", FALSE, {}); + test_sriov_parse_vlan_one("4096", FALSE, {}); + test_sriov_parse_vlan_one("1.10", TRUE, {1, 10, 0}); + test_sriov_parse_vlan_one("1.20.ad", TRUE, {1, 20, 1}); + test_sriov_parse_vlan_one("1.21.q", TRUE, {1, 21, 0}); + test_sriov_parse_vlan_one("9.20.foo", FALSE, {}); + test_sriov_parse_vlan_one("1.20.ad.12", FALSE, {}); + test_sriov_parse_vlan_one("1;1.10", FALSE, {}); + test_sriov_parse_vlan_one("1..1;2", FALSE, {}); + test_sriov_parse_vlan_one("1..ad;2", FALSE, {}); + test_sriov_parse_vlan_one("1.2.ad;2.0.q;5;3", TRUE, {1, 2, 1}, {2, 0, 0}, {3, 0, 0}, {5, 0, 0}); +} + +static void +test_bridge_vlans(void) +{ + NMBridgeVlan *v1, *v2; + GError * error = NULL; + guint16 vid_start, vid_end; + char * str; + + v1 = nm_bridge_vlan_from_str("1 foobar", &error); + nmtst_assert_no_success(v1, error); + g_clear_error(&error); + + v1 = nm_bridge_vlan_from_str("4095", &error); + nmtst_assert_no_success(v1, error); + g_clear_error(&error); + + /* test ranges */ + v1 = nm_bridge_vlan_from_str("2-1000 untagged", &error); + nmtst_assert_success(v1, error); + g_assert_cmpint(nm_bridge_vlan_get_vid_range(v1, &vid_start, &vid_end), ==, TRUE); + g_assert_cmpuint(vid_start, ==, 2); + g_assert_cmpuint(vid_end, ==, 1000); + g_assert_cmpint(nm_bridge_vlan_is_pvid(v1), ==, FALSE); + g_assert_cmpint(nm_bridge_vlan_is_untagged(v1), ==, TRUE); + nm_bridge_vlan_unref(v1); + + /* test comparison (1) */ + v1 = nm_bridge_vlan_from_str("10 untagged", &error); + nmtst_assert_success(v1, error); + + g_assert_cmpint(nm_bridge_vlan_get_vid_range(v1, &vid_start, &vid_end), ==, FALSE); + g_assert_cmpuint(vid_start, ==, 10); + g_assert_cmpuint(vid_end, ==, 10); + g_assert_cmpint(nm_bridge_vlan_is_sealed(v1), ==, FALSE); + g_assert_cmpint(nm_bridge_vlan_is_pvid(v1), ==, FALSE); + g_assert_cmpint(nm_bridge_vlan_is_untagged(v1), ==, TRUE); + + nm_bridge_vlan_set_pvid(v1, TRUE); + nm_bridge_vlan_set_untagged(v1, FALSE); + nm_bridge_vlan_seal(v1); + + g_assert_cmpint(nm_bridge_vlan_is_sealed(v1), ==, TRUE); + g_assert_cmpint(nm_bridge_vlan_is_pvid(v1), ==, TRUE); + g_assert_cmpint(nm_bridge_vlan_is_untagged(v1), ==, FALSE); + + str = nm_bridge_vlan_to_str(v1, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "10 pvid"); + nm_clear_g_free(&str); + + v2 = nm_bridge_vlan_from_str(" 10 pvid ", &error); + nmtst_assert_success(v2, error); + + g_assert_cmpint(nm_bridge_vlan_cmp(v1, v2), ==, 0); + + nm_bridge_vlan_unref(v1); + nm_bridge_vlan_unref(v2); + + /* test comparison (2) */ + v1 = nm_bridge_vlan_from_str("10", &error); + nmtst_assert_success(v1, error); + v2 = nm_bridge_vlan_from_str("20", &error); + nmtst_assert_success(v2, error); + + g_assert_cmpint(nm_bridge_vlan_cmp(v1, v2), <, 0); + + nm_bridge_vlan_unref(v1); + nm_bridge_vlan_unref(v2); +} + +static void +create_bridge_connection(NMConnection **con, NMSettingBridge **s_bridge) +{ + NMSettingConnection *s_con; + + g_assert(con); + g_assert(s_bridge); + + *con = nmtst_create_minimal_connection("bridge", NULL, NM_SETTING_BOND_SETTING_NAME, &s_con); + + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "bridge0", NULL); + + *s_bridge = (NMSettingBridge *) nm_setting_bridge_new(); + g_assert(*s_bridge); + + nm_connection_add_setting(*con, NM_SETTING(*s_bridge)); +} + +#define test_verify_options_bridge(exp, ...) \ + _test_verify_options_bridge(exp, NM_MAKE_STRV(__VA_ARGS__)) + +static void +_test_verify_options_bridge(gboolean expected_result, const char *const *options) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingBridge * s_bridge; + const char *const * option; + + g_assert(NM_PTRARRAY_LEN(options) % 2 == 0); + + create_bridge_connection(&con, &s_bridge); + + for (option = options; option[0]; option += 2) { + const char *option_key = option[0]; + const char *option_val = option[1]; + GParamSpec *pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(s_bridge), option_key); + + g_assert(pspec); + g_assert(option_val); + + switch (G_PARAM_SPEC_VALUE_TYPE(pspec)) { + case G_TYPE_UINT: + { + guint uvalue; + + uvalue = _nm_utils_ascii_str_to_uint64(option_val, 10, 0, G_MAXUINT, -1); + g_assert(errno == 0); + g_object_set(s_bridge, option_key, uvalue, NULL); + } break; + case G_TYPE_BOOLEAN: + { + int bvalue; + + bvalue = _nm_utils_ascii_str_to_bool(option_val, -1); + g_assert(bvalue != -1); + g_object_set(s_bridge, option_key, bvalue, NULL); + } break; + case G_TYPE_STRING: + g_object_set(s_bridge, option_key, option_val, NULL); + break; + default: + g_assert_not_reached(); + break; + } + } + + if (expected_result) + nmtst_assert_connection_verifies_and_normalizable(con); + else { + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + } +} + +static void +test_bridge_verify(void) +{ + /* group-address */ + test_verify_options_bridge(FALSE, "group-address", "nonsense"); + test_verify_options_bridge(FALSE, "group-address", "FF:FF:FF:FF:FF:FF"); + test_verify_options_bridge(FALSE, "group-address", "01:02:03:04:05:06"); + test_verify_options_bridge(TRUE, "group-address", "01:80:C2:00:00:00"); + test_verify_options_bridge(FALSE, "group-address", "01:80:C2:00:00:02"); + test_verify_options_bridge(FALSE, "group-address", "01:80:C2:00:00:03"); + test_verify_options_bridge(TRUE, "group-address", "01:80:C2:00:00:00"); + test_verify_options_bridge(TRUE, "group-address", "01:80:C2:00:00:0A"); + /* vlan-protocol */ + test_verify_options_bridge(FALSE, "vlan-protocol", "nonsense124"); + test_verify_options_bridge(FALSE, "vlan-protocol", "802.11"); + test_verify_options_bridge(FALSE, "vlan-protocol", "802.1Q1"); + test_verify_options_bridge(TRUE, "vlan-protocol", "802.1Q"); + test_verify_options_bridge(TRUE, "vlan-protocol", "802.1ad"); + /* multicast-router */ + test_verify_options_bridge(FALSE, "multicast-router", "nonsense"); + test_verify_options_bridge(TRUE, "multicast-snooping", "no", "multicast-router", "auto"); + test_verify_options_bridge(TRUE, "multicast-snooping", "no", "multicast-router", "enabled"); + test_verify_options_bridge(TRUE, "multicast-snooping", "no", "multicast-router", "disabled"); + test_verify_options_bridge(TRUE, "multicast-snooping", "yes", "multicast-router", "enabled"); + test_verify_options_bridge(TRUE, "multicast-snooping", "yes", "multicast-router", "auto"); + test_verify_options_bridge(TRUE, "multicast-snooping", "yes", "multicast-router", "disabled"); + /* multicast-hash-max */ + test_verify_options_bridge(TRUE, "multicast-hash-max", "1024"); + test_verify_options_bridge(TRUE, "multicast-hash-max", "8192"); + test_verify_options_bridge(FALSE, "multicast-hash-max", "3"); +} + +/*****************************************************************************/ + +static void +test_tc_config_qdisc(void) +{ + NMTCQdisc *qdisc1, *qdisc2; + char * str; + GError * error = NULL; + GVariant * variant; + + qdisc1 = nm_tc_qdisc_new("fq_codel", TC_H_ROOT, &error); + nmtst_assert_success(qdisc1, error); + + qdisc2 = nm_tc_qdisc_new("fq_codel", TC_H_ROOT, &error); + nmtst_assert_success(qdisc2, error); + + g_assert(nm_tc_qdisc_equal(qdisc1, qdisc2)); + + nm_tc_qdisc_unref(qdisc2); + qdisc2 = nm_tc_qdisc_dup(qdisc1); + + g_assert(nm_tc_qdisc_equal(qdisc1, qdisc2)); + + g_assert_cmpstr(nm_tc_qdisc_get_kind(qdisc1), ==, "fq_codel"); + g_assert(nm_tc_qdisc_get_handle(qdisc1) == TC_H_UNSPEC); + g_assert(nm_tc_qdisc_get_parent(qdisc1) == TC_H_ROOT); + + str = nm_utils_tc_qdisc_to_str(qdisc1, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "root fq_codel"); + g_free(str); + + nm_tc_qdisc_unref(qdisc1); + qdisc1 = nm_tc_qdisc_new("ingress", TC_H_INGRESS, &error); + nmtst_assert_success(qdisc1, error); + + g_assert(!nm_tc_qdisc_equal(qdisc1, qdisc2)); + + str = nm_utils_tc_qdisc_to_str(qdisc1, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "ingress"); + g_free(str); + + nm_tc_qdisc_unref(qdisc1); + qdisc1 = nm_utils_tc_qdisc_from_str("narodil sa kristus pan", &error); + nmtst_assert_no_success(qdisc1, error); + g_clear_error(&error); + + qdisc1 = nm_utils_tc_qdisc_from_str("handle 1234 parent fff1:1 pfifo_fast", &error); + nmtst_assert_success(qdisc1, error); + + g_assert_cmpstr(nm_tc_qdisc_get_kind(qdisc1), ==, "pfifo_fast"); + g_assert(nm_tc_qdisc_get_handle(qdisc1) == TC_H_MAKE(0x1234u << 16, 0x0000u)); + g_assert(nm_tc_qdisc_get_parent(qdisc1) == TC_H_MAKE(0xfff1u << 16, 0x0001u)); + + str = nm_utils_tc_qdisc_to_str(qdisc1, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "parent fff1:1 handle 1234: pfifo_fast"); + g_free(str); + + nm_tc_qdisc_unref(qdisc2); + str = nm_utils_tc_qdisc_to_str(qdisc1, &error); + nmtst_assert_success(str, error); + qdisc2 = nm_utils_tc_qdisc_from_str(str, &error); + nmtst_assert_success(qdisc2, error); + g_free(str); + + g_assert(nm_tc_qdisc_equal(qdisc1, qdisc2)); + + nm_tc_qdisc_unref(qdisc1); + nm_tc_qdisc_unref(qdisc2); + + qdisc1 = nm_utils_tc_qdisc_from_str("clsact", &error); + nmtst_assert_success(qdisc1, error); + str = nm_utils_tc_qdisc_to_str(qdisc1, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "clsact"); + nm_tc_qdisc_unref(qdisc1); + g_free(str); + +#define CHECK_ATTRIBUTE(qdisc, name, vtype, type, value) \ + variant = nm_tc_qdisc_get_attribute(qdisc, name); \ + g_assert(variant); \ + g_assert(g_variant_is_of_type(variant, vtype)); \ + g_assert_cmpint(g_variant_get_##type(variant), ==, value); + + qdisc1 = nm_utils_tc_qdisc_from_str("handle 1235 root sfq perturb 10 quantum 1480 " + "limit 9000 flows 1024 divisor 500 depth 12", + &error); + nmtst_assert_success(qdisc1, error); + + g_assert_cmpstr(nm_tc_qdisc_get_kind(qdisc1), ==, "sfq"); + g_assert(nm_tc_qdisc_get_handle(qdisc1) == TC_H_MAKE(0x1235u << 16, 0x0000u)); + g_assert(nm_tc_qdisc_get_parent(qdisc1) == TC_H_ROOT); + CHECK_ATTRIBUTE(qdisc1, "perturb", G_VARIANT_TYPE_INT32, int32, 10); + CHECK_ATTRIBUTE(qdisc1, "quantum", G_VARIANT_TYPE_UINT32, uint32, 1480); + CHECK_ATTRIBUTE(qdisc1, "limit", G_VARIANT_TYPE_UINT32, uint32, 9000); + CHECK_ATTRIBUTE(qdisc1, "flows", G_VARIANT_TYPE_UINT32, uint32, 1024); + CHECK_ATTRIBUTE(qdisc1, "divisor", G_VARIANT_TYPE_UINT32, uint32, 500); + CHECK_ATTRIBUTE(qdisc1, "depth", G_VARIANT_TYPE_UINT32, uint32, 12); + nm_tc_qdisc_unref(qdisc1); + + qdisc1 = nm_utils_tc_qdisc_from_str("handle 1235 root tbf rate 1000000 burst 5000 limit 10000", + &error); + nmtst_assert_success(qdisc1, error); + + g_assert_cmpstr(nm_tc_qdisc_get_kind(qdisc1), ==, "tbf"); + g_assert(nm_tc_qdisc_get_handle(qdisc1) == TC_H_MAKE(0x1235u << 16, 0x0000u)); + g_assert(nm_tc_qdisc_get_parent(qdisc1) == TC_H_ROOT); + CHECK_ATTRIBUTE(qdisc1, "rate", G_VARIANT_TYPE_UINT64, uint64, 1000000); + CHECK_ATTRIBUTE(qdisc1, "burst", G_VARIANT_TYPE_UINT32, uint32, 5000); + CHECK_ATTRIBUTE(qdisc1, "limit", G_VARIANT_TYPE_UINT32, uint32, 10000); + nm_tc_qdisc_unref(qdisc1); + +#undef CHECK_ATTRIBUTE +} + +static void +test_tc_config_action(void) +{ + NMTCAction *action1, *action2; + char * str; + GError * error = NULL; + + action1 = nm_tc_action_new("drop", &error); + nmtst_assert_success(action1, error); + action2 = nm_tc_action_new("drop", &error); + nmtst_assert_success(action2, error); + + g_assert(nm_tc_action_equal(action1, action2)); + g_assert_cmpstr(nm_tc_action_get_kind(action1), ==, "drop"); + + nm_tc_action_unref(action1); + action1 = nm_tc_action_new("simple", &error); + nmtst_assert_success(action1, error); + nm_tc_action_set_attribute(action1, "sdata", g_variant_new_bytestring("Hello")); + + g_assert(!nm_tc_action_equal(action1, action2)); + + str = nm_utils_tc_action_to_str(action1, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "simple sdata Hello"); + g_free(str); + + str = nm_utils_tc_action_to_str(action2, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "drop"); + g_free(str); + + nm_tc_action_unref(action2); + action2 = nm_tc_action_dup(action1); + + g_assert(nm_tc_action_equal(action1, action2)); + + nm_tc_action_unref(action1); + action1 = nm_utils_tc_action_from_str("narodil sa kristus pan", &error); + nmtst_assert_no_success(action1, error); + g_clear_error(&error); + + action1 = nm_utils_tc_action_from_str("simple sdata Hello", &error); + nmtst_assert_success(action1, error); + + g_assert_cmpstr(nm_tc_action_get_kind(action1), ==, "simple"); + g_assert_cmpstr(g_variant_get_bytestring(nm_tc_action_get_attribute(action1, "sdata")), + ==, + "Hello"); + + nm_tc_action_unref(action1); + nm_tc_action_unref(action2); +} + +static void +test_tc_config_tfilter_matchall_sdata(void) +{ + NMTCAction * action1; + NMTCTfilter *tfilter1, *tfilter2; + char * str; + GError * error = NULL; + + tfilter1 = nm_tc_tfilter_new("matchall", TC_H_MAKE(0x1234u << 16, 0x0000u), &error); + nmtst_assert_success(tfilter1, error); + + tfilter2 = nm_tc_tfilter_new("matchall", TC_H_MAKE(0x1234u << 16, 0x0000u), &error); + nmtst_assert_success(tfilter2, error); + + g_assert(nm_tc_tfilter_equal(tfilter1, tfilter2)); + + action1 = nm_tc_action_new("simple", &error); + nmtst_assert_success(action1, error); + nm_tc_action_set_attribute(action1, "sdata", g_variant_new_bytestring("Hello")); + nm_tc_tfilter_set_action(tfilter1, action1); + nm_tc_action_unref(action1); + + g_assert(!nm_tc_tfilter_equal(tfilter1, tfilter2)); + + str = nm_utils_tc_tfilter_to_str(tfilter1, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "parent 1234: matchall action simple sdata Hello"); + g_free(str); + + nm_tc_tfilter_unref(tfilter2); + tfilter2 = nm_tc_tfilter_dup(tfilter1); + + g_assert(nm_tc_tfilter_equal(tfilter1, tfilter2)); + + nm_tc_tfilter_unref(tfilter1); + tfilter1 = nm_utils_tc_tfilter_from_str("narodil sa kristus pan", &error); + nmtst_assert_no_success(tfilter1, error); + g_clear_error(&error); + + str = nm_utils_tc_tfilter_to_str(tfilter2, &error); + nmtst_assert_success(str, error); + tfilter1 = nm_utils_tc_tfilter_from_str(str, &error); + nmtst_assert_success(tfilter1, error); + g_free(str); + + g_assert(nm_tc_tfilter_equal(tfilter1, tfilter2)); + + nm_tc_tfilter_unref(tfilter1); + nm_tc_tfilter_unref(tfilter2); +} + +static void +test_tc_config_tfilter_matchall_mirred(void) +{ + NMTCAction * action; + NMTCTfilter * tfilter1; + GError * error = NULL; + gs_strfreev char **attr_names = NULL; + gs_free char * str; + GVariant * variant; + + tfilter1 = + nm_utils_tc_tfilter_from_str("parent ffff: matchall action mirred ingress mirror dev eth0", + &error); + nmtst_assert_success(tfilter1, error); + g_assert_cmpint(nm_tc_tfilter_get_parent(tfilter1), ==, TC_H_MAKE(0xffff << 16, 0)); + g_assert_cmpstr(nm_tc_tfilter_get_kind(tfilter1), ==, "matchall"); + + action = nm_tc_tfilter_get_action(tfilter1); + nm_assert(action); + g_assert_cmpstr(nm_tc_action_get_kind(action), ==, "mirred"); + attr_names = nm_tc_action_get_attribute_names(action); + g_assert(attr_names); + g_assert_cmpint(g_strv_length(attr_names), ==, 3); + + variant = nm_tc_action_get_attribute(action, "ingress"); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)); + g_assert(g_variant_get_boolean(variant)); + + variant = nm_tc_action_get_attribute(action, "mirror"); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)); + g_assert(g_variant_get_boolean(variant)); + + variant = nm_tc_action_get_attribute(action, "dev"); + g_assert(variant); + g_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING)); + g_assert_cmpstr(g_variant_get_string(variant, NULL), ==, "eth0"); + + str = nm_utils_tc_tfilter_to_str(tfilter1, &error); + nmtst_assert_success(str, error); + g_assert_cmpstr(str, ==, "parent ffff: matchall action mirred dev eth0 ingress mirror"); + + nm_tc_tfilter_unref(tfilter1); +} + +static void +test_tc_config_setting_valid(void) +{ + gs_unref_object NMSettingTCConfig *s_tc = NULL; + NMTCQdisc * qdisc1, *qdisc2; + GError * error = NULL; + + s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new(); + + qdisc1 = nm_tc_qdisc_new("fq_codel", TC_H_ROOT, &error); + nmtst_assert_success(qdisc1, error); + + qdisc2 = nm_tc_qdisc_new("pfifo_fast", TC_H_MAKE(0xfff1u << 16, 0x0001u), &error); + nmtst_assert_success(qdisc2, error); + nm_tc_qdisc_set_handle(qdisc2, TC_H_MAKE(0x1234u << 16, 0x0000u)); + + g_assert(nm_setting_tc_config_get_num_qdiscs(s_tc) == 0); + g_assert(nm_setting_tc_config_add_qdisc(s_tc, qdisc1) == TRUE); + g_assert(nm_setting_tc_config_get_num_qdiscs(s_tc) == 1); + g_assert(nm_setting_tc_config_get_qdisc(s_tc, 0) != NULL); + g_assert(nm_setting_tc_config_remove_qdisc_by_value(s_tc, qdisc2) == FALSE); + g_assert(nm_setting_tc_config_add_qdisc(s_tc, qdisc2) == TRUE); + g_assert(nm_setting_tc_config_get_num_qdiscs(s_tc) == 2); + g_assert(nm_setting_tc_config_remove_qdisc_by_value(s_tc, qdisc1) == TRUE); + g_assert(nm_setting_tc_config_get_num_qdiscs(s_tc) == 1); + nm_setting_tc_config_clear_qdiscs(s_tc); + g_assert(nm_setting_tc_config_get_num_qdiscs(s_tc) == 0); + + nm_tc_qdisc_unref(qdisc1); + nm_tc_qdisc_unref(qdisc2); +} + +static void +test_tc_config_setting_duplicates(void) +{ + gs_unref_ptrarray GPtrArray *qdiscs = NULL; + gs_unref_ptrarray GPtrArray *tfilters = NULL; + NMSettingConnection * s_con; + NMConnection * con; + NMSetting * s_tc; + NMTCQdisc * qdisc; + NMTCTfilter * tfilter; + GError * error = NULL; + + con = nmtst_create_minimal_connection("dummy", NULL, NM_SETTING_DUMMY_SETTING_NAME, &s_con); + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, "dummy1", NULL); + + s_tc = nm_setting_tc_config_new(); + nm_connection_add_setting(con, s_tc); + qdiscs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_qdisc_unref); + tfilters = g_ptr_array_new_with_free_func((GDestroyNotify) nm_tc_tfilter_unref); + + /* 1. add duplicate qdiscs */ + qdisc = nm_utils_tc_qdisc_from_str("handle 1234 parent fff1:1 pfifo_fast", &error); + nmtst_assert_success(qdisc, error); + g_ptr_array_add(qdiscs, qdisc); + + qdisc = nm_utils_tc_qdisc_from_str("handle 1234 parent fff1:1 pfifo_fast", &error); + nmtst_assert_success(qdisc, error); + g_ptr_array_add(qdiscs, qdisc); + + g_object_set(s_tc, NM_SETTING_TC_CONFIG_QDISCS, qdiscs, NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + + /* 2. make qdiscs unique */ + g_ptr_array_remove_index(qdiscs, 0); + g_object_set(s_tc, NM_SETTING_TC_CONFIG_QDISCS, qdiscs, NULL); + nmtst_assert_connection_verifies_and_normalizable(con); + + /* 3. add duplicate filters */ + tfilter = + nm_utils_tc_tfilter_from_str("parent 1234: matchall action simple sdata Hello", &error); + nmtst_assert_success(tfilter, error); + g_ptr_array_add(tfilters, tfilter); + + tfilter = + nm_utils_tc_tfilter_from_str("parent 1234: matchall action simple sdata Hello", &error); + nmtst_assert_success(tfilter, error); + g_ptr_array_add(tfilters, tfilter); + + g_object_set(s_tc, NM_SETTING_TC_CONFIG_TFILTERS, tfilters, NULL); + nmtst_assert_connection_unnormalizable(con, + NM_CONNECTION_ERROR, + NM_CONNECTION_ERROR_INVALID_PROPERTY); + + /* 4. make filters unique */ + g_ptr_array_remove_index(tfilters, 0); + g_object_set(s_tc, NM_SETTING_TC_CONFIG_TFILTERS, tfilters, NULL); + nmtst_assert_connection_verifies_and_normalizable(con); +} + +static void +test_tc_config_dbus(void) +{ + NMConnection *connection1, *connection2; + NMSetting * s_tc; + NMTCQdisc * qdisc1, *qdisc2; + NMTCTfilter * tfilter1, *tfilter2; + NMTCAction * action; + GVariant * dbus, *tc_dbus, *var1, *var2; + GError * error = NULL; + gboolean success; + + connection1 = + nmtst_create_minimal_connection("dummy", NULL, NM_SETTING_DUMMY_SETTING_NAME, NULL); + + s_tc = nm_setting_tc_config_new(); + + qdisc1 = nm_tc_qdisc_new("fq_codel", TC_H_ROOT, &error); + nmtst_assert_success(qdisc1, error); + nm_tc_qdisc_set_handle(qdisc1, TC_H_MAKE(0x1234u << 16, 0x0000u)); + nm_setting_tc_config_add_qdisc(NM_SETTING_TC_CONFIG(s_tc), qdisc1); + + qdisc2 = nm_tc_qdisc_new("ingress", TC_H_INGRESS, &error); + nmtst_assert_success(qdisc2, error); + nm_tc_qdisc_set_handle(qdisc2, TC_H_MAKE(TC_H_INGRESS, 0u)); + nm_setting_tc_config_add_qdisc(NM_SETTING_TC_CONFIG(s_tc), qdisc2); + + tfilter1 = nm_tc_tfilter_new("matchall", TC_H_MAKE(0x1234u << 16, 0x0000u), &error); + nmtst_assert_success(tfilter1, error); + action = nm_tc_action_new("drop", &error); + nmtst_assert_success(action, error); + nm_tc_tfilter_set_action(tfilter1, action); + nm_tc_action_unref(action); + nm_setting_tc_config_add_tfilter(NM_SETTING_TC_CONFIG(s_tc), tfilter1); + nm_tc_tfilter_unref(tfilter1); + + tfilter2 = nm_tc_tfilter_new("matchall", TC_H_MAKE(TC_H_INGRESS, 0u), &error); + nmtst_assert_success(tfilter2, error); + action = nm_tc_action_new("simple", &error); + nmtst_assert_success(action, error); + nm_tc_action_set_attribute(action, "sdata", g_variant_new_bytestring("Hello")); + nm_tc_tfilter_set_action(tfilter2, action); + nm_tc_action_unref(action); + nm_setting_tc_config_add_tfilter(NM_SETTING_TC_CONFIG(s_tc), tfilter2); + nm_tc_tfilter_unref(tfilter2); + + nm_connection_add_setting(connection1, s_tc); + + dbus = nm_connection_to_dbus(connection1, NM_CONNECTION_SERIALIZE_ALL); + + tc_dbus = g_variant_lookup_value(dbus, "tc", G_VARIANT_TYPE_VARDICT); + g_assert(tc_dbus); + + var1 = g_variant_lookup_value(tc_dbus, "qdiscs", G_VARIANT_TYPE("aa{sv}")); + var2 = g_variant_new_parsed("[{'kind': <'fq_codel'>," + " 'handle': ," + " 'parent': }," + " {'kind': <'ingress'>," + " 'handle': ," + " 'parent': }]"); + g_assert(g_variant_equal(var1, var2)); + g_variant_unref(var1); + g_variant_unref(var2); + + var1 = g_variant_lookup_value(tc_dbus, "tfilters", G_VARIANT_TYPE("aa{sv}")); + var2 = g_variant_new_parsed("[{'kind': <'matchall'>," + " 'handle': ," + " 'parent': ," + " 'action': <{'kind': <'drop'>}>}," + " {'kind': <'matchall'>," + " 'handle': ," + " 'parent': ," + " 'action': <{'kind': <'simple'>," + " 'sdata': }>}]"); + g_variant_unref(var1); + g_variant_unref(var2); + + g_variant_unref(tc_dbus); + + connection2 = nm_simple_connection_new(); + success = nm_connection_replace_settings(connection2, dbus, &error); + nmtst_assert_success(success, error); + + g_assert(nm_connection_diff(connection1, connection2, NM_SETTING_COMPARE_FLAG_EXACT, NULL)); + + g_variant_unref(dbus); + + nm_tc_qdisc_unref(qdisc1); + nm_tc_qdisc_unref(qdisc2); + + g_object_unref(connection1); + g_object_unref(connection2); +} + +/*****************************************************************************/ + +static void +_rndt_wired_add_s390_options(NMSettingWired *s_wired, char **out_keyfile_entries) +{ + gsize n_opts; + gsize i, j; + const char *const * option_names; + gs_free const char **opt_keys = NULL; + gs_strfreev char ** opt_vals = NULL; + gs_free bool * opt_found = NULL; + GString * keyfile_entries; + nm_auto_free_gstring GString *str_tmp = NULL; + + option_names = nm_setting_wired_get_valid_s390_options(nmtst_get_rand_bool() ? NULL : s_wired); + + n_opts = NM_PTRARRAY_LEN(option_names); + opt_keys = g_new(const char *, (n_opts + 1)); + nmtst_rand_perm(NULL, opt_keys, option_names, sizeof(const char *), n_opts); + n_opts = nmtst_get_rand_uint32() % (n_opts + 1); + opt_keys[n_opts] = NULL; + + opt_vals = g_new0(char *, n_opts + 1); + opt_found = g_new0(bool, n_opts + 1); + for (i = 0; i < n_opts; i++) { + if (nm_streq(opt_keys[i], "bridge_role")) + opt_vals[i] = g_strdup(nmtst_rand_select_str("primary", "secondary", "none")); + else { + guint p = nmtst_get_rand_uint32() % 1000; + if (p < 200) + opt_vals[i] = nm_strdup_int(i); + else { + opt_vals[i] = g_strdup_printf("%s%s%s%s-%zu", + ((p % 5) % 2) ? "\n" : "", + ((p % 7) % 2) ? "\t" : "", + ((p % 11) % 2) ? "x" : "", + ((p % 13) % 2) ? "=" : "", + i); + } + } + } + + if (nmtst_get_rand_bool()) { + gs_unref_hashtable GHashTable *hash = NULL; + + hash = g_hash_table_new(nm_str_hash, g_str_equal); + for (i = 0; i < n_opts; i++) + g_hash_table_insert(hash, (char *) opt_keys[i], opt_vals[i]); + g_object_set(s_wired, NM_SETTING_WIRED_S390_OPTIONS, hash, NULL); + } else { + _nm_setting_wired_clear_s390_options(s_wired); + for (i = 0; i < n_opts; i++) { + if (!nm_setting_wired_add_s390_option(s_wired, opt_keys[i], opt_vals[i])) + g_assert_not_reached(); + } + } + + g_assert_cmpint(nm_setting_wired_get_num_s390_options(s_wired), ==, n_opts); + + keyfile_entries = g_string_new(NULL); + str_tmp = g_string_new(NULL); + if (n_opts > 0) + g_string_append_printf(keyfile_entries, "[ethernet-s390-options]\n"); + for (i = 0; i < n_opts; i++) { + gssize idx; + const char *k, *v; + + nm_setting_wired_get_s390_option(s_wired, i, &k, &v); + g_assert(k); + g_assert(v); + + idx = nm_utils_strv_find_first((char **) opt_keys, n_opts, k); + g_assert(idx >= 0); + g_assert(!opt_found[idx]); + opt_found[idx] = TRUE; + g_assert_cmpstr(opt_keys[idx], ==, k); + g_assert_cmpstr(opt_vals[idx], ==, v); + + g_string_truncate(str_tmp, 0); + for (j = 0; v[j] != '\0'; j++) { + if (v[j] == '\n') + g_string_append(str_tmp, "\\n"); + else if (v[j] == '\t') + g_string_append(str_tmp, "\\t"); + else + g_string_append_c(str_tmp, v[j]); + } + + g_string_append_printf(keyfile_entries, "%s=%s\n", k, str_tmp->str); + } + for (i = 0; i < n_opts; i++) + g_assert(opt_found[i]); + if (n_opts > 0) + g_string_append_printf(keyfile_entries, "\n"); + *out_keyfile_entries = g_string_free(keyfile_entries, FALSE); +} + +static GPtrArray * +_rndt_wg_peers_create(void) +{ + GPtrArray *wg_peers; + guint i, n; + + wg_peers = g_ptr_array_new_with_free_func((GDestroyNotify) nm_wireguard_peer_unref); + + n = nmtst_get_rand_uint32() % 10; + for (i = 0; i < n; i++) { + NMWireGuardPeer *peer; + guint8 public_key_buf[NM_WIREGUARD_PUBLIC_KEY_LEN]; + guint8 preshared_key_buf[NM_WIREGUARD_SYMMETRIC_KEY_LEN]; + gs_free char * public_key = NULL; + gs_free char * preshared_key = NULL; + gs_free char * s_endpoint = NULL; + guint i_aip, n_aip; + + /* we don't bother to create a valid curve25519 public key. Of course, libnm cannot + * check whether the public key is bogus or not. Hence, for our purpose a random + * bogus key is good enough. */ + public_key = g_base64_encode(nmtst_rand_buf(NULL, public_key_buf, sizeof(public_key_buf)), + sizeof(public_key_buf)); + + preshared_key = + g_base64_encode(nmtst_rand_buf(NULL, preshared_key_buf, sizeof(preshared_key_buf)), + sizeof(preshared_key_buf)); + + s_endpoint = _create_random_ipaddr(AF_UNSPEC, TRUE); + + peer = nm_wireguard_peer_new(); + if (!nm_wireguard_peer_set_public_key(peer, public_key, TRUE)) + g_assert_not_reached(); + + if (!nm_wireguard_peer_set_preshared_key(peer, + nmtst_rand_select(NULL, preshared_key), + TRUE)) + g_assert_not_reached(); + + nm_wireguard_peer_set_preshared_key_flags( + peer, + nmtst_rand_select(NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_SECRET_FLAG_AGENT_OWNED)); + + nm_wireguard_peer_set_persistent_keepalive( + peer, + nmtst_rand_select((guint32) 0, nmtst_get_rand_uint32())); + + if (!nm_wireguard_peer_set_endpoint(peer, nmtst_rand_select(s_endpoint, NULL), TRUE)) + g_assert_not_reached(); + + n_aip = nmtst_rand_select(0, nmtst_get_rand_uint32() % 10); + for (i_aip = 0; i_aip < n_aip; i_aip++) { + gs_free char *aip = NULL; + + aip = _create_random_ipaddr(AF_UNSPEC, FALSE); + if (!nm_wireguard_peer_append_allowed_ip(peer, aip, FALSE)) + g_assert_not_reached(); + } + + g_assert(nm_wireguard_peer_is_valid(peer, TRUE, TRUE, NULL)); + + nm_wireguard_peer_seal(peer); + g_ptr_array_add(wg_peers, peer); + } + + return wg_peers; +} + +static const char * +_rndt_wg_peers_to_keyfile(GPtrArray *wg_peers, gboolean strict, char **out_str) +{ + nm_auto_free_gstring GString *gstr = NULL; + nm_auto_free_gstring GString *gstr_aip = NULL; + guint i, j; + + g_assert(wg_peers); + g_assert(out_str && !*out_str); + + nm_gstring_prepare(&gstr); + for (i = 0; i < wg_peers->len; i++) { + const NMWireGuardPeer *peer = wg_peers->pdata[i]; + gs_free char * s_endpoint = NULL; + gs_free char * s_preshared_key = NULL; + gs_free char * s_preshared_key_flags = NULL; + gs_free char * s_persistent_keepalive = NULL; + gs_free char * s_allowed_ips = NULL; + + if (nm_wireguard_peer_get_endpoint(peer)) + s_endpoint = g_strdup_printf("endpoint=%s\n", nm_wireguard_peer_get_endpoint(peer)); + else if (!strict) + s_endpoint = g_strdup_printf("endpoint=\n"); + + if (nm_wireguard_peer_get_preshared_key(peer) || !strict) { + if (nm_wireguard_peer_get_preshared_key_flags(peer) == NM_SETTING_SECRET_FLAG_NONE) + s_preshared_key = g_strdup_printf("preshared-key=%s\n", + nm_wireguard_peer_get_preshared_key(peer) ?: ""); + } + + if (nm_wireguard_peer_get_preshared_key_flags(peer) != NM_SETTING_SECRET_FLAG_NOT_REQUIRED + || !strict) + s_preshared_key_flags = + g_strdup_printf("preshared-key-flags=%d\n", + (int) nm_wireguard_peer_get_preshared_key_flags(peer)); + + if (nm_wireguard_peer_get_persistent_keepalive(peer) != 0 || !strict) + s_persistent_keepalive = + g_strdup_printf("persistent-keepalive=%u\n", + nm_wireguard_peer_get_persistent_keepalive(peer)); + + if (nm_wireguard_peer_get_allowed_ips_len(peer) > 0 || !strict) { + nm_gstring_prepare(&gstr_aip); + for (j = 0; j < nm_wireguard_peer_get_allowed_ips_len(peer); j++) + g_string_append_printf(gstr_aip, + "%s;", + nm_wireguard_peer_get_allowed_ip(peer, j, NULL)); + s_allowed_ips = g_strdup_printf("allowed-ips=%s\n", gstr_aip->str); + } + + if (!s_endpoint && !s_preshared_key && !s_preshared_key_flags && !s_persistent_keepalive + && !s_allowed_ips) + s_endpoint = g_strdup_printf("endpoint=\n"); + + g_string_append_printf(gstr, + "\n" + "[wireguard-peer.%s]\n" + "%s" /* endpoint */ + "%s" /* preshared-key */ + "%s" /* preshared-key-flags */ + "%s" /* persistent-keepalive */ + "%s" /* allowed-ips */ + "", + nm_wireguard_peer_get_public_key(peer), + s_endpoint ?: "", + s_preshared_key ?: "", + s_preshared_key_flags ?: "", + s_persistent_keepalive ?: "", + s_allowed_ips ?: ""); + } + + return (*out_str = g_string_free(g_steal_pointer(&gstr), FALSE)); +} + +static void +_rndt_wg_peers_assert_equal(NMSettingWireGuard *s_wg, + GPtrArray * peers, + gboolean consider_persistent_secrets, + gboolean consider_all_secrets, + gboolean expect_no_secrets) +{ + guint i; + + g_assert(NM_IS_SETTING_WIREGUARD(s_wg)); + g_assert(peers); + + g_assert_cmpint(peers->len, ==, nm_setting_wireguard_get_peers_len(s_wg)); + + for (i = 0; i < peers->len; i++) { + const NMWireGuardPeer *a = peers->pdata[i]; + const NMWireGuardPeer *b = nm_setting_wireguard_get_peer(s_wg, i); + gboolean consider_secrets; + + g_assert(a); + g_assert(b); + + g_assert_cmpint(nm_wireguard_peer_cmp(a, b, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS), ==, 0); + + if (consider_all_secrets || !nm_wireguard_peer_get_preshared_key(a)) + consider_secrets = TRUE; + else if (nm_wireguard_peer_get_preshared_key(b)) + consider_secrets = TRUE; + else if (consider_persistent_secrets + && nm_wireguard_peer_get_preshared_key_flags(b) == NM_SETTING_SECRET_FLAG_NONE) + consider_secrets = TRUE; + else + consider_secrets = FALSE; + + if (consider_secrets) { + g_assert_cmpstr(nm_wireguard_peer_get_preshared_key(a), + ==, + nm_wireguard_peer_get_preshared_key(b)); + g_assert_cmpint(nm_wireguard_peer_cmp(a, b, NM_SETTING_COMPARE_FLAG_EXACT), ==, 0); + } + + if (expect_no_secrets) + g_assert_cmpstr(nm_wireguard_peer_get_preshared_key(b), ==, NULL); + } +} + +static void +_rndt_wg_peers_fix_secrets(NMSettingWireGuard *s_wg, GPtrArray *peers) +{ + guint i; + + g_assert(NM_IS_SETTING_WIREGUARD(s_wg)); + g_assert(peers); + + g_assert_cmpint(peers->len, ==, nm_setting_wireguard_get_peers_len(s_wg)); + + for (i = 0; i < peers->len; i++) { + const NMWireGuardPeer *a = peers->pdata[i]; + const NMWireGuardPeer *b = nm_setting_wireguard_get_peer(s_wg, i); + nm_auto_unref_wgpeer NMWireGuardPeer *b_clone = NULL; + + g_assert(a); + g_assert(b); + + g_assert_cmpint(nm_wireguard_peer_get_preshared_key_flags(a), + ==, + nm_wireguard_peer_get_preshared_key_flags(b)); + g_assert_cmpint(nm_wireguard_peer_cmp(a, b, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS), ==, 0); + + if (!nm_streq0(nm_wireguard_peer_get_preshared_key(a), + nm_wireguard_peer_get_preshared_key(b))) { + g_assert_cmpstr(nm_wireguard_peer_get_preshared_key(a), !=, NULL); + g_assert_cmpstr(nm_wireguard_peer_get_preshared_key(b), ==, NULL); + g_assert(NM_IN_SET(nm_wireguard_peer_get_preshared_key_flags(a), + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NM_SETTING_SECRET_FLAG_NOT_SAVED)); + b_clone = nm_wireguard_peer_new_clone(b, TRUE); + if (!nm_wireguard_peer_set_preshared_key(b_clone, + nm_wireguard_peer_get_preshared_key(a), + TRUE)) + g_assert_not_reached(); + nm_setting_wireguard_set_peer(s_wg, b_clone, i); + b = nm_setting_wireguard_get_peer(s_wg, i); + g_assert(b == b_clone); + } else { + if (nm_wireguard_peer_get_preshared_key(a)) { + g_assert(NM_IN_SET(nm_wireguard_peer_get_preshared_key_flags(a), + NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_SECRET_FLAG_NOT_REQUIRED)); + } else { + g_assert(NM_IN_SET(nm_wireguard_peer_get_preshared_key_flags(a), + NM_SETTING_SECRET_FLAG_AGENT_OWNED, + NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_SECRET_FLAG_NOT_REQUIRED)); + } + } + + g_assert_cmpstr(nm_wireguard_peer_get_preshared_key(a), + ==, + nm_wireguard_peer_get_preshared_key(b)); + g_assert_cmpint(nm_wireguard_peer_cmp(a, b, NM_SETTING_COMPARE_FLAG_EXACT), ==, 0); + } +} + +static void +test_roundtrip_conversion(gconstpointer test_data) +{ + const int MODE = GPOINTER_TO_INT(test_data); + const char *ID = nm_sprintf_bufa(100, "roundtrip-conversion-%d", MODE); + const char *UUID = "63376701-b61e-4318-bf7e-664a1c1eeaab"; + const char *INTERFACE_NAME = nm_sprintf_bufa(100, "ifname%d", MODE); + guint32 ETH_MTU = nmtst_rand_select((guint32) 0u, nmtst_get_rand_uint32()); + const char *WG_PRIVATE_KEY = + nmtst_get_rand_bool() ? "yGXGK+5bVnxSJUejH4vbpXbq+ZtaG4NB8IHRK/aVtE0=" : NULL; + const NMSettingSecretFlags WG_PRIVATE_KEY_FLAGS = + nmtst_rand_select(NM_SETTING_SECRET_FLAG_NONE, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_SECRET_FLAG_AGENT_OWNED); + const guint WG_LISTEN_PORT = nmtst_rand_select(0u, nmtst_get_rand_uint32() % 0x10000); + const guint WG_FWMARK = nmtst_rand_select(0u, nmtst_get_rand_uint32()); + gs_unref_ptrarray GPtrArray *kf_data_arr = g_ptr_array_new_with_free_func(g_free); + gs_unref_ptrarray GPtrArray * wg_peers = NULL; + const NMConnectionSerializationFlags dbus_serialization_flags[] = { + NM_CONNECTION_SERIALIZE_ALL, + NM_CONNECTION_SERIALIZE_WITH_NON_SECRET, + NM_CONNECTION_SERIALIZE_WITH_SECRETS, + }; + guint dbus_serialization_flags_idx; + gs_unref_object NMConnection *con = NULL; + gs_free_error GError *error = NULL; + gs_free char * tmp_str = NULL; + guint kf_data_idx; + NMSettingConnection * s_con = NULL; + NMSettingWired * s_eth = NULL; + NMSettingWireGuard * s_wg = NULL; + union { + struct { + NMSettingIPConfig *s_6; + NMSettingIPConfig *s_4; + }; + NMSettingIPConfig *s_x[2]; + } s_ip; + int is_ipv4; + guint i; + gboolean success; + gs_free char *s390_keyfile_entries = NULL; + + switch (MODE) { + case 0: + con = nmtst_create_minimal_connection(ID, UUID, NM_SETTING_WIRED_SETTING_NAME, &s_con); + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, INTERFACE_NAME, NULL); + nmtst_connection_normalize(con); + + s_eth = NM_SETTING_WIRED(nm_connection_get_setting(con, NM_TYPE_SETTING_WIRED)); + g_assert(NM_IS_SETTING_WIRED(s_eth)); + + g_object_set(s_eth, NM_SETTING_WIRED_MTU, ETH_MTU, NULL); + + _rndt_wired_add_s390_options(s_eth, &s390_keyfile_entries); + + g_ptr_array_add( + kf_data_arr, + g_strdup_printf("[connection]\n" + "id=%s\n" + "uuid=%s\n" + "type=ethernet\n" + "interface-name=%s\n" + "permissions=\n" + "\n" + "[ethernet]\n" + "mac-address-blacklist=\n" + "%s" /* mtu */ + "\n" + "%s" /* [ethernet-s390-options] */ + "[ipv4]\n" + "dns-search=\n" + "method=auto\n" + "\n" + "[ipv6]\n" + "addr-gen-mode=stable-privacy\n" + "dns-search=\n" + "method=auto\n" + "\n" + "[proxy]\n" + "", + ID, + UUID, + INTERFACE_NAME, + (ETH_MTU != 0) ? nm_sprintf_bufa(100, "mtu=%u\n", ETH_MTU) : "", + s390_keyfile_entries)); + + g_ptr_array_add( + kf_data_arr, + g_strdup_printf("[connection]\n" + "id=%s\n" + "uuid=%s\n" + "type=ethernet\n" + "interface-name=%s\n" + "permissions=\n" + "\n" + "[ethernet]\n" + "mac-address-blacklist=\n" + "%s" /* mtu */ + "\n" + "%s" /* [ethernet-s390-options] */ + "[ipv4]\n" + "dns-search=\n" + "method=auto\n" + "\n" + "[ipv6]\n" + "addr-gen-mode=stable-privacy\n" + "dns-search=\n" + "method=auto\n" + "", + ID, + UUID, + INTERFACE_NAME, + (ETH_MTU != 0) ? nm_sprintf_bufa(100, "mtu=%d\n", (int) ETH_MTU) : "", + s390_keyfile_entries)); + + break; + + case 1: + con = nmtst_create_minimal_connection(ID, UUID, "wireguard", &s_con); + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, INTERFACE_NAME, NULL); + nmtst_connection_normalize(con); + + s_wg = NM_SETTING_WIREGUARD(nm_connection_get_setting(con, NM_TYPE_SETTING_WIREGUARD)); + + g_ptr_array_add(kf_data_arr, + g_strdup_printf("[connection]\n" + "id=%s\n" + "uuid=%s\n" + "type=wireguard\n" + "interface-name=%s\n" + "permissions=\n" + "\n" + "[wireguard]\n" + "\n" + "[ipv4]\n" + "dns-search=\n" + "method=disabled\n" + "\n" + "[ipv6]\n" + "addr-gen-mode=stable-privacy\n" + "dns-search=\n" + "method=ignore\n" + "\n" + "[proxy]\n" + "", + ID, + UUID, + INTERFACE_NAME)); + break; + + case 2: + con = nmtst_create_minimal_connection(ID, UUID, "wireguard", &s_con); + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, INTERFACE_NAME, NULL); + nmtst_connection_normalize(con); + + s_wg = NM_SETTING_WIREGUARD(nm_connection_get_setting(con, NM_TYPE_SETTING_WIREGUARD)); + g_object_set(s_wg, + NM_SETTING_WIREGUARD_PRIVATE_KEY, + WG_PRIVATE_KEY, + NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, + WG_PRIVATE_KEY_FLAGS, + NM_SETTING_WIREGUARD_LISTEN_PORT, + WG_LISTEN_PORT, + NM_SETTING_WIREGUARD_FWMARK, + WG_FWMARK, + NULL); + + wg_peers = _rndt_wg_peers_create(); + + for (i = 0; i < wg_peers->len; i++) + nm_setting_wireguard_append_peer(s_wg, wg_peers->pdata[i]); + + nm_clear_g_free(&tmp_str); + + g_ptr_array_add( + kf_data_arr, + g_strdup_printf( + "[connection]\n" + "id=%s\n" + "uuid=%s\n" + "type=wireguard\n" + "interface-name=%s\n" + "permissions=\n" + "\n" + "[wireguard]\n" + "%s" /* fwmark */ + "%s" /* listen-port */ + "%s" /* private-key-flags */ + "%s" /* private-key */ + "%s" /* [wireguard-peers*] */ + "\n" + "[ipv4]\n" + "dns-search=\n" + "method=disabled\n" + "\n" + "[ipv6]\n" + "addr-gen-mode=stable-privacy\n" + "dns-search=\n" + "method=ignore\n" + "\n" + "[proxy]\n" + "", + ID, + UUID, + INTERFACE_NAME, + ((WG_FWMARK != 0) ? nm_sprintf_bufa(100, "fwmark=%u\n", WG_FWMARK) : ""), + ((WG_LISTEN_PORT != 0) ? nm_sprintf_bufa(100, "listen-port=%u\n", WG_LISTEN_PORT) + : ""), + ((WG_PRIVATE_KEY_FLAGS != NM_SETTING_SECRET_FLAG_NONE) + ? nm_sprintf_bufa(100, "private-key-flags=%u\n", (guint) WG_PRIVATE_KEY_FLAGS) + : ""), + ((WG_PRIVATE_KEY && WG_PRIVATE_KEY_FLAGS == NM_SETTING_SECRET_FLAG_NONE) + ? nm_sprintf_bufa(100, "private-key=%s\n", WG_PRIVATE_KEY) + : ""), + _rndt_wg_peers_to_keyfile(wg_peers, TRUE, &tmp_str))); + + _rndt_wg_peers_assert_equal(s_wg, wg_peers, TRUE, TRUE, FALSE); + break; + + case 3: + con = nmtst_create_minimal_connection(ID, UUID, NM_SETTING_WIRED_SETTING_NAME, &s_con); + g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, INTERFACE_NAME, NULL); + nmtst_connection_normalize(con); + + s_eth = NM_SETTING_WIRED(nm_connection_get_setting(con, NM_TYPE_SETTING_WIRED)); + g_assert(NM_IS_SETTING_WIRED(s_eth)); + + g_object_set(s_eth, NM_SETTING_WIRED_MTU, ETH_MTU, NULL); + + s_ip.s_4 = NM_SETTING_IP_CONFIG(nm_connection_get_setting(con, NM_TYPE_SETTING_IP4_CONFIG)); + g_assert(NM_IS_SETTING_IP4_CONFIG(s_ip.s_4)); + + s_ip.s_6 = NM_SETTING_IP_CONFIG(nm_connection_get_setting(con, NM_TYPE_SETTING_IP6_CONFIG)); + g_assert(NM_IS_SETTING_IP6_CONFIG(s_ip.s_6)); + + for (is_ipv4 = 0; is_ipv4 < 2; is_ipv4++) { + g_assert(NM_IS_SETTING_IP_CONFIG(s_ip.s_x[is_ipv4])); + for (i = 0; i < 3; i++) { + char addrstr[NM_UTILS_INET_ADDRSTRLEN]; + + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rr = NULL; + + rr = nm_ip_routing_rule_new(is_ipv4 ? AF_INET : AF_INET6); + nm_ip_routing_rule_set_priority(rr, i + 1); + if (i > 0) { + if (is_ipv4) + nm_sprintf_buf(addrstr, "192.168.%u.0", i); + else + nm_sprintf_buf(addrstr, "1:2:3:%x::", 10 + i); + nm_ip_routing_rule_set_from(rr, addrstr, is_ipv4 ? 24 + i : 64 + i); + } + nm_ip_routing_rule_set_table(rr, 1000 + i); + + success = nm_ip_routing_rule_validate(rr, &error); + nmtst_assert_success(success, error); + + nm_setting_ip_config_add_routing_rule(s_ip.s_x[is_ipv4], rr); + } + } + + g_ptr_array_add( + kf_data_arr, + g_strdup_printf("[connection]\n" + "id=%s\n" + "uuid=%s\n" + "type=ethernet\n" + "interface-name=%s\n" + "permissions=\n" + "\n" + "[ethernet]\n" + "mac-address-blacklist=\n" + "%s" /* mtu */ + "\n" + "[ipv4]\n" + "dns-search=\n" + "method=auto\n" + "routing-rule1=priority 1 from 0.0.0.0/0 table 1000\n" + "routing-rule2=priority 2 from 192.168.1.0/25 table 1001\n" + "routing-rule3=priority 3 from 192.168.2.0/26 table 1002\n" + "\n" + "[ipv6]\n" + "addr-gen-mode=stable-privacy\n" + "dns-search=\n" + "method=auto\n" + "routing-rule1=priority 1 from ::/0 table 1000\n" + "routing-rule2=priority 2 from 1:2:3:b::/65 table 1001\n" + "routing-rule3=priority 3 from 1:2:3:c::/66 table 1002\n" + "\n" + "[proxy]\n" + "", + ID, + UUID, + INTERFACE_NAME, + (ETH_MTU != 0) ? nm_sprintf_bufa(100, "mtu=%u\n", ETH_MTU) : "")); + + break; + + default: + g_assert_not_reached(); + } + + /* the first kf_data_arr entry is special: it is the exact result of what we expect + * when converting @con to keyfile. Write @con to keyfile and compare the expected result + * literally. */ + { + nm_auto_unref_keyfile GKeyFile *kf = NULL; + + kf = nm_keyfile_write(con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success(kf, error); + + /* the first kf_data_arr entry is special: it must be what the writer would + * produce again. */ + nmtst_keyfile_assert_data(kf, kf_data_arr->pdata[0], -1); + } + + /* check that reading any of kf_data_arr yields the same result that we expect. */ + for (kf_data_idx = 0; kf_data_idx < kf_data_arr->len; kf_data_idx++) { + gs_unref_object NMConnection *con2 = NULL; + NMSettingWireGuard * s_wg2 = NULL; + NMSettingWired * s_eth2 = NULL; + + con2 = nmtst_create_connection_from_keyfile(kf_data_arr->pdata[kf_data_idx], + "/no/where/file.nmconnection"); + + switch (MODE) { + case 0: + s_eth2 = NM_SETTING_WIRED(nm_connection_get_setting(con2, NM_TYPE_SETTING_WIRED)); + g_assert(NM_IS_SETTING_WIRED(s_eth2)); + + if (ETH_MTU > (guint32) G_MAXINT && kf_data_idx == 1) { + /* older versions wrote values > 2^21 as signed integers, but the reader would + * always reject such negative values for G_TYPE_UINT. + * + * The test case kf_data_idx #1 still writes the values in the old style. + * The behavior was fixed, but such values are still rejected as invalid. + * + * Patch the setting so that the comparison below succeeds are usual. */ + g_assert_cmpint(nm_setting_wired_get_mtu(s_eth2), ==, 0); + g_object_set(s_eth2, NM_SETTING_WIRED_MTU, ETH_MTU, NULL); + } + + g_assert_cmpint(nm_setting_wired_get_mtu(s_eth), ==, ETH_MTU); + g_assert_cmpint(nm_setting_wired_get_mtu(s_eth2), ==, ETH_MTU); + + g_assert_cmpint(nm_setting_wired_get_num_s390_options(s_eth2), + ==, + nm_setting_wired_get_num_s390_options(s_eth)); + + break; + + case 1: + s_wg2 = + NM_SETTING_WIREGUARD(nm_connection_get_setting(con2, NM_TYPE_SETTING_WIREGUARD)); + g_assert(NM_IS_SETTING_WIREGUARD(s_wg2)); + + g_assert_cmpstr(nm_setting_wireguard_get_private_key(s_wg), ==, NULL); + g_assert_cmpstr(nm_setting_wireguard_get_private_key(s_wg2), ==, NULL); + break; + + case 2: + s_wg2 = + NM_SETTING_WIREGUARD(nm_connection_get_setting(con2, NM_TYPE_SETTING_WIREGUARD)); + g_assert(NM_IS_SETTING_WIREGUARD(s_wg2)); + + /* the private key was lost due to the secret-flags. Patch it. */ + if (WG_PRIVATE_KEY_FLAGS != NM_SETTING_SECRET_FLAG_NONE) { + g_assert_cmpstr(nm_setting_wireguard_get_private_key(s_wg2), ==, NULL); + g_object_set(s_wg2, NM_SETTING_WIREGUARD_PRIVATE_KEY, WG_PRIVATE_KEY, NULL); + } + + g_assert_cmpstr(nm_setting_wireguard_get_private_key(s_wg), ==, WG_PRIVATE_KEY); + g_assert_cmpstr(nm_setting_wireguard_get_private_key(s_wg2), ==, WG_PRIVATE_KEY); + + _rndt_wg_peers_assert_equal(s_wg2, wg_peers, TRUE, FALSE, FALSE); + _rndt_wg_peers_fix_secrets(s_wg2, wg_peers); + _rndt_wg_peers_assert_equal(s_wg2, wg_peers, TRUE, TRUE, FALSE); + break; + } + + nmtst_assert_connection_equals(con, nmtst_get_rand_bool(), con2, nmtst_get_rand_bool()); + } + + for (dbus_serialization_flags_idx = 0; + dbus_serialization_flags_idx < G_N_ELEMENTS(dbus_serialization_flags); + dbus_serialization_flags_idx++) { + NMConnectionSerializationFlags flag = + dbus_serialization_flags[dbus_serialization_flags_idx]; + gs_unref_variant GVariant *con_var = NULL; + gs_unref_object NMConnection *con2 = NULL; + NMSettingWireGuard * s_wg2 = NULL; + + con_var = nm_connection_to_dbus(con, flag); + g_assert(g_variant_is_of_type(con_var, NM_VARIANT_TYPE_CONNECTION)); + g_assert(g_variant_is_floating(con_var)); + g_variant_ref_sink(con_var); + + if (flag == NM_CONNECTION_SERIALIZE_ALL) { + con2 = _connection_new_from_dbus_strict(con_var, TRUE); + nmtst_assert_connection_equals(con, nmtst_get_rand_bool(), con2, nmtst_get_rand_bool()); + + { + nm_auto_unref_keyfile GKeyFile *kf = NULL; + + kf = nm_keyfile_write(con2, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success(kf, error); + nmtst_keyfile_assert_data(kf, kf_data_arr->pdata[0], -1); + } + } + + switch (MODE) { + case 2: + if (flag == NM_CONNECTION_SERIALIZE_ALL) { + s_wg2 = NM_SETTING_WIREGUARD( + nm_connection_get_setting(con2, NM_TYPE_SETTING_WIREGUARD)); + + if (flag == NM_CONNECTION_SERIALIZE_ALL) + _rndt_wg_peers_assert_equal(s_wg2, wg_peers, TRUE, TRUE, FALSE); + else if (flag == NM_CONNECTION_SERIALIZE_WITH_NON_SECRET) + _rndt_wg_peers_assert_equal(s_wg2, wg_peers, FALSE, FALSE, TRUE); + else + g_assert_not_reached(); + } + break; + } + } +} + +/*****************************************************************************/ + +static NMIPRoutingRule * +_rr_from_str_get_impl(const char *str, const char *const *aliases) +{ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rr = NULL; + gs_free_error GError * error = NULL; + gboolean vbool; + int addr_family; + int i; + NMIPRoutingRuleAsStringFlags to_string_flags; + + rr = nm_ip_routing_rule_from_string(str, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE, + NULL, + &error); + nmtst_assert_success(rr, error); + + addr_family = nm_ip_routing_rule_get_addr_family(rr); + g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6)); + + if (addr_family == AF_INET) + to_string_flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET; + else + to_string_flags = NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET6; + + for (i = 0; TRUE; i++) { + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rr2 = NULL; + gs_free char * str1 = NULL; + gs_unref_variant GVariant *variant1 = NULL; + const char * cstr1; + + switch (i) { + case 0: + rr2 = nm_ip_routing_rule_ref(rr); + break; + + case 1: + rr2 = nm_ip_routing_rule_from_string( + str, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (nmtst_get_rand_bool() ? to_string_flags + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE), + NULL, + &error); + nmtst_assert_success(rr, error); + break; + + case 2: + str1 = nm_ip_routing_rule_to_string( + rr, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (nmtst_get_rand_bool() ? to_string_flags + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE), + NULL, + &error); + nmtst_assert_success(str1 && str1[0], error); + + g_assert_cmpstr(str, ==, str1); + + rr2 = nm_ip_routing_rule_from_string( + str1, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (nmtst_get_rand_bool() ? to_string_flags + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE), + NULL, + &error); + nmtst_assert_success(rr, error); + break; + + case 3: + variant1 = nm_ip_routing_rule_to_dbus(rr); + g_assert(variant1); + g_assert(g_variant_is_floating(variant1)); + g_assert(g_variant_is_of_type(variant1, G_VARIANT_TYPE_VARDICT)); + + rr2 = nm_ip_routing_rule_from_dbus(variant1, TRUE, &error); + nmtst_assert_success(rr, error); + break; + + default: + if (!aliases || !aliases[0]) + goto done; + cstr1 = (aliases++)[0]; + rr2 = nm_ip_routing_rule_from_string( + cstr1, + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_VALIDATE + | (nmtst_get_rand_bool() ? to_string_flags + : NM_IP_ROUTING_RULE_AS_STRING_FLAGS_NONE), + NULL, + &error); + nmtst_assert_success(rr, error); + break; + } + + g_assert(rr2); + vbool = nm_ip_routing_rule_validate(rr, &error); + nmtst_assert_success(vbool, error); + vbool = nm_ip_routing_rule_validate(rr2, &error); + nmtst_assert_success(vbool, error); + + g_assert_cmpint(nm_ip_routing_rule_cmp(rr, rr2), ==, 0); + g_assert_cmpint(nm_ip_routing_rule_cmp(rr2, rr), ==, 0); + } + +done: + return g_steal_pointer(&rr); +} +#define _rr_from_str_get(a, ...) _rr_from_str_get_impl(a, &(NM_MAKE_STRV(NULL, ##__VA_ARGS__))[1]) + +#define _rr_from_str(...) \ + G_STMT_START \ + { \ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *_rr = NULL; \ + \ + _rr = _rr_from_str_get(__VA_ARGS__); \ + g_assert(_rr); \ + } \ + G_STMT_END + +static void +test_routing_rule(gconstpointer test_data) +{ + nm_auto_unref_ip_routing_rule NMIPRoutingRule *rr1 = NULL; + gboolean success; + char ifname_buf[16]; + gs_free_error GError *error = NULL; + + _rr_from_str("priority 5 from 0.0.0.0 table 1", " from 0.0.0.0 priority 5 lookup 1 "); + _rr_from_str("priority 5 from 0.0.0.0/0 table 4"); + _rr_from_str("priority 5 to 0.0.0.0 table 6"); + _rr_from_str("priority 5 to 0.0.0.0 table 254", "priority 5 to 0.0.0.0/32"); + _rr_from_str("priority 5 from 1.2.3.4 table 15", + "priority 5 from 1.2.3.4/32 table 0xF ", + "priority 5 from 1.2.3.4/32 to 0.0.0.0/0 lookup 15 "); + _rr_from_str("priority 5 from 1.2.3.4 to 0.0.0.0 table 8"); + _rr_from_str("priority 5 to a:b:c:: tos 0x16 table 25", + "priority 5 to a:b:c::/128 table 0x19 tos 16", + "priority 5 to a:b:c::/128 lookup 0x19 dsfield 16", + "priority 5 to a:b:c::/128 lookup 0x19 dsfield 16 fwmark 0/0x00", + "priority 5 to a:b:c:: from all lookup 0x19 dsfield 16 fwmark 0x0/0"); + _rr_from_str("priority 5 from :: fwmark 0 table 25", + "priority 5 from ::/128 to all table 0x19 fwmark 0/0xFFFFFFFF", + "priority 5 from :: to ::/0 table 0x19 fwmark 0x00/4294967295"); + _rr_from_str("priority 5 from :: iif aab table 25"); + _rr_from_str("priority 5 from :: iif aab oif er table 25", + "priority 5 from :: table 0x19 dev aab oif er"); + _rr_from_str("priority 5 from :: iif a\\\\303b table 25"); + _rr_from_str("priority 5 to 0.0.0.0 sport 10 table 6", + "priority 5 to 0.0.0.0 sport 10-10 table 6"); + _rr_from_str("priority 5 not to 0.0.0.0 dport 10-133 table 6", + "not priority 5 to 0.0.0.0 dport 10-133 table 6", + "not priority 5 not to 0.0.0.0 dport 10-133 table 6", + "priority 5 to 0.0.0.0 not dport 10-133 not table 6", + "priority 5 to 0.0.0.0 not dport 10-\\ 133 not table 6"); + _rr_from_str("priority 5 to 0.0.0.0 ipproto 10 sport 10 table 6"); + _rr_from_str("priority 5 to 0.0.0.0 type blackhole", "priority 5 to 0.0.0.0 blackhole"); + + rr1 = _rr_from_str_get("priority 5 from :: iif aab table 25"); + g_assert_cmpstr(nm_ip_routing_rule_get_iifname(rr1), ==, "aab"); + success = nm_ip_routing_rule_get_xifname_bin(rr1, FALSE, ifname_buf); + g_assert(!success); + success = nm_ip_routing_rule_get_xifname_bin(rr1, TRUE, ifname_buf); + g_assert_cmpstr(ifname_buf, ==, "aab"); + g_assert(success); + nm_clear_pointer(&rr1, nm_ip_routing_rule_unref); + + rr1 = _rr_from_str_get("priority 5 from :: iif a\\\\303\\\\261xb table 254"); + g_assert_cmpstr(nm_ip_routing_rule_get_iifname(rr1), ==, "a\\303\\261xb"); + success = nm_ip_routing_rule_get_xifname_bin(rr1, FALSE, ifname_buf); + g_assert(!success); + success = nm_ip_routing_rule_get_xifname_bin(rr1, TRUE, ifname_buf); + g_assert_cmpstr(ifname_buf, ==, "a\303\261xb"); + g_assert(success); + nm_clear_pointer(&rr1, nm_ip_routing_rule_unref); + + rr1 = _rr_from_str_get("priority 5 from :: oif \\\\101=\\\\303\\\\261xb table 7"); + g_assert_cmpstr(nm_ip_routing_rule_get_oifname(rr1), ==, "\\101=\\303\\261xb"); + success = nm_ip_routing_rule_get_xifname_bin(rr1, FALSE, ifname_buf); + g_assert_cmpstr(ifname_buf, ==, "A=\303\261xb"); + g_assert(success); + success = nm_ip_routing_rule_get_xifname_bin(rr1, TRUE, ifname_buf); + g_assert(!success); + nm_clear_pointer(&rr1, nm_ip_routing_rule_unref); + + rr1 = _rr_from_str_get("priority 5 to 0.0.0.0 tos 0x10 table 7"); + g_assert_cmpstr(NULL, ==, nm_ip_routing_rule_get_from(rr1)); + g_assert(!nm_ip_routing_rule_get_from_bin(rr1)); + g_assert_cmpint(0, ==, nm_ip_routing_rule_get_from_len(rr1)); + g_assert_cmpstr("0.0.0.0", ==, nm_ip_routing_rule_get_to(rr1)); + g_assert(nm_ip_addr_is_null(AF_INET, nm_ip_routing_rule_get_to_bin(rr1))); + g_assert_cmpint(32, ==, nm_ip_routing_rule_get_to_len(rr1)); + g_assert_cmpint(7, ==, nm_ip_routing_rule_get_table(rr1)); + g_assert_cmpint(0x10, ==, nm_ip_routing_rule_get_tos(rr1)); + nm_clear_pointer(&rr1, nm_ip_routing_rule_unref); + + rr1 = _rr_from_str_get("priority 5 from :: iif a\\\\303\\\\261,x;b table 254", + "priority 5 from :: iif a\\\\303\\\\261,x;b table 254"); + g_assert_cmpstr(nm_ip_routing_rule_get_iifname(rr1), ==, "a\\303\\261,x;b"); + success = nm_ip_routing_rule_get_xifname_bin(rr1, FALSE, ifname_buf); + g_assert(!success); + success = nm_ip_routing_rule_get_xifname_bin(rr1, TRUE, ifname_buf); + g_assert_cmpstr(ifname_buf, ==, "a\303\261,x;b"); + g_assert(success); + nm_clear_pointer(&rr1, nm_ip_routing_rule_unref); + + rr1 = nm_ip_routing_rule_from_string("priority 6 blackhole", + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET, + NULL, + &error); + nmtst_assert_success(rr1, error); + nm_clear_pointer(&rr1, nm_ip_routing_rule_unref); + nm_clear_error(&error); + + rr1 = nm_ip_routing_rule_from_string("priority 6 bogus", + NM_IP_ROUTING_RULE_AS_STRING_FLAGS_AF_INET, + NULL, + &error); + nmtst_assert_no_success(rr1, error); + nm_clear_pointer(&rr1, nm_ip_routing_rule_unref); + nm_clear_error(&error); +} + +/*****************************************************************************/ + +static void +test_parse_tc_handle(void) +{ +#define _parse_tc_handle(str, exp) \ + G_STMT_START \ + { \ + gs_free_error GError *_error = NULL; \ + GError ** _perror = nmtst_get_rand_bool() ? &_error : NULL; \ + guint32 _v; \ + const guint32 _v_exp = (exp); \ + \ + _v = _nm_utils_parse_tc_handle("" str "", _perror); \ + \ + if (_v != _v_exp) \ + g_error("%s:%d: \"%s\" gave %08x but %08x expected.", \ + __FILE__, \ + __LINE__, \ + "" str "", \ + _v, \ + _v_exp); \ + \ + if (_v == TC_H_UNSPEC) \ + g_assert(!_perror || *_perror); \ + else \ + g_assert(!_perror || !*_perror); \ + } \ + G_STMT_END + +#define _parse_tc_handle_inval(str) _parse_tc_handle(str, TC_H_UNSPEC) +#define _parse_tc_handle_valid(str, maj, min) \ + _parse_tc_handle(str, TC_H_MAKE(((guint32) (maj)) << 16, ((guint16) (min)))) + + _parse_tc_handle_inval(""); + _parse_tc_handle_inval(" "); + _parse_tc_handle_inval(" \n"); + _parse_tc_handle_valid("1", 1, 0); + _parse_tc_handle_valid(" 1 ", 1, 0); + _parse_tc_handle_valid("1:", 1, 0); + _parse_tc_handle_valid("1: ", 1, 0); + _parse_tc_handle_valid("1:0", 1, 0); + _parse_tc_handle_valid("1 :0", 1, 0); + _parse_tc_handle_valid("1 \t\n\f\r:0", 1, 0); + _parse_tc_handle_inval("1 \t\n\f\r\v:0"); + _parse_tc_handle_valid(" 1 : 0 ", 1, 0); + _parse_tc_handle_inval(" \t\v\n1: 0"); + _parse_tc_handle_valid("1:2", 1, 2); + _parse_tc_handle_valid("01:02", 1, 2); + _parse_tc_handle_inval("0x01:0x02"); + _parse_tc_handle_valid(" 01: 02", 1, 2); + _parse_tc_handle_valid("019: 020", 0x19, 0x20); + _parse_tc_handle_valid("FFFF: 020", 0xFFFF, 0x20); + _parse_tc_handle_valid("FfFF: ffff", 0xFFFF, 0xFFFF); + _parse_tc_handle_valid("FFFF", 0xFFFF, 0); + _parse_tc_handle_inval("0xFFFF"); + _parse_tc_handle_inval("10000"); + _parse_tc_handle_valid("\t\n\f\r FFFF", 0xFFFF, 0); + _parse_tc_handle_inval("\t\n\f\r \vFFFF"); +} + +/*****************************************************************************/ + +static void +test_empty_setting(void) +{ + gs_unref_object NMConnection *con = NULL; + gs_unref_object NMConnection *con2 = NULL; + NMSettingBluetooth * s_bt; + NMSettingGsm * s_gsm; + nm_auto_unref_keyfile GKeyFile *kf = NULL; + gs_free_error GError *error = NULL; + + con = nmtst_create_minimal_connection("bt-empty-gsm", + "dca3192a-f2dc-48eb-b806-d0ff788f122c", + NM_SETTING_BLUETOOTH_SETTING_NAME, + NULL); + + s_bt = _nm_connection_get_setting(con, NM_TYPE_SETTING_BLUETOOTH); + g_object_set(s_bt, + NM_SETTING_BLUETOOTH_TYPE, + "dun", + NM_SETTING_BLUETOOTH_BDADDR, + "aa:bb:cc:dd:ee:ff", + NULL); + + s_gsm = NM_SETTING_GSM(nm_setting_gsm_new()); + nm_connection_add_setting(con, NM_SETTING(s_gsm)); + + nmtst_connection_normalize(con); + + nmtst_assert_connection_verifies_without_normalization(con); + + kf = nm_keyfile_write(con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error); + nmtst_assert_success(kf, error); + + g_assert(g_key_file_has_group(kf, "gsm")); + g_assert_cmpint(nmtst_keyfile_get_num_keys(kf, "gsm"), ==, 0); + + con2 = nm_keyfile_read(kf, + "/ignored/current/working/directory/for/loading/relative/paths", + NM_KEYFILE_HANDLER_FLAGS_NONE, + NULL, + NULL, + &error); + nmtst_assert_success(con2, error); + + g_assert(nm_connection_get_setting(con2, NM_TYPE_SETTING_GSM)); + + nmtst_assert_connection_verifies_without_normalization(con2); +} + +/*****************************************************************************/ + +static guint +_PROP_IDX_PACK(NMMetaSettingType meta_type, guint idx) +{ + return (((guint) meta_type) & 0xFFu) | (idx << 8); +} + +static const char * +_PROP_IDX_OWNER(GHashTable *h_property_types, const NMSettInfoPropertType *property_type) +{ + const NMSettInfoSetting *sett_info_settings = nmtst_sett_info_settings(); + const NMSettInfoSetting *sis; + const NMMetaSettingInfo *msi; + GArray * arr; + guint idx; + NMMetaSettingType meta_type; + guint prop_idx; + char sbuf[300]; + + g_assert(h_property_types); + g_assert(property_type); + + arr = g_hash_table_lookup(h_property_types, property_type); + + g_assert(arr); + g_assert(arr->len > 0); + + idx = g_array_index(arr, guint, 0); + + meta_type = (idx & 0xFFu); + prop_idx = idx >> 8; + + g_assert(meta_type < _NM_META_SETTING_TYPE_NUM); + + sis = &sett_info_settings[meta_type]; + msi = &nm_meta_setting_infos[meta_type]; + + g_assert(prop_idx < sis->property_infos_len); + + nm_sprintf_buf(sbuf, "%s.%s", msi->setting_name, sis->property_infos[prop_idx].name); + + return g_intern_string(sbuf); +} + +static void +test_setting_metadata(void) +{ + const NMSettInfoSetting *sett_info_settings = nmtst_sett_info_settings(); + NMMetaSettingType meta_type; + gs_unref_hashtable GHashTable *h_property_types = NULL; + + G_STATIC_ASSERT(_NM_META_SETTING_TYPE_NUM == NM_META_SETTING_TYPE_UNKNOWN); + + h_property_types = + g_hash_table_new_full(nm_direct_hash, NULL, NULL, (GDestroyNotify) g_array_unref); + + for (meta_type = 0; meta_type < _NM_META_SETTING_TYPE_NUM; meta_type++) { + const NMMetaSettingInfo *msi = &nm_meta_setting_infos[meta_type]; + nm_auto_unref_gtypeclass NMSettingClass *klass = NULL; + GType gtype; + + g_assert(msi->setting_name); + g_assert(msi->get_setting_gtype); + g_assert(msi->meta_type == meta_type); + g_assert(msi->setting_priority >= NM_SETTING_PRIORITY_CONNECTION); + g_assert(msi->setting_priority <= NM_SETTING_PRIORITY_USER); + + if (meta_type > 0) + g_assert_cmpint( + strcmp(nm_meta_setting_infos[meta_type - 1].setting_name, msi->setting_name), + <, + 0); + + gtype = msi->get_setting_gtype(); + + g_assert(g_type_is_a(gtype, NM_TYPE_SETTING)); + g_assert(gtype != NM_TYPE_SETTING); + + klass = g_type_class_ref(gtype); + g_assert(klass); + g_assert(NM_IS_SETTING_CLASS(klass)); + + g_assert(msi == klass->setting_info); + } + + g_assert(sett_info_settings); + + for (meta_type = 0; meta_type < _NM_META_SETTING_TYPE_NUM; meta_type++) { + const NMSettInfoSetting *sis = &sett_info_settings[meta_type]; + const NMMetaSettingInfo *msi = &nm_meta_setting_infos[meta_type]; + gs_unref_hashtable GHashTable *h_properties = NULL; + GType gtype; + gs_unref_object NMSetting *setting = NULL; + guint prop_idx; + gs_free GParamSpec **property_specs = NULL; + guint n_property_specs; + + g_assert(sis); + + g_assert(NM_IS_SETTING_CLASS(sis->setting_class)); + + gtype = msi->get_setting_gtype(); + + g_assert(G_TYPE_FROM_CLASS(sis->setting_class) == gtype); + + setting = g_object_new(gtype, NULL); + + g_assert(NM_IS_SETTING(setting)); + + g_assert_cmpint(sis->property_infos_len, >, 0); + g_assert(sis->property_infos); + + h_properties = g_hash_table_new(nm_str_hash, g_str_equal); + + for (prop_idx = 0; prop_idx < sis->property_infos_len; prop_idx++) { + const NMSettInfoProperty *sip = &sis->property_infos[prop_idx]; + GArray * property_types_data; + guint prop_idx_val; + gboolean can_set_including_default = FALSE; + + g_assert(sip->name); + + if (prop_idx > 0) + g_assert_cmpint(strcmp(sis->property_infos[prop_idx - 1].name, sip->name), <, 0); + + g_assert(sip->property_type); + g_assert(sip->property_type->dbus_type); + g_assert(g_variant_type_string_is_valid((const char *) sip->property_type->dbus_type)); + + if (!sip->property_type->to_dbus_fcn) { + /* it's allowed to have no to_dbus_fcn(), to ignore a property. But such + * properties must not have a param_spec and no gprop_to_dbus_fcn. */ + g_assert(!sip->param_spec); + g_assert(!sip->to_dbus_data.none); + } else if (sip->property_type->to_dbus_fcn == _nm_setting_property_to_dbus_fcn_gprop) { + g_assert(sip->param_spec); + switch (sip->property_type->typdata_to_dbus.gprop_type) { + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_BYTES: + g_assert(sip->param_spec->value_type == G_TYPE_BYTES); + goto check_done; + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_ENUM: + g_assert(g_type_is_a(sip->param_spec->value_type, G_TYPE_ENUM)); + goto check_done; + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_FLAGS: + g_assert(g_type_is_a(sip->param_spec->value_type, G_TYPE_FLAGS)); + goto check_done; + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_GARRAY_UINT: + g_assert(sip->param_spec->value_type == G_TYPE_ARRAY); + goto check_done; + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_STRDICT: + g_assert(sip->param_spec->value_type == G_TYPE_HASH_TABLE); + goto check_done; + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_MAC_ADDRESS: + g_assert(sip->param_spec->value_type == G_TYPE_STRING); + goto check_done; + case NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_DEFAULT: + goto check_done; + } + g_assert_not_reached(); +check_done:; + if (sip->property_type->typdata_to_dbus.gprop_type + != NM_SETTING_PROPERTY_TO_DBUS_FCN_GPROP_TYPE_DEFAULT) + g_assert(!sip->to_dbus_data.gprop_to_dbus_fcn); + can_set_including_default = TRUE; + } else if (sip->property_type->to_dbus_fcn + == _nm_setting_property_to_dbus_fcn_get_boolean) { + g_assert(sip->param_spec); + g_assert(sip->param_spec->value_type == G_TYPE_BOOLEAN); + g_assert(sip->to_dbus_data.get_boolean); + can_set_including_default = TRUE; + } else if (sip->property_type->to_dbus_fcn + == _nm_setting_property_to_dbus_fcn_get_string) { + g_assert(sip->param_spec); + g_assert(sip->param_spec->value_type == G_TYPE_STRING); + g_assert(sip->to_dbus_data.get_string); + } + + if (!can_set_including_default) + g_assert(!sip->to_dbus_data.including_default); + + g_assert(!sip->property_type->from_dbus_fcn + || !sip->property_type->gprop_from_dbus_fcn); + + if (!g_hash_table_insert(h_properties, (char *) sip->name, sip->param_spec)) + g_assert_not_reached(); + + property_types_data = g_hash_table_lookup(h_property_types, sip->property_type); + if (!property_types_data) { + property_types_data = g_array_new(FALSE, FALSE, sizeof(guint)); + if (!g_hash_table_insert(h_property_types, + (gpointer) sip->property_type, + property_types_data)) + g_assert_not_reached(); + } + prop_idx_val = _PROP_IDX_PACK(meta_type, prop_idx); + g_array_append_val(property_types_data, prop_idx_val); + + if (sip->param_spec) { + nm_auto_unset_gvalue GValue val = G_VALUE_INIT; + + g_assert_cmpstr(sip->name, ==, sip->param_spec->name); + + g_value_init(&val, sip->param_spec->value_type); + g_object_get_property(G_OBJECT(setting), sip->name, &val); + + if (sip->param_spec->value_type == G_TYPE_STRING) { + const char *default_value; + + default_value = ((const GParamSpecString *) sip->param_spec)->default_value; + if (default_value) { + /* having a string property with a default != NULL is really ugly. They + * should be best avoided... */ + if (meta_type == NM_META_SETTING_TYPE_DCB + && nm_streq(sip->name, NM_SETTING_DCB_APP_FCOE_MODE)) { + /* Whitelist the properties that have a non-NULL default value. */ + g_assert_cmpstr(default_value, ==, NM_SETTING_DCB_FCOE_MODE_FABRIC); + } else + g_assert_not_reached(); + } + + if (nm_streq(sip->name, NM_SETTING_NAME)) + g_assert_cmpstr(g_value_get_string(&val), ==, msi->setting_name); + else + g_assert_cmpstr(g_value_get_string(&val), ==, default_value); + } + + if (NM_FLAGS_HAS(sip->param_spec->flags, NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS)) + g_assert(sip->property_type->to_dbus_fcn); + } + } + + /* check that all GObject based properties are tracked by the settings. */ + property_specs = + g_object_class_list_properties(G_OBJECT_CLASS(sis->setting_class), &n_property_specs); + g_assert(property_specs); + g_assert_cmpint(n_property_specs, >, 0); + for (prop_idx = 0; prop_idx < n_property_specs; prop_idx++) { + const GParamSpec *pip = property_specs[prop_idx]; + + g_assert(g_hash_table_lookup(h_properties, pip->name) == pip); + } + + /* check that property_infos_sorted is as expected. */ + if (sis->property_infos_sorted) { + gs_unref_hashtable GHashTable *h = g_hash_table_new(nm_direct_hash, NULL); + + /* property_infos_sorted is only implemented for [connection] type */ + g_assert_cmpint(meta_type, ==, NM_META_SETTING_TYPE_CONNECTION); + + /* ensure that there are no duplicates, and that all properties are also + * tracked by sis->property_infos. */ + for (prop_idx = 0; prop_idx < sis->property_infos_len; prop_idx++) { + const NMSettInfoProperty *sip = sis->property_infos_sorted[prop_idx]; + + if (!g_hash_table_add(h, (gpointer) sip)) + g_assert_not_reached(); + } + for (prop_idx = 0; prop_idx < sis->property_infos_len; prop_idx++) { + const NMSettInfoProperty *sip = &sis->property_infos[prop_idx]; + + g_assert(g_hash_table_contains(h, sip)); + } + } else + g_assert_cmpint(meta_type, !=, NM_META_SETTING_TYPE_CONNECTION); + + /* consistency check for gendata-info. */ + if (sis->detail.gendata_info) { + g_assert_cmpint(meta_type, ==, NM_META_SETTING_TYPE_ETHTOOL); + g_assert(sis->detail.gendata_info->get_variant_type); + + /* the gendata info based setting has only one regular property: the "name". */ + g_assert_cmpint(sis->property_infos_len, ==, 1); + g_assert_cmpstr(sis->property_infos[0].name, ==, NM_SETTING_NAME); + } else + g_assert_cmpint(meta_type, !=, NM_META_SETTING_TYPE_ETHTOOL); + } + + { + gs_free NMSettInfoPropertType **a_property_types = NULL; + guint a_property_types_len; + guint prop_idx; + guint prop_idx_2; + + a_property_types = + (NMSettInfoPropertType **) g_hash_table_get_keys_as_array(h_property_types, + &a_property_types_len); + + for (prop_idx = 0; prop_idx < a_property_types_len; prop_idx++) { + const NMSettInfoPropertType *pt = a_property_types[prop_idx]; + + for (prop_idx_2 = prop_idx + 1; prop_idx_2 < a_property_types_len; prop_idx_2++) { + const NMSettInfoPropertType *pt_2 = a_property_types[prop_idx_2]; + + if (!g_variant_type_equal(pt->dbus_type, pt_2->dbus_type) + || pt->to_dbus_fcn != pt_2->to_dbus_fcn + || pt->from_dbus_fcn != pt_2->from_dbus_fcn + || pt->missing_from_dbus_fcn != pt_2->missing_from_dbus_fcn + || pt->gprop_from_dbus_fcn != pt_2->gprop_from_dbus_fcn + || memcmp(&pt->typdata_to_dbus, + &pt_2->typdata_to_dbus, + sizeof(pt->typdata_to_dbus)) + != 0) + continue; + + if ((pt == &nm_sett_info_propert_type_plain_i + && pt_2 == &nm_sett_info_propert_type_deprecated_ignore_i) + || (pt_2 == &nm_sett_info_propert_type_plain_i + && pt == &nm_sett_info_propert_type_deprecated_ignore_i) + || (pt == &nm_sett_info_propert_type_plain_u + && pt_2 == &nm_sett_info_propert_type_deprecated_ignore_u) + || (pt_2 == &nm_sett_info_propert_type_plain_u + && pt == &nm_sett_info_propert_type_deprecated_ignore_u)) { + /* These are known to be duplicated. This is the case for + * "gsm.network-type" and plain properties like "802-11-wireless-security.fils" ("i" D-Bus type) + * "gsm.allowed-bands" and plain properties like "802-11-olpc-mesh.channel" ("u" D-Bus type) + * While the content/behaviour of the property types are identical, their purpose + * is different. So allow them. + */ + continue; + } + + /* the property-types with same content should all be shared. Here we have two that + * are the same content, but different instances. Bug. */ + g_error("The identical property type for D-Bus type \"%s\" is used by: %s and %s. " + "If a NMSettInfoPropertType is identical, it should be shared by creating " + "a common instance of the property type", + (const char *) pt->dbus_type, + _PROP_IDX_OWNER(h_property_types, pt), + _PROP_IDX_OWNER(h_property_types, pt_2)); + } + } + } +} + +/*****************************************************************************/ + +static void +test_setting_connection_secondaries_verify(void) +{ + gs_unref_object NMConnection *con = NULL; + NMSettingConnection * s_con; + guint i_run; + guint i_word; + + con = nmtst_create_minimal_connection("test-sec", NULL, NM_SETTING_WIRED_SETTING_NAME, &s_con); + nmtst_connection_normalize(con); + + for (i_run = 0; i_run < 100; i_run++) { + guint word_len = nmtst_get_rand_word_length(NULL); + gs_unref_ptrarray GPtrArray *arr = NULL; + gs_unref_ptrarray GPtrArray *arr_norm = NULL; + gboolean was_normalized; + gboolean was_normalized2; + + /* create a random list of invalid, normalizable and normalized UUIDs. */ + arr = g_ptr_array_new(); + for (i_word = 0; i_word < word_len; i_word++) { + g_ptr_array_add(arr, + (char *) nmtst_rand_select((const char *) "", + "52c3feb9-3aa2-46f6-a07b-1765918699eb", + "52C3feb9-3aa2-46f6-a07b-1765918699eb", + "52C3feb9-3aa2-46f6-a07b-1765918699eb", + "f86dfb13df764894ab3836b7cdb9d82dde8b27c4", + "52C3feb93-aa2-46f6-a07b-1765918699eb", + "bogus")); + } + g_ptr_array_add(arr, NULL); + + /* set the new list of secondaries, and assert that the result is as expected. */ + + nmtst_assert_connection_verifies_without_normalization(con); + + g_object_set(s_con, NM_SETTING_CONNECTION_SECONDARIES, arr->pdata, NULL); + +#define _assert_secondaries(s_con, expected) \ + G_STMT_START \ + { \ + NMSettingConnection *const _s_con = (s_con); \ + const char *const * _expected = (expected); \ + GArray * _secondaries; \ + const guint _expected_len = NM_PTRARRAY_LEN(_expected); \ + gs_strfreev char ** _sec_strv = NULL; \ + guint _i; \ + \ + g_assert(_expected); \ + \ + if (nmtst_get_rand_bool()) { \ + _secondaries = _nm_setting_connection_get_secondaries(_s_con); \ + g_assert_cmpint(_expected_len, ==, nm_g_array_len(_secondaries)); \ + g_assert((_expected_len == 0) == (!_secondaries)); \ + g_assert(nm_utils_strv_equal(_expected, \ + _secondaries ? nm_strvarray_get_strv(&_secondaries, NULL) \ + : NM_PTRARRAY_EMPTY(const char *))); \ + } \ + \ + if (nmtst_get_rand_bool()) { \ + g_object_get(_s_con, NM_SETTING_CONNECTION_SECONDARIES, &_sec_strv, NULL); \ + g_assert_cmpint(_expected_len, ==, NM_PTRARRAY_LEN(_sec_strv)); \ + g_assert((_expected_len == 0) == (!_sec_strv)); \ + g_assert(nm_utils_strv_equal(_expected, _sec_strv ?: NM_STRV_EMPTY())); \ + } \ + \ + g_assert_cmpint(nm_setting_connection_get_num_secondaries(_s_con), ==, _expected_len); \ + if (nmtst_get_rand_bool()) { \ + for (_i = 0; _i < _expected_len; _i++) { \ + g_assert_cmpstr(nm_setting_connection_get_secondary(_s_con, _i), \ + ==, \ + _expected[_i]); \ + } \ + g_assert_null(nm_setting_connection_get_secondary(_s_con, _expected_len)); \ + } \ + } \ + G_STMT_END + + _assert_secondaries(s_con, (const char *const *) arr->pdata); + + /* reimplement the normalization that we expect to happen and + * create an array @arr_norm with the expected result after normalization. */ + arr_norm = g_ptr_array_new_with_free_func(g_free); + for (i_word = 0; i_word < word_len; i_word++) { + const char *s = arr->pdata[i_word]; + gboolean is_normalized; + char uuid_normalized[37]; + + if (!nm_uuid_is_valid_nm(s, &is_normalized, uuid_normalized)) + continue; + + if (is_normalized) + s = uuid_normalized; + + if (nm_utils_strv_find_first((char **) arr_norm->pdata, arr_norm->len, s) >= 0) + continue; + + g_ptr_array_add(arr_norm, g_strdup(s)); + } + g_ptr_array_add(arr_norm, NULL); + + was_normalized = !nm_utils_strv_equal((char **) arr->pdata, (char **) arr_norm->pdata); + + if (was_normalized) + nmtst_assert_connection_verifies_and_normalizable(con); + else + nmtst_assert_connection_verifies_without_normalization(con); + + if (was_normalized || nmtst_get_rand_bool()) { + was_normalized2 = nmtst_connection_normalize(con); + g_assert(was_normalized == was_normalized2); + } + + _assert_secondaries(s_con, (const char *const *) arr_norm->pdata); + } +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init(&argc, &argv, TRUE); + + g_test_add_func("/libnm/test_connection_uuid", test_connection_uuid); + + g_test_add_func("/libnm/settings/test_nm_meta_setting_types_by_priority", + test_nm_meta_setting_types_by_priority); + + g_test_add_data_func("/libnm/setting-8021x/key-and-cert", + "test_key_and_cert.pem, test", + test_8021x); + g_test_add_data_func("/libnm/setting-8021x/key-only", "test-key-only.pem, test", test_8021x); + g_test_add_data_func("/libnm/setting-8021x/pkcs8-enc-key", + "pkcs8-enc-key.pem, 1234567890", + test_8021x); + g_test_add_data_func("/libnm/setting-8021x/pkcs12", "test-cert.p12, test", test_8021x); + + g_test_add_func("/libnm/settings/test_setting_connection_secondaries_verify", + test_setting_connection_secondaries_verify); + + g_test_add_func("/libnm/settings/bond/verify", test_bond_verify); + g_test_add_func("/libnm/settings/bond/compare", test_bond_compare); + g_test_add_func("/libnm/settings/bond/normalize", test_bond_normalize); + + g_test_add_func("/libnm/settings/dcb/flags-valid", test_dcb_flags_valid); + g_test_add_func("/libnm/settings/dcb/flags-invalid", test_dcb_flags_invalid); + g_test_add_func("/libnm/settings/dcb/app-priorities", test_dcb_app_priorities); + g_test_add_func("/libnm/settings/dcb/priorities", test_dcb_priorities_valid); + g_test_add_func("/libnm/settings/dcb/bandwidth-sums", test_dcb_bandwidth_sums); + + g_test_add_func("/libnm/settings/ethtool/features", test_ethtool_features); + g_test_add_func("/libnm/settings/ethtool/coalesce", test_ethtool_coalesce); + g_test_add_func("/libnm/settings/ethtool/ring", test_ethtool_ring); + g_test_add_func("/libnm/settings/ethtool/pause", test_ethtool_pause); + + g_test_add_func("/libnm/settings/sriov/vf", test_sriov_vf); + g_test_add_func("/libnm/settings/sriov/vf-dup", test_sriov_vf_dup); + g_test_add_func("/libnm/settings/sriov/vf-vlan", test_sriov_vf_vlan); + g_test_add_func("/libnm/settings/sriov/setting", test_sriov_setting); + g_test_add_func("/libnm/settings/sriov/vlans", test_sriov_parse_vlans); + + g_test_add_func("/libnm/settings/tc_config/qdisc", test_tc_config_qdisc); + g_test_add_func("/libnm/settings/tc_config/action", test_tc_config_action); + g_test_add_func("/libnm/settings/tc_config/tfilter/matchall_sdata", + test_tc_config_tfilter_matchall_sdata); + g_test_add_func("/libnm/settings/tc_config/tfilter/matchall_mirred", + test_tc_config_tfilter_matchall_mirred); + g_test_add_func("/libnm/settings/tc_config/setting/valid", test_tc_config_setting_valid); + g_test_add_func("/libnm/settings/tc_config/setting/duplicates", + test_tc_config_setting_duplicates); + g_test_add_func("/libnm/settings/tc_config/dbus", test_tc_config_dbus); + + g_test_add_func("/libnm/settings/bridge/vlans", test_bridge_vlans); + g_test_add_func("/libnm/settings/bridge/verify", test_bridge_verify); + + g_test_add_func("/libnm/test_nm_json", test_nm_json); + g_test_add_func("/libnm/settings/team/sync_runner_from_config_roundrobin", + test_runner_roundrobin_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_runner_from_config_broadcast", + test_runner_broadcast_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_runner_from_config_random", + test_runner_random_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_runner_from_config_activebackup", + test_runner_activebackup_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_runner_from_config_loadbalance", + test_runner_loadbalance_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_runner_from_config_lacp", + test_runner_lacp_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_watcher_from_config_ethtool", + test_watcher_ethtool_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_watcher_from_config_nsna_ping", + test_watcher_nsna_ping_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_watcher_from_config_arp_ping", + test_watcher_arp_ping_sync_from_config); + g_test_add_func("/libnm/settings/team/sync_watcher_from_config_all", + test_multiple_watchers_sync_from_config); + + g_test_add_func("/libnm/settings/team-port/sync_from_config_defaults", test_team_port_default); + g_test_add_func("/libnm/settings/team-port/sync_from_config_queue_id", test_team_port_queue_id); + g_test_add_func("/libnm/settings/team-port/sync_from_config_prio", test_team_port_prio); + g_test_add_func("/libnm/settings/team-port/sync_from_config_sticky", test_team_port_sticky); + g_test_add_func("/libnm/settings/team-port/sync_from_config_lacp_prio", + test_team_port_lacp_prio); + g_test_add_func("/libnm/settings/team-port/sync_from_config_lacp_key", test_team_port_lacp_key); + g_test_add_func("/libnm/settings/team-port/sycn_from_config_full", test_team_port_full_config); + + g_test_add_data_func("/libnm/settings/roundtrip-conversion/general/0", + GINT_TO_POINTER(0), + test_roundtrip_conversion); + g_test_add_data_func("/libnm/settings/roundtrip-conversion/wireguard/1", + GINT_TO_POINTER(1), + test_roundtrip_conversion); + g_test_add_data_func("/libnm/settings/roundtrip-conversion/wireguard/2", + GINT_TO_POINTER(2), + test_roundtrip_conversion); + g_test_add_data_func("/libnm/settings/roundtrip-conversion/general/3", + GINT_TO_POINTER(3), + test_roundtrip_conversion); + + g_test_add_data_func("/libnm/settings/routing-rule/1", GINT_TO_POINTER(0), test_routing_rule); + + g_test_add_func("/libnm/parse-tc-handle", test_parse_tc_handle); + + g_test_add_func("/libnm/test_team_setting", test_team_setting); + + g_test_add_func("/libnm/test_empty_setting", test_empty_setting); + + g_test_add_func("/libnm/test_setting_metadata", test_setting_metadata); + + return g_test_run(); +} diff --git a/src/libnm-core-impl/tests/test-settings-defaults.c b/src/libnm-core-impl/tests/test-settings-defaults.c new file mode 100644 index 00000000..b9db7107 --- /dev/null +++ b/src/libnm-core-impl/tests/test-settings-defaults.c @@ -0,0 +1,109 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2008 - 2011 Red Hat, Inc. + */ + +#include "libnm-core-impl/nm-default-libnm-core.h" + +#include "nm-utils.h" +#include "nm-setting-8021x.h" +#include "nm-setting-cdma.h" +#include "nm-setting-connection.h" +#include "nm-setting-gsm.h" +#include "nm-setting-ip4-config.h" +#include "nm-setting-ip6-config.h" +#include "nm-setting-ppp.h" +#include "nm-setting-pppoe.h" +#include "nm-setting-serial.h" +#include "nm-setting-vpn.h" +#include "nm-setting-wired.h" +#include "nm-setting-wireless.h" +#include "nm-setting-wireless-security.h" + +#include "libnm-glib-aux/nm-test-utils.h" + +static void +test_defaults(GType type, const char *name) +{ + GParamSpec **property_specs; + guint n_property_specs; + GObject * setting; + int i; + + setting = g_object_new(type, NULL); + + property_specs = g_object_class_list_properties(G_OBJECT_GET_CLASS(setting), &n_property_specs); + g_assert(property_specs); + + for (i = 0; i < n_property_specs; i++) { + GParamSpec *prop_spec = property_specs[i]; + GValue value = G_VALUE_INIT; + GValue defvalue = G_VALUE_INIT; + char * actual, *expected; + gboolean ok = FALSE; + + /* Ignore non-fundamental types since they won't really have + * defaults. + */ + if (!G_TYPE_IS_FUNDAMENTAL(prop_spec->value_type)) + continue; + + g_value_init(&value, prop_spec->value_type); + g_object_get_property(G_OBJECT(setting), prop_spec->name, &value); + + g_value_init(&defvalue, prop_spec->value_type); + g_param_value_set_default(prop_spec, &defvalue); + + actual = g_strdup_value_contents(&value); + expected = g_strdup_value_contents(&defvalue); + + if (!strcmp(prop_spec->name, NM_SETTING_NAME)) { + /* 'name' is always the setting name, not the default value */ + ok = !strcmp(nm_setting_get_name(NM_SETTING(setting)), name); + g_free(expected); + expected = g_strdup(name); + } else + ok = g_param_value_defaults(prop_spec, &value); + + g_assert(ok); + + g_free(actual); + g_free(expected); + g_value_unset(&value); + g_value_unset(&defvalue); + } + + g_free(property_specs); + g_object_unref(setting); +} + +static void +defaults(void) +{ + /* The tests */ + test_defaults(NM_TYPE_SETTING_CONNECTION, NM_SETTING_CONNECTION_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_802_1X, NM_SETTING_802_1X_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_CDMA, NM_SETTING_CDMA_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_GSM, NM_SETTING_GSM_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_IP4_CONFIG, NM_SETTING_IP4_CONFIG_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_IP6_CONFIG, NM_SETTING_IP6_CONFIG_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_PPP, NM_SETTING_PPP_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_PPPOE, NM_SETTING_PPPOE_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_SERIAL, NM_SETTING_SERIAL_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_VPN, NM_SETTING_VPN_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_WIRED, NM_SETTING_WIRED_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_WIRELESS, NM_SETTING_WIRELESS_SETTING_NAME); + test_defaults(NM_TYPE_SETTING_WIRELESS_SECURITY, NM_SETTING_WIRELESS_SECURITY_SETTING_NAME); +} + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init(&argc, &argv, TRUE); + + g_test_add_func("/libnm/defaults", defaults); + + return g_test_run(); +} -- cgit 1.3.0-6-gf8a5