/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2008  Felix Moeller <felix@derklecks.de> (man page)
 * Copyright (C) 2008-2009  Michael Bell <michael.bell@opensync.org>
 *
 * 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 "<type>" "<path>" "<directory>"
 *        normal two-way sync
 *
 * @param --slow-sync "<type>" "<path>" "<directory>"
 *        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 "<url>"
 *
 * @arg @b url must be an http URL like http://localhost:8080
 *
 * @subsection OPTION_HTTP_CLIENT_SERVER_CONFIG HTTP server configuration
 *
 * @param --http-server "<port>"
 *
 * @arg @b port must be a port for the http server.
 *
 * @subsection OPTION_OBEX_CLIENT_CONFIG OBEX client configuration
 *
 * @param -s "<device>"
 *        Connect to the serial device.
 *
 * @param -u
 *        List all available USB interfaces.
 *
 * @param -u "<id>"
 *        Connect to the given usb interface number. You may discover them with -u.
 *
 * @param -b "<addr>" "<channel>"
 *        Connect to the given bluetooth device.
 *
 * @param --ip "<addr>" "<port>"
 *        Connect to this TCP/IP address.
 *
 * @param --irda
 *        Connect using IrDA.
 *
 * @param --irda-service "<service>"
 *        Use the given IrDA service (default: OBEX).
 *
 * @subsection OPTION_OBEX_SERVER_CONFIG OBEX server configuration
 *
 * @param --port "<port>"
 *        Listen to this TCP/IP port.
 *
 * @subsection OPTION_GENERAL General SyncML options
 *
 * @param --identifier "<ident>"
 *        set the local identity of SyncML (source).
 *
 * @param --target "<ident>"
 *        set the remote identity of SyncML (target).
 *
 * @param --username "<username>"
 *        set the username for authentication.
 *
 * @param --password "<password>"
 *        set the password for authentication.
 *
 * @param --maxMsgSize "<limit>"
 *        set the maximum message size (default: 0)
 *
 * @param --maxObjSize* "<limit>"
 *        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 "<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 "<Man>"
 *        set the manufacturer of the faked device.
 *
 * @param --fake-model "<Mod>"
 *        set the model of the faked device.
 *
 * @param --fake-software-version "<SwV>"
 *        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 <mac> <channel> --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 <interface> --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, <felix@derklecks.de>
 *
 * @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 <libsyncml/syncml.h>
#include <libsyncml/syncml_internals.h>
#include <libsyncml/data_sync_api/defines.h>
#include <libsyncml/data_sync_api/standard.h>
#include <libsyncml/data_sync_api/callbacks.h>
#include <glib.h>
#include <glib/gstdio.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef ENABLE_OBEX
/* necessary for list_interfaces */
#include <openobex/obex.h>
#endif

#define STATUS_FILENAME "SYNCML-DS-TOOL-LAST-SYNC"

/* ************************************ */
/* *********** CONFIG ***************** */
/* ************************************ */

typedef struct SmlDsToolLocationType {
	char *source;
	SmlBool slow;
	char *contentType;
	char *directory;
	GHashTable *remoteHash;
} SmlDsToolLocationType;

GList *datastores = NULL;

char *identifier = NULL;
char *target = NULL;
char *username = NULL;
char *password = NULL;

SmlSessionType sessionType = SML_SESSION_TYPE_SERVER;
SmlMimeType mimeType = SML_MIMETYPE_XML;
char *syncmlVersion = NULL;

char *maxMsgSize = NULL;
char *maxObjSize = NULL;
GMutex *runMutex = NULL;

SmlBool dumpinfo = FALSE;
SmlBool useNumberOfChanges = TRUE;
SmlBool useStringTable = FALSE;

SmlDevInf *remoteDevinf = NULL;

SmlBool localWinsConflicts = TRUE;
time_t checkpoint;

SmlBool readOnly = FALSE;

/* ************************************ */
/* *********** CALLBACKS ************** */
/* ************************************ */

static void writeSyncStatus(SmlBool success)
{
	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) {
				GError *gerror = NULL;
				if (!g_file_set_contents(absolute, "DONE", 4, &gerror)) {
					g_warning("Cannot write data to %s. %s",
						absolute, gerror->message);
					g_error_free(gerror);
				}
			} else {
				g_unlink(STATUS_FILENAME);
			}
			smlSafeCFree(&absolute);
		}
	}
}

