#!/usr/bin/perl

# This script manipulate the user table in the codepot datbase.
# It doesn't handle LDAP.

use strict;

use Switch;
use Config::Simple;
use DBI;
use Digest::SHA1 qw (sha1_hex);


my $CFG_FILE = '@CFGDIR@/codepot.ini';
my $QC = '';

sub get_config
{
	my $cfg = new Config::Simple();

	if (!$cfg->read ($CFG_FILE))
	{
		return undef;
	}

	my $config = {
		database_hostname => $cfg->param ("database_hostname"),
		database_port => $cfg->param ("database_port"),
		database_username => $cfg->param ("database_username"),
		database_password => $cfg->param ("database_password"),
		database_name => $cfg->param ("database_name"),
		database_driver => $cfg->param ("database_driver"),
		database_prefix => $cfg->param ("database_prefix"),

		codepot_user_executor => $cfg->param("codepot_user_executor")
	};

	return $config;
}

sub open_database
{
	my ($cfg) = @_;

	my $dbtype = $cfg->{database_driver};
	my $dbname = $cfg->{database_name};
	my $dbhost = $cfg->{database_hostname};
	my $dbport = $cfg->{database_port};
	my $dbprefix = $cfg->{database_prefix};

	if ($dbtype eq 'postgre') { $dbtype = 'Pg'; }
	elsif ($dbtype eq 'oci8') { $dbtype = 'Oracle'; }
	elsif ($dbtype eq 'mysqli') { $dbtype = 'mysql'; }

	my $dbstr;
	my $dbuser;
	my $dbpass;
	if ($dbtype eq 'Oracle')
	{
		$QC = '"';
		$dbstr = "DBI:$dbtype:";
		$dbuser = $cfg->{database_username} . '/' . $cfg->{database_password} . '@' . $dbhost;
		$dbpass = '';
	}
	else
	{
		$dbstr = "DBI:$dbtype:database=$dbname;";
		if (length($dbhost) > 0) { $dbstr .= "host=$dbhost;"; }
		if (length($dbport) > 0) { $dbstr .= "port=$dbport;"; }

		$dbuser = $cfg->{database_username};
		$dbpass = $cfg->{database_password};
	}

	my $dbh = DBI->connect(
		$dbstr, $dbuser, $dbpass,
		{ RaiseError => 0, PrintError => 0, AutoCommit => 0 }
	);

	return $dbh;
}

sub close_database
{
	my ($dbh) = @_;
	$dbh->disconnect ();
}

sub rand_string 
{
	my ($length) = @_;

	my $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

	my $str = '';
	my $size = length ($chars);
	for( my $i = 0; $i < $length; $i++ )
	{
		$str = $str . substr ($chars, rand($size), 1) ;
	}

	return $str;
}

sub format_passwd 
{
	my ($password, $binsalt, $hexsalt) = @_;
	my $fmt_pw = '{ssha1}' . sha1_hex ($password . $binsalt) . $hexsalt;
	return $fmt_pw;
}

sub authenticate_database
{
	my ($dbh, $prefix, $userid, $password) = @_;
	
	my $query = $dbh->prepare ("SELECT ${QC}userid${QC},${QC}passwd${QC} FROM ${QC}${prefix}user_account${QC} WHERE ${QC}userid${QC}=? and ${QC}enabled${QC}='N'");
	if (!$query || !$query->execute ($userid))
	{
		return (-1, $dbh->errstr());
	}
	
	my @row = $query->fetchrow_array;
	$query->finish ();

	if (scalar(@row) <= 0) { return (0, undef); }

	my $db_pw = $row[1];
	if (length($db_pw) < 10) { return (0, undef); }

	my $hexsalt = substr ($db_pw, -10);
	my $binsalt = pack ('H*', $hexsalt);

	#my $fmt_pw = '{ssha1}' . sha1_hex ($password . $binsalt) . $hexsalt;
	my $fmt_pw = format_passwd ($password, $binsalt, $hexsalt);
	return  (($fmt_pw eq $db_pw? 1: 0), undef);
}

