/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2008 Novell, Inc.
 * Copyright (C) 2008 - 2012 Red Hat, Inc.
 */

#include "src/core/nm-default-daemon.h"

#include "nm-ppp-manager.h"

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <asm/types.h>
#include <sys/stat.h>

#include <linux/ppp_defs.h>
#ifndef aligned_u64
#define aligned_u64 unsigned long long __attribute__((aligned(8)))
#endif
#include <linux/if.h>
#include <linux/if_ppp.h>
#include <linux/rtnetlink.h>

#include "NetworkManagerUtils.h"
#include "libnm-platform/nm-platform.h"
#include "libnm-platform/nm-platform-utils.h"
#include "libnm-core-intern/nm-core-internal.h"
#include "nm-act-request.h"
#include "nm-l3-config-data.h"
#include "nm-dbus-object.h"

#include "nm-pppd-plugin.h"
#include "nm-ppp-plugin-api.h"
#include "nm-ppp-status.h"

#define NM_PPPD_PLUGIN PPPD_PLUGIN_DIR "/nm-pppd-plugin.so"

static NM_CACHED_QUARK_FCN("ppp-manager-secret-tries", ppp_manager_secret_tries_quark);

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

/* FIXME(l3cfg:ppp): I think NMPPPManager's API should be improved to be easier
 * usable (by the higher layers). That means to make the class more complex, to
 * provide a simpler API.
 *
 * For example:
 *
 * - NM_PPP_MANAGER_SIGNAL_STATE_CHANGED just gets re-emitted when we receive
 *   the D-Bus call from the plugin. The emitted state is like NM_PPP_STATUS_SERIALCONN,
 *   but none of the users cares about this (what would it mean anyway)? The
 *   class should itself consume the state, and emit something more consumable
 *   (like: interface is ready (with ifindex), IPvX configuration done (with l3cd).
 *
 * - currently signals can be emitted in any order, and it's not clear which
 *   signals we can expect. For example, when we activate a device, we may want to wait
 *   for IPv4 and IPv6 configuration, but it's not clear whether this configuration
 *   is still to be received or whether we can stop waiting.
 **/

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

#define NM_TYPE_PPP_MANAGER (nm_ppp_manager_get_type())
#define NM_PPP_MANAGER(obj) \
    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PPP_MANAGER, NMPPPManager))
#define NM_PPP_MANAGER_CLASS(klass) \
    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_PPP_MANAGER, NMPPPManagerClass))
#define NM_IS_PPP_MANAGER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_PPP_MANAGER))
#define NM_IS_PPP_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_PPP_MANAGER))
#define NM_PPP_MANAGER_GET_CLASS(obj) \
    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_PPP_MANAGER, NMPPPManagerClass))

GType nm_ppp_manager_get_type(void);

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

enum {
    STATE_CHANGED,
    IFINDEX_SET,
    NEW_CONFIG,
    STATS,

    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = {0};

NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT_IFACE, );

typedef struct {
    GPid pid;

    char *parent_iface;
    char *ip_iface;
    int   ifindex;

    NMActRequest                 *act_req;
    GDBusMethodInvocation        *pending_secrets_context;
    NMActRequestGetSecretsCallId *secrets_id;
    const char                   *secrets_setting_name;

    guint ppp_watch_id;
    guint ppp_timeout_handler;

    /* Monitoring */
    int   monitor_fd;
    guint monitor_id;
} NMPPPManagerPrivate;

struct _NMPPPManager {
    NMDBusObject        parent;
    NMPPPManagerPrivate _priv;
};

typedef struct {
    NMDBusObjectClass parent;
} NMPPPManagerClass;

G_DEFINE_TYPE(NMPPPManager, nm_ppp_manager, NM_TYPE_DBUS_OBJECT)

#define NM_PPP_MANAGER_GET_PRIVATE(self) \
    _NM_GET_PRIVATE(self, NMPPPManager, NM_IS_PPP_MANAGER, NMDBusObject)

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

#define _NMLOG_DOMAIN      LOGD_PPP
#define _NMLOG(level, ...) __NMLOG_DEFAULT(level, _NMLOG_DOMAIN, "ppp-manager", __VA_ARGS__)

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

static void _ppp_cleanup(NMPPPManager *self);

static NMPPPManagerStopHandle *_ppp_manager_stop(NMPPPManager            *self,
                                                 GCancellable            *cancellable,
                                                 NMPPPManagerStopCallback callback,
                                                 gpointer                 user_data);

static void _ppp_manager_stop_cancel(NMPPPManagerStopHandle *handle);

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

static void
_emit_signal_new_config(NMPPPManager             *self,
                        int                       addr_family,
                        const NML3ConfigData     *l3cd,
                        const NMUtilsIPv6IfaceId *iid)
{
    nm_assert(NM_IS_PPP_MANAGER(self));
    nm_assert_addr_family(addr_family);
    nm_assert(NM_IS_L3_CONFIG_DATA(l3cd));
    nm_assert((!!iid) == (addr_family == AF_INET6));

    nm_l3_config_data_seal(l3cd);

    g_signal_emit(self, signals[NEW_CONFIG], 0, addr_family, l3cd, iid);
}

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

static gboolean
monitor_cb(gpointer user_data)
{
    NMPPPManager        *self = NM_PPP_MANAGER(user_data);
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self);
    const char          *ifname;
    int                  errsv;

    ifname = nm_platform_link_get_name(NM_PLATFORM_GET, priv->ifindex);

    if (ifname) {
        struct ppp_stats stats = {};
        struct ifreq     req   = {
                  .ifr_data = (caddr_t) &stats,
        };

        nm_utils_ifname_cpy(req.ifr_name, ifname);
        if (ioctl(priv->monitor_fd, SIOCGPPPSTATS, &req) < 0) {
            errsv = errno;
            if (errsv != ENODEV)
                _LOGW("could not read ppp stats: %s", nm_strerror_native(errsv));
        } else {
            g_signal_emit(self,
                          signals[STATS],
                          0,
                          (guint) stats.p.ppp_ibytes,
                          (guint) stats.p.ppp_obytes);
        }
    }

    return G_SOURCE_CONTINUE;
}

static void
monitor_stats(NMPPPManager *self)
{
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self);
    int                  errsv;

    /* already monitoring */
    if (priv->monitor_fd >= 0)
        return;

    priv->monitor_fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
    if (priv->monitor_fd < 0) {
        errsv = errno;
        _LOGW("could not monitor PPP stats: %s", nm_strerror_native(errsv));
        return;
    }

    g_warn_if_fail(priv->monitor_id == 0);
    if (priv->monitor_id)
        g_source_remove(priv->monitor_id);
    priv->monitor_id = g_timeout_add_seconds(5, monitor_cb, self);
}

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

