#!/usr/local/bin/perl -w
# $Id: ngbbsd.pl,v 0.1.6 2003/07/22 13:50:23 x86 Exp $
# $Header: /usr/local/ngbbs/ngbbsd.pl,v 0.1.6 2003/07/22 13:50:23 x86 Exp $
#
#
#    ngBBS-0.1.6 - Next Generation BBS Daemon
#    Copyright (C) 2003 Bryce Porter
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program 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 General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
#    Author: Bryce Porter <bryce at duskglow dot com>
#
#

$|++;

use strict;
use IO::Socket;
use Sys::Hostname;
use Symbol;
use POSIX;
use Fcntl qw( F_GETFL F_GETFD F_SETFL O_NONBLOCK );
use DBI;
use Time::HiRes qw( gettimeofday tv_interval );
use Digest::MD5 qw( md5_hex );
use Term::ANSIColor qw( :constants );
use Term::ANSIScreen;
use FindBin qw( $RealBin $RealScript );
use Getopt::Long;

my %conf = (	'bbsname'		=>	'Your BBS Name',
		'banner'		=>	'ngbbs-0.1.6',
		'port'			=>	23,
		'jail'			=>	'/usr/local/ngbbs',
		'prefork'		=>	10,
		'mpc'			=>	10,
		'tz'			=>	'CST',
		'dbtype'		=>	'mysql',
		'dbname'		=>	'ngbbs',
		'dbuser'		=>	'ngbbs',
		'dbpass'		=>	'password',
		'user'			=>	'ngbbs',
		'group'			=>	'ngbbs',
		'max_con_per_host'	=>	5,
		'max_con_per_user'	=>	1	);

my %proc;
GetOptions(	"restart"	=>	\$proc{r},
		"stop"		=>	\$proc{s}	);

my $ts = strftime "%Y%m%d%H%M%S", localtime;
if ($proc{r}) {
	if (-f "$conf{jail}/var/run/ngbbs.pid") {
		open my $pf, "<$conf{jail}/var/run/ngbbs.pid"||die "Failed to open pid file: $!\n";
		my $pid = <$pf>;
		close $pf;
		chomp($pid);
		print "Restarting $RealScript\n";
		kill 'HUP' => $pid;
		exit;
	}
	else {
		print "Failed to find pid file, is $RealScript running?\n";
		exit;
	}
}
if ($proc{s}) {
	if (-f "$conf{jail}/var/run/ngbbs.pid") {
		open my $pf, "<$conf{jail}/var/run/ngbbs.pid"||die "Failed to open pid file: $!\n";
		my $pid = <$pf>;
		close $pf;
		chomp($pid);
		print "Stopping $RealScript\n";
		kill 'TERM' => $pid;
		exit;
	}
	else {
		print "Failed to find pid file, is $RealScript running?\n";
		exit;
	}
}

my %months = (	'01'	=>	'January',
		'02'	=>	'February',
		'03'	=>	'March',
		'04'	=>	'April',
		'05'	=>	'May',
		'06'	=>	'June',
		'07'	=>	'July',
		'08'	=>	'August',
		'09'	=>	'September',
		'10'	=>	'October',
		'11'	=>	'November',
		'12'	=>	'December'	);

my %colors = (	'1'	=>	'RED',
		'2'	=>	'GREEN',
		'3'	=>	'YELLOW',
		'4'	=>	'BLUE',
		'5'	=>	'MAGENTA',
		'6'	=>	'CYAN',
		'7'	=>	'WHITE'	);

$ts = strftime "%Y%m%d%H%M%S", localtime;
if (-f "$conf{jail}/var/run/ngbbs.pid") {
	open my $pf, "<$conf{jail}/var/run/ngbbs.pid";
	my $pid = <$pf>;
	close $pf;
	chomp($pid);
	print "$RealScript already running as pid $pid? If not, delete $conf{jail}/var/run/ngbbs.pid\n";
	exit;
}
else {
	print "Starting $RealScript\n";
}

# Re-direct output so anything that is inevitably outputted does not slew all
# over the terminal
close STDIN;
open STDERR, ">>/$conf{jail}/var/log/errorlog";
open STDOUT, ">>/$conf{jail}/var/log/messages";
open CONLOG, ">>/$conf{jail}/var/log/conlog";

print STDOUT "$RealScript ($$) [$ts] $conf{banner} Initializing [UID:$</GID:$>]\n";

# Establish socket, bind and listen
my $server = IO::Socket::INET->new(	LocalPort	=>	$conf{port},
					Type		=>	SOCK_STREAM,
					Proto		=>	'tcp',
					Reuse		=>	1,
					Listen		=>	10,
					Timeout		=>	1,
					Blocking	=>	0	)||die "Failed to make socket: $!\n";

my $flags = fcntl($server, F_GETFL, 0)||die "Failed to get flags for socket: $!\n";
$flags = fcntl($server, F_SETFL, $flags | O_NONBLOCK)||die "Failed to set flags for socket: $!\n";

my $dbh = DBI->connect("dbi:$conf{dbtype}:$conf{dbname}", $conf{dbuser}, $conf{dbpass})||die "Failed to connect to database: $DBI::errstr\n";

my %children;
my $num_child = 0;
my $ts_in;
my %friends;

$Term::ANSIColor::AUTORESET = 1;

sub reaper {
	$SIG{CHLD} = \&reaper;
	my $pid = wait;
	$num_child--;
	delete $children{$pid};
}

sub huntsman {
	$ts = strftime "%Y%m%d%H%M%S", localtime;
	kill 'INT' => keys %children;
	print STDOUT "$RealScript ($$) [$ts] Caught SIGINT, exiting gracefully [UID:$</GID:$>]\n";
	local ($SIG{CHLD}) = 'IGNORE';
	my $sth = $dbh->prepare("UPDATE process SET down = ? WHERE pid = ?");
	my ($s,$us) = gettimeofday;
	$sth->execute($s,$$);
	$sth->finish;
	$dbh->disconnect;
	$server->shutdown(2);
	if (-f "$conf{jail}/var/run/ngbbs.pid") {
		unlink "$conf{jail}/var/run/ngbbs.pid";
	}
	exit;
}

sub gunman {
	$ts = strftime "%Y%m%d%H%M%S", localtime;
	kill 'KILL' => keys %children;
	print STDOUT "$RealScript ($$) [$ts] Caught SIGKILL, exiting gracefully [UID:$</GID:$>]\n";
	local ($SIG{CHLD}) = 'IGNORE';
	local ($SIG{INT}) = 'IGNORE';
	local ($SIG{TERM}) = 'IGNORE';
	my $sth = $dbh->prepare("UPDATE process SET down = ? WHERE pid = ?");
	my ($s,$us) = gettimeofday;
	$sth->execute($s,$$);
	$sth->finish;
	$dbh->disconnect;
	$server->shutdown(2);
	close STDOUT;
	close STDERR;
	close CONLOG;
	if (-f "$conf{jail}/var/run/ngbbs.pid") {
		unlink "$conf{jail}/var/run/ngbbs.pid";
	}
	exit;
}

sub hawk {
	$ts = strftime "%Y%m%d%H%M%S", localtime;
	kill 'TERM' => keys %children;
	print STDOUT "$RealScript ($$) [$ts] Caught SIGTERM, exiting gracefully [UID:$</GID:$>]\n";
	local ($SIG{CHLD}) = 'IGNORE';
	local ($SIG{TERM}) = 'IGNORE';
	my $sth = $dbh->prepare("UPDATE process SET down = ? WHERE pid = ?");
	my ($s,$us) = gettimeofday;
	$sth->execute($s,$$);
	$sth->finish;
	$dbh->disconnect;
	$server->shutdown(2);
	close STDOUT;
	close STDERR;
	close CONLOG;
	if (-f "$conf{jail}/var/run/ngbbs.pid") {
		unlink "$conf{jail}/var/run/ngbbs.pid";
	}
	exit;
}

sub pheonix {
	$ts = strftime "%Y%m%d%H%M%S", localtime;
	kill 'KILL' => keys %children;
	my $self = "$RealBin/$RealScript";
	print STDOUT "$RealScript ($$) [$ts] Caught SIGHUP, restarting [UID:$</GID:$>]\n";
	my $sth = $dbh->prepare("UPDATE process SET down = ? WHERE pid = ?");
	my ($s,$us) = gettimeofday;
	$sth->execute($s,$$);
	$sth->finish;
	$dbh->disconnect;
	$server->shutdown(2);
	close(STDOUT);
	close(STDERR);
	close(CONLOG);
	if (-f "$conf{jail}/var/run/ngbbs.pid") {
		unlink "$conf{jail}/var/run/ngbbs.pid";
	}
	exec ($self);
	exit;
}

# Install signal handlers
$SIG{CHLD} = \&reaper;
$SIG{INT} = \&huntsman;
$SIG{HUP} = \&pheonix;
$SIG{KILL} = \&gunman;
$SIG{TERM} = \&hawk;

my $seed = $$;

# Dissociate from the controlling terminal and stop being part of whatever
# process group we had been a member of
my $pid = fork;
exit if $pid;
$|++;

$ts = strftime "%Y%m%d%H%M%S", localtime;
open my $pf, ">$conf{jail}/var/run/ngbbs.pid"||print STDOUT "$RealScript ($$) [$ts] Failed to create PID file: $!\n";
print $pf "$$\n";
close $pf;

# Spawn our children
for (my $i = 1 ; $i <= $conf{prefork} ; $i++) {
	make_kid();
}

