/*   -*- c -*-
 * 
 *  ----------------------------------------------------------------------
 *  CcXstream Server for XBOX Media Player
 *  ----------------------------------------------------------------------
 *
 *  Copyright (c) 2002-2003 by PuhPuh
 *  
 *  This code is copyrighted property of the author.  It can still
 *  be used for any non-commercial purpose following conditions:
 *  
 *      1) This copyright notice is not removed.
 *      2) Source code follows any distribution of the software
 *         if possible.
 *      3) Copyright notice above is found in the documentation
 *         of the distributed software.
 *  
 *  Any express or implied warranties are disclaimed.  Author is
 *  not liable for any direct or indirect damages caused by the use
 *  of this software.
 *
 *  ----------------------------------------------------------------------
 *
 */

#include "ccincludes.h"
#include "ccbuffer.h"
#include "ccxstream.h"
#include "ccxmltrans.h"

const char *av0;

CcXstreamConfigurationOption cc_xstream_global_configuration_init(CcXstreamProg prog);
CcXstreamConfigurationOption cc_xstream_local_configuration_init(CcXstreamConnection conn);
char *cc_xstream_root_dir_info(void);
char *cc_xstream_configuration_dir_info(const char *name);
CcStringList cc_xstream_configuration_options(CcXstreamConnection conn, CcXstreamProg prog);
CcXstreamConfigurationOption cc_xstream_configuration_get_option(CcXstreamConnection conn,
								 CcXstreamProg prog,
								 const char *name);
char *cc_xstream_configuration_option_info(CcXstreamConfigurationOption opt);
char *cc_xstream_path_relative_to_absolute(CcXstreamProg prog, const char *path);
int cc_xstream_is_share_mountpoint(CcXstreamProg prog, const char *path);
size_t cc_xstream_server_discovery_reply_packet(CcXstreamProg prog, 
						unsigned long handle,
						unsigned char **p, size_t *p_len);

static unsigned long next_handle(CcXstreamConnection conn);

void usage(int exitval);

void usage(int exitval)
{
  fprintf(stderr, "Usage: %s [options]\n", av0);
  fprintf(stderr, "  options:\n");
  fprintf(stderr, "    -h                Print this message\n");
  fprintf(stderr, "    -l address        Listen only given local address (default is all).\n");
  fprintf(stderr, "    -p port           Listen given port (default is %d).\n",
	  (int)CC_XSTREAM_DEFAULT_PORT);
  fprintf(stderr, "    -r directory      Use diven document root (default is current dir).\n");
  fprintf(stderr, "                      Flag -r - makes an empty virtual root directory\n");
  fprintf(stderr, "                      where user can insert directories with -S flag.\n");
  fprintf(stderr, "    -u user           Run as given user.\n");
  fprintf(stderr, "    -P password       Require password authentication from the client.\n");
  fprintf(stderr, "    -f                Fork process to background.\n");
  fprintf(stderr, "    -F pidfile        Save pid number to file.\n");
  fprintf(stderr, "    -S mountpoint=dir Show dir in the root of the fileserver as mountpoint.\n");
  fprintf(stderr, "    -L                Follow symbolic directory in the data directory.\n");
  fprintf(stderr, "    -D                Don't listen (nor reply) to server discovery broadcasts.\n");
  exit(exitval);
}

static unsigned long next_handle(CcXstreamConnection conn)
{
  static unsigned long x = 0;
  int i;

  while (1) {
    x = (x % 0xffffffd) + 1;
    if (conn->open_dir_handle == x)
      continue;
    if (conn->open_auth_handle == x)
      continue;
    for (i = 0; i < CC_XSTREAM_MAX_OPEN_FILES; i++)
      if (conn->open_file_handle[i] == x)
	continue;
    break;
  }
  return x;
}

int cc_xstream_is_share_mountpoint(CcXstreamProg prog, const char *path)
{
  CcXstreamShare share;

  for (share = prog->shares; share != NULL; share = share->next)
    if (strcmp(share->mountpoint, path) == 0)
      return 1;
  return 0;
}

char *cc_xstream_root_dir_info()
{
  char *r, *tmp;
  CcBufferRec buf[1];

  cc_buffer_init(buf);
  cc_buffer_append_string(buf, "<DIRECTORYITEM>");
  cc_buffer_append_string(buf, "<NAME>");
  tmp = cc_xstream_xml_encode(CC_XSTREAM_VERSION_STRING);
  cc_buffer_append_string(buf, tmp);
  cc_xfree(tmp);
  cc_buffer_append_string(buf, "</NAME>");
  cc_buffer_append_string(buf, "<PATHNAME DEPTH=\"0\"></PATHNAME>");
  cc_buffer_append_string(buf, "<ATTRIB>directory</ATTRIB>");
  cc_buffer_append_string(buf, "<INFO></INFO>");
  cc_buffer_append_string(buf, "</DIRECTORYITEM>");
  r = cc_xmemdup(cc_buffer_ptr(buf), cc_buffer_len(buf));
  cc_buffer_uninit(buf);
  return r;
}

char *cc_xstream_configuration_dir_info(const char *name)
{
  char *r,  *tmp;
  CcBufferRec buf[1];

  cc_buffer_init(buf);
  cc_buffer_append_string(buf, "<DIRECTORYITEM>");
  cc_buffer_append_string(buf, "<NAME>");
  tmp = cc_xstream_xml_encode((name != NULL) ? name : CC_XSTREAM_CONFIG_DIR_NAME);
  cc_buffer_append_string(buf, tmp);
  cc_xfree(tmp);
  cc_buffer_append_string(buf, "</NAME>");
  cc_buffer_append_string(buf, "<PATHNAME DEPTH=\"1\">");
  cc_buffer_append_string(buf, "<COMPONENT LEVEL=\"1\">");
  tmp = cc_xstream_xml_encode(CC_XSTREAM_CONFIG_DIR_ITEM_NAME);
  cc_buffer_append_string(buf, tmp);
  cc_xfree(tmp);
  cc_buffer_append_string(buf, "</COMPONENT>");
  cc_buffer_append_string(buf, "</PATHNAME>");
  cc_buffer_append_string(buf, "<ATTRIB>directory</ATTRIB>");
  cc_buffer_append_string(buf, "<INFO>");
  tmp = cc_xstream_xml_encode(CC_XSTREAM_CONFIG_DIR_INFO);
  cc_buffer_append_string(buf, tmp);
  cc_xfree(tmp);
  cc_buffer_append_string(buf, "</INFO>");
  cc_buffer_append_string(buf, "</DIRECTORYITEM>");
  r = cc_xmemdup(cc_buffer_ptr(buf), cc_buffer_len(buf));
  cc_buffer_uninit(buf);
  return r;
}

CcXstreamConfigurationOption cc_xstream_configuration_get_option(CcXstreamConnection conn,
								 CcXstreamProg prog,
								 const char *name)
{
  CcXstreamConfigurationOption o;

  if (conn != NULL)
    {
      for (o = conn->configuration; o != NULL; o = o->next)
	{
	  if (strcmp(o->itemname, name) == 0)
	    return o;
	}
    }
  if (prog != NULL)
    {
      for (o = prog->configuration; o != NULL; o = o->next)
	{
	  if (strcmp(o->itemname, name) == 0)
	    return o;
	}
    }
  return NULL;
}

