#
# perl/hosttype.pl

#
# infer_hosttype - classify host by banner, the dns HINFO record may be wrong.
# Output to: 
#
# $hosttype{$target}		operating system type per host
# $sysclass{$type}		OS class that an OS type belongs to
# $hosttype_flag		(*)
# $systype_counts{type}		number of hosts per OS type
# $systype_severities{type}	ditto, with at least one vulnerability
# $sysclass_counts{class}	number of hosts per OS class
# $sysclass_severities{class}	ditto, with at least one vulnerability
# $hosts_by_systype{type}	string with hosts per OS type
# $systypes_by_class{class}	string with OS types per OS class
#
# (*) Is reset whenever the $hosttype etc. tables are updated. To recalculate,
# invoke make_hosttype_info_flag().
#
# Standalone usage: perl hosttype.pl [saint_record_files...]
# 

$hosttype_files = "rules/hosttype";
$hosttype_unknown = "unknown type";
$hosttype_notprobed = "not scanned";

sub build_infer_hosttype {
    local($files) = @_;
    local($type, $code, $file, $cond, $type, $class);

    $code = "sub infer_hosttype {\n";
    $code .= "\tlocal(\$type);\n";
    $code .= "\tif (\$service !~ /^(smtp|telnet|ftp|nmap|smurf|netbios-ssn|snmp)\$/) {\n\t\treturn;\n\t}\n";
    $class = "other";

    foreach $file (split(/\s+/, $files)) {
	open(RULES, $file) || die "cannot open $file: $!";
	while (<RULES>) {
	    chop;
	    while (/\\$/) {
		chop;
		$_ .= <RULES>;
		chop;
	    }
	    s/#.*$//;
	    s/\s+$//;
	    next if /^$/;
	    if (/^CLASS\s+(.+)/i) {
		$class = $1;
	    } else {
		s/\bUNKNOWN\b/(HOSTTYPE eq "" || HOSTTYPE eq \"$hosttype_unknown\" || HOSTTYPE eq \"$hosttype_notprobed\")/;
		s/\bHOSTTYPE\b/\$hosttype{\$target}/g;
		s/@/\\@/g;
		($cond, $type) = split(/\t+/, $_, 2);
		$type = "\$1" if ($type eq "");
		$code .= "\
	if ($cond) {
		(\$type = $type) =~ s/[^-_ ,a-zA-Z0-9:.!\"\\/]//g;
		\$hosttype{\$target} = \$type; 
		\$sysclass{\$type} = \"$class\";
		\$hosttype_flag = 0;
		return;
	}
";
	    }
	}
	close(RULES);
    }
    $code .= "\t\$hosttype{\$target} = \"\"
		if !exists(\$hosttype{\$target});\n}";
    return $code;
}

#
# Generate hosttype-dependent statistics.
#
sub make_hosttype_info {
    local($host, $class, $type, $count);

    if ($hosttype_flag > 0) {
	return;
    }
    $hosttype_flag = time();

    print "Rebuild host type statistics...\n" if $debug;
    &make_severity_info();

    %hosts_by_systype = ();
    %systypes_by_class = ();
    %systype_severities = ();
    %systype_counts = ();
    %sysclass_severities = ();
    %sysclass_counts = ();

    for $host (keys %all_hosts) {
	$hosttype{$host} = $hosttype_unknown 
	    if ($host && $hosttype{$host} eq "" && &get_host_time($host) > 1);
	$hosttype{$host} = $hosttype_notprobed 
	    if ($host && $hosttype{$host} eq "");
    }
    $sysclass{$hosttype_unknown} = "other";
    $sysclass{$hosttype_notprobed} = "other";

    for $type (sort keys %sysclass) {
	$systype_severities{$type} = 0;
	$sysclass_severities{$sysclass{$type}} = 0;
    }
    for $host (sort keys %hosttype) {
	$type = $hosttype{$host};
	if ($type eq "") {
	    $type = $hosttype{$host} = $hosttype_unknown;
	}
	if (exists($severity_host_type_info{$host})) {
	     $systype_severities{$type}++;
	}
	$hosts_by_systype{$type} .= $host . "\n";
	$systype_counts{$type}++;
    }

    for $type (sort keys %sysclass) {
	if (($count = $systype_counts{$type}) > 0) {
	    $class = $sysclass{$type};
	    $systypes_by_class{$class} .= $type . "\n";
	    $sysclass_counts{$class} += $count;
	    if ($systype_severities{$type}) {
		$sysclass_severities{$class} += $systype_severities{$type};
	    }
	} else {
	    delete($sysclass{$type});
	}
    }
}

#
# Reset the host type tables.
#
sub clear_hosttype_info {
    %hosttype = ();

    %systype = ();
    %hosts_by_systype = ();
    %systypes_by_class = ();
    %systype_severities = ();
    %systype_counts = ();
    %sysclass_severities = ();
    %sysclass_counts = ();
    $hosttype_flag = 0;
}

#
# Some scaffolding for stand-alone operation
#
if ($running_under_saint) {
    eval &build_infer_hosttype($hosttype_files);
    die "error in $hosttype_files: $@" if $@;
} else {
    $running_under_saint = -1;
    $debug = 1;

    require 'perl/misc.pl';
    require 'perl/severities.pl';

    #
    # Generate code from rules files.
    #
    $code = &build_infer_hosttype($hosttype_files);
    print "Code generated from $hosttype_files:\n\n";
    print $code;
    eval $code; 
    die "error in $hosttype_files: $@" if $@;

    #
    # Apply rules.
    #
    print "\nApplying rules to all SAINT records...\n";
    while (<>) {
	chop;
	if (&saint_split($_) == 0) {
	    &infer_hosttype();
	}
    }
    &make_hosttype_info();
    for (sort keys %hosttype) {
	print "$hosttype{$_}	$_\n";
    }
    print "Host class information:\n";
    for $class (sort keys %systypes_by_class) {
	print "$class	";
	print join('	', split(/\n/, $systypes_by_class{$class}));
	print "\n";
    }
}

1;
