package db;

# This file is part of sqlsus
#
# Copyright (c) 2008-2011 Jérémy Ruffet (sativouf)
# http://sqlsus.sourceforge.net/
#
# Licensed under GPLv3+: GNU GPL version 3 or later
# See LICENSE file or http://www.gnu.org/licenses/gpl.html

use strict;
use warnings;

use Time::HiRes qw/sleep/;
use constant MYNULL => 0x6;

##################################
sub init_databases {
	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","");
	$dbh->do("CREATE TABLE IF NOT EXISTS queries ('query', 'table_name')");
	$dbh->do("CREATE TABLE IF NOT EXISTS tables ('table_name', 'column_name')");
	$dbh->do("CREATE TABLE IF NOT EXISTS databases ('database_name', 'table_name', 'column_name', 'count')");
	$dbh->do("CREATE TABLE IF NOT EXISTS privileges ('grantee', 'privilege_type')");
	$dbh->do("CREATE TABLE IF NOT EXISTS history ('command')");
}


##################################
# create temporary sqlite table
sub create_table {
	my $query = shift;
	my @header = @_;

	# time to order the queries
	# $$ to distinguish sessions
	my $temp_table = "query_" . time . "_${$}_" . &functions::generate_random_string(10, 1..9);

	# allow to keep weird names, with quotes, NULL, etc
	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","",{AutoCommit => 0});

	$dbh->do("INSERT INTO queries VALUES (?,?)", undef, ($query, $temp_table));

	my $sth = $dbh->prepare("INSERT INTO tables VALUES (?,?)");
	for (@header) {
		$sth->execute(($temp_table, $_));
	}
	$sth->finish();

	$dbh->commit();

	$dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","");

	my @nums = (0..1000);
	$dbh->do("CREATE TABLE $temp_table (row, x" . join (',x', @nums[0..$#header]) . ")");
	$dbh->disconnect;

	return $temp_table;
}

##################################
sub get_header {
	my $table = shift;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","");
	my @result = map { $_ = $_->[0] } @{ $dbh->selectall_arrayref("SELECT column_name FROM tables WHERE table_name = ?", undef, $table) };

	return @result;
}


##################################
sub insert_log {
	my ($table, $row, $items, @contents) = @_;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","", {AutoCommit => 0});

	my $sql = "INSERT INTO $table VALUES (?, " . join(',', ('?') x $items) . ")";
	my $sth = $dbh->prepare($sql);

	for (my $i = 0; $i < $#contents + 1; $i += $items) {
		$sth->execute($row++, @contents[$i..$i+$items-1]);
	}
	$sth->finish();
	$dbh->commit();
}

##################################
sub insert_item {
	my ($table, $row, $index, $result) = @_;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","");
	my @res = $dbh->selectrow_array("SELECT row FROM $table WHERE row = $row");
	if (@res) {
		$dbh->do("UPDATE $table SET x$index = ? WHERE row = $row", undef, $result);
	} else {
		my @columns = map { $_ = "NULL" } &get_header($table);
		$columns[$index] = "?";
		$dbh->do("INSERT INTO $table VALUES ($row," . join (',', @columns) . ")", undef, $result);
	}
}

##################################
#- remove the internal row column and return an array
sub table_to_array {
	my $table = shift;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","");
	my @columns = map { $_ = &get_real_column($table, $_) } &get_header($table);

	my $sth = $dbh->prepare("SELECT row," . join(',', @columns) . " FROM $table ORDER BY ABS(row)");
	$sth->execute();

	my @result;
	while (my @values = $sth->fetchrow_array()) {
		# remove row
		shift @values;
		push @result, @values;
	}
	return @result;
}

##################################
sub get_real_column {
	my $table = shift;
	my $column = shift;

	my @columns = &get_header($table);
	my( $i ) = grep { $columns[$_] eq $column } 0 .. $#columns;
	my @nums = (0..10000);
	return "x$nums[$i]";
}


##################################
sub drop_table {
	my $table = shift;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","", {AutoCommit => 0});
	$dbh->do("DELETE FROM tables WHERE table_name = '$table'");
	$dbh->do("DELETE FROM queries WHERE table_name = '$table'");
	$dbh->do("DROP TABLE $table");
	$dbh->commit();
}

##################################
sub query_in_log {
	my $query = shift;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","");
	return $dbh->selectrow_array("SELECT table_name FROM queries WHERE query = ?", undef, $query);
}

##################################
sub count_rows {
	my $database = shift;
	my $table = shift;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$database","","");
	return $dbh->selectrow_array("SELECT count(*) FROM $table");
}

##################################
sub query {
	my $query = shift;
	my @args = @_;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","");
	my $sth = $dbh->prepare($query);
	$sth->execute(@args);
	my @result;
	while (my @values = $sth->fetchrow_array()) {
		push @result, @values;
	}
	if (defined $result[0]) {
		return @result;
	} else {
		return;
	}
}


##################################
# return longest length for one item in one column in one table (in one db, in one...)
# or 0 if no item found
sub longest {
	my $database = shift;
	my $table = shift;
	my $column = shift;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$database","","");

	$column = &get_real_column($table, $column);

	return $dbh->selectrow_array("SELECT IFNULL(length($column),0) FROM $table ORDER BY length($column) DESC LIMIT 1");
}

##################################
sub save_history {
	my @history = @_;

	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","",{AutoCommit => 0});
	my $sth = $dbh->prepare("INSERT INTO history VALUES (?)");
	for (@history) {
		$sth->execute($_);
	}
	$sth->finish();
	$dbh->commit();
}

##################################
sub init_vars {
	&db::query("CREATE TABLE IF NOT EXISTS variables (variable, value)");
	# initialize new variables to NULL in sqlsus DB (first run or new variable in case of sqlsus upgrade)
	my @to_init = qw(http_error_retries binary columns uploader backdoor crawler_depth document_root debug max_returned_length max_url_length max_inj_length max_subqueries blind_max_length processes sleep_after_hit proxy cookie table_prefix database);
	push @to_init, map { "target::$_" } keys %conf::target_keys;
	for (@to_init) {
		&db::query("INSERT INTO variables SELECT ?,NULL WHERE NOT EXISTS (SELECT value FROM variables WHERE variable = ?)", $_, $_);
	}
}

##################################
sub load_var_from_saved_value {
	my $var_name = shift;
	my $conf_var = shift;

	my $old_conf_var = $$conf_var;
	$$conf_var = join '', &db::query("SELECT IFNULL(value,?) FROM variables WHERE variable = '$var_name'", $$conf_var);
	if (!grep($_ eq $var_name, qw(max_url_length max_inj_length))) {
		if (defined $old_conf_var and $old_conf_var ne $$conf_var) {
			&msg::info("Configuration option \"$var_name\" overriden by saved value ($var_name = $$conf_var)");
		}
	}
}

##################################
sub load_vars {
	if (not &db::query("SELECT value FROM variables")) {
		&msg::info("Session \"$main::server\" created");
		return;
	}

	$main::database = join '', &db::query("SELECT IFNULL(value,'') FROM variables WHERE variable = 'database'");
	for my $target_name (&db::query("SELECT variable FROM variables WHERE variable LIKE 'target::%'")) {
		my $value = join '', &db::query("SELECT IFNULL(value,'') FROM variables WHERE variable = '$target_name'");
		$target_name =~ s/target:://;
		$main::target{$target_name} = $value if $value;
	}

	if ($conf::allow_override) {
		@conf::columns		= split ',', join '', &db::query("SELECT IFNULL(value,?) FROM variables WHERE variable = 'columns'", $conf::columns) if not @conf::columns;
		&load_var_from_saved_value("uploader", \$conf::uploader);
		&load_var_from_saved_value("backdoor", \$conf::backdoor);
		&load_var_from_saved_value("crawler_depth", \$conf::crawler_depth);
		&load_var_from_saved_value("document_root", \$conf::document_root);
		&load_var_from_saved_value("debug", \$conf::debug);
		&load_var_from_saved_value("binary", \$conf::binary);
		&load_var_from_saved_value("max_returned_length", \$conf::max_returned_length);
		&load_var_from_saved_value("max_url_length", \$conf::max_url_length);
		&load_var_from_saved_value("max_inj_length", \$conf::max_inj_length);
		&load_var_from_saved_value("max_subqueries", \$conf::max_subqueries);
		&load_var_from_saved_value("blind_max_length", \$conf::blind_max_length);
		&load_var_from_saved_value("processes", \$conf::processes);
		&load_var_from_saved_value("sleep_after_hit", \$conf::sleep_after_hit);
		&load_var_from_saved_value("http_error_retries", \$conf::http_error_retries);
		&load_var_from_saved_value("cookie", \$conf::cookie);
		&load_var_from_saved_value("proxy", \$conf::proxy);
		&load_var_from_saved_value("table_prefix", \$conf::table_prefix);
	} else {
		&msg::info('allow_override set to 0, setting variables to reflect the configuration file provided');
	}
	# BINARY mandatory for numeric types
	# HEX(173) == AD
	# HEX(BINARY 173) == 313733 
	$conf::hex_start = "HEX(BINARY(" if $conf::binary;
	$conf::hex_end = "))" if $conf::binary;
	
	&msg::info("Session \"$main::server\" loaded");
}

##################################
sub save_vars {
	my $dbh = DBI->connect("dbi:SQLite:dbname=$main::logdb","","",{AutoCommit => 0});
	my $sth = $dbh->prepare("UPDATE variables SET value = ? WHERE variable = ?");

	if ($conf::allow_override) {
		$sth->execute(join (',', @conf::columns), "columns");
		$sth->execute($conf::uploader, "uploader");
		$sth->execute($conf::backdoor, "backdoor");
		$sth->execute($conf::crawler_depth, "crawler_depth");
		$sth->execute($conf::document_root, "document_root");
		$sth->execute($conf::debug, "debug");
		$sth->execute($conf::binary, "binary");
		$sth->execute($conf::max_url_length, "max_url_length");
		$sth->execute($conf::max_inj_length, "max_inj_length");
		$sth->execute($conf::max_subqueries, "max_subqueries");
		$sth->execute($conf::blind_max_length, "blind_max_length");
		$sth->execute($conf::processes, "processes");
		$sth->execute($conf::sleep_after_hit, "sleep_after_hit");
		$sth->execute($conf::http_error_retries, "http_error_retries");
		$sth->execute($conf::cookie, "cookie");
		$sth->execute($conf::proxy, "proxy");
		$sth->execute($conf::table_prefix, "table_prefix");
	} else {
		&msg::info('allow_override set to 0, not saving variables');
	}

	$sth->execute($main::database, "database");

	for (keys %main::target) {
		$sth->execute($main::target{$_}, "target::$_") if $main::target{$_};
	}

	$sth->finish;
	$dbh->commit;
}

1