char *cc_xstream_configuration_option_info(CcXstreamConfigurationOption opt)
{
  char *r, *tmp;
  CcBufferRec buf[1];
  CcStringList x;

  cc_buffer_init(buf);
  cc_buffer_append_string(buf, "<DIRECTORYITEM>");
  cc_buffer_append_string(buf, "<NAME>");
  cc_buffer_append_string(buf, opt->name);
  cc_buffer_append_string(buf, "</NAME>");
  cc_buffer_append_string(buf, "<PATHNAME DEPTH=\"2\">");
  cc_buffer_append_string(buf, "<COMPONENT LEVEL=\"1\">");
  tmp = cc_xstream_xml_encode(CC_XSTREAM_CONFIG_DIR_ITEM_NAME);
  cc_buffer_append_string(buf, tmp);
  cc_xfree(tmp);
  cc_buffer_append_string(buf, "</COMPONENT>");
  cc_buffer_append_string(buf, "<COMPONENT LEVEL=\"2\">");
  tmp = cc_xstream_xml_encode(opt->itemname);
  cc_buffer_append_string(buf, tmp);
  cc_xfree(tmp);
  cc_buffer_append_string(buf, "</COMPONENT>");
  cc_buffer_append_string(buf, "</PATHNAME>");
  cc_buffer_append_string(buf, "<ATTRIB>");
  if (opt->readonly)
    cc_buffer_append_string(buf, "readonlyoption");
  else
    cc_buffer_append_string(buf, "option");
  cc_buffer_append_string(buf, "</ATTRIB>");
  cc_buffer_append_string(buf, "<OPTIONVALUE>");
  tmp = cc_xstream_xml_encode(opt->value);
  cc_buffer_append_string(buf, tmp);
  cc_xfree(tmp);
  cc_buffer_append_string(buf, "</OPTIONVALUE>");
  if (opt->legal_values != NULL)
    {
      cc_buffer_append_string(buf, "<LEGALOPTIONVALUES>");
      for (x = opt->legal_values; x != NULL; x = x->next)
	{
	  cc_buffer_append_string(buf, "<OPTIONVALUE>");
	  tmp = cc_xstream_xml_encode(x->s);
	  cc_buffer_append_string(buf, tmp);
	  cc_xfree(tmp);
	  cc_buffer_append_string(buf, "</OPTIONVALUE>");
	}
      cc_buffer_append_string(buf, "</LEGALOPTIONVALUES>");
    }
  cc_buffer_append_string(buf, "<INFO>");
  tmp = cc_xstream_xml_encode(opt->info);
  cc_buffer_append_string(buf, tmp);
  cc_xfree(tmp);
  cc_buffer_append_string(buf, "</INFO>");
  cc_buffer_append_string(buf, "</DIRECTORYITEM>");
  r = cc_xmemdup(cc_buffer_ptr(buf), cc_buffer_len(buf));
  cc_buffer_uninit(buf);
  return r;
}

char *cc_xstream_path_relative_to_absolute(CcXstreamProg prog, const char *path)
{
  CcXstreamShare share;
  const char *tmp;
  char *r;

  while (*path == '/')
    path++;
  if (*path == '\0')
    {
      if (prog->rootdir == NULL)
	return NULL;
      else
	return cc_xstrdup(prog->rootdir);
    }
  tmp = strchr(path, '/');
  if (tmp == NULL)
    tmp = path + strlen(path);
  for (share = prog->shares; share != NULL; share = share->next)
    {
      if (((tmp - path) == strlen(share->mountpoint)) &&
	  (strncmp(path, share->mountpoint, tmp - path) == 0))
	{
	  r = cc_xmalloc(strlen(share->dir) + strlen(tmp) + 1);
	  strcpy(r, share->dir);
	  strcat(r, tmp);
	  return r;
	}
    }
  if (prog->rootdir == NULL)
    return NULL;
  r = cc_xmalloc(strlen(prog->rootdir) + strlen(path) + 2);
  strcpy(r, prog->rootdir);
  strcat(r, "/");
  strcat(r, path);
  return r;
}

CcStringList cc_xstream_configuration_options(CcXstreamConnection conn, CcXstreamProg prog)
{
  CcStringList r, x;
  CcXstreamConfigurationOption o;

  r = NULL;
  if (prog != NULL)
    {
      for (o = prog->configuration; o != NULL; o = o->next)
	{
	  x = cc_xcalloc(1, sizeof (*x));
	  x->s = cc_xstrdup(o->itemname);
	  x->next = r;
	  r = x;
	}
    }
  if (conn != NULL)
    {
      for (o = conn->configuration; o != NULL; o = o->next)
	{
	  x = cc_xcalloc(1, sizeof (*x));
	  x->s = cc_xstrdup(o->itemname);
	  x->next = r;
	  r = x;
	}
    }
  return r;
}

CcXstreamConfigurationOption cc_xstream_local_configuration_init(CcXstreamConnection conn)
{
  CcXstreamConfigurationOption r, opt;
  CcStringList x;

  r = NULL;

  opt = cc_xcalloc(1, sizeof (*opt));
  opt->name = cc_xstrdup("Demo Textual Local Option");
  opt->itemname = cc_xstrdup("demo_text_local");
  opt->value = cc_xstrdup("foobar");
  opt->info = cc_xstrdup("Connection specific option just provided for testing of remote configuration of free form textual options.");
  opt->readonly = 0;
  opt->next = r;
  r = opt;

  opt = cc_xcalloc(1, sizeof (*opt));
  opt->name = cc_xstrdup("Demo Toggle Local Option");
  opt->itemname = cc_xstrdup("demo_toggle_local");
  opt->value = cc_xstrdup("YES");
  opt->info = cc_xstrdup("Connection specific option just provided for testing of remote configuration of YES/NO options.");
  opt->readonly = 0;
  x = cc_xcalloc(1, sizeof (*x));
  x->s = cc_xstrdup("YES");
  x->next = opt->legal_values;
  opt->legal_values = x;
  x = cc_xcalloc(1, sizeof (*x));
  x->s = cc_xstrdup("NO");
  x->next = opt->legal_values;
  opt->legal_values = x;
  opt->next = r;
  r = opt;

  return r;
}

CcXstreamConfigurationOption cc_xstream_global_configuration_init(CcXstreamProg prog)
{
  CcXstreamConfigurationOption r, opt;
  char nb[16];
  CcStringList x;

  r = NULL;

  opt = cc_xcalloc(1, sizeof (*opt));
  opt->name = cc_xstrdup("Root Directory");
  opt->itemname = cc_xstrdup("root_directory");
  opt->value = cc_xstrdup((prog->rootdir != NULL) ? prog->rootdir : "-*- NONE -*-");
  opt->info = cc_xstrdup("Root directory for server data");
  opt->readonly = 1;
  opt->next = r;
  r = opt;

  opt = cc_xcalloc(1, sizeof (*opt));
  opt->name = cc_xstrdup("Local Address");
  opt->itemname = cc_xstrdup("local_address");
  opt->value = cc_xstrdup(prog->localaddr);
  opt->info = cc_xstrdup("Interface address that server listens to");
  opt->readonly = 1;
  opt->next = r;
  r = opt;

  opt = cc_xcalloc(1, sizeof (*opt));
  opt->name = cc_xstrdup("Local Port");
  opt->itemname = cc_xstrdup("local_port");
  snprintf(nb, sizeof (nb), "%u", prog->localport);
  opt->value = cc_xstrdup(nb);
  opt->info = cc_xstrdup("TCP port that server listens to");
  opt->readonly = 1;
  opt->next = r;
  r = opt;

  opt = cc_xcalloc(1, sizeof (*opt));
  opt->name = cc_xstrdup("Demo Textual Option");
  opt->itemname = cc_xstrdup("demo_text");
  opt->value = cc_xstrdup("foobar");
  opt->info = cc_xstrdup("Option just provided for testing of remote configuration of free form textual options.");
  opt->readonly = 0;
  opt->next = r;
  r = opt;

  opt = cc_xcalloc(1, sizeof (*opt));
  opt->name = cc_xstrdup("Demo Toggle Option");
  opt->itemname = cc_xstrdup("demo_toggle");
  opt->value = cc_xstrdup("YES");
  opt->info = cc_xstrdup("Option just provided for testing of remote configuration of YES/NO options.");
  opt->readonly = 0;
  x = cc_xcalloc(1, sizeof (*x));
  x->s = cc_xstrdup("YES");
  x->next = opt->legal_values;
  opt->legal_values = x;
  x = cc_xcalloc(1, sizeof (*x));
  x->s = cc_xstrdup("NO");
  x->next = opt->legal_values;
  opt->legal_values = x;
  opt->next = r;
  r = opt;

  return r;
}

void cc_xstream_configuration_option_free(CcXstreamConfigurationOption opt);

void cc_xstream_configuration_option_free(CcXstreamConfigurationOption opt)
{
  CcXstreamConfigurationOption tmp_opt;
  CcStringList tmp_lst;

  while (opt != NULL)
    {
      cc_xfree(opt->name);
      cc_xfree(opt->itemname);
      cc_xfree(opt->info);
      cc_xfree(opt->value);
      while (opt->legal_values != NULL)
	{
	  cc_xfree(opt->legal_values->s);
	  tmp_lst = opt->legal_values;
	  opt->legal_values = opt->legal_values->next;
	  cc_xfree(tmp_lst);
	}
      tmp_opt = opt;
      opt = opt->next;
      cc_xfree(tmp_opt);
    }
}

CcXstreamConnection cc_xstream_connection_allocate()
{
  CcXstreamConnection r;
	
  r = cc_xcalloc(1, sizeof (*r));
  r->cwd = cc_xstrdup("");
  return r;
}

void cc_xstream_connection_free(CcXstreamConnection conn)
{
  int i;

  if (conn)
    {
      for (i = 0; i < CC_XSTREAM_MAX_OPEN_FILES; i++)
	{
	  if (conn->f[i] != NULL)
	    {
	      fclose(conn->f[i]);
	      conn->f[i] = NULL;
	    }
	}
      cc_xfree(conn->cwd);
      cc_xfree(conn->open_dir_cwd);
      cc_xfree(conn->fn);
      cc_xfree(conn->inbuf);
      cc_xfree(conn->outbuf);
      cc_xstream_configuration_option_free(conn->configuration);
      cc_xfree(conn);
    }
  return;
}

