3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2026-02-07 02:28:02 +01:00
iwd/src/agent.c
Andrew Zaborowski 7fd6803c7a agent: Fix cancelling running request
If the request being cancelled by agent_request_cancel has already been
sent over dbus we need to reset pending_id, the timeout, call l_dbus_cancel
to avoid the agent_receive_reply callback (and crash) and perhaps start
the next request.  Alternatively we could only reset the callback and not
free the request, then wait until the agent method to return before starting
the next request.
2018-02-20 11:07:00 -06:00

545 lines
12 KiB
C

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2015 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <ell/ell.h>
#include "src/dbus.h"
#include "src/agent.h"
static unsigned int next_request_id = 0;
/* Agent dbus request is done from iwd towards the agent */
struct agent_request {
struct l_dbus_message *message;
unsigned int id;
void *user_data;
void *user_callback;
struct l_dbus_message *trigger;
agent_request_destroy_func_t destroy;
};
struct agent {
char *owner;
char *path;
unsigned int disconnect_watch;
uint32_t pending_id;
struct l_timeout *timeout;
int timeout_secs;
struct l_queue *requests;
};
static struct l_queue *agents;
/*
* How long we wait for user to input things.
* Return value is in seconds.
*
* This should probably be configurable by user via
* config file/command line option/env variable.
*/
static unsigned int agent_timeout_input_request(void)
{
return 120;
}
static void send_request(struct agent *agent, const char *request)
{
struct l_dbus_message *message;
l_debug("send %s request to %s %s", request, agent->owner,
agent->path);
message = l_dbus_message_new_method_call(dbus_get_bus(),
agent->owner,
agent->path,
IWD_AGENT_INTERFACE,
request);
l_dbus_message_set_arguments(message, "");
l_dbus_send(dbus_get_bus(), message);
}
static void send_cancel_request(void *user_data, int reason)
{
struct agent *agent = user_data;
struct l_dbus_message *message;
const char *reasonstr;
switch (reason) {
case -ECANCELED:
reasonstr = "user-canceled";
break;
case -ETIMEDOUT:
reasonstr = "timed-out";
break;
case -ERANGE:
reasonstr = "out-of-range";
break;
case -ESHUTDOWN:
reasonstr = "shutdown";
break;
default:
reasonstr = "unknown";
}
l_debug("send a Cancel(%s) to %s %s", reasonstr,
agent->owner, agent->path);
message = l_dbus_message_new_method_call(dbus_get_bus(),
agent->owner,
agent->path,
IWD_AGENT_INTERFACE,
"Cancel");
l_dbus_message_set_arguments(message, "s", reasonstr);
l_dbus_send(dbus_get_bus(), message);
}
static void agent_request_free(void *user_data)
{
struct agent_request *request = user_data;
l_dbus_message_unref(request->message);
if (request->trigger)
dbus_pending_reply(&request->trigger,
dbus_error_aborted(request->trigger));
if (request->destroy)
request->destroy(request->user_data);
l_free(request);
}
static void passphrase_reply(struct l_dbus_message *reply,
struct agent_request *request)
{
const char *error, *text;
char *passphrase = NULL;
enum agent_result result = AGENT_RESULT_FAILED;
agent_request_passphrase_func_t user_callback = request->user_callback;
if (l_dbus_message_get_error(reply, &error, &text))
goto done;
if (!l_dbus_message_get_arguments(reply, "s", &passphrase))
goto done;
result = AGENT_RESULT_OK;
done:
user_callback(result, passphrase, request->trigger, request->user_data);
}
static void agent_finalize_pending(struct agent *agent,
struct l_dbus_message *reply)
{
struct agent_request *pending;
if (agent->timeout) {
l_timeout_remove(agent->timeout);
agent->timeout = NULL;
}
pending = l_queue_pop_head(agent->requests);
passphrase_reply(reply, pending);
if (pending->trigger) {
l_dbus_message_unref(pending->trigger);
pending->trigger = NULL;
}
agent_request_free(pending);
}
static void agent_free(void *data)
{
struct agent *agent = data;
l_debug("agent free %p", agent);
if (agent->timeout)
l_timeout_remove(agent->timeout);
if (agent->pending_id)
l_dbus_cancel(dbus_get_bus(), agent->pending_id);
l_queue_destroy(agent->requests, agent_request_free);
if (agent->disconnect_watch)
l_dbus_remove_watch(dbus_get_bus(), agent->disconnect_watch);
l_free(agent->owner);
l_free(agent->path);
l_free(agent);
}
static void agent_send_next_request(struct agent *agent);
static void request_timeout(struct l_timeout *timeout, void *user_data)
{
struct agent *agent = user_data;
l_dbus_cancel(dbus_get_bus(), agent->pending_id);
send_cancel_request(agent, -ETIMEDOUT);
agent_finalize_pending(agent, NULL);
agent_send_next_request(agent);
}
static void agent_receive_reply(struct l_dbus_message *message,
void *user_data)
{
struct agent *agent = user_data;
l_debug("agent %p request id %u", agent, agent->pending_id);
agent->pending_id = 0;
agent_finalize_pending(agent, message);
agent_send_next_request(agent);
}
static void agent_send_next_request(struct agent *agent)
{
struct agent_request *pending;
pending = l_queue_peek_head(agent->requests);
if (!pending)
return;
agent->timeout = l_timeout_create(agent->timeout_secs,
request_timeout,
agent, NULL);
l_debug("send request to %s %s", agent->owner, agent->path);
agent->pending_id = l_dbus_send_with_reply(dbus_get_bus(),
pending->message,
agent_receive_reply,
agent, NULL);
pending->message = NULL;
return;
}
static unsigned int agent_queue_request(struct agent *agent,
struct l_dbus_message *message,
int timeout, void *callback,
struct l_dbus_message *trigger,
void *user_data,
agent_request_destroy_func_t destroy)
{
struct agent_request *request;
request = l_new(struct agent_request, 1);
request->message = message;
request->id = ++next_request_id;
request->user_data = user_data;
request->user_callback = callback;
request->trigger = l_dbus_message_ref(trigger);
request->destroy = destroy;
agent->timeout_secs = timeout;
l_queue_push_tail(agent->requests, request);
if (l_queue_length(agent->requests) == 1)
agent_send_next_request(agent);
return request->id;
}
static struct agent *agent_lookup(const char *owner)
{
const struct l_queue_entry *entry;
if (!owner)
return NULL;
for (entry = l_queue_get_entries(agents); entry; entry = entry->next) {
struct agent *agent = entry->data;
if (strcmp(agent->owner, owner))
continue;
return agent;
}
return NULL;
}
static struct agent *get_agent(const char *owner)
{
struct agent *agent = agent_lookup(owner);
if (agent)
return agent;
return l_queue_peek_head(agents);
}
/**
* agent_request_passphrase:
* @path: object path related to this request (like network object path)
* @callback: user callback called when the request is ready
* @trigger: Message associated with (e.g. that triggered) this request
* @user_data: user defined data
* @destroy: callback to release @user_data when this request finishes
*
* Called when a passphrase information is needed from the user. Returns an
* id that can be used to cancel the request.
*
* If @trigger is not NULL, then a reference is taken automatically. If
* agent_cancel_request is called subsequently, a dbus_aborted error is
* automatically generated for @trigger. Otherwise, after @callback is
* called, the reference to @trigger is dropped. It is assumed that the
* caller will take ownership of @trigger in the callback if needed.
*/
unsigned int agent_request_passphrase(const char *path,
agent_request_passphrase_func_t callback,
struct l_dbus_message *trigger,
void *user_data,
agent_request_destroy_func_t destroy)
{
struct agent *agent = get_agent(l_dbus_message_get_sender(trigger));
struct l_dbus_message *message;
if (!agent || !callback)
return 0;
l_debug("agent %p owner %s path %s", agent, agent->owner, agent->path);
message = l_dbus_message_new_method_call(dbus_get_bus(),
agent->owner,
agent->path,
IWD_AGENT_INTERFACE,
"RequestPassphrase");
l_dbus_message_set_arguments(message, "o", path);
return agent_queue_request(agent, message,
agent_timeout_input_request(),
callback, trigger, user_data, destroy);
}
static bool find_request(const void *a, const void *b)
{
const struct agent_request *request = a;
unsigned int id = L_PTR_TO_UINT(b);
return request->id == id;
}
bool agent_request_cancel(unsigned int req_id, int reason)
{
struct agent_request *request = NULL;
struct agent *agent;
const struct l_queue_entry *entry;
for (entry = l_queue_get_entries(agents); entry; entry = entry->next) {
agent = entry->data;
request = l_queue_remove_if(agent->requests, find_request,
L_UINT_TO_PTR(req_id));
if (request)
break;
}
if (!request)
return false;
if (!request->message) {
send_cancel_request(agent, reason);
l_dbus_cancel(dbus_get_bus(), agent->pending_id);
agent->pending_id = 0;
if (agent->timeout) {
l_timeout_remove(agent->timeout);
agent->timeout = NULL;
}
agent_send_next_request(agent);
}
agent_request_free(request);
return true;
}
static void agent_disconnect(struct l_dbus *dbus, void *user_data)
{
struct agent *agent = user_data;
l_debug("agent %s disconnected", agent->owner);
l_queue_remove(agents, agent);
l_idle_oneshot(agent_free, agent, NULL);
}
static struct agent *agent_create(struct l_dbus *dbus, const char *name,
const char *path)
{
struct agent *agent;
agent = l_new(struct agent, 1);
agent->owner = l_strdup(name);
agent->path = l_strdup(path);
agent->requests = l_queue_new();
agent->disconnect_watch = l_dbus_add_disconnect_watch(dbus, name,
agent_disconnect,
agent, NULL);
return agent;
}
static struct l_dbus_message *agent_register(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct agent *agent = agent_lookup(l_dbus_message_get_sender(message));
struct l_dbus_message *reply;
const char *path;
if (agent)
return dbus_error_already_exists(message);
l_debug("agent register called");
if (!l_dbus_message_get_arguments(message, "o", &path))
return dbus_error_invalid_args(message);
agent = agent_create(dbus, l_dbus_message_get_sender(message), path);
if (!agent)
return dbus_error_failed(message);
l_queue_push_tail(agents, agent);
l_debug("agent %s path %s", agent->owner, agent->path);
reply = l_dbus_message_new_method_return(message);
l_dbus_message_set_arguments(reply, "");
return reply;
}
static struct l_dbus_message *agent_unregister(struct l_dbus *dbus,
struct l_dbus_message *message,
void *user_data)
{
struct agent *agent = agent_lookup(l_dbus_message_get_sender(message));
struct l_dbus_message *reply;
l_debug("agent unregister");
if (!agent)
return dbus_error_not_found(message);
l_queue_remove(agents, agent);
agent_free(agent);
reply = l_dbus_message_new_method_return(message);
l_dbus_message_set_arguments(reply, "");
return reply;
}
static void setup_agent_interface(struct l_dbus_interface *interface)
{
l_dbus_interface_method(interface, "RegisterAgent", 0,
agent_register,
"", "o", "path");
l_dbus_interface_method(interface, "UnregisterAgent", 0,
agent_unregister,
"", "o", "path");
}
static bool release_agent(void *data, void *user_data)
{
struct agent *agent = data;
send_request(agent, "Release");
agent_free(agent);
return true;
}
bool agent_init(struct l_dbus *dbus)
{
agents = l_queue_new();
if (!l_dbus_register_interface(dbus, IWD_AGENT_MANAGER_INTERFACE,
setup_agent_interface,
NULL, false)) {
l_info("Unable to register %s interface",
IWD_AGENT_MANAGER_INTERFACE);
return false;
}
if (!l_dbus_object_add_interface(dbus, IWD_AGENT_MANAGER_PATH,
IWD_AGENT_MANAGER_INTERFACE,
NULL)) {
l_info("Unable to register the agent manager object on '%s'",
IWD_AGENT_MANAGER_PATH);
l_dbus_unregister_interface(dbus, IWD_AGENT_MANAGER_INTERFACE);
return false;
}
return true;
}
bool agent_exit(struct l_dbus *dbus)
{
l_dbus_unregister_object(dbus, IWD_AGENT_MANAGER_PATH);
l_dbus_unregister_interface(dbus, IWD_AGENT_MANAGER_INTERFACE);
l_queue_destroy(agents, agent_free);
agents = NULL;
return true;
}
void agent_shutdown(void)
{
l_queue_foreach_remove(agents, release_agent, NULL);
}