// *Sandstorm's failed Password System in a Nut-Shell* //
// *You will need to replace certain things yourself as my* //
// *Mud is FAR from stock.* //

// *This file contains my password system, and my block system* //
// *As they are tighly wound together to aid in the protection of my* //
// *Mud.* //

// *Example: CD is CHAR_DATA, sendf is printf_to_char, LINK_SINGLE* //
// *is the standard linking code for making a singly linked list (i know, evil)* //

// *Bad Passwords*//
// *Why did i do this?  Because i was raised by a security professional* //
// *and am one myself, password protection is a VERY good thing.* //
// *If you know what the failed passwords were, then you have a good* //
// *Know-how of how close they were to cracking your password.  Which* //
// *btw, is better then not knowing that someone has tried to crack your password* //
// *Plus if they were close to getting it, you can easily change your password* //
// *Thus offsetting how close they were.* //

// *Block data* //
// *Okay, the internal blocking system parses through the muds updater.  And it only holds* //
// *the blocked data for 30 minutes, when its time it up, it clears, this is just a temporary block* //
// *to block out people with bad passwords, i know, i know, 30 minutes of blocking someone* //
// *for forgetting their own password, harsh eh, but they would be the first to complain if someone* //
// *broke into their account and there failed attempts weren't blocked. * //

// *Perm-Block* //
// *Okay, the block data contains afew things, however, i have discovered that blocking people perm* //
// *is fun, especialy when they try 37 times to break into an account and fail.* //
// *So this block system will count 'how' many times an ip has been blocked, and if it has been blocked* //
// *1 too many times, it will get perm-blocked, the perm-block is actauly allot less then one might think.* //
// *But 30 days is an aweful-long time.* //

// *Gets rid of the block-data after a day of not having to block someone.* //


// *Contact Me with questions, comments, whatever: ldevil@hotmail.com* //

// *This code uses the diku/merc/rom and smaug licences, you must agree to them all to use * //
// *This code.* //

// merc.h //

// *Failed Data* //
typedef struct failed_data FAILED_DATA;
typedef struct block_data BLOCK_DATA;

struct failed_data
{
	FAILED_DATA *next;
	char *login_password;
	char *ip;
	char *ident;
};

struct block_data
{
	BLOCK_DATA *next;
	char *ip;
	char *ident;
	int count;
	int timer;
	int permtimer;
	int rebock_count;
	bool blocked;
	bool perm;
	bool was_blocked;
                time_t  last_blocked;
};

#define MAX_ATTEMPTS     3

// *Saves time on doing the whole void do_whatever(CD *ch, char *argument)  makes life easier.* //
#define MUDCMD(name) void name(CD *ch, char *argument)

#define PULSE_BLOCK   (4 * PULSE_PER_SECOND)


// in pc_data; add
FAILED_DATA *failed;

with the externs add
extern BLOCK_DATA *block_list;

with the prototypes
void write_blocked(void);
void read_blocked(void);

// *act_wiz or ban.c... Your choice ;)* //

MUDCMD(do_blocked)
{
	BLOCK_DATA *blocked;
	BUFFER *output = NewBuf();
	int count = 0; 

	BufPrintf(output, "[Num] %16s %15s %8s %8s", "Ip Addr", "Host Name", "blocked", "perm");

	for(blocked = block_list; blocked; blocked = blocked->next)
	{
		count++;

		BufPrintf(output, "[%3d] %16s %15s %8s %8s\n\r", count, blocked->ip, blocked->ident, blocked->blocked ? "Yes" : "No", blocked->perm ? "Yes" : "No");
	}

	BufPrintf(output, "There are %d items on the blocked list.\n\r", count);	
	page_to_char(buf_string(output), ch);
	free_buf(output);
	return;
}

// *comm.c* //

with the rest of the local variables in comm.c

Add

BLOCK_DATA *block_list;

where it says 'wrong password' put this bellow it.
bad_password(d->character, argument);

Further Down, where it says do_look(ch, "auto");
put this right above it..

view_bad_passwords(ch);

in init_descriptor right before it sends the greeting, add this