sub add_user 
{
	my ($dbh, $prefix, $userid, $passwd, $email) = @_;

	my $binsalt = rand_string (5);
	my $hexsalt = unpack ("H*", $binsalt);
	my $fmt_pw = format_passwd ($passwd, $binsalt, $hexsalt);
	
	$dbh->begin_work ();

	my $query = $dbh->prepare ("INSERT INTO ${QC}${prefix}user_account${QC} (${QC}userid${QC},${QC}passwd${QC},${QC}email${QC},${QC}enabled${QC}) VALUES (?, ?, ?, ?)");
	if (!$query || !$query->execute ($userid, $fmt_pw, $email, 'N'))
	{
		my $errstr = $dbh->errstr();
		$dbh->rollback ();
		print (STDERR "Cannot add a user - $errstr\n");
		return -1;
	}

	$query->finish ();
	$dbh->commit ();
	return 0;
}

sub delete_user 
{
	my ($dbh, $prefix, $userid) = @_;

	$dbh->begin_work ();

	my $query = $dbh->prepare ("DELETE FROM ${QC}${prefix}user_account${QC} WHERE ${QC}userid${QC}=?");
	if (!$query || !$query->execute ($userid) || $query->rows() <= 0)
	{
		my $errstr = $dbh->errstr();
		$dbh->rollback ();
		print (STDERR "Cannot delete a user - $errstr\n");
		return -1;
	}

	$query->finish ();
	$dbh->commit ();
	return 0;
}

sub toggle_user 
{
	my ($dbh, $prefix, $userid, $enabled) = @_;

	$dbh->begin_work ();

	my $query = $dbh->prepare ("UPDATE ${QC}${prefix}user_account${QC} SET ${QC}enabled${QC}=? WHERE ${QC}userid${QC}=?");
	if (!$query || !$query->execute ($enabled, $userid) || $query->rows() <= 0)
	{
		my $errstr = $dbh->errstr();
		$dbh->rollback ();
		print (STDERR "Cannot enable/disable a user - $errstr\n");
		return -1;
	}

	$query->finish ();
	$dbh->commit ();
	return 0;
}

sub print_usage 
{
	print (STDERR "Usage: codepot-user add user-id password email\n");
	print (STDERR "       codepot-user delete user-id\n");
	print (STDERR "       codepot-user enable user-id\n");
	print (STDERR "       codepot-user disable user-id\n");
}

#------------------------------------------------------------
# MAIN
#------------------------------------------------------------

my $ARGC = scalar(@ARGV);
my $USERID;
my $PASSWD;
my $EMAIL;
my $op = 0;
my $ret = 0;

if ($ARGC >= 1) 
{ 
	if ($ARGV[0] eq 'add')
	{
		if ($ARGC == 4) 
		{
			$USERID = $ARGV[1];
			$PASSWD = $ARGV[2];
			$EMAIL = $ARGV[3];
			$op = 1;
		}
	}
	elsif ($ARGV[0] eq 'delete')
	{
		if ($ARGC == 2)
		{
			$USERID = $ARGV[1];
			$op = 2;
		}
	}
	elsif ($ARGV[0] eq 'enable')
	{
		if ($ARGC == 2)
		{
			$USERID = $ARGV[1];
			$op = 3;
		}
	}
	elsif ($ARGV[0] eq 'disable')
	{
		if ($ARGC == 2)
		{
			$USERID = $ARGV[1];
			$op = 4;
		}
	}
}

if ($op <= 0)
{
	print_usage ();
	exit (2);
}

my $cfg = get_config ();
if (!defined($cfg))
{
	print (STDERR "Cannot load codepot configuration file\n");
	exit (1);
}

my $allowed_to_execute = 1;
my @executors = split (/\s*,\s*/, $cfg->{codepot_user_executor});
if (scalar(@executors) > 0)
{
	for my $executor (@executors) 
	{
		my $uid = getpwnam ($executor);
		if (defined($uid) && $> == $uid) 
		{
			$allowed_to_execute = 1;
			last;
		}
		else
		{
			$allowed_to_execute = 0;
		}
	}
}

if ($allowed_to_execute == 0)
{
	print (STDERR "Not allowed to run this program\n");
	exit (1);
}

my $dbh = open_database ($cfg);
if (!defined($dbh))
{
	printf (STDERR "Cannot open database - %s\n", $DBI::errstr);
	exit (1);
}

switch ($op)
{
	case 1 { $ret = add_user ($dbh, $cfg->{database_prefix}, $USERID, $PASSWD, $EMAIL); }
	case 2 { $ret = delete_user ($dbh, $cfg->{database_prefix}, $USERID); }
	case 3 { $ret = toggle_user ($dbh, $cfg->{database_prefix}, $USERID, 'Y'); }
	case 4 { $ret = toggle_user ($dbh, $cfg->{database_prefix}, $USERID, 'N'); }
}

close_database ($dbh);
exit ($ret);
