diff --git a/policy/bro.init b/policy/bro.init index ba5c1d0b8f..f51c2d0a17 100644 --- a/policy/bro.init +++ b/policy/bro.init @@ -720,54 +720,186 @@ 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 a null pointer, 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 +1381,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..50fb774df0 100644 --- a/src/Analyzer.cc +++ b/src/Analyzer.cc @@ -38,6 +38,8 @@ #include "Syslog-binpac.h" #include "ConnSizeAnalyzer.h" +#include + // Keep same order here as in AnalyzerTag definition! const Analyzer::Config Analyzer::analyzer_configs[] = { { AnalyzerTag::Error, "", 0, 0, 0, false }, @@ -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..61636d133f 100644 --- a/src/BroString.cc +++ b/src/BroString.cc @@ -7,9 +7,12 @@ #include #include +#include + #include "BroString.h" #include "Var.h" + #ifdef DEBUG #define DEBUG_STR(msg) DBG_LOG(DBG_STRING, msg) #else 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..375805a492 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,312 @@ #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); } 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: + if ( rpc_success && nfs_status == BifEnum::NFS3::NFS3ERR_OK ) + 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: + if (rpc_success) + reply = nfs3_lookup_reply(buf, n, nfs_status); + event = nfs_proc_lookup; break; - case NFS_PROC_FSSTAT: - if ( success ) + case BifEnum::NFS3::PROC_READ: + if (rpc_success) { - if ( ! buf || status != 0 ) - return 0; - - 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 - - reply = r; - event = nfs_request_fsstat; - } - else - { - reply = ExtractOptAttrs(buf, n); - event = nfs_attempt_fsstat; + 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; + case BifEnum::NFS3::PROC_READLINK: + if (rpc_success) + reply = nfs3_readlink_reply(buf, n, nfs_status); + + event = nfs_proc_readlink; + break; + + case BifEnum::NFS3::PROC_WRITE: + if (rpc_success) + reply = nfs3_write_reply(buf, n, nfs_status); + + event = nfs_proc_write; + break; + + case BifEnum::NFS3::PROC_CREATE: + if (rpc_success) + reply = nfs3_newobj_reply(buf, n, nfs_status); + + event = nfs_proc_create; + break; + + case BifEnum::NFS3::PROC_MKDIR: + if (rpc_success) + reply = nfs3_newobj_reply(buf, n, nfs_status); + + event = nfs_proc_mkdir; + break; + + case BifEnum::NFS3::PROC_REMOVE: + if (rpc_success) + reply = nfs3_delobj_reply(buf, n); + + event = nfs_proc_remove; + break; + + case BifEnum::NFS3::PROC_RMDIR: + if (rpc_success) + reply = nfs3_delobj_reply(buf, n); + + event = nfs_proc_rmdir; + break; + + case BifEnum::NFS3::PROC_READDIR: + if (rpc_success) + reply = nfs3_readdir_reply(false, buf, n, nfs_status); + + event = nfs_proc_readdir; + break; + + case BifEnum::NFS3::PROC_READDIRPLUS: + if (rpc_success) + 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 ( ! 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 +327,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 +349,296 @@ 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)); + rep->Assign(4, ExtractUint64(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. + } + 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 +651,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..bf3e308697 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,58 @@ 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 +77,50 @@ 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_request_getattr || rpc_call; } + static bool Available() { return true; } }; +#if 0 +namespace nfs3_types { +#define NFS3_MAX_FHSIZE 64 + class nfs3_type { + public: + //nfs3_type(const u_char*&buf, int& len) = 0; + virtual ~nfs3_type() + { + } + virtual Val *GetVal() = 0; + bool IsValid() { return valid; }; + + bool valid; + }; + + // A file handle + class nfs3_fh : public nfs3_type { + public: + nfs3_fh(const u_char*&buf, int& len) { + const u_char *fh_tmp; + int fh_len; + valid = false; + fh_tmp = extract_XDR_opaque(buf,len,fh_len,NFS3_MAX_FHSIZE); + if (fh_tmp) { + fh = new StringVal(new BroString(fh, fh_len, 0)); + valid = true; + } + else + fh = 0; + } + + ~nfs3_fh() { printf("~nfs3_fh\n"); } + + Val *GetVal() { return fh; } + + // Data + StringVal *fh; + }; // nfs3_fh + +}; +#endif + #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..760b97171b 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..efa88484d4 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,55 +275,114 @@ 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 = INSYNC; + resync_toskip = 0; + start_time = 0; + last_time = 0; } void Contents_RPC::Init() @@ -323,181 +393,279 @@ void Contents_RPC::Init() static_cast(Parent())->TCP(); assert(tcp); - resync = (IsOrig() ? tcp->OrigState() : tcp->RespState()) != - TCP_ENDPOINT_ESTABLISHED; + if ( (IsOrig() ? tcp->OrigState() : tcp->RespState()) != TCP_ENDPOINT_ESTABLISHED ) + { + NeedResync(); + } } -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 == 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 +688,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 +706,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 +714,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..349f4870e4 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,106 @@ 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 data. data and len will be adjustes + // accordingly. Returns true if we "exptected" 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_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 +242,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 7f83e16784..9d8dd814d4 100644 --- a/src/bro.bif +++ b/src/bro.bif @@ -3457,3 +3457,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; diff --git a/src/util.h b/src/util.h index a288ed30c8..51b3244a9e 100644 --- a/src/util.h +++ b/src/util.h @@ -13,6 +13,11 @@ // Expose C99 functionality from inttypes.h, which would otherwise not be // available in C++. +#ifndef _ISOC99_SOURCE +# define _ISOC99_SOURCE +#endif +#define __STDC_LIMIT_MACROS +#define __STDC_CONSTANT_MACROS #define __STDC_FORMAT_MACROS #include