Files @ c318109520cc
Branch filter:

Location: DistRen/src/server/user_mgr.c

binki
User authentication and some access checking.
/*
 *  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
{
  /* items are of type user_t */
  list_t user_list;
  /* where to load/save the userlist */
  char *userlist_filename;
};

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

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

user_t user_find(user_mgr_t user_mgr, const char *username)
{
  int list_direction;
  list_traverse_func_t traverse_func;

  user_t user;
  char *username_copy;

  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;

  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;
    };

  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;

  if(!strcmp(username, user->username))
    return user;

  return NULL;
}

int user_add(user_mgr_t user_mgr, const char *username, const char *pass)
{
  user_t user;
  short insert_after;

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

  /*
   * 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;
    }

  /*
   * 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;

  /* 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 0;
}

/**
 * \brief For list_free() et al
 */
static void user_free(user_t user)
{
  free(user->username);
  free(user->pass);
  free(user);
}

int user_delete(user_mgr_t user_mgr, user_t user)
{
  user_t user_found;
  int ret;

  ret = 0;

  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;
    }

  user_free(user);

  return ret;
}

static int user_mgr_save_traverse(xmlTextWriterPtr writer, user_t user)
{
  char *tmp;

  xmlTextWriterStartElement(writer, (xmlChar *)"user");

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

  _distren_asprintf(&tmp, "%d", user->last_job);
  xmlTextWriterWriteAttribute(writer, (xmlChar *)"last_job", (xmlChar*)tmp);
  free(tmp);

  _distren_asprintf(&tmp, "%d", user->render_power);
  xmlTextWriterWriteAttribute(writer, (xmlChar *)"render_power", (xmlChar*)tmp);
  free(tmp);

  xmlTextWriterEndElement(writer);

  return TRUE;
}

int user_mgr_save(user_mgr_t user_mgr, const char *filename)
{
  xmlTextWriterPtr writer;

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

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

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

  /**
   * \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 0;
}

user_mgr_t user_mgr_init(const char *userfile)
{
  xmlDocPtr doc;
  xmlNodePtr cur;

  user_mgr_t user_mgr;

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

  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, "user_mgr: xml document is wrong type. Using empty userlist, which will overwrite the old userlist soon probably...");
      xmlFreeDoc(doc);
      return user_mgr;
    }

  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;
	}

      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;
	}

      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);

      /*
       * 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;
}