btmux-0.6-rc4/doc/
btmux-0.6-rc4/event/
btmux-0.6-rc4/game/
btmux-0.6-rc4/game/maps/
btmux-0.6-rc4/game/mechs/
btmux-0.6-rc4/game/text/help/
btmux-0.6-rc4/game/text/help/cat_faction/
btmux-0.6-rc4/game/text/help/cat_inform/
btmux-0.6-rc4/game/text/help/cat_misc/
btmux-0.6-rc4/game/text/help/cat_mux/
btmux-0.6-rc4/game/text/help/cat_mux/cat_commands/
btmux-0.6-rc4/game/text/help/cat_mux/cat_functions/
btmux-0.6-rc4/game/text/help/cat_templates/
btmux-0.6-rc4/game/text/wizhelp/
btmux-0.6-rc4/include/
btmux-0.6-rc4/misc/
btmux-0.6-rc4/python/
btmux-0.6-rc4/src/hcode/btech/
btmux-0.6-rc4/tree/
/*
 * player.c 
 */

#include "copyright.h"
#include "config.h"

#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "interface.h"
#include "externs.h"
#include "alloc.h"
#include "attrs.h"
#include "powers.h"
#include "command.h"

#define	NUM_GOOD	4			/*
								 * # of successful logins to save data for 
								 */
#define NUM_BAD		3			/*
								 * # of failed logins to save data for 
								 */

typedef struct hostdtm HOSTDTM;
struct hostdtm {
	char *host;
	char *dtm;
};

typedef struct logindata LDATA;
struct logindata {
	HOSTDTM good[NUM_GOOD];
	HOSTDTM bad[NUM_BAD];
	int tot_good;
	int tot_bad;
	int new_bad;
};

extern char *crypt(const char *, const char *);
extern time_t time(time_t *);

/**
 * Decode login info.
 */
static void decrypt_logindata(char *atrbuf, LDATA * info)
{
	int i;
	char *tmpc;

	info->tot_good = 0;
	info->tot_bad = 0;
	info->new_bad = 0;
	for(i = 0; i < NUM_GOOD; i++) {
		info->good[i].host = NULL;
		info->good[i].dtm = NULL;
	}
	for(i = 0; i < NUM_BAD; i++) {
		info->bad[i].host = NULL;
		info->bad[i].dtm = NULL;
	}

	if(*atrbuf == '#') {
		atrbuf++;
		if(!(tmpc = grabto(&atrbuf, ';')))
			return;
		info->tot_good = atoi(tmpc);
		for(i = 0; i < NUM_GOOD; i++) {
			if(!(tmpc = grabto(&atrbuf, ';')))
				return;
			info->good[i].host = tmpc;
			if(!(tmpc = grabto(&atrbuf, ';')))
				return;
			info->good[i].dtm = tmpc;
		}
		if(!(tmpc = grabto(&atrbuf, ';')))
			return;
		info->new_bad = atoi(tmpc);
		if(!(tmpc = grabto(&atrbuf, ';')))
			return;
		info->tot_bad = atoi(tmpc);
		for(i = 0; i < NUM_BAD; i++) {
			if(!(tmpc = grabto(&atrbuf, ';')))
				return;
			info->bad[i].host = tmpc;
			if(!(tmpc = grabto(&atrbuf, ';')))
				return;
			info->bad[i].dtm = tmpc;
		}
	}
}

/**
 * Encode login info.
 */
static void encrypt_logindata(char *atrbuf, LDATA * info)
{
	char *bp, nullc;
	int i;

	/*
	 * Make sure the SPRINTF call tracks NUM_GOOD and NUM_BAD for the * * 
	 * 
	 * *  * * number of host/dtm pairs of each type. 
	 */

	nullc = '\0';
	for(i = 0; i < NUM_GOOD; i++) {
		if(!info->good[i].host)
			info->good[i].host = &nullc;
		if(!info->good[i].dtm)
			info->good[i].dtm = &nullc;
	}
	for(i = 0; i < NUM_BAD; i++) {
		if(!info->bad[i].host)
			info->bad[i].host = &nullc;
		if(!info->bad[i].dtm)
			info->bad[i].dtm = &nullc;
	}
	bp = alloc_lbuf("encrypt_logindata");
	sprintf(bp, "#%d;%s;%s;%s;%s;%s;%s;%s;%s;%d;%d;%s;%s;%s;%s;%s;%s;",
			info->tot_good, info->good[0].host, info->good[0].dtm,
			info->good[1].host, info->good[1].dtm, info->good[2].host,
			info->good[2].dtm, info->good[3].host, info->good[3].dtm,
			info->new_bad, info->tot_bad, info->bad[0].host, info->bad[0].dtm,
			info->bad[1].host, info->bad[1].dtm, info->bad[2].host,
			info->bad[2].dtm);
	StringCopy(atrbuf, bp);
	free_lbuf(bp);
}

