/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2010 - 2013 Red Hat, Inc.
*/
#include "src/core/nm-default-daemon.h"
#include "nm-agent-manager.h"
#include <pwd.h>
#include "libnm-core-aux-intern/nm-common-macros.h"
#include "nm-dbus-interface.h"
#include "nm-secret-agent.h"
#include "nm-auth-utils.h"
#include "nm-setting-vpn.h"
#include "nm-auth-manager.h"
#include "nm-dbus-manager.h"
#include "nm-session-monitor.h"
#include "nm-simple-connection.h"
#include "NetworkManagerUtils.h"
#include "libnm-core-intern/nm-core-internal.h"
#include "c-list/src/c-list.h"
/*****************************************************************************/
enum {
AGENT_REGISTERED,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = {0};
typedef struct {
NMAuthManager * auth_mgr;
NMSessionMonitor *session_monitor;
CList agent_lst_head;
CList request_lst_head;
guint64 agent_version_id;
} NMAgentManagerPrivate;
struct _NMAgentManager {
NMDBusObject parent;
NMAgentManagerPrivate _priv;
};
struct _NMAgentManagerClass {
NMDBusObjectClass parent;
};
G_DEFINE_TYPE(NMAgentManager, nm_agent_manager, NM_TYPE_DBUS_OBJECT)
#define NM_AGENT_MANAGER_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMAgentManager, NM_IS_AGENT_MANAGER)
/*****************************************************************************/
NM_DEFINE_SINGLETON_GETTER(NMAgentManager, nm_agent_manager_get, NM_TYPE_AGENT_MANAGER);
/*****************************************************************************/
#define _NMLOG_PREFIX_NAME "agent-manager"
#define _NMLOG_DOMAIN LOGD_AGENTS
#define _NMLOG(level, agent, ...) \
G_STMT_START \
{ \
if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \
char __prefix1[32]; \
char __prefix2[128]; \
NMSecretAgent *__agent = (agent); \
\
if (!(self)) \
g_snprintf(__prefix1, \
sizeof(__prefix1), \
"%s%s", \
""_NMLOG_PREFIX_NAME \
"", \
"[]"); \
else if ((self) != singleton_instance) \
g_snprintf(__prefix1, \
sizeof(__prefix1), \
"%s[" NM_HASH_OBFUSCATE_PTR_FMT "]", \
""_NMLOG_PREFIX_NAME \
"", \
NM_HASH_OBFUSCATE_PTR(self)); \
else \
g_strlcpy(__prefix1, _NMLOG_PREFIX_NAME, sizeof(__prefix1)); \
if (__agent) { \
g_snprintf(__prefix2, \
sizeof(__prefix2), \
": agent[" NM_HASH_OBFUSCATE_PTR_FMT ",%s]", \
NM_HASH_OBFUSCATE_PTR(__agent), \
nm_secret_agent_get_description(__agent)); \
} else \
__prefix2[0] = '\0'; \
_nm_log((level), \
(_NMLOG_DOMAIN), \
0, \
NULL, \
NULL, \
"%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
__prefix1, \
__prefix2 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
#define LOG_REQ_FMT "[" NM_HASH_OBFUSCATE_PTR_FMT "/%s%s%s%s%s%s]"
#define LOG_REQ_ARG(req) \
NM_HASH_OBFUSCATE_PTR(req), NM_PRINT_FMT_QUOTE_STRING((req)->detail), \
NM_PRINT_FMT_QUOTED(((req)->request_type == REQUEST_TYPE_CON_GET) \
&& (req)->con.get.setting_name, \
"/\"", \
(req)->con.get.setting_name, \
"\"", \
((req)->request_type == REQUEST_TYPE_CON_GET \
? "/(none)" \
: _request_type_to_string((req)->request_type, FALSE)))
/*****************************************************************************/
typedef struct _NMAgentManagerCallId Request;
static void request_add_agent(Request *req, NMSecretAgent *agent);
static void request_remove_agent(Request *req, NMSecretAgent *agent);
static void request_next_agent(Request *req);
static void _con_get_request_start(Request *req);
static void _con_save_request_start(Request *req);
static void _con_del_request_start(Request *req);
static gboolean _con_get_try_complete_early(Request *req);
static void agent_disconnected_cb(NMSecretAgent *agent, gpointer user_data);
/*****************************************************************************/
guint64
nm_agent_manager_get_agent_version_id(NMAgentManager *self)
{
g_return_val_if_fail(NM_IS_AGENT_MANAGER(self), 0);
return NM_AGENT_MANAGER_GET_PRIVATE(self)->agent_version_id;
}
/*****************************************************************************/
typedef enum {
REQUEST_TYPE_INVALID,
REQUEST_TYPE_CON_GET,
REQUEST_TYPE_CON_SAVE,
REQUEST_TYPE_CON_DEL,
} RequestType;
static const char *
_request_type_to_string(RequestType request_type, gboolean verbose)
{
switch (request_type) {
case REQUEST_TYPE_CON_GET:
return verbose ? "getting" : "get";
case REQUEST_TYPE_CON_SAVE:
return verbose ? "saving" : "sav";
case REQUEST_TYPE_CON_DEL:
return verbose ? "deleting" : "del";
default:
return "??";
}
}
/*****************************************************************************/
struct _NMAgentManagerCallId {
CList request_lst;
NMAgentManager *self;
RequestType request_type;
char *detail;
NMAuthSubject *subject;
/* Current agent being asked for secrets */
NMSecretAgent * current;
NMSecretAgentCallId *current_call_id;
/* Stores the sorted list of NMSecretAgents which will be asked for secrets */
GSList *pending;
guint idle_id;
union {
struct {
char * path;
NMConnection *connection;
NMAuthChain *chain;
/* Whether the agent currently being asked for secrets
* has the system.modify privilege.
*/
gboolean current_has_modify;
union {
struct {
NMSecretAgentGetSecretsFlags flags;
char * setting_name;
char ** hints;
GVariant *existing_secrets;
NMAgentSecretsResultFunc callback;
gpointer callback_data;
} get;
};
} con;
};
};
/*****************************************************************************/
static NMSecretAgent *
_agent_find_by_owner(NMAgentManagerPrivate *priv, const char *owner)
{
NMSecretAgent *agent;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (nm_streq(nm_secret_agent_get_dbus_owner(agent), owner))
return agent;
}
return NULL;
}
static NMSecretAgent *
_agent_find_by_identifier_and_uid(NMAgentManagerPrivate *priv,
const char * identifier,
gulong sender_uid)
{
NMSecretAgent *agent;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (nm_streq0(nm_secret_agent_get_identifier(agent), identifier)
&& sender_uid == nm_secret_agent_get_owner_uid(agent))
return agent;
}
return NULL;
}
/*****************************************************************************/
static void
_agent_remove(NMAgentManager *self, NMSecretAgent *agent)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
CList * iter, *safe;
nm_assert(NM_IS_SECRET_AGENT(agent));
nm_assert(c_list_contains(&priv->agent_lst_head, &agent->agent_lst));
_LOGD(agent, "agent unregistered or disappeared");
nm_clear_pointer(&agent->auth_chain, nm_auth_chain_destroy);
c_list_unlink(&agent->agent_lst);
g_signal_handlers_disconnect_by_func(agent, G_CALLBACK(agent_disconnected_cb), self);
/* Remove this agent from any in-progress secrets requests */
c_list_for_each_safe (iter, safe, &priv->request_lst_head)
request_remove_agent(c_list_entry(iter, Request, request_lst), agent);
g_object_unref(agent);
}
/* Call this *after* calling request_next_agent() */
static void
maybe_remove_agent_on_error(NMAgentManager *self, NMSecretAgent *agent, GError *error)
{
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CLOSED)
&& !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_DISCONNECTED)
&& !g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER))
return;
if (!c_list_is_empty(&agent->agent_lst))
_agent_remove(self, agent);
}
/*****************************************************************************/
static gboolean
validate_identifier(const char *identifier, GError **error)
{
const char *p = identifier;
size_t id_len;
if (!identifier) {
g_set_error_literal(error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"No identifier was given");
return FALSE;
}
/* Length between 3 and 255 characters inclusive */
id_len = strlen(identifier);
if (id_len < 3 || id_len > 255) {
g_set_error_literal(error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"Identifier length not between 3 and 255 characters (inclusive)");
return FALSE;
}
if ((identifier[0] == '.') || (identifier[id_len - 1] == '.')) {
g_set_error_literal(error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"Identifier must not start or end with '.'");
return FALSE;
}
/* FIXME: do complete validation here */
while (p && *p) {
if (!g_ascii_isalnum(*p) && (*p != '_') && (*p != '-') && (*p != '.')) {
g_set_error(error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"Identifier contains invalid character '%c'",
*p);
return FALSE;
}
if ((*p == '.') && (*(p + 1) == '.')) {
g_set_error_literal(error,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_INVALID_IDENTIFIER,
"Identifier contains two '.' characters in sequence");
return FALSE;
}
p++;
}
return TRUE;
}
static void
_agent_permissions_check_done(NMAuthChain * chain,
GDBusMethodInvocation *context,
gpointer user_data)
{
NMAgentManager * self = NM_AGENT_MANAGER(user_data);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
NMSecretAgent * agent;
Request * request;
nm_assert(!context || G_IS_DBUS_METHOD_INVOCATION(context));
agent = nm_auth_chain_steal_data(chain, "agent");
nm_assert(NM_IS_SECRET_AGENT(agent));
nm_assert(agent->auth_chain == chain);
nm_assert(agent->fully_registered == (!context));
nm_assert(c_list_contains(&priv->agent_lst_head, &agent->agent_lst));
agent->auth_chain = NULL;
nm_secret_agent_add_permission(
agent,
NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED,
(nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED)
== NM_AUTH_CALL_RESULT_YES));
nm_secret_agent_add_permission(
agent,
NM_AUTH_PERMISSION_WIFI_SHARE_OPEN,
(nm_auth_chain_get_result(chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN)
== NM_AUTH_CALL_RESULT_YES));
if (agent->fully_registered) {
_LOGD(agent, "updated agent permissions");
return;
}
_LOGI(agent, "agent registered");
agent->fully_registered = TRUE;
priv->agent_version_id += 1;
g_dbus_method_invocation_return_value(context, NULL);
c_list_for_each_entry (request, &priv->request_lst_head, request_lst)
request_add_agent(request, agent);
g_signal_emit(self, signals[AGENT_REGISTERED], 0, agent);
}
static NMAuthChain *
_agent_create_auth_chain(NMAgentManager *self, NMSecretAgent *agent, GDBusMethodInvocation *context)
{
NMAuthChain *chain;
_LOGD(agent, "requesting permissions");
nm_assert(!agent->auth_chain
|| (agent->fully_registered == (!nm_auth_chain_get_context(agent->auth_chain))));
if (agent->auth_chain && !context && !agent->fully_registered) {
/* we restart the authorization check (without a @context), but the currently
* pending auth-chain carries a context. We need to pass it on as we replace
* the auth-chain. */
context = nm_auth_chain_get_context(agent->auth_chain);
nm_assert(context);
}
chain = nm_auth_chain_new_subject(nm_secret_agent_get_subject(agent),
context,
_agent_permissions_check_done,
self);
nm_auth_chain_set_data(chain, "agent", agent, NULL);
nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED, FALSE);
nm_auth_chain_add_call(chain, NM_AUTH_PERMISSION_WIFI_SHARE_OPEN, FALSE);
nm_clear_pointer(&agent->auth_chain, nm_auth_chain_destroy);
agent->auth_chain = chain;
return chain;
}
static void
agent_disconnected_cb(NMSecretAgent *agent, gpointer user_data)
{
_agent_remove(NM_AGENT_MANAGER(user_data), agent);
}
static void
agent_manager_register_with_capabilities(NMAgentManager * self,
GDBusMethodInvocation *context,
const char * identifier,
guint32 capabilities)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
gs_unref_object NMAuthSubject *subject = NULL;
gulong sender_uid = G_MAXULONG;
GError * error = NULL;
NMSecretAgent * agent;
subject = nm_dbus_manager_new_auth_subject_from_context(context);
if (!subject) {
error = g_error_new_literal(NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
NM_UTILS_ERROR_MSG_REQ_UID_UKNOWN);
g_dbus_method_invocation_take_error(context, error);
return;
}
sender_uid = nm_auth_subject_get_unix_process_uid(subject);
/* Validate the identifier */
if (!validate_identifier(identifier, &error)) {
g_dbus_method_invocation_take_error(context, error);
return;
}
/* Only one agent for each identifier is allowed per user */
if (_agent_find_by_identifier_and_uid(priv, identifier, sender_uid)) {
error = g_error_new_literal(NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_PERMISSION_DENIED,
"An agent with this ID is already registered for this user.");
g_dbus_method_invocation_take_error(context, error);
return;
}
agent = nm_secret_agent_new(context, subject, identifier, capabilities);
g_signal_connect(agent, NM_SECRET_AGENT_DISCONNECTED, G_CALLBACK(agent_disconnected_cb), self);
c_list_link_tail(&priv->agent_lst_head, &agent->agent_lst);
_agent_create_auth_chain(self, agent, context);
}
static void
impl_agent_manager_register(NMDBusObject * obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended * method_info,
GDBusConnection * connection,
const char * sender,
GDBusMethodInvocation * invocation,
GVariant * parameters)
{
const char *identifier;
g_variant_get(parameters, "(&s)", &identifier);
agent_manager_register_with_capabilities(NM_AGENT_MANAGER(obj), invocation, identifier, 0);
}
static void
impl_agent_manager_register_with_capabilities(NMDBusObject * obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended * method_info,
GDBusConnection * connection,
const char * sender,
GDBusMethodInvocation * invocation,
GVariant * parameters)
{
const char *identifier;
guint32 capabilities;
g_variant_get(parameters, "(&su)", &identifier, &capabilities);
agent_manager_register_with_capabilities(NM_AGENT_MANAGER(obj),
invocation,
identifier,
capabilities);
}
static void
impl_agent_manager_unregister(NMDBusObject * obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended * method_info,
GDBusConnection * connection,
const char * sender,
GDBusMethodInvocation * invocation,
GVariant * parameters)
{
NMAgentManager * self = NM_AGENT_MANAGER(obj);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
NMSecretAgent * agent;
agent = _agent_find_by_owner(priv, sender);
if (!agent) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_NOT_REGISTERED,
"Caller is not registered as an Agent");
return;
}
_agent_remove(self, agent);
g_dbus_method_invocation_return_value(invocation, NULL);
}
/*****************************************************************************/
static Request *
request_new(NMAgentManager *self,
RequestType request_type,
const char * detail,
NMAuthSubject * subject)
{
Request *req;
req = g_slice_new0(Request);
req->self = g_object_ref(self);
req->request_type = request_type;
req->detail = g_strdup(detail);
req->subject = g_object_ref(subject);
c_list_link_tail(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head, &req->request_lst);
return req;
}
static void
request_free(Request *req)
{
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
case REQUEST_TYPE_CON_SAVE:
case REQUEST_TYPE_CON_DEL:
g_object_unref(req->con.connection);
g_free(req->con.path);
nm_clear_pointer(&req->con.chain, nm_auth_chain_destroy);
if (req->request_type == REQUEST_TYPE_CON_GET) {
g_free(req->con.get.setting_name);
g_strfreev(req->con.get.hints);
if (req->con.get.existing_secrets)
g_variant_unref(req->con.get.existing_secrets);
}
break;
default:
g_assert_not_reached();
}
if (req->idle_id)
g_source_remove(req->idle_id);
/* cancel-secrets invokes the done-callback synchronously -- in which case
* the handler just return.
* Hence, we can proceed to free @req... */
nm_secret_agent_cancel_call(req->current, req->current_call_id);
g_object_unref(req->subject);
g_free(req->detail);
g_slist_free_full(req->pending, g_object_unref);
g_object_unref(req->self);
if (req->current)
g_object_unref(req->current);
memset(req, 0, sizeof(Request));
g_slice_free(Request, req);
}
static void
req_complete_release(Request * req,
GVariant * secrets,
const char *agent_dbus_owner,
const char *agent_username,
GError * error)
{
NMAgentManager *self = req->self;
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
req->con.get.callback(self,
req,
agent_dbus_owner,
agent_username,
req->con.current_has_modify,
req->con.get.setting_name,
req->con.get.flags,
error ? NULL : secrets,
error,
req->con.get.callback_data);
break;
case REQUEST_TYPE_CON_SAVE:
case REQUEST_TYPE_CON_DEL:
break;
default:
g_return_if_reached();
}
request_free(req);
}
static void
req_complete_cancel(Request *req, gboolean is_disposing)
{
gs_free_error GError *error = NULL;
nm_assert(req && req->self);
nm_assert(!c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(req->self)->request_lst_head,
&req->request_lst));
nm_utils_error_set_cancelled(&error, is_disposing, "NMAgentManager");
req_complete_release(req, NULL, NULL, NULL, error);
}
static void
req_complete(Request * req,
GVariant * secrets,
const char *agent_dbus_owner,
const char *agent_username,
GError * error)
{
NMAgentManager *self = req->self;
nm_assert(
c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head, &req->request_lst));
c_list_unlink(&req->request_lst);
req_complete_release(req, secrets, agent_dbus_owner, agent_username, error);
}
static void
req_complete_error(Request *req, GError *error)
{
req_complete(req, NULL, NULL, NULL, error);
}
static int
agent_compare_func(gconstpointer aa, gconstpointer bb, gpointer user_data)
{
NMSecretAgent * a = (NMSecretAgent *) aa;
NMSecretAgent * b = (NMSecretAgent *) bb;
Request * req = user_data;
NMSessionMonitor *sm;
gboolean a_active, b_active;
gulong a_pid, b_pid, requester;
guint64 a_start, b_start;
a_pid = nm_secret_agent_get_pid(a);
b_pid = nm_secret_agent_get_pid(b);
/* Prefer agents in the process the request came from */
if (nm_auth_subject_get_subject_type(req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
requester = nm_auth_subject_get_unix_process_pid(req->subject);
if (a_pid != b_pid) {
if (a_pid == requester)
return -1;
else if (b_pid == requester)
return 1;
}
}
/* Prefer agents in active sessions */
sm = NM_AGENT_MANAGER_GET_PRIVATE(req->self)->session_monitor;
a_active = nm_session_monitor_session_exists(sm, nm_secret_agent_get_owner_uid(a), TRUE);
b_active = nm_session_monitor_session_exists(sm, nm_secret_agent_get_owner_uid(b), TRUE);
if (a_active && !b_active)
return -1;
else if (!a_active && b_active)
return 1;
/* Prefer agents launched later (this is essentially to ease agent debugging) */
a_start = nm_utils_get_start_time_for_pid(a_pid, NULL, NULL);
b_start = nm_utils_get_start_time_for_pid(b_pid, NULL, NULL);
if (a_start > b_start)
return -1;
else if (a_start < b_start)
return 1;
return 0;
}
static void
request_add_agent(Request *req, NMSecretAgent *agent)
{
NMAgentManager *self;
g_return_if_fail(req != NULL);
g_return_if_fail(agent != NULL);
self = req->self;
if (req->request_type == REQUEST_TYPE_CON_GET) {
NMAuthSubject *subject = nm_secret_agent_get_subject(agent);
/* Ensure the caller's username exists in the connection's permissions,
* or that the permissions is empty (ie, visible by everyone).
*/
if (!nm_auth_is_subject_in_acl(req->con.connection, subject, NULL)) {
_LOGD(agent,
"agent ignored for secrets request " LOG_REQ_FMT " (not in ACL)",
LOG_REQ_ARG(req));
/* Connection not visible to this agent's user */
return;
}
}
/* If the request should filter agents by UID, do that now */
if (nm_auth_subject_get_subject_type(req->subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS) {
uid_t agent_uid, subject_uid;
agent_uid = nm_secret_agent_get_owner_uid(agent);
subject_uid = nm_auth_subject_get_unix_process_uid(req->subject);
if (agent_uid != subject_uid) {
_LOGD(agent,
"agent ignored for secrets request " LOG_REQ_FMT " "
"(uid %ld not required %ld)",
LOG_REQ_ARG(req),
(long) agent_uid,
(long) subject_uid);
return;
}
}
_LOGD(agent, "agent allowed for secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req));
/* Add this agent to the list, sorted appropriately */
req->pending =
g_slist_insert_sorted_with_data(req->pending, g_object_ref(agent), agent_compare_func, req);
}
static void
request_add_agents(NMAgentManager *self, Request *req)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
NMSecretAgent * agent;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (agent->fully_registered)
request_add_agent(req, agent);
}
}
static void
request_next_agent(Request *req)
{
NMAgentManager *self;
GError * error = NULL;
self = req->self;
nm_secret_agent_cancel_call(req->current, req->current_call_id);
nm_assert(!req->current_call_id);
g_clear_object(&req->current);
if (req->pending) {
/* Send the request to the next agent */
req->current = req->pending->data;
req->pending = g_slist_remove(req->pending, req->current);
_LOGD(req->current,
"agent %s secrets for request " LOG_REQ_FMT,
_request_type_to_string(req->request_type, TRUE),
LOG_REQ_ARG(req));
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
_con_get_request_start(req);
break;
case REQUEST_TYPE_CON_SAVE:
_con_save_request_start(req);
break;
case REQUEST_TYPE_CON_DEL:
_con_del_request_start(req);
break;
default:
g_assert_not_reached();
}
} else {
/* No more secret agents are available to fulfill this secrets request */
error = g_error_new_literal(NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_NO_SECRETS,
"No agents were available for this request.");
req_complete_error(req, error);
g_error_free(error);
}
}
static void
request_remove_agent(Request *req, NMSecretAgent *agent)
{
NMAgentManager *self;
g_return_if_fail(req != NULL);
g_return_if_fail(agent != NULL);
self = req->self;
if (agent == req->current) {
nm_assert(!g_slist_find(req->pending, agent));
_LOGD(agent, "current agent removed from secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req));
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
case REQUEST_TYPE_CON_SAVE:
case REQUEST_TYPE_CON_DEL:
/* This cancels the pending authorization requests. */
nm_clear_pointer(&req->con.chain, nm_auth_chain_destroy);
break;
default:
g_assert_not_reached();
}
request_next_agent(req);
} else if (g_slist_find(req->pending, agent)) {
req->pending = g_slist_remove(req->pending, agent);
_LOGD(agent, "agent removed from secrets request " LOG_REQ_FMT, LOG_REQ_ARG(req));
g_object_unref(agent);
}
}
static gboolean
request_start(gpointer user_data)
{
Request *req = user_data;
req->idle_id = 0;
switch (req->request_type) {
case REQUEST_TYPE_CON_GET:
if (_con_get_try_complete_early(req))
goto out;
break;
default:
break;
}
request_next_agent(req);
out:
return FALSE;
}
/*****************************************************************************/
static void
_con_get_request_done(NMSecretAgent * agent,
NMSecretAgentCallId *call_id,
GVariant * secrets,
GError * error,
gpointer user_data)
{
NMAgentManager *self;
Request * req = user_data;
GVariant * setting_secrets;
const char * agent_dbus_owner;
gs_free char * agent_name = NULL;
g_return_if_fail(call_id == req->current_call_id);
g_return_if_fail(agent == req->current);
g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET);
self = req->self;
req->current_call_id = NULL;
if (error) {
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
_LOGD(agent, "get secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req));
return;
}
_LOGD(agent,
"agent failed secrets request " LOG_REQ_FMT ": %s",
LOG_REQ_ARG(req),
error->message);
if (g_error_matches(error, NM_SECRET_AGENT_ERROR, NM_SECRET_AGENT_ERROR_USER_CANCELED)) {
error = g_error_new_literal(NM_AGENT_MANAGER_ERROR,
NM_AGENT_MANAGER_ERROR_USER_CANCELED,
"User canceled the secrets request.");
req_complete_error(req, error);
g_error_free(error);
} else {
/* Tell the failed agent we're no longer interested. */
nm_secret_agent_cancel_call(req->current, req->current_call_id);
/* Try the next agent */
request_next_agent(req);
maybe_remove_agent_on_error(self, agent, error);
}
return;
}
/* Ensure the setting we wanted secrets for got returned and has something in it */
setting_secrets =
g_variant_lookup_value(secrets, req->con.get.setting_name, NM_VARIANT_TYPE_SETTING);
if (!setting_secrets || !g_variant_n_children(setting_secrets)) {
_LOGD(agent, "agent returned no secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req));
/* Try the next agent */
request_next_agent(req);
return;
}
_LOGD(agent, "agent returned secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req));
agent_name = nm_utils_uid_to_name(nm_secret_agent_get_owner_uid(agent));
if (agent_name && !g_utf8_validate(agent_name, -1, NULL)) {
/* Needs to be UTF-8 valid since it may be pushed through D-Bus */
nm_clear_g_free(&agent_name);
}
agent_dbus_owner = nm_secret_agent_get_dbus_owner(agent);
req_complete(req, secrets, agent_dbus_owner, agent_name, NULL);
}
static void
set_secrets_not_required(NMConnection *connection, GVariant *dict)
{
GVariantIter iter, setting_iter;
const char * setting_name = NULL;
GVariant * setting_dict = NULL;
/* Iterate through the settings dicts */
g_variant_iter_init(&iter, dict);
while (g_variant_iter_next(&iter, "{&s@a{sv}}", &setting_name, &setting_dict)) {
const char *key_name = NULL;
NMSetting * setting;
GVariant * val;
setting = nm_connection_get_setting_by_name(connection, setting_name);
if (setting) {
/* Now through each secret in the setting and mark it as not required */
g_variant_iter_init(&setting_iter, setting_dict);
while (g_variant_iter_next(&setting_iter, "{&sv}", &key_name, &val)) {
/* For each secret, set the flag that it's not required; VPN
* secrets need slightly different treatment here since the
* "secrets" property is actually a dictionary of secrets.
*/
if (strcmp(setting_name, NM_SETTING_VPN_SETTING_NAME) == 0
&& strcmp(key_name, NM_SETTING_VPN_SECRETS) == 0
&& g_variant_is_of_type(val, G_VARIANT_TYPE("a{ss}"))) {
GVariantIter vpn_secret_iter;
const char * secret_name, *secret;
g_variant_iter_init(&vpn_secret_iter, val);
while (g_variant_iter_next(&vpn_secret_iter, "{&s&s}", &secret_name, &secret))
nm_setting_set_secret_flags(setting,
secret_name,
NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
NULL);
} else
nm_setting_set_secret_flags(setting,
key_name,
NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
NULL);
g_variant_unref(val);
}
}
g_variant_unref(setting_dict);
}
}
static void
_con_get_request_start_proceed(Request *req, gboolean include_system_secrets)
{
NMConnection *tmp;
g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET);
tmp = nm_simple_connection_new_clone(req->con.connection);
nm_connection_clear_secrets(tmp);
if (include_system_secrets) {
if (req->con.get.existing_secrets)
(void) nm_connection_update_secrets(tmp,
req->con.get.setting_name,
req->con.get.existing_secrets,
NULL);
} else {
/* Update secret flags in the temporary connection to indicate that
* the system secrets we're not sending to the agent aren't required,
* so the agent can properly validate UI controls and such.
*/
if (req->con.get.existing_secrets)
set_secrets_not_required(tmp, req->con.get.existing_secrets);
}
req->current_call_id = nm_secret_agent_get_secrets(req->current,
req->con.path,
tmp,
req->con.get.setting_name,
(const char **) req->con.get.hints,
req->con.get.flags,
_con_get_request_done,
req);
if (!req->current_call_id) {
g_warn_if_reached();
request_next_agent(req);
}
g_object_unref(tmp);
}
static void
_con_get_request_start_validated(NMAuthChain * chain,
GDBusMethodInvocation *context,
gpointer user_data)
{
NMAgentManager *self;
Request * req = user_data;
const char * perm;
g_return_if_fail(req->request_type == REQUEST_TYPE_CON_GET);
self = req->self;
req->con.chain = NULL;
/* If the agent obtained the 'modify' permission, we send all system secrets
* to it. If it didn't, we still ask it for secrets, but we don't send
* any system secrets.
*/
perm = nm_auth_chain_get_data(chain, "perm");
g_assert(perm);
if (nm_auth_chain_get_result(chain, perm) == NM_AUTH_CALL_RESULT_YES)
req->con.current_has_modify = TRUE;
_LOGD(req->current,
"agent " LOG_REQ_FMT " MODIFY check result %s",
LOG_REQ_ARG(req),
req->con.current_has_modify ? "YES" : "NO");
_con_get_request_start_proceed(req, req->con.current_has_modify);
}
static void
_con_get_request_start(Request *req)
{
NMAgentManager * self;
NMSettingConnection *s_con;
const char * agent_dbus_owner, *perm;
self = req->self;
req->con.current_has_modify = FALSE;
agent_dbus_owner = nm_secret_agent_get_dbus_owner(req->current);
/* If the request flags allow user interaction, and there are existing
* system secrets (or blank secrets that are supposed to be system-owned),
* check whether the agent has the 'modify' permission before sending those
* secrets to the agent. We shouldn't leak system-owned secrets to
* unprivileged users.
*/
if ((req->con.get.flags != NM_SECRET_AGENT_GET_SECRETS_FLAG_NONE)
&& (req->con.get.existing_secrets
|| _nm_connection_aggregate(req->con.connection,
NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS,
NULL))) {
_LOGD(NULL,
"(" LOG_REQ_FMT ") request has system secrets; checking agent %s for MODIFY",
LOG_REQ_ARG(req),
agent_dbus_owner);
req->con.chain = nm_auth_chain_new_subject(nm_secret_agent_get_subject(req->current),
NULL,
_con_get_request_start_validated,
req);
nm_assert(req->con.chain);
/* If the caller is the only user in the connection's permissions, then
* we use the 'modify.own' permission instead of 'modify.system'. If the
* request affects more than just the caller, require 'modify.system'.
*/
s_con = nm_connection_get_setting_connection(req->con.connection);
g_assert(s_con);
if (nm_setting_connection_get_num_permissions(s_con) == 1)
perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_OWN;
else
perm = NM_AUTH_PERMISSION_SETTINGS_MODIFY_SYSTEM;
nm_auth_chain_set_data(req->con.chain, "perm", (gpointer) perm, NULL);
nm_auth_chain_add_call_unsafe(req->con.chain, perm, TRUE);
} else {
_LOGD(NULL,
"(" LOG_REQ_FMT ") requesting user-owned secrets from agent %s",
LOG_REQ_ARG(req),
agent_dbus_owner);
_con_get_request_start_proceed(req, FALSE);
}
}
static gboolean
_con_get_try_complete_early(Request *req)
{
NMAgentManager * self;
gs_unref_variant GVariant *setting_secrets = NULL;
gs_unref_object NMConnection *tmp = NULL;
GError * error = NULL;
self = req->self;
/* Check if there are any existing secrets */
if (req->con.get.existing_secrets)
setting_secrets = g_variant_lookup_value(req->con.get.existing_secrets,
req->con.get.setting_name,
NM_VARIANT_TYPE_SETTING);
if (!setting_secrets || !g_variant_n_children(setting_secrets))
return FALSE;
/* The connection already had secrets; check if any more are required.
* If no more are required, we're done. If secrets are still needed,
* ask a secret agent for more. This allows admins to provide generic
* secrets but allow additional user-specific ones as well.
*/
tmp = nm_simple_connection_new_clone(req->con.connection);
g_assert(tmp);
if (!nm_connection_update_secrets(tmp,
req->con.get.setting_name,
req->con.get.existing_secrets,
&error)) {
req_complete_error(req, error);
g_clear_error(&error);
return TRUE;
}
/* Do we have everything we need? */
if (NM_FLAGS_HAS(req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM)
|| ((nm_connection_need_secrets(tmp, NULL) == NULL)
&& !NM_FLAGS_HAS(req->con.get.flags, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW))) {
_LOGD(NULL, "(" LOG_REQ_FMT ") system settings secrets sufficient", LOG_REQ_ARG(req));
/* Got everything, we're done */
req_complete(req, req->con.get.existing_secrets, NULL, NULL, NULL);
return TRUE;
}
_LOGD(NULL,
"(" LOG_REQ_FMT ") system settings secrets insufficient, asking agents",
LOG_REQ_ARG(req));
/* We don't, so ask some agents for additional secrets */
if (req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_NO_ERRORS && !req->pending) {
/* The request initiated from GetSecrets() via DBus,
* don't error out if any secrets are missing. */
req_complete(req, req->con.get.existing_secrets, NULL, NULL, NULL);
return TRUE;
}
/* Couldn't get secrets from system settings, so now we ask the
* agents for secrets. Let the Agent Manager handle which agents
* we'll ask and in which order.
*/
return FALSE;
}
/**
* nm_agent_manager_get_secrets:
* @self:
* @path:
* @connection:
* @subject:
* @existing_secrets:
* @flags:
* @hints:
* @callback:
* @callback_data:
*
* Requests secrets for a connection.
*
* This function cannot fail. The callback will be invoked
* asynchronously, but it will always be invoked exactly once.
* Even for cancellation and disposing of @self. In those latter
* cases, the callback is invoked synchronously during the cancellation/
* disposal.
*
* Returns: a call-id to cancel the call.
*/
NMAgentManagerCallId
nm_agent_manager_get_secrets(NMAgentManager * self,
const char * path,
NMConnection * connection,
NMAuthSubject * subject,
GVariant * existing_secrets,
const char * setting_name,
NMSecretAgentGetSecretsFlags flags,
const char *const * hints,
NMAgentSecretsResultFunc callback,
gpointer callback_data)
{
Request *req;
g_return_val_if_fail(self != NULL, NULL);
g_return_val_if_fail(path && *path, NULL);
g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
g_return_val_if_fail(callback != NULL, NULL);
nm_log_dbg(LOGD_SETTINGS,
"Secrets requested for connection %s (%s/%s)",
path,
nm_connection_get_id(connection),
setting_name);
/* NOTE: a few things in the Request handling depend on existing_secrets
* being NULL if there aren't any system-owned secrets for this connection.
* This in turn depends on nm_connection_to_dbus() and nm_setting_to_hash()
* both returning NULL if they didn't hash anything.
*/
req = request_new(self, REQUEST_TYPE_CON_GET, nm_connection_get_id(connection), subject);
req->con.path = g_strdup(path);
req->con.connection = g_object_ref(connection);
if (existing_secrets)
req->con.get.existing_secrets = g_variant_ref(existing_secrets);
req->con.get.setting_name = g_strdup(setting_name);
req->con.get.hints = g_strdupv((char **) hints);
req->con.get.flags = flags;
req->con.get.callback = callback;
req->con.get.callback_data = callback_data;
if (!(req->con.get.flags & NM_SECRET_AGENT_GET_SECRETS_FLAG_ONLY_SYSTEM))
request_add_agents(self, req);
req->idle_id = g_idle_add(request_start, req);
return req;
}
void
nm_agent_manager_cancel_secrets(NMAgentManager *self, NMAgentManagerCallId request_id)
{
g_return_if_fail(self != NULL);
g_return_if_fail(request_id);
g_return_if_fail(request_id->request_type == REQUEST_TYPE_CON_GET);
nm_assert(c_list_contains(&NM_AGENT_MANAGER_GET_PRIVATE(self)->request_lst_head,
&request_id->request_lst));
c_list_unlink(&request_id->request_lst);
req_complete_cancel(request_id, FALSE);
}
/*****************************************************************************/
static void
_con_save_request_done(NMSecretAgent * agent,
NMSecretAgentCallId *call_id,
GVariant * secrets,
GError * error,
gpointer user_data)
{
NMAgentManager *self;
Request * req = user_data;
const char * agent_dbus_owner;
g_return_if_fail(call_id == req->current_call_id);
g_return_if_fail(agent == req->current);
g_return_if_fail(req->request_type == REQUEST_TYPE_CON_SAVE);
self = req->self;
req->current_call_id = NULL;
if (error) {
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
_LOGD(agent, "save secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req));
return;
}
_LOGD(agent,
"agent failed save secrets request " LOG_REQ_FMT ": %s",
LOG_REQ_ARG(req),
error->message);
/* Try the next agent */
request_next_agent(req);
maybe_remove_agent_on_error(self, agent, error);
return;
}
_LOGD(agent, "agent saved secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req));
agent_dbus_owner = nm_secret_agent_get_dbus_owner(agent);
req_complete(req, NULL, NULL, agent_dbus_owner, NULL);
}
static void
_con_save_request_start(Request *req)
{
req->current_call_id = nm_secret_agent_save_secrets(req->current,
req->con.path,
req->con.connection,
_con_save_request_done,
req);
if (!req->current_call_id) {
g_warn_if_reached();
request_next_agent(req);
}
}
void
nm_agent_manager_save_secrets(NMAgentManager *self,
const char * path,
NMConnection * connection,
NMAuthSubject * subject)
{
Request *req;
g_return_if_fail(self);
g_return_if_fail(path && *path);
g_return_if_fail(NM_IS_CONNECTION(connection));
nm_log_dbg(LOGD_SETTINGS,
"Saving secrets for connection %s (%s)",
path,
nm_connection_get_id(connection));
req = request_new(self, REQUEST_TYPE_CON_SAVE, nm_connection_get_id(connection), subject);
req->con.path = g_strdup(path);
req->con.connection = g_object_ref(connection);
request_add_agents(self, req);
req->idle_id = g_idle_add(request_start, req);
}
/*****************************************************************************/
static void
_con_del_request_done(NMSecretAgent * agent,
NMSecretAgentCallId *call_id,
GVariant * secrets,
GError * error,
gpointer user_data)
{
NMAgentManager *self;
Request * req = user_data;
g_return_if_fail(call_id == req->current_call_id);
g_return_if_fail(agent == req->current);
g_return_if_fail(req->request_type == REQUEST_TYPE_CON_DEL);
self = req->self;
req->current_call_id = NULL;
if (error) {
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
_LOGD(agent, "delete secrets request cancelled: " LOG_REQ_FMT, LOG_REQ_ARG(req));
return;
}
_LOGD(agent,
"agent failed delete secrets request " LOG_REQ_FMT ": %s",
LOG_REQ_ARG(req),
error->message);
} else {
_LOGD(agent, "agent deleted secrets for request " LOG_REQ_FMT, LOG_REQ_ARG(req));
}
/* Tell the next agent to delete secrets */
request_next_agent(req);
if (error)
maybe_remove_agent_on_error(self, agent, error);
}
static void
_con_del_request_start(Request *req)
{
req->current_call_id = nm_secret_agent_delete_secrets(req->current,
req->con.path,
req->con.connection,
_con_del_request_done,
req);
if (!req->current_call_id) {
g_warn_if_reached();
request_next_agent(req);
}
}
void
nm_agent_manager_delete_secrets(NMAgentManager *self, const char *path, NMConnection *connection)
{
NMAuthSubject *subject;
Request * req;
g_return_if_fail(self != NULL);
g_return_if_fail(path && *path);
g_return_if_fail(NM_IS_CONNECTION(connection));
nm_log_dbg(LOGD_SETTINGS,
"Deleting secrets for connection %s (%s)",
path,
nm_connection_get_id(connection));
subject = nm_auth_subject_new_internal();
req = request_new(self, REQUEST_TYPE_CON_DEL, nm_connection_get_id(connection), subject);
req->con.path = g_strdup(path);
req->con.connection = g_object_ref(connection);
g_object_unref(subject);
request_add_agents(self, req);
req->idle_id = g_idle_add(request_start, req);
}
/*****************************************************************************/
gboolean
nm_agent_manager_has_agent_with_permission(NMAgentManager *self,
const char * username,
const char * permission)
{
NMAgentManagerPrivate *priv;
NMSecretAgent * agent;
g_return_val_if_fail(NM_IS_AGENT_MANAGER(self), FALSE);
g_return_val_if_fail(username, FALSE);
g_return_val_if_fail(permission, FALSE);
priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (!agent->fully_registered)
continue;
if (!nm_streq0(nm_secret_agent_get_owner_username(agent), username))
continue;
if (nm_secret_agent_has_permission(agent, permission))
return TRUE;
}
return FALSE;
}
/*****************************************************************************/
gboolean
nm_agent_manager_all_agents_have_capability(NMAgentManager * manager,
NMAuthSubject * subject,
NMSecretAgentCapabilities capability)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(manager);
NMSecretAgent * agent;
gboolean subject_is_unix_process =
(nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_UNIX_PROCESS);
gulong subject_uid =
subject_is_unix_process ? nm_auth_subject_get_unix_process_uid(subject) : 0u;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst) {
if (!agent->fully_registered)
continue;
if (subject_is_unix_process && nm_secret_agent_get_owner_uid(agent) != subject_uid)
continue;
if (!(nm_secret_agent_get_capabilities(agent) & capability))
return FALSE;
}
return TRUE;
}
/*****************************************************************************/
static void
authority_changed_cb(NMAuthManager *auth_manager, NMAgentManager *self)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
NMSecretAgent * agent;
c_list_for_each_entry (agent, &priv->agent_lst_head, agent_lst)
_agent_create_auth_chain(self, agent, NULL);
}
/*****************************************************************************/
static void
nm_agent_manager_init(NMAgentManager *self)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
priv->agent_version_id = 1;
c_list_init(&priv->agent_lst_head);
c_list_init(&priv->request_lst_head);
}
static void
constructed(GObject *object)
{
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(object);
G_OBJECT_CLASS(nm_agent_manager_parent_class)->constructed(object);
priv->auth_mgr = g_object_ref(nm_auth_manager_get());
priv->session_monitor = g_object_ref(nm_session_monitor_get());
nm_dbus_object_export(NM_DBUS_OBJECT(object));
g_signal_connect(priv->auth_mgr,
NM_AUTH_MANAGER_SIGNAL_CHANGED,
G_CALLBACK(authority_changed_cb),
object);
}
static void
dispose(GObject *object)
{
NMAgentManager * self = NM_AGENT_MANAGER(object);
NMAgentManagerPrivate *priv = NM_AGENT_MANAGER_GET_PRIVATE(self);
Request * request;
NMSecretAgent * agent;
while ((request = c_list_first_entry(&priv->request_lst_head, Request, request_lst))) {
c_list_unlink(&request->request_lst);
req_complete_cancel(request, TRUE);
}
while ((agent = c_list_first_entry(&priv->agent_lst_head, NMSecretAgent, agent_lst)))
_agent_remove(self, agent);
if (priv->auth_mgr) {
g_signal_handlers_disconnect_by_func(priv->auth_mgr,
G_CALLBACK(authority_changed_cb),
object);
g_clear_object(&priv->auth_mgr);
}
nm_dbus_object_unexport(NM_DBUS_OBJECT(object));
g_clear_object(&priv->session_monitor);
G_OBJECT_CLASS(nm_agent_manager_parent_class)->dispose(object);
}
static const NMDBusInterfaceInfoExtended interface_info_agent_manager = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
NM_DBUS_INTERFACE_AGENT_MANAGER,
.methods = NM_DEFINE_GDBUS_METHOD_INFOS(
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
NM_DEFINE_GDBUS_METHOD_INFO_INIT(
"Register",
.in_args =
NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("identifier", "s"), ), ),
.handle = impl_agent_manager_register, ),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
NM_DEFINE_GDBUS_METHOD_INFO_INIT(
"RegisterWithCapabilities",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS(
NM_DEFINE_GDBUS_ARG_INFO("identifier", "s"),
NM_DEFINE_GDBUS_ARG_INFO("capabilities", "u"), ), ),
.handle = impl_agent_manager_register_with_capabilities, ),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Unregister", ),
.handle = impl_agent_manager_unregister, ), ), ),
};
static void
nm_agent_manager_class_init(NMAgentManagerClass *agent_manager_class)
{
GObjectClass * object_class = G_OBJECT_CLASS(agent_manager_class);
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(agent_manager_class);
dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH_AGENT_MANAGER);
dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_agent_manager);
object_class->constructed = constructed;
object_class->dispose = dispose;
signals[AGENT_REGISTERED] = g_signal_new(NM_AGENT_MANAGER_AGENT_REGISTERED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
G_TYPE_OBJECT);
}