blob: 5e40e1a706e105f0d75db587b67b97e81a9d098c [file] [log] [blame]
/*
*
* 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);