void cc_xstream_write_data_string(CcXstreamConnection conn, const unsigned char *data, size_t len)
{
  cc_xstream_write_int(conn, len);
  cc_xstream_write_data(conn, data, len);
}

void cc_xstream_write_string(CcXstreamConnection conn, const char *string)
{
  cc_xstream_write_data_string(conn, (const unsigned char *)string, strlen(string));
}

void cc_xstream_write_int(CcXstreamConnection conn, unsigned long n)
{
  unsigned char buf[4];

  cc_xstream_encode_int(buf, n);
  cc_xstream_write_data(conn, buf, 4);
}

void cc_xstream_write_data(CcXstreamConnection conn, const unsigned char *data, size_t len)
{
  if (len > 0)
    {
      if (conn->outbuf == NULL)
	{
	  conn->outbuf = cc_xmalloc(len);
	  conn->outbuf_len = 0;
	}
      else
	{
	  conn->outbuf = cc_xrealloc(conn->outbuf, conn->outbuf_len + len);
	}
      memcpy(conn->outbuf + conn->outbuf_len, data, len);
      conn->outbuf_len += len;
    }
  return;
}

void cc_xstream_write_byte(CcXstreamConnection conn, unsigned char b)
{
  cc_xstream_write_data(conn, &b, 1);
}

void cc_xstream_send_ok(CcXstreamConnection conn, unsigned long id)
{
  CC_DEBUG(10, ("Sending ok id=%lu", id));
  cc_xstream_write_int(conn, 5);
  cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_OK);
  cc_xstream_write_int(conn, id);
}

void cc_xstream_send_error(CcXstreamConnection conn, unsigned long id, CcXstreamError error, const char *msg)
{
  CC_DEBUG(10, ("Sending error id=%lu, errno=%d, msg=\"%s\"", id, (int)error, (msg != NULL) ? msg : ""));
  cc_xstream_write_int(conn, 10 + ((msg != NULL) ? strlen(msg) : 0));
  cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_ERROR);
  cc_xstream_write_int(conn, id);
  cc_xstream_write_byte(conn, (int)error);
  cc_xstream_write_string(conn, (msg != NULL) ? msg : "");
}

void cc_xstream_send_handle(CcXstreamConnection conn, unsigned long id, unsigned long handle)
{
  CC_DEBUG(10, ("Sending handle id=%lu, handle=%lu", id, handle));
  cc_xstream_write_int(conn, 9);
  cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_HANDLE);
  cc_xstream_write_int(conn, id);
  cc_xstream_write_int(conn, handle);
}