SmlChangeType getChangeType(
			SmlDsToolLocationType *datastore,
			const char *filename,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%s, %s, %p)", __func__, VA_STRING(datastore->source), VA_STRING(filename), error);
	struct stat fdata;
	struct stat fstatus;
	char *absolute_status = NULL;

	/* does the file exist */
	if (g_stat(filename, &fdata) != 0) {
		smlErrorSet(error, SML_ERROR_GENERIC,
			"Cannot stat file %s (%d).",
			filename, errno);
		goto error;
	}

	/* was the file touched after the checkpoint */
	if (fdata.st_mtime >= checkpoint) {
		if (sessionType == SML_SESSION_TYPE_CLIENT) {
			smlErrorSet(error, SML_ERROR_GENERIC,
				"Someone accessed the file absolute after the synchronization started.",
				filename);
			goto error;
		} else {
			/* this can be an error but it is not sure */
			smlTrace(TRACE_EXIT, "%s: changed after checkpoint (%d >= %d)",
				__func__, fdata.st_mtime, checkpoint);
			return SML_CHANGE_UNKNOWN;
		}
	}

	/* is there a status file from an earlier sync */
	absolute_status = g_strdup_printf("%s/%s", datastore->directory, STATUS_FILENAME);
	if (!g_file_test(absolute_status, G_FILE_TEST_EXISTS)) {
		if (!datastore->slow) {
			smlErrorSet(error, SML_ERROR_GENERIC,
				"Status file %s is missing but it is no SLOW-SYNC.",
				absolute_status);
			goto error;
		} else {
			smlSafeCFree(&absolute_status);
			smlTrace(TRACE_EXIT, "%s: %d", __func__, SML_CHANGE_ADD);
			return SML_CHANGE_ADD;
		}
	}

	/* read the stat from the status file */
	if (g_stat(absolute_status, &fstatus) != 0) {
		/* this is a general error */
		smlErrorSet(error, SML_ERROR_GENERIC,
			"Cannot stat file %s (%d).",
			absolute_status, errno);
		goto error;
	}
	smlSafeCFree(&absolute_status);

	/* check if it was added after the last sync */
	if (fdata.st_mtime <= fstatus.st_mtime) {
		if (datastore->slow) {
			if (sessionType == SML_SESSION_TYPE_SERVER &&
			    g_hash_table_lookup(datastore->remoteHash, filename)) {
				smlTrace(TRACE_EXIT, "%s: %d (old known item but slowsync)",
					__func__, SML_CHANGE_REPLACE);
				return SML_CHANGE_REPLACE;
			} else {
				smlTrace(TRACE_EXIT, "%s: %d (old item but slowsync)",
					__func__, SML_CHANGE_ADD);
				return SML_CHANGE_ADD;
			}
		} else {
			smlTrace(TRACE_EXIT, "%s: %d (old item and no slowsync)",
				__func__, SML_CHANGE_UNKNOWN);
			return SML_CHANGE_UNKNOWN;
		}
	} else {
		/* ctime cannot be used - to unreliable */
		/* normal sync does not work reliable therefore */
		if (sessionType == SML_SESSION_TYPE_SERVER &&
		    g_hash_table_lookup(datastore->remoteHash, filename)) {
			smlTrace(TRACE_EXIT, "%s: %d (new but known)",
				__func__, SML_CHANGE_REPLACE);
			return SML_CHANGE_REPLACE;
		} else {
			smlTrace(TRACE_EXIT, "%s: %d (new)",
				__func__, SML_CHANGE_ADD);
			return SML_CHANGE_ADD;
		}
	}
error:
	if (absolute_status)
		smlSafeCFree(&absolute_status);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return SML_CHANGE_UNKNOWN;
} 