my $parent = $$;

my $sth = $dbh->prepare("INSERT INTO process VALUES('',?,'',?)");
my ($s,$us) = gettimeofday;
if (!$sth->execute($s,$parent)) {
	die "Failed to setup new process in database: $DBI::errstr\n";
}
$sth->finish;

# Re-direct output so anything that is inevitably outputted does not slew all
# over the terminal
close STDIN;
open STDERR, ">>/$conf{jail}/var/log/errorlog";
open STDOUT, ">>/$conf{jail}/var/log/messages";
open CONLOG, ">>/$conf{jail}/var/log/conlog";
select CONLOG;
$|++;
select STDERR;

POSIX::setsid();

# If you want to be able to restart via a SIGHUP, you must have the parent
# process owned by user root group root to be able to re-bind the socket on
# a priviliged port (1-1024). You can use a port over 1024 to avoid this and
# have all processes be owned by the same, un-priviliged user.
my ($uid,$gid);
$ts = strftime "%Y%m%d%H%M%S", localtime;

if ($conf{port} > 1024) {
	$gid = getgrnam($conf{group}) || print STDERR "$RealScript ($$) [$ts] WARNING: FAILED TO GET GROUP ID: $!\n";
	$uid = getpwnam($conf{user}) || print STDERR "$RealScript ($$) [$ts] WARNING: FAILED TO GET USER ID: $!\n";
	POSIX::setgid( $gid );
	POSIX::setuid( $uid);
	#chroot "$conf{jail}" || die "$RealScript ($$) [$ts] FATAL ERROR: Failed to chroot: $! $?\n";
	chdir("/");
}
else {
	chdir("/");

}

$ts = strftime "%Y%m%d%H%M%S", localtime;
print STDOUT "$RealScript ($$) [$ts] $conf{banner} Started [UID:$</GID:$)]\n";
print STDOUT "$RealScript ($$) [$ts] $conf{banner} Listening for connections on port $conf{port}\n";

# Maintain population
while (1) {
	sleep;
	for (my $i = $num_child ; $i <= $conf{prefork} ; $i++) {
		make_kid();
	}
}

sub make_kid {
	$|=1;
	my ($pid, $sigset);

	$ts = strftime "%Y%m%d%H%M%S", localtime;

	$sigset = POSIX::SigSet->new(SIGINT);
	sigprocmask (SIG_BLOCK, $sigset)||die "$RealScript ($$) [$ts] FATAL ERROR: Failed to block SIGINT for fork: $!\n";

	die "$RealScript ($$) [$ts] FATAL ERROR: Failed to fork: $! [$@]\n" unless(defined($pid = fork));
	if ($pid) {
		# Parent records child's birth and returns
		sigprocmask (SIG_UNBLOCK, $sigset)||die "Failed to unblock SIGINT for fork: $!\n";
		$children{$pid} = 1;
		$num_child++;
		return;
	}
	else {
		# Child can _not_ return from this subroutine
		$SIG{INT} = 'DEFAULT';

		sigprocmask (SIG_UNBLOCK, $sigset)||die "$RealScript ($$) [$ts] FATAL ERROR: Failed to unblock SIGINT for fork: $!\n";

		# Old way: Handle connections until we've reached
		# max connections per child (MPC), actually, may work if we
		# dont use SO_NONBLOCK
		#for (my $i = 0 ; $i < $conf{mpc} ; $i++) {
		#	print STDERR "CON: $i\nMPC: $conf{mpc}\n";
		#	my $client = $server->accept();
		#	$client->autoflush(1);
		#	$client->timeout(1);
		#	entertain( $client );
		#}
		# New way: One connection per child
		my $gid = getgrnam($conf{group}) || print STDERR "$RealScript ($$) [$ts] FAILED TO GET GROUP ID: $!\n";
		my $uid = getpwnam($conf{user}) || print STDERR "$RealScript ($$) [$ts] FAILED TO GET USER ID: $!\n";
		$gid = POSIX::setgid( $gid );
		$uid = POSIX::setuid( $uid);
		#chroot "$conf{jail}" || die "Failed to chroot: $! $?\n";
		chdir("/");
		my $client;
		while ( 1 ) {
			last if $client = $server->accept();
		}
		my $peer = getpeername($client);
		my ($port,$iaddr) = unpack_sockaddr_in($peer);
		my $chost = gethostbyaddr($iaddr, AF_INET);
		my $ip = inet_ntoa($iaddr);
		$sth = $dbh->prepare("SELECT * FROM connections WHERE cip = ? AND outtime = '00000000000000'");
		if (!$sth->execute($ip)) {
			warn "Failed to get current connection count for $ip: $DBI::errstr\n";
			$sth->finish;
			$client->shutdown(2);
			exit;
		}
		if ($sth->rows >= $conf{max_con_per_host}) {
			$sth->finish;
			my $msg = "Error: Too many connections from your host...\n";
			$client->send($msg,0);
			$client->shutdown(2);
			$ts = strftime "%Y%m%d%H%M%S", localtime;
			print CONLOG "$RealScript ($$) [$ts] Connection refused from host $chost" ."[" . &inet_ntoa($iaddr) . "]: Maximum connections ($conf{max_con_per_host}) reached for this IP\n";
			exit;
		}
		$sth->finish;
		$client->timeout(1);
		#fcntl($client, F_GETFD, my $fd);
		#print STDERR "FILE DESCRIPTOR FOR CLIENT: $fd\n";
		entertain( $client );
		exit;
	}
}

sub entertain {
	my $fh = shift;
	$ts_in = [gettimeofday];
	my %user;
	my ($msg,$m_in);
	my $c_out = 0;
	my $c_in = 0;
	my $peer = getpeername($fh)||return -1;
	my ($port, $iaddr) = unpack_sockaddr_in($peer);
	my $chost = gethostbyaddr($iaddr, AF_INET);
	my $instamp = strftime "%Y%m%d%H%M%S", localtime;

	$msg = &Term::ANSIScreen::setmode(3);
	$msg = &Term::ANSIScreen::cls();

	print CONLOG "$RealScript ($$) [$instamp] Connection from host $chost" ."[" . &inet_ntoa($iaddr) . "]\n";
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = "Welcome to $conf{bbsname}\n";
	$msg = &center($msg);
	$msg .= &bar('BLUE');
	$c_out += length($msg);
	$fh->send(BOLD . GREEN . $msg . RESET,0);

	(my $ret,$msg) = &stats($c_out);
	$c_out += length($ret);
	$fh->send($msg,0);

	my $hostname = hostname();
	$msg = &bar('BLUE');
	$msg .= BOLD . YELLOW . "Account Setup" . RESET . BOLD . WHITE . ":" . RESET . "\n" . BOLD . BLUE . "http://$hostname/cgi-bin/ngbbsman.cgi\n\n" . RESET;
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = "Login: ";
	$fh->send($msg,0);
	$c_out += length($msg);
	$fh->recv($user{login},16,0);
	chomp($user{login});
	$c_in += length($user{login});
	print CONLOG "$RealScript ($$) [$instamp] Was given username '$user{login}', attempting to authenticate\n";
	$msg = "Password: ";
	$fh->send($msg,0);
	$c_out += length($msg);
	$fh->recv($user{pass},64,0);
	chomp($user{pass});
	$c_in += length($user{pass});

	$sth = $dbh->prepare("SELECT * FROM user WHERE uname = ?");
	if (!$sth->execute($user{login})) {
		$sth->finish;
		kick($fh,$instamp,$c_out,$c_in);
		return undef;
	}
	my $d_user = $sth->fetchrow_hashref||undef;
	$sth->finish;
	if (!$d_user) {
		print CONLOG "$RealScript ($$) [$instamp] Authentication failure: no such user\n";
		$msg = BOLD . RED . "Login incorrect.\n" . RESET;
		$c_out = $c_out + length($msg);
		$fh->send($msg,0);
		kick($fh,$instamp,$c_out,$c_in);
		return undef;
	}
	$user{cpass} = md5_hex($user{pass});
	if ($d_user->{sec} ne $user{cpass}) {
		print CONLOG "$RealScript ($$) [$instamp] Authentication failure: password incorrect\n";
		$msg = BOLD . RED . "Login incorrect.\n" . RESET;
		$c_out = $c_out + length($msg);
		$fh->send($msg,0);
		kick($fh,$instamp,$c_out,$c_in);
		return undef;
	}

	$sth = $dbh->prepare("SELECT seq FROM connections WHERE uid = ? AND outtime = '00000000000000'");
	if (!$sth->execute($d_user->{seq})) {
		print STDERR "$RealScript ($$) [$instamp] Failed to get concurrent user logins: $DBI::errstr\n";
		$sth->finish;
		kick ($fh,$instamp,$c_out,$c_in);
		exit;
	}
	if ($sth->rows >= $conf{max_con_per_user}) {
		print CONLOG "$RealScript ($$) [$instamp] Access denied: user has reached maximum connections per user ($conf{max_con_per_user})\n";
		$sth->finish;
		$msg = "Access denied: you have reached to maximum connections allowed per user\n";
		$fh->send($msg,0);
		$fh->shutdown(2);
		exit;
	}
	$sth->finish;

	$sth = $dbh->prepare("SELECT user.uname,lockout.lo_time,lockout.mesg FROM user LEFT JOIN lockout ON lockout.sysop = user.seq WHERE lockout.uid = ?");
	if (!$sth->execute($d_user->{seq})) {
		$sth->finish;
		kick ($fh,$instamp,$c_out,$c_in);
		exit;
	}
	my $lockout = $sth->fetchrow_hashref;
	if ($sth->rows > 0) {
		$sth->finish;
		$msg = BOLD . RED . "Access denied:" . RESET . BOLD . WHITE . " $lockout->{uname}" . RESET . " has locked you out as of" . BOLD . WHITE . " $lockout->{lo_time} $conf{tz}\n" . RESET . "Reason: " . BOLD . GREEN . "$lockout->{mesg}\n\n";
		$c_out += length($msg);
		$fh->send($msg,0);
		kick ($fh,$instamp,$c_out,$c_in);
		print STDERR "$RealScript ($$) [$instamp] Access denied: $d_user->{uname} attempted to login while locked out\n";
		exit;
	}
 	$sth->finish;

	setup ($fh,$instamp,$d_user);
	($c_out,$c_in) = main_menu ($fh,$c_out,$c_in,$d_user,$instamp);
	kick ($fh,$instamp,$c_out,$c_in);
	print CONLOG "$RealScript ($$) [$ts] Logout from $d_user->{uname}\n";
}

