#!/usr/bin/perl

##########################################################################
# AthenãOCT[o[ێc[ Ver.1.05b  by Ӓ
#
# @NAthenalogin.exeɐڑăAJEg̕ێsc[.
# @AJEg̒ǉA폜ApX[h̕ύXAXg\ł܂.
#
##########################################################################
#-------------------------------------------------------------------
# ݒ
#   ȉ̕ϐ𐳂ݒ肵܂B
#   iOCIIPA|[gAǗ҃pX[hj
#-------------------------------------------------------------------------

my($loginserverip)="127.0.0.1";			#OCIIP
my($loginserverport)=6900;			#OCĨ|[g
my($loginserveradminpassword)="admin";		#OCI̊Ǘ҃pX[h

my($connecttimeout)=10;				# ڑ^CAEg(b)

my($passenc)=2;					# pX[hÍ^Cv

#-------------------------------------------------------------------------
# vvgɂ
# @ȂŋNƃvvgł܂BR}h͌qB
# @R}h͍ŏ̐ł܂܂
#	<>  q  <= quit , li <= list , pass <= passwd cȂ
#
# @R}hXg
#	quit				I
#
#	list	[start_id [end_id]]	AJEgXg\
#		start_id,end_id͋ɏȗ\łB
#		<> list 10 9999999
#		AJEgł̌Ȃǂ͂ł܂
#
#	search	keyword			AJEg
#	search	--regex keyword		AJEgK\
#		L[[h}b`AJEg̃Xg\܂
#
#	add	userid gender passwd	AJEgǉ
#		ID,,passłBʂ MFł(ŏ̂PŔf)
#		<> add testname Male testpass
#		passwdȗƃL[{[hGR[Ń^Cvł܂
#	
#	del	userid			AJEg폜
#		xł̂ły͂ƍ폜܂
#
#	passwd	userid newpassword	AJEgpX[hύX
#		newpasswordȗƃL[{[h^Cvł܂
#
#	state	userid newstate		AJEg̃oԕύX
#		newstatebȂoAu܂nȂoł
#
#	help				ȒPȃwveLXg\
#
#-------------------------------------------------------------------------
# VF̒PR}hƂĂ̍
#	./ladmin --mode param1 ...
#
#	--add		userid gender passwd	AJEg̒ǉ(-ał)
#	--del		userid			AJEg̍폜(-d)
#	--passwd	userid newpasswd	pX[h̕ύX(-p)
#	--list		start_id end_id		AJEgXg\(-l)
#	--search	keyword			AJEg(-s)
#	--search	--regex keyword		K\(-s -e)
#	--state		userid newstate		oԕύX(-t)
#	<> ./ladmin --addaccount testname Male testpass
#
#-------------------------------------------------------------------------
# VF̃̕R}hƂĂ̍
#	炩 --makesymlink w肵ċNAV{bNN
#	쐬ĂÃt@CŋN܂B
#	ÂVeraddaccount͍폜ĒuĂB
#	
#	addaccount	userid gender passwd	AJEg̒ǉ
#	delaccount	userid			AJEg̍폜
#	passwdaccount	userid newpasswd	pX[h̕ύX
#	listaccount	start_id end_id		AJEgXg\
#	searchaccount	keyword			AJEg
#	searchaccount	--regex keyword		AJEgK\
#	stateaccount	userid newstate		oԕύX
#	<> ./addaccount testname Male testpass
#
#-------------------------------------------------------------------------
# Íɂ
# @Digest::MD5W[KvłBDigest::MD5ǂݍ߂ȂꍇA
#   IɈÍȂ[h($passenc=0)ɂȂ̂ŁAW[ȂĂ
#   ladmin͓̂삵܂BiÍ͍s܂񂪁j
# @Ȃ݂CygwinDigest::MD5͍ŏĂ悤łB
#
#-------------------------------------------------------------------------
# UNIXnOSł̎gpɂ
# @R}hƂĎsɂ̓t@CɎsKvłB܂sR[h
#   ύXKv܂Bperl𒼐ڋNȂKv܂B
# @sɂ͊֌W܂񂪁ARg̓VtgJISŏĂ̂
#   {GR[hEUC-JPɂĂB
#   <R}h̗̏>
#       $ mv ladmin ladmin_org
#       $ nkf -eLu ladmin_org > ladmin
#       $ chmod 700 ladmin
#   <Perl𒼐ڋN>
#       $ perl ladmin
#
##########################################################################









use strict;
use IO::Socket;
use Term::ReadLine;
use POSIX qw(:termios_h);

eval " use Digest::MD5 qw(md5); " if $passenc;
$passenc=0 if($@);

my($ver)="1.05b";



