zeek/policy/ftp-anonymizer.bro

846 lines
23 KiB
Text

# $Id: ftp-anonymizer.bro 47 2004-06-11 07:26:32Z vern $
@load ftp
@load anon
# Definitions of constants.
# Check if those commands carry any argument; anonymize non-empty
# argument.
const ftp_cmds_with_no_arg = {
"CDUP", "QUIT", "REIN", "PASV", "STOU",
"ABOR", "PWD", "SYST", "NOOP",
"FEAT", "XPWD",
};
const ftp_cmds_with_file_arg = {
"APPE", "CWD", "DELE", "LIST", "MKD",
"NLST", "RMD", "RNFR", "RNTO", "RETR",
"STAT", "STOR", "SMNT",
# FTP extensions
"SIZE", "MDTM",
"MLSD", "MLST",
"XCWD",
};
# For following commands, we check if the argument conforms to the
# specification -- if so, it is safe to be left in the clear.
const ftp_cmds_with_safe_arg = {
"TYPE", "STRU", "MODE", "ALLO", "REST",
"HELP",
"MACB", # MacBinary encoding
};
# ftp_other_cmds can be redefined in site/trace-specific ways.
const ftp_other_cmds = {
"LPRT", "OPTS", "CLNT", "RETP",
"EPSV", "XPWD",
"SOCK", # old FTP command (RFC 354)
} &redef;
# Below defines patterns of arguments of FTP commands
# The following patterns are case-insensitive
const ftp_safe_cmd_arg_pattern =
/TYPE (([AE]( [NTC])?)|I|(L [0-9]+))/
| /STRU [FRP]/
| /MODE [SBC]/
| /ALLO [0-9]+([ \t]+R[ \t]+[0-9]+)?/
| /REST [!-~]+/
| /MACB (E|DISABLE|ENABLE)/
| /SITE TRUTH ON/
&redef;
# The following list includes privacy-safe [cmd, arg] pairs and can be
# customized for particular traces
const ftp_safe_arg_list: set[string, string] = {
} &redef;
# ftp_special_cmd_args offers an even more flexible way of customizing
# argument anonymization: for each [cmd, arg] pair in the table, the
# corresponding value will be the anonymized argument.
const ftp_special_cmd_args: table[string, string] of string = {
} &redef;
# The following words are safe to be left in the clear as the argument
# of a HELP command.
const ftp_help_words = {
"USER", "PORT", "STOR", "MSAM", "RNTO", "NLST", "MKD", "CDUP",
"PASS", "PASV", "APPE", "MRSQ", "ABOR", "SITE", "XMKD", "XCUP",
"ACCT", "TYPE", "MLFL", "MRCP", "DELE", "SYST", "RMD", "STOU",
"SMNT", "STRU", "MAIL", "ALLO", "CWD", "STAT", "XRMD", "SIZE",
"REIN", "MODE", "MSND", "REST", "XCWD", "HELP", "PWD", "MDTM",
"QUIT", "RETR", "MSOM", "RNFR", "LIST", "NOOP", "XPWD",
} &redef;
const ftp_port_pat = /[0-9]+([[:blank:]]*,[[:blank:]]*[0-9]+){5}/;
# Pattern for the argument of EPRT command.
# TODO: the pattern works fot the common case but is not RFC2428-complete.
const ftp_eprt_pat = /\|1\|[0-9]{1,3}(\.[0-9]{1,3}){3}\|[0-9]{1,5}\|/;
# IP addresses.
const ftp_ip_pat = /[0-9]{1,3}(\.[0-9]{1,3}){3}/;
# Domain names (deficiency: domain suffices of countries).
const ftp_domain_name_pat =
/([\-0-9a-zA-Z]+\.)+(com|edu|net|org|gov|mil|uk|fr|nl|es|jp|it)/;
# File names (printable characters).
const ftp_file_name_pat = /[[:print:]]+/;
# File names that can be left in the clear.
const ftp_public_files =
/\// | /\.\./ # "/" and ".."
| /(\/etc\/|master\.)?(passwd|shadow|s?pwd\.db)/ # ftp_hot_files
| /\/(etc|usr\/bin|bin|sbin|kernel)(\/)?/
| /\.rhosts/ | /\.forward/ # ftp_hot_guest_files
&redef;
const ftp_sensitive_files =
/.*(etc\/|master\.)?(passwd|shadow|s?pwd\.db)/ # ftp_hot_files
| /\/(etc|usr\/bin|bin|sbin|kernel)\/.*/
| /.*\.rhosts/ | /.*\.forward/ # ftp_hot_guest_files
&redef;
# Public servers.
const ftp_public_servers: set[addr] = {} &redef;
# Whether we keep all file names (valid or invalid) for public servers.
const ftp_keep_all_files_for_public_servers = F &redef;
# Public files.
const ftp_known_public_files: set[addr, string] = {} &redef;
# Hidden file/directory.
const ftp_hidden_file = /.*\/\.[^.\/].*/;
const ftp_public_hidden_file = /0/ &redef;
# Options for file commands (LIST, NLST) that can be left in the clear.
const ftp_known_option = /-[[:alpha:]]{1,5}[ ]*/;
const ftp_known_site_cmd = {
"UMASK", "GROUP", "INDEX", "GROUPS",
"IDLE", "GPASS", "EXEC", "CHECKMETHOD",
"CHMOD", "NEWER", "ALIAS", "CHECKSUM",
"HELP", "MINFO", "CDPATH",
"TRUTH", "UTIME",
} &redef;
const ftp_sensitive_ids: set[string] = {
"backdoor", "bomb", "diag", "gdm", "issadmin", "msql", "netfrack",
"netphrack", "own", "r00t", "root", "ruut", "smtp", "sundiag", "sync",
"sys", "sysadm", "sysdiag", "sysop", "sysoper", "system", "toor", "tour",
"y0uar3ownd",
};
redef anonymize_ip_addr = T;
redef rewriting_ftp_trace = T;
global ftp_anon_log = open_log_file("ftp-anon") &redef;
# Anonymized arguments, indexed by the anonymization seed.
global anonymized_args: table[string] of string;
# Arguments left in the clear, indexed by the argument and the context.
global ftp_arg_left_in_the_clear: set[string, string];
# Valid files on public servers.
global ftp_valid_public_files: set[addr, string];
type ftp_cmd_arg_anon_result: record {
anonymized: bool;
cmd: string;
arg: string;
};
# Whether anonymize_trace_specific_cmd_arg is defined:
const trace_specific_cmd_arg_anonymization = F &redef;
# This function is to be defined in a trace-specific script. By
# default, use ftp-anonymizer-trace.bro.
global anonymize_trace_specific_cmd_arg:
function(session: ftp_session_info, cmd: string, arg: string):
ftp_cmd_arg_anon_result;
# Anonymize FTP replies by message patterns.
const process_ftp_reply_by_message_pattern = F &redef;
global anonymize_ftp_reply_by_msg_pattern:
function(code: count, act_msg: string,
cmd_arg: ftp_cmd_arg, session: ftp_session_info): string;
# Anonymize an argument *completely* with a hash value of the string,
# and log the anonymization.
function anonymize_arg(typ: string, session: ftp_session_info, cmd: string, arg: string, seed: string): string
{
if ( arg == "" )
return ""; # an empty argument is safe
local arg_seed = string_cat(typ, seed, arg);
if ( arg_seed in anonymized_args )
return anonymized_args[arg_seed];
local a = anonymize_string(arg_seed);
anonymized_args[arg_seed] = a;
print ftp_anon_log,
fmt("anonymize_arg: (%s) {%s} %s \"%s\" to \"%s\" in [%s]",
typ, seed, cmd,
to_string_literal(arg), to_string_literal(a),
id_string(session$connection_id));
return a;
}
# This function is called whenever an argument is to be left in the
# clear. It logs the action if it hasn't occurred before.
function leave_in_the_clear(msg: string, session: ftp_session_info,
arg: string, context: string): string
{
if ( [arg, context] !in ftp_arg_left_in_the_clear )
{
add ftp_arg_left_in_the_clear[arg, context];
print ftp_anon_log, fmt("leave_in_the_clear: (%s) \"%s\" [%s] in [%s]",
msg, to_string_literal(arg), context,
id_string(session$connection_id));
}
return arg;
}
# Sometimes the argument of a file command contains an option string
# before the file name, such as in 'LIST -l /xyz/', the following
# function identifies such option strings and separate the argument
# accordingly.
type separate_option_str_result: record {
opt_str: string;
file_name: string;
};
function separate_option_str(file_name: string): separate_option_str_result
{
local ret: separate_option_str_result;
if ( file_name == /-[[:alpha:]]+( .*)?/ )
{
local parts = split_all(file_name, /-[[:alpha:]]+[ ]*/);
ret$opt_str = string_cat(parts[1], parts[2]);
parts[1] = ""; parts[2] = "";
ret$file_name = cat_string_array(parts);
return ret;
}
else
return [$opt_str = "", $file_name = file_name];
}
# Anonymize a user id
type login_status_type: enum {
LOGIN_PENDING,
LOGIN_SUCCESSFUL,
LOGIN_FAILED,
LOGIN_UNKNOWN,
};
function anonymize_user_id(session: ftp_session_info, id: string, login_status: login_status_type, msg: string): string
{
if ( id in ftp_guest_ids )
{
leave_in_the_clear("guest_id", session, id, msg);
return id;
}
else if ( id in ftp_sensitive_ids && login_status == LOGIN_FAILED )
{
leave_in_the_clear("sensitive_id", session, id, msg);
return id;
}
else
return anonymize_arg("user_name", session, "USER", id, cat(session$connection_id$resp_h, login_status));
}
# Anonymize a file name argument.
function anonymize_file_name_arg(session: ftp_session_info, cmd: string, arg: string, valid_file_name: bool): string
{
local file_name = arg;
local opt_str = "";
if ( cmd == /LIST|NLST/ )
{
# Separate the option from file name if there is one
local ret = separate_option_str(file_name);
if ( ret$opt_str != "" )
{
opt_str = ret$opt_str;
# Shall we anonymize the option string?
if ( opt_str != ftp_known_option )
{
# Anonymize the option conservatively
print ftp_anon_log, fmt("option_anonymized: \"%s\" from (%s %s)",
to_string_literal(opt_str), cmd, file_name);
opt_str = "-<option>";
}
else
# Leave in the clear
print ftp_anon_log, fmt("option_left_in_the_clear: \"%s\" from (%s %s)",
to_string_literal(opt_str), cmd, file_name);
file_name = ret$file_name;
}
}
if ( file_name == "" )
return opt_str;
if ( file_name != ftp_file_name_pat )
{
# Log special file names (e.g. those containing
# control characters) for manual inspection -- such
# file names are rare and may present problems in
# reply anonymization.
print ftp_anon_log, fmt("unrecognized_file_name: \"%s\" (%s) [%s]",
to_string_literal(file_name), cmd, id_string(session$connection_id));
}
if ( strstr(file_name, " ") > 0 )
{
# Log file names that contain spaces (for debugging only)
print ftp_anon_log, fmt("space_in_file_name: \"%s\" (%s) [%s]",
to_string_literal(file_name), cmd, id_string(session$connection_id));
}
# Compute the absolute and clean (without '..' and duplicate
# '/') path
local abs_path = absolute_path(session, file_name);
local resp_h = session$connection_id$resp_h;
local known_public_file =
[resp_h, abs_path] in ftp_known_public_files ||
[resp_h, abs_path] in ftp_valid_public_files;
if ( file_name == ftp_public_files || abs_path == ftp_public_files )
{
leave_in_the_clear("public_path_name", session,
arg, fmt("(%s %s) %s", cmd, file_name, abs_path));
}
else if ( resp_h in ftp_public_servers &&
(abs_path != ftp_hidden_file ||
abs_path == ftp_public_hidden_file) &&
(ftp_keep_all_files_for_public_servers || valid_file_name ||
known_public_file) )
{
if ( valid_file_name && ! known_public_file )
{
add ftp_valid_public_files[resp_h, abs_path];
print ftp_anon_log,
fmt("valid_public_file: [%s, \"%s\"]",
resp_h, to_string_literal(abs_path));
}
leave_in_the_clear("file_on_public_server", session, arg,
fmt("%s %s:%s", cmd, session$connection_id$resp_h, abs_path));
}
else
{
local anon_type: string;
if ( file_name == ftp_sensitive_files ||
abs_path == ftp_sensitive_files )
anon_type = "sensitive_path_name";
else if ( abs_path == ftp_hidden_file )
anon_type = "hidden_path_name";
else if ( resp_h in ftp_public_servers )
anon_type = "invalid_public_file";
else
anon_type = "path_name";
file_name = anonymize_arg(anon_type, session, cmd, abs_path, cat("file:", session$connection_id$resp_h));
}
# concatenate the option string with the file_name
return string_cat(opt_str, file_name);
}
# The argument is presumably privacy-safe, but we should not assume
# that it is the case. Instead, we check if the argument is legal and
# thus privacy-free.
function check_safe_arg(session: ftp_session_info, cmd: string,
arg: string): string
{
if ( cmd == "HELP" )
return ( arg in ftp_help_words ) ?
leave_in_the_clear("known_help_word", session,
arg,
fmt("%s %s", cmd, arg))
: anonymize_arg("unknown_help_string", session, cmd, arg, cmd);
else
# Note that we have already checked the (cmd, arg)
# against ftp_safe_cmd_arg_pattern. So the argument
# here must have an unrecognized pattern and should be
# anonymized.
return anonymize_arg("illegal_argument", session, cmd, arg, cmd);
}
function check_site_arg(session: ftp_session_info, cmd: string,
arg: string): string
{
local site_cmd_arg = split1(arg, /[ \t]*/);
local site_cmd = site_cmd_arg[1];
local site_cmd_upper = to_upper(site_cmd);
if ( site_cmd_upper in ftp_known_site_cmd )
{
if ( length(site_cmd_arg) > 1 )
{
# If the there is an argument after "SITE <cmd>"
local site_arg = site_cmd_arg[2];
site_arg =
anonymize_arg(fmt("arg_of_site_%s", site_cmd),
session, cmd, site_arg, cmd);
return string_cat(site_cmd, " ", site_arg);
}
else
{
return leave_in_the_clear("known_site_command", session,
site_cmd,
fmt("%s %s", cmd, arg));
}
}
else
return anonymize_arg("site_arg", session, cmd, arg, cmd);
}
function anonymize_port_arg(session: ftp_session_info, cmd: string,
arg: string): string
{
local data = parse_ftp_port(arg);
if ( data$valid )
{
local a: addr;
# Anonymize the address part
a = anonymize_address(data$h, session$connection_id);
return fmt_ftp_port(a, data$p);
}
else
return anonymize_arg("unrecognized_ftp_port", session, cmd, arg, "");
}
# EPRT is an extension to the PORT command
function anonymize_eprt_arg(session: ftp_session_info, cmd: string,
arg: string): string
{
if ( arg != ftp_eprt_pat )
return anonymize_arg("unrecognized_EPRT_arg", session, cmd, arg, "");
local parts = split(arg, /\|/);
# Anonymize the address part
local a = parse_dotted_addr(parts[3]);
a = anonymize_address(a, session$connection_id);
return fmt("|%s|%s|%s|", parts[2], a, parts[4]);
}
# Anonymize arguments of commands that we do not understand
function anonymize_other_arg(session: ftp_session_info, cmd: string, arg: string): string
{
local anon: string;
# Try to guess what the arg is
local data = parse_ftp_port(arg);
if ( arg == ftp_port_pat && data$valid )
# Here we do not check whether data$h == session$connection_id$orig_h
# because sometimes it's not the case, but we will try to anonymize it anyway.
{
anon = anonymize_port_arg(session, cmd, arg);
print ftp_anon_log, fmt("anonymize_arg: (%s) {} %s \"%s\" to \"%s\" in [%s]",
"port arg of non-port command",
cmd, arg, anon, id_string(session$connection_id));
}
else if ( arg == ftp_ip_pat )
{
local a = parse_dotted_addr(arg);
a = anonymize_address(a, session$connection_id);
anon = cat(a);
}
else if ( arg == ftp_domain_name_pat )
{
anon = "<domain name>";
}
else if ( arg == "." )
{
anon = arg;
leave_in_the_clear(".", session, arg, fmt("%s %s", cmd, arg));
}
else
# Anonymize by default.
anon = anonymize_arg("cannot_understand_arg",
session, cmd, arg, cmd);
return anon;
}
# Anonymize the command and argument, and put the results in
# cmd_arg$anonymized_{cmd, arg}
function anonymize_ftp_cmd_arg(session: ftp_session_info,
cmd_arg: ftp_cmd_arg)
{
local cmd = cmd_arg$cmd;
local arg = cmd_arg$arg;
local anon : string;
cmd_arg$anonymized_cmd = cmd;
local ret: ftp_cmd_arg_anon_result;
if ( trace_specific_cmd_arg_anonymization )
ret = anonymize_trace_specific_cmd_arg(session, cmd, arg);
else
ret$anonymized = F;
if ( ret$anonymized )
{
# If the trace-specific anonymization applies to the cmd_arg
print ftp_anon_log, fmt("anonymize_arg: (%s) \"%s %s\" to \"%s %s\" in [%s]",
"trace-specific",
cmd, to_string_literal(arg),
ret$cmd, to_string_literal(ret$arg),
id_string(session$connection_id));
cmd_arg$anonymized_cmd = ret$cmd;
anon = ret$arg;
}
else if ( [cmd, arg] in ftp_special_cmd_args )
{
anon = ftp_special_cmd_args[cmd, arg];
print ftp_anon_log, fmt("anonymize_arg: (%s) [%s] \"%s\" to \"%s\" in [%s]",
"special_arg_transformation",
cmd,
to_string_literal(arg),
to_string_literal(anon),
id_string(session$connection_id));
}
else if ( to_upper(string_cat(cmd, " ", arg)) == ftp_safe_cmd_arg_pattern ||
[cmd, arg] in ftp_safe_arg_list )
{
leave_in_the_clear("safe_arg", session, arg, fmt("%s %s", cmd, arg));
anon = arg;
}
else if ( cmd == "USER" || cmd == "ACCT" )
anon = (arg in ftp_guest_ids) ?
arg : anonymize_user_id(session, arg, LOGIN_PENDING, "");
else if ( cmd == "PASS" )
anon = "<password>";
else if ( cmd in ftp_cmds_with_no_arg )
anon = (arg == "") ?
"" :
anonymize_arg("should_have_been_empty",
session, cmd, arg, cmd);
else if ( cmd in ftp_cmds_with_file_arg )
{
if ( session$user in ftp_guest_ids )
anon = ( arg == "" ) ? "" : anonymize_file_name_arg(session, cmd, arg, F);
else
anon = "<path>";
}
else if ( cmd in ftp_cmds_with_safe_arg )
anon = check_safe_arg(session, cmd, arg);
else if ( cmd == "SITE" )
anon = check_site_arg(session, cmd, arg);
else if ( cmd == "PORT" )
anon = anonymize_port_arg(session, cmd, arg);
else if ( cmd == "EPRT" )
anon = anonymize_eprt_arg(session, cmd, arg);
else if ( cmd == "AUTH" )
anon = anonymize_arg("rejected_auth_arg", session, cmd, arg, cmd);
else
{
if ( cmd == /<.*>/ )
cmd_arg$anonymized_cmd = "";
else if ( cmd !in ftp_other_cmds )
{
local a = anonymize_string(string_cat("cmd:", cmd));
print ftp_anon_log, fmt("anonymize_cmd: (%s) \"%s\" [%s] to \"%s\" in [%s]",
"unrecognized command", to_string_literal(cmd),
to_string_literal(arg), a,
id_string(session$connection_id));
cmd_arg$anonymized_cmd = a;
}
anon = anonymize_other_arg(session, cmd, arg);
}
if ( cmd == "USER" )
session$anonymized_user = anon;
cmd_arg$anonymized_arg = anon;
}
# We delay anonymization of certain requests till we see the reply:
# when the argument of a USER command is a sensitive user ID, we
# anonymize the ID if the login is successful and leave the ID in the
# clear otherwise.
#
# The function returns T if the decision should be delayed.
function delay_rewriting_request(session: ftp_session_info, cmd: string,
arg: string): bool
{
return (cmd == "USER" && arg !in ftp_guest_ids) ||
(cmd == "AUTH") ||
(cmd in ftp_cmds_with_file_arg &&
session$connection_id$resp_h in ftp_public_servers);
}
function ftp_request_rewrite(c: connection, session: ftp_session_info,
cmd_arg: ftp_cmd_arg): bool
{
local cmd = cmd_arg$cmd;
local arg = cmd_arg$arg;
if ( delay_rewriting_request(session, cmd, arg) )
{
cmd_arg$rewrite_slot = reserve_rewrite_slot(c);
session$delayed_request_rewrite[cmd_arg$seq] = cmd_arg;
return F;
}
else
{
anonymize_ftp_cmd_arg(session, cmd_arg);
rewrite_ftp_request(c, cmd_arg$anonymized_cmd,
cmd_arg$anonymized_arg);
return T;
}
}
function do_rewrite_delayed_ftp_request(c: connection, delayed: ftp_cmd_arg)
{
seek_rewrite_slot(c, delayed$rewrite_slot);
rewrite_ftp_request(c, delayed$cmd == /<.*>/ ? "" : delayed$cmd,
delayed$anonymized_arg);
release_rewrite_slot(c, delayed$rewrite_slot);
delayed$rewrite_slot = 0;
}
function delayed_ftp_request_rewrite(c: connection, session: ftp_session_info,
delayed: ftp_cmd_arg,
current: ftp_cmd_arg,
reply_code: count)
{
local cmd = delayed$cmd;
local arg = delayed$arg;
if ( cmd == "USER" )
{
delayed$anonymized_cmd = cmd;
local login_status: login_status_type;
if ( reply_code == 0 )
login_status = LOGIN_UNKNOWN;
else if ( delayed$seq == current$seq ||
current$cmd == "PASS" ||
current$cmd == "ACCT" )
{
if ( reply_code >= 330 && reply_code < 340 ) # need PASS/ACCT
login_status = LOGIN_PENDING; # wait to see outcome of PASS
else if ( reply_code >= 400 && reply_code < 600 )
login_status = LOGIN_FAILED;
else if ( reply_code >= 230 && reply_code < 240 )
login_status = LOGIN_SUCCESSFUL;
else
login_status = LOGIN_UNKNOWN;
}
else if ( current$cmd == "USER" ) # another login attempt
login_status = LOGIN_FAILED;
else if ( reply_code == 230 )
login_status = LOGIN_SUCCESSFUL;
else if ( reply_code == 530 )
login_status = LOGIN_FAILED;
else
login_status = LOGIN_UNKNOWN;
if ( login_status != LOGIN_PENDING )
{
delayed$anonymized_arg =
anonymize_user_id(session, arg, login_status,
fmt("(%s %s) %s %s -> %d", cmd, arg, current$cmd, current$arg, reply_code));
do_rewrite_delayed_ftp_request(c, delayed);
}
}
else if ( cmd == "AUTH" )
{
delayed$anonymized_cmd = cmd;
if ( reply_code >= 500 && reply_code < 600 )
# if AUTH fails
{
anonymize_ftp_cmd_arg(session, delayed);
do_rewrite_delayed_ftp_request(c, delayed);
}
else if ( reply_code >= 300 && reply_code < 400 )
;
else # otherwise always anonymize the argument
{
delayed$anonymized_arg = "<auth_mechanism>";
do_rewrite_delayed_ftp_request(c, delayed);
}
}
else if ( cmd in ftp_cmds_with_file_arg && session$connection_id$resp_h in ftp_public_servers )
{
delayed$anonymized_cmd = cmd;
# The argument represents a valid file name on a
# public server only if the operation is successful.
delayed$anonymized_arg =
anonymize_file_name_arg(session, cmd, arg,
(cmd != "LIST" && cmd != "NLST" &&
reply_code >= 100 && reply_code < 300));
do_rewrite_delayed_ftp_request(c, delayed);
}
else
{
print ftp_anon_log, "ERROR! unrecognizable delayed ftp request rewrite";
anonymize_ftp_cmd_arg(session, delayed);
do_rewrite_delayed_ftp_request(c, delayed);
}
}
function process_delayed_rewrites(c: connection, session: ftp_session_info, reply_code: count, cmd_arg: ftp_cmd_arg)
{
local written: table[count] of ftp_cmd_arg;
for ( s in session$delayed_request_rewrite )
{
local ca = session$delayed_request_rewrite[s];
delayed_ftp_request_rewrite(c, session, ca, cmd_arg,
reply_code);
if ( ca$rewrite_slot == 0 )
written[ca$seq] = ca;
}
for ( s in written )
delete session$delayed_request_rewrite[s];
}
function ftp_reply_rewrite(c: connection, session: ftp_session_info,
code: count, msg: string, cont_resp: bool,
cmd_arg: ftp_cmd_arg)
{
local actual_code = session$reply_code;
local xyz = parse_ftp_reply_code(actual_code);
process_delayed_rewrites(c, session, actual_code, cmd_arg);
if ( process_ftp_reply_by_message_pattern )
{
# See *ftp-reply-pattern.bro* for reply anonymization
local anon_msg = anonymize_ftp_reply_by_msg_pattern(actual_code, msg,
cmd_arg, session);
rewrite_ftp_reply(c, code, anon_msg, cont_resp);
}
else
rewrite_ftp_reply(c, code, "<ftp reply message stripped out>", cont_resp);
}
const eliminate_scans = F &redef;
const port_scanners: set[addr] &redef;
global eliminate_scan_for_host: table[addr] of bool;
function eliminate_scan(id: conn_id): bool
{
local h = id$resp_h;
if ( h !in eliminate_scan_for_host )
{
# if the hash string starts with [0-7], i.e. with probability of 50%
eliminate_scan_for_host[h] = (/^[0-7]/ in md5_hmac(h));
if ( eliminate_scan_for_host[h] )
print ftp_anon_log, fmt("eliminate_scans_for_host %s", h);
}
return eliminate_scan_for_host[h];
}
redef call_ftp_connection_remove = T;
function ftp_connection_remove(c: connection)
{
if ( c$id in ftp_sessions )
{
local session = ftp_sessions[c$id];
process_delayed_rewrites(c, session, 0, find_ftp_pending_cmd(session$pending_requests, 0, ""));
}
if ( eliminate_scans && ! requires_trace_commitment )
print ftp_anon_log,
fmt("ERROR: requires_trace_commitment must be set to true in order to allow scan elimination");
if ( requires_trace_commitment )
{
local id = c$id;
local eliminate = F;
if ( eliminate_scans &&
# To check if the connection is part of a port scan
(id !in ftp_sessions ||
ftp_sessions[id]$num_requests == 0 ||
id$orig_h in port_scanners) &&
eliminate_scan(id) )
rewrite_commit_trace(c, F, T);
else
rewrite_commit_trace(c, T, T);
}
}