RaM Fire Updated/
/*
 * RAM $Id: ban.c 84 2009-01-17 23:19:56Z ghasatta $
 */

/***************************************************************************   
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

/***************************************************************************
*       ROM 2.4 is copyright 1993-1998 Russ Taylor                         *
*       ROM has been brought to you by the ROM consortium                  *
*           Russ Taylor (rtaylor@hypercube.org)                            *
*           Gabrielle Taylor (gtaylor@hypercube.org)                       *
*           Brian Moore (zump@rom.org)                                     *
*       By using this code, you have agreed to follow the terms of the     *
*       ROM license, in the file Rom24/doc/rom.license                     *
***************************************************************************/

#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#include <string>

#include "merc.h"
#include "strings.h"
#include "db.h"
#include "interp.h"
#include "ban.h"

#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <iomanip>

ban_manager *ban_manager::pInstance = NULL;

ban_manager *ban_manager::Instance()
{
    if(ban_manager::pInstance == NULL)
	ban_manager::pInstance = new ban_manager;

    return ban_manager::pInstance;
}

ban_manager::ban_manager()
{
}

ban_manager::~ban_manager()
{
}

bool ban_manager::add_ban(int _level, std::string _name, int _type, bool _perm)
{
    std::transform(_name.begin(), _name.end(), _name.begin(), tolower);
    
    // remove existing ban for _name, if any.
    if(this->remove_ban(_level, _name, false) == true)
    {
	ban_data new_ban(_level, _name, _type, _perm);
	
	this->bans.push_back(new_ban);

	this->save_bans();   
    
	return true;
    }

    else
	return false;
}

bool ban_manager::remove_ban(int _level, std::string _name, bool should_save /* = true */)
{
    std::transform(_name.begin(), _name.end(), _name.begin(), tolower);

    // remove any asterisks.
    if(_name.empty() == false)
    {
	if(_name.at(0) == '*')
	    _name.erase(0,1);

	if(_name.at(_name.size() - 1) == '*')
	    _name.erase(_name.size() - 1, 1);
    }
    
    std::vector<ban_data>::iterator existing_iter = this->find_ban(_name, true);

    // if we already have a ban for _name, verify that _level is sufficient to change the ban. If so, remove the existing ban.
    if(existing_iter != this->bans.end())
    {
	if(_level < existing_iter->level)
	    return false;

	else
	    this->bans.erase(existing_iter);
    }

    if(should_save == true)
	this->save_bans();

    return true;
}

void ban_manager::save_bans() const
{
    std::ofstream out;

    out.exceptions(std::ios::eofbit | std::ios::failbit | std::ios::badbit);

    try
    {
	out.open(BAN_FILE_NEW, std::ios::trunc);

	for(std::vector<ban_data>::const_iterator i = this->bans.begin(); i != this->bans.end(); ++i)
	    i->write_save(out);

	out.close();
    }

    catch(std::exception &e)
    {
	log_error("ban_manager::save_bans(): %s", e.what());
    }
}

void ban_manager::load_bans()
{
    if(this->load_old_format() == false)
    {
	std::ifstream in;
	
	in.exceptions(std::ios::eofbit | std::ios::failbit | std::ios::badbit);
    
	try
	{
	    in.open(BAN_FILE_NEW, std::ios::in);

	    while(in.eof() == false)
	    {
		std::string name;
		int level;
		std::string flags;
		
		in >> name >> level >> flags;
		
		this->bans.push_back(ban_data(level, name, str_read_flag(flags.c_str())));
	    }
	    
	    in.close();
	}
	
	catch(std::exception &e)
	{
	    log_error("ban_manager::load_bans(): %s", e.what());
	}
    }
}