sub setup {
	my $fh = shift;
	my $instamp = shift;
	my $user = shift;
	my $peer = getpeername($fh)||return -1;
	my ($port, $iaddr) = unpack_sockaddr_in($peer);
	my $chost = gethostbyaddr($iaddr, AF_INET);
	$ts = strftime "%Y%m%d%H%M%S", localtime;
	my $sth = $dbh->prepare("INSERT INTO connections VALUES('',?,?,?,?,'00000000000000','','',?)");
	if (!$sth->execute($user->{seq},&inet_ntoa($iaddr),$chost,$instamp,$port)) {
		print STDERR "$RealScript ($$) [$ts] Failed to insert new connection [&inet_ntoa($iaddr)($chost):$port]: $DBI::errstr\n";
	}
	$sth->finish;
	print CONLOG "$RealScript ($$) [$ts] Login from $chost" . "[" . &inet_ntoa($iaddr) . "] handle $user->{uname}\n";
}

sub kick {
	my $fh = shift;
	my $instamp = shift;
	my $c_out = shift;
	my $c_in = shift;
	my $msg;
	my $outstamp = strftime "%Y%m%d%H%M%S", localtime;
	my $ttl = tv_interval($ts_in);

	if ($ttl<60) {
		$msg = sprintf("You were connected for %.2f seconds\n",$ttl);
	}
	elsif ($ttl>=60 && $ttl<3600) {
		my $min = $ttl/60;
		$msg = sprintf("You were connected for %.2f minutes\n",$min);
	}
	elsif ($ttl>=3600 && $ttl<86400) {
		my $hrs = $ttl/3600;
		$msg = sprintf("You were connected for %.2f hours\n",$hrs);
	}
	elsif ($ttl>=86400 && $ttl<2592000) {
		my $dys = $ttl/86400;
		$msg = sprintf("You were connected for %.2f days\n",$dys);
	}
	elsif ($ttl>=2592000 && $ttl<31536000) {
		my $mos = $ttl/2592000;
		$msg = sprintf("You were connected for %.2f months\n",$mos);
	}
	elsif ($ttl>=31536000 && $ttl<315360000) {
		my $yrs = $ttl/31536000;
		$msg = sprintf("You were connected for %.2f years\n",$yrs);
	}
	elsif ($ttl>=315360000) {
		my $dec = $ttl/315360000;
		$msg = sprintf("You were connected for %.2f decades\n",$dec);
	}
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = "In: ";
	$c_out += length($msg);
	$fh->send($msg,0);
	if ($c_in<1024) {
		$msg = "$c_in B\n";
	}
	elsif ($c_in>=1024 && $c_in<1024000) {
		$msg = sprintf("%.2f KB\n",$c_in/1024);
	}
	elsif ($c_in>=1024000) {
		$msg = sprintf("%.2f MB\n",$c_in/1024000);
	}
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = "Out: ";
	$c_out += length($msg);
	$fh->send($msg,0);
	if ($c_out<1024) {
		$msg = "$c_out B\n";
	}
	elsif ($c_out>=1024 && $c_out<1024000) {
		$msg = sprintf("%.2f KB\n",$c_out/1024);
	}
	elsif ($c_out>=1024000) {
		$msg = sprintf("%.2f MB\n",$c_out/1024000);
	}
	$c_out += length($msg);
	$fh->send($msg,0);
	$fh->shutdown(2);
	my $sth = $dbh->prepare("UPDATE connections SET outtime = ?, bytes_in = ?, bytes_out = ? WHERE intime = ?");
	if (!$sth->execute($outstamp,$c_in,$c_out,$instamp)) {
		print STDERR "$RealScript ($$) [$ts] Failed to update connection: $DBI::errstr\n";
	}
	$sth->finish;
}