/**
 * Record successful or failed login attempt.
 * If successful, report last successful login and number of failures since
 * last successful login.
 */
void record_login(dbref player, int isgood, char *ldate, char *lhost,
				  char *lusername)
{
	LDATA login_info;
	char *atrbuf;
	dbref aowner;
	int aflags, i;

	atrbuf = atr_get(player, A_LOGINDATA, &aowner, &aflags);
	decrypt_logindata(atrbuf, &login_info);
	if(isgood) {
		if(login_info.new_bad > 0) {
			notify(player, "");
			notify_printf(player,
						  "**** %d failed connect%s since your last successful connect. ****",
						  login_info.new_bad,
						  (login_info.new_bad == 1 ? "" : "s"));
			notify_printf(player,
						  "Most recent attempt was from %s on %s.",
						  login_info.bad[0].host, login_info.bad[0].dtm);
			notify(player, "");
			login_info.new_bad = 0;
		}
		if(login_info.good[0].host && *login_info.good[0].host &&
		   login_info.good[0].dtm && *login_info.good[0].dtm) {
			notify_printf(player, "Last connect was from %s on %s.",
						  login_info.good[0].host, login_info.good[0].dtm);
		}
		if(mudconf.have_mailer)
			check_mail(player, 0, 0);
		for(i = NUM_GOOD - 1; i > 0; i--) {
			login_info.good[i].dtm = login_info.good[i - 1].dtm;
			login_info.good[i].host = login_info.good[i - 1].host;
		}
		login_info.good[0].dtm = ldate;
		login_info.good[0].host = lhost;
		login_info.tot_good++;
		if(*lusername)
			atr_add_raw(player, A_LASTSITE, tprintf("%s@%s", lusername,
													lhost));
		else
			atr_add_raw(player, A_LASTSITE, lhost);
	} else {
		for(i = NUM_BAD - 1; i > 0; i--) {
			login_info.bad[i].dtm = login_info.bad[i - 1].dtm;
			login_info.bad[i].host = login_info.bad[i - 1].host;
		}
		login_info.bad[0].dtm = ldate;
		login_info.bad[0].host = lhost;
		login_info.tot_bad++;
		login_info.new_bad++;
	}
	encrypt_logindata(atrbuf, &login_info);
	atr_add_raw(player, A_LOGINDATA, atrbuf);
	free_lbuf(atrbuf);
}

/**
 * Test a password to see if it is correct.
 */
int check_pass(dbref player, const char *password)
{
	dbref aowner;
	int aflags;
	char *target;
	char *hashed;

	target = atr_get(player, A_PASS, &aowner, &aflags);
	hashed = crypt(password, "XX");
	if(*target && strcmp(target, password) && strcmp(hashed, target)) {
		free_lbuf(target);
		return 0;
	}
	free_lbuf(target);

	/*
	 * This is needed to prevent entering the raw encrypted password from
	 * working. Do it better if you like, but it's needed. 
	 *
	 * Not really, you should just not really allow unencrypted passwords.
	 * -Hag
	 */

	if((strlen(password) == 13) && (password[0] == 'X') &&
	   (password[1] == 'X'))
		return 0;

	return 1;
}

/**
 * Try to connect to an existing player.
 */
dbref connect_player(char *name, char *password, char *host, char *username)
{
	dbref player, aowner;
	int aflags;
	time_t tt;
	char *time_str, *player_last, *allowance;

	time(&tt);
	time_str = ctime(&tt);
	time_str[strlen(time_str) - 1] = '\0';

	if((player = lookup_player(NOTHING, name, 0)) == NOTHING)
		return NOTHING;
	if(!check_pass(player, password)) {
		record_login(player, 0, time_str, host, username);
		return NOTHING;
	}
	time(&tt);
	time_str = ctime(&tt);
	time_str[strlen(time_str) - 1] = '\0';

	/*
	 * compare to last connect see if player gets salary 
	 */
	player_last = atr_get(player, A_LAST, &aowner, &aflags);
	if(strncmp(player_last, time_str, 10) != 0) {
		allowance = atr_pget(player, A_ALLOWANCE, &aowner, &aflags);
		if(*allowance == '\0')
			giveto(player, mudconf.paycheck);
		else
			giveto(player, atoi(allowance));
		free_lbuf(allowance);
	}
	atr_add_raw(player, A_LAST, time_str);
	free_lbuf(player_last);
	return player;
}

/**
 * Create a new player.
 */