bool ban_manager::load_old_format()
{
    FILE                   *fp = NULL;
    
    if ( ( fp = fopen( BAN_FILE, "r" ) ) == NULL )
        return false;
    
    while(!feof(fp))
    {
	std::string name = fread_word( fp );
	int level = fread_number( fp );
        int ban_flags = fread_flag( fp );
        fread_to_eol( fp );

	this->bans.push_back(ban_data(level, name, ban_flags));
    }

    fclose(fp);

    // write in the new format.
    this->save_bans();

    // remove the old file.
    unlink(BAN_FILE);

    return true;
}

std::string ban_manager::print_bans() const
{
    std::ostringstream out;
    const char *_type;
    const char *_perm;
    
    out << "Banned sites  level  type     status\r\n";

    for(std::vector<ban_manager::ban_data>::const_iterator i = this->bans.begin(); i != this->bans.end(); ++i)
    {
	std::string name;
	
	if(IS_SET(i->ban_flags, BAN_PREFIX))
	    name += "*";

	name += i->name;

	if(IS_SET(i->ban_flags, BAN_SUFFIX))
	    name += "*";

	out << std::setw(12) << name << "  ";

	out << std::setprecision(3) << i->level << "  ";

	_type = IS_SET( i->ban_flags, BAN_NEWBIES ) ? "newbies" :
	    IS_SET( i->ban_flags, BAN_PERMIT ) ? "permit" :
	    IS_SET( i->ban_flags, BAN_ALL ) ? "all" : "";

	_perm = IS_SET( i->ban_flags, BAN_PERMANENT ) ? "perm" : "temp";
	
	out << std::setw(7) << std::left << _type << "  ";
	
	out << _perm;

	out << "\r\n";
    }

    if(this->bans.empty() == true)
	out << "(none)" << "\r\n";

    return out.str();
}
    

std::vector<ban_manager::ban_data>::iterator ban_manager::find_ban(std::string _name, bool exact_match /* = false */)
{
    std::transform(_name.begin(), _name.end(), _name.begin(), tolower);
    
    for(std::vector<ban_manager::ban_data>::iterator i = this->bans.begin(); i != this->bans.end(); ++i)
	if(i->is_match(_name, exact_match) == true)
	    return i;

    // else... not found
    return this->bans.end();
}

std::vector<ban_manager::ban_data>::const_iterator ban_manager::find_ban(std::string _name, bool exact_match /* = false */) const
{
    std::transform(_name.begin(), _name.end(), _name.begin(), tolower);
    
    for(std::vector<ban_manager::ban_data>::const_iterator i = this->bans.begin(); i != this->bans.end(); ++i)
	if(i->is_match(_name, exact_match) == true)
	    return i;

    // else... not found
    return this->bans.end();
}

bool ban_manager::is_banned(std::string _name, int _type) const
{
    // look for an exact match first.
    std::vector<ban_manager::ban_data>::const_iterator i = this->find_ban(_name, true);

    // if we didn't find an exact match, look for a wildcard match next.
    if(i == this->bans.end())
	i = this->find_ban(_name, false);
    
    // if we still haven't found a matching ban, there is no ban, return false.
    if(i == this->bans.end())
	return false;

    else if(IS_SET(i->ban_flags, _type))
	return true;

    // else...
    return false;
}

ban_manager::ban_data::ban_data(int _level, std::string _name, int flags)
{
    name = _name;
    level = _level;
    ban_flags = flags;
}

ban_manager::ban_data::ban_data(int _level, std::string _name, int _type, bool _perm)
{
    name = _name;
    level = _level;
    ban_flags = _type;

    if(_perm == true)
	SET_BIT(this->ban_flags, BAN_PERMANENT);

    this->parse_name();
}

ban_manager::ban_data::~ban_data()
{
}

void ban_manager::ban_data::parse_name()
{
    // transform to lowercase.
    std::transform(this->name.begin(), this->name.end(), this->name.begin(), tolower);
    
    if(name.find("*") == 0)
    {
	name.erase(0, 1);
	SET_BIT(this->ban_flags, BAN_PREFIX);
    }

    if(name.rfind("*") == (name.size() - 1))
    {
	SET_BIT(this->ban_flags, BAN_SUFFIX);
	name.erase(name.size() - 1);
    }

    // if name is empty, it means both suffix and prefix are true.
    else if(name.empty() == true)
	SET_BIT(this->ban_flags, BAN_SUFFIX);
}

