mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00
Merge remote branch 'origin/master' into topic/policy-scripts-new
This commit is contained in:
commit
00f4751ada
46 changed files with 2168 additions and 1295 deletions
|
@ -1,13 +1,23 @@
|
|||
# $Id: nfs.bro 4017 2007-02-28 07:11:54Z vern $
|
||||
|
||||
@load udp
|
||||
|
||||
module NFS;
|
||||
|
||||
module NFS3;
|
||||
|
||||
export {
|
||||
global log_file = open_log_file("nfs") &redef;
|
||||
global names_log_file = open_log_file("nfs-files") &redef;
|
||||
global readdir_log = open_log_file("nfs-readdir") &redef;
|
||||
|
||||
# We want to estimate how long it takes to lookup a chain of FH (directories)
|
||||
# until we reach a FH that is used in a read or write operation. Whenever we
|
||||
# get a new FH, we check how long ago we got the FH's parent. If this is less
|
||||
# than fh_chain_maxtime, we assume that they belong to a lookup chain and set
|
||||
# the dt value for the FH accordingly.
|
||||
global fh_chain_maxtime = 100 msec;
|
||||
}
|
||||
|
||||
|
||||
redef capture_filters += {
|
||||
["nfs"] = "port 2049",
|
||||
# NFS UDP packets are often fragmented.
|
||||
|
@ -17,84 +27,382 @@ redef capture_filters += {
|
|||
global nfs_ports = { 2049/tcp, 2049/udp } &redef;
|
||||
redef dpd_config += { [ANALYZER_NFS] = [$ports = nfs_ports] };
|
||||
|
||||
# Information about a filehandle
|
||||
type fh_info : record {
|
||||
id: count; # A unique ID (counter) for more readable representation of the FH
|
||||
pathname: string &default="@"; # the path leading to this FH
|
||||
basename: string &default=""; # the name of this FHs file or directory
|
||||
mimetype: string &default="";
|
||||
t0: time &default=double_to_time(0); # time when we first saw this FH
|
||||
dt: interval &default=0 sec; # time it took to get this FH (assuming a chain of
|
||||
# procedures that ultimately yield the FH for the file
|
||||
# a client is interested in
|
||||
chainlen: count &default=0;
|
||||
attr: fattr_t &optional;
|
||||
};
|
||||
|
||||
# Maps opaque file handles to numbers for easier tracking.
|
||||
global num_fhs = 0;
|
||||
global fh_map: table[string] of count;
|
||||
global fh_map: table[addr,string] of fh_info;
|
||||
|
||||
function map_fh(fh: string): string
|
||||
# Maps connids to number for easier post processing
|
||||
global num_nfs_conns = 0;
|
||||
global nfs_conns: table[conn_id] of count;
|
||||
|
||||
|
||||
# Get the FH info. Create a new info if it doesn't exists
|
||||
function get_fh_info(c: connection, fh: string): fh_info
|
||||
{
|
||||
if ( fh !in fh_map )
|
||||
fh_map[fh] = ++num_fhs;
|
||||
if ( [c$id$resp_h, fh] !in fh_map )
|
||||
{
|
||||
# Don't have a mapping for this FH yet. E.g., a root FH
|
||||
local newfhinfo: fh_info = [ $id=++num_fhs ];
|
||||
newfhinfo$pathname = fmt("@%d", newfhinfo$id);
|
||||
newfhinfo$t0 = network_time();
|
||||
fh_map[c$id$resp_h, fh] = newfhinfo;
|
||||
}
|
||||
return fh_map[c$id$resp_h, fh];
|
||||
}
|
||||
|
||||
return cat("FH", fh_map[fh]);
|
||||
function log_filename(proc: string, info: fh_info)
|
||||
{
|
||||
print names_log_file, fmt("%.6f %s path FH%d %s/%s", network_time(), proc,
|
||||
info$id, info$pathname, info$basename);
|
||||
##print fmt("%.6f FH%d <%s> <%s>", network_time(), info$id, info$pathname, info$basename);
|
||||
}
|
||||
|
||||
function fmt_attr(a: fattr_t): string
|
||||
{
|
||||
local s = fmt("%s %s %d %d %d %d %d %d %d %d %d %.2f %.2f %.2f",
|
||||
a$ftype, mode2string(a$mode), a$nlink, a$uid, a$gid, a$size, a$used, a$rdev1, a$rdev2,
|
||||
a$fsid, a$fileid, a$atime, a$mtime, a$ctime);
|
||||
return s;
|
||||
}
|
||||
|
||||
function log_attributes(c: connection, proc: string, fh: string, attr: fattr_t)
|
||||
{
|
||||
local info = get_fh_info(c,fh);
|
||||
local did_change = F;
|
||||
# check whether the attributes have changes
|
||||
if (info?$attr)
|
||||
{
|
||||
# We can't compare records for equality :-(. So we use a hack.
|
||||
# We add the two instance we want to compare to a set. If there
|
||||
# are two elements in the set, the records are not equal...
|
||||
local dummy: set[fattr_t];
|
||||
add dummy[info$attr];
|
||||
add dummy[attr];
|
||||
if (|dummy| > 1)
|
||||
did_change = T;
|
||||
}
|
||||
else
|
||||
did_change=T;
|
||||
if (did_change)
|
||||
{
|
||||
info$attr = attr;
|
||||
print names_log_file, fmt("%.6f %s attr FH%d %s", network_time(), proc,
|
||||
info$id, fmt_attr(attr));
|
||||
}
|
||||
}
|
||||
|
||||
# Update (or add) a filehandle mapping.
|
||||
# parentfh ... parent (directory)
|
||||
# name ....... the name for this FH
|
||||
# fh ......... the new FH
|
||||
function add_update_fh(c: connection, proc: string, parentfh: string, name: string, fh: string)
|
||||
{
|
||||
local info = get_fh_info(c, fh);
|
||||
|
||||
# TODO: we could/should check if we already have a pathname and/or basename
|
||||
# for this FH and if so whether it matches the parent we just got!
|
||||
if (name == ".")
|
||||
return;
|
||||
info$basename = name;
|
||||
if (parentfh != "")
|
||||
{
|
||||
local parentinfo = get_fh_info(c, parentfh);
|
||||
info$pathname = cat(parentinfo$pathname, "/", parentinfo$basename);
|
||||
if ( (network_time() - parentinfo$t0) < fh_chain_maxtime
|
||||
&& info$dt < 0 sec )
|
||||
{
|
||||
# The FH is part of lookup chain and it doesn't yet have a dt value
|
||||
# TODO: this should probably be moved to get_fh_info(). But then get_fh_info()
|
||||
# would need information about a FH's parent....
|
||||
# TODO: We are using network_time(), but we really should use request
|
||||
# and reply time!!!
|
||||
info$dt = parentinfo$dt + (network_time() - parentinfo$t0);
|
||||
info$chainlen = parentinfo$chainlen + 1;
|
||||
}
|
||||
}
|
||||
log_filename(proc, info);
|
||||
}
|
||||
|
||||
function set_fh_mimetype(c: connection, fh: string, proc:string, data: string)
|
||||
{
|
||||
local info = get_fh_info(c,fh);
|
||||
local mimetype = identify_data(data, T);
|
||||
if (info$mimetype != mimetype)
|
||||
{
|
||||
info$mimetype = mimetype;
|
||||
print names_log_file, fmt("%.6f %s type FH%d %s/%s %s", network_time(), proc,
|
||||
info$id, info$pathname, info$basename, (mimetype!="") ? mimetype : "X/X");
|
||||
}
|
||||
}
|
||||
|
||||
# Get the total time of the lookup chain for this FH to the
|
||||
# current network time. Returns a negative interal if no
|
||||
# lookup chain was found
|
||||
function get_fh_chaintime_str(c:connection, fh:string): string
|
||||
{
|
||||
local info = get_fh_info(c, fh);
|
||||
if ((network_time() - info$t0) < fh_chain_maxtime)
|
||||
return fmt("%d %.6f", info$chainlen, info$dt + (network_time() - info$t0));
|
||||
else
|
||||
return fmt("%d %.6f", 0, 0.0);
|
||||
}
|
||||
|
||||
# Get a FH ID
|
||||
function get_fh_id(c:connection, fh: string): string
|
||||
{
|
||||
return cat("FH", get_fh_info(c, fh)$id);
|
||||
}
|
||||
|
||||
# Get the basename for the FH
|
||||
function get_fh_basename(c:connection, fh: string): string
|
||||
{
|
||||
return get_fh_info(c, fh)$basename;
|
||||
}
|
||||
|
||||
# Get the fullname for the FH
|
||||
function get_fh_fullname(c:connection, fh: string): string
|
||||
{
|
||||
local info = get_fh_info(c, fh);
|
||||
return cat(info$pathname, "/", info$basename);
|
||||
}
|
||||
|
||||
function print_attr(attr: fattr_t): string
|
||||
{
|
||||
return fmt("%s", attr);
|
||||
}
|
||||
|
||||
function map_conn(cid: conn_id): count
|
||||
{
|
||||
if (cid !in nfs_conns)
|
||||
nfs_conns[cid] = ++num_nfs_conns;
|
||||
return nfs_conns[cid];
|
||||
}
|
||||
|
||||
|
||||
function NFS_request(n: connection, req: string, addl: string)
|
||||
function is_success(info: info_t): bool
|
||||
{
|
||||
print log_file, fmt("%.06f %s NFS %s: %s",
|
||||
network_time(), id_string(n$id), req, addl);
|
||||
return (info$rpc_stat == RPC_SUCCESS && info$nfs_stat == NFS3ERR_OK);
|
||||
}
|
||||
|
||||
function NFS_attempt(n: connection, req: string, status: count, addl: string)
|
||||
function is_rpc_success(info: info_t): bool
|
||||
{
|
||||
print log_file, fmt("%.06f %s NFS attempt %s (%d): %s",
|
||||
network_time(), id_string(n$id), req, status, addl);
|
||||
return (info$rpc_stat == RPC_SUCCESS);
|
||||
}
|
||||
|
||||
function nfs_get_log_prefix(c: connection, info: info_t, proc: string): string
|
||||
{
|
||||
local nfs_stat_str = (info$rpc_stat == RPC_SUCCESS) ? fmt("%s", info$nfs_stat) : "X";
|
||||
return fmt("%.06f %.06f %d %.06f %.06f %d %s %s %d %s %s %s",
|
||||
info$req_start, info$req_dur, info$req_len,
|
||||
info$rep_start, info$rep_dur, info$rep_len,
|
||||
id_string(c$id), get_port_transport_proto(c$id$orig_p),
|
||||
map_conn(c$id),
|
||||
proc, info$rpc_stat, nfs_stat_str);
|
||||
}
|
||||
|
||||
|
||||
event nfs_request_null(n: connection)
|
||||
event nfs_proc_not_implemented(c: connection, info: info_t, proc: proc_t)
|
||||
{
|
||||
NFS_request(n, "null", "");
|
||||
local prefix = nfs_get_log_prefix(c, info, fmt("%s", proc));
|
||||
|
||||
print log_file, fmt("%s Not_implemented", prefix);
|
||||
}
|
||||
|
||||
event nfs_attempt_null(n: connection, status: count)
|
||||
event nfs_proc_null(c: connection, info: info_t)
|
||||
{
|
||||
NFS_attempt(n, "null", status, "");
|
||||
local prefix = nfs_get_log_prefix(c, info, "null");
|
||||
|
||||
print log_file, prefix;
|
||||
}
|
||||
|
||||
|
||||
event nfs_request_getattr(n: connection, fh: string, attrs: nfs3_attrs)
|
||||
event nfs_proc_getattr (c: connection, info: info_t, fh: string, attrs: fattr_t)
|
||||
{
|
||||
NFS_request(n, "getattr", fmt("%s -> %s", map_fh(fh), attrs));
|
||||
local prefix = nfs_get_log_prefix(c, info, "getattr");
|
||||
|
||||
if (is_success(info))
|
||||
log_attributes(c, "getattr", fh, attrs);
|
||||
|
||||
print log_file, fmt("%s %s", prefix, get_fh_id(c,fh));
|
||||
}
|
||||
|
||||
event nfs_attempt_getattr(n: connection, status: count, fh: string)
|
||||
event nfs_proc_lookup(c: connection, info: info_t, req: diropargs_t, rep: lookup_reply_t)
|
||||
{
|
||||
NFS_attempt(n, "getattr", status, map_fh(fh));
|
||||
local prefix = nfs_get_log_prefix(c, info, "lookup");
|
||||
|
||||
if (! is_success(info) )
|
||||
{
|
||||
print log_file, fmt("%s %s + %s", prefix, get_fh_id(c, req$dirfh), req$fname);
|
||||
# could print dir_attr, if they are set ....
|
||||
return;
|
||||
}
|
||||
if (rep?$dir_attr)
|
||||
log_attributes(c, "lookup", req$dirfh, rep$dir_attr);
|
||||
if (is_rpc_success(info) && rep?$obj_attr)
|
||||
log_attributes(c, "lookup", rep$fh, rep$obj_attr);
|
||||
add_update_fh(c, "lookup", req$dirfh, req$fname, rep$fh);
|
||||
print log_file, fmt("%s %s + %s => %s", prefix, get_fh_id(c, req$dirfh), req$fname, get_fh_id(c, rep$fh));
|
||||
|
||||
}
|
||||
|
||||
|
||||
function opt_attr_fmt(a: nfs3_opt_attrs): string
|
||||
event nfs_proc_read(c: connection, info: info_t, req: readargs_t, rep: read_reply_t)
|
||||
{
|
||||
return a?$attrs ? fmt("%s", a$attrs) : "<missing>";
|
||||
local msg = nfs_get_log_prefix(c, info, "read");
|
||||
|
||||
msg = fmt("%s %s @%d: %d", msg, get_fh_id(c, req$fh), req$offset, req$size);
|
||||
if (is_success(info))
|
||||
{
|
||||
msg = fmt("%s got %d bytes %s %s", msg, rep$size, (rep$eof) ? "<eof>" : "x",
|
||||
get_fh_chaintime_str(c, req$fh));
|
||||
if (rep?$data && req$offset==0 && rep$size>0)
|
||||
set_fh_mimetype(c, req$fh, "read", rep$data);
|
||||
if (is_rpc_success(info) && rep?$attr)
|
||||
log_attributes(c, "read", req$fh, rep$attr);
|
||||
}
|
||||
|
||||
event nfs_request_lookup(n: connection, req: nfs3_lookup_args, rep: nfs3_lookup_reply)
|
||||
{
|
||||
NFS_request(n, "lookup", fmt("%s -> %s (file-attr: %s, dir-attr: %s)",
|
||||
req, rep$fh,
|
||||
opt_attr_fmt(rep$file_attr),
|
||||
opt_attr_fmt(rep$dir_attr)));
|
||||
print log_file, msg;
|
||||
}
|
||||
|
||||
event nfs_attempt_lookup(n: connection, status: count, req: nfs3_lookup_args)
|
||||
event nfs_proc_readlink(c: connection, info: info_t, fh: string, rep: readlink_reply_t)
|
||||
{
|
||||
NFS_attempt(n, "lookup", status, fmt("%s", req));
|
||||
local msg = nfs_get_log_prefix(c, info, "readlink");
|
||||
|
||||
msg = fmt("%s %s", msg, get_fh_id(c, fh));
|
||||
if (is_success(info))
|
||||
{
|
||||
msg = fmt("%s : %s", msg, rep$nfspath);
|
||||
if (rep?$attr)
|
||||
log_attributes(c, "readlink", fh, rep$attr);
|
||||
}
|
||||
|
||||
|
||||
event nfs_request_fsstat(n: connection, root_fh: string, stat: nfs3_fsstat)
|
||||
{
|
||||
NFS_request(n, "fsstat", fmt("%s -> attr: %s, tbytes: %s, fbytes: %s, abytes: %s, tfiles: %s, ffiles: %s, afiles: %s, invarsec: %s",
|
||||
map_fh(root_fh),
|
||||
opt_attr_fmt(stat$attrs),
|
||||
stat$tbytes, stat$fbytes, stat$abytes,
|
||||
stat$tfiles, stat$ffiles, stat$afiles,
|
||||
stat$invarsec));
|
||||
print log_file, msg;
|
||||
}
|
||||
|
||||
event nfs_attempt_fsstat(n: connection, status: count, root_fh: string)
|
||||
event nfs_proc_write(c: connection, info: info_t, req: writeargs_t, rep: write_reply_t)
|
||||
{
|
||||
NFS_attempt(n, "fsstat", status, map_fh(root_fh));
|
||||
local msg = nfs_get_log_prefix(c, info, "write");
|
||||
|
||||
msg = fmt("%s %s @%d: %d %s", msg, get_fh_id(c, req$fh), req$offset, req$size, req$stable);
|
||||
if (is_success(info))
|
||||
{
|
||||
msg = fmt("%s wrote %d bytes %s %s", msg, rep$size, rep$commited,
|
||||
get_fh_chaintime_str(c, req$fh));
|
||||
if (req?$data && req$offset==0 && rep$size>0)
|
||||
set_fh_mimetype(c, req$fh, "write", req$data);
|
||||
if (rep?$postattr)
|
||||
log_attributes(c, "write", req$fh, rep$postattr);
|
||||
}
|
||||
|
||||
print log_file, msg;
|
||||
}
|
||||
|
||||
function nfs_newobj(c: connection, info: info_t, proc: string, req: diropargs_t, rep: newobj_reply_t)
|
||||
{
|
||||
local prefix = nfs_get_log_prefix(c, info, proc);
|
||||
local newfh_str: string;
|
||||
if (! is_success(info) )
|
||||
{
|
||||
print log_file, fmt("%s %s + %s", prefix, get_fh_id(c, req$dirfh), req$fname);
|
||||
# could print dir_attr, if they are set ....
|
||||
return;
|
||||
}
|
||||
if (is_rpc_success(info) && rep?$dir_post_attr)
|
||||
log_attributes(c, proc, req$dirfh, rep$dir_post_attr);
|
||||
# TODO: could print dir_pre_attr
|
||||
if (is_rpc_success(info) && rep?$obj_attr)
|
||||
log_attributes(c, proc, rep$fh, rep$obj_attr);
|
||||
add_update_fh(c, proc, req$dirfh, req$fname, rep$fh);
|
||||
|
||||
newfh_str = (rep?$fh) ? get_fh_id(c, rep$fh) : "FH??";
|
||||
print log_file, fmt("%s %s + %s => %s", prefix, get_fh_id(c, req$dirfh), req$fname, get_fh_id(c, rep$fh));
|
||||
}
|
||||
|
||||
event nfs_proc_create(c: connection, info: info_t, req: diropargs_t, rep: newobj_reply_t)
|
||||
{
|
||||
# TODO: create request attributes not implemented in core
|
||||
nfs_newobj(c, info, "create", req, rep);
|
||||
}
|
||||
|
||||
event nfs_proc_mkdir(c: connection, info: info_t, req: diropargs_t, rep: newobj_reply_t)
|
||||
{
|
||||
# TODO: mkidir request attributes not implemented in core
|
||||
nfs_newobj(c, info, "mkdir", req, rep);
|
||||
}
|
||||
|
||||
function nfs_delobj(c: connection, info: info_t, proc: string, req: diropargs_t, rep: delobj_reply_t)
|
||||
{
|
||||
local prefix = nfs_get_log_prefix(c, info, proc);
|
||||
print log_file, fmt("%s %s - %s", prefix, get_fh_id(c, req$dirfh), req$fname);
|
||||
if (is_rpc_success(info) && rep?$dir_post_attr)
|
||||
log_attributes(c, proc, req$dirfh, rep$dir_post_attr);
|
||||
# TODO: could print dir_pre_attr
|
||||
}
|
||||
|
||||
event nfs_proc_remove(c: connection, info: info_t, req: diropargs_t, rep: delobj_reply_t)
|
||||
{
|
||||
nfs_delobj(c, info, "remove", req, rep);
|
||||
}
|
||||
|
||||
event nfs_proc_rmdir(c: connection, info: info_t, req: diropargs_t, rep: delobj_reply_t)
|
||||
{
|
||||
nfs_delobj(c, info, "rmdir", req, rep);
|
||||
}
|
||||
|
||||
function fmt_direntry(c: connection, e: direntry_t): string
|
||||
{
|
||||
local rv = "";
|
||||
rv = fmt("%d %s %d", e$fileid, e$fname, e$cookie);
|
||||
if (e?$fh)
|
||||
rv = fmt("%s %s", rv, get_fh_id(c, e$fh));
|
||||
return rv;
|
||||
|
||||
}
|
||||
|
||||
event nfs_proc_readdir(c: connection, info: info_t, req: readdirargs_t, rep: readdir_reply_t)
|
||||
{
|
||||
local isplus = req$isplus;
|
||||
local proc = (isplus) ? "readdirplus" : "readdir";
|
||||
local msg = nfs_get_log_prefix(c, info, proc);
|
||||
msg = fmt("%s %s @%d (%x)", msg, get_fh_id(c, req$dirfh), req$cookie, req$cookieverf);
|
||||
if (is_success(info))
|
||||
{
|
||||
msg = fmt("%s %d entries %d", msg, |rep$entries|, rep$eof);
|
||||
print readdir_log, msg;
|
||||
for (i in rep$entries)
|
||||
{
|
||||
local curentry = rep$entries[i];
|
||||
if (curentry?$attr && curentry?$fh)
|
||||
log_attributes(c, proc, curentry$fh, curentry$attr);
|
||||
if (curentry?$fh)
|
||||
add_update_fh(c, proc, req$dirfh, curentry$fname, curentry$fh);
|
||||
print readdir_log,fmt(" %s", fmt_direntry(c, curentry));
|
||||
}
|
||||
if (rep?$dir_attr)
|
||||
log_attributes(c, proc, req$dirfh, rep$dir_attr);
|
||||
}
|
||||
else if (is_rpc_success(info) && rep?$dir_attr)
|
||||
{
|
||||
log_attributes(c, proc, req$dirfh, rep$dir_attr);
|
||||
}
|
||||
print log_file, msg;
|
||||
}
|
||||
|
||||
event connection_state_remove(c: connection)
|
||||
{
|
||||
if ( c$id !in nfs_conns )
|
||||
return;
|
||||
delete nfs_conns[c$id];
|
||||
}
|
||||
|
|
|
@ -118,8 +118,8 @@ export {
|
|||
|
||||
# Indexed by the portmapper request and a boolean that's T if
|
||||
# the request was answered, F it was attempted but not answered.
|
||||
# If there's an entry in the set, then the access won't be logged
|
||||
# (unless the connection is hot for some other reason).
|
||||
# If there's an entry in the set, then the access won't lead to a
|
||||
# NOTICE (unless the connection is hot for some other reason).
|
||||
const RPC_do_not_complain: set[string, bool] = {
|
||||
["pm_null", [T, F]],
|
||||
} &redef;
|
||||
|
@ -133,24 +133,31 @@ export {
|
|||
[NFS_world_servers, NFS_services],
|
||||
[sun-rpc.mcast.net, "ypserv"], # sigh
|
||||
} &redef;
|
||||
|
||||
# Logs all portmapper activity as readable "messages"
|
||||
# Format: timestamp orig_p resp_h resp_p proto localInit PortmapProcedure success details
|
||||
const log_file = open_log_file("portmapper") &redef;
|
||||
# Logs all portmapper mappings that we observe (i.e., getport and
|
||||
# dump replies. Format:
|
||||
# timestamp orig_h orig_p resp_h resp_p proto localInit PortmapProcedure RPCprogram version port proto
|
||||
# the mapping is then: <resp_h> accepts <RPCprogram> with <version>
|
||||
# calls on <port> <proto>. We learned this mapping via <PortmapProcedure>
|
||||
const mapping_log_file = open_log_file("portmapper-maps") &redef;
|
||||
}
|
||||
|
||||
redef capture_filters += { ["portmapper"] = "port 111" };
|
||||
|
||||
const portmapper_ports = { 111/tcp } &redef;
|
||||
const portmapper_ports = { 111/tcp, 111/udp } &redef;
|
||||
redef dpd_config += { [ANALYZER_PORTMAPPER] = [$ports = portmapper_ports] };
|
||||
|
||||
const portmapper_binpac_ports = { 111/udp } &redef;
|
||||
redef dpd_config += { [ANALYZER_RPC_UDP_BINPAC] = [$ports = portmapper_binpac_ports] };
|
||||
|
||||
# Indexed by source and destination addresses, plus the portmapper service.
|
||||
# If the tuple is in the set, then we already logged it and shouldn't do
|
||||
# so again.
|
||||
global did_pm_log: set[addr, addr, string];
|
||||
# If the tuple is in the set, then we already created a NOTICE for it and
|
||||
# shouldn't do so again.
|
||||
global did_pm_notice: set[addr, addr, string];
|
||||
|
||||
# Indexed by source and portmapper service. If set, we already logged
|
||||
# and shouldn't do so again.
|
||||
global suppress_pm_log: set[addr, string];
|
||||
# Indexed by source and portmapper service. If set, we already created
|
||||
# a notice and shouldn't do so again.
|
||||
global suppress_pm_notice: set[addr, string];
|
||||
|
||||
|
||||
function RPC_weird_action_filter(c: connection): Weird::WeirdAction
|
||||
|
@ -166,6 +173,7 @@ redef Weird::weird_action_filters += {
|
|||
RPC_weird_action_filter,
|
||||
};
|
||||
|
||||
|
||||
function rpc_prog(p: count): string
|
||||
{
|
||||
if ( p in rpc_programs )
|
||||
|
@ -174,6 +182,56 @@ function rpc_prog(p: count): string
|
|||
return fmt("unknown-%d", p);
|
||||
}
|
||||
|
||||
|
||||
function pm_get_conn_string(cid: conn_id) : string
|
||||
{
|
||||
return fmt("%s %d %s %d %s %s",
|
||||
cid$orig_h, cid$orig_p,
|
||||
cid$resp_h, cid$resp_p,
|
||||
get_port_transport_proto(cid$resp_p),
|
||||
is_local_addr(cid$orig_h) ? "L" : "X"
|
||||
);
|
||||
}
|
||||
|
||||
# Log a pm_request or pm_attempt to the log file
|
||||
function pm_log(r: connection, proc: string, msg: string, success: bool)
|
||||
{
|
||||
print log_file, fmt("%f %s %s %s %s", network_time(),
|
||||
pm_get_conn_string(r$id),
|
||||
proc, success, msg);
|
||||
}
|
||||
|
||||
# Log portmapper mappings received from a dump procedure
|
||||
function pm_log_mapping_dump(r: connection, m: pm_mappings)
|
||||
{
|
||||
# TODO: sort by program and version
|
||||
for ( mp in m )
|
||||
{
|
||||
local prog = rpc_prog(m[mp]$program);
|
||||
local ver = m[mp]$version;
|
||||
local p = m[mp]$p;
|
||||
|
||||
print mapping_log_file, fmt("%f %s pm_dump %s %d %d %s", network_time(),
|
||||
pm_get_conn_string(r$id),
|
||||
prog, ver, p, get_port_transport_proto(p));
|
||||
}
|
||||
}
|
||||
|
||||
# Log portmapper mappings received from a getport procedure
|
||||
# Unfortunately, pm_request_getport doesn't return pm_mapping,
|
||||
# but returns the parameters separately ....
|
||||
function pm_log_mapping_getport(r: connection, pr: pm_port_request, p: port)
|
||||
{
|
||||
local prog = rpc_prog(pr$program);
|
||||
local ver = pr$version;
|
||||
|
||||
print mapping_log_file, fmt("%f %s pm_getport %s %d %d %s", network_time(),
|
||||
pm_get_conn_string(r$id),
|
||||
prog, ver, p, get_port_transport_proto(p));
|
||||
}
|
||||
|
||||
|
||||
|
||||
function pm_check_getport(r: connection, prog: string): bool
|
||||
{
|
||||
if ( prog in RPC_okay_services ||
|
||||
|
@ -187,25 +245,25 @@ function pm_check_getport(r: connection, prog: string): bool
|
|||
return T;
|
||||
}
|
||||
|
||||
function pm_activity(r: connection, log_it: bool, proc: string)
|
||||
function pm_activity(r: connection, do_notice: bool, proc: string)
|
||||
{
|
||||
local id = r$id;
|
||||
|
||||
if ( log_it &&
|
||||
[id$orig_h, id$resp_h, proc] !in did_pm_log &&
|
||||
[id$orig_h, proc] !in suppress_pm_log )
|
||||
if ( do_notice &&
|
||||
[id$orig_h, id$resp_h, proc] !in did_pm_notice &&
|
||||
[id$orig_h, proc] !in suppress_pm_notice )
|
||||
{
|
||||
NOTICE([$note=SensitivePortmapperAccess, $conn=r,
|
||||
$msg=fmt("rpc: %s %s: %s",
|
||||
id_string(r$id), proc, r$addl)]);
|
||||
add did_pm_log[id$orig_h, id$resp_h, proc];
|
||||
add did_pm_notice[id$orig_h, id$resp_h, proc];
|
||||
}
|
||||
}
|
||||
|
||||
function pm_request(r: connection, proc: string, addl: string, log_it: bool)
|
||||
function pm_request(r: connection, proc: string, addl: string, do_notice: bool)
|
||||
{
|
||||
if ( [proc, T] in RPC_do_not_complain )
|
||||
log_it = F;
|
||||
do_notice = F;
|
||||
|
||||
if ( ! is_tcp_port(r$id$orig_p) )
|
||||
{
|
||||
|
@ -235,7 +293,8 @@ function pm_request(r: connection, proc: string, addl: string, log_it: bool)
|
|||
|
||||
add r$service[proc];
|
||||
Hot::check_hot(r, Hot::CONN_FINISHED);
|
||||
pm_activity(r, log_it || r$hot > 0, proc);
|
||||
pm_activity(r, do_notice || r$hot > 0, proc);
|
||||
pm_log(r, proc, addl, T);
|
||||
}
|
||||
|
||||
|
||||
|
@ -273,13 +332,16 @@ function update_RPC_server_map(server: addr, p: port, prog: string)
|
|||
event pm_request_getport(r: connection, pr: pm_port_request, p: port)
|
||||
{
|
||||
local prog = rpc_prog(pr$program);
|
||||
local log_it = pm_check_getport(r, prog);
|
||||
local do_notice = pm_check_getport(r, prog);
|
||||
|
||||
update_RPC_server_map(r$id$resp_h, p, prog);
|
||||
|
||||
pm_request(r, "pm_getport", fmt("%s -> %s", prog, p), log_it);
|
||||
pm_request(r, "pm_getport", fmt("%s -> %s", prog, p), do_notice);
|
||||
pm_log_mapping_getport(r, pr, p);
|
||||
}
|
||||
|
||||
# Note, this function has the side effect of updating the
|
||||
# RPC_server_map
|
||||
function pm_mapping_to_text(server: addr, m: pm_mappings): string
|
||||
{
|
||||
# Used to suppress multiple entries for multiple versions.
|
||||
|
@ -313,30 +375,33 @@ function pm_mapping_to_text(server: addr, m: pm_mappings): string
|
|||
|
||||
event pm_request_dump(r: connection, m: pm_mappings)
|
||||
{
|
||||
local log_it = [r$id$orig_h, r$id$resp_h] !in RPC_dump_okay;
|
||||
pm_request(r, "pm_dump", length(m) == 0 ? "(nil)" : "(done)", log_it);
|
||||
append_addl(r, cat("<", pm_mapping_to_text(r$id$resp_h, m), ">"));
|
||||
local do_notice = [r$id$orig_h, r$id$resp_h] !in RPC_dump_okay;
|
||||
# pm_mapping_to_text has the side-effect of updating RPC_server_map
|
||||
pm_request(r, "pm_dump",
|
||||
length(m) == 0 ? "(nil)" : pm_mapping_to_text(r$id$resp_h, m),
|
||||
do_notice);
|
||||
pm_log_mapping_dump(r, m);
|
||||
}
|
||||
|
||||
event pm_request_callit(r: connection, call: pm_callit_request, p: port)
|
||||
{
|
||||
local orig_h = r$id$orig_h;
|
||||
local prog = rpc_prog(call$program);
|
||||
local log_it = [orig_h, prog] !in suppress_pm_log;
|
||||
local do_notice = [orig_h, prog] !in suppress_pm_notice;
|
||||
|
||||
pm_request(r, "pm_callit", fmt("%s/%d (%d bytes) -> %s",
|
||||
prog, call$proc, call$arg_size, p), log_it);
|
||||
prog, call$proc, call$arg_size, p), do_notice);
|
||||
|
||||
if ( prog == "walld" )
|
||||
add suppress_pm_log[orig_h, prog];
|
||||
add suppress_pm_notice[orig_h, prog];
|
||||
}
|
||||
|
||||
|
||||
function pm_attempt(r: connection, proc: string, status: rpc_status,
|
||||
addl: string, log_it: bool)
|
||||
addl: string, do_notice: bool)
|
||||
{
|
||||
if ( [proc, F] in RPC_do_not_complain )
|
||||
log_it = F;
|
||||
do_notice = F;
|
||||
|
||||
if ( ! is_tcp_port(r$id$orig_p) )
|
||||
{
|
||||
|
@ -351,6 +416,7 @@ function pm_attempt(r: connection, proc: string, status: rpc_status,
|
|||
|
||||
# Current policy is ignore any failed attempts.
|
||||
pm_activity(r, F, proc);
|
||||
pm_log(r, proc, addl, F);
|
||||
}
|
||||
|
||||
event pm_attempt_null(r: connection, status: rpc_status)
|
||||
|
@ -371,14 +437,14 @@ event pm_attempt_unset(r: connection, status: rpc_status, m: pm_mapping)
|
|||
event pm_attempt_getport(r: connection, status: rpc_status, pr: pm_port_request)
|
||||
{
|
||||
local prog = rpc_prog(pr$program);
|
||||
local log_it = pm_check_getport(r, prog);
|
||||
pm_attempt(r, "pm_getport", status, prog, log_it);
|
||||
local do_notice = pm_check_getport(r, prog);
|
||||
pm_attempt(r, "pm_getport", status, prog, do_notice);
|
||||
}
|
||||
|
||||
event pm_attempt_dump(r: connection, status: rpc_status)
|
||||
{
|
||||
local log_it = [r$id$orig_h, r$id$resp_h] !in RPC_dump_okay;
|
||||
pm_attempt(r, "pm_dump", status, "", log_it);
|
||||
local do_notice = [r$id$orig_h, r$id$resp_h] !in RPC_dump_okay;
|
||||
pm_attempt(r, "pm_dump", status, "", do_notice);
|
||||
}
|
||||
|
||||
event pm_attempt_callit(r: connection, status: rpc_status,
|
||||
|
@ -386,14 +452,14 @@ event pm_attempt_callit(r: connection, status: rpc_status,
|
|||
{
|
||||
local orig_h = r$id$orig_h;
|
||||
local prog = rpc_prog(call$program);
|
||||
local log_it = [orig_h, prog] !in suppress_pm_log;
|
||||
local do_notice = [orig_h, prog] !in suppress_pm_notice;
|
||||
|
||||
pm_attempt(r, "pm_callit", status,
|
||||
fmt("%s/%d (%d bytes)", prog, call$proc, call$arg_size),
|
||||
log_it);
|
||||
do_notice);
|
||||
|
||||
if ( prog == "walld" )
|
||||
add suppress_pm_log[orig_h, prog];
|
||||
add suppress_pm_notice[orig_h, prog];
|
||||
}
|
||||
|
||||
event pm_bad_port(r: connection, bad_p: count)
|
||||
|
|
188
policy/bro.init
188
policy/bro.init
|
@ -711,45 +711,176 @@ const RPC_status = {
|
|||
[RPC_UNKNOWN_ERROR] = "unknown"
|
||||
};
|
||||
|
||||
module NFS3;
|
||||
|
||||
type nfs3_file_type: enum {
|
||||
NFS3_REG, NFS3_DIR, NFS3_BLK, NFS3_CHR, NFS3_LNK, NFS3_SOCK, NFS3_FIFO,
|
||||
};
|
||||
export {
|
||||
# Should the read and write events return the file data that has been
|
||||
# read/written?
|
||||
const return_data = F &redef;
|
||||
|
||||
type nfs3_attrs: record {
|
||||
ftype: nfs3_file_type;
|
||||
# If nfs_return_data is true, how much data should be returned at most.
|
||||
const return_data_max = 512 &redef;
|
||||
|
||||
# If nfs_return_data is true, whether to *only* return data if the read or write
|
||||
# offset is 0, i.e., only return data for the beginning of the file.
|
||||
const return_data_first_only = T &redef;
|
||||
|
||||
# This record summarizes the general results and status of NFSv3 request/reply
|
||||
# pairs. It's part of every NFSv3 event.
|
||||
type info_t: record {
|
||||
rpc_stat: rpc_status; # If this indicates not successful, the reply record in the
|
||||
# events will be empty and contain uninitialized fields, so
|
||||
# don't use it.
|
||||
nfs_stat: status_t;
|
||||
|
||||
# The start time, duration, and length in bytes of the request (call). Note that
|
||||
# the start and end time might not be accurate. For TCP, we record the
|
||||
# time when a chunk of data is delivered to the analyzer. Depending on the
|
||||
# Reassembler, this might be well after the first packet of the request
|
||||
# was received.
|
||||
req_start: time;
|
||||
req_dur: interval;
|
||||
req_len: count;
|
||||
|
||||
# Same for the reply.
|
||||
rep_start: time;
|
||||
rep_dur: interval;
|
||||
rep_len: count;
|
||||
};
|
||||
|
||||
# NFSv3 types. Type names are based on RFC 1813.
|
||||
type fattr_t: record {
|
||||
ftype: file_type_t;
|
||||
mode: count;
|
||||
nlink: count;
|
||||
uid: count;
|
||||
gid: count;
|
||||
size: double;
|
||||
used: double;
|
||||
size: count;
|
||||
used: count;
|
||||
rdev1: count;
|
||||
rdev2: count;
|
||||
fsid: double;
|
||||
fileid: double;
|
||||
fsid: count;
|
||||
fileid: count;
|
||||
atime: time;
|
||||
mtime: time;
|
||||
ctime: time;
|
||||
};
|
||||
};
|
||||
|
||||
type nfs3_opt_attrs: record {
|
||||
attrs: nfs3_attrs &optional;
|
||||
};
|
||||
type diropargs_t : record {
|
||||
dirfh: string; # the file handle of the directory
|
||||
fname: string; # the name of the file we are interested in
|
||||
};
|
||||
|
||||
type nfs3_lookup_args: record {
|
||||
fh: string; # file handle of directory in which to search
|
||||
name: string; # name of file to look for in directory
|
||||
};
|
||||
# Note, we don't need a "post_op_attr" type. We use an "fattr_t &optional"
|
||||
# instead.
|
||||
|
||||
type nfs3_lookup_reply: record {
|
||||
fh: string; # file handle of object looked up
|
||||
file_attr: nfs3_opt_attrs; # optional attributes associated w/ file
|
||||
dir_attr: nfs3_opt_attrs; # optional attributes associated w/ dir.
|
||||
};
|
||||
type lookup_reply_t: record {
|
||||
# If the lookup failed, dir_attr may be set.
|
||||
# If the lookup succeeded, fh is always set and obj_attr and dir_attr may be set.
|
||||
fh: string &optional; # file handle of object looked up
|
||||
obj_attr: fattr_t &optional; # optional attributes associated w/ file
|
||||
dir_attr: fattr_t &optional; # optional attributes associated w/ dir.
|
||||
};
|
||||
|
||||
type nfs3_fsstat: record {
|
||||
attrs: nfs3_opt_attrs;
|
||||
type readargs_t: record {
|
||||
fh: string; # file handle to read from
|
||||
offset: count; # offset in file
|
||||
size: count; # number of bytes to read
|
||||
};
|
||||
|
||||
type read_reply_t: record {
|
||||
# If the lookup fails, attr may be set. If the lookup succeeds, attr may be set
|
||||
# and all other fields are set.
|
||||
attr: fattr_t &optional; # attributes
|
||||
size: count &optional; # number of bytes read
|
||||
eof: bool &optional; # did the read end at EOF
|
||||
data: string &optional; # the actual data; not yet implemented.
|
||||
};
|
||||
|
||||
type readlink_reply_t: record {
|
||||
# If the request fails, attr may be set. If the request succeeds, attr may be
|
||||
# set and all other fields are set.
|
||||
attr: fattr_t &optional; # attributes
|
||||
nfspath: string &optional; # the contents of the symlink; in general a pathname as text
|
||||
};
|
||||
|
||||
type writeargs_t: record {
|
||||
fh: string; # file handle to write to
|
||||
offset: count; # offset in file
|
||||
size: count; # number of bytes to write
|
||||
stable: stable_how_t; # how and when data is commited
|
||||
data: string &optional; # the actual data; not implemented yet
|
||||
};
|
||||
|
||||
type wcc_attr_t: record {
|
||||
size: count;
|
||||
atime: time;
|
||||
mtime: time;
|
||||
};
|
||||
|
||||
type write_reply_t: record {
|
||||
# If the request fails, pre|post attr may be set. If the request succeeds,
|
||||
# pre|post attr may be set and all other fields are set.
|
||||
preattr: wcc_attr_t &optional; # pre operation attributes
|
||||
postattr: fattr_t &optional; # post operation attributes
|
||||
size: count &optional;
|
||||
commited: stable_how_t &optional;
|
||||
verf: count &optional; # write verifier cookue
|
||||
};
|
||||
|
||||
# reply for create, mkdir, symlink
|
||||
type newobj_reply_t: record {
|
||||
# If the proc failed, dir_*_attr may be set. If the proc succeeded, fh and
|
||||
# the attr's may be set. Note: no guarantee that fh is set after
|
||||
# success.
|
||||
fh: string &optional; # file handle of object created
|
||||
obj_attr: fattr_t &optional; # optional attributes associated w/ new object
|
||||
dir_pre_attr: wcc_attr_t &optional; # optional attributes associated w/ dir
|
||||
dir_post_attr: fattr_t &optional; # optional attributes associated w/ dir
|
||||
};
|
||||
|
||||
# reply for remove, rmdir
|
||||
# Corresponds to "wcc_data" in the spec.
|
||||
type delobj_reply_t: record {
|
||||
dir_pre_attr: wcc_attr_t &optional; # optional attributes associated w/ dir
|
||||
dir_post_attr: fattr_t &optional; # optional attributes associated w/ dir
|
||||
};
|
||||
|
||||
# This record is used for both readdir and readdirplus.
|
||||
type readdirargs_t: record {
|
||||
isplus: bool; # is this a readdirplus request?
|
||||
dirfh: string; # the directory filehandle
|
||||
cookie: count; # cookie / pos in dir; 0 for first call
|
||||
cookieverf: count; # the cookie verifier
|
||||
dircount: count; # "count" field for readdir; maxcount otherwise (in bytes)
|
||||
maxcount: count &optional; # only used for readdirplus. in bytes
|
||||
};
|
||||
|
||||
type direntry_t: record {
|
||||
# fh and attr are used for readdirplus. However, even for readdirplus they may
|
||||
# not be filled out.
|
||||
fileid: count; # e.g., inode number
|
||||
fname: string; # filename
|
||||
cookie: count;
|
||||
attr: fattr_t &optional; # readdirplus: the FH attributes for the entry
|
||||
fh: string &optional; # readdirplus: the FH for the entry
|
||||
};
|
||||
|
||||
type direntry_vec_t: vector of direntry_t;
|
||||
|
||||
# Used for readdir and readdirplus.
|
||||
type readdir_reply_t: record {
|
||||
# If error: dir_attr might be set. If success: dir_attr may be set, all others
|
||||
# must be set.
|
||||
isplus: bool; # is the reply for a readdirplus request
|
||||
dir_attr: fattr_t &optional;
|
||||
cookieverf: count &optional;
|
||||
entries: direntry_vec_t &optional;
|
||||
eof: bool; # if true, no more entries in dir.
|
||||
};
|
||||
|
||||
type fsstat_t: record {
|
||||
attrs: fattr_t &optional;
|
||||
tbytes: double;
|
||||
fbytes: double;
|
||||
abytes: double;
|
||||
|
@ -757,8 +888,10 @@ type nfs3_fsstat: record {
|
|||
ffiles: double;
|
||||
afiles: double;
|
||||
invarsec: interval;
|
||||
};
|
||||
};
|
||||
} # end export
|
||||
|
||||
module GLOBAL;
|
||||
|
||||
type ntp_msg: record {
|
||||
id: count;
|
||||
|
@ -1245,6 +1378,11 @@ global load_sample_freq = 20 &redef;
|
|||
# degree the measurement process appears to exhibit loss.
|
||||
const gap_report_freq = 1.0 sec &redef;
|
||||
|
||||
# Whether we want content_gap and drop reports for partial connections
|
||||
# (a connection is partial if it is missing a full handshake). Note that
|
||||
# gap reports for partial connections might not be reliable.
|
||||
const report_gaps_for_partial = F &redef;
|
||||
|
||||
# Globals associated with entire-run statistics on gaps (useful
|
||||
# for final summaries).
|
||||
|
||||
|
|
147
policy/rpc.bro
Normal file
147
policy/rpc.bro
Normal file
|
@ -0,0 +1,147 @@
|
|||
|
||||
#
|
||||
# Log RPC request and reply messages. Does not in itself start/activate
|
||||
# an analyzer. You need to load portmap and/or NFS for that
|
||||
#
|
||||
# TODO: maybe automatically load portmap, add a generic RPC analyzer and
|
||||
# use expect connection, so that we can see RPC request/replies for RPC
|
||||
# programs for which we don't have an analyzer.
|
||||
#
|
||||
|
||||
module RPC;
|
||||
|
||||
export {
|
||||
global log_file = open_log_file("rpc") &redef;
|
||||
# whether to match request to replies on the policy layer.
|
||||
# (will report on rexmit and missing requests or replies)
|
||||
global track_requests_replies = T &redef;
|
||||
}
|
||||
|
||||
|
||||
type rpc_call_state: enum {
|
||||
NONE,
|
||||
HAVE_CALL,
|
||||
HAVE_REPLY
|
||||
};
|
||||
|
||||
type rpc_call_info: record {
|
||||
state: rpc_call_state;
|
||||
calltime: time;
|
||||
cid: conn_id;
|
||||
};
|
||||
|
||||
function new_call(cid: conn_id): rpc_call_info
|
||||
{
|
||||
local ci: rpc_call_info;
|
||||
|
||||
ci$state = NONE;
|
||||
ci$calltime = network_time();
|
||||
ci$cid = cid;
|
||||
return ci;
|
||||
}
|
||||
|
||||
function rpc_expire_xid(t: table[count] of rpc_call_info, xid: count): interval
|
||||
{
|
||||
local ci = t[xid];
|
||||
if (ci$state != HAVE_REPLY)
|
||||
print log_file, fmt("%.6f %s %s note XID %d never recevied a reply",
|
||||
ci$calltime, id_string(ci$cid),
|
||||
get_port_transport_proto(ci$cid$orig_p), xid);
|
||||
return 0 sec;
|
||||
}
|
||||
|
||||
function new_xid_table(): table[count] of rpc_call_info
|
||||
{
|
||||
local inner: table[count] of rpc_call_info &write_expire=rpc_timeout &expire_func=rpc_expire_xid;
|
||||
return inner;
|
||||
}
|
||||
|
||||
|
||||
# Match requests to replies.
|
||||
# The analyzer does this indepently and might differ in timeouts and
|
||||
# handling of xid reuse.
|
||||
# FIXME: add timeouts. Note, we do clean up on connection_state_remove
|
||||
global rpc_calls: table[conn_id] of table[count] of rpc_call_info;
|
||||
# &write_expire = rpc_timeout &expire_func=expire_rpc_call;
|
||||
|
||||
|
||||
event rpc_dialogue(c: connection, prog: count, ver: count, proc: count, status: rpc_status, start_time: time, call_len: count, reply_len: count)
|
||||
{
|
||||
# TODO: We currently do nothing here.
|
||||
# using the rpc_call and rpc_reply events, is all we need.
|
||||
}
|
||||
|
||||
event rpc_call(c: connection, xid: count, prog: count, ver: count, proc: count, call_len: count)
|
||||
{
|
||||
if (track_requests_replies)
|
||||
{
|
||||
if (c$id !in rpc_calls)
|
||||
rpc_calls[c$id] = new_xid_table();
|
||||
if (xid !in rpc_calls[c$id])
|
||||
rpc_calls[c$id][xid] = new_call(c$id);
|
||||
local curstate = rpc_calls[c$id][xid]$state;
|
||||
|
||||
if (curstate == HAVE_CALL)
|
||||
print log_file, fmt("%.6f %s %s note XID %d call retransmitted",
|
||||
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
|
||||
xid);
|
||||
else if (curstate == HAVE_REPLY)
|
||||
print log_file, fmt("%.6f %s %s note XID %d call received after reply",
|
||||
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
|
||||
xid);
|
||||
rpc_calls[c$id][xid]$state = HAVE_CALL;
|
||||
}
|
||||
|
||||
print log_file, fmt("%.6f %s %s rpc_call %d %d %d %d %d",
|
||||
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
|
||||
xid, prog, ver, proc, call_len);
|
||||
}
|
||||
|
||||
event rpc_reply(c: connection, xid: count, status: rpc_status, reply_len: count)
|
||||
{
|
||||
if (track_requests_replies)
|
||||
{
|
||||
if (c$id !in rpc_calls)
|
||||
rpc_calls[c$id] = new_xid_table();
|
||||
if (xid !in rpc_calls[c$id])
|
||||
{
|
||||
rpc_calls[c$id][xid] = new_call(c$id);
|
||||
# XXX: what to do about calltime in rpc_call_info??
|
||||
}
|
||||
if (rpc_calls[c$id][xid]$state == NONE)
|
||||
print log_file, fmt("%.6f %s %s note XID %d reply but call is missing",
|
||||
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
|
||||
xid);
|
||||
else if (rpc_calls[c$id][xid]$state == HAVE_REPLY)
|
||||
print log_file, fmt("%.6f %s %s note XID %d reply retransmitted",
|
||||
network_time(), id_string(c$id), get_port_transport_proto(c$id$orig_p),
|
||||
xid);
|
||||
rpc_calls[c$id][xid]$state = HAVE_REPLY;
|
||||
}
|
||||
|
||||
print log_file, fmt("%.6f %s %s rpc_reply %d %s %d",
|
||||
network_time(), reverse_id_string(c$id), get_port_transport_proto(c$id$orig_p),
|
||||
xid, status, reply_len);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function finish_calls(cid: conn_id)
|
||||
{
|
||||
for (xid in rpc_calls[cid])
|
||||
rpc_expire_xid(rpc_calls[cid], xid);
|
||||
}
|
||||
|
||||
event connection_state_remove(c: connection)
|
||||
{
|
||||
if (c$id !in rpc_calls)
|
||||
return;
|
||||
finish_calls(c$id);
|
||||
delete rpc_calls[c$id];
|
||||
}
|
||||
|
||||
event bro_done()
|
||||
{
|
||||
for (cid in rpc_calls)
|
||||
finish_calls(cid);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
// $Id: Analyzer.cc,v 1.1.4.28 2006/06/01 17:18:10 sommer Exp $
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Analyzer.h"
|
||||
#include "PIA.h"
|
||||
#include "Event.h"
|
||||
|
@ -132,9 +134,6 @@ const Analyzer::Config Analyzer::analyzer_configs[] = {
|
|||
{ AnalyzerTag::HTTP_BINPAC, "HTTP_BINPAC",
|
||||
HTTP_Analyzer_binpac::InstantiateAnalyzer,
|
||||
HTTP_Analyzer_binpac::Available, 0, false },
|
||||
{ AnalyzerTag::RPC_UDP_BINPAC, "RPC_UDP_BINPAC",
|
||||
RPC_UDP_Analyzer_binpac::InstantiateAnalyzer,
|
||||
RPC_UDP_Analyzer_binpac::Available, 0, false },
|
||||
{ AnalyzerTag::SSL, "SSL",
|
||||
SSL_Analyzer_binpac::InstantiateAnalyzer,
|
||||
SSL_Analyzer_binpac::Available, 0, false },
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace AnalyzerTag {
|
|||
|
||||
// Application-layer analyzers, binpac-generated.
|
||||
DHCP_BINPAC, DNS_TCP_BINPAC, DNS_UDP_BINPAC,
|
||||
HTTP_BINPAC, RPC_UDP_BINPAC, SSL, SYSLOG_BINPAC,
|
||||
HTTP_BINPAC, SSL, SYSLOG_BINPAC,
|
||||
|
||||
// Other
|
||||
File, Backdoor, InterConn, SteppingStone, TCPStats,
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include <sys/types.h>
|
||||
#include <regex.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
# define FMT_INT "%" PRId64
|
||||
# define FMT_UINT "%" PRIu64
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <algorithm>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "BroString.h"
|
||||
#include "Var.h"
|
||||
|
||||
|
|
|
@ -193,8 +193,6 @@ binpac_target(http.pac
|
|||
binpac_target(ncp.pac)
|
||||
binpac_target(netflow.pac
|
||||
netflow-protocol.pac netflow-analyzer.pac)
|
||||
binpac_target(rpc.pac
|
||||
rpc-analyzer.pac portmap-analyzer.pac)
|
||||
binpac_target(smb.pac
|
||||
smb-protocol.pac smb-pipe.pac smb-mailslot.pac)
|
||||
binpac_target(ssl.pac
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// $Id: ContentLine.cc,v 1.1.2.8 2006/06/01 01:55:42 sommer Exp $
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ContentLine.h"
|
||||
#include "TCP.h"
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
#endif
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "DNS_Mgr.h"
|
||||
#include "Event.h"
|
||||
#include "Net.h"
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "Net.h"
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "File.h"
|
||||
#include "Type.h"
|
||||
#include "Timer.h"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// $Id: FileAnalyzer.cc,v 1.1.4.2 2006/06/01 17:18:10 sommer Exp $
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "FileAnalyzer.h"
|
||||
|
||||
#ifdef HAVE_LIBMAGIC
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "NetVar.h"
|
||||
#include "HTTP.h"
|
||||
#include "Gnutella.h"
|
||||
#include "Event.h"
|
||||
#include "PIA.h"
|
||||
|
||||
|
||||
GnutellaMsgState::GnutellaMsgState()
|
||||
{
|
||||
buffer = "";
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "Net.h"
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "util.h"
|
||||
#include "IOSource.h"
|
||||
|
||||
|
|
697
src/NFS.cc
697
src/NFS.cc
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "NetVar.h"
|
||||
|
@ -9,171 +11,296 @@
|
|||
#include "NFS.h"
|
||||
#include "Event.h"
|
||||
|
||||
#define NFS_PROC_NULL 0
|
||||
#define NFS_PROC_GETATTR 1
|
||||
#define NFS_PROC_SETATTR 2
|
||||
#define NFS_PROC_LOOKUP 3
|
||||
#define NFS_PROC_READ 6
|
||||
#define NFS_PROC_WRITE 7
|
||||
#define NFS_PROC_FSSTAT 18
|
||||
|
||||
int NFS_Interp::RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n)
|
||||
{
|
||||
if ( c->Program() != 100003 )
|
||||
Weird("bad_RPC_program");
|
||||
Weird(fmt("bad_RPC_program (%d)", c->Program()));
|
||||
|
||||
uint32 proc = c->Proc();
|
||||
// The call arguments, depends on the call type obviously ...
|
||||
Val *callarg = 0;
|
||||
|
||||
switch ( proc ) {
|
||||
case NFS_PROC_NULL:
|
||||
case BifEnum::NFS3::PROC_NULL:
|
||||
break;
|
||||
|
||||
case NFS_PROC_GETATTR:
|
||||
{
|
||||
Val* v = ExtractFH(buf, n);
|
||||
if ( ! v )
|
||||
return 0;
|
||||
c->AddVal(v);
|
||||
}
|
||||
case BifEnum::NFS3::PROC_GETATTR:
|
||||
callarg = nfs3_fh(buf, n);
|
||||
break;
|
||||
|
||||
case NFS_PROC_LOOKUP:
|
||||
{
|
||||
StringVal* fh = ExtractFH(buf, n);
|
||||
|
||||
int name_len;
|
||||
const u_char* name = extract_XDR_opaque(buf, n, name_len);
|
||||
|
||||
if ( ! fh || ! name )
|
||||
return 0;
|
||||
|
||||
RecordVal* args = new RecordVal(nfs3_lookup_args);
|
||||
args->Assign(0, fh);
|
||||
args->Assign(1, new StringVal(new BroString(name, name_len, 0)));
|
||||
c->AddVal(args);
|
||||
}
|
||||
case BifEnum::NFS3::PROC_LOOKUP:
|
||||
callarg = nfs3_diropargs(buf, n);
|
||||
break;
|
||||
|
||||
case NFS_PROC_FSSTAT:
|
||||
{
|
||||
Val* v = ExtractFH(buf, n);
|
||||
if ( ! v )
|
||||
return 0;
|
||||
c->AddVal(v);
|
||||
}
|
||||
case BifEnum::NFS3::PROC_READ:
|
||||
callarg = nfs3_readargs(buf, n);
|
||||
break;
|
||||
|
||||
case NFS_PROC_READ:
|
||||
case BifEnum::NFS3::PROC_READLINK:
|
||||
callarg = nfs3_fh(buf, n);
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_WRITE:
|
||||
callarg = nfs3_writeargs(buf, n);
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_CREATE:
|
||||
callarg = nfs3_diropargs(buf, n);
|
||||
// TODO: implement create attributes. For now we just skip
|
||||
// over them.
|
||||
n = 0;
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_MKDIR:
|
||||
callarg = nfs3_diropargs(buf, n);
|
||||
// TODO: implement mkdir attributes. For now we just skip
|
||||
// over them.
|
||||
n = 0;
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_REMOVE:
|
||||
callarg = nfs3_diropargs(buf, n);
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_RMDIR:
|
||||
callarg = nfs3_diropargs(buf, n);
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_READDIR:
|
||||
callarg = nfs3_readdirargs(false, buf, n);
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_READDIRPLUS:
|
||||
callarg = nfs3_readdirargs(true, buf, n);
|
||||
break;
|
||||
|
||||
default:
|
||||
callarg = 0;
|
||||
if ( proc < BifEnum::NFS3::PROC_END_OF_PROCS )
|
||||
{
|
||||
// We know the procedure but haven't implemented it.
|
||||
// Otherwise DeliverRPC would complain about
|
||||
// excess_RPC.
|
||||
n = 0;
|
||||
}
|
||||
else
|
||||
Weird(fmt("unknown_NFS_request(%u)", proc));
|
||||
|
||||
// Return 1 so that replies to unprocessed calls will still
|
||||
// be processed, and the return status extracted
|
||||
// be processed, and the return status extracted.
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NFS_Interp::RPC_BuildReply(const RPC_CallInfo* c, int success,
|
||||
const u_char*& buf, int& n,
|
||||
EventHandlerPtr& event, Val*& reply)
|
||||
if ( ! buf )
|
||||
{
|
||||
reply = 0;
|
||||
uint32 status = 0;
|
||||
if ( success )
|
||||
// There was a parse error while trying to extract the call
|
||||
// arguments. However, we don't know where exactly it
|
||||
// happened and whether Vals where already allocated (e.g., a
|
||||
// RecordVal was allocated but we failed to fill it). So we
|
||||
// Unref() the call arguments, and we are fine.
|
||||
Unref(callarg);
|
||||
callarg = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
c->AddVal(callarg); // It's save to AddVal(0).
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NFS_Interp::RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status rpc_status,
|
||||
const u_char*& buf, int& n, double start_time,
|
||||
double last_time, int reply_len)
|
||||
{
|
||||
EventHandlerPtr event = 0;
|
||||
Val *reply = 0;
|
||||
BifEnum::NFS3::status_t nfs_status = BifEnum::NFS3::NFS3ERR_OK;
|
||||
bool rpc_success = ( rpc_status == BifEnum::RPC_SUCCESS );
|
||||
|
||||
// Reply always starts with the NFS status.
|
||||
if ( rpc_success )
|
||||
{
|
||||
if ( n >= 4 )
|
||||
status = extract_XDR_uint32(buf, n);
|
||||
nfs_status = (BifEnum::NFS3::status_t)extract_XDR_uint32(buf, n);
|
||||
else
|
||||
status = 0xffffffff;
|
||||
nfs_status = BifEnum::NFS3::NFS3ERR_UNKNOWN;
|
||||
}
|
||||
|
||||
if ( nfs_reply_status )
|
||||
{
|
||||
val_list* vl = new val_list;
|
||||
vl->append(analyzer->BuildConnVal());
|
||||
vl->append(new Val(status, TYPE_COUNT));
|
||||
val_list* vl = event_common_vl(c, rpc_status, nfs_status,
|
||||
start_time, last_time, reply_len);
|
||||
analyzer->ConnectionEvent(nfs_reply_status, vl);
|
||||
}
|
||||
|
||||
if ( ! rpc_success )
|
||||
{
|
||||
// We set the buffer to NULL, the function that extract the
|
||||
// reply from the data stream will then return empty records.
|
||||
//
|
||||
buf = NULL;
|
||||
n = 0;
|
||||
}
|
||||
|
||||
switch ( c->Proc() ) {
|
||||
case NFS_PROC_NULL:
|
||||
event = success ? nfs_request_null : nfs_attempt_null;
|
||||
case BifEnum::NFS3::PROC_NULL:
|
||||
event = nfs_proc_null;
|
||||
break;
|
||||
|
||||
case NFS_PROC_GETATTR:
|
||||
if ( success )
|
||||
{
|
||||
if ( ! buf || status != 0 )
|
||||
return 0;
|
||||
|
||||
reply = ExtractAttrs(buf, n);
|
||||
event = nfs_request_getattr;
|
||||
}
|
||||
else
|
||||
event = nfs_attempt_getattr;
|
||||
|
||||
case BifEnum::NFS3::PROC_GETATTR:
|
||||
reply = nfs3_fattr(buf, n);
|
||||
event = nfs_proc_getattr;
|
||||
break;
|
||||
|
||||
case NFS_PROC_LOOKUP:
|
||||
if ( success )
|
||||
{
|
||||
if ( ! buf || status != 0 )
|
||||
return 0;
|
||||
|
||||
RecordVal* r = new RecordVal(nfs3_lookup_reply);
|
||||
r->Assign(0, ExtractFH(buf, n));
|
||||
r->Assign(1, ExtractOptAttrs(buf, n));
|
||||
r->Assign(2, ExtractOptAttrs(buf, n));
|
||||
|
||||
reply = r;
|
||||
event = nfs_request_lookup;
|
||||
}
|
||||
else
|
||||
{
|
||||
reply = ExtractOptAttrs(buf, n);
|
||||
event = nfs_attempt_lookup;
|
||||
}
|
||||
|
||||
case BifEnum::NFS3::PROC_LOOKUP:
|
||||
reply = nfs3_lookup_reply(buf, n, nfs_status);
|
||||
event = nfs_proc_lookup;
|
||||
break;
|
||||
|
||||
case NFS_PROC_FSSTAT:
|
||||
if ( success )
|
||||
{
|
||||
if ( ! buf || status != 0 )
|
||||
return 0;
|
||||
case BifEnum::NFS3::PROC_READ:
|
||||
bro_uint_t offset;
|
||||
offset = c->RequestVal()->AsRecordVal()->Lookup(1)->AsCount();
|
||||
reply = nfs3_read_reply(buf, n, nfs_status, offset);
|
||||
event = nfs_proc_read;
|
||||
break;
|
||||
|
||||
RecordVal* r = new RecordVal(nfs3_fsstat);
|
||||
r->Assign(0, ExtractOptAttrs(buf, n));
|
||||
r->Assign(1, ExtractLongAsDouble(buf, n)); // tbytes
|
||||
r->Assign(2, ExtractLongAsDouble(buf, n)); // fbytes
|
||||
r->Assign(3, ExtractLongAsDouble(buf, n)); // abytes
|
||||
r->Assign(4, ExtractLongAsDouble(buf, n)); // tfiles
|
||||
r->Assign(5, ExtractLongAsDouble(buf, n)); // ffiles
|
||||
r->Assign(6, ExtractLongAsDouble(buf, n)); // afiles
|
||||
r->Assign(7, ExtractInterval(buf, n)); // invarsec
|
||||
case BifEnum::NFS3::PROC_READLINK:
|
||||
reply = nfs3_readlink_reply(buf, n, nfs_status);
|
||||
event = nfs_proc_readlink;
|
||||
break;
|
||||
|
||||
reply = r;
|
||||
event = nfs_request_fsstat;
|
||||
}
|
||||
else
|
||||
{
|
||||
reply = ExtractOptAttrs(buf, n);
|
||||
event = nfs_attempt_fsstat;
|
||||
}
|
||||
case BifEnum::NFS3::PROC_WRITE:
|
||||
reply = nfs3_write_reply(buf, n, nfs_status);
|
||||
event = nfs_proc_write;
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_CREATE:
|
||||
reply = nfs3_newobj_reply(buf, n, nfs_status);
|
||||
event = nfs_proc_create;
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_MKDIR:
|
||||
reply = nfs3_newobj_reply(buf, n, nfs_status);
|
||||
event = nfs_proc_mkdir;
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_REMOVE:
|
||||
reply = nfs3_delobj_reply(buf, n);
|
||||
event = nfs_proc_remove;
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_RMDIR:
|
||||
reply = nfs3_delobj_reply(buf, n);
|
||||
event = nfs_proc_rmdir;
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_READDIR:
|
||||
reply = nfs3_readdir_reply(false, buf, n, nfs_status);
|
||||
event = nfs_proc_readdir;
|
||||
break;
|
||||
|
||||
case BifEnum::NFS3::PROC_READDIRPLUS:
|
||||
reply = nfs3_readdir_reply(true, buf, n, nfs_status);
|
||||
event = nfs_proc_readdir;
|
||||
break;
|
||||
|
||||
default:
|
||||
if ( c->Proc() < BifEnum::NFS3::PROC_END_OF_PROCS )
|
||||
{
|
||||
// We know the procedure but haven't implemented it.
|
||||
// Otherwise DeliverRPC would complain about
|
||||
// excess_RPC.
|
||||
n = 0;
|
||||
reply = new EnumVal(c->Proc(), BifType::Enum::NFS3::proc_t);
|
||||
event = nfs_proc_not_implemented;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( rpc_success && ! buf )
|
||||
{
|
||||
// There was a parse error. We have to unref the reply. (see
|
||||
// also comments in RPC_BuildCall.
|
||||
Unref(reply);
|
||||
reply = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Note: if reply == 0, it won't be added to the val_list for the
|
||||
// event. While we can check for that on the policy layer it's kinda
|
||||
// ugly, because it's contrary to the event prototype. But having
|
||||
// this optional argument to the event is really helpful. Otherwise I
|
||||
// have to let reply point to a RecordVal where all fields are
|
||||
// optional and all are set to 0 ...
|
||||
if ( event )
|
||||
{
|
||||
val_list* vl = event_common_vl(c, rpc_status, nfs_status,
|
||||
start_time, last_time, reply_len);
|
||||
|
||||
Val *request = c->TakeRequestVal();
|
||||
|
||||
if ( request )
|
||||
vl->append(request);
|
||||
|
||||
if ( reply )
|
||||
vl->append(reply);
|
||||
|
||||
analyzer->ConnectionEvent(event, vl);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
StringVal* NFS_Interp::ExtractFH(const u_char*& buf, int& n)
|
||||
StringVal* NFS_Interp::nfs3_file_data(const u_char*& buf, int& n, uint64_t offset, int size)
|
||||
{
|
||||
int data_n;
|
||||
|
||||
// extract the data, move buf and n
|
||||
const u_char *data = extract_XDR_opaque(buf, n, data_n, 1 << 30, true);
|
||||
|
||||
// check whether we have to deliver data to the event
|
||||
if ( ! BifConst::NFS3::return_data )
|
||||
return 0;
|
||||
|
||||
if ( BifConst::NFS3::return_data_first_only && offset != 0 )
|
||||
return 0;
|
||||
|
||||
// Ok, so we want to return some data
|
||||
data_n = min(data_n, size);
|
||||
data_n = min(data_n, int(BifConst::NFS3::return_data_max));
|
||||
|
||||
if ( data_n > 0 )
|
||||
return new StringVal(new BroString(data, data_n, 0));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
val_list* NFS_Interp::event_common_vl(RPC_CallInfo *c, BifEnum::rpc_status rpc_status,
|
||||
BifEnum::NFS3::status_t nfs_status,
|
||||
double rep_start_time,
|
||||
double rep_last_time, int reply_len)
|
||||
{
|
||||
// Returns a new val_list that already has a conn_val, and nfs3_info.
|
||||
// These are the first parameters for each nfs_* event ...
|
||||
val_list *vl = new val_list;
|
||||
vl->append(analyzer->BuildConnVal());
|
||||
|
||||
RecordVal *info = new RecordVal(BifType::Record::NFS3::info_t);
|
||||
info->Assign(0, new EnumVal(rpc_status, BifType::Enum::rpc_status));
|
||||
info->Assign(1, new EnumVal(nfs_status, BifType::Enum::NFS3::status_t));
|
||||
info->Assign(2, new Val(c->StartTime(), TYPE_TIME));
|
||||
info->Assign(3, new Val(c->LastTime()-c->StartTime(), TYPE_INTERVAL));
|
||||
info->Assign(4, new Val(c->RPCLen(), TYPE_COUNT));
|
||||
info->Assign(5, new Val(rep_start_time, TYPE_TIME));
|
||||
info->Assign(6, new Val(rep_last_time-rep_start_time, TYPE_INTERVAL));
|
||||
info->Assign(7, new Val(reply_len, TYPE_COUNT));
|
||||
|
||||
vl->append(info);
|
||||
return vl;
|
||||
}
|
||||
|
||||
StringVal* NFS_Interp::nfs3_fh(const u_char*& buf, int& n)
|
||||
{
|
||||
int fh_n;
|
||||
const u_char* fh = extract_XDR_opaque(buf, n, fh_n, 64);
|
||||
|
@ -184,20 +311,21 @@ StringVal* NFS_Interp::ExtractFH(const u_char*& buf, int& n)
|
|||
return new StringVal(new BroString(fh, fh_n, 0));
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::ExtractAttrs(const u_char*& buf, int& n)
|
||||
RecordVal* NFS_Interp::nfs3_fattr(const u_char*& buf, int& n)
|
||||
{
|
||||
RecordVal* attrs = new RecordVal(nfs3_attrs);
|
||||
attrs->Assign(0, ExtractCount(buf, n)); // file type
|
||||
attrs->Assign(1, ExtractCount(buf, n)); // mode
|
||||
attrs->Assign(2, ExtractCount(buf, n)); // nlink
|
||||
attrs->Assign(3, ExtractCount(buf, n)); // uid
|
||||
attrs->Assign(4, ExtractCount(buf, n)); // gid
|
||||
attrs->Assign(5, ExtractLongAsDouble(buf, n)); // size
|
||||
attrs->Assign(6, ExtractLongAsDouble(buf, n)); // used
|
||||
attrs->Assign(7, ExtractCount(buf, n)); // rdev1
|
||||
attrs->Assign(8, ExtractCount(buf, n)); // rdev2
|
||||
attrs->Assign(9, ExtractLongAsDouble(buf, n)); // fsid
|
||||
attrs->Assign(10, ExtractLongAsDouble(buf, n)); // fileid
|
||||
RecordVal* attrs = new RecordVal(BifType::Record::NFS3::fattr_t);
|
||||
|
||||
attrs->Assign(0, nfs3_ftype(buf, n)); // file type
|
||||
attrs->Assign(1, ExtractUint32(buf, n)); // mode
|
||||
attrs->Assign(2, ExtractUint32(buf, n)); // nlink
|
||||
attrs->Assign(3, ExtractUint32(buf, n)); // uid
|
||||
attrs->Assign(4, ExtractUint32(buf, n)); // gid
|
||||
attrs->Assign(5, ExtractUint64(buf, n)); // size
|
||||
attrs->Assign(6, ExtractUint64(buf, n)); // used
|
||||
attrs->Assign(7, ExtractUint32(buf, n)); // rdev1
|
||||
attrs->Assign(8, ExtractUint32(buf, n)); // rdev2
|
||||
attrs->Assign(9, ExtractUint64(buf, n)); // fsid
|
||||
attrs->Assign(10, ExtractUint64(buf, n)); // fileid
|
||||
attrs->Assign(11, ExtractTime(buf, n)); // atime
|
||||
attrs->Assign(12, ExtractTime(buf, n)); // mtime
|
||||
attrs->Assign(13, ExtractTime(buf, n)); // ctime
|
||||
|
@ -205,27 +333,297 @@ RecordVal* NFS_Interp::ExtractAttrs(const u_char*& buf, int& n)
|
|||
return attrs;
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::ExtractOptAttrs(const u_char*& buf, int& n)
|
||||
EnumVal* NFS_Interp::nfs3_ftype(const u_char*& buf, int& n)
|
||||
{
|
||||
BifEnum::NFS3::file_type_t t = (BifEnum::NFS3::file_type_t)extract_XDR_uint32(buf, n);
|
||||
return new EnumVal(t, BifType::Enum::NFS3::file_type_t);
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_wcc_attr(const u_char*& buf, int& n)
|
||||
{
|
||||
RecordVal* attrs = new RecordVal(BifType::Record::NFS3::wcc_attr_t);
|
||||
|
||||
attrs->Assign(0, ExtractUint64(buf, n)); // size
|
||||
attrs->Assign(1, ExtractTime(buf, n)); // mtime
|
||||
attrs->Assign(2, ExtractTime(buf, n)); // ctime
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
StringVal *NFS_Interp::nfs3_filename(const u_char*& buf, int& n)
|
||||
{
|
||||
int name_len;
|
||||
const u_char* name = extract_XDR_opaque(buf, n, name_len);
|
||||
|
||||
if ( ! name )
|
||||
return 0;
|
||||
|
||||
return new StringVal(new BroString(name, name_len, 0));
|
||||
}
|
||||
|
||||
RecordVal *NFS_Interp::nfs3_diropargs(const u_char*& buf, int& n)
|
||||
{
|
||||
RecordVal *diropargs = new RecordVal(BifType::Record::NFS3::diropargs_t);
|
||||
|
||||
diropargs->Assign(0, nfs3_fh(buf, n));
|
||||
diropargs->Assign(1, nfs3_filename(buf, n));
|
||||
|
||||
return diropargs;
|
||||
}
|
||||
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_post_op_attr(const u_char*& buf, int& n)
|
||||
{
|
||||
int have_attrs = extract_XDR_uint32(buf, n);
|
||||
|
||||
RecordVal* opt_attrs = new RecordVal(nfs3_opt_attrs);
|
||||
if ( buf && have_attrs )
|
||||
opt_attrs->Assign(0, ExtractAttrs(buf, n));
|
||||
else
|
||||
opt_attrs->Assign(0, 0);
|
||||
if ( have_attrs )
|
||||
return nfs3_fattr(buf, n);
|
||||
|
||||
return opt_attrs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Val* NFS_Interp::ExtractCount(const u_char*& buf, int& n)
|
||||
StringVal* NFS_Interp::nfs3_post_op_fh(const u_char*& buf, int& n)
|
||||
{
|
||||
int have_fh = extract_XDR_uint32(buf, n);
|
||||
|
||||
if ( have_fh )
|
||||
return nfs3_fh(buf, n);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_pre_op_attr(const u_char*& buf, int& n)
|
||||
{
|
||||
int have_attrs = extract_XDR_uint32(buf, n);
|
||||
|
||||
if ( have_attrs )
|
||||
return nfs3_wcc_attr(buf, n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EnumVal *NFS_Interp::nfs3_stable_how(const u_char*& buf, int& n)
|
||||
{
|
||||
BifEnum::NFS3::stable_how_t stable = (BifEnum::NFS3::stable_how_t)extract_XDR_uint32(buf, n);
|
||||
return new EnumVal(stable, BifType::Enum::NFS3::stable_how_t);
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_lookup_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status)
|
||||
{
|
||||
RecordVal *rep = new RecordVal(BifType::Record::NFS3::lookup_reply_t);
|
||||
|
||||
if ( status == BifEnum::NFS3::NFS3ERR_OK )
|
||||
{
|
||||
rep->Assign(0, nfs3_fh(buf,n));
|
||||
rep->Assign(1, nfs3_post_op_attr(buf, n));
|
||||
rep->Assign(2, nfs3_post_op_attr(buf, n));
|
||||
}
|
||||
else
|
||||
{
|
||||
rep->Assign(0, 0);
|
||||
rep->Assign(1, 0);
|
||||
rep->Assign(2, nfs3_post_op_attr(buf, n));
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
|
||||
RecordVal *NFS_Interp::nfs3_readargs(const u_char*& buf, int& n)
|
||||
{
|
||||
RecordVal *readargs = new RecordVal(BifType::Record::NFS3::readargs_t);
|
||||
|
||||
readargs->Assign(0, nfs3_fh(buf, n));
|
||||
readargs->Assign(1, ExtractUint64(buf, n)); // offset
|
||||
readargs->Assign(2, ExtractUint32(buf,n)); // size
|
||||
|
||||
return readargs;
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_read_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status,
|
||||
bro_uint_t offset)
|
||||
{
|
||||
RecordVal *rep = new RecordVal(BifType::Record::NFS3::read_reply_t);
|
||||
|
||||
if (status == BifEnum::NFS3::NFS3ERR_OK)
|
||||
{
|
||||
uint32_t bytes_read;
|
||||
|
||||
rep->Assign(0, nfs3_post_op_attr(buf, n));
|
||||
bytes_read = extract_XDR_uint32(buf, n);
|
||||
rep->Assign(1, new Val(bytes_read, TYPE_COUNT));
|
||||
rep->Assign(2, ExtractBool(buf, n));
|
||||
rep->Assign(3, nfs3_file_data(buf, n, offset, bytes_read));
|
||||
}
|
||||
else
|
||||
{
|
||||
rep->Assign(0, nfs3_post_op_attr(buf, n));
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_readlink_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status)
|
||||
{
|
||||
RecordVal *rep = new RecordVal(BifType::Record::NFS3::readlink_reply_t);
|
||||
|
||||
if (status == BifEnum::NFS3::NFS3ERR_OK)
|
||||
{
|
||||
rep->Assign(0, nfs3_post_op_attr(buf, n));
|
||||
rep->Assign(1, nfs3_nfspath(buf,n));
|
||||
}
|
||||
else
|
||||
{
|
||||
rep->Assign(0, nfs3_post_op_attr(buf, n));
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
RecordVal *NFS_Interp::nfs3_writeargs(const u_char*& buf, int& n)
|
||||
{
|
||||
uint32_t bytes;
|
||||
uint64_t offset;
|
||||
RecordVal *writeargs = new RecordVal(BifType::Record::NFS3::writeargs_t);
|
||||
|
||||
offset = extract_XDR_uint64(buf, n);
|
||||
bytes = extract_XDR_uint32(buf, n);
|
||||
|
||||
writeargs->Assign(0, nfs3_fh(buf, n));
|
||||
writeargs->Assign(1, new Val(offset, TYPE_COUNT));
|
||||
writeargs->Assign(2, new Val(bytes, TYPE_COUNT));
|
||||
writeargs->Assign(3, nfs3_stable_how(buf, n));
|
||||
writeargs->Assign(4, nfs3_file_data(buf, n, offset, bytes));
|
||||
|
||||
return writeargs;
|
||||
}
|
||||
|
||||
RecordVal *NFS_Interp::nfs3_write_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status)
|
||||
{
|
||||
RecordVal *rep = new RecordVal(BifType::Record::NFS3::write_reply_t);
|
||||
|
||||
if ( status == BifEnum::NFS3::NFS3ERR_OK )
|
||||
{
|
||||
rep->Assign(0, nfs3_pre_op_attr(buf, n));
|
||||
rep->Assign(1, nfs3_post_op_attr(buf, n));
|
||||
rep->Assign(2, ExtractUint32(buf, n));
|
||||
rep->Assign(3, nfs3_stable_how(buf, n));
|
||||
|
||||
// Writeverf. While the RFC says that this should be a fixed
|
||||
// length opaque, it specifies the lenght as 8 bytes, so we
|
||||
// can also just as easily extract a uint64.
|
||||
rep->Assign(4, ExtractUint64(buf, n));
|
||||
}
|
||||
else
|
||||
{
|
||||
rep->Assign(0, nfs3_post_op_attr(buf, n));
|
||||
rep->Assign(1, nfs3_pre_op_attr(buf, n));
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_newobj_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status)
|
||||
{
|
||||
RecordVal *rep = new RecordVal(BifType::Record::NFS3::newobj_reply_t);
|
||||
|
||||
if (status == BifEnum::NFS3::NFS3ERR_OK)
|
||||
{
|
||||
int i = 0;
|
||||
rep->Assign(0, nfs3_post_op_fh(buf,n));
|
||||
rep->Assign(1, nfs3_post_op_attr(buf, n));
|
||||
// wcc_data
|
||||
rep->Assign(2, nfs3_pre_op_attr(buf, n));
|
||||
rep->Assign(3, nfs3_post_op_attr(buf, n));
|
||||
}
|
||||
else
|
||||
{
|
||||
rep->Assign(0, 0);
|
||||
rep->Assign(1, 0);
|
||||
rep->Assign(2, nfs3_pre_op_attr(buf, n));
|
||||
rep->Assign(3, nfs3_post_op_attr(buf, n));
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_delobj_reply(const u_char*& buf, int& n)
|
||||
{
|
||||
RecordVal *rep = new RecordVal(BifType::Record::NFS3::delobj_reply_t);
|
||||
|
||||
// wcc_data
|
||||
rep->Assign(0, nfs3_pre_op_attr(buf, n));
|
||||
rep->Assign(1, nfs3_post_op_attr(buf, n));
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_readdirargs(bool isplus, const u_char*& buf, int&n)
|
||||
{
|
||||
RecordVal *args = new RecordVal(BifType::Record::NFS3::readdirargs_t);
|
||||
|
||||
args->Assign(0, new Val(isplus, TYPE_BOOL));
|
||||
args->Assign(1, nfs3_fh(buf, n));
|
||||
args->Assign(2, ExtractUint64(buf,n)); // cookie
|
||||
args->Assign(3, ExtractUint64(buf,n)); // cookieverf
|
||||
args->Assign(4, ExtractUint32(buf,n)); // dircount
|
||||
|
||||
if ( isplus )
|
||||
args->Assign(5, ExtractUint32(buf,n));
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
RecordVal* NFS_Interp::nfs3_readdir_reply(bool isplus, const u_char*& buf,
|
||||
int&n, BifEnum::NFS3::status_t status)
|
||||
{
|
||||
RecordVal *rep = new RecordVal(BifType::Record::NFS3::readdir_reply_t);
|
||||
|
||||
rep->Assign(0, new Val(isplus, TYPE_BOOL));
|
||||
|
||||
if ( status == BifEnum::NFS3::NFS3ERR_OK )
|
||||
{
|
||||
unsigned pos;
|
||||
VectorVal *entries = new VectorVal(BifType::Vector::NFS3::direntry_vec_t);
|
||||
|
||||
rep->Assign(1, nfs3_post_op_attr(buf,n)); // dir_attr
|
||||
rep->Assign(2, ExtractUint64(buf,n)); // cookieverf
|
||||
|
||||
pos = 1;
|
||||
|
||||
while ( extract_XDR_uint32(buf,n) )
|
||||
{
|
||||
RecordVal *entry = new RecordVal(BifType::Record::NFS3::direntry_t);
|
||||
entry->Assign(0, ExtractUint64(buf,n)); // fileid
|
||||
entry->Assign(1, nfs3_filename(buf,n)); // fname
|
||||
entry->Assign(2, ExtractUint64(buf,n)); // cookie
|
||||
|
||||
if ( isplus )
|
||||
{
|
||||
entry->Assign(3, nfs3_post_op_attr(buf,n));
|
||||
entry->Assign(4, nfs3_post_op_fh(buf,n));
|
||||
}
|
||||
|
||||
entries->Assign(pos, entry, 0);
|
||||
pos++;
|
||||
}
|
||||
|
||||
rep->Assign(3, entries);
|
||||
rep->Assign(4, ExtractBool(buf,n)); // eof
|
||||
}
|
||||
else
|
||||
{
|
||||
rep->Assign(1, nfs3_post_op_attr(buf,n));
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
Val* NFS_Interp::ExtractUint32(const u_char*& buf, int& n)
|
||||
{
|
||||
return new Val(extract_XDR_uint32(buf, n), TYPE_COUNT);
|
||||
}
|
||||
|
||||
Val* NFS_Interp::ExtractLongAsDouble(const u_char*& buf, int& n)
|
||||
Val* NFS_Interp::ExtractUint64(const u_char*& buf, int& n)
|
||||
{
|
||||
return new Val(extract_XDR_uint64_as_double(buf, n), TYPE_DOUBLE);
|
||||
return new Val(extract_XDR_uint64(buf, n), TYPE_COUNT);
|
||||
}
|
||||
|
||||
Val* NFS_Interp::ExtractTime(const u_char*& buf, int& n)
|
||||
|
@ -238,37 +636,14 @@ Val* NFS_Interp::ExtractInterval(const u_char*& buf, int& n)
|
|||
return new IntervalVal(double(extract_XDR_uint32(buf, n)), 1.0);
|
||||
}
|
||||
|
||||
void NFS_Interp::Event(EventHandlerPtr f, Val* request, int status, Val* reply)
|
||||
Val* NFS_Interp::ExtractBool(const u_char*& buf, int& n)
|
||||
{
|
||||
if ( ! f )
|
||||
{
|
||||
Unref(request);
|
||||
Unref(reply);
|
||||
return;
|
||||
return new Val(extract_XDR_uint32(buf, n), TYPE_BOOL);
|
||||
}
|
||||
|
||||
val_list* vl = new val_list;
|
||||
|
||||
vl->append(analyzer->BuildConnVal());
|
||||
if ( status == RPC_SUCCESS )
|
||||
{
|
||||
if ( request )
|
||||
vl->append(request);
|
||||
if ( reply )
|
||||
vl->append(reply);
|
||||
}
|
||||
else
|
||||
{
|
||||
vl->append(new Val(status, TYPE_COUNT));
|
||||
if ( request )
|
||||
vl->append(request);
|
||||
}
|
||||
|
||||
analyzer->ConnectionEvent(f, vl);
|
||||
}
|
||||
|
||||
NFS_Analyzer::NFS_Analyzer(Connection* conn)
|
||||
: RPC_Analyzer(AnalyzerTag::NFS, conn, new NFS_Interp(this))
|
||||
: RPC_Analyzer(AnalyzerTag::NFS, conn, new NFS_Interp(this))
|
||||
{
|
||||
orig_rpc = resp_rpc = 0;
|
||||
}
|
||||
|
|
73
src/NFS.h
73
src/NFS.h
|
@ -6,6 +6,8 @@
|
|||
#define nfs_h
|
||||
|
||||
#include "RPC.h"
|
||||
#include "XDR.h"
|
||||
#include "Event.h"
|
||||
|
||||
class NFS_Interp : public RPC_Interpreter {
|
||||
public:
|
||||
|
@ -13,19 +15,61 @@ public:
|
|||
|
||||
protected:
|
||||
int RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n);
|
||||
int RPC_BuildReply(const RPC_CallInfo* c, int success,
|
||||
const u_char*& buf, int& n,
|
||||
EventHandlerPtr& event, Val*& reply);
|
||||
int RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status rpc_status,
|
||||
const u_char*& buf, int& n, double start_time,
|
||||
double last_time, int reply_len);
|
||||
|
||||
// Returns a new val_list that already has a conn_val, rpc_status and
|
||||
// nfs_status. These are the first parameters for each nfs_* event
|
||||
// ...
|
||||
val_list* event_common_vl(RPC_CallInfo *c, BifEnum::rpc_status rpc_status,
|
||||
BifEnum::NFS3::status_t nfs_status,
|
||||
double rep_start_time, double rep_last_time,
|
||||
int reply_len);
|
||||
|
||||
// These methods parse the appropriate NFSv3 "type" out of buf. If
|
||||
// there are any errors (i.e., buffer to short, etc), buf will be set
|
||||
// to 0. However, the methods might still return an allocated Val * !
|
||||
// So, you might want to Unref() the Val if buf is 0. Method names
|
||||
// are based on the type names of RFC 1813.
|
||||
StringVal* nfs3_fh(const u_char*& buf, int& n);
|
||||
RecordVal* nfs3_fattr(const u_char*& buf, int& n);
|
||||
EnumVal* nfs3_ftype(const u_char*& buf, int& n);
|
||||
RecordVal* nfs3_wcc_attr(const u_char*& buf, int& n);
|
||||
RecordVal* nfs3_diropargs(const u_char*&buf, int &n);
|
||||
StringVal* nfs3_filename(const u_char*& buf, int& n);
|
||||
StringVal* nfs3_nfspath(const u_char*& buf, int& n)
|
||||
{
|
||||
return nfs3_filename(buf,n);
|
||||
}
|
||||
|
||||
RecordVal* nfs3_post_op_attr(const u_char*&buf, int &n); // Return 0 or an fattr
|
||||
RecordVal* nfs3_pre_op_attr(const u_char*&buf, int &n); // Return 0 or an wcc_attr
|
||||
RecordVal* nfs3_lookup_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status);
|
||||
RecordVal* nfs3_readargs(const u_char*& buf, int& n);
|
||||
RecordVal* nfs3_read_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status, bro_uint_t offset);
|
||||
RecordVal* nfs3_readlink_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status);
|
||||
RecordVal* nfs3_writeargs(const u_char*& buf, int& n);
|
||||
EnumVal* nfs3_stable_how(const u_char*& buf, int& n);
|
||||
RecordVal* nfs3_write_reply(const u_char*& buf, int& n, BifEnum::NFS3::status_t status);
|
||||
RecordVal* nfs3_newobj_reply(const u_char*& buf, int&n, BifEnum::NFS3::status_t status);
|
||||
RecordVal* nfs3_delobj_reply(const u_char*& buf, int& n);
|
||||
StringVal* nfs3_post_op_fh(const u_char*& buf, int& n);
|
||||
RecordVal* nfs3_readdirargs(bool isplus, const u_char*& buf, int&n);
|
||||
RecordVal* nfs3_readdir_reply(bool isplus, const u_char*& buf, int&n, BifEnum::NFS3::status_t status);
|
||||
|
||||
// Consumes the file data in the RPC message. Depending on NFS::return_data* consts
|
||||
// in bro.init returns NULL or the data as string val:
|
||||
// * offset is the offset of the read/write call
|
||||
// * size is the amount of bytes read (or requested to be written),
|
||||
StringVal* nfs3_file_data(const u_char*& buf, int& n, uint64_t offset, int size);
|
||||
|
||||
StringVal* ExtractFH(const u_char*& buf, int& n);
|
||||
RecordVal* ExtractAttrs(const u_char*& buf, int& n);
|
||||
RecordVal* ExtractOptAttrs(const u_char*& buf, int& n);
|
||||
Val* ExtractCount(const u_char*& buf, int& n);
|
||||
Val* ExtractLongAsDouble(const u_char*& buf, int& n);
|
||||
Val* ExtractUint32(const u_char*& buf, int& n);
|
||||
Val* ExtractUint64(const u_char*& buf, int& n);
|
||||
Val* ExtractTime(const u_char*& buf, int& n);
|
||||
Val* ExtractInterval(const u_char*& buf, int& n);
|
||||
|
||||
void Event(EventHandlerPtr f, Val* request, int status, Val* reply);
|
||||
Val* ExtractBool(const u_char*& buf, int& n);
|
||||
};
|
||||
|
||||
class NFS_Analyzer : public RPC_Analyzer {
|
||||
|
@ -36,7 +80,16 @@ public:
|
|||
static Analyzer* InstantiateAnalyzer(Connection* conn)
|
||||
{ return new NFS_Analyzer(conn); }
|
||||
|
||||
static bool Available() { return nfs_request_getattr || rpc_call; }
|
||||
static bool Available()
|
||||
{
|
||||
return ( nfs_proc_null || nfs_proc_not_implemented || nfs_proc_getattr ||
|
||||
nfs_proc_lookup || nfs_proc_read || nfs_proc_readlink ||
|
||||
nfs_proc_write || nfs_proc_create || nfs_proc_mkdir ||
|
||||
nfs_proc_remove || nfs_proc_rmdir || nfs_proc_readdir ||
|
||||
nfs_reply_status ||
|
||||
rpc_dialogue || rpc_call || rpc_reply );
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -110,12 +110,6 @@ TableType* pm_mappings;
|
|||
RecordType* pm_port_request;
|
||||
RecordType* pm_callit_request;
|
||||
|
||||
RecordType* nfs3_attrs;
|
||||
RecordType* nfs3_opt_attrs;
|
||||
RecordType* nfs3_lookup_args;
|
||||
RecordType* nfs3_lookup_reply;
|
||||
RecordType* nfs3_fsstat;
|
||||
|
||||
RecordType* ntp_msg;
|
||||
|
||||
TableVal* samba_cmds;
|
||||
|
@ -450,12 +444,6 @@ void init_net_var()
|
|||
pm_port_request = internal_type("pm_port_request")->AsRecordType();
|
||||
pm_callit_request = internal_type("pm_callit_request")->AsRecordType();
|
||||
|
||||
nfs3_attrs = internal_type("nfs3_attrs")->AsRecordType();
|
||||
nfs3_opt_attrs = internal_type("nfs3_opt_attrs")->AsRecordType();
|
||||
nfs3_lookup_args = internal_type("nfs3_lookup_args")->AsRecordType();
|
||||
nfs3_lookup_reply = internal_type("nfs3_lookup_reply")->AsRecordType();
|
||||
nfs3_fsstat = internal_type("nfs3_fsstat")->AsRecordType();
|
||||
|
||||
ntp_msg = internal_type("ntp_msg")->AsRecordType();
|
||||
|
||||
samba_cmds = internal_val("samba_cmds")->AsTableVal();
|
||||
|
|
|
@ -114,12 +114,6 @@ extern TableType* pm_mappings;
|
|||
extern RecordType* pm_port_request;
|
||||
extern RecordType* pm_callit_request;
|
||||
|
||||
extern RecordType* nfs3_attrs;
|
||||
extern RecordType* nfs3_opt_attrs;
|
||||
extern RecordType* nfs3_lookup_args;
|
||||
extern RecordType* nfs3_lookup_reply;
|
||||
extern RecordType* nfs3_fsstat;
|
||||
|
||||
extern RecordType* ntp_msg;
|
||||
|
||||
extern TableVal* samba_cmds;
|
||||
|
|
|
@ -71,11 +71,14 @@ int PortmapperInterp::RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n)
|
|||
return 1;
|
||||
}
|
||||
|
||||
int PortmapperInterp::RPC_BuildReply(const RPC_CallInfo* c, int success,
|
||||
int PortmapperInterp::RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status status,
|
||||
const u_char*& buf, int& n,
|
||||
EventHandlerPtr& event, Val*& reply)
|
||||
double start_time, double last_time,
|
||||
int reply_len)
|
||||
{
|
||||
reply = 0;
|
||||
EventHandlerPtr event;
|
||||
Val *reply = 0;
|
||||
int success = (status == BifEnum::RPC_SUCCESS);
|
||||
|
||||
switch ( c->Proc() ) {
|
||||
case PMAPPROC_NULL:
|
||||
|
@ -184,6 +187,7 @@ int PortmapperInterp::RPC_BuildReply(const RPC_CallInfo* c, int success,
|
|||
return 0;
|
||||
}
|
||||
|
||||
Event(event, c->TakeRequestVal(), status, reply);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -267,7 +271,7 @@ uint32 PortmapperInterp::CheckPort(uint32 port)
|
|||
return port;
|
||||
}
|
||||
|
||||
void PortmapperInterp::Event(EventHandlerPtr f, Val* request, int status, Val* reply)
|
||||
void PortmapperInterp::Event(EventHandlerPtr f, Val* request, BifEnum::rpc_status status, Val* reply)
|
||||
{
|
||||
if ( ! f )
|
||||
{
|
||||
|
@ -279,7 +283,8 @@ void PortmapperInterp::Event(EventHandlerPtr f, Val* request, int status, Val* r
|
|||
val_list* vl = new val_list;
|
||||
|
||||
vl->append(analyzer->BuildConnVal());
|
||||
if ( status == RPC_SUCCESS )
|
||||
|
||||
if ( status == BifEnum::RPC_SUCCESS )
|
||||
{
|
||||
if ( request )
|
||||
vl->append(request);
|
||||
|
|
|
@ -13,12 +13,12 @@ public:
|
|||
|
||||
protected:
|
||||
int RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n);
|
||||
int RPC_BuildReply(const RPC_CallInfo* c, int success,
|
||||
const u_char*& buf, int& n,
|
||||
EventHandlerPtr& event, Val*& reply);
|
||||
int RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status success,
|
||||
const u_char*& buf, int& n, double start_time,
|
||||
double last_time, int reply_len);
|
||||
uint32 CheckPort(uint32 port);
|
||||
|
||||
void Event(EventHandlerPtr f, Val* request, int status, Val* reply);
|
||||
void Event(EventHandlerPtr f, Val* request, BifEnum::rpc_status status, Val* reply);
|
||||
|
||||
Val* ExtractMapping(const u_char*& buf, int& len);
|
||||
Val* ExtractPortRequest(const u_char*& buf, int& len);
|
||||
|
|
566
src/RPC.cc
566
src/RPC.cc
|
@ -2,10 +2,12 @@
|
|||
//
|
||||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "NetVar.h"
|
||||
#include "XDR.h"
|
||||
#include "RPC.h"
|
||||
|
@ -15,19 +17,19 @@ namespace { // local namespace
|
|||
const bool DEBUG_rpc_resync = false;
|
||||
}
|
||||
|
||||
// TODO: Should we add start_time and last_time to the rpc_* events??
|
||||
|
||||
// TODO: make this configurable
|
||||
#define MAX_RPC_LEN 65536
|
||||
|
||||
// The following correspond to the different RPC status values defined
|
||||
// in bro.init.
|
||||
// #define BRO_RPC_TIMEOUT 6
|
||||
// #define BRO_RPC_AUTH_ERROR 7
|
||||
// #define BRO_RPC_UNKNOWN_ERROR 8
|
||||
|
||||
RPC_CallInfo::RPC_CallInfo(uint32 arg_xid, const u_char*& buf, int& n)
|
||||
RPC_CallInfo::RPC_CallInfo(uint32 arg_xid, const u_char*& buf, int& n, double arg_start_time, double arg_last_time, int arg_rpc_len)
|
||||
{
|
||||
xid = arg_xid;
|
||||
|
||||
start_time = network_time;
|
||||
start_time = arg_start_time;
|
||||
last_time = arg_last_time;
|
||||
rpc_len = arg_rpc_len;
|
||||
call_n = n;
|
||||
call_buf = new u_char[call_n];
|
||||
memcpy((void*) call_buf, (const void*) buf, call_n);
|
||||
|
@ -76,10 +78,12 @@ RPC_Interpreter::~RPC_Interpreter()
|
|||
{
|
||||
}
|
||||
|
||||
int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
|
||||
int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int rpclen,
|
||||
int is_orig, double start_time, double last_time)
|
||||
{
|
||||
uint32 xid = extract_XDR_uint32(buf, n);
|
||||
uint32 msg_type = extract_XDR_uint32(buf, n);
|
||||
int rpc_len = n;
|
||||
|
||||
if ( ! buf )
|
||||
return 0;
|
||||
|
@ -97,6 +101,14 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
|
|||
if ( ! call->CompareRexmit(buf, n) )
|
||||
Weird("RPC_rexmit_inconsistency");
|
||||
|
||||
// TODO: Should we update start_time and last_time or
|
||||
// not??
|
||||
call->SetStartTime(start_time);
|
||||
call->SetLastTime(last_time);
|
||||
|
||||
// TODO: Not sure whether the handling if rexmit
|
||||
// inconsistencies are correct. Maybe we should use
|
||||
// the info in the new call for further processing.
|
||||
if ( call->HeaderLen() > n )
|
||||
{
|
||||
Weird("RPC_underflow");
|
||||
|
@ -109,9 +121,10 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
|
|||
|
||||
else
|
||||
{
|
||||
call = new RPC_CallInfo(xid, buf, n);
|
||||
call = new RPC_CallInfo(xid, buf, n, start_time, last_time, rpc_len);
|
||||
if ( ! buf )
|
||||
{
|
||||
Weird("bad_RPC");
|
||||
delete call;
|
||||
return 0;
|
||||
}
|
||||
|
@ -119,6 +132,11 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
|
|||
calls.Insert(&h, call);
|
||||
}
|
||||
|
||||
// We now have a valid RPC_CallInfo (either the previous one
|
||||
// in case of a rexmit or the current one).
|
||||
// TODO: What to do in case of a rexmit_inconistency??
|
||||
Event_RPC_Call(call);
|
||||
|
||||
if ( RPC_BuildCall(call, buf, n) )
|
||||
call->SetValidCall();
|
||||
else
|
||||
|
@ -137,7 +155,7 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
|
|||
if ( ! buf )
|
||||
return 0;
|
||||
|
||||
uint32 status = BifEnum::RPC_UNKNOWN_ERROR;
|
||||
BifEnum::rpc_status status = BifEnum::RPC_UNKNOWN_ERROR;
|
||||
|
||||
if ( reply_stat == RPC_MSG_ACCEPTED )
|
||||
{
|
||||
|
@ -147,7 +165,7 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
|
|||
// The first members of BifEnum::RPC_* correspond
|
||||
// to accept_stat.
|
||||
if ( accept_stat <= RPC_SYSTEM_ERR )
|
||||
status = accept_stat;
|
||||
status = (BifEnum::rpc_status)accept_stat;
|
||||
|
||||
if ( ! buf )
|
||||
return 0;
|
||||
|
@ -199,13 +217,14 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
|
|||
else
|
||||
Weird("bad_RPC");
|
||||
|
||||
// We now have extracted the status we want to use.
|
||||
Event_RPC_Reply(xid, status, n);
|
||||
|
||||
if ( call )
|
||||
{
|
||||
int success = status == RPC_SUCCESS;
|
||||
|
||||
if ( ! call->IsValidCall() )
|
||||
{
|
||||
if ( success )
|
||||
if ( status == BifEnum::RPC_SUCCESS )
|
||||
Weird("successful_RPC_reply_to_invalid_request");
|
||||
// We can't process this further, even if
|
||||
// it was successful, because the call
|
||||
|
@ -214,19 +233,11 @@ int RPC_Interpreter::DeliverRPC(const u_char* buf, int n, int is_orig)
|
|||
|
||||
else
|
||||
{
|
||||
EventHandlerPtr event;
|
||||
Val* reply;
|
||||
if ( ! RPC_BuildReply(call, success, buf,
|
||||
n, event, reply) )
|
||||
if ( ! RPC_BuildReply(call, (BifEnum::rpc_status)status, buf, n, start_time, last_time, rpc_len) )
|
||||
Weird("bad_RPC");
|
||||
else
|
||||
{
|
||||
Event(event, call->TakeRequestVal(),
|
||||
status, reply);
|
||||
}
|
||||
}
|
||||
|
||||
RPC_Event(call, status, n);
|
||||
Event_RPC_Dialogue(call, status, n);
|
||||
|
||||
delete calls.RemoveEntry(&h);
|
||||
}
|
||||
|
@ -264,119 +275,214 @@ void RPC_Interpreter::Timeout()
|
|||
|
||||
while ( (c = calls.NextEntry(cookie)) )
|
||||
{
|
||||
RPC_Event(c, BifEnum::RPC_TIMEOUT, 0);
|
||||
Event_RPC_Dialogue(c, BifEnum::RPC_TIMEOUT, 0);
|
||||
|
||||
if ( c->IsValidCall() )
|
||||
{
|
||||
const u_char* buf;
|
||||
int n = 0;
|
||||
EventHandlerPtr event;
|
||||
Val* reply;
|
||||
if ( ! RPC_BuildReply(c, 0, buf, n, event, reply) )
|
||||
|
||||
if ( ! RPC_BuildReply(c, BifEnum::RPC_TIMEOUT, buf, n, network_time, network_time, 0) )
|
||||
Weird("bad_RPC");
|
||||
else
|
||||
{
|
||||
Event(event, c->TakeRequestVal(),
|
||||
BifEnum::RPC_TIMEOUT, reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RPC_Interpreter::RPC_Event(RPC_CallInfo* c, int status, int reply_len)
|
||||
void RPC_Interpreter::Event_RPC_Dialogue(RPC_CallInfo* c, BifEnum::rpc_status status, int reply_len)
|
||||
{
|
||||
if ( rpc_call )
|
||||
if ( rpc_dialogue )
|
||||
{
|
||||
val_list* vl = new val_list;
|
||||
vl->append(analyzer->BuildConnVal());
|
||||
vl->append(new Val(c->Program(), TYPE_COUNT));
|
||||
vl->append(new Val(c->Version(), TYPE_COUNT));
|
||||
vl->append(new Val(c->Proc(), TYPE_COUNT));
|
||||
vl->append(new Val(status, TYPE_COUNT));
|
||||
vl->append(new EnumVal(status, BifType::Enum::rpc_status));
|
||||
vl->append(new Val(c->StartTime(), TYPE_TIME));
|
||||
vl->append(new Val(c->CallLen(), TYPE_COUNT));
|
||||
vl->append(new Val(reply_len, TYPE_COUNT));
|
||||
analyzer->ConnectionEvent(rpc_dialogue, vl);
|
||||
}
|
||||
}
|
||||
|
||||
void RPC_Interpreter::Event_RPC_Call(RPC_CallInfo* c)
|
||||
{
|
||||
if ( rpc_call )
|
||||
{
|
||||
val_list* vl = new val_list;
|
||||
vl->append(analyzer->BuildConnVal());
|
||||
vl->append(new Val(c->XID(), TYPE_COUNT));
|
||||
vl->append(new Val(c->Program(), TYPE_COUNT));
|
||||
vl->append(new Val(c->Version(), TYPE_COUNT));
|
||||
vl->append(new Val(c->Proc(), TYPE_COUNT));
|
||||
vl->append(new Val(c->CallLen(), TYPE_COUNT));
|
||||
analyzer->ConnectionEvent(rpc_call, vl);
|
||||
}
|
||||
}
|
||||
|
||||
void RPC_Interpreter::Event_RPC_Reply(uint32_t xid, BifEnum::rpc_status status, int reply_len)
|
||||
{
|
||||
if ( rpc_reply )
|
||||
{
|
||||
val_list* vl = new val_list;
|
||||
vl->append(analyzer->BuildConnVal());
|
||||
vl->append(new Val(xid, TYPE_COUNT));
|
||||
vl->append(new EnumVal(status, BifType::Enum::rpc_status));
|
||||
vl->append(new Val(reply_len, TYPE_COUNT));
|
||||
analyzer->ConnectionEvent(rpc_reply, vl);
|
||||
}
|
||||
}
|
||||
|
||||
void RPC_Interpreter::Weird(const char* msg)
|
||||
{
|
||||
analyzer->Weird(msg);
|
||||
}
|
||||
|
||||
|
||||
void RPC_Reasm_Buffer::Init(int64_t arg_maxsize, int64_t arg_expected) {
|
||||
if ( buf )
|
||||
delete [] buf;
|
||||
expected = arg_expected;
|
||||
maxsize = arg_maxsize;
|
||||
fill = processed = 0;
|
||||
buf = new u_char[maxsize];
|
||||
};
|
||||
|
||||
bool RPC_Reasm_Buffer::ConsumeChunk(const u_char*& data, int& len)
|
||||
{
|
||||
// How many bytes do we want to process with this call? Either the
|
||||
// all of the bytes available or the number of bytes that we are
|
||||
// still missing.
|
||||
int64_t to_process = min(int64_t(len), (expected-processed));
|
||||
|
||||
if ( fill < maxsize )
|
||||
{
|
||||
// We haven't yet filled the buffer. How many bytes to copy
|
||||
// into the buff. Either all of the bytes we want to process
|
||||
// or the number of bytes until we reach maxsize.
|
||||
int64_t to_copy = min( to_process, (maxsize-fill) );
|
||||
if ( to_copy )
|
||||
memcpy(buf+fill, data, to_copy);
|
||||
|
||||
fill += to_copy;
|
||||
}
|
||||
|
||||
processed += to_process;
|
||||
len -= to_process;
|
||||
data += to_process;
|
||||
return (expected == processed);
|
||||
}
|
||||
|
||||
Contents_RPC::Contents_RPC(Connection* conn, bool orig,
|
||||
RPC_Interpreter* arg_interp)
|
||||
: TCP_SupportAnalyzer(AnalyzerTag::Contents_RPC, conn, orig)
|
||||
: TCP_SupportAnalyzer(AnalyzerTag::Contents_RPC, conn, orig)
|
||||
{
|
||||
interp = arg_interp;
|
||||
resync = false;
|
||||
msg_buf = 0;
|
||||
InitBuffer();
|
||||
state = WAIT_FOR_MESSAGE;
|
||||
resync_state = RESYNC_INIT;
|
||||
resync_toskip = 0;
|
||||
start_time = 0;
|
||||
last_time = 0;
|
||||
}
|
||||
|
||||
void Contents_RPC::Init()
|
||||
{
|
||||
TCP_SupportAnalyzer::Init();
|
||||
|
||||
TCP_Analyzer* tcp =
|
||||
static_cast<TCP_ApplicationAnalyzer*>(Parent())->TCP();
|
||||
assert(tcp);
|
||||
|
||||
resync = (IsOrig() ? tcp->OrigState() : tcp->RespState()) !=
|
||||
TCP_ENDPOINT_ESTABLISHED;
|
||||
}
|
||||
|
||||
void Contents_RPC::InitBuffer()
|
||||
{
|
||||
buf_len = 4;
|
||||
|
||||
// For record marker:
|
||||
delete [] msg_buf;
|
||||
msg_buf = new u_char[buf_len];
|
||||
|
||||
buf_n = 0;
|
||||
last_frag = 0;
|
||||
state = RPC_RECORD_MARKER;
|
||||
}
|
||||
|
||||
Contents_RPC::~Contents_RPC()
|
||||
{
|
||||
delete [] msg_buf;
|
||||
}
|
||||
|
||||
void Contents_RPC::Undelivered(int seq, int len, bool orig)
|
||||
{
|
||||
TCP_SupportAnalyzer::Undelivered(seq, len, orig);
|
||||
|
||||
// Re-sync after content gaps.
|
||||
InitBuffer();
|
||||
resync = true;
|
||||
NeedResync();
|
||||
}
|
||||
|
||||
void Contents_RPC::DeliverStream(int len, const u_char* data, bool orig)
|
||||
bool Contents_RPC::CheckResync(int& len, const u_char*& data, bool orig)
|
||||
{
|
||||
TCP_SupportAnalyzer::DeliverStream(len, data, orig);
|
||||
uint32 frame_len;
|
||||
bool last_frag;
|
||||
uint32 xid;
|
||||
uint32 frame_type;
|
||||
|
||||
if ( state == RPC_COMPLETE )
|
||||
InitBuffer();
|
||||
bool discard_this_chunk = false;
|
||||
|
||||
// This is an attempt to re-synchronize the stream with RPC
|
||||
// frames after a content gap. We try to look for the beginning
|
||||
// of an RPC frame, assuming (1) RPC frames begin at packet
|
||||
// boundaries (though they may span over multiple packets) and
|
||||
// (2) the first piece is longer than 12 bytes. (If we see a
|
||||
// piece shorter than 12 bytes, it is likely that it's the
|
||||
// remaining piece of a previous RPC frame, so the code here
|
||||
// skips that piece.) It then checks if the frame type and length
|
||||
// make any sense, and if so, it assumes that is beginning of
|
||||
// a frame.
|
||||
if ( resync && state == RPC_RECORD_MARKER && buf_n == 0 )
|
||||
if ( resync_state == RESYNC_INIT )
|
||||
{
|
||||
// First time CheckResync is called. If the TCP endpoint
|
||||
// is fully established we are in sync (since it's the first chunk
|
||||
// of data after the SYN if its not established we need to
|
||||
// resync.
|
||||
TCP_Analyzer* tcp =
|
||||
static_cast<TCP_ApplicationAnalyzer*>(Parent())->TCP();
|
||||
assert(tcp);
|
||||
|
||||
if ( (IsOrig() ? tcp->OrigState() : tcp->RespState()) !=
|
||||
TCP_ENDPOINT_ESTABLISHED )
|
||||
{
|
||||
NeedResync();
|
||||
}
|
||||
else
|
||||
resync_state = INSYNC;
|
||||
}
|
||||
|
||||
if ( resync_state == INSYNC )
|
||||
return true;
|
||||
|
||||
// This is an attempt to re-synchronize the stream with RPC frames
|
||||
// after a content gap. Returns true if we are in sync. Returns
|
||||
// false otherwise (we are in resync mode)
|
||||
//
|
||||
// We try to look for the beginning of a RPC frame, assuming RPC
|
||||
// frames begin at packet boundaries (though they may span over
|
||||
// multiple packets) (note that the data* of DeliverStream() usually
|
||||
// starts at a packet boundrary).
|
||||
//
|
||||
// If we see a frame start that makes sense (direction and frame
|
||||
// lenght seem ok), we try to read (skip over) the next RPC message.
|
||||
// If this is successfull and we the place we are seems like a valid
|
||||
// start of a RPC msg (direction and frame length seem ok). We assume
|
||||
// that we have successfully resync'ed.
|
||||
|
||||
// Assuming RPC frames align with packet boundaries ...
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
if ( resync_toskip )
|
||||
{
|
||||
if ( DEBUG_rpc_resync )
|
||||
DEBUG_MSG("RPC resync: skipping %d bytes.\n", len);
|
||||
|
||||
// We have some bytes to skip over.
|
||||
if ( resync_toskip < len )
|
||||
{
|
||||
len -= resync_toskip;
|
||||
data += resync_toskip;
|
||||
resync_toskip = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
resync_toskip -= len;
|
||||
data += len;
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( resync_toskip != 0 )
|
||||
// Should never happen.
|
||||
internal_error("RPC resync: skipping over data failed");
|
||||
|
||||
// Now lets see whether data points to the beginning of a RPC
|
||||
// frame. If the resync processs is successful, we should be
|
||||
// at the beginning of a frame.
|
||||
|
||||
|
||||
if ( len < 12 )
|
||||
{
|
||||
// Ignore small fragmeents.
|
||||
// Ignore small chunks.
|
||||
if ( len != 1 && DEBUG_rpc_resync )
|
||||
{
|
||||
// One-byte fragments are likely caused by
|
||||
|
@ -388,116 +494,187 @@ void Contents_RPC::DeliverStream(int len, const u_char* data, bool orig)
|
|||
fmt("RPC resync: discard %d bytes\n",
|
||||
len));
|
||||
}
|
||||
return;
|
||||
|
||||
NeedResync();
|
||||
return false;
|
||||
}
|
||||
|
||||
const u_char* xdata = data;
|
||||
int xlen = len;
|
||||
uint32 frame_len = extract_XDR_uint32(xdata, xlen);
|
||||
uint32 xid = extract_XDR_uint32(xdata, xlen);
|
||||
uint32 frame_type = extract_XDR_uint32(xdata, xlen);
|
||||
|
||||
const u_char *xdata = data;
|
||||
int xlen = len;
|
||||
frame_len = extract_XDR_uint32(xdata, xlen);
|
||||
last_frag = (frame_len & 0x80000000) != 0;
|
||||
frame_len &= 0x7fffffff;
|
||||
xid = extract_XDR_uint32(xdata, xlen);
|
||||
frame_type = extract_XDR_uint32(xdata, xlen);
|
||||
|
||||
// Check if the direction makes sense and the length of the
|
||||
// frame to expect.
|
||||
if ( (IsOrig() && frame_type != 0) ||
|
||||
(! IsOrig() && frame_type != 1) ||
|
||||
frame_len < 16 )
|
||||
|
||||
discard_this_chunk = true;
|
||||
|
||||
// Make sure the frame isn't too long.
|
||||
// TODO: Could possible even reduce this number even further.
|
||||
if ( frame_len > MAX_RPC_LEN )
|
||||
discard_this_chunk = true;
|
||||
|
||||
if ( discard_this_chunk )
|
||||
{
|
||||
// Skip this packet.
|
||||
// Skip this chunk
|
||||
if ( DEBUG_rpc_resync )
|
||||
DEBUG_MSG("RPC resync: Need to resync. dicarding %d bytes.\n", len);
|
||||
|
||||
NeedResync(); // let's try the resync again from the beginning
|
||||
return false;
|
||||
}
|
||||
|
||||
// Looks like we are at the start of a frame and have successfully
|
||||
// extracted the frame length (marker).
|
||||
|
||||
switch (resync_state) {
|
||||
case NEED_RESYNC:
|
||||
case RESYNC_WAIT_FOR_MSG_START:
|
||||
// Initial phase of resyncing. Skip frames until we get a frame
|
||||
// with the last_fragment bit set.
|
||||
resync_toskip = frame_len + 4;
|
||||
|
||||
if ( last_frag )
|
||||
resync_state = RESYNC_WAIT_FOR_FULL_MSG;
|
||||
else
|
||||
resync_state = RESYNC_WAIT_FOR_MSG_START;
|
||||
break;
|
||||
|
||||
case RESYNC_WAIT_FOR_FULL_MSG:
|
||||
// If the resync was successful so far, we should now be the start
|
||||
// of a new RPC message. Try to skip over it.
|
||||
resync_toskip = frame_len + 4;
|
||||
|
||||
if ( last_frag )
|
||||
resync_state = RESYNC_HAD_FULL_MSG;
|
||||
break;
|
||||
|
||||
case RESYNC_HAD_FULL_MSG:
|
||||
// We have now successfully skipped over a full RPC message.
|
||||
// If we got that far, we are in sync.
|
||||
resync_state = INSYNC;
|
||||
|
||||
if ( DEBUG_rpc_resync )
|
||||
DEBUG_MSG("RPC resync: success.\n");
|
||||
return true;
|
||||
|
||||
default:
|
||||
// Should never happen.
|
||||
NeedResync();
|
||||
return false;
|
||||
} // end switch
|
||||
} // end while (len>0)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Contents_RPC::DeliverStream(int len, const u_char* data, bool orig)
|
||||
{
|
||||
DEBUG_MSG("RPC resync: skipping %d bytes\n",
|
||||
len);
|
||||
}
|
||||
return;
|
||||
}
|
||||
TCP_SupportAnalyzer::DeliverStream(len, data, orig);
|
||||
uint32 marker;
|
||||
bool last_frag;
|
||||
|
||||
resync = false;
|
||||
}
|
||||
if ( ! CheckResync(len, data, orig) )
|
||||
return; // Not in sync yet. Still resyncing.
|
||||
|
||||
int n;
|
||||
for ( n = 0; buf_n < buf_len && n < len; ++n )
|
||||
msg_buf[buf_n++] = data[n];
|
||||
// Should be in sync now.
|
||||
|
||||
if ( buf_n < buf_len )
|
||||
// Haven't filled up the message buffer yet, no more to do.
|
||||
return;
|
||||
while (len > 0)
|
||||
{
|
||||
last_time = network_time;
|
||||
|
||||
switch ( state ) {
|
||||
case RPC_RECORD_MARKER:
|
||||
{ // Have the whole record marker.
|
||||
int prev_frag_len = buf_len - 4;
|
||||
const u_char* buf = &msg_buf[prev_frag_len];
|
||||
int n = 4;
|
||||
switch (state) {
|
||||
case WAIT_FOR_MESSAGE:
|
||||
// A new RPC message is starting. Initialize state.
|
||||
|
||||
uint32 marker = extract_XDR_uint32(buf, n);
|
||||
if ( ! buf )
|
||||
// We expect and want 4 bytes of the frame markers.
|
||||
marker_buf.Init(4,4);
|
||||
|
||||
// We want at most 64KB of message data and we don't
|
||||
// know yet how much we expect, so we set expected to
|
||||
// 0.
|
||||
msg_buf.Init(MAX_RPC_LEN, 0);
|
||||
last_frag = 0;
|
||||
state = WAIT_FOR_MARKER;
|
||||
start_time = network_time;
|
||||
// no break. fall through
|
||||
|
||||
case WAIT_FOR_MARKER:
|
||||
{
|
||||
bool got_marker = marker_buf.ConsumeChunk(data,len);
|
||||
|
||||
if ( got_marker )
|
||||
{
|
||||
const u_char *dummy_p = marker_buf.GetBuf();
|
||||
int dummy_len = (int) marker_buf.GetFill();
|
||||
|
||||
// have full marker
|
||||
marker = extract_XDR_uint32(dummy_p, dummy_len);
|
||||
marker_buf.Init(4,4);
|
||||
|
||||
if ( ! dummy_p )
|
||||
{
|
||||
internal_error("inconsistent RPC record marker extraction");
|
||||
|
||||
if ( prev_frag_len > 0 && last_frag )
|
||||
internal_error("last_frag set but more fragments");
|
||||
}
|
||||
|
||||
last_frag = (marker & 0x80000000) != 0;
|
||||
|
||||
marker &= 0x7fffffff;
|
||||
//printf("%.6f %d marker= %u <> last_frag= %d <> expected=%llu <> processed= %llu <> len = %d\n",
|
||||
// network_time, IsOrig(), marker, last_frag, msg_buf.GetExpected(), msg_buf.GetProcessed(), len);
|
||||
|
||||
if ( prev_frag_len > 0 )
|
||||
// We're adding another fragment.
|
||||
marker += prev_frag_len;
|
||||
if ( ! msg_buf.AddToExpected(marker) )
|
||||
Conn()->Weird(fmt("RPC_message_too_long (%" PRId64 ")" , msg_buf.GetExpected()));
|
||||
|
||||
// Fragment length is now given by marker. Sanity-check.
|
||||
if ( marker > MAX_RPC_LEN )
|
||||
{
|
||||
Conn()->Weird("excessive_RPC_len");
|
||||
marker = MAX_RPC_LEN;
|
||||
if ( last_frag )
|
||||
state = WAIT_FOR_LAST_DATA;
|
||||
else
|
||||
state = WAIT_FOR_DATA;
|
||||
}
|
||||
|
||||
// The new size is either the full record size (if this
|
||||
// is the last fragment), or that plus 4 more bytes for
|
||||
// the next fragment header.
|
||||
int new_size = last_frag ? marker : marker + 4;
|
||||
|
||||
u_char* tmp = new u_char[new_size];
|
||||
int msg_len = (unsigned int) buf_len < marker ? buf_len : marker;
|
||||
for ( int i = 0; i < msg_len; ++i )
|
||||
tmp[i] = msg_buf[i];
|
||||
|
||||
delete [] msg_buf;
|
||||
msg_buf = tmp;
|
||||
|
||||
buf_len = marker; // we only want to fill to here
|
||||
buf_n = prev_frag_len; // overwrite this fragment's header
|
||||
|
||||
state = RPC_MESSAGE_BUFFER;
|
||||
}
|
||||
// Else remain in state. Haven't got the full 4 bytes
|
||||
// for the marker yet.
|
||||
break;
|
||||
|
||||
case RPC_MESSAGE_BUFFER:
|
||||
{ // Have the whole fragment.
|
||||
if ( ! last_frag )
|
||||
case WAIT_FOR_DATA:
|
||||
case WAIT_FOR_LAST_DATA:
|
||||
{
|
||||
// We earlier made sure to leave an extra 4 bytes
|
||||
// at the end of the buffer - use them now for
|
||||
// the new fragment header.
|
||||
buf_len += 4;
|
||||
state = RPC_RECORD_MARKER;
|
||||
break;
|
||||
}
|
||||
bool got_all_data = msg_buf.ConsumeChunk(data, len);
|
||||
|
||||
if ( ! interp->DeliverRPC(msg_buf, buf_n, IsOrig()) )
|
||||
if ( got_all_data )
|
||||
{
|
||||
// Got all the data we expected. Now let's
|
||||
// see whether there is another fragment
|
||||
// coming or whether we just finished the
|
||||
// last fragment.
|
||||
if ( state == WAIT_FOR_LAST_DATA )
|
||||
{
|
||||
const u_char *dummy_p = msg_buf.GetBuf();
|
||||
int dummy_len = (int) msg_buf.GetFill();
|
||||
|
||||
if ( ! interp->DeliverRPC(dummy_p, dummy_len, (int)msg_buf.GetExpected(), IsOrig(), start_time, last_time) )
|
||||
Conn()->Weird("partial_RPC");
|
||||
|
||||
state = RPC_COMPLETE;
|
||||
delete [] msg_buf;
|
||||
msg_buf = 0;
|
||||
state = WAIT_FOR_MESSAGE;
|
||||
}
|
||||
else
|
||||
state = WAIT_FOR_MARKER;
|
||||
}
|
||||
// Else remain in state. Haven't read all the data
|
||||
// yet.
|
||||
}
|
||||
break;
|
||||
|
||||
case RPC_COMPLETE:
|
||||
internal_error("RPC state inconsistency");
|
||||
}
|
||||
|
||||
if ( n < len )
|
||||
// More data to munch on.
|
||||
DeliverStream(len - n, data + n, orig);
|
||||
} // end switch
|
||||
} // end while
|
||||
}
|
||||
|
||||
RPC_Analyzer::RPC_Analyzer(AnalyzerTag::Tag tag, Connection* conn,
|
||||
|
@ -520,15 +697,16 @@ void RPC_Analyzer::DeliverPacket(int len, const u_char* data, bool orig,
|
|||
int seq, const IP_Hdr* ip, int caplen)
|
||||
{
|
||||
TCP_ApplicationAnalyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
|
||||
len = min(len, caplen);
|
||||
|
||||
if ( orig )
|
||||
{
|
||||
if ( ! interp->DeliverRPC(data, len, 1) )
|
||||
if ( ! interp->DeliverRPC(data, len, len, 1, network_time, network_time) )
|
||||
Weird("bad_RPC");
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( ! interp->DeliverRPC(data, len, 0) )
|
||||
if ( ! interp->DeliverRPC(data, len, len, 0, network_time, network_time) )
|
||||
Weird("bad_RPC");
|
||||
}
|
||||
}
|
||||
|
@ -537,23 +715,6 @@ void RPC_Analyzer::Done()
|
|||
{
|
||||
TCP_ApplicationAnalyzer::Done();
|
||||
|
||||
// This code was replicated in NFS.cc and Portmap.cc, so we factor
|
||||
// it into here. The semantics have slightly changed - it used
|
||||
// to be we'd always execute interp->Timeout(), but now we only
|
||||
// do for UDP.
|
||||
|
||||
if ( Conn()->ConnTransport() == TRANSPORT_TCP && TCP() )
|
||||
{
|
||||
if ( orig_rpc->State() != RPC_COMPLETE &&
|
||||
(TCP()->OrigState() == TCP_ENDPOINT_CLOSED ||
|
||||
TCP()->OrigPrevState() == TCP_ENDPOINT_CLOSED) &&
|
||||
// Sometimes things like tcpwrappers will immediately
|
||||
// close the connection, without any data having been
|
||||
// transferred. Don't bother flagging these.
|
||||
TCP()->Orig()->Size() > 0 )
|
||||
Weird("partial_RPC_request");
|
||||
}
|
||||
else
|
||||
interp->Timeout();
|
||||
}
|
||||
|
||||
|
@ -562,44 +723,3 @@ void RPC_Analyzer::ExpireTimer(double /* t */)
|
|||
Event(connection_timeout);
|
||||
sessions->Remove(Conn());
|
||||
}
|
||||
|
||||
// The binpac version of interpreter.
|
||||
#include "rpc_pac.h"
|
||||
|
||||
RPC_UDP_Analyzer_binpac::RPC_UDP_Analyzer_binpac(Connection* conn)
|
||||
: Analyzer(AnalyzerTag::RPC_UDP_BINPAC, conn)
|
||||
{
|
||||
interp = new binpac::SunRPC::RPC_Conn(this);
|
||||
ADD_ANALYZER_TIMER(&RPC_UDP_Analyzer_binpac::ExpireTimer,
|
||||
network_time + rpc_timeout, 1, TIMER_RPC_EXPIRE);
|
||||
}
|
||||
|
||||
RPC_UDP_Analyzer_binpac::~RPC_UDP_Analyzer_binpac()
|
||||
{
|
||||
delete interp;
|
||||
}
|
||||
|
||||
void RPC_UDP_Analyzer_binpac::Done()
|
||||
{
|
||||
Analyzer::Done();
|
||||
interp->Timeout();
|
||||
}
|
||||
|
||||
void RPC_UDP_Analyzer_binpac::DeliverPacket(int len, const u_char* data, bool orig, int seq, const IP_Hdr* ip, int caplen)
|
||||
{
|
||||
Analyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
|
||||
try
|
||||
{
|
||||
interp->NewData(orig, data, data + len);
|
||||
}
|
||||
catch ( binpac::Exception &e )
|
||||
{
|
||||
Weird(fmt("bad_RPC: %s", e.msg().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void RPC_UDP_Analyzer_binpac::ExpireTimer(double /* t */)
|
||||
{
|
||||
Event(connection_timeout);
|
||||
sessions->Remove(Conn());
|
||||
}
|
||||
|
|
148
src/RPC.h
148
src/RPC.h
|
@ -49,7 +49,8 @@ enum {
|
|||
|
||||
class RPC_CallInfo {
|
||||
public:
|
||||
RPC_CallInfo(uint32 xid, const u_char*& buf, int& n);
|
||||
RPC_CallInfo(uint32 xid, const u_char*& buf, int& n, double start_time,
|
||||
double last_time, int rpc_len);
|
||||
~RPC_CallInfo();
|
||||
|
||||
void AddVal(Val* arg_v) { Unref(v); v = arg_v; }
|
||||
|
@ -63,7 +64,11 @@ public:
|
|||
uint32 Proc() const { return proc; }
|
||||
|
||||
double StartTime() const { return start_time; }
|
||||
void SetStartTime(double t) { start_time = t; }
|
||||
double LastTime() const { return last_time; }
|
||||
void SetLastTime(double t) { last_time = t; }
|
||||
int CallLen() const { return call_n; }
|
||||
int RPCLen() const { return rpc_len; }
|
||||
int HeaderLen() const { return header_len; }
|
||||
|
||||
uint32 XID() const { return xid; }
|
||||
|
@ -76,6 +81,8 @@ protected:
|
|||
uint32 cred_flavor, verf_flavor;
|
||||
u_char* call_buf; // copy of original call buffer
|
||||
double start_time;
|
||||
double last_time;
|
||||
int rpc_len; // size of the full RPC call, incl. xid and msg_type
|
||||
int call_n; // size of call buf
|
||||
int header_len; // size of data before the arguments
|
||||
bool valid_call; // whether call was well-formed
|
||||
|
@ -93,19 +100,19 @@ public:
|
|||
// Delivers the given RPC. Returns true if "len" bytes were
|
||||
// enough, false otherwise. "is_orig" is true if the data is
|
||||
// from the originator of the connection.
|
||||
int DeliverRPC(const u_char* data, int len, int is_orig);
|
||||
int DeliverRPC(const u_char* data, int len, int caplen, int is_orig, double start_time, double last_time);
|
||||
|
||||
void Timeout();
|
||||
|
||||
protected:
|
||||
virtual int RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n) = 0;
|
||||
virtual int RPC_BuildReply(const RPC_CallInfo* c, int success,
|
||||
const u_char*& buf, int& n,
|
||||
EventHandlerPtr& event, Val*& reply) = 0;
|
||||
virtual int RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status success,
|
||||
const u_char*& buf, int& n, double start_time, double last_time,
|
||||
int reply_len) = 0;
|
||||
|
||||
virtual void Event(EventHandlerPtr f, Val* request, int status, Val* reply) = 0;
|
||||
|
||||
void RPC_Event(RPC_CallInfo* c, int status, int reply_len);
|
||||
void Event_RPC_Dialogue(RPC_CallInfo* c, BifEnum::rpc_status status, int reply_len);
|
||||
void Event_RPC_Call(RPC_CallInfo* c);
|
||||
void Event_RPC_Reply(uint32_t xid, BifEnum::rpc_status status, int reply_len);
|
||||
|
||||
void Weird(const char* name);
|
||||
|
||||
|
@ -113,35 +120,108 @@ protected:
|
|||
Analyzer* analyzer;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
RPC_RECORD_MARKER, // building up the stream record marker
|
||||
RPC_MESSAGE_BUFFER, // building up the message in the buffer
|
||||
RPC_COMPLETE // message fully built
|
||||
} TCP_RPC_state;
|
||||
|
||||
/* A simple buffer for reassembling the fragments that RPC-over-TCP
|
||||
* uses. Only needed by RPC_Contents.
|
||||
|
||||
* However, RPC messages can be quite large. As a first step, we only
|
||||
* extract and analyzer the first part of an RPC message and skip
|
||||
* over the rest.
|
||||
*
|
||||
* We specify:
|
||||
* maxsize: the number of bytes we want to copy into the buffer to analyze.
|
||||
* expected: the total number of bytes in the RPC message. Can be
|
||||
* quite large. We will be "skipping over" expected-maxsize bytes.
|
||||
*
|
||||
* We can extend "expected" (by calling AddToExpected()), but maxsize is
|
||||
* fixed.
|
||||
*
|
||||
* TODO: grow buffer dynamically
|
||||
*/
|
||||
class RPC_Reasm_Buffer {
|
||||
public:
|
||||
RPC_Reasm_Buffer() {
|
||||
maxsize = expected = 0;
|
||||
fill = processed = 0;
|
||||
buf = 0;
|
||||
};
|
||||
|
||||
~RPC_Reasm_Buffer() { if (buf) delete [] buf; }
|
||||
|
||||
void Init(int64_t arg_maxsize, int64_t arg_expected);
|
||||
|
||||
const u_char *GetBuf() { return buf; } // Pointer to the buffer
|
||||
int64_t GetFill() { return fill; } // Number of bytes in buf
|
||||
int64_t GetSkipped() { return processed-fill; } // How many bytes did we skipped?
|
||||
int64_t GetExpected() { return expected; } // How many bytes are we expecting?
|
||||
int64_t GetProcessed() { return processed; } // How many bytes are we expecting?
|
||||
|
||||
// Expand expected by delta bytes. Returns false if the number of
|
||||
// expected bytes exceeds maxsize (which means that we will truncate
|
||||
// the message).
|
||||
bool AddToExpected(int64_t delta)
|
||||
{ expected += delta; return ! (expected > maxsize); }
|
||||
|
||||
// Consume a chunk of input data (pointed to by data, up len in
|
||||
// size). data and len will be adjusted accordingly. Returns true if
|
||||
// "expected" bytes have been processed, i.e., returns true when we
|
||||
// don't expect any more data.
|
||||
bool ConsumeChunk(const u_char*& data, int& len);
|
||||
|
||||
protected:
|
||||
int64_t fill; // how many bytes we currently have in the buffer
|
||||
int64_t maxsize; // maximum buffer size we want to allocate
|
||||
int64_t processed; // number of bytes we have processed so far
|
||||
int64_t expected; // number of input bytes we expect
|
||||
u_char *buf;
|
||||
|
||||
};
|
||||
|
||||
/* Support Analyzer for reassembling RPC-over-TCP messages */
|
||||
class Contents_RPC : public TCP_SupportAnalyzer {
|
||||
public:
|
||||
Contents_RPC(Connection* conn, bool orig, RPC_Interpreter* interp);
|
||||
virtual ~Contents_RPC();
|
||||
|
||||
TCP_RPC_state State() const { return state; }
|
||||
|
||||
protected:
|
||||
typedef enum {
|
||||
WAIT_FOR_MESSAGE,
|
||||
WAIT_FOR_MARKER,
|
||||
WAIT_FOR_DATA,
|
||||
WAIT_FOR_LAST_DATA,
|
||||
} state_t;
|
||||
|
||||
typedef enum {
|
||||
NEED_RESYNC,
|
||||
RESYNC_WAIT_FOR_MSG_START,
|
||||
RESYNC_WAIT_FOR_FULL_MSG,
|
||||
RESYNC_HAD_FULL_MSG,
|
||||
INSYNC,
|
||||
RESYNC_INIT,
|
||||
} resync_state_t;
|
||||
|
||||
virtual void Init();
|
||||
virtual bool CheckResync(int& len, const u_char*& data, bool orig);
|
||||
virtual void DeliverStream(int len, const u_char* data, bool orig);
|
||||
virtual void Undelivered(int seq, int len, bool orig);
|
||||
|
||||
virtual void InitBuffer();
|
||||
virtual void NeedResync() {
|
||||
resync_state = NEED_RESYNC;
|
||||
resync_toskip = 0;
|
||||
state = WAIT_FOR_MESSAGE;
|
||||
}
|
||||
|
||||
RPC_Interpreter* interp;
|
||||
|
||||
u_char* msg_buf;
|
||||
int buf_n; // number of bytes in msg_buf
|
||||
int buf_len; // size off msg_buf
|
||||
int last_frag; // if this buffer corresponds to the last "fragment"
|
||||
bool resync;
|
||||
RPC_Reasm_Buffer marker_buf; // reassembles the 32bit RPC-over-TCP marker
|
||||
RPC_Reasm_Buffer msg_buf; // reassembles RPC messages
|
||||
state_t state;
|
||||
|
||||
TCP_RPC_state state;
|
||||
double start_time;
|
||||
double last_time;
|
||||
|
||||
resync_state_t resync_state;
|
||||
int resync_toskip;
|
||||
};
|
||||
|
||||
class RPC_Analyzer : public TCP_ApplicationAnalyzer {
|
||||
|
@ -164,28 +244,4 @@ protected:
|
|||
Contents_RPC* resp_rpc;
|
||||
};
|
||||
|
||||
#include "rpc_pac.h"
|
||||
|
||||
class RPC_UDP_Analyzer_binpac : public Analyzer {
|
||||
public:
|
||||
RPC_UDP_Analyzer_binpac(Connection* conn);
|
||||
virtual ~RPC_UDP_Analyzer_binpac();
|
||||
|
||||
virtual void Done();
|
||||
virtual void DeliverPacket(int len, const u_char* data, bool orig,
|
||||
int seq, const IP_Hdr* ip, int caplen);
|
||||
|
||||
static Analyzer* InstantiateAnalyzer(Connection* conn)
|
||||
{ return new RPC_UDP_Analyzer_binpac(conn); }
|
||||
|
||||
static bool Available()
|
||||
{ return pm_request || rpc_call; }
|
||||
|
||||
protected:
|
||||
friend class AnalyzerTimer;
|
||||
void ExpireTimer(double t);
|
||||
|
||||
binpac::SunRPC::RPC_Conn* interp;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "Reassem.h"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// $Id: RuleMatcher.cc 6724 2009-06-07 09:23:03Z vern $
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "Analyzer.h"
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "NetVar.h"
|
||||
#include "PIA.h"
|
||||
#include "File.h"
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// $Id: TCP_Reassembler.cc,v 1.1.2.8 2006/05/31 01:52:02 sommer Exp $
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Analyzer.h"
|
||||
#include "TCP_Reassembler.h"
|
||||
#include "TCP.h"
|
||||
|
@ -218,8 +220,9 @@ void TCP_Reassembler::Undelivered(int up_to_seq)
|
|||
// handshakes, but Oh Well.
|
||||
|
||||
if ( content_gap &&
|
||||
endpoint->state == TCP_ENDPOINT_ESTABLISHED &&
|
||||
peer->state == TCP_ENDPOINT_ESTABLISHED )
|
||||
(BifConst::report_gaps_for_partial ||
|
||||
(endpoint->state == TCP_ENDPOINT_ESTABLISHED &&
|
||||
peer->state == TCP_ENDPOINT_ESTABLISHED ) ) )
|
||||
{
|
||||
val_list* vl = new val_list;
|
||||
vl->append(dst_analyzer->BuildConnVal());
|
||||
|
@ -487,8 +490,9 @@ void TCP_Reassembler::AckReceived(int seq)
|
|||
return;
|
||||
|
||||
bool test_active = ! skip_deliveries && ! tcp_analyzer->Skipping() &&
|
||||
endp->state == TCP_ENDPOINT_ESTABLISHED &&
|
||||
endp->peer->state == TCP_ENDPOINT_ESTABLISHED;
|
||||
( BifConst::report_gaps_for_partial ||
|
||||
(endp->state == TCP_ENDPOINT_ESTABLISHED &&
|
||||
endp->peer->state == TCP_ENDPOINT_ESTABLISHED ) );
|
||||
|
||||
int num_missing = TrimToSeq(seq);
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "Net.h"
|
||||
|
|
36
src/XDR.cc
36
src/XDR.cc
|
@ -2,6 +2,8 @@
|
|||
//
|
||||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "XDR.h"
|
||||
|
@ -26,18 +28,18 @@ uint32 extract_XDR_uint32(const u_char*& buf, int& len)
|
|||
return bits32;
|
||||
}
|
||||
|
||||
double extract_XDR_uint64_as_double(const u_char*& buf, int& len)
|
||||
uint64 extract_XDR_uint64(const u_char*& buf, int& len)
|
||||
{
|
||||
if ( ! buf || len < 8 )
|
||||
{
|
||||
buf = 0;
|
||||
return 0.0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 uhi = extract_XDR_uint32(buf, len);
|
||||
uint32 ulo = extract_XDR_uint32(buf, len);
|
||||
uint64 uhi = extract_XDR_uint32(buf, len);
|
||||
uint64 ulo = extract_XDR_uint32(buf, len);
|
||||
|
||||
return double(uhi) * 4294967296.0 + double(ulo);
|
||||
return (uhi << 32) + ulo;
|
||||
}
|
||||
|
||||
double extract_XDR_time(const u_char*& buf, int& len)
|
||||
|
@ -54,12 +56,15 @@ double extract_XDR_time(const u_char*& buf, int& len)
|
|||
return double(uhi) + double(ulo) / 1e9;
|
||||
}
|
||||
|
||||
const u_char* extract_XDR_opaque(const u_char*& buf, int& len, int& n, int max_len)
|
||||
const u_char* extract_XDR_opaque(const u_char*& buf, int& len, int& n, int max_len, bool short_buf_ok)
|
||||
{
|
||||
n = int(extract_XDR_uint32(buf, len));
|
||||
if ( ! buf )
|
||||
return 0;
|
||||
|
||||
if ( short_buf_ok )
|
||||
n = std::min(n, len);
|
||||
|
||||
if ( n < 0 || n > len || n > max_len )
|
||||
{ // ### Should really flag this as a different sort of error.
|
||||
buf = 0;
|
||||
|
@ -75,6 +80,25 @@ const u_char* extract_XDR_opaque(const u_char*& buf, int& len, int& n, int max_l
|
|||
return opaque;
|
||||
}
|
||||
|
||||
const u_char* extract_XDR_opaque_fixed(const u_char*& buf, int& len, int n)
|
||||
{
|
||||
if ( ! buf )
|
||||
return 0;
|
||||
if ( n < 0 || n > len)
|
||||
{
|
||||
buf = 0;
|
||||
return 0;
|
||||
}
|
||||
int n4 = ((n + 3) >> 2) << 2; // n rounded up to next multiple of 4
|
||||
|
||||
len -= n4;
|
||||
const u_char* opaque = buf;
|
||||
buf += n4;
|
||||
|
||||
return opaque;
|
||||
}
|
||||
|
||||
|
||||
uint32 skip_XDR_opaque_auth(const u_char*& buf, int& len)
|
||||
{
|
||||
uint32 auth_flavor = extract_XDR_uint32(buf, len);
|
||||
|
|
|
@ -11,10 +11,11 @@
|
|||
#include "util.h"
|
||||
|
||||
extern uint32 extract_XDR_uint32(const u_char*& buf, int& len);
|
||||
extern double extract_XDR_uint64_as_double(const u_char*& buf, int& len);
|
||||
extern uint64 extract_XDR_uint64(const u_char*& buf, int& len);
|
||||
extern double extract_XDR_time(const u_char*& buf, int& len);
|
||||
extern const u_char* extract_XDR_opaque(const u_char*& buf, int& len,
|
||||
int& n, int max_len=8192);
|
||||
int& n, int max_len=8192, bool short_buf_ok=false);
|
||||
extern const u_char* extract_XDR_opaque_fixed(const u_char*& buf, int& len, int n);
|
||||
extern uint32 skip_XDR_opaque_auth(const u_char*& buf, int& len);
|
||||
|
||||
#endif
|
||||
|
|
90
src/bro.bif
90
src/bro.bif
|
@ -1348,7 +1348,7 @@ function fmt_ftp_port%(a: addr, p: port%): string
|
|||
function decode_netbios_name%(name: string%): string
|
||||
%{
|
||||
char buf[16];
|
||||
char result[32];
|
||||
char result[16];
|
||||
const u_char* s = name->Bytes();
|
||||
int i, j;
|
||||
|
||||
|
@ -1370,7 +1370,7 @@ function decode_netbios_name%(name: string%): string
|
|||
break;
|
||||
}
|
||||
|
||||
return new StringVal(result);
|
||||
return new StringVal(i, result);
|
||||
%}
|
||||
|
||||
function decode_netbios_name_type%(name: string%): count
|
||||
|
@ -3453,3 +3453,89 @@ function x509_err2str%(err_num: count%): string
|
|||
%{
|
||||
return new StringVal(X509_verify_cert_error_string(err_num));
|
||||
%}
|
||||
|
||||
function NFS3::mode2string%(mode: count%): string
|
||||
%{
|
||||
char str[12];
|
||||
char *p = str;
|
||||
|
||||
/* usr */
|
||||
if (mode & S_IRUSR)
|
||||
*p++ = 'r';
|
||||
else
|
||||
*p++ = '-';
|
||||
|
||||
if (mode & S_IWUSR)
|
||||
*p++ = 'w';
|
||||
else
|
||||
*p++ = '-';
|
||||
|
||||
switch (mode & (S_IXUSR | S_ISUID)) {
|
||||
case 0:
|
||||
*p++ = '-';
|
||||
break;
|
||||
case S_IXUSR:
|
||||
*p++ = 'x';
|
||||
break;
|
||||
case S_ISUID:
|
||||
*p++ = 'S';
|
||||
break;
|
||||
case S_IXUSR | S_ISUID:
|
||||
*p++ = 's';
|
||||
break;
|
||||
}
|
||||
|
||||
/* group */
|
||||
if (mode & S_IRGRP)
|
||||
*p++ = 'r';
|
||||
else
|
||||
*p++ = '-';
|
||||
if (mode & S_IWGRP)
|
||||
*p++ = 'w';
|
||||
else
|
||||
*p++ = '-';
|
||||
|
||||
switch (mode & (S_IXGRP | S_ISGID)) {
|
||||
case 0:
|
||||
*p++ = '-';
|
||||
break;
|
||||
case S_IXGRP:
|
||||
*p++ = 'x';
|
||||
break;
|
||||
case S_ISGID:
|
||||
*p++ = 'S';
|
||||
break;
|
||||
case S_IXGRP | S_ISGID:
|
||||
*p++ = 's';
|
||||
break;
|
||||
}
|
||||
|
||||
/* other */
|
||||
if (mode & S_IROTH)
|
||||
*p++ = 'r';
|
||||
else
|
||||
*p++ = '-';
|
||||
if (mode & S_IWOTH)
|
||||
*p++ = 'w';
|
||||
else
|
||||
*p++ = '-';
|
||||
|
||||
switch (mode & (S_IXOTH | S_ISVTX)) {
|
||||
case 0:
|
||||
*p++ = '-';
|
||||
break;
|
||||
case S_IXOTH:
|
||||
*p++ = 'x';
|
||||
break;
|
||||
case S_ISVTX:
|
||||
*p++ = 'T';
|
||||
break;
|
||||
case S_IXOTH | S_ISVTX:
|
||||
*p++ = 't';
|
||||
break;
|
||||
}
|
||||
|
||||
*p = '\0';
|
||||
|
||||
return new StringVal(str);
|
||||
%}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
# $Id: const.bif 3929 2007-01-14 00:37:59Z vern $
|
||||
|
||||
# Documentation and default values for these are located in policy/bro.dif.
|
||||
# Documentation and default values for these are located in policy/bro.init.
|
||||
|
||||
const ignore_keep_alive_rexmit: bool;
|
||||
const skip_http_data: bool;
|
||||
const parse_udp_tunnels: bool;
|
||||
const use_conn_size_analyzer: bool;
|
||||
const report_gaps_for_partial: bool;
|
||||
|
||||
const NFS3::return_data: bool;
|
||||
const NFS3::return_data_max: count;
|
||||
const NFS3::return_data_first_only: bool;
|
||||
|
||||
|
|
|
@ -154,7 +154,13 @@ event mime_all_data%(c: connection, length: count, data: string%);
|
|||
event mime_event%(c: connection, event_type: string, detail: string%);
|
||||
event mime_content_hash%(c: connection, content_len: count, hash_value: string%);
|
||||
|
||||
event rpc_call%(c: connection, prog: count, ver: count, proc: count, status: count, start_time: time, call_len: count, reply_len: count%);
|
||||
# Generated for each RPC request / reply *pair* (if there is no reply, the event
|
||||
# will be generated on timeout).
|
||||
event rpc_dialogue%(c: connection, prog: count, ver: count, proc: count, status: rpc_status, start_time: time, call_len: count, reply_len: count%);
|
||||
# Generated for each (correctly formed) RPC_CALL message received.
|
||||
event rpc_call%(c: connection, xid: count, prog: count, ver: count, proc: count, call_len: count%);
|
||||
# Generated for each (correctly formed) RPC_REPLY message received.
|
||||
event rpc_reply%(c: connection, xid: count, status: rpc_status, reply_len: count%);
|
||||
|
||||
event pm_request_null%(r: connection%);
|
||||
event pm_request_set%(r: connection, m: pm_mapping, success: bool%);
|
||||
|
@ -170,15 +176,28 @@ event pm_attempt_dump%(r: connection, status: rpc_status%);
|
|||
event pm_attempt_callit%(r: connection, status: rpc_status, call: pm_callit_request%);
|
||||
event pm_bad_port%(r: connection, bad_p: count%);
|
||||
|
||||
event nfs_request_null%(n: connection%);
|
||||
event nfs_request_getattr%(n: connection, fh: string, attrs: nfs3_attrs%);
|
||||
event nfs_request_lookup%(n: connection, req: nfs3_lookup_args, rep: nfs3_lookup_reply%);
|
||||
event nfs_request_fsstat%(n: connection, root_fh: string, stat: nfs3_fsstat%);
|
||||
event nfs_attempt_null%(n: connection, status: count%);
|
||||
event nfs_attempt_getattr%(n: connection, status: count, fh: string%);
|
||||
event nfs_attempt_lookup%(n: connection, status: count, req: nfs3_lookup_args%);
|
||||
event nfs_attempt_fsstat%(n: connection, status: count, root_fh: string%);
|
||||
event nfs_reply_status%(n: connection, status: count%);
|
||||
# Events for the NFS analyzer. An event is generated if we have received a
|
||||
# Call (request) / Response pair (or in case of a time out). info$rpc_stat and
|
||||
# info$nfs_stat show whether the request was successful. The request record is
|
||||
# always filled out, however, the reply record(s) might not be set or might only
|
||||
# be partially set. See the comments for the record types in bro.init to see which
|
||||
# reply fields are set when.
|
||||
event nfs_proc_null%(c: connection, info: NFS3::info_t%);
|
||||
event nfs_proc_not_implemented%(c: connection, info: NFS3::info_t, proc: NFS3::proc_t%);
|
||||
|
||||
event nfs_proc_getattr%(c: connection, info: NFS3::info_t, fh: string, attrs: NFS3::fattr_t%);
|
||||
event nfs_proc_lookup%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::lookup_reply_t%);
|
||||
event nfs_proc_read%(c: connection, info: NFS3::info_t, req: NFS3::readargs_t, rep: NFS3::read_reply_t%);
|
||||
event nfs_proc_readlink%(c: connection, info: NFS3::info_t, fh: string, rep: NFS3::readlink_reply_t%);
|
||||
event nfs_proc_write%(c: connection, info: NFS3::info_t, req: NFS3::writeargs_t, rep: NFS3::write_reply_t%);
|
||||
event nfs_proc_create%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::newobj_reply_t%);
|
||||
event nfs_proc_mkdir%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::newobj_reply_t%);
|
||||
event nfs_proc_remove%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::delobj_reply_t%);
|
||||
event nfs_proc_rmdir%(c: connection, info: NFS3::info_t, req: NFS3::diropargs_t, rep: NFS3::delobj_reply_t%);
|
||||
event nfs_proc_readdir%(c: connection, info: NFS3::info_t, req: NFS3::readdirargs_t, rep: NFS3::readdir_reply_t%);
|
||||
|
||||
# Generated for each NFS reply message we receive, giving just gives the status.
|
||||
event nfs_reply_status%(n: connection, info: NFS3::info_t%);
|
||||
|
||||
event ntp_message%(u: connection, msg: ntp_msg, excess: string%);
|
||||
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
# $Id:$
|
||||
|
||||
%include portmap-protocol.pac
|
||||
|
||||
# Add a function to the hook in the RPC connection to build the call Val.
|
||||
refine casefunc RPC_BuildCallVal += {
|
||||
RPC_SERVICE_PORTMAP ->
|
||||
PortmapBuildCallVal(call, call.params.portmap);
|
||||
};
|
||||
|
||||
# ... and a function invocation to handle successful portmap replies.
|
||||
refine typeattr PortmapResults += &let {
|
||||
action_reply: bool = $context.connection.ProcessPortmapReply(this);
|
||||
};
|
||||
|
||||
# ... and a function to handle failed portmap calls.
|
||||
refine casefunc RPC_CallFailed += {
|
||||
RPC_SERVICE_PORTMAP -> PortmapCallFailed(connection, call, status);
|
||||
};
|
||||
|
||||
# Build portmap call Val.
|
||||
function PortmapBuildCallVal(call: RPC_Call, params: PortmapParams): BroVal =
|
||||
case call.proc of {
|
||||
PMAPPROC_NULL, PMAPPROC_DUMP
|
||||
-> NULL;
|
||||
PMAPPROC_SET, PMAPPROC_UNSET
|
||||
-> PortmapBuildMappingVal(params.mapping);
|
||||
PMAPPROC_GETPORT
|
||||
-> PortmapBuildPortRequest(params.mapping);
|
||||
PMAPPROC_CALLIT
|
||||
-> PortmapBuildCallItVal(params.callit);
|
||||
};
|
||||
|
||||
function PortmapBuildPortVal(port: uint32, proto: uint32): BroPortVal
|
||||
%{
|
||||
// TODO: replace port with CheckPort(port)
|
||||
return new PortVal(port, proto == IPPROTO_TCP ?
|
||||
TRANSPORT_TCP : TRANSPORT_UDP);
|
||||
%}
|
||||
|
||||
function PortmapBuildMappingVal(params: PortmapMapping): BroVal
|
||||
%{
|
||||
RecordVal* mapping = new RecordVal(pm_mapping);
|
||||
|
||||
mapping->Assign(0, new Val(params->prog(), TYPE_COUNT));
|
||||
mapping->Assign(1, new Val(params->vers(), TYPE_COUNT));
|
||||
mapping->Assign(2, PortmapBuildPortVal(params->port(),
|
||||
params->proto()));
|
||||
|
||||
return mapping;
|
||||
%}
|
||||
|
||||
function PortmapBuildPortRequest(params: PortmapMapping): BroVal
|
||||
%{
|
||||
RecordVal* request = new RecordVal(pm_port_request);
|
||||
|
||||
request->Assign(0, new Val(params->prog(), TYPE_COUNT));
|
||||
request->Assign(1, new Val(params->vers(), TYPE_COUNT));
|
||||
request->Assign(2, new Val(params->proto() == IPPROTO_TCP, TYPE_BOOL));
|
||||
|
||||
return request;
|
||||
%}
|
||||
|
||||
function PortmapBuildCallItVal(params: PortmapCallItParams): BroVal
|
||||
%{
|
||||
RecordVal* c = new RecordVal(pm_callit_request);
|
||||
|
||||
c->Assign(0, new Val(params->prog(), TYPE_COUNT));
|
||||
c->Assign(1, new Val(params->vers(), TYPE_COUNT));
|
||||
c->Assign(2, new Val(params->proc(), TYPE_COUNT));
|
||||
c->Assign(3, new Val(params->params()->length(), TYPE_COUNT));
|
||||
|
||||
return c;
|
||||
%}
|
||||
|
||||
function PortmapBuildDumpVal(params: PortmapDumpResults): BroVal
|
||||
%{
|
||||
TableVal* mappings = new TableVal(pm_mappings);
|
||||
|
||||
for ( int i = 0; i < params->size(); ++i )
|
||||
{
|
||||
// The last element has cont()!=1 and this element doesn't contain a
|
||||
// mapping.
|
||||
if ((*params)[i]->cont() != 1)
|
||||
continue;
|
||||
Val* m = PortmapBuildMappingVal((*params)[i]->mapping());
|
||||
Val* index = new Val(i + 1, TYPE_COUNT);
|
||||
mappings->Assign(index, m);
|
||||
Unref(index);
|
||||
}
|
||||
|
||||
return mappings;
|
||||
%}
|
||||
|
||||
refine connection RPC_Conn += {
|
||||
function ProcessPortmapReply(results: PortmapResults): bool
|
||||
%{
|
||||
RPC_Call const* call = results->call();
|
||||
PortmapParams const* params = call->params()->portmap();
|
||||
|
||||
switch ( call->proc() ) {
|
||||
case PMAPPROC_NULL:
|
||||
BifEvent::generate_pm_request_null(bro_analyzer(), bro_analyzer()->Conn());
|
||||
break;
|
||||
|
||||
case PMAPPROC_SET:
|
||||
BifEvent::generate_pm_request_set(bro_analyzer(),
|
||||
bro_analyzer()->Conn(),
|
||||
call->call_val(), results->set());
|
||||
break;
|
||||
|
||||
case PMAPPROC_UNSET:
|
||||
BifEvent::generate_pm_request_unset(bro_analyzer(),
|
||||
bro_analyzer()->Conn(),
|
||||
call->call_val(), results->unset());
|
||||
break;
|
||||
|
||||
case PMAPPROC_GETPORT:
|
||||
BifEvent::generate_pm_request_getport(bro_analyzer(),
|
||||
bro_analyzer()->Conn(),
|
||||
call->call_val(),
|
||||
PortmapBuildPortVal(results->getport(),
|
||||
params->mapping()->proto()));
|
||||
break;
|
||||
|
||||
case PMAPPROC_DUMP:
|
||||
BifEvent::generate_pm_request_dump(bro_analyzer(),
|
||||
bro_analyzer()->Conn(),
|
||||
PortmapBuildDumpVal(results->dump()));
|
||||
break;
|
||||
|
||||
case PMAPPROC_CALLIT:
|
||||
BifEvent::generate_pm_request_callit(bro_analyzer(),
|
||||
bro_analyzer()->Conn(),
|
||||
call->call_val(),
|
||||
new PortVal(results->callit()->port(),
|
||||
TRANSPORT_UDP));
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
%}
|
||||
};
|
||||
|
||||
function PortmapCallFailed(connection: RPC_Conn,
|
||||
call: RPC_Call,
|
||||
status: EnumRPCStatus): bool
|
||||
%{
|
||||
// BifEnum::rpc_status st = static_cast<BifEnum::rpc_status>(status);
|
||||
Val *st = new EnumVal(status, BifType::Enum::rpc_status);
|
||||
|
||||
switch ( call->proc() ) {
|
||||
case PMAPPROC_NULL:
|
||||
BifEvent::generate_pm_attempt_null(connection->bro_analyzer(),
|
||||
connection->bro_analyzer()->Conn(), st);
|
||||
break;
|
||||
|
||||
case PMAPPROC_SET:
|
||||
BifEvent::generate_pm_attempt_set(connection->bro_analyzer(),
|
||||
connection->bro_analyzer()->Conn(), st, call->call_val());
|
||||
break;
|
||||
|
||||
case PMAPPROC_UNSET:
|
||||
BifEvent::generate_pm_attempt_unset(connection->bro_analyzer(),
|
||||
connection->bro_analyzer()->Conn(), st, call->call_val());
|
||||
break;
|
||||
|
||||
case PMAPPROC_GETPORT:
|
||||
BifEvent::generate_pm_attempt_getport(connection->bro_analyzer(),
|
||||
connection->bro_analyzer()->Conn(), st, call->call_val());
|
||||
break;
|
||||
|
||||
case PMAPPROC_DUMP:
|
||||
BifEvent::generate_pm_attempt_dump(connection->bro_analyzer(),
|
||||
connection->bro_analyzer()->Conn(), st);
|
||||
break;
|
||||
|
||||
case PMAPPROC_CALLIT:
|
||||
BifEvent::generate_pm_attempt_callit(connection->bro_analyzer(),
|
||||
connection->bro_analyzer()->Conn(), st, call->call_val());
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
%}
|
|
@ -1,77 +0,0 @@
|
|||
# $Id:$
|
||||
|
||||
# Extends rpc-protocol.pac.
|
||||
|
||||
###################
|
||||
# Hooks into RPC data structures.
|
||||
|
||||
refine casefunc RPC_Service += {
|
||||
100000 -> RPC_SERVICE_PORTMAP;
|
||||
};
|
||||
|
||||
refine casetype RPC_Params += {
|
||||
RPC_SERVICE_PORTMAP -> portmap: PortmapParams(call);
|
||||
};
|
||||
|
||||
refine casetype RPC_Results += {
|
||||
RPC_SERVICE_PORTMAP -> portmap: PortmapResults(call);
|
||||
};
|
||||
|
||||
###################
|
||||
# Portmap types.
|
||||
|
||||
enum PortmapProc {
|
||||
PMAPPROC_NULL = 0,
|
||||
PMAPPROC_SET = 1,
|
||||
PMAPPROC_UNSET = 2,
|
||||
PMAPPROC_GETPORT = 3,
|
||||
PMAPPROC_DUMP = 4,
|
||||
PMAPPROC_CALLIT = 5,
|
||||
};
|
||||
|
||||
type PortmapParams(call: RPC_Call) = case call.proc of {
|
||||
PMAPPROC_NULL -> null: empty;
|
||||
PMAPPROC_SET, PMAPPROC_UNSET, PMAPPROC_GETPORT
|
||||
-> mapping: PortmapMapping;
|
||||
PMAPPROC_DUMP -> dump: empty;
|
||||
PMAPPROC_CALLIT -> callit: PortmapCallItParams;
|
||||
};
|
||||
|
||||
type PortmapResults(call: RPC_Call) = case call.proc of {
|
||||
PMAPPROC_NULL -> null: empty;
|
||||
PMAPPROC_SET -> set: uint32;
|
||||
PMAPPROC_UNSET -> unset: uint32;
|
||||
PMAPPROC_GETPORT -> getport: uint32;
|
||||
PMAPPROC_DUMP -> dump: PortmapDumpResults;
|
||||
PMAPPROC_CALLIT -> callit: PortmapCallItResults;
|
||||
};
|
||||
|
||||
type PortmapMapping = record {
|
||||
prog: uint32;
|
||||
vers: uint32;
|
||||
proto: uint32;
|
||||
port: uint32;
|
||||
};
|
||||
|
||||
type PortmapCallItParams = record {
|
||||
prog: uint32;
|
||||
vers: uint32;
|
||||
proc: uint32;
|
||||
params: RPC_Opaque; # TODO: parse params
|
||||
};
|
||||
|
||||
type PortmapDumpEntry = record {
|
||||
cont: uint32;
|
||||
optmapping: case cont of {
|
||||
0 -> none: empty;
|
||||
default -> mapping: PortmapMapping;
|
||||
};
|
||||
};
|
||||
|
||||
# The final element that has cont!=1 will be included in the array.
|
||||
type PortmapDumpResults = PortmapDumpEntry[] &until($element.cont != 1);
|
||||
|
||||
type PortmapCallItResults = record {
|
||||
port: uint32;
|
||||
results: RPC_Opaque; # TODO: parse results
|
||||
};
|
|
@ -1,196 +0,0 @@
|
|||
# $Id:$
|
||||
|
||||
########################################
|
||||
# Protocol Syntax.
|
||||
|
||||
%include rpc-protocol.pac
|
||||
|
||||
########################################
|
||||
# Connections and flows.
|
||||
|
||||
# External headers (placed outside the "binpac" namespace).
|
||||
%extern{
|
||||
#include <map>
|
||||
using namespace std;
|
||||
%}
|
||||
|
||||
# Extensible function for building a Bro Val for the RPC call.
|
||||
# See portmap-connection.pac.
|
||||
function RPC_BuildCallVal(call: RPC_Call): BroVal =
|
||||
case RPC_Service(call) of {
|
||||
default -> NULL;
|
||||
};
|
||||
|
||||
# Adding extra let-fields to type RPC_Call.
|
||||
refine typeattr RPC_Call += &let {
|
||||
start_time: double = network_time();
|
||||
|
||||
# Build the Bro Val.
|
||||
call_val: BroVal = RPC_BuildCallVal(this);
|
||||
|
||||
action_call: bool = $context.flow.ProcessCall(this);
|
||||
};
|
||||
|
||||
# ... and to RPC_Reply.
|
||||
refine typeattr RPC_Reply += &let {
|
||||
status = RPC_Status(this);
|
||||
action_reply: bool = $context.flow.ProcessReply(this);
|
||||
};
|
||||
|
||||
# RPC status in Bro (this is a repeat of enum rpc_status in const.bif :-().
|
||||
enum EnumRPCStatus {
|
||||
RPC_STATUS_SUCCESS = 0,
|
||||
RPC_STATUS_PROG_UNAVAIL = 1,
|
||||
RPC_STATUS_PROG_MISMATCH= 2,
|
||||
RPC_STATUS_PROC_UNAVAIL = 3,
|
||||
RPC_STATUS_GARBAGE_ARGS = 4,
|
||||
RPC_STATUS_SYSTEM_ERR = 5,
|
||||
RPC_STATUS_TIMEOUT = 6,
|
||||
RPC_STATUS_MISMATCH = 7,
|
||||
RPC_STATUS_AUTH_ERROR = 8,
|
||||
RPC_STATUS_UNKNOWN_ERROR = 9,
|
||||
};
|
||||
|
||||
function RPC_Status(reply: RPC_Reply): EnumRPCStatus =
|
||||
case reply.stat of {
|
||||
MSG_ACCEPTED -> reply.areply.stat;
|
||||
MSG_DENIED -> case reply.rreply.stat of {
|
||||
RPC_MISMATCH -> RPC_STATUS_MISMATCH;
|
||||
AUTH_ERROR -> RPC_STATUS_AUTH_ERROR;
|
||||
};
|
||||
};
|
||||
|
||||
# An RPC interpreter.
|
||||
connection RPC_Conn(bro_analyzer: BroAnalyzer) {
|
||||
upflow = RPC_Flow(true);
|
||||
downflow = RPC_Flow(false);
|
||||
|
||||
# Returns the call corresponding to the xid. Returns
|
||||
# NULL if not found.
|
||||
function FindCall(xid: uint32): RPC_Call
|
||||
%{
|
||||
RPC_CallTable::const_iterator it = call_table.find(xid);
|
||||
return it == call_table.end() ? 0 : it->second;
|
||||
%}
|
||||
|
||||
function NewCall(xid: uint32, call: RPC_Call): bool
|
||||
%{
|
||||
if ( call_table.find(xid) != call_table.end() )
|
||||
{
|
||||
// Compare retransmission with the original.
|
||||
RPC_Call* orig_call = call_table[xid];
|
||||
if ( RPC_CompareRexmit(orig_call, call) )
|
||||
Weird("RPC_rexmit_inconsistency");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add reference count to msg.
|
||||
call->msg()->Ref();
|
||||
call_table[xid] = call;
|
||||
return true;
|
||||
}
|
||||
%}
|
||||
|
||||
# Returns true if different.
|
||||
function RPC_CompareRexmit(orig_call: RPC_Call, new_call: RPC_Call): bool
|
||||
%{
|
||||
if ( ${orig_call.msg.length} != ${new_call.msg.length} )
|
||||
return true;
|
||||
|
||||
return memcmp(${orig_call.msg_source_data}.begin(),
|
||||
${new_call.msg_source_data}.begin(),
|
||||
${orig_call.msg.length}) != 0;
|
||||
%}
|
||||
|
||||
function FinishCall(call: RPC_Call): void
|
||||
%{
|
||||
call_table.erase(call->msg()->xid());
|
||||
Unref(call->msg());
|
||||
%}
|
||||
|
||||
function Timeout(): void
|
||||
%{
|
||||
for ( RPC_CallTable::const_iterator it = call_table.begin();
|
||||
it != call_table.end(); ++it )
|
||||
{
|
||||
RPC_Call* call = it->second;
|
||||
RPC_CallFailed(this, call, RPC_STATUS_TIMEOUT);
|
||||
Unref(call->msg());
|
||||
}
|
||||
|
||||
call_table.clear();
|
||||
%}
|
||||
|
||||
function Weird(msg: string): void
|
||||
%{
|
||||
bro_analyzer()->Weird(msg.c_str());
|
||||
%}
|
||||
|
||||
%member{
|
||||
typedef ::std::map<uint32, RPC_Call*> RPC_CallTable;
|
||||
RPC_CallTable call_table;
|
||||
%}
|
||||
};
|
||||
|
||||
# A virtual RPC flow.
|
||||
flow RPC_Flow (is_orig: bool) {
|
||||
# An RPC flow consists of RPC_Message datagrams.
|
||||
datagram = RPC_Message withcontext (connection, this);
|
||||
|
||||
function ProcessCall(call: RPC_Call): bool
|
||||
%{
|
||||
if ( ! is_orig() )
|
||||
Weird("responder_RPC_call");
|
||||
return true;
|
||||
%}
|
||||
|
||||
function ProcessReply(reply: RPC_Reply): bool
|
||||
%{
|
||||
if ( is_orig() )
|
||||
Weird("originator_RPC_reply");
|
||||
|
||||
RPC_Call* call = reply->call();
|
||||
if ( ! call )
|
||||
{
|
||||
Weird("unpaired_RPC_response");
|
||||
return false;
|
||||
}
|
||||
|
||||
BifEvent::generate_rpc_call(connection()->bro_analyzer(),
|
||||
connection()->bro_analyzer()->Conn(),
|
||||
call->prog(),
|
||||
call->vers(),
|
||||
call->proc(),
|
||||
reply->status(),
|
||||
call->start_time(),
|
||||
call->msg()->length(),
|
||||
reply->msg()->length());
|
||||
|
||||
if ( ! reply->success() )
|
||||
RPC_CallFailed(connection(), call, reply->status());
|
||||
|
||||
connection()->FinishCall(call);
|
||||
|
||||
return true;
|
||||
%}
|
||||
|
||||
function Weird(msg: string): void
|
||||
%{
|
||||
connection()->Weird(msg);
|
||||
%}
|
||||
|
||||
# TODO: deal with exceptions
|
||||
# exception(e: Exception)
|
||||
# {
|
||||
# Weird(string("bad RPC: ") + e.msg());
|
||||
# }
|
||||
};
|
||||
|
||||
# Extensible function for handling failed (rejected/timeout) RPC calls.
|
||||
function RPC_CallFailed(connection: RPC_Conn,
|
||||
call: RPC_Call,
|
||||
status: EnumRPCStatus): bool =
|
||||
case RPC_Service(call) of {
|
||||
default -> false;
|
||||
};
|
|
@ -1,164 +0,0 @@
|
|||
# $Id:$
|
||||
|
||||
# RPCv2. RFC 1831: http://www.ietf.org/rfc/rfc1831.txt
|
||||
|
||||
# This is an analyzer-independent (almost) description of the RPC protocol.
|
||||
|
||||
########################################
|
||||
# Constants.
|
||||
|
||||
enum RPC_MsgType {
|
||||
RPC_CALL = 0,
|
||||
RPC_REPLY = 1,
|
||||
};
|
||||
|
||||
enum RPC_ReplyStat {
|
||||
MSG_ACCEPTED = 0,
|
||||
MSG_DENIED = 1,
|
||||
};
|
||||
|
||||
enum RPC_AcceptStat {
|
||||
SUCCESS = 0,
|
||||
PROG_UNAVAIL = 1,
|
||||
PROG_MISMATCH = 2,
|
||||
PROC_UNAVAIL = 3,
|
||||
GARBAGE_ARGS = 4,
|
||||
SYSTEM_ERR = 5,
|
||||
};
|
||||
|
||||
enum RPC_RejectStat {
|
||||
RPC_MISMATCH = 0,
|
||||
AUTH_ERROR = 1,
|
||||
};
|
||||
|
||||
enum RPC_AuthStat {
|
||||
AUTH_OK = 0,
|
||||
|
||||
# failed at remote end
|
||||
AUTH_BADCRED = 1, # bad credential (seal broken)
|
||||
AUTH_REJECTEDCRED = 2, # client must begin new session
|
||||
AUTH_BADVERF = 3, # bad verifier (seal broken)
|
||||
AUTH_REJECTEDVERF = 4, # verifier expired or replayed
|
||||
AUTH_TOOWEAK = 5, # rejected for security reasons
|
||||
# failed locally
|
||||
AUTH_INVALIDRESP = 6, # bogus response verifier
|
||||
AUTH_FAILED = 7, # reason unknown
|
||||
};
|
||||
|
||||
|
||||
########################################
|
||||
|
||||
# To be redef'ed in various protocols.
|
||||
function RPC_Service(prog: uint32, vers: uint32): EnumRPCService =
|
||||
case prog of {
|
||||
default -> RPC_SERVICE_UNKNOWN;
|
||||
};
|
||||
|
||||
# Param "call" might be NULL for RPC_Results, thus "RPC_Service(call)"
|
||||
# rather than simply "call.service".
|
||||
|
||||
%code{
|
||||
|
||||
inline EnumRPCService RPC_Service(const RPC_Call* call)
|
||||
{
|
||||
// If it's an unpaired response, we ignore it for now and
|
||||
// complain later in the analyzer.
|
||||
return call ? call->service() : RPC_SERVICE_UNKNOWN;
|
||||
}
|
||||
%}
|
||||
|
||||
|
||||
########################################
|
||||
# Data structures.
|
||||
|
||||
# Export the source data for each RPC_Message for RPC retransmission checking.
|
||||
# With &exportsourcedata, "sourcedata" are defined as members of
|
||||
# class RPC_Message.
|
||||
|
||||
type RPC_Message = record {
|
||||
xid: uint32;
|
||||
msg_type: uint32;
|
||||
msg_body: case msg_type of {
|
||||
RPC_CALL -> call: RPC_Call(this);
|
||||
RPC_REPLY -> reply: RPC_Reply(this);
|
||||
} &requires(length);
|
||||
} &let {
|
||||
length = sourcedata.length(); # length of the RPC_Message
|
||||
} &byteorder = bigendian, &exportsourcedata, &refcount;
|
||||
|
||||
type RPC_Call(msg: RPC_Message) = record {
|
||||
rpcvers: uint32;
|
||||
prog: uint32;
|
||||
vers: uint32;
|
||||
proc: uint32;
|
||||
cred: RPC_OpaqueAuth;
|
||||
verf: RPC_OpaqueAuth;
|
||||
|
||||
# Compute 'service' before parsing params.
|
||||
params: RPC_Params(this) &requires(service);
|
||||
} &let {
|
||||
service: EnumRPCService = RPC_Service(prog, vers);
|
||||
|
||||
# Copy the source data for retransmission checking.
|
||||
msg_source_data: bytestring = msg.sourcedata;
|
||||
|
||||
# Register the RPC call by the xid.
|
||||
newcall: bool = context.connection.NewCall(msg.xid, this)
|
||||
&requires(msg_source_data);
|
||||
};
|
||||
|
||||
type RPC_Reply(msg: RPC_Message) = record {
|
||||
# Find the corresponding RPC call.
|
||||
# Further parsing of reply depends on call.{prog, vers, proc}
|
||||
stat: uint32;
|
||||
reply: case stat of {
|
||||
MSG_ACCEPTED -> areply: RPC_AcceptedReply(call);
|
||||
MSG_DENIED -> rreply: RPC_RejectedReply(call);
|
||||
} &requires(call);
|
||||
} &let {
|
||||
call: RPC_Call = context.connection.FindCall(msg.xid);
|
||||
success: bool = (stat == MSG_ACCEPTED && areply.stat == SUCCESS);
|
||||
};
|
||||
|
||||
type RPC_AcceptedReply(call: RPC_Call) = record {
|
||||
verf: RPC_OpaqueAuth;
|
||||
stat: uint32;
|
||||
data: case stat of {
|
||||
SUCCESS -> results: RPC_Results(call);
|
||||
PROG_MISMATCH -> mismatch: RPC_MismatchInfo;
|
||||
default -> other: empty;
|
||||
};
|
||||
};
|
||||
|
||||
type RPC_RejectedReply(call: RPC_Call) = record {
|
||||
stat: uint32;
|
||||
data: case stat of {
|
||||
RPC_MISMATCH -> mismatch: RPC_MismatchInfo;
|
||||
AUTH_ERROR -> auth_stat: uint32; # RPC_AuthStat
|
||||
};
|
||||
};
|
||||
|
||||
type RPC_MismatchInfo = record {
|
||||
hi: uint32;
|
||||
low: uint32;
|
||||
};
|
||||
|
||||
type RPC_Opaque = record {
|
||||
length: uint32;
|
||||
data: uint8[length];
|
||||
pad: padding align 4; # pad to 4-byte boundary
|
||||
};
|
||||
|
||||
type RPC_OpaqueAuth = record {
|
||||
flavor: uint32;
|
||||
opaque: RPC_Opaque;
|
||||
};
|
||||
|
||||
# To be extended by higher level protocol analyzers. See portmap-protocol.pac.
|
||||
type RPC_Params(call: RPC_Call) = case RPC_Service(call) of {
|
||||
default -> stub: uint8[] &restofdata;
|
||||
};
|
||||
|
||||
type RPC_Results(call: RPC_Call) = case RPC_Service(call) of {
|
||||
default -> stub: uint8[] &restofdata;
|
||||
};
|
19
src/rpc.pac
19
src/rpc.pac
|
@ -1,19 +0,0 @@
|
|||
# $Id:$
|
||||
|
||||
# RPCv2. RFC 1831: http://www.ietf.org/rfc/rfc1831.txt
|
||||
|
||||
%include binpac.pac
|
||||
%include bro.pac
|
||||
|
||||
analyzer SunRPC withcontext {
|
||||
connection: RPC_Conn;
|
||||
flow: RPC_Flow;
|
||||
};
|
||||
|
||||
enum EnumRPCService {
|
||||
RPC_SERVICE_UNKNOWN,
|
||||
RPC_SERVICE_PORTMAP,
|
||||
};
|
||||
|
||||
%include rpc-analyzer.pac
|
||||
%include portmap-analyzer.pac
|
|
@ -4,6 +4,11 @@
|
|||
|
||||
#include <errno.h>
|
||||
|
||||
#include <stack>
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include "input.h"
|
||||
#include "util.h"
|
||||
#include "Scope.h"
|
||||
|
@ -19,10 +24,6 @@
|
|||
#include "Analyzer.h"
|
||||
#include "AnalyzerTags.h"
|
||||
|
||||
#include <stack>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
extern YYLTYPE yylloc; // holds start line and column of token
|
||||
extern int print_loaded_scripts;
|
||||
extern int generate_documentation;
|
||||
|
|
106
src/types.bif
106
src/types.bif
|
@ -51,6 +51,110 @@ enum rpc_status %{
|
|||
RPC_UNKNOWN_ERROR,
|
||||
%}
|
||||
|
||||
module NFS3;
|
||||
|
||||
enum proc_t %{ # NFSv3 procedures
|
||||
PROC_NULL = 0, # done
|
||||
PROC_GETATTR = 1, # done
|
||||
PROC_SETATTR = 2, # not implemented
|
||||
PROC_LOOKUP = 3, # done
|
||||
PROC_ACCESS = 4, # not implemented
|
||||
PROC_READLINK = 5, # done
|
||||
PROC_READ = 6, # done
|
||||
PROC_WRITE = 7, # done
|
||||
PROC_CREATE = 8, # partial
|
||||
PROC_MKDIR = 9, # partial
|
||||
PROC_SYMLINK = 10, # not implemented
|
||||
PROC_MKNOD = 11, # not implemented
|
||||
PROC_REMOVE = 12, # done
|
||||
PROC_RMDIR = 13, # done
|
||||
PROC_RENAME = 14, # not implemented
|
||||
PROC_LINK = 15, # not implemented
|
||||
PROC_READDIR = 16, # done
|
||||
PROC_READDIRPLUS = 17, # done
|
||||
PROC_FSSTAT = 18, # not implemented
|
||||
PROC_FSINFO = 19, # not implemented
|
||||
PROC_PATHCONF = 20, # not implemented
|
||||
PROC_COMMIT = 21, # not implemented
|
||||
PROC_END_OF_PROCS = 22, # not implemented
|
||||
%}
|
||||
|
||||
enum status_t %{ # NFSv3 return status
|
||||
NFS3ERR_OK = 0,
|
||||
NFS3ERR_PERM = 1,
|
||||
NFS3ERR_NOENT = 2,
|
||||
NFS3ERR_IO = 5,
|
||||
NFS3ERR_NXIO = 6,
|
||||
NFS3ERR_ACCES = 13,
|
||||
NFS3ERR_EXIST = 17,
|
||||
NFS3ERR_XDEV = 18,
|
||||
NFS3ERR_NODEV = 19,
|
||||
NFS3ERR_NOTDIR = 20,
|
||||
NFS3ERR_ISDIR = 21,
|
||||
NFS3ERR_INVAL = 22,
|
||||
NFS3ERR_FBIG = 27,
|
||||
NFS3ERR_NOSPC = 28,
|
||||
NFS3ERR_ROFS = 30,
|
||||
NFS3ERR_MLINK = 31,
|
||||
NFS3ERR_NAMETOOLONG = 63,
|
||||
NFS3ERR_NOTEMPTY = 66,
|
||||
NFS3ERR_DQUOT = 69,
|
||||
NFS3ERR_STALE = 70,
|
||||
NFS3ERR_REMOTE = 71,
|
||||
NFS3ERR_BADHANDLE = 10001,
|
||||
NFS3ERR_NOT_SYNC = 10002,
|
||||
NFS3ERR_BAD_COOKIE = 10003,
|
||||
NFS3ERR_NOTSUPP = 10004,
|
||||
NFS3ERR_TOOSMALL = 10005,
|
||||
NFS3ERR_SERVERFAULT = 10006,
|
||||
NFS3ERR_BADTYPE = 10007,
|
||||
NFS3ERR_JUKEBOX = 10008,
|
||||
NFS3ERR_UNKNOWN = 0xffffffff,
|
||||
%}
|
||||
|
||||
enum file_type_t %{
|
||||
FTYPE_REG = 1,
|
||||
FTYPE_DIR = 2,
|
||||
FTYPE_BLK = 3,
|
||||
FTYPE_CHR = 4,
|
||||
FTYPE_LNK = 5,
|
||||
FTYPE_SOCK = 6,
|
||||
FTYPE_FIFO = 7,
|
||||
%}
|
||||
|
||||
enum stable_how_t %{
|
||||
UNSTABLE = 0,
|
||||
DATA_SYNC = 1,
|
||||
FILE_SYNC = 2,
|
||||
%}
|
||||
|
||||
enum createmode_t %{
|
||||
UNCHECKED = 0,
|
||||
GUARDED = 1,
|
||||
EXCLUSIVE = 2,
|
||||
%}
|
||||
|
||||
# Decleare record types that we want to access from the even engine. These are
|
||||
# defined in bro.init.
|
||||
type info_t: record;
|
||||
type fattr_t: record;
|
||||
type diropargs_t: record;
|
||||
type lookup_reply_t: record;
|
||||
type readargs_t: record;
|
||||
type read_reply_t: record;
|
||||
type readlink_reply_t: record;
|
||||
type writeargs_t: record;
|
||||
type wcc_attr_t: record;
|
||||
type write_reply_t: record;
|
||||
type newobj_reply_t: record;
|
||||
type delobj_reply_t: record;
|
||||
type readdirargs_t: record;
|
||||
type direntry_t: record;
|
||||
type direntry_vec_t: vector;
|
||||
type readdir_reply_t: record;
|
||||
|
||||
type fsstat_t: record;
|
||||
|
||||
module Log;
|
||||
|
||||
enum Writer %{
|
||||
|
@ -61,3 +165,5 @@ enum Writer %{
|
|||
enum ID %{
|
||||
Unknown,
|
||||
%}
|
||||
|
||||
module GLOBAL;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#
|
||||
# @TEST-EXEC: BRO_SEED_FILE= bro %INPUT 2>/dev/null >out
|
||||
# @TEST-EXEC: BRO_SEED_FILE= bro %INPUT 2>/dev/null >>out
|
||||
# @TEST-EXEC: cat out | sort | uniq | wc -l >count
|
||||
# @TEST-EXEC: cat out | sort | uniq | wc -l | sed 's/ //g' >count
|
||||
# @TEST-EXEC: btest-diff count
|
||||
|
||||
print unique_id("A-");
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# Without a seed, they should differ each time:
|
||||
#
|
||||
# @TEST-EXEC: unset BRO_SEED_FILE && bro -C -r $TRACES/wikipedia.trace %INPUT tcp >output2
|
||||
# @TEST-EXEC: cat output output2 | sort | uniq -c | wc -l >counts
|
||||
# @TEST-EXEC: cat output output2 | sort | uniq -c | wc -l | sed 's/ //g' >counts
|
||||
# @TEST-EXEC: btest-diff counts
|
||||
#
|
||||
# Make sure it works without the connection compressor as well.
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
# @TEST-EXEC: echo "@load foo/test.bro" >foo/__load__.bro
|
||||
# @TEST-EXEC: cp %INPUT foo/test.bro
|
||||
# @TEST-EXEC: bro -l foo >output 2>&1
|
||||
# @TEST-EXEC: btest-diff output
|
||||
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff output
|
||||
|
||||
print "Foo loaded";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# @TEST-EXEC-FAIL: bro %INPUT >output 2>&1
|
||||
# @TEST-EXEC: btest-diff output
|
||||
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff output
|
||||
|
||||
type X: record {
|
||||
a: count;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue