/* 
   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_cmd.c,v 1.38.2.1 2004/09/09 17:13:49 azrael2 Exp $
 */

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <glib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <pthread.h>

#include <multisync.h>
#include "config.h"
#include "syncml_cmd.h"
#include "syncml_engine.h"
#include "syncml_plugin.h"
#include "syncml_ssl.h"

#if USE_LIBWBXML
#include <wbxml.h>
#endif

extern gboolean multisync_debug;

// Like read() but with a timeout of timeout seconds
int syncml_conn_read(int fd, char *data, int len, int timeout) {
  struct timeval tv;
  fd_set rset, wset, xset;
  int lenleft = len;

  if (fd < 0)
    return(0);
  while (lenleft > 0) {
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_ZERO(&xset);
    FD_SET(fd, &rset);
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
    if (select(fd+1,  &rset, &wset, &xset, &tv)) {
      int ret;
      ret = read(fd, data+len-lenleft, lenleft); 
      if (ret > 0) {
	lenleft -= ret;
      } else {
	return(len-lenleft);
      }
    } else {
      return(-1);
    }
  }
  return(len);
}

// Like write() but with a timeout of timeout seconds
int syncml_conn_write(int fd, char *data, int len, int timeout) {
  struct timeval tv;
  fd_set rset, wset, xset;
  int lenleft = len;

  if (fd < 0)
    return(0);
  while (lenleft > 0) {
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_ZERO(&xset);
    FD_SET(fd, &wset);
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
    if (select(fd+1,  &rset, &wset, &xset, &tv)) {
      int ret;
      ret = write(fd, data+len-lenleft, lenleft); 
      if (ret > 0) {
	lenleft -= ret;
      } else {
	return(ret);
      }
    } else {
      return(-1);
    }
  }
  return(len);
}

// Receive one line, with timeout
int syncml_conn_recv_line(syncml_state *state, char *line, int len) {
  int i = 0;
  if (len == 0)
    return(0);
  if (state->connfd >= 0) {
    int res = -1;
    i = -1;
    do {
      i++;
      if (state->conntype == SYNCML_CONN_TYPE_HTTP)
	res = syncml_conn_read(state->connfd, line+i, 1, 
			       SYNCML_CONN_TIMEOUT);
      if (state->conntype == SYNCML_CONN_TYPE_HTTPS)
	res = syncml_ssl_read(state, line+i, 1, 
			       SYNCML_CONN_TIMEOUT);
    } while (res == 1 && i < len && line[i] != '\n');
    if (line[i] != '\n' || res <= 0) {
      // Timeout or close
      dd(printf("SyncML:  Disconnection, length %d.\n", res));
      syncml_conn_disconnect(state, res==-1?SYNCML_DISCONNECT_TIMEOUT:
			     SYNCML_DISCONNECT_CLOSED);
      return(-1);
    }
    line[i] = 0;
    if (i > 0 && line[i-1] == '\r')
      line[--i] = 0;
  } else
    return(-1);
  return(i);
}

int syncml_conn_recv(syncml_state *state, char *data, int len) {
  if (len == 0)
    return(0);
  if (state->connfd >= 0) {
    int res = -1;
    if (state->conntype == SYNCML_CONN_TYPE_HTTP)
      res = syncml_conn_read(state->connfd, data, len, SYNCML_CONN_TIMEOUT);
    if (state->conntype == SYNCML_CONN_TYPE_HTTPS)
      res = syncml_ssl_read(state, data, len, SYNCML_CONN_TIMEOUT);
    if (res < len) {
      syncml_conn_disconnect(state, res==-1?SYNCML_DISCONNECT_TIMEOUT:
			     SYNCML_DISCONNECT_CLOSED);
      return(-1);
    }
  } else
    return(-1);
  dd(printf("%s\n", data));
  return(len);
}

int syncml_conn_send(syncml_state *state, char *data, int len) {
  int res = 0;
  if (len == 0)
    return(0);
  if (state->connfd >= 0) {
    int res = -1;
    if (state->conntype == SYNCML_CONN_TYPE_HTTP)
      res = syncml_conn_write(state->connfd, data, len, SYNCML_CONN_TIMEOUT);
    if (state->conntype == SYNCML_CONN_TYPE_HTTPS)
      res = syncml_ssl_write(state, data, len, SYNCML_CONN_TIMEOUT);
    if (res < len) {
      syncml_conn_disconnect(state, res==-1?SYNCML_DISCONNECT_TIMEOUT:
			     SYNCML_DISCONNECT_CLOSED);
      return(-1);
    }
  } else
    return(-1);
  dd(printf("%s\n", data));
  return(len);
}