SmlBool sendAllChanges(
		SmlDataSyncObject *dsObject,
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	GList *o = datastores;
	GError *gerror = NULL;
	/* If readOnly then data MUST NOT be send. */
	for (;!readOnly && o;o=o->next) {
		SmlDsToolLocationType *datastore = o->data;
		if (!datastore->directory)
			continue;
		smlTrace(TRACE_INTERNAL, "%s: checking %s", __func__, VA_STRING(datastore->source));
		GDir *dir = g_dir_open(datastore->directory, 0, &gerror);
		if (!dir)
			goto gerror;
		const gchar *filename = g_dir_read_name(dir);
		for (;filename; filename = g_dir_read_name(dir)) {
			smlTrace(TRACE_INTERNAL, "%s: checking %s", __func__, VA_STRING(filename));
			if (!strcmp(filename, STATUS_FILENAME))
				continue;
			char *absolute = g_strdup_printf("%s/%s", datastore->directory, filename);
			SmlChangeType changeType = getChangeType(datastore, absolute, error);
			if (changeType == SML_CHANGE_UNKNOWN) {
				/* error or ignorable file */
				smlSafeCFree(&absolute);
				if (*error) {
					goto error;
				} else {
					continue;
				}
			}
			/* all time checks succeeded, so send sync */
			smlTrace(TRACE_INTERNAL, "%s: sending %s", __func__, VA_STRING(absolute));
			gsize length;
			char *data = NULL;
			if (!g_file_get_contents(absolute, &data, &length, &gerror)) {
				smlSafeCFree(&absolute);
				goto gerror;
			}
			smlSafeCFree(&absolute);
			if (!smlDataSyncAddChange(dsObject, datastore->source, changeType,
					filename, data, length, NULL, error))
				goto error;
			smlSafeCFree(&data);
		}
		g_dir_close(dir);
		dir = NULL;
	}
	smlTrace(TRACE_EXIT, "%s", __func__);
	return smlDataSyncSendChanges(dsObject, error);
gerror:
	smlErrorSet(error, SML_ERROR_GENERIC, gerror->message);
	g_error_free(gerror);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return FALSE;
}

static void recvEventCallback(
			SmlDataSyncObject *dsObject,
			SmlDataSyncEventType type,
			void *userdata,
			SmlError *error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p)", __func__, dsObject, type, userdata, error);
	SmlError *locerror = NULL;
	
	switch (type) {
		case SML_DATA_SYNC_EVENT_ERROR:
			writeSyncStatus(FALSE);
			g_message("ERROR: %s\n", smlErrorPrint(&error));
			smlErrorDeref(&error);
			exit(2);
			break;
		case SML_DATA_SYNC_EVENT_CONNECT:
			g_message("Remote device was successfully connected.");
			break;
		case SML_DATA_SYNC_EVENT_DISCONNECT:
			g_message("Remote device was successfully disconnected.");
			break;
		case SML_DATA_SYNC_EVENT_FINISHED:
			g_message("SyncML session finished successfully.");
			writeSyncStatus(TRUE);
			g_mutex_unlock(runMutex);
			break;
		case SML_DATA_SYNC_EVENT_GOT_ALL_ALERTS:
			g_message("All alerts of the remote device were received.");
			if (sessionType == SML_SESSION_TYPE_CLIENT)
			{
				if (!sendAllChanges(dsObject, &locerror))
					goto error;
			}
			break;
		case SML_DATA_SYNC_EVENT_GOT_ALL_CHANGES:
			g_message("All changes of the remote device were received.");
			if (sessionType == SML_SESSION_TYPE_SERVER)
			{
				if (!sendAllChanges(dsObject, &locerror))
					goto error;
			}
			/* the map of the client is send automatically */
			break;
		case SML_DATA_SYNC_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", smlErrorPrint(&locerror));
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(&locerror));
	smlErrorDeref(&locerror);
	exit(3);
}

