/* -*- 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, ""); cc_buffer_append_string(buf, ""); tmp = cc_xstream_xml_encode(CC_XSTREAM_VERSION_STRING); cc_buffer_append_string(buf, tmp); cc_xfree(tmp); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, "directory"); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); 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, ""); cc_buffer_append_string(buf, ""); 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, ""); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); 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, ""); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, "directory"); cc_buffer_append_string(buf, ""); tmp = cc_xstream_xml_encode(CC_XSTREAM_CONFIG_DIR_INFO); cc_buffer_append_string(buf, tmp); cc_xfree(tmp); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); 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, ""); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, opt->name); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); 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, ""); cc_buffer_append_string(buf, ""); tmp = cc_xstream_xml_encode(opt->itemname); cc_buffer_append_string(buf, tmp); cc_xfree(tmp); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); if (opt->readonly) cc_buffer_append_string(buf, "readonlyoption"); else cc_buffer_append_string(buf, "option"); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); tmp = cc_xstream_xml_encode(opt->value); cc_buffer_append_string(buf, tmp); cc_xfree(tmp); cc_buffer_append_string(buf, ""); if (opt->legal_values != NULL) { cc_buffer_append_string(buf, ""); for (x = opt->legal_values; x != NULL; x = x->next) { cc_buffer_append_string(buf, ""); tmp = cc_xstream_xml_encode(x->s); cc_buffer_append_string(buf, tmp); cc_xfree(tmp); cc_buffer_append_string(buf, ""); } cc_buffer_append_string(buf, ""); } cc_buffer_append_string(buf, ""); tmp = cc_xstream_xml_encode(opt->info); cc_buffer_append_string(buf, tmp); cc_xfree(tmp); cc_buffer_append_string(buf, ""); cc_buffer_append_string(buf, ""); 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) */