// Receive all data on a stream until EOF. Return as g_malloc'ed block.
int syncml_conn_recv_all(syncml_state *state, char **data) {
  int len = 0;
  *data = NULL;
  if (state->connfd >= 0) {
    int blocklen = 1024, offset=0, res;
    *data = g_malloc(blocklen);
    while((res = syncml_conn_read(state->connfd, *data+offset, 
				  blocklen-offset, 
				  SYNCML_CONN_TIMEOUT)) == blocklen-offset) {
      char *tmp = *data;
      offset = blocklen;
      *data = g_malloc(blocklen*2);
      memcpy(*data, tmp, blocklen);
      blocklen *= 2;
      g_free(tmp);
    }
    len = offset;
    if (res >= 0)
      len += res;
    syncml_conn_disconnect(state, SYNCML_DISCONNECT_DISCONNECT);
  }
  return(len);
}

void syncml_conn_disconnect(syncml_state *state, 
			    syncml_disconnect_reason reason) {
  if (state->conntype == SYNCML_CONN_TYPE_HTTPS)
    syncml_ssl_disconnect(state);
  if (state->connfd >= 0)
    close(state->connfd);
  state->connfd = -1;
  state->tcpreuseconnection = FALSE;
  if (reason != SYNCML_DISCONNECT_DISCONNECT) {
    dd(printf("SyncML:  SyncML disconnected.\n"));
    syncml_disconnected(state, reason);
  }
}



char *syncml_http_rsp_to_string(syncml_http_code code) {
  switch(code) {
  case SYNCML_HTTP_CONTINUE:
    return("Continue");
  case SYNCML_HTTP_OK:
    return("OK");
  case SYNCML_HTTP_CREATED:
    return("Created");
  case SYNCML_HTTP_ACCEPTED:
    return("Accepted");
  case SYNCML_HTTP_NOTFOUND:
    return("Not found");
  }
  return(NULL);
}

int syncml_http_send_rsp(syncml_state *state, char *data, int len, int code,
			 char *contenttype) {
  char buf[1024];
  char datestr[1024], *end;
  char *sendbuf;
  time_t currtime;
  // Send HTTP response
  if (state->connfd < 0)
    return -1;
  time(&currtime);
  ctime_r(&currtime, datestr);
  end = strstr(datestr, "\n");
  if (end)
    end[0] = 0;


  snprintf(buf, 1023, "HTTP/1.1 %d %s\r\n"
	   "Date: %s\r\n"
	   "Expires: %s\r\n"
	   "Content-Length: %d\r\n" 
	   "Content-Type: %s\r\n"
	   "Accept-Charset: UTF-8\r\n"
	   "Accept: application/vnd.syncml+xml, application/vnd.syncml+wbxml\r\n"
	   "Cache-Control: no-store\r\n"
	   "Server: MultiSync Plugin\r\n"
	   "\r\n", 
	   code, syncml_http_rsp_to_string(code), datestr, datestr, len,
	   contenttype);
  sendbuf = g_malloc(strlen(buf)+len);
  memcpy(sendbuf, buf, strlen(buf));
  memcpy(sendbuf+strlen(buf), data, len);
  if (syncml_conn_send(state, sendbuf, len+strlen(buf)) == len+strlen(buf)) {
    g_free(sendbuf);
    return(0);
  }
  g_free(sendbuf);
  /*if (syncml_conn_send(state, buf, strlen(buf)) == strlen(buf)) {
    if (data) {
      if (syncml_conn_send(state, data, len) == len)
	return(0);
    } else
      return(0);
      }*/
  return(-1);
}

int syncml_http_send_cont(syncml_state *state) {
  char buf[256];
  // Send HTTP response
  if (state->connfd < 0)
    return -1;
  
  snprintf(buf, 255, "HTTP/1.1 100 Continue\r\n\r\n");
  if (syncml_conn_send(state, buf, strlen(buf)) == strlen(buf)) {
    return(0);
  }
  return(-1);
}


