diff --git a/.gitignore b/.gitignore index 22f745efe9..041f681f00 100644 --- a/.gitignore +++ b/.gitignore @@ -427,6 +427,7 @@ test-*.trs /src/initrd/tests/test-ibft-reader /src/ndisc/tests/test-ndisc-fake /src/ndisc/tests/test-ndisc-linux +/src/nm-daemon-helper/nm-daemon-helper /src/nm-iface-helper /src/platform/tests/dump /src/platform/tests/monitor diff --git a/Makefile.am b/Makefile.am index dbcd9816e7..e57d01f11c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4598,6 +4598,31 @@ EXTRA_DIST += \ src/nm-dispatcher/tests/meson.build \ $(NULL) +############################################################################### +# src/nm-daemon-helper +############################################################################### + +libexec_PROGRAMS += src/nm-daemon-helper/nm-daemon-helper + +src_nm_daemon_helper_nm_daemon_helper_CPPFLAGS = \ + $(dflt_cppflags) \ + -I$(srcdir)/src \ + -I$(builddir)/src \ + $(NULL) + +src_nm_daemon_helper_nm_daemon_helper_LDFLAGS = \ + -Wl,--version-script="$(srcdir)/linker-script-binary.ver" \ + $(SANITIZER_EXEC_LDFLAGS) \ + $(NULL) + +src_nm_daemon_helper_nm_daemon_helper_LDADD = \ + src/libnm-std-aux/libnm-std-aux.la \ + $(NULL) + +EXTRA_DIST += \ + src/nm-daemon-helper/meson.build \ + $(NULL) + ############################################################################### # src/nm-online ############################################################################### diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index 78fc552aa1..f8374519db 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -998,6 +998,7 @@ fi %{_libexecdir}/nm-dispatcher %{_libexecdir}/nm-iface-helper %{_libexecdir}/nm-initrd-generator +%{_libexecdir}/nm-daemon-helper %dir %{_libdir}/%{name} %dir %{nmplugindir} %{nmplugindir}/libnm-settings-plugin*.so diff --git a/src/core/devices/nm-device-utils.c b/src/core/devices/nm-device-utils.c index 5ff703ed1a..f40ca570f6 100644 --- a/src/core/devices/nm-device-utils.c +++ b/src/core/devices/nm-device-utils.c @@ -1,8 +1,11 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "src/core/nm-default-daemon.h" +#include "src/core/dns/nm-dns-manager.h" +#include "src/core/dns/nm-dns-systemd-resolved.h" #include "nm-device-utils.h" +#include "nm-core-utils.h" /*****************************************************************************/ @@ -152,3 +155,202 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_device_ip_state_to_str, NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_CONF, "conf"), NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_DONE, "done"), NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_FAIL, "fail"), ); + +/*****************************************************************************/ + +#define SD_RESOLVED_DNS (1UL << 0) +/* Don't answer request from locally synthesized records (which includes /etc/hosts) */ +#define SD_RESOLVED_NO_SYNTHESIZE (1UL << 11) + +typedef struct { + int addr_family; + NMIPAddr address; + gulong cancellable_id; + GTask * task; + NMDnsSystemdResolvedResolveHandle *resolved_handle; +} ResolveAddrInfo; + +#define _NMLOG_PREFIX_NAME "resolve-addr" +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG2(level, info, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ + ResolveAddrInfo *_info = (info); \ + char _addr_str[NM_UTILS_INET_ADDRSTRLEN]; \ + \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \ + ",%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(_info), \ + nm_utils_inet_ntop(_info->addr_family, &_info->address, _addr_str) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +static void +resolve_addr_info_free(ResolveAddrInfo *info) +{ + nm_assert(info->cancellable_id == 0); + nm_assert(!info->resolved_handle); + g_object_unref(info->task); + g_free(info); +} + +static void +resolve_addr_complete(ResolveAddrInfo *info, char *hostname_take, GError *error_take) +{ + nm_assert(!!hostname_take != !!error_take); + + nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id); + if (error_take) + g_task_return_error(info->task, error_take); + else + g_task_return_pointer(info->task, hostname_take, g_free); + + resolve_addr_info_free(info); +} + +static void +resolve_addr_helper_cb(GObject *source, GAsyncResult *result, gpointer user_data) +{ + ResolveAddrInfo *info = user_data; + gs_free_error GError *error = NULL; + gs_free char * output = NULL; + + output = nm_utils_spawn_helper_finish(result, &error); + if (nm_utils_error_is_cancelled(error)) + return; + + _LOG2D(info, "helper returned hostname '%s'", output); + + resolve_addr_complete(info, g_steal_pointer(&output), g_steal_pointer(&error)); +} + +static void +resolve_addr_spawn_helper(ResolveAddrInfo *info) +{ + char addr_str[NM_UTILS_INET_ADDRSTRLEN]; + + nm_utils_inet_ntop(info->addr_family, &info->address, addr_str); + _LOG2D(info, "start lookup via nm-daemon-helper"); + nm_utils_spawn_helper(NM_MAKE_STRV("resolve-address", addr_str), + g_task_get_cancellable(info->task), + resolve_addr_helper_cb, + info); +} + +static void +resolve_addr_resolved_cb(NMDnsSystemdResolved * resolved, + NMDnsSystemdResolvedResolveHandle * handle, + const NMDnsSystemdResolvedAddressResult *names, + guint names_len, + guint64 flags, + GError * error, + gpointer user_data) +{ + ResolveAddrInfo *info = user_data; + + info->resolved_handle = NULL; + + if (nm_utils_error_is_cancelled(error)) + return; + + if (error) { + gs_free char *dbus_error = NULL; + + _LOG2D(info, "error resolving via systemd-resolved: %s", error->message); + + dbus_error = g_dbus_error_get_remote_error(error); + if (nm_streq0(dbus_error, "org.freedesktop.resolve1.DnsError.NXDOMAIN")) { + resolve_addr_complete(info, NULL, g_error_copy(error)); + return; + } + + resolve_addr_spawn_helper(info); + return; + } + + if (names_len == 0) { + _LOG2D(info, "systemd-resolved returned no result"); + resolve_addr_complete(info, g_strdup(""), NULL); + return; + } + + _LOG2D(info, "systemd-resolved returned hostname '%s'", names[0].name); + resolve_addr_complete(info, g_strdup(names[0].name), NULL); +} + +static void +resolve_addr_cancelled(GObject *object, gpointer user_data) +{ + ResolveAddrInfo *info = user_data; + GError * error = NULL; + + nm_clear_g_signal_handler(g_task_get_cancellable(info->task), &info->cancellable_id); + nm_clear_pointer(&info->resolved_handle, nm_dns_systemd_resolved_resolve_cancel); + nm_utils_error_set_cancelled(&error, FALSE, NULL); + resolve_addr_complete(info, NULL, error); +} + +void +nm_device_resolve_address(int addr_family, + gconstpointer address, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer cb_data) +{ + ResolveAddrInfo * info; + NMDnsSystemdResolved *resolved; + + info = g_new(ResolveAddrInfo, 1); + *info = (ResolveAddrInfo){ + .task = nm_g_task_new(NULL, cancellable, nm_device_resolve_address, callback, cb_data), + .addr_family = addr_family, + .address = nm_ip_addr_init(addr_family, address), + }; + + if (cancellable) { + gulong signal_id; + + signal_id = + g_cancellable_connect(cancellable, G_CALLBACK(resolve_addr_cancelled), info, NULL); + if (signal_id == 0) { + /* the request is already cancelled. Return. */ + return; + } + info->cancellable_id = signal_id; + } + + resolved = (NMDnsSystemdResolved *) nm_dns_manager_get_systemd_resolved(nm_dns_manager_get()); + if (resolved) { + _LOG2D(info, "start lookup via systemd-resolved"); + info->resolved_handle = + nm_dns_systemd_resolved_resolve_address(resolved, + 0, + addr_family, + address, + SD_RESOLVED_DNS | SD_RESOLVED_NO_SYNTHESIZE, + 20000, + resolve_addr_resolved_cb, + info); + return; + } + + resolve_addr_spawn_helper(info); +} + +char * +nm_device_resolve_address_finish(GAsyncResult *result, GError **error) +{ + GTask *task = G_TASK(result); + + nm_assert(nm_g_task_is_valid(result, NULL, nm_device_resolve_address)); + + return g_task_propagate_pointer(task, error); +} diff --git a/src/core/devices/nm-device-utils.h b/src/core/devices/nm-device-utils.h index fdeda26ecb..8bc957a4ba 100644 --- a/src/core/devices/nm-device-utils.h +++ b/src/core/devices/nm-device-utils.h @@ -85,4 +85,14 @@ const char *nm_device_ip_state_to_str(NMDeviceIPState ip_state); /*****************************************************************************/ +/*****************************************************************************/ + +void nm_device_resolve_address(int addr_family, + gconstpointer address, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer cb_data); + +char *nm_device_resolve_address_finish(GAsyncResult *result, GError **error); + #endif /* __DEVICES_NM_DEVICE_UTILS_H__ */ diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 21863b9533..f3b8a740a5 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -73,6 +73,7 @@ #include "nm-audit-manager.h" #include "nm-connectivity.h" #include "nm-dbus-interface.h" +#include "nm-hostname-manager.h" #include "nm-device-generic.h" #include "nm-device-vlan.h" @@ -220,7 +221,6 @@ typedef enum { typedef struct { ResolverState state; - GResolver * resolver; GInetAddress *address; GCancellable *cancellable; char * hostname; @@ -777,7 +777,6 @@ _hostname_resolver_free(HostnameResolver *resolver) nm_clear_g_source(&resolver->timeout_id); nm_clear_g_cancellable(&resolver->cancellable); - nm_g_object_unref(resolver->resolver); nm_g_object_unref(resolver->address); g_free(resolver->hostname); nm_g_slice_free(resolver); @@ -17666,21 +17665,37 @@ hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer use NMDevice * self; gs_free char * hostname = NULL; gs_free char * addr_str = NULL; + gs_free char * output = NULL; gs_free_error GError *error = NULL; - hostname = g_resolver_lookup_by_address_finish(G_RESOLVER(source), result, &error); + output = nm_device_resolve_address_finish(result, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) return; - resolver = user_data; - self = resolver->device; - resolver->state = RESOLVER_DONE; - resolver->hostname = g_strdup(hostname); + resolver = user_data; + self = resolver->device; + resolver->state = RESOLVER_DONE; - _LOGD(LOGD_DNS, - "hostname-from-dns: lookup done for %s, result %s%s%s", - (addr_str = g_inet_address_to_string(resolver->address)), - NM_PRINT_FMT_QUOTE_STRING(hostname)); + if (error) { + _LOGD(LOGD_DNS, + "hostname-from-dns: lookup error for %s: %s", + (addr_str = g_inet_address_to_string(resolver->address)), + error->message); + } else { + gboolean valid; + + resolver->hostname = g_steal_pointer(&output); + valid = nm_hostname_manager_validate_hostname(resolver->hostname); + + _LOGD(LOGD_DNS, + "hostname-from-dns: lookup done for %s, result %s%s%s%s", + (addr_str = g_inet_address_to_string(resolver->address)), + NM_PRINT_FMT_QUOTE_STRING(resolver->hostname), + valid ? "" : " (invalid)"); + + if (!valid) + g_clear_pointer(&resolver->hostname, g_free); + } nm_clear_g_cancellable(&resolver->cancellable); g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0); @@ -17778,7 +17793,6 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean if (!resolver) { resolver = g_slice_new(HostnameResolver); *resolver = (HostnameResolver){ - .resolver = g_resolver_get_default(), .device = self, .addr_family = addr_family, .state = RESOLVER_WAIT_ADDRESS, @@ -17828,20 +17842,15 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean } if (address_changed && new_address) { - gs_free char *str = NULL; - - _LOGT(LOGD_DNS, - "hostname-from-dns: starting lookup for address %s", - (str = g_inet_address_to_string(new_address))); - resolver->state = RESOLVER_IN_PROGRESS; resolver->cancellable = g_cancellable_new(); resolver->address = g_steal_pointer(&new_address); - g_resolver_lookup_by_address_async(resolver->resolver, - resolver->address, - resolver->cancellable, - hostname_dns_lookup_callback, - resolver); + + nm_device_resolve_address(addr_family, + g_inet_address_to_bytes(resolver->address), + resolver->cancellable, + hostname_dns_lookup_callback, + resolver); nm_clear_g_source(&resolver->timeout_id); } diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c index f35091d167..ab8243d00f 100644 --- a/src/core/dhcp/nm-dhcp-client.c +++ b/src/core/dhcp/nm-dhcp-client.c @@ -517,20 +517,12 @@ daemon_watch_cb(GPid pid, int status, gpointer user_data) { NMDhcpClient * self = NM_DHCP_CLIENT(user_data); NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self); + gs_free char * desc = NULL; g_return_if_fail(priv->watch_id); priv->watch_id = 0; - if (WIFEXITED(status)) - _LOGI("client pid %d exited with status %d", pid, WEXITSTATUS(status)); - else if (WIFSIGNALED(status)) - _LOGI("client pid %d killed by signal %d", pid, WTERMSIG(status)); - else if (WIFSTOPPED(status)) - _LOGI("client pid %d stopped by signal %d", pid, WSTOPSIG(status)); - else if (WIFCONTINUED(status)) - _LOGI("client pid %d resumed (by SIGCONT)", pid); - else - _LOGW("client died abnormally"); + _LOGI("client pid %d %s", pid, (desc = nm_utils_get_process_exit_status_desc(status))); priv->pid = -1; diff --git a/src/core/dns/nm-dns-dnsmasq.h b/src/core/dns/nm-dns-dnsmasq.h index bd6d4c60b7..575b0b263f 100644 --- a/src/core/dns/nm-dns-dnsmasq.h +++ b/src/core/dns/nm-dns-dnsmasq.h @@ -7,6 +7,7 @@ #define __NETWORKMANAGER_DNS_DNSMASQ_H__ #include "nm-dns-plugin.h" +#include "nm-dns-manager.h" #define NM_TYPE_DNS_DNSMASQ (nm_dns_dnsmasq_get_type()) #define NM_DNS_DNSMASQ(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DNS_DNSMASQ, NMDnsDnsmasq)) diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c index f161b14c0d..4c498c8248 100644 --- a/src/core/dns/nm-dns-manager.c +++ b/src/core/dns/nm-dns-manager.c @@ -378,11 +378,11 @@ _mgr_get_configs_lst_head(NMDnsManager *self) /*****************************************************************************/ -gboolean -nm_dns_manager_has_systemd_resolved(NMDnsManager *self) +NMDnsPlugin * +nm_dns_manager_get_systemd_resolved(NMDnsManager *self) { - NMDnsManagerPrivate * priv; - NMDnsSystemdResolved *plugin = NULL; + NMDnsManagerPrivate *priv; + NMDnsPlugin * plugin = NULL; g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE); @@ -390,11 +390,14 @@ nm_dns_manager_has_systemd_resolved(NMDnsManager *self) if (priv->sd_resolve_plugin) { nm_assert(!NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)); - plugin = NM_DNS_SYSTEMD_RESOLVED(priv->sd_resolve_plugin); + plugin = priv->sd_resolve_plugin; } else if (NM_IS_DNS_SYSTEMD_RESOLVED(priv->plugin)) - plugin = NM_DNS_SYSTEMD_RESOLVED(priv->plugin); + plugin = priv->plugin; - return plugin && nm_dns_systemd_resolved_is_running(plugin); + if (plugin && nm_dns_systemd_resolved_is_running(NM_DNS_SYSTEMD_RESOLVED(plugin))) + return plugin; + + return NULL; } /*****************************************************************************/ diff --git a/src/core/dns/nm-dns-manager.h b/src/core/dns/nm-dns-manager.h index 937ba62a05..501085f701 100644 --- a/src/core/dns/nm-dns-manager.h +++ b/src/core/dns/nm-dns-manager.h @@ -11,6 +11,7 @@ #include "nm-ip4-config.h" #include "nm-ip6-config.h" #include "nm-setting-connection.h" +#include "nm-dns-plugin.h" typedef enum { NM_DNS_IP_CONFIG_TYPE_REMOVED = -1, @@ -148,7 +149,7 @@ typedef enum { void nm_dns_manager_stop(NMDnsManager *self); -gboolean nm_dns_manager_has_systemd_resolved(NMDnsManager *self); +NMDnsPlugin *nm_dns_manager_get_systemd_resolved(NMDnsManager *self); /*****************************************************************************/ diff --git a/src/core/dns/nm-dns-plugin.h b/src/core/dns/nm-dns-plugin.h index 644d01e51b..82ca7d6b37 100644 --- a/src/core/dns/nm-dns-plugin.h +++ b/src/core/dns/nm-dns-plugin.h @@ -6,7 +6,8 @@ #ifndef __NM_DNS_PLUGIN_H__ #define __NM_DNS_PLUGIN_H__ -#include "nm-dns-manager.h" +#include "c-list/src/c-list.h" + #include "nm-config-data.h" #define NM_TYPE_DNS_PLUGIN (nm_dns_plugin_get_type()) diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c index 972ef643a3..4b8c4f9418 100644 --- a/src/core/dns/nm-dns-systemd-resolved.c +++ b/src/core/dns/nm-dns-systemd-resolved.c @@ -767,7 +767,7 @@ _resolve_handle_call_cb(GObject *source, GAsyncResult *result, gpointer user_dat n = nm_g_array_append_new(v_names, NMDnsSystemdResolvedAddressResult); *n = (NMDnsSystemdResolvedAddressResult){ - .name = g_strdup(v_name), + .name = g_steal_pointer(&v_name), .ifindex = v_ifindex, }; } diff --git a/src/core/dns/nm-dns-systemd-resolved.h b/src/core/dns/nm-dns-systemd-resolved.h index b7e30d81f7..9f9733184c 100644 --- a/src/core/dns/nm-dns-systemd-resolved.h +++ b/src/core/dns/nm-dns-systemd-resolved.h @@ -8,6 +8,7 @@ #define __NETWORKMANAGER_DNS_SYSTEMD_RESOLVED_H__ #include "nm-dns-plugin.h" +#include "nm-dns-manager.h" #define NM_TYPE_DNS_SYSTEMD_RESOLVED (nm_dns_systemd_resolved_get_type()) #define NM_DNS_SYSTEMD_RESOLVED(obj) \ diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c index a29be6d2aa..85d41b4919 100644 --- a/src/core/nm-connectivity.c +++ b/src/core/nm-connectivity.c @@ -904,7 +904,7 @@ nm_connectivity_check_start(NMConnectivity * self, * This is relatively cumbersome to avoid, because we would have to go through * NMDnsSystemdResolved trying to asynchronously start the service, to ensure there * is only one attempt to start the service. */ - has_systemd_resolved = nm_dns_manager_has_systemd_resolved(nm_dns_manager_get()); + has_systemd_resolved = !!nm_dns_manager_get_systemd_resolved(nm_dns_manager_get()); if (has_systemd_resolved) { GDBusConnection *dbus_connection; diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index 13400f4be2..364625159e 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -29,6 +29,7 @@ #include "libnm-glib-aux/nm-io-utils.h" #include "libnm-glib-aux/nm-secret-utils.h" #include "libnm-glib-aux/nm-time-utils.h" +#include "libnm-glib-aux/nm-str-buf.h" #include "nm-utils.h" #include "libnm-core-intern/nm-core-internal.h" #include "nm-setting-connection.h" @@ -4656,3 +4657,323 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_activation_type_to_string, NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_MANAGED, "managed"), NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_ASSUME, "assume"), NM_UTILS_LOOKUP_STR_ITEM(NM_ACTIVATION_TYPE_EXTERNAL, "external"), ); + +/*****************************************************************************/ + +typedef struct { + GPid pid; + GTask * task; + gulong cancellable_id; + GSource *child_watch_source; + GSource *timeout_source; + + int child_stdin; + int child_stdout; + GSource *input_source; + GSource *output_source; + + NMStrBuf in_buffer; + NMStrBuf out_buffer; + gsize out_buffer_offset; +} HelperInfo; + +#define _NMLOG_PREFIX_NAME "helper" +#define _NMLOG_DOMAIN LOGD_CORE +#define _NMLOG2(level, info, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \ + HelperInfo *_info = (info); \ + \ + _nm_log((level), \ + (_NMLOG_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \ + ",%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(_info), \ + _info->pid _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +static void +helper_info_free(gpointer data) +{ + HelperInfo *info = data; + + nm_clear_g_source_inst(&info->child_watch_source); + nm_clear_g_source_inst(&info->timeout_source); + g_object_unref(info->task); + + nm_str_buf_destroy(&info->in_buffer); + nm_str_buf_destroy(&info->out_buffer); + nm_clear_g_source_inst(&info->input_source); + nm_clear_g_source_inst(&info->output_source); + + if (info->child_stdout != -1) + nm_close(info->child_stdout); + if (info->child_stdin != -1) + nm_close(info->child_stdin); + + if (info->pid != -1) { + nm_assert(info->pid > 1); + nm_utils_kill_child_async(info->pid, SIGKILL, LOGD_CORE, _NMLOG_PREFIX_NAME, 0, NULL, NULL); + } + + g_free(info); +} + +static void +helper_complete(HelperInfo *info, GError *error) +{ + if (error) { + nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), + &info->cancellable_id); + g_task_return_error(info->task, error); + helper_info_free(info); + return; + } + + if (info->input_source || info->output_source || info->pid != -1) { + /* Wait that pipes are closed and process has terminated */ + return; + } + + nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id); + g_task_return_pointer(info->task, nm_str_buf_finalize(&info->in_buffer, NULL), g_free); + helper_info_free(info); +} + +static gboolean +helper_can_write(int fd, GIOCondition condition, gpointer user_data) +{ + HelperInfo *info = user_data; + gssize n_written; + int errsv; + + if (NM_FLAGS_HAS(condition, G_IO_ERR)) { + errsv = EIO; + goto out_error; + } else if (NM_FLAGS_HAS(condition, G_IO_HUP)) { + errsv = EPIPE; + goto out_error; + } + + n_written = write(info->child_stdin, + &((nm_str_buf_get_str_unsafe(&info->out_buffer))[info->out_buffer_offset]), + info->out_buffer.len - info->out_buffer_offset); + errsv = errno; + + if (n_written < 0 && errsv != EAGAIN) + goto out_error; + + if (n_written > 0) { + if ((gsize) n_written >= (info->out_buffer.len - info->out_buffer_offset)) { + nm_assert((gsize) n_written == (info->out_buffer.len - info->out_buffer_offset)); + nm_clear_g_source_inst(&info->output_source); + nm_close(info->child_stdin); + info->child_stdin = -1; + return G_SOURCE_CONTINUE; + } + info->out_buffer_offset += (gsize) n_written; + } + + return G_SOURCE_CONTINUE; + +out_error: + nm_clear_g_source_inst(&info->output_source); + helper_complete(info, + g_error_new(NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "error writing to helper: %d (%s)", + errsv, + nm_strerror_native(errsv))); + return G_SOURCE_CONTINUE; +} + +static gboolean +helper_have_data(int fd, GIOCondition condition, gpointer user_data) +{ + HelperInfo *info = user_data; + gssize n_read; + GError * error = NULL; + + n_read = nm_utils_fd_read(fd, &info->in_buffer); + _LOG2T(info, "read returns %ld", (long) n_read); + + if (n_read > 0) + return G_SOURCE_CONTINUE; + + nm_clear_g_source_inst(&info->input_source); + nm_close(info->child_stdout); + info->child_stdout = -1; + + _LOG2T(info, "stdout closed"); + + if (n_read < 0) { + error = g_error_new(NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "read from process returned %d (%s)", + (int) -n_read, + nm_strerror_native((int) -n_read)); + } + + helper_complete(info, error); + return G_SOURCE_CONTINUE; +} + +static void +helper_child_terminated(GPid pid, int status, gpointer user_data) +{ + HelperInfo * info = user_data; + GError * error = NULL; + gs_free char *status_desc = NULL; + + _LOG2D(info, "process %s", (status_desc = nm_utils_get_process_exit_status_desc(status))); + + info->pid = -1; + nm_clear_g_source_inst(&info->child_watch_source); + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + if (!status_desc) + status_desc = nm_utils_get_process_exit_status_desc(status); + error = + g_error_new(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "helper process %s", status_desc); + } + + helper_complete(info, error); +} + +static gboolean +helper_timeout(gpointer user_data) +{ + HelperInfo *info = user_data; + + nm_clear_g_source_inst(&info->timeout_source); + helper_complete(info, g_error_new_literal(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "timed out")); + + return G_SOURCE_CONTINUE; +} + +static void +helper_cancelled(GObject *object, gpointer user_data) +{ + HelperInfo *info = user_data; + GError * error = NULL; + + nm_clear_g_signal_handler(g_task_get_cancellable(info->task), &info->cancellable_id); + nm_utils_error_set_cancelled(&error, FALSE, NULL); + helper_complete(info, error); +} + +void +nm_utils_spawn_helper(const char *const * args, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer cb_data) +{ + gs_free_error GError *error = NULL; + gs_free char * commands = NULL; + HelperInfo * info; + int fd_flags; + const char *const * arg; + + nm_assert(args && args[0]); + + info = g_new(HelperInfo, 1); + *info = (HelperInfo){ + .task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data), + .child_stdin = -1, + .child_stdout = -1, + .pid = -1, + }; + + if (!g_spawn_async_with_pipes("/", + (char **) NM_MAKE_STRV(LIBEXECDIR "/nm-daemon-helper"), + (char **) NM_MAKE_STRV(), + G_SPAWN_DO_NOT_REAP_CHILD, + NULL, + NULL, + &info->pid, + &info->child_stdin, + &info->child_stdout, + NULL, + &error)) { + info->child_stdin = -1; + info->child_stdout = -1; + info->pid = -1; + g_task_return_error(info->task, + g_error_new(NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "error spawning nm-helper: %s", + error->message)); + helper_info_free(info); + return; + } + + _LOG2D(info, "spawned process with args: %s", (commands = g_strjoinv(" ", (char **) args))); + + info->child_watch_source = g_child_watch_source_new(info->pid); + g_source_set_callback(info->child_watch_source, + G_SOURCE_FUNC(helper_child_terminated), + info, + NULL); + g_source_attach(info->child_watch_source, g_main_context_get_thread_default()); + + info->timeout_source = + nm_g_timeout_source_new_seconds(20, G_PRIORITY_DEFAULT, helper_timeout, info, NULL); + g_source_attach(info->timeout_source, g_main_context_get_thread_default()); + + /* Set file descriptors as non-blocking */ + fd_flags = fcntl(info->child_stdin, F_GETFD, 0); + fcntl(info->child_stdin, F_SETFL, fd_flags | O_NONBLOCK); + fd_flags = fcntl(info->child_stdout, F_GETFD, 0); + fcntl(info->child_stdout, F_SETFL, fd_flags | O_NONBLOCK); + + /* Watch process stdin */ + nm_str_buf_init(&info->out_buffer, 32, TRUE); + for (arg = args; *arg; arg++) { + nm_str_buf_append(&info->out_buffer, *arg); + nm_str_buf_append_c(&info->out_buffer, '\0'); + } + info->output_source = nm_g_unix_fd_source_new(info->child_stdin, + G_IO_OUT | G_IO_ERR | G_IO_HUP, + G_PRIORITY_DEFAULT, + helper_can_write, + info, + NULL); + g_source_attach(info->output_source, g_main_context_get_thread_default()); + + /* Watch process stdout */ + nm_str_buf_init(&info->in_buffer, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE); + info->input_source = nm_g_unix_fd_source_new(info->child_stdout, + G_IO_IN | G_IO_ERR | G_IO_HUP, + G_PRIORITY_DEFAULT, + helper_have_data, + info, + NULL); + g_source_attach(info->input_source, g_main_context_get_thread_default()); + + if (cancellable) { + gulong signal_id; + + signal_id = g_cancellable_connect(cancellable, G_CALLBACK(helper_cancelled), info, NULL); + if (signal_id == 0) { + /* the request is already cancelled. Return. */ + return; + } + info->cancellable_id = signal_id; + } +} + +char * +nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error) +{ + GTask *task = G_TASK(result); + + nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_spawn_helper)); + + return g_task_propagate_pointer(task, error); +} diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index 7fd96d3ac5..bcb182b849 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -420,4 +420,13 @@ guint8 nm_wifi_utils_level_to_quality(int val); #define NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN "Unable to determine UID of the request" #define NM_UTILS_ERROR_MSG_INSUFF_PRIV "Insufficient privileges" +/*****************************************************************************/ + +void nm_utils_spawn_helper(const char *const * args, + GCancellable * cancellable, + GAsyncReadyCallback callback, + gpointer cb_data); + +char *nm_utils_spawn_helper_finish(GAsyncResult *result, GError **error); + #endif /* __NM_CORE_UTILS_H__ */ diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c index 0cac64b5d1..7a49298ec3 100644 --- a/src/libnm-glib-aux/nm-shared-utils.c +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -6306,3 +6306,20 @@ nm_crypto_md5_hash(const guint8 *salt, g_checksum_update(ctx, digest.ptr, NM_UTILS_CHECKSUM_LENGTH_MD5); } } + +/*****************************************************************************/ + +char * +nm_utils_get_process_exit_status_desc(int status) +{ + if (WIFEXITED(status)) + return g_strdup_printf("exited with status %d", WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + return g_strdup_printf("killed by signal %d", WTERMSIG(status)); + else if (WIFSTOPPED(status)) + return g_strdup_printf("stopped by signal %d", WSTOPSIG(status)); + else if (WIFCONTINUED(status)) + return g_strdup("resumed by SIGCONT)"); + else + return g_strdup_printf("exited with unknown status 0x%x", status); +} diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index b19bce9c36..2e4215fdf7 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -277,7 +277,29 @@ nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src) nm_assert(dst); nm_assert(src); - memcpy(dst, src, (addr_family != AF_INET6) ? sizeof(in_addr_t) : sizeof(struct in6_addr)); + memcpy(dst, src, NM_IS_IPv4(addr_family) ? sizeof(in_addr_t) : sizeof(struct in6_addr)); +} + +static inline NMIPAddr +nm_ip_addr_init(int addr_family, gconstpointer src) +{ + NMIPAddr a; + + nm_assert_addr_family(addr_family); + nm_assert(src); + + G_STATIC_ASSERT_EXPR(sizeof(NMIPAddr) == sizeof(struct in6_addr)); + + if (NM_IS_IPv4(addr_family)) { + memcpy(&a, src, sizeof(in_addr_t)); + + /* ensure all bytes of the union are initialized. If only to make + * valgrind happy. */ + memset(&a.array[sizeof(in_addr_t)], 0, sizeof(a) - sizeof(in_addr_t)); + } else + memcpy(&a, src, sizeof(struct in6_addr)); + + return a; } gboolean nm_ip_addr_set_from_untrusted(int addr_family, @@ -2920,4 +2942,8 @@ void nm_crypto_md5_hash(const guint8 *salt, guint8 * buffer, gsize buflen); +/*****************************************************************************/ + +char *nm_utils_get_process_exit_status_desc(int status); + #endif /* __NM_SHARED_UTILS_H__ */ diff --git a/src/meson.build b/src/meson.build index 962e2f87fe..39bfe7ef78 100644 --- a/src/meson.build +++ b/src/meson.build @@ -93,6 +93,7 @@ if enable_nmtui endif subdir('nmcli') subdir('nm-dispatcher') +subdir('nm-daemon-helper') subdir('nm-online') if enable_nmtui subdir('nmtui') diff --git a/src/nm-daemon-helper/meson.build b/src/nm-daemon-helper/meson.build new file mode 100644 index 0000000000..da0d6571e1 --- /dev/null +++ b/src/nm-daemon-helper/meson.build @@ -0,0 +1,15 @@ +executable( + 'nm-daemon-helper', + 'nm-daemon-helper.c', + include_directories : [ + src_inc, + top_inc, + ], + link_with: [ + libnm_std_aux, + ], + link_args: ldflags_linker_script_binary, + link_depends: linker_script_binary, + install: true, + install_dir: nm_libexecdir, +) diff --git a/src/nm-daemon-helper/nm-daemon-helper.c b/src/nm-daemon-helper/nm-daemon-helper.c new file mode 100644 index 0000000000..3a95c6829f --- /dev/null +++ b/src/nm-daemon-helper/nm-daemon-helper.c @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Copyright (C) 2021 Red Hat, Inc. */ + +#include +#include +#include +#include +#include + +#include "libnm-std-aux/nm-std-aux.h" + +enum { + RETURN_SUCCESS = 0, + RETURN_INVALID_CMD = 1, + RETURN_INVALID_ARGS = 2, + RETURN_ERROR = 3, +}; + +static char * +read_arg(void) +{ + nm_auto_free char *arg = NULL; + size_t len = 0; + + if (getdelim(&arg, &len, '\0', stdin) < 0) + return NULL; + + return nm_steal_pointer(&arg); +} + +static int +more_args(void) +{ + nm_auto_free char *arg = NULL; + + arg = read_arg(); + + return !!arg; +} + +static int +cmd_version(void) +{ + if (more_args()) + return RETURN_INVALID_ARGS; + + printf("1"); + return RETURN_SUCCESS; +} + +static int +cmd_resolve_address(void) +{ + nm_auto_free char *address = NULL; + union { + struct sockaddr_in in; + struct sockaddr_in6 in6; + } sockaddr; + socklen_t sockaddr_size; + char name[NI_MAXHOST]; + + address = read_arg(); + if (!address) + return RETURN_INVALID_ARGS; + + if (more_args()) + return RETURN_INVALID_ARGS; + + memset(&sockaddr, 0, sizeof(sockaddr)); + __nss_configure_lookup("hosts", "dns"); + + if (inet_pton(AF_INET, address, &sockaddr.in.sin_addr) == 1) { + sockaddr.in.sin_family = AF_INET; + sockaddr_size = sizeof(struct sockaddr_in); + } else if (inet_pton(AF_INET6, address, &sockaddr.in6.sin6_addr) == 1) { + sockaddr.in6.sin6_family = AF_INET6; + sockaddr_size = sizeof(struct sockaddr_in6); + } else + return RETURN_INVALID_ARGS; + + if (getnameinfo((struct sockaddr *) &sockaddr, + sockaddr_size, + name, + sizeof(name), + NULL, + 0, + NI_NAMEREQD) + != 0) + return RETURN_ERROR; + + printf("%s", name); + + return RETURN_SUCCESS; +} + +int +main(int argc, char **argv) +{ + nm_auto_free char *cmd = NULL; + + cmd = read_arg(); + if (!cmd) + return RETURN_INVALID_CMD; + + if (nm_streq(cmd, "version")) + return cmd_version(); + if (nm_streq(cmd, "resolve-address")) + return cmd_resolve_address(); + + return RETURN_INVALID_CMD; +} diff --git a/src/nm-dispatcher/nm-dispatcher.c b/src/nm-dispatcher/nm-dispatcher.c index eb1781faf6..5df329592d 100644 --- a/src/nm-dispatcher/nm-dispatcher.c +++ b/src/nm-dispatcher/nm-dispatcher.c @@ -361,8 +361,8 @@ complete_script(ScriptInfo *script) static void script_watch_cb(GPid pid, int status, gpointer user_data) { - ScriptInfo *script = user_data; - guint err; + ScriptInfo * script = user_data; + gs_free char *status_desc = NULL; g_assert(pid == script->pid); @@ -372,23 +372,11 @@ script_watch_cb(GPid pid, int status, gpointer user_data) if (!script->wait) script->request->num_scripts_nowait--; - if (WIFEXITED(status)) { - err = WEXITSTATUS(status); - if (err == 0) - script->result = DISPATCH_RESULT_SUCCESS; - else { - script->error = - g_strdup_printf("Script '%s' exited with error status %d.", script->script, err); - } - } else if (WIFSTOPPED(status)) { - script->error = g_strdup_printf("Script '%s' stopped unexpectedly with signal %d.", - script->script, - WSTOPSIG(status)); - } else if (WIFSIGNALED(status)) { - script->error = - g_strdup_printf("Script '%s' died with signal %d", script->script, WTERMSIG(status)); + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + script->result = DISPATCH_RESULT_SUCCESS; } else { - script->error = g_strdup_printf("Script '%s' died from an unknown cause", script->script); + status_desc = nm_utils_get_process_exit_status_desc(status); + script->error = g_strdup_printf("Script '%s' %s.", script->script, status_desc); } if (script->result == DISPATCH_RESULT_SUCCESS) {