static char * getSafeFilename(const char *filename)
{
	SmlBool 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 SmlBool recvChangeCallback(
			SmlDataSyncObject *dsObject,
			const char *source,
			SmlChangeType type,
			const char *uid,
			char *data,
			unsigned int size,
			void *userdata,
			SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %d, %s, %p, %d, %p, %p)", __func__, dsObject, VA_STRING(source), type, VA_STRING(uid), data, size, userdata, error);
	char *absolute_uid = NULL;
	char *absolute_status = NULL;

	/* find the appropriate datasoure */
	SmlDsToolLocationType *datastore = NULL;
	GList *o = datastores;
	while (o) {
		datastore = o->data;
		if (!strcmp(datastore->source, source)) {
			/* abort the scan */
			o = NULL;
		} else {
			datastore = NULL;
		}
		if (o) o = o->next;
	}
	smlTrace(TRACE_INTERNAL, "%s: datastores scanned", __func__);

	/* handle the item */
	if (datastore) {
		if (datastore->directory) {
			printf("Writing item %s to directory %s.\n", uid, datastore->directory);
			/* prepare absolute filenames */
			char *safeUID = getSafeFilename(uid);
			absolute_uid = g_strdup_printf("%s/%s", datastore->directory, safeUID);
			absolute_status = g_strdup_printf("%s/%s", datastore->directory, STATUS_FILENAME);
			/* sanity check for uid */
			if (!strcmp(safeUID, STATUS_FILENAME))
			{
				smlErrorSet(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;
			}
			smlSafeCFree(&safeUID);

			/* cache that the remote device knows the uid */
			if (datastore->remoteHash) {
				g_hash_table_insert(datastore->remoteHash, g_strdup(absolute_uid), (char *)"1");
			}
			/* the directory is checked by scanArguments */
			SmlBool doWrite = FALSE;
			if (!doWrite && !g_file_test(absolute_uid, G_FILE_TEST_EXISTS))
				doWrite = TRUE;
			if (!doWrite && sessionType == SML_SESSION_TYPE_CLIENT)
				doWrite = TRUE;
			if (!doWrite && !localWinsConflicts)
				doWrite = TRUE;
			if (!doWrite &&
			    g_file_test(absolute_status, G_FILE_TEST_EXISTS))
			{
				if (datastore->slow)
				{
					/* We have to check the content.
					 * If the content is identical
					 * then we simply touch the file
					 * to commit it. The file will
					 * not be uploaded because of the
					 * new timestamp.
					 */
					char *remoteData = NULL;
					gsize remoteSize = 0;
					if (g_file_get_contents(absolute_uid, &remoteData, &remoteSize, NULL) &&
					    remoteSize == size &&
					    !strncmp(data, remoteData, size))
						g_file_set_contents(absolute_uid, data, size, NULL);
				} else { //!datastore->slow
					/* let's check if there is a local change */
					struct stat fdata;
					struct stat status;
					if (g_stat(absolute_uid, &fdata) != 0) {
						smlErrorSet(error, SML_ERROR_GENERIC,
							"Cannot stat uid file %s (%d).",
							absolute_uid, errno);
						goto error;
					}
					if (g_stat(absolute_status, &status) != 0) {
						smlErrorSet(error, SML_ERROR_GENERIC,
							"Cannot stat status file %s (%d).",
							absolute_status, errno);
						goto error;
					}
					if (status.st_mtime > fdata.st_mtime)
						doWrite = TRUE;
				}
			}

			printf("\tConflicts were checked.\n");
			/* write the change */
			if (doWrite) {
				if (type == SML_CHANGE_DELETE) {
					g_unlink(absolute_uid);
					printf("\tThe item was successfully deleted.\n");
				} else {
					GError *gerror = NULL;
					if (!g_file_set_contents(absolute_uid, data, size, &gerror)) {
						smlErrorSet(error, SML_ERROR_GENERIC,
							"Cannot write data to %s. %s",
							absolute_uid, gerror->message);
						g_error_free(gerror);
						goto error;
					}
					printf("\tThe item was successfully written.\n");
				}
			} else {
				printf("\tThe item was not touched.\n");
			}
			/* free absolute filenames */
			smlSafeCFree(&absolute_uid);
			smlSafeCFree(&absolute_status);
		} else {
			/* print received change */
			printf("-----BEGIN CHANGE-----\n");
			if (type == SML_CHANGE_DELETE)
				printf("DELETE %s\n", uid);
			else
				printf("%s", data);
			printf("-----END CHANGE-----\n");
		}
	}

	smlTrace(TRACE_EXIT, "%s - TRUE", __func__);
	return TRUE;
error:
	if (absolute_uid)
		smlSafeCFree(&absolute_uid);
	if (absolute_status)
		smlSafeCFree(&absolute_status);
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return FALSE;
}

static SmlBool recvDevInfCallback(
			SmlDataSyncObject *dsObject,
			SmlDevInf *devinf,
			void *userdata,
			SmlError **error)
{
	smlTrace(TRACE_INTERNAL, "%s(%p, %p, %p, %p)", dsObject, devinf, userdata, error);
	printf("Received device information.\n");
	remoteDevinf = devinf;
	smlDevInfRef(remoteDevinf);
	return TRUE;
}

static SmlAlertType recvAlertTypeCallback(
                        SmlDataSyncObject *dsObject,
                        const char *source,
                        SmlAlertType type,
                        void *userdata,
                        SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %d, %p, %p)", __func__, dsObject, VA_STRING(source), type, userdata, error);
	/* find the appropriate datasoure */
	SmlDsToolLocationType *datastore = NULL;
	GList *o = datastores;
	while (o) {
		datastore = o->data;
		if (!strcmp(datastore->source, source)) {
			/* abort the scan */
			o = NULL;
		} else {
			datastore = NULL;
		}
		if (o) o = o->next;
	}
	if (!datastore) {
		smlErrorSet(error, SML_ERROR_GENERIC,
			"Cannot found datastore %s.",
			source);
		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;

	smlTrace(TRACE_EXIT, "%s - slow == %d", __func__, datastore->slow);
	return type;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
	return SML_ALERT_UNKNOWN;
}

#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;
	}
	
	if (geteuid() != 0)
		fprintf(stderr, "Superuser privileges are required to access complete USB information.\n");

	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