int syncml_http_send_req(syncml_state *state, char *data, int len, char *cmd,
			 char *contenttype) {
  char buf[1024];
  char *file;
  char *hostname, *host;
  int port=80,t;

  // Send HTTP response
  if (state->connfd < 0) {
    if (!syncml_conn_connect(state))
      return -1;
  }

  file = syncml_get_URI_file(state->otherURI);
  hostname = syncml_get_URI_host(state->otherURI);
  port = syncml_get_URI_port(state->otherURI);
  host = g_strdup_printf("%s:%d", hostname, port);
  g_free(hostname);
  snprintf(buf, 1023, 
	   "%s %s HTTP/1.1\r\n"
	   "Content-Length: %d\r\n" 
	   "Content-Type: %s\r\n"
	   "Accept: application/vnd.syncml+xml, application/vnd.syncml+wbxml\r\n"
	   "Host: %s\r\n"
	   "Accept-Charset: UTF-8\r\n"
	   "Cache-Control: no-store\r\n"
	   "\r\n",
	   cmd, file, len, contenttype, host);
  g_free(file);
  g_free(host);
  /*printf("SyncML:  Sending:\n");
  for (t = 0; t < len; t++) {
    if (!(t & 0x3f))
      printf("\n");
    if (data[t] >= ' ' && data[t] <= 'z')
      printf("%c", data[t]);
    else
      printf(" %d ", data[t]);
      }*/
  if (syncml_conn_send(state, buf, strlen(buf)) == strlen(buf)) {
    if (syncml_conn_send(state, data, len) == len)
      return(0);
  }
  return(-1);
}

// Possibly convert an outgoing message to WBXML.
// WILL free the string in if converted, and return a new string
char *syncml_xml_out_convert(syncml_state *state, char *in, int *outlen) {
  char *ret = NULL;
#if USE_LIBWBXML
  if (in && state->wbxml) {
    WB_UTINY *wbxml = NULL, *xml;
    WB_LONG wbxml_len = 0;
    WBXMLError res = 0;
	
	WBXMLConvXML2WBXMLParams params = {WBXML_VERSION_11, TRUE, TRUE};
	
	syncml_connection *syncmlconn = state->userdata;
	if (syncmlconn->disablestrtbl) {
		params.use_strtbl = FALSE;
	}
	
    xml = in;
    if ((res = wbxml_conv_xml2wbxml(xml, &wbxml, &wbxml_len, &params)) == 
	WBXML_OK) {
      ret = g_malloc(wbxml_len);
      memcpy(ret, wbxml, wbxml_len);
      if (outlen)
	*outlen = wbxml_len;
      wbxml_free(wbxml);
      g_free(in);
      return(ret);
    }
    dd(printf("SyncML:  WBXML convert error: %d\n", res));
  }
#endif
  if (outlen) {
    if (in)
      *outlen = strlen(in);
    else
      *outlen = 0;
  }
  return(in);
}

void syncml_print_binary(unsigned char *data, int len) {
  int t;
  for (t = 0; t < len; t++) {
    if (data[t] >= ' ' && data[t] <= 'z')
      dd(printf("%c  ", data[t]));
    else
      dd(printf("%02x ", data[t]));
  }
  dd(printf("\n"));
}

int syncml_transport_msg_size(syncml_state *state,
			      unsigned char* data, int len) {
  char *copy = g_strdup(data);
  int size = 0;
  copy = syncml_xml_out_convert(state, copy, &size);
  g_free(copy);
  return(size);
}

