/* 
   MultiSync SyncML plugin - Implementation of SyncML 1.1 and 1.0
   Copyright (C) 2002-2003 Bo Lincoln <lincoln@lysator.liu.se>

   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 <lincoln@lysator.liu.se>
   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 <unistd.h>
#include <string.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <multisync.h>
#include <openssl/md5.h>
#include <time.h>
#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);
}


