mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-23 07:29:28 +01:00
d04ab5ad96
It was found that if the user cancels/disconnects the agent prior to entering credentials, IWD would get stuck and could no longer accept any connect calls with the error "Operation already in progress". For example exiting iwctl in the Password prompt would cause this: iwctl $ station wlan0 connect myssid $ Password: <Ctrl-C> This was due to the agent never calling the network callback in the case of an agent disconnect. Network would wait indefinitely for the credentials, and disallow any future connect attempts. To fix this agent_finalize_pending can be called in agent_disconnect with a NULL reply which behaves the same as if there was an internal timeout and ultimately allows network to fail the connection
669 lines
16 KiB
C
669 lines
16 KiB
C
/*
|
|
*
|
|
* Wireless daemon for Linux
|
|
*
|
|
* Copyright (C) 2015-2019 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"
|
|
#include "src/iwd.h"
|
|
#include "src/module.h"
|
|
|
|
static unsigned int next_request_id = 0;
|
|
|
|
enum agent_request_type {
|
|
AGENT_REQUEST_TYPE_PASSPHRASE,
|
|
AGENT_REQUEST_TYPE_USER_NAME_PASSWD,
|
|
};
|
|
|
|
/* Agent dbus request is done from iwd towards the agent */
|
|
struct agent_request {
|
|
enum agent_request_type type;
|
|
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 user_name_passwd_reply(struct l_dbus_message *reply,
|
|
struct agent_request *request)
|
|
{
|
|
const char *error, *text;
|
|
char *username = NULL;
|
|
char *passwd = NULL;
|
|
enum agent_result result = AGENT_RESULT_FAILED;
|
|
agent_request_user_name_passwd_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, "ss", &username, &passwd))
|
|
goto done;
|
|
|
|
result = AGENT_RESULT_OK;
|
|
|
|
done:
|
|
user_callback(result, username, passwd,
|
|
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);
|
|
|
|
switch (pending->type) {
|
|
case AGENT_REQUEST_TYPE_PASSPHRASE:
|
|
passphrase_reply(reply, pending);
|
|
break;
|
|
case AGENT_REQUEST_TYPE_USER_NAME_PASSWD:
|
|
user_name_passwd_reply(reply, pending);
|
|
break;
|
|
}
|
|
|
|
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);
|
|
|
|
if (!agent->pending_id)
|
|
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,
|
|
enum agent_request_type type,
|
|
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->type = type;
|
|
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, AGENT_REQUEST_TYPE_PASSPHRASE,
|
|
message, agent_timeout_input_request(),
|
|
callback, trigger, user_data, destroy);
|
|
}
|
|
|
|
unsigned int agent_request_pkey_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,
|
|
"RequestPrivateKeyPassphrase");
|
|
|
|
l_dbus_message_set_arguments(message, "o", path);
|
|
|
|
return agent_queue_request(agent, AGENT_REQUEST_TYPE_PASSPHRASE,
|
|
message, agent_timeout_input_request(),
|
|
callback, trigger, user_data, destroy);
|
|
}
|
|
|
|
unsigned int agent_request_user_name_password(const char *path,
|
|
agent_request_user_name_passwd_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,
|
|
"RequestUserNameAndPassword");
|
|
|
|
l_dbus_message_set_arguments(message, "o", path);
|
|
|
|
return agent_queue_request(agent, AGENT_REQUEST_TYPE_USER_NAME_PASSWD,
|
|
message, agent_timeout_input_request(),
|
|
callback, trigger, user_data, destroy);
|
|
}
|
|
|
|
unsigned int agent_request_user_password(const char *path, const char *user,
|
|
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,
|
|
"RequestUserPassword");
|
|
|
|
l_dbus_message_set_arguments(message, "os", path, user ?: "");
|
|
|
|
return agent_queue_request(agent, AGENT_REQUEST_TYPE_PASSPHRASE,
|
|
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);
|
|
|
|
if (agent->pending_id)
|
|
agent_finalize_pending(agent, NULL);
|
|
|
|
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;
|
|
}
|
|
|
|
static int agent_init(void)
|
|
{
|
|
struct l_dbus *dbus = dbus_get_bus();
|
|
|
|
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 -EIO;
|
|
}
|
|
|
|
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 -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void agent_exit(void)
|
|
{
|
|
struct l_dbus *dbus = dbus_get_bus();
|
|
|
|
l_dbus_unregister_interface(dbus, IWD_AGENT_MANAGER_INTERFACE);
|
|
|
|
l_queue_destroy(agents, agent_free);
|
|
agents = NULL;
|
|
}
|
|
|
|
void agent_shutdown(void)
|
|
{
|
|
l_queue_foreach_remove(agents, release_agent, NULL);
|
|
}
|
|
|
|
IWD_MODULE(agent, agent_init, agent_exit);
|