void syncml_http_recv(syncml_state *state) {
  // We have connection data
  int res = 0;
  char line[1024];
  dd(printf("SyncML: We got some request data.\n"));
  if ((res = syncml_conn_recv_line(state, line, 1024)) >= 0) {
    char httpcmd[32] = "";
    char file[256];
    char httpver[32];
    int rspcode = 0;
    gboolean httpreq=FALSE;
    gboolean httprsp=FALSE;
    
    // Parse HTTP line
    dd(printf("SyncML:  Line: %s\n", line));
    if (sscanf(line, "%31s %255s HTTP/%31s", httpcmd, file, httpver) == 3) {
      if (!strcmp(httpcmd, "POST")) {
	httpreq=TRUE;
	state->lastreq = TRUE;
      } else {
	char *rsp = "No such file or directory.";
	syncml_http_send_rsp(state, rsp, strlen(rsp), 404, "text/plain");
	return;
      }
    }
    if (sscanf(line, "HTTP/%31s %d", httpver, &rspcode) == 2) {
      if (rspcode == SYNCML_HTTP_OK ||
	  rspcode == SYNCML_HTTP_ACCEPTED) {
	httprsp=TRUE;
	state->lastreq = FALSE;
      }
    }
    if (httpreq || httprsp) {
      int contentlength = -1;
      int res = 0;
      int expect = 0;
      char *data = NULL;
      char contenttype[1024] = "", transferencoding[256] = "";
      char cookie[1024] = "";
      gboolean headers = TRUE;
      // Get headers
      while (headers) {
	if (syncml_conn_recv_line(state, line, 1024) >= 0) {
	  char key[256], value[1024];
	  dd(printf("SyncML:  Line: %s\n", line));
	  if (strlen(line) > 0) {
	    if (sscanf(line, "%255[^:]: %1023[^\n]", key, value) == 2) {
	      if (!g_strcasecmp(key, "content-length"))
		sscanf(value, "%d", &contentlength);
	      if (!g_strcasecmp(key, "content-type"))
		sscanf(value, "%1023[^;]", &contenttype);
	      if (!g_strcasecmp(key, "expect"))
		sscanf(value, "%d", &expect);
	      if (!g_strcasecmp(key, "transfer-encoding"))
		sscanf(value, "%255[^;]", &transferencoding);
	      if (!g_strcasecmp(key, "cookie")) {
		if (sscanf(value, "%1023[^\n]", &cookie) >= 1) {
		  char *pos = cookie;
		  while (pos) {
		    char name[256]="",value[256];
		    sscanf(pos, "%255[^=;]=%255[^;]", name, value);
		    if (!strcmp(name, "syncml-session")) {
		      if (!state->tcpreuseconnection && 
			  state->sessionidcookie &&
			  !strcmp(state->sessionidcookie, value))
			// This is a continued session
			dd(printf("SyncML:  Session cookie OK.\n"));
			state->tcpreuseconnection = TRUE;
		    }
		    pos = strstr(pos,";");
		    while (pos && *pos && (*pos == ' ' || *pos == ';'))
		      pos++;
		  } 
		}
	      }
	    }
	  } else
	    headers = FALSE;
	} else
	  headers = FALSE;
      }
      if (httpreq) {
	// Parse the HTTP variables in file URI
	char *pos = strstr(file, "?");
	if (pos)
	  pos++;
	while (pos) {
	  char key[256], value[256];
	  if (sscanf(pos, "%255[^=&]=%255[^&]", key, value) == 2) {
	    if (!strcmp(key, "sessionid") && 
		state->sessionidcookie &&
		!strcmp(value, state->sessionidcookie)) {
	      dd(printf("SyncML:  Session ID OK.\n"));
	      state->tcpreuseconnection = TRUE;
	    }
	  }
	  pos = strstr(file, "&");
	  if (pos)
	    pos++;
	}
      }

      if (!state->tcpreuseconnection)
	syncml_reset_state(state); // This is a new, unknown session
      state->tcpreuseconnection = TRUE;
      if (expect == 100)
	syncml_http_send_cont(state);
      if (contentlength < 0) {
	char *tmp;
	if (!g_strcasecmp(transferencoding, "chunked")) {
	  int len = 0, totlen = 0;
	  dd(printf("SyncML:  Reading chunked data.\n"));
	  do {
	    if ((res = syncml_conn_recv_line(state, line, 1024)) >= 0) {
	      sscanf(line, "%x", &len);
	    } else
	      len = 0;
	    if (len > 0) {
	      dd(printf("SyncML:  Reading %d bytes.\n", len));
	      tmp = g_malloc(totlen+len);
	      if (totlen > 0) {
		memcpy(tmp, data, totlen);
		g_free(data);
	      }
	      data = tmp;
	      res = syncml_conn_recv(state, data+totlen, len);
	      if (res == len)
		totlen += len;
	    }
	  } while (len > 0);
	  contentlength = totlen;
	} else {
	  res = contentlength = syncml_conn_recv_all(state, &tmp);
	  data = g_malloc(contentlength+1);
	  memcpy(data, tmp, contentlength);
	  g_free(tmp);
	  data[contentlength]=0;
	}
      }
      else {
	data = g_malloc(contentlength+1);
	res = syncml_conn_recv(state, data, contentlength);
	data[contentlength]=0;
      }
      if (res > 0) {
	char *syncmlrsp;
#if USE_LIBWBXML
	if (!strcmp(contenttype, "application/vnd.syncml+wbxml")) {
	  // We got WBXML data, time to convert
	  WB_UTINY *in = data, *xml = NULL;
	  WB_LONG insize = contentlength;
	  WBXMLConvWBXML2XMLParams params = {WBXML_ENCODER_XML_GEN_INDENT,
					WBXML_LANG_UNKNOWN, 2, TRUE };
	  syncml_print_binary(in, insize);
	  if (wbxml_conv_wbxml2xml(in, insize, &xml, &params) == WBXML_OK) {
	    strcpy(contenttype, "application/vnd.syncml+xml");
	    state->wbxml = TRUE;
	    g_free(data);
	    data = g_strdup(xml);
	    wbxml_free(xml);
	    contentlength = strlen(data);
	    dd(printf("SyncML:  Successfully converted from WBXML to XML. Data:\n%s\n", data));
	  }
	}
#endif
	// Send data to SyncML engine
	if (!strcmp(contenttype, "application/vnd.syncml+xml")) {
	  syncml_parse_msg(state, data, contentlength);
	  syncmlrsp = syncml_action(state);
	  if (syncmlrsp) {
	    int len = 0;
	    // Immediate response	    
	    syncmlrsp = syncml_xml_out_convert(state, syncmlrsp, &len);
	    if (httpreq)
	      syncml_http_send_rsp(state, syncmlrsp, len, 
				   SYNCML_HTTP_OK, 
				   state->wbxml?"application/vnd.syncml+wbxml":"application/vnd.syncml+xml");
	    else
	      syncml_http_send_req(state, syncmlrsp, len, "POST",
				   state->wbxml?"application/vnd.syncml+wbxml":"application/vnd.syncml+xml");
	    g_free(syncmlrsp);
	  }
	} else {
	  dd(printf("SyncML:  Got data of type %s, which I cannot parse.\n",
		 contenttype));
	  if (httpreq) {
	    char *rsp = "No such file or directory.";
	    syncml_http_send_rsp(state, rsp, strlen(rsp), SYNCML_HTTP_NOTFOUND,
				 "text/plain");
	  }
	}
      }
      if (data)
	g_free(data);
    }
  }
}

