Changeset - c318109520cc
[Not reviewed]
default
0 18 0
Nathan Brink (binki) - 15 years ago 2010-08-07 20:08:12
ohnobinki@ohnopublishing.net
User authentication and some access checking.
18 files changed with 623 insertions and 299 deletions:
0 comments (0 inline, 0 general)
doc/architecture.txt
Show inline comments
 
@@ -37,30 +37,34 @@ Concepts:
 
      restrictions of ordering of frame numbers except that they are not to be negative. There
 
      need be no sequencing of frame numbers either. Thus, a frame identification URL would look
 
      like distren://<servername>/<jobid>/frame/<frameid>
 
  - size: A frame hopefully represents a smaller unit of work in terms of the rendering
 
      back-end's capabilities. For example, POV-Ray's CLI can initiate and carry out the rendering
 
      of an entire animation. But distren would hopefully be able to provide a clean method for
 
      rendering each individual frame and bringing the resulting set of files back to the user
 
      in a much shorter time than a single computer could on its own. Many smaller and distinct
 
      is key to a project being benefited by distren.
 
  - dependencies: One cannot escape from frame's completion potentially requiring the completion
 
      of another. Thus, each frame's record must explicitly list the URLs of frames that need to
 
      be completed prior to the said frame. For a server to complete a frame dependent on other
 
      frames, those other frames must be transfered to the first server and made available.
 
  - packaging: To render a single frame and move it about somewhere is normally trivial. However,
 
      one frame or rendering unit of a given backend may produce multiple files. For this reason
 
      and for further uniformity and simplification, the data files representing one frame shall
 
      be transferred using the tarball format.
 

	
 
- client: A distren client is able to submit, query state of, and download completed frames of
 
      jobs registered in a server.
 
  - servers: A server, though having many more functions, shall be able to also perform the list
 
      of actions a client may perform.
 

	
 
- user: A user is an entity which is given access to a distren server.
 
  - user identifiction: As users are per-server, a user shall be identified by the name of the
 
      server he has an account on combined with his handle. A tilde prefixes the username to
 
      differentiate this URL from a job's URL.
 
      distren://<servername>/~<username> , like distren://ohnopub.net/~ohnobinki
 

	
 
- file: There are different uses of files above and distributed rendering requires file distribution.
 
  - file identification: Every file mentioned above was at least in the context of a job. Thus,
 
      file identification numbers shall be assigned in the context of a job identification number.
 
      They shall, however, be numeric.
 
      distren://<servername>/<jobid>/file/<fileid>
etc/distrencommon.conf
Show inline comments
 
@@ -7,27 +7,28 @@
 
  currently, server's are only supported rudimentarily. If a job has n frames and there are s servers, n / (s + 1) frames will be sent to each individual server and also rendered on the machine the job was submitted to. There will be no recursive distribution of jobs, but I want to make that possible in the future. AND, I want job IDs to have significance based on 1: the files, 2: the versions of software used to render just like git, mercurial, or bazaar's commit IDs have significance. This will allow global distribution of renderjobs without requiring central servers to coordinate the jobs - a network only need be distributed. And complex algorithms based on timeouts and completion of jobs should allow slow servers to reassign jobs to fast ones and, possibly, find shorter routes to return the resulting images to the original job submitter. Multiple server declarations are currently included, but an implementation for multiple servers is lacking.
 
*/
 

	
 
server protofusion
 
{
 
  hostname = "protofusion.org"
 
  username = "distrenc"
 
  method = "ssh"
 
  types = {"submit", "distribution"} /* submit means it accepts jobs, distribution means it can host files */
 
}
 

	
 
server ohnopublishing
 
{
 
  hostname = "ohnopublishing.net"
 
  username = "distrenc"
 
  method = "ssh"
 
  types = {"distribution"}
 
}
 

	
 

	
 
server localhost
 
{
 
  hostname = "localhost"
 
  username = "test"
 
  password = "xyzzy123"
 
  method = "tcp"
 
  types = {"submit", "distribution", "master"}
 
}
src/client/libdistren.c
Show inline comments
 
@@ -26,94 +26,142 @@
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 
#include "common/request.h"
 

	
 
#include "libdistren.h"
 

	
 
#include <errno.h>
 
#include <libgen.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 

	
 
/**
 
 * Handle common cleanup actions for distren_init().
 
 */
 
static void distren_init_cleanup(distren_t distren);
 

	
 
int distren_init(distren_t *handle)
 