// *Are you blocked?* //
if(check_blocked(dnew))
{
	write_to_buffer(d, "You are currently blocked for failed password attempts.\n\r" ,0);
	CloseSocket(dnew);
	return;
}

// *Add this function before init_descriptor* //

// *Check to see if they are blocked* //
bool check_blocked(DESCRIPTOR *d)
{
	BLOCK_DATA *block;

	// *Are you blocked?* //
	for(block = block_list; block; block = block->next)
	{
		// *Ensure they are blocked before checking* //
		if(block->blocked)
		{
			// *Got the IP yet?* //
			if(!StrCmp(block->ip, d->host))
				return true;

			// *Got their Ident yet?* //
			if(!StrCmp(block->ident, d->ident))
				return true;
		}
	}

	// *not blocked... YAY!* //
	return false;
}


// *Add these functions before the nanny becuase they are not globaly defined.* //

// *Log the failed password-count, and the password used.* //
void bad_password(CD *ch, char *password)
{
	FAILED_DATA *fail;
	BLOCK_DATA *block;

	// Add a new Failure.
	CREATE(fail, FAILED_DATA, 1);
	fail->login_password = StrDup(password);
	fail->ip	= StrDup(ch->desc->host);
	fail->ident = StrDup(ch->desc->ident);
	LINK_SINGLE(fail, next, ch->pcdata->failed);

	for(block = block_list; block; block = block->next)
	{
		if(!StrCmp(block->ip, fail->ip))
		{
			// *Failed attempts are getting up there.* //
			block->count++;

			// *Reset their timer, so they will ALWAYS have the 30 minute wait.* //
			block->timer = 0;
			if(block->count == MAX_ATTEMPTS)
			{
				sendf(ch,"You have been blocked for 30 minutes.\n\r");
				block->blocked = true;
				block->last_blocked = ctime(&current_time);
				if(block->was_blocked)
				{
					block->reblock_count++;

					// *Uh oh asshole, perm now.* //
					if(block->reblock_count == MAX_ATTEMPTS)
						block->perm = true;
				}
				
			}
			break;
		}
			
	}

	// *Not blocked eh?  Start the counter!* //
	if(!block)
	{
		CREATE(block, BLOCK_DATA, 1);
		LINK_SINGLE(block, next, block_list);
		block->ip = StrDup(fail->ip);
		block->ident = StrDup(fail->ip);
		block->count = 1;
		block->reblock_count = 0;
		block->blocked = false;
		block->perm = false;
		// Start the timer..  Will update every minute by 1
		block->timer = 0;
		block->last_blocked = ctime(&current_time);
	}

	// *Wiznet the failure* //
	wiznet_printf(NULL, NULL, WIZ_PASSWORD, 0, IMMORTAL, "%s has a failed login attempt from site %s:%s.",ch->name, fail->ip, fail->ident);
}


// *View the failed attempts* //
void view_bad_passwords(CD *ch)
{
	FAILED_DATA *fail, *fail_next;
	int counter = 0;

	// *Found a bad attempt* //
	if(ch->pcdata->failed)
	{
		sendf(ch, "[Num] %16s %15s %15s", "Password", "IP", "Ident");
	}

	for(fail = ch->pcdata->failed; fail; fail = fail_next)
	{
		fail_next = fail->next;
		sendf(ch, "[%3d] %16s %15s %15s", counter, fail->password, fail->ip, fail->ident);

		counter++;
		// *Remove it from the list.* //
		UNLINK_SINGLE(fail, next, FAILED_DATA, ch->pcdata->failed);

		// *Dispose of the failed data.* //
		DISPOSE(failed->password);
		DISPOSE(failed->ip);
		DISPOSE(failed->ident);
		DISPOSE(failed);
	}

	if(counter == 0)
		sendf(ch, "There have been no failed login attempts.\n\r" );
	else
		sendf(ch, "You have had %d failed login attempt%s.\n\r", counter, counter > 1 ? "s" : "");

	// *Just ending the function* //
	return;
}

// *in db.c* //

in boot_db, near the end after area-loading is done, add in this.