gboolean syncml_conn_connect(syncml_state *state) {
  char *hostname = NULL;
  int hostport = 80;
  if (state->connfd >= 0)
    return(TRUE);
  hostname = syncml_get_URI_host(state->otherURI);
  if (hostname && !strcmp(hostname, "<this computer>")) {
    g_free(hostname);
    hostname = g_strdup("localhost");
  }
  if (!hostname || state->isserver) {
    syncml_conn_disconnect(state, SYNCML_DISCONNECT_CONNECTIONFAILED);
    return(FALSE);
  }
  hostport = syncml_get_URI_port(state->otherURI);
  if ((state->connfd = socket(PF_INET, SOCK_STREAM, 0)) >= 0) {
    struct hostent *hostent;
    struct sockaddr_in servaddr;
    // Look up name IP
    dd(printf("SyncML:  Looking up %s\n", hostname));
    if ((hostent = gethostbyname(hostname))) {
      unsigned char *a;
      servaddr.sin_family = AF_INET;
      servaddr.sin_port = htons(hostport);
      servaddr.sin_addr.s_addr = 
	(*((unsigned int*)hostent->h_addr_list[0]));
      a = (unsigned char*) &servaddr.sin_addr.s_addr;
      dd(printf("SyncML:  Connecting to %d.%d.%d.%d...\n", a[0],a[1],a[2],a[3]));
      if (!connect(state->connfd, (struct sockaddr*) &servaddr, 
		   sizeof(struct sockaddr_in))) {
	char *txt;
	fcntl(state->connfd, F_SETFL, O_NONBLOCK);
	if (state->conntype == SYNCML_CONN_TYPE_HTTPS)
	  syncml_ssl_client_connect(state); // Prepare for SSL connection
	txt = g_strdup_printf("Connected to %s.", hostname);
	syncml_info(state, state->userdata, txt);
	g_free(txt);
	return(TRUE);
      }
    }
    close(state->connfd);
  }
  state->connfd = -1;
  syncml_conn_disconnect(state, SYNCML_DISCONNECT_CONNECTIONFAILED);
  return(FALSE);
}