sub main_menu {
	my $fh = shift;
	my $c_out = shift;
	my $c_in = shift;
	my $user = shift;
	my $instamp = shift;
	my ($msg,$m_in);
	my $expert = 0;
	$msg = &Term::ANSIScreen::cls();
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = &Term::ANSIScreen::locate(0,0);
	$c_out += length($msg);
	$fh->send($msg,0);
	if ($user->{fname} && $user->{lname}) {
		$msg = "Welcome to $conf{bbsname}, $user->{fname} $user->{lname}\n";
	}
	else {
		$msg = "Welcome to $conf{bbsname}, $user->{uname}\n";
	}

	$msg = &center($msg);
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = &bar('BLUE');
	$c_out += length($msg);
	$fh->send($msg,0);

	# Get a list of all online uid's
	my $sth = $dbh->prepare("SELECT uid FROM connections WHERE outtime = '00000000000000'");
	if (!$sth->execute) {
		$sth->finish;
	}
	my @onuids;
	while (my $uid = $sth->fetchrow_hashref) {
		push (@onuids, $uid->{uid});
	}
	$sth->finish;

	# Get a list of all users which have the connecting user as a notify,
	# if any
	$sth = $dbh->prepare("SELECT uid FROM notify WHERE target = ?");
	if (!$sth->execute($user->{seq})) {
		$sth->finish;
	}
	my @notify = ();
	if ($sth->rows > 0) {
		
		# Indeed the connecting user has some friends. If any are
		# currently online, tell them the connecting user is online now.
		while (my $notusr = $sth->fetchrow_hashref) {
			push (@notify, $notusr->{uid});
		}
	}
	$sth->finish;

	# Compute intersection of @onuids and @notify
	my %isect;
	my @int;
	foreach my $test (@onuids,@notify) {
		$isect{$test}++;
		if ($isect{$test} > 1) {
			push (@int, $test);
		}
	}

	# Use int as a list to insert messages to online users.
	if (scalar(@int) > 0) {
		$sth = $dbh->prepare("INSERT INTO note VALUES('',?,?,?,'00000000000000')");
		my $ts = strftime "%Y%m%d%H%M%S", localtime;
		my $nmsg = BOLD . YELLOW . "Notify" . RESET . BOLD . WHITE . ": " . RESET . "'" . BOLD . CYAN . "$user->{uname}" . RESET . "' ". BOLD . YELLOW . "has just logged in" . RESET;
		while (my $t_uid = shift(@int)) {
			$sth->execute($t_uid,$ts,$nmsg);
		}
		$sth->finish;
	}

	# Get a list of uids the connecting user has in it's notify list
	$sth = $dbh->prepare("SELECT notify.target,user.uname FROM notify LEFT JOIN user ON notify.target = user.seq WHERE notify.uid = ?");
	if (!$sth->execute($user->{seq})) {
		$sth->finish;
	}
	if ($sth->rows > 0) {
		while (my $friend = $sth->fetchrow_hashref) {
			$friends{$friend->{target}} = $friend->{uname};
		}
	}
	$sth->finish;

	$msg = &Term::ANSIScreen::setscroll(4,23);
	$msg .= &Term::ANSIScreen::locate(4,0);
	$c_out += length($msg);
	$fh->send($msg,0);

	while ( 1 ) {
		my $ts = strftime "%Y%m%d%H%M%S", localtime;
		# Check if we've been kicked
		$sth = $dbh->prepare("SELECT kick.kick_time,kick.sysop,kick.mesg,kick.seq,user.uname FROM kick LEFT JOIN user ON kick.sysop = user.seq WHERE kick.uid = ?");
		if (!$sth->execute($user->{seq})) {
			print STDERR "$RealScript ($$) [$ts] FATAL Database error: $DBI::errstr\n";
			$sth->finish;
			$fh->shutdown(2);
			$dbh->disconnect;
			exit;
		}
		if ($sth->rows > 0) {
			# We've been kicked, insert boot in client's ass
			my $kick = $sth->fetchrow_hashref;
			$sth->finish;
			$sth = $dbh->prepare("DELETE FROM kick WHERE uid = ?");
			if (!$sth->execute($user->{seq})) {
				print STDERR "$RealScript ($$) [$ts] FATAL Database error: $DBI::errstr\n";
				$sth->finish;
			}
			$msg = &Term::ANSIScreen::cls();
			$msg .= &Term::ANSIScreen::locate(0,0);
			$msg .= BOLD . RED . "You've been kicked by $kick->{uname}\n" . RESET . BOLD . YELLOW . "Reason" . RESET . BOLD . WHITE . ": " . RESET . BOLD . RED . "$kick->{mesg}\n" . RESET;
			$c_out += length($msg);
			$fh->send($msg);
			return ($c_out, $c_in);
		}
		$sth->finish;

		# Get a list of all online uid's
		$sth = $dbh->prepare("SELECT uid FROM connections WHERE outtime = '00000000000000'");
		if (!$sth->execute) {
			$sth->finish;
		}
		@onuids = ();
		while (my $uid = $sth->fetchrow_hashref) {
			push (@onuids, $uid->{uid});
		}
		$sth->finish;

		# Compute intersection of @onuids and %friends
		%isect = ();
		@int = ();
		foreach my $test (@onuids,keys(%friends)) {
			$isect{$test}++;
			if ($isect{$test} > 1) {
				push (@int, $test);
			}
		}

		# Use int to insert messages to online users.
		if (scalar(@int) > 0) {
			$sth = $dbh->prepare("INSERT INTO note VALUES('',?,?,?,'00000000000000')");
			
			while (my $friend = shift(@int)) {
				my $nmsg = BOLD . YELLOW . "Notify" . RESET . BOLD . WHITE . ": " . RESET . BOLD . CYAN . "$friends{$friend} " . RESET . "has just logged in";
				$sth->execute($ts,$user->{seq},$nmsg);
			}
			$sth->finish;
		}

		my $muted = 0;

		my $rv = $dbh->prepare("SELECT seq FROM mute WHERE uid = ?");
		if (!$rv->execute($user->{seq})) {
			$rv->finish;
			last;
		}
		if ($rv->rows > 0)  {
			$muted = 1;
		}
		

		$sth = $dbh->prepare("SELECT mesg.seq,mesg.intime,mesg.f_uid,mesg.t_uid,mesg.mesg,user.uname FROM mesg LEFT JOIN user ON mesg.f_uid = user.seq WHERE mesg.t_uid = ? AND mesg.recvd = '00000000000000'");
		if (!$sth->execute($user->{seq})) {
			$sth->finish;
		}
		if ($sth->rows > 0) {
			if (!$muted) {
				while (my $mesg = $sth->fetchrow_hashref) {
					my $ts = strftime "%Y%m%d%H%M%S", localtime;
					my $mer = 'pm';
					my $rc = $dbh->prepare("UPDATE mesg SET recvd = ? WHERE seq = ?");
					if (!$rc->execute($ts,$mesg->{seq})) {
						$rc->finish;
					}
					$rc->finish;
					my ($yr,$mo,$dy,$hr,$mn,$sc) = unpack("A4A2A2A2A2A2",$mesg->{intime});
					$dy = sprintf("%d",$dy);
					$hr = sprintf("%d",$hr);
					if ($hr<12) {
						$mer = 'am';
					}
					if ($hr>12) {
						$hr -= 12;
					}
					$msg = &Term::ANSIScreen::wrapon();
					$c_out += length($msg);
					$fh->send($msg,0);
					$msg = BOLD . YELLOW . "Incoming message".RESET.BOLD.WHITE.":".RESET." On $dy, $months{$mo} $yr at $hr:$mn:$sc$mer, '".BOLD.CYAN."$mesg->{uname}".RESET."' wrote:\n";
					$c_out += length($msg);
					$fh->send($msg,0);
					$msg = "\"$mesg->{mesg}\"";
					$msg = &center($msg);
					$msg .= "\n";
					$c_out += length($msg);
					$fh->send($msg);
					$msg = &Term::ANSIScreen::wrapoff();
					$c_out += length($msg);
					$fh->send($msg,0);
				}
			}
		}
		$sth->finish;

		$sth = $dbh->prepare("SELECT seq,mesg FROM note WHERE recvd = '00000000000000' AND t_uid = ?");
		if (!$sth->execute($user->{seq})) {
			$sth->finish;
		}
		if ($sth->rows > 0) {
			while (my $mesg = $sth->fetchrow_hashref) {
				my $ts = strftime "%Y%m%d%H%M%S", localtime;
				my $rc = $dbh->prepare("UPDATE note SET recvd = ? WHERE seq = ?");
				if (!$rc->execute($ts,$mesg->{seq})) {
					$rc->finish;
				}
				$rc->finish;
				$msg = "$mesg->{mesg}\n";
				$c_out += length($msg);
				$fh->send($msg,0);
			}
		}
		$sth->finish;

		$msg = &Term::ANSIScreen::savepos();
		$c_out += length($msg);
		$fh->send($msg,0);

		$msg = &Term::ANSIScreen::locate(25,0);
		$msg .= &Term::ANSIScreen::clline();
		$msg .= &Term::ANSIScreen::locate(24,0);
		$msg .= &Term::ANSIScreen::clline();
		if (!$colors{$user->{prompt}}) {
			$msg .= &prompt('blue',$user->{uname});
		}
		else {
			$msg .= &prompt($colors{$user->{prompt}}||'blue',$user->{uname});
		}

		$c_out += length($msg);
		$fh->send($msg,0);
		$fh->recv($m_in,277,0);
		$msg = &Term::ANSIScreen::loadpos();
		$c_out += length($msg);
		$fh->send($msg,0);
		# max mesg length is 255 chars, max username is 16 chars, 2 spaces and alotment for 4 chars of command
		$c_in += length($m_in);
		chomp($m_in);
		$c_in += length($m_in);
		my @cmdline = split(/\s+/,$m_in);
		next unless $cmdline[0];
		$cmdline[0] =~ tr [A-Z] [a-z];

		if ($cmdline[0] eq 'q'||$cmdline[0] eq 'quit') {
			$msg = &Term::ANSIScreen::cls();
			$c_out += length($msg);
			$fh->send($msg,0);
			return ($c_out,$c_in);
		}
		elsif ($cmdline[0] eq 'st'||$cmdline[0] eq 'stats') {
			(my $ret,$msg) = &stats($c_out);
			$c_out += length($ret);
			$fh->send($msg,0);
			next;
		}
		elsif ($cmdline[0] eq 'p'||$cmdline[0] eq 'prompt') {
			if (!$cmdline[1] || $cmdline[1] !~ /\d/ || $cmdline[1] < 1 || $cmdline[1] > 7) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " prompt [1-7]\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth = $dbh->prepare("UPDATE user SET prompt = ? WHERE seq = ?");
			if (!$sth->execute($cmdline[1],$user->{seq})) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ":" . RESET . " $DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth->finish;
			$user->{prompt} = $cmdline[1];
			next;
		}
		elsif ($cmdline[0] eq 'w'||$cmdline[0] eq 'who') {
			($c_out) = who($fh, $c_out);
			next;
		}
		elsif ($cmdline[0] eq 'wi'||$cmdline[0] eq 'whois') {
			if (!$cmdline[1]) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " whois [handle]\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			($c_out) = whois($fh, $c_out, $cmdline[1]);
			next;
		}
		elsif ($cmdline[0] eq 'n'||$cmdline[0] eq 'notify') {
			# Notify
			($c_out) = notify($cmdline[1], $user, $fh, $c_out);
			next;
		}
		elsif ($cmdline[0] eq 'kick') {
			next unless $expert == 1;
			if ($user->{priv} < 90) {
				$msg = BOLD . RED . "You lack sufficient permissions for this action\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if (!$cmdline[1]) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ": " . RESET . "kick [handle] <why>\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth = $dbh->prepare("SELECT seq,priv,uname FROM user WHERE uname = ?");
			if (!$sth->execute($cmdline[1])) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($sth->rows == 0) {
				$msg = BOLD . RED . "Sorry, I dont recognize that handle\n" . RESET;
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			my $luser = $sth->fetchrow_hashref;
			$sth->finish;
			if ($luser->{priv} >= $user->{priv}) {
				$msg = BOLD . RED . "You can not kick someone with equal or greater privilige than you\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			shift (@cmdline);
			shift (@cmdline);
			my $reason = join(" ",@cmdline) || "You have been kicked";
			$sth = $dbh->prepare("INSERT INTO kick VALUES('',NOW(),?,?,?)");
			if (!$sth->execute($user->{seq},$luser->{seq},$reason)) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth->finish;
			$msg = BOLD . YELLOW . "Kicked handle " . RESET . "'" . BOLD . CYAN . "$luser->{uname}" . RESET . "' " . BOLD . YELLOW . "off the system\n" . RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
			next;
		}
		elsif ($cmdline[0] eq 'bc') {
			# Broadcast a message to everyone logged in
			next unless $expert == 1;
			if ($user->{priv} < 85) {
				$msg = BOLD . RED . "You lack sufficient permissions for this action\n" . RESET;
				next;
			}
			if (!$cmdline[1]) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " bc [message]\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			my $sth = $dbh->prepare("SELECT user.seq FROM connections LEFT JOIN user ON connections.uid = user.seq WHERE connections.outtime = '00000000000000'");
			if (!$sth->execute()) {
				$sth->finish;
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "Failed to select users.\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			my $ts = strftime "%Y%m%d%H%M%S", localtime;
			my $m = '';
			shift(@cmdline);
			$m = join(" ",@cmdline);
			my $count = 0;
			while (my $to = $sth->fetchrow_hashref) {
				# in_time, f_uid, t_uid, mesg, recvd
				my $rc = $dbh->prepare("INSERT INTO mesg VALUES('',?,?,?,?,'00000000000000')");
				if (!$rc->execute($ts,$user->{seq},$to->{seq},$m)) {
					print STDERR "$RealScript ($$) [$ts] Failed to send message at '$ts' from '$user->{seq}' to '$to->{seq}' using '$m' $DBI::errstr\n";
					$msg = BOLD.RED."Failed to send message".RESET.BOLD.WHITE.": ".RESET."$DBI::errstr\n";
					$rc->finish;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
				$rc->finish;
				$count++;
			}
			$sth->finish;
			$msg = BOLD.YELLOW."Sent message to ".RESET.BOLD.CYAN."$count".RESET.BOLD.YELLOW." people.\n".RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
			next;
		}
		elsif ($cmdline[0] eq 'm'||$cmdline[0] eq 'mesg') {
			if ($user->{priv} < 10) {
				$msg = BOLD . RED . "You lack sufficient permissions for this action\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($cmdline[1] && $cmdline[2]) {
				shift(@cmdline);
				my $t_uid = shift(@cmdline);
				my $n_mesg = join(" ",@cmdline);
				$c_out += &mesg($user,$fh,$t_uid,$n_mesg,$c_out);
				next;
			}
			else {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " mesg [handle] [message]\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			next;
		}
		elsif ($cmdline[0] eq 's'||$cmdline[0] eq 'seen') {
			if (!$cmdline[1]) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " seen [handle]\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			# First check to make sure they are not currently
			# logged in.
			my $sth = $dbh->prepare("SELECT uname FROM connections LEFT JOIN user ON connections.uid = user.seq WHERE uname = ? AND outtime = 00000000000000");
			if (!$sth->execute($cmdline[1])) {
				$sth->finish;
				next;
			}
			if ($sth->fetchrow_array) {
				$sth->finish;
				$msg = BOLD.GREEN."User is on right now!\n".RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			else {
				$sth->finish;
				$sth = $dbh->prepare("SELECT intime FROM connections LEFT JOIN user ON connections.uid = user.seq WHERE user.uname = ? ORDER BY -connections.outtime LIMIT 1");
				if (!$sth->execute($cmdline[1])) {
					$sth->finish;
					next;
				}
				my $found = $sth->fetchrow_hashref;
				$sth->finish;
				if ($found) {
					my ($yr,$mo,$dy,$hr,$mn,$se) = unpack("A4A2A2A2A2A2",$found->{intime});
					$dy = sprintf("%d",$dy);
					$hr = sprintf("%d",$hr);
					my $mer = 'pm';
					if ($hr<12) {
						$mer = 'am';
					}
					if ($hr>12) {
						$hr -= 12;
					}
					$msg = "'" . BOLD . CYAN . "$cmdline[1]" . RESET . "'" . BOLD . YELLOW . " was last seen $dy $months{$mo}, $yr at $hr:$mn$mer\n" . RESET;
				}
				else {
					$msg = BOLD . YELLOW . "Hmm... I dont recall seeing a ".RESET . "'" . BOLD . CYAN . "$cmdline[1]" . RESET ."'\n";
				}
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			next;
		}
		elsif ($cmdline[0] eq 'nu') {
			next unless $expert == 1;
			if ($user->{priv} < 95) {
				$msg = BOLD . RED . "You have insufficient permissions for the given action...\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if (!$cmdline[1]||!$cmdline[2]||!$cmdline[3]) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " nu [handle] [pass] [cc]\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			# Run a check to see if user already exists
			$sth = $dbh->prepare("SELECT seq FROM user WHERE uname = ?");
			if (!$sth->execute($cmdline[1])) {
				$sth->finish;
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($sth->rows > 0) {
				$sth->finish;
				$msg = BOLD . RED . "Error" . RESET . BOLD . WHITE . ": " . RESET . "user already exists\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth->finish;
			my $ts = strftime "%Y%m%d%H%M%S", localtime;
			$sth = $dbh->prepare("INSERT INTO user VALUES('',?,?,md5(?),10,4,'','',?,'','','','','','')");
			if (!$sth->execute($ts,$cmdline[1],$cmdline[2],uc($cmdline[3]))) {
				$sth->finish;
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$c_out += length($msg);
				$fh->send($msg);
				next;
			}
			$sth->finish;
			$msg = BOLD.YELLOW."New user successfully added...\n".RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
			next;
		}
		elsif ($cmdline[0] eq 'du') {
			next unless $expert == 1;
			if ($user->{priv} < 95) {
				$msg = BOLD . RED . "You have insufficient permissions for the given action...\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if (!$cmdline[1]) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " du [handle]\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			# Run a check to see if user exists
			$sth = $dbh->prepare("SELECT seq FROM user WHERE uname = ?");
			if (!$sth->execute($cmdline[1])) {
				$sth->finish;
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($sth->rows == 0) {
				$sth->finish;
				$msg = BOLD.RED."Error".RESET.BOLD.WHITE.": ".RESET." user does not exist\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			my $del = $sth->fetchrow_hashref;
			$sth->finish;
			$sth = $dbh->prepare("DELETE FROM user WHERE seq = ?");
			if (!$sth->execute($del->{seq})) {
				$sth->finish;
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$c_out += length($msg);
				$fh->send($msg);
				next;
			}
			$sth->finish;
			$msg = BOLD.GREEN."User deleted.\n".RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
			next;
		}
		elsif ($cmdline[0] eq 'lo') {
			next unless $expert == 1;
			if ($user->{priv} < 90) {
				$msg = BOLD . RED . "You have insufficient permissions for the given action...\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if (!$cmdline[1]) {
				$sth = $dbh->prepare("SELECT lockout.lo_time,lockout.mesg,lockout.sysop,user.uname FROM lockout LEFT JOIN user ON lockout.uid = user.seq");
				if (!$sth->execute) {
					$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
					$sth->finish;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
				if ($sth->rows == 0) {
					$sth->finish;
					$msg = BOLD.YELLOW."Currently, no one is locked out\n".RESET;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
				while (my $lo = $sth->fetchrow_hashref) {
					my $rv = $dbh->prepare("SELECT uname FROM user WHERE seq = ?");
					if (!$rv->execute($lo->{sysop})) {
						$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
						$rv->finish;
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					my $sysop = $rv->fetchrow_hashref;
					$rv->finish;
					$msg = BOLD.YELLOW."$lo->{lo_time}".BOLD.WHITE.": ".RESET."'".BOLD.CYAN."$lo->{uname}".RESET."' ".BOLD.YELLOW."locked out by ".RESET."'".BOLD.CYAN."$sysop->{uname}".RESET."' " . BOLD . YELLOW. "Reason".RESET.BOLD.WHITE.": ".RESET . "'".BOLD.RED."$lo->{mesg}".RESET."'\n";
					$c_out += length($msg);
					$fh->send($msg,0);
				}
				$sth->finish;
				next;
			}
			my $reason = BOLD . RED . "You have been locked out of this BBS, contact the sysop" . RESET;
			$sth = $dbh->prepare("SELECT seq,priv FROM user WHERE uname = ?");
			if (!$sth->execute($cmdline[1])) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			my $lo = $sth->fetchrow_hashref;
			if ($sth->rows == 0) {
				$msg = BOLD . RED . "Error" . RESET . BOLD . WHITE . ": " . RESET . "no such user\n";
				$c_out += length($msg);
				$sth->finish;
				$fh->send($msg,0);
				next;
			}
			$sth->finish;
			if ($lo->{priv} >= $user->{priv}) {
				$msg = BOLD . RED . "Error" . RESET . BOLD . WHITE ": " . RESET . "user has equal or higher privilige level than you\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth = $dbh->prepare("SELECT seq FROM lockout WHERE uid = ?");
			if (!$sth->execute($lo->{seq})) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($sth->rows > 0) {
				my $lou = $sth->fetchrow_hashref;
				$sth->finish;
				$sth = $dbh->prepare("DELETE FROM lockout WHERE seq = ?");
				if (!$sth->execute($lou->{seq})) {
					$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE ": " . RESET . "$DBI::errstr\n";
					$sth->finish;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
				$msg = BOLD . YELLOW . "User removed from the lockout list\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth->finish;
			if ($cmdline[2]) {
				shift(@cmdline);
				shift(@cmdline);
				$reason = join(" ",@cmdline);
			}
			my $ts = strftime "%Y%m%d%H%M%S", localtime;
			$sth = $dbh->prepare("INSERT INTO lockout VALUES('',?,?,?,?)");
			if (!$sth->execute($ts,$user->{seq},$lo->{seq},$reason)) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth->finish;
			$msg = BOLD . YELLOW . "User is now locked out\n" . RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
			next;
		}
		elsif ($cmdline[0] eq 'priv') {
			next unless $expert == 1;
			if ($user->{priv} < 90) {
				$msg = BOLD . RED . "You have insufficient permissions for the given action...\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if (!$cmdline[1]) {
				$msg = BOLD . YELLOW . "Your privilige level is $user->{priv}\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth = $dbh->prepare("SELECT priv FROM user WHERE uname = ?");
			if (!$sth->execute($cmdline[1])) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($sth->rows == 0) {
				$msg = BOLD . RED . "Sorry, I dont recognize that handle...\n" . RESET;
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			my $level = $sth->fetchrow_hashref;
			$sth->finish;
			if (!$cmdline[2]) {
				$msg = BOLD . YELLOW . "Handle " . RESET . "'" . BOLD . CYAN . "$cmdline[1]" . RESET . "' " . BOLD . YELLOW . "currently has a privilige level of " . RESET . BOLD . CYAN . "$level->{priv}\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($level->{priv} >= $user->{priv}) {
				$msg = BOLD . RED . "Handle " . RESET . "'" . BOLD . CYAN . "$cmdline[1]" . RESET . "' " . BOLD . RED . "has an equal or higher privilige than you\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($cmdline[2] >= $user->{priv}) {
				$msg = BOLD . RED . "You can not set privilige levels greater than or equal to your own\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth = $dbh->prepare("UPDATE user SET priv = ? WHERE uname = ?");
			if (!$sth->execute($cmdline[2], $cmdline[1])) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . "$DBI::errstr\n" . RESET;
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth->finish;
			$msg = BOLD . YELLOW . "Updated privilige from " . RESET . BOLD . CYAN . "$level->{priv}" . RESET . BOLD . YELLOW . " to " . RESET . BOLD . CYAN . "$cmdline[2]" . RESET . BOLD . YELLOW . " for handle " . RESET . "'" . BOLD . CYAN . "$cmdline[1]" . RESET . "'\n";
			$c_out += length($msg);
			$fh->send($msg,0);
			next;
		}
		elsif ($cmdline[0] eq 'mu') {
			next unless $expert == 1;
			if ($user->{priv} < 90) {
				$msg = BOLD . RED . "You have insufficient permissions for the given action...\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if (!$cmdline[1]) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " mu [handle]\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}

			$sth = $dbh->prepare("SELECT seq,priv FROM user WHERE uname = ?");
			if (!$sth->execute($cmdline[1])) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($sth->rows == 0) {
				$sth->finish;
				$msg = BOLD . RED . "Sorry, I dont recognize that handle...\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			my $luser = $sth->fetchrow_hashref;
			$sth->finish;
			if ($luser->{priv} >= $user->{priv}) {
				$msg = BOLD . RED . "You can not mute a handle that has equal or greater privilige than yourself\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth = $dbh->prepare("SELECT seq FROM mute WHERE uid = ?");
			if (!$sth->execute($luser->{seq})) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($sth->rows == 0) {
				$sth->finish;
				my $ts = strftime "%Y%m%d%H%M%S", localtime;
				$sth = $dbh->prepare("INSERT INTO mute VALUES('',?,?,?)");
				if (!$sth->execute($ts,$user->{seq},$luser->{seq})) {
					$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
					$sth->finish;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
				$sth->finish;
				$msg = BOLD . YELLOW . "Muted handle " . RESET "'" . BOLD . CYAN . "$cmdline[1]" . RESET . "'\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			my $mute = $sth->fetchrow_hashref;
			$sth->finish;
			$sth = $dbh->prepare("DELETE FROM mute WHERE seq = ?");
			if (!$sth->execute($mute->{seq})) {
				$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
				$sth->finish;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			$sth->finish;
			$msg = BOLD . YELLOW . "Un-muted handle ". RESET ."'". BOLD . CYAN . "$cmdline[1]" . RESET . "'\n";
			$c_out += length($msg);
			$fh->send($msg,0);
			next;
		}
		elsif ($cmdline[0] eq 'mask') {
			next unless $expert == 1;
			if ($user->{priv} < 60) {
				$msg = BOLD.RED."You have insufficient permissions for the given action...\n".RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if (!$cmdline[1]) {
				$msg = BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET . " mask [user] <host>\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($cmdline[1] eq $user->{uname}) {
				if ($cmdline[2]) {
					$sth = $dbh->prepare("SELECT seq,host FROM mask WHERE uid = ?");
					if (!$sth->execute($user->{seq})) {
						$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
						$sth->finish;
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					if ($sth->rows == 0) {
						$sth->finish;
						my $ts = strftime "%Y%m%d%H%M%S", localtime;
						$sth = $dbh->prepare("INSERT INTO mask VALUES('',?,?,?,?)");
						if (!$sth->execute($ts,$user->{seq},$user->{seq},$cmdline[2])) {
							$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
							$sth->finish;
							$c_out += length($msg);
							$fh->send($msg,0);
							next;
						}
						$sth->finish;
						$msg = BOLD.YELLOW."Applied your new hostmask".RESET.BOLD.WHITE.":".RESET." '".BOLD.CYAN."$cmdline[2]".RESET."'\n";
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					else {
						my $inf = $sth->fetchrow_hashref;
						$sth->finish;
						$sth = $dbh->prepare("UPDATE mask SET host = ? WHERE seq = ?");
						if (!$sth->execute($cmdline[2],$inf->{seq})) {
							$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
							$sth->finish;
							$c_out += length($msg);
							$fh->send($msg,0);
							next;
						}
						$sth->finish;
						$msg = BOLD.YELLOW."Updated your hostmask from ".RESET."'".BOLD.CYAN."$inf->{host}".RESET."'".BOLD.YELLOW." to ".RESET."'".BOLD.CYAN."$cmdline[2]".RESET."'\n";
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
				}
				else {
					$sth = $dbh->prepare("DELETE FROM mask WHERE uid = ?");
					if (!$sth->execute($user->{seq})) {
						$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
						$sth->finish;
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					if ($sth->rows == 0) {
						$sth->finish;
						$msg = BOLD.RED."You do not currently have a hostmask\n".RESET;
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					$msg = BOLD.YELLOW."Deleted your hostmask\n".RESET;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
			}
			else {
				if ($user->{priv} < 90) {
					$msg = BOLD.RED."You have insufficient permissions for the given action...\n".RESET;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
				my $sth = $dbh->prepare("SELECT user.seq,user.priv,mask.host FROM user LEFT JOIN mask ON mask.uid = user.seq WHERE user.uname = ?");
				if (!$sth->execute($cmdline[1])) {
					$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
					$c_out += length($msg);
					$sth->finish;
					$fh->send($msg,0);
					next;
				}
				my $luser = $sth->fetchrow_hashref;
				$sth->finish;
				if (!$luser->{priv}) {
					$msg = BOLD.RED."Sorry, I dont recognize that handle...\n".RESET;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
				if ($cmdline[2]) {
					$sth = $dbh->prepare("SELECT seq,host FROM mask WHERE uid = ?");
					if (!$sth->execute($cmdline[1])) {
						$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
						$sth->finish;
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					if ($sth->rows == 0) {
						$sth->finish;
						my $ts = strftime "%Y%m%d%H%M%S", localtime;
						$sth = $dbh->prepare("INSERT INTO mask VALUES('',?,?,?,?)");
						if (!$sth->execute($ts,$user->{seq},$luser->{seq},$cmdline[2])) {
							$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
							$sth->finish;
							$c_out += length($msg);
							$fh->send($msg,0);
							next;
						}
						$sth->finish;
						$msg = BOLD.YELLOW."Applied new hostmask ".RESET."'".BOLD.CYAN."$cmdline[2]".RESET."'".BOLD.YELLOW." for handle ".RESET."'".BOLD.CYAN."$cmdline[1]".RESET."'\n";
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					else {
						my $inf = $sth->fetchrow_hashref;
						$sth->finish;
						$sth = $dbh->prepare("UPDATE mask SET host = ? WHERE seq = ?");
						if (!$sth->execute($cmdline[2],$inf->{seq})) {
							$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
							$sth->finish;
							$c_out += length($msg);
							$fh->send($msg,0);
							next;
						}
						$sth->finish;
						$msg = BOLD.YELLOW."Updated hostmask from ".RESET."'".BOLD.CYAN."$inf->{host}".RESET."'".BOLD.YELLOW." to ".RESET."'".BOLD.CYAN."$cmdline[2]".RESET."'".BOLD.YELLOW." for handle ".RESET."'".BOLD.CYAN."$cmdline[1]".RESET."'\n";
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
				}
				else {
					$sth = $dbh->prepare("DELETE FROM mask WHERE uid = ?");
					if (!$sth->execute($luser->{seq})) {
						$msg = BOLD . RED . "Database error" . RESET . BOLD . WHITE . ": " . RESET . "$DBI::errstr\n";
						$sth->finish;
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					if ($sth->rows == 0) {
						$sth->finish;
						$msg = BOLD.RED."Handle does not currently have a hostmask\n".RESET;
						$c_out += length($msg);
						$fh->send($msg,0);
						next;
					}
					$msg = BOLD.YELLOW."Deleted current hostmask for ".RESET."'".BOLD.CYAN."$cmdline[1]".RESET."'\n";
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
			}
		}
		elsif ($cmdline[0] eq 'pass') {
			my %cuser = ();
			$msg = BOLD . YELLOW . "Current password" . RESET . BOLD . WHITE .": " . RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
			$fh->recv($cuser{cpass},64,0);
			$c_in += length($cuser{cpass});
			chomp($cuser{cpass});
			if ($user->{sec} eq md5_hex($cuser{cpass})) {
				# OK, prompt to change
				$msg = BOLD . YELLOW . "New password" . RESET . BOLD . WHITE . ": " . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				$fh->recv($cuser{npass},64,0);
				$c_in += length($cuser{npass});
				chomp($cuser{npass});
				$msg = BOLD . YELLOW . "New password " . RESET . "(" . BOLD . CYAN. "again" . RESET . ")" . BOLD . WHITE .": " . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				$fh->recv($cuser{vpass},64,0);
				$c_in += length($cuser{vpass});
				chomp($cuser{vpass});
				if ($cuser{npass} ne $cuser{vpass}) {
					$msg = BOLD . RED . "Sorry, passwords do not match, aborting...\n" . RESET;
					$c_out += length($msg);
					$fh->send($msg,0);
					next;
				}
				$cuser{md5pass} = md5_hex($cuser{vpass});
				$sth = $dbh->prepare("UPDATE user SET sec = ? WHERE seq = ?");
				if (!$sth->execute($cuser{md5pass},$user->{seq})) {
					$sth->finish;
					next;
				}
				$sth->finish;
				$user->{sec} = md5_hex($cuser{vpass});
				$msg = BOLD . YELLOW . "Password changed ". RESET . BOLD . CYAN . ";P\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg);
				next;
			}
			else {
				# Doesnt match
				$msg = BOLD . RED . "Sorry, password does not match...\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
		}
		elsif ($cmdline[0] eq 'expert'||$cmdline[0] eq 'e') {
			if ($user->{priv} < 40) {
				$msg = BOLD . RED . "You have insufficient permissions for the given action...\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			if ($expert == 0) {
				$expert = 1;
				$msg = BOLD . YELLOW . "Enabling expert mode\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
			else {
				$expert = 0;
				$msg = BOLD . YELLOW . "Disabling expert mode\n" . RESET;
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
		}
		elsif ($cmdline[0] eq 'help'||$cmdline[0] eq '?') {
			if (!$cmdline[1]) {
				$msg = &bar('YELLOW');
				$msg .= "You are connected to a system known as a BBS or Bulietin Board System.\nThe system allows you to do many things, and is told by you what to do using simple commands.\nHere is a list of commands that I understand and what they do:\n\n". &bar('GREEN') . "\t".BOLD.YELLOW."help".RESET." - this help screen.\n\t".BOLD.YELLOW."pass".RESET." - allows you to change your password.\n\t".BOLD.YELLOW."prompt 1-7".RESET." - allows you to change your prompt color.\n\t".BOLD.YELLOW."mesg handle message".RESET." - allows you to send a private message to someone\n\t" . BOLD . YELLOW . "who" . RESET . " - shows you who is online right now\n\t" . BOLD . YELLOW . "whois handle" . RESET ." - lets you view information on someone\n\t" . BOLD . YELLOW . "seen handle" . RESET ." - displays the last time handle was logged in\n\t" . BOLD . YELLOW . "quit" . RESET " - logs you off of the system\n" . &bar('GREEN') . "\n";
				$c_out += length($msg);
				$fh->send($msg,0);
				next;
			}
		}
		else {
			$msg = BOLD.YELLOW."Hmm... I dont understand that command, try ".RESET."'".BOLD.CYAN."help".RESET."'\n";
		}
		$c_out += length($msg);
		$fh->send($msg,0);
		next;
	}
}


sub notify {
	# Notify list management sub-routine
	my $cmdline = shift;	# $cmdline[1] from calling sub entertain()
	my $user = shift;	# $user hash reference; info on current user
	my $fh = shift;		# client socket filehandle
	my $c_out = shift;	# output byte counter

	my ($msg,$sth);
	if (!$cmdline) {
		# List everyone in user's notify list
		$sth = $dbh->prepare("SELECT user.uname FROM notify LEFT JOIN user ON notify.target = user.seq WHERE notify.uid = ?");
		if (!$sth->execute($user->{seq})) {
			$sth->finish;
			return($c_out,\%friends);
		}
		my @lusers;
		if ($sth->rows > 0) {
			while (my $fr = $sth->fetchrow_hashref) {
				push (@lusers,$fr->{uname});
			}
		}
		else {
			$sth->finish;
			$msg = BOLD . YELLOW . "Sorry, you have no friends...\n" . RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
			return($c_out,\%friends);
		}
		$sth->finish;
		$msg = BOLD . YELLOW . 'Handles in your notify list' . RESET . BOLD . WHITE . ": " . RESET;
		foreach my $luser (@lusers) {
			next if !$luser;
			$msg .= "'" . BOLD . CYAN . "$luser" . RESET . "' ";
		}
		$msg .= "\n";
		$c_out += length($msg);
		$fh->send($msg,0);
		return($c_out,\%friends);
	}
	else {
		# Validate given handle, add it to user's notify list
		$sth = $dbh->prepare("SELECT seq FROM user WHERE uname = ?");
		if (!$sth->execute($cmdline)) {
			$sth->finish;
			return($c_out,\%friends);
		}
		if ($sth->rows == 0) {
			$sth->finish;
			$msg = BOLD . YELLOW . "Sorry, I dont know who that is...\n" . RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
			return($c_out,\%friends);
		}
		my $luser = $sth->fetchrow_hashref;
		$sth->finish;
		if ($friends{$luser->{seq}}) {
			# Handle is already in notify list, remove them.
			$sth = $dbh->prepare("DELETE FROM notify WHERE target = ?");
			if (!$sth->execute($luser->{seq})) {
				$sth->finish;
				return($c_out,\%friends);
			}
			$sth->finish;
			delete $friends{$luser->{seq}};
			$msg = BOLD . YELLOW . "Removed " . RESET . "'" . BOLD . CYAN . "$cmdline" . RESET . "' " . BOLD . YELLOW . "from your notify list\n" . RESET;
			$c_out += length($msg);
			$fh->send($msg,0);
		}
		else {
			# Add handle to notify list
			$sth = $dbh->prepare("INSERT INTO notify VALUES('',?,?)");
			if (!$sth->execute($user->{seq},$luser->{seq})) {
				$sth->finish;
				return($c_out,\%friends);
			}
			$sth->finish;
			$friends{$luser->{seq}} = $cmdline;
			$msg = BOLD . YELLOW . "Added " . RESET . "'" . BOLD . CYAN . "$cmdline" . RESET . "' " . BOLD . YELLOW . "to your notify list\n";
			$c_out += length($msg);
			$fh->send($msg,0);
		}
		return($c_out,\%friends);
	}
}

sub who {
	# Display who is currently online
	my $fh = shift;		# Client socket file handle
	my $c_out = shift;	# Output byte counter
	my $msg;

	my $sth = $dbh->prepare("SELECT user.seq,user.uname,connections.host,connections.intime FROM connections LEFT JOIN user ON connections.uid = user.seq WHERE connections.outtime = '00000000000000'");
	if (!$sth->execute()) {
		$sth->finish;
		return($c_out);
	}
	my $num = $sth->rows;

	$msg = " Handle" . BOLD . WHITE . ":" . RESET;
	$msg .= " "x10;
	$msg .= "From" . BOLD . WHITE . ":" . RESET;
	$msg .= " "x28;
	$msg .= "Logged In" . BOLD . WHITE ":" . RESET . "\n";
	$msg .= &bar('green');
	$c_out += length($msg);
	$fh->send($msg,0);
	while (my $usr = $sth->fetchrow_hashref) {
		my $rv = $dbh->prepare("SELECT host FROM mask WHERE uid = ?");
		if (!$rv->execute($usr->{seq})) {
			$rv->finish;
			next;
		}
		if ($rv->rows > 0) {
			my $mask = $rv->fetchrow_hashref;
			$usr->{host} = $mask->{host};
		}
		$rv->finish;
		my ($yr,$mo,$dy,$hr,$mn,$sc) = unpack("A4A2A2A2A2A2", $usr->{intime});
		$dy = sprintf("%d", $dy);
		$hr = sprintf("%d", $hr);
		my $mer = 'pm';
		if ($hr<12) {
			$mer = 'am';
		}
		if ($hr>12) {
			$hr -= 12;
		}
		if (length($usr->{host}) > 28) {
			my $len = length($usr->{host});
			my @parts = (substr($usr->{host},0,12),"[..]",substr($usr->{host},$len-12,12));
			$usr->{host} = "$parts[0]$parts[1]$parts[2]";
		}
		my @spaces;
		$spaces[0] = 17 - length($usr->{uname});
		$spaces[1] = 33 - length($usr->{host});
		$msg = " $usr->{uname}";
		$msg .= " " x $spaces[0];
		$msg .= "$usr->{host}";
		$msg .= " " x $spaces[1];
		$msg .= "$dy, $months{$mo} $yr $hr:$mn$mer\n";
		$c_out += length($msg);
		$fh->send($msg,0);
	}
	$msg = &bar('green');
	$c_out += length($msg);
	$fh->send($msg,0);
	if ($num > 1) {
		$msg = "There are " . BOLD . GREEN . "$num" . RESET . " users online.\n\n";
	}
	else {
		$msg = "There is " . BOLD . RED . "$num" . RESET . " user online.\n\n";
	}
	$c_out += length($msg);
	$fh->send($msg,0);
	$sth->finish;
	return($c_out);
}

sub whois {
	# Show whois information on given handle
	my $fh = shift;
	my $c_out = shift;
	my $handle = shift;
	my $msg;

	my $sth = $dbh->prepare("SELECT * FROM user WHERE uname = ?");
	if (!$sth->execute($handle)) {
		$sth->finish;
		return ($c_out);
	}
	my $whois = $sth->fetchrow_hashref;
	$sth->finish;
	if (!$whois->{uname}) {
		$msg = BOLD . YELLOW . "Hmm... I dont recognize that handle\n" . RESET;
		$c_out += length($msg);
		$fh->send($msg,0);
		return ($c_out);
	}
	$msg = BOLD . YELLOW . "General information for" . RESET . BOLD . WHITE . ": " . RESET . "'" . BOLD . CYAN . "$handle" . RESET . "'\n";
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = &bar('green');
	$c_out += length($msg);
	$fh->send($msg,0);
	my $sd = substr($whois->{signup},0,8);
	# Extract YYYYMMDD from YYYYMMDDHHMMSS
	my $mo = substr($sd,4,2);
	$msg = sprintf(CYAN . "Member since" . RESET . BOLD . WHITE . ": " . RESET . "%d %s, %d\n",substr($sd,6,2),$months{substr($sd,4,2)}, substr($sd,0,4));
	$c_out += length($msg);
	$fh->send($msg,0);
	if ($whois->{fname} && $whois->{lname}) {
		$msg = CYAN . "Real name" . RESET . BOLD . WHITE . ": " . RESET . "$whois->{fname} $whois->{lname}\n";
		$c_out += length($msg);
		$fh->send($msg,0);
	}
	if ($whois->{city}) {
		$msg = CYAN . "Location" . RESET . BOLD . WHITE . ": " . RESET . "$whois->{city}\n";
	}
	$msg = CYAN . "Country" . RESET . BOLD . WHITE . ": " . RESET . "$whois->{cc}\n\n";
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = BOLD . YELLOW . "Contact information" . RESET . BOLD . WHITE .":\n" . RESET;
	$c_out += length($msg);
	$fh->send($msg,0);
	$msg = &bar('green');
	$c_out += length($msg);
	$fh->send($msg,0);
	my $cc = 0;
	if ($whois->{email}) {
		$msg = CYAN . "Email" . RESET . BOLD . WHITE . ": " . RESET . "$whois->{email}\n";
		$c_out += length($msg);
		$fh->send($msg,0);
		$cc++;
	}
	if ($whois->{aim}) {
		$msg = CYAN . "AOL Instant Messanger" . RESET . BOLD . WHITE . ": " . RESET . "$whois->{aim}\n";
		$c_out += length($msg);
		$fh->send($msg,0);
		$cc++;
	}
	if ($whois->{yim}) {
		$msg = CYAN . "Yahoo! Instant Messanger" . RESET . BOLD . WHITE . ": " . RESET . "$whois->{yim}\n";
		$c_out += length($msg);
		$fh->send($msg,0);
		$cc++
	}
	if ($whois->{icq}) {
		$msg = CYAN . "ICQ" . RESET . BOLD . WHITE . ": " . RESET . "$whois->{icq}\n";
		$c_out += length($msg);
		$fh->send($msg,0);
		$cc++
	}
	if ($whois->{url}) {
		$msg = CYAN . "Homepage" . RESET . BOLD . WHITE . ": " . RESET . "$whois->{url}\n";
		$c_out += length($msg);
		$fh->send($msg,0);
		$cc++;
	}
	if ($cc == 0) {
		$msg = BOLD . RED . "None given\n" . RESET;
		$c_out += length($msg);
		$fh->send($msg,0);
	}
	return($c_out);
}

sub mesg {
	# Handle private messages between users
	my $user = shift;	# Hashref containing info on current user
	my $fh = shift;		# Scalar reference containing client socket filehandle
	my $cmdline = shift;	# Scalar containing UID to send message to
	my $n_mesg = shift;	# Scalar containing the message to send
	my $c_out = shift;	# Output byte counter
	my $msg;

	my $sth = $dbh->prepare("SELECT seq FROM user WHERE uname = ?");
	if (!$sth->execute($cmdline)) {
		$sth->finish;
		return ($c_out);
	}
	my $to = $sth->fetchrow_hashref;
	$sth->finish;
	my $ts = strftime "%Y%m%d%H%M%S", localtime;
	# in_time, f_uid, t_uid, mesg, recvd
	$sth = $dbh->prepare("INSERT INTO mesg VALUES('',?,?,?,?,'00000000000000')");
	if (!$sth->execute($ts,$user->{seq},$to->{seq},$n_mesg)) {
		print STDERR "$RealScript ($$) [$ts] Failed to send message from '$user->{seq}' to '$to->{seq}' containing '$n_mesg': $DBI::errstr\n";
		$msg = BOLD . RED . "Failed to send message" . RESET . ", please try again or contact the sysop.\n" . BOLD . RED . "usage" . RESET . BOLD . WHITE . ":" . RESET .  " mesg [handle] [message]\n";
		$sth->finish;
		$c_out += length($msg);
		$fh->send($msg,0);
		return ($c_out);
	}
	$sth->finish;
	$msg = BOLD . YELLOW . "Message sent\n" . RESET;
	$c_out += length($msg);
	$fh->send($msg,0);
	return ($c_out);
}

sub bar {
	my $col = uc(shift);
	my $out;
	$out = eval($col) . "--" . RESET . BOLD . eval($col) . "-" . RESET . eval($col) . "-" . RESET . BOLD . eval($col) . "---" . RESET . BOLD . WHITE . "-" . RESET . BOLD . eval($col) . "-" . RESET . BOLD . WHITE . "----" . RESET . BOLD . eval($col) . "-" . RESET . BOLD . WHITE;
	$out .= "-"x52;
	$out .= RESET . BOLD . eval($col) . "-" . RESET . BOLD . WHITE . "----" . RESET . BOLD . eval($col) . "-" . RESET . BOLD . WHITE . "-". RESET . BOLD . eval($col) . "---" . RESET . eval($col) . "-" . RESET . BOLD . eval($col) . "-" . RESET . eval($col) . "--" . RESET . "\n";
	return ($out);
}

sub prompt {
	my $col = uc(shift);
	my $uname = shift;
	my $out;

	my $mer = 'pm';
	my $ts = strftime "%Y%m%d%H%M%S", localtime;
	my ($yr,$mo,$dy,$hr,$mn,$sc) = unpack("A4A2A2A2A2A2",$ts);
	$dy = sprintf("%d",$dy);
	$hr = sprintf("%d",$hr);
	if ($hr<12) {
		$mer = 'am';
	}
	if ($hr>12) {
		$hr -= 12;
	}
	$out = eval($col) . "--" . RESET . BOLD . eval($col) . "-" . RESET . eval($col) . "-" . RESET . BOLD . eval($col) . "--=" . RESET . BOLD . WHITE . "[ " . RESET . BOLD . CYAN . "$uname" . RESET . BOLD . WHITE . " ]" . RESET . BOLD . eval($col) . "=-" . RESET . eval($col) . "-" . RESET . BOLD . eval($col) . "-=" . RESET . BOLD . WHITE . "[ " . RESET . "$dy" . BOLD . WHITE . ", " . RESET . "$months{$mo} $yr " . BOLD . WHITE . "]" . RESET . BOLD . eval($col) . "=-" . RESET . eval($col) . "-" . RESET . BOLD . eval($col) . "-=" . RESET . BOLD . WHITE . "[ " . RESET . "$hr" . BOLD . WHITE . BLINK . ":" . RESET . "$mn $mer ($conf{tz}) " . BOLD . WHITE . "]" . RESET . BOLD . eval($col) . "=--" . RESET . BOLD . WHITE . "> " . RESET;
	return ($out);
}

sub center {
	my $mg = shift;
	my $wl = length($mg);
	my $fs = 80-$wl;
	$mg = " "x ($fs / 2) . $mg;
	return ($mg);
}

sub stats {
	my $c_out = shift;
	my $msg;

	my $ts = strftime "%Y%m%d%H%M%S", localtime;
	my $sth = $dbh->prepare("SELECT seq FROM connections");
	if (!$sth->execute()) {
		print STDERR "$RealScript ($$) [$ts] Failed to get connection count: $DBI::errstr\n";
	}
	my $cons = $sth->rows;
	$sth->finish;
	my $parent = &POSIX::getppid();
	$sth = $dbh->prepare("SELECT up FROM process WHERE pid = ?");
	if (!$sth->execute($parent)) {
		print STDERR "$RealScript ($$) [$ts] Failed to get uptime: $DBI::errstr\n";
	}
	my $up = $sth->fetchrow_hashref;
	$sth->finish;
	my $delta = tv_interval( [$up->{up},0] );
	my $suf = 'seconds';
	if ($delta > 60 && $delta < 3600 ) {
		$delta = $delta / 60;
		if ($delta == 1) {
			$suf = 'minute';
		}
		else {
			$suf = 'minutes';
		}
	}
	if ($delta > 3600 && $delta < 86400 ) {
		$delta = $delta / 3600;
		if ($delta == 1) {
			$suf = 'hour';
		}
		else {
			$suf = 'hours';
		}
	}
	if ($delta > 86400 ) {
		$delta = $delta / 86400;
		if ($delta == 1) {
			$suf = 'day';
		}
		else {
			$suf = 'days';
		}
	}

	$msg = BOLD . YELLOW . "Statistics" . RESET . BOLD . WHITE . ":" . RESET . "\n";
	$msg .= BOLD . GREEN . "Version" . RESET . BOLD . WHITE . ": " . RESET . BOLD . CYAN . "$conf{banner}" . RESET . "\n";
	$msg .= BOLD . GREEN . "Uptime" . RESET . BOLD . WHITE . ": " . RESET . BOLD . CYAN . sprintf("%.2f",$delta) . RESET . " $suf\n";
	$msg .= BOLD . GREEN . "Total connections" . RESET . BOLD . WHITE . ": " . RESET . BOLD . CYAN . "$cons\n" . RESET;
	$sth = $dbh->prepare("SELECT seq FROM connections WHERE outtime = '00000000000000'");
	if (!$sth->execute()) {
		print STDERR "$RealScript ($$) [$ts] Failed to get active connections: $DBI::errstr\n";
	}
	$cons = $sth->rows;
	$sth->finish;
	$msg .= BOLD . GREEN . "Current connections" . RESET . BOLD . WHITE . ": " . RESET;
	if (!$cons) {
		$msg .= BOLD . RED . "None\n" . RESET;
	}
	elsif ($cons == 1) {
		$msg .= BOLD . CYAN . "One\n" . RESET;
	}
	else {
		$msg .= BOLD . CYAN . "$cons\n" . RESET;
	}
	$c_out += length($msg);
	return ($c_out,$msg);
}