/* ************************************ */
/* *********** USAGE ****************** */
/* ************************************ */

/* This usage message is from syncml-obex-client.c
 * FIXME: It must be replaced by a new message.
 */
static void usage (char *name)
{
	fprintf(stderr, "Usage: %s\n\n", name);

	fprintf(stderr, "\tDatastore configuration:\n");
	fprintf(stderr, "\t========================\n\n");
	fprintf(stderr, "\t--sync <type> <path> <directory>\tnormal two-way sync\n");
	fprintf(stderr, "\t--slow-sync <type> <path> <directory>\tslow two-way sync\n\n");
	fprintf(stderr, "\t<type>\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<path>\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<directory>\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 <url>\n\n");
	fprintf(stderr, "\t--ssl-ca-certs <file>\n\n");
	fprintf(stderr, "\t<url>\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 <port>\n");
	fprintf(stderr, "\t--ssl-key <file>\n");
	fprintf(stderr, "\t--ssl-cert <file>\n\n");
	fprintf(stderr, "\t<port>\tmust be a port for the http server.\n\n");

	fprintf(stderr, "\tOBEX client configuration:\n");
	fprintf(stderr, "\t==========================\n\n");
	fprintf(stderr, "\t-s <device>\tConnect to the serial device.\n");
	fprintf(stderr, "\t-u\t\tList all available USB interfaces.\n");
	fprintf(stderr, "\t-u <id>\t\tConnect to the given usb interface number.\n");
	fprintf(stderr, "\t-b <addr> <channel>\tConnect to the given bluetooth device.\n");
	fprintf(stderr, "\t--ip <addr> <port>\tConnect to this TCP/IP address.\n");
	fprintf(stderr, "\t--irda\t\tConnect using IrDA.\n");
	fprintf(stderr, "\t--irda-service <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 <port>\tListen to this TCP/IP port.\n\n");

	fprintf(stderr, "\tGeneral SyncML options:\n");
	fprintf(stderr, "\t=======================\n\n");
	fprintf(stderr, "\t--identifier <ident>\tsets the local identity of the SyncML (source).\n");
	fprintf(stderr, "\t--target <ident>\tsets the remote identity of SyncML (target).\n");
	fprintf(stderr, "\t--username <username>\tsets the username for authentication.\n");
	fprintf(stderr, "\t--password <password>\tsets the password for authentication.\n");
	fprintf(stderr, "\t--maxMsgSize <limit>\tsets the maximum message size (default: %s)\n", maxMsgSize);
	fprintf(stderr, "\t--maxObjSize <limit>\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 <version>\tsets the SyncML version.\n\n");
	fprintf(stderr, "\t<ident>\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<version>\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 <Man>\tset the manufacturer of the faked device.\n");
	fprintf(stderr, "\t--fake-model <Mod>\t\tset the model of the faked device.\n");
	fprintf(stderr, "\t--fake-software-version <SwV>\tset the software version of the faked device.\n");

	fprintf(stderr, "\n");
	exit (1);
}

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;
}

