/* 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"

/*****************************************************************************/

NM_UTILS_LOOKUP_STR_DEFINE(
    nm_device_state_queued_state_to_string,
    NMDeviceState,
    NM_UTILS_LOOKUP_DEFAULT(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "???"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNKNOWN,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unknown"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNMANAGED,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unmanaged"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNAVAILABLE,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unavailable"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DISCONNECTED,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "disconnected"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_PREPARE,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "prepare"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_CONFIG,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "config"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_NEED_AUTH,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "need-auth"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CONFIG,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-config"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CHECK,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-check"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_SECONDARIES,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "secondaries"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_ACTIVATED,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "activated"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DEACTIVATING,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "deactivating"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_FAILED,
                             NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "failed"), );

const char *
nm_device_state_to_string(NMDeviceState state)
{
    return nm_device_state_queued_state_to_string(state)
           + NM_STRLEN(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE);
}

NM_UTILS_LOOKUP_STR_DEFINE(
    nm_device_state_reason_to_string,
    NMDeviceStateReason,
    NM_UTILS_LOOKUP_DEFAULT(NULL),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNKNOWN, "unknown"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NONE, "none"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_MANAGED, "managed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_UNMANAGED, "unmanaged"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONFIG_FAILED, "config-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE, "ip-config-unavailable"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED, "ip-config-expired"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NO_SECRETS, "no-secrets"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, "supplicant-disconnect"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED,
                             "supplicant-config-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED, "supplicant-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT, "supplicant-timeout"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_START_FAILED, "ppp-start-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_DISCONNECT, "ppp-disconnect"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_FAILED, "ppp-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_START_FAILED, "dhcp-start-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_ERROR, "dhcp-error"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_FAILED, "dhcp-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_START_FAILED, "sharing-start-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_FAILED, "sharing-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED, "autoip-start-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_ERROR, "autoip-error"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_FAILED, "autoip-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_BUSY, "modem-busy"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE, "modem-no-dialtone"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER, "modem-no-carrier"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT, "modem-dial-timeout"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED, "modem-dial-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED, "modem-init-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_APN_FAILED, "gsm-apn-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING,
                             "gsm-registration-idle"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED,
                             "gsm-registration-denied"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT,
                             "gsm-registration-timeout"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED,
                             "gsm-registration-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED, "gsm-pin-check-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_FIRMWARE_MISSING, "firmware-missing"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_REMOVED, "removed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SLEEPING, "sleeping"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, "connection-removed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_USER_REQUESTED, "user-requested"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CARRIER, "carrier-changed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED, "connection-assumed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, "supplicant-available"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND, "modem-not-found"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BT_FAILED, "bluetooth-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED, "gsm-sim-not-inserted"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED, "gsm-sim-pin-required"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED, "gsm-sim-puk-required"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_WRONG, "gsm-sim-wrong"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_INFINIBAND_MODE, "infiniband-mode"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED, "dependency-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BR2684_FAILED, "br2684-bridge-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE,
                             "modem-manager-unavailable"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SSID_NOT_FOUND, "ssid-not-found"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED,
                             "secondary-connection-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED, "dcb-fcoe-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED, "teamd-control-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_FAILED, "modem-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_AVAILABLE, "modem-available"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT, "sim-pin-incorrect"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NEW_ACTIVATION, "new-activation"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_CHANGED, "parent-changed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED,
                             "parent-managed-changed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_OVSDB_FAILED, "ovsdb-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE, "ip-address-duplicate"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED, "ip-method-unsupported"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,
                             "sriov-configuration-failed"),
    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND, "peer-not-found"), );

NM_UTILS_LOOKUP_STR_DEFINE(nm_device_mtu_source_to_string,
                           NMDeviceMtuSource,
                           NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_NONE, "none"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_PARENT, "parent"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_IP_CONFIG, "ip-config"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_CONNECTION,
                                                    "connection"), );

NM_UTILS_LOOKUP_STR_DEFINE(nm_device_sys_iface_state_to_string,
                           NMDeviceSysIfaceState,
                           NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, "external"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_ASSUME, "assume"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_MANAGED, "managed"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_REMOVED,
                                                    "removed"), );

NM_UTILS_LOOKUP_STR_DEFINE(nm_device_ip_state_to_string,
                           NMDeviceIPState,
                           NM_UTILS_LOOKUP_DEFAULT_WARN("unknown"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_NONE, "none"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_PENDING, "pending"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_READY, "done"),
                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_FAILED, "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_STR_HAS_PREFIX(dbus_error, "org.freedesktop.resolve1.")) {
            /* systemd-resolved is enabled but it couldn't resolve the
             * address via DNS.  Don't fall back to spawning the helper,
             * because the helper will possibly ask again to
             * systemd-resolved (via /etc/resolv.conf), potentially using
             * other protocols than DNS or returning synthetic results.
             *
             * Consider the error as the final indication that the address
             * can't be resolved.
             *
             * See: https://www.freedesktop.org/wiki/Software/systemd/resolved/#commonerrors
             */
            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);
}
