From 27f692799f82dcc18c3156892c63c2a1598580ba Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Tue, 7 Jun 2011 23:47:39 -0400 Subject: [PATCH 01/10] Small but crucial fix for the new unique_id function. --- src/bro.bif | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bro.bif b/src/bro.bif index a330ea77e8..0eea41bf7d 100644 --- a/src/bro.bif +++ b/src/bro.bif @@ -3349,7 +3349,7 @@ function bro_has_ipv6%(%) : bool #endif %} -function unique_id%(prefix: string%) : bool +function unique_id%(prefix: string%) : string %{ char tmp[20]; uint64 uid = calculate_unique_id(); From 80558a994a7ef2040164f79b3992df1ee91bbae7 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Jun 2011 11:53:51 -0500 Subject: [PATCH 02/10] Fix core.load-pkg unit test. Removed the test's diff against baseline output that contained absolute paths so that it will work across systems. Also don't redirect anything to stderr so that failure information shows up in btest diagnostic output. --- testing/btest/Baseline/core.load-pkg/output | 13 ------------- testing/btest/core/load-pkg.bro | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/testing/btest/Baseline/core.load-pkg/output b/testing/btest/Baseline/core.load-pkg/output index 01c77289d2..119b2f9a18 100644 --- a/testing/btest/Baseline/core.load-pkg/output +++ b/testing/btest/Baseline/core.load-pkg/output @@ -1,14 +1 @@ -loading /home/robin/bro/master/policy/bro.init - loading /home/robin/bro/master/build/src/const.bif.bro - loading /home/robin/bro/master/build/src/types.bif.bro - loading /home/robin/bro/master/build/src/strings.bif.bro - loading /home/robin/bro/master/build/src/bro.bif.bro - loading /home/robin/bro/master/policy/logging.bro - loading /home/robin/bro/master/build/src/logging.bif.bro - loading /home/robin/bro/master/policy/logging-ascii.bro - loading /home/robin/bro/master/build/src/event.bif.bro - loading /home/robin/bro/master/policy/pcap.bro - loading /home/robin/bro/master/policy/server-ports.bro -loading ./foo/test.bro -loading ./foo/__load__.bro Foo loaded diff --git a/testing/btest/core/load-pkg.bro b/testing/btest/core/load-pkg.bro index 907dde78ce..81f6cbcf87 100644 --- a/testing/btest/core/load-pkg.bro +++ b/testing/btest/core/load-pkg.bro @@ -1,7 +1,7 @@ # @TEST-EXEC: mkdir foo # @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: bro foo >output # @TEST-EXEC: btest-diff output print "Foo loaded"; From 13c90fc732bddf6d9b59438c0c6f713c35dc2c4f Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Jun 2011 12:17:10 -0500 Subject: [PATCH 03/10] Fix core.conn-id test on some platforms. The output of some versions of `wc` (e.g. MacOS) seems to indent their output while others don't, causing the baseline diff to fail. So pipe to sed to get rid of spaces before diffing. --- testing/btest/core/conn-id.bro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/btest/core/conn-id.bro b/testing/btest/core/conn-id.bro index 04724bba3c..51711559d9 100644 --- a/testing/btest/core/conn-id.bro +++ b/testing/btest/core/conn-id.bro @@ -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. From d358ef1e71e7808eb24143c8161378b295c60f4e Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Jun 2011 12:59:05 -0500 Subject: [PATCH 04/10] Null-terminate the string created by decode_netbios_name BiF. (initially observed through failures of bifs.netbios-functions unit test) --- src/bro.bif | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bro.bif b/src/bro.bif index 0eea41bf7d..2c1f2c316c 100644 --- a/src/bro.bif +++ b/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; @@ -1367,7 +1367,10 @@ function decode_netbios_name%(name: string%): string buf[i] < 3 ) result[i] = buf[i]; else + { + result[i] = 0; break; + } } return new StringVal(result); From 90196b4dc896c91792a904b0cc7bbeb7b3008403 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Jun 2011 13:27:08 -0500 Subject: [PATCH 05/10] Fix bifs.unique_id-rnd test failing because of wc output formatting --- testing/btest/bifs/unique_id-rnd.bro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/btest/bifs/unique_id-rnd.bro b/testing/btest/bifs/unique_id-rnd.bro index 4ac41ff1b5..1b24c662e2 100644 --- a/testing/btest/bifs/unique_id-rnd.bro +++ b/testing/btest/bifs/unique_id-rnd.bro @@ -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-"); From cb8944059364c0ddefb035f10fb5a73b9e393cba Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Jun 2011 14:56:49 -0500 Subject: [PATCH 06/10] Fix language.wrong-delete-field test by running through abs path canonifier --- testing/btest/language/wrong-delete-field.bro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/btest/language/wrong-delete-field.bro b/testing/btest/language/wrong-delete-field.bro index 0b58cc6fa0..e0d0093258 100644 --- a/testing/btest/language/wrong-delete-field.bro +++ b/testing/btest/language/wrong-delete-field.bro @@ -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; From 9e747a040d564ee94ebd603ea09eb4014a575b19 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Jun 2011 15:01:35 -0500 Subject: [PATCH 07/10] Revert "Fix core.load-pkg unit test." This reverts commit 80558a994a7ef2040164f79b3992df1ee91bbae7. --- testing/btest/Baseline/core.load-pkg/output | 13 +++++++++++++ testing/btest/core/load-pkg.bro | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/testing/btest/Baseline/core.load-pkg/output b/testing/btest/Baseline/core.load-pkg/output index 119b2f9a18..01c77289d2 100644 --- a/testing/btest/Baseline/core.load-pkg/output +++ b/testing/btest/Baseline/core.load-pkg/output @@ -1 +1,14 @@ +loading /home/robin/bro/master/policy/bro.init + loading /home/robin/bro/master/build/src/const.bif.bro + loading /home/robin/bro/master/build/src/types.bif.bro + loading /home/robin/bro/master/build/src/strings.bif.bro + loading /home/robin/bro/master/build/src/bro.bif.bro + loading /home/robin/bro/master/policy/logging.bro + loading /home/robin/bro/master/build/src/logging.bif.bro + loading /home/robin/bro/master/policy/logging-ascii.bro + loading /home/robin/bro/master/build/src/event.bif.bro + loading /home/robin/bro/master/policy/pcap.bro + loading /home/robin/bro/master/policy/server-ports.bro +loading ./foo/test.bro +loading ./foo/__load__.bro Foo loaded diff --git a/testing/btest/core/load-pkg.bro b/testing/btest/core/load-pkg.bro index 81f6cbcf87..907dde78ce 100644 --- a/testing/btest/core/load-pkg.bro +++ b/testing/btest/core/load-pkg.bro @@ -1,7 +1,7 @@ # @TEST-EXEC: mkdir foo # @TEST-EXEC: echo "@load foo/test.bro" >foo/__load__.bro # @TEST-EXEC: cp %INPUT foo/test.bro -# @TEST-EXEC: bro foo >output +# @TEST-EXEC: bro -l foo >output 2>&1 # @TEST-EXEC: btest-diff output print "Foo loaded"; From b4d70a22dbdf61244887bcb53f2458f83cf0f352 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 10 Jun 2011 15:07:32 -0500 Subject: [PATCH 08/10] Fixed core.load-pkg test w/ diff canonifier instead --- testing/btest/core/load-pkg.bro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/btest/core/load-pkg.bro b/testing/btest/core/load-pkg.bro index 907dde78ce..c7aa27fd86 100644 --- a/testing/btest/core/load-pkg.bro +++ b/testing/btest/core/load-pkg.bro @@ -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"; From eb85ae9654ee251a5e7b00fc406dec376f5b4a2b Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Sun, 12 Jun 2011 08:46:58 -0500 Subject: [PATCH 09/10] Really, null-terminate full 15-char NetBIOS host names, too. --- src/bro.bif | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/bro.bif b/src/bro.bif index 2c1f2c316c..889ec0cd3a 100644 --- a/src/bro.bif +++ b/src/bro.bif @@ -1367,13 +1367,10 @@ function decode_netbios_name%(name: string%): string buf[i] < 3 ) result[i] = buf[i]; else - { - result[i] = 0; break; - } } - return new StringVal(result); + return new StringVal(i, result); %} function decode_netbios_name_type%(name: string%): count From 5bd8caa7a04969166c4d50922198586e602ba746 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Tue, 7 Jun 2011 15:39:09 -0700 Subject: [PATCH 10/10] Merge remote branch 'origin/topic/gregor/rpc' Note, I haven't gone through the script-level code as that will change soon anyway. --- policy/bro.init | 220 +++++++++--- policy/nfs.bro | 394 +++++++++++++++++++--- policy/portmapper.bro | 140 +++++--- policy/rpc.bro | 147 +++++++++ src/Analyzer.cc | 5 +- src/AnalyzerTags.h | 2 +- src/BitTorrentTracker.cc | 2 + src/BroString.cc | 2 + src/CMakeLists.txt | 2 - src/ContentLine.cc | 2 + src/DNS_Mgr.cc | 2 + src/Discard.cc | 2 + src/File.cc | 2 + src/FileAnalyzer.cc | 2 + src/Gnutella.cc | 3 +- src/ICMP.cc | 2 + src/IOSource.cc | 2 + src/NFS.cc | 697 ++++++++++++++++++++++++++++++--------- src/NFS.h | 73 +++- src/NetVar.cc | 12 - src/NetVar.h | 6 - src/Portmap.cc | 17 +- src/Portmap.h | 8 +- src/RPC.cc | 600 +++++++++++++++++++-------------- src/RPC.h | 150 ++++++--- src/Reassem.cc | 2 + src/RuleMatcher.cc | 2 + src/TCP.cc | 2 + src/TCP_Reassembler.cc | 12 +- src/UDP.cc | 2 + src/XDR.cc | 36 +- src/XDR.h | 5 +- src/bro.bif | 86 +++++ src/const.bif | 7 +- src/event.bif | 39 ++- src/portmap-analyzer.pac | 191 ----------- src/portmap-protocol.pac | 77 ----- src/rpc-analyzer.pac | 196 ----------- src/rpc-protocol.pac | 164 --------- src/rpc.pac | 19 -- src/scan.l | 9 +- src/types.bif | 106 ++++++ 42 files changed, 2161 insertions(+), 1288 deletions(-) create mode 100644 policy/rpc.bro delete mode 100644 src/portmap-analyzer.pac delete mode 100644 src/portmap-protocol.pac delete mode 100644 src/rpc-analyzer.pac delete mode 100644 src/rpc-protocol.pac delete mode 100644 src/rpc.pac diff --git a/policy/bro.init b/policy/bro.init index ba5c1d0b8f..6c544d9be5 100644 --- a/policy/bro.init +++ b/policy/bro.init @@ -720,54 +720,187 @@ 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; - mode: count; - nlink: count; - uid: count; - gid: count; - size: double; - used: double; - rdev1: count; - rdev2: count; - fsid: double; - fileid: double; - atime: time; - mtime: time; - ctime: time; -}; + # If nfs_return_data is true, how much data should be returned at most. + const return_data_max = 512 &redef; -type nfs3_opt_attrs: record { - attrs: nfs3_attrs &optional; -}; + # 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; -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 -}; + # 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; -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. -}; + # 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; -type nfs3_fsstat: record { - attrs: nfs3_opt_attrs; - tbytes: double; - fbytes: double; - abytes: double; - tfiles: double; - ffiles: double; - afiles: double; - invarsec: interval; -}; + # 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: count; + used: count; + rdev1: count; + rdev2: count; + fsid: count; + fileid: count; + atime: time; + mtime: time; + ctime: time; + }; + + type diropargs_t : record { + dirfh: string; # the file handle of the directory + fname: string; # the name of the file we are interested in + }; + + # Note, we don't need a "post_op_attr" type. We use an "fattr_t &optional" + # instead. + + 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 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; + tfiles: double; + ffiles: double; + afiles: double; + invarsec: interval; + }; +} # end export + +module GLOBAL; type ntp_msg: record { id: count; @@ -1249,6 +1382,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). diff --git a/policy/nfs.bro b/policy/nfs.bro index dec46d593e..0d572b52c7 100644 --- a/policy/nfs.bro +++ b/policy/nfs.bro @@ -1,14 +1,24 @@ -# $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 += { + +redef capture_filters += { ["nfs"] = "port 2049", # NFS UDP packets are often fragmented. ["nfs-frag"] = "(ip[6:2] & 0x3fff != 0) and udp", @@ -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) : ""; + 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) ? "" : "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); + } + + print log_file, msg; } -event nfs_request_lookup(n: connection, req: nfs3_lookup_args, rep: nfs3_lookup_reply) +event nfs_proc_readlink(c: connection, info: info_t, fh: string, rep: readlink_reply_t) { - 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))); + 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); + } + + print log_file, msg; } -event nfs_attempt_lookup(n: connection, status: count, req: nfs3_lookup_args) +event nfs_proc_write(c: connection, info: info_t, req: writeargs_t, rep: write_reply_t) { - NFS_attempt(n, "lookup", status, fmt("%s", req)); + 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; } - -event nfs_request_fsstat(n: connection, root_fh: string, stat: nfs3_fsstat) +function nfs_newobj(c: connection, info: info_t, proc: string, req: diropargs_t, rep: newobj_reply_t) { - 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)); + 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_attempt_fsstat(n: connection, status: count, root_fh: string) +event nfs_proc_create(c: connection, info: info_t, req: diropargs_t, rep: newobj_reply_t) { - NFS_attempt(n, "fsstat", status, map_fh(root_fh)); + # 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]; } diff --git a/policy/portmapper.bro b/policy/portmapper.bro index a67f24821b..4829812154 100644 --- a/policy/portmapper.bro +++ b/policy/portmapper.bro @@ -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: accepts with + # calls on . We learned this mapping via + 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) diff --git a/policy/rpc.bro b/policy/rpc.bro new file mode 100644 index 0000000000..936684a728 --- /dev/null +++ b/policy/rpc.bro @@ -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); + } diff --git a/src/Analyzer.cc b/src/Analyzer.cc index 44b87d4aa5..da81e54478 100644 --- a/src/Analyzer.cc +++ b/src/Analyzer.cc @@ -1,5 +1,7 @@ // $Id: Analyzer.cc,v 1.1.4.28 2006/06/01 17:18:10 sommer Exp $ +#include + #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 }, diff --git a/src/AnalyzerTags.h b/src/AnalyzerTags.h index 947ba7dc74..e64a4ec76e 100644 --- a/src/AnalyzerTags.h +++ b/src/AnalyzerTags.h @@ -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, diff --git a/src/BitTorrentTracker.cc b/src/BitTorrentTracker.cc index aaf925f196..f0b290751d 100644 --- a/src/BitTorrentTracker.cc +++ b/src/BitTorrentTracker.cc @@ -8,6 +8,8 @@ #include #include +#include + # define FMT_INT "%" PRId64 # define FMT_UINT "%" PRIu64 diff --git a/src/BroString.cc b/src/BroString.cc index 08c724f5f2..1bbe0099f2 100644 --- a/src/BroString.cc +++ b/src/BroString.cc @@ -7,6 +7,8 @@ #include #include +#include + #include "BroString.h" #include "Var.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4c1d16376..316a9cdf33 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/ContentLine.cc b/src/ContentLine.cc index 4ab5d0358e..5f58fa1f0c 100644 --- a/src/ContentLine.cc +++ b/src/ContentLine.cc @@ -1,5 +1,7 @@ // $Id: ContentLine.cc,v 1.1.2.8 2006/06/01 01:55:42 sommer Exp $ +#include + #include "ContentLine.h" #include "TCP.h" diff --git a/src/DNS_Mgr.cc b/src/DNS_Mgr.cc index 742ff00b8f..d179ccec49 100644 --- a/src/DNS_Mgr.cc +++ b/src/DNS_Mgr.cc @@ -29,6 +29,8 @@ #endif #include +#include + #include "DNS_Mgr.h" #include "Event.h" #include "Net.h" diff --git a/src/Discard.cc b/src/Discard.cc index e0dd8deed0..fcee23e5e0 100644 --- a/src/Discard.cc +++ b/src/Discard.cc @@ -2,6 +2,8 @@ // // See the file "COPYING" in the main distribution directory for copyright. +#include + #include "config.h" #include "Net.h" diff --git a/src/File.cc b/src/File.cc index a94b8706e7..2e6929fc89 100644 --- a/src/File.cc +++ b/src/File.cc @@ -20,6 +20,8 @@ #include #include +#include + #include "File.h" #include "Type.h" #include "Timer.h" diff --git a/src/FileAnalyzer.cc b/src/FileAnalyzer.cc index 7f9e0b2c2d..17d3ad1dbc 100644 --- a/src/FileAnalyzer.cc +++ b/src/FileAnalyzer.cc @@ -1,5 +1,7 @@ // $Id: FileAnalyzer.cc,v 1.1.4.2 2006/06/01 17:18:10 sommer Exp $ +#include + #include "FileAnalyzer.h" #ifdef HAVE_LIBMAGIC diff --git a/src/Gnutella.cc b/src/Gnutella.cc index 5b1a2e39e8..9787147400 100644 --- a/src/Gnutella.cc +++ b/src/Gnutella.cc @@ -6,13 +6,14 @@ #include +#include + #include "NetVar.h" #include "HTTP.h" #include "Gnutella.h" #include "Event.h" #include "PIA.h" - GnutellaMsgState::GnutellaMsgState() { buffer = ""; diff --git a/src/ICMP.cc b/src/ICMP.cc index 95169cd518..4e11583651 100644 --- a/src/ICMP.cc +++ b/src/ICMP.cc @@ -2,6 +2,8 @@ // // See the file "COPYING" in the main distribution directory for copyright. +#include + #include "config.h" #include "Net.h" diff --git a/src/IOSource.cc b/src/IOSource.cc index cc3ad8dbbd..83f4ef15f2 100644 --- a/src/IOSource.cc +++ b/src/IOSource.cc @@ -5,6 +5,8 @@ #include #include +#include + #include "util.h" #include "IOSource.h" diff --git a/src/NFS.cc b/src/NFS.cc index 8d7e920e2d..2951361baf 100644 --- a/src/NFS.cc +++ b/src/NFS.cc @@ -2,6 +2,8 @@ // // See the file "COPYING" in the main distribution directory for copyright. +#include + #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: - Weird(fmt("unknown_NFS_request(%u)", proc)); + 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; } + if ( ! buf ) + { + // 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(const RPC_CallInfo* c, int success, - const u_char*& buf, int& n, - EventHandlerPtr& event, Val*& reply) +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) { - reply = 0; - uint32 status = 0; - if ( success ) + 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: - return 0; + 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; - } - - 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); + return new Val(extract_XDR_uint32(buf, n), TYPE_BOOL); } + 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; } diff --git a/src/NFS.h b/src/NFS.h index 87a0d041e9..28a1d5c4ac 100644 --- a/src/NFS.h +++ b/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 diff --git a/src/NetVar.cc b/src/NetVar.cc index db16d714b8..52f14d9dab 100644 --- a/src/NetVar.cc +++ b/src/NetVar.cc @@ -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(); diff --git a/src/NetVar.h b/src/NetVar.h index a3c0d8a33e..90df40b6fd 100644 --- a/src/NetVar.h +++ b/src/NetVar.h @@ -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; diff --git a/src/Portmap.cc b/src/Portmap.cc index bcf52daf4e..e806acdc7a 100644 --- a/src/Portmap.cc +++ b/src/Portmap.cc @@ -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, - const u_char*& buf, int& n, - EventHandlerPtr& event, Val*& reply) +int PortmapperInterp::RPC_BuildReply(RPC_CallInfo* c, BifEnum::rpc_status status, + const u_char*& buf, int& n, + 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); diff --git a/src/Portmap.h b/src/Portmap.h index 6595fc86d3..cb2cb1293c 100644 --- a/src/Portmap.h +++ b/src/Portmap.h @@ -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); diff --git a/src/RPC.cc b/src/RPC.cc index ef9a925fea..915f2efe9e 100644 --- a/src/RPC.cc +++ b/src/RPC.cc @@ -2,10 +2,12 @@ // // See the file "COPYING" in the main distribution directory for copyright. -#include "config.h" - #include +#include + +#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,240 +275,406 @@ 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(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(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) { - // Assuming RPC frames align with packet boundaries ... + 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 // TCP keep-alive retransmissions. DEBUG_MSG("%.6f RPC resync: " - "discard small pieces: %d\n", - network_time, len); + "discard small pieces: %d\n", + network_time, len); Conn()->Weird( fmt("RPC resync: discard %d bytes\n", len)); } - return; + + NeedResync(); + return false; } - const u_char* xdata = data; + + 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); + 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 ) + (! 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: skipping %d bytes\n", - len); - } - return; + DEBUG_MSG("RPC resync: Need to resync. dicarding %d bytes.\n", len); + + NeedResync(); // let's try the resync again from the beginning + return false; } - resync = false; - } + // Looks like we are at the start of a frame and have successfully + // extracted the frame length (marker). - int n; - for ( n = 0; buf_n < buf_len && n < len; ++n ) - msg_buf[buf_n++] = data[n]; + 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 ( buf_n < buf_len ) - // Haven't filled up the message buffer yet, no more to do. - return; - - 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; - - uint32 marker = extract_XDR_uint32(buf, n); - if ( ! buf ) - 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; - - if ( prev_frag_len > 0 ) - // We're adding another fragment. - marker += prev_frag_len; - - // Fragment length is now given by marker. Sanity-check. - if ( marker > MAX_RPC_LEN ) - { - Conn()->Weird("excessive_RPC_len"); - marker = MAX_RPC_LEN; - } - - // 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; - } - break; - - case RPC_MESSAGE_BUFFER: - { // Have the whole fragment. - if ( ! last_frag ) - { - // 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; + if ( last_frag ) + resync_state = RESYNC_WAIT_FOR_FULL_MSG; + else + resync_state = RESYNC_WAIT_FOR_MSG_START; break; - } - if ( ! interp->DeliverRPC(msg_buf, buf_n, IsOrig()) ) - Conn()->Weird("partial_RPC"); + 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; - state = RPC_COMPLETE; - delete [] msg_buf; - msg_buf = 0; - } - break; + if ( last_frag ) + resync_state = RESYNC_HAD_FULL_MSG; + break; - case RPC_COMPLETE: - internal_error("RPC state inconsistency"); + 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; } - if ( n < len ) - // More data to munch on. - DeliverStream(len - n, data + n, orig); + + + +void Contents_RPC::DeliverStream(int len, const u_char* data, bool orig) + { + TCP_SupportAnalyzer::DeliverStream(len, data, orig); + uint32 marker; + bool last_frag; + + if ( ! CheckResync(len, data, orig) ) + return; // Not in sync yet. Still resyncing. + + // Should be in sync now. + + while (len > 0) + { + last_time = network_time; + + switch (state) { + case WAIT_FOR_MESSAGE: + // A new RPC message is starting. Initialize state. + + // 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"); + } + + 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 ( ! msg_buf.AddToExpected(marker) ) + Conn()->Weird(fmt("RPC_message_too_long (%" PRId64 ")" , msg_buf.GetExpected())); + + if ( last_frag ) + state = WAIT_FOR_LAST_DATA; + else + state = WAIT_FOR_DATA; + } + } + // Else remain in state. Haven't got the full 4 bytes + // for the marker yet. + break; + + case WAIT_FOR_DATA: + case WAIT_FOR_LAST_DATA: + { + bool got_all_data = msg_buf.ConsumeChunk(data, len); + + 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 = WAIT_FOR_MESSAGE; + } + else + state = WAIT_FOR_MARKER; + } + // Else remain in state. Haven't read all the data + // yet. + } + break; + } // 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,24 +715,7 @@ 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(); + interp->Timeout(); } void RPC_Analyzer::ExpireTimer(double /* t */) @@ -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()); - } diff --git a/src/RPC.h b/src/RPC.h index 11acb68977..1b75b6cc48 100644 --- a/src/RPC.h +++ b/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,8 +64,12 @@ 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 HeaderLen() const { return header_len; } + 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 diff --git a/src/Reassem.cc b/src/Reassem.cc index 8970c23f61..0153bde178 100644 --- a/src/Reassem.cc +++ b/src/Reassem.cc @@ -2,6 +2,8 @@ // // See the file "COPYING" in the main distribution directory for copyright. +#include + #include "config.h" #include "Reassem.h" diff --git a/src/RuleMatcher.cc b/src/RuleMatcher.cc index 2a0246d121..4bc28b4c32 100644 --- a/src/RuleMatcher.cc +++ b/src/RuleMatcher.cc @@ -1,5 +1,7 @@ // $Id: RuleMatcher.cc 6724 2009-06-07 09:23:03Z vern $ +#include + #include "config.h" #include "Analyzer.h" diff --git a/src/TCP.cc b/src/TCP.cc index ea9d31e7a0..62039b7fa7 100644 --- a/src/TCP.cc +++ b/src/TCP.cc @@ -2,6 +2,8 @@ // // See the file "COPYING" in the main distribution directory for copyright. +#include + #include "NetVar.h" #include "PIA.h" #include "File.h" diff --git a/src/TCP_Reassembler.cc b/src/TCP_Reassembler.cc index f7611a46e1..e221ab4118 100644 --- a/src/TCP_Reassembler.cc +++ b/src/TCP_Reassembler.cc @@ -1,5 +1,7 @@ // $Id: TCP_Reassembler.cc,v 1.1.2.8 2006/05/31 01:52:02 sommer Exp $ +#include + #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); diff --git a/src/UDP.cc b/src/UDP.cc index 21d5b96945..b141a1e9a2 100644 --- a/src/UDP.cc +++ b/src/UDP.cc @@ -2,6 +2,8 @@ // // See the file "COPYING" in the main distribution directory for copyright. +#include + #include "config.h" #include "Net.h" diff --git a/src/XDR.cc b/src/XDR.cc index bea34b575e..53e9a4b2dd 100644 --- a/src/XDR.cc +++ b/src/XDR.cc @@ -2,6 +2,8 @@ // // See the file "COPYING" in the main distribution directory for copyright. +#include + #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); diff --git a/src/XDR.h b/src/XDR.h index 047acd90f5..2c6e1d69ac 100644 --- a/src/XDR.h +++ b/src/XDR.h @@ -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 diff --git a/src/bro.bif b/src/bro.bif index a330ea77e8..64e4b65443 100644 --- a/src/bro.bif +++ b/src/bro.bif @@ -3463,3 +3463,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); + %} diff --git a/src/const.bif b/src/const.bif index e51028c742..825c21e7a5 100644 --- a/src/const.bif +++ b/src/const.bif @@ -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; diff --git a/src/event.bif b/src/event.bif index 786e04588a..1fc6c7edd3 100644 --- a/src/event.bif +++ b/src/event.bif @@ -157,7 +157,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%); @@ -173,15 +179,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%); diff --git a/src/portmap-analyzer.pac b/src/portmap-analyzer.pac deleted file mode 100644 index a7b64ada5d..0000000000 --- a/src/portmap-analyzer.pac +++ /dev/null @@ -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(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; - %} diff --git a/src/portmap-protocol.pac b/src/portmap-protocol.pac deleted file mode 100644 index 65a478fb2d..0000000000 --- a/src/portmap-protocol.pac +++ /dev/null @@ -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 -}; diff --git a/src/rpc-analyzer.pac b/src/rpc-analyzer.pac deleted file mode 100644 index 86ac81b857..0000000000 --- a/src/rpc-analyzer.pac +++ /dev/null @@ -1,196 +0,0 @@ -# $Id:$ - -######################################## -# Protocol Syntax. - -%include rpc-protocol.pac - -######################################## -# Connections and flows. - -# External headers (placed outside the "binpac" namespace). -%extern{ -#include -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 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; - }; diff --git a/src/rpc-protocol.pac b/src/rpc-protocol.pac deleted file mode 100644 index d70195bac6..0000000000 --- a/src/rpc-protocol.pac +++ /dev/null @@ -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; -}; diff --git a/src/rpc.pac b/src/rpc.pac deleted file mode 100644 index 486a3f4f5c..0000000000 --- a/src/rpc.pac +++ /dev/null @@ -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 diff --git a/src/scan.l b/src/scan.l index da038e1c77..8db694c53c 100644 --- a/src/scan.l +++ b/src/scan.l @@ -4,6 +4,11 @@ #include +#include +#include +#include +#include + #include "input.h" #include "util.h" #include "Scope.h" @@ -19,10 +24,6 @@ #include "Analyzer.h" #include "AnalyzerTags.h" -#include -#include -#include - extern YYLTYPE yylloc; // holds start line and column of token extern int print_loaded_scripts; extern int generate_documentation; diff --git a/src/types.bif b/src/types.bif index 4496e444a1..8bc5ab8510 100644 --- a/src/types.bif +++ b/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;