SmlBool scanArguments(
		SmlDataSyncObject *dsObject,
		int argc, char *argv[],
		SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s", __func__);
	if (argc == 1)
		usage (argv[0]);
	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);

			/* sync type */

			if (!strcmp(arg, "--slow-sync"))
				datastore->slow = TRUE;
			else
				datastore->slow = FALSE;
				
			/* load content type */

			i++;
			if (!argv[i])
				usage (argv[0]);

			datastore->contentType = 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]);
			
			datastore->source = 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)
				{
					smlErrorSet(error, SML_ERROR_INTERNAL_MISCONFIGURATION,
						"There is a problem with the directory %s (%d).",
						datastore->directory, errno);
					goto error;
				}
			} else {
				datastore->directory = NULL;
			}

			/* init uid hash table if it is an OMA DS server */

			if (sessionType == SML_SESSION_TYPE_SERVER)
				datastore->remoteHash = g_hash_table_new(g_str_hash, g_str_equal);

			/* register datastore */
			if (!smlDataSyncAddDatastore(
					dsObject,
					datastore->contentType,
					NULL,
					datastore->source,
					error))
				goto error;

		} else if (!strcmp (arg, "-u")) {
			if (!smlDataSyncSetOption(
					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();
				return 0;
#else
				printf("OBEX not available in this build\n");
				return 1;
#endif
			}
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_PORT,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "-b")) {
#ifdef ENABLE_BLUETOOTH
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_BLUETOOTH,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_BLUETOOTH_ADDRESS, 
					argv[i], error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_BLUETOOTH_CHANNEL, 
					argv[i], error))
				goto error;
#else
			printf("Bluetooth is not available in this build\n");
			return 1;
#endif
		} else if (!strcmp (arg, "--irda")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_IRDA,
					error))
				goto error;
		} else if (!strcmp (arg, "--irda-service")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_IRDA,
					error))
				goto error;
			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_IRDA_SERVICE, 
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "-s")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_SERIAL,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_URL, 
					argv[i], error))
				goto error;

		} else if (!strcmp (arg, "--channel")) {
#ifdef ENABLE_BLUETOOTH
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_BLUETOOTH,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_BLUETOOTH_CHANNEL, 
					argv[i], error))
				goto error;
#else
			printf("Bluetooth is not available in this build\n");
			return 1;