dbref create_player(char *name, char *password, dbref creator, int isrobot,
					int isguest)
{
	dbref player;
	char *pbuf;

	/*
	 * Make sure the password is OK.  Name is checked in create_obj 
	 */

	pbuf = trim_spaces(password);
	if(!ok_password(pbuf)) {
		free_lbuf(pbuf);
		return NOTHING;
	}
	/*
	 * If so, go create him 
	 */

	player = create_obj(creator, TYPE_PLAYER, name, isrobot);
	if(player == NOTHING) {
		free_lbuf(pbuf);
		return NOTHING;
	}
	/*
	 * initialize everything 
	 */
	if(isguest) {
		if(*mudconf.guests_channel)
			do_addcom(player, player, 0, "g", mudconf.guests_channel);
	} else {
		if(*mudconf.public_channel)
			do_addcom(player, player, 0, "pub", mudconf.public_channel);
	}

	s_Pass(player, crypt(pbuf, "XX"));
	s_Home(player, start_home());
	s_Fixed(player);
	free_lbuf(pbuf);
	return player;
}

/**
 * Change the password for a player
 */
void do_password(dbref player, dbref cause, int key, char *oldpass,
				 char *newpass)
{
	dbref aowner;
	int aflags;
	char *target;

	target = atr_get(player, A_PASS, &aowner, &aflags);
	if(!*target || !check_pass(player, oldpass)) {
		notify(player, "Sorry.");
	} else if(!ok_password(newpass)) {
		notify(player, "Bad new password.");
	} else {
		atr_add_raw(player, A_PASS, crypt(newpass, "XX"));
		notify(player, "Password changed.");
	}
	free_lbuf(target);
}

/**
 * Display login history data.
 */
static void disp_from_on(dbref player, char *dtm_str, char *host_str)
{
	if(dtm_str && *dtm_str && host_str && *host_str) {
		notify_printf(player, "     From: %s   On: %s", dtm_str, host_str);
	}
}

void do_last(dbref player, dbref cause, int key, char *who)
{
	dbref target, aowner;
	LDATA login_info;
	char *atrbuf;
	int i, aflags;

	if(!who || !*who) {
		target = Owner(player);
	} else if(!(string_compare(who, "me"))) {
		target = Owner(player);
	} else {
		target = lookup_player(player, who, 1);
	}

	if(target == NOTHING) {
		notify(player, "I couldn't find that player.");
	} else if(!Controls(player, target)) {
		notify(player, "Permission denied.");
	} else {
		atrbuf = atr_get(target, A_LOGINDATA, &aowner, &aflags);
		decrypt_logindata(atrbuf, &login_info);

		notify_printf(player, "Total successful connects: %d",
					  login_info.tot_good);
		for(i = 0; i < NUM_GOOD; i++) {
			disp_from_on(player, login_info.good[i].host,
						 login_info.good[i].dtm);
		}
		notify_printf(player, "Total failed connects: %d",
					  login_info.tot_bad);
		for(i = 0; i < NUM_BAD; i++) {
			disp_from_on(player, login_info.bad[i].host,
						 login_info.bad[i].dtm);
		}
		free_lbuf(atrbuf);
	}
}

/*
 * add_player_name, delete_player_name, lookup_player:
 * Manage playername->dbref mapping
 */
int add_player_name(dbref player, char *name)
{
	int stat;
	dbref *p;
	char *temp, *tp;

	/*
	 * Convert to all lowercase 
	 */

	tp = temp = alloc_lbuf("add_player_name");
	safe_str(name, temp, &tp);
	*tp = '\0';
	for(tp = temp; *tp; tp++)
		*tp = ToLower(*tp);

	p = (int *) hashfind(temp, &mudstate.player_htab);
	if(p) {

		/*
		 * Entry found in the hashtable.  If a player, succeed if the
		 * * * numbers match (already correctly in the hash table),
		 * fail * * if they don't.  Fail if the name is a disallowed
		 * name * * (value AMBIGUOUS). 
		 */

		if(*p == AMBIGUOUS) {
			free_lbuf(temp);
			return 0;
		}
		if(Good_obj(*p) && (Typeof(*p) == TYPE_PLAYER)) {
			free_lbuf(temp);
			if(*p == player) {
				return 1;
			} else {
				return 0;
			}
		}
		/*
		 * It's an alias (or an incorrect entry).  Clobber it 
		 */
		free(p);
		p = (dbref *) malloc(sizeof(int));

		*p = player;
		stat = hashrepl(temp, p, &mudstate.player_htab);
		free_lbuf(temp);
	} else {
		p = (dbref *) malloc(sizeof(int));

		*p = player;
		stat = hashadd(temp, p, &mudstate.player_htab);
		free_lbuf(temp);
		stat = (stat < 0) ? 0 : 1;
	}
	return stat;
}