static void
cancel_get_secrets(NMPPPManager *self)
{
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self);

    if (priv->secrets_id)
        nm_act_request_cancel_secrets(priv->act_req, priv->secrets_id);

    g_return_if_fail(!priv->secrets_id && !priv->secrets_setting_name);
}

static gboolean
extract_details_from_connection(NMConnection *connection,
                                const char   *secrets_setting_name,
                                const char  **username,
                                const char  **password,
                                GError      **error)
{
    NMSettingConnection *s_con;
    NMSetting           *setting;
    const char          *setting_name;

    g_return_val_if_fail(connection != NULL, FALSE);
    g_return_val_if_fail(username != NULL, FALSE);
    g_return_val_if_fail(password != NULL, FALSE);

    if (secrets_setting_name)
        setting_name = secrets_setting_name;
    else {
        /* Get the setting matching the connection type */
        s_con = nm_connection_get_setting_connection(connection);
        g_assert(s_con);

        setting_name = nm_setting_connection_get_connection_type(s_con);
        g_assert(setting_name);

        /* In case of bluetooth connection, use GSM or CDMA setting */
        if (strcmp(setting_name, NM_SETTING_BLUETOOTH_SETTING_NAME) == 0) {
            if (nm_connection_get_setting_gsm(connection))
                setting_name = NM_SETTING_GSM_SETTING_NAME;
            else
                setting_name = NM_SETTING_CDMA_SETTING_NAME;
        }
    }

    setting = nm_connection_get_setting_by_name(connection, setting_name);
    if (!setting) {
        /* This shouldn't ever happen */
        g_set_error_literal(error,
                            NM_MANAGER_ERROR,
                            NM_MANAGER_ERROR_FAILED,
                            "Missing type-specific setting; no secrets could be found.");
        return FALSE;
    }

    if (NM_IS_SETTING_PPPOE(setting)) {
        *username = nm_setting_pppoe_get_username(NM_SETTING_PPPOE(setting));
        *password = nm_setting_pppoe_get_password(NM_SETTING_PPPOE(setting));
    } else if (NM_IS_SETTING_ADSL(setting)) {
        *username = nm_setting_adsl_get_username(NM_SETTING_ADSL(setting));
        *password = nm_setting_adsl_get_password(NM_SETTING_ADSL(setting));
    } else if (NM_IS_SETTING_GSM(setting)) {
        *username = nm_setting_gsm_get_username(NM_SETTING_GSM(setting));
        *password = nm_setting_gsm_get_password(NM_SETTING_GSM(setting));
    } else if (NM_IS_SETTING_CDMA(setting)) {
        *username = nm_setting_cdma_get_username(NM_SETTING_CDMA(setting));
        *password = nm_setting_cdma_get_password(NM_SETTING_CDMA(setting));
    }

    return TRUE;
}

static void
ppp_secrets_cb(NMActRequest                 *req,
               NMActRequestGetSecretsCallId *call_id,
               NMSettingsConnection         *settings_connection, /* unused (we pass NULL here) */
               GError                       *error,
               gpointer                      user_data)
{
    NMPPPManager        *self     = NM_PPP_MANAGER(user_data);
    NMPPPManagerPrivate *priv     = NM_PPP_MANAGER_GET_PRIVATE(self);
    const char          *username = NULL;
    const char          *password = NULL;
    GError              *local    = NULL;
    NMConnection        *applied_connection;

    g_return_if_fail(priv->pending_secrets_context != NULL);
    g_return_if_fail(req == priv->act_req);
    g_return_if_fail(call_id == priv->secrets_id);

    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        goto out;

    if (error) {
        _LOGW("%s", error->message);
        g_dbus_method_invocation_return_gerror(priv->pending_secrets_context, error);
        goto out;
    }

    applied_connection = nm_act_request_get_applied_connection(req);

    if (!extract_details_from_connection(applied_connection,
                                         priv->secrets_setting_name,
                                         &username,
                                         &password,
                                         &local)) {
        _LOGW("%s", local->message);
        g_dbus_method_invocation_take_error(priv->pending_secrets_context, local);
        goto out;
    }

    /* This is sort of a hack but...
     * pppd plugin only ever needs username and password. Passing the full
     * connection there would mean some bloat: the plugin would need to link
     * against libnm just to parse this. So instead, let's just send what
     * it needs.
     */
    g_dbus_method_invocation_return_value(priv->pending_secrets_context,
                                          g_variant_new("(ss)", username ?: "", password ?: ""));

out:
    priv->pending_secrets_context = NULL;
    priv->secrets_id              = NULL;
    priv->secrets_setting_name    = NULL;
}

static void
impl_ppp_manager_need_secrets(NMDBusObject                      *obj,
                              const NMDBusInterfaceInfoExtended *interface_info,
                              const NMDBusMethodInfoExtended    *method_info,
                              GDBusConnection                   *connection,
                              const char                        *sender,
                              GDBusMethodInvocation             *invocation,
                              GVariant                          *parameters)
{
    NMPPPManager                *self = NM_PPP_MANAGER(obj);
    NMPPPManagerPrivate         *priv = NM_PPP_MANAGER_GET_PRIVATE(self);
    NMConnection                *applied_connection;
    const char                  *username = NULL;
    const char                  *password = NULL;
    guint32                      tries;
    gs_unref_ptrarray GPtrArray *hints = NULL;
    GError                      *error = NULL;
    NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;

    nm_active_connection_clear_secrets(NM_ACTIVE_CONNECTION(priv->act_req));

    applied_connection = nm_act_request_get_applied_connection(priv->act_req);

    priv->secrets_setting_name = nm_connection_need_secrets(applied_connection, &hints);
    if (!priv->secrets_setting_name) {
        /* Use existing secrets from the connection */
        if (extract_details_from_connection(applied_connection,
                                            NULL,
                                            &username,
                                            &password,
                                            &error)) {
            /* Send existing secrets to the PPP plugin */
            priv->pending_secrets_context = invocation;
            ppp_secrets_cb(priv->act_req, priv->secrets_id, NULL, NULL, self);
        } else {
            _LOGW("%s", error->message);
            g_dbus_method_invocation_take_error(priv->pending_secrets_context, error);
        }
        return;
    }

    /* Only ask for completely new secrets after retrying them once; some devices
     * appear to ask a few times when they actually don't even care what you
     * pass back.
     */
    tries = GPOINTER_TO_UINT(
        g_object_get_qdata(G_OBJECT(applied_connection), ppp_manager_secret_tries_quark()));
    if (tries > 1)
        flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;

    if (hints)
        g_ptr_array_add(hints, NULL);

    priv->secrets_id = nm_act_request_get_secrets(priv->act_req,
                                                  FALSE,
                                                  priv->secrets_setting_name,
                                                  flags,
                                                  hints ? (const char *const *) hints->pdata : NULL,
                                                  ppp_secrets_cb,
                                                  self);
    g_object_set_qdata(G_OBJECT(applied_connection),
                       ppp_manager_secret_tries_quark(),
                       GUINT_TO_POINTER(++tries));
    priv->pending_secrets_context = invocation;
}