int cc_xstream_handle_packet(CcXstreamConnection conn, const unsigned char *packet, size_t len)
{
  CcXstreamPacket cmd;
  unsigned long id;
  char *hlp, *hlp2, *hlp3;
  CcBufferRec buf[1];
  char *s;
  size_t s_len;
  unsigned long handle, rlen, levels;
  off_t fpos, offset;
  size_t sz; 
  int otype, fh;
  CcStringList tmp;
  CcXstreamConfigurationOption opt;
  CcXstreamShare share;

  if (len < 5)
    return 0;
  cmd = (CcXstreamPacket)(packet[0]);
  id = cc_xstream_decode_int(packet + 1);
  packet += 5;
  len -= 5;

  CC_DEBUG(10, ("Received packet type=%d id=%lu", (int)cmd, id));

  switch (cmd)
    {
    case CC_XSTREAM_XBMSP_PACKET_NULL:
      {
	cc_xstream_send_ok(conn, id);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_UPCWD:
      {
	if (len != 4)
	  return 0;
	levels = cc_xstream_decode_int(packet);
	while ((levels > 0) && (strchr(conn->cwd, '/') != NULL))
	  {
	    hlp = strrchr(conn->cwd, '/');
	    *hlp = '\0';
	    levels--;
	  }
	if (levels > 0)
	  {
	    cc_xfree(conn->cwd);
	    conn->cwd = cc_xstrdup("");
	  }
	CC_DEBUG(10, ("New working directory is \"%s\"", conn->cwd));
	cc_xstream_send_ok(conn, id);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_SETCWD:
      {
	if (len < 4)
	  return 0;
	s_len = cc_xstream_decode_int(packet);
	if (s_len != len - 4)
	  return 0;
	if (! conn->authenticated)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_AUTHENTICATION_NEEDED, "Not authenticated.");
	    return 1;
	  }
	s = cc_xmemdup(packet + 4, s_len);
        if (strcmp(s, "") == 0)
	  {
	    /* Stay in the current directory (no-op). */
	    cc_xfree(s);
	    CC_DEBUG(10, ("New working directory is \"%s\"", conn->cwd));
	    cc_xstream_send_ok(conn, id);
	    return 1;
	  }
	else if (strcmp(s, "/") == 0)
	  {
	    /* Go to the root directory */
	    CC_DEBUG(5,  ("Client uses nonstandard \"/\" path name."));
	    cc_xfree(conn->cwd);
	    conn->cwd = cc_xstrdup("");
	    cc_xfree(s);
	    CC_DEBUG(10, ("New working directory is \"%s\"", conn->cwd));
	    cc_xstream_send_ok(conn, id);
	    return 1;
	  }
	else if (strcmp(s, ".") == 0)
	  {
	    /* Stay in the current directory (no-op) */
	    CC_DEBUG(5,  ("Client uses nonstandard \".\" path name."));
	    cc_xfree(s);
	    CC_DEBUG(10, ("New working directory is \"%s\"", conn->cwd));
	    cc_xstream_send_ok(conn, id);
	    return 1;
	  }
	else if (strcmp(s, "..") == 0)
	  {
	    /* Go to the parent directory */
	    CC_DEBUG(5,  ("Client uses nonstandard \"..\" path name."));
	    if (strchr(conn->cwd, '/') != NULL)
	      {
		hlp = strrchr(conn->cwd, '/');
		*hlp = '\0';
		hlp = cc_xstrdup(conn->cwd);
		cc_xfree(conn->cwd);
		conn->cwd = hlp;
	      }
	    else
	      {
		cc_xfree(conn->cwd);
		conn->cwd = cc_xstrdup("");
	      }
	    cc_xfree(s);
	    CC_DEBUG(10, ("New working directory is \"%s\"", conn->cwd));
	    cc_xstream_send_ok(conn, id);
	    return 1;
	  }
	else if (conn->prog->remoteconf &&
		 (strcmp(conn->cwd, "") == 0) &&
		 (strcmp(s, CC_XSTREAM_CONFIG_DIR_ITEM_NAME) == 0))
	  {
	    cc_xfree(conn->cwd);
	    conn->cwd = cc_xstrdup(CC_XSTREAM_CONFIG_DIR_ITEM_NAME);
	    cc_xfree(s);
	    CC_DEBUG(10, ("New working directory is \"%s\"", conn->cwd));
	    cc_xstream_send_ok(conn, id);
	    return 1;
	  }
	else
	  {
	    if (strchr(s, '/') != NULL)
	      {
		cc_xfree(s);
		cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_NO_SUCH_FILE, "Illegal directory name.");
		return 1;
	      }
	    cc_buffer_init(buf);
	    if (strlen(conn->cwd) > 0)
	      {
		cc_buffer_append_string(buf, conn->cwd);
		cc_buffer_append_string(buf, "/");
	      }
	    cc_buffer_append_string(buf, s);
	    cc_xfree(s);
	    hlp = cc_xmemdup(cc_buffer_ptr(buf), cc_buffer_len(buf));
	    cc_buffer_uninit(buf);
	    hlp2 = cc_xstream_path_relative_to_absolute(conn->prog, hlp);
	    if ((hlp2 != NULL) && cc_xstream_path_is_directory(conn->prog, hlp2))
	      {
		cc_xfree(hlp2);
		cc_xfree(conn->cwd);
		conn->cwd = hlp;
		CC_DEBUG(10, ("New working directory is \"%s\"", conn->cwd));
		cc_xstream_send_ok(conn, id);
		return 1;
	      }
	    else
	      {
		cc_xfree(hlp);
		cc_xfree(hlp2);
		cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_NO_SUCH_FILE, "No such directory.");
		return 1;
	      }
	  }
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_FILELIST_OPEN:
      {
	if (len != 0)
	  return 0;
	if (conn->open_dir_handle != 0)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_TOO_MANY_OPEN_FILES, "Too many directories open.");
	    return 1;
	  }
	if (! conn->authenticated)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_AUTHENTICATION_NEEDED, "Not authenticated.");
	    return 1;
	  }
	conn->open_dir_cwd = cc_xstrdup(conn->cwd);
	hlp = cc_xstream_path_relative_to_absolute(conn->prog, conn->open_dir_cwd);
	if (conn->prog->remoteconf && (strcmp(conn->open_dir_cwd, CC_XSTREAM_CONFIG_DIR_ITEM_NAME) == 0))
	  {
	    conn->dirfile = cc_xstream_configuration_options(conn, conn->prog);
	  }
	else
	  {
	    if (hlp != NULL)
	      conn->dirfile = cc_xstream_read_directory(hlp);
	    else
	      conn->dirfile = NULL;
	  }
	if (strcmp(conn->open_dir_cwd, "") == 0)
	    for (share = conn->prog->shares; share != NULL; share = share->next)
	      conn->dirfile = cc_xstream_string_list_add(conn->dirfile, share->mountpoint);
	if (conn->prog->remoteconf && (strcmp(conn->open_dir_cwd, "") == 0))
	  conn->dirfile = cc_xstream_string_list_add(conn->dirfile, CC_XSTREAM_CONFIG_DIR_ITEM_NAME);
	cc_xfree(hlp);
	conn->open_dir_handle = next_handle(conn);
	cc_xstream_send_handle(conn, id, conn->open_dir_handle);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_FILELIST_READ:
      {
	if (len != 4)
	  return 0;
	handle = cc_xstream_decode_int(packet);
	if ((conn->open_dir_handle == 0) || (conn->open_dir_handle != handle))
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_INVALID_HANDLE, "Invalid directory handle.");
	    return 1;
	  }
      iterate_to_next_file:
	if (conn->dirfile == NULL)
	  {
	    conn->open_dir_handle = 0;
	    cc_xfree(conn->open_dir_cwd);
	    conn->open_dir_cwd = NULL;
	    cc_xstream_write_int(conn, 1 + 4 + 4 + 4);
	    cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_FILE_DATA);
	    cc_xstream_write_int(conn, id);
	    cc_xstream_write_int(conn, 0);
	    cc_xstream_write_int(conn, 0);
	    return 1;
	  }
	else if (conn->prog->remoteconf &&
		 (strcmp(conn->open_dir_cwd, "") == 0) &&
		 (strcmp(conn->dirfile->s, CC_XSTREAM_CONFIG_DIR_ITEM_NAME) == 0))
	  {
	    cc_xfree(conn->dirfile->s);
	    conn->dirfile->s = NULL;
	    tmp = conn->dirfile;
	    conn->dirfile = conn->dirfile->next;
	    cc_xfree(tmp);
	    hlp = cc_xstream_configuration_dir_info(NULL);
	    cc_xstream_write_int(conn, 1 + 4 + 4 + strlen(CC_XSTREAM_CONFIG_DIR_ITEM_NAME) + 4 + strlen(hlp));
	    cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_FILE_DATA);
	    cc_xstream_write_int(conn, id);
	    cc_xstream_write_string(conn, CC_XSTREAM_CONFIG_DIR_ITEM_NAME);
	    cc_xstream_write_string(conn, hlp);
	    return 1;
	  }
	else if (conn->prog->remoteconf && 
		 (strcmp(conn->open_dir_cwd, CC_XSTREAM_CONFIG_DIR_ITEM_NAME) == 0))
	  {
	    opt = cc_xstream_configuration_get_option(conn, conn->prog, conn->dirfile->s);
	    cc_xfree(conn->dirfile->s);
	    conn->dirfile->s = NULL;
	    tmp = conn->dirfile;
	    conn->dirfile = conn->dirfile->next;
	    cc_xfree(tmp);
	    if (opt == NULL)
	      goto iterate_to_next_file;
	    hlp = cc_xstream_configuration_option_info(opt);
	    cc_xstream_write_int(conn, 1 + 4 + 4 + strlen(opt->itemname) + 4 + strlen(hlp));
	    cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_FILE_DATA);
	    cc_xstream_write_int(conn, id);
	    cc_xstream_write_string(conn, opt->itemname);
	    cc_xstream_write_string(conn, hlp);
	    cc_xfree(hlp);
	    return 1;
	  }
	else
	  {
	    cc_buffer_init(buf);
	    cc_buffer_append_string(buf, conn->open_dir_cwd);
	    cc_buffer_append_string(buf, "/");
	    cc_buffer_append_string(buf, conn->dirfile->s);
	    hlp3 = cc_xmemdup(cc_buffer_ptr(buf), cc_buffer_len(buf));
	    cc_buffer_uninit(buf);
	    hlp2 = cc_xstream_path_relative_to_absolute(conn->prog, hlp3);
	    if (hlp2 != NULL)
	      {
		hlp = cc_xstream_file_info(conn->prog, hlp2, hlp3);
		cc_xfree(hlp2);
	      }
	    else
	      {
		hlp = NULL;
	      }
	    cc_xfree(hlp3);
	    hlp2 = conn->dirfile->s;
	    conn->dirfile->s = NULL;
	    tmp = conn->dirfile;
	    conn->dirfile = conn->dirfile->next;
	    cc_xfree(tmp);
	    if (hlp == NULL)
	      {
		cc_xfree(hlp2);
		goto iterate_to_next_file;
	      }
	    cc_xstream_write_int(conn, 1 + 4 + 4 + strlen(hlp2) + 4 + strlen(hlp));
	    cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_FILE_DATA);
	    cc_xstream_write_int(conn, id);
	    cc_xstream_write_string(conn, hlp2);
	    cc_xstream_write_string(conn, hlp);
	    return 1;
	  }
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_FILE_INFO:
      {
	if (len < 4)
	  return 0;
	s_len = cc_xstream_decode_int(packet);
	if (s_len != len - 4)
	  return 0;
	if (! conn->authenticated)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_AUTHENTICATION_NEEDED, "Not authenticated.");
	    return 1;
	  }
	s = cc_xmemdup(packet + 4, s_len);
	if ((strchr(s, '/') != NULL) || (strcmp(s, "..") == 0))
	  {
	    cc_xfree(s);
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_NO_SUCH_FILE, "Illegal file name.");
	    return 1;
	  }
	cc_buffer_init(buf);
	cc_buffer_append_string(buf, conn->cwd);
	if (strcmp(s, "") == 0)
	  {
	    /*NOTHING*/
	  }
	else if (strcmp(s, ".") == 0)
	  {
	    CC_DEBUG(5,  ("Client uses nonstandard \".\" file name."));
	    cc_xfree(s);
	    s = cc_xstrdup("");
	  }
	else
	  {
	    if (cc_buffer_len(buf) > 0)
	      cc_buffer_append_string(buf, "/");
	    cc_buffer_append_string(buf, s);
	  }
	hlp3 = cc_xmemdup(cc_buffer_ptr(buf), cc_buffer_len(buf));
	cc_buffer_uninit(buf);
	hlp = cc_xstream_path_relative_to_absolute(conn->prog, hlp3);

	if ((strcmp(s, "") == 0) && (strcmp(conn->cwd, "") == 0))
	  {
	    hlp2 = cc_xstream_root_dir_info();
	  }
	else if (strcmp(hlp3, CC_XSTREAM_CONFIG_DIR_ITEM_NAME) == 0)
	  {
	    hlp2 = cc_xstream_configuration_dir_info(NULL);
	  }
	else if (conn->prog->remoteconf &&
		 (strcmp(conn->cwd, CC_XSTREAM_CONFIG_DIR_ITEM_NAME) == 0))
	  {
	    opt = cc_xstream_configuration_get_option(conn, conn->prog, s);
	    if (opt != NULL)
	      hlp2 = cc_xstream_configuration_option_info(opt);
	    else
	      hlp2 = NULL;
	  }
	else
	  {
	    if (hlp != NULL)
	      hlp2 = cc_xstream_file_info(conn->prog, hlp, hlp3);
	    else
	      hlp2 = NULL;
	  }
	cc_xfree(hlp);
	cc_xfree(hlp3);
	if (hlp2 != NULL)
	  {
	    cc_xstream_write_int(conn, 1 + 4 + 4 + strlen(s) + 4 + strlen(hlp2));
	    cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_FILE_DATA);
	    cc_xstream_write_int(conn, id);
	    cc_xstream_write_string(conn, s);
	    cc_xstream_write_string(conn, hlp2);
	    cc_xfree(hlp2);
	    cc_xfree(s);
	    return 1;
	  }
	else
	  {
	    cc_xfree(s);
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_NO_SUCH_FILE, "No such file.");
	    return 1;
	  }
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_FILE_OPEN:
      {
	if (len < 4)
	  return 0;
	s_len = cc_xstream_decode_int(packet);
	if (s_len != len - 4)
	  return 0;
	if (! conn->authenticated)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_AUTHENTICATION_NEEDED, "Not authenticated.");
	    return 1;
	  }
	for (fh = 0; fh < CC_XSTREAM_MAX_OPEN_FILES; fh++)
	  if (conn->open_file_handle[fh] == 0)
	    break;
	if (fh >= CC_XSTREAM_MAX_OPEN_FILES)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_TOO_MANY_OPEN_FILES, "Too many files open.");
	    return 1;
	  }
	s = cc_xmemdup(packet + 4, s_len);
	CC_DEBUG(10, ("Opening file \"%s\"", s));
	if ((strchr(s, '/') != NULL) || (strcmp(s, "..") == 0) || (strcmp(s, ".") == 0) || (strcmp(s, "") == 0))
	  {
	    cc_xfree(s);
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_NO_SUCH_FILE, "Illegal file name.");
	    return 1;
	  }
	cc_buffer_init(buf);
	if (strlen(conn->cwd) > 0)
	  {
	    cc_buffer_append_string(buf, conn->cwd);
	    cc_buffer_append_string(buf, "/");
	  }
	cc_buffer_append_string(buf, s);
	hlp2 = cc_xmemdup(cc_buffer_ptr(buf), cc_buffer_len(buf));
	cc_buffer_uninit(buf);
	hlp = cc_xstream_path_relative_to_absolute(conn->prog, hlp2);
	cc_xfree(hlp2);
	if ((hlp == NULL) || (! cc_xstream_path_is_file(conn->prog, hlp)))
	  {
	    cc_xfree(s);
	    cc_xfree(hlp);
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_NO_SUCH_FILE, "Can't find file.");
	    return 1;
	  }
	conn->f[fh] = fopen(hlp, "r");
	cc_xfree(hlp);
	cc_xfree(s);
	if (conn->f[fh] == NULL)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_OPEN_FAILED, "Can't open file.");
	    return 1;
	  }
	conn->end_offset[fh] = cc_xstream_open_file_size(conn->f[fh]);
	CC_DEBUG(6, ("File size is " CC_UINT_64_PRINTF_FORMAT " bytes", (CC_UINT_64_TYPE_NAME)conn->end_offset[fh]));
	if (conn->end_offset == 0)
	  {
	    fclose(conn->f[fh]);
	    conn->f[fh] = NULL;
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_OPEN_FAILED, "File is invalid or zero length.");
	    return 1;
	  }
	conn->open_file_handle[fh] = next_handle(conn);
	cc_xstream_send_handle(conn, id, conn->open_file_handle[fh]);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_FILE_READ:
      {
	if (len != 8)
	  return 0;
	handle = cc_xstream_decode_int(packet);
	rlen = cc_xstream_decode_int(packet + 4);
	for (fh = 0; fh < CC_XSTREAM_MAX_OPEN_FILES; fh++)
	  if (conn->open_file_handle[fh] == handle)
	    break;
	if (fh >= CC_XSTREAM_MAX_OPEN_FILES)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_INVALID_HANDLE, "Invalid file handle.");
	    return 1;
	  }
	if (rlen > 0x20000)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_TOO_LONG_READ, "Read too long.");
	    return 1;
	  }
	hlp = cc_xmalloc(rlen);
	sz = fread(hlp, 1, rlen, conn->f[fh]);
	cc_xstream_write_int(conn, 1 + 4 + 4 + sz);
	cc_xstream_write_byte(conn, (int)CC_XSTREAM_XBMSP_PACKET_FILE_CONTENTS);
	cc_xstream_write_int(conn, id);
	cc_xstream_write_data_string(conn, (unsigned char *)hlp, sz);
	cc_xfree(hlp);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_FILE_SEEK:
      {
	if ((len != 9) && (len != 13))
	  return 0;
	handle = cc_xstream_decode_int(packet);
	otype = packet[4];
	if (len == 13)
	  {
	    offset = (off_t)(cc_xstream_decode_int(packet + 5));
	    /* Avoid compiler warning with two shifts instead of one in 
	       systems where off_t is 32 bits. */
	    offset <<= 16;
	    offset <<= 16;
	    offset |= (off_t)(cc_xstream_decode_int(packet + 9));
	  }
	else
	  {
	    CC_DEBUG(5,  ("Client uses obsolete 4 byte offset in seek"));
	    offset = cc_xstream_decode_int(packet + 5);
	  }
	for (fh = 0; fh < CC_XSTREAM_MAX_OPEN_FILES; fh++)
	  if (conn->open_file_handle[fh] == handle)
	    break;
	if (fh >= CC_XSTREAM_MAX_OPEN_FILES)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_INVALID_HANDLE, "Invalid file handle.");
	    return 1;
	  }
	fpos = ftello(conn->f[fh]);
	CC_DEBUG(10, ("seek, " CC_UINT_64_PRINTF_FORMAT " %d", (CC_UINT_64_TYPE_NAME)offset, (int)otype));
	switch (otype)
	  {
	  case 0:
	    if (offset <= conn->end_offset[fh])
	      {
		fpos = offset;
		if (fseeko(conn->f[fh], fpos, SEEK_SET) == 0)
		  cc_xstream_send_ok(conn, id);
		else
		  cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "Seek failed.");
		return 1;
	      }
	    else
	      {
		cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "Seeking beyond EOF.");
		return 1;
	      }
	    /*NOTREACHED*/

	  case 1:
	    if (offset <= conn->end_offset[fh])
	      {
		fpos = conn->end_offset[fh] - offset;
		if (fseeko(conn->f[fh], fpos, SEEK_SET) == 0)
		  cc_xstream_send_ok(conn, id);
		else
		  cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "Seek failed.");
		return 1;
	      }
	    else
	      {
		cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "Seeking beyond beginning.");
		return 1;
	      }
	    /*NOTREACHED*/

	  case 2:
	    if (((fpos + offset) <= conn->end_offset[fh]) && ((fpos + offset) > offset))
	      {
		fpos += offset;
		if (fseeko(conn->f[fh], fpos, SEEK_SET) == 0)
		  cc_xstream_send_ok(conn, id);
		else
		  cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "Seek failed.");
		return 1;
	      }
	    else
	      {
		cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "Seeking beyond EOF.");
		return 1;
	      }
	    /*NOTREACHED*/

	  case 3:
	    if (fpos > offset)
	      {
		fpos -= offset;
		if (fseeko(conn->f[fh], fpos, SEEK_SET) == 0)
		  cc_xstream_send_ok(conn, id);
		else
		  cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "Seek failed.");
		return 1;
	      }
	    else
	      {
		cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "Seeking beyond beginning.");
		return 1;
	      }
	    /*NOTREACHED*/

	  default:
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_UNSUPPORTED, "Unsupported seek type.");
	    return 1;
	  }
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_CLOSE:
      {
	if (len != 4)
	  return 0;
	handle = cc_xstream_decode_int(packet);
	if ((conn->open_dir_handle != 0) && (handle == conn->open_dir_handle))
	  {
	    while (conn->dirfile != NULL)
	      {
		tmp = conn->dirfile;
		conn->dirfile = conn->dirfile->next;
		cc_xfree(tmp->s);
		cc_xfree(tmp);
	      }
	    conn->open_dir_handle = 0;
	    cc_xfree(conn->open_dir_cwd);
	    conn->open_dir_cwd = NULL;
	    cc_xstream_send_ok(conn, id);
	    return 1;
	  }
	for (fh = 0; fh < CC_XSTREAM_MAX_OPEN_FILES; fh++)
	  if (conn->open_file_handle[fh] == handle)
	    break;
	if (fh < CC_XSTREAM_MAX_OPEN_FILES)
	  {
	    fclose(conn->f[fh]);
	    conn->f[fh] = NULL;
	    conn->end_offset[fh] = 0;
	    conn->open_file_handle[fh] = 0;
	    cc_xstream_send_ok(conn, id);
	    return 1;
	  }
	cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_INVALID_HANDLE, "Invalid handle.");
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_CLOSE_ALL:
      {
	if (len != 0)
	  return 0;
	for (fh = 0; fh < CC_XSTREAM_MAX_OPEN_FILES; fh++)
	  {
	    if (conn->open_file_handle[fh] != 0)
	      {
		fclose(conn->f[fh]);
		conn->f[fh] = NULL;
		conn->end_offset[fh] = 0;
		conn->open_file_handle[fh] = 0;
	      }
	  }
	if (conn->open_dir_handle != 0)
	  {
	    while (conn->dirfile != NULL)
	      {
		tmp = conn->dirfile;
		conn->dirfile = conn->dirfile->next;
		cc_xfree(tmp->s);
		cc_xfree(tmp);
	      }
	    conn->open_dir_handle = 0;
	    cc_xfree(conn->open_dir_cwd);
	    conn->open_dir_cwd = NULL;
	  }
	cc_xstream_send_ok(conn, id);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_SET_CONFIGURATION_OPTION:
      {
	if (len < 4)
	  return 0;
	s_len = cc_xstream_decode_int(packet);
	if (s_len > len - 4)
	  return 0;
	hlp = cc_xmemdup(packet + 4, s_len);
	packet += s_len + 4;
	len -= s_len + 4;
	if (len < 4)
	  {
	    cc_xfree(hlp);
	    return 0;
	  }
	s_len = cc_xstream_decode_int(packet);
	if (s_len != len - 4)
	  {
	    cc_xfree(hlp);
	    return 0;
	  }
	hlp2 = cc_xmemdup(packet + 4, s_len);
	if ((! conn->prog->remoteconf) ||
	    (strcmp(conn->cwd, CC_XSTREAM_CONFIG_DIR_ITEM_NAME) != 0))
	  {
	    cc_xfree(hlp);
	    cc_xfree(hlp2);
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_INVALID_FILE, "Invalid option.");
	    return 1;
	  }
	opt = cc_xstream_configuration_get_option(conn, conn->prog, hlp);
	cc_xfree(hlp);
	if (opt == NULL)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_INVALID_FILE, "Invalid option.");
	  }
	else
	  {
	    if (opt->readonly)
	      {
		cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_OPTION_IS_READ_ONLY, "Option is read only.");
	      }
	    else if (opt->legal_values == NULL)
	      {
		cc_xfree(opt->value);
		opt->value = cc_xstrdup(hlp2);
		cc_xstream_send_ok(conn, id);
	      }
	    else
	      {
		for (tmp = opt->legal_values; tmp != NULL; tmp = tmp->next)
		  {
		    if (strcasecmp(tmp->s, hlp2) == 0)
		      {
			cc_xfree(opt->value);
			opt->value = cc_xstrdup(tmp->s);
			break;
		      }
		  }
		if (tmp == NULL)
		  cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_INVALID_OPTION_VALUE, "Illegal value.");
		else
		  cc_xstream_send_ok(conn, id);
	      }
	  }
	cc_xfree(hlp2);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_AUTHENTICATION_INIT:
      {
	if (len < 4)
	  return 0;
	s_len = cc_xstream_decode_int(packet);
	if (s_len != len - 4)
	  return 0;
	if (conn->open_auth_handle != 0)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_TOO_MANY_OPEN_FILES, "Authentication in progress.");
	    return 1;
	  }
	s = cc_xmemdup(packet + 4, s_len);
	if (strcmp(s, "password") != 0)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_UNSUPPORTED, "Unknown authentication method.");
	    return 1;
	  }
	conn->open_auth_method = s;
	s = NULL;
	conn->open_auth_handle = next_handle(conn);
	cc_xstream_send_handle(conn, id, conn->open_auth_handle);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_AUTHENTICATE:
      {
	if (len < 4)
	  return 0;
	handle = cc_xstream_decode_int(packet);
	len -= 4;
	packet += 4;
	if (len < 4)
	  return 0;
	s_len = cc_xstream_decode_int(packet);
	if (s_len > len - 4)
	  return 0;
	hlp = cc_xmemdup(packet + 4, s_len);
	packet += s_len + 4;
	len -= s_len + 4;
	if (len < 4)
	  {
	    cc_xfree(hlp);
	    return 0;
	  }
	s_len = cc_xstream_decode_int(packet);
	if (s_len != len - 4)
	  {
	    cc_xfree(hlp);
	    return 0;
	  }
	hlp2 = cc_xmemdup(packet + 4, s_len);
	if (handle != conn->open_auth_handle)
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_INVALID_HANDLE, "Invalid handle.");
	    cc_xfree(hlp);
	    cc_xfree(hlp2);
	    return 1;
	  }
	if (((conn->prog->user_id == NULL) || (strcmp(hlp, conn->prog->user_id) == 0)) &&
	    ((conn->prog->user_password == NULL) || (strcmp(hlp2, conn->prog->user_password) == 0)))
	  {
	    cc_xstream_send_ok(conn, id);
	    conn->authenticated = 1;
	  }
	else
	  {
	    cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_AUTHENTICATION_FAILED, "Invalid password.");
	  }
	cc_xfree(conn->open_auth_method);
	conn->open_auth_method = NULL;
	conn->open_auth_handle = 0;
	cc_xfree(hlp);
	cc_xfree(hlp2);
	return 1;
      }
      /*NOTREACHED*/

    case CC_XSTREAM_XBMSP_PACKET_OK:
    case CC_XSTREAM_XBMSP_PACKET_ERROR:
    case CC_XSTREAM_XBMSP_PACKET_HANDLE:
    case CC_XSTREAM_XBMSP_PACKET_FILE_DATA:
    case CC_XSTREAM_XBMSP_PACKET_FILE_CONTENTS:
    case CC_XSTREAM_XBMSP_PACKET_AUTHENTICATION_CONTINUE:
      /* These packets are to be sent by the server only. */
      return 0;
      /*NOTREACHED*/

    default:
      cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_UNSUPPORTED, "Unsupported command.");
      return 1;
      /*NOTREACHED*/
    }
  /*NOTREACHED*/
}

