diff --git a/policy/logging.bro b/policy/logging.bro new file mode 100644 index 0000000000..2a61a12a63 --- /dev/null +++ b/policy/logging.bro @@ -0,0 +1,93 @@ +module Logging; + +export { + # The set of writers Bro provides. + type Writer: enum { + WRITER_DEFAULT, # See default_writer below. + WRITER_CSV, + WRITER_DATA_SERIES, + WRITER_SYSLOG + }; + + # Each stream gets a unique ID. This type will be extended by + # other scripts. + type ID: enum { + Unknown + }; + + # The default writer to use if a filter does not specify + # anything else. + const default_writer = WRITER_CSV &redef; + + # Type defining a stream. + type Stream: record { + id : ID; # The ID of the stream. + columns : any; # A record type defining the stream's output columns. + }; + + # A filter defining what to record. + type Filter: record { + # A name to reference this filter. + name: string; + + # A predicate returning True if the filter wants a log entry + # to be recorded. If not given, an implicit True is assumed + # for all entries. The predicate receives one parameter: + # an instance of the log's record type with the fields to be + # logged. + pred: function(log: any) &optional; + + # A path for outputting everything matching this + # filter. The path is either a string, or a function + # called with a single ``ID`` argument and returning a string. + # + # The specific interpretation of the string is left to the + # Writer, but if it's refering to a file, it's assumed that no + # extension is given; the writer will add whatever is + # appropiate. + path: any &optional; + + # A subset of column names to record. If not given, all + # columns are recorded. + select: set[string] &optional; + + # An event that is raised whenever the filter is applied + # to an entry. The event receives the same parameter + # as the predicate. It will always be generated, + # independent of what the predicate returns. + ev: event(c: connection, log: any) &optional; + + # The writer to use. + writer: Writer &default=default_writer; + }; + + global filters: table[ID] of set[Filter]; + + # Logs the record "rec" to the stream "id". The type of + # "rec" must match the stream's "columns" field. + global log: function(id: ID, rec: any); + + # Returns an existing filter previously installed for stream + # "id" under the given "name". If no such filter exists, + # the record "NoSuchFilter" is returned. + global get_filter: function(id: ID, name: string) : Filter; + + global create: function(stream: Stream); + global add_filter: function(id: ID, filter: Filter); +} + +# Sentinel representing an unknown filter. +const NoSuchFilter: Filter = [$name=""]; + +function add_filter(id: ID, filter: Filter) + { + if ( id !in filters ) + filters[id] = set(); + + add filters[id][filter]; + } + +function log(id: ID, rec: any) + { + logging_log(id, rec); + } \ No newline at end of file diff --git a/policy/test-logging.bro b/policy/test-logging.bro new file mode 100644 index 0000000000..267f9c36a5 --- /dev/null +++ b/policy/test-logging.bro @@ -0,0 +1,27 @@ +module SSH; + +@load logging + +# Create a new ID for our log stream +redef enum Logging::ID += { SSH }; + +# Define a record with all the columns the log file can have. +# (I'm using a subset of fields from ssh-ext for demonstration.) +type Log: record { + t: time; + id: conn_id; # Will be rolled out into individual columns. + status: string &optional; + country: string &default="unknown"; +}; + +event bro_init() +{ + # Create the stream. + Logging::create([$id=SSH, $columns=Log]); + + # Add a default filter that simply logs everything to "ssh.log" using the default writer. + #Logging::add_filter(SSH, [$name="default", $path="ssh"]); +} + +# Log something. +Logging::log(SSH, [$t=network_time(), $status="ok"]); diff --git a/src/Type.cc b/src/Type.cc index 1f5c22d58b..7e08c4b140 100644 --- a/src/Type.cc +++ b/src/Type.cc @@ -1336,7 +1336,9 @@ static int is_init_compat(const BroType* t1, const BroType* t2) int same_type(const BroType* t1, const BroType* t2, int is_init) { - if ( t1 == t2 ) + if ( t1 == t2 || + t1->Tag() == TYPE_ANY || + t2->Tag() == TYPE_ANY ) return 1; t1 = flatten_type(t1); diff --git a/src/Val.cc b/src/Val.cc index f43bafe4d7..307bad61d1 100644 --- a/src/Val.cc +++ b/src/Val.cc @@ -3300,6 +3300,10 @@ Val* check_and_promote(Val* v, const BroType* t, int is_init) TypeTag t_tag = t->Tag(); TypeTag v_tag = vt->Tag(); + + // More thought definitely needs to go into this. + if ( t_tag == TYPE_ANY || v_tag == TYPE_ANY ) + return v; if ( ! EitherArithmetic(t_tag, v_tag) || /* allow sets as initializers */ diff --git a/src/bro.bif b/src/bro.bif index 0de77bfc49..78e83b920b 100644 --- a/src/bro.bif +++ b/src/bro.bif @@ -359,6 +359,18 @@ function cat%(...%): string return new StringVal(s); %} + +function logging_log%(id: Logging_ID, rec: any%): bool + %{ + // Generate the log line event + // Lookup the log file + TableVal *f = opt_internal_table("Logging::filters"); + TableVal *s = f->Lookup(id, 0)->AsTableVal(); + + // For each filter on 'id' + // Format the output + // Print the line + %} function cat_sep%(sep: string, def: string, ...%): string %{