static void
impl_ppp_manager_set_state(NMDBusObject                      *obj,
                           const NMDBusInterfaceInfoExtended *interface_info,
                           const NMDBusMethodInfoExtended    *method_info,
                           GDBusConnection                   *connection,
                           const char                        *sender,
                           GDBusMethodInvocation             *invocation,
                           GVariant                          *parameters)
{
    NMPPPManager *self = NM_PPP_MANAGER(obj);
    guint32       ppp_state;

    g_variant_get(parameters, "(u)", &ppp_state);

    if (ppp_state >= NM_PPP_STATUS_INTERN_DEAD) {
        /* we don't expect an intern state to be reported by the plugin. */
        ppp_state = NM_PPP_STATUS_INTERN_UNKNOWN;
    }

    g_signal_emit(self, signals[STATE_CHANGED], 0, (guint) ppp_state);
    g_dbus_method_invocation_return_value(invocation, NULL);
}

static void
impl_ppp_manager_set_ifindex(NMDBusObject                      *obj,
                             const NMDBusInterfaceInfoExtended *interface_info,
                             const NMDBusMethodInfoExtended    *method_info,
                             GDBusConnection                   *connection,
                             const char                        *sender,
                             GDBusMethodInvocation             *invocation,
                             GVariant                          *parameters)
{
    NMPPPManager                   *self            = NM_PPP_MANAGER(obj);
    gs_unref_object NMPPPManager   *self_keep_alive = NULL;
    NMPPPManagerPrivate            *priv            = NM_PPP_MANAGER_GET_PRIVATE(self);
    const NMPlatformLink           *plink           = NULL;
    nm_auto_nmpobj const NMPObject *obj_keep_alive  = NULL;
    gint32                          ifindex;

    g_variant_get(parameters, "(i)", &ifindex);

    if (priv->ifindex >= 0) {
        if (priv->ifindex == ifindex)
            _LOGD("set-ifindex: ignore repeated calls setting ifindex to %d", (int) ifindex);
        else
            _LOGW("set-ifindex: can't change the ifindex from %d to %d",
                  priv->ifindex,
                  (int) ifindex);
        goto out;
    }

    if (ifindex > 0) {
        plink = nm_platform_link_get(NM_PLATFORM_GET, ifindex);
        if (!plink) {
            /* processing events has side-effects. We need to keep self alive
             * during that.*/
            self_keep_alive = g_object_ref(self);

            nm_platform_process_events(NM_PLATFORM_GET);
            plink = nm_platform_link_get(NM_PLATFORM_GET, ifindex);
        }
    }

    if (!plink) {
        _LOGW("set-ifindex: unknown interface with ifindex %d", ifindex);
        ifindex = 0;
    } else {
        obj_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink));
        _LOGD("set-ifindex: %d, name \"%s\"", (int) ifindex, plink->name);
    }

    priv->ifindex = ifindex;

    g_signal_emit(self, signals[IFINDEX_SET], 0, ifindex, plink ? plink->name : NULL);

out:
    g_dbus_method_invocation_return_value(invocation, NULL);
}

static gboolean
set_ip_config_common(NMPPPManager *self, GVariant *config_dict, guint32 *out_mtu)
{
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self);
    NMConnection        *applied_connection;
    NMSettingPpp        *s_ppp;

    if (priv->ifindex <= 0)
        return FALSE;

    /* Got successful IP config; obviously the secrets worked */
    applied_connection = nm_act_request_get_applied_connection(priv->act_req);
    g_object_set_qdata(G_OBJECT(applied_connection), ppp_manager_secret_tries_quark(), NULL);

    if (out_mtu) {
        /* Get any custom MTU */
        s_ppp    = nm_connection_get_setting_ppp(applied_connection);
        *out_mtu = s_ppp ? nm_setting_ppp_get_mtu(s_ppp) : 0;
    }

    monitor_stats(self);
    return TRUE;
}

static void
impl_ppp_manager_set_ip4_config(NMDBusObject                      *obj,
                                const NMDBusInterfaceInfoExtended *interface_info,
                                const NMDBusMethodInfoExtended    *method_info,
                                GDBusConnection                   *connection,
                                const char                        *sender,
                                GDBusMethodInvocation             *invocation,
                                GVariant                          *parameters)
{
    NMPPPManager                           *self = NM_PPP_MANAGER(obj);
    NMPPPManagerPrivate                    *priv = NM_PPP_MANAGER_GET_PRIVATE(self);
    nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
    NMPlatformIP4Address                    address;
    guint32                                 u32, mtu;
    GVariantIter                           *iter;
    gs_unref_variant GVariant              *config_dict = NULL;

    _LOGI("(IPv4 Config Get) reply received.");

    g_variant_get(parameters, "(@a{sv})", &config_dict);

    nm_clear_g_source(&priv->ppp_timeout_handler);

    if (!set_ip_config_common(self, config_dict, &mtu))
        goto out;

    l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(NM_PLATFORM_GET),
                                 priv->ifindex,
                                 NM_IP_CONFIG_SOURCE_PPP);

    nm_l3_config_data_set_mtu(l3cd, mtu);

    address = (NMPlatformIP4Address){
        .plen = 32,
    };

    if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_ADDRESS, "u", &u32))
        address.address = u32;

    if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_PREFIX, "u", &u32))
        address.plen = u32;

    if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_GATEWAY, "u", &u32)) {
        const NMPlatformIP4Route r = {
            .ifindex       = priv->ifindex,
            .rt_source     = NM_IP_CONFIG_SOURCE_PPP,
            .gateway       = u32,
            .table_any     = TRUE,
            .table_coerced = 0,
            .metric_any    = TRUE,
            .metric        = 0,
        };

        nm_l3_config_data_add_route_4(l3cd, &r);
        address.peer_address = u32;
    } else
        address.peer_address = address.address;

    if (address.address && address.plen > 0 && address.plen <= 32) {
        address.addr_source = NM_IP_CONFIG_SOURCE_PPP;
        nm_l3_config_data_add_address_4(l3cd, &address);
    } else {
        _LOGE("invalid IPv4 address received!");
        goto out;
    }

    if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_DNS, "au", &iter)) {
        while (g_variant_iter_next(iter, "u", &u32))
            nm_l3_config_data_add_nameserver_detail(l3cd, AF_INET, &u32, NULL);
        g_variant_iter_free(iter);
    }

    if (g_variant_lookup(config_dict, NM_PPP_IP4_CONFIG_WINS, "au", &iter)) {
        while (g_variant_iter_next(iter, "u", &u32))
            nm_l3_config_data_add_wins(l3cd, u32);
        g_variant_iter_free(iter);
    }

    _emit_signal_new_config(self, AF_INET, l3cd, NULL);

