/* -*- 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) || (conn->open_file_handle[fh] <= 0) )
{
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]);
if ( (sz < rlen) && ( (ferror(conn->f[fh]) != 0) || (feof(conn->f[fh]) == 0) ) )
{
fclose(conn->f[fh]);
conn->f[fh] = NULL;
cc_xstream_send_error(conn, id, CC_XSTREAM_XBMSP_ERROR_ILLEGAL_SEEK, "File read failed.");
}
else
{
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 */
#ifdef TCP_NODELAY
c = 1;
setsockopt(prog->s, IPPROTO_TCP, TCP_NODELAY, (char *)&c, sizeof (c));
#endif /* TCP_NODELAY */
#ifdef SO_KEEPALIVE
c = 1;
setsockopt(prog->s, SOL_SOCKET, SO_KEEPALIVE, (char *)&c, sizeof (c));
#endif /* SO_KEEPALIVE */
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 */
#ifdef SO_KEEPALIVE
c = 1;
setsockopt(prog->bs, SOL_SOCKET, SO_KEEPALIVE, (char *)&c, sizeof (c));
#endif /* SO_KEEPALIVE */
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) */
syntax highlighted by Code2HTML, v. 0.9.1