/* MultiSync SyncML plugin - Implementation of SyncML 1.1 and 1.0 Copyright (C) 2002-2003 Bo Lincoln This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation; In addition, as a special exception, Bo Lincoln gives permission to link the code of this program with the OpenSSL library (or with modified versions of OpenSSL that use the same license as OpenSSL), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED. */ /* * $Id: syncml_plugin.c,v 1.40 2004/04/06 09:47:22 lincoln Exp $ */ #include #include #include #include #include "syncml_plugin.h" #include "syncml_engine.h" #include "syncml_cmd.h" #include "gui.h" extern gboolean multisync_debug; void syncml_changes_received(syncml_state *state, syncml_connection* conn, GList *changes, gboolean final, sync_object_type newdbs) { GList *change = changes; GList *results = NULL; while (change) { syncobj_modify_result *result; syncml_changed_object *obj = change->data; if (state->isserver) { result = g_malloc0(sizeof(syncobj_modify_result)); result->result = SYNC_MSG_REQDONE; results = g_list_append(results, result); } // General shape-up if (obj->change.comp) { char *tmp = sync_vtype_convert(obj->change.comp, (obj->datatype==SYNCML_DATA_TYPE_VCALENDAR1?VOPTION_CALENDAR1TO2:0)| (obj->datatype==SYNCML_DATA_TYPE_VCARD21?VOPTION_ADDUTF8CHARSET:0)| (conn->removeutc?VOPTION_REMOVEUTC:VOPTION_CONVERTUTC), NULL); g_free(obj->change.comp); obj->change.comp = tmp; } change = change->next; } if (state->isserver) { syncml_cmd_send_changes_result(state, results); conn->changelist = g_list_concat(conn->changelist, changes); conn->newdbs = newdbs; if (final) { if (conn->mode == SYNCML_PLUGIN_MODE_IDLE) { // Tell MultiSync we have changes conn->mode = SYNCML_PLUGIN_SERVER_MODE_GOTCHANGES; sync_object_changed(conn->sync_pair); dd(printf("SyncML: Told sync engine to get changes.\n")); } if (conn->mode == SYNCML_PLUGIN_SERVER_MODE_SYNCNOTIFIED) { // We are still within the get_changes() call change_info *info = g_malloc0(sizeof(change_info)); info->changes = conn->changelist; info->newdbs = newdbs; conn->changelist = NULL; conn->mode = SYNCML_PLUGIN_MODE_IDLE; sync_set_requestdata(info, conn->sync_pair); dd(printf("SyncML: Sent changes to sync engine.\n")); } } } else { // If we are client, send changes to other plugin directly dd(printf("SyncML: Client: Sending %d modifications to sync engine.\n", g_list_length(changes))); sync_feedthrough_modify(conn->sync_pair, conn->conntype, changes); } } // Syncengine returns our change results void resp_modify(syncml_connection *conn, int respcode, GList *results) { dd(printf("SyncML: Client: Got modification results.\n")); syncml_cmd_send_changes_result(conn->state, results); sync_set_requestdone(conn->sync_pair); } void syncml_changes_results_received(syncml_state *state, syncml_connection *conn, GList *results) { dd(printf("SyncML: *** Got change results! *** \n")); if (state->isserver) { // Send modification results back to sync engine sync_set_requestdata(results, conn->sync_pair); } else { sync_free_modify_results(results); } } void syncml_sync_done_received(syncml_state *state, syncml_connection* conn) { dd(printf("SyncML: *** Got sync done!*** \n")); if (!state->isserver) { conn->mode = SYNCML_PLUGIN_MODE_IDLE; sync_feedthrough_syncdone(conn->sync_pair, conn->conntype, TRUE); } } char* syncml_error_to_str(syncml_error_type err) { switch (err) { case SYNCML_ERROR_TIMEOUT: return("SyncML timeout."); case SYNCML_ERROR_CONNECTIONFAILED: return("Connection to server failed."); case SYNCML_ERROR_NOPORT: return("Could not open port."); break; case SYNCML_ERROR_OTHERAUTHFAILED: case SYNCML_ERROR_MYAUTHFAILED: return("Authentication failed."); default: return("Unknown error."); } } // Called by the SyncML engine when something went wrong void syncml_error(syncml_state *state, syncml_connection *conn, syncml_error_type err) { switch (conn->mode) { case SYNCML_PLUGIN_SERVER_MODE_GOTCHANGES: sync_free_changes(conn->changelist); conn->changelist = NULL; break; case SYNCML_PLUGIN_MODE_GET_DEVINFO: syncml_gui_devinfo_received(NULL, syncml_error_to_str(err)); break; case SYNCML_PLUGIN_SERVER_MODE_SENTMODIFY: case SYNCML_PLUGIN_SERVER_MODE_SYNCNOTIFIED: switch (err) { case SYNCML_ERROR_TIMEOUT: case SYNCML_ERROR_CONNECTIONFAILED: sync_set_requestmsg(SYNC_MSG_CONNECTIONERROR,conn->sync_pair); break; case SYNCML_ERROR_NOPORT: sync_set_requestfailederror("Could not open port.", conn->sync_pair); break; case SYNCML_ERROR_OTHERAUTHFAILED: case SYNCML_ERROR_MYAUTHFAILED: sync_set_requestmsgerror(SYNC_MSG_NORIGHTSERROR, "Authentication failed.", conn->sync_pair); default: sync_set_requestfailed(conn->sync_pair); } break; case SYNCML_PLUGIN_CLIENT_MODE_SYNC: sync_feedthrough_syncdone(conn->sync_pair, conn->conntype, FALSE); sync_log(conn->sync_pair, "Could not connect to SyncML server.", SYNC_LOG_ERROR); break; default: // We are idle, but should report some events anyway switch (err) { case SYNCML_ERROR_NOPORT: sync_log(conn->sync_pair, "Could not open server port.", SYNC_LOG_ERROR); break; case SYNCML_ERROR_OTHERAUTHFAILED: case SYNCML_ERROR_MYAUTHFAILED: sync_log(conn->sync_pair, "Authentication failed.", SYNC_LOG_ERROR); default: break; } break; } conn->mode = SYNCML_PLUGIN_MODE_IDLE; dd(printf("SyncML: SyncML server: Got error %d.\n", err)); } void syncml_info(syncml_state *state, syncml_connection *conn, char *txt) { sync_log(conn->sync_pair, txt, SYNC_LOG_SUCCESS); } // The server has sent us a "please synchronize" command void syncml_sync_serverinit_received(syncml_state *state, syncml_connection *conn) { if (!conn->isserver) { // FIXME: Newdbs? dd(printf("SyncML: Got change notification from server. Getting changes.\n")); conn->mode = SYNCML_PLUGIN_CLIENT_MODE_SYNC; sync_feedthrough_get_changes(conn->sync_pair, conn->conntype, 0); } } // Called by sync engine when a change has been detected void resp_objchanged(syncml_connection *conn) { if (!conn->isserver && conn->mode == SYNCML_PLUGIN_MODE_IDLE) { dd(printf("SyncML: Got change notification. Getting changes.\n")); conn->mode = SYNCML_PLUGIN_CLIENT_MODE_SYNC; sync_feedthrough_get_changes(conn->sync_pair, conn->conntype, 0); } else { dd(printf("SyncML: Did not get changes, as mode = %d\n", conn->mode)); } sync_set_requestdone(conn->sync_pair); } // Get changes from client again void syncml_reget_changes(syncml_state *state, syncml_connection *conn, sync_object_type newdbs) { dd(printf("SyncML: Getting changes again.\n")); conn->mode = SYNCML_PLUGIN_CLIENT_MODE_SYNC; sync_feedthrough_get_changes(conn->sync_pair, conn->conntype, newdbs); } GList *syncml_convert_copy_change_list(syncml_connection *conn, GList *orig) { GList *change_copy = NULL; while (orig) { char *tmp; sync_voption vopts = 0; syncml_data_type type = SYNCML_DATA_TYPE_UNKNOWN; changed_object *chobj = sync_copy_changed_object(orig->data); syncml_changed_object *obj = g_malloc0(sizeof(syncml_changed_object)); memcpy(obj, chobj, sizeof(changed_object)); g_free(chobj); switch (obj->change.object_type) { case SYNC_OBJECT_TYPE_CALENDAR: case SYNC_OBJECT_TYPE_TODO: type = syncml_get_db_datatype(conn, obj->change.object_type, FALSE, SYNCML_DATA_TYPE_VCALENDAR2); if (type == SYNCML_DATA_TYPE_VCALENDAR1 && obj->change.comp) { // We have to convert to vCALENDAR 1.0 vopts |= VOPTION_CALENDAR2TO1 | VOPTION_CONVERTALLDAYEVENT; } vopts |= VOPTION_ADDUTF8CHARSET; break; case SYNC_OBJECT_TYPE_PHONEBOOK: type = syncml_get_db_datatype(conn, obj->change.object_type, FALSE, SYNCML_DATA_TYPE_VCARD21); vopts |= VOPTION_ADDUTF8CHARSET; break; default: break; } if (vopts) { char *tmp = sync_vtype_convert(obj->change.comp, vopts, NULL); g_free(obj->change.comp); obj->change.comp = tmp; } obj->datatype = type; change_copy = g_list_append(change_copy, obj); orig = orig->next; } return(change_copy); } // Called by syncengine when the change list is returned void resp_get_changes(syncml_connection *conn, int respcode, change_info *changes) { change_info *info_copy = g_malloc0(sizeof(change_info)); GList *change_list = changes->changes; if (respcode >=0) { // First copy the change list as it will be freed by the sync engine info_copy->changes = syncml_convert_copy_change_list(conn, change_list); info_copy->newdbs = changes->newdbs; syncml_cmd_send_changes(conn->state, info_copy); } sync_set_requestdone(conn->sync_pair); } // If a server, return the changes from the client which we // already obtained from syncml_changes_received() void get_changes(syncml_connection* conn, sync_object_type newdbs) { change_info *info; if (!conn->isserver) { sync_set_requestfailed(conn->sync_pair); return; } dd(printf("SyncML: Get changes, mode %d\n", conn->mode)); if (conn->mode == SYNCML_PLUGIN_SERVER_MODE_GOTCHANGES) { info = g_malloc0(sizeof(change_info)); info->changes = conn->changelist; info->newdbs = conn->newdbs; conn->changelist = NULL; conn->mode = SYNCML_PLUGIN_SERVER_MODE_WAITING_FOR_MODIFY; sync_set_requestdata(info, conn->sync_pair); dd(printf("SyncML: Returned changes.\n")); } else if (conn->mode == SYNCML_PLUGIN_MODE_IDLE) { // Send "please sync" to client, but only if MultiSync // (doesn't work otherwise anyway) if (syncml_is_partner_multisync(conn->state)) { conn->mode = SYNCML_PLUGIN_SERVER_MODE_SYNCNOTIFIED; syncml_cmd_send_sync_serverinit(conn->state, newdbs); } else sync_set_requestfailed(conn->sync_pair); } else sync_set_requestfailed(conn->sync_pair); } // Called when synchronization is done void sync_done(syncml_connection *conn, gboolean success) { dd(printf("SyncML: SyncML: Got sync done from syncengine.\n")); if (conn->isserver) { conn->mode = SYNCML_PLUGIN_MODE_IDLE; syncml_cmd_send_sync_done(conn->state); } sync_set_requestdone(conn->sync_pair); } // Called by sync engine to make modifications void syncobj_modify_list(syncml_connection *conn, GList *mods) { GList *change_copy = NULL; GList *change_list = mods; change_info *info = g_malloc0(sizeof(change_info)); dd(printf("SyncML: Got modifications (%d of them).\n", g_list_length(mods))); // Send modification list if (!conn->isserver) { sync_set_requestfailed(conn->sync_pair); return; } // First copy the change list as it will be freed by the sync engine change_copy = syncml_convert_copy_change_list(conn, change_list); conn->mode = SYNCML_PLUGIN_SERVER_MODE_SENTMODIFY; dd(printf("SyncML: Sending modify.\n")); info->changes = change_copy; info->newdbs = 0; syncml_cmd_send_changes(conn->state, info); } void syncml_get_devinfo(syncml_connection *conn){ conn->mode = SYNCML_PLUGIN_MODE_GET_DEVINFO; syncml_cmd_get_devinfo(conn->state); } void syncml_devinfo_received(syncml_state *state, syncml_connection *conn, syncml_devinfo *devinfo) { dd(printf("SyncML: Received device info.\n")); if (conn->devinfo) syncml_free_devinfo(conn->devinfo); conn->devinfo = devinfo; if (conn->mode == SYNCML_PLUGIN_MODE_GET_DEVINFO) { syncml_gui_devinfo_received(devinfo, NULL); conn->mode = SYNCML_PLUGIN_MODE_IDLE; } } // Return a datatype which the remote db supports. If it supports pref, // then retunr pref, otherwise whatever is in "Rx-Pref". syncml_data_type syncml_get_db_datatype(syncml_connection *conn, sync_object_type objtype, gboolean tx, syncml_data_type pref) { if (conn->devinfo) { GList *stores = conn->devinfo->datastores; while (stores) { syncml_datastore *store = stores->data; GList *types; if (tx) types = store->tx; else types = store->rx; while (types) { syncml_data_type type = (syncml_data_type) types->data; if (syncml_data_type_to_objtype(type) & objtype) { if (type == pref) return(pref); } types = types->next; } if (tx && (syncml_data_type_to_objtype(store->txpref) & objtype)) return(store->txpref); if (!tx && (syncml_data_type_to_objtype(store->rxpref) & objtype)) return(store->rxpref); stores = stores->next; } } return(pref); } // Just a wrapper char *syncml_get_datapath(syncml_connection *conn) { return(sync_get_datapath(conn->sync_pair)); } #define SYNCMLFILE "syncml" #define SYNCMLENGINEFILE "syncmlengine" void syncml_load_state(syncml_connection *conn) { char *filename; FILE *f; char line[256]; filename = g_strdup_printf("%s/%s%s", sync_get_datapath(conn->sync_pair), (conn->conntype==CONNECTION_TYPE_LOCAL?"local":"remote"), SYNCMLFILE); conn->isserver = TRUE; if ((f = fopen(filename, "r"))) { while (fgets(line, 256, f)) { char prop[128], data[256]; if (sscanf(line, "%128s = %256[^\n]", prop, data) == 2) { if (!strcmp(prop, "isserver")) { if (!strcmp(data, "yes")) conn->isserver = TRUE; else conn->isserver = FALSE; } if (!strcmp(prop, "serverURI")) conn->serverURI = g_strdup(data); if (!strcmp(prop, "login")) conn->login = g_strdup(data); if (!strcmp(prop, "passwd")) conn->passwd = g_strdup(data); if (!strcmp(prop, "othercalendardb")) conn->othercalendardb = g_strdup(data); if (!strcmp(prop, "otherphonebookdb")) conn->otherphonebookdb = g_strdup(data); if (!strcmp(prop, "removeutc")) { if (!strcmp(data, "yes")) conn->removeutc = TRUE; else conn->removeutc = FALSE; } } } fclose(f); } g_free(filename); } void syncml_save_state(syncml_connection *conn) { FILE *f; char *filename; filename = g_strdup_printf("%s/%s%s", sync_get_datapath(conn->sync_pair), (conn->conntype==CONNECTION_TYPE_LOCAL?"local":"remote"), SYNCMLFILE); if ((f = fopen(filename, "w"))) { fprintf(f, "isserver = %s\n", conn->isserver?"yes":"no"); if (conn->serverURI) fprintf(f, "serverURI = %s\n", conn->serverURI); if (conn->login) fprintf(f, "login = %s\n", conn->login); if (conn->passwd) fprintf(f, "passwd = %s\n", conn->passwd); if (conn->othercalendardb) fprintf(f, "othercalendardb = %s\n", conn->othercalendardb); if (conn->otherphonebookdb) fprintf(f, "otherphonebookdb = %s\n", conn->otherphonebookdb); fprintf(f, "removeutc = %s\n", conn->removeutc?"yes":"no"); fclose(f); } g_free(filename); } gboolean syncml_start_syncml_engine(syncml_connection *conn) { char *filename; filename = g_strdup_printf("%s/%s%s", sync_get_datapath(conn->sync_pair), (conn->conntype==CONNECTION_TYPE_LOCAL?"local":"remote"), SYNCMLENGINEFILE); conn->state = syncml_create(conn->isserver, conn->serverURI, filename, conn); g_free(filename); if (!conn->state) return FALSE; if (conn->isserver) conn->commondata.is_feedthrough = FALSE; else conn->commondata.is_feedthrough = TRUE; syncml_set_login(conn->state, conn->login, conn->passwd); if (conn->commondata.object_types & SYNC_OBJECT_TYPE_PHONEBOOK) { syncml_add_db(conn->state, "addressbook", SYNC_OBJECT_TYPE_PHONEBOOK); if (!conn->isserver) syncml_add_remote_db(conn->state, "addressbook", conn->otherphonebookdb); } if (conn->commondata.object_types & (SYNC_OBJECT_TYPE_CALENDAR|SYNC_OBJECT_TYPE_TODO)) { syncml_add_db(conn->state, "calendar", SYNC_OBJECT_TYPE_CALENDAR|SYNC_OBJECT_TYPE_TODO); if (!conn->isserver) syncml_add_remote_db(conn->state, "calendar", conn->othercalendardb); } return(TRUE); } void syncml_stop_syncml_engine(syncml_connection *conn) { if (conn->state) { syncml_save_engine_state(conn->state); syncml_cmd_quit(conn->state); conn->state = NULL; } } syncml_connection* sync_connect(sync_pair* handle, connection_type type, sync_object_type object_types) { syncml_connection *conn; conn = g_malloc0(sizeof(syncml_connection)); conn->sync_pair = handle; conn->conntype = type; conn->commondata.object_types = object_types; syncml_load_state(conn); if (!conn->othercalendardb) conn->othercalendardb = g_strdup("calendar"); if (!conn->otherphonebookdb) conn->otherphonebookdb = g_strdup("addressbook"); if (!syncml_start_syncml_engine(conn)) goto err; sync_set_requestdone(conn->sync_pair); return(conn); err: g_free(conn); sync_set_requestfailed(conn->sync_pair); return(NULL); } void syncml_free_connection(syncml_connection *conn) { SYNCML_FREE_STRING(conn->serverURI); SYNCML_FREE_STRING(conn->login); SYNCML_FREE_STRING(conn->passwd); SYNCML_FREE_STRING(conn->othercalendardb); SYNCML_FREE_STRING(conn->otherphonebookdb); if (conn->changelist) sync_free_changes(conn->changelist); if (conn->devinfo) syncml_free_devinfo(conn->devinfo); g_free(conn); } void sync_disconnect(syncml_connection *conn) { syncml_engine_cmd cmd; sync_pair *pair = conn->sync_pair; syncml_stop_syncml_engine(conn); syncml_free_connection(conn); sync_set_requestdone(pair); } gboolean always_connected() { return(TRUE); } char* short_name() { return("syncml-plugin"); } char* long_name() { return("SyncML"); } // Return the types of objects that this client handle sync_object_type object_types() { return(SYNC_OBJECT_TYPE_CALENDAR | SYNC_OBJECT_TYPE_PHONEBOOK | SYNC_OBJECT_TYPE_TODO); } void plugin_init(void) { syncml_init(); } char* plugin_info(void) { return("The SyncML plugin works as either a server or a client. It " "can connect to " "any SyncML-compliant device, or be used to connect two MultiSync's " "over the internet."); } int plugin_API_version(void) { return(3); }