out:
    g_dbus_method_invocation_return_value(invocation, NULL);
}

/* Converts the named Interface Identifier item to an IPv6 LL address and
 * returns the IID.
 */
static gboolean
iid_value_to_ll6_addr(GVariant           *dict,
                      const char         *prop,
                      struct in6_addr    *out_addr,
                      NMUtilsIPv6IfaceId *out_iid)
{
    guint64 iid;

    if (!g_variant_lookup(dict, prop, "t", &iid)) {
        _LOGD("pppd plugin property '%s' missing or not a uint64", prop);
        return FALSE;
    }
    g_return_val_if_fail(iid != 0, FALSE);

    /* Construct an IPv6 LL address from the interface identifier.  See
     * http://tools.ietf.org/html/rfc4291#section-2.5.1 (IPv6) and
     * http://tools.ietf.org/html/rfc5072#section-4.1 (IPv6 over PPP).
     */
    memset(out_addr->s6_addr, 0, sizeof(out_addr->s6_addr));
    out_addr->s6_addr16[0] = htons(0xfe80);
    memcpy(out_addr->s6_addr + 8, &iid, sizeof(iid));
    if (out_iid)
        nm_utils_ipv6_interface_identifier_get_from_addr(out_iid, out_addr);
    return TRUE;
}

static void
impl_ppp_manager_set_ip6_config(NMDBusObject                      *obj,
                                const NMDBusInterfaceInfoExtended *interface_info,
                                const NMDBusMethodInfoExtended    *method_info,
                                GDBusConnection                   *connection,
                                const char                        *sender,
                                GDBusMethodInvocation             *invocation,
                                GVariant                          *parameters)
{
    NMPPPManager                           *self = NM_PPP_MANAGER(obj);
    NMPPPManagerPrivate                    *priv = NM_PPP_MANAGER_GET_PRIVATE(self);
    nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
    NMPlatformIP6Address                    address;
    struct in6_addr                         a;
    guint32                                 mtu;
    NMUtilsIPv6IfaceId                      iid         = NM_UTILS_IPV6_IFACE_ID_INIT;
    gboolean                                has_peer    = FALSE;
    gs_unref_variant GVariant              *config_dict = NULL;

    _LOGI("(IPv6 Config Get) reply received.");

    g_variant_get(parameters, "(@a{sv})", &config_dict);

    nm_clear_g_source(&priv->ppp_timeout_handler);

    if (!set_ip_config_common(self, config_dict, &mtu))
        goto out;

    l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(NM_PLATFORM_GET),
                                 priv->ifindex,
                                 NM_IP_CONFIG_SOURCE_PPP);

    nm_l3_config_data_set_mtu(l3cd, mtu);

    address = (NMPlatformIP6Address){
        .plen        = 64,
        .addr_source = NM_IP_CONFIG_SOURCE_PPP,
    };

    if (iid_value_to_ll6_addr(config_dict, NM_PPP_IP6_CONFIG_PEER_IID, &a, NULL)) {
        const NMPlatformIP6Route r = {
            .ifindex       = priv->ifindex,
            .rt_source     = NM_IP_CONFIG_SOURCE_PPP,
            .gateway       = a,
            .table_any     = TRUE,
            .table_coerced = 0,
            .metric_any    = TRUE,
            .metric        = 0,
        };

        nm_l3_config_data_add_route_6(l3cd, &r);
        address.peer_address = a;
        has_peer             = TRUE;
    }

    if (iid_value_to_ll6_addr(config_dict, NM_PPP_IP6_CONFIG_OUR_IID, &address.address, &iid)) {
        if (!has_peer)
            address.peer_address = address.address;
        nm_l3_config_data_add_address_6(l3cd, &address);

        _emit_signal_new_config(self, AF_INET6, l3cd, &iid);
    } else
        _LOGE("invalid IPv6 address received!");

out:
    g_dbus_method_invocation_return_value(invocation, NULL);
}

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

static NM_UTILS_LOOKUP_STR_DEFINE(
    pppd_exit_code_to_str,
    int,
    NM_UTILS_LOOKUP_DEFAULT("Unknown error"),
    NM_UTILS_LOOKUP_STR_ITEM(1, "Fatal pppd error");
    NM_UTILS_LOOKUP_STR_ITEM(2, "pppd options error"),
    NM_UTILS_LOOKUP_STR_ITEM(3, "No root priv error"),
    NM_UTILS_LOOKUP_STR_ITEM(4, "No ppp module error"),
    NM_UTILS_LOOKUP_STR_ITEM(5, "pppd received a signal"),
    NM_UTILS_LOOKUP_STR_ITEM(6, "Serial port lock failed"),
    NM_UTILS_LOOKUP_STR_ITEM(7, "Serial port open failed"),
    NM_UTILS_LOOKUP_STR_ITEM(8, "Connect script failed"),
    NM_UTILS_LOOKUP_STR_ITEM(9, "Pty program error"),
    NM_UTILS_LOOKUP_STR_ITEM(10, "PPP negotiation failed"),
    NM_UTILS_LOOKUP_STR_ITEM(11, "Peer didn't authenticatie itself"),
    NM_UTILS_LOOKUP_STR_ITEM(12, "Link idle: Idle Seconds reached."),
    NM_UTILS_LOOKUP_STR_ITEM(13, "Connect time limit reached."),
    NM_UTILS_LOOKUP_STR_ITEM(14, "Callback negotiated, call should come back."),
    NM_UTILS_LOOKUP_STR_ITEM(15, "Lack of LCP echo responses"),
    NM_UTILS_LOOKUP_STR_ITEM(16, "A modem hung up the phone"),
    NM_UTILS_LOOKUP_STR_ITEM(17, "Loopback detected"),
    NM_UTILS_LOOKUP_STR_ITEM(18, "The init script failed"),
    NM_UTILS_LOOKUP_STR_ITEM(19,
                             "Authentication error. "
                             "We failed to authenticate ourselves to the peer. "
                             "Maybe bad account or password?"), );