read_blocked();

after boot_db, before it goes into all the 'area' loading stuff, add these two functions.

void write_blocked(void)
{
	FILE *fp;
	BLOCK_DATA *block;

	if((fp = FileOpen(BLOCK_FILE, "w")) == NULL)
	{
		log_string("Odd, no block file.");
		// And then we continue;
	}

	for(block = block_list; block; block = block->next)
	{
		fprintf(fp, "BLOCK %s~ %s~ %d %d %ld %d\n", block->ip, block->ident, block->blocked, block->perm, block->last_blocked, block->reblock_count);
	}	

	fprintf(fp, "End");
	FileClose(fp);
}

void read_blocked(void)
{
	FILE *fp;

	if((fp = FileOpen(BLOCK_FILE, "r")) == NULL)
	{
		perror(BLOCK_FILE);
		return;
	}

	for( ;; )
	{
		BLOCK_DATA *block;
		char *wword = fread_word(fp);

		// *Sick eh?* //
		if(!StrCmp(wword, "End"))
			break;

		CREATE(block, BLOCK_DATA, 1);
		block->ip = fread_string(fp);
		block->ident = fread_string(fp);
		block->blocked = fread_number(fp);
		block->perm = fread_number(fp);
		block->last_blocked = fread_number(fp);
		block->reblock_count = fread_number(fp);

		// *Block them :)* //
		LINK_SINGLE(block, next, block_list);		
	}

	FileClose(fp);	
}

interp.c

Add do_blocked to both interp.c and interp.h


// *Save the failed password attempt* //
                  save.c

in fwrite_char Add

FAILED_DATA *fail;


and somewhere else, with all the for-loops.

Add..

for(fail = ch->pcdata->failed; fail; fail = fail->next)
{
	fprintf(fp, "Failed %s~ %s~ %s~", fail->login_password, fail->ip, fail->ident);
}


in fread_char, under 'F' Add

if(!StrCmp(wword, "Failed"))
{
	FAILED_DATA *fail;
	CREATE(fail, FAILED_DATA, 1);	

	// *So sick, yet so easy.* //
	fail->login_password = fread_string(fp);
	fail->ip = fread_string(fp);
	fail->ident = fread_string(fp);

	LINK_SINGLE(fail, next, ch->pcdata->failed);

	found = true;
	break;
}

in update.c  

Add this *before* update_handler

void update_blocks(void)
{
	BLOCK_DATA *block, *block_next;

	// *parse through the list.* //	
	for(block = block_list; block; block = block_next)
	{
		int month = block->last_blocked->tm_mon;
		int year = block->last_blocked->tm_year;
		bool remove = false;;

		block_next = block->next;

		block->timer++;

		// *Uh oh, the year is less then the system's year, this = bad! remove them!* //
		if(year < current_time->tm_year)
		{
			remove = true;
		}
		else if(month < current_time->tm_mon && !block->perm) // Okay, back to normality
		{
			if(day > current_time->tm_wday)
				remove = false;	
			else
				remove = true;
		}

		// *Yuppers, we gotta remove you.* //
		if(remove)
		{
			UNLINK_SINGLE(block, next, BLOCK_DATA, block_list);
			DISPOSE(block->ip);
			DISPOSE(block->ident);
			DISPOSE(block);
			continue;
		}

		// *Older then 30 minutes.  Remove the block.* //
		if(block->timer > 30 && !block->perm)
		{
			// *Close the blocked data* //
			block->count = 0;
			block->blocked = false;
			block->perm = false;
			block->was_blocked = true;
		}
		else if(block->timer > 720000 && block->perm) // *Older then 1 month (i think)* //
		{
			// *Close the blocked data* //
			block->count = 0;
			block->blocked = false;
			block->was_blocked = true;
			block->perm = false;
		}
	}

	// *Thats right, everytime we update, re-write the file.* //
	write_blocked();
}

in update_handler Add this.

	static int pulse_block;

	if(--pulse_block % PULSE_BLOCK)
	{
		pulse_block = PULSE_BLOCK;
		update_blocks();
	}