blob: 326e56f94671217e8d881d2de119f168cd8f0fad [file] [log] [blame]
/*
*
* OBEX Server
*
* Copyright (C) 2007-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; 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 <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <gdbus/gdbus.h>
#include <sys/socket.h>
#include <inttypes.h>
#include <gobex/gobex.h>
#include "btio/btio.h"
#include "obexd.h"
#include "obex.h"
#include "obex-priv.h"
#include "server.h"
#include "manager.h"
#include "log.h"
#include "service.h"
#define OBEX_BASE_PATH "/org/bluez/obex"
#define SESSION_BASE_PATH OBEX_BASE_PATH "/server"
#define OBEX_MANAGER_INTERFACE OBEXD_SERVICE ".AgentManager1"
#define ERROR_INTERFACE OBEXD_SERVICE ".Error"
#define TRANSFER_INTERFACE OBEXD_SERVICE ".Transfer1"
#define SESSION_INTERFACE OBEXD_SERVICE ".Session1"
#define AGENT_INTERFACE OBEXD_SERVICE ".Agent1"
#define TIMEOUT 60*1000 /* Timeout for user response (miliseconds) */
struct agent {
char *bus_name;
char *path;
gboolean auth_pending;
char *new_name;
char *new_folder;
unsigned int watch_id;
};
enum {
TRANSFER_STATUS_QUEUED = 0,
TRANSFER_STATUS_ACTIVE,
TRANSFER_STATUS_COMPLETE,
TRANSFER_STATUS_ERROR
};
struct obex_transfer {
uint8_t status;
char *path;
struct obex_session *session;
};
static struct agent *agent = NULL;
static DBusConnection *connection = NULL;
static void agent_free(struct agent *agent)
{
if (!agent)
return;
g_free(agent->new_folder);
g_free(agent->new_name);
g_free(agent->bus_name);
g_free(agent->path);
g_free(agent);
}
static inline DBusMessage *invalid_args(DBusMessage *msg)
{
return g_dbus_create_error(msg,
ERROR_INTERFACE ".InvalidArguments",
"Invalid arguments in method call");
}
static inline DBusMessage *not_supported(DBusMessage *msg)
{
return g_dbus_create_error(msg,
ERROR_INTERFACE ".NotSupported",
"Operation is not supported");
}
static inline DBusMessage *agent_already_exists(DBusMessage *msg)
{
return g_dbus_create_error(msg,
ERROR_INTERFACE ".AlreadyExists",
"Agent already exists");
}
static inline DBusMessage *agent_does_not_exist(DBusMessage *msg)
{
return g_dbus_create_error(msg,
ERROR_INTERFACE ".DoesNotExist",
"Agent does not exist");
}
static inline DBusMessage *not_authorized(DBusMessage *msg)
{
return g_dbus_create_error(msg,
ERROR_INTERFACE ".NotAuthorized",
"Not authorized");
}
static void agent_disconnected(DBusConnection *conn, void *user_data)
{
DBG("Agent exited");
agent_free(agent);
agent = NULL;
}
static DBusMessage *register_agent(DBusConnection *conn,
DBusMessage *msg, void *data)
{
const char *path, *sender;
if (agent)
return agent_already_exists(msg);
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID))
return invalid_args(msg);
sender = dbus_message_get_sender(msg);
agent = g_new0(struct agent, 1);
agent->bus_name = g_strdup(sender);
agent->path = g_strdup(path);
agent->watch_id = g_dbus_add_disconnect_watch(conn, sender,
agent_disconnected, NULL, NULL);
DBG("Agent registered");
return dbus_message_new_method_return(msg);
}
static DBusMessage *unregister_agent(DBusConnection *conn,
DBusMessage *msg, void *data)
{
const char *path, *sender;
if (!agent)
return agent_does_not_exist(msg);
if (!dbus_message_get_args(msg, NULL,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID))
return invalid_args(msg);
if (strcmp(agent->path, path) != 0)
return agent_does_not_exist(msg);
sender = dbus_message_get_sender(msg);
if (strcmp(agent->bus_name, sender) != 0)
return not_authorized(msg);
g_dbus_remove_watch(conn, agent->watch_id);
agent_free(agent);
agent = NULL;
DBG("Agent unregistered");
return dbus_message_new_method_return(msg);
}
static gboolean get_source(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_session *os = data;
char *s;
s = os->src;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &s);
return TRUE;
}
static gboolean get_destination(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_session *os = data;
char *s;
s = os->dst;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &s);
return TRUE;
}
static gboolean session_target_exists(const GDBusPropertyTable *property,
void *data)
{
struct obex_session *os = data;
return os->service->target ? TRUE : FALSE;
}
static char *target2str(const uint8_t *t)
{
if (!t)
return NULL;
return g_strdup_printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-"
"%02X%02X-%02X%02X%02X%02X%02X%02X",
t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7],
t[8], t[9], t[10], t[11], t[12], t[13], t[14],
t[15]);
}
static gboolean get_target(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_session *os = data;
char *uuid;
uuid = target2str(os->service->target);
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid);
g_free(uuid);
return TRUE;
}
static gboolean get_root(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
const char *root;
root = obex_option_root_folder();
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &root);
return TRUE;
}
static DBusMessage *transfer_cancel(DBusConnection *connection,
DBusMessage *msg, void *user_data)
{
struct obex_transfer *transfer = user_data;
struct obex_session *os = transfer->session;
const char *sender;
if (!os)
return invalid_args(msg);
sender = dbus_message_get_sender(msg);
if (strcmp(agent->bus_name, sender) != 0)
return not_authorized(msg);
os->aborted = TRUE;
return dbus_message_new_method_return(msg);
}
static const char *status2str(uint8_t status)
{
switch (status) {
case TRANSFER_STATUS_QUEUED:
return "queued";
case TRANSFER_STATUS_ACTIVE:
return "active";
case TRANSFER_STATUS_COMPLETE:
return "complete";
case TRANSFER_STATUS_ERROR:
default:
return "error";
}
}
static gboolean transfer_get_status(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_transfer *transfer = data;
const char *status = status2str(transfer->status);
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status);
return TRUE;
}
static gboolean transfer_get_session(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
char *path;
if (session == NULL)
return FALSE;
path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, session->id);
dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path);
g_free(path);
return TRUE;
}
static gboolean transfer_name_exists(const GDBusPropertyTable *property,
void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
return session->name != NULL;
}
static gboolean transfer_get_name(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
if (session->name == NULL)
return FALSE;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->name);
return TRUE;
}
static gboolean transfer_type_exists(const GDBusPropertyTable *property,
void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
return session->type != NULL;
}
static gboolean transfer_get_type(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
if (session->type == NULL)
return FALSE;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->type);
return TRUE;
}
static gboolean transfer_size_exists(const GDBusPropertyTable *property,
void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
return session->size != OBJECT_SIZE_UNKNOWN;
}
static gboolean transfer_get_size(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
if (session->size == OBJECT_SIZE_UNKNOWN)
return FALSE;
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &session->size);
return TRUE;
}
static gboolean transfer_time_exists(const GDBusPropertyTable *property,
void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
return session->time != 0;
}
static gboolean transfer_get_time(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
dbus_uint64_t time_u64;
if (session->size == 0)
return FALSE;
time_u64 = session->time;
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &time_u64);
return TRUE;
}
static gboolean transfer_filename_exists(const GDBusPropertyTable *property,
void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
return session->path != NULL;
}
static gboolean transfer_get_filename(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
if (session->path == NULL)
return FALSE;
dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->path);
return TRUE;
}
static gboolean transfer_get_transferred(const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{
struct obex_transfer *transfer = data;
struct obex_session *session = transfer->session;
dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64,
&session->offset);
return TRUE;
}
static const GDBusMethodTable manager_methods[] = {
{ GDBUS_METHOD("RegisterAgent",
GDBUS_ARGS({ "agent", "o" }), NULL, register_agent) },
{ GDBUS_METHOD("UnregisterAgent",
GDBUS_ARGS({ "agent", "o" }), NULL, unregister_agent) },
{ }
};
static const GDBusMethodTable transfer_methods[] = {
{ GDBUS_METHOD("Cancel", NULL, NULL, transfer_cancel) },
{ }
};
static const GDBusPropertyTable transfer_properties[] = {
{ "Status", "s", transfer_get_status },
{ "Session", "o", transfer_get_session },
{ "Name", "s", transfer_get_name, NULL, transfer_name_exists },
{ "Type", "s", transfer_get_type, NULL, transfer_type_exists },
{ "Size", "t", transfer_get_size, NULL, transfer_size_exists },
{ "Time", "t", transfer_get_time, NULL, transfer_time_exists },
{ "Filename", "s", transfer_get_filename, NULL,
transfer_filename_exists },
{ "Transferred", "t", transfer_get_transferred },
{ }
};
static const GDBusPropertyTable session_properties[] = {
{ "Source", "s", get_source },
{ "Destination", "s", get_destination },
{ "Target", "s", get_target, NULL, session_target_exists },
{ "Root", "s", get_root },
{ }
};
gboolean manager_init(void)
{
DBusError err;
DBG("");
dbus_error_init(&err);
connection = g_dbus_setup_bus(DBUS_BUS_SESSION, OBEXD_SERVICE, &err);
if (connection == NULL) {
if (dbus_error_is_set(&err) == TRUE) {
fprintf(stderr, "%s\n", err.message);
dbus_error_free(&err);
} else
fprintf(stderr, "Can't register with session bus\n");
return FALSE;
}
g_dbus_attach_object_manager(connection);
return g_dbus_register_interface(connection, OBEX_BASE_PATH,
OBEX_MANAGER_INTERFACE,
manager_methods, NULL, NULL,
NULL, NULL);
}
void manager_cleanup(void)
{
DBG("");
g_dbus_unregister_interface(connection, OBEX_BASE_PATH,
OBEX_MANAGER_INTERFACE);
/* FIXME: Release agent? */
if (agent)
agent_free(agent);
g_dbus_detach_object_manager(connection);
dbus_connection_unref(connection);
}
void manager_emit_transfer_started(struct obex_transfer *transfer)
{
transfer->status = TRANSFER_STATUS_ACTIVE;
g_dbus_emit_property_changed(connection, transfer->path,
TRANSFER_INTERFACE, "Status");
}
static void emit_transfer_completed(struct obex_transfer *transfer,
gboolean success)
{
if (transfer->path == NULL)
return;
transfer->status = success ? TRANSFER_STATUS_COMPLETE :
TRANSFER_STATUS_ERROR;
g_dbus_emit_property_changed(connection, transfer->path,
TRANSFER_INTERFACE, "Status");
}
static void emit_transfer_progress(struct obex_transfer *transfer,
uint32_t total, uint32_t transferred)
{
if (transfer->path == NULL)
return;
g_dbus_emit_property_changed(connection, transfer->path,
TRANSFER_INTERFACE, "Transferred");
}
static void transfer_free(struct obex_transfer *transfer)
{
g_free(transfer->path);
g_free(transfer);
}
struct obex_transfer *manager_register_transfer(struct obex_session *os)
{
struct obex_transfer *transfer;
static unsigned int id = 0;
transfer = g_new0(struct obex_transfer, 1);
transfer->path = g_strdup_printf("%s/session%u/transfer%u",
SESSION_BASE_PATH, os->id, id++);
transfer->session = os;
if (!g_dbus_register_interface(connection, transfer->path,
TRANSFER_INTERFACE,
transfer_methods, NULL,
transfer_properties, transfer, NULL)) {
error("Cannot register Transfer interface.");
transfer_free(transfer);
return NULL;
}
return transfer;
}
void manager_unregister_transfer(struct obex_transfer *transfer)
{
struct obex_session *os;
if (transfer == NULL)
return;
os = transfer->session;
if (transfer->status == TRANSFER_STATUS_ACTIVE)
emit_transfer_completed(transfer, os->offset == os->size);
g_dbus_unregister_interface(connection, transfer->path,
TRANSFER_INTERFACE);
transfer_free(transfer);
}
static void agent_cancel(void)
{
DBusMessage *msg;
if (agent == NULL)
return;
msg = dbus_message_new_method_call(agent->bus_name, agent->path,
AGENT_INTERFACE, "Cancel");
g_dbus_send_message(connection, msg);
}
static void agent_reply(DBusPendingCall *call, void *user_data)
{
DBusMessage *reply = dbus_pending_call_steal_reply(call);
const char *name;
DBusError derr;
gboolean *got_reply = user_data;
*got_reply = TRUE;
/* Received a reply after the agent exited */
if (!agent)
return;
agent->auth_pending = FALSE;
dbus_error_init(&derr);
if (dbus_set_error_from_message(&derr, reply)) {
error("Agent replied with an error: %s, %s",
derr.name, derr.message);
if (dbus_error_has_name(&derr, DBUS_ERROR_NO_REPLY))
agent_cancel();
dbus_error_free(&derr);
dbus_message_unref(reply);
return;
}
if (dbus_message_get_args(reply, NULL,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID)) {
/* Splits folder and name */
const char *slash = strrchr(name, '/');
DBG("Agent replied with %s", name);
if (!slash) {
agent->new_name = g_strdup(name);
agent->new_folder = NULL;
} else {
agent->new_name = g_strdup(slash + 1);
agent->new_folder = g_strndup(name, slash - name);
}
}
dbus_message_unref(reply);
}
static gboolean auth_error(GIOChannel *io, GIOCondition cond, void *user_data)
{
agent->auth_pending = FALSE;
return FALSE;
}
int manager_request_authorization(struct obex_transfer *transfer, int32_t time,
char **new_folder, char **new_name)
{
struct obex_session *os = transfer->session;
DBusMessage *msg;
DBusPendingCall *call;
unsigned int watch;
gboolean got_reply;
if (!agent)
return -1;
if (agent->auth_pending)
return -EPERM;
if (!new_folder || !new_name)
return -EINVAL;
msg = dbus_message_new_method_call(agent->bus_name, agent->path,
AGENT_INTERFACE,
"AuthorizePush");
dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &transfer->path,
DBUS_TYPE_INVALID);
if (!g_dbus_send_message_with_reply(connection, msg, &call, TIMEOUT)) {
dbus_message_unref(msg);
return -EPERM;
}
dbus_message_unref(msg);
agent->auth_pending = TRUE;
got_reply = FALSE;
/* Catches errors before authorization response comes */
watch = g_io_add_watch_full(os->io, G_PRIORITY_DEFAULT,
G_IO_HUP | G_IO_ERR | G_IO_NVAL,
auth_error, NULL, NULL);
dbus_pending_call_set_notify(call, agent_reply, &got_reply, NULL);
/* Workaround: process events while agent doesn't reply */
while (agent && agent->auth_pending)
g_main_context_iteration(NULL, TRUE);
g_source_remove(watch);
if (!got_reply) {
dbus_pending_call_cancel(call);
agent_cancel();
}
dbus_pending_call_unref(call);
if (!agent || !agent->new_name)
return -EPERM;
*new_folder = agent->new_folder;
*new_name = agent->new_name;
agent->new_folder = NULL;
agent->new_name = NULL;
return 0;
}
static DBusMessage *session_get_capabilities(DBusConnection *connection,
DBusMessage *message, void *user_data)
{
return not_supported(message);
}
static const GDBusMethodTable session_methods[] = {
{ GDBUS_ASYNC_METHOD("GetCapabilities",
NULL, GDBUS_ARGS({ "capabilities", "s" }),
session_get_capabilities) },
{ }
};
void manager_register_session(struct obex_session *os)
{
char *path;
path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, os->id);
if (!g_dbus_register_interface(connection, path,
SESSION_INTERFACE,
session_methods, NULL,
session_properties, os, NULL))
error("Cannot register Session interface.");
g_free(path);
}
void manager_unregister_session(struct obex_session *os)
{
char *path;
path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, os->id);
g_dbus_unregister_interface(connection, path, SESSION_INTERFACE);
g_free(path);
}
void manager_emit_transfer_progress(struct obex_transfer *transfer)
{
emit_transfer_progress(transfer, transfer->session->size,
transfer->session->offset);
}
void manager_emit_transfer_completed(struct obex_transfer *transfer)
{
struct obex_session *session;
if (transfer == NULL)
return;
session = transfer->session;
if (session == NULL || session->object == NULL)
return;
emit_transfer_completed(transfer, !session->aborted);
}
DBusConnection *manager_dbus_get_connection(void)
{
if (connection == NULL)
return NULL;
return dbus_connection_ref(connection);
}