Fixing SMTP state tracking.

This fixes the case that an SMTP session has multiple mails sent from
the originator but we miss the server's response (e.g., because we
don't see server side packets at all).
This commit is contained in:
Robin Sommer 2014-06-10 16:47:26 -07:00
parent c289a2743b
commit 9301ef5a4f
6 changed files with 73 additions and 51 deletions

View file

@ -58,31 +58,31 @@ export {
## Indicates if client activity has been seen, but not yet logged.
has_client_activity: bool &default=F;
};
type State: record {
helo: string &optional;
## Count the number of individual messages transmitted during
## this SMTP session. Note, this is not the number of
## recipients, but the number of message bodies transferred.
messages_transferred: count &default=0;
pending_messages: set[Info] &optional;
};
## Direction to capture the full "Received from" path.
## REMOTE_HOSTS - only capture the path until an internal host is found.
## LOCAL_HOSTS - only capture the path until the external host is discovered.
## ALL_HOSTS - always capture the entire path.
## NO_HOSTS - never capture the path.
const mail_path_capture = ALL_HOSTS &redef;
## Create an extremely shortened representation of a log line.
global describe: function(rec: Info): string;
global log_smtp: event(rec: Info);
}
redef record connection += {
redef record connection += {
smtp: Info &optional;
smtp_state: State &optional;
};
@ -95,7 +95,7 @@ event bro_init() &priority=5
Log::create_stream(SMTP::LOG, [$columns=SMTP::Info, $ev=log_smtp]);
Analyzer::register_for_ports(Analyzer::ANALYZER_SMTP, ports);
}
function find_address_in_smtp_header(header: string): string
{
local ips = find_ip_addresses(header);
@ -116,17 +116,17 @@ function new_smtp_log(c: connection): Info
l$ts=network_time();
l$uid=c$uid;
l$id=c$id;
# The messages_transferred count isn't incremented until the message is
# The messages_transferred count isn't incremented until the message is
# finished so we need to increment the count by 1 here.
l$trans_depth = c$smtp_state$messages_transferred+1;
if ( c$smtp_state?$helo )
l$helo = c$smtp_state$helo;
# The path will always end with the hosts involved in this connection.
# The lower values in the vector are the end of the path.
l$path = vector(c$id$resp_h, c$id$orig_h);
return l;
}
@ -134,7 +134,7 @@ function set_smtp_session(c: connection)
{
if ( ! c?$smtp_state )
c$smtp_state = [];
if ( ! c?$smtp )
c$smtp = new_smtp_log(c);
}
@ -142,17 +142,17 @@ function set_smtp_session(c: connection)
function smtp_message(c: connection)
{
if ( c$smtp$has_client_activity )
{
Log::write(SMTP::LOG, c$smtp);
c$smtp = new_smtp_log(c);
}
}
event smtp_request(c: connection, is_orig: bool, command: string, arg: string) &priority=5
{
set_smtp_session(c);
local upper_command = to_upper(command);
if ( upper_command != "QUIT" )
c$smtp$has_client_activity = T;
if ( upper_command == "HELO" || upper_command == "EHLO" )
{
c$smtp_state$helo = arg;
@ -161,23 +161,28 @@ event smtp_request(c: connection, is_orig: bool, command: string, arg: string) &
else if ( upper_command == "RCPT" && /^[tT][oO]:/ in arg )
{
if ( ! c$smtp?$rcptto )
if ( ! c$smtp?$rcptto )
c$smtp$rcptto = set();
add c$smtp$rcptto[split1(arg, /:[[:blank:]]*/)[2]];
c$smtp$has_client_activity = T;
}
else if ( upper_command == "MAIL" && /^[fF][rR][oO][mM]:/ in arg )
{
# Flush last message in case we didn't see the server's acknowledgement.
smtp_message(c);
local partially_done = split1(arg, /:[[:blank:]]*/)[2];
c$smtp$mailfrom = split1(partially_done, /[[:blank:]]?/)[1];
c$smtp$has_client_activity = T;
}
}
event smtp_reply(c: connection, is_orig: bool, code: count, cmd: string,
msg: string, cont_resp: bool) &priority=5
{
set_smtp_session(c);
# This continually overwrites, but we want the last reply,
# so this actually works fine.
c$smtp$last_reply = fmt("%d %s", code, msg);
@ -198,7 +203,6 @@ event smtp_reply(c: connection, is_orig: bool, code: count, cmd: string,
event mime_one_header(c: connection, h: mime_header_rec) &priority=5
{
if ( ! c?$smtp ) return;
c$smtp$has_client_activity = T;
if ( h$name == "MESSAGE-ID" )
c$smtp$msg_id = h$value;
@ -241,19 +245,19 @@ event mime_one_header(c: connection, h: mime_header_rec) &priority=5
if ( 1 in addresses )
c$smtp$x_originating_ip = to_addr(addresses[1]);
}
else if ( h$name == "X-MAILER" ||
h$name == "USER-AGENT" ||
h$name == "X-USER-AGENT" )
c$smtp$user_agent = h$value;
}
# This event handler builds the "Received From" path by reading the
# This event handler builds the "Received From" path by reading the
# headers in the mail
event mime_one_header(c: connection, h: mime_header_rec) &priority=3
{
# If we've decided that we're done watching the received headers for
# whatever reason, we're done. Could be due to only watching until
# whatever reason, we're done. Could be due to only watching until
# local addresses are seen in the received from headers.
if ( ! c?$smtp || h$name != "RECEIVED" || ! c$smtp$process_received_from )
return;
@ -263,7 +267,7 @@ event mime_one_header(c: connection, h: mime_header_rec) &priority=3
return;
local ip = to_addr(text_ip);
if ( ! addr_matches_host(ip, mail_path_capture) &&
if ( ! addr_matches_host(ip, mail_path_capture) &&
! Site::is_private_addr(ip) )
{
c$smtp$process_received_from = F;
@ -280,8 +284,11 @@ event connection_state_remove(c: connection) &priority=-5
event smtp_starttls(c: connection) &priority=5
{
if ( c?$smtp )
if ( c?$smtp )
{
c$smtp$tls = T;
c$smtp$has_client_activity = T;
}
}
function describe(rec: Info): string