| /* | |
| * | |
| * OBEX Server | |
| * | |
| * Copyright (C) 2007-2010 Intel Corporation | |
| * 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 <string.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <sys/types.h> | |
| #include <glib.h> | |
| #include <dbus/dbus.h> | |
| #include "lib/bluetooth.h" | |
| #include "gdbus/gdbus.h" | |
| #include "btio/btio.h" | |
| #include "obexd/src/plugin.h" | |
| #include "obexd/src/obex.h" | |
| #include "obexd/src/service.h" | |
| #include "obexd/src/mimetype.h" | |
| #include "obexd/src/log.h" | |
| #include "obexd/src/manager.h" | |
| #include "obexd/src/obexd.h" | |
| #include "filesystem.h" | |
| #define SYNCML_TARGET_SIZE 11 | |
| static const uint8_t SYNCML_TARGET[SYNCML_TARGET_SIZE] = { | |
| 0x53, 0x59, 0x4E, 0x43, 0x4D, 0x4C, 0x2D, 0x53, | |
| 0x59, 0x4E, 0x43 }; | |
| #define SYNCEVOLUTION_CHANNEL 19 | |
| #define SYNCEVOLUTION_RECORD "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\ | |
| <record> \ | |
| <attribute id=\"0x0001\"> \ | |
| <sequence> \ | |
| <uuid value=\"00000002-0000-1000-8000-0002ee000002\"/> \ | |
| </sequence> \ | |
| </attribute> \ | |
| \ | |
| <attribute id=\"0x0004\"> \ | |
| <sequence> \ | |
| <sequence> \ | |
| <uuid value=\"0x0100\"/> \ | |
| </sequence> \ | |
| <sequence> \ | |
| <uuid value=\"0x0003\"/> \ | |
| <uint8 value=\"%u\" name=\"channel\"/> \ | |
| </sequence> \ | |
| <sequence> \ | |
| <uuid value=\"0x0008\"/> \ | |
| </sequence> \ | |
| </sequence> \ | |
| </attribute> \ | |
| \ | |
| <attribute id=\"0x0100\"> \ | |
| <text value=\"%s\" name=\"name\"/> \ | |
| </attribute> \ | |
| </record>" | |
| #define SYNCE_BUS_NAME "org.syncevolution" | |
| #define SYNCE_PATH "/org/syncevolution/Server" | |
| #define SYNCE_SERVER_INTERFACE "org.syncevolution.Server" | |
| #define SYNCE_CONN_INTERFACE "org.syncevolution.Connection" | |
| struct synce_context { | |
| struct obex_session *os; | |
| DBusConnection *dbus_conn; | |
| char *conn_obj; | |
| unsigned int reply_watch; | |
| unsigned int abort_watch; | |
| GString *buffer; | |
| int lasterr; | |
| char *id; | |
| }; | |
| static void append_dict_entry(DBusMessageIter *dict, const char *key, | |
| int type, void *val) | |
| { | |
| DBusMessageIter entry; | |
| dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, | |
| NULL, &entry); | |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); | |
| dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &val); | |
| dbus_message_iter_close_container(dict, &entry); | |
| } | |
| static gboolean reply_signal(DBusConnection *conn, DBusMessage *msg, | |
| void *data) | |
| { | |
| struct synce_context *context = data; | |
| const char *path = dbus_message_get_path(msg); | |
| DBusMessageIter iter, array_iter; | |
| char *value; | |
| int length; | |
| if (strcmp(context->conn_obj, path) != 0) { | |
| obex_object_set_io_flags(context, G_IO_ERR, -EPERM); | |
| context->lasterr = -EPERM; | |
| return FALSE; | |
| } | |
| dbus_message_iter_init(msg, &iter); | |
| dbus_message_iter_recurse(&iter, &array_iter); | |
| dbus_message_iter_get_fixed_array(&array_iter, &value, &length); | |
| context->buffer = g_string_new_len(value, length); | |
| obex_object_set_io_flags(context, G_IO_IN, 0); | |
| context->lasterr = 0; | |
| return TRUE; | |
| } | |
| static gboolean abort_signal(DBusConnection *conn, DBusMessage *msg, | |
| void *data) | |
| { | |
| struct synce_context *context = data; | |
| obex_object_set_io_flags(context, G_IO_ERR, -EPERM); | |
| context->lasterr = -EPERM; | |
| return TRUE; | |
| } | |
| static void connect_cb(DBusPendingCall *call, void *user_data) | |
| { | |
| struct synce_context *context = user_data; | |
| DBusConnection *conn; | |
| DBusMessage *reply; | |
| DBusError err; | |
| char *path; | |
| conn = context->dbus_conn; | |
| reply = dbus_pending_call_steal_reply(call); | |
| dbus_error_init(&err); | |
| if (dbus_message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &path, | |
| DBUS_TYPE_INVALID) == FALSE) { | |
| error("%s", err.message); | |
| dbus_error_free(&err); | |
| goto failed; | |
| } | |
| DBG("Got conn object %s from syncevolution", path); | |
| context->conn_obj = g_strdup(path); | |
| context->reply_watch = g_dbus_add_signal_watch(conn, NULL, path, | |
| SYNCE_CONN_INTERFACE, "Reply", | |
| reply_signal, context, NULL); | |
| context->abort_watch = g_dbus_add_signal_watch(conn, NULL, path, | |
| SYNCE_CONN_INTERFACE, "Abort", | |
| abort_signal, context, NULL); | |
| dbus_message_unref(reply); | |
| return; | |
| failed: | |
| obex_object_set_io_flags(context, G_IO_ERR, -EPERM); | |
| context->lasterr = -EPERM; | |
| } | |
| static void process_cb(DBusPendingCall *call, void *user_data) | |
| { | |
| struct synce_context *context = user_data; | |
| DBusMessage *reply; | |
| DBusError derr; | |
| reply = dbus_pending_call_steal_reply(call); | |
| dbus_error_init(&derr); | |
| if (dbus_set_error_from_message(&derr, reply)) { | |
| error("process_cb(): syncevolution replied with an error:" | |
| " %s, %s", derr.name, derr.message); | |
| dbus_error_free(&derr); | |
| obex_object_set_io_flags(context, G_IO_ERR, -EPERM); | |
| context->lasterr = -EPERM; | |
| goto done; | |
| } | |
| obex_object_set_io_flags(context, G_IO_OUT, 0); | |
| context->lasterr = 0; | |
| done: | |
| dbus_message_unref(reply); | |
| } | |
| static void *synce_connect(struct obex_session *os, int *err) | |
| { | |
| DBusConnection *conn; | |
| struct synce_context *context; | |
| char *address; | |
| manager_register_session(os); | |
| conn = manager_dbus_get_connection(); | |
| if (!conn) | |
| goto failed; | |
| context = g_new0(struct synce_context, 1); | |
| context->dbus_conn = conn; | |
| context->lasterr = -EAGAIN; | |
| context->os = os; | |
| if (obex_getpeername(os, &address) == 0) { | |
| context->id = g_strdup_printf("%s+%d", address, | |
| SYNCEVOLUTION_CHANNEL); | |
| g_free(address); | |
| } | |
| if (err) | |
| *err = 0; | |
| return context; | |
| failed: | |
| if (err) | |
| *err = -EPERM; | |
| return NULL; | |
| } | |
| static int synce_put(struct obex_session *os, void *user_data) | |
| { | |
| return 0; | |
| } | |
| static int synce_get(struct obex_session *os, void *user_data) | |
| { | |
| return obex_get_stream_start(os, NULL); | |
| } | |
| static void close_cb(DBusPendingCall *call, void *user_data) | |
| { | |
| DBusMessage *reply; | |
| DBusError derr; | |
| reply = dbus_pending_call_steal_reply(call); | |
| dbus_error_init(&derr); | |
| if (dbus_set_error_from_message(&derr, reply)) { | |
| error("close_cb(): syncevolution replied with an error:" | |
| " %s, %s", derr.name, derr.message); | |
| dbus_error_free(&derr); | |
| } | |
| dbus_message_unref(reply); | |
| } | |
| static void synce_disconnect(struct obex_session *os, void *user_data) | |
| { | |
| struct synce_context *context = user_data; | |
| g_free(context); | |
| } | |
| static void *synce_open(const char *name, int oflag, mode_t mode, | |
| void *user_data, size_t *size, int *err) | |
| { | |
| struct synce_context *context = user_data; | |
| if (err) | |
| *err = context ? 0 : -EFAULT; | |
| return user_data; | |
| } | |
| static int synce_close(void *object) | |
| { | |
| struct synce_context *context = object; | |
| DBusMessage *msg; | |
| const char *error; | |
| gboolean normal; | |
| DBusPendingCall *call; | |
| if (!context->conn_obj) | |
| goto done; | |
| msg = dbus_message_new_method_call(SYNCE_BUS_NAME, context->conn_obj, | |
| SYNCE_CONN_INTERFACE, "Close"); | |
| if (!msg) | |
| goto failed; | |
| normal = TRUE; | |
| error = "none"; | |
| dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &normal, | |
| DBUS_TYPE_STRING, &error, DBUS_TYPE_INVALID); | |
| g_dbus_send_message_with_reply(context->dbus_conn, msg, &call, -1); | |
| dbus_pending_call_set_notify(call, close_cb, NULL, NULL); | |
| dbus_message_unref(msg); | |
| dbus_pending_call_unref(call); | |
| failed: | |
| g_dbus_remove_watch(context->dbus_conn, context->reply_watch); | |
| context->reply_watch = 0; | |
| g_dbus_remove_watch(context->dbus_conn, context->abort_watch); | |
| context->abort_watch = 0; | |
| g_free(context->conn_obj); | |
| context->conn_obj = NULL; | |
| done: | |
| dbus_connection_unref(context->dbus_conn); | |
| g_free(context); | |
| return 0; | |
| } | |
| static ssize_t synce_read(void *object, void *buf, size_t count) | |
| { | |
| struct synce_context *context = object; | |
| DBusConnection *conn; | |
| char transport[36], transport_description[24]; | |
| const char *session; | |
| DBusMessage *msg; | |
| DBusMessageIter iter, dict; | |
| gboolean authenticate; | |
| DBusPendingCall *call; | |
| if (context->buffer) | |
| return string_read(context->buffer, buf, count); | |
| conn = manager_dbus_get_connection(); | |
| if (conn == NULL) | |
| return -EPERM; | |
| msg = dbus_message_new_method_call(SYNCE_BUS_NAME, SYNCE_PATH, | |
| SYNCE_SERVER_INTERFACE, "Connect"); | |
| if (!msg) | |
| return -EPERM; | |
| dbus_message_iter_init_append(msg, &iter); | |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, | |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING | |
| DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING | |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); | |
| append_dict_entry(&dict, "id", DBUS_TYPE_STRING, context->id); | |
| snprintf(transport, sizeof(transport), "%s.obexd", OBEXD_SERVICE); | |
| append_dict_entry(&dict, "transport", DBUS_TYPE_STRING, transport); | |
| snprintf(transport_description, sizeof(transport_description), | |
| "version %s", VERSION); | |
| append_dict_entry(&dict, "transport_description", DBUS_TYPE_STRING, | |
| transport_description); | |
| dbus_message_iter_close_container(&iter, &dict); | |
| authenticate = FALSE; | |
| session = ""; | |
| dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &authenticate, | |
| DBUS_TYPE_STRING, &session, DBUS_TYPE_INVALID); | |
| if (!g_dbus_send_message_with_reply(conn, msg, &call, -1)) { | |
| error("D-Bus call to %s failed.", SYNCE_SERVER_INTERFACE); | |
| dbus_message_unref(msg); | |
| return -EPERM; | |
| } | |
| dbus_pending_call_set_notify(call, connect_cb, context, NULL); | |
| dbus_pending_call_unref(call); | |
| dbus_message_unref(msg); | |
| return -EAGAIN; | |
| } | |
| static ssize_t synce_write(void *object, const void *buf, size_t count) | |
| { | |
| struct synce_context *context = object; | |
| DBusMessage *msg; | |
| DBusMessageIter iter, array_iter; | |
| DBusPendingCall *call; | |
| const char *type = obex_get_type(context->os); | |
| if (context->lasterr == 0) | |
| return count; | |
| if (!context->conn_obj) | |
| return -EFAULT; | |
| msg = dbus_message_new_method_call(SYNCE_BUS_NAME, context->conn_obj, | |
| SYNCE_CONN_INTERFACE, "Process"); | |
| if (!msg) | |
| return -EFAULT; | |
| dbus_message_iter_init_append(msg, &iter); | |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, | |
| DBUS_TYPE_BYTE_AS_STRING, &array_iter); | |
| dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE, | |
| &buf, count); | |
| dbus_message_iter_close_container(&iter, &array_iter); | |
| dbus_message_append_args(msg, DBUS_TYPE_STRING, &type, | |
| DBUS_TYPE_INVALID); | |
| if (!g_dbus_send_message_with_reply(context->dbus_conn, msg, | |
| &call, -1)) { | |
| error("D-Bus call to %s failed.", SYNCE_CONN_INTERFACE); | |
| dbus_message_unref(msg); | |
| return -EPERM; | |
| } | |
| dbus_pending_call_set_notify(call, process_cb, context, NULL); | |
| dbus_message_unref(msg); | |
| dbus_pending_call_unref(call); | |
| return -EAGAIN; | |
| } | |
| static struct obex_mime_type_driver synce_driver = { | |
| .target = SYNCML_TARGET, | |
| .target_size = SYNCML_TARGET_SIZE, | |
| .open = synce_open, | |
| .close = synce_close, | |
| .read = synce_read, | |
| .write = synce_write, | |
| }; | |
| static struct obex_service_driver synce = { | |
| .name = "OBEX server for SyncML, using SyncEvolution", | |
| .service = OBEX_SYNCEVOLUTION, | |
| .channel = SYNCEVOLUTION_CHANNEL, | |
| .secure = TRUE, | |
| .record = SYNCEVOLUTION_RECORD, | |
| .target = SYNCML_TARGET, | |
| .target_size = SYNCML_TARGET_SIZE, | |
| .get = synce_get, | |
| .put = synce_put, | |
| .connect = synce_connect, | |
| .disconnect = synce_disconnect, | |
| }; | |
| static int synce_init(void) | |
| { | |
| int err; | |
| err = obex_mime_type_driver_register(&synce_driver); | |
| if (err < 0) | |
| return err; | |
| return obex_service_driver_register(&synce); | |
| } | |
| static void synce_exit(void) | |
| { | |
| obex_service_driver_unregister(&synce); | |
| obex_mime_type_driver_unregister(&synce_driver); | |
| } | |
| OBEX_PLUGIN_DEFINE(syncevolution, synce_init, synce_exit) |