##! Disables analyzers if protocol violations occur, and adds service information
##! to connection log.

@load ./main

module DPD;

export {
	## Analyzers which you don't want to remove on violations.
	option ignore_violations: set[Analyzer::Tag] = set();

	## Ignore violations which go this many bytes into the connection.
	## Set to 0 to never ignore protocol violations.
	option ignore_violations_after = 10 * 1024;

	## Change behavior of service field in conn.log:
	## Failed services are no longer removed. Instead, for a failed
	## service, a second entry with a "-" in front of it is added.
	## E.g. a http connection with a violation would be logged as
	## "http,-http".
	option track_removed_services_in_connection = F;
}

redef record connection += {
	## The set of prototol analyzers that were removed due to a protocol
	## violation after the same analyzer had previously been confirmed.
	failed_analyzers: set[string] &default=set() &ordered;
};

# Add confirmed protocol analyzers to conn.log service field
event analyzer_confirmation_info(atype: AllAnalyzers::Tag, info: AnalyzerConfirmationInfo) &priority=10
	{
	if ( ! is_protocol_analyzer(atype) && ! is_packet_analyzer(atype) )
		return;

	if ( ! info?$c )
		return;

	local c = info$c;
	local analyzer = Analyzer::name(atype);
	add c$service[analyzer];
	}

# Remove failed analyzers from service field and add them to c$failed_analyzers
# Low priority to allow other handlers to check if the analyzer was confirmed
event analyzer_failed(ts: time, atype: AllAnalyzers::Tag, info: AnalyzerViolationInfo) &priority=-5
	{
	if ( ! is_protocol_analyzer(atype) )
		return;

	if ( ! info?$c )
		return;

	local c = info$c;
	local analyzer = Analyzer::name(atype);
	# If the service hasn't been confirmed yet, or already failed,
	# don't generate a log message for the protocol violation.
	if ( analyzer !in c$service )
		return;

	# If removed service tracking is active, don't delete the service here.
	if ( ! track_removed_services_in_connection )
		delete c$service[analyzer];

	# if statement is separate, to allow repeated removal of service, in case there are several
	# confirmation and violation events
	if ( analyzer !in c$failed_analyzers )
		add c$failed_analyzers[analyzer];

	# add "-service" to the list of services on removal due to violation, if analyzer was confirmed before
	if ( track_removed_services_in_connection && Analyzer::name(atype) in c$service )
		{
		local rname = cat("-", Analyzer::name(atype));
		if ( rname !in c$service )
			add c$service[rname];
		}
	}

event analyzer_violation_info(atype: AllAnalyzers::Tag, info: AnalyzerViolationInfo ) &priority=5
	{
	if ( ! is_protocol_analyzer(atype) )
		return;

	if ( ! info?$c || ! info?$aid )
		return;

	if ( atype in ignore_violations )
		return;

	local c = info$c;
	local aid = info$aid;
	local size = c$orig$size + c$resp$size;
	if ( ignore_violations_after > 0 && size > ignore_violations_after )
		return;

	# analyzer already was removed or connection finished
	# let's still log this.
	if ( lookup_connection_analyzer_id(c$id, atype) == 0 )
		{
		event analyzer_failed(network_time(), atype, info);
		return;
		}

	local disabled = disable_analyzer(c$id, aid, F);

	# If analyzer was disabled, send failed event
	if ( disabled )
		event analyzer_failed(network_time(), atype, info);
	}