#endif
		} else if (!strcmp (arg, "--ip")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_NET,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_URL, 
					argv[i], error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_PORT, 
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--port")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_CONNECTION_TYPE, 
					SML_DATA_SYNC_CONFIG_CONNECTION_NET,
					error))
				goto error;

			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_PORT, 
					argv[i], error))
				goto error;
 		} else if (!strcmp (arg, "--identifier")) {
			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					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]);
			if (!smlDataSyncSetOption(
					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]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_AUTH_USERNAME, 
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--password")) {
			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_AUTH_PASSWORD, 
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--maxMsgSize")) {
			i++;
			if (!argv[i])
				usage (argv[0]);
			maxMsgSize = argv[i];
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_MAX_MSG_SIZE,
					maxMsgSize, error))
				goto error;
		} else if (!strcmp (arg, "--maxObjSize")) {
			i++;
			if (!argv[i])
				usage (argv[0]);
			maxObjSize = argv[i];
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_MAX_OBJ_SIZE,
					maxObjSize, error))
				goto error;
		} else if (!strcmp (arg, "--useStringTable")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_STRING_TABLE,
					"1", error))
				goto error;
		} else if (!strcmp (arg, "--disableNumberOfChanges")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_NUMBER_OF_CHANGES,
					"0", error))
				goto error;
		} else if (!strcmp (arg, "--useNumberAnchor")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_TIMESTAMP_ANCHOR,
					"0", error))
				goto error;
		} else if (!strcmp (arg, "--useLocaltime")) {
			if (!smlDataSyncSetOption(
					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: 1075 $)\n", VERSION);
				return 0;
			}

			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_VERSION,
					argv[i], error))
				goto error;
			syncmlVersion = g_strdup(argv[i]);
		} else if (!strcmp (arg, "--help")) {
			usage (argv[0]);
		} else if (!strcmp (arg, "--wbxml")) {
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_USE_WBXML,
					"1", error))
				goto error;
			mimeType = SML_MIMETYPE_WBXML;
		} else if (!strcmp (arg, "--dumpinfo")) {
			dumpinfo = TRUE;
			smlDataSyncRegisterHandleRemoteDevInfCallback(dsObject, recvDevInfCallback, NULL);
		} else if (!strcmp (arg, "--remoteWinsConflicts")) {
			localWinsConflicts = FALSE;
			if (sessionType == SML_SESSION_TYPE_CLIENT)
				usage(argv[0]);
		} else if (!strcmp(arg, "--read-only")) {
			readOnly = TRUE;
		} else if (!strcmp (arg, "--http-client")) {
			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_URL,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--ssl-ca-certs")) {
			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					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]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_PORT,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--ssl-key")) {
			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_TRANSPORT_CONFIG_SSL_KEY,
					argv[i], error))
				goto error;
		} else if (!strcmp (arg, "--ssl-cert")) {
			i++;
			if (!argv[i])
				usage (argv[0]);
			if (!smlDataSyncSetOption(
					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]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_DEVICE,
					"1", error))
				goto error;
			if (!smlDataSyncSetOption(
					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]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_DEVICE,
					"1", error))
				goto error;
			if (!smlDataSyncSetOption(
					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]);
			if (!smlDataSyncSetOption(
					dsObject,
					SML_DATA_SYNC_CONFIG_FAKE_DEVICE,
					"1", error))
				goto error;
			if (!smlDataSyncSetOption(
					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]);
		}
	}

	if (g_list_length(datastores) == 0) {
		if (syncmlVersion == NULL || strcmp(syncmlVersion, "1.2")) {
			smlErrorSet(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,
		"Failed to start the client: %s\n",
		smlErrorPrint(error));
	return FALSE;
}

/* ************************************ */
/* ************** TOOL **************** */
/* ************************************ */

int main(int argc, char *argv[])
{
	SmlError *error = NULL;
	if (!g_thread_supported ()) g_thread_init (NULL);

	/* init DS object */
	
	SmlTransportType tspType = getTransportType(argc, argv);
	sessionType = getSessionType(argc, argv);
	SmlDataSyncObject *dsObject = smlDataSyncNew(sessionType, tspType, &error);
	if (!dsObject)
		goto error;
	if (!scanArguments(dsObject, argc, argv, &error))
		goto error;

	smlDataSyncRegisterEventCallback(dsObject, recvEventCallback, NULL);
	smlDataSyncRegisterGetAlertTypeCallback(dsObject, recvAlertTypeCallback, NULL);
	smlDataSyncRegisterChangeCallback(dsObject, recvChangeCallback, NULL);
	// smlDataSyncRegisterChangeStatusCallback(dsObject, recvChangeStatusCallback);
	// smlDataSyncRegisterMappingCallback(dsObject, recvMappingCallback, NULL);

	if (!smlDataSyncInit(dsObject, &error))
		goto error;

	/* run the sync */
	checkpoint = time(NULL);
	if (!smlDataSyncRun(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", smlDevInfGetManufacturer(remoteDevinf));
			printf("Mod: %s\n", smlDevInfGetModel(remoteDevinf));
			printf("FirmwareVersion: %s\n", smlDevInfGetFirmwareVersion(remoteDevinf));
			printf("SoftwareVersion: %s\n", smlDevInfGetSoftwareVersion(remoteDevinf));
			printf("HardwareVersion: %s\n", smlDevInfGetHardwareVersion(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 < smlDevInfNumDataStores(remoteDevinf); i++) {
				const SmlDevInfDataStore *datastore = smlDevInfGetNthDataStore(remoteDevinf, i);
				printf("\tLocations: %s\n", smlDevInfDataStoreGetSourceRef(datastore));
			}
			printf("Wbxml: %s\n", mimeType == SML_MIMETYPE_WBXML ? "Yes" : "No");
			printf("SyncML Version: %s\n", syncmlVersion);
			printf("SupportsUTC: %s\n", smlDevInfSupportsUTC(remoteDevinf) ? "Yes" : "No");
			printf("SupportsNumberOfChanges: %s\n", smlDevInfSupportsNumberOfChanges(remoteDevinf) ? "Yes" : "No");
			printf("SupportsLargeObjects: %s\n", smlDevInfSupportsLargeObjs(remoteDevinf) ? "Yes" : "No");
			smlDevInfUnref(remoteDevinf);
			printf("\nlibsyncml Version: %s ($Revision: 1075 $)\n", VERSION);
		}
	}
	/* close the object */
	smlDataSyncObjectUnref(&dsObject);

	return 0;
error:
	if (error != NULL) {
		fprintf(stderr, "ERROR: %s\n", smlErrorPrint(&error));
		smlErrorDeref(&error);
	}
	return 1;
}