# termios̏
my($termios,$orgterml,$termlecho,$termlnoecho)=();
eval{
	$termios=POSIX::Termios->new();
	$termios->getattr(fileno(STDIN));
	$orgterml=$termios->getlflag();
	$termlecho=ECHO | ECHOK | ICANON;
	$termlnoecho=$orgterml & ~$termlecho;
};

# pX[h͗p
sub cbreak(){
	if($termios){
		$termios->setlflag($termlnoecho);
		$termios->setcc(VTIME,1);
		$termios->setattr(fileno(STDIN),TCSANOW);
	}
}
# A
sub cooked(){
	if($termios){
		$termios->setlflag($orgterml);
		$termios->setcc(VTIME,0);
		$termios->setattr(fileno(STDIN),TCSANOW);
	}
}
END{ cooked() }

print "Athena login-server administration tool Ver.$ver\n";

# V{bNN쐬
if( $ARGV[0] eq "--makesymlink" ){
	symlink $0,"addaccount";
	symlink $0,"delaccount";
	symlink $0,"passwdaccount";
	symlink $0,"listaccount";
	symlink $0,"searchaccount";
	symlink $0,"stateaccount";
	print "Symbolic link created.\n";
	exit(0);
}
# T[o[ɐڑ
my($so,$er)=();
eval{
	$so=IO::Socket::INET->new(
		PeerAddr=> $loginserverip,
		PeerPort=> $loginserverport,
		Proto	=> "tcp",
		Timeout	=> $connecttimeout) or $er=1;
};
if($er || $@){
	print "\nCant connect to login server [${loginserverip}:$loginserverport] !\n";
	exit(2);
}

# OCv
if($passenc==0){
	print $so pack("v3a24",0x7918,30,0,$loginserveradminpassword);
	$so->flush();
}else{
	print $so pack("v",0x791a);
	$so->flush();
	my($buf)=readso(4);
	if(unpack("v",$buf)!=0x01dc){
		print "login error. (md5key creation failed)\n";
	}
	$buf=readso(unpack("x2v",$buf)-4);
	my($md5bin)=md5( ($passenc==1)?$buf.$loginserveradminpassword:
			$loginserveradminpassword.$buf );
	print $so pack("v3a16",0x7918,22,$passenc,$md5bin);
	$so->flush();
}

# ԓ҂
my($buf)=readso(3);
if(unpack("v",$buf)!=0x7919 || unpack("x2c",$buf)!=0 ){
	print "login error. (password incorrect ?)\n";
	exit(4);
}
print "logged on.\n";

# vvg̏

if( $0=~/addaccount$/ ||
    (($ARGV[0] eq "-a" || $ARGV[0] eq "--add") && ((shift @ARGV),1)) ){
	my($r)=addaccount($ARGV[0],$ARGV[1],$ARGV[2]);
	quit();
	exit($r);
}elsif( $0=~/delaccount$/||
    (($ARGV[0] eq "-d" || $ARGV[0] eq "--del") && ((shift @ARGV),1)) ){
	my($r)=delaccount($ARGV[0]);
	quit();
	exit($r);
}elsif( $0=~/passwdaccount$/||
    (($ARGV[0] eq "-p" || $ARGV[0] eq "--passwd") && ((shift @ARGV),1)) ){
	my($r)=changepasswd($ARGV[0],$ARGV[1]);
	quit();
	exit($r);
}elsif( $0=~/listaccount$/||
    (($ARGV[0] eq "-l" || $ARGV[0] eq "--list") && ((shift @ARGV),1)) ){
	my($r)=listaccount(int($ARGV[0]),int($ARGV[1]));
	quit();
	exit($r);
}elsif( $0=~/searchaccount$/||
    (($ARGV[0] eq "-s" || $ARGV[0] eq "--search") && ((shift @ARGV),1)) ){
	my($r)=searchaccount($ARGV[0],$ARGV[1]);
	quit();
	exit($r);
}elsif( $0=~/stateaccount$/||
    (($ARGV[0] eq "-t" || $ARGV[0] eq "--state") && ((shift @ARGV),1)) ){
	my($r)=changestate($ARGV[0],$ARGV[1]);
	quit();
	exit($r);
}

my($term)= new Term::ReadLine "ladmin";