static void
ppp_watch_cb(GPid pid, int status, gpointer user_data)
{
    NMPPPManager        *self = NM_PPP_MANAGER(user_data);
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self);
    int                  err;
    const long long      lpid = (long long) pid;

    g_return_if_fail(pid == priv->pid);

    if (WIFEXITED(status)) {
        err = WEXITSTATUS(status);
        if (err) {
            _LOGW("pppd pid %lld exited with error %d: %s", lpid, err, pppd_exit_code_to_str(err));
        } else
            _LOGD("pppd pid %lld exited with success", lpid);
    } else if (WIFSTOPPED(status)) {
        _LOGW("pppd pid %lld stopped unexpectedly with signal %d", lpid, WSTOPSIG(status));
    } else if (WIFSIGNALED(status)) {
        _LOGW("pppd pid %lld died with signal %d", lpid, WTERMSIG(status));
    } else
        _LOGW("pppd pid %lld died from an unknown cause", lpid);

    priv->pid          = 0;
    priv->ppp_watch_id = 0;
    _ppp_cleanup(self);
    g_signal_emit(self, signals[STATE_CHANGED], 0, (guint) NM_PPP_STATUS_INTERN_DEAD);
}

static gboolean
pppd_timed_out(gpointer data)
{
    NMPPPManager *self = NM_PPP_MANAGER(data);

    /* FIXME(l3cfg): we should not use NMPPPManager directly, instead use
     * NMPppMgr. That one already schedules a (better) timer. We don't need
     * a timeout here anymore.
     *
     * At this moment, NMPPPManager is still used by NMModem. Once that changes,
     * this timer needs to go. */
    _LOGW("pppd timed out or didn't initialize our dbus module");
    _ppp_manager_stop(self, NULL, NULL, NULL);

    g_signal_emit(self, signals[STATE_CHANGED], 0, (guint) NM_PPP_STATUS_INTERN_DEAD);

    return FALSE;
}

