# $Id: software.bro,v 1.6 2002/10/13 21:07:59 vern Exp $
#
# Keeps track of the software running on hosts.

@load site

global software_file = open_log_file("software");

# Invoked whenever we discover new software for a host (but not for a new
# version of previously found software).
global software_new: event(host: addr, s: software, raw: string);

# Invoked whenever we discover a new version of previously discovered
# software.
global software_version_change: event(host: addr, s: software,
	old_version: software_version, raw: string);

# Index is the name of the software.
type software_set: table[string] of software;

# You may or may not want to define a timeout for this.
global software_table: table[addr] of software_set;

# Some software can be installed twice on the same server
# with different major numbers.
global software_ident_by_major: set[string] = {
	"PHP"
} &redef;

# Compare two versions.
#   Returns -1 for v1 < v2, 0 for v1 == v2, 1 for v1 > v2.
#   If the numerical version numbers match, the addl string
#   is compared lexicographically.
function software_cmp_version(v1: software_version, v2: software_version): int
	{
	if ( v1$major < v2$major )
		return -1;
	if ( v1$major > v2$major )
		return 1;

	if ( v1$minor < v2$minor )
		return -1;
	if ( v1$minor > v2$minor )
		return 1;

	if ( v1$minor2 < v2$minor2 )
		return -1;
	if ( v1$minor2 > v2$minor2 )
		return 1;

	return strcmp(v1$addl, v2$addl);
	}

# Convert a version into a string "a.b.c-x".
function software_fmt_version(v: software_version): string
	{
	return fmt("%s%s%s%s",
			v$major >= 0 ? fmt("%d", v$major) : "",
			v$minor >= 0 ? fmt(".%d", v$minor) : "",
			v$minor2 >= 0 ? fmt(".%d", v$minor2) : "",
			v$addl != "" ? fmt("-%s", v$addl) : "");
	}

# Convert a software into a string "name a.b.cx".
function software_fmt(s: software): string
	{
	return fmt("%s %s", s$name, software_fmt_version(s$version));
	}

# Insert a mapping into the table
# Overides old entries for the same software and generates events if needed.
### FIXME: Do we need a software_unregister() as well?
function software_register(host: addr, s: software, raw: string)
	{
	# Host already known?
	if ( host !in software_table )
		{
		local empty_set: software_set;
		software_table[host] = empty_set;
		}

	# If a software can be installed more than once on a host
	# (with a different major version), we identify it by "<name>-<major>"
	if ( s$name in software_ident_by_major && s$version$major >= 0 )
		s$name = fmt("%s-%d", s$name, s$version$major);

	local soft_set = software_table[host];

	# Software already registered for this host?
	if ( s$name in soft_set )
		{
		# Is it a different version?
		local old = soft_set[s$name];
		if ( software_cmp_version(old$version, s$version) != 0 )
			event software_version_change(host, s, old$version, raw);
		}
	else
		event software_new(host, s, raw);

	soft_set[s$name] = s;
	}

event software_version_found(host: addr, s: software, raw: string)
	{
	if ( is_local_addr(host) )
		software_register(host, s, raw);
	}

event software_parse_error(host: addr, s: string)
	{
	if ( is_local_addr(host) )
		print software_file, fmt("%.6f %s: can't parse '%s'",
						network_time(), host, s);
	}

event software_new(host: addr, s: software, raw: string)
	{
	print software_file, fmt("%.6f %s uses %s (%s)", network_time(),
					host, software_fmt(s), raw);
	}

event software_version_change(host: addr, s: software,
				old_version: software_version, raw: string)
	{
	local msg = fmt("%.6f %s switched from %s to %s (%s)",
			network_time(), host, software_fmt_version(old_version),
			software_fmt(s), raw);

	print software_file, msg;
	}
