/* * libsyncml - A syncml protocol implementation * Copyright (C) 2005 Armin Bauer * Copyright (C) 2008 Felix Moeller (man page) * Copyright (C) 2008-2009 Michael Bell * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /** @page syncml-ds-tool * * @section SYNOPSIS * syncml-ds-tool 'OPTIONS' ... * * @brief A SyncML Data Synchronization tool for Unix. * The syncml-ds-tool supports OMA DS server and client mode. * Additionally OBEX and HTTP transports are supported in * client and server mode. * * @section OPTIONS * * @subsection OPTIONS_DS_CONFIG Datastore configuration * * @param --sync "" "" "" * normal two-way sync * * @param --slow-sync "" "" "" * slow two-way sync * * @arg @b type is the content-type of a datastore * text/x-vcard for contacts * text/x-vcalendar for calendar * text/plain for notes * text/x-vMessage for SMS * * @arg @b path is the used (virtual) source URL/path * It is the local name of the database. * You can choose something there. * * @arg @b directory is the local path to the synchronized directory * The directory is optional and an absolute path. * This directory is a persistent storage. * * @subsection OPTION_HTTP_CLIENT_CONFIG HTTP client configuration * * @param --http-client "" * * @arg @b url must be an http URL like http://localhost:8080 * * @subsection OPTION_HTTP_CLIENT_SERVER_CONFIG HTTP server configuration * * @param --http-server "" * * @arg @b port must be a port for the http server. * * @subsection OPTION_OBEX_CLIENT_CONFIG OBEX client configuration * * @param -s "" * Connect to the serial device. * * @param -u * List all available USB interfaces. * * @param -u "" * Connect to the given usb interface number. You may discover them with -u. * * @param -b "" "" * Connect to the given bluetooth device. * * @param --ip "" "" * Connect to this TCP/IP address. * * @param --irda * Connect using IrDA. * * @param --irda-service "" * Use the given IrDA service (default: OBEX). * * @subsection OPTION_OBEX_SERVER_CONFIG OBEX server configuration * * @param --port "" * Listen to this TCP/IP port. * * @subsection OPTION_GENERAL General SyncML options * * @param --identifier "" * set the local identity of SyncML (source). * * @param --target "" * set the remote identity of SyncML (target). * * @param --username "" * set the username for authentication. * * @param --password "" * set the password for authentication. * * @param --maxMsgSize "" * set the maximum message size (default: 0) * * @param --maxObjSize* "" * set the maximum object size (default: 0) * * @param --useStringTable * Use wbxml string tables (default: NEVER EVER) * * @param --disableNumberOfChanges * the most new phones support it (default: enabled) * * @param --useNumberAnchor * Use numbers as anchors. * * @param --wbxml * Use wbxml (WAP Binary XML) instead of plain xml. * * @param --read-only * No write actions will be performed. * * @param --remoteWinsConflicts * If there is a conflict then the remote change wins. * The default is local wins always. * This option is only usable in OMA DS server mode. * * @param --dumpinfo * Print info about the phone. * * @param --version * prints the version of the tool. * * @param --version "" * sets the SyncML version. * * @arg @b ident Some devices require a special identity string. * Nokias for example often requires "PC Suite". * Please use --identifier "PC Suite" in this case. * @arg @b version can be "1.0", "1.1" or "1.2". * The default version is "1.1". * * @subsection OPTION_FAKE_DEVICE Device faking options * * Some SyncML servers try to enforce access policies via device filtering. * These options can be used to work around such filters. * * @param --fake-manufacturer "" * set the manufacturer of the faked device. * * @param --fake-model "" * set the model of the faked device. * * @param --fake-software-version "" * set the software version of the faked device. * * @section EXAMPLES * * @subsection EXAMPLE_BLUETOOTH Get the contacts from your phone via Bluetooth * @verbatim $ syncml-ds-tool -b --slow-sync text/x-vcard contacts --wbxml --identifier "PC Suite" @endverbatim * @subsection EXAMPLE_USB Get the notes from a USB connected phone * @verbatim $ syncml-ds-tool -u --slow-sync text/plain notes --wbxml --identifier "PC Suite" @endverbatim * * @section BUGS * * @par * There is a bugtracker running at http://libsyncml.opensync.org/. * If you have a problem please look there to see * if it is already reported and add your information * to the relavant ticket if possible. * * @par * When opening a new ticket please provide as many information as possible. * For faster processing of your bug it helps to attach the trace files. * You may find a description how to create them at * http://opensync.org/wiki/tracing. * For crashes the output of gdb could help. * * @section AUTHOR * * Written by Felix Moeller, * * @section RESOURCES * * Website: http://libsyncml.opensync.org * * @section COPYING * * Copyright (C) 2008 OpenSync Team. Free use of this software is * granted under the terms of the GNU Lesser General Public License (LGPL). * */ #include #include #include #include #include #include #include #include "syncml-ds-tool_internals.h" #include "config.h" #ifdef ENABLE_OBEX /* necessary for list_interfaces */ #include #endif #define STATUS_FILENAME "SYNCML-DS-TOOL-LAST-SYNC" /* ************************************ */ /* *********** CONFIG ***************** */ /* ************************************ */ #define TOOL_ERROR g_quark_from_static_string("syncml-ds-tool") typedef struct SmlDsToolLocationType { SmlDataSyncDataStore *object; SmlDataSyncDataStoreSession *session; /* char *source; */ gboolean slow; /* char *contentType; */ char *directory; GKeyFile *index; } SmlDsToolLocationType; GList *datastores = NULL; char *identifier = NULL; char *target = NULL; char *username = NULL; char *password = NULL; SmlSessionType sessionType = SML_SESSION_TYPE_SERVER; gboolean useWbxml = FALSE; char *syncmlVersion = NULL; char *maxMsgSize = NULL; char *maxObjSize = NULL; GMutex *runMutex = NULL; gboolean dumpinfo = FALSE; gboolean useNumberOfChanges = TRUE; gboolean useStringTable = FALSE; SmlDevInf *remoteDevinf = NULL; gboolean localWinsConflicts = TRUE; time_t checkpoint; gboolean readOnly = FALSE; /* ************************************ */ /* *********** USAGE ****************** */ /* ************************************ */ static void usage (char *name, gboolean isError) { fprintf(stderr, "Usage: %s\n\n", name); fprintf(stderr, "\tDatastore configuration:\n"); fprintf(stderr, "\t========================\n\n"); fprintf(stderr, "\t--sync \tnormal two-way sync\n"); fprintf(stderr, "\t--slow-sync \tslow two-way sync\n\n"); fprintf(stderr, "\t\tis the content-type of a datastore\n"); fprintf(stderr, "\t\ttext/x-vcard for contacts\n"); fprintf(stderr, "\t\ttext/x-vcalendar for calendar\n"); fprintf(stderr, "\t\ttext/plain for notes\n"); fprintf(stderr, "\t\ttext/x-vMessage for SMS\n"); fprintf(stderr, "\t\tis the used (virtual) source URL/path\n"); fprintf(stderr, "\t\tIt is the local name of the database.\n"); fprintf(stderr, "\t\tYou can choose something there.\n"); fprintf(stderr, "\t\tis the local path to the synchronized directory\n"); fprintf(stderr, "\t\tThe directory is optional and an absolute path.\n"); fprintf(stderr, "\t\tThis directory is a persistent storage.\n\n"); fprintf(stderr, "\tHTTP client configuration:\n"); fprintf(stderr, "\t==========================\n\n"); fprintf(stderr, "\t--http-client \n\n"); fprintf(stderr, "\t--ssl-ca-certs \n\n"); fprintf(stderr, "\t\tmust be an http URL like http://localhost:8080\n\n"); fprintf(stderr, "\tHTTP server configuration:\n"); fprintf(stderr, "\t==========================\n\n"); fprintf(stderr, "\t--http-server \n"); fprintf(stderr, "\t--ssl-key \n"); fprintf(stderr, "\t--ssl-cert \n\n"); fprintf(stderr, "\t\tmust be a port for the http server.\n\n"); #ifdef ENABLE_OBEX fprintf(stderr, "\tOBEX client configuration:\n"); fprintf(stderr, "\t==========================\n\n"); fprintf(stderr, "\t-s \tConnect to the serial device.\n"); fprintf(stderr, "\t-u\t\tList all available USB interfaces.\n"); fprintf(stderr, "\t-u \t\tConnect to the given usb interface number.\n"); #ifdef ENABLE_BLUETOOTH fprintf(stderr, "\t-b \tConnect to the given bluetooth device.\n"); #endif fprintf(stderr, "\t--ip \tConnect to this TCP/IP address.\n"); fprintf(stderr, "\t--irda\t\tConnect using IrDA.\n"); fprintf(stderr, "\t--irda-service \tUse the given IrDA service (default: OBEX).\n\n"); fprintf(stderr, "\tOBEX server configuration:\n"); fprintf(stderr, "\t==========================\n\n"); fprintf(stderr, "\t--port \tListen to this TCP/IP port.\n\n"); #endif fprintf(stderr, "\tGeneral SyncML options:\n"); fprintf(stderr, "\t=======================\n\n"); fprintf(stderr, "\t--identifier \tsets the local identity of the SyncML (source).\n"); fprintf(stderr, "\t--target \tsets the remote identity of SyncML (target).\n"); fprintf(stderr, "\t--username \tsets the username for authentication.\n"); fprintf(stderr, "\t--password \tsets the password for authentication.\n"); fprintf(stderr, "\t--maxMsgSize \tsets the maximum message size (default: %s)\n", maxMsgSize); fprintf(stderr, "\t--maxObjSize \tsets the maximum object size (default: %s)\n", maxObjSize); fprintf(stderr, "\t--useStringTable\tUse wbxml string tables (default: NEVER EVER)\n"); fprintf(stderr, "\t--disableNumberOfChanges\tthe most new phones support it (default: enabled)\n"); fprintf(stderr, "\t--useNumberAnchor\t\tUse numbers as anchors.\n"); fprintf(stderr, "\t--useLocaltime\t\tUse localtime instead of UTC.\n"); fprintf(stderr, "\t--wbxml\t\t\tUse wbxml (WAP Binary XML) instead of plain xml.\n"); fprintf(stderr, "\t--read-only\t\tno write actions will be performed.\n"); fprintf(stderr, "\t--remoteWinsConflicts\tIf there is a conflict then the remote change wins.\n"); fprintf(stderr, "\t\t\t\tThe default is local wins always.\n"); fprintf(stderr, "\t\t\t\tThis option is only usable in OMA DS server mode.\n"); fprintf(stderr, "\t--dumpinfo\t\tPrint info about the phone.\n"); fprintf(stderr, "\t--version\t\tprints the version of the tool.\n"); fprintf(stderr, "\t--version \tsets the SyncML version.\n\n"); fprintf(stderr, "\t\tSome devices require a special identity string.\n"); fprintf(stderr, "\t\tNokias for example often requires \"PC Suite\".\n"); fprintf(stderr, "\t\tPlease use --identifier \"PC Suite\" in this case.\n"); fprintf(stderr, "\t\tcan be \"1.0\", \"1.1\" or \"1.2\".\n"); fprintf(stderr, "\t\tThe default version is \"1.1\".\n\n"); fprintf(stderr, "\tDevice faking configuration:\n"); fprintf(stderr, "\t============================\n\n"); fprintf(stderr, "\t--fake-manufacturer \tset the manufacturer of the faked device.\n"); fprintf(stderr, "\t--fake-model \t\tset the model of the faked device.\n"); fprintf(stderr, "\t--fake-software-version \tset the software version of the faked device.\n"); fprintf(stderr, "\n"); if (isError) exit (1); else exit(0); } /* ************************************ */ /* ******** CLI OPTIONS *************** */ /* ************************************ */ #ifdef ENABLE_OBEX /* directly copied from syncml-obex-client */ static void discover_cb(obex_t *handle, obex_object_t *object, int mode, int event, int obex_cmd, int obex_rsp) { (void) handle; (void) object; (void) mode; (void) event; (void) obex_cmd; (void) obex_rsp; } void list_interfaces() { obex_t *handle; obex_interface_t* obex_intf; int i, interfaces_number = 0; if(!(handle = OBEX_Init(OBEX_TRANS_USB, discover_cb, 0))) { printf("OBEX_Init failed\n"); return; } #ifndef WIN32 if (geteuid() != 0) fprintf(stderr, "Superuser privileges are required to access complete USB information.\n"); #endif interfaces_number = OBEX_FindInterfaces(handle, &obex_intf); printf("Found %d USB OBEX interfaces\n", interfaces_number); for (i = 0; i < interfaces_number; i++) printf("Interface %d:\n\tManufacturer: %s\n\tProduct: %s\n\tInterface description: %s\n", i, obex_intf[i].usb.manufacturer, obex_intf[i].usb.product, obex_intf[i].usb.control_interface); printf("Use '-u interface_number' to connect\n"); OBEX_Cleanup(handle); } #endif static gboolean smlDsToolRecvDevInfCallback (SmlDataSyncSession *self, SmlDevInf *devinf, void *userdata, GError **error); static SmlAlertType smlDsToolRecvAlertTypeCallback (SmlDataSyncDataStoreSession *self, SmlAlertType type, void *userdata, GError **error); static gboolean smlDsToolRecvChangeCallback (SmlDataSyncDataStoreSession *self, SmlDataSyncChangeItem *item, void *userdata, GError **error); static gboolean smlDsToolRecvChangeStatusCallback (SmlDataSyncDataStoreSession *self, SmlDataSyncChangeItem *item, SmlErrorType code, void *userdata, GError **error); static gboolean smlDsToolRecvMappingCallback (SmlDataSyncDataStoreSession *self, SmlMapItem *item, void *userdata, GError **error); static gchar* smlDsToolGetAnchorCallback (SmlDataSyncDataStoreSession *self, gboolean remote, void *userdata, GError **error); static gboolean smlDsToolSetAnchorCallback (SmlDataSyncDataStoreSession *self, gboolean remote, const gchar *anchor, void *userdata, GError **error); SmlTransportType getTransportType(int argc, char *argv[]) { smlTrace(TRACE_ENTRY, "%s", __func__); SmlTransportType tspType = SML_TRANSPORT_OBEX_CLIENT; int i = 0; for (i = 1; i < argc; i++) { char *arg = argv[i]; if (!strcmp (arg, "--http-client")) { tspType = SML_TRANSPORT_HTTP_CLIENT; } else if (!strcmp (arg, "--http-server")) { tspType = SML_TRANSPORT_HTTP_SERVER; } else if (!strcmp (arg, "--port")) { tspType = SML_TRANSPORT_OBEX_SERVER; } } smlTrace(TRACE_EXIT, "%s - %d", __func__, tspType); return tspType; } SmlSessionType getSessionType(int argc, char *argv[]) { smlTrace(TRACE_ENTRY, "%s", __func__); SmlSessionType type = SML_SESSION_TYPE_SERVER; int i = 0; for (i = 1; i < argc; i++) { char *arg = argv[i]; if (!strcmp (arg, "--http-client")) { type = SML_SESSION_TYPE_CLIENT; } else if (!strcmp (arg, "--http-server")) { type = SML_SESSION_TYPE_SERVER; } else if (!strcmp (arg, "--port")) { /* this is not a typo */ type = SML_SESSION_TYPE_CLIENT; } } smlTrace(TRACE_EXIT, "%s - %d", __func__, type); return type; } gboolean scanArguments( SmlDataSync *dsObject, int argc, char *argv[], GError **error) { smlTrace(TRACE_ENTRY, "%s", __func__); if (argc == 1) usage (argv[0], TRUE); maxMsgSize = g_strdup("65535"); maxObjSize = g_strdup("3000000"); int i = 0; for (i = 1; i < argc; i++) { char *arg = argv[i]; if (!strcmp (arg, "--sync") || !strcmp(arg, "--slow-sync")) { /* prepare datastore */ SmlDsToolLocationType *datastore = NULL; datastore = (SmlDsToolLocationType *) smlTryMalloc0(sizeof(SmlDsToolLocationType), error); if (!datastore) goto error; datastores = g_list_append(datastores, datastore); datastore->object = sml_data_sync_data_store_new(); sml_data_sync_data_store_register_get_alert_type_callback(datastore->object, smlDsToolRecvAlertTypeCallback, datastore); sml_data_sync_data_store_register_change_callback(datastore->object, smlDsToolRecvChangeCallback, datastore); sml_data_sync_data_store_register_change_status_callback(datastore->object, smlDsToolRecvChangeStatusCallback, datastore); sml_data_sync_data_store_register_mapping_callback(datastore->object, smlDsToolRecvMappingCallback, datastore); /* sync type */ if (!strcmp(arg, "--slow-sync")) datastore->slow = TRUE; else datastore->slow = FALSE; /* load content type */ i++; if (!argv[i]) usage (argv[0], TRUE); sml_data_sync_data_store_set_content_type(datastore->object, argv[i]); if (!strstr(argv[i], "/")) fprintf(stderr, "WARNING: Specified database type \"%s\" doesn't look like a valid MIME type!\n" "WARNING: (Mixed up database path/location with database type?)\n", argv[i]); /* load location */ i++; if (!argv[i]) usage (argv[0], TRUE); sml_data_sync_data_store_set_local_uri(datastore->object, argv[i]); /* load directory if available */ if (argv[i+1] && argv[i+1][0] != '-') { i++; datastore->directory = argv[i]; /* check the directory */ if (g_mkdir_with_parents(datastore->directory, 0700) != 0) { g_set_error(error, TOOL_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION, "There is a problem with the directory %s (%d).", datastore->directory, errno); goto error; } /* load the index if available */ gchar *filename = g_strdup_printf("%s/%s.INDEX", datastore->directory, STATUS_FILENAME); gsize length = 0; gchar *data = NULL; if (g_file_test(filename, G_FILE_TEST_EXISTS) && !g_file_get_contents(filename, &data, &length, error)) { g_set_error(error, TOOL_ERROR, SML_ERROR_INTERNAL_MISCONFIGURATION, "The index file %s cannot be loaded.", filename); g_free(filename); goto error; } g_free(filename); datastore->index = g_key_file_new(); if (data && length && !g_key_file_load_from_data(datastore->index, data, length, 0, error)) { g_free(data); goto error; } g_free(data); /* anchors are only managed if they can be cached */ sml_data_sync_data_store_register_get_anchor_callback(datastore->object, smlDsToolGetAnchorCallback, datastore); sml_data_sync_data_store_register_set_anchor_callback(datastore->object, smlDsToolSetAnchorCallback, datastore); } else { datastore->directory = NULL; } /* register datastore */ if (!sml_data_sync_add_data_store( dsObject, datastore->object, error)) goto error; } else if (!strcmp (arg, "-u")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, SML_DATA_SYNC_CONFIG_CONNECTION_USB, error)) goto error; i++; if (!argv[i]) { #ifdef ENABLE_OBEX list_interfaces(); smlTrace(TRACE_EXIT, "%s - OBEX list returned", __func__); return FALSE; #else printf("OBEX not available in this build\n"); smlTrace(TRACE_EXIT_ERROR, "%s - OBEX list requested but not available", __func__); return FALSE; #endif } if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_PORT, argv[i], error)) goto error; } else if (!strcmp (arg, "-b")) { #ifdef ENABLE_BLUETOOTH if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, SML_DATA_SYNC_CONFIG_CONNECTION_BLUETOOTH, error)) goto error; i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_BLUETOOTH_ADDRESS, argv[i], error)) goto error; i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_BLUETOOTH_CHANNEL, argv[i], error)) goto error; #else printf("Bluetooth is not available in this build\n"); smlTrace(TRACE_EXIT, "%s - Bluetooth requested but not available", __func__); return FALSE; #endif } else if (!strcmp (arg, "--irda")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, SML_DATA_SYNC_CONFIG_CONNECTION_IRDA, error)) goto error; } else if (!strcmp (arg, "--irda-service")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, SML_DATA_SYNC_CONFIG_CONNECTION_IRDA, error)) goto error; i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_IRDA_SERVICE, argv[i], error)) goto error; } else if (!strcmp (arg, "-s")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, SML_DATA_SYNC_CONFIG_CONNECTION_SERIAL, error)) goto error; i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_URL, argv[i], error)) goto error; } else if (!strcmp (arg, "--channel")) { #ifdef ENABLE_BLUETOOTH if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, SML_DATA_SYNC_CONFIG_CONNECTION_BLUETOOTH, error)) goto error; i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_BLUETOOTH_CHANNEL, argv[i], error)) goto error; #else printf("Bluetooth is not available in this build\n"); smlTrace(TRACE_EXIT, "%s - Bluetooth requested but not available", __func__); return FALSE; #endif } else if (!strcmp (arg, "--ip")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, SML_DATA_SYNC_CONFIG_CONNECTION_NET, error)) goto error; i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_URL, argv[i], error)) goto error; i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_PORT, argv[i], error)) goto error; } else if (!strcmp (arg, "--port")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, SML_DATA_SYNC_CONFIG_CONNECTION_NET, error)) goto error; i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_PORT, argv[i], error)) goto error; } else if (!strcmp (arg, "--identifier")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_IDENTIFIER, argv[i], error)) goto error; identifier = g_strdup(argv[i]); } else if (!strcmp (arg, "--target")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_TARGET, argv[i], error)) goto error; target = g_strdup(argv[i]); } else if (!strcmp (arg, "--username")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_AUTH_USERNAME, argv[i], error)) goto error; } else if (!strcmp (arg, "--password")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_AUTH_PASSWORD, argv[i], error)) goto error; } else if (!strcmp (arg, "--maxMsgSize")) { i++; if (!argv[i]) usage (argv[0], TRUE); maxMsgSize = argv[i]; if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_MAX_MSG_SIZE, maxMsgSize, error)) goto error; } else if (!strcmp (arg, "--maxObjSize")) { i++; if (!argv[i]) usage (argv[0], TRUE); maxObjSize = argv[i]; if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_MAX_OBJ_SIZE, maxObjSize, error)) goto error; } else if (!strcmp (arg, "--useStringTable")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_USE_STRING_TABLE, "1", error)) goto error; } else if (!strcmp (arg, "--disableNumberOfChanges")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_USE_NUMBER_OF_CHANGES, "0", error)) goto error; } else if (!strcmp (arg, "--useNumberAnchor")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_USE_TIMESTAMP_ANCHOR, "0", error)) goto error; } else if (!strcmp (arg, "--useLocaltime")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_USE_LOCALTIME, "1", error)) goto error; } else if (!strcmp (arg, "--version")) { i++; if (!argv[i]) { fprintf(stdout, "Version: %s ($Revision$)\n", VERSION); smlTrace(TRACE_EXIT, "%s - Only the version was requested.", __func__); return FALSE; } if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_VERSION, argv[i], error)) goto error; syncmlVersion = g_strdup(argv[i]); } else if (!strcmp (arg, "--help")) { usage (argv[0], FALSE); } else if (!strcmp (arg, "--wbxml")) { if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_USE_WBXML, "1", error)) goto error; useWbxml = TRUE; } else if (!strcmp (arg, "--dumpinfo")) { dumpinfo = TRUE; sml_data_sync_register_handle_remote_dev_inf_callback (dsObject, smlDsToolRecvDevInfCallback, NULL); } else if (!strcmp (arg, "--remoteWinsConflicts")) { localWinsConflicts = FALSE; if (sessionType == SML_SESSION_TYPE_CLIENT) usage(argv[0], TRUE); } else if (!strcmp(arg, "--read-only")) { readOnly = TRUE; } else if (!strcmp (arg, "--http-client")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_URL, argv[i], error)) goto error; } else if (!strcmp (arg, "--ssl-ca-certs")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_SSL_CA_FILE, argv[i], error)) goto error; } else if (!strcmp (arg, "--http-server")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_PORT, argv[i], error)) goto error; } else if (!strcmp (arg, "--ssl-key")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_SSL_KEY, argv[i], error)) goto error; } else if (!strcmp (arg, "--ssl-cert")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_TRANSPORT_CONFIG_SSL_SERVER_CERT, argv[i], error)) goto error; } else if (!strcmp (arg, "--fake-manufacturer")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_FAKE_DEVICE, "1", error)) goto error; if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_FAKE_MANUFACTURER, argv[i], error)) goto error; } else if (!strcmp (arg, "--fake-model")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_FAKE_DEVICE, "1", error)) goto error; if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_FAKE_MODEL, argv[i], error)) goto error; } else if (!strcmp (arg, "--fake-software-version")) { i++; if (!argv[i]) usage (argv[0], TRUE); if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_FAKE_DEVICE, "1", error)) goto error; if (!sml_data_sync_set_option( dsObject, SML_DATA_SYNC_CONFIG_FAKE_SOFTWARE_VERSION, argv[i], error)) goto error; } else if (!strcmp (arg, "--")) { break; } else { g_warning("Unknown parameter: %s", arg); usage (argv[0], TRUE); } } if (g_list_length(datastores) == 0) { if (syncmlVersion == NULL || strcmp(syncmlVersion, "1.2")) { g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "You have to configure at least one database"); goto error; } else { printf("All datastores will be requested because no datastore is configured.\n"); } } smlTrace(TRACE_EXIT, "%s - TRUE", __func__); return TRUE; error: smlTrace(TRACE_EXIT_ERROR, "%s - Failed to start the client: %s\n", (*error)->message); return FALSE; } /* ************************************ */ /* *********** CALLBACKS ************** */ /* ************************************ */ static void writeSyncStatus(gboolean success) { smlTrace(TRACE_ENTRY, "%s (%s)", __func__, success ? "TRUE" : "FALSE"); GError *error = NULL; SmlDsToolLocationType *datastore = NULL; GList *o = datastores; for(;o;o = o->next) { datastore = o->data; if (datastore->directory) { char *absolute = g_strdup_printf("%s/%s", datastore->directory, STATUS_FILENAME); if (success) { if (!g_file_set_contents(absolute, "Okay.", 5, &error)) goto error; /* migrate all entries from committed_index to last_index */ char **keys = NULL; if (g_key_file_has_group(datastore->index, "committed_index")) keys = g_key_file_get_keys(datastore->index, "committed_index", NULL, NULL); int pos = 0; while (keys != NULL && keys[pos] != NULL) { char *digest = g_key_file_get_string(datastore->index, "committed_index", keys[pos], &error); if (!digest && error) goto error; /* NOTICE: glib changed the return type of g_key_file_remove_key */ g_key_file_remove_key(datastore->index, "committed_index", keys[pos], &error); if (error) goto error; g_key_file_set_string(datastore->index, "last_index", keys[pos], digest); smlSafeCFree(&digest); pos++; } g_strfreev(keys); /* dump index */ gchar *filename = g_strdup_printf("%s/%s.INDEX", datastore->directory, STATUS_FILENAME); gsize length = 0; gchar *data = g_key_file_to_data(datastore->index, &length, &error); if (!data && error) goto error; if (!g_file_set_contents(filename, data, length, &error)) goto error; g_free(filename); } else { g_unlink(STATUS_FILENAME); } smlSafeCFree(&absolute); } } smlTrace(TRACE_EXIT, "%s", __func__); return; error: smlTrace(TRACE_EXIT_ERROR, "%s - %s", error->message); g_error("%s", error->message); } gboolean sendAllChanges (SmlDataSyncSession *session, GError **error) { smlTrace(TRACE_ENTRY, "%s", __func__); GList *o = datastores; /* If readOnly then data MUST NOT be sent. */ for (;!readOnly && o;o=o->next) { SmlDsToolLocationType *datastore = o->data; if (!datastore->directory) continue; printf("Sending all changes of directory %s ...\n", datastore->directory); smlTrace(TRACE_INTERNAL, "%s: checking %s", __func__, VA_STRING(sml_data_sync_data_store_get_local_uri(datastore->object))); GDir *dir = g_dir_open(datastore->directory, 0, error); if (!dir) goto error; const gchar *filename = g_dir_read_name(dir); datastore->session = sml_data_sync_session_get_data_store_session(session, datastore->object, error); if (!datastore->session) goto error; /* handle all required ADD and REPLACE commands * * The INDEX file includes a group called last_index. * last_index includes a hash of every known item. * So every found file will be checked. * 1. If it is new or changed * then the item will be migrated to the change_index. * 2. If the file is already known * then the item will be migrated to the committed_index. */ for (;filename; filename = g_dir_read_name(dir)) { smlTrace(TRACE_INTERNAL, "%s: checking %s", __func__, VA_STRING(filename)); if (!strncmp(filename, STATUS_FILENAME, strlen(STATUS_FILENAME))) continue; char *absolute = g_strdup_printf("%s/%s", datastore->directory, filename); /* load data */ gsize length; char *data = NULL; if (!g_file_get_contents(absolute, &data, &length, error)) { smlSafeCFree(&absolute); goto error; } smlSafeCFree(&absolute); /* calculate and load checksum */ #ifdef HAVE_GLIB_GCHECKSUM_H char *digest = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *) data, length); #else char *digest = sml_ds_tool_get_md5 (data, length, error); if (!digest) goto error; #endif char *original = NULL; if (g_key_file_has_group(datastore->index, "last_index")) original = g_key_file_get_string(datastore->index, "last_index", filename, NULL); /* fix indexes */ SmlChangeType changeType = SML_CHANGE_UNKNOWN; if (original) { /* potentially changed item */ if (strcmp(digest, original)) { /* changed item */ printf("\tItem %s was locally changed.\n", filename); if (datastore->slow) { /* SLOWSYNC and a conflict => create a duplicate */ printf("\tCONFLICT: SLOWSYNC => Creating a duplicate ...\n"); changeType = SML_CHANGE_ADD; g_key_file_set_string(datastore->index, "change_index", filename, digest); } else { changeType = SML_CHANGE_REPLACE; g_key_file_set_string(datastore->index, "change_index", filename, digest); /* NOTICE: glib changed the return type of g_key_file_remove_key */ g_key_file_remove_key(datastore->index, "last_index", filename, error); if (*error) goto error; } } else { /* nothing to do */ g_key_file_set_string(datastore->index, "committed_index", filename, digest); /* NOTICE: glib changed the return type of g_key_file_remove_key */ g_key_file_remove_key(datastore->index, "last_index", filename, error); if (*error) goto error; continue; } } else { /* new item */ printf("\tItem %s was locally added.\n", filename); changeType = SML_CHANGE_ADD; g_key_file_set_string(datastore->index, "change_index", filename, digest); } /* add change */ smlTrace(TRACE_INTERNAL, "%s: sending %s", __func__, VA_STRING(filename)); SmlDataSyncChangeItem *item = sml_data_sync_change_item_new(); sml_data_sync_change_item_set_action(item, changeType); SmlLocation *uid = sml_location_new(); if (changeType != SML_CHANGE_ADD) { const gchar *map = g_key_file_get_string(datastore->index, "map", filename, error); if (!map) goto error; sml_location_set_uri(uid, map); } else { sml_location_set_uri(uid, filename); } if (!sml_data_sync_change_item_set_location(item, uid, error)) goto error; if (!sml_data_sync_change_item_set_data(item, data, length, error)) goto error; if (!sml_data_sync_data_store_session_add_change(datastore->session, item, NULL, error)) goto error; smlSafeCFree(&data); } g_dir_close(dir); dir = NULL; /* All items which are still in last_index must be deleted. * Those items will be written to delete_index. */ gsize count = 0; char **keys = NULL; if (g_key_file_has_group(datastore->index, "last_index")) { keys = g_key_file_get_keys(datastore->index, "last_index", &count, error); if (*error) goto error; } gsize i = 0; for (; !datastore->slow && i < count; i++) { printf("\tItem %s was locally deleted.\n", keys[i]); /* fix indexes */ char *digest = g_key_file_get_string(datastore->index, "last_index", keys[i], error); if (!digest && *error) goto error; g_key_file_set_string(datastore->index, "change_index", keys[i], digest); /* NOTICE: glib changed the return type of g_key_file_remove_key */ g_key_file_remove_key(datastore->index, "last_index", keys[i], error); if (*error) goto error; /* add change */ SmlDataSyncChangeItem *item = sml_data_sync_change_item_new(); sml_data_sync_change_item_set_action(item, SML_CHANGE_DELETE); SmlLocation *uid = sml_location_new(); const gchar *map = g_key_file_get_string(datastore->index, "map", keys[i], error); if (!map) goto error; sml_location_set_uri(uid, map); if (!sml_data_sync_change_item_set_location(item, uid, error)) goto error; if (!sml_data_sync_data_store_session_add_change(datastore->session, item, NULL, error)) goto error; } } smlTrace(TRACE_EXIT, "%s", __func__); return sml_data_sync_session_send_changes(session, error); error: smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message); return FALSE; } static void smlDsToolRecvEventCallback (SmlDataSyncSession *session, SmlDataSync *dsObject, SmlDataSyncEventType type, void *userdata, const GError *error) { smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p, %p)", __func__, session, dsObject, type, userdata, error); GError *locerror = NULL; switch (type) { case SML_DATA_SYNC_SESSION_EVENT_ERROR: g_message("ERROR: %s\n", error->message); writeSyncStatus(FALSE); // g_error_free(error); smlTrace(TRACE_EXIT_ERROR, "%s - failed.", __func__); exit(2); break; case SML_DATA_SYNC_SESSION_EVENT_CONNECT: g_message("Remote device was successfully connected."); break; case SML_DATA_SYNC_SESSION_EVENT_DISCONNECT: g_message("Remote device was successfully disconnected."); break; case SML_DATA_SYNC_SESSION_EVENT_FINISHED: g_message("SyncML session finished successfully."); writeSyncStatus(TRUE); g_mutex_unlock(runMutex); break; case SML_DATA_SYNC_SESSION_EVENT_GOT_ALL_ALERTS: g_message("All alerts of the remote device were received."); if (sessionType == SML_SESSION_TYPE_CLIENT) { if (!sendAllChanges(session, &locerror)) goto error; } break; case SML_DATA_SYNC_SESSION_EVENT_GOT_ALL_CHANGES: g_message("All changes of the remote device were received."); if (sessionType == SML_SESSION_TYPE_SERVER) { if (!sendAllChanges(session, &locerror)) goto error; } /* the map of the client is send automatically */ break; case SML_DATA_SYNC_SESSION_EVENT_GOT_ALL_MAPPINGS: if (sessionType == SML_SESSION_TYPE_SERVER) { g_message("All mappings of the remote device were received."); } else { g_error("Received a map but I'm a client!"); } break; default: g_error("Unknown event(%d).\n", type); break; } smlTrace(TRACE_EXIT, "%s", __func__); return; error: fprintf(stderr, "An error occured while handling events: %s\n", locerror->message); smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, locerror->message); g_error_free(locerror); exit(3); } static char * getSafeFilename(const char *filename) { gboolean clean = TRUE; size_t i; for (i=0; i < strlen(filename); i++) { if (!g_ascii_isalnum(filename[i]) && filename[i] != '-' && filename[i] != '_') clean = FALSE; } if (clean) { return g_strdup(filename); } else { return g_base64_encode((const unsigned char *) filename, strlen(filename)); } } static gboolean smlDsToolRecvChangeCallback (SmlDataSyncDataStoreSession *session, SmlDataSyncChangeItem *item, void *userdata, GError **error) { smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, session, item, userdata, error); char *absolute_uid = NULL; /* find the appropriate datasoure */ SmlDsToolLocationType *datastore = userdata; if (!datastore) { g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "The userdata of the callback %s is missing.", __func__); goto error; } /* handle the item */ if (datastore->directory) { /* prepare UIDs and absolute filenames */ SmlLocation *uid = sml_data_sync_change_item_get_location(item); const char *orgUID = sml_location_get_uri(uid); char *safeUID = getSafeFilename(orgUID); printf("Writing item %s to directory %s.\n", safeUID, datastore->directory); /* map the item if necessary */ if (sml_data_sync_change_item_get_action(item) != SML_CHANGE_ADD) { /* sometimes an item is not mappeni * (e.g. client:ADD => server:DELETE because of error 419) */ const char *map = NULL; if (g_key_file_has_group(datastore->index, "reverse_map")) map = g_key_file_get_string(datastore->index, "reverse_map", safeUID, error); if (!map && *error) goto error; if (map) { smlSafeCFree(&safeUID); safeUID = g_strdup(map); } printf("\tMap item to %s.\n", safeUID); } absolute_uid = g_strdup_printf("%s/%s", datastore->directory, safeUID); /* sanity check for uid */ if (!strncmp(safeUID, STATUS_FILENAME, strlen(STATUS_FILENAME))) { g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "The filename %s cannot be used as UID. This is a potential attack.", STATUS_FILENAME); printf("\tIllegal filename %s detected.\n", STATUS_FILENAME); g_warning("Potential attack against status file %s detected.", STATUS_FILENAME); goto error; } SmlChangeType action = sml_data_sync_change_item_get_action(item); /* load digest information */ char *index_digest = NULL; char *item_digest = NULL; char *file_digest = NULL; if (g_key_file_has_group(datastore->index, "last_index")) index_digest = g_key_file_get_string(datastore->index, "last_index", safeUID, NULL); const char *data = sml_data_sync_change_item_get_data(item); if (data) { #ifdef HAVE_GLIB_GCHECKSUM_H item_digest = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *) data, strlen(data)); #else item_digest = sml_ds_tool_get_md5 (data, strlen(data), error); if (!item_digest) goto error; #endif } if (g_file_test(absolute_uid, G_FILE_TEST_EXISTS)) { char *file_data = NULL; gsize file_length = 0; if (!g_file_get_contents(absolute_uid, &file_data, &file_length, error)) goto error; #ifdef HAVE_GLIB_GCHECKSUM_H file_digest = g_compute_checksum_for_data(G_CHECKSUM_SHA256, (guchar *) file_data, file_length); #else file_digest = sml_ds_tool_get_md5 (file_data, file_length, error); if (!file_digest) goto error; #endif smlSafeCFree(&file_data); } /* determine the action */ gboolean doWrite = FALSE; switch(action) { case SML_CHANGE_ADD: if (g_file_test(absolute_uid, G_FILE_TEST_EXISTS)) { if (datastore->slow) { if (strcmp(file_digest, item_digest) == 0) { /* nothing to do - items identical */ g_key_file_set_string(datastore->index, "last_index", safeUID, file_digest); printf("\tIdentical items detected.\n"); } else { /* The items do not match. * This is a classical conflict. */ if (localWinsConflicts) { printf("\tConflict: Remote add command is ignored.\n"); printf("\tConflict: Local item wins conflict.\n"); g_key_file_set_string(datastore->index, "last_index", safeUID, file_digest); } else { printf("\tConflict: Local item is ignored.\n"); printf("\tConflict: Remote add command wins conflict.\n"); doWrite = TRUE; } } } else { /* This should be a new remote item. * The entry must be duplicated. */ doWrite = TRUE; while (g_file_test(absolute_uid, G_FILE_TEST_EXISTS)) { char *new = g_strdup_printf("%s%s", safeUID, "_dup"); smlSafeCFree(&safeUID); smlSafeCFree(&absolute_uid); safeUID = new; absolute_uid = g_strdup_printf("%s/%s", datastore->directory, safeUID); } SmlMapItem *map = sml_map_item_new(); if (!sml_map_item_set_remote(map, uid, error)) goto error; SmlLocation *local = sml_location_new(); sml_location_set_uri(local, safeUID); if (!sml_map_item_set_local(map, local, error)) goto error; if (!sml_data_sync_data_store_session_add_mapping(session, map, NULL, error)) goto error; g_object_unref(map); g_object_unref(local); g_key_file_set_string(datastore->index, "map", safeUID, orgUID); g_key_file_set_string(datastore->index, "reverse_map", orgUID, safeUID); printf("\tItem %s will be duplicated.\n", safeUID); } } else { if (index_digest && strcmp(index_digest, item_digest) == 0) { /* the item was locally deleted => ignore add */ printf("\tThe item was already locally deleted.\n"); g_key_file_remove_key(datastore->index, "last_index", safeUID, NULL); } else if (!index_digest) { /* normal add */ printf("\tThe item is new.\n"); doWrite = TRUE; SmlMapItem *map = sml_map_item_new(); if (!sml_map_item_set_remote(map, uid, error)) goto error; SmlLocation *local = sml_location_new(); sml_location_set_uri(local, safeUID); if (!sml_map_item_set_local(map, local, error)) goto error; if (!sml_data_sync_data_store_session_add_mapping(session, map, NULL, error)) goto error; g_object_unref(map); g_object_unref(local); g_key_file_set_string(datastore->index, "map", safeUID, orgUID); g_key_file_set_string(datastore->index, "reverse_map", orgUID, safeUID); } else { /* The item was locally different * but it was already deleted. * This is a classical conflict. */ if (localWinsConflicts) { printf("\tConflict: Remote add command is ignored.\n"); printf("\tConflict: Local deletion wins conflict.\n"); g_key_file_remove_key(datastore->index, "last_index", safeUID, NULL); } else { printf("\tConflict: Local deletion is ignored.\n"); printf("\tConflict: Remote add command wins conflict.\n"); doWrite = TRUE; SmlMapItem *map = sml_map_item_new(); if (!sml_map_item_set_remote(map, uid, error)) goto error; SmlLocation *local = sml_location_new(); sml_location_set_uri(local, safeUID); if (!sml_map_item_set_local(map, local, error)) goto error; if (!sml_data_sync_data_store_session_add_mapping(session, map, NULL, error)) goto error; g_object_unref(map); g_object_unref(local); g_key_file_set_string(datastore->index, "map", safeUID, orgUID); g_key_file_set_string(datastore->index, "reverse_map", orgUID, safeUID); } } } break; case SML_CHANGE_REPLACE: if (g_file_test(absolute_uid, G_FILE_TEST_EXISTS)) { if (strcmp(file_digest, index_digest) == 0) { /* There is no local change. * The local item is in sync. * So the remote change should be accepted. */ printf("\tThe local item will be changed.\n"); doWrite = TRUE; } else if (strcmp(file_digest, item_digest) == 0) { /* nothing to do - items identical */ printf("\tIdentical items detected.\n"); g_key_file_set_string(datastore->index, "last_index", safeUID, file_digest); } else if (strcmp(index_digest, item_digest) == 0) { /* nothing to do - the change is already known */ printf("\tOld change detected.\n"); } else { /* The items do not match and the change is unknown. * This is a classical conflict. */ if (localWinsConflicts) { printf("\tConflict: Remote replace command is ignored.\n"); printf("\tConflict: Local changed item wins conflict.\n"); } else { printf("\tConflict: Local changed item is ignored.\n"); printf("\tConflict: Remote replace command wins conflict.\n"); doWrite = TRUE; } } } else { /* The item was locally deleted. * If you need to restore it * then you must add it again. * A normal synchronization is no backup. * You can only restore it if you do a slow sync. * So the change will be committed but not executed. */ printf("\tThe item was already locally deleted.\n"); } break; case SML_CHANGE_DELETE: if (g_file_test(absolute_uid, G_FILE_TEST_EXISTS)) { /* The item was deleted on the remote device. * If you need to restore it * then you must add it again. * A normal synchronization is no backup. * You can only restore it if you do a slow sync. * So the change will be executed. */ printf("\tThe item will be deleted.\n"); doWrite = TRUE; } else { /* The item was already locally deleted. */ printf("\tThe item was already locally deleted.\n"); g_key_file_remove_key(datastore->index, "last_index", safeUID, NULL); } break; default: g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "The change type of %s is not supported.", safeUID); printf("\t%s\n", (*error)->message); goto error; } /* write the change */ if (doWrite) { switch(action) { case SML_CHANGE_ADD: case SML_CHANGE_REPLACE: data = sml_data_sync_change_item_get_data(item); if (!g_file_set_contents(absolute_uid, data, strlen(data), error)) goto error; g_key_file_set_string(datastore->index, "last_index", safeUID, item_digest); /* printf("\tDigest: %s\n", item_digest); */ printf("\tThe item was successfully written.\n"); break; case SML_CHANGE_DELETE: if (g_unlink(absolute_uid) != 0) { g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "The file %s cannot be removed.", absolute_uid); goto error; } /* NOTICE: glib changed the return type of g_key_file_remove_key */ g_key_file_remove_key(datastore->index, "last_index", safeUID, error); if (*error) goto error; printf("\tThe item was successfully deleted.\n"); break; default: g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "The change type of %s is not supported.", safeUID); printf("\t%s\n", (*error)->message); goto error; } } else { printf("\tThe change was ignored.\n"); } /* cleanup UIDs */ smlSafeCFree(&safeUID); smlSafeCFree(&absolute_uid); } else { /* print received change */ printf("-----BEGIN CHANGE-----\n"); if (sml_data_sync_change_item_get_action(item) == SML_CHANGE_DELETE) printf("DELETE %s\n", sml_location_get_uri(sml_data_sync_change_item_get_location(item))); else printf("%s", sml_data_sync_change_item_get_data(item)); printf("-----END CHANGE-----\n"); } smlTrace(TRACE_EXIT, "%s - TRUE", __func__); return TRUE; error: if (absolute_uid) smlSafeCFree(&absolute_uid); smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message); return FALSE; } static gboolean smlDsToolRecvMappingCallback (SmlDataSyncDataStoreSession *session, SmlMapItem *item, void *userdata, GError **error) { smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, session, item, userdata, error); /* find the appropriate datasoure */ SmlDsToolLocationType *datastore = userdata; if (!datastore) { g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "The userdata of the callback %s is missing.", __func__); goto error; } /* handle the item */ if (datastore->index) { const gchar *local = sml_map_item_get_local_uri(item); const gchar *remote = sml_map_item_get_remote_uri(item); g_key_file_set_string(datastore->index, "map", local, remote); g_key_file_set_string(datastore->index, "reverse_map", remote, local); } smlTrace(TRACE_EXIT, "%s - TRUE", __func__); return TRUE; error: smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message); return FALSE; } static gboolean smlDsToolRecvChangeStatusCallback (SmlDataSyncDataStoreSession *session, SmlDataSyncChangeItem *item, SmlErrorType code, void *userdata, GError **error) { smlTrace(TRACE_ENTRY, "%s(%p, %p, %d, %p, %p)", __func__, session, item, code, userdata, error); /* If there is an error then the whole operation must be aborted. * 418 => The item is already present. So no problem. * 419 => There was a conflict but the server data wins. * Usually this means that the item should be removed. */ if (sml_error_get_class(code) != SML_ERRORCLASS_SUCCESS && code != SML_ERROR_ALREADY_EXISTS && code != SML_ERROR_CONFLICT_SERVER_WINS) { g_set_error(error, TOOL_ERROR, code, smlErrorTypeGetMessage(code)); goto error; } /* find the appropriate datasoure */ SmlDsToolLocationType *datastore = userdata; if (!datastore) { g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "The userdata of the callback %s is missing.", __func__); goto error; } SmlLocation *loc = sml_data_sync_change_item_get_location(item); const char *key = sml_location_get_uri(loc); if (sml_data_sync_change_item_get_action(item) != SML_CHANGE_ADD) key = g_key_file_get_string(datastore->index, "reverse_map", key, error); const char *digest = g_key_file_get_string(datastore->index, "change_index", key, error); if (!digest && *error) goto error; if (sml_data_sync_change_item_get_action(item) != SML_CHANGE_DELETE && ! (code == SML_ERROR_CONFLICT_SERVER_WINS && sml_data_sync_change_item_get_action(item) == SML_CHANGE_ADD ) ) { /* No delete and no 419 of an add. */ g_key_file_set_string(datastore->index, "committed_index", key, digest); } /* NOTICE: glib changed the return type of g_key_file_remove_key */ g_key_file_remove_key(datastore->index, "change_index", key, error); if (*error) goto error; // /* 419 => conflict and server wins // * => item must be locally removed. // */ // if (code == SML_ERROR_CONFLICT_SERVER_WINS && // sml_data_sync_change_item_get_action(item) == SML_CHANGE_ADD) // { // char *safeUID = getSafeFilename(key); // char *absoluteUID = g_strdup_printf("%s/%s", datastore->directory, safeUID); // g_unlink(absoluteUID); // smlSafeCFree(&absoluteUID); // smlSafeCFree(&safeUID); // } smlTrace(TRACE_EXIT, "%s - TRUE", __func__); return TRUE; error: smlTrace(TRACE_EXIT_ERROR, "%s - %d => %s", __func__, (*error)->code, (*error)->message); return FALSE; } static gboolean smlDsToolRecvDevInfCallback (SmlDataSyncSession *session, SmlDevInf *devinf, void *userdata, GError **error) { smlTrace(TRACE_INTERNAL, "%s(%p, %p, %p, %p)", session, devinf, userdata, error); printf("Received device information.\n"); remoteDevinf = devinf; g_object_ref(remoteDevinf); return TRUE; } static SmlAlertType smlDsToolRecvAlertTypeCallback (SmlDataSyncDataStoreSession *session, SmlAlertType type, void *userdata, GError **error) { smlTrace(TRACE_ENTRY, "%s(%p, %d, %p, %p)", __func__, session, type, userdata, error); /* find the appropriate datasoure */ SmlDsToolLocationType *datastore = userdata; if (!datastore) { SmlDataSyncDataStore *ds = sml_data_sync_data_store_session_get_data_store(session); g_set_error(error, TOOL_ERROR, SML_ERROR_GENERIC, "Cannot find datastore %s.", sml_data_sync_data_store_get_local_uri(ds)); goto error; } smlTrace(TRACE_INTERNAL, "%s: datastores scanned", __func__); /* synchronize the alert type */ if (type == SML_ALERT_SLOW_SYNC) datastore->slow = TRUE; if (datastore->slow) type = SML_ALERT_SLOW_SYNC; char *status = g_strdup_printf("%s/%s", datastore->directory, STATUS_FILENAME); if (!g_file_test(status, G_FILE_TEST_EXISTS)) { /* directory is out-of-sync => enforce slow sync */ datastore->slow = TRUE; type = SML_ALERT_SLOW_SYNC; } smlSafeCFree(&status); if (type == SML_ALERT_SLOW_SYNC) { g_message("\t%s => SLOW SYNC.", sml_data_sync_data_store_get_local_uri(datastore->object)); } else { g_message("\t%s => NORMAL SYNC.", sml_data_sync_data_store_get_local_uri(datastore->object)); } smlTrace(TRACE_EXIT, "%s - slow == %d", __func__, datastore->slow); return type; error: smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message); return SML_ALERT_UNKNOWN; } /* ***** sync anchor management ***** */ static gchar* smlDsToolGetAnchorCallback (SmlDataSyncDataStoreSession *self, gboolean remote, void *userdata, GError **error) { smlTrace(TRACE_ENTRY, "%s (%p, %d, %p, %p)", __func__, self, remote, userdata, error); SmlDsToolLocationType *datastore = userdata; gchar *filename = g_strdup_printf("%s/%s.%s", datastore->directory, STATUS_FILENAME, remote ? "REMOTE" : "LOCAL"); /* does the file exist */ struct stat fdata; if (g_stat(filename, &fdata) != 0) { g_free(filename); smlTrace(TRACE_EXIT, "%s - There is no such sync anchor.", __func__); return NULL; } gsize length = 0; gchar *data = NULL; if (!g_file_get_contents(filename, &data, &length, error)) { g_free(filename); smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message); return NULL; } g_free(filename); gchar *result = smlTryMalloc0(length+1, error); if (!result) { g_free(data); smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message); return NULL; } memcpy(result, data, length); g_free(data); smlTrace(TRACE_EXIT, "%s - %s", __func__, result); return result; } static gboolean smlDsToolSetAnchorCallback (SmlDataSyncDataStoreSession *self, gboolean remote, const gchar* anchor, void *userdata, GError **error) { smlTrace(TRACE_ENTRY, "%s (%p, %d, %p, %p)", __func__, self, remote, userdata, error); SmlDsToolLocationType *datastore = userdata; gchar *filename = g_strdup_printf("%s/%s.%s", datastore->directory, STATUS_FILENAME, remote ? "REMOTE" : "LOCAL"); gboolean result = g_file_set_contents(filename, anchor, strlen(anchor), error); g_free(filename); smlTrace(TRACE_EXIT, "%s - %d", __func__, result); return result; } /* ************************************ */ /* ************** TOOL **************** */ /* ************************************ */ int main(int argc, char *argv[]) { GError *error = NULL; if (!g_thread_supported ()) g_thread_init (NULL); g_type_init(); /* init DS object */ SmlTransportType tspType = getTransportType(argc, argv); sessionType = getSessionType(argc, argv); SmlDataSync *dsObject = sml_data_sync_new(); if (!sml_data_sync_set_transport_type(dsObject, tspType, &error)) goto error; if (!sml_data_sync_set_session_type(dsObject, sessionType, &error)) goto error; if (!scanArguments(dsObject, argc, argv, &error)) goto error; sml_data_sync_register_event_callback(dsObject, smlDsToolRecvEventCallback, NULL); if (!sml_data_sync_initialize(dsObject, &error)) goto error; g_message("Starting data sync ..."); /* run the sync */ checkpoint = time(NULL); if (!sml_data_sync_run(dsObject, &error)) goto error; runMutex = g_mutex_new(); g_mutex_lock(runMutex); g_mutex_lock(runMutex); g_mutex_unlock(runMutex); g_mutex_free(runMutex); runMutex = NULL; /* dump some information */ if (dumpinfo) { if (!remoteDevinf) { printf("Didn't receive the device information though it was requested.\n"); } else { printf("Send the output below to the libsyncml developers\n"); printf("\n========================================\n"); printf("Man: %s\n", sml_dev_inf_get_man(remoteDevinf)); printf("Mod: %s\n", sml_dev_inf_get_mod(remoteDevinf)); printf("FirmwareVersion: %s\n", sml_dev_inf_get_fwv(remoteDevinf)); printf("SoftwareVersion: %s\n", sml_dev_inf_get_swv(remoteDevinf)); printf("HardwareVersion: %s\n", sml_dev_inf_get_hwv(remoteDevinf)); printf("\n"); printf("MaxMsgSize: %s\n", maxMsgSize); printf("MaxObjSize: %s\n", maxObjSize); printf("Transport used: "); switch(tspType) { case SML_TRANSPORT_HTTP_CLIENT: printf("HTTP client\n"); break; case SML_TRANSPORT_HTTP_SERVER: printf("HTTP server\n"); break; case SML_TRANSPORT_OBEX_CLIENT: printf("OBEX client\n"); break; default: printf("unknown\n"); break; } printf("OMA DS mode used: "); switch(sessionType) { case SML_SESSION_TYPE_CLIENT: printf("client\n"); break; case SML_SESSION_TYPE_SERVER: printf("server\n"); break; default: printf("unknown\n"); break; } printf("Identifier (Source): %s \n", identifier); printf("Target: %s\n", target); printf("\nDatastores:\n"); unsigned int i = 0; for (i = 0; i < sml_dev_inf_num_data_stores(remoteDevinf); i++) { SmlDevInfDataStore *datastore = sml_dev_inf_get_nth_data_store(remoteDevinf, i); printf("\tLocations: %s\n", sml_dev_inf_data_store_get_source_ref(datastore)); } printf("Wbxml: %s\n", useWbxml ? "Yes" : "No"); printf("SyncML Version: %s\n", syncmlVersion); printf("SupportsUTC: %s\n", sml_dev_inf_get_support_utc(remoteDevinf) ? "Yes" : "No"); printf("SupportsNumberOfChanges: %s\n", sml_dev_inf_get_support_number_of_changes(remoteDevinf) ? "Yes" : "No"); printf("SupportsLargeObjects: %s\n", sml_dev_inf_get_support_large_objs(remoteDevinf) ? "Yes" : "No"); g_object_unref(remoteDevinf); printf("\nlibsyncml Version: %s ($Revision$)\n", VERSION); } } /* close the object */ g_object_unref(dsObject); g_message("syncml-ds-tool succeeded."); return 0; error: if (error != NULL) { fprintf(stderr, "ERROR: %s\n", error->message); g_error_free(error); } g_message("syncml-ds-tool failed."); return 1; }