|  | /* | 
|  | * | 
|  | *  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) |