int cc_xstream_parse_remote_version(CcXstreamConnection conn, const char *proto)
{
  if (strncmp("XBMSP-1.0 ", proto, strlen("XBMSP-1.0 ")) == 0)
    {
      conn->remote_version = 10;
      return 1;
    }
  return 0;
}

int cc_xstream_consume_input(CcXstreamConnection conn)
{
  size_t len;
  char *proto;

  if (conn->remote_version == 0)
    {
      for (len = 0; len < conn->inbuf_len; len++)
	{
	  if (conn->inbuf[len] == '\r')
	    {
	      /* If we see CR, we assume that next will be LF and terminate
		 incoming protocol string here. */
	      conn->inbuf[len] = '\0';
	    }
	  else if (conn->inbuf[len] == '\n')
	    {
	      conn->inbuf[len] = '\0';
	      proto = cc_xstrdup((char *)conn->inbuf);
	      if ((len + 1) < conn->inbuf_len)
		memmove(conn->inbuf, conn->inbuf + len + 1, conn->inbuf_len - len - 1);
	      conn->inbuf_len -= len + 1;
	      if (cc_xstream_parse_remote_version(conn, proto) == 0)
		return -1;
	      return 1;
	    }
	}
    }
  else
    {
      if (conn->inbuf_len >= 4)
	{
	  len = cc_xstream_decode_int(conn->inbuf);
	  if (len > 0x20000)
	    {
	      return -1;
	    }
	  if (len <= conn->inbuf_len + 4)
	    {
	      if (cc_xstream_handle_packet(conn, conn->inbuf + 4, len) == 0)
		return -1;
	      len += 4;
	      if (len < conn->inbuf_len)
		memmove(conn->inbuf, conn->inbuf + len, conn->inbuf_len - len);
	      conn->inbuf_len -= len;
	    }
	}
    }
  return 0;
}

