/* * * OBEX Server * * Copyright (C) 2007-2010 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include #include "gdbus/gdbus.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 (!agent) return agent_does_not_exist(msg); 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 && session->size != OBJECT_SIZE_DELETE); } 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 || session->size == OBJECT_SIZE_DELETE) 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? */ agent_free(agent); g_dbus_detach_object_manager(connection); dbus_connection_unref(connection); } void manager_emit_transfer_property(struct obex_transfer *transfer, char *name) { if (!transfer->path) return; g_dbus_emit_property_changed(connection, transfer->path, TRANSFER_INTERFACE, name); } void manager_emit_transfer_started(struct obex_transfer *transfer) { transfer->status = TRANSFER_STATUS_ACTIVE; manager_emit_transfer_property(transfer, "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; manager_emit_transfer_property(transfer, "Status"); } 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, 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) { manager_emit_transfer_property(transfer, "Transferred"); } 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); }