/* 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_engine.c,v 1.64 2004/04/03 17:08:02 lincoln Exp $ */ #include #include #include #include #include #include #include #include "syncml_engine.h" #include "base64.h" #include "syncml_plugin.h" #include "syncml_cmd.h" #include "config.h" extern gboolean multisync_debug; // Return gmalloc'ed file part of URI char *syncml_get_URI_file(char *URI) { char proto[32], file[1024]="", host[256]; int port=0; if (!URI) return(g_strdup("/")); if (sscanf(URI, "%31[^:]://%255[^:/]:%d/%1023s", proto, host, &port, file) >= 3) return(g_strdup_printf("/%s", file)); if (sscanf(URI, "%31[^:]://%255[^:/]/%1023s", proto, host, file) >= 2) return(g_strdup_printf("/%s", file)); if (sscanf(URI, "./%1023s", file) >= 1 || sscanf(URI, "/%1023s", file) >= 1 ) return(g_strdup_printf("/%s", file)); if (strlen(URI) > 0) return(g_strdup_printf("/%s", URI)); return(g_strdup("/")); } // Return gmalloc'ed host part of URI char *syncml_get_URI_host(char *URI) { char proto[32], host[256]; int port=0; if (!URI) return(NULL); if (sscanf(URI, "%31[^:]://%255[^:/]:%d", proto, host, &port) >= 2) return(g_strdup(host)); return(NULL); } // Return URI port (zero if not explicit) int syncml_get_URI_port(char *URI) { char proto[32], host[256]; int port=80; if (!URI) return(0); if (syncml_get_URI_proto(URI) == SYNCML_CONN_TYPE_HTTPS) port = 443; sscanf(URI, "%31[^:]://%255[^:/]:%d", proto, host, &port); return(port); } // Return URI protocol syncml_conn_type syncml_get_URI_proto(char *URI) { char proto[32]; syncml_conn_type type = SYNCML_CONN_TYPE_UNKNOWN; if (!URI) return(type); if (sscanf(URI, "%31[^:]://", proto) >= 1) { if (!g_strcasecmp(proto, "http")) type = SYNCML_CONN_TYPE_HTTP; if (!g_strcasecmp(proto, "https")) type = SYNCML_CONN_TYPE_HTTPS; if (!g_strcasecmp(proto, "obex")) type = SYNCML_CONN_TYPE_OBEX; if (!g_strcasecmp(proto, "wsp")) type = SYNCML_CONN_TYPE_WSP; } return(type); } char *syncml_build_md5_auth(syncml_state *state, char *nextnonce) { char md5[MD5_DIGEST_LENGTH]; char credb64[256]; int b64len = 256; char *str, *ret; if (!nextnonce) return(NULL); if (state->syncmlversion == SYNCML_VER_10) { char nonce[256]; int noncelen = 256; char buf[1024]; int buflen; // Use old MD5 authorization snprintf(buf, 1024, "%s:%s:", state->user, state->passwd); buflen = strlen(buf); syncml_decode64(nextnonce, strlen(nextnonce), nonce, &noncelen); if (buflen + noncelen < 1024) memcpy(buf + buflen, nonce, noncelen); MD5(buf, buflen+noncelen, md5); if (syncml_encode64(md5, MD5_DIGEST_LENGTH, credb64, 256, &b64len)>= 0) { ret = g_strdup(credb64); return(ret); } } else { str = g_strdup_printf("%s:%s", state->user, state->passwd); MD5(str, strlen(str), md5); g_free(str); if (syncml_encode64(md5, MD5_DIGEST_LENGTH, credb64, 256,&b64len)>= 0) { char buf[256]; int buflen = b64len; char nonce[256]; int noncelen = 256; memcpy(buf, credb64, b64len); buf[buflen++] = ':'; syncml_decode64(nextnonce, strlen(nextnonce), nonce, &noncelen); memcpy(buf+buflen, nonce, noncelen); buflen+=noncelen; MD5(buf,buflen, md5); if (syncml_encode64(md5, MD5_DIGEST_LENGTH, credb64, 256, &b64len)>= 0) { ret = g_strdup(credb64); return(ret); } } } return(NULL); } xmlNodePtr xmlNewChildInt(xmlNodePtr parent, xmlNsPtr ns, xmlChar *name, int content) { char *str; xmlNodePtr ptr; str = g_strdup_printf("%d", content); ptr = xmlNewChild(parent, ns, name, str); g_free(str); return(ptr); } xmlNodePtr syncml_build_header(syncml_state *state) { xmlNodePtr hdr, node; char *str; char credb64[256]; int b64len = 0; hdr = xmlNewNode(NULL, "SyncHdr"); node = xmlNewChild(hdr, NULL, "VerDTD", state->syncmlversion==SYNCML_VER_11?"1.1":"1.0"); node = xmlNewChild(hdr, NULL, "VerProto", state->syncmlversion==SYNCML_VER_11?"SyncML/1.1": "SyncML/1.0"); str = g_strdup_printf("%d", state->sessid); node = xmlNewChild(hdr, NULL, "SessionID", str); g_free(str); node = xmlNewChildInt(hdr, NULL, "MsgID", state->msgid); node = xmlNewChild(hdr, NULL, "Target", NULL); xmlNewChild(node, NULL, "LocURI", state->otherURI); node = xmlNewChild(hdr, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", state->myURI); // For debugging //node = xmlNewChild(hdr, NULL, "Meta", NULL); //node = xmlNewChildInt(node, NULL, "MaxMsgSize", 2000); if (!state->myauthok && state->user && state->passwd) { if (state->chal == SYNCML_AUTH_BASIC && !state->isserver) { state->credsent++; str = g_strdup_printf("%s:%s", state->user, state->passwd); if (syncml_encode64(str, strlen(str), credb64, 256, &b64len) >= 0) { xmlNodePtr cred = xmlNewChild(hdr, NULL, "Cred", NULL); node = xmlNewChild(cred, NULL, "Meta", NULL); node = xmlNewChild(node, NULL, "Type", "syncml:auth-basic"); xmlNewProp(node, "xmlns", "syncml:metinf"); node = xmlNewChild(cred, NULL, "Data", credb64); } g_free(str); } else if (state->mynextnonce) { char *md5 = syncml_build_md5_auth(state, state->mynextnonce); g_free(state->mynextnonce); state->credsent++; state->mynextnonce = NULL; if (md5) { xmlNodePtr cred = xmlNewChild(hdr, NULL, "Cred", NULL); node = xmlNewChild(cred, NULL, "Meta", NULL); node = xmlNewChild(node, NULL, "Type", "syncml:auth-md5"); xmlNewProp(node, "xmlns", "syncml:metinf"); node = xmlNewChild(cred, NULL, "Data", md5); g_free(md5); } } } if (state->isserver && state->authok && state->sessionidcookie && state->myURI) { // Build a response URI based on the URI the client sent + session ID char *uri = g_strdup(state->myURI); char *pos = strstr(uri, "?"); char *newuri = NULL; if (pos) *pos = 0; newuri = g_strdup_printf("%s?sessionid=%s", uri, state->sessionidcookie); node = xmlNewChild(hdr, NULL, "RespURI", newuri); g_free(newuri); g_free(uri); } return(hdr); } xmlNodePtr syncml_build_chal(syncml_state *state) { xmlNodePtr chal, meta, node; char nextnonce[16]; char nonceb64[256]; int b64len; chal = xmlNewNode(NULL, "Chal"); meta = xmlNewChild(chal, NULL, "Meta", NULL); if (state->defaultauth == SYNCML_AUTH_MD5) node = xmlNewChild(meta, NULL, "Type", "syncml:auth-md5"); else node = xmlNewChild(meta, NULL, "Type", "syncml:auth-basic"); xmlNewProp(node, "xmlns", "syncml:metinf"); node = xmlNewChild(meta, NULL, "Format", "b64"); xmlNewProp(node, "xmlns", "syncml:metinf"); if (state->defaultauth == SYNCML_AUTH_MD5) { int t; for (t = 0; t < 16; t++) { long r = random(); nextnonce[t] = (r&0xff); } if (syncml_encode64(nextnonce, 16, nonceb64, 256, &b64len) >= 0) { node = xmlNewChild(meta, NULL, "NextNonce", nonceb64); xmlNewProp(node, "xmlns", "syncml:metinf"); if (state->othernextnonce) g_free(state->othernextnonce); state->othernextnonce = g_strdup(nonceb64); } } return(chal); } xmlNodePtr syncml_build_devinf(syncml_state *state) { xmlNodePtr info, node, store, cap, rx, tx; int t = 0; info = xmlNewNode(NULL, "DevInf"); xmlNewProp(info, "xmlns", "syncml:devinf"); node = xmlNewChild(info, NULL, "VerDTD", state->syncmlversion==SYNCML_VER_11?"1.1":"1.0"); node = xmlNewChild(info, NULL, "Man", "The MultiSync Project"); node = xmlNewChild(info, NULL, "DevID", state->devID); node = xmlNewChild(info, NULL, "DevTyp", "workstation"); for (t = 0; t < g_list_length(state->db_pairs); t++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, t); store = xmlNewChild(info, NULL, "DataStore", NULL); node = xmlNewChild(store, NULL, "SourceRef", pair->myDB); if (pair->name) node = xmlNewChild(store, NULL, "DisplayName", pair->name); if (pair->object_type & SYNC_OBJECT_TYPE_CALENDAR || pair->object_type & SYNC_OBJECT_TYPE_TODO) { rx = xmlNewChild(store, NULL, "Rx-Pref", NULL); node = xmlNewChild(rx, NULL, "CTType", "text/calendar"); node = xmlNewChild(rx, NULL, "VerCT", "2.0"); rx = xmlNewChild(store, NULL, "Rx", NULL); node = xmlNewChild(rx, NULL, "CTType", "text/x-vcalendar"); node = xmlNewChild(rx, NULL, "VerCT", "1.0"); tx = xmlNewChild(store, NULL, "Tx-Pref", NULL); node = xmlNewChild(tx, NULL, "CTType", "text/calendar"); node = xmlNewChild(tx, NULL, "VerCT", "2.0"); tx = xmlNewChild(store, NULL, "Tx", NULL); node = xmlNewChild(tx, NULL, "CTType", "text/x-vcalendar"); node = xmlNewChild(tx, NULL, "VerCT", "1.0"); } if (pair->object_type & SYNC_OBJECT_TYPE_PHONEBOOK) { rx = xmlNewChild(store, NULL, "Rx-Pref", NULL); node = xmlNewChild(rx, NULL, "CTType", "text/x-vcard"); node = xmlNewChild(rx, NULL, "VerCT", "2.1"); tx = xmlNewChild(store, NULL, "Tx-Pref", NULL); node = xmlNewChild(tx, NULL, "CTType", "text/x-vcard"); node = xmlNewChild(tx, NULL, "VerCT", "2.1"); // FIXME: Add support for vCARD 3.0 } cap = xmlNewChild(store, NULL, "SyncCap", NULL); node = xmlNewChildInt(cap, NULL, "SyncType", 1); node = xmlNewChildInt(cap, NULL, "SyncType", 7); } // Taken from the VCALENDAR 1.0 Standard cap = xmlNewChild(info, NULL, "CTCap", NULL); node = xmlNewChild(cap, NULL, "CTType", "text/x-vcalendar"); node = xmlNewChild(cap, NULL, "PropName", "BEGIN"); node = xmlNewChild(cap, NULL, "ValEnum", "VCALENDAR"); node = xmlNewChild(cap, NULL, "ValEnum", "VEVENT"); node = xmlNewChild(cap, NULL, "ValEnum", "VTODO"); node = xmlNewChild(cap, NULL, "PropName", "DTSTART"); node = xmlNewChild(cap, NULL, "PropName", "DTEND"); node = xmlNewChild(cap, NULL, "PropName", "DTSTAMP"); node = xmlNewChild(cap, NULL, "PropName", "SEQUENCE"); node = xmlNewChild(cap, NULL, "PropName", "END"); node = xmlNewChild(cap, NULL, "ValEnum", "VCALENDAR"); node = xmlNewChild(cap, NULL, "ValEnum", "VEVENT"); node = xmlNewChild(cap, NULL, "ValEnum", "VTODO"); node = xmlNewChild(cap, NULL, "PropName", "UID"); node = xmlNewChild(cap, NULL, "PropName", "SUMMARY"); node = xmlNewChild(cap, NULL, "PropName", "VERSION"); node = xmlNewChild(cap, NULL, "ValEnum", "1.0"); node = xmlNewChild(cap, NULL, "PropName", "AALARM"); node = xmlNewChild(cap, NULL, "PropName", "CATEGORIES"); node = xmlNewChild(cap, NULL, "PropName", "CLASS"); node = xmlNewChild(cap, NULL, "PropName", "DALARM"); node = xmlNewChild(cap, NULL, "PropName", "EXDATE"); //node = xmlNewChild(cap, NULL, "PropName", "RDATE"); node = xmlNewChild(cap, NULL, "PropName", "RESOURCES"); node = xmlNewChild(cap, NULL, "PropName", "STATUS"); node = xmlNewChild(cap, NULL, "PropName", "ATTACH"); node = xmlNewChild(cap, NULL, "PropName", "ATTENDEE"); node = xmlNewChild(cap, NULL, "PropName", "DCREATED"); node = xmlNewChild(cap, NULL, "PropName", "COMPLETED"); node = xmlNewChild(cap, NULL, "PropName", "DESCRIPTION"); node = xmlNewChild(cap, NULL, "PropName", "DUE"); //node = xmlNewChild(cap, NULL, "PropName", "EXRULE"); node = xmlNewChild(cap, NULL, "PropName", "LAST-MODIFIED"); node = xmlNewChild(cap, NULL, "PropName", "LOCATION"); node = xmlNewChild(cap, NULL, "PropName", "PRIORITY"); node = xmlNewChild(cap, NULL, "PropName", "RELATED-TO"); node = xmlNewChild(cap, NULL, "PropName", "RRULE"); node = xmlNewChild(cap, NULL, "PropName", "TRANSP"); node = xmlNewChild(cap, NULL, "PropName", "URL"); // Approx the same features as above cap = xmlNewChild(info, NULL, "CTCap", NULL); node = xmlNewChild(cap, NULL, "CTType", "text/calendar"); node = xmlNewChild(cap, NULL, "PropName", "BEGIN"); node = xmlNewChild(cap, NULL, "ValEnum", "VCALENDAR"); node = xmlNewChild(cap, NULL, "ValEnum", "VEVENT"); node = xmlNewChild(cap, NULL, "ValEnum", "VTODO"); node = xmlNewChild(cap, NULL, "ValEnum", "VALARM"); node = xmlNewChild(cap, NULL, "PropName", "DTSTART"); node = xmlNewChild(cap, NULL, "PropName", "DTEND"); node = xmlNewChild(cap, NULL, "PropName", "DTSTAMP"); node = xmlNewChild(cap, NULL, "PropName", "SEQUENCE"); node = xmlNewChild(cap, NULL, "PropName", "END"); node = xmlNewChild(cap, NULL, "ValEnum", "VCALENDAR"); node = xmlNewChild(cap, NULL, "ValEnum", "VEVENT"); node = xmlNewChild(cap, NULL, "ValEnum", "VTODO"); node = xmlNewChild(cap, NULL, "ValEnum", "VALARM"); node = xmlNewChild(cap, NULL, "PropName", "UID"); node = xmlNewChild(cap, NULL, "PropName", "SUMMARY"); node = xmlNewChild(cap, NULL, "PropName", "VERSION"); node = xmlNewChild(cap, NULL, "ValEnum", "2.0"); node = xmlNewChild(cap, NULL, "PropName", "CATEGORIES"); node = xmlNewChild(cap, NULL, "PropName", "CLASS"); node = xmlNewChild(cap, NULL, "PropName", "DALARM"); node = xmlNewChild(cap, NULL, "PropName", "EXDATE"); //node = xmlNewChild(cap, NULL, "PropName", "RDATE"); node = xmlNewChild(cap, NULL, "PropName", "RESOURCES"); node = xmlNewChild(cap, NULL, "PropName", "STATUS"); node = xmlNewChild(cap, NULL, "PropName", "ATTACH"); node = xmlNewChild(cap, NULL, "PropName", "ATTENDEE"); node = xmlNewChild(cap, NULL, "PropName", "DCREATED"); node = xmlNewChild(cap, NULL, "PropName", "COMPLETED"); node = xmlNewChild(cap, NULL, "PropName", "DESCRIPTION"); node = xmlNewChild(cap, NULL, "PropName", "DUE"); //node = xmlNewChild(cap, NULL, "PropName", "EXRULE"); node = xmlNewChild(cap, NULL, "PropName", "LAST-MODIFIED"); node = xmlNewChild(cap, NULL, "PropName", "LOCATION"); node = xmlNewChild(cap, NULL, "PropName", "PRIORITY"); node = xmlNewChild(cap, NULL, "PropName", "RELATED-TO"); node = xmlNewChild(cap, NULL, "PropName", "TRANSP"); node = xmlNewChild(cap, NULL, "PropName", "URL"); node = xmlNewChild(cap, NULL, "PropName", "RRULE"); node = xmlNewChild(cap, NULL, "PropName", "COMMMENT"); // For VALARMs node = xmlNewChild(cap, NULL, "PropName", "ACTION"); node = xmlNewChild(cap, NULL, "PropName", "TRIGGER"); node = xmlNewChild(cap, NULL, "PropName", "DURATION"); node = xmlNewChild(cap, NULL, "PropName", "REPEAT"); cap = xmlNewChild(info, NULL, "CTCap", NULL); node = xmlNewChild(cap, NULL, "CTType", "text/x-vcard"); node = xmlNewChild(cap, NULL, "PropName", "BEGIN"); node = xmlNewChild(cap, NULL, "ValEnum", "VCARD"); node = xmlNewChild(cap, NULL, "PropName", "END"); node = xmlNewChild(cap, NULL, "ValEnum", "VCARD"); node = xmlNewChild(cap, NULL, "PropName", "VERSION"); node = xmlNewChild(cap, NULL, "ValEnum", "2.1"); node = xmlNewChild(cap, NULL, "PropName", "ENCODING"); node = xmlNewChild(cap, NULL, "PropName", "VALUE"); node = xmlNewChild(cap, NULL, "PropName", "CHARSET"); node = xmlNewChild(cap, NULL, "PropName", "FN"); node = xmlNewChild(cap, NULL, "PropName", "N"); node = xmlNewChild(cap, NULL, "PropName", "NAME"); node = xmlNewChild(cap, NULL, "PropName", "NICKNAME"); node = xmlNewChild(cap, NULL, "PropName", "PHOTO"); node = xmlNewChild(cap, NULL, "PropName", "BDAY"); node = xmlNewChild(cap, NULL, "PropName", "ADR"); node = xmlNewChild(cap, NULL, "PropName", "LABEL"); node = xmlNewChild(cap, NULL, "PropName", "TEL"); node = xmlNewChild(cap, NULL, "PropName", "EMAIL"); node = xmlNewChild(cap, NULL, "PropName", "MAILER"); node = xmlNewChild(cap, NULL, "PropName", "TZ"); node = xmlNewChild(cap, NULL, "PropName", "GEO"); node = xmlNewChild(cap, NULL, "PropName", "TITLE"); node = xmlNewChild(cap, NULL, "PropName", "ROLE"); node = xmlNewChild(cap, NULL, "PropName", "LOGO"); node = xmlNewChild(cap, NULL, "PropName", "AGENT"); node = xmlNewChild(cap, NULL, "PropName", "ORG"); node = xmlNewChild(cap, NULL, "PropName", "CATEGORIES"); node = xmlNewChild(cap, NULL, "PropName", "NOTE"); node = xmlNewChild(cap, NULL, "PropName", "PRODID"); node = xmlNewChild(cap, NULL, "PropName", "REV"); node = xmlNewChild(cap, NULL, "PropName", "SORT-STRING"); node = xmlNewChild(cap, NULL, "PropName", "SOUND"); node = xmlNewChild(cap, NULL, "PropName", "URL"); node = xmlNewChild(cap, NULL, "PropName", "UID"); node = xmlNewChild(cap, NULL, "PropName", "CLASS"); node = xmlNewChild(cap, NULL, "PropName", "KEY"); return(info); } xmlNodePtr syncml_build_devinfput(syncml_state *state, xmlNodePtr parent, syncml_cmd *refcmd) { xmlNodePtr devinf = syncml_build_devinf(state); xmlNodePtr node, meta, it; GList *items; node = xmlNewChildInt(parent, NULL, "CmdID", state->cmdid++); if (refcmd && state->othermsgid) node = xmlNewChild(parent, NULL, "MsgRef", state->othermsgid); if (refcmd && refcmd->cmdID) node = xmlNewChild(parent, NULL, "CmdRef", refcmd->cmdID); meta = xmlNewChild(parent, NULL, "Meta", NULL); node = xmlNewChild(meta, NULL, "Type", "application/vnd.syncml-devinf+xml"); xmlNewProp(node, "xmlns", "syncml:metinf"); it = xmlNewChild(parent, NULL, "Item", NULL); if (refcmd) { items = refcmd->items; if (items && items->data) { syncml_item *item = items->data; node = xmlNewChild(it, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", item->targetURI); } } else { node = xmlNewChild(it, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", state->syncmlversion==SYNCML_VER_11?"./devinf11":"./devinf10"); } node = xmlNewChild(it, NULL, "Data", NULL); xmlAddChild(node, devinf); return(parent); } xmlNodePtr syncml_build_devinfget(syncml_state *state) { xmlNodePtr node, meta, it, get; get = xmlNewNode(NULL, "Get"); node = xmlNewChildInt(get, NULL, "CmdID", state->cmdid++); meta = xmlNewChild(get, NULL, "Meta", NULL); node = xmlNewChild(meta, NULL, "Type", "application/vnd.syncml-devinf+xml"); xmlNewProp(node, "xmlns", "syncml:metinf"); it = xmlNewChild(get, NULL, "Item", NULL); node = xmlNewChild(it, NULL, "Target", NULL); xmlNewChild(node, NULL, "LocURI", state->syncmlversion==SYNCML_VER_11?"./devinf11":"./devinf10"); return(get); } xmlNodePtr syncml_build_alert(syncml_state *state, syncml_db_pair *pair, syncml_alert_code code) { xmlNodePtr hdr, node, item, meta; int alertno; hdr = xmlNewNode(NULL, "Alert"); node = xmlNewChildInt(hdr, NULL, "CmdID", state->cmdid++); alertno = code; node = xmlNewChildInt(hdr, NULL, "Data", alertno); if (code != ALERT_NEXTMSG){ item = xmlNewChild(hdr, NULL, "Item", NULL); if (pair->otherDB) { node = xmlNewChild(item, NULL, "Target", NULL); xmlNewChild(node, NULL, "LocURI", pair->otherDB); } if (pair->myDB){ node = xmlNewChild(item, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", pair->myDB); } if (code < ALERT_TWOWAYBYSERVER) { meta = xmlNewChild(item, NULL, "Meta", NULL); node = xmlNewChild(meta, NULL, "Anchor", NULL); xmlNewProp(node, "xmlns", "syncml:metinf"); if (pair->mylastanchor) xmlNewChild(node, NULL, "Last", pair->mylastanchor); else xmlNewChildInt(node, NULL, "Last", 0); if (pair->mynextanchor) g_free(pair->mynextanchor); pair->mynextanchor = g_strdup_printf("%d", (int) time(NULL)); xmlNewChild(node, NULL, "Next", pair->mynextanchor); } } else { item = xmlNewChild(hdr, NULL, "Item", NULL); node = xmlNewChild(item, NULL, "Target", NULL); xmlNewChild(node, NULL, "LocURI", state->otherURI); node = xmlNewChild(item, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", state->myURI); } return(hdr); } // Return true if all entries fit in this message gboolean syncml_build_sync(syncml_state *state, syncml_db_pair *pair) { xmlNodePtr hdr, node, cmd, meta, item; gboolean maxsizereached = FALSE; int n; hdr = xmlNewNode(NULL, "Sync"); xmlAddChild(state->outBody, hdr); node = xmlNewChildInt(hdr, NULL, "CmdID", state->cmdid++); if (pair->otherDB) { node = xmlNewChild(hdr, NULL, "Target", NULL); xmlNewChild(node, NULL, "LocURI", pair->otherDB); } if (pair->myDB){ node = xmlNewChild(hdr, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", pair->myDB); } for (n = 0; n < g_list_length(state->changelist); n++) { syncml_changed_object *obj = g_list_nth_data(state->changelist, n); cmd = NULL; if (!obj->sent && obj->change.object_type & pair->object_type) { if (maxsizereached) return(FALSE); // We want to send, but we cannot obj->sent = TRUE; if (obj->change.change_type == SYNC_OBJ_MODIFIED) cmd = xmlNewChild(hdr, NULL, "Replace", NULL); else if (obj->change.change_type == SYNC_OBJ_ADDED) cmd = xmlNewChild(hdr, NULL, "Add", NULL); else if (obj->change.change_type == SYNC_OBJ_SOFTDELETED || obj->change.change_type == SYNC_OBJ_HARDDELETED) cmd = xmlNewChild(hdr, NULL, "Delete", NULL); if (cmd) { node = xmlNewChildInt(cmd, NULL, "CmdID", state->cmdid++); meta = xmlNewChild(cmd, NULL, "Meta", NULL); if (obj->datatype != SYNCML_DATA_TYPE_UNKNOWN) { node = xmlNewChild(meta, NULL, "Type", syncml_data_type_to_str(obj->datatype)); xmlNewProp(node, "xmlns", "syncml:metinf"); } item = xmlNewChild(cmd, NULL, "Item", NULL); if (state->isserver) { if (obj->change.uid) { node = xmlNewChild(item, NULL, "Target", NULL); xmlNewChild(node, NULL, "LocURI", obj->change.uid); } // Add a faked source URI based on list position so that // we can answer with a result list in the same order node = xmlNewChild(item, NULL, "Source", NULL); xmlNewChildInt(node, NULL, "LocURI", n); } else { // We are a client and do not have to care about UID mappings. node = xmlNewChild(item, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", obj->change.uid); } { xmlNodePtr data = NULL; if ((obj->change.change_type == SYNC_OBJ_SOFTDELETED || obj->change.change_type == SYNC_OBJ_HARDDELETED)) { // If deleted, SyncML cannot differ todo from event. // Add fake data to make it possible to differ if (obj->change.object_type == SYNC_OBJECT_TYPE_TODO) { char *card = "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR"; data = xmlNewCDataBlock(state->outDoc, card, strlen(card)); } else if (obj->change.object_type == SYNC_OBJECT_TYPE_CALENDAR) { char *card = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR"; data = xmlNewCDataBlock(state->outDoc, card, strlen(card)); } } if (!data && obj->change.comp) data = xmlNewCDataBlock(state->outDoc, obj->change.comp, strlen(obj->change.comp)); node = xmlNewChild(item, NULL, "Data", NULL); xmlAddChild(node, data); if (state->othermaxmsgsize) { // Calculate the approximate message size so far. // Actually quite CPU intensive since the full tree // has to be made a string, which is then converted. int size = syncml_get_msg_size(state)+1000; if (size > ((float) state->othermaxmsgsize)*0.9) { dd(printf("SyncML: Maximum message size almost reached (%d bytes of %d).\n", size, state->othermaxmsgsize)); maxsizereached = TRUE; } } } } } } return(TRUE); } // Build a map command for each new URI xmlNodePtr syncml_build_map(syncml_state *state, syncml_db_pair *pair, GList *results) { xmlNodePtr hdr, node, meta, mapitem; gboolean found = FALSE; int n; // Any map commands for this db pair? for (n = 0; !found && n < g_list_length(results); n++) { syncobj_modify_result *result = g_list_nth_data(results, n); syncml_cmd *cmd = g_list_nth_data(state->obj_cmds, n); if (result->result >= 0 && result->returnuid && cmd && cmd->dbpair == pair) { found = TRUE; } } if (!found) return(NULL); hdr = xmlNewNode(NULL, "Map"); node = xmlNewChildInt(hdr, NULL, "CmdID", state->cmdid++); if (pair->otherDB) { node = xmlNewChild(hdr, NULL, "Target", NULL); xmlNewChild(node, NULL, "LocURI", pair->otherDB); } if (pair->myDB){ node = xmlNewChild(hdr, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", pair->myDB); } for (n = 0; n < g_list_length(results); n++) { syncobj_modify_result *result = g_list_nth_data(results, n); syncml_cmd *cmd = g_list_nth_data(state->obj_cmds, n); if (result->result >= 0 && result->returnuid && cmd && cmd->dbpair == pair) { mapitem = xmlNewChild(hdr, NULL, "MapItem", NULL); if (cmd->items && cmd->items->data) { syncml_item *item = cmd->items->data; if (item->sourceURI) { node = xmlNewChild(mapitem, NULL, "Target", NULL); xmlNewChild(node, NULL, "LocURI", item->sourceURI); } } node = xmlNewChild(mapitem, NULL, "Source", NULL); xmlNewChild(node, NULL, "LocURI", result->returnuid); } } return(hdr); } syncml_changed_object *syncml_cmd_to_changed_object(syncml_state *state, syncml_cmd *cmd) { syncml_changed_object *obj = g_malloc0(sizeof(syncml_changed_object)); char *type = NULL; if (cmd->meta && cmd->meta->type) type = cmd->meta->type; if (cmd->items && cmd->items->data) { syncml_item *item = cmd->items->data; if (item->data) obj->change.comp = g_strdup(item->data); if (item->meta && item->meta->type) type = item->meta->type; if (state->isserver) { if (item->sourceURI) obj->change.uid = g_strdup(item->sourceURI); } else { if (item->targetURI) obj->change.uid = g_strdup(item->targetURI); } } obj->change.object_type = SYNC_OBJECT_TYPE_UNKNOWN; if (type) obj->datatype = syncml_str_to_data_type(type); else { // For some reason, no data type was given // Take Tx-Pref from database if (state->otherdevinfo && cmd->dbpair) { GList *stores = state->otherdevinfo->datastores; while (stores) { syncml_datastore *store = stores->data; GList *types; if (store->sourceref && cmd->dbpair->otherDB && !g_strcasecmp(store->sourceref, cmd->dbpair->otherDB)) { dd(printf("SyncML: Found Tx database type: %d\n", store->txpref)); obj->datatype = store->txpref; } stores = stores->next; } } } if (obj->datatype == SYNCML_DATA_TYPE_VCALENDAR1 || obj->datatype == SYNCML_DATA_TYPE_VCALENDAR2) { // Fix for a bug in P800 which reports version 2 although it is 1 if (obj->change.comp && strstr(obj->change.comp, "\nVERSION:1.0")) { obj->datatype = SYNCML_DATA_TYPE_VCALENDAR1; } if (obj->change.comp && strstr(obj->change.comp, "\nBEGIN:VEVENT")) { obj->change.object_type = SYNC_OBJECT_TYPE_CALENDAR; } else if (obj->change.comp && strstr(obj->change.comp, "\nBEGIN:VTODO")) { obj->change.object_type = SYNC_OBJECT_TYPE_TODO; } else { obj->change.object_type = SYNC_OBJECT_TYPE_CALENDAR; } } if (obj->datatype == SYNCML_DATA_TYPE_VCARD21 || obj->datatype == SYNCML_DATA_TYPE_VCARD30) obj->change.object_type = SYNC_OBJECT_TYPE_PHONEBOOK; switch (cmd->cmd) { case SYNCML_CMD_ADD: obj->change.change_type = SYNC_OBJ_ADDED; break; case SYNCML_CMD_REPLACE: obj->change.change_type = SYNC_OBJ_MODIFIED; break; case SYNCML_CMD_DELETE: obj->change.change_type = SYNC_OBJ_HARDDELETED; if (obj->change.comp) g_free(obj->change.comp); obj->change.comp = NULL; break; default: break; } return(obj); } char* syncml_cmd_string(syncml_cmd_type type) { switch(type) { case SYNCML_CMD_ADD: return("Add"); case SYNCML_CMD_ALERT: return("Alert"); case SYNCML_CMD_DELETE: return("Delete"); case SYNCML_CMD_GET: return("Get"); case SYNCML_CMD_MAP: return("Map"); case SYNCML_CMD_PUT: return("Put"); case SYNCML_CMD_REPLACE: return("Replace"); case SYNCML_CMD_RESULTS: return("Results"); case SYNCML_CMD_SYNC: return("Sync"); case SYNCML_CMD_SYNCHDR: return("SyncHdr"); default: return("Unknown"); } return(NULL); } syncml_cmd_type syncml_string_cmd(char* cmd) { if (!strcmp(cmd, "Add")) return SYNCML_CMD_ADD; if (!strcmp(cmd, "Alert")) return SYNCML_CMD_ALERT; if (!strcmp(cmd, "Delete")) return SYNCML_CMD_DELETE; if (!strcmp(cmd, "Get")) return SYNCML_CMD_GET; if (!strcmp(cmd, "Map")) return SYNCML_CMD_MAP; if (!strcmp(cmd, "Put")) return SYNCML_CMD_PUT; if (!strcmp(cmd, "Replace")) return SYNCML_CMD_REPLACE; if (!strcmp(cmd, "Results")) return SYNCML_CMD_RESULTS; if (!strcmp(cmd, "Sync")) return SYNCML_CMD_SYNC; if (!strcmp(cmd, "SyncHdr")) return SYNCML_CMD_SYNCHDR; return (SYNCML_CMD_UNKNOWN); } xmlNodePtr syncml_build_status(syncml_state *state, syncml_cmd *cmd, int cmdstatus) { xmlNodePtr status, node; int noitems=0; status = xmlNewNode(NULL, "Status"); node = xmlNewChildInt(status, NULL, "CmdID", state->cmdid++); if (state->othermsgid) node = xmlNewChild(status, NULL, "MsgRef", state->othermsgid); if (cmd->cmdID) node = xmlNewChild(status, NULL, "CmdRef", cmd->cmdID); node = xmlNewChild(status, NULL, "Cmd", syncml_cmd_string(cmd->cmd)); if (cmd->targetURI) node = xmlNewChild(status, NULL, "TargetRef", cmd->targetURI); else if (cmd->items && cmd->items) { // Weird but true syncml_item *item = cmd->items->data; if (item->targetURI) node = xmlNewChild(status, NULL, "TargetRef", item->targetURI); } if (cmd->sourceURI) node = xmlNewChild(status, NULL, "SourceRef", cmd->sourceURI); else if (cmd->items && cmd->items) { syncml_item *item = cmd->items->data; if (item->sourceURI) node = xmlNewChild(status, NULL, "SourceRef", item->sourceURI); } noitems = g_list_length(cmd->items); if (noitems > 1) { syncml_item *item = cmd->items->data; if (item->targetURI) node = xmlNewChild(status, NULL, "TargetRef", item->targetURI); if (item->sourceURI) node = xmlNewChild(status, NULL, "SourceRef", item->sourceURI); } node = xmlNewChildInt(status, NULL, "Data", cmdstatus); return(status); } int syncml_parse_node_value(xmlDocPtr doc, xmlNodePtr node, char **keys, int *vals) { char *t = NULL; int defaultval = vals[0]; syncml_get_node_value(doc, node, &t); while(keys && *keys) { if (!strcmp(t, *keys)) { g_free(t); return(*vals); } keys++; vals++; } g_free(t); return(defaultval); } // Put a copy of the node value in *ptr (after freeing whatever is in *ptr) void syncml_get_node_value(xmlDocPtr doc, xmlNodePtr node, char** ptr) { xmlChar *text = xmlNodeListGetString(doc, node->children, 1); if (*ptr) g_free(*ptr); *ptr = g_strdup(text); if (*ptr) // FIXME: Only a bug-workaround for wbxml *ptr = g_strstrip(*ptr); free(text); } int syncml_get_node_int(xmlDocPtr doc, xmlNodePtr node) { xmlChar *text = xmlNodeListGetString(doc, node->children, 1); int res = 0; sscanf(text, "%d", &res); free(text); return(res); } char *syncml_data_type_to_str(syncml_data_type type) { switch(type) { case SYNCML_DATA_TYPE_VCARD21: return("text/x-vcard"); case SYNCML_DATA_TYPE_VCARD30: return("text/vcard"); case SYNCML_DATA_TYPE_VCALENDAR1: return("text/x-vcalendar"); case SYNCML_DATA_TYPE_VCALENDAR2: return("text/calendar"); default: return("text/unknown"); } } syncml_data_type syncml_str_to_data_type(char *str) { if (!str) return(SYNCML_DATA_TYPE_UNKNOWN); if (!strcmp(str, "text/x-vcard")) return(SYNCML_DATA_TYPE_VCARD21); if (!strcmp(str, "text/vcard")) return(SYNCML_DATA_TYPE_VCARD30); if (!strcmp(str, "text/x-vcalendar")) return(SYNCML_DATA_TYPE_VCALENDAR1); if (!strcmp(str, "text/calendar")) return(SYNCML_DATA_TYPE_VCALENDAR2); return(SYNCML_DATA_TYPE_UNKNOWN); } sync_object_type syncml_data_type_to_objtype(syncml_data_type type) { switch(type) { case SYNCML_DATA_TYPE_VCARD21: return(SYNC_OBJECT_TYPE_PHONEBOOK); case SYNCML_DATA_TYPE_VCARD30: return(SYNC_OBJECT_TYPE_PHONEBOOK); case SYNCML_DATA_TYPE_VCALENDAR1: return(SYNC_OBJECT_TYPE_CALENDAR|SYNC_OBJECT_TYPE_TODO); case SYNCML_DATA_TYPE_VCALENDAR2: return(SYNC_OBJECT_TYPE_CALENDAR|SYNC_OBJECT_TYPE_TODO); default: return(SYNC_OBJECT_TYPE_UNKNOWN); } } void syncml_parse_devinf(syncml_state *state, xmlDocPtr doc, xmlNodePtr devinf) { if (state->otherdevinfo) syncml_free_devinfo(state->otherdevinfo); state->otherdevinfo = g_malloc0(sizeof(syncml_devinfo)); //dd(printf("SyncML: Parsing devinfo.\n")); while (devinf) { if (!strcmp(devinf->name, "DevInf")) { xmlNodePtr info = devinf->children; while (info) { char *data = NULL; syncml_get_node_value(doc, info, &data); if (!strcmp(info->name, "Man")) { dd(printf("SyncML: Manufacturer: %s\n", data)); state->otherdevinfo->manufacturer = g_strdup(data); } if (!strcmp(info->name, "DevID")) { dd(printf("SyncML: Device ID: %s\n", data)); state->otherdevinfo->devID = g_strdup(data); } if (!strcmp(info->name, "Model")) { dd(printf("SyncML: Device model: %s\n", data)); state->otherdevinfo->model = g_strdup(data); } if (!strcmp(info->name, "DevTyp")) dd(printf("SyncML: Device type: %s\n", data)); if (data) g_free(data); if (!strcmp(info->name, "DataStore")) { xmlNodePtr store = info->children; syncml_datastore *dstore = g_malloc0(sizeof(syncml_datastore)); while (store) { if (!strcmp(store->name, "SourceRef")) { syncml_get_node_value(doc, store, &(dstore->sourceref)); } if (!strcmp(store->name, "Rx-Pref")) { xmlNodePtr format = store->children; while (format) { if (!strcmp(format->name, "CTType")) { char *data = NULL; syncml_get_node_value(doc, format, &data); dstore->rxpref = syncml_str_to_data_type(data); g_free(data); } format = format->next; } } if (!strcmp(store->name, "Tx-Pref")) { xmlNodePtr format = store->children; while (format) { if (!strcmp(format->name, "CTType")) { char *data = NULL; syncml_get_node_value(doc, format, &data); dstore->txpref = syncml_str_to_data_type(data); g_free(data); } format = format->next; } } if (!strcmp(store->name, "Rx")) { xmlNodePtr format = store->children; while (format) { if (!strcmp(format->name, "CTType")) { char *data = NULL; syncml_get_node_value(doc, format, &data); dstore->rx = g_list_append(dstore->rx, (gpointer) syncml_str_to_data_type(data)); g_free(data); } format = format->next; } } if (!strcmp(store->name, "Tx")) { xmlNodePtr format = store->children; while (format) { if (!strcmp(format->name, "CTType")) { char *data = NULL; syncml_get_node_value(doc, format, &data); dstore->tx = g_list_append(dstore->tx, (gpointer) syncml_str_to_data_type(data)); g_free(data); } format = format->next; } } store = store->next; } state->otherdevinfo->datastores = g_list_append(state->otherdevinfo->datastores, dstore); } info = info->next; } } devinf = devinf->next; } } // Return true if one of the nodes children's name matches "name" gboolean syncml_cmp_node_child(xmlNodePtr node, char *name) { node = node->children; while (node) { if (!strcmp(node->name, name)) return(TRUE); node = node->next; } return(FALSE); } // Get the data of the child of the node with name "name" gboolean syncml_get_child_value(xmlDocPtr doc, xmlNodePtr node, char *name, char **data) { node = node->children; while (node) { if (!strcmp(node->name, name)) { if (data) syncml_get_node_value(doc, node, data); return(TRUE); } node = node->next; } return(FALSE); } void syncml_generate_session_cookie(syncml_state* state) { char id[17]; int t; char hex[] = "0123456789abcdef"; if (state->sessionidcookie) g_free(state->sessionidcookie); for (t = 0; t < 16; t++) { long r = random(); id[t] = hex[(r&0xf)]; } id[t] = 0; state->sessionidcookie = g_strdup(id); } void syncml_parse_synchdr(syncml_state *state, xmlDocPtr doc, xmlNodePtr hdr) { xmlNodePtr status, node; syncml_status_code cmdstatus; gboolean authenticated = FALSE; //dd(printf("SyncML: Parsing header.\n")); while (hdr) { if (!strcmp(hdr->name, "VerDTD")) { char *ver = NULL; syncml_get_node_value(doc, hdr, &ver); if (ver && !strcmp(ver, "1.0")) { state->syncmlversion = SYNCML_VER_10; dd(printf("SyncML: Using SyncML 1.0\n")); } if (ver && !strcmp(ver, "1.1")) { state->syncmlversion = SYNCML_VER_11; dd(printf("SyncML: Using SyncML 1.1\n")); } if (ver) g_free(ver); } if (!strcmp(hdr->name, "SessionID")) { char *id = NULL; syncml_get_node_value(doc, hdr, &id); state->sessid = atoi(id); if (id) g_free(id); } if (!strcmp(hdr->name, "Meta")) { xmlNodePtr meta = hdr->children; while (meta) { if (!strcmp(meta->name, "MaxMsgSize")) { state->othermaxmsgsize = syncml_get_node_int(doc, meta); dd(printf("SyncML: The maximum message size is %d bytes.\n", state->othermaxmsgsize)); } meta = meta->next; } } if (!strcmp(hdr->name, "Target")) { if (state->isserver) { // If we are server, copy the requested URI to "myURI" syncml_get_child_value(doc, hdr, "LocURI", &state->myURI); } } if (!strcmp(hdr->name, "Source")) { if (state->isserver) { // If we are server, copy the requested URI to "myURI" syncml_get_child_value(doc, hdr, "LocURI", &state->otherURI); } } if (!strcmp(hdr->name, "RespURI")) { char *uri = NULL; syncml_get_node_value(doc, hdr, &uri); if (state->otherURI && uri && strcmp(state->otherURI, uri)) { // Disconnect if the new URI differs from the last if (state->connfd >= 0) close(state->connfd); state->connfd = -1; } if (state->otherURI) g_free(state->otherURI); state->otherURI = uri; } if (!strcmp(hdr->name, "MsgID")) syncml_get_node_value(doc, hdr, &state->othermsgid); if (!strcmp(hdr->name, "Cred")) { // Authentication xmlNodePtr cred = hdr->children; syncml_auth_type type = SYNCML_AUTH_BASIC; syncml_format_type format = SYNCML_FORMAT_B64; char *data = NULL; while (cred) { if (!strcmp(cred->name, "Meta")) { xmlNodePtr meta = cred->children; while(meta) { if (!strcmp(meta->name, "Type")) { char* authstr[] = {"syncml:auth-basic", "syncml:auth-md5", NULL}; int authtype[] = { SYNCML_AUTH_BASIC, SYNCML_AUTH_MD5 }; type = syncml_parse_node_value(doc, meta, authstr, authtype); state->usedauth = type; } if (!strcmp(meta->name, "Format")) { char* formstr[] = {"b64", NULL }; int formtype[] = { SYNCML_FORMAT_B64 }; format = syncml_parse_node_value(doc, meta, formstr, formtype); } meta = meta->next; } } if (!strcmp(cred->name, "Data")) { syncml_get_node_value(doc, cred, &data); } cred = cred->next; } if (data) { char decodedata[256]; int decodedatalen = 256; syncml_decode64(data, strlen(data), decodedata, &decodedatalen); switch(type) { case SYNCML_AUTH_BASIC: { char userid[256], passwd[256]; dd(printf("SyncML: Found basic auth.\n")); if (sscanf(decodedata, "%255[^:]:%255s", userid, passwd) == 2) { if (!strcmp(userid, state->user) && !strcmp(passwd, state->passwd)) { state->authok = TRUE; authenticated = TRUE; syncml_generate_session_cookie(state); dd(printf("SyncML: Basic authorization succeeded.\n")); } } } break; case SYNCML_AUTH_MD5: { char *md5 = syncml_build_md5_auth(state, state->othernextnonce); g_free(state->othernextnonce); state->othernextnonce = NULL; if (md5) { if (!strcmp(md5, data)) { state->authok = TRUE; authenticated = TRUE; syncml_generate_session_cookie(state); dd(printf("SyncML: MD5 authorization succeeded.\n")); } g_free(md5); } } break; /* SYNCML_DISCONNECT_DISCONNECT, SYNCML_AUTH_NONESYNCML_AUTH_NONE */ default: break; } g_free(data); } } hdr = hdr->next; } if (authenticated) cmdstatus = SYNCML_STATUS_AUTHFORSESSION; else if (state->authok) cmdstatus = SYNCML_STATUS_OK; else { cmdstatus = SYNCML_STATUS_NOCRED; if (state->chalsent) state->disconnect = TRUE; } status = xmlNewNode(NULL, "Status"); node = xmlNewChildInt(status, NULL, "CmdID", state->cmdid++); node = xmlNewChild(status, NULL, "MsgRef", state->othermsgid); node = xmlNewChildInt(status, NULL, "CmdRef", 0); node = xmlNewChild(status, NULL, "Cmd", "SyncHdr"); if (state->myURI) node = xmlNewChild(status, NULL, "TargetRef", state->myURI); if (state->otherURI) node = xmlNewChild(status, NULL, "SourceRef", state->otherURI); if (cmdstatus == SYNCML_STATUS_NOCRED) { xmlAddChild(status, syncml_build_chal(state)); state->chalsent = TRUE; state->respwanted = TRUE; } if (authenticated && state->usedauth == SYNCML_AUTH_MD5) { xmlAddChild(status, syncml_build_chal(state)); } node = xmlNewChildInt(status, NULL, "Data", cmdstatus); xmlAddChild(state->outBody, status); // Do NOT increase nocmds counter (there is always a SyncHdr response) } syncml_chal* syncml_parse_chal(syncml_state *state, xmlDocPtr doc, xmlNodePtr node) { syncml_chal *chal = g_malloc0(sizeof(syncml_chal)); syncml_format_type format; while (node) { if (!strcmp(node->name, "Meta")) { xmlNodePtr meta = node->children; while(meta) { if (!strcmp(meta->name, "Type")) { char* authstr[] = {"syncml:auth-basic", "syncml:auth-md5", NULL}; int authtype[] = { SYNCML_AUTH_BASIC, SYNCML_AUTH_MD5 }; chal->type = syncml_parse_node_value(doc, meta, authstr, authtype); } if (!strcmp(meta->name, "Format")) { char* formstr[] = {"b64", NULL }; int formtype[] = { SYNCML_FORMAT_B64 }; format = syncml_parse_node_value(doc, meta, formstr, formtype); } if (!strcmp(meta->name, "NextNonce")) { syncml_get_node_value(doc, meta, &(chal->nextnonce)); } meta = meta->next; } } node = node->next; } return(chal); } syncml_meta* syncml_parse_meta(syncml_state *state, xmlDocPtr doc, xmlNodePtr node) { syncml_meta *meta = g_malloc0(sizeof(syncml_meta)); while (node) { if (!strcmp(node->name, "Anchor")) { xmlNodePtr anchor = node->children; while (anchor){ if (!strcmp(anchor->name, "Last")) syncml_get_node_value(doc, anchor, &meta->lastanchor); if (!strcmp(anchor->name, "Next")) syncml_get_node_value(doc, anchor, &meta->nextanchor); anchor = anchor->next; } } if (!strcmp(node->name, "Type")) syncml_get_node_value(doc, node, &meta->type); node = node->next; } return(meta); } syncml_item* syncml_parse_item(syncml_state *state, xmlDocPtr doc, xmlNodePtr node) { syncml_item *item = g_malloc0(sizeof(syncml_item)); while (node) { if (!strcmp(node->name, "Target")) { xmlNodePtr target = node->children; while (target) { if (!strcmp(target->name, "LocURI")) syncml_get_node_value(doc, target, &item->targetURI); target = target->next; } } if (!strcmp(node->name, "Source")) { xmlNodePtr source = node->children; while (source) { if (!strcmp(source->name, "LocURI")) syncml_get_node_value(doc, source, &item->sourceURI); source = source->next; } } if (!strcmp(node->name, "Meta")) item->meta = syncml_parse_meta(state, doc, node->children); if (!strcmp(node->name, "Data")) { syncml_get_node_value(doc, node, &item->data); item->dataptr = node->children; } node = node->next; } return(item); } syncml_db_pair *syncml_find_dbpair(syncml_state *state, char *target) { GList *pairs = state->db_pairs; while(pairs) { syncml_db_pair *pair = pairs->data; if (pair->myDB && target) { char *file = syncml_get_URI_file(target); char *db = syncml_get_URI_file(pair->myDB); if (!strcmp(db, file)) { g_free(file); g_free(db); return(pair); } g_free(file); g_free(db); } pairs = pairs->next; } return(NULL); } // Generic command parser syncml_cmd* syncml_parse_cmd(syncml_state *state, xmlDocPtr doc, xmlNodePtr node) { syncml_cmd *cmd = g_malloc0(sizeof(syncml_cmd)); while (node) { if (!strcmp(node->name, "CmdID")) syncml_get_node_value(doc, node, &cmd->cmdID); if (!strcmp(node->name, "Data")) syncml_get_node_value(doc, node, &cmd->data); if (!strcmp(node->name, "Target")) { xmlNodePtr target = node->children; while (target) { if (!strcmp(target->name, "LocURI")) syncml_get_node_value(doc, target, &cmd->targetURI); target = target->next; } } if (!strcmp(node->name, "Source")) { xmlNodePtr source = node->children; while (source) { if (!strcmp(source->name, "LocURI")) syncml_get_node_value(doc, source, &cmd->sourceURI); source = source->next; } } if (!strcmp(node->name, "Meta")) cmd->meta = syncml_parse_meta(state, doc, node->children); if (!strcmp(node->name, "Item")) cmd->items = g_list_append(cmd->items, syncml_parse_item(state, doc, node->children)); if (!strcmp(node->name, "MapItem")) cmd->mapitems = g_list_append(cmd->mapitems, syncml_parse_item(state, doc, node->children)); node = node->next; } cmd->dbpair = syncml_find_dbpair(state, cmd->targetURI); return(cmd); } syncml_status* syncml_parse_status(syncml_state *state, xmlDocPtr doc, xmlNodePtr node) { syncml_status *status = g_malloc0(sizeof(syncml_status)); while (node) { if (!strcmp(node->name, "Data")) status->code = syncml_get_node_int(doc, node); if (!strcmp(node->name, "Meta")) status->meta = syncml_parse_meta(state, doc, node->children); if (!strcmp(node->name, "CmdRef")) syncml_get_node_value(doc, node, &status->cmdref); if (!strcmp(node->name, "SourceRef")) { syncml_get_node_value(doc, node, &status->sourceref); status->dbpair = syncml_find_dbpair(state, status->sourceref); } if (!strcmp(node->name, "TargetRef")) syncml_get_node_value(doc, node, &status->targetref); if (!strcmp(node->name, "Chal")) status->chal = syncml_parse_chal(state, doc, node->children); if (!strcmp(node->name, "Cmd")) { char *cmd = NULL; syncml_get_node_value(doc, node, &cmd); status->cmd = syncml_string_cmd(cmd); g_free(cmd); } if (!strcmp(node->name, "Item")) status->items = g_list_append(status->items, syncml_parse_item(state, doc, node->children)); node = node->next; } return(status); } void syncml_parse_alert(syncml_state *state, xmlDocPtr doc, xmlNodePtr cmd) { int n; gboolean dbfound = FALSE; gboolean wronganchors = FALSE; xmlNodePtr status, node; syncml_alert_code alertcode = ALERT_TWOWAY; syncml_cmd *alert = syncml_parse_cmd(state, doc, cmd); alert->cmd = SYNCML_CMD_ALERT; // Find all databases to be synchronized if (alert->data) sscanf(alert->data, "%d", &alertcode); if (alertcode == ALERT_TWOWAYBYSERVER) state->syncbyserverreceived = TRUE; if (alertcode == ALERT_SLOWSYNC || alertcode == ALERT_TWOWAY) state->alertreceived = TRUE; if (alertcode != ALERT_NEXTMSG) { for (n = 0; n < g_list_length(alert->items); n++) { syncml_item *item = g_list_nth_data(alert->items, n); if (item->targetURI && item->sourceURI) { syncml_db_pair *pair = syncml_find_dbpair(state, item->targetURI); if (pair) { if (alertcode == ALERT_SLOWSYNC) { dd(printf("SyncML: Slow sync requested by other side.\n")); pair->slowsync = TRUE; } if (state->isserver) pair->otherDB = g_strdup(item->sourceURI); pair->dosynchronize = TRUE; if (item->meta) { if (alertcode != ALERT_SLOWSYNC) { if (pair->lastanchor && item->meta->lastanchor && !strcmp(pair->lastanchor, item->meta->lastanchor)) { dd(printf("SyncML: Last anchors are equal, do normal sync.\n")); } else { dd(printf("SyncML: Last anchors differ (%s-%s), do slow sync.\n", pair->lastanchor, item->meta->lastanchor)); wronganchors = TRUE; pair->slowsync = TRUE; } } SYNCML_FREE_STRING(pair->lastanchor); if (item->meta->lastanchor) pair->lastanchor = g_strdup(item->meta->nextanchor); dd(printf("SyncML: Found DB pair: %s - %s\n", pair->myDB, pair->otherDB)); dbfound = TRUE; } } } } if (dbfound) { status = syncml_build_status(state, alert, wronganchors?SYNCML_STATUS_REFRESHREQ: SYNCML_STATUS_OK); if (alert->items) { syncml_item *item = alert->items->data; if (item->meta && item->meta->nextanchor) { node = xmlNewChild(status, NULL, "Item", NULL); node = xmlNewChild(node, NULL, "Data", NULL); node = xmlNewChild(node, NULL, "Anchor", NULL); node = xmlNewChild(node, NULL, "Next", item->meta->nextanchor); syncml_save_engine_state(state); // Save anchors 'n' stuff } } } else status = syncml_build_status(state, alert, SYNCML_STATUS_NOTFOUND); } else { // If ALERT_NEXTMSG status = syncml_build_status(state, alert, SYNCML_STATUS_OK); } xmlAddChild(state->outBody, status); state->nocmds++; } int syncml_compare_syncml_modify_results(syncml_modify_result *r1, syncml_modify_result *r2) { if (r1->listpos > r2->listpos) return(1); else return(-1); } void syncml_parse_syncbody(syncml_state *state, xmlDocPtr doc, xmlNodePtr body) { //dd(printf("SyncML: Parsing body.\n")); while (body) { // Go through commands if (!strcmp(body->name, "Status")) { syncml_status *status = syncml_parse_status(state, doc, body->children); if (status->cmd == SYNCML_CMD_ALERT && ((status->code >= 200 && status->code < 300) || status->code == SYNCML_STATUS_REFRESHREQ)) { GList *items = status->items; // Alert success if (status->dbpair) { while (items) { // Move the returned nextanchor to my lastanchor syncml_item *item = items->data; xmlNodePtr data = item->dataptr; while (data) { if (!strcmp(data->name, "Anchor")) { xmlNodePtr anch = data->children; while (anch) { if (!strcmp(anch->name, "Next")) syncml_get_node_value(doc, anch, &(status->dbpair->mylastanchor)); anch = anch->next; } } data = data->next; } items = items->next; } } if (state->initalertsent) state->initalertsuccess = TRUE; state->inited = TRUE; } if (status->cmd == SYNCML_CMD_SYNCHDR && state->mapsent) { state->mapstatusreceived = TRUE; state->mapsent = FALSE; } if (status->code == SYNCML_STATUS_AUTHFORSESSION) { dd(printf("SyncML: OK, I'm authenticated.\n")); state->myauthok = TRUE; } if (status->chal) { if (status->code == SYNCML_STATUS_NOCRED || status->code == SYNCML_STATUS_INVCRED) { state->resendpkg = TRUE; if (state->credsent >= 2) state->disconnect = TRUE; } state->chal = status->chal->type; if (status->chal->nextnonce) { if (state->mynextnonce) g_free(state->mynextnonce); state->mynextnonce = g_strdup(status->chal->nextnonce); } } if (status->cmd == SYNCML_CMD_MAP) { state->mapstatusreceived = TRUE; } if (status->cmd == SYNCML_CMD_SYNC) state->syncstatusreceived = TRUE; if ((status->cmd == SYNCML_CMD_ADD || status->cmd == SYNCML_CMD_REPLACE || status->cmd == SYNCML_CMD_DELETE)) { // We got a result for our sync command // => build syncobj_modify_result syncml_modify_result *result = g_malloc0(sizeof(syncml_modify_result)); if (status->code >= 200 && status->code < 300) result->result.result = SYNC_MSG_REQDONE; else result->result.result = SYNC_MSG_REQFAILED; if (status->sourceref) // We should get a source ID = list position sscanf(status->sourceref, "%d", &(result->listpos)); // Add changes, and sort according to list position state->changeresults = g_list_insert_sorted(state->changeresults, result, (GCompareFunc) syncml_compare_syncml_modify_results); state->syncstatusreceived = TRUE; } syncml_free_status(status); } else if (!strcmp(body->name, "Final")) { state->finalreceived = TRUE; } else if (!state->authok) { // If not authorized, any command should fail if (strcmp(body->name, "text")) { // Only commands, not text syncml_cmd *cmd = syncml_parse_cmd(state, doc, body->children); xmlNodePtr status; cmd->cmd = syncml_string_cmd((char*)body->name); status = syncml_build_status(state, cmd, SYNCML_STATUS_INVCRED); xmlAddChild(state->outBody, status); state->nocmds++; syncml_free_cmd(cmd); } } else if (!strcmp(body->name, "Alert")) { syncml_parse_alert(state, doc, body->children); } else if (!strcmp(body->name, "Sync")) { // Sync if (state->inited) { xmlNodePtr status; xmlNodePtr sync = body->children; syncml_cmd *synccmd = syncml_parse_cmd(state, doc, body->children); synccmd->cmd = syncml_string_cmd((char*)body->name); // Send status for "Sync" immediately status = syncml_build_status(state, synccmd, SYNCML_STATUS_OK); xmlAddChild(state->outBody, status); state->nocmds++; while (sync) { if (!strcmp(sync->name, "Replace") || !strcmp(sync->name, "Add") || !strcmp(sync->name, "Delete")) { xmlNodePtr status; syncml_cmd *cmd = syncml_parse_cmd(state, doc, sync->children); cmd->cmd = syncml_string_cmd((char*) sync->name); cmd->dbpair = synccmd->dbpair; state->obj_cmds = g_list_append(state->obj_cmds, cmd); state->changelist = g_list_append(state->changelist, syncml_cmd_to_changed_object(state, cmd)); } sync = sync->next; } syncml_free_cmd(synccmd); state->syncreceived = TRUE; } else { // Send error } } else if (!strcmp(body->name, "Map")) { syncml_cmd *cmd = syncml_parse_cmd(state, doc, body->children); GList *mapitems = cmd->mapitems; cmd->cmd = syncml_string_cmd((char*)body->name); state->map_cmds = g_list_append(state->map_cmds, cmd); while (mapitems) { syncml_item *item = mapitems->data; if (item->targetURI && item->sourceURI) { int n = -1; if (sscanf(item->targetURI, "%d", &n) >= 1) { GList *results = state->changeresults; // We got a new LUID back while (results) { syncml_modify_result *result = results->data; if (result && result->listpos == n) { if (result->result.returnuid) g_free(result->result.returnuid); result->result.returnuid = g_strdup(item->sourceURI); } results = results->next; } } } mapitems = mapitems->next; } } else if (!strcmp(body->name, "Put") || !strcmp(body->name, "Results")) { syncml_cmd *cmd = syncml_parse_cmd(state, doc, body->children); xmlNodePtr status; GList *items = cmd->items; cmd->cmd = syncml_string_cmd((char*)body->name); while (items) { syncml_item *item = items->data; if (item->sourceURI && !strncmp(item->sourceURI, "./devinf", 8)) { syncml_parse_devinf(state, doc, item->dataptr); state->devinfreceived = TRUE; status = syncml_build_status(state, cmd, SYNCML_STATUS_OK); } else { status = syncml_build_status(state, cmd, SYNCML_STATUS_PERMDENIED); } if (cmd->cmd == SYNCML_CMD_PUT) { // Respond only if Put ... xmlAddChild(state->outBody, status); state->nocmds++; } else xmlFreeNodeList(status); // ... not if Results items = items->next; } syncml_free_cmd(cmd); } else if (!strcmp(body->name, "Get")) { syncml_cmd *cmd = syncml_parse_cmd(state, doc, body->children); xmlNodePtr status; GList *items = cmd->items; cmd->cmd = syncml_string_cmd((char*)body->name); if (items && items->data) { syncml_item *item = items->data; if (item->targetURI && !strncmp(item->targetURI, "./devinf", 8)) { // Return our devinfo status = syncml_build_status(state, cmd, SYNCML_STATUS_OK); xmlAddChild(state->outBody, status); state->nocmds++; status = xmlNewNode(NULL, "Results"); syncml_build_devinfput(state, status, cmd); xmlAddChild(state->outBody, status); state->nocmds++; } else { status = syncml_build_status(state, cmd, SYNCML_STATUS_NOTFOUND); xmlAddChild(state->outBody, status); state->nocmds++; } } syncml_free_cmd(cmd); } else { // Generic command if (strcmp(body->name, "text")) { // Only commands, not text syncml_cmd *cmd = syncml_parse_cmd(state, doc, body->children); xmlNodePtr status; cmd->cmd = syncml_string_cmd((char*)body->name); status = syncml_build_status(state, cmd, SYNCML_STATUS_NOTIMPLEMENTED); xmlAddChild(state->outBody, status); state->nocmds++; syncml_free_cmd(cmd); } } body = body->next; } } void syncml_parse(syncml_state *state, xmlDocPtr doc, xmlNodePtr node) { xmlNodePtr syncml; while (node && strcmp(node->name, "SyncML")) node = node->next; if (!node) return; syncml = node->children; while (syncml) { if (!strcmp(syncml->name, "SyncHdr")) { syncml_parse_synchdr(state, doc, syncml->children); } if (!strcmp(syncml->name, "SyncBody")) { syncml_parse_syncbody(state, doc, syncml->children); } syncml = syncml->next; } } syncml_db_pair* syncml_db_pair_new(char *localdb, char *remotedb, char* lastanchor) { syncml_db_pair* pair = g_malloc0(sizeof(syncml_db_pair)); if (localdb) pair->myDB = g_strdup(localdb); if (remotedb) pair->otherDB = g_strdup(remotedb); if (lastanchor) pair->lastanchor = g_strdup(lastanchor); return(pair); } syncml_datastore* syncml_copy_datastore(syncml_datastore *orig) { syncml_datastore *store; if (!orig) return(NULL); store = g_malloc0(sizeof(syncml_datastore)); if (orig->sourceref) store->sourceref = g_strdup(orig->sourceref); store->tx = g_list_copy(orig->tx); store->rx = g_list_copy(orig->rx); store->txpref = orig->txpref; store->rxpref = orig->rxpref; return(store); } void syncml_free_datastore(syncml_datastore *store) { if (!store) return; SYNCML_FREE_STRING(store->sourceref); g_list_free(store->tx); g_list_free(store->rx); g_free(store); } syncml_devinfo* syncml_copy_devinfo(syncml_devinfo *orig) { GList *stores; syncml_devinfo *info; if (!orig) return(NULL); info = g_malloc0(sizeof(syncml_devinfo)); if (orig->manufacturer) info->manufacturer = g_strdup(orig->manufacturer); if (orig->model) info->model = g_strdup(orig->model); if (orig->devID) info->devID = g_strdup(orig->devID); stores = orig->datastores; while (stores) { syncml_datastore *store = stores->data; info->datastores = g_list_append(info->datastores, syncml_copy_datastore(store)); stores = stores->next; } return(info); } void syncml_free_devinfo(syncml_devinfo *info) { GList *stores; if (!info) return; SYNCML_FREE_STRING(info->manufacturer); SYNCML_FREE_STRING(info->model); SYNCML_FREE_STRING(info->devID); stores = info->datastores; while (stores) { syncml_datastore *store = stores->data; syncml_free_datastore(store); stores = g_list_remove(stores, stores->data); } g_free(info); } void syncml_free_dbpair(syncml_db_pair *pair) { if (pair->myDB) g_free(pair->myDB); if (pair->otherDB) g_free(pair->otherDB); SYNCML_FREE_STRING(pair->lastanchor); SYNCML_FREE_STRING(pair->nextanchor); g_free(pair); } void syncml_free_meta(syncml_meta *meta) { if (!meta) return; SYNCML_FREE_STRING(meta->lastanchor); SYNCML_FREE_STRING(meta->nextanchor); SYNCML_FREE_STRING(meta->type); g_free(meta); } void syncml_free_item(syncml_item *item) { if (!item) return; SYNCML_FREE_STRING(item->targetURI); SYNCML_FREE_STRING(item->sourceURI); syncml_free_meta(item->meta); SYNCML_FREE_STRING(item->data); g_free(item); } void syncml_free_cmd(syncml_cmd *cmd) { if (!cmd) return; SYNCML_FREE_STRING(cmd->cmdID); SYNCML_FREE_STRING(cmd->data); SYNCML_FREE_STRING(cmd->targetURI); SYNCML_FREE_STRING(cmd->sourceURI); syncml_free_meta(cmd->meta); while (cmd->items) { syncml_free_item(cmd->items->data); cmd->items = g_list_remove(cmd->items, cmd->items->data); } while (cmd->mapitems) { syncml_free_item(cmd->mapitems->data); cmd->mapitems = g_list_remove(cmd->mapitems, cmd->mapitems->data); } g_free(cmd); } void syncml_free_chal(syncml_chal *chal) { if (!chal) return; SYNCML_FREE_STRING(chal->nextnonce); g_free(chal); } void syncml_free_status(syncml_status *status) { if (!status) return; SYNCML_FREE_STRING(status->cmdref); SYNCML_FREE_STRING(status->msgref); SYNCML_FREE_STRING(status->sourceref); SYNCML_FREE_STRING(status->targetref); syncml_free_meta(status->meta); syncml_free_chal(status->chal); while (status->items) { syncml_free_item(status->items->data); status->items = g_list_remove(status->items, status->items->data); } g_free(status); } void syncml_free_cmds(GList **cmds) { while (*cmds) { syncml_free_cmd((*cmds)->data); *cmds = g_list_remove(*cmds, (*cmds)->data); } } void syncml_free_state(syncml_state* state) { if (state->otherURI) g_free(state->otherURI); if (state->myURI) g_free(state->myURI); SYNCML_FREE_STRING(state->devID); SYNCML_FREE_STRING(state->mynextnonce); SYNCML_FREE_STRING(state->othernextnonce); while (state->dbanchors) { syncml_db_anchors *anch = state->dbanchors->data; if (anch) { SYNCML_FREE_STRING(anch->db); SYNCML_FREE_STRING(anch->mylast); SYNCML_FREE_STRING(anch->otherlast); g_free(anch); } SYNCML_FREE_STRING(state->statefilename); state->dbanchors = g_list_remove(state->dbanchors, state->dbanchors->data); } syncml_free_devinfo(state->otherdevinfo); SYNCML_FREE_STRING(state->sessionidcookie); if (state->user) g_free(state->user); if (state->passwd) g_free(state->passwd); while (state->db_pairs) { syncml_free_dbpair(state->db_pairs->data); state->db_pairs = g_list_remove(state->db_pairs, state->db_pairs->data); } syncml_free_cmds(&(state->in_cmds)); syncml_free_cmds(&(state->obj_cmds)); syncml_free_cmds(&(state->map_cmds)); if (state->outCmds) xmlFreeNodeList(state->outCmds); while (state->engine_cmds) { g_free(state->engine_cmds->data); state->engine_cmds = g_list_remove(state->engine_cmds, state->engine_cmds->data); } g_free(state); } void syncml_reset_state(syncml_state *state) { int t; if (state->isserver) state->authok = FALSE; SYNCML_FREE_STRING(state->sessionidcookie); state->myauthok = FALSE; state->nocmds = 0; state->respwanted = FALSE; state->inited = FALSE; state->finalreceived = TRUE; state->disconnect = FALSE; state->chalsent = FALSE; state->mapsent = FALSE; state->waitforcmd = SYNCML_ENGINE_CMD_NONE; state->moresynccmds = FALSE; state->sendfinal = TRUE; state->msgid = 0; state->credsent = 0; syncml_free_devinfo(state->otherdevinfo); state->otherdevinfo = NULL; // Reset all db pairs for (t = 0; t < g_list_length(state->db_pairs); t++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, t); pair->slowsync = FALSE; if (state->isserver) pair->dosynchronize = FALSE; else pair->dosynchronize = TRUE; } dd(printf("SyncML: Resetting state.\n")); } void syncml_disconnected(syncml_state *state, syncml_disconnect_reason reason) { // The connection was broken. dd(printf("SyncML: Got disconnection, reasaon %d.\n", reason)); if (state->respwanted) { switch (reason) { case SYNCML_DISCONNECT_TIMEOUT: // Something went wrong in communication syncml_error(state, state->userdata, SYNCML_ERROR_TIMEOUT); syncml_reset_state(state); break; case SYNCML_DISCONNECT_CLOSED: if (state->credsent > 1 && !state->myauthok) { // Other end disconnected, probably auth failed syncml_error(state, state->userdata, SYNCML_ERROR_MYAUTHFAILED); syncml_reset_state(state); } else if (!state->isserver) { // If I am a client and the other end disconnected while I was // waiting for an answer, assume everything is reset. syncml_error(state, state->userdata, SYNCML_ERROR_CONNECTIONFAILED); syncml_reset_state(state); } break; case SYNCML_DISCONNECT_CONNECTIONFAILED: syncml_error(state, state->userdata, SYNCML_ERROR_CONNECTIONFAILED); syncml_reset_state(state); break; /* SYNCML_DISCONNECT_DISCONNECT */ default: break; } } else { if (!state->isserver) syncml_reset_state(state); } } void syncml_parse_msg(syncml_state *state, char *msg, int len) { xmlDocPtr doc = NULL; xmlNodePtr node = NULL; state->respwanted = FALSE; state->sendfinal = TRUE; state->nocmds = 0; state->cmdid = 1; state->msgid++; if (state->outDoc) xmlFreeDoc(state->outDoc); if (state->outSyncML) xmlFreeNodeList(state->outSyncML); if (state->outBody) xmlFreeNodeList(state->outBody); if (state->finalreceived) { state->finalreceived = FALSE; syncml_free_cmds(&(state->in_cmds)); syncml_free_cmds(&(state->obj_cmds)); syncml_free_cmds(&(state->map_cmds)); } state->outDoc = xmlNewDoc("1.0"); state->outDoc->encoding = xmlStrdup("UTF-8"); state->outSyncML = xmlNewNode(NULL, "SyncML"); state->outBody = xmlNewNode(NULL, "SyncBody"); if (msg && len) { #if SYNCML_DEBUG printf("SyncML: %s: Got SyncML msg:\n%s\n*********\n", state->isserver?"Server":"Client",msg); #endif xmlPedanticParserDefault(0); doc = xmlRecoverMemory(msg, len); // Parse even if "broken" if (doc) { if ((node = xmlDocGetRootElement(doc))) syncml_parse(state, doc, node); xmlFreeDoc(doc); } } } // Add a sync command to the message void syncml_add_sync(syncml_state *state) { int n; state->moresynccmds = FALSE; for (n = 0; n < g_list_length(state->db_pairs); n++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, n); if (pair->dosynchronize) { if (!syncml_build_sync(state, pair)) { state->sendfinal = FALSE; state->moresynccmds = TRUE; } state->nocmds++; state->respwanted = TRUE; } } if (!state->moresynccmds) { sync_free_changes(state->changelist); state->changelist = NULL; } } // Add an init alert to the message void syncml_add_init(syncml_state *state, syncml_alert_code code) { int n; xmlNodePtr node; for (n = 0; n < g_list_length(state->db_pairs); n++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, n); if (pair->dosynchronize || code == ALERT_TWOWAYBYSERVER) { if (code == ALERT_TWOWAYBYSERVER) node = syncml_build_alert(state, pair, code); else // Override code if we need slowsync node = syncml_build_alert(state, pair, pair->slowsync?ALERT_SLOWSYNC:code); xmlAddChild(state->outBody, node); xmlAddChild(state->outCmds, xmlCopyNodeList(node)); state->initalertsent = TRUE; state->nocmds++; state->respwanted = TRUE; } } // Add devinfo if (code != ALERT_TWOWAYBYSERVER) { node = xmlNewNode(NULL, "Put"); syncml_build_devinfput(state, node, NULL); xmlAddChild(state->outBody, node); state->nocmds++; xmlAddChild(state->outCmds, xmlCopyNodeList(node)); // Request devinfo node = syncml_build_devinfget(state); xmlAddChild(state->outBody, node); state->nocmds++; state->respwanted = TRUE; xmlAddChild(state->outCmds, xmlCopyNodeList(node)); } } // Add map's to the message void syncml_add_map(syncml_state *state, GList *results) { int n; for (n = 0; n < g_list_length(state->db_pairs); n++) { xmlNodePtr map = NULL; syncml_db_pair *pair = g_list_nth_data(state->db_pairs, n); map = syncml_build_map(state, pair, results); if (map) { xmlAddChild(state->outBody, map); xmlAddChild(state->outCmds, xmlCopyNodeList(map)); state->nocmds++; state->respwanted = TRUE; } } } // Return the approximate message transport size in bytes. Does // not include the size of the header, since rebuilding every time may // cause errors. int syncml_get_msg_size(syncml_state *state) { xmlDocPtr doc; xmlNodePtr syncml; xmlChar* xml; int len = 0; int size = 0; doc = xmlNewDoc("1.0"); doc->encoding = xmlStrdup("UTF-8"); syncml = xmlNewNode(NULL, "SyncML"); xmlAddChild(syncml, xmlCopyNode(state->outBody, 1)); xmlDocSetRootElement(doc, syncml); // We have a message to return if (state->syncmlversion==SYNCML_VER_11) xmlCreateIntSubset(doc, "SyncML", "-//SYNCML//DTD SyncML 1.1//EN", "http://www.syncml.org/docs/syncml_represent_v11_20020213.dtd"); else xmlCreateIntSubset(doc, "SyncML", "-//SYNCML//DTD SyncML 1.0//EN", "http://www.syncml.org/docs/syncml_represent_v10_20001207.dtd"); xmlDocDumpMemory(doc, &xml, &len); size = syncml_transport_msg_size(state, xml, len); free(xml); xmlFreeDoc(doc); return(size); } void syncml_one_sync_done(syncml_state *state) { // One synchronization round done, but don't hang up int t; // Reset slow sync status for (t = 0; t < g_list_length(state->db_pairs); t++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, t); pair->slowsync = FALSE; } } // Build the next SyncML message. Return zero if no message is needed. char* syncml_action(syncml_state *state) { xmlChar *xml = NULL; char *msg = NULL; int len; int n; gboolean morecmds = TRUE; dd(printf("SyncML: Action: %d %d %d %d\n", state->finalreceived, state->resendpkg, state->syncreceived, state->moresynccmds)); if (state->disconnect) { dd(printf("SyncML: I'm disconnecting!\n")); syncml_conn_disconnect(state, FALSE); if (state->isserver) syncml_error(state, state->userdata, SYNCML_ERROR_OTHERAUTHFAILED); else syncml_error(state, state->userdata, SYNCML_ERROR_MYAUTHFAILED); return(NULL); } if (state->outHdr) xmlFreeNodeList(state->outHdr); if (!state->outDoc) { state->outDoc = xmlNewDoc("1.0"); state->outDoc->encoding = xmlStrdup("UTF-8"); } if (!state->outSyncML) { state->outSyncML = xmlNewNode(NULL, "SyncML"); } if (!state->outBody) state->outBody = xmlNewNode(NULL, "SyncBody"); // List of only commands, for resending if (state->oldOutCmds) xmlFreeNodeList(state->oldOutCmds); state->oldOutCmds = state->outCmds; state->outCmds = xmlNewNode(NULL, "SyncBody"); if (state->syncstatusreceived) { if (state->finalreceived) { syncml_changes_results_received(state, state->userdata, state->changeresults); // Assume the above frees the list. state->changeresults = NULL; if (!state->isserver) // Wait for MAP command to be sent state->waitforcmd = SYNCML_ENGINE_CMD_MAP; } state->task = SYNCML_ENGINE_CMD_NONE; state->syncstatusreceived = FALSE; } if (state->syncreceived) { int t; sync_object_type newdbs = 0; for (t = 0; t < g_list_length(state->db_pairs); t++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, t); if (pair->slowsync) { newdbs |= pair->object_type; } } syncml_changes_received(state, state->userdata, state->changelist, state->finalreceived, newdbs); if (!state->finalreceived) state->sendfinal = FALSE; // Assume the above frees the list. state->changelist = NULL; state->syncreceived = FALSE; if (state->finalreceived && state->isserver) state->waitforcmd = SYNCML_ENGINE_CMD_SYNC; else state->waitforcmd = SYNCML_ENGINE_CMD_SYNC_STATUS; } if (state->mapstatusreceived) { syncml_one_sync_done(state); syncml_sync_done_received(state, state->userdata); state->mapstatusreceived = FALSE; } if (state->devinfreceived) { syncml_devinfo *tmp = syncml_copy_devinfo(state->otherdevinfo); syncml_devinfo_received(state, state->userdata, tmp); state->devinfreceived = FALSE; } if (state->alertreceived) { if (state->isserver) { // If client sent us ALERT, answer with the same for (n = 0; n < g_list_length(state->db_pairs); n++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, n); if (pair->dosynchronize) { xmlNodePtr node = syncml_build_alert(state, pair, pair->slowsync?ALERT_SLOWSYNC: ALERT_TWOWAY); xmlAddChild(state->outBody, node); xmlAddChild(state->outCmds, xmlCopyNodeList(node)); state->nocmds++; } } state->respwanted = TRUE; } state->alertreceived = FALSE; } if (state->moresynccmds) syncml_add_sync(state); // If we have more sync commands to send if (state->syncbyserverreceived) { // Server has initiated sync if (!state->isserver) syncml_sync_serverinit_received(state, state->userdata); state->syncbyserverreceived = FALSE; } if (state->initalertsuccess) { // Our init command succeeded syncml_save_engine_state(state); // Save anchors 'n' stuff if (state->task == SYNCML_ENGINE_CMD_SYNC) { int t; sync_object_type newdbs = 0; for (t = 0; t < g_list_length(state->db_pairs); t++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, t); if (pair->slowsync) { newdbs |= pair->object_type; } } if (newdbs) {// Slow sync, re-get client changes syncml_reget_changes(state, state->userdata, newdbs); state->waitforcmd = SYNCML_ENGINE_CMD_SYNC; } else syncml_add_sync(state); } state->initalertsuccess = FALSE; state->initalertsent = FALSE; } while (morecmds && state->engine_cmds) { syncml_engine_cmd *cmd; if (state->engine_cmds) { cmd = state->engine_cmds->data; state->engine_cmds = g_list_remove(state->engine_cmds, cmd); state->task = cmd->cmd; if (cmd->cmd == state->waitforcmd) state->waitforcmd = SYNCML_ENGINE_CMD_NONE; switch(cmd->cmd) { case SYNCML_ENGINE_CMD_SYNC: { change_info *info = cmd->data; int n; state->changelist = info->changes; // Check slow sync for (n = 0; n < g_list_length(state->db_pairs); n++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, n); if (pair->object_type & info->newdbs) pair->slowsync = TRUE; } if (!state->inited) { // We need to be inited for synchronization syncml_add_init(state, ALERT_TWOWAY); morecmds = FALSE; } else { // Send sync syncml_add_sync(state); } } break; case SYNCML_ENGINE_CMD_GETDEVINFO: { xmlNodePtr node; node = syncml_build_devinfget(state); xmlAddChild(state->outBody, node); xmlAddChild(state->outCmds, xmlCopyNodeList(node)); state->nocmds++; state->respwanted = TRUE; } break; case SYNCML_ENGINE_CMD_SYNC_SERVERINIT: { sync_object_type newdbs = (sync_object_type) cmd->data; // Check slow sync if (newdbs) { for (n = 0; n < g_list_length(state->db_pairs); n++) { syncml_db_pair *pair = g_list_nth_data(state->db_pairs, n); if (pair->object_type & newdbs) { pair->slowsync = TRUE; SYNCML_FREE_STRING(pair->mylastanchor); SYNCML_FREE_STRING(pair->lastanchor); } } syncml_save_engine_state(state); // Save anchors 'n' stuff } if (state->nocmds == 0 && !state->engine_cmds && !state->respwanted) // Don't send this if we are doing something else (synchronizing) syncml_add_init(state, ALERT_TWOWAYBYSERVER); else syncml_error(state, state->userdata, SYNCML_ERROR_BUSY); } break; case SYNCML_ENGINE_CMD_SYNC_STATUS: { // Sent status to a list of Sync commands GList *objcmds = state->obj_cmds; GList *results = cmd->data; // Pick out the last commands objcmds = g_list_nth(objcmds, g_list_length(objcmds)- g_list_length(results)); while(results && objcmds) { syncml_cmd *cmd = objcmds->data; syncobj_modify_result *result = results->data; xmlNodePtr status = NULL; if (result->result >= 0) { if (cmd->cmd == SYNCML_CMD_REPLACE || cmd->cmd == SYNCML_CMD_ADD) status = syncml_build_status(state, cmd, SYNCML_STATUS_ADDED); else status = syncml_build_status(state, cmd, SYNCML_STATUS_DELETEDWITHOUTARCHIVE); } else status = syncml_build_status(state, cmd, SYNCML_STATUS_SYNCERROR); xmlAddChild(state->outBody, status); xmlAddChild(state->outCmds, xmlCopyNodeList(status)); state->nocmds++; state->respwanted = TRUE; results = results->next; objcmds = objcmds->next; } if (!state->isserver) { // Add map commands if necessary syncml_add_map(state, cmd->data); state->mapsent = TRUE; } sync_free_modify_results(cmd->data); dd(printf("SyncML: Sending sync status.\n")); } break; case SYNCML_ENGINE_CMD_MAP_STATUS: { // Final msg sent in sync if (state->isserver) { GList *mapcmds = state->map_cmds; state->nocmds++; // Just to make sure the status is sent back if (mapcmds) { while(mapcmds) { syncml_cmd *cmd = mapcmds->data; xmlNodePtr node = syncml_build_status(state, cmd, SYNCML_STATUS_OK); xmlAddChild(state->outBody, node); xmlAddChild(state->outCmds, xmlCopyNodeList(node)); state->nocmds++; mapcmds = mapcmds->next; } } syncml_one_sync_done(state); // FIXME: Free state->map_cmds. Is doc still in memory? } } break; default: break; } g_free(cmd); } } if ((state->waitforcmd == SYNCML_ENGINE_CMD_NONE && (state->nocmds > 0)) || state->resendpkg) { if (!state->finalreceived) { xmlNodePtr node; node = syncml_build_alert(state, NULL, ALERT_NEXTMSG); xmlAddChild(state->outBody, node); } if (state->sendfinal) xmlNewChild(state->outBody, NULL, "Final", NULL); state->outHdr = syncml_build_header(state); xmlNewProp(state->outSyncML, "xmlns", state->syncmlversion==SYNCML_VER_11?"SYNCML:SYNCML1.1": "SYNCML:SYNCML1.0"); xmlAddChild(state->outSyncML, state->outHdr); if (state->resendpkg) { while (state->oldOutCmds && state->oldOutCmds->children) { xmlNodePtr node = state->oldOutCmds->children; xmlUnlinkNode(node); xmlAddChild(state->outBody, node); } } xmlAddChild(state->outSyncML, state->outBody); xmlDocSetRootElement(state->outDoc, state->outSyncML); state->resendpkg = FALSE; state->outHdr = NULL; state->outBody = NULL; state->outSyncML = NULL; // We have a message to return if (state->syncmlversion==SYNCML_VER_11) xmlCreateIntSubset(state->outDoc, "SyncML", "-//SYNCML//DTD SyncML 1.1//EN", "http://www.syncml.org/docs/syncml_represent_v11_20020213.dtd"); else xmlCreateIntSubset(state->outDoc, "SyncML", "-//SYNCML//DTD SyncML 1.0//EN", "http://www.syncml.org/docs/syncml_represent_v10_20001207.dtd"); xmlDocDumpMemory(state->outDoc, &xml, &len); msg = g_strdup(xml); free(xml); xmlFreeDoc(state->outDoc); state->outDoc = NULL; state->nocmds = 0; if (!state->resendpkg) { if (state->respwanted) { state->connectedtimeout = time(NULL)+SYNCML_CMD_CONNECTED_TIMEOUT; state->unconnectedtimeout = time(NULL)+SYNCML_CMD_UNCONNECTED_TIMEOUT; } else { state->connectedtimeout = 0; state->unconnectedtimeout = 0; } } return(msg); } return(NULL); }