static GPtrArray *
create_pppd_cmd_line(NMPPPManager   *self,
                     NMSettingPpp   *setting,
                     NMSettingPppoe *pppoe,
                     NMSettingAdsl  *adsl,
                     const char     *ppp_name,
                     guint           baud_override,
                     gboolean        ip4_enabled,
                     gboolean        ip6_enabled,
                     GError        **err)
{
    NMPPPManagerPrivate         *priv        = NM_PPP_MANAGER_GET_PRIVATE(self);
    const char                  *pppd_binary = NULL;
    gs_unref_ptrarray GPtrArray *cmd         = NULL;
    gboolean                     ppp_debug;

    g_return_val_if_fail(setting != NULL, NULL);

#ifndef PPPD_PATH
#define PPPD_PATH NULL
#endif

    pppd_binary = nm_utils_find_helper("pppd", PPPD_PATH, err);
    if (!pppd_binary)
        return NULL;

    if (!ip4_enabled && !ip6_enabled) {
        g_set_error_literal(err,
                            NM_MANAGER_ERROR,
                            NM_MANAGER_ERROR_FAILED,
                            "Neither IPv4 or IPv6 allowed.");
        return NULL;
    }

    cmd = g_ptr_array_new_with_free_func(g_free);

    nm_strv_ptrarray_add_string_dup(cmd, pppd_binary);

    nm_strv_ptrarray_add_string_dup(cmd, "nodetach");
    nm_strv_ptrarray_add_string_dup(cmd, "lock");

    /* NM handles setting the default route */
    nm_strv_ptrarray_add_string_dup(cmd, "nodefaultroute");

    if (!ip4_enabled)
        nm_strv_ptrarray_add_string_dup(cmd, "noip");

    if (ip6_enabled) {
        /* Allow IPv6 to be configured by IPV6CP */
        nm_strv_ptrarray_add_string_dup(cmd, "ipv6");
        nm_strv_ptrarray_add_string_dup(cmd, ",");
    } else
        nm_strv_ptrarray_add_string_dup(cmd, "noipv6");

    ppp_debug = !!getenv("NM_PPP_DEBUG");
    if (nm_logging_enabled(LOGL_DEBUG, LOGD_PPP))
        ppp_debug = TRUE;

    if (ppp_debug)
        nm_strv_ptrarray_add_string_dup(cmd, "debug");

    if (ppp_name) {
        nm_strv_ptrarray_add_string_dup(cmd, "user");
        nm_strv_ptrarray_add_string_dup(cmd, ppp_name);
    }

    if (pppoe) {
        const char *pppoe_service;

        nm_strv_ptrarray_add_string_dup(cmd, "plugin");
        nm_strv_ptrarray_add_string_dup(cmd, NM_PPPOE_PLUGIN_NAME);

        nm_strv_ptrarray_add_string_concat(cmd, "nic-", priv->parent_iface);

        pppoe_service = nm_setting_pppoe_get_service(pppoe);
        if (pppoe_service) {
            nm_strv_ptrarray_add_string_dup(cmd, "rp_pppoe_service");
            nm_strv_ptrarray_add_string_dup(cmd, pppoe_service);
        }
    } else if (adsl) {
        const char *protocol = nm_setting_adsl_get_protocol(adsl);

        if (!strcmp(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA)) {
            guint32     vpi    = nm_setting_adsl_get_vpi(adsl);
            guint32     vci    = nm_setting_adsl_get_vci(adsl);
            const char *encaps = nm_setting_adsl_get_encapsulation(adsl);

            nm_strv_ptrarray_add_string_dup(cmd, "plugin");
            nm_strv_ptrarray_add_string_dup(cmd, "pppoatm.so");

            nm_strv_ptrarray_add_string_printf(cmd, "%d.%d", vpi, vci);

            if (g_strcmp0(encaps, NM_SETTING_ADSL_ENCAPSULATION_LLC) == 0)
                nm_strv_ptrarray_add_string_dup(cmd, "llc-encaps");
            else /*if (g_strcmp0 (encaps, NM_SETTING_ADSL_ENCAPSULATION_VCMUX) == 0)*/
                nm_strv_ptrarray_add_string_dup(cmd, "vc-encaps");

        } else if (!strcmp(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)) {
            nm_strv_ptrarray_add_string_dup(cmd, "plugin");
            nm_strv_ptrarray_add_string_dup(cmd, NM_PPPOE_PLUGIN_NAME);
            nm_strv_ptrarray_add_string_dup(cmd, priv->parent_iface);
        }

        nm_strv_ptrarray_add_string_dup(cmd, "noipdefault");
    } else {
        nm_strv_ptrarray_add_string_dup(cmd, priv->parent_iface);
        /* Don't send some random address as the local address */
        nm_strv_ptrarray_add_string_dup(cmd, "noipdefault");
    }

    if (nm_setting_ppp_get_baud(setting))
        nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_baud(setting));
    else if (baud_override)
        nm_strv_ptrarray_add_int(cmd, baud_override);

    /* noauth by default, because we certainly don't have any information
     * with which to verify anything the peer gives us if we ask it to
     * authenticate itself, which is what 'auth' really means.
     */
    nm_strv_ptrarray_add_string_dup(cmd, "noauth");

    if (nm_setting_ppp_get_refuse_eap(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "refuse-eap");
    if (nm_setting_ppp_get_refuse_pap(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "refuse-pap");
    if (nm_setting_ppp_get_refuse_chap(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "refuse-chap");
    if (nm_setting_ppp_get_refuse_mschap(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "refuse-mschap");
    if (nm_setting_ppp_get_refuse_mschapv2(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "refuse-mschap-v2");
    if (nm_setting_ppp_get_nobsdcomp(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "nobsdcomp");
    if (nm_setting_ppp_get_no_vj_comp(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "novj");
    if (nm_setting_ppp_get_nodeflate(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "nodeflate");
    if (nm_setting_ppp_get_require_mppe(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "require-mppe");
    if (nm_setting_ppp_get_require_mppe_128(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "require-mppe-128");
    if (nm_setting_ppp_get_mppe_stateful(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "mppe-stateful");
    if (nm_setting_ppp_get_crtscts(setting))
        nm_strv_ptrarray_add_string_dup(cmd, "crtscts");

    /* Always ask for DNS, we don't have to use them if the connection
     * overrides the returned servers.
     */
    nm_strv_ptrarray_add_string_dup(cmd, "usepeerdns");

    if (nm_setting_ppp_get_mru(setting)) {
        nm_strv_ptrarray_add_string_dup(cmd, "mru");
        nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_mru(setting));
    }

    if (nm_setting_ppp_get_mtu(setting)) {
        nm_strv_ptrarray_add_string_dup(cmd, "mtu");
        nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_mtu(setting));
    }

    nm_strv_ptrarray_add_string_dup(cmd, "lcp-echo-failure");
    nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_lcp_echo_failure(setting));

    nm_strv_ptrarray_add_string_dup(cmd, "lcp-echo-interval");
    nm_strv_ptrarray_add_int(cmd, nm_setting_ppp_get_lcp_echo_interval(setting));

    /* Avoid pppd to exit if no traffic going through */
    nm_strv_ptrarray_add_string_dup(cmd, "idle");
    nm_strv_ptrarray_add_string_dup(cmd, "0");

    nm_strv_ptrarray_add_string_dup(cmd, "ipparam");
    nm_strv_ptrarray_add_string_dup(cmd, nm_dbus_object_get_path(NM_DBUS_OBJECT(self)));

    nm_strv_ptrarray_add_string_dup(cmd, "plugin");
    nm_strv_ptrarray_add_string_dup(cmd, NM_PPPD_PLUGIN);

    if (pppoe && nm_setting_pppoe_get_parent(pppoe)) {
        static int unit;

        /* The PPP interface is going to be renamed, so pass a
         * different unit each time so that activations don't
         * race with each others. */
        nm_strv_ptrarray_add_string_dup(cmd, "unit");
        nm_strv_ptrarray_add_int(cmd, unit);
        unit = unit < G_MAXINT ? unit + 1 : 0;
    }

    g_ptr_array_add(cmd, NULL);
    return g_steal_pointer(&cmd);
}

static void
pppoe_fill_defaults(NMSettingPpp *setting)
{
    if (!nm_setting_ppp_get_mtu(setting))
        g_object_set(setting, NM_SETTING_PPP_MTU, (guint32) 1492, NULL);

    if (!nm_setting_ppp_get_mru(setting))
        g_object_set(setting, NM_SETTING_PPP_MRU, (guint32) 1492, NULL);

    g_object_set(setting, NM_SETTING_PPP_NOAUTH, TRUE, NM_SETTING_PPP_NODEFLATE, TRUE, NULL);

    /* FIXME: These commented settings should be set as well, update NMSettingPpp first. */
#if 0
    setting->noipdefault = TRUE;
    setting->default_asyncmap = TRUE;
    setting->defaultroute = TRUE;
    setting->hide_password = TRUE;
    setting->noaccomp = TRUE;
    setting->nopcomp = TRUE;
    setting->novj = TRUE;
    setting->novjccomp = TRUE;
#endif
}

static gboolean
_ppp_manager_start(NMPPPManager *self,
                   NMActRequest *req,
                   const char   *ppp_name,
                   guint32       timeout_secs,
                   guint         baud_override,
                   GError      **err)
{
    NMPPPManagerPrivate          *priv;
    NMConnection                 *connection;
    NMSettingPpp                 *s_ppp;
    gs_unref_object NMSettingPpp *s_ppp_free = NULL;
    NMSettingPppoe               *pppoe_setting;
    NMSettingAdsl                *adsl_setting;
    gs_unref_ptrarray GPtrArray  *ppp_cmd = NULL;
    gs_free char                 *cmd_str = NULL;
    struct stat                   st;
    gboolean                      ip6_enabled;
    gboolean                      ip4_enabled;

    g_return_val_if_fail(NM_IS_PPP_MANAGER(self), FALSE);
    g_return_val_if_fail(NM_IS_ACT_REQUEST(req), FALSE);

    priv = NM_PPP_MANAGER_GET_PRIVATE(self);

#if !WITH_PPP
    /* PPP support disabled */
    g_set_error_literal(err,
                        NM_MANAGER_ERROR,
                        NM_MANAGER_ERROR_FAILED,
                        "PPP support is not enabled.");
    return FALSE;
#endif

    nm_dbus_object_export(NM_DBUS_OBJECT(self));

    priv->pid = 0;

    /* Make sure /dev/ppp exists (bgo #533064) */
    if (stat("/dev/ppp", &st) || !S_ISCHR(st.st_mode))
        nmp_utils_modprobe(NULL, FALSE, "ppp_generic", NULL);

    connection = nm_act_request_get_applied_connection(req);
    g_return_val_if_fail(connection, FALSE);

    s_ppp = nm_connection_get_setting_ppp(connection);
    if (!s_ppp) {
        /* If the PPP settings are all default we may not have a PPP setting yet,
         * so just make a default one here.
         */
        s_ppp = s_ppp_free = NM_SETTING_PPP(nm_setting_ppp_new());
    }

    pppoe_setting = nm_connection_get_setting_pppoe(connection);
    if (pppoe_setting) {
        /* We can't modify the applied connection's setting, make a copy */
        if (!s_ppp_free)
            s_ppp = s_ppp_free = NM_SETTING_PPP(nm_setting_duplicate((NMSetting *) s_ppp));
        pppoe_fill_defaults(s_ppp);
    }

    adsl_setting = (NMSettingAdsl *) nm_connection_get_setting(connection, NM_TYPE_SETTING_ADSL);

    nm_utils_ppp_ip_methods_enabled(connection, &ip4_enabled, &ip6_enabled);

    ppp_cmd = create_pppd_cmd_line(self,
                                   s_ppp,
                                   pppoe_setting,
                                   adsl_setting,
                                   ppp_name,
                                   baud_override,
                                   ip4_enabled,
                                   ip6_enabled,
                                   err);
    if (!ppp_cmd)
        goto fail;

    _LOGI("starting PPP connection");

    _LOGD("command line: %s", (cmd_str = g_strjoinv(" ", (char **) ppp_cmd->pdata)));

    priv->pid = 0;
    if (!g_spawn_async(NULL,
                       (char **) ppp_cmd->pdata,
                       NULL,
                       G_SPAWN_DO_NOT_REAP_CHILD,
                       nm_utils_setpgid,
                       NULL,
                       &priv->pid,
                       err))
        goto fail;

    nm_assert(priv->pid > 0);

    _LOGI("pppd started with pid %lld", (long long) priv->pid);

    priv->ppp_watch_id = g_child_watch_add(priv->pid, (GChildWatchFunc) ppp_watch_cb, self);
    if (timeout_secs > 0)
        priv->ppp_timeout_handler = g_timeout_add_seconds(timeout_secs, pppd_timed_out, self);
    priv->act_req = g_object_ref(req);

    return TRUE;
fail:
    nm_dbus_object_unexport(NM_DBUS_OBJECT(self));
    return FALSE;
}

static void
_ppp_cleanup(NMPPPManager *self)
{
    NMPPPManagerPrivate *priv;

    g_return_if_fail(NM_IS_PPP_MANAGER(self));

    priv = NM_PPP_MANAGER_GET_PRIVATE(self);

    cancel_get_secrets(self);

    nm_clear_g_source(&priv->monitor_id);

    if (priv->monitor_fd >= 0) {
        /* Get the stats one last time */
        monitor_cb(self);
        nm_close(priv->monitor_fd);
        priv->monitor_fd = -1;
    }

    nm_clear_g_source(&priv->ppp_timeout_handler);
    nm_clear_g_source(&priv->ppp_watch_id);
}

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

struct _NMPPPManagerStopHandle {
    NMPPPManager            *self;
    NMPPPManagerStopCallback callback;
    gpointer                 user_data;

    /* this object delays shutdown, because we still need to wait until
     * pppd process terminated. */
    GObject *shutdown_waitobj;

    GCancellable *cancellable;

    gulong cancellable_id;

    guint idle_id;
};

static void
_stop_handle_complete(NMPPPManagerStopHandle *handle, gboolean was_cancelled)
{
    gs_unref_object NMPPPManager *self = NULL;
    NMPPPManagerStopCallback      callback;

    if (handle->cancellable_id) {
        g_cancellable_disconnect(handle->cancellable, nm_steal_int(&handle->cancellable_id));
    }

    g_clear_object(&handle->cancellable);

    self = g_steal_pointer(&handle->self);
    if (!self)
        return;

    if (!handle->callback)
        return;

    callback         = handle->callback;
    handle->callback = NULL;
    callback(self, handle, was_cancelled, handle->user_data);
}

static void
_stop_handle_destroy(NMPPPManagerStopHandle *handle, gboolean was_cancelled)
{
    _stop_handle_complete(handle, was_cancelled);
    nm_clear_g_source(&handle->idle_id);
    g_clear_object(&handle->shutdown_waitobj);
    g_slice_free(NMPPPManagerStopHandle, handle);
}

static void
_stop_child_cb(pid_t pid, gboolean success, int child_status, gpointer user_data)
{
    _stop_handle_destroy(user_data, FALSE);
}

static gboolean
_stop_idle_cb(gpointer user_data)
{
    NMPPPManagerStopHandle *handle = user_data;

    handle->idle_id = 0;
    _stop_handle_destroy(handle, FALSE);
    return G_SOURCE_REMOVE;
}

static void
_stop_cancelled_cb(GCancellable *cancellable, gpointer user_data)
{
    NMPPPManagerStopHandle *handle = user_data;

    nm_clear_g_signal_handler(handle->cancellable, &handle->cancellable_id);
    _ppp_manager_stop_cancel(handle);
}

static NMPPPManagerStopHandle *
_ppp_manager_stop(NMPPPManager            *self,
                  GCancellable            *cancellable,
                  NMPPPManagerStopCallback callback,
                  gpointer                 user_data)
{
    NMPPPManagerPrivate    *priv = NM_PPP_MANAGER_GET_PRIVATE(self);
    NMDBusObject           *dbus = NM_DBUS_OBJECT(self);
    NMPPPManagerStopHandle *handle;

    if (nm_dbus_object_is_exported(dbus))
        nm_dbus_object_unexport(dbus);

    _ppp_cleanup(self);

    if (!priv->pid && !callback) {
        /* nothing to do further...
         *
         * In this case, we return a %NULL handle. The caller cannot cancel this
         * event, but clearly he is not waiting for a callback anyway. */
        return NULL;
    }

    handle            = g_slice_new0(NMPPPManagerStopHandle);
    handle->self      = g_object_ref(self);
    handle->callback  = callback;
    handle->user_data = user_data;
    if (cancellable) {
        handle->cancellable = g_object_ref(cancellable);
        handle->cancellable_id =
            g_cancellable_connect(cancellable, G_CALLBACK(_stop_cancelled_cb), handle, NULL);
    }

    if (!priv->pid) {
        /* No PID. There is nothing to kill, however, invoke the callback in
         * an idle handler.
         *
         * Note that we don't register nm_shutdown_wait_obj_register_object().
         * In order for shutdown to work properly, the caller must always
         * explicitly cancel the action to go down. With the idle-handler,
         * cancelling the handle completes the request. */
        handle->idle_id = g_idle_add(_stop_idle_cb, handle);
        return handle;
    }

    /* we really want to kill the process and delay shutdown of NetworkManager
     * until the process terminated. We do that, by registering an object
     * that delays shutdown. */
    handle->shutdown_waitobj = g_object_new(G_TYPE_OBJECT, NULL);
    nm_shutdown_wait_obj_register_object(handle->shutdown_waitobj, "ppp-manager-wait-kill-pppd");
    nm_utils_kill_child_async(nm_steal_int(&priv->pid),
                              SIGTERM,
                              LOGD_PPP,
                              "pppd",
                              NM_SHUTDOWN_TIMEOUT_5000_MSEC,
                              _stop_child_cb,
                              handle);

    return handle;
}

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

static void
_ppp_manager_stop_cancel(NMPPPManagerStopHandle *handle)
{
    g_return_if_fail(handle);
    g_return_if_fail(NM_IS_PPP_MANAGER(handle->self));

    if (handle->idle_id) {
        /* we can complete this fake handle right away. */
        _stop_handle_destroy(handle, TRUE);
        return;
    }

    /* a real handle. Only invoke the callback (synchronously). This marks
     * the handle as handled, but it keeps shutdown_waitobj around, until
     * nm_utils_kill_child_async() returns. */
    _stop_handle_complete(handle, TRUE);
}

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

static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(object);

    switch (prop_id) {
    case PROP_PARENT_IFACE:
        g_value_set_string(value, priv->parent_iface);
        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)
{
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(object);

    switch (prop_id) {
    case PROP_PARENT_IFACE:
        /* construct-only */
        priv->parent_iface = g_value_dup_string(value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

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

static void
nm_ppp_manager_init(NMPPPManager *self)
{
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self);

    priv->ifindex    = -1;
    priv->monitor_fd = -1;
}

static NMPPPManager *
_ppp_manager_new(const char *iface)
{
    g_return_val_if_fail(iface != NULL, NULL);

    return g_object_new(NM_TYPE_PPP_MANAGER, NM_PPP_MANAGER_PARENT_IFACE, iface, NULL);
}

static void
dispose(GObject *object)
{
    NMPPPManager        *self = (NMPPPManager *) object;
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(self);

    /* we expect the user to first stop the manager. As fallback,
     * still stop. */
    g_warn_if_fail(!priv->pid);
    g_warn_if_fail(!nm_dbus_object_is_exported(NM_DBUS_OBJECT(self)));
    _ppp_manager_stop(self, NULL, NULL, NULL);

    g_clear_object(&priv->act_req);

    G_OBJECT_CLASS(nm_ppp_manager_parent_class)->dispose(object);
}

static void
finalize(GObject *object)
{
    NMPPPManagerPrivate *priv = NM_PPP_MANAGER_GET_PRIVATE(object);

    g_free(priv->parent_iface);

    G_OBJECT_CLASS(nm_ppp_manager_parent_class)->finalize(object);
}

static const NMDBusInterfaceInfoExtended interface_info_ppp = {
    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
        NM_DBUS_INTERFACE_PPP,
        .methods = NM_DEFINE_GDBUS_METHOD_INFOS(
            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
                    "NeedSecrets",
                    .out_args =
                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("username", "s"),
                                                  NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ),
                .handle = impl_ppp_manager_need_secrets, ),
            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
                    "SetIp4Config",
                    .in_args =
                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("config", "a{sv}"), ), ),
                .handle = impl_ppp_manager_set_ip4_config, ),
            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
                    "SetIp6Config",
                    .in_args =
                        NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("config", "a{sv}"), ), ),
                .handle = impl_ppp_manager_set_ip6_config, ),
            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
                NM_DEFINE_GDBUS_METHOD_INFO_INIT("SetState",
                                                 .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
                                                     NM_DEFINE_GDBUS_ARG_INFO("state", "u"), ), ),
                .handle = impl_ppp_manager_set_state, ),
            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
                NM_DEFINE_GDBUS_METHOD_INFO_INIT("SetIfindex",
                                                 .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
                                                     NM_DEFINE_GDBUS_ARG_INFO("ifindex", "i"), ), ),
                .handle = impl_ppp_manager_set_ifindex, ), ), ),
};