void cc_xstream_server_make_non_blocking(int s)
{
  int r;

  r = fcntl(s, F_GETFL, 0);
#ifdef O_NDELAY
  r = r | O_NDELAY;
#endif /* O_NDELAY */
  fcntl(s, F_SETFL, r);
  return;
}

size_t cc_xstream_server_discovery_reply_packet(CcXstreamProg prog,
						unsigned long handle,
						unsigned char **p, size_t *p_len)
{
  CcBufferRec buf[1];
  unsigned char b[16];

  /* Initialize the buffer. */
  cc_buffer_init(buf);
  /* Encode packet */
  cc_xstream_buffer_encode_byte(buf, (unsigned char)CC_XSTREAM_XBMSP_PACKET_SERVER_DISCOVERY_REPLY);
  cc_xstream_buffer_encode_int(buf, handle);
  if ((prog->localaddr == NULL) || (strcmp(prog->localaddr, "ANY") == 0))
    cc_xstream_buffer_encode_string(buf, "");
  else
    cc_xstream_buffer_encode_string(buf, prog->localaddr);
  snprintf(b, sizeof (b), "%d", prog->localport);
  cc_xstream_buffer_encode_string(buf, b);
  cc_xstream_buffer_encode_string(buf, CC_XSTREAM_PROTOCOL_INIT);
  cc_xstream_buffer_encode_string(buf, prog->server_comment);
  cc_xstream_buffer_encode_packet_length(buf);
  /* Return payload */
  *p = cc_xmemdup(cc_buffer_ptr(buf), cc_buffer_len(buf));
  *p_len = cc_buffer_len(buf);
  /* Free the buffer */
  cc_buffer_uninit(buf);
  return *p_len;
}