gpointer syncml_main_thread(gpointer statep) {
  syncml_state *state = statep;
  gboolean quit = FALSE;
  while (!quit) {
    int n = 0;
    struct timeval tv;
    time_t timeleft = 0;
    fd_set rset, wset, xset;
    if (state->socketfd > n) n = state->socketfd;
    if (state->readmsg > n) n = state->readmsg;
    if (state->connfd > n) n = state->connfd;
    FD_ZERO(&rset);
    FD_ZERO(&wset);
    FD_ZERO(&xset);
    if (state->socketfd >= 0)
      FD_SET(state->socketfd, &rset);
    FD_SET(state->readmsg, &rset);
    if (state->connfd >= 0) {
      FD_SET(state->connfd, &rset);
      FD_SET(state->connfd, &xset);
    }
    if (state->unconnectedtimeout && state->connfd < 0) {
      timeleft = state->unconnectedtimeout-time(NULL);
      if (timeleft <= 0) timeleft = 1;
      tv.tv_sec = timeleft;
      tv.tv_usec = 0;
    }
    if (state->connectedtimeout && state->connfd >= 0) {
      timeleft = state->connectedtimeout-time(NULL);
      if (timeleft <= 0) timeleft = 1;
      tv.tv_sec = timeleft;
      tv.tv_usec = 0;
    }
    if (select(n+1,  &rset, &wset, &xset, timeleft?&tv:NULL)) {
      if (state->connfd >= 0 && FD_ISSET(state->connfd, &xset)) {
	// Our connection is closed!
	dd(printf("SyncML:  The other end closed the connection.\n"));
	syncml_conn_disconnect(state, SYNCML_DISCONNECT_CLOSED);
      }
      if (state->socketfd >= 0 && FD_ISSET(state->socketfd, &rset)) {
	struct sockaddr_in otheraddr;
	socklen_t addrlen = sizeof(struct sockaddr_in);
	unsigned char *a;
        char *txt;

	state->connfd = accept(state->socketfd, 
				(struct sockaddr*) &otheraddr, 
				&addrlen);
	fcntl(state->connfd, F_SETFL, O_NONBLOCK);
	state->tcpreuseconnection = FALSE;
	a = (unsigned char*) &otheraddr.sin_addr.s_addr;

	dd(printf("SyncML:  Client connected from %d.%d.%d.%d.\n",  a[0],a[1],a[2],a[3]));
	txt = g_strdup_printf("Client connected from %d.%d.%d.%d.",  a[0],a[1],a[2],a[3]);
	syncml_info(state, state->userdata, txt);
	g_free(txt);

	if (state->conntype == SYNCML_CONN_TYPE_HTTPS)
	  syncml_ssl_server_connect(state); // Prepare for SSL connection
	if (!state->connectedtimeout) {
	  // Close if no activity
	  state->connectedtimeout = time(NULL)+SYNCML_CONN_TIMEOUT; 
	}
      }
      if (state->connfd >= 0 && FD_ISSET(state->connfd, &rset)) {
	syncml_http_recv(state);
      }
      if (FD_ISSET(state->readmsg, &rset)) {
	syncml_engine_cmd cmd;
	if (read(state->readmsg, &cmd, sizeof(syncml_engine_cmd)) == 
		 sizeof(syncml_engine_cmd)) {
	  dd(printf("SyncML:  Got engine cmd: %d\n", cmd.cmd));
	  switch (cmd.cmd) {
	  case SYNCML_ENGINE_CMD_QUIT:
	    if (state->connfd >=0)
	      close(state->connfd);
	    if (state->socketfd >=0)
	      close(state->socketfd);
	    close(state->readmsg);
	    syncml_ssl_exit(state);
	    syncml_free_state(state);
	    return(NULL);
	    break;
	  default:
	    syncml_do_cmd(state, &cmd);
	    break;
	  }
	}
      }
    } else {
      // Timeout
      syncml_conn_disconnect(state, SYNCML_DISCONNECT_TIMEOUT);
      state->connectedtimeout = 0;
      state->unconnectedtimeout = 0;
    }
  }
  {
    // Quit OK.
    if (state->connfd >= 0) {
      shutdown(state->connfd, SHUT_RDWR);
      close(state->connfd);
    }
    if (state->socketfd >= 0) { 
      shutdown(state->socketfd, SHUT_RDWR);
      close(state->socketfd);
    }
    if (state->readmsg >= 0)
      close(state->readmsg);
    if (state->writemsg >= 0)
      close(state->writemsg);
  }
  return(NULL);
}

// Add a command to the SyncML engine command list and execute it
// if possible.
void syncml_do_cmd(syncml_state *state, syncml_engine_cmd *cmd) {
  syncml_engine_cmd *cmdcpy = g_malloc(sizeof(syncml_engine_cmd));
  char *syncmlcmd;

  memcpy(cmdcpy, cmd, sizeof(syncml_engine_cmd));
  state->engine_cmds = g_list_append(state->engine_cmds, cmdcpy);
  
  syncmlcmd = syncml_action(state);
  if (syncmlcmd) {
    int len = 0;
    // Immediate response	    
    syncmlcmd = syncml_xml_out_convert(state, syncmlcmd, &len);
    if (state->lastreq)
      syncml_http_send_rsp(state, syncmlcmd, len, 
			   SYNCML_HTTP_OK, 
			   state->wbxml?"application/vnd.syncml+wbxml":"application/vnd.syncml+xml");
    else
      syncml_http_send_req(state, syncmlcmd, len, "POST",
			   state->wbxml?"application/vnd.syncml+wbxml":"application/vnd.syncml+xml");
    g_free(syncmlcmd);
  }
}