static void
nm_ppp_manager_class_init(NMPPPManagerClass *manager_class)
{
    GObjectClass      *object_class      = G_OBJECT_CLASS(manager_class);
    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(manager_class);

    object_class->dispose      = dispose;
    object_class->finalize     = finalize;
    object_class->get_property = get_property;
    object_class->set_property = set_property;

    dbus_object_class->export_path     = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/PPP");
    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_ppp);

    obj_properties[PROP_PARENT_IFACE] =
        g_param_spec_string(NM_PPP_MANAGER_PARENT_IFACE,
                            "",
                            "",
                            NULL,
                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);

    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);

    signals[STATE_CHANGED] = g_signal_new(NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
                                          G_OBJECT_CLASS_TYPE(object_class),
                                          G_SIGNAL_RUN_FIRST,
                                          0,
                                          NULL,
                                          NULL,
                                          NULL,
                                          G_TYPE_NONE,
                                          1,
                                          G_TYPE_UINT /* ppp_state */);

    signals[IFINDEX_SET] = g_signal_new(NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
                                        G_OBJECT_CLASS_TYPE(object_class),
                                        G_SIGNAL_RUN_FIRST,
                                        0,
                                        NULL,
                                        NULL,
                                        NULL,
                                        G_TYPE_NONE,
                                        2,
                                        G_TYPE_INT /* ifindex */,
                                        G_TYPE_STRING /* ifname */);

    signals[NEW_CONFIG] = g_signal_new(NM_PPP_MANAGER_SIGNAL_NEW_CONFIG,
                                       G_OBJECT_CLASS_TYPE(object_class),
                                       G_SIGNAL_RUN_FIRST,
                                       0,
                                       NULL,
                                       NULL,
                                       NULL,
                                       G_TYPE_NONE,
                                       3,
                                       G_TYPE_INT,      /* addr_family */
                                       G_TYPE_POINTER,  /* (const NML3ConfigData *) */
                                       G_TYPE_POINTER); /* (const NMUtilsIPv6IfaceId *) */

    signals[STATS] = g_signal_new(NM_PPP_MANAGER_SIGNAL_STATS,
                                  G_OBJECT_CLASS_TYPE(object_class),
                                  G_SIGNAL_RUN_FIRST,
                                  0,
                                  NULL,
                                  NULL,
                                  NULL,
                                  G_TYPE_NONE,
                                  2,
                                  G_TYPE_UINT,  /* guint32 in_bytes */
                                  G_TYPE_UINT); /* guint32 out_bytes */
}

const NMPPPOps ppp_ops = {
    .create      = _ppp_manager_new,
    .start       = _ppp_manager_start,
    .stop        = _ppp_manager_stop,
    .stop_cancel = _ppp_manager_stop_cancel,
};
