diff --git a/src/analyzer/protocol/imap/CMakeLists.txt b/src/analyzer/protocol/imap/CMakeLists.txt index 755221b25a..921dde2444 100644 --- a/src/analyzer/protocol/imap/CMakeLists.txt +++ b/src/analyzer/protocol/imap/CMakeLists.txt @@ -6,6 +6,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DI bro_plugin_begin(Bro IMAP) bro_plugin_cc(Plugin.cc) bro_plugin_cc(IMAP.cc) +bro_plugin_bif(events.bif) bro_plugin_pac(imap.pac imap-analyzer.pac imap-protocol.pac) bro_plugin_end() diff --git a/src/analyzer/protocol/imap/events.bif b/src/analyzer/protocol/imap/events.bif new file mode 100644 index 0000000000..903d1f0dff --- /dev/null +++ b/src/analyzer/protocol/imap/events.bif @@ -0,0 +1,10 @@ +## Generated for an SSL/TLS client's initial *hello* message. SSL/TLS sessions +## start with an unencrypted handshake, and Bro extracts as much information out +## of that as it can. This event provides access to the initial information +## sent by the client. +## +## c: The connection. +## +## capabilities: The list of IMAP capabilities as sent by the server. +event imap_capabilities%(c: connection, capabilities: string_vec%); + diff --git a/src/analyzer/protocol/imap/imap-analyzer.pac b/src/analyzer/protocol/imap/imap-analyzer.pac index 918d339cfe..67da283609 100644 --- a/src/analyzer/protocol/imap/imap-analyzer.pac +++ b/src/analyzer/protocol/imap/imap-analyzer.pac @@ -49,9 +49,25 @@ refine connection IMAP_Conn += { return true; %} + function proc_server_capability(capabilities: Capability[]): bool + %{ + VectorVal* capv = new VectorVal(internal_type("string_vec")->AsVectorType()); + for ( unsigned int i = 0; i< capabilities->size(); i++ ) + { + const bytestring& capability = (*capabilities)[i]->cap(); + capv ->Assign(i, new StringVal(capability.length(), (const char*)capability.data())); + } + + BifEvent::generate_imap_capabilities(bro_analyzer(), bro_analyzer()->Conn(), capv); + return true; + %} + }; -refine typeattr IMAP_TOKEN += &let { - proc: bool = $context.connection.proc_imap_token(is_orig, tag, command); +refine typeattr ImapToken += &let { + proc: bool = $context.connection.proc_imap_token(is_orig, tag, command); }; +refine typeattr ServerCapability += &let { + proc: bool = $context.connection.proc_server_capability(capabilities); +}; diff --git a/src/analyzer/protocol/imap/imap-protocol.pac b/src/analyzer/protocol/imap/imap-protocol.pac index 15bb753475..5fa1e34555 100644 --- a/src/analyzer/protocol/imap/imap-protocol.pac +++ b/src/analyzer/protocol/imap/imap-protocol.pac @@ -1,17 +1,70 @@ +# commands that we support parsing. The numbers do not really mean anything +# in this case +enum ImapCommand { + CMD_CAPABILITY, + CMD_UNKNOWN +} + type TAG = RE/[[:alnum:][:punct:]]+/; type CONTENT = RE/[^\r\n]*/; type SPACING = RE/[ ]+/; type OPTIONALSPACING = RE/[ ]*/; type NEWLINE = RE/[\r\n]+/; +type OPTIONALNEWLINE = RE/[\r\n]*/; -type IMAP_PDU(is_orig: bool) = IMAP_TOKEN(is_orig)[] &until($input.length() == 0); +type IMAP_PDU(is_orig: bool) = ImapToken(is_orig)[] &until($input.length() == 0); -type IMAP_TOKEN(is_orig: bool) = record { +type ImapToken(is_orig: bool) = record { tag : TAG; : SPACING; command: TAG; : OPTIONALSPACING; + client_or_server: case is_orig of { + true -> client: UnknownCommand(this) ; + false -> server: ServerContentText(this); + } &requires(pcommand) ; +} &let { + pcommand: int = $context.connection.determine_command(is_orig, tag, command); +}; + +type ServerContentText(rec: ImapToken) = case rec.pcommand of { + CMD_CAPABILITY -> capability: ServerCapability(rec); + default -> unknown: UnknownCommand(rec); +}; + +type Capability = record { + cap: TAG; + : OPTIONALSPACING; + nl: OPTIONALNEWLINE; +}; + +type ServerCapability(rec: ImapToken) = record { + capabilities: Capability[] &until($context.connection.strlen($element.nl) > 0); +}; + +type UnknownCommand(rec: ImapToken) = record { tagcontent: CONTENT; : NEWLINE; }; +refine connection IMAP_Conn += { + + function determine_command(is_orig: bool, tag: bytestring, command: bytestring): int + %{ + string cmdstr = std_str(command); + std::transform(cmdstr.begin(), cmdstr.end(), cmdstr.begin(), ::tolower); + string tagstr = std_str(tag); + + if ( !is_orig && cmdstr == "capability" && tag == "*" ) { + return CMD_CAPABILITY; + } + + return CMD_UNKNOWN; + %} + + function strlen(str: bytestring): int + %{ + return str.length(); + %} + +}; diff --git a/src/analyzer/protocol/imap/imap.pac b/src/analyzer/protocol/imap/imap.pac index 33382bc26d..f5c7559294 100644 --- a/src/analyzer/protocol/imap/imap.pac +++ b/src/analyzer/protocol/imap/imap.pac @@ -7,6 +7,8 @@ %include bro.pac %extern{ +#include "events.bif.h" + namespace analyzer { namespace imap { class IMAP_Analyzer; } } namespace binpac { namespace IMAP { class IMAP_Conn; } } typedef analyzer::imap::IMAP_Analyzer* IMAPAnalyzer; diff --git a/testing/btest/Baseline/scripts.base.protocols.imap.capabilities/.stdout b/testing/btest/Baseline/scripts.base.protocols.imap.capabilities/.stdout new file mode 100644 index 0000000000..bf69e13682 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.imap.capabilities/.stdout @@ -0,0 +1 @@ +[IMAP4rev1, CHILDREN, ENABLE, ID, IDLE, LIST-EXTENDED, LIST-STATUS, LITERAL+, MOVE, NAMESPACE, SASL-IR, SORT, SPECIAL-USE, THREAD=ORDEREDSUBJECT, UIDPLUS, UNSELECT, WITHIN, STARTTLS, AUTH=LOGIN, AUTH=PLAIN] diff --git a/testing/btest/scripts/base/protocols/imap/capabilities.test b/testing/btest/scripts/base/protocols/imap/capabilities.test new file mode 100644 index 0000000000..06bdb56b7d --- /dev/null +++ b/testing/btest/scripts/base/protocols/imap/capabilities.test @@ -0,0 +1,12 @@ +# @TEST-EXEC: bro -b -C -r $TRACES/tls/imap-starttls.pcap %INPUT +# @TEST-EXEC: btest-diff .stdout + +@load base/protocols/ssl +@load base/protocols/conn +@load base/frameworks/dpd +@load base/protocols/imap + +event imap_capabilities(c: connection, capabilities: string_vec) + { + print capabilities; + }