{
 
  int tmp;
 

	
 
  struct distren_request *req;
 
  void *data;
 

	
 
  const char *username;
 
  const char *pass;
 

	
 
  if(!handle)
 
    return 1;
 

	
 
  *handle = malloc(sizeof(struct distren));
 
  if(!*handle)
 
    return 1;
 

	
 
  memset(*handle, 0, sizeof(struct distren));
 

	
 
  /* now the environment is ready for general use */
 
  if(_distren_getoptions(*handle))
 
    {
 
      fprintf(stderr, "error getting configuration\n");
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 

	
 
  tmp = remoteio_authinfo_get((*handle)->options->remoteio,
 
			      (*handle)->server,
 
			      &username,
 
			      &pass);
 
  if(tmp
 
     || !username
 
     || !pass)
 
    {
 
      fprintf(stderr, "error: unable to find information necessary to connect to the server named ``%s'', please check your configuration file.\n",
 
	      (*handle)->server);
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 

	
 
  tmp = remoteio_open_server(&(*handle)->rem,
 
			     (*handle)->options->remoteio,
 
			     (remoteio_read_handle_func_t)&libdistren_remoteio_read_handle,
 
			     *handle,
 
			     (remoteio_close_handle_func_t)&libdistren_remoteio_close_handle,
 
			     (*handle)->server);
 
  if(tmp)
 
    {
 
      fprintf(stderr, "error: unable to connect to server\n");
 

	
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 

	
 
  /* send off a DISTREN_REQUEST_VERSION to the server */
 
  tmp = distren_request_version(&req, &data, DISTREN_SERVERTYPE_CLIENT, PACKAGE_STRING);
 
  if(tmp)
 
    {
 
      fprintf(stderr, "error: unable to allocate request");
 

	
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 
  distren_request_send((*handle)->rem, req, data);
 
  distren_request_free_with_data(req, data);
 

	
 
  tmp = distren_request_pass(&req, &data, username, pass);
 
  if(tmp)
 
    {
 
      fprintf(stderr, "error: unable to allocate request");
 

	
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 
  distren_request_send((*handle)->rem, req, data);
 
  distren_request_free_with_data(req, data);
 

	
 
  /*
 
   * There is no response to the DISTREN_REQUEST_PASS packet. However,
 
   * we want to ensure that the username/password we sent are valid
 
   * before returning to the claler. Thus, we here send a PING
 
   * packet. If we get the DISTREN_REQUEST_PONG, we know we're
 
   * authenticated. Otherwise, we'll receive a quit/discconect packet.
 
   */
 
  tmp = distren_request_poing(&req, &data, 1, "auth test", strlen("auth test"));
 
  if(tmp)
 
    {
 
      fprintf(stderr, "error: Unable to allocate a DISTREN_REQUEST_PING\n");
 

	
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 
  distren_request_send((*handle)->rem, req, data);
 
  distren_request_free_with_data(req, data);
 

	
 
  /* flush out the above packets. */
 
  while((*handle)->rem
 
	&& (*handle)->state == DISTREN_STATE_VERSION)
 
      multiio_poll((*handle)->multiio, 500);
 
	&& ((*handle)->state == DISTREN_STATE_VERSION
 
	    || (*handle)->state == DISTREN_STATE_AUTH))
 
    multiio_poll((*handle)->multiio, 500);
 

	
 
  if(!(*handle)->rem)
 
    {
 
      distren_init_cleanup(*handle);
 
      return 1;
 
    }
 

	
 
  return 0;
 
}
 

	
 
static void distren_init_cleanup(distren_t distren)
 
{
 
  if(distren->rem)
 
    remoteio_close(distren->rem);
 
  distren->rem = NULL;
 
  distren_free(distren);
 
}
 

	
 
#ifdef _WIN32
 
#define FOPEN_BINARY "b"
 
#else
 
#define FOPEN_BINARY ""
 
#endif
 
int distren_submit_file(distren_t handle, distren_job_t *job, const char *filename)
src/client/libdistren.h
Show inline comments
 
@@ -22,49 +22,48 @@
 

	
 
/*
 
  Private definitions for libdistren.
 
 */
 

	
 
#include "distren.h"
 

	
 
#include "common/multiio.h"
 
#include "common/options.h"
 
#include "common/remoteio.h"
 

	
 
#include <stdint.h>
 

	
 
enum distren_state
 
  {
 
    /**
 
     * client is waiting for a VERSION packet from the server.
 
     */
 
    DISTREN_STATE_VERSION,
 
    /**
 
     * We are waiting to be authenticated.
 
     */
 
    DISTREN_STATE_AUTH,
 
    DISTREN_STATE_NORMAL,
 
    DISTREN_STATE_UPLOADING,
 
  };
 

	
 
struct distren
 
{
 
  struct options_common *options;
 
  char *server;
 

	
 
  enum distren_state state;
 

	
 
  /*
 
   * If rem is NULL, we're not connected to the server. This is the
 
   * way to detect a communication error.
 
   */
 
  struct remoteio *rem;
 
  /*
 
   * for libdistren_remoteio_read_handle(): determine whether or not
 
   * we've passed through the server's hacky MOTD
 
   */
 
  short done_ad;
 

	
 
  /*
 
   * The servertype bitmask of the remote server.
 
   */
 
  uint32_t servertype;
src/client/libdistren_request.c
Show inline comments
 
@@ -4,141 +4,157 @@
 
 * This file is a part of DistRen.
 
 *
 
 * DistRen is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * DistRen is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
#include "common/config.h"
 

	
 
#include "libdistren.h"
 

	
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 
#include "common/request.h"
 

	
 
#include <string.h>
 

	
 
static void handle_ping(struct remoteio *rem, struct distren_request *req, void *req_data);
 
static void handle_pong(distren_t distren, struct remoteio *rem, struct distren_request *req, void *req_data);
 
static void handle_version(distren_t distren, struct distren_request *req, void *req_data);
 
static void handle_disconnect(distren_t distren, struct remoteio *rem, struct distren_request *req, void *req_data);
 

	
 
size_t libdistren_remoteio_read_handle(struct remoteio *rem, void *garbage, void *buf, size_t len, distren_t distren)
 
{
 
  size_t to_return;
 
  size_t last_len;
 
  short err;
 

	
 
  struct distren_request *req;
 
  void *req_data;
 

	
 
  to_return = 0;
 
  while(!distren->done_ad)
 
    {
 
      if(!len)
 
	return to_return;
 

	
 
      putchar(*(char *)buf);
 
      if(*(char *)buf == '\n')
 
	distren->done_ad = 1;
 

	
 
      len --;
 
      to_return ++;
 
      buf ++;
 
    }
 

	
 
  /* hack to get into the loop at all ;-) */
 
  last_len = 1; 
 
  while(last_len)
 
    {
 
      last_len = distren_request_handle(NULL, buf, len, &req, &req_data, &err);
 
      if(err)
 
	{
 
	  remoteio_close(rem);
 
	  return to_return;
 
	}
 
      if(!last_len)
 
	return to_return;
 

	
 
      switch((enum distren_request_type)req->type)
 
	{
 
	case DISTREN_REQUEST_PING:
 
	  handle_ping(rem, req, req_data);
 
	  break;
 

	
 
	case DISTREN_REQUEST_PONG:
 
	  handle_pong(distren, rem, req, req_data);
 
	  break;
 

	
 
	case DISTREN_REQUEST_VERSION:
 
	  handle_version(distren, req, req_data);
 
	  break;
 

	
 
	case DISTREN_REQUEST_DISCONNECT:
 
	  handle_disconnect(distren, rem, req, req_data);
 
	  break;
 

	
 
	default:
 
	  /*
 
	   * we don't implement everything because we don't need to do
 
	   * so. But, we should complain when we get something we
 
	   * don't recognize because... server protocols change
 
	   * ;-). Oh, and when I'm first writing this, this
 
	   * block will be hit a lot ;-).
 
	   */
 
	  fprintf(stderr, "Unrecognized request type: %lu\n", (unsigned long)req->type);
 
	  break;
 
	}
 

	
 
      distren_request_free(req);
 
      buf += last_len;
 
      len -= last_len;
 
    }
 

	
 
  return to_return;
 
}
 

	
 
void libdistren_remoteio_close_handle(void *garbage, distren_t distren)
 
{
 
  distren->rem = NULL;
 
}
 

	
 

	
 
/* handlers */
 

	
 
static void handle_ping(struct remoteio *rem, struct distren_request *req, void *req_data)
 
{
 
  struct distren_request *pong_req;
 
  void *pong_req_data;
 

	
 
  distren_request_poing(&pong_req, &pong_req_data, 0, req_data, req->len);
 
  distren_request_send(rem, pong_req, pong_req_data);
 
  distren_request_free_with_data(pong_req, pong_req_data);
 
}
 

	
 
static void handle_pong(distren_t distren, struct remoteio *rem, struct distren_request *req, void *req_data)
 
{
 
  const char *auth_test_str = "auth test";
 

	
 
  if(req->len == strlen(auth_test_str)
 
     && !strncmp(req_data, auth_test_str, req->len))
 
    distren->state = DISTREN_STATE_NORMAL;
 
}
 

	
 
static void handle_version(distren_t distren, struct distren_request *req, void *req_data)
 
{
 
  struct distren_request_version version;
 
  int tmp;
 

	
 
  tmp = distren_request_parse_version(req, req_data, &version);
 
  if(tmp)
 
    {
 
      fprintf(stderr, "error: Invalid DISTREN_REQUEST_VERSION, disconnecting from server\n");
 
      /*
 
       * our remoteio_close handler sets distren->rem to NULL, thus we
 
       * don't need to return an error code.
 
       */
 
      remoteio_close(distren->rem);
 
      return;
 
    }
 
  distren->servertype = version.servertype;
 
  distren->state = DISTREN_STATE_AUTH;
 

	
 
  fprintf(stderr, "info: connected to a server running %s\n", version.package_string);
 
  if(version.servertype & DISTREN_SERVERTYPE_CLIENT)
 
    fprintf(stderr, "\tis a client\n");
 
  if(version.servertype & DISTREN_SERVERTYPE_SUBMIT)
 
    fprintf(stderr, "\taccepts frame submissions\n");
src/common/libremoteio.h
Show inline comments
 
@@ -28,48 +28,49 @@
 
#include <queue.h>
 

	
 
/**
 
  private declarations for remoteio, to be included by options.c and remoteio.c
 
 */
 

	
 
enum remoteio_method
 
  {
 
    REMOTEIO_METHOD_SSH = 0,
 
#ifndef WINDOWS
 
    REMOTEIO_METHOD_UNIX = 1,
 
#endif
 
    REMOTEIO_METHOD_TCP = 2,
 
    /*    REMOETIO_METHOD_XMLRPC */ /*< maybe someday */
 
    /** method for the remoteio_open_socket() function, where we don't call open() ourselves: */
 
    REMOTEIO_METHOD_SOCKET = 3,
 
    REMOTEIO_METHOD_MAX = 4 /*< This is a number used to check the consitency of remoteio_server structs */
 
  };
 

	
 
struct remoteio_server
 
{
 
  char *name; /*< The friendly named passed to remoteio_open() */
 
  char *hostname;
 
  char *username;
 
  char *password;
 
  enum remoteio_method method;
 
  unsigned int types; /*< See ``Server types'' in protocol.h */
 
};
 

	
 
struct remoteio_opts
 
{
 
  char *ssh_command;
 
  list_t servers;  /* type: (struct remoteio_server *) */
 
  /* store the multiio context for general use */
 
  multiio_context_t multiio;
 
  /* store remoteio's socket_type */
 
  multiio_socket_type_t socket_type;
 

	
 
  /* an argument for the remoteio_read_handle_func_t */
 
  void *generic_handler_data;
 
};
 

	
 
/**
 
 * Used to describe the nature of the data stored in the
 
 * outbound and message queues that power remoteio_write()
 
 * and remoteio_read() (?).
 
 */
 
struct remoteio_packet
 
{
src/common/options.c
Show inline comments
 
@@ -152,48 +152,49 @@ int options_init(int argc, char *argv[],
 
      free(configfile);
 
      free((*allopts)->data);
 
      free(*allopts);
 
      return 1;
 
    }
 
  memset((*allopts)->remoteio, '\0', sizeof(struct remoteio_opts));
 
  (*allopts)->remoteio->ssh_command = strdup("ssh");
 
  /** @TODO: check for NULL return above -- a segfault dereferencing NULL is more fun than such a check ;-) */
 
  (*allopts)->remoteio->multiio = multiio;
 
  multiio_socket_type_register(multiio, &(*allopts)->remoteio->socket_type);
 

	
 
  cfg_opt_t common_opts[] =
 
    {
 
      CFG_SIMPLE_STR("ssh-command", &(*allopts)->remoteio->ssh_command),
 
      CFG_END()
 
    };
 

	
 
  /**
 
    In these arrays, I should replace NULL with a pointer to a string in the struct which should hold the result of confuse processing an option. This is partially because confuse will not free the values it parses.
 
    the server sections in the distrencommon.conf describe different servers that the client may connect to
 
   */
 
  cfg_opt_t server_opts[] =
 
    {
 
      CFG_STR("username", NULL, CFGF_NONE),
 
      CFG_STR("password", NULL, CFGF_NONE),
 
      CFG_STR("hostname", NULL, CFGF_NONE),
 
      CFG_STR("method", "ssh", CFGF_NONE),
 
      CFG_STR_LIST("types", NULL, CFGF_NONE),
 
      CFG_END()
 
    };
 

	
 
  cfg_opt_t opts[] =
 
    {
 
      CFG_SEC("common",
 
	      common_opts,
 
	      CFGF_NONE),
 
      CFG_SEC("server",
 
	      server_opts,
 
	      CFGF_MULTI | CFGF_TITLE),
 
      CFG_SEC(myname,
 
	      myopts,
 
	      CFGF_NONE),
 
      CFG_FUNC("include",
 
	       &cfg_include),
 
      CFG_END()
 
    };
 

	
 

	
 

	
src/common/protocol.h
Show inline comments
 
@@ -40,223 +40,266 @@
 
#define DISTREN_SERVERTYPE_CLIENT (0x8)
 

	
 
/**
 
 * This file defines the constants and structs that distren clients
 
 * and server use to converse.
 
 */
 

	
 

	
 
/**
 
 * List of request types and metadata.
 
 */
 
enum distren_request_type
 
  {
 
    /**
 
     * identifies the version of software being used by the sender and
 
     * tells if it is a client or server. Sends PACKAGE_STRING as well
 
     * as informing the other server what type of server this is.
 
     *
 
     * DATA: struct distren_request followed by PACKAGE_STRING. The length of PACKAGE_STRING must be no longer than 32 bytes.
 
     *
 
     * REQUIRED: ALL
 
    */
 
    DISTREN_REQUEST_VERSION = 1,
 
    /**
 
     * Test if the end has a live server.
 
     *
 
     * Only authenticated clients may use this request. Thus, if a
 
     * client wants to confirm that a DISTREN_REQUEST_PASS request was
 
     * successful, that client may send a DISTREN_REQUEST_PING
 
     * immediately after the DISTREN_REQUEST_PASS and then wait for
 
     * the PONG.
 
     *
 
     * DATA: up to 32 bytes of a PING cookie
 
     *
 
     * REQUIRED: ALL
 
     */
 
    DISTREN_REQUEST_PING = 2,
 
    /**
 
     * Response to DISTREN_REQUEST_PING.
 
     *
 
     * Unauthenticated clients may be penalized for responding to PING
 
     * requests. This is because a newly connecting client should
 
     * queue a DISTREN_REQUEST_VERSION and DISTREN_REQUEST_PASS
 
     * back-to-back before checking for and processing data from the
 
     * remote server.
 
     *
 
     * DATA: up to 32 bytes copied from a received PING cookie
 
     *
 
     * REQUIRED: ALL
 
     */
 
    DISTREN_REQUEST_PONG = 3,
 
    /**
 
     * The data is the a reason describing why the one end is
 
     * disconnecting. The other end should respectfully close()
 
     * the socket or the sender will timeout shortly.
 
     *
 
     * DATA: A string describing the reason for the connection's
 
     * termination.
 
     *
 
     * REQUIRED: ALL
 
     */
 
    DISTREN_REQUEST_DISCONNECT = 4,
 

	
 

	
 
    /**
 
     * Allow a client to identify itself using simple password
 
     * authentication.
 
     *
 
     * As there is no distren request which affirms a
 
     * DISTREN_REQUEST_PASS went through, clients may send a
 
     * DISTREN_REQUEST_PING and wait for the DISTREN_REQUETS_PONG they
 
     * want to block until they're authenticated.
 
     *
 
     * DATA: struct distren_request_pass
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_SUBMIT (for now, since
 
     * server2server links are only protected using password
 
     * authentication, all server types have to support this except
 
     * for the client.)
 
     */
 
    DISTREN_REQUEST_PASS = 5,
 

	
 
    /**
 
     * DATA: struct distren_request_submit
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_SUBMIT
 
     */
 
    DISTREN_REQUEST_SUBMIT = 5,
 
    DISTREN_REQUEST_SUBMIT = 6,
 

	
 
    /**
 
     * Inform the other party about a job.
 
     *
 
     * DATA: struct distren_request_jobinfo
 
     *
 
     * REQUIRED: ALL
 
     */
 
    DISTREN_REQUEST_JOBINFO = 6,
 
    DISTREN_REQUEST_JOBINFO = 7,
 

	
 
    /**
 
     * Request a DISTREN_REQUEST_JOBINFO
 
     *
 
     * DATA: struct distren_request_jobinfo_get
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_JOBINFO_GET = 7,
 
    DISTREN_REQUEST_JOBINFO_GET = 8,
 

	
 
    /**
 
     * Command the other party to render a frame
 
     *
 
     * DATA: struct distren_request_frame_render
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_RENDER
 
     */
 
    DISTREN_REQUEST_FRAME_RENDER = 8,
 
    DISTREN_REQUEST_FRAME_RENDER = 9,
 
    /**
 
     * Inform the receiver of the sender's state, such as frames being
 
     * rendered or jobs that need to be completed.
 
     *
 
     * DATA: struct distren_request_status
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_RENDER, DISTREN_SERVERTYPE_DISTRIBUTE, DISTREN_SERVERTYPE_CLIENT
 
     */
 
    DISTREN_REQUEST_STATUS = 9,
 
    DISTREN_REQUEST_STATUS = 10,
 

	
 
    /**
 
     * Request that the receiver send a DISTREN_REQUEST_FRAME_STATUS
 
     * packet. No data because the sending party might not beforehand
 
     * know what frames/jobs the receiving server is managing.
 
     *
 
     * DATA: (none)
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_RENDER
 
     */
 
    DISTREN_REQUEST_STATUS_GET = 10,
 
    DISTREN_REQUEST_STATUS_GET = 11,
 

	
 
    /**
 
     * Declare that a client is preparing to post a file using a
 
     * particular post-id and with a user-friendly filename.
 
     *
 
     * Yes, this is evil. Yes, this tries to replicate
 
     * well-established protocols like FTP and HTTP (which is
 
     * sometimes itself mistreated as FTP). Yes, this will be
 
     * buggy. Why is it needed? For my personal coding experience and
 
     * because I don't know off-hand of any libraries that allow a
 
     * file upload to be interleaved over an existing connection with
 
     * its own protocol. Over TCP? We aren't doing real-time streaming
 
     * or anything :-p.
 
     *
 
     * Possible future expansions: ``transparent'' zlib compression.
 
     *
 
     * DATA: struct distren_request_file_post_start
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_FILE_POST_START = 11,
 
    DISTREN_REQUEST_FILE_POST_START = 12,
 

	
 
    /**
 
     * Allow a client to upload a job tarball over a remoteio line.  A
 
     * client that wants to do this must take care not to overbuffer
 
     * his sendqueue so as to be able to respond to PING packets in
 
     * time. A server receiving such a message will want to write the
 
     * file as directly to disk as possible to avoid bogging the
 
     * server down in swap. This packet may only be sent if
 
     * DISTREN_REQUEST_FILE_POST_START has previously been sent.
 
     *
 
     * It is potential that if a server is DISTREN_SERVERTYPE_SUBMIT
 
     * but not also DISTREN_SERVERTYPE_DISTRIBUTE, that this request
 
     * would be relayed by the DISTREN_SERVERTYPE_SUBMIT server to a
 
     * DISTREN_SERVERTYPE_DISTRIBUTE server so that other clients can
 
     * obtain the file from the distribution server. In this case, the
 
     * file's URL is already known.
 
     *
 
     * Of course, having this sort of functionality at all is where
 
     * the nasty security issues start coming into play :-D.
 
     *
 
     * DATA: struct distren_request_file_post followed by a maximum of 131072 bytes (128kB).
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_FILE_POST = 12,
 
    DISTREN_REQUEST_FILE_POST = 13,
 

	
 
    /**
 
     * Marks a post-id's file as having completely uploaded. Provides
 
     * verification information so that the file's integrity may be
 
     * verified.
 
     *
 
     * DATA: struct distren_request_file_post_finish
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_SUBMIT, DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_FILE_POST_FINISH = 13,
 
    DISTREN_REQUEST_FILE_POST_FINISH = 14,
 

	
 
    /**
 
     * Request information about obtaining a file (such as a
 
     * cURL-compatible URL) based on a distren file URL.
 
     *
 
     * DATA: struct distren_request_file_find
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_FILE_FIND = 14,
 
    DISTREN_REQUEST_FILE_FIND = 15,
 

	
 
    /**
 
     * Provide information about obtaining a file (such as a URL).
 
     *
 
     * DATA: struct distren_request_file
 
     *
 
     * REQUIRED: DISTREN_SERVERTYPE_DISTRIBUTE
 
     */
 
    DISTREN_REQUEST_FILE = 15,
 
    DISTREN_REQUEST_FILE = 16,
 
  };
 

	
 
struct distren_request
 
{
 
  uint32_t magic;
 
  /* the length of the data associated with the packet excluding the header */
 
  uint32_t len;
 
  /** treat type as an enum distren_request_type using casting */
 
  uint32_t /* enum distren_request_type */ type;
 
};
 

	
 
#define DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN (32)
 
/**
 
 * A DISTREN_REQUEST_VERSION is started with a bitmask specification
 
 * of the DISTREN_SERVERTYPE_* values.
 
 */
 
struct distren_request_version
 
{
 
  uint32_t servertype;
 
  /* + 1 is for terminating NULL */
 
  char package_string[DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN + 1];
 
};
 

	
 
#define DISTREN_REQUEST_PASS_USERNAME_LEN (16)
 
#define DISTREN_REQUEST_PASS_PASS_LEN (32)
 
struct distren_request_pass
 
{
 
  char username[DISTREN_REQUEST_PASS_USERNAME_LEN];
 
  char pass[DISTREN_REQUEST_PASS_PASS_LEN];
 
};
 

	
 
#define DISTREN_REQUEST_FILE_POST_NAME_LEN (64)
 
struct distren_request_file_post_start
 
{
 
  /**
 
   * Uniquely identify this file upload (per connection).
 
   */
 
  uint32_t post_id;
 
  /**
 
   * The user-friendly filename
 
   */
 
  char filename[DISTREN_REQUEST_FILE_POST_NAME_LEN];
 
};
 

	
 
#define DISTREN_REQUEST_FILE_POST_DATA_LEN (1024*128)
 
struct distren_request_file_post
 
{
 
  /**
 
   * Uniquely identify this file upload (per connection).
 
   */
 
  uint32_t post_id;
 
};
 

	
 
struct distren_request_file_post_finish
 
{
src/common/remoteio.c
Show inline comments
 
@@ -120,52 +120,59 @@ int remoteio_config(cfg_t *cfg, struct r
 
				 opts->socket_type,
 
				 POLLOUT,
 
				 (multiio_event_handler_func_t)&_remoteio_handle_write,
 
				 opts);
 
  multiio_event_handler_register(opts->multiio,
 
				 opts->socket_type,
 
				 POLLIN,
 
				 (multiio_event_handler_func_t)&_remoteio_handle_read,
 
				 opts);
 

	
 
  opts->servers = list_init();
 
  if(!opts->servers)
 
    {
 
      fprintf(stderr, "@todo cleanup!\n");
 
      abort();
 
    }
 
  
 
  numservers = cfg_size(cfg, "server");
 
  for(counter = 0; counter < numservers; counter ++)
 
    {
 
      cfg_t *cfg_aserver;
 
      char *method;
 
      
 
      cfg_aserver = cfg_getnsec(cfg, "server", counter);
 

	
 
      aserver.name = NULL;
 
      aserver.hostname = NULL;
 
      aserver.username = NULL;
 
      aserver.password = NULL;
 
      
 
      aserver.name = strdup(cfg_title(cfg_aserver));
 
      aserver.hostname = strdup(cfg_getstr(cfg_aserver, "hostname"));
 
      aserver.username = strdup(cfg_getstr(cfg_aserver, "username"));
 
      if(cfg_getstr(cfg_aserver, "password"))
 
	aserver.password = strdup(cfg_getstr(cfg_aserver, "password"));
 

	
 
      aserver.method = REMOTEIO_METHOD_MAX;
 
      method = cfg_getstr(cfg_aserver, "method");
 
      for(counter2 = 0; funcmap[counter2].name; counter2 ++)
 
	if(strcmp(method, funcmap[counter2].name) == 0)
 
	  aserver.method = funcmap[counter2].method;
 
      if(aserver.method == REMOTEIO_METHOD_MAX)
 
	{
 
	  fprintf(stderr, "No such method as %s\n", method);
 
	  if(!haslisted_methods)
 
	    {
 
	      fprintf(stderr, "Available methods:\n");
 
	      for(counter2 = 0; funcmap[counter2].name; counter2 ++)
 
		fprintf(stderr, "\t%s\n", funcmap[counter2].name);
 
	      
 
	      haslisted_methods ++;
 
	    }
 
	  abort();
 
	}
 
      list_insert_after(opts->servers, &aserver, sizeof(struct remoteio_server));
 
    }
 
  
 
  return 0;
 
}
 
@@ -267,48 +274,68 @@ int remoteio_open_server(struct remoteio
 
    return 1;
 
  rem = *remoteio;
 

	
 
  tmp = funcmap[theserver->method].open_func(rem, theserver);
 
  if(tmp)
 
    {
 
      fprintf(stderr, "Error using method %s for server ``%s''", funcmap[theserver->method].name, servername);
 
      free(rem->inbuf.data);
 
      q_free(rem->outmsgs, QUEUE_NODEALLOC);
 
      free(rem);
 
      *remoteio = NULL;
 
      return tmp;
 
    }
 

	
 
  /**
 
   * @todo make this code slightly more generic... able to handle
 
   * execio's multi-sockets by letting execio register itself with
 
   * multiio instead of us registering here perhaps
 
   */
 
  multiio_socket_add(opts->multiio, rem->sock, opts->socket_type, rem, POLLIN);
 
  
 
  return 0;
 
}
 

	
 
int remoteio_authinfo_get(struct remoteio_opts *rem_opts, const char *servername, const char **username, const char **pass)
 
{
 
  struct remoteio_server *server;
 

	
 
  *username = NULL;
 
  *pass = NULL;
 

	
 
  server = remoteio_getserver(rem_opts, servername);
 
  if(!server)
 
    {
 
      fprintf(stderr, "%s:%d: Could not find server named ``%s''\n", __FILE__, __LINE__, servername);
 
      return 1;
 
    }
 

	
 
  *username = server->username;
 
  *pass = server->password;
 

	
 
  return 0;
 
}
 

	
 
/**
 
 * Implementation of multiio_event_handler_func_t
 
 */
 
int _remoteio_handle_read(multiio_context_t multiio,
 
			  int fd,
 
			  short revent,
 
			  struct remoteio_opts *opts,
 
			  struct remoteio *rem)
 
{
 
  struct remoteio_packet packet;
 
  size_t readlen;
 
  char buf[8192];
 

	
 
  int tmp;
 

	
 
  packet.len = 0;
 
  packet.data = NULL;
 

	
 
  if(rem->sock != fd)
 
    fprintf(stderr, "%d != %d\n", rem->sock, fd);
 

	
 
  tmp = funcmap[rem->method].read_func(rem, buf, sizeof(buf), &readlen);
 
  if(tmp)
 
    {
src/common/remoteio.h
Show inline comments
 
@@ -99,40 +99,55 @@ int remoteio_open_server(struct remoteio
 
 * @param rem a pointer to where the poiner to the newly allocated struct remoteio should be stored.
 
 * @param opts remoteio's options
 
 * @param read_handler the function to call when data has been read from the server.
 
 * @param read_handler_data the data to pass to the read_handler function.
 
 * @param opts self explanatory.
 
 */
 
int remoteio_open_socket(struct remoteio **rem,
 
			 struct remoteio_opts *opts,
 
			 remoteio_read_handle_func_t read_handler,
 
			 void *read_handler_data,
 
			 remoteio_close_handle_func_t close_handler,
 
			 int fd);
 

	
 
/**
 
 * Queue bytes to be written to the remote host.
 
 *
 
 * @param rem the remoteio handle
 
 * @param buf a buffer to be queued for writing. We will copy this, so the caller has to handle its memory (and free() it if necessary).
 
 * @param len number of bytes to grab from buf
 
 * @return 0 on success, 1 on failure
 
 */
 
int remoteio_write(struct remoteio *rem, const void *buf, size_t len);
 

	
 
/**
 
 * \brief Retrieves authentication information associated with a
 
 *   server's configuration entry.
 
 *
 
 * Possibly, the whole idea of remoteio handling server names with the
 
 * remoteio_open() function should be moved somewhere else and
 
 * remoteio should be more general-purpose I/O?
 
 *
 
 * \param rem_opts A remoteio options handle.
 
 * \param servername The name of the server whose information should be retrieved.
 
 * \param username Where to store a pointer to the username. Do not free this.
 
 * \param pass Where to store a pointer to the password. Do not free this.
 
 */
 
int remoteio_authinfo_get(struct remoteio_opts *rem_opts, const char *servername, const char **username, const char **pass);
 

	
 
/**
 
 * Closes a remoteio session.
 
 *
 
 * It is safe to call this function from within
 
 * remoteio_read_handle_func_t.
 
 *
 
 * @return nonzero on error
 
*/
 
int remoteio_close(struct remoteio *rem);
 

	
 
/**
 
 * Returns the number of unfulfilled remoteio_write() calls pending on
 
 * a remoteio handle.
 
 */
 
size_t remoteio_sendq_len(const struct remoteio *rem);
 

	
 
#endif
src/common/request.c
Show inline comments
 
@@ -48,48 +48,77 @@ int distren_request_version(struct distr
 
      return 1;
 
    }
 

	
 
  memset(version, 0, sizeof(struct distren_request_version));
 
  version->servertype = servertype;
 
  strncpy(version->package_string, package_string, DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN);
 

	
 
  *data = version;
 

	
 
  return 0;
 
}
 

	
 
int distren_request_parse_version(struct distren_request *req, void *data, struct distren_request_version *version)
 
{
 
  if(req->len < sizeof(struct distren_request_version))
 
    return 1;
 

	
 
  memcpy(version, data, sizeof(struct distren_request_version));
 
  /* there is space for another '\0' */
 
  version->package_string[DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN] = '\0';
 

	
 
  return 0;
 
}
 

	
 
int distren_request_pass(struct distren_request **req, void **data, const char *username, const char *pass)
 
{
 
  struct distren_request_pass *request_pass;
 

	
 
  distren_request_new(req, sizeof(struct distren_request_pass), DISTREN_REQUEST_PASS);
 
  request_pass = malloc(sizeof(struct distren_request_pass));
 
  if(!request_pass || !*req)
 
    {
 
      if(*req)
 
	distren_request_free(*req);
 
      free(request_pass);
 

	
 
      return 1;
 
    }
 

	
 
  /*
 
   * The packet itself doesn't need the string's NULL terminator
 
   * _unless_ if the string is shorter than the maximum length of the
 
   * username or password. Thus, strncpy()'s behavior (not strlcpy()'s
 
   * behavior) is _exactly_ what we want for this case.
 
   */
 
  strncpy(request_pass->username, username, DISTREN_REQUEST_PASS_USERNAME_LEN);
 
  strncpy(request_pass->pass, pass, DISTREN_REQUEST_PASS_PASS_LEN);
 

	
 
  *data = request_pass;
 

	
 
  return 0;
 
}
 

	
 
int distren_request_poing(struct distren_request **req, void **data, short is_ping, const void *poing_cookie, size_t poing_data_len)
 
{
 
  enum distren_request_type type;
 

	
 
  if(is_ping)
 
    type = DISTREN_REQUEST_PING;
 
  else
 
    type = DISTREN_REQUEST_PONG;
 
  distren_request_new(req, poing_data_len, type);
 
  (*data) = malloc(poing_data_len);
 
  memcpy(*data, poing_cookie, poing_data_len);
 

	
 
  return 0;
 
}
 

	
 
/*
 
 * file posting stuffs
 
 */
 
struct distren_request_file_post_context
 
{
 
  EVP_MD_CTX *digest_ctx;
 
  /* stored in network-byte order */
 
  uint32_t post_id;
 
};
src/common/request.h
Show inline comments
 
@@ -35,48 +35,58 @@
 
 */
 
int distren_request_free_with_data(struct distren_request *req, void *data);
 

	
 
/**
 
 * Initialize a VERSION request.
 
 *
 
 * @param req pointer to where the poitner to the new req should be stored..
 
 * @param data pointer to where the newly allocated data's address should go.
 
 * @param servertype the ORing of different DISTREN_SERVERTYPE_* constants.
 
 * @param package_string the PACKAGE_STRING constant.
 
 */
 
int distren_request_version(struct distren_request **req, void **data, uint32_t servertype, const char *package_string);
 

	
 
/**
 
 * Parses a DISTREN_REQUEST_VERSION packet.
 
 *
 
 * @param req the request to parse.
 
 * @param data the request's data.
 
 * @param version where the result should be stored.
 
 * @return 0 on success, 1 if the packet is invalid (if the length of package_version is longer than 32-bytes, for example).
 
 */
 
int distren_request_parse_version(struct distren_request *req, void *data, struct distren_request_version *version);
 

	
 
/**
 
 * Initialize a PASS request so as to identify to a server.
 
 *
 
 * \param req Where to store the newly allocated req.
 
 * \param data Where to store the newly allocated data.
 
 * \param user The username to send to the server.
 
 * \param pass The password to send to the server.
 
 */
 
int distren_request_pass(struct distren_request **req, void **data, const char *username, const char *pass);
 

	
 
/**
 
 * Initialize a PING or PONG request.
 
 *
 
 * @param data a place to allocate storage for the data associated with this request
 
 * @param is_ping 1 if this is a DISTREN_REQUEST_PING or 0 if this is a DISTREN_REQUEST_PONG
 
 * @param poing_cookie chocolate chip, chocolate chunk, or oatmeal chocolate chip
 
 * @param poing_data_len bytes in the poing_cookie
 
 * @return the length of the data allocated for this request
 
 */
 
int distren_request_poing(struct distren_request **req, void **data, short is_ping, const void *poing_cookie, size_t poing_data_len);
 

	
 
struct distren_request_file_post_context;
 
typedef struct distren_request_file_post_context *distren_request_file_post_context_t;
 

	
 
/**
 
 * Frees a post_context for if a client is disconnected or an upload
 
 * otherwise interrupted.
 
 */
 
int distren_request_file_post_context_free(distren_request_file_post_context_t post_context);
 

	
 
/**
 
 * Prepares for a file upload and initializes a
 
 * distren_request_file_post_context_t.
 
 *
 
 * If you call this function and you never finish an upload, you
src/server/distrend.c
Show inline comments
 
@@ -3,122 +3,132 @@
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
*/
 

	
 
/* This file contains the code which both processes (renders) jobs as a slave, and the code which distributes frames to slaves after receiving them from the client portion of the codebase. */
 

	
 
#include "common/config.h"
 

	
 
#include "distrenjob.h"
 
#include "listen.h"
 
#include "mysql.h"
 
#include "slavefuncs.h"
 
#include "mysql.h"
 
#include "user_mgr.h"
 

	
 
#include "common/asprintf.h"
 
#include "common/execio.h"
 
#include "common/options.h"
 
#include "common/protocol.h"
 
#include "common/request.h"
 

	
 
#include <arpa/inet.h>
 
#include <confuse.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
#include <sys/stat.h>
 
#include <sys/types.h>
 
#include <time.h>
 
#include <unistd.h>
 

	
 
#include <libxml/encoding.h>
 
#include <libxml/parser.h>
 
#include <libxml/tree.h>
 
#include <libxml/xmlmemory.h>
 
#include <libxml/xmlreader.h>
 
#include <libxml/xmlwriter.h>
 

	
 
/* ******************* Structs ************************ */
 
struct general_info
 
{
 
  struct distrenjob head;
 
  distrend_mysql_conn_t conn;
 

	
 
  struct distrend_config *config;
 

	
 
  struct
 
  {
 
    /** general_info.xml */
 
    char *geninfo;
 

	
 
    /**
 
     * \brief Where to store the user listing.
 
     */
 
    char *userlist;
 

	
 
    /**
 
     * where to store in-progress uploads or things that should
 
     * otherwise be on the same filesystem as the rest of the datadir
 
     * so that it may be rename()d.
 
     */
 
    char *tmpdir;
 
    
 
  } files;
 

	
 
  int jobs_in_queue;
 
  unsigned int free_clients;
 
  unsigned int rendering_clients;
 
  unsigned int total_finished_jobs;
 
  unsigned int total_frames_rendered;
 
  unsigned int highest_jobnum;
 
  int hibernate;
 
  time_t timestamp;
 
  unsigned long total_render_power;
 
  unsigned long total_priority_pieces;
 

	
 
  user_mgr_t user_mgr;
 
};
 

	
 

	
 
/* *********************************************
 
   Function Prototypes
 
   ********************************************* */
 

	
 
/* ************General Functions************* */
 
int distrend_do();
 
int distrend_do_config(int argc, char *argv[], struct distrend_config **config, multiio_context_t multiio);
 
int distrend_config_free(struct distrend_config *config);
 
int distrend_handle_request(struct distrend_listens *listens, struct distrend_client *client, struct distren_request *req, void *reqdata, struct general_info *geninfo);
 

	
 
/**
 
 * client request handlers
 
 */
 
int distrend_handle_version(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 
int distrend_handle_pass(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 
int distrend_handle_file_post_start(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 
int distrend_handle_file_post(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 
int distrend_handle_file_post_finish(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 

	
 
/* functions of some generic sort ...ish */
 
int distrend_handle_successful_upload(struct distrend_client *client, struct distrend_client_file_post *client_file_post);
 

	
 
/* **************XML Functions**************** */
 
void update_general_info(struct general_info *geninfo);
 
int import_general_info(struct general_info *general_info);
 
int update_xml_joblist(struct general_info *geninfo);
 

	
 
/* **************Test Functions**************** */
 
int interactiveTest(int test, multiio_context_t multiio, struct general_info *general_info);
 

	
 
/* **************** Main ********************* */
 
int main(int argc, char *argv[])
 
{
 
  /* Parse arguments */
 
  int counter;
 
  int test = 0; /*< Interactive mode if 1 */
 
  int tmp;
 
  struct general_info general_info;
 
  multiio_context_t multiio;
 
@@ -150,107 +160,123 @@ Ethan Zonca <e@ethanzonca.com>\n\
 
	  return 2;
 
      }
 

	
 
      else if(strcmp(argv[counter], "-t") == 0)
 
      {
 
    	  fprintf(stderr, "Entering into test mode...\n\n");
 
    	  test = 1;
 
      }
 
    }
 

	
 

	
 
  multiio = multiio_context_new();
 

	
 
  if(distrend_do_config(argc, argv, &general_info.config, multiio))
 
    return 1;
 

	
 
  /** preset paths */
 
  _distren_asprintf(&general_info.files.geninfo, "%s/general_info.xml",
 
		    general_info.config->datadir);
 

	
 
  _distren_asprintf(&general_info.files.tmpdir, "%s/tmp",
 
		    general_info.config->datadir);
 
  distren_mkdir_recurse(general_info.files.tmpdir);
 

	
 
  _distren_asprintf(&general_info.files.userlist, "%s/users.xml",
 
		    general_info.config->datadir);
 

	
 
  /** configuraton stuff that depends on the paths being calculated, such as loading data */
 
  general_info.user_mgr = user_mgr_init(general_info.files.userlist);
 
  if(!general_info.user_mgr)
 
    {
 
      fprintf(stderr, "Error initializing user_mgr\n");
 
      return 1;
 
    }
 

	
 

	
 
  /** MySQL Connection */
 
  fprintf(stderr,"Connecting to mysql...\n");
 
  if(mysqlConnect(&general_info.conn,
 
		  general_info.config->mysql_user,
 
		  general_info.config->mysql_host,
 
		  general_info.config->mysql_pass,
 
		  general_info.config->mysql_database) )
 
    {
 
      fprintf(stderr, "%s:%d: mysqlConnect() failed\n", __FILE__, __LINE__);
 
      fprintf(stderr, "don't test mysql stuff\n");
 
      interactiveTest(test, multiio, &general_info);
 
      return 1;
 
    }
 
  fprintf(stderr,"Finished connecting!\n");
 

	
 
  /** Execute test function */
 
  interactiveTest(test, multiio, &general_info);
 

	
 
  general_info.config->listens = distrend_listens_new(multiio, &general_info, general_info.config->options);
 
  if(!general_info.config->listens)
 
    {
 
      fprintf(stderr, "error initializing listens\n");
 
      return 1;
 
    }
 
  remoteio_generic_data_set(general_info.config->options->remoteio, general_info.config->listens);
 
  for(counter = 0; general_info.config->listen_ports[counter]; counter ++)
 
    {
 
      tmp = distrend_listen_add(general_info.config->listens, general_info.config->listen_ports[counter]);
 
      if(tmp)
 
	{
 
	  fprintf(stderr, "Error listening on port %d\n", general_info.config->listen_ports[counter]);
 
	  return 1;
 
	}
 
    }
 

	
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_VERSION, &distrend_handle_version);
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_START, &distrend_handle_file_post_start);
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST, &distrend_handle_file_post);
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_FINISH, &distrend_handle_file_post_finish);
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_VERSION, &distrend_handle_version, (uint8_t)DISTREND_CLIENT_PREVERSION);
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_PASS,
 
			      &distrend_handle_pass, (uint8_t)DISTREND_CLIENT_PREAUTH);
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_START, &distrend_handle_file_post_start, (uint8_t)DISTREND_CLIENT_GOOD);
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST, &distrend_handle_file_post, (uint8_t)DISTREND_CLIENT_GOOD);
 
  distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_FINISH, &distrend_handle_file_post_finish, (uint8_t)DISTREND_CLIENT_GOOD);
 

	
 
  /* Main Loop */
 
  general_info.config->die = 0;
 
  while(!general_info.config->die)
 
    {
 
      multiio_poll(multiio, 15000);
 

	
 
      tabletennis_serve(general_info.config->listens->tabletennis);
 

	
 
      /* Run the watchdog, @TODO: like every 10 mins or something */
 
      frame_watchdog(general_info.conn);
 
    }
 

	
 
  distrend_listen_free(general_info.config->listens);
 
  distrend_config_free(general_info.config);
 

	
 
  xmlcleanup();
 

	
 
  /** free() paths */
 
  free(general_info.files.geninfo);
 
  free(general_info.files.tmpdir);
 
  free(general_info.files.userlist);
 
  mysqlDisconnect(general_info.conn);
 

	
 
  return 0;
 
}
 

	
 
/* ********************** Functions ************************* */
 

	
 
int distrend_handle_version(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data)
 
{
 
  char *tmp_str;
 
  struct distren_request_version version;
 

	
 
  if(distren_request_parse_version(req, req_data, &version))
 
    {
 
      distrend_send_disconnect(client, "Invalid DISTREN_REQUEST_VERSION packet.");
 
      return 1;
 
    }
 

	
 
  if(client->state != DISTREND_CLIENT_PREVERSION)
 
    {
 
      distrend_send_disconnect(client, "You have already sent the VERSION command.");
 
      return 1;
 
    }
 
  if(!strncmp(PACKAGE_STRING, version.package_string, DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN))
 
@@ -264,48 +290,87 @@ int distrend_handle_version(struct gener
 
       */
 
      client->state = DISTREND_CLIENT_PREAUTH;
 
    }
 
  else
 
    {
 
      /**
 
       * The client claims to be of a different version of distren.
 
       * Now we will just send a disconnect packet and disconnect the client.
 
       */
 
      _distren_asprintf(&tmp_str, "You have tried to connect to a %s server when your client claims to be running %s. Bye ;-)\n", PACKAGE_STRING, version.package_string);
 
      if(tmp_str)
 
	{
 
	  distrend_send_disconnect(client, tmp_str);
 
	  free(tmp_str);
 
	}
 
      else
 
	distrend_send_disconnect(client, "Invalid PACKAGE_VERSION :-|.");
 
      return 1;
 
    }
 

	
 
  return 0;
 
}
 

	
 
/**
 
 * Handle a DISTREN_REQUEST_PASS request.
 
 */
 
int distrend_handle_pass(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data)
 
{
 
  struct distren_request_pass *pass_req;
 

	
 
  char username[DISTREN_REQUEST_PASS_USERNAME_LEN + 1];
 
  char pass[DISTREN_REQUEST_PASS_PASS_LEN + 1];
 

	
 
  struct user *user;
 

	
 
  if(req->len < sizeof(struct distren_request_pass))
 
    {
 
      distrend_send_disconnect(client, "You tried to send too short of a DISTREN_REQUEST_PASS.");
 
      return 1;
 
    }
 

	
 
  pass_req = req_data;
 
  memcpy(username, pass_req->username, DISTREN_REQUEST_PASS_USERNAME_LEN);
 
  username[DISTREN_REQUEST_PASS_USERNAME_LEN] = '\0';
 

	
 
  memcpy(pass, pass_req->pass, DISTREN_REQUEST_PASS_PASS_LEN);
 
  pass[DISTREN_REQUEST_PASS_PASS_LEN] = '\0';
 

	
 
  user = user_find(geninfo->user_mgr, username);
 
  if(!user
 
     || strcmp(user->username, username)
 
     || strcmp(user->pass, pass))
 
    {
 
      distrend_send_disconnect(client, "Invalid username or password.");
 
      return 1;
 
    }
 

	
 
  client->state = DISTREND_CLIENT_GOOD;
 

	
 
  return 0;
 
}
 

	
 
/**
 
 * Traversal helper for distrend_client_find_post().
 
 */
 
int distrend_client_find_post_traverse(uint32_t *post_id, struct distrend_client_file_post *client_file_post)
 
{
 
  if(*post_id == client_file_post->post_id)
 
    return FALSE;
 

	
 
  return TRUE;
 
}
 

	
 
/**
 
 * Find the record for an in-progress client's file posting.
 
 */
 
struct distrend_client_file_post *distrend_client_find_post(struct distrend_client *client, uint32_t post_id)
 
{
 
  if(list_traverse(client->file_post_list, &post_id, (list_traverse_func_t)&distrend_client_find_post_traverse, LIST_ALTR | LIST_FORW | LIST_FRNT) != LIST_EXTENT)
 
    return list_curr(client->file_post_list);
 
  return NULL;
 
}
 

	
 
/**
 
 * Finds a post_context based on the post_id and client.
 
 *
 
 * Compatible the distren_request_parse_file_post_find_context_func_t.
src/server/listen.c
Show inline comments
 
@@ -21,48 +21,53 @@
 

	
 
#include "listen.h"
 

	
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 
#include "common/request.h"
 

	
 
#include <errno.h>
 
#include <list.h>
 
#include <netinet/in.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
#include <sys/types.h>
 
#include <poll.h>
 
#include <sys/socket.h>
 
#include <unistd.h>
 

	
 
/* local */
 

	
 
struct distrend_request_handler_info
 
{
 
  enum distren_request_type request_type;
 
  distrend_handle_request_func_t handler;
 
  /**
 
   * A bitmasking of different enum distren_client_states which are
 
   * allowed to use this particular command.
 
   */
 
  uint8_t client_states;
 
};
 

	
 
struct distrend_client *distrend_client_new(struct distrend_listens *listens,
 
					    enum distrend_client_state state,
 
					    struct remoteio *rem,
 
					    int connection_id);
 
int distrend_client_free(struct distrend_client *client);
 
int distrend_dispatch_request(struct distrend_listens *listens, struct remoteio *rem, struct distrend_client *client, struct distren_request *req, void *reqdata);
 
struct distrend_polled_sock *distrend_polled_sock_get_by_offset(struct distrend_listens *listens, size_t pollfds_offset);
 
static size_t distrend_listen_read_handle(struct remoteio *rem, struct distrend_listens *listens, void *buf, size_t len, struct distrend_client *client);
 
static void distrend_listen_remoteio_handle_close(struct distrend_listens *listens, struct distrend_client *client);
 

	
 
int listen_handle_accept(multiio_context_t multiio,
 
			 int fd,
 
			 short revent,
 
			 struct distrend_listens *listens,
 
			 int *port);
 
int listen_handle_error(multiio_context_t multiio,
 
			int fd,
 
			short revent,
 
			struct distrend_listens *listens,
 
			int *port);
 

	
 
/*** TO BE MOVED TO REMOTEIO */
 
@@ -256,49 +261,48 @@ int listen_handle_existence(multiio_cont
 
	fprintf(stderr, __FILE__ ":%d: aaarrrrgh!\n :-D\n", __LINE__);
 
	break;
 

	
 
      default:
 
	break;
 
      }
 
  return 0;
 
}
 

	
 
struct distrend_accept_client_proc_data
 
{
 
  struct distrend_listens *listens;
 
  time_t current_time;
 
};
 

	
 

	
 
/**
 
 * Handle new connections.
 
 */
 
int listen_handle_accept(multiio_context_t multiio,
 
			 int fd,
 
			 short revent,
 
			 struct distrend_listens *listens,
 
			 int *port)
 
   //int distrend_accept(struct distrend_listens *listens)//, struct distrend_clientset *clients, distrend_handle_request_t handlereq, void *handlereqdata)
 
 {
 
   struct distrend_client *newclient;
 

	
 
   int newclientsock;
 

	
 
   struct remoteio *rem;
 

	
 
   struct distren_request *req;
 
   void *data;
 

	
 
   newclientsock = accept(fd, (struct sockaddr *)NULL, (socklen_t *)NULL);
 
   /* used to call int distrend_client_add(struct distrend_listens *listens, int sock, DISTREND_CLIENT_PREVERSION)*/
 

	
 
   newclient = distrend_client_new(listens, DISTREND_CLIENT_PREVERSION, NULL, newclientsock);
 

	
 
   if(remoteio_open_socket(&rem, listens->options->remoteio, (remoteio_read_handle_func_t)&distrend_listen_read_handle, newclient, (remoteio_close_handle_func_t)&distrend_listen_remoteio_handle_close, newclientsock))
 
     {
 
       fprintf(stderr, "error allocating/adding client struct\n");
 
       return 1;
 
     }
 
   newclient->rem = rem;
 

	
 
   fprintf(stderr, "accepted new connection; fd=%d\n", newclientsock);
 

	
 
@@ -340,48 +344,54 @@ int listen_handle_accept(multiio_context
 
   *//* provide for termination of this loop *//*
 
       if(newclient == list_curr(listens->clients))
 
	 newclient = NULL;
 
       else
 
	 newclient = list_curr(listens->clients);
 
     }
 
					       */
 
   return 0;
 
 }
 

	
 
 /**
 
  * Handle read events from remoteio, remoteio_read_handle_func_t.
 
  *
 
  * This func requires that someone called remoteio_generic_data_set(remoteio_opts, listens);
 
  *
 
  * @param client the client associated with this remoteio instance.
 
  */
 
size_t distrend_listen_read_handle(struct remoteio *rem, struct distrend_listens *listens, void *buf, size_t len, struct distrend_client *client)
 
 {
 
   struct distren_request *req;
 
   void *reqdata;
 

	
 
   size_t used_len;
 

	
 
  /*
 
   * ignore packets from a client who's going to be disconnected.
 
   */
 
  if(client->state == DISTREND_CLIENT_BAD)
 
    return len;
 

	
 
   used_len = 0;
 
   /**
 
    * Manage input, etc.
 
    */
 
   if(client->expectlen == 0)
 
     {
 
       /* search out header from input so far */
 
       if(len > sizeof(struct distren_request))
 
	 {
 
	   if(distren_request_new_fromdata(&req, buf, len))
 
	     {
 
	       fprintf(stderr, "Error handling data from client (magic likely did not match), closing connection\n");
 

	
 
	       /*
 
		* yes, this is safe and legal... because of hackishness
 
		* in remoteio_close() ;-)
 
		*/
 
	      remoteio_close(rem);
 
	      return 1;
 
	    }
 
	  client->expectlen = req->len + sizeof(struct distren_request);
 
	  distren_request_free(req);
 
	}
 
    }
 
@@ -534,76 +544,89 @@ int distrend_client_read(struct distrend
 
  packet = q_dequeue(client->inmsgs);
 
  *lenread = packet->len;
 
  *toread = packet->data;
 
  free(packet);
 

	
 
  return 0;
 
}
 
*/
 

	
 

	
 
int distrend_send_disconnect(struct distrend_client *client, const char *quit_msg)
 
{
 
  struct distren_request *req;
 

	
 
  distren_request_new(&req, strlen(quit_msg), DISTREN_REQUEST_DISCONNECT);
 
  distrend_client_write_request(client, req, quit_msg);
 
  distren_request_free(req);
 

	
 
  client->state = DISTREND_CLIENT_BAD;
 
  client->cleanup_time = time(NULL) + DISTREND_LISTEN_DISCONNECT_GRACE;
 

	
 
  return 0;
 
}
 

	
 
int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler)
 
int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler, uint8_t client_state_mask)
 
{
 
  struct distrend_request_handler_info *handler_info;
 

	
 
  handler_info = malloc(sizeof(struct distrend_request_handler_info));
 
  if(!handler_info)
 
    return 1;
 

	
 
  handler_info->request_type = type;
 
  handler_info->handler = handler;
 
  handler_info->client_states = client_state_mask;
 
  list_insert_after(listens->request_handlers, handler_info, 0);
 

	
 
  return 0;
 
}
 

	
 
struct distrend_dispatch_request_data
 
{
 
  struct general_info *geninfo;
 
  struct distrend_client *client;
 
  struct distren_request *req;
 
  void *req_data;
 
};
 

	
 
/**
 
   traversal function for distrend_dispatch_request().
 
 */
 
int _distrend_dispatch_request_trav(struct distrend_dispatch_request_data *data, struct distrend_request_handler_info *handler_info)
 
{
 
  if(handler_info->request_type == data->req->type)
 
    (*handler_info->handler)(data->geninfo, data->client, data->req, data->req_data);
 
    {
 
      /* check permissions first */
 
      if(!(data->client->state & handler_info->client_states))
 
	{
 
	  distrend_send_disconnect(data->client, "You attempted to use a command out of context.");
 
	  return FALSE;
 
	}
 

	
 
      (*handler_info->handler)(data->geninfo, data->client, data->req, data->req_data);
 

	
 
      /* shortcut one a hit ;-) */
 
      return FALSE;
 
    }
 

	
 
  return TRUE;
 
}
 

	
 
/**
 
   helper for distrend_listen_read_handle() which looks up the correct
 
   request handler and handles handing the the request to the
 
   handler. :-p
 
*/
 
int distrend_dispatch_request(struct distrend_listens *listens, struct remoteio *rem, struct distrend_client *client, struct distren_request *req, void *reqdata)
 
{
 
  struct distrend_dispatch_request_data data;
 

	
 
  data.geninfo = listens->geninfo;
 
  data.client = client;
 
  data.req = req;
 
  data.req_data = reqdata;
 

	
 
  list_traverse(listens->request_handlers, &data, (list_traverse_func_t)&_distrend_dispatch_request_trav, LIST_FRNT | LIST_SAVE);
 

	
 
  return 0;
 
}
src/server/listen.h
Show inline comments
 
@@ -38,69 +38,73 @@ struct distrend_client;
 
#include "common/options.h"
 
#include "common/multiio.h"
 
#include "common/protocol.h"
 
#include "common/remoteio.h"
 
#include "common/request.h"
 

	
 
#include <queue.h>
 
#include <time.h>
 

	
 
/**
 
   How long a client has after connecting to send
 
   authentication information before his connection is cleaned
 
   up.
 
 */
 
#define DISTREND_LISTEN_AUTHTIME 32
 

	
 
/**
 
   How long a client has when in DISTREND_CLIENT_BAD before
 
   his connection is dropped. This grace time is intended so that
 
   the client will actually see his disconnect message instead of
 
   just having his connection reset.
 
 */
 
#define DISTREND_LISTEN_DISCONNECT_GRACE 8
 

	
 
/**
 
 * Numbers are explicitly given so that commands can be restricted to
 
 * clients in certain states.
 
 */
 
enum distrend_client_state
 
  {
 
    /**
 
       The client hasn't yet given us its version.
 
     */
 
    DISTREND_CLIENT_PREVERSION,
 
    DISTREND_CLIENT_PREVERSION = 1,
 
    /**
 
       We don't yet know the client. It may only use authentication
 
       commands.
 
     */
 
    DISTREND_CLIENT_PREAUTH,
 
    DISTREND_CLIENT_PREAUTH = 2,
 
    /**
 
       The client is authenticated, etc.
 
     */
 
    DISTREND_CLIENT_GOOD,
 
    DISTREND_CLIENT_GOOD = 4,
 
    /**
 
       The client is queued to be disconnected. (This state exists
 
       so that the client at least has a chance to recieve its
 
       disconnect message/error before being dumped).
 
     */
 
    DISTREND_CLIENT_BAD,
 
    DISTREND_CLIENT_BAD = 8,
 
  };
 

	
 
struct distrend_listens
 
{
 
  /* of type (struct distrend_request_handler_info) */
 
  list_t request_handlers;
 
  /* the data to pass on to all request handlers */
 
  struct general_info *geninfo;
 
  /* the distrend config */
 
  struct options_common *options;
 

	
 
  tabletennis_t tabletennis;
 

	
 
  /* of type (struct distrend_client)  (multiio stores a pointer per socket, we'll store each strut distrend_client as that pointer instead of using this list) */
 
  /* list_t clients; */
 

	
 
  /* the multiio context the listening interface should use/initialize */
 
  multiio_context_t multiio;
 
  /*
 
   * the socket type reserved for us, i.e., the listen()/accept()
 
   * socket type whose events we handle.
 
  */
 
  multiio_socket_type_t socket_type;
 
};
 
@@ -167,53 +171,57 @@ struct distrend_client
 
   @param data the message received from the client.
 
 */
 
typedef int(*distrend_handle_request_func_t)(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 

	
 
/**
 
   Initializes the listens member of struct distrend_config.
 

	
 
   @param multiio the multiio context in which we should register a new socket type and insert records for clients who connect.
 
   @param geninfo general info to apss to the request handler.
 
   @return Must be free()d with distrend_listen_free();
 
*/
 
struct distrend_listens *distrend_listens_new(multiio_context_t multiio, struct general_info *geninfo, struct options_common *opts);
 

	
 
/**
 
   Adds a socket and configures it to listen().
 

	
 

	
 
   @param listens The handle for this set of listens, obtained via distrend_listen_init().
 
 */
 
int distrend_listen_add(struct distrend_listens *listens, int port);
 

	
 
/**
 
 * Register a request handler with the listener.
 
 *
 
 * @param config distrend's configuration
 
 * @param type the request type for which this handler should be called
 
 * @param handler the handler to call when a request of type type is received.
 
 * \param config distrend's configuration.
 
 * \param type The request type for which this handler should be
 
 *   called
 
 * \param handler The handler to call when a request of type type is
 
 *   received.
 
 * \param client_state_mask A mask of enum distrend_client_state
 
 *   values that are allowed to use this command.
 
 */
 
int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler);
 
int distrend_listen_handler_add(struct distrend_listens *listens, enum distren_request_type type, distrend_handle_request_func_t handler, uint8_t client_state_mask);
 

	
 
/**
 
 * cleans listening sockets/frees main struct. Unnecessary for a working server, currently a stub.
 
 */
 
int distrend_listen_free(struct distrend_listens *listens);
 

	
 
/**
 
   writes request to client.
 
   @param client client to write to
 
   @param req the request struct. caller must free this.
 
   @param data the data of the request which is req->len bytes long. caller must free this.
 
 */
 
int distrend_client_write_request(struct distrend_client *client, const struct distren_request *req, const void *data);
 

	
 
/**
 
   This is probably just NOT a placeholder for remotio
 
*/
 
void remotio_send_to_client();
 

	
 
/**
 
 * Queue a DISTREN_REQUEST_DISCONNECT and prepare a client
 
 * to be disconnected.
 
 */
 
int distrend_send_disconnect(struct distrend_client *client, const char *quit_msg);
src/server/tabletennis.c
Show inline comments
 
@@ -39,50 +39,50 @@ struct tabletennis
 
  /* of type (struct distrend_client *) */
 
  queue_t clients_to_ping;
 

	
 
  /* of type (struct distrend_client *) */
 
  queue_t clients_need_pong;
 

	
 
  struct timespec time_last_check;
 
};
 

	
 
static int tabletennis_pong_request_handle(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 
static int tabletennis_ping_request_handle(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data);
 

	
 
tabletennis_t tabletennis_new(struct distrend_listens *listens, unsigned int ping_interval, unsigned int pong_time)
 
{
 
  tabletennis_t tabletennis;
 

	
 
  tabletennis = malloc(sizeof(struct tabletennis));
 

	
 
  tabletennis->ping_interval = ping_interval;
 
  tabletennis->pong_time = pong_time;
 
  tabletennis->clients_to_ping = q_init();
 
  tabletennis->clients_need_pong = q_init();
 
  clock_gettime(CLOCK_MONOTONIC, &tabletennis->time_last_check);
 

	
 
  distrend_listen_handler_add(listens, DISTREN_REQUEST_PING, &tabletennis_ping_request_handle);
 
  distrend_listen_handler_add(listens, DISTREN_REQUEST_PONG, &tabletennis_pong_request_handle);
 
  distrend_listen_handler_add(listens, DISTREN_REQUEST_PING, &tabletennis_ping_request_handle, (uint8_t)DISTREND_CLIENT_GOOD);
 
  distrend_listen_handler_add(listens, DISTREN_REQUEST_PONG, &tabletennis_pong_request_handle, (uint8_t)DISTREND_CLIENT_GOOD);
 

	
 
  return tabletennis;
 
}
 

	
 
int tabletennis_add_client(tabletennis_t tabletennis, struct distrend_client *client)
 
{
 
  client->tabletennis_client.state = TABLETENNIS_NEED_PING;
 
  client->tabletennis_client.time_next_check = tabletennis->time_last_check.tv_sec
 
    + tabletennis->ping_interval;
 

	
 
  q_enqueue(tabletennis->clients_to_ping, client, 0);
 

	
 
  return 0;
 
}
 

	
 
int tabletennis_serve(tabletennis_t tabletennis)
 
{
 
  struct timespec time_now;
 
  struct distrend_client *client;
 
  time_t time_next_check;
 

	
 
  struct distren_request *req;
 
  void *req_data;
 

	
src/server/user_mgr.c
Show inline comments
 
/*
 
  Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matthew Orlando
 

	
 
  This file is a part of DistRen.
 

	
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 
 *  Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matthew Orlando
 
 *
 
 * This file is a part of DistRen.
 
 *
 
 * DistRen is free software: you can redistribute it and/or modify
 
 * it under the terms of the GNU Affero General Public License as published by
 
 * the Free Software Foundation, either version 3 of the License, or
 
 * (at your option) any later version.
 
 *
 
 * DistRen is distributed in the hope that it will be useful,
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 * GNU Affero General Public License for more details.
 
 *
 
 * You should have received a copy of the GNU Affero General Public License
 
 * along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
#include "common/config.h"
 

	
 
#include "user_mgr.h"
 

	
 
#include "common/asprintf.h"
 

	
 
#include <list.h>
 

	
 
#include <libxml/xmlmemory.h>
 
#include <libxml/parser.h>
 
#include <libxml/tree.h>
 
#include <libxml/encoding.h>
 
#include <libxml/xmlwriter.h>
 
#include <libxml/xmlreader.h>
 

	
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 
#include <unistd.h>
 
#include <sys/stat.h>
 

	
 
struct user_mgr_info
 
struct user_mgr
 
{
 
	struct user *user_array;
 
	int current_users;
 
	int user_array_size;
 
} user_mgr_info;
 

	
 
int resize_user_array()
 
{
 
	int counter;
 
	int counter2;
 

	
 
	// create array twice the size of the current amount of users
 
	user_mgr_info.user_array_size = user_mgr_info.current_users * 2;
 
	struct user *new_user_array = malloc(sizeof(struct user) * user_mgr_info.user_array_size);
 
  /* items are of type user_t */
 
  list_t user_list;
 
  /* where to load/save the userlist */
 
  char *userlist_filename;
 
};
 

	
 
	// this copies the original user_array over to the new one
 
	// using two counters allows the array to be resized at any time
 
	// leaving exactly 1 open space between each user when it is done;
 
	counter2 = 0;
 
	for(counter = 0; counter < user_mgr_info.current_users; counter++)
 
	{
 
		if(user_mgr_info.user_array[counter].username != 0)
 
		{
 
			new_user_array[counter2*2] = user_mgr_info.user_array[counter];
 
			counter2++;
 
		}
 
	}
 
static int user_find_traverse_forw(char *search_username, user_t user)
 
{
 
  if(strcmp(search_username, user->username) >= 0)
 
    return FALSE;
 
  return TRUE;
 
}
 

	
 
	// cleanup old array
 
	free(user_mgr_info.user_array);
 

	
 
	// change the pointer to point to the new user array
 
	user_mgr_info.user_array = new_user_array;
 

	
 
	return 1;
 
static int user_find_traverse_back(char *search_username, user_t user)
 
{
 
  if(strcmp(search_username, user->username) <= 0)
 
    return FALSE;
 
  return TRUE;
 
}
 

	
 
struct user *findUser(char *nameOfUser)
 
user_t user_find(user_mgr_t user_mgr, const char *username)
 
{
 
	int high;
 
	int low;
 
	int middle;
 
	int result;
 
  int list_direction;
 
  list_traverse_func_t traverse_func;
 

	
 
  user_t user;
 
  char *username_copy;
 

	
 
	high = user_mgr_info.user_array_size - 1;
 
	low = 0;
 
	result = -1;
 
  if(list_empty(user_mgr->user_list))
 
    return NULL;
 

	
 
  /* grab the current user for tiny optimizations.. */
 
  user = list_curr(user_mgr->user_list);
 
  if(!user)
 
    return NULL;
 

	
 
	for(middle = (low+high)/2; 1 == 1; middle = (low+high)/2)
 
	{
 
		// in case middle lands on a part of the array with no user
 
		while(user_mgr_info.user_array[middle].username == 0)
 
		{
 
			if(result < 0)
 
				middle --;
 
			else
 
				middle ++;
 
		}
 
  list_direction = LIST_FORW;
 
  traverse_func = (list_traverse_func_t)&user_find_traverse_forw;
 
  if(strcmp(user->username, username) < 0)
 
    {
 
      list_direction = LIST_BACK;
 
      traverse_func = (list_traverse_func_t)&user_find_traverse_back;
 
    };
 

	
 
		// this is where the array is cut in half and the half that the nameOfUser is on is kept
 
		result = strcmp(nameOfUser, user_mgr_info.user_array[middle].username);
 
		if(result == 0)
 
			return &user_mgr_info.user_array[middle];
 
		else if(result < 0)
 
			high = middle;
 
		else
 
			low = middle;
 
  username_copy = strdup(username);
 
  list_traverse(user_mgr->user_list, username_copy, traverse_func, list_direction|LIST_CURR|LIST_ALTR);
 
  free(username_copy);
 
  user = list_curr(user_mgr->user_list);
 
  if(!user)
 
    return NULL;
 

	
 
		// in case the user doesn't exist
 
		if(high-low <= 0)
 
			return 0;
 
	}
 
  if(!strcmp(username, user->username))
 
    return user;
 

	
 
  return NULL;
 
}
 

	
 
int deleteUser(struct user *user_ptr)
 
int user_add(user_mgr_t user_mgr, const char *username, const char *pass)
 
{
 
	free(user_ptr->username);
 
	memset(user_ptr, '\0', sizeof(struct user));
 
  user_t user;
 
  short insert_after;
 

	
 
	user_mgr_info.current_users--;
 

	
 
	return 1;
 

	
 
	backup_list_XML();
 
}
 
  user = user_find(user_mgr, username);
 
  if(user)
 
    return 1;
 

	
 
int createUser(struct user *user_ptr, char *nameOfUser)
 
{
 
	// clear old memory
 
	memset(user_ptr, '\0', sizeof(struct user));
 
  /*
 
   * The list should be positioned within one element of where we want
 
   * to insert username.
 
   */
 
  insert_after = 1;
 
  user = list_curr(user_mgr->user_list);
 
  if(user)
 
    {
 
      if(strcmp(username, user->username) < 0)
 
	insert_after = 0;
 
    }
 

	
 
	user_ptr->username = nameOfUser;
 
	user_ptr->render_power = 0;
 
	user_ptr->last_job = 0;
 
  /*
 
   * construct the new user.
 
   */
 
  user = malloc(sizeof(struct user));
 
  if(!user)
 
    return 1;
 
  user->username = strdup(username);
 
  user->pass = strdup(pass);
 
  user->render_power = 0;
 
  user->last_job = 0;
 

	
 
	user_mgr_info.current_users++;
 
  /* I admit it... I'm completely crazy --binki */
 
  if(insert_after)
 
    list_insert_after(user_mgr->user_list, user, 0);
 
  else
 
    list_insert_before(user_mgr->user_list, user, 0);
 

	
 
	return 1;
 
  return 0;
 
}
 

	
 
// places the new user at position index in the array, moves other users if it needs to
 
int placeUser(int index, char *nameOfUser)
 
/**
 
 * \brief For list_free() et al
 
 */
 
static void user_free(user_t user)
 
{
 
	int higher;
 
	int lower;
 
	int total_moves;
 

	
 
	total_moves = 0;
 
  free(user->username);
 
  free(user->pass);
 
  free(user);
 
}
 

	
 
	// I shift data in the array to create an open the space where the user should be added
 
	// but first I figure out which way is the shortest
 
	if(user_mgr_info.user_array[index].username != 0)
 
	{
 
		higher = index + 1;
 
		while(user_mgr_info.user_array[higher].username != 0)
 
			higher++;
 

	
 
		lower = index - 1;
 
		while(user_mgr_info.user_array[lower].username != 0)
 
			lower--;
 
int user_delete(user_mgr_t user_mgr, user_t user)
 
{
 
  user_t user_found;
 
  int ret;
 

	
 
		// here the data is shifted to open up a space
 
		if(index - lower < higher - index)
 
		  {
 
		    total_moves = index - lower;
 
		    for(; lower < index; lower++)
 
				memcpy(&user_mgr_info.user_array[lower], &user_mgr_info.user_array[lower + 1], sizeof(struct user));
 
		  }
 
		else
 
		  {
 
		    total_moves = higher - index;
 
		    for(; higher > index; higher--)
 
				memcpy(&user_mgr_info.user_array[higher], &user_mgr_info.user_array[higher - 1], sizeof(struct user));
 
		  }
 
	}
 
  ret = 0;
 

	
 
	// add the user to the array
 
	createUser(&user_mgr_info.user_array[index], nameOfUser);
 
  user_found = user_find(user_mgr, user->username);
 
  if(user_found
 
     && user_found == user
 
     && user == list_curr(user_mgr->user_list))
 
    list_remove_curr(user_mgr->user_list);
 
  else
 
    {
 
      fprintf(stderr, __FILE__ ":%d:user_delete(): List is inconsistent :-/\n", __LINE__);
 
      ret = 1;
 
    }
 

	
 
	if(total_moves > 50)
 
	  resize_user_array();
 
  user_free(user);
 

	
 
	return 1;
 
  return ret;
 
}
 

	
 
int addUser(char *nameOfUser)
 
static int user_mgr_save_traverse(xmlTextWriterPtr writer, user_t user)
 
{
 
	int high;
 
	int low;
 
	int middle;
 
	int result;
 

	
 
	high = user_mgr_info.user_array_size - 1;
 
	low = 0;
 
	result = -1;
 
  char *tmp;
 

	
 
	for(middle = (low+high)/2; 1 == 1; middle = (low+high)/2)
 
	{
 
		// in case middle lands on a part of the array with no user
 
		while(user_mgr_info.user_array[middle].username == 0)
 
		{
 
			if(result < 0)
 
				middle--;
 
			else
 
				middle++;
 
		}
 
  xmlTextWriterStartElement(writer, (xmlChar *)"user");
 

	
 
  xmlTextWriterWriteAttribute(writer, (xmlChar *)"name", (xmlChar*)user->username);
 
  xmlTextWriterWriteAttribute(writer, (xmlChar *)"pass", (xmlChar*)user->pass);
 

	
 
		// this is where the array is cut in half and the half that the nameOfUser is on is kept
 
		result = strcmp(nameOfUser, user_mgr_info.user_array[middle].username);
 
		if(result == 0)
 
			return 0;
 
		else if(result < 0)
 
			high = middle;
 
		else
 
			low = middle;
 

	
 
		// once there are less than 10 possible places for the user to be placed
 
		// break out of this loop and begin next loop
 
		if(high-low <= 10)
 
			break;
 
	}
 
  _distren_asprintf(&tmp, "%d", user->last_job);
 
  xmlTextWriterWriteAttribute(writer, (xmlChar *)"last_job", (xmlChar*)tmp);
 
  free(tmp);
 

	
 
	// this function starts at the low index number, and goes up until it finds a
 
	// username that is bigger than it alphabetically, and tells the userPlacer
 
	// that it needs to go 1 before that spot
 
	for(; low <= high; low++)
 
	{
 
		while(user_mgr_info.user_array[low].username == 0)
 
			low++;
 
  _distren_asprintf(&tmp, "%d", user->render_power);
 
  xmlTextWriterWriteAttribute(writer, (xmlChar *)"render_power", (xmlChar*)tmp);
 
  free(tmp);
 

	
 
		result = strcmp(nameOfUser, user_mgr_info.user_array[low].username) < 0;
 
		if(result < 0)
 
		{
 
			placeUser(low - 1, nameOfUser);
 
			return 1;
 
		}
 
		if(result == 0)
 
		{
 
			fprintf(stderr, "user already exists");
 
			return 0;
 
		}
 
	}
 
  xmlTextWriterEndElement(writer);
 

	
 
	backup_list_XML();
 
	return 0;
 
  return TRUE;
 
}
 

	
 
int initialize_users()
 
int user_mgr_save(user_mgr_t user_mgr, const char *filename)
 
{
 
  struct stat buffer;
 
  // cif user_list.xml exists
 
  if(stat("user_list.xml", &buffer) == 0)
 
  {
 
    restart_From_XML_backup();
 
  }
 
  else
 
  {
 
    user_mgr_info.current_users = 0;
 
  xmlTextWriterPtr writer;
 

	
 
  if(!filename)
 
    filename = user_mgr->userlist_filename;
 

	
 
  writer = xmlNewTextWriterFilename(filename, 0);
 
  xmlTextWriterStartDocument(writer, NULL, "utf-8", NULL);
 

	
 
    user_mgr_info.user_array_size = 50;
 
    user_mgr_info.user_array = malloc(sizeof(struct user) * 50);
 
  }
 
  /*
 
   * create root element user_list
 
   */
 
  xmlTextWriterStartElement(writer, (xmlChar*)"user_list");
 

	
 
  // if XML file is not found create new array of size 50
 
  /**
 
   * \todo error checking/handling?
 
   */
 
  list_traverse(user_mgr->user_list, writer, (list_traverse_func_t)&user_mgr_save_traverse, LIST_FRNT|LIST_FORW|LIST_SAVE);
 

	
 
  xmlTextWriterEndElement(writer);
 
  xmlTextWriterEndDocument(writer);
 
  xmlTextWriterFlush(writer);
 

	
 
  return 1;
 
  return 0;
 
}
 

	
 
/********************************** XMLness *****************************/
 

	
 
int backup_list_XML()
 
user_mgr_t user_mgr_init(const char *userfile)
 
{
 
	xmlTextWriterPtr writer;
 
        char *tmp;
 
	int counter;
 

	
 

	
 
	writer = xmlNewTextWriterFilename("user_list.xml", 0);
 
	xmlTextWriterStartDocument(writer, NULL, "utf-8", NULL);
 

	
 
	// create root element user_list
 
	xmlTextWriterStartElement(writer, (xmlChar*)"user_list");
 

	
 
	_distren_asprintf(&tmp, "%d", user_mgr_info.current_users);
 
	xmlTextWriterWriteAttribute(writer, (xmlChar*)"amount_of_users", (xmlChar*)tmp);
 
	free(tmp);
 

	
 
	for(counter = 0; counter < user_mgr_info.user_array_size; counter++)
 
	{
 
		if(user_mgr_info.user_array[counter].username != 0)
 
		{
 
			xmlTextWriterStartElement(writer, (xmlChar*)"user");
 

	
 
			xmlTextWriterWriteAttribute(writer, (xmlChar*)"name", (xmlChar*)user_mgr_info.user_array[counter].username);
 

	
 
			_distren_asprintf(&tmp, "%d", user_mgr_info.user_array[counter].last_job);
 
			xmlTextWriterWriteAttribute(writer, (xmlChar*)"last_job", (xmlChar*)tmp);
 
			free(tmp);
 

	
 
			_distren_asprintf(&tmp, "%d", user_mgr_info.user_array[counter].render_power);
 
			xmlTextWriterWriteAttribute(writer, (xmlChar*)"render_power", (xmlChar*)tmp);
 
			free(tmp);
 

	
 
			xmlTextWriterEndElement(writer);
 
		}
 
	}
 

	
 
	return 0;
 

	
 
}
 

	
 
int restart_From_XML_backup(){
 
  xmlDocPtr doc;
 
  xmlNodePtr cur;
 
  int counter;
 

	
 
  user_mgr_t user_mgr;
 

	
 
  user_t user;
 
  xmlChar *username;
 
  xmlChar *pass;
 
  xmlChar *tmp;
 
  int render_power;
 
  int last_job;
 

	
 
  doc = xmlParseFile("user_list.xml");
 
  user_mgr = malloc(sizeof(struct user_mgr));
 
  user_mgr->user_list = list_init();
 
  user_mgr->userlist_filename = strdup(userfile);
 

	
 
  doc = xmlParseFile(userfile);
 
  if (!doc)
 
    {
 
      fprintf(stderr, "user_mgr: Error opening userlist, assuming we should start with an empty userlist\n");
 
      return user_mgr;
 
    }
 

	
 
  cur = xmlDocGetRootElement(doc);
 
  if (xmlStrcmp(cur->name, (xmlChar*)"user_list"))
 
    {
 
      fprintf(stderr, "xml document is wrong type");
 
      fprintf(stderr, "user_mgr: xml document is wrong type. Using empty userlist, which will overwrite the old userlist soon probably...");
 
      xmlFreeDoc(doc);
 
      return 1;
 
      return user_mgr;
 
    }
 

	
 
  user_mgr_info.current_users = atoi((char*)xmlGetProp(cur, (xmlChar*)"amount_of_users"));
 
  for(cur = cur->xmlChildrenNode; cur; cur = cur->next)
 
    {
 
      if (cur->type != XML_ELEMENT_NODE)
 
	/* skip the implicit XML_TEXT_NODEs */
 
	continue;
 

	
 
      if (xmlStrcmp(cur->name, (xmlChar *)"user"))
 
	{
 
	  fprintf(stderr, "user_mgr: Unrecognized XML element: <%s />\n",
 
		 cur->name);
 

	
 
	  continue;
 
	}
 

	
 
  user_mgr_info.user_array_size = user_mgr_info.current_users * 2;
 
  user_mgr_info.user_array = malloc(sizeof(struct user) * user_mgr_info.user_array_size);
 
      username = xmlGetProp(cur, (xmlChar *)"name");
 
      pass = xmlGetProp(cur, (xmlChar *)"pass");
 

	
 
      if(!username)
 
	{
 
	  fprintf(stderr, "<user /> is missing a name attribute! (skipping)\n");
 
	  continue;
 
	}
 
      if(!pass)
 
	{
 
	  fprintf(stderr, "<user name=\"%s\"/> is missing a pass attribute! (skipping)\n",
 
		  username ? (char *)username : "");
 
	  continue;
 
	}
 

	
 
  cur = cur->xmlChildrenNode;
 
  for(counter = 0; cur->next; counter++){
 
    user_mgr_info.user_array[counter*2].username = (char*)xmlGetProp(cur, (xmlChar*)"amount_of_users");
 
    user_mgr_info.user_array[counter*2].last_job = atoi((char*)xmlGetProp(cur, (xmlChar*)"last_job"));
 
    user_mgr_info.user_array[counter*2].render_power = atoi((char*)xmlGetProp(cur, (xmlChar*)"render_power"));
 
    cur = cur->next;
 
  }
 
      last_job = 0;
 
      tmp = xmlGetProp(cur, (xmlChar *)"last_job");
 
      if(tmp)
 
	{
 
	  last_job = atoi((char *)tmp);
 
	  xmlFree(tmp);
 
	}
 

	
 
      render_power = 0;
 
      if(tmp)
 
	{
 
	  tmp = xmlGetProp(cur, (xmlChar *)"render_power");
 
	  render_power = atoi((char *)tmp);
 
	  xmlFree(tmp);
 
	}
 

	
 
      user_add(user_mgr, (char *)username, (char *)pass);
 
      xmlFree(username);
 
      xmlFree(pass);
 

	
 
  return 0;
 
      /*
 
       * user_find should be a very inexpensive operation immediately
 
       * after the user_add() above. I just don't want to trust to
 
       * list_curr() right in this function ;-).
 
       *
 
       * Also, we set this information without passing the following
 
       * as arguments to user_add() because user_add() is an external
 
       * API used for when a user is initially added to the
 
       * userlist. Thus, everybody'd be calling it with
 
       * user_add("username", "pass", 0, 0);
 
       */
 
      user = user_find(user_mgr, (char *)username);
 
      if(user)
 
	{
 
	  user->render_power = render_power;
 
	  user->last_job = last_job;
 
	}
 
    }
 

	
 
  return user_mgr;
 
}
src/server/user_mgr.h
Show inline comments
 
@@ -6,34 +6,84 @@
 
  DistRen is free software: you can redistribute it and/or modify
 
  it under the terms of the GNU Affero General Public License as published by
 
  the Free Software Foundation, either version 3 of the License, or
 
  (at your option) any later version.
 

	
 
  DistRen is distributed in the hope that it will be useful,
 
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  GNU Affero General Public License for more details.
 

	
 
  You should have received a copy of the GNU Affero General Public License
 
  along with DistRen.  If not, see <http://www.gnu.org/licenses/>.
 
*/
 

	
 
/*
 
 * Stores, keeps track of, retrieves information concerning,
 
 * and backs up to XML DistRen's userdb system
 
 */
 

	
 
#ifndef _DISTREN_USER_MGR_H
 
#define _DISTREN_USER_MGR_H
 

	
 
struct user
 
{
 
	char *username;
 
	int render_power;
 
	int last_job;
 
  char *username;
 
  char *pass;
 
  int render_power;
 
  int last_job;
 
};
 
typedef struct user *user_t;
 

	
 
struct user_mgr;
 
typedef struct user_mgr *user_mgr_t;
 

	
 
/**
 
 * \brief Allocate and initialize a user_mgr.
 
 *
 
 * \param userfile The path to where and XML file with user
 
 *   information may be found and where the users file should be save
 
 *   to when new users are added.
 
 * \return The new user_mgr or NULL on error.
 
 */
 
user_mgr_t user_mgr_init(const char *userfile);
 

	
 
/**
 
 * \brief Find a user by username.
 
 *
 
 * \param user_mgr The user_mgr...
 
 * \param username The username to search for.
 
 * \return The user if it is in user_mgr or NULL.
 
 */
 
user_t user_find(user_mgr_t user_mgr, const char *username);
 

	
 
/**
 
 * \brief Add a user to the user_mgr.
 
 *
 
 * \param username The user's username (which is local in scope to this server).
 
 * \param pass The user's plaintext password (insecurity is sooo much easier ;-) ).
 
 * \return 0 on success, 1 on failure (attempt to insert duplicate user, etc.).
 
 */
 
int user_add(user_mgr_t user_mgr, const char *username, const char *pass);
 

	
 
/**
 
 * \brief Delete a user.
 
 *
 
 * The user handle passed to this function is no longer valid after
 
 * this function is called.
 
 *
 
 * \param user The user's handle as retrieved via user_find().
 
 * \return 0 on success, 1 on failure (user does not exist, inconsistency, etc.)
 
 */
 
int user_delete(user_mgr_t user_mgr, user_t user);
 

	
 
/**
 
 * \brief Write out the XML file listing all of the users local to this server.
 
 *
 
 * \param filename The file to write the userlist to or NULL if you
 
 *   want to write to the same filename you loaded the usre_mgr from.
 
 */
 
int user_mgr_save(user_mgr_t user_mgr, const char *filename);
 

	
 

	
 
int restart_From_XML_backup();
 
int backup_list_XML();
 

	
 
#endif
0 comments (0 inline, 0 general)