syncml_state* syncml_create(gboolean isserver, char* uri, 
			    char *statefilename, gpointer userdata) {
  syncml_state *state;
  int msgpipe[2];
  syncml_db_pair *pair;
  syncml_error_type error = SYNCML_ERROR_UNKNOWN;

  state = g_malloc0(sizeof(syncml_state));
  state->isserver = isserver;
  state->inited = FALSE;
  state->socketfd = -1;
  state->connfd = -1;
  state->syncmlversion = SYNCML_VER_11;
  state->wbxml = FALSE;
  state->usedauth = state->defaultauth = SYNCML_AUTH_MD5;
  state->userdata = userdata;
  state->statefilename = g_strdup(statefilename);
  syncml_reset_state(state); // Reset the sync engine state
  syncml_load_engine_state(state);
  if (!state->devID) {
    int t;
    char nrs[] = "0123456789ABCDEF";
    // Create a "unique" ID for this device
    state->devID = g_malloc0(12+1);
    for (t = 0; t < 12; t++) {
      long r = random();
      state->devID[t] = nrs[r&0xf];
    }
  }

  state->sessid = 1;
  state->msgid = 1;
  state->cmdid = 1;

  if (!state->isserver) {
    state->authok = TRUE; // Server does not need to authenticate itself
    // This means we are vulnerable to man-in-the-middle attack.
    if (uri)
      state->otherURI = g_strdup(uri);
    state->myURI = g_strdup(state->devID);
    state->conntype = syncml_get_URI_proto(state->otherURI);
    if (state->conntype == SYNCML_CONN_TYPE_HTTPS)
      if (!syncml_ssl_init_client(state)) {
	goto err;
      }
  } else {
    int serverport = syncml_get_URI_port(uri);
    state->myURI = g_strdup(uri);
    state->socketfd = socket(PF_INET, SOCK_STREAM, 0);
    state->conntype = syncml_get_URI_proto(state->myURI);
    if (state->conntype == SYNCML_CONN_TYPE_HTTPS)
      if (!syncml_ssl_init_server(state)) {
	goto err;
      }
    if (state->socketfd >= 0) {
      struct sockaddr_in servaddr;
      servaddr.sin_family = AF_INET;
      servaddr.sin_port = htons(serverport);
      servaddr.sin_addr.s_addr = INADDR_ANY;
      if (!bind(state->socketfd, (struct sockaddr*) &servaddr, 
		sizeof(struct sockaddr_in))) {
	listen(state->socketfd, 0);
      } else {
	error = SYNCML_ERROR_NOPORT;
	goto err;
      }
    } else {
      error = SYNCML_ERROR_NOPORT;
      goto err;
    }
    dd(printf("SyncML:  Socket opened.\n"));
  }
  pipe(msgpipe);
  state->readmsg = msgpipe[0];
  state->writemsg = msgpipe[1];
  pthread_create(&state->thread, NULL, syncml_main_thread, state);
  return(state);

 err:
  syncml_error(state, state->userdata, error);
  if (state->socketfd >= 0)
    close(state->socketfd);
  state->socketfd = -1;
  return(NULL);
}

// Load syncml internal data from the open FILE f
void syncml_load_engine_state(syncml_state *state) {
  char line[256];
  FILE *f;
  if ((f = fopen(state->statefilename, "r"))) {
    while (fgets(line, 256, f)) {
      char prop[128], data[256];
      if (sscanf(line, "%127s = %255[^\n]", prop, data) == 2) {
	if (!strcmp(prop, "devID"))
	  state->devID = g_strdup(data);
	if (!strcmp(prop, "mynextnonce"))
	  state->mynextnonce = g_strdup(data);
	if (!strcmp(prop, "othernextnonce"))
	  state->othernextnonce = g_strdup(data);
	if (!strcmp(prop, "dbinfo")) {
	  char dbname[256]="",otherlast[256]="",mylast[256]="";
	  if (sscanf(data, "%255[^;];%255[^;];%255[^;]", dbname,otherlast,
		     mylast) > 0) {
	    syncml_db_anchors *anch = g_malloc0(sizeof(syncml_db_anchors));
	    anch->db = g_strdup(dbname);
	    anch->mylast = g_strdup(mylast);
	    anch->otherlast = g_strdup(otherlast);
	    state->dbanchors = g_list_append(state->dbanchors, anch);
	  }
	}
      }
    }
    fclose(f);
  }
}

// Save syncml internal data from open FILE f
void syncml_save_engine_state(syncml_state *state) {
  FILE *f;
  if ((f = fopen(state->statefilename, "w"))) {
    GList *pairs = state->db_pairs;
    if (state->devID) 
      fprintf(f, "devID = %s\n", state->devID);
    if (state->mynextnonce) 
      fprintf(f, "mynextnonce = %s\n", state->mynextnonce);
    if (state->othernextnonce) 
      fprintf(f, "othernextnonce = %s\n", state->othernextnonce);
    while (pairs) {
      syncml_db_pair *pair = pairs->data;
      if (pair && pair->myDB) {
	fprintf(f, "dbinfo = %s;", pair->myDB);
	if (pair->lastanchor)
	  fprintf(f, "%s", pair->lastanchor);
	fprintf(f, ";");
	if (pair->mylastanchor)
	  fprintf(f, "%s", pair->mylastanchor);
	fprintf(f, "\n");
      }
      pairs = pairs->next;
    }

    fclose(f);
  }
}

// Return true if the clien/server in the other end is a MultiSync plugin
gboolean syncml_is_partner_multisync(syncml_state *state) {
  if (state->otherdevinfo) {
    if (!g_strcasecmp(state->otherdevinfo->manufacturer,
		      "The MultiSync Project"))
      return(TRUE);
  }
  return(FALSE);
}

void syncml_cmd_send_changes(syncml_state *state, change_info *info) {
  syncml_engine_cmd cmd;

  cmd.cmd = SYNCML_ENGINE_CMD_SYNC;
  cmd.data = info;
  // Send message to command loop
  write(state->writemsg, &cmd, sizeof(syncml_engine_cmd));
}


void syncml_cmd_send_changes_result(syncml_state *state, GList *result) {
  syncml_engine_cmd cmd;

  cmd.cmd = SYNCML_ENGINE_CMD_SYNC_STATUS;
  cmd.data = result;
  // Send message to command loop
  write(state->writemsg, &cmd, sizeof(syncml_engine_cmd));
}

void syncml_cmd_send_sync_done(syncml_state *state) {
  syncml_engine_cmd cmd;
  
  cmd.cmd = SYNCML_ENGINE_CMD_MAP_STATUS;
  cmd.data = NULL;
  // Send message to command loop
  write(state->writemsg, &cmd, sizeof(syncml_engine_cmd));
}

void syncml_cmd_send_sync_serverinit(syncml_state *state, 
				     sync_object_type newdbs) {
  syncml_engine_cmd cmd;
  
  cmd.cmd = SYNCML_ENGINE_CMD_SYNC_SERVERINIT;
  cmd.data = (gpointer) newdbs;
  // Send message to command loop
  write(state->writemsg, &cmd, sizeof(syncml_engine_cmd));  
}

void syncml_cmd_get_devinfo(syncml_state *state) {
  syncml_engine_cmd cmd;
  
  cmd.cmd = SYNCML_ENGINE_CMD_GETDEVINFO;
  cmd.data = NULL;
  // Send message to command loop
  write(state->writemsg, &cmd, sizeof(syncml_engine_cmd));  
}


void syncml_cmd_quit(syncml_state *state) {
  syncml_engine_cmd cmd;

  cmd.cmd = SYNCML_ENGINE_CMD_QUIT;
  cmd.data = NULL;
  write(state->writemsg, &cmd, sizeof(syncml_engine_cmd));
}

// Set login and password
void syncml_set_login(syncml_state *state, char *login, char *passwd) {
  if (!state)
    return;
  SYNCML_FREE_STRING(state->user);
  SYNCML_FREE_STRING(state->passwd);
  state->user = g_strdup(login);
  state->passwd = g_strdup(passwd);
}

void syncml_add_db(syncml_state *state, char* localdb,
		   sync_object_type objtype) {
  GList *anchors;
  syncml_db_pair *pair;
  if (!state)
    return;
  pair = syncml_db_pair_new(localdb, NULL, NULL);
  pair->object_type = objtype;
  anchors = state->dbanchors;
  while (anchors) { // Get lastanchors from loaded engine state
    syncml_db_anchors *anch = anchors->data;
    if (anch && anch->db && !g_strcasecmp(anch->db, localdb)) {
      if (anch->otherlast) {
	SYNCML_FREE_STRING(pair->lastanchor);
	pair->lastanchor = g_strdup(anch->otherlast);
      }
      if (anch->mylast) {
	SYNCML_FREE_STRING(pair->mylastanchor);
	pair->mylastanchor = g_strdup(anch->mylast);
      }
    }
    anchors = anchors->next;
  }
  state->db_pairs = g_list_append(state->db_pairs, pair);
}

void syncml_add_remote_db(syncml_state *state, char* localdb,
			  char *remotedb) {
  GList *pairs = state->db_pairs;
  while (pairs) {
    syncml_db_pair *pair = pairs->data;
    if (!g_strcasecmp(pair->myDB, localdb)) {
      if (pair->otherDB)
	g_free(pair->otherDB);
      pair->otherDB = g_strdup(remotedb);
      pair->dosynchronize = TRUE;
    }
    pairs = pairs->next;
  }
}

// Initialization, called only once
void syncml_init() {
  syncml_ssl_init();
  xmlInitParser();
}