bool ban_manager::ban_data::is_match(std::string _name, bool exact_match) const
{
    // transform arg to lowercase.
    std::transform(_name.begin(), _name.end(), _name.begin(), tolower);
    
    if( (exact_match == true && this->name == _name) ||
	(exact_match == false && IS_SET(this->ban_flags, BAN_PREFIX) && _name.rfind(this->name) == (_name.size() - this->name.size())) ||
	(exact_match == false && IS_SET(this->ban_flags, BAN_SUFFIX) && _name.find(this->name) == 0) )
	return true;

    // else...
    return false;
}

void ban_manager::ban_data::write_save(std::ofstream &out) const
{
    // only write if this is a permanent ban
    if(IS_SET(this->ban_flags, BAN_PERMANENT))
	out << this->name << " "
	    << this->level << " "
	    << print_flags(this->ban_flags) << std::endl;
}


void ban_manager::do_ban(CHAR_DATA *ch, const char *argument)
{
    char arg1[MAX_INPUT_LENGTH];
    char arg2[MAX_INPUT_LENGTH];
    char arg3[MAX_INPUT_LENGTH];

    argument = one_argument(argument, arg1);
    argument = one_argument(argument, arg2);
    argument = one_argument(argument, arg3);
    
    // no arg means just print the ban list.
    if(arg1[0] == '\0')
	send_to_char(ban_manager::Instance()->print_bans().c_str(), ch);

    // else we want to add a ban
    else
    {
	std::string name(arg1);
	int type = 0;
	bool perm;
	
	// arg1 is name
	// arg2 is type
	// arg3 is either perm or temp

	// verify name contains something besides the wildcard character.
	if(name.find_first_not_of("*") == std::string::npos)
	{
	    ch_printf(ch, "Ban name must include at least 1 character besides the wildcard '*'");
	    return;
	}
	
	if(!str_prefix(arg2, "all"))
	    type = BAN_ALL;
	else if(!str_prefix(arg2, "newbies"))
	    type = BAN_NEWBIES;
	else if(!str_prefix(arg2, "permit"))
	    type = BAN_PERMIT;
	else
	{
	    ch_printf( ch, "Acceptable ban types are all, newbies, and permit.\r\n" );
	    return;
	}

	if(!str_prefix(arg3, "temp"))
	    perm = false;
	else if(!str_prefix(arg3, "perm"))
	    perm = true;
	else
	{
	    ch_printf(ch, "Error: Must specify either 'perm' or 'temp' for ban length.\r\n");
	    return;
	}
	
	bool success = ban_manager::Instance()->add_ban(get_trust(ch), name, type, perm);

	if(success == true)
	    ch_printf(ch, "%s has been banned.\r\n", name.c_str());
	else
	    ch_printf(ch, "Error: Ban was not successful. Check for existing bans set to a level higher than your trust level.\r\n");
    }
}

void ban_manager::do_permban(CHAR_DATA *ch, const char *argument)
{
    send_to_char("Error: Permban command is obsolete. Use: ban sitename type perm/temp", ch);
}

void ban_manager::do_allow(CHAR_DATA *ch, const char *argument)
{
    char arg1[MAX_INPUT_LENGTH];
    bool success;

    one_argument(argument, arg1);

    if(arg1[0] == '\0')
    {
        ch_printf( ch, "Remove which site from the ban list?\r\n" );
        return;
    }

    success = ban_manager::Instance()->remove_ban(get_trust(ch), arg1);

    if(success == true)
	ch_printf( ch, "Ban on %s lifted.\r\n", arg1 );
    else
	ch_printf( ch, "You are not powerful enough to lift that ban.\r\n" );
}