# OCł̂Ńvvg̃[v
while(1){
	# vvg\Ɠ
	my($cmd)=$term->readline("ladmin> ");
	chomp $cmd;
	$cmd=~s/\x1b\[\d*\w//g;
	$cmd=~s/[\x00-\x1f]//g;
	my(@cmdlist)=split /\s+/,$cmd;
	
	$cmdlist[0]="help" if( $cmdlist[0] eq "?" );
	

	# R}h
	eval{
		if("help"=~/^\Q$cmdlist[0]/ ){
			print << "EOD";
  add username gender password -- add account
  del username                 -- delete account
  passwd username new_password -- change password of account
  list [start_id [end_id] ]    -- list account
  search expr                  -- search account
  search -r expr               -- search account by regular-expression
  state username newstate      -- change status of account (bann)
  help                         -- this help
  quit                         -- quit
EOD
		}elsif("quit"=~/^\Q$cmdlist[0]/){
			last;
		}elsif("add"=~/^\Q$cmdlist[0]/){
			addaccount($cmdlist[1],$cmdlist[2],$cmdlist[3]);
		}elsif("del"=~/^\Q$cmdlist[0]/){
			delaccount($cmdlist[1]);
		}elsif("passwd"=~/^\Q$cmdlist[0]/){
			changepasswd($cmdlist[1],$cmdlist[2]);
		}elsif("list"=~/^\Q$cmdlist[0]/ || $cmdlist[0] eq "ls"){
			listaccount(int($cmdlist[1]),int($cmdlist[2]));
		}elsif("search"=~/^\Q$cmdlist[0]/){
			searchaccount($cmdlist[1],$cmdlist[2]);
		}elsif("state"=~/^\Q$cmdlist[0]/){
			changestate($cmdlist[1],$cmdlist[2]);
		}elsif($cmdlist[0]){
			print "Unknown command [".$cmdlist[0]."]\n";
		}
#		$term->addhistory($cmd) if $cmdlist[0];
	};
	if($@){
		print "Error [".$cmdlist[0]."]\n$@";
	}
};

# I
quit();

print "bye.\n";
exit(0);



#--------------------------------------------------------------------------

# AJEgXg\
sub listaccount(){
	my($st,$ed)= @_;
	print $so pack("vV2c",0x7920,$st,$ed,0);
	$so->flush();
	$buf=readso(4);
	if(unpack("v",$buf)!=0x7921){
		print "List failed.\n";
		exit(10);
	}
	#      0123456789 012345678901234567890123 012345 
	print "account_id user_id                  gender count state\n";
	print "-------------------------------------------------------\n";
	my($i);
	my($len)=unpack("x2v",$buf);
	for($i=4;$i<$len;$i+=61){
		my(@dat)=unpack("Va24ca24VV",readso(61));
		printf "%10d %-24s %-6s%6d %-6s\n",$dat[0],$dat[1],
			("Female","Male","Server")[$dat[2]],$dat[4],
			("Normal","Banned")[$dat[5]?1:0];
	}
	return 0;
}
# AJEgǉ
sub addaccount(){
	my($userid,$sex,$passwd)= @_;
	if($userid=~/[^A-Za-z0-9\@-_-]/){
		print "Illeagal charactor found in user_id ".$`."[${&}]${'}\n";
		return 101;
	}
	if(length($userid)<4 || length($userid)>24){
		print "Account id too short or long. please input 4-24bytes.\n";
		return 102;
	}
	$sex=uc(substr($sex,0,1));
	if( $sex!~/^[MF]$/ ){
		print "Illeagal gender [$sex] please input M or F.\n";
		return 103;
	}
	if($passwd eq ""){
		return 108 if( ($passwd=typepasswd()) eq "" );
	}
	if($passwd=~/[\x00-\x1f]/){
		my($c)=length($`)+1;
		print "Illeagal charactor found in password (".makeordinal($c)." charactor).\n";
		return 104;
	}
	if(length($passwd)<4){
		print "Password too short or long. please input 4-24bytes.!\n";
		return 105;
	}
	print $so pack("v2a24a24a1", 0x7930,53, $userid,$passwd,$sex );
	$so->flush();
	$buf=readso(2);
	if(unpack("v",$buf)!=0x7931){
		print "Packet error.\n";
		return 106;
	}
	$buf=readso(26);
	print "Account [$userid] ";
	if(unpack("v",$buf)!=0){
		print "creation failed. same account exists.\n";
		return 107;
	}else{
		print "is successfully created.\n";
	}
	return 0;
}
# AJEg폜
sub delaccount(){
	my($userid)= @_;
	print "** Are you really sure to DELETE account [$userid]? (y/n) ";
	if(lc(substr(<STDIN>,0,1)) ne "y"){
		return 121;
	}
	print $so pack("v2a24", 0x7932,28, $userid);
	$so->flush();
	$buf=readso(2);
	if(unpack("v",$buf)!=0x7933){
		print "Packet error.\n";
		return 122;
	}
	$buf=readso(26);
	print "Account [$userid] ";
	if(unpack("v",$buf)!=0){
		print "deletion failed. account dosent exist.\n";
		return 123;
	}else{
		print "is successfully DELETED.\n";
	}
	return 0;
}
# AJEgpX[hύX
sub changepasswd(){
	my($userid,$passwd)= @_;
	if($userid eq ""){
		print "Please input account id.\n";
		return 136;
	}
	if($passwd eq ""){
		return 134 if( ($passwd=typepasswd()) eq "" );
	}
	if(length($passwd)<4){
		print "New password too short or long. please input 4-24bytes.!\n";
		return 131;
	}
	if($passwd=~/[\x00-\x1f]/){
		my($c)=length($`)+1;
		print "Illeagal charactor found in password (".makeordinal($c)." charactor).\n";
		return 135;
	}
	print $so pack("v2a24a24", 0x7934,52, $userid,$passwd);
	$so->flush();
	$buf=readso(2);
	if(unpack("v",$buf)!=0x7935){
		print "Packet error.\n";
		return 132;
	}
	$buf=readso(26);
	print "Account [$userid] ";
	if(unpack("v",$buf)!=0){
		print "password changing failed. account dosent exist.\n";
		return 133;
	}else{
		print "password successfully changed.\n";
	}
	return 130;
}
# AJEg
sub searchaccount(){
	my($p1,$p2)= @_;
	my($exp,$st,$n)=("",0,0);
	if($p1 eq"-e" || $p1 eq"-r" || $p1 eq"--regex" || $p1 eq"--expr"){
		$exp=$p2;
	}else{
		my($c)=0;
		$exp=$p1;
		$exp=~s/([\@])/\\$1/g;
		$c+= $exp=~s/([\-\[\]])/\\$1/g;
		$c+= $exp=~s/([\*\?])/.$1/g;
		$c+= $exp=~s/\\\[(.)\\\-(.)\\\]/[$1-$2]/g;
		$exp="^$exp\$" if $c;
	}
	if( eval{ ""=~/$exp/; }, $@ ){
		print "Regular-Expression compiling failed.\n";
		return 141;
	}
	#      0123456789 012345678901234567890123 012345 
	print "account_id user_id                  gender count state\n";
	print "-------------------------------------------------------\n";
	while(1){
		print $so pack("vV2c",0x7920,$st,0,0);
		$so->flush();
		$buf=readso(4);
		if(unpack("v",$buf)!=0x7921){
			print "Search failed.\n";
			exit(10);
		}
		my($i);
		my($len)=unpack("x2v",$buf);
		last if($len<=4);
		for($i=4;$i<$len;$i+=61){
			my(@dat)=unpack("Va24ca24VV",readso(61));
			$st=$dat[0]+1;
			next if( $dat[1]!~/$exp/ );
			printf "%10d %-24s %-6s%6d %-6s\n",$dat[0],$dat[1],
				("Female","Male","Server")[$dat[2]],$dat[4],
				("Normal","Banned")[$dat[5]?1:0];
			$n++;
		}
	}
	print "$n account(s) found.\n";
	return 0;
}
# oԕύXv
sub changestate {
	my($userid,$s)= @_;
	my(%p)=("n"=>0,"u"=>0,"b"=>1);
	if($s eq ""){
		print "Please input new state [b] or [n].\n";
		return 151;
	}
	$s=lc(substr($s,0,1));
	if(exists $p{$s}){ $s=$p{$s}; }
	else{ $s=int($s); }
	print $so pack("vva24V",0x7936,32,$userid,$s);
	$so->flush();
	$buf=readso(2);
	if(unpack("v",$buf)!=0x7937){
		print "Packet error.\n";
		return 152;
	}
	$buf=readso(30);
	my(@dat)=unpack("va24V",$buf);
	if($dat[0]==0){
		print "account [$userid] is successfully ".
		( ("Unbanned","Banned")[$dat[2]?1:0]).".\n";
	}else{
		print "account [$userid] state changing failed. (".
			(( "Normal","Banned" )[$dat[2]?1:0]) .")\n";
	}

}
# ؒfv
sub quit(){
	print $so pack("v",0x7532);
	$so->flush();
}
# READYM҂igpj
sub waitready(){
	$buf=readso(2);
	if(unpack("v",$buf)!=0x791f){
		print "Command stream error.\n";
		exit(9);
	}
	return 0;
}
# Wo͂̃tbV(gp)
sub flush_stdout {
	$|=1;
	$|=0;
}

# \Pbgf[^ǂݏo
sub readso(){
	my($len)=shift;
	my($buf);
	if( read($so,$buf,$len)<$len ){
		print "Socket read error.\n";
		exit(3);
	}
	return $buf;
}


# pX[h
sub typepasswd {
	my($passwd1,$passwd2);
	cbreak();
	print "type password > "; $passwd1=<STDIN>; chomp($passwd1); print "\n";
	print "verify password > "; $passwd2=<STDIN>; chomp($passwd2); print "\n";
	cooked();
	if($passwd1 ne $passwd2){
		print "Password verification failed. Please input same password.\n";
		return "";
	}
	return $passwd1;
}
# 쐬
sub makeordinal {
	my($c)= shift;
	if($c%10<4 && $c%10!=0 && ($c<10 || $c>20) ){
		return $c.("st","nd","rd")[$c%10-1];
	}
	return $c."th";
}
