diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c427188ca..0738eb387d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,10 @@ project(Bro C CXX) + +# When changing the minimum version here, also adapt +# cmake/BroPluginDynamic and +# aux/bro-aux/plugin-support/skeleton/CMakeLists.txt cmake_minimum_required(VERSION 2.6.3 FATAL_ERROR) + include(cmake/CommonCMakeConfig.cmake) ######################################################################## @@ -17,6 +22,7 @@ set(BRO_SCRIPT_SOURCE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/scripts) get_filename_component(BRO_SCRIPT_INSTALL_PATH ${BRO_SCRIPT_INSTALL_PATH} ABSOLUTE) +set(BRO_PLUGIN_INSTALL_PATH ${BRO_ROOT_DIR}/share/bro/plugins) set(BRO_MAGIC_INSTALL_PATH ${BRO_ROOT_DIR}/share/bro/magic) set(BRO_MAGIC_SOURCE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/magic/database) @@ -183,6 +189,10 @@ include(MiscTests) include(PCAPTests) include(OpenSSLTests) include(CheckNameserCompat) +include(GetArchitecture) + +# Tell the plugin code that we're building as part of the main tree. +set(BRO_PLUGIN_INTERNAL_BUILD true CACHE INTERNAL "" FORCE) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) diff --git a/cmake b/cmake index 0187b33a29..fc53d57770 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 0187b33a29d5ec824f940feff60dc5d8c2fe314f +Subproject commit fc53d57770c86fbf9bd2d9694f06a1d539ebe35f diff --git a/config.h.in b/config.h.in index 2d065f755e..61124d2f67 100644 --- a/config.h.in +++ b/config.h.in @@ -209,3 +209,14 @@ /* Common IPv6 extension structure */ #cmakedefine HAVE_IP6_EXT + +/* String with host architecture (e.g., "linux-x86_64") */ +#define HOST_ARCHITECTURE "@HOST_ARCHITECTURE@" + +/* String with extension of dynamic libraries (e.g., ".so") */ +#define SHARED_LIBRARY_SUFFIX "@CMAKE_SHARED_LIBRARY_SUFFIX@" + +/* True if we're building outside of the main Bro source code tree. */ +#ifndef BRO_PLUGIN_INTERNAL_BUILD +#define BRO_PLUGIN_INTERNAL_BUILD @BRO_PLUGIN_INTERNAL_BUILD@ +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c881cc4df1..34af4eb974 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -414,6 +414,17 @@ add_dependencies(bro bif_loader_plugins) # Install *.bif.bro. install(DIRECTORY ${CMAKE_BINARY_DIR}/scripts/base/bif DESTINATION ${BRO_SCRIPT_INSTALL_PATH}/base) -# Make clean removes the bif directory. -set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/scripts/base/bif) +# Install the plugin directory with a short README. +set(plugin_binary_dir "${CMAKE_BINARY_DIR}/plugins") + +add_custom_command(OUTPUT ${plugin_binary_dir}/README + COMMAND mkdir ARGS -p ${plugin_binary_dir} + COMMAND echo ARGS "This directory holds dynamic Bro plugins." >${plugin_binary_dir}/README) + +add_custom_target(plugin-dir DEPENDS ${plugin_binary_dir}/README) +add_dependencies(bro plugin-dir) + +# Make clean removes the bif and plugin directories. +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/scripts/base/bif) +set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${plugin_binary_dir}) diff --git a/src/DebugLogger.cc b/src/DebugLogger.cc index 95049ef70b..267383503d 100644 --- a/src/DebugLogger.cc +++ b/src/DebugLogger.cc @@ -5,6 +5,7 @@ #include "DebugLogger.h" #include "Net.h" +#include "plugin/Plugin.h" DebugLogger debug_logger("debug"); @@ -73,10 +74,12 @@ void DebugLogger::EnableStreams(const char* s) { if ( strcasecmp("verbose", tok) == 0 ) verbose = true; - else + else if ( strncmp(tok, "plugin-", 7) != 0 ) reporter->FatalError("unknown debug stream %s\n", tok); } + enabled_streams.insert(tok); + tok = strtok(0, ","); } @@ -105,4 +108,24 @@ void DebugLogger::Log(DebugStream stream, const char* fmt, ...) fflush(file); } +void DebugLogger::Log(const plugin::Plugin& plugin, const char* fmt, ...) + { + string tok = string("plugin-") + plugin.Name(); + tok = strreplace(tok, "::", "-"); + + if ( enabled_streams.find(tok) == enabled_streams.end() ) + return; + + fprintf(file, "%17.06f/%17.06f [plugin %s] ", + network_time, current_time(true), plugin.Name()); + + va_list ap; + va_start(ap, fmt); + vfprintf(file, fmt, ap); + va_end(ap); + + fputc('\n', file); + fflush(file); + } + #endif diff --git a/src/DebugLogger.h b/src/DebugLogger.h index c5744642f5..a5d3c2878c 100644 --- a/src/DebugLogger.h +++ b/src/DebugLogger.h @@ -7,6 +7,8 @@ #ifdef DEBUG #include +#include +#include // To add a new debugging stream, add a constant here as well as // an entry to DebugLogger::streams in DebugLogger.cc. @@ -27,7 +29,7 @@ enum DebugStream { DBG_INPUT, // Input streams DBG_THREADING, // Threading system DBG_FILE_ANALYSIS, // File analysis - DBG_PLUGINS, + DBG_PLUGINS, // Plugin support NUM_DBGS // Has to be last }; @@ -39,6 +41,10 @@ enum DebugStream { #define DBG_PUSH(stream) debug_logger.PushIndent(stream) #define DBG_POP(stream) debug_logger.PopIndent(stream) +#define PLUGIN_DBG_LOG(plugin, args...) debug_logger.Log(plugin, args) + +namespace plugin { class Plugin; } + class DebugLogger { public: // Output goes to stderr per default. @@ -46,6 +52,7 @@ public: ~DebugLogger(); void Log(DebugStream stream, const char* fmt, ...); + void Log(const plugin::Plugin& plugin, const char* fmt, ...); void PushIndent(DebugStream stream) { ++streams[int(stream)].indent; } @@ -76,6 +83,8 @@ private: bool enabled; }; + std::set enabled_streams; + static Stream streams[NUM_DBGS]; }; @@ -86,6 +95,7 @@ extern DebugLogger debug_logger; #define DBG_LOG_VERBOSE(args...) #define DBG_PUSH(stream) #define DBG_POP(stream) +#define PLUGIN_DBG_LOG(plugin, args...) #endif #endif diff --git a/src/Event.cc b/src/Event.cc index 0fb76d10bc..7774042ac4 100644 --- a/src/Event.cc +++ b/src/Event.cc @@ -6,6 +6,7 @@ #include "Func.h" #include "NetVar.h" #include "Trigger.h" +#include "plugin/Manager.h" EventMgr mgr; @@ -77,6 +78,9 @@ EventMgr::~EventMgr() void EventMgr::QueueEvent(Event* event) { + if ( plugin_mgr->QueueEvent(event) ) + return; + if ( ! head ) head = tail = event; else @@ -115,6 +119,8 @@ void EventMgr::Drain() SegmentProfiler(segment_logger, "draining-events"); + plugin_mgr->DrainEvents(); + draining = true; while ( head ) Dispatch(); diff --git a/src/FlowSrc.cc b/src/FlowSrc.cc index 32aa4c4e3a..563153b634 100644 --- a/src/FlowSrc.cc +++ b/src/FlowSrc.cc @@ -49,7 +49,7 @@ void FlowSrc::Process() // This is normally done by calling net_packet_dispatch(), // but as we don't have a packet to dispatch ... - network_time = next_timestamp; + net_update_time(next_timestamp); expire_timers(); netflow_analyzer->downflow()->set_exporter_ip(exporter_ip); diff --git a/src/Func.cc b/src/Func.cc index 11749a8a9c..d8e0ab2c96 100644 --- a/src/Func.cc +++ b/src/Func.cc @@ -46,6 +46,7 @@ #include "Event.h" #include "Traverse.h" #include "Reporter.h" +#include "plugin/Manager.h" extern RETSIGTYPE sig_handler(int signo); @@ -226,7 +227,7 @@ TraversalCode Func::Traverse(TraversalCallback* cb) const HANDLE_TC_STMT_PRE(tc); // FIXME: Traverse arguments to builtin functions, too. - if ( kind == BRO_FUNC ) + if ( kind == BRO_FUNC && scope ) { tc = scope->Traverse(cb); HANDLE_TC_STMT_PRE(tc); @@ -281,6 +282,50 @@ Val* BroFunc::Call(val_list* args, Frame* parent) const #ifdef PROFILE_BRO_FUNCTIONS DEBUG_MSG("Function: %s\n", id->Name()); #endif + + Val* plugin_result = plugin_mgr->CallFunction(this, args); + + if ( plugin_result ) + { + // TODO: We should factor this out into its own method. + switch ( Flavor() ) { + case FUNC_FLAVOR_EVENT: + Unref(plugin_result); + plugin_result = 0; + break; + + case FUNC_FLAVOR_HOOK: + if ( plugin_result->Type()->Tag() != TYPE_BOOL ) + reporter->InternalError("plugin returned non-bool for hook"); + + break; + + case FUNC_FLAVOR_FUNCTION: + { + BroType* yt = FType()->YieldType(); + + if ( (! yt) || yt->Tag() == TYPE_VOID ) + { + Unref(plugin_result); + plugin_result = 0; + } + + else + { + if ( plugin_result->Type()->Tag() != yt->Tag() ) + reporter->InternalError("plugin returned wrong type for function call"); + } + + break; + } + } + + loop_over_list(*args, i) + Unref((*args)[i]); + + return plugin_result; + } + if ( bodies.empty() ) { // Can only happen for events and hooks. diff --git a/src/Net.cc b/src/Net.cc index ac4dacf9b8..6cc148770e 100644 --- a/src/Net.cc +++ b/src/Net.cc @@ -30,6 +30,7 @@ #include "PacketSort.h" #include "Serializer.h" #include "PacketDumper.h" +#include "plugin/Manager.h" extern "C" { #include "setsignal.h" @@ -144,13 +145,17 @@ RETSIGTYPE watchdog(int /* signo */) return RETSIGVAL; } +void net_update_time(double new_network_time) + { + network_time = new_network_time; + plugin_mgr->UpdateNetworkTime(network_time); + } + void net_init(name_list& interfaces, name_list& readfiles, name_list& netflows, name_list& flowfiles, const char* writefile, const char* filter, const char* secondary_filter, int do_watchdog) { - init_net_var(); - if ( readfiles.length() > 0 || flowfiles.length() > 0 ) { reading_live = pseudo_realtime > 0.0; @@ -323,7 +328,7 @@ void net_packet_dispatch(double t, const struct pcap_pkthdr* hdr, : timer_mgr; // network_time never goes back. - network_time = tmgr->Time() < t ? t : tmgr->Time(); + net_update_time(tmgr->Time() < t ? t : tmgr->Time()); current_pktsrc = src_ps; current_iosrc = src_ps; @@ -456,7 +461,7 @@ void net_run() { // Take advantage of the lull to get up to // date on timers and events. - network_time = ct; + net_update_time(ct); expire_timers(); usleep(1); // Just yield. } @@ -478,7 +483,7 @@ void net_run() // date on timers and events. Because we only // have timers as sources, going to sleep here // doesn't risk blocking on other inputs. - network_time = current_time(); + net_update_time(current_time()); expire_timers(); // Avoid busy-waiting - pause for 100 ms. diff --git a/src/Net.h b/src/Net.h index 5b959d1688..765e9f709d 100644 --- a/src/Net.h +++ b/src/Net.h @@ -19,6 +19,7 @@ extern void net_run(); extern void net_get_final_stats(); extern void net_finish(int drain_events); extern void net_delete(); // Reclaim all memory, etc. +extern void net_update_time(double new_network_time); extern void net_packet_arrival(double t, const struct pcap_pkthdr* hdr, const u_char* pkt, int hdr_size, PktSrc* src_ps); diff --git a/src/RemoteSerializer.cc b/src/RemoteSerializer.cc index c8cf03667b..e60ad34b46 100644 --- a/src/RemoteSerializer.cc +++ b/src/RemoteSerializer.cc @@ -1455,7 +1455,7 @@ void RemoteSerializer::Process() // FIXME: The following chunk of code is copied from // net_packet_dispatch(). We should change that function // to accept an IOSource instead of the PktSrc. - network_time = p->time; + net_update_time(p->time); SegmentProfiler(segment_logger, "expiring-timers"); TimerMgr* tmgr = sessions->LookupTimerMgr(GetCurrentTag()); diff --git a/src/builtin-func.l b/src/builtin-func.l index 3e5f7bce0a..63df96eb80 100644 --- a/src/builtin-func.l +++ b/src/builtin-func.l @@ -247,14 +247,13 @@ void init_alternative_mode() fprintf(fp_func_init, "\n"); fprintf(fp_func_init, "#include \n"); fprintf(fp_func_init, "#include \n"); + fprintf(fp_func_init, "#include \"plugin/Plugin.h\"\n"); fprintf(fp_func_init, "#include \"%s.h\"\n", input_filename); fprintf(fp_func_init, "\n"); fprintf(fp_func_init, "namespace plugin { namespace %s {\n", plugin); fprintf(fp_func_init, "\n"); - fprintf(fp_func_init, "std::list > __bif_%s_init()\n", name); + fprintf(fp_func_init, "void __bif_%s_init(plugin::Plugin* plugin)\n", name); fprintf(fp_func_init, "\t{\n"); - fprintf(fp_func_init, "\tstd::list > bifs;\n"); - fprintf(fp_func_init, "\n"); } } @@ -266,7 +265,6 @@ void finish_alternative_mode() if ( plugin ) { fprintf(fp_func_init, "\n"); - fprintf(fp_func_init, "\treturn bifs;\n"); fprintf(fp_func_init, "\t}\n"); fprintf(fp_func_init, "} }\n"); fprintf(fp_func_init, "\n"); diff --git a/src/builtin-func.y b/src/builtin-func.y index 662256b8d6..1b22436fff 100644 --- a/src/builtin-func.y +++ b/src/builtin-func.y @@ -267,12 +267,12 @@ void print_event_c_body(FILE *fp) //fprintf(fp, "%s // end namespace\n", decl.generate_c_namespace_end.c_str()); } -void record_bif_item(const char* id, int type) +void record_bif_item(const char* id, const char* type) { if ( ! plugin ) return; - fprintf(fp_func_init, "\tbifs.push_back(std::make_pair(\"%s\", %d));\n", id, type); + fprintf(fp_func_init, "\tplugin->AddBifItem(\"%s\", plugin::BifItem::%s);\n", id, type); } %} @@ -358,7 +358,7 @@ type_def: TOK_TYPE opt_ws TOK_ID opt_ws ':' opt_ws type_def_types opt_ws ';' decl.c_fullname.c_str(), decl.bro_fullname.c_str(), type_name.c_str()); - record_bif_item(decl.bro_fullname.c_str(), 5); + record_bif_item(decl.bro_fullname.c_str(), "TYPE"); } ; @@ -401,7 +401,7 @@ enum_def: enum_def_1 enum_list TOK_RPB "\t%s = internal_type(\"%s\")->AsEnumType();\n", decl.c_fullname.c_str(), decl.bro_fullname.c_str()); - record_bif_item(decl.bro_fullname.c_str(), 5); + record_bif_item(decl.bro_fullname.c_str(), "TYPE"); } ; @@ -457,7 +457,7 @@ const_def: TOK_CONST opt_ws TOK_ID opt_ws ':' opt_ws TOK_ID opt_ws ';' decl.c_fullname.c_str(), decl.bro_fullname.c_str(), accessor); - record_bif_item(decl.bro_fullname.c_str(), 3); + record_bif_item(decl.bro_fullname.c_str(), "CONSTANT"); } attr_list: @@ -545,7 +545,7 @@ head_1: TOK_ID opt_ws arg_begin "Val* %s(Frame* frame, val_list* %s)", decl.c_fullname.c_str(), arg_list_name); - record_bif_item(decl.bro_fullname.c_str(), 1); + record_bif_item(decl.bro_fullname.c_str(), "FUNCTION"); } else if ( definition_type == EVENT_DEF ) { @@ -562,7 +562,7 @@ head_1: TOK_ID opt_ws arg_begin "\t%s = internal_handler(\"%s\");\n", decl.c_fullname.c_str(), decl.bro_fullname.c_str()); - record_bif_item(decl.bro_fullname.c_str(), 2); + record_bif_item(decl.bro_fullname.c_str(), "EVENT"); // C++ prototypes of bro_event_* functions will // be generated later. diff --git a/src/main.cc b/src/main.cc index 0f60a4c70f..bfbcf77a6d 100644 --- a/src/main.cc +++ b/src/main.cc @@ -198,6 +198,7 @@ void usage() fprintf(stderr, " -N|--print-plugins | print available plugins and exit (-NN for verbose)\n"); fprintf(stderr, " -O|--optimize | optimize policy script\n"); fprintf(stderr, " -P|--prime-dns | prime DNS\n"); + fprintf(stderr, " -Q|--time | print execution time summary to stderr\n"); fprintf(stderr, " -R|--replay | replay events\n"); fprintf(stderr, " -S|--debug-rules | enable rule debugging\n"); fprintf(stderr, " -T|--re-level | set 'RE_level' for rules\n"); @@ -220,8 +221,10 @@ void usage() fprintf(stderr, " -n|--idmef-dtd | specify path to IDMEF DTD file\n"); #endif - fprintf(stderr, " $BROPATH | file search path (%s)\n", bro_path()); + fprintf(stderr, " $BROPATH | file search path (%s)\n", bro_path().c_str()); fprintf(stderr, " $BROMAGIC | libmagic mime magic database search path (%s)\n", bro_magic_path()); + fprintf(stderr, " $BRO_PLUGINS | plugin search path (%s)\n", bro_plugin_path()); + fprintf(stderr, " $BRO_PREFIXES | prefix list (%s)\n", bro_prefixes()); fprintf(stderr, " $BRO_PREFIXES | prefix list (%s)\n", bro_prefixes().c_str()); fprintf(stderr, " $BRO_DNS_FAKE | disable DNS lookups (%s)\n", bro_dns_fake()); fprintf(stderr, " $BRO_SEED_FILE | file to load seeds from (not set)\n"); @@ -435,6 +438,8 @@ int main(int argc, char** argv) { std::set_new_handler(bro_new_handler); + double time_start = current_time(true); + brofiler.ReadStats(); bro_argc = argc; @@ -464,6 +469,7 @@ int main(int argc, char** argv) int rule_debug = 0; int RE_level = 4; int print_plugins = 0; + int time_bro = 0; static struct option long_opts[] = { {"bare-mode", no_argument, 0, 'b'}, @@ -545,7 +551,7 @@ int main(int argc, char** argv) opterr = 0; char opts[256]; - safe_strncpy(opts, "B:D:e:f:I:i:K:l:n:p:R:r:s:T:t:U:w:x:X:y:Y:z:CFGLNOPSWbdghvZ", + safe_strncpy(opts, "B:D:e:f:I:i:K:l:n:p:R:r:s:T:t:U:w:x:X:y:Y:z:CFGLNOPSWbdghvZQ", sizeof(opts)); #ifdef USE_PERFTOOLS_DEBUG @@ -674,6 +680,10 @@ int main(int argc, char** argv) dns_type = DNS_PRIME; break; + case 'Q': + time_bro = 1; + break; + case 'R': events_file = optarg; break; @@ -760,6 +770,7 @@ int main(int argc, char** argv) reporter = new Reporter(); thread_mgr = new threading::Manager(); + plugin_mgr = new plugin::Manager(); #ifdef DEBUG if ( debug_streams ) @@ -810,6 +821,8 @@ int main(int argc, char** argv) if ( ! bare_mode ) add_input_file("base/init-default.bro"); + plugin_mgr->LoadPluginsFrom(bro_plugin_path()); + if ( optind == argc && read_files.length() == 0 && flow_files.length() == 0 && interfaces.length() == 0 && @@ -842,7 +855,6 @@ int main(int argc, char** argv) analyzer_mgr = new analyzer::Manager(); log_mgr = new logging::Manager(); input_mgr = new input::Manager(); - plugin_mgr = new plugin::Manager(); file_mgr = new file_analysis::Manager(); plugin_mgr->InitPreScript(); @@ -877,9 +889,13 @@ int main(int argc, char** argv) yyparse(); - plugin_mgr->InitPostScript(); - analyzer_mgr->InitPostScript(); - file_mgr->InitPostScript(); + init_general_global_var(); + init_net_var(); + + plugin_mgr->InitBifs(); + + if ( reporter->Errors() > 0 ) + exit(1); if ( print_plugins ) { @@ -887,6 +903,10 @@ int main(int argc, char** argv) exit(1); } + plugin_mgr->InitPostScript(); + analyzer_mgr->InitPostScript(); + file_mgr->InitPostScript(); + #ifdef USE_PERFTOOLS_DEBUG } #endif @@ -916,8 +936,6 @@ int main(int argc, char** argv) reporter->InitOptions(); - init_general_global_var(); - if ( user_pcap_filter ) { ID* id = global_scope()->Lookup("cmd_line_bpf_filter"); @@ -1079,7 +1097,7 @@ int main(int argc, char** argv) if ( ! reading_live && ! reading_traces ) // Set up network_time to track real-time, since // we don't have any other source for it. - network_time = current_time(); + net_update_time(current_time()); EventHandlerPtr bro_init = internal_handler("bro_init"); if ( bro_init ) //### this should be a function @@ -1165,7 +1183,43 @@ int main(int argc, char** argv) #endif + double time_net_start = current_time(true);; + + unsigned int mem_net_start_total; + unsigned int mem_net_start_malloced; + + if ( time_bro ) + { + get_memory_usage(&mem_net_start_total, &mem_net_start_malloced); + + fprintf(stderr, "# initialization %.6f\n", time_net_start - time_start); + + fprintf(stderr, "# initialization %uM/%uM\n", + mem_net_start_total / 1024 / 1024, + mem_net_start_malloced / 1024 / 1024); + } + net_run(); + + double time_net_done = current_time(true);; + + unsigned int mem_net_done_total; + unsigned int mem_net_done_malloced; + + if ( time_bro ) + { + get_memory_usage(&mem_net_done_total, &mem_net_done_malloced); + + fprintf(stderr, "# total time %.6f, processing %.6f\n", + time_net_done - time_start, time_net_done - time_net_start); + + fprintf(stderr, "# total mem %uM/%uM, processing %uM/%uM\n", + mem_net_done_total / 1024 / 1024, + mem_net_done_malloced / 1024 / 1024, + (mem_net_done_total - mem_net_start_total) / 1024 / 1024, + (mem_net_done_malloced - mem_net_start_malloced) / 1024 / 1024); + } + done_with_network(); net_delete(); diff --git a/src/plugin/ComponentManager.h b/src/plugin/ComponentManager.h index ccc076db28..71994d82d1 100644 --- a/src/plugin/ComponentManager.h +++ b/src/plugin/ComponentManager.h @@ -83,8 +83,6 @@ public: */ T GetComponentTag(Val* v) const; -protected: - /** * Add a component the internal maps used to keep track of it and create * a script-layer ID for the component's enum value. diff --git a/src/plugin/Macros.h b/src/plugin/Macros.h index 9362642e91..3248031e98 100644 --- a/src/plugin/Macros.h +++ b/src/plugin/Macros.h @@ -28,18 +28,15 @@ * must be unique across all loaded plugins. */ #define BRO_PLUGIN_BEGIN(_ns, _name) \ - namespace plugin { namespace _ns ## _ ## _name {\ + namespace plugin { namespace _ns ## _ ## _name { \ class Plugin : public plugin::Plugin { \ protected: \ - void InitPreScript() \ + void InitPreScript() \ { \ SetName(#_ns "::" #_name); \ - SetVersion(-1);\ - SetAPIVersion(BRO_PLUGIN_API_VERSION);\ - SetDynamicPlugin(false); -// TODO: The SetDynamicPlugin() call is currently hardcoded to false. Change -// once we have dynamic plugins as well. - + SetVersion(-1); \ + SetAPIVersion(BRO_PLUGIN_API_VERSION); \ + SetDynamicPlugin(! BRO_PLUGIN_INTERNAL_BUILD);\ /** * Ends the definition of a plugin. @@ -76,8 +73,8 @@ * interpreter. */ #define BRO_PLUGIN_BIF_FILE(file) \ - extern std::list > __bif_##file##_init(); \ - AddBifInitFunction(&__bif_##file##_init); + extern void __bif_##file##_init(plugin::Plugin*); \ + __AddBifInitFunction(&__bif_##file##_init); /** * Defines a component implementing a protocol analyzer. diff --git a/src/plugin/Manager.cc b/src/plugin/Manager.cc index 67f4dea2bd..ddd953ab22 100644 --- a/src/plugin/Manager.cc +++ b/src/plugin/Manager.cc @@ -1,11 +1,23 @@ // See the file "COPYING" in the main distribution directory for copyright. +#include +#include +#include +#include +#include +#include + #include "Manager.h" #include "../Reporter.h" +#include "../Func.h" +#include "../Event.h" using namespace plugin; +string Manager::current_dir; +string Manager::current_sopath; + Manager::Manager() { init = false; @@ -16,18 +28,162 @@ Manager::~Manager() assert(! init); } -bool Manager::LoadPlugin(const std::string& path) +void Manager::LoadPluginsFrom(const string& dir) { assert(! init); - reporter->InternalError("plugin::Manager::LoadPlugin not yet implemented"); - return false; + + if ( dir.empty() ) + return; + + if ( dir.find(":") != string::npos ) + { + // Split at ":". + std::stringstream s(dir); + std::string d; + + while ( std::getline(s, d, ':') ) + LoadPluginsFrom(d); + + return; + } + + if ( ! is_dir(dir) ) + { + DBG_LOG(DBG_PLUGINS, "Not a valid plugin directory: %s", dir.c_str()); + return; + } + + int rc = LoadPlugin(dir); + + if ( rc >= 0 ) + return; + + DBG_LOG(DBG_PLUGINS, "Searching directory %s recursively for plugins", dir.c_str()); + + DIR* d = opendir(dir.c_str()); + + if ( ! d ) + { + DBG_LOG(DBG_PLUGINS, "Cannot open directory %s", dir.c_str()); + return; + } + + bool found = false; + + struct dirent *dp; + + while ( (dp = readdir(d)) ) + { + struct stat st; + + if ( strcmp(dp->d_name, "..") == 0 + || strcmp(dp->d_name, ".") == 0 ) + continue; + + string path = dir + "/" + dp->d_name; + + if( stat(path.c_str(), &st) < 0 ) + { + DBG_LOG(DBG_PLUGINS, "Cannot stat %s: %s", path.c_str(), strerror(errno)); + continue; + } + + if ( st.st_mode & S_IFDIR ) + LoadPluginsFrom(path); + } } -bool Manager::LoadPluginsFrom(const std::string& dir) +int Manager::LoadPlugin(const std::string& dir) { assert(! init); - reporter->InternalError("plugin::Manager::LoadPluginsFrom not yet implemented"); - return false; + + // Check if it's a plugin dirctory. + if ( ! is_file(dir + "/__bro_plugin__") ) + return -1; + + DBG_LOG(DBG_PLUGINS, "Loading plugin from %s", dir.c_str()); + + // Add the "scripts" and "bif" directories to BROPATH. + string scripts = dir + "/scripts"; + + if ( is_dir(scripts) ) + { + DBG_LOG(DBG_PLUGINS, " Adding %s to BROPATH", scripts.c_str()); + add_to_bro_path(scripts); + } + + string bif = dir + "/bif"; + + if ( is_dir(bif) ) + { + DBG_LOG(DBG_PLUGINS, " Adding %s to BROPATH", bif.c_str()); + add_to_bro_path(bif); + } + + // Load dylib/scripts/__load__.bro automatically. + string dyinit = dir + "/dylib/scripts/__load__.bro"; + + if ( is_file(dyinit) ) + { + DBG_LOG(DBG_PLUGINS, " Adding %s for loading", dyinit.c_str()); + add_input_file(dyinit.c_str()); + } + + // Load scripts/__load__.bro automatically. + string init = scripts + "/__load__.bro"; + + if ( is_file(init) ) + { + DBG_LOG(DBG_PLUGINS, " Adding %s for loading", init.c_str()); + add_input_file(init.c_str()); + } + + // Load bif/__load__.bro automatically. + init = bif + "/__load__.bro"; + + if ( is_file(init) ) + { + DBG_LOG(DBG_PLUGINS, " Adding %s for loading", init.c_str()); + add_input_file(init.c_str()); + } + + // Load shared libraries. + + string dypattern = dir + "/dylib/*." + HOST_ARCHITECTURE + SHARED_LIBRARY_SUFFIX; + + DBG_LOG(DBG_PLUGINS, " Searching for shared libraries %s", dypattern.c_str()); + + glob_t gl; + + if ( glob(dypattern.c_str(), 0, 0, &gl) == 0 ) + { + for ( size_t i = 0; i < gl.gl_pathc; i++ ) + { + const char* path = gl.gl_pathv[i]; + + current_dir = dir; + current_sopath = path; + void* hdl = dlopen(path, RTLD_LAZY | RTLD_GLOBAL); + current_dir.clear(); + current_sopath.clear(); + + if ( ! hdl ) + { + const char* err = dlerror(); + reporter->FatalError("cannot load plugin library %s: %s", path, err ? err : ""); + } + + DBG_LOG(DBG_PLUGINS, " Loaded %s", path); + } + } + + else + { + DBG_LOG(DBG_PLUGINS, " No shared library found"); + return 1; + } + + return 1; } static bool plugin_cmp(const Plugin* a, const Plugin* b) @@ -39,22 +195,61 @@ bool Manager::RegisterPlugin(Plugin *plugin) { Manager::PluginsInternal()->push_back(plugin); + if ( current_dir.size() && current_sopath.size() ) + plugin->SetPluginLocation(current_dir.c_str(), current_sopath.c_str()); + // Sort plugins by name to make sure we have a deterministic order. PluginsInternal()->sort(plugin_cmp); return true; } +static bool interpreter_plugin_cmp(const InterpreterPlugin* a, const InterpreterPlugin* b) + { + if ( a->Priority() == b->Priority() ) + return a->Name() < b->Name(); + + // Reverse sort. + return a->Priority() > b->Priority(); + } + void Manager::InitPreScript() { assert(! init); for ( plugin_list::iterator i = Manager::PluginsInternal()->begin(); i != Manager::PluginsInternal()->end(); i++ ) - (*i)->InitPreScript(); + { + Plugin* plugin = *i; + + if ( plugin->PluginType() == Plugin::INTERPRETER ) + interpreter_plugins.push_back(dynamic_cast(plugin)); + + plugin->InitPreScript(); + + // Track the file extensions the plugin can handle. + std::stringstream ext(plugin->FileExtensions()); + + // Split at ":". + std::string e; + + while ( std::getline(ext, e, ':') ) + { + DBG_LOG(DBG_PLUGINS, "Plugin %s handles *.%s", plugin->Name(), e.c_str()); + extensions.insert(std::make_pair(e, plugin)); + } + } + + interpreter_plugins.sort(interpreter_plugin_cmp); init = true; } +void Manager::InitBifs() + { + for ( plugin_list::iterator i = Manager::PluginsInternal()->begin(); i != Manager::PluginsInternal()->end(); i++ ) + (*i)->InitBifs(); + } + void Manager::InitPostScript() { assert(init); @@ -78,6 +273,28 @@ void Manager::FinishPlugins() init = false; } +int Manager::TryLoadFile(const char* file) + { + assert(file); + const char* ext = strrchr(file, '.'); + + if ( ! ext ) + return -1; + + extension_map::iterator i = extensions.find(++ext); + if ( i == extensions.end() ) + return -1; + + Plugin* plugin = i->second; + + DBG_LOG(DBG_PLUGINS, "Loading %s with %s", file, plugin->Name()); + + if ( i->second->LoadFile(file) ) + return 1; + + return 0; + } + Manager::plugin_list Manager::Plugins() const { return *Manager::PluginsInternal(); @@ -92,3 +309,67 @@ Manager::plugin_list* Manager::PluginsInternal() return plugins; } + +Val* Manager::CallFunction(const Func* func, val_list* args) const + { + Val* result = 0; + + for ( interpreter_plugin_list::const_iterator i = interpreter_plugins.begin(); + i != interpreter_plugins.end() && ! result; i++ ) + { + result = (*i)->CallFunction(func, args); + + if ( result ) + { + DBG_LOG(DBG_PLUGINS, "Plugin %s replaced call to %s", (*i)->Name(), func->Name()); + return result; + } + } + + return 0; + } + +bool Manager::QueueEvent(Event* event) const + { + for ( interpreter_plugin_list::const_iterator i = interpreter_plugins.begin(); + i != interpreter_plugins.end(); i++ ) + { + if ( (*i)->QueueEvent(event) ) + { + DBG_LOG(DBG_PLUGINS, "Plugin %s handled queueing of event %s", (*i)->Name(), event->Handler()->Name()); + return true; + } + } + + return false; + } + + +void Manager::UpdateNetworkTime(double network_time) const + { + for ( interpreter_plugin_list::const_iterator i = interpreter_plugins.begin(); + i != interpreter_plugins.end(); i++ ) + (*i)->UpdateNetworkTime(network_time); + } + +void Manager::DrainEvents() const + { + for ( interpreter_plugin_list::const_iterator i = interpreter_plugins.begin(); + i != interpreter_plugins.end(); i++ ) + (*i)->DrainEvents(); + } + +void Manager::DisableInterpreterPlugin(const InterpreterPlugin* plugin) + { + for ( interpreter_plugin_list::iterator i = interpreter_plugins.begin(); + i != interpreter_plugins.end(); i++ ) + { + if ( *i == plugin ) + { + interpreter_plugins.erase(i); + return; + } + } + } + + diff --git a/src/plugin/Manager.h b/src/plugin/Manager.h index 2bbcaeb0f1..85b60fb763 100644 --- a/src/plugin/Manager.h +++ b/src/plugin/Manager.h @@ -3,6 +3,8 @@ #ifndef PLUGIN_MANAGER_H #define PLUGIN_MANAGER_H +#include + #include "Plugin.h" #include "Component.h" @@ -17,6 +19,7 @@ class Manager { public: typedef std::list plugin_list; + typedef std::list interpreter_plugin_list; typedef Plugin::component_list component_list; /** @@ -30,24 +33,17 @@ public: ~Manager(); /** - * Loads a plugin dynamically from a file. This must be called only - * before InitPluginsPreScript() + * Loads all plugins dynamically from a set of directories. Multiple + * directories are split by ':'. If a directory does not contain a + * plugin itself, the method searches for plugins recursively. For + * plugins found, the method loads the plugin's shared library and + * makes its scripts available to the interpreter. * - * This is not currently implemented. - * - * @param file The path to the plugin to load. - */ - bool LoadPlugin(const std::string& file); - - /** - * Loads plugins dynamically found in a directory. This must be - * called only before InitPluginsPreScript(). - * - * This is not currently implemented. + * This must be called only before InitPluginsPreScript(). * * @param dir The directory to search for plugins. */ - bool LoadPluginsFrom(const std::string& dir); + void LoadPluginsFrom(const std::string& dir); /** * First-stage initializion of the manager. This is called early on @@ -57,7 +53,13 @@ public: void InitPreScript(); /** - * Second-stage initialization of the manager. This is called late + * Second-stage initialization of the manager. This is called in + * between pre- and post-script to make BiFs available. + */ + void InitBifs(); + + /** + * Third-stage initialization of the manager. This is called late * during Bro's initialization after any scripts are processed, and * forwards to the corresponding Plugin methods. */ @@ -69,6 +71,22 @@ public: */ void FinishPlugins(); + /** + * This tries to load the given file by searching for a plugin that + * support that extension. If a correspondign plugin is found, it's + * asked to loead the file. If that fails, the method reports an + * error message. + * + * This method must be called only between InitPreScript() and + * InitPostScript(). + * + * @return 1 if the file was sucessfully loaded by a plugin; 0 if a + * plugin was found that supports the file's extension, yet it + * encountered a problem loading the file; and -1 if we don't have a + * plugin that supports this extension. + */ + int TryLoadFile(const char* file); + /** * Returns a list of all available plugins. This includes all that * are compiled in statically, as well as those loaded dynamically so @@ -83,6 +101,52 @@ public: */ template std::list Components() const; + /** + * Filters a function/event/hook call through all interpreter plugins. + * + * @param func The function to be called. + * + * @param args The function call's arguments; they may be modified. + * + * @return If a plugin handled the call, a +1 Val with the result + * value to pass back to the interpreter (for void functions and + * events, it may be any Val and must be ignored). If no plugin + * handled the call, the method returns null. + */ + Val* CallFunction(const Func* func, val_list* args) const; + + /** + * Filter the queuing of an event through all interpreter plugins. + * + * @param event The event to be queued; it may be modified. + * + * @return Returns true if a plugin handled the queuing; in that case the + * plugin will have taken ownership. + * + */ + bool QueueEvent(Event* event) const; + + /** + * Informs all interpreter plugins about an update in network time. + * + * @param networkt_time The new network time. + */ + void UpdateNetworkTime(double network_time) const; + + /** + * Informs all interpreter plugins that the event queue has been drained. + */ + void DrainEvents() const; + + /** + * Disables an interpreter plugin's hooking of the script interpreter. + * The remaining functionality of the Plugin base class remains + * available. + * + * @param plugin The plugin to disable. + */ + void DisableInterpreterPlugin(const InterpreterPlugin* plugin); + /** * Internal method that registers a freshly instantiated plugin with * the manager. @@ -91,12 +155,38 @@ public: * ownership, yet assumes the pointer will stay valid at least until * the Manager is destroyed. */ - static bool RegisterPlugin(Plugin *plugin); + static bool RegisterPlugin(Plugin* plugin); + +protected: + /** + * Loads a plugin dynamically from a given directory. It loads the + * plugin's shared library, and makes its scripts available to the + * interpreter. Different from LoadPluginsFrom() this method does not + * further descend the directory tree recursively to search for + * plugins. + * + * This must be called only before InitPluginsPreScript() + * + * @param file The path to the plugin to load. + * + * @return 0 if there's a plugin in this directory, but there was a + * problem loading it; -1 if there's no plugin at all in this + * directory; 1 if there's a plugin in this directory and we loaded + * it successfully. + */ + int LoadPlugin(const std::string& dir); private: static plugin_list* PluginsInternal(); bool init; + typedef std::map extension_map; + extension_map extensions; + + interpreter_plugin_list interpreter_plugins; + + static string current_dir; + static string current_sopath; }; template diff --git a/src/plugin/Plugin.cc b/src/plugin/Plugin.cc index eaac8a3b25..ff3cf8f414 100644 --- a/src/plugin/Plugin.cc +++ b/src/plugin/Plugin.cc @@ -10,9 +10,9 @@ using namespace plugin; -BifItem::BifItem(const std::string& arg_id, Type arg_type) +BifItem::BifItem(const char* arg_id, Type arg_type) { - id = copy_string(arg_id.c_str()); + id = copy_string(arg_id); type = arg_type; } @@ -47,6 +47,9 @@ Plugin::Plugin() version = -9999; api_version = -9999; dynamic = false; + base_dir = 0; + sopath = 0; + extensions = 0; Manager::RegisterPlugin(this); } @@ -57,6 +60,14 @@ Plugin::~Plugin() delete [] name; delete [] description; + delete [] base_dir; + delete [] sopath; + delete [] extensions; + } + +Plugin::Type Plugin::PluginType() const + { + return STANDARD; } const char* Plugin::Name() const @@ -66,6 +77,7 @@ const char* Plugin::Name() const void Plugin::SetName(const char* arg_name) { + delete [] name; name = copy_string(arg_name); } @@ -76,6 +88,7 @@ const char* Plugin::Description() const void Plugin::SetDescription(const char* arg_description) { + delete [] description; description = copy_string(arg_description); } @@ -99,6 +112,16 @@ bool Plugin::DynamicPlugin() const return dynamic; } +const char* Plugin::PluginDirectory() const + { + return base_dir; + } + +const char* Plugin::PluginPath() const + { + return sopath; + } + void Plugin::SetAPIVersion(int arg_version) { api_version = arg_version; @@ -109,22 +132,26 @@ void Plugin::SetDynamicPlugin(bool arg_dynamic) dynamic = arg_dynamic; } +void Plugin::SetPluginLocation(const char* arg_dir, const char* arg_sopath) + { + delete [] base_dir; + delete [] sopath; + base_dir = copy_string(arg_dir); + sopath = copy_string(arg_sopath); + } + void Plugin::InitPreScript() { } void Plugin::InitPostScript() { - for ( bif_init_func_list::const_iterator f = bif_inits.begin(); f != bif_inits.end(); f++ ) - { - bif_init_func_result items = (**f)(); + } - for ( bif_init_func_result::const_iterator i = items.begin(); i != items.end(); i++ ) - { - BifItem bi((*i).first, (BifItem::Type)(*i).second); - bif_items.push_back(bi); - } - } +void Plugin::InitBifs() + { + for ( bif_init_func_list::const_iterator f = bif_inits.begin(); f != bif_inits.end(); f++ ) + (**f)(this); } Plugin::bif_item_list Plugin::BifItems() const @@ -143,6 +170,22 @@ Plugin::bif_item_list Plugin::CustomBifItems() const return bif_item_list(); } +const char* Plugin::FileExtensions() const + { + return extensions ? extensions : ""; + } + +void Plugin::SetFileExtensions(const char* ext) + { + extensions = copy_string(ext); + } + +bool Plugin::LoadFile(const char* file) + { + reporter->InternalError("Plugin::LoadFile not overriden for %s", file); + return false; + } + void Plugin::Done() { for ( component_list::const_iterator i = components.begin(); i != components.end(); i++ ) @@ -170,11 +213,23 @@ void Plugin::AddComponent(Component* c) components.sort(component_cmp); } -void Plugin::AddBifInitFunction(bif_init_func c) +bool Plugin::LoadBroFile(const char* file) + { + add_input_file(file); + return true; + } + +void Plugin::__AddBifInitFunction(bif_init_func c) { bif_inits.push_back(c); } +void Plugin::AddBifItem(const char* name, BifItem::Type type) + { + BifItem bi(name, (BifItem::Type)type); + bif_items.push_back(bi); + } + void Plugin::Describe(ODesc* d) const { d->Add("Plugin: "); @@ -190,7 +245,7 @@ void Plugin::Describe(ODesc* d) const { if ( version > 0 ) { - d->Add(" (version "); + d->Add(" (dynamic, version "); d->Add(version); d->Add(")"); } @@ -201,6 +256,18 @@ void Plugin::Describe(ODesc* d) const else d->Add(" (built-in)"); + switch ( PluginType() ) { + case STANDARD: + break; + + case INTERPRETER: + d->Add( " (interpreter plugin)"); + break; + + default: + reporter->InternalError("unknown plugin type in Plugin::Describe"); + } + d->Add("\n"); if ( d->IsShort() ) @@ -252,4 +319,46 @@ void Plugin::Describe(ODesc* d) const } } +InterpreterPlugin::InterpreterPlugin(int arg_priority) + { + priority = arg_priority; + } + +InterpreterPlugin::~InterpreterPlugin() + { + } + +int InterpreterPlugin::Priority() const + { + return priority; + } + +Plugin::Type InterpreterPlugin::PluginType() const + { + return INTERPRETER; + } + +Val* InterpreterPlugin::CallFunction(const Func* func, val_list* args) + { + return 0; + } + +bool InterpreterPlugin::QueueEvent(Event* event) + { + return false; + } + +void InterpreterPlugin::UpdateNetworkTime(double network_time) + { + } + +void InterpreterPlugin::DrainEvents() + { + } + +void InterpreterPlugin::DisableInterpreterPlugin() const + { + plugin_mgr->DisableInterpreterPlugin(this); + } + diff --git a/src/plugin/Plugin.h b/src/plugin/Plugin.h index 4abd260550..306c97e59e 100644 --- a/src/plugin/Plugin.h +++ b/src/plugin/Plugin.h @@ -9,6 +9,8 @@ #include "Macros.h" class ODesc; +class Func; +class Event; namespace plugin { @@ -22,8 +24,6 @@ class BifItem { public: /** * Type of the item. - * - * The values here must match the integers that \c bifcl generated. */ enum Type { FUNCTION = 1, EVENT = 2, CONSTANT = 3, GLOBAL = 4, TYPE = 5 }; @@ -35,7 +35,7 @@ public: * * @param type The type of the item. */ - BifItem(const std::string& id, Type type); + BifItem(const char* id, Type type); /** * Copy constructor. @@ -88,6 +88,26 @@ class Plugin { public: typedef std::list component_list; typedef std::list bif_item_list; + typedef std::list > bif_init_func_result; + typedef void (*bif_init_func)(Plugin *); + + /** + * Type of a plugin. Plugin types are set implicitly by deriving from + * the corresponding base class. */ + enum Type { + /** + * A standard plugin. This is the type for all plugins + * derived directly from \a Plugin. */ + STANDARD, + + /** + * An interpreter plugin. These plugins get hooked into the + * script interpreter and can modify, or even replace, its + * execution of Bro script code. To create an interpreter + * plugin, derive from \aInterpreterPlugin. + */ + INTERPRETER + }; /** * Constructor. @@ -99,6 +119,11 @@ public: */ virtual ~Plugin(); + /** + * Returns the type of the plugin. + */ + virtual Type PluginType() const; + /** * Returns the name of the plugin. */ @@ -121,6 +146,23 @@ public: */ bool DynamicPlugin() const; + /** + * Returns a colon-separated list of file extensions the plugin handles. + */ + const char* FileExtensions() const; + + /** + * For dynamic plugins, returns the base directory from which it was + * loaded. For static plugins, returns null. + **/ + const char* PluginDirectory() const; + + /** + * For dynamic plugins, returns the full path to the shared library + * from which it was loaded. For static plugins, returns null. + **/ + const char* PluginPath() const; + /** * Returns the internal API version that this plugin relies on. Only * plugins that match Bro's current API version may be used. For @@ -173,9 +215,38 @@ public: */ void Describe(ODesc* d) const; + /** + * Registering an individual BiF that the plugin defines. The + * information is for informational purpuses only and will show up in + * the result of BifItems() as well as in the Describe() output. + * Another way to add this information is via overriding + * CustomBifItems(). + * + * @param name The name of the BiF item. + * + * @param type The item's type. + */ + void AddBifItem(const char* name, BifItem::Type type); + + /** + * Adds a file to the list of files Bro loads at startup. This will + * normally be a Bro script, but it passes through the plugin system + * as well to load files with other extensions as supported by any of + * the current plugins. In other words, calling this method is + * similar to given a file on the command line. Note that the file + * may be only queued for now, and actually loaded later. + * + * This method must not be called after InitPostScript(). + * + * @param file The file to load. It will be searched along the standard paths. + * + * @return True if successful (which however may only mean + * "successfully queued"). + */ + bool LoadBroFile(const char* file); + protected: - typedef std::list > bif_init_func_result; - typedef bif_init_func_result (*bif_init_func)(); + friend class Manager; /** * Sets the plugins name. @@ -212,6 +283,28 @@ protected: */ void SetDynamicPlugin(bool dynamic); + /** + * Reports the extensions of input files the plugin handles. If Bro + * wants to load a file with one of these extensions it will pass + * them to LoadFile() and then then ignore otherwise. + * + * ext: A list of colon-separated file extensions the plugin handles. + */ + void SetFileExtensions(const char* ext); + + /** + * Sets the base directory and shared library path from which the + * plugin was loaded. This should be called only from the manager for + * dynamic plugins. + * + * @param dir The plugin directory. The functions makes an internal + * copy of string. + * + * @param sopath The full path the shared library loaded. The + * functions makes an internal copy of string. + */ + void SetPluginLocation(const char* dir, const char* sopath); + /** * Takes ownership. */ @@ -227,17 +320,41 @@ protected: */ virtual bif_item_list CustomBifItems() const; + /** + * Virtual method that can be overriden by derived class to load + * files with extensions reported via SetFileExtension(). + * + * This method will be called between InitPreScript() and + * InitPostScript(), but with no further order or timing guaranteed. + * It will be called once for each file encountered with of the + * specificed extensions (i.e., duplicates are filtered out + * automatically). + * + * @return True if the file was loaded successfuly, false if not. Bro + * will abort in the latter case. + */ + virtual bool LoadFile(const char* file); + + /** + * Initializes the BiF items added with AddBifItem(). Internal method + * that will be called by the manager at the right time. + */ + void InitBifs(); + /** * Internal function adding an entry point for registering * auto-generated BiFs. */ - void AddBifInitFunction(bif_init_func c); + void __AddBifInitFunction(bif_init_func c); private: typedef std::list bif_init_func_list; const char* name; const char* description; + const char* base_dir; + const char* sopath; + const char* extensions; int version; int api_version; bool dynamic; @@ -247,6 +364,105 @@ private: bif_init_func_list bif_inits; }; +/** + * Class for hooking into script execution. An interpreter plugin can do + * everything a normal plugin can, yet will also be interfaced to the script + * interpreter for learning about, modidying, or potentially replacing + * standard functionality. + */ +class InterpreterPlugin : public Plugin { +public: + /** + * Constructor. + * + * @param priority Imposes an order on InterpreterPlugins in which + * they'll be chained. The higher the prioritu, the earlier a plugin + * is called when the interpreter goes through the chain. */ + InterpreterPlugin(int priority); + + /** + * Destructor. + */ + virtual ~InterpreterPlugin(); + + /** + * Returns the plugins priority. + */ + int Priority() const; + + /** + * Callback for executing a function/event/hook. Whenever the script + * interpreter is about to execution a function, it first gives all + * InterpreterPlugins a chance to handle the call (in the order of their + * priorities). A plugin can either just inspect the call, or replace it + * (i.e., prevent the interpreter from executing it). In the latter case + * it must provide a matching return value. + * + * The default implementation does never handle the call in any way. + * + * @param func The function being called. + * + * @param args The function arguments. The method can modify the list + * in place long as it ensures matching types and correct reference + * counting. + * + * @return If the plugin handled the call, a +1 Val with the result + * value to pass back to the interpreter (for void functions and + * events any \a Val is fine; it will be ignored; best to use a \c + * TYPE_ANY). If the plugin did not handle the call, it must return + * null. + */ + virtual Val* CallFunction(const Func* func, val_list* args); + + /** + * Callback for raising an event. Whenever the script interpreter is + * about to queue an event for later execution, it first gives all + * InterpreterPlugins a chance to handle the queuing otherwise (in the + * order of their priorities). A plugin can either just inspect the + * event, or take it over (i.e., prevent the interpreter from queuing it + * it). + * + * The default implementation does never handle the queuing in any way. + * + * @param event The even to be queued. The method can modify it in in + * place long as it ensures matching types and correct reference + * counting. + * + * @return True if the plugin took charge of the event; in that case it + * must have assumed ownership of the event and the intpreter will not do + * anything further with it. False otherwise. + * + */ + virtual bool QueueEvent(Event* event); + + /** + * Callback for updates in network time. This method will be called + * whenever network time is advanced. + * + * @param networkt_time The new network time. + */ + virtual void UpdateNetworkTime(double network_time); + + /** + * Callback for event queue draining. This method will be called + * whenever the event manager has drained it queue. + */ + virtual void DrainEvents(); + + /** + * Disables interpreter hooking. The functionality of the Plugin base + * class remains available. + */ + void DisableInterpreterPlugin() const; + + // Overridden from base class. + virtual Type PluginType() const; + +private: + int priority; + +}; + } #endif diff --git a/src/scan.l b/src/scan.l index 636ec5b251..4c67a2f75e 100644 --- a/src/scan.l +++ b/src/scan.l @@ -30,6 +30,8 @@ #include "analyzer/Analyzer.h" +#include "plugin/Manager.h" + extern YYLTYPE yylloc; // holds start line and column of token extern int print_loaded_scripts; extern int generate_documentation; @@ -579,6 +581,24 @@ YYLTYPE GetCurrentLocation() static int load_files(const char* orig_file) { + int rc = plugin_mgr->TryLoadFile(orig_file); + + if ( rc == 1 ) + return 0; // A plugin took care of it, just skip. + + if ( rc == 0 ) + { + if ( ! reporter->Errors() ) + // This is just in case the plugin failed to report + // the error itself, in which case we want to at + // least tell the user that something went wrong. + reporter->Error("Plugin reported error loading %s", orig_file); + + exit(1); + } + + assert(rc == -1); // No plugin in charge of this file. + // Whether we pushed on a FileInfo that will restore the // current module after the final file has been scanned. bool did_module_restore = false; diff --git a/src/util-config.h.in b/src/util-config.h.in index ff5e28537a..90919a63f4 100644 --- a/src/util-config.h.in +++ b/src/util-config.h.in @@ -3,3 +3,4 @@ #define BRO_BUILD_SOURCE_PATH "@CMAKE_BINARY_DIR@/src" #define BRO_BUILD_SCRIPTS_PATH "@CMAKE_BINARY_DIR@/scripts" #define BRO_MAGIC_INSTALL_PATH "@BRO_MAGIC_INSTALL_PATH@" +#define BRO_PLUGIN_INSTALL_PATH "@BRO_PLUGIN_INSTALL_PATH@" diff --git a/src/util.cc b/src/util.cc index dd232a83fa..3b917495bc 100644 --- a/src/util.cc +++ b/src/util.cc @@ -619,13 +619,13 @@ bool ensure_dir(const char *dirname) return true; } -bool is_dir(const char* path) +bool is_dir(const std::string& path) { struct stat st; - if ( stat(path, &st) < 0 ) + if ( stat(path.c_str(), &st) < 0 ) { if ( errno != ENOENT ) - reporter->Warning("can't stat %s: %s", path, strerror(errno)); + reporter->Warning("can't stat %s: %s", path.c_str(), strerror(errno)); return false; } @@ -633,6 +633,37 @@ bool is_dir(const char* path) return S_ISDIR(st.st_mode); } +bool is_file(const std::string& path) + { + struct stat st; + if ( stat(path.c_str(), &st) < 0 ) + { + if ( errno != ENOENT ) + reporter->Warning("can't stat %s: %s", path.c_str(), strerror(errno)); + + return false; + } + + return S_ISREG(st.st_mode); + } + +string strreplace(const string& s, const string& o, const string& n) + { + string r = s; + + while ( true ) + { + size_t i = r.find(o); + + if ( i == std::string::npos ) + break; + + r.replace(i, o.size(), n); + } + + return r; +} + int hmac_key_set = 0; uint8 shared_hmac_md5_key[16]; @@ -872,16 +903,31 @@ int int_list_cmp(const void* v1, const void* v2) return 1; } -const char* bro_path() +static string bro_path_value; + +const std::string& bro_path() { - const char* path = getenv("BROPATH"); - if ( ! path ) - path = ".:" + if ( bro_path_value.empty() ) + { + const char* path = getenv("BROPATH"); + if ( ! path ) + path = ".:" BRO_SCRIPT_INSTALL_PATH ":" BRO_SCRIPT_INSTALL_PATH "/policy" ":" BRO_SCRIPT_INSTALL_PATH "/site"; - return path; + bro_path_value = path; + } + + return bro_path_value; + } + +extern void add_to_bro_path(const string& dir) + { + // Make sure path is initialized. + bro_path(); + + bro_path_value += string(":") + dir; } const char* bro_magic_path() @@ -894,6 +940,16 @@ const char* bro_magic_path() return path; } +const char* bro_plugin_path() + { + const char* path = getenv("BRO_PLUGINS"); + + if ( ! path ) + path = BRO_PLUGIN_INSTALL_PATH; + + return path; + } + string bro_prefixes() { string rval; @@ -1089,9 +1145,9 @@ FILE* search_for_file(const char* filename, const char* ext, // @loads can be referenced relatively. if ( current_scanned_file_path != "" && filename[0] == '.' ) safe_snprintf(path, sizeof(path), "%s:%s", - current_scanned_file_path.c_str(), bro_path()); + current_scanned_file_path.c_str(), bro_path().c_str()); else - safe_strncpy(path, bro_path(), sizeof(path)); + safe_strncpy(path, bro_path().c_str(), sizeof(path)); char* dir_beginning = path; char* dir_ending = path; @@ -1505,25 +1561,16 @@ void get_memory_usage(unsigned int* total, unsigned int* malloced) if ( malloced ) *malloced = mi.uordblks; - ret_total = mi.arena; +#endif - if ( total ) - *total = ret_total; -#else struct rusage r; getrusage(RUSAGE_SELF, &r); - if ( malloced ) - *malloced = 0; - // At least on FreeBSD it's in KB. ret_total = r.ru_maxrss * 1024; if ( total ) *total = ret_total; -#endif - - // return ret_total; } #ifdef malloc diff --git a/src/util.h b/src/util.h index fcdfd6d499..a7d493485d 100644 --- a/src/util.h +++ b/src/util.h @@ -147,7 +147,13 @@ extern const char* fmt_access_time(double time); extern bool ensure_dir(const char *dirname); // Returns true if path exists and is a directory. -bool is_dir(const char* path); +bool is_dir(const std::string& path); + +// Returns true if path exists and is a file. +bool is_file(const std::string& path); + +// Replaces all occurences of *o* in *s* with *n*. +extern std::string strreplace(const std::string& s, const std::string& o, const std::string& n); extern uint8 shared_hmac_md5_key[16]; @@ -202,9 +208,12 @@ static const SourceID SOURCE_LOCAL = 0; extern void pinpoint(); extern int int_list_cmp(const void* v1, const void* v2); -extern const char* bro_path(); +extern const std::string& bro_path(); +extern void add_to_bro_path(const std::string& dir); + extern const char* bro_magic_path(); -extern std::string bro_prefixes(); +extern const char* bro_plugin_path(); +extern const char* bro_prefixes(); std::string dot_canon(std::string path, std::string file, std::string prefix = ""); const char* normalize_path(const char* path); void get_script_subpath(const std::string& full_filename, const char** subpath); diff --git a/testing/btest/Baseline/core.plugins.analyzer/output b/testing/btest/Baseline/core.plugins.analyzer/output new file mode 100644 index 0000000000..deb8a7655d --- /dev/null +++ b/testing/btest/Baseline/core.plugins.analyzer/output @@ -0,0 +1,6 @@ +Plugin: Demo::Foo - A Foo test analyzer (dynamic, version 1) + [Analyzer] Foo (ANALYZER_FOO, enabled) + [Event] foo_message + +=== +foo_message, [orig_h=::1, orig_p=37927/tcp, resp_h=::1, resp_p=4242/tcp], Hello, Foo!^J diff --git a/testing/btest/Baseline/core.plugins.test-plugin/output b/testing/btest/Baseline/core.plugins.test-plugin/output new file mode 100644 index 0000000000..e0663ab42e --- /dev/null +++ b/testing/btest/Baseline/core.plugins.test-plugin/output @@ -0,0 +1,11 @@ +Plugin: Demo::Foo - (dynamic, version 1) + [Event] plugin_event + [Function] hello_plugin_world + +=== +plugin: automatically loaded at startup +calling bif, Hello from the plugin! +=== +plugin: automatically loaded at startup +calling bif, Hello from the plugin! +plugin: manually loaded diff --git a/testing/btest/Traces/port4242.trace b/testing/btest/Traces/port4242.trace new file mode 100644 index 0000000000..e999b43b92 Binary files /dev/null and b/testing/btest/Traces/port4242.trace differ diff --git a/testing/btest/core/plugins/analyzer-plugin/.btest-ignore b/testing/btest/core/plugins/analyzer-plugin/.btest-ignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/btest/core/plugins/analyzer-plugin/CMakeLists.txt b/testing/btest/core/plugins/analyzer-plugin/CMakeLists.txt new file mode 100644 index 0000000000..a032edbd89 --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/CMakeLists.txt @@ -0,0 +1,20 @@ + +project(Bro-Plugin-Demo-Foo) + +cmake_minimum_required(VERSION 2.6.3) + +if ( NOT BRO_DIST ) + message(FATAL_ERROR "BRO_DIST not set") +endif () + +set(CMAKE_MODULE_PATH ${BRO_DIST}/cmake) + +include(BroPlugin) + +bro_plugin_begin(Demo Foo) +bro_plugin_cc(src/Plugin.cc) +bro_plugin_cc(src/Foo.cc) +bro_plugin_bif(src/events.bif) +bro_plugin_bif(src/functions.bif) +bro_plugin_pac(src/foo.pac src/foo-protocol.pac src/foo-analyzer.pac) +bro_plugin_end() diff --git a/testing/btest/core/plugins/analyzer-plugin/__bro_plugin__ b/testing/btest/core/plugins/analyzer-plugin/__bro_plugin__ new file mode 100644 index 0000000000..c10d6ee17c --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/__bro_plugin__ @@ -0,0 +1,2 @@ +A place-holder file that marks a directory as holding a Bro plugin. +Content is ignored. diff --git a/testing/btest/core/plugins/analyzer-plugin/scripts/Demo/Foo/base/main.bro b/testing/btest/core/plugins/analyzer-plugin/scripts/Demo/Foo/base/main.bro new file mode 100644 index 0000000000..2e2d174b47 --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/scripts/Demo/Foo/base/main.bro @@ -0,0 +1,7 @@ + +const ports = { 4242/tcp }; + +event bro_init() &priority=5 + { + Analyzer::register_for_ports(Analyzer::ANALYZER_FOO, ports); + } diff --git a/testing/btest/core/plugins/analyzer-plugin/scripts/__load__.bro b/testing/btest/core/plugins/analyzer-plugin/scripts/__load__.bro new file mode 100644 index 0000000000..330718c604 --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/scripts/__load__.bro @@ -0,0 +1 @@ +@load Demo/Foo/base/main diff --git a/testing/btest/core/plugins/analyzer-plugin/src/Foo.cc b/testing/btest/core/plugins/analyzer-plugin/src/Foo.cc new file mode 100644 index 0000000000..b23c4b7817 --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/src/Foo.cc @@ -0,0 +1,59 @@ + +#include "Foo.h" +#include "foo_pac.h" +#include "events.bif.h" + +#include + +using namespace analyzer::Foo; + +Foo_Analyzer::Foo_Analyzer(Connection* conn) +: tcp::TCP_ApplicationAnalyzer("Foo", conn) + { + interp = new binpac::Foo::Foo_Conn(this); + } + +Foo_Analyzer::~Foo_Analyzer() + { + delete interp; + } + +void Foo_Analyzer::Done() + { + tcp::TCP_ApplicationAnalyzer::Done(); + + interp->FlowEOF(true); + interp->FlowEOF(false); + } + +void Foo_Analyzer::EndpointEOF(bool is_orig) + { + tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig); + interp->FlowEOF(is_orig); + } + +void Foo_Analyzer::DeliverStream(int len, const u_char* data, bool orig) + { + tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); + + assert(TCP()); + + if ( TCP()->IsPartial() ) + // punt on partial. + return; + + try + { + interp->NewData(orig, data, data + len); + } + catch ( const binpac::Exception& e ) + { + ProtocolViolation(fmt("Binpac exception: %s", e.c_msg())); + } + } + +void Foo_Analyzer::Undelivered(int seq, int len, bool orig) + { + tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); + interp->NewGap(orig, len); + } diff --git a/testing/btest/core/plugins/analyzer-plugin/src/Foo.h b/testing/btest/core/plugins/analyzer-plugin/src/Foo.h new file mode 100644 index 0000000000..dfc50a9d76 --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/src/Foo.h @@ -0,0 +1,31 @@ + +#ifndef BRO_PLUGIN_DEMO_FOO_H +#define BRO_PLUGIN_DEMO_FOO_H + +#include "analyzer/protocol/tcp/TCP.h" +#include "analyzer/protocol/pia/PIA.h" + +namespace binpac { namespace Foo { class Foo_Conn; } } + +namespace analyzer { namespace Foo { + +class Foo_Analyzer : public tcp::TCP_ApplicationAnalyzer { +public: + Foo_Analyzer(Connection* conn); + ~Foo_Analyzer(); + + virtual void Done(); + virtual void DeliverStream(int len, const u_char* data, bool orig); + virtual void Undelivered(int seq, int len, bool orig); + virtual void EndpointEOF(bool is_orig); + + static analyzer::Analyzer* InstantiateAnalyzer(Connection* conn) + { return new Foo_Analyzer(conn); } + +protected: + binpac::Foo::Foo_Conn* interp; +}; + +} } // namespace analyzer::* + +#endif diff --git a/testing/btest/core/plugins/analyzer-plugin/src/Plugin.cc b/testing/btest/core/plugins/analyzer-plugin/src/Plugin.cc new file mode 100644 index 0000000000..b37a60d148 --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/src/Plugin.cc @@ -0,0 +1,12 @@ + +#include + +#include "Foo.h" + +BRO_PLUGIN_BEGIN(Demo, Foo) + BRO_PLUGIN_VERSION(1); + BRO_PLUGIN_DESCRIPTION("A Foo test analyzer"); + BRO_PLUGIN_ANALYZER("Foo", Foo::Foo_Analyzer); + BRO_PLUGIN_BIF_FILE(events); + BRO_PLUGIN_BIF_FILE(functions); +BRO_PLUGIN_END diff --git a/testing/btest/core/plugins/analyzer-plugin/src/events.bif b/testing/btest/core/plugins/analyzer-plugin/src/events.bif new file mode 100644 index 0000000000..4603fe4cf6 --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/src/events.bif @@ -0,0 +1,2 @@ + +event foo_message%(c: connection, data: string%); diff --git a/testing/btest/core/plugins/analyzer-plugin/src/foo-analyzer.pac b/testing/btest/core/plugins/analyzer-plugin/src/foo-analyzer.pac new file mode 100644 index 0000000000..a210a8430c --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/src/foo-analyzer.pac @@ -0,0 +1,15 @@ + +refine connection Foo_Conn += { + + function Foo_data(msg: Foo_Message): bool + %{ + StringVal* data = new StringVal(${msg.data}.length(), (const char*) ${msg.data}.data()); + BifEvent::generate_foo_message(bro_analyzer(), bro_analyzer()->Conn(), data); + return true; + %} + +}; + +refine typeattr Foo_Message += &let { + proc: bool = $context.connection.Foo_data(this); +}; diff --git a/testing/btest/core/plugins/analyzer-plugin/src/foo-protocol.pac b/testing/btest/core/plugins/analyzer-plugin/src/foo-protocol.pac new file mode 100644 index 0000000000..892513c4f0 --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/src/foo-protocol.pac @@ -0,0 +1,4 @@ + +type Foo_Message(is_orig: bool) = record { + data: bytestring &restofdata; +}; diff --git a/testing/btest/core/plugins/analyzer-plugin/src/foo.pac b/testing/btest/core/plugins/analyzer-plugin/src/foo.pac new file mode 100644 index 0000000000..826bcc624e --- /dev/null +++ b/testing/btest/core/plugins/analyzer-plugin/src/foo.pac @@ -0,0 +1,26 @@ +%include binpac.pac +%include bro.pac + +%extern{ +#include "Foo.h" + +#include "events.bif.h" +%} + +analyzer Foo withcontext { + connection: Foo_Conn; + flow: Foo_Flow; +}; + +connection Foo_Conn(bro_analyzer: BroAnalyzer) { + upflow = Foo_Flow(true); + downflow = Foo_Flow(false); +}; + +%include foo-protocol.pac + +flow Foo_Flow(is_orig: bool) { + datagram = Foo_Message(is_orig) withcontext(connection, this); +}; + +%include foo-analyzer.pac diff --git a/testing/btest/core/plugins/analyzer-plugin/src/functions.bif b/testing/btest/core/plugins/analyzer-plugin/src/functions.bif new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/btest/core/plugins/analyzer.bro b/testing/btest/core/plugins/analyzer.bro new file mode 100644 index 0000000000..2a72797564 --- /dev/null +++ b/testing/btest/core/plugins/analyzer.bro @@ -0,0 +1,13 @@ +# @TEST-EXEC: ${DIST}/aux/bro-aux/plugin-support/init-plugin Demo Foo +# @TEST-EXEC: cp -r %DIR/analyzer-plugin/* . +# @TEST-EXEC: make BRO=${DIST} +# @TEST-EXEC: BROPLUGINS=`pwd` bro -NN | awk '/^Plugin:.*Demo/ {p=1; print; next} /^Plugin:/{p=0} p==1{print}' >>output +# @TEST-EXEC: echo === >>output +# @TEST-EXEC: BROPLUGINS=`pwd` bro -r $TRACES/port4242.trace %INPUT >>output +# @TEST-EXEC: btest-diff output + +event foo_message(c: connection, data: string) + { + print "foo_message", c$id, data; + } + diff --git a/testing/btest/core/plugins/test-plugin.sh b/testing/btest/core/plugins/test-plugin.sh new file mode 100644 index 0000000000..87b09b2e4d --- /dev/null +++ b/testing/btest/core/plugins/test-plugin.sh @@ -0,0 +1,45 @@ +# @TEST-EXEC: ${DIST}/aux/bro-aux/plugin-support/init-plugin Demo Foo +# @TEST-EXEC: bash %INPUT +# @TEST-EXEC: make BRO=${DIST} +# @TEST-EXEC: BROPLUGINS=`pwd` bro -NN | awk '/^Plugin:.*Demo/ {p=1; print; next} /^Plugin:/{p=0} p==1{print}' >>output +# @TEST-EXEC: echo === >>output +# @TEST-EXEC: BROPLUGINS=`pwd` bro -r $TRACES/empty.trace >>output +# @TEST-EXEC: echo === >>output +# @TEST-EXEC: BROPLUGINS=`pwd` bro demo/foo -r $TRACES/empty.trace >>output +# @TEST-EXEC: btest-diff output + +cat >scripts/__load__.bro <scripts/demo/foo/__load__.bro <scripts/demo/foo/manually.bro <scripts/demo/foo/base/at-startup.bro <src/functions.bif <src/events.bif <