##! Framework for managing Zeek's protocol analyzers.
##!
##! The analyzer framework allows to dynamically enable or disable analyzers, as
##! well as to manage the well-known ports which automatically activate a
##! particular analyzer for new connections.
##!
##! Protocol analyzers are identified by unique tags of type
##! :zeek:type:`Analyzer::Tag`, such as :zeek:enum:`Analyzer::ANALYZER_HTTP`.
##! These tags are defined internally by
##! the analyzers themselves, and documented in their analyzer-specific
##! description along with the events that they generate.
##!
##! Analyzer tags are also inserted into a global :zeek:type:`AllAnalyzers::Tag` enum
##! type. This type contains duplicates of all of the :zeek:type:`Analyzer::Tag`,
##! :zeek:type:`PacketAnalyzer::Tag` and :zeek:type:`Files::Tag` enum values
##! and can be used for arguments to function/hook/event definitions where they
##! need to handle any analyzer type. See :zeek:id:`Analyzer::register_for_ports`
##! for an example.

@load base/frameworks/packet-filter/utils

module Analyzer;

export {
	## If true, all available analyzers are initially disabled at startup.
	## One can then selectively enable them with
	## :zeek:id:`Analyzer::enable_analyzer`.
	global disable_all = F &redef;

	## Enables an analyzer. Once enabled, the analyzer may be used for analysis
	## of future connections as decided by Zeek's dynamic protocol detection.
	##
	## tag: The tag of the analyzer to enable.
	##
	## Returns: True if the analyzer was successfully enabled.
	global enable_analyzer: function(tag: Analyzer::Tag) : bool;

	## Disables an analyzer. Once disabled, the analyzer will not be used
	## further for analysis of future connections.
	##
	## tag: The tag of the analyzer to disable.
	##
	## Returns: True if the analyzer was successfully disabled.
	global disable_analyzer: function(tag: Analyzer::Tag) : bool;

	## Registers a set of well-known ports for an analyzer. If a future
	## connection on one of these ports is seen, the analyzer will be
	## automatically assigned to parsing it. The function *adds* to all ports
	## already registered, it doesn't replace them.
	##
	## tag: The tag of the analyzer.
	##
	## ports: The set of well-known ports to associate with the analyzer.
	##
	## Returns: True if the ports were successfully registered.
	global register_for_ports: function(tag: Analyzer::Tag, ports: set[port]) : bool;

	## Registers an individual well-known port for an analyzer. If a future
	## connection on this port is seen, the analyzer will be automatically
	## assigned to parsing it. The function *adds* to all ports already
	## registered, it doesn't replace them.
	##
	## tag: The tag of the analyzer.
	##
	## p: The well-known port to associate with the analyzer.
	##
	## Returns: True if the port was successfully registered.
	global register_for_port: function(tag: Analyzer::Tag, p: port) : bool;

	## Returns a set of all well-known ports currently registered for a
	## specific analyzer.
	##
	## tag: The tag of the analyzer.
	##
	## Returns: The set of ports.
	global registered_ports: function(tag: AllAnalyzers::Tag) : set[port];

	## Returns a table of all ports-to-analyzer mappings currently registered.
	##
	## Returns: A table mapping each analyzer to the set of ports
	##          registered for it.
	global all_registered_ports: function() : table[AllAnalyzers::Tag] of set[port];

	## Translates an analyzer type to a string with the analyzer's name.
	##
	## tag: The analyzer tag.
	##
	## Returns: The analyzer name corresponding to the tag.
	global name: function(tag: Analyzer::Tag) : string;

	## Translates an analyzer's name to a tag enum value.
	##
	## name: The analyzer name.
	##
	## Returns: The analyzer tag corresponding to the name.
	global get_tag: function(name: string): Analyzer::Tag;

	## Schedules an analyzer for a future connection originating from a
	## given IP address and port.
	##
	## orig: The IP address originating a connection in the future.
	##       0.0.0.0 can be used as a wildcard to match any originator address.
	##
	## resp: The IP address responding to a connection from *orig*.
	##
	## resp_p: The destination port at *resp*.
	##
	## analyzer: The analyzer ID.
	##
	## tout: A timeout interval after which the scheduling request will be
	##       discarded if the connection has not yet been seen.
	##
	## Returns: True if successful.
	global schedule_analyzer: function(orig: addr, resp: addr, resp_p: port,
	                                   analyzer: Analyzer::Tag, tout: interval) : bool;

	## Automatically creates a BPF filter for the specified protocol based
	## on the data supplied for the protocol through the
	## :zeek:see:`Analyzer::register_for_ports` function.
	##
	## tag: The analyzer tag.
	##
	## Returns: BPF filter string.
	global analyzer_to_bpf: function(tag: Analyzer::Tag): string;

	## Create a BPF filter which matches all of the ports defined
	## by the various protocol analysis scripts as "registered ports"
	## for the protocol.
	global get_bpf: function(): string;

	## A set of analyzers to disable by default at startup. The default set
	## contains legacy analyzers that are no longer supported.
	global disabled_analyzers: set[Analyzer::Tag] = {
		ANALYZER_TCPSTATS,
	} &redef;

	## A table of ports mapped to analyzers that handle those ports. This is
	## used by BPF filtering and DPD. Session analyzers can add to this using
	## Analyzer::register_for_port(s) and packet analyzers can add to this
	## using PacketAnalyzer::register_for_port(s).
	global ports: table[AllAnalyzers::Tag] of set[port];
}

@load base/bif/analyzer.bif

event zeek_init() &priority=5
	{
	if ( disable_all )
		__disable_all_analyzers();

	for ( a in disabled_analyzers )
		disable_analyzer(a);
	}

function enable_analyzer(tag: Analyzer::Tag) : bool
	{
	return __enable_analyzer(tag);
	}

function disable_analyzer(tag: Analyzer::Tag) : bool
	{
	return __disable_analyzer(tag);
	}

function register_for_ports(tag: Analyzer::Tag, ports: set[port]) : bool
	{
	local rc = T;

	for ( p in ports )
		{
		if ( ! register_for_port(tag, p) )
			rc = F;
		}

	return rc;
	}

function register_for_port(tag: Analyzer::Tag, p: port) : bool
	{
	if ( ! __register_for_port(tag, p) )
		return F;

	if ( tag !in ports )
		ports[tag] = set();

	add ports[tag][p];
	return T;
	}

function registered_ports(tag: AllAnalyzers::Tag) : set[port]
	{
	return tag in ports ? ports[tag] : set();
	}

function all_registered_ports(): table[AllAnalyzers::Tag] of set[port]
	{
	return ports;
	}

function name(atype: AllAnalyzers::Tag) : string
	{
	return __name(atype);
	}

function get_tag(name: string): AllAnalyzers::Tag
	{
	return __tag(name);
	}

function schedule_analyzer(orig: addr, resp: addr, resp_p: port,
			   analyzer: Analyzer::Tag, tout: interval) : bool
	{
	return __schedule_analyzer(orig, resp, resp_p, analyzer, tout);
	}

function analyzer_to_bpf(tag: Analyzer::Tag): string
	{
	# Return an empty string if an undefined analyzer was given.
	if ( tag !in ports )
		return "";

	local output = "";
	for ( p in ports[tag] )
		output = PacketFilter::combine_filters(output, "or", PacketFilter::port_to_bpf(p));
	return output;
	}

function get_bpf(): string
	{
	local output = "";
	for ( tag in ports )
		{
		output = PacketFilter::combine_filters(output, "or", analyzer_to_bpf(tag));
		}
	return output;
	}