int main(int argc, char **argv)
{
  int c, mf, rv;
  struct in_addr la;
  struct sockaddr_in sa;
  socklen_t sa_len;
  char *user = NULL, *cwd;
  struct passwd *pw;
  uid_t uid;
  gid_t gid;
  fd_set rs, ws;
  struct timeval tv;
  CcXstreamConnection conn, prev;
  CcXstreamProg prog;
  CcXstreamShare share;
  CcXstreamServerDiscoveryReplyPacket reply;
  char *tmp;

  if (strchr(argv[0], '/'))
    av0 = strrchr(argv[0], '/') + 1;
  else
    av0 = argv[0];

  la.s_addr = htonl(INADDR_ANY);
  prog = cc_xcalloc(1, sizeof (*prog));
  cwd = cc_xmalloc(PATH_MAX + 1);
  if (getcwd(cwd, PATH_MAX) == NULL)
    {
      fprintf(stderr, "%s: Can't find current directory.\n", av0);
      exit(1);
    }
  prog->rootdir = cc_xstrdup(cwd);
  cc_xfree(cwd);
  prog->localaddr = cc_xstrdup("ANY");
  prog->localport = CC_XSTREAM_DEFAULT_PORT;
  prog->server_comment = cc_xcalloc(1, 129);
  if (gethostname(prog->server_comment, 128) != 0)
    prog->server_comment[0] = '\0';

  while ((c = getopt(argc, argv, "hl:p:u:r:cP:F:fdS:LDC:")) != EOF)
    switch(c)
      {
      case 'h': 
	usage(0);
	
      case 'd':
	cc_debug_set_level(cc_debug_level() + 1);
	break;

      case 'c':
	prog->remoteconf = 1;
	break;

      case 'C':
	cc_xfree(prog->server_comment);
	prog->server_comment = cc_xstrdup(optarg);
	break;

      case 'l':
	if (inet_aton(optarg, &la) == 0)
	  {
	    fprintf(stderr, "%s: Bad local address %s.\n", av0, optarg);
	    usage(-1);
	  }
	cc_xfree(prog->localaddr);
	prog->localaddr = cc_xstrdup(optarg);
	break;

      case 'L':
	prog->follow_symlinks++;
	break;

      case 'p': 
	prog->localport = atoi(optarg);
	if ((prog->localport < 1) || (prog->localport > 65535))
	  {
	    fprintf(stderr, "%s: Bad port number %s.\n", av0, optarg);
	    usage(-1);
	  }
	break;

      case 'f':
	prog->daemonize++;
	break;

      case 'F':
	cc_xfree(prog->pidfile);
	prog->pidfile = cc_xstrdup(optarg);
	break;

      case 'P':
	cc_xfree(prog->user_password);
	prog->user_password = cc_xstrdup(optarg);
	memset(optarg, ' ', strlen(optarg));
	break;

      case 'r':
	cc_xfree(prog->rootdir);
	if (strcmp(optarg, "-") != 0)
	  prog->rootdir = cc_xstrdup(optarg);
	else
	  prog->rootdir = NULL;
	break;

      case 'D':
	prog->discovery_limit = -1;
	break;

      case 'S':
	if (strchr(optarg, '=') != NULL)
	  {
	    share = cc_xcalloc(1, sizeof (*share));
	    share->dir = cc_xstrdup(strchr(optarg, '=') + 1);
	    share->mountpoint = cc_xstrdup(optarg);
	    tmp = strchr(share->mountpoint, '=');
	    *tmp = '\0';
	    if ((share->dir[0] == '\0') || (share->mountpoint[0] == '\0'))
	      {
		fprintf(stderr, "%s: Invalid share definition \"%s\".\n", av0, optarg);
		exit(-1);
	      }
	    share->next = prog->shares;
	    prog->shares = share;
	  } else {
	    fprintf(stderr, "%s: Invalid share definition \"%s\".\n", av0, optarg);
	    exit(-1);
	  }
	break;

      case 'u': 
	cc_xfree(user);
	user = cc_xstrdup(optarg);
	pw = getpwnam(user);
	if (pw == NULL)
	  {
	    fprintf(stderr, "%s: Unknown user %s.\n", av0, user);
	    exit(-1);
	  }
	uid = pw->pw_uid;
	gid = pw->pw_gid;
	break;

      default:
	fprintf(stderr, "%s: Bad command line option -%c.\n", av0, optopt);
	usage(-1);
      }

  CC_DEBUG(6, ("sizeof (off_t) = %d%s", (int)(sizeof (off_t)), (sizeof (off_t) != 8) ? "!!!" : ""));

  if (prog->rootdir != NULL)
  {
    int tmp;

    /* Let's allow symlinks here always */
    tmp = prog->follow_symlinks;
    prog->follow_symlinks = 1;
    if (! cc_xstream_path_is_directory(prog, prog->rootdir))
      {
	fprintf(stderr, "%s: Invalid root directory \"%s\".\n", av0, prog->rootdir);
	exit(1);
      }
    prog->follow_symlinks = tmp;
  }
  for (share = prog->shares; share != NULL; share = share->next)
    {
      if (! cc_xstream_path_is_directory(prog, share->dir))
	{
	  fprintf(stderr, "%s: Invalid share directory \"%s\".\n", av0, share->dir);
	  exit(1);
	}
    }

  prog->configuration = cc_xstream_global_configuration_init(prog);

  prog->s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (prog->s < 0)
    {
      fprintf(stderr, "%s: Can't create server socket.\n", av0);
      exit(1);
    }

#ifdef SO_REUSEADDR
  c = 1;
  setsockopt(prog->s, SOL_SOCKET, SO_REUSEADDR, (char *)&c, sizeof (c));
#endif /* SO_REUSEADDR */

#ifdef SO_REUSEPORT
  c = 1;
  setsockopt(prog->s, SOL_SOCKET, SO_REUSEPORT, (char *)&c, sizeof (c));
#endif /* SO_REUSEPORT */

  memset(&sa, 0, sizeof (sa));
  sa.sin_family = AF_INET;
  sa.sin_addr = la;
  sa.sin_port = htons(prog->localport);
  sa_len = sizeof (sa);
  if (bind(prog->s, (struct sockaddr *)&sa, sa_len) < 0)
    {
      fprintf(stderr, "%s: Can't bind server socket to port %d.\n", av0, prog->localport);
      exit(1);
    }

  if (prog->discovery_limit >= 0)
    {
      prog->bs = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
      if (prog->bs < 0)
	{
	  fprintf(stderr, "%s: Can't create server socket.\n", av0);
	  exit(1);
	}

#ifdef SO_REUSEADDR
      c = 1;
      setsockopt(prog->bs, SOL_SOCKET, SO_REUSEADDR, (char *)&c, sizeof (c));
#endif /* SO_REUSEADDR */
#ifdef SO_REUSEPORT
      c = 1;
      setsockopt(prog->bs, SOL_SOCKET, SO_REUSEPORT, (char *)&c, sizeof (c));
#endif /* SO_REUSEPORT */
#ifdef SO_BROADCAST
      c = 1;
      setsockopt(prog->bs, SOL_SOCKET, SO_BROADCAST, (char *)&c, sizeof (c));
#endif /* SO_BROADCAST */

      memset(&sa, 0, sizeof (sa));
      sa.sin_family = AF_INET;
#if 1
      /* This is actually wrong, but broadcast listener seems to be 
	 deaf (in linux) if local address is defined here. */
      sa.sin_addr.s_addr = htonl(INADDR_ANY);
#else
      sa.sin_addr = la;
#endif
      sa.sin_port = htons(prog->localport);
      sa_len = sizeof (sa);
      if (bind(prog->bs, (struct sockaddr *)&sa, sa_len) < 0)
	{
	  fprintf(stderr, "%s: Can't bind server discovery socket to port %d.\n", av0, prog->localport);
	  exit(1);
	}
    }

  CC_DEBUG(1, ("%s starting up.", CC_XSTREAM_VERSION_STRING));

  if (prog->daemonize)
    {
      pid_t p;

      switch (p = fork())
	{
	case -1:
	  fprintf(stderr, "%s: Can't fork to background.\n", av0);
	  exit(1);

	case 0:
	  freopen("/dev/null", "r", stdin);
	  freopen("/dev/null", "w", stdout);
	  freopen("/dev/null", "w", stderr);
	  break;

	default:
	  CC_DEBUG(1, ("Forked to background.  Child pid is %d.", (int)p));
	  close(prog->s);
	  if (prog->bs > 0)
	    close(prog->bs);
	  close(0);
	  close(1);
	  close(2);
	  exit(0);
	}
    }

  if (prog->pidfile != NULL)
    {
      FILE *f;
      
      f = fopen(prog->pidfile, "w");
      if (f != NULL)
	{
	  fprintf(f, "%d\n", (int)getpid());
	  fclose(f);
	}
    }

  if (user != NULL)
    {
      if (setgid(gid) != 0)
        {
          fprintf(stderr, "%s: Can't set user group.\n", av0);
          exit(1);
        }
      if (setegid(gid) != 0)
        {
          fprintf(stderr, "%s: Can't set effective user group.\n", av0);
          exit(1);
        }
      if (setuid(uid) != 0)
        {
          fprintf(stderr, "%s: Can't set user id.\n", av0);
          exit(1);
        }
      if (seteuid(uid) != 0)
        {
          fprintf(stderr, "%s: Can't set effective user id.\n", av0);
          exit(1);
        }
    }

  if (listen(prog->s, 8) != 0)
    {
      fprintf(stderr, "%s: Can't set server socket to listen state.\n", av0);
      exit(1);
    }

  cc_xstream_server_make_non_blocking(prog->s);
  if (prog->bs > 0)
    cc_xstream_server_make_non_blocking(prog->bs);
    
  while (1)
    {
      if (prog->discovery_reply == NULL)
	{
	  tv.tv_sec = 55 + random() % 11;
	  tv.tv_usec = random() % 1000000;
	}
      else
	{
	  tv.tv_sec = 0;
	  tv.tv_usec = random() % 20000;
	}
      FD_ZERO(&rs);
      FD_ZERO(&ws);
      FD_SET(prog->s, &rs);
      mf = prog->s;
      if (prog->bs > 0)
	{
	  FD_SET(prog->bs, &rs);
	  if (prog->bs > prog->s)
	    mf = prog->bs;
	}
      for (conn = prog->connection; conn != NULL; conn = conn->next)
	{
	  if (conn->outbuf_len > 0)
	    {
	      FD_SET(conn->s, &ws);
	    }
	  else
	    {
	      FD_SET(conn->s, &rs);
	    }
	  if (conn->s > mf)
	    mf = conn->s;
	}
      CC_DEBUG(20, ("Entering select with timeout value %d.%06d", (int)tv.tv_sec, (int)tv.tv_usec));
      rv = select(mf + 1, &rs, &ws, NULL, &tv);
      CC_DEBUG(20, ("Select call returns %d", (int)rv));
      if (rv >= 0)
	{
	  CC_DEBUG(20, ("Sending pending discovery replies"));
	  while (prog->discovery_reply != NULL)
	    {
	      reply = prog->discovery_reply;
	      prog->discovery_reply = reply->next;
	      sendto(prog->bs, reply->packet, reply->packet_len, 0, reply->packet_addr, reply->packet_addr_len);
	      cc_xfree(reply->packet);
	      cc_xfree(reply->packet_addr);
	      cc_xfree(reply);
	    }
	}
      switch (rv)
	{
	case -1:
	  /* Error */
	  fprintf(stderr, "%s: Select failed with error %d.", av0, errno);
	  exit(3);
	  
	case 0:
	  /* Timeout */
	  break;
	  
	default:
	  prev = NULL;
	  for (conn = prog->connection; conn != NULL; conn = ((conn != NULL) ? conn->next : NULL))
	    {
	      if ((conn->outbuf_len > 0) && FD_ISSET(conn->s, &ws))
		{
		  rv = write(conn->s, conn->outbuf, conn->outbuf_len);
		  CC_DEBUG(20, ("Write call returns %d", (int)rv));
		  if (rv <= 0)
		    {
		      close(conn->s);
		      conn->s = 0;
		      if (prev != NULL)
			prev->next = conn->next;
		      else
			prog->connection = conn->next;
		      cc_xstream_connection_free(conn);
		      conn = prev;
		    }
		  else if (rv > 0)
		    {
		      if (rv < conn->outbuf_len)
			{
			  memmove(conn->outbuf, conn->outbuf + rv, conn->outbuf_len - rv);
			  conn->outbuf_len -= rv;
			}
		      else
			{
			  cc_xfree(conn->outbuf);
			  conn->outbuf = NULL;
			  conn->outbuf_len = 0;
			}
		    }
		}
	      else if (FD_ISSET(conn->s, &rs))
		{
		  if (conn->inbuf == NULL)
		    {
		      conn->inbuf = cc_xmalloc(1024);
		      conn->inbuf_len = 0;
		    }
		  else
		    {
		      conn->inbuf = cc_xrealloc(conn->inbuf, conn->inbuf_len + 1024);
		    }
		  rv = read(conn->s, conn->inbuf + conn->inbuf_len, 1024);
		  CC_DEBUG(20, ("Read call returns %d", (int)rv));
		  if (rv <= 0)
		    {
		      CC_DEBUG(1, ("Connection closing"));
		      close(conn->s);
		      conn->s = 0;
		      if (prev != NULL)
			prev->next = conn->next;
		      else
			prog->connection = conn->next;
		      cc_xstream_connection_free(conn);
		      conn = prev;
		    }
		  else if (rv > 0)
		    {
		      conn->inbuf_len += rv;
		      while ((rv = cc_xstream_consume_input(conn)) > 0)
			/*NOTHING*/;
		      if (rv < 0)
			{
			  CC_DEBUG(1, ("Terminating connection because of protocol violation"));
			  close(conn->s);
			  conn->s = 0;
			  if (prev != NULL)
			    prev->next = conn->next;
			  else
			    prog->connection = conn->next;
			  cc_xstream_connection_free(conn);
			  conn = prev;
			}
		    }
		}
	      prev = conn;
	    }
	  if (FD_ISSET(prog->s, &rs))
	    {
	      /* New connection */
	      CC_DEBUG(1, ("New connection"));
	      conn = cc_xstream_connection_allocate();
	      conn->prog = prog;
	      conn->authenticated = (prog->user_password == NULL);
	      conn->configuration = cc_xstream_local_configuration_init(conn);
	      sa_len = sizeof (sa);
	      conn->s = accept(prog->s, (struct sockaddr *)&sa, &sa_len);
	      if (conn->s >= 0)
		{
		  cc_xstream_server_make_non_blocking(conn->s);
		  conn->next = prog->connection;
		  prog->connection = conn;
		  cc_xstream_write_data(conn, CC_XSTREAM_PROTOCOL_INIT "\n", strlen(CC_XSTREAM_PROTOCOL_INIT "\n"));
		}
	      else
		{
		  /* Accept failed */
		  conn->s = 0;
		  cc_xstream_connection_free(conn);
		}
	    }
	  if ((prog->bs > 0) && FD_ISSET(prog->bs, &rs))
	    {
	      unsigned char ch;
	      CcBufferRec buf[1];
	      int len;
	      struct sockaddr_in sa;
	      socklen_t sa_len;
	      unsigned long p_len, p_handle;
	      char *p_version = NULL;

	      memset(&sa, 0, sizeof (sa));
	      sa_len = sizeof (sa);
	      cc_buffer_init(buf);
	      cc_buffer_append_space(buf, 2000);
	      len = recvfrom(prog->bs, cc_buffer_ptr(buf), cc_buffer_len(buf), 0, (struct sockaddr *)(&sa), &sa_len);
	      if (len > 0)
		{
		  cc_buffer_consume_end(buf, cc_buffer_len(buf) - len);
		  if (cc_xstream_buffer_decode_int(buf, &p_len) &&
		      (p_len == cc_buffer_len(buf)) &&
		      cc_xstream_buffer_decode_byte(buf, &ch) &&
		      ((CcXstreamPacket)ch == CC_XSTREAM_XBMSP_PACKET_SERVER_DISCOVERY_QUERY) &&
		      cc_xstream_buffer_decode_int(buf, &p_handle) &&
		      cc_xstream_buffer_decode_string(buf, (unsigned char **)(&p_version), NULL) &&
		      (cc_buffer_len(buf) == 0))
		    {
		      CC_DEBUG(10, ("Received a discovery datagram from remote client \"%s\"", p_version));
		      reply = cc_xcalloc(1, sizeof(*reply));
		      cc_xstream_server_discovery_reply_packet(prog, p_handle, &(reply->packet), &(reply->packet_len));
		      reply->packet_addr = cc_xmemdup(&sa, sa_len);
		      reply->packet_addr_len = sa_len;
		      reply->next = prog->discovery_reply;
		      prog->discovery_reply = reply;
		    }
		  else
		    {
		      CC_DEBUG(5, ("Received an invalid datagram of %d bytes", len));
		    }
		  cc_xfree(p_version);
		}
	      else
		{
		  CC_DEBUG(5, ("Unable to read datagram"));
		}
	      cc_buffer_uninit(buf);
	    }
	}
    }
  /*NOTREACHED*/
  exit(0);
}

/* eof (ccxstream.c) */