int delete_player_name(dbref player, char *name)
{
	dbref *p;
	char *temp, *tp;

	tp = temp = alloc_lbuf("delete_player_name");
	safe_str(name, temp, &tp);
	*tp = '\0';
	for(tp = temp; *tp; tp++)
		*tp = ToLower(*tp);

	p = (int *) hashfind(temp, &mudstate.player_htab);
	if(!p || (*p == NOTHING) || ((player != NOTHING) && (*p != player))) {
		free_lbuf(temp);
		return 0;
	}
	free(p);
	hashdelete(temp, &mudstate.player_htab);
	free_lbuf(temp);
	return 1;
}

dbref lookup_player(dbref doer, char *name, int check_who)
{
	dbref *p, thing;
	char *temp, *tp;

	if(!string_compare(name, "me"))
		return doer;

	if(*name == NUMBER_TOKEN) {
		name++;
		if(!is_number(name))
			return NOTHING;
		thing = atoi(name);
		if(!Good_obj(thing))
			return NOTHING;
		if(!((Typeof(thing) == TYPE_PLAYER) || God(doer)))
			thing = NOTHING;
		return thing;
	}
	tp = temp = alloc_lbuf("lookup_player");
	safe_str(name, temp, &tp);
	*tp = '\0';
	for(tp = temp; *tp; tp++)
		*tp = ToLower(*tp);
	p = (int *) hashfind(temp, &mudstate.player_htab);
	free_lbuf(temp);
	if(!p) {
		if(check_who) {
			thing = find_connected_name(doer, name);
			if(Dark(thing))
				thing = NOTHING;
		} else
			thing = NOTHING;
	} else if(!Good_obj(*p)) {
		thing = NOTHING;
	} else
		thing = *p;

	return thing;
}

void load_player_names(void)
{
	dbref i, j, aowner;
	int aflags;
	char *alias;

	j = 0;
	DO_WHOLE_DB(i) {
		if(Typeof(i) == TYPE_PLAYER) {
			add_player_name(i, Name(i));
		}
	}
	alias = alloc_lbuf("load_player_names");
	j = 0;
	DO_WHOLE_DB(i) {
		if(Typeof(i) == TYPE_PLAYER) {
			alias = atr_pget_str(alias, i, A_ALIAS, &aowner, &aflags);
			if(*alias)
				add_player_name(i, alias);
		}
	}
	free_lbuf(alias);
}

/**
 * badname_add, badname_check, badname_list: Add/look for/display bad names.
 */
void badname_add(char *bad_name)
{
	BADNAME *bp;

	/*
	 * Make a new node and link it in at the top 
	 */

	bp = (BADNAME *) XMALLOC(sizeof(BADNAME), "badname.struc");
	bp->name = XMALLOC(strlen(bad_name) + 1, "badname.name");
	bp->next = mudstate.badname_head;
	mudstate.badname_head = bp;
	StringCopy(bp->name, bad_name);
}

void badname_remove(char *bad_name)
{
	BADNAME *bp, *backp;

	/*
	 * Look for an exact match on the bad name and remove if found 
	 */

	backp = NULL;
	for(bp = mudstate.badname_head; bp; backp = bp, bp = bp->next) {
		if(!string_compare(bad_name, bp->name)) {
			if(backp)
				backp->next = bp->next;
			else
				mudstate.badname_head = bp->next;
			XFREE(bp->name, "badname.name");
			XFREE(bp, "badname.struc");
			return;
		}
	}
}

int badname_check(char *bad_name)
{
	BADNAME *bp;

	/*
	 * Walk the badname list, doing wildcard matching.  If we get a hit * 
	 * 
	 * *  * *  * * then return false.  If no matches in the list, return
	 * true.  
	 */

	for(bp = mudstate.badname_head; bp; bp = bp->next) {
		if(quick_wild(bp->name, bad_name))
			return 0;
	}
	return 1;
}

void badname_list(dbref player, const char *prefix)
{
	BADNAME *bp;
	char *buff, *bufp;

	/*
	 * Construct an lbuf with all the names separated by spaces 
	 */

	buff = bufp = alloc_lbuf("badname_list");
	safe_str((char *) prefix, buff, &bufp);
	for(bp = mudstate.badname_head; bp; bp = bp->next) {
		safe_chr(' ', buff, &bufp);
		safe_str(bp->name, buff, &bufp);
	}
	*bufp = '\0';

	/*
	 * Now display it 
	 */

	notify(player, buff);
	free_lbuf(buff);
}