From 35da82be59ba1a4a8d2fa8dbddc9d49294fd2377 Mon Sep 17 00:00:00 2001 From: "Russ Combs (rucombs)" Date: Tue, 14 Jun 2016 16:24:28 -0400 Subject: [PATCH] Merge pull request #517 in SNORT/snort3 from appid_port_ws1_merge_this to master Squashed commit of the following: commit b53b7f1d5e5c5249515b25b7db35f65cdbeebe71 Author: davis mcpherson Date: Thu Jun 9 11:50:39 2016 -0400 initial prep work to port appid to snort++ initial work to get dns/smtp detectors working and cleanup of many issues left from initial prep of appid search tool unit tests Moved AM_CPPFLAGS change into appid specific Makefile.am files. fixed compiler warnings fixed formating issues, misc minor code review nits Fix cmake problems and warnings. use REMOVED_WHILE_NOT_IN_USE for all code #ifdef'ed out for porting issues change patterns defined by const char* to const char array init net_list_by_zone array to 0 initialize all array member variables to nullptr initial net list by zone array to nullptr free memory allocated by snort_strdup for module config strings AppIdModuleConfig is now a class not a struct for now use snort_free instead of delete no defaults for appid detector dir and thirdparty dir init member variables for AppIdData free memory allocated for AppIdData member variables initialize ALL member variables of AppIdData class move functions called to free memory in AppIdData to the AppIdData class source formating issue, fix type mismatch on debug print specifier for IpProtocol --- configure.ac | 6 + src/Makefile.am | 5 + src/flow/flow.cc | 2 +- src/main/snort_debug.h | 1 + src/network_inspectors/CMakeLists.txt | 8 +- src/network_inspectors/Makefile.am | 1 + src/network_inspectors/appid/CHEAT_SHEET.txt | 59 + src/network_inspectors/appid/CMakeLists.txt | 69 + src/network_inspectors/appid/MANIFEST.txt | 188 + src/network_inspectors/appid/Makefile.am | 60 + src/network_inspectors/appid/app_forecast.cc | 88 + src/network_inspectors/appid/app_forecast.h | 68 + .../appid/app_info_table.cc | 785 ++++ src/network_inspectors/appid/app_info_table.h | 116 + src/network_inspectors/appid/appid.h | 60 + src/network_inspectors/appid/appid_api.cc | 695 +++ src/network_inspectors/appid/appid_api.h | 274 ++ src/network_inspectors/appid/appid_config.cc | 1098 +++++ src/network_inspectors/appid/appid_config.h | 193 + .../appid/appid_flow_data.cc | 403 ++ .../appid/appid_flow_data.h | 355 ++ .../appid/appid_inspector.cc | 149 + .../appid/appid_inspector.h | 52 + src/network_inspectors/appid/appid_module.cc | 160 + src/network_inspectors/appid/appid_module.h | 69 + src/network_inspectors/appid/appid_stats.cc | 528 +++ src/network_inspectors/appid/appid_stats.h | 34 + .../appid/application_ids.h | 1013 ++++ .../appid/client_plugins/CMakeLists.txt | 34 + .../appid/client_plugins/Makefile.am | 33 + .../appid/client_plugins/client_app_aim.cc | 318 ++ .../appid/client_plugins/client_app_aim.h | 30 + .../appid/client_plugins/client_app_api.h | 152 + .../appid/client_plugins/client_app_base.cc | 997 ++++ .../appid/client_plugins/client_app_base.h | 65 + .../appid/client_plugins/client_app_bit.cc | 229 + .../client_plugins/client_app_bit_tracker.cc | 274 ++ .../appid/client_plugins/client_app_config.h | 51 + .../appid/client_plugins/client_app_msn.cc | 216 + .../appid/client_plugins/client_app_msn.h | 30 + .../appid/client_plugins/client_app_rtp.cc | 358 ++ .../appid/client_plugins/client_app_smtp.cc | 751 +++ .../appid/client_plugins/client_app_smtp.h | 30 + .../appid/client_plugins/client_app_ssh.cc | 638 +++ .../client_plugins/client_app_timbuktu.cc | 243 + .../appid/client_plugins/client_app_tns.cc | 436 ++ .../appid/client_plugins/client_app_vnc.cc | 204 + .../appid/client_plugins/client_app_ym.cc | 227 + .../appid/client_plugins/client_app_ym.h | 30 + .../appid/detector_plugins/CMakeLists.txt | 30 + .../appid/detector_plugins/Makefile.am | 30 + .../appid/detector_plugins/detector_api.h | 54 + .../appid/detector_plugins/detector_base.cc | 129 + .../appid/detector_plugins/detector_base.h | 28 + .../appid/detector_plugins/detector_dns.cc | 835 ++++ .../appid/detector_plugins/detector_dns.h | 43 + .../appid/detector_plugins/detector_http.cc | 2496 ++++++++++ .../appid/detector_plugins/detector_http.h | 195 + .../appid/detector_plugins/detector_imap.cc | 1054 +++++ .../detector_plugins/detector_kerberos.cc | 1134 +++++ .../detector_plugins/detector_pattern.cc | 760 +++ .../appid/detector_plugins/detector_pattern.h | 90 + .../appid/detector_plugins/detector_pop3.cc | 959 ++++ .../appid/detector_plugins/detector_sip.cc | 799 ++++ .../appid/detector_plugins/detector_sip.h | 58 + .../detector_plugins/http_url_patterns.cc | 249 + .../detector_plugins/http_url_patterns.h | 47 + src/network_inspectors/appid/dns_defs.h | 122 + src/network_inspectors/appid/errors.sh | 29 + src/network_inspectors/appid/flow_error.h | 36 + src/network_inspectors/appid/fw_appid.cc | 4108 +++++++++++++++++ src/network_inspectors/appid/fw_appid.h | 374 ++ .../appid/host_port_app_cache.cc | 93 + .../appid/host_port_app_cache.h | 55 + src/network_inspectors/appid/http_common.h | 260 ++ .../appid/length_app_cache.cc | 79 + .../appid/length_app_cache.h | 55 + .../appid/lua_detector_api.cc | 3392 ++++++++++++++ .../appid/lua_detector_api.h | 164 + .../appid/lua_detector_flow_api.cc | 481 ++ .../appid/lua_detector_flow_api.h | 42 + .../appid/lua_detector_module.cc | 961 ++++ .../appid/lua_detector_module.h | 70 + .../appid/lua_detector_util.h | 72 + .../appid/service_plugins/CMakeLists.txt | 81 + .../appid/service_plugins/Makefile.am | 78 + .../appid/service_plugins/dcerpc.cc | 74 + .../appid/service_plugins/dcerpc.h | 30 + .../appid/service_plugins/service_api.h | 213 + .../appid/service_plugins/service_base.cc | 2456 ++++++++++ .../appid/service_plugins/service_base.h | 138 + .../service_plugins/service_battle_field.cc | 233 + .../service_plugins/service_battle_field.h | 30 + .../appid/service_plugins/service_bgp.cc | 256 + .../appid/service_plugins/service_bgp.h | 30 + .../appid/service_plugins/service_bit.cc | 212 + .../appid/service_plugins/service_bootp.cc | 340 ++ .../appid/service_plugins/service_bootp.h | 32 + .../appid/service_plugins/service_config.h | 109 + .../appid/service_plugins/service_dcerpc.cc | 203 + .../appid/service_plugins/service_dcerpc.h | 32 + .../service_plugins/service_direct_connect.cc | 324 ++ .../service_plugins/service_direct_connect.h | 32 + .../appid/service_plugins/service_flap.cc | 242 + .../appid/service_plugins/service_flap.h | 32 + .../appid/service_plugins/service_ftp.cc | 1322 ++++++ .../appid/service_plugins/service_ftp.h | 32 + .../appid/service_plugins/service_irc.cc | 338 ++ .../appid/service_plugins/service_irc.h | 32 + .../appid/service_plugins/service_lpr.cc | 261 ++ .../appid/service_plugins/service_lpr.h | 32 + .../appid/service_plugins/service_mdns.cc | 600 +++ .../appid/service_plugins/service_mdns.h | 30 + .../appid/service_plugins/service_mysql.cc | 164 + .../appid/service_plugins/service_mysql.h | 30 + .../appid/service_plugins/service_netbios.cc | 1243 +++++ .../appid/service_plugins/service_netbios.h | 30 + .../appid/service_plugins/service_nntp.cc | 390 ++ .../appid/service_plugins/service_nntp.h | 30 + .../appid/service_plugins/service_ntp.cc | 178 + .../appid/service_plugins/service_ntp.h | 30 + .../appid/service_plugins/service_radius.cc | 340 ++ .../appid/service_plugins/service_radius.h | 30 + .../appid/service_plugins/service_rexec.cc | 355 ++ .../appid/service_plugins/service_rexec.h | 30 + .../appid/service_plugins/service_rfb.cc | 143 + .../appid/service_plugins/service_rfb.h | 28 + .../appid/service_plugins/service_rlogin.cc | 175 + .../appid/service_plugins/service_rlogin.h | 28 + .../appid/service_plugins/service_rpc.cc | 952 ++++ .../appid/service_plugins/service_rpc.h | 28 + .../appid/service_plugins/service_rshell.cc | 345 ++ .../appid/service_plugins/service_rshell.h | 29 + .../appid/service_plugins/service_rsync.cc | 165 + .../appid/service_plugins/service_rsync.h | 29 + .../appid/service_plugins/service_rtmp.cc | 707 +++ .../appid/service_plugins/service_rtmp.h | 29 + .../appid/service_plugins/service_smtp.cc | 350 ++ .../appid/service_plugins/service_smtp.h | 31 + .../appid/service_plugins/service_snmp.cc | 632 +++ .../appid/service_plugins/service_snmp.h | 33 + .../appid/service_plugins/service_ssh.cc | 587 +++ .../appid/service_plugins/service_ssh.h | 31 + .../appid/service_plugins/service_ssl.cc | 1179 +++++ .../appid/service_plugins/service_ssl.h | 41 + .../appid/service_plugins/service_telnet.cc | 185 + .../appid/service_plugins/service_telnet.h | 30 + .../appid/service_plugins/service_tftp.cc | 382 ++ .../appid/service_plugins/service_tftp.h | 30 + .../appid/service_plugins/service_timbuktu.cc | 200 + .../appid/service_plugins/service_tns.cc | 317 ++ .../appid/service_plugins/service_util.h | 43 + src/network_inspectors/appid/service_state.cc | 228 + src/network_inspectors/appid/service_state.h | 135 + src/network_inspectors/appid/sfaddr_temp.h | 38 + .../appid/test/CMakeLists.txt | 9 + src/network_inspectors/appid/test/Makefile.am | 121 + .../appid/test/appid_simple_test.cc | 44 + .../appid/test/appid_tests.cc | 881 ++++ .../appid/test/external_apis.cc | 249 + .../appid/test/external_apis.h | 72 + src/network_inspectors/appid/test/mpse.cc | 761 +++ .../appid/test/process_http_test.cc | 548 +++ src/network_inspectors/appid/test/rna.conf | 26 + .../appid/test/session_file.cc | 513 ++ .../appid/test/session_file.h | 182 + src/network_inspectors/appid/test/sf_iph.cc | 494 ++ src/network_inspectors/appid/test/snort.lua | 47 + .../appid/thirdparty_appid_api.h | 102 + .../appid/thirdparty_appid_types.h | 96 + .../appid/thirdparty_appid_utils.cc | 202 + .../appid/thirdparty_appid_utils.h | 37 + .../appid/util/CMakeLists.txt | 31 + src/network_inspectors/appid/util/Makefile.am | 29 + .../appid/util/common_util.h | 103 + .../appid/util/fw_avltree.cc | 423 ++ .../appid/util/fw_avltree.h | 64 + src/network_inspectors/appid/util/ip_funcs.cc | 207 + src/network_inspectors/appid/util/ip_funcs.h | 83 + .../appid/util/network_set.cc | 1252 +++++ .../appid/util/network_set.h | 400 ++ .../appid/util/output_file.cc | 53 + .../appid/util/output_file.h | 32 + src/network_inspectors/appid/util/sf_mlmp.cc | 653 +++ src/network_inspectors/appid/util/sf_mlmp.h | 51 + .../appid/util/sf_multi_mpse.cc | 432 ++ .../appid/util/sf_multi_mpse.h | 46 + .../appid/util/sfksearch.cc | 951 ++++ src/network_inspectors/appid/util/sfksearch.h | 112 + src/network_inspectors/appid/util/sfutil.cc | 165 + src/network_inspectors/appid/util/sfutil.h | 34 + src/network_inspectors/network_inspectors.cc | 2 + src/search_engines/ac_bnfa.cc | 2 + src/search_engines/search_tool.cc | 20 +- src/search_engines/search_tool.h | 3 + src/search_engines/test/Makefile.am | 12 +- src/search_engines/test/search_tool_test.cc | 217 + src/sfip/sfip_t.h | 6 + .../preprocessor_states/CMakeLists.txt | 1 + .../snort2lua/preprocessor_states/Makefile.am | 1 + .../preprocessor_states/pps_appid.cc | 173 + .../preprocessor_states/preprocessor_api.cc | 2 + 202 files changed, 59009 insertions(+), 7 deletions(-) create mode 100644 src/network_inspectors/appid/CHEAT_SHEET.txt create mode 100644 src/network_inspectors/appid/CMakeLists.txt create mode 100644 src/network_inspectors/appid/MANIFEST.txt create mode 100644 src/network_inspectors/appid/Makefile.am create mode 100644 src/network_inspectors/appid/app_forecast.cc create mode 100644 src/network_inspectors/appid/app_forecast.h create mode 100644 src/network_inspectors/appid/app_info_table.cc create mode 100644 src/network_inspectors/appid/app_info_table.h create mode 100644 src/network_inspectors/appid/appid.h create mode 100644 src/network_inspectors/appid/appid_api.cc create mode 100644 src/network_inspectors/appid/appid_api.h create mode 100644 src/network_inspectors/appid/appid_config.cc create mode 100644 src/network_inspectors/appid/appid_config.h create mode 100644 src/network_inspectors/appid/appid_flow_data.cc create mode 100644 src/network_inspectors/appid/appid_flow_data.h create mode 100644 src/network_inspectors/appid/appid_inspector.cc create mode 100644 src/network_inspectors/appid/appid_inspector.h create mode 100644 src/network_inspectors/appid/appid_module.cc create mode 100644 src/network_inspectors/appid/appid_module.h create mode 100644 src/network_inspectors/appid/appid_stats.cc create mode 100644 src/network_inspectors/appid/appid_stats.h create mode 100644 src/network_inspectors/appid/application_ids.h create mode 100644 src/network_inspectors/appid/client_plugins/CMakeLists.txt create mode 100644 src/network_inspectors/appid/client_plugins/Makefile.am create mode 100644 src/network_inspectors/appid/client_plugins/client_app_aim.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_aim.h create mode 100644 src/network_inspectors/appid/client_plugins/client_app_api.h create mode 100644 src/network_inspectors/appid/client_plugins/client_app_base.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_base.h create mode 100644 src/network_inspectors/appid/client_plugins/client_app_bit.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_bit_tracker.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_config.h create mode 100644 src/network_inspectors/appid/client_plugins/client_app_msn.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_msn.h create mode 100644 src/network_inspectors/appid/client_plugins/client_app_rtp.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_smtp.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_smtp.h create mode 100644 src/network_inspectors/appid/client_plugins/client_app_ssh.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_timbuktu.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_tns.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_vnc.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_ym.cc create mode 100644 src/network_inspectors/appid/client_plugins/client_app_ym.h create mode 100644 src/network_inspectors/appid/detector_plugins/CMakeLists.txt create mode 100644 src/network_inspectors/appid/detector_plugins/Makefile.am create mode 100644 src/network_inspectors/appid/detector_plugins/detector_api.h create mode 100644 src/network_inspectors/appid/detector_plugins/detector_base.cc create mode 100644 src/network_inspectors/appid/detector_plugins/detector_base.h create mode 100644 src/network_inspectors/appid/detector_plugins/detector_dns.cc create mode 100644 src/network_inspectors/appid/detector_plugins/detector_dns.h create mode 100644 src/network_inspectors/appid/detector_plugins/detector_http.cc create mode 100644 src/network_inspectors/appid/detector_plugins/detector_http.h create mode 100644 src/network_inspectors/appid/detector_plugins/detector_imap.cc create mode 100644 src/network_inspectors/appid/detector_plugins/detector_kerberos.cc create mode 100644 src/network_inspectors/appid/detector_plugins/detector_pattern.cc create mode 100644 src/network_inspectors/appid/detector_plugins/detector_pattern.h create mode 100644 src/network_inspectors/appid/detector_plugins/detector_pop3.cc create mode 100644 src/network_inspectors/appid/detector_plugins/detector_sip.cc create mode 100644 src/network_inspectors/appid/detector_plugins/detector_sip.h create mode 100644 src/network_inspectors/appid/detector_plugins/http_url_patterns.cc create mode 100644 src/network_inspectors/appid/detector_plugins/http_url_patterns.h create mode 100644 src/network_inspectors/appid/dns_defs.h create mode 100755 src/network_inspectors/appid/errors.sh create mode 100644 src/network_inspectors/appid/flow_error.h create mode 100644 src/network_inspectors/appid/fw_appid.cc create mode 100644 src/network_inspectors/appid/fw_appid.h create mode 100644 src/network_inspectors/appid/host_port_app_cache.cc create mode 100644 src/network_inspectors/appid/host_port_app_cache.h create mode 100644 src/network_inspectors/appid/http_common.h create mode 100644 src/network_inspectors/appid/length_app_cache.cc create mode 100644 src/network_inspectors/appid/length_app_cache.h create mode 100644 src/network_inspectors/appid/lua_detector_api.cc create mode 100644 src/network_inspectors/appid/lua_detector_api.h create mode 100644 src/network_inspectors/appid/lua_detector_flow_api.cc create mode 100644 src/network_inspectors/appid/lua_detector_flow_api.h create mode 100644 src/network_inspectors/appid/lua_detector_module.cc create mode 100644 src/network_inspectors/appid/lua_detector_module.h create mode 100644 src/network_inspectors/appid/lua_detector_util.h create mode 100644 src/network_inspectors/appid/service_plugins/CMakeLists.txt create mode 100644 src/network_inspectors/appid/service_plugins/Makefile.am create mode 100644 src/network_inspectors/appid/service_plugins/dcerpc.cc create mode 100644 src/network_inspectors/appid/service_plugins/dcerpc.h create mode 100644 src/network_inspectors/appid/service_plugins/service_api.h create mode 100644 src/network_inspectors/appid/service_plugins/service_base.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_base.h create mode 100644 src/network_inspectors/appid/service_plugins/service_battle_field.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_battle_field.h create mode 100644 src/network_inspectors/appid/service_plugins/service_bgp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_bgp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_bit.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_bootp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_bootp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_config.h create mode 100644 src/network_inspectors/appid/service_plugins/service_dcerpc.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_dcerpc.h create mode 100644 src/network_inspectors/appid/service_plugins/service_direct_connect.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_direct_connect.h create mode 100644 src/network_inspectors/appid/service_plugins/service_flap.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_flap.h create mode 100644 src/network_inspectors/appid/service_plugins/service_ftp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_ftp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_irc.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_irc.h create mode 100644 src/network_inspectors/appid/service_plugins/service_lpr.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_lpr.h create mode 100644 src/network_inspectors/appid/service_plugins/service_mdns.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_mdns.h create mode 100644 src/network_inspectors/appid/service_plugins/service_mysql.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_mysql.h create mode 100644 src/network_inspectors/appid/service_plugins/service_netbios.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_netbios.h create mode 100644 src/network_inspectors/appid/service_plugins/service_nntp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_nntp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_ntp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_ntp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_radius.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_radius.h create mode 100644 src/network_inspectors/appid/service_plugins/service_rexec.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_rexec.h create mode 100644 src/network_inspectors/appid/service_plugins/service_rfb.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_rfb.h create mode 100644 src/network_inspectors/appid/service_plugins/service_rlogin.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_rlogin.h create mode 100644 src/network_inspectors/appid/service_plugins/service_rpc.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_rpc.h create mode 100644 src/network_inspectors/appid/service_plugins/service_rshell.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_rshell.h create mode 100644 src/network_inspectors/appid/service_plugins/service_rsync.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_rsync.h create mode 100644 src/network_inspectors/appid/service_plugins/service_rtmp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_rtmp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_smtp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_smtp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_snmp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_snmp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_ssh.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_ssh.h create mode 100644 src/network_inspectors/appid/service_plugins/service_ssl.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_ssl.h create mode 100644 src/network_inspectors/appid/service_plugins/service_telnet.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_telnet.h create mode 100644 src/network_inspectors/appid/service_plugins/service_tftp.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_tftp.h create mode 100644 src/network_inspectors/appid/service_plugins/service_timbuktu.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_tns.cc create mode 100644 src/network_inspectors/appid/service_plugins/service_util.h create mode 100644 src/network_inspectors/appid/service_state.cc create mode 100644 src/network_inspectors/appid/service_state.h create mode 100644 src/network_inspectors/appid/sfaddr_temp.h create mode 100644 src/network_inspectors/appid/test/CMakeLists.txt create mode 100644 src/network_inspectors/appid/test/Makefile.am create mode 100644 src/network_inspectors/appid/test/appid_simple_test.cc create mode 100644 src/network_inspectors/appid/test/appid_tests.cc create mode 100644 src/network_inspectors/appid/test/external_apis.cc create mode 100644 src/network_inspectors/appid/test/external_apis.h create mode 100644 src/network_inspectors/appid/test/mpse.cc create mode 100644 src/network_inspectors/appid/test/process_http_test.cc create mode 100644 src/network_inspectors/appid/test/rna.conf create mode 100644 src/network_inspectors/appid/test/session_file.cc create mode 100644 src/network_inspectors/appid/test/session_file.h create mode 100644 src/network_inspectors/appid/test/sf_iph.cc create mode 100644 src/network_inspectors/appid/test/snort.lua create mode 100644 src/network_inspectors/appid/thirdparty_appid_api.h create mode 100644 src/network_inspectors/appid/thirdparty_appid_types.h create mode 100644 src/network_inspectors/appid/thirdparty_appid_utils.cc create mode 100644 src/network_inspectors/appid/thirdparty_appid_utils.h create mode 100644 src/network_inspectors/appid/util/CMakeLists.txt create mode 100644 src/network_inspectors/appid/util/Makefile.am create mode 100644 src/network_inspectors/appid/util/common_util.h create mode 100644 src/network_inspectors/appid/util/fw_avltree.cc create mode 100644 src/network_inspectors/appid/util/fw_avltree.h create mode 100644 src/network_inspectors/appid/util/ip_funcs.cc create mode 100644 src/network_inspectors/appid/util/ip_funcs.h create mode 100644 src/network_inspectors/appid/util/network_set.cc create mode 100644 src/network_inspectors/appid/util/network_set.h create mode 100644 src/network_inspectors/appid/util/output_file.cc create mode 100644 src/network_inspectors/appid/util/output_file.h create mode 100644 src/network_inspectors/appid/util/sf_mlmp.cc create mode 100644 src/network_inspectors/appid/util/sf_mlmp.h create mode 100644 src/network_inspectors/appid/util/sf_multi_mpse.cc create mode 100644 src/network_inspectors/appid/util/sf_multi_mpse.h create mode 100644 src/network_inspectors/appid/util/sfksearch.cc create mode 100644 src/network_inspectors/appid/util/sfksearch.h create mode 100644 src/network_inspectors/appid/util/sfutil.cc create mode 100644 src/network_inspectors/appid/util/sfutil.h create mode 100644 src/search_engines/test/search_tool_test.cc create mode 100644 tools/snort2lua/preprocessor_states/pps_appid.cc diff --git a/configure.ac b/configure.ac index d2c73acf7..273766a29 100644 --- a/configure.ac +++ b/configure.ac @@ -1107,6 +1107,12 @@ src/stream/user/Makefile \ src/stream/file/Makefile \ src/stream/tcp/test/Makefile \ src/network_inspectors/Makefile \ +src/network_inspectors/appid/Makefile \ +src/network_inspectors/appid/client_plugins/Makefile \ +src/network_inspectors/appid/detector_plugins/Makefile \ +src/network_inspectors/appid/service_plugins/Makefile \ +src/network_inspectors/appid/test/Makefile \ +src/network_inspectors/appid/util/Makefile \ src/network_inspectors/arp_spoof/Makefile \ src/network_inspectors/binder/Makefile \ src/network_inspectors/normalize/Makefile \ diff --git a/src/Makefile.am b/src/Makefile.am index 3392f0a4b..03f3cc2d0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -7,6 +7,11 @@ main.h if STATIC_INSPECTORS lib_list = \ +network_inspectors/appid/libappid.a \ +network_inspectors/appid/client_plugins/libappid_client_plugins.a \ +network_inspectors/appid/detector_plugins/libappid_detector_plugins.a \ +network_inspectors/appid/service_plugins/libappid_service_plugins.a \ +network_inspectors/appid/util/libappid_util.a \ network_inspectors/arp_spoof/libarp_spoof.a \ network_inspectors/packet_capture/libpacket_capture.a \ service_inspectors/back_orifice/libback_orifice.a \ diff --git a/src/flow/flow.cc b/src/flow/flow.cc index 04391b9e1..da8dbe659 100644 --- a/src/flow/flow.cc +++ b/src/flow/flow.cc @@ -31,7 +31,7 @@ #include "protocols/packet.h" #include "sfip/sf_ip.h" -unsigned FlowData:: flow_id = 0; +unsigned FlowData::flow_id = 0; FlowData::FlowData(unsigned u, Inspector* ph) { diff --git a/src/main/snort_debug.h b/src/main/snort_debug.h index 08146a57a..0049e26c6 100644 --- a/src/main/snort_debug.h +++ b/src/main/snort_debug.h @@ -88,6 +88,7 @@ #define DEBUG_DCE_TCP 0x0080000000000000LL #define DEBUG_DCE_SMB 0x0100000000000000LL #define DEBUG_DCE_COMMON 0x0200000000000000LL +#define DEBUG_APPID 0x0400000000000000LL #ifdef PIGLET #define DEBUG_PIGLET 0x0400000000000000LL diff --git a/src/network_inspectors/CMakeLists.txt b/src/network_inspectors/CMakeLists.txt index 3d5db0e16..df74c9411 100644 --- a/src/network_inspectors/CMakeLists.txt +++ b/src/network_inspectors/CMakeLists.txt @@ -1,4 +1,5 @@ +add_subdirectory(appid) add_subdirectory(arp_spoof) add_subdirectory(binder) add_subdirectory(normalize) @@ -21,10 +22,15 @@ add_library( network_inspectors STATIC target_link_libraries( network_inspectors ${STATIC_INSPECTOR_LIBS} + appid + appid_client_plugins + appid_detector_plugins + appid_service_plugins + appid_util binder normalize perf_monitor port_scan - reputation + reputation stream_tcp ) diff --git a/src/network_inspectors/Makefile.am b/src/network_inspectors/Makefile.am index b03fff1b1..2dca17d90 100644 --- a/src/network_inspectors/Makefile.am +++ b/src/network_inspectors/Makefile.am @@ -14,6 +14,7 @@ network_inspectors.h #port_scan/libport_scan.a SUBDIRS = \ +appid \ arp_spoof \ binder \ normalize \ diff --git a/src/network_inspectors/appid/CHEAT_SHEET.txt b/src/network_inspectors/appid/CHEAT_SHEET.txt new file mode 100644 index 000000000..a95b01b42 --- /dev/null +++ b/src/network_inspectors/appid/CHEAT_SHEET.txt @@ -0,0 +1,59 @@ +- Remove 'struct ' before parameter and variable declarations. +- If 'static' and 'inline' are on separate lines, put them on one line. +- `SFSnortPacket` -> `Packet` +- `p->src_port` -> `p->ptrs.sp` +- `p->dst_port` -> `p->ptrs.dp` +- `GET_DST_IP(p)` -> `p->ptrs.ip_api.get_dst()` +- `GET_SRC_IP(p)` -> `p->ptrs.ip_api.get_src()` +- `p->pkt_header` -> `p->pkth` +- `p->payload` -> `p->data` +- `p->payload_size` -> `p->dsize` +- `SF_SO_PUBLIC` -> `SO_PUBLIC` +- `p->ether_header` -> `layer::get_eth_layer(p)` +- `p->tcp_header->flags & TCPHEADER_*` -> `p->ptrs.tcph->are_flags_set(TH_*)` +- `ntohs(pkt->tcp_header->urgent_pointer)` -> `p->ptrs.tcph->urp()` +- `_dpd.searchAPI` -> `SeachTool` +- _dpd.logMsg -> LogMessage +- _dpd.errMsg -> ErrorMessage +- _dpd.fatalMsg -> FatalError +- _dpd.debugMsg -> If there are printf args, then use DebugFormat. if not, + use DebugMessage. +- _dpd.addProtocolReference(name) -> AddProtocolReference(name) + +Non-reentrant functions in use: + +* strerror strerror_r +* strtok strtok_r + +Other non-reentrant functions: + +* asctime asctime_r +* ctime ctime_r +* getgrnam getgrnam_r +* gethostbyaddr gethostbyname_r +* gethostbyname gethostbyaddr_r +* gethostent gethostent_r +* getprotobyname getprotobyname_r +* getprotobynumber getprotobynumber_r +* getpwnam getpwnam_r +* getpwuid getpwuid_r +* getservbyname getservbyname_r +* gmtime gmtime_r +* hcreate hcreate_r +* hsearch hsearch_r +* hdestroy hdestroy_r +* localtime localtime_r +* pcap_strerror strerror_r +* random random_r +* readdir readdir_r +* ttyname ttyname_r + +special case; inet_ntoa_r not available on linux; +inet_ntoa is redefined to sfip_ntoa() +* inet_ntoa +inet_ntop + +# mac os doesn't have random_r and srandom_r. And rand_r # is weak. + +* random random_r +* rand rand_r +* srandom srandom_r diff --git a/src/network_inspectors/appid/CMakeLists.txt b/src/network_inspectors/appid/CMakeLists.txt new file mode 100644 index 000000000..72ff610f7 --- /dev/null +++ b/src/network_inspectors/appid/CMakeLists.txt @@ -0,0 +1,69 @@ +set ( APPID_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR} ) + +add_subdirectory(client_plugins) +add_subdirectory(detector_plugins) +add_subdirectory(service_plugins) +add_subdirectory(util) +add_subdirectory ( test ) + +set ( APPID_SOURCES + app_forecast.cc + app_forecast.h + appid_api.cc + appid_api.h + appid_config.cc + appid_config.h + appid_flow_data.cc + appid_flow_data.h + appid.h + appid_inspector.cc + appid_inspector.h + appid_module.cc + appid_module.h + appid_stats.cc + appid_stats.h + app_info_table.cc + app_info_table.h + application_ids.h + dns_defs.h + flow_error.h + fw_appid.cc + fw_appid.h + host_port_app_cache.cc + host_port_app_cache.h + http_common.h + length_app_cache.cc + length_app_cache.h + lua_detector_api.cc + lua_detector_api.h + lua_detector_flow_api.cc + lua_detector_flow_api.h + lua_detector_module.cc + lua_detector_module.h + lua_detector_util.h + service_state.cc + service_state.h + sfaddr_temp.h + thirdparty_appid_api.h + thirdparty_appid_types.h + thirdparty_appid_utils.cc + thirdparty_appid_utils.h + ) + +add_library ( appid STATIC + ${APPID_SOURCES} +) + +target_include_directories ( appid PRIVATE ${APPID_INCLUDE_DIR} ) + +target_link_libraries ( appid + appid_client_plugins + appid_detector_plugins + appid_service_plugins +) + +# FIXIT-H: Add unit tests + +#install (FILES ${APPID_INCLUDES} +# DESTINATION "${INCLUDE_INSTALL_PATH}/appid" +#) diff --git a/src/network_inspectors/appid/MANIFEST.txt b/src/network_inspectors/appid/MANIFEST.txt new file mode 100644 index 000000000..f0bfd1fef --- /dev/null +++ b/src/network_inspectors/appid/MANIFEST.txt @@ -0,0 +1,188 @@ +FILENAME PORTING STATUS +-------- -------------- +# Syntax: +# - lines beginning with '#' are comments +# - anything after a '#' on a line is a comment +# - unprepped: +# - in-progress: WORKING +# - not in-progress but not complete: PARTIAL +# - completed: DONE +# +# A file should set to PARTIAL when an updated version is committed but not all the work is done +# and the developer committing is not going to continue making changes, i.e. someone else can +# grab it and make more changes. +# +# you can add a short note/comment after the file if necessary +# example: +# dns_defs.h WORKING jocornet # fixed everything but includes +# + +#FILENAME STATUS USER +#-------- ------ ---- + +appid.h DONE jocornet +app_forecast.cc DONE jocornet +app_forecast.h DONE jocornet +app_info_table.cc DONE jocornet +app_info_table.h DONE jocornet +appid_api.cc DONE jocornet +appid_api.h DONE jocornet +appid_config.cc DONE jocornet +appid_config.h DONE jocornet +appid_stats.cc DONE jocornet +appid_stats.h DONE jocornet +application_ids.h DONE jocornet +common_app_matcher.cc DONE jocornet +common_app_matcher.h DONE jocornet +dns_defs.h DONE jocornet +appid_flow_data.cc DONE jocornet +appid_flow_data.h DONE jocornet +flow_error.h DONE jocornet +fw_appid.cc DONE jocornet +fw_appid.h DONE eborgoyn +host_port_app_cache.cc DONE eborgoyn +host_port_app_cache.h DONE eborgoyn +http_common.h DONE eborgoyn +length_app_cache.cc DONE eborgoyn +length_app_cache.h DONE eborgoyn +lua_detector_api.cc DONE jocornet +lua_detector_api.h DONE eborgoyn +lua_detector_flow_api.cc DONE eborgoyn # pending sfaddr -> sfip conversion +lua_detector_flow_api.h DONE eborgoyn +lua_detector_module.cc DONE eborgoyn # compiles, but needs a ton of work +lua_detector_module.h DONE eborgoyn +lua_detector_util.h DONE eborgoyn # added by jocornet +service_state.cc DONE eborgoyn +service_state.h DONE eborgoyn +sfaddr_temp.h DONE eborgoyn +spp_appid.cc DONE huica +spp_appid.h DONE huica +thirdparty_appid_api.h DONE huica +thirdparty_appid_types.h DONE huica +thirdparty_appid_utils.cc DONE huica +thirdparty_appid_utils.h DONE huica +client_plugins/client_app_aim.cc DONE huica +client_plugins/client_app_aim.h DONE huica +client_plugins/client_app_api.h DONE huica +client_plugins/client_app_base.cc DONE huica +client_plugins/client_app_base.h DONE huica +client_plugins/client_app_bit.cc DONE huica +client_plugins/client_app_bit_tracker.cc DONE huica +client_plugins/client_app_config.h DONE huica +client_plugins/client_app_msn.cc DONE huica +client_plugins/client_app_msn.h DONE huica +client_plugins/client_app_rtp.cc DONE mdagon +client_plugins/client_app_smtp.cc DONE mdagon +client_plugins/client_app_smtp.h DONE mdagon +client_plugins/client_app_ssh.cc DONE mdagon +client_plugins/client_app_timbuktu.cc DONE mdagon +client_plugins/client_app_tns.cc DONE mdagon +client_plugins/client_app_vnc.cc DONE mdagon +client_plugins/client_app_ym.cc DONE mdagon +client_plugins/client_app_ym.h DONE mdagon +detector_plugins/detector_api.h DONE mdagon +detector_plugins/detector_base.cc DONE mdagon +detector_plugins/detector_base.h DONE mdagon +detector_plugins/detector_dns.cc DONE mdagon +detector_plugins/detector_dns.h DONE mdagon +detector_plugins/detector_http.cc DONE mdagon # sfaddr_t problems (notably 2x `sfip_set_raw`) +detector_plugins/detector_http.h DONE mdagon +detector_plugins/detector_imap.cc DONE mialtize +detector_plugins/detector_kerberos.cc DONE mialtize +detector_plugins/detector_pattern.cc DONE mialtize +detector_plugins/detector_pattern.h DONE mialtize +detector_plugins/detector_pop3.cc DONE mialtize +detector_plugins/detector_sip.cc DONE mialtize +detector_plugins/detector_sip.h DONE mialtize +detector_plugins/http_url_patterns.cc DONE mialtize +detector_plugins/http_url_patterns.h DONE mialtize +service_plugins/dcerpc.cc DONE mialtize +service_plugins/dcerpc.h DONE mialtize +service_plugins/service_api.h DONE mialtize +service_plugins/service_battle_field.cc DONE mialtize +service_plugins/service_battle_field.h DONE mialtize +service_plugins/service_bgp.cc DONE mialtize +service_plugins/service_bgp.h DONE mialtize +service_plugins/service_base.cc DONE davmcphe # SF_LIST and sfip_t/sfaddr_t badness +service_plugins/service_base.h DONE davmcphe +service_plugins/service_bit.cc DONE davmcphe +service_plugins/service_bootp.cc DONE davmcphe +service_plugins/service_bootp.h DONE davmcphe +service_plugins/service_config.h DONE davmcphe +service_plugins/service_dcerpc.cc DONE davmcphe +service_plugins/service_dcerpc.h DONE davmcphe +service_plugins/service_direct_connect.cc DONE davmcphe +service_plugins/service_direct_connect.h DONE davmcphe +service_plugins/service_flap.cc DONE davmcphe +service_plugins/service_flap.h DONE davmcphe +service_plugins/service_ftp.cc DONE davmcphe +service_plugins/service_ftp.h DONE davmcphe +service_plugins/service_irc.cc DONE davmcphe +service_plugins/service_irc.h DONE davmcphe +service_plugins/service_lpr.cc DONE davmcphe +service_plugins/service_lpr.h DONE rucombs +service_plugins/service_mdns.cc DONE rucombs # name change, searchAPI -> SearchTool not completed +service_plugins/service_mdns.h DONE rucombs # name change +service_plugins/service_mysql.cc DONE rucombs +service_plugins/service_mysql.h DONE rucombs +service_plugins/service_netbios.cc DONE rucombs +service_plugins/service_netbios.h DONE rucombs +service_plugins/service_nntp.cc DONE rucombs +service_plugins/service_nntp.h DONE rucombs +service_plugins/service_ntp.cc DONE rucombs +service_plugins/service_ntp.h DONE rucombs +service_plugins/service_radius.cc DONE rucombs +service_plugins/service_radius.h DONE rucombs +service_plugins/service_rexec.cc DONE rucombs +service_plugins/service_rexec.h DONE rucombs +service_plugins/service_rfb.cc DONE rucombs +service_plugins/service_rfb.h WORKING stechew +service_plugins/service_rlogin.cc WORKING stechew +service_plugins/service_rlogin.h WORKING stechew +service_plugins/service_rpc.cc WORKING stechew +service_plugins/service_rpc.h WORKING stechew +service_plugins/service_rshell.cc WORKING stechew +service_plugins/service_rshell.h WORKING stechew +service_plugins/service_rsync.cc WORKING stechew +service_plugins/service_rsync.h WORKING stechew +service_plugins/service_rtmp.cc WORKING stechew +service_plugins/service_rtmp.h WORKING stechew +service_plugins/service_smtp.cc WORKING stechew +service_plugins/service_smtp.h WORKING stechew +service_plugins/service_snmp.cc WORKING stechew +service_plugins/service_snmp.h WORKING stechew +service_plugins/service_ssh.cc WORKING stechew +service_plugins/service_ssh.h DONE thopeter +service_plugins/service_ssl.cc DONE thopeter +service_plugins/service_ssl.h DONE thopeter +service_plugins/service_telnet.cc DONE thopeter +service_plugins/service_telnet.h DONE thopeter +service_plugins/service_tftp.cc DONE thopeter +service_plugins/service_tftp.h DONE thopeter +service_plugins/service_timbuktu.cc DONE thopeter +service_plugins/service_tns.cc DONE thopeter +service_plugins/service_util.h DONE thopeter +test/appid_tests.cc PARTIAL thopeter +test/external_apis.cc PARTIAL viroemer +test/external_apis.h DONE thopeter +test/mpse.cc PARTIAL thopeter +test/session_file.cc PARTIAL thopeter +test/session_file.h DONE thopeter +test/sf_iph.cc PARTIAL thopeter +util/common_util.h DONE viroemer +util/fw_avltree.cc DONE viroemer +util/fw_avltree.h DONE viroemer +util/ip_funcs.cc DONE viroemer +util/ip_funcs.h DONE viroemer +util/network_set.cc DONE viroemer +util/network_set.h DONE viroemer +util/output_file.cc DONE viroemer +util/output_file.h DONE viroemer +util/sf_mlmp.cc DONE viroemer +util/sf_mlmp.h DONE viroemer +util/sf_multi_mpse.cc DONE viroemer +util/sf_multi_mpse.h DONE viroemer +util/sfksearch.cc DONE viroemer +util/sfksearch.h DONE viroemer +util/sfutil.cc DONE viroemer +util/sfutil.h DONE viroemer diff --git a/src/network_inspectors/appid/Makefile.am b/src/network_inspectors/appid/Makefile.am new file mode 100644 index 000000000..fcac3f6b8 --- /dev/null +++ b/src/network_inspectors/appid/Makefile.am @@ -0,0 +1,60 @@ + +AM_CPPFLAGS+=-I$(top_srcdir)/src/network_inspectors/appid + +file_list = \ +app_forecast.cc \ +app_forecast.h \ +appid_api.cc \ +appid_api.h \ +appid_config.cc \ +appid_config.h \ +appid_flow_data.cc \ +appid_flow_data.h \ +appid.h \ +appid_inspector.cc \ +appid_inspector.h \ +appid_module.cc \ +appid_module.h \ +appid_stats.cc \ +appid_stats.h \ +app_info_table.cc \ +app_info_table.h \ +application_ids.h \ +dns_defs.h \ +flow_error.h \ +fw_appid.cc \ +fw_appid.h \ +host_port_app_cache.cc \ +host_port_app_cache.h \ +http_common.h \ +length_app_cache.cc \ +length_app_cache.h \ +lua_detector_api.cc \ +lua_detector_api.h \ +lua_detector_flow_api.cc \ +lua_detector_flow_api.h \ +lua_detector_module.cc \ +lua_detector_module.h \ +lua_detector_util.h \ +service_state.cc \ +service_state.h \ +sfaddr_temp.h \ +thirdparty_appid_api.h \ +thirdparty_appid_types.h \ +thirdparty_appid_utils.cc \ +thirdparty_appid_utils.h + +noinst_LIBRARIES = libappid.a +libappid_a_SOURCES = $(file_list) + +SUBDIRS = \ +client_plugins \ +detector_plugins \ +service_plugins \ +util + +#if BUILD_CPPUTESTS +#SUBDIRS += test +#endif + + diff --git a/src/network_inspectors/appid/app_forecast.cc b/src/network_inspectors/appid/app_forecast.cc new file mode 100644 index 000000000..5c5a68708 --- /dev/null +++ b/src/network_inspectors/appid/app_forecast.cc @@ -0,0 +1,88 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// app_forecast.cc author Sourcefire Inc. + +#include "app_forecast.h" + +#include "hash/sfxhash.h" +#include "time/packet_time.h" + +#include "application_ids.h" + +static AFActKey master_key; + +static inline void rekeyMasterAFActKey(Packet* p, int dir, ApplicationId forecast) +{ + const sfip_t* src = dir ? p->ptrs.ip_api.get_dst() : p->ptrs.ip_api.get_src(); + + for (int i = 0; i < 4; i++) + master_key.ip[i] = src->ip32[i]; + + master_key.forecast = forecast; +} + +void checkSessionForAFIndicator( + Packet* p, int dir, const AppIdConfig* pConfig, ApplicationId indicator) +{ + AFElement* ind_element; + if (!(ind_element = (AFElement*)sfxhash_find(pConfig->AF_indicators, &indicator))) + return; + + rekeyMasterAFActKey(p, dir, ind_element->forecast); + + AFActVal* test_active_value; + if ((test_active_value = (AFActVal*)sfxhash_find(pConfig->AF_actives, &master_key))) + { + test_active_value->last = packet_time(); + test_active_value->target = ind_element->target; + return; + } + + AFActVal new_active_value; + new_active_value.target = ind_element->target; + new_active_value.last = packet_time(); + + sfxhash_add(pConfig->AF_actives, &master_key, &new_active_value); +} + +AppId checkSessionForAFForecast( + AppIdData* session, Packet* p, int dir, const AppIdConfig* pConfig, ApplicationId forecast) +{ + AFActVal* check_act_val; + + rekeyMasterAFActKey(p, dir, forecast); + + //get out if there is no value + if (!(check_act_val = (AFActVal*)sfxhash_find(pConfig->AF_actives, &master_key))) + return APP_ID_UNKNOWN; + + //if the value is older than 5 minutes, remove it and get out + time_t age; + age = packet_time() - check_act_val->last; + if (age < 0 || age > 300) + { + sfxhash_remove(pConfig->AF_actives, &master_key); + return APP_ID_UNKNOWN; + } + + session->payloadAppId = check_act_val->target; + return forecast; +} + diff --git a/src/network_inspectors/appid/app_forecast.h b/src/network_inspectors/appid/app_forecast.h new file mode 100644 index 000000000..b44cfc3ec --- /dev/null +++ b/src/network_inspectors/appid/app_forecast.h @@ -0,0 +1,68 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// app_forecast.h author Sourcefire Inc. + +#ifndef APP_FORECAST_H +#define APP_FORECAST_H + +// AppId flow forcasting data structures and methods +// + +#include +#include "appid_api.h" +#include "appid_config.h" +#include "protocols/packet.h" + +#include "appid_flow_data.h" + +// indicator - the appId that indicates there may be subsequent flows to look for, from the same host +// forecast - the appId in the subsequent flow that we are looking for +// target - the appId we want to set in that subsequent flow +// +// for now, indicator and target are WEB APPLICATIONS. The forecast is APP PROTOCOL. We can change this +// later by adding app type info for each, if we find a use case. + +class AppIdConfig; +enum ApplicationId : int32_t; + +struct AFElement +{ + ApplicationId indicator; + ApplicationId forecast; + ApplicationId target; +}; + +struct AFActKey +{ + uint32_t ip[4]; + ApplicationId forecast; +}; + +struct AFActVal +{ + ApplicationId target; + time_t last; +}; + +void checkSessionForAFIndicator(Packet*, int, const AppIdConfig*, ApplicationId); +AppId checkSessionForAFForecast(AppIdData*, Packet*, int, const AppIdConfig*, ApplicationId); + +#endif + diff --git a/src/network_inspectors/appid/app_info_table.cc b/src/network_inspectors/appid/app_info_table.cc new file mode 100644 index 000000000..d1ba1435c --- /dev/null +++ b/src/network_inspectors/appid/app_info_table.cc @@ -0,0 +1,785 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// app_info_table.cc author Sourcefire Inc. + +#include "app_info_table.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "application_ids.h" + +#include "log/messages.h" +#include "hash/sfghash.h" +#include "main/snort_debug.h" +#include "target_based/snort_protocols.h" +#include "utils/util.h" + +#define APP_MAPPING_FILE "appMapping.data" +#define APP_CONFIG_FILE "appid.conf" +#define USR_CONFIG_FILE "userappid.conf" + +#define MAX_TABLE_LINE_LEN 1024 +#define CONF_SEPARATORS "\t\n\r" +#define MIN_MAX_TP_FLOW_DEPTH 1 +#define MAX_MAX_TP_FLOW_DEPTH 1000000 + +struct DynamicArray +{ + AppInfoTableEntry** table; + size_t indexStart; + size_t indexCurrent; + size_t usedCount; + size_t allocatedCount; + size_t stepSize; +}; + +static inline DynamicArray* dynamicArrayCreate(unsigned indexStart) +{ + DynamicArray* array; + + array = (DynamicArray*)snort_calloc(sizeof(DynamicArray)); + array->stepSize = 1; + array->indexStart = indexStart; + return array; +} + +static inline void dynamicArrayDestroy(DynamicArray* array) +{ + unsigned i; + AppInfoTableEntry* entry; + + if (!array) + return; + for (i = 0; i < array->usedCount; i++) + { + entry = array->table[i]; + snort_free(entry->appName); + snort_free(entry); + } + + // FIXIT - array table is still alloc'ed with calloc/realloc + free(array->table); + snort_free(array); +} + +static inline void dynamicArraySetIndex(DynamicArray* array, unsigned index, + AppInfoTableEntry* data) +{ + if (index >= array->indexStart && index < (array->indexStart + array->usedCount)) + array->table[index - array->indexStart] = data; +} + +static inline AppInfoTableEntry* dynamicArrayGetIndex(DynamicArray* array, unsigned index) +{ + if (index >= array->indexStart && index < (array->indexStart + array->usedCount)) + return array->table[index - array->indexStart]; + return nullptr; +} + +static inline bool dynamicArrayCreateIndex(DynamicArray* array, unsigned* index) +{ + if (array->usedCount == array->allocatedCount) + { + AppInfoTableEntry** tmp = + (AppInfoTableEntry**)realloc(array->table, + (array->allocatedCount + array->stepSize) * sizeof(*tmp)); + if (!tmp) + { + return false; + } + array->table = tmp; + array->allocatedCount += array->stepSize; + } + *index = array->indexStart + (array->usedCount++); + return true; +} + +static inline void* dynamicArrayGetFirst(DynamicArray* array) +{ + AppInfoTableEntry* entry; + for (array->indexCurrent = 0; array->indexCurrent < array->usedCount; array->indexCurrent++) + { + if ((entry = array->table[array->indexCurrent])) + return entry; + } + return nullptr; +} + +static inline void* dynamicArrayGetNext(DynamicArray* array) +{ + AppInfoTableEntry* entry; + for (array->indexCurrent++; array->indexCurrent < array->usedCount; array->indexCurrent++) + { + if ((entry = array->table[array->indexCurrent])) + return entry; + } + return nullptr; +} + +// End of Dynamic array +SFGHASH* appNameHashInit() +{ + SFGHASH* appNameHash; + appNameHash = sfghash_new(65, 0, 0 /* alloc copies of lowercased keys */, nullptr); + if (!appNameHash) + { + FatalError("AppNameHash: Failed to Initialize\n"); + } + return appNameHash; +} + +void appNameHashFini(SFGHASH* appNameHash) +{ + if (appNameHash) + { + sfghash_delete(appNameHash); + } +} + +static inline char* strdupToLower(const char* source) +{ + char* dest = snort_strdup(source); + char* lcd = dest; + + while(*lcd) + { + *lcd = tolower(*lcd); + lcd++; + } + + return dest; +} + +void appNameHashAdd(SFGHASH* appNameHash, const char* appName, void* data) +{ + char* searchName; + int errCode; + + if (!appName || !appNameHash) + return; + + searchName = strdupToLower(appName); + if (!searchName) + return; + + if (SFGHASH_OK == (errCode = sfghash_add(appNameHash, searchName, data))) + { + DebugFormat(DEBUG_INSPECTOR, "App name added for %s\n", appName); + } + else if (SFGHASH_INTABLE == errCode) + { + /* Note that, although this entry is not placed in the hash table, + being a duplicate, it remains in the list of allocated entries + for cleanup by appInfoTableFini() */ + + // Rediscover the existing, hashed entry for the purpose of a complete error message. + AppInfoTableEntry* tableEntry = (AppInfoTableEntry*)sfghash_find(appNameHash, searchName); + + if (tableEntry) + { + ErrorMessage("App name, \"%s\", is a duplicate of \"%s\" and has been ignored.\n", + appName, tableEntry->appName); + } + else + { + ErrorMessage("App name, \"%s\", has been ignored. Hash key \"%s\" is not unique.\n", + appName, searchName); + } + } + snort_free(searchName); +} + +void* appNameHashFind(SFGHASH* appNameHash, const char* appName) +{ + void* data; + char* searchName; + + if (!appName || !appNameHash) + return nullptr; + + searchName = strdupToLower(appName); + if (!searchName) + return nullptr; + + data = sfghash_find(appNameHash, searchName); + + snort_free(searchName); + + return data; +} + +// End of appName hash + +static void appIdConfLoad(const char* path); + +static unsigned int getAppIdStaticIndex(AppId appid) +{ + if (appid > 0 && appid < SF_APPID_BUILDIN_MAX) + return appid; + if (appid >= SF_APPID_CSD_MIN && appid < SF_APPID_CSD_MIN+(SF_APPID_MAX-SF_APPID_BUILDIN_MAX)) + return (SF_APPID_BUILDIN_MAX + appid - SF_APPID_CSD_MIN); + return 0; +} + +AppInfoTableEntry* appInfoEntryGet(AppId appId, const AppIdConfig* pConfig) +{ + AppId tmp; + if ((tmp = getAppIdStaticIndex(appId))) + return pConfig->AppInfoTable[tmp]; + return dynamicArrayGetIndex(pConfig->AppInfoTableDyn, appId); +} + +AppInfoTableEntry* appInfoEntryCreate(const char* appName, AppIdConfig* pConfig) +{ + AppId appId; + AppInfoTableEntry* entry; + + if (!appName || strlen(appName) >= MAX_EVENT_APPNAME_LEN) + { + ErrorMessage("Appname invalid or too long: %s\n", appName); + return nullptr; + } + + entry = static_cast(appNameHashFind(pConfig->AppNameHash, appName)); + + if (!entry) + { + if (!dynamicArrayCreateIndex(pConfig->AppInfoTableDyn, (uint32_t*)&appId)) + return nullptr; + + entry = static_cast(snort_calloc(sizeof(AppInfoTableEntry))); + entry->appId = appId; + entry->serviceId = entry->appId; + entry->clientId = entry->appId; + entry->payloadId = entry->appId; + entry->appName = snort_strdup(appName); + dynamicArraySetIndex(pConfig->AppInfoTableDyn, appId, entry); + } + return entry; +} + +void appInfoTableInit(const char* path, AppIdConfig* pConfig) +{ + FILE* tableFile; + const char* token; + char buf[MAX_TABLE_LINE_LEN]; + AppInfoTableEntry* entry; + AppId appId; + uint32_t clientId, serviceId, payloadId; + char filepath[PATH_MAX]; + char* appName; + char* snortName=nullptr; + char* context; + + pConfig->AppInfoTableDyn = dynamicArrayCreate(SF_APPID_DYNAMIC_MIN); + + snprintf(filepath, sizeof(filepath), "%s/odp/%s", path, APP_MAPPING_FILE); + + tableFile = fopen(filepath, "r"); + if (tableFile == nullptr) + { + ErrorMessage("Could not open RnaAppMapping Table file: %s\n", filepath); + return; + } + + DebugFormat(DEBUG_INSPECTOR, "AppInfo read from %s\n", filepath); + + while (fgets(buf, sizeof(buf), tableFile)) + { + token = strtok_r(buf, CONF_SEPARATORS, &context); + if (!token) + { + ErrorMessage("Could not read id for Rna Id\n"); + continue; + } + + appId = strtol(token, nullptr, 10); + + token = strtok_r(nullptr, CONF_SEPARATORS, &context); + if (!token) + { + ErrorMessage("Could not read appName. Line %s\n", buf); + continue; + } + + appName = snort_strdup(token); + token = strtok_r(nullptr, CONF_SEPARATORS, &context); + if (!token) + { + ErrorMessage("Could not read service id for Rna Id\n"); + snort_free(appName); + continue; + } + + serviceId = strtol(token, nullptr, 10); + + token = strtok_r(nullptr, CONF_SEPARATORS, &context); + if (!token) + { + ErrorMessage("Could not read client id for Rna Id\n"); + snort_free(appName); + continue; + } + + clientId = strtol(token, nullptr, 10); + + token = strtok_r(nullptr, CONF_SEPARATORS, &context); + if (!token) + { + ErrorMessage("Could not read payload id for Rna Id\n"); + snort_free(appName); + continue; + } + + payloadId = strtol(token, nullptr, 10); + + /* snort service key, if it exists */ + token = strtok_r(nullptr, CONF_SEPARATORS, &context); + if (token) + snortName = snort_strdup(token); + + entry = static_cast(snort_calloc(sizeof(AppInfoTableEntry))); + entry->next = pConfig->AppInfoList; + pConfig->AppInfoList = entry; + entry->snortId = AddProtocolReference(snortName); + snort_free(snortName); + snortName = nullptr; + entry->appName = appName; + entry->appId = appId; + entry->serviceId = serviceId; + entry->clientId = clientId; + entry->payloadId = payloadId; + entry->priority = APP_PRIORITY_DEFAULT; + + if ((appId = getAppIdStaticIndex(entry->appId))) + pConfig->AppInfoTable[appId] = entry; + if ((appId = getAppIdStaticIndex(entry->serviceId))) + pConfig->AppInfoTableByService[appId] = entry; + if ((appId = getAppIdStaticIndex(entry->clientId))) + pConfig->AppInfoTableByClient[appId] = entry; + if ((appId = getAppIdStaticIndex(entry->payloadId))) + pConfig->AppInfoTableByPayload[appId] = entry; + + if (!pConfig->AppNameHash) + { + pConfig->AppNameHash = appNameHashInit(); + } + appNameHashAdd(pConfig->AppNameHash, appName, entry); + } + fclose(tableFile); + + /* Configuration defaults. */ + pAppidActiveConfig->mod_config->rtmp_max_packets = 15; + pAppidActiveConfig->mod_config->mdns_user_reporting = 1; + pAppidActiveConfig->mod_config->dns_host_reporting = 1; + pAppidActiveConfig->mod_config->max_tp_flow_depth = 5; + pAppidActiveConfig->mod_config->http2_detection_enabled = 0; + + snprintf(filepath, sizeof(filepath), "%s/odp/%s", path, APP_CONFIG_FILE); + appIdConfLoad (filepath); + snprintf(filepath, sizeof(filepath), "%s/custom/%s", path, USR_CONFIG_FILE); + appIdConfLoad (filepath); +} + +void appInfoTableFini(AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry; + + while ((entry = pConfig->AppInfoList)) + { + pConfig->AppInfoList = entry->next; + snort_free(entry->appName); + snort_free(entry); + } + + dynamicArrayDestroy(pConfig->AppInfoTableDyn); + pConfig->AppInfoTableDyn = nullptr; + + appNameHashFini(pConfig->AppNameHash); +} + +void appInfoTableDump(AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry; + AppId appId; + + ErrorMessage("Cisco provided detectors:\n"); + for (appId = 1; appId < SF_APPID_MAX; appId++) + { + entry = pConfig->AppInfoTable[appId]; + if (entry) + ErrorMessage("%s\t%d\t%s\n", entry->appName, entry->appId, (entry->flags & + APPINFO_FLAG_ACTIVE) ? "active" : "inactive"); + } + ErrorMessage("User provided detectors:\n"); + for (entry = (decltype(entry))dynamicArrayGetFirst(pConfig->AppInfoTableDyn); entry; entry = + (decltype(entry))dynamicArrayGetNext(pConfig->AppInfoTableDyn)) + { + ErrorMessage("%s\t%d\t%s\n", entry->appName, entry->appId, (entry->flags & + APPINFO_FLAG_ACTIVE) ? "active" : "inactive"); + } +} + +AppId appGetAppFromServiceId(uint32_t appId, AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry; + AppId tmp; + + if ((tmp = getAppIdStaticIndex(appId))) + entry = pConfig->AppInfoTableByService[tmp]; + else + entry = dynamicArrayGetIndex(pConfig->AppInfoTableDyn, appId); + + return entry ? entry->appId : APP_ID_NONE; +} + +AppId appGetAppFromClientId(uint32_t appId, AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry; + AppId tmp; + + if ((tmp = getAppIdStaticIndex(appId))) + entry = pConfig->AppInfoTableByClient[tmp]; + else + entry = dynamicArrayGetIndex(pConfig->AppInfoTableDyn, appId); + + return entry ? entry->appId : APP_ID_NONE; +} + +AppId appGetAppFromPayloadId(uint32_t appId, AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry; + AppId tmp; + + if ((tmp = getAppIdStaticIndex(appId))) + entry = pConfig->AppInfoTableByPayload[tmp]; + else + entry = dynamicArrayGetIndex(pConfig->AppInfoTableDyn, appId); + + return entry ? entry->appId : APP_ID_NONE; +} + +const char* appGetAppName(int32_t appId) +{ + AppInfoTableEntry* entry; + AppIdConfig* pConfig = pAppidActiveConfig; + AppId tmp; + + if ((tmp = getAppIdStaticIndex(appId))) + entry = pConfig->AppInfoTable[tmp]; + else + entry = dynamicArrayGetIndex(pConfig->AppInfoTableDyn, appId); + + return entry ? entry->appName : nullptr; +} + +int32_t appGetAppId(const char* appName) +{ + AppInfoTableEntry* entry; + AppIdConfig* pConfig = pAppidActiveConfig; + + entry = (decltype(entry))appNameHashFind(pConfig->AppNameHash, appName); + return entry ? entry->appId : 0; +} + +void appInfoSetActive(AppId appId, bool active) +{ + AppInfoTableEntry* entry = nullptr; + AppIdConfig* pConfig = pAppidActiveConfig; + AppId tmp; + + if (appId == APP_ID_NONE) + return; + + if ((tmp = getAppIdStaticIndex(appId))) + entry = pConfig->AppInfoTable[tmp]; + else + entry = dynamicArrayGetIndex(pConfig->AppInfoTableDyn, appId); + + if (entry) + { + if (active) + entry->flags |= APPINFO_FLAG_ACTIVE; + else + entry->flags &= ~APPINFO_FLAG_ACTIVE; + } + else + { + ErrorMessage("AppInfo: AppId %d is UNKNOWN\n", appId); + } +} + +static void appIdConfLoad(const char* path) +{ + FILE* config_file; + char* token; + char buf[1024]; + char referred_app_list[4096]; + int referred_app_index; + char* conf_type; + char* conf_key; + char* conf_val; + unsigned line = 0; + AppIdConfig* pConfig = pAppidActiveConfig; + int max_tp_flow_depth; + char* context; + + config_file = fopen(path, "r"); + if (config_file == nullptr) + return; + + DebugFormat(DEBUG_INSPECTOR, "Loading configuration file %s\n", path); + + while (fgets(buf, sizeof(buf), config_file) != nullptr) + { + line++; + token = strtok_r(buf, CONF_SEPARATORS, &context); + if (token == nullptr) + { + ErrorMessage("Could not read configuration at line %s:%u\n", path, line); + continue; + } + conf_type = token; + + token = strtok_r(nullptr, CONF_SEPARATORS, &context); + if (token == nullptr) + { + ErrorMessage("Could not read configuration value at line %s:%u\n", path, line); + continue; + } + conf_key = token; + + token = strtok_r(nullptr, CONF_SEPARATORS, &context); + if (token == nullptr) + { + ErrorMessage("Could not read configuration value at line %s:%u\n", path, line); + continue; + } + conf_val = token; + + /* APPID configurations are for anything else - currently we only have ssl_reinspect */ + if (!(strcasecmp(conf_type, "appid"))) + { + if (!(strcasecmp(conf_key, "max_tp_flow_depth"))) + { + max_tp_flow_depth = atoi(conf_val); + if (max_tp_flow_depth < MIN_MAX_TP_FLOW_DEPTH || max_tp_flow_depth > + MAX_MAX_TP_FLOW_DEPTH) + { + DebugFormat(DEBUG_INSPECTOR, + "AppId: invalid max_tp_flow_depth %d, must be between %d and %d\n.", + max_tp_flow_depth, MIN_MAX_TP_FLOW_DEPTH, MAX_MAX_TP_FLOW_DEPTH); + } + else + { + DebugFormat(DEBUG_INSPECTOR, + "AppId: setting max thirdparty inspection flow depth to %d packets.\n", + max_tp_flow_depth); + pAppidActiveConfig->mod_config->max_tp_flow_depth = max_tp_flow_depth; + } + } + else if (!(strcasecmp(conf_key, "tp_allow_probes"))) + { + if (!(strcasecmp(conf_val, "enabled"))) + { + DebugMessage(DEBUG_INSPECTOR, + "AppId: TCP probes will be analyzed by NAVL.\n"); + + pAppidActiveConfig->mod_config->tp_allow_probes = 1; + } + } + else if (!(strcasecmp(conf_key, "tp_client_app"))) + { + DebugFormat(DEBUG_INSPECTOR, + "AppId: if thirdparty reports app %d, we will use it as a client.\n", + atoi(conf_val)); + appInfoEntryFlagSet(atoi(conf_val), APPINFO_FLAG_TP_CLIENT, pConfig); + } + else if (!(strcasecmp(conf_key, "ssl_reinspect"))) + { + DebugFormat(DEBUG_INSPECTOR, + "AppId: adding app %d to list of SSL apps that get more granular inspection.\n", + atoi(conf_val)); + appInfoEntryFlagSet(atoi(conf_val), APPINFO_FLAG_SSL_INSPECT, pConfig); + } + else if (!(strcasecmp(conf_key, "disable_safe_search"))) + { + if (!(strcasecmp(conf_val, "disabled"))) + { + DEBUG_WRAP(DebugMessage(DEBUG_INSPECTOR, + "AppId: disabling safe search enforcement.\n"); ); + pAppidActiveConfig->mod_config->disable_safe_search = 1; + } + } + else if (!(strcasecmp(conf_key, "ssl_squelch"))) + { + DebugFormat(DEBUG_INSPECTOR, + "AppId: adding app %d to list of SSL apps that may open a second SSL connection.\n", + atoi(conf_val)); + appInfoEntryFlagSet(atoi(conf_val), APPINFO_FLAG_SSL_SQUELCH, pConfig); + } + else if (!(strcasecmp(conf_key, "defer_to_thirdparty"))) + { + DebugFormat(DEBUG_INSPECTOR, + "AppId: adding app %d to list of apps where we should take thirdparty ID over the NDE's.\n", + atoi(conf_val)); + appInfoEntryFlagSet(atoi(conf_val), APPINFO_FLAG_DEFER, pConfig); + } + else if (!(strcasecmp(conf_key, "defer_payload_to_thirdparty"))) + { + DebugFormat(DEBUG_INSPECTOR, + "AppId: adding app %d to list of apps where we should take " + "thirdparty payload ID over the NDE's.\n", + atoi(conf_val)); + appInfoEntryFlagSet(atoi(conf_val), APPINFO_FLAG_DEFER_PAYLOAD, pConfig); + } + else if (!(strcasecmp(conf_key, "chp_userid"))) + { + if (!(strcasecmp(conf_val, "disabled"))) + { + DebugMessage(DEBUG_INSPECTOR, + "AppId: HTTP UserID collection disabled.\n"); + pAppidActiveConfig->mod_config->chp_userid_disabled = 1; + continue; + } + } + else if (!(strcasecmp(conf_key, "chp_body_collection"))) + { + if (!(strcasecmp(conf_val, "disabled"))) + { + DebugMessage(DEBUG_INSPECTOR, + "AppId: HTTP Body header reading disabled.\n"); + pAppidActiveConfig->mod_config->chp_body_collection_disabled = 1; + continue; + } + } + else if (!(strcasecmp(conf_key, "chp_fflow"))) + { + if (!(strcasecmp(conf_val, "disabled"))) + { + DEBUG_WRAP(DebugMessage(DEBUG_INSPECTOR, + "AppId: HTTP future flow creation disabled.\n"); ); + pAppidActiveConfig->mod_config->chp_fflow_disabled = 1; + continue; + } + } + else if (!(strcasecmp(conf_key, "ftp_userid"))) + { + if (!(strcasecmp(conf_val, "disabled"))) + { + DEBUG_WRAP(DebugMessage(DEBUG_INSPECTOR, "AppId: FTP userID disabled.\n"); ); + pAppidActiveConfig->mod_config->ftp_userid_disabled = 1; + continue; + } + } + /* App Priority bit set*/ + else if (!(strcasecmp(conf_key, "app_priority"))) + { + int temp_appid; + temp_appid = strtol(conf_val, nullptr, 10); + token = strtok_r(nullptr, CONF_SEPARATORS, &context); + if (token == nullptr) + { + ErrorMessage("Could not read app_priority at line %u\n", line); + continue; + } + conf_val = token; + uint8_t temp_val; + temp_val = strtol(conf_val, nullptr, 10); + appInfoEntryPrioritySet (temp_appid, temp_val, pConfig); + DebugFormat(DEBUG_INSPECTOR,"AppId: %d Setting priority bit %d .\n", + temp_appid, temp_val); + } + else if (!(strcasecmp(conf_key, "referred_appId"))) + { + if (!(strcasecmp(conf_val, "disabled"))) + { + pAppidActiveConfig->mod_config->referred_appId_disabled = 1; + continue; + } + else if (!pAppidActiveConfig->mod_config->referred_appId_disabled) + { + referred_app_index=0; + referred_app_index += sprintf(referred_app_list, "%d ", atoi(conf_val)); + appInfoEntryFlagSet(atoi(conf_val), APPINFO_FLAG_REFERRED, pConfig); + + while ((token = strtok_r(nullptr, CONF_SEPARATORS, &context)) != nullptr) + { + referred_app_index += sprintf(referred_app_list+referred_app_index, "%d ", + atoi(token)); + appInfoEntryFlagSet(atoi(token), APPINFO_FLAG_REFERRED, pConfig); + } + DebugFormat(DEBUG_INSPECTOR, + "AppId: adding appIds to list of referred web apps: %s\n", + referred_app_list); + } + } + else if (!(strcasecmp(conf_key, "rtmp_max_packets"))) + { + pAppidActiveConfig->mod_config->rtmp_max_packets = atoi(conf_val); + } + else if (!(strcasecmp(conf_key, "mdns_user_report"))) + { + pAppidActiveConfig->mod_config->mdns_user_reporting = atoi(conf_val); + } + else if (!(strcasecmp(conf_key, "dns_host_report"))) + { + pAppidActiveConfig->mod_config->dns_host_reporting = atoi(conf_val); + } + else if (!(strcasecmp(conf_key, "chp_body_max_bytes"))) + { + pAppidActiveConfig->mod_config->chp_body_collection_max = atoi(conf_val); + } + else if (!(strcasecmp(conf_key, "ignore_thirdparty_appid"))) + { + LogMessage("AppId: adding app %d to list of ignore thirdparty apps.\n", atoi( + conf_val)); + appInfoEntryFlagSet(atoi(conf_val), APPINFO_FLAG_IGNORE, pConfig); + } + else if (!(strcasecmp(conf_key, "http2_detection"))) + { + // This option will control our own HTTP/2 detection. We can + // still be told externally, though, that it's HTTP/2 (either + // from HTTP Inspect or 3rd Party). This is intended to be + // used to ask AppID to detect unencrypted HTTP/2 on non-std + // ports. + if (!(strcasecmp(conf_val, "disabled"))) + { + LogMessage("AppId: disabling internal HTTP/2 detection.\n"); + pAppidActiveConfig->mod_config->http2_detection_enabled = 0; + } + else if (!(strcasecmp(conf_val, "enabled"))) + { + LogMessage("AppId: enabling internal HTTP/2 detection.\n"); + pAppidActiveConfig->mod_config->http2_detection_enabled = 1; + } + else + { + LogMessage("AppId: ignoring invalid option for http2_detection: %s\n", + conf_val); + } + } + } + } + fclose(config_file); +} + diff --git a/src/network_inspectors/appid/app_info_table.h b/src/network_inspectors/appid/app_info_table.h new file mode 100644 index 000000000..62ae1c836 --- /dev/null +++ b/src/network_inspectors/appid/app_info_table.h @@ -0,0 +1,116 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// app_info_table.h author Sourcefire Inc. + +#ifndef APP_INFO_TABLE_H +#define APP_INFO_TABLE_H + +#include + +#include "appid_api.h" +#include "appid_config.h" + +#define APP_PRIORITY_DEFAULT 2 + +struct RNAClientAppModule; +struct RNAServiceElement; + +enum AppInfoFlags +{ + APPINFO_FLAG_SERVICE_ADDITIONAL = (1<<0), + APPINFO_FLAG_SERVICE_UDP_REVERSED = (1<<1), + APPINFO_FLAG_CLIENT_ADDITIONAL = (1<<2), + APPINFO_FLAG_CLIENT_USER = (1<<3), + APPINFO_FLAG_ACTIVE = (1<<4), + APPINFO_FLAG_SSL_INSPECT = (1<<5), + APPINFO_FLAG_REFERRED = (1<<6), + APPINFO_FLAG_DEFER = (1<<7), + + APPINFO_FLAG_IGNORE = (1<<8), + APPINFO_FLAG_SSL_SQUELCH = (1<<9), + APPINFO_FLAG_PERSISTENT = (1<<10), + APPINFO_FLAG_TP_CLIENT = (1<<11), + APPINFO_FLAG_DEFER_PAYLOAD = (1<<12), + APPINFO_FLAG_SEARCH_ENGINE = (1<<13), + APPINFO_FLAG_SUPPORTED_SEARCH = (1<<14) +}; + +struct AppInfoTableEntry +{ + AppInfoTableEntry* next; + AppId appId; + uint32_t serviceId; + uint32_t clientId; + uint32_t payloadId; + int16_t snortId; + uint32_t flags; + const RNAClientAppModule* clntValidator; + const RNAServiceElement* svrValidator; + uint32_t priority; + char* appName; +}; + +void appInfoTableInit(const char* path, AppIdConfig*); +void appInfoTableFini(AppIdConfig*); + +AppInfoTableEntry* appInfoEntryGet(AppId, const AppIdConfig*); +AppInfoTableEntry* appInfoEntryCreate(const char* appName, AppIdConfig*); +AppId appGetSnortIdFromAppId(AppId); +void AppIdDumpStats(int exit_flag); +void appInfoTableDump(AppIdConfig*); +void appInfoSetActive(AppId, bool active); +const char* appGetAppName(int32_t appId); +int32_t appGetAppId(const char* appName); + +inline void appInfoEntryFlagSet(AppId appId, unsigned flags, AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry = appInfoEntryGet(appId, pConfig); + if ( entry ) + entry->flags |= flags; +} + +inline void appInfoEntryFlagClear(AppId appId, unsigned flags, AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry = appInfoEntryGet(appId, pConfig); + if ( entry ) + entry->flags &= (~flags); +} + +inline unsigned appInfoEntryFlagGet(AppId app_id, unsigned flags, AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry = appInfoEntryGet(app_id, pConfig); + return entry ? entry->flags & flags : 0; +} + +inline void appInfoEntryPrioritySet(AppId appId, unsigned priority, AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry = appInfoEntryGet(appId, pConfig); + if ( entry ) + entry->priority |= priority; +} + +inline unsigned appInfoEntryPriorityGet(AppId app_id, AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry = appInfoEntryGet(app_id, pConfig); + return entry ? entry->priority : 0; +} + +#endif + diff --git a/src/network_inspectors/appid/appid.h b/src/network_inspectors/appid/appid.h new file mode 100644 index 000000000..ec7266c7f --- /dev/null +++ b/src/network_inspectors/appid/appid.h @@ -0,0 +1,60 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid.h author Sourcefire Inc. + +#ifndef APPID_H +#define APPID_H + +#include "thirdparty_appid_types.h" + +#define SF_APPID_MAX 40000 +#define SF_APPID_BUILDIN_MAX 30000 +#define APPID_MAX_PRIORITY 3 +#define SF_APPID_CSD_MIN 1000000 +#define SF_APPID_DYNAMIC_MIN 2000000 +#define NUMBER_OF_PTYPES 9 + +// FIXIT-H J stuff like this needs to be defined in config.h +#define RESPONSE_CODE_PACKET_THRESHHOLD 0 + +// FIXIT-L J properly scoped enum would be more appropriate +enum APPID_SESSION_DIRECTION +{ + APP_ID_FROM_INITIATOR, + APP_ID_FROM_RESPONDER, + APP_ID_APPID_SESSION_DIRECTION_MAX // Maximum value of a direction (must be last in the list) +}; + +// FIXIT-M J this doesn't seem appropriate for an enum +enum SERVICE_HOST_INFO_CODE +{ + SERVICE_HOST_INFO_NETBIOS_NAME = 1 +}; + +// FIXIT-M J this should go in a separate header +#define DHCP_OPTION55_LEN_MAX 255 + +// FIXIT-M J this should go in a separate header +#define FINGERPRINT_UDP_FLAGS_XENIX 0x00000800 +#define FINGERPRINT_UDP_FLAGS_NT 0x00001000 +#define FINGERPRINT_UDP_FLAGS_MASK (FINGERPRINT_UDP_FLAGS_XENIX | FINGERPRINT_UDP_FLAGS_NT) + +#endif + diff --git a/src/network_inspectors/appid/appid_api.cc b/src/network_inspectors/appid/appid_api.cc new file mode 100644 index 000000000..d4dd79d72 --- /dev/null +++ b/src/network_inspectors/appid/appid_api.cc @@ -0,0 +1,695 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_api.cc author Sourcefire Inc. + +#include "appid_api.h" + +#include "appid.h" +#include "service_plugins/service_base.h" +#include "app_info_table.h" +#include "fw_appid.h" + +#include "utils/util.h" + +#define SSL_WHITELIST_PKT_LIMIT 20 + +AppId getServiceAppId(AppIdData* appIdData) +{ + if (appIdData) + return pickServiceAppId(appIdData); + + return APP_ID_NONE; +} + +AppId getOnlyServiceAppId(AppIdData* appIdData) +{ + if (appIdData) + return pickOnlyServiceAppId(appIdData); + + return APP_ID_NONE; +} + +AppId getMiscAppId(AppIdData* appIdData) +{ + if (appIdData) + return pickMiscAppId(appIdData); + + return APP_ID_NONE; +} + +AppId getClientAppId(AppIdData* appIdData) +{ + if (appIdData) + return pickClientAppId(appIdData); + + return APP_ID_NONE; +} + +AppId getPayloadAppId(AppIdData* appIdData) +{ + if (appIdData) + return pickPayloadId(appIdData); + + return APP_ID_NONE; +} + +AppId getReferredAppId(AppIdData* appIdData) +{ + if (appIdData) + return pickReferredPayloadId(appIdData); + + return APP_ID_NONE; +} + +AppId getFwServiceAppId(AppIdData* appIdData) +{ + if (appIdData) + return fwPickServiceAppId(appIdData); + + return APP_ID_NONE; +} + +AppId getFwMiscAppId(AppIdData* appIdData) +{ + if (appIdData) + return fwPickMiscAppId(appIdData); + + return APP_ID_NONE; +} + +AppId getFwClientAppId(AppIdData* appIdData) +{ + if (appIdData) + return fwPickClientAppId(appIdData); + + return APP_ID_NONE; +} + +AppId getFwPayloadAppId(AppIdData* appIdData) +{ + if (appIdData) + return fwPickPayloadAppId(appIdData); + + return APP_ID_NONE; +} + +AppId getFwReferredAppId(AppIdData* appIdData) +{ + if (appIdData) + return fwPickReferredPayloadAppId(appIdData); + + return APP_ID_NONE; +} + +bool isSessionSslDecrypted(AppIdData* appIdData) +{ + if (appIdData) + return isFwSessionSslDecrypted(appIdData); + + return false; +} + +AppIdData* getAppIdData(void* lwssn) +{ + AppIdData* appIdData = (AppIdData*)(((Flow*)lwssn)->get_application_data(AppIdData::flow_id)); + + return (appIdData && appIdData->common.fsf_type.flow_type == APPID_SESSION_TYPE_NORMAL) ? + appIdData : nullptr; +} + +bool IsAppIdInspectingSession(AppIdData* appIdSession) +{ + if (appIdSession && appIdSession->common.fsf_type.flow_type == APPID_SESSION_TYPE_NORMAL) + { + if (appIdSession->rnaServiceState != RNA_STATE_FINISHED || + !TPIsAppIdDone(appIdSession->tpsession) || + getAppIdFlag(appIdSession, APPID_SESSION_HTTP_SESSION | APPID_SESSION_CONTINUE) || + (getAppIdFlag(appIdSession, APPID_SESSION_ENCRYPTED) && + (getAppIdFlag(appIdSession, APPID_SESSION_DECRYPTED) || + appIdSession->session_packet_count < SSL_WHITELIST_PKT_LIMIT))) + { + return true; + } + if (appIdSession->rnaClientState != RNA_STATE_FINISHED && + (!getAppIdFlag(appIdSession, APPID_SESSION_CLIENT_DETECTED) || + (appIdSession->rnaServiceState != RNA_STATE_STATEFUL && getAppIdFlag(appIdSession, + APPID_SESSION_CLIENT_GETS_SERVER_PACKETS)))) + { + return true; + } + if (appIdSession->tpAppId == APP_ID_SSH && appIdSession->payloadAppId != APP_ID_SFTP && + appIdSession->session_packet_count < MAX_SFTP_PACKET_COUNT) + { + return true; + } + } + return false; +} + +char* getUserName(AppIdData* appIdData, AppId* service, bool* isLoginSuccessful) +{ + char* userName = nullptr; + if (appIdData) + { + userName = appIdData->username; + *service = appIdData->usernameService; + *isLoginSuccessful = getAppIdFlag(appIdData, APPID_SESSION_LOGIN_SUCCEEDED) ? true : false; + appIdData->username = nullptr; //transfer ownership to caller. + return userName; + } + return nullptr; +} + +bool isAppIdAvailable(AppIdData* appIdData) +{ + if (appIdData) + { + if (getAppIdFlag(appIdData, APPID_SESSION_NO_TPI)) + return true; + return TPIsAppIdAvailable(appIdData->tpsession); + } + return false; +} + +char* getClientVersion(AppIdData* appIdData) +{ + return appIdData ? appIdData->clientVersion : nullptr; +} + +uint64_t getAppIdSessionAttribute(AppIdData* appIdData, uint64_t flags) +{ + return appIdData ? getAppIdFlag(appIdData, flags) : 0; +} + +APPID_FLOW_TYPE getFlowType(AppIdData* appIdData) +{ + return appIdData ? appIdData->common.fsf_type.flow_type : APPID_FLOW_TYPE_IGNORE; +} + +void getServiceInfo(AppIdData* appIdData, char** serviceVendor, char** serviceVersion, + RNAServiceSubtype** serviceSubtype) +{ + if (appIdData) + { + *serviceVendor = appIdData->serviceVendor; + *serviceVersion = appIdData->serviceVersion; + *serviceSubtype = appIdData->subtype; + } + else + { + *serviceVendor = nullptr; + *serviceVersion = nullptr; + *serviceSubtype = nullptr; + } +} + +short getServicePort(AppIdData* appIdData) +{ + if (appIdData) + return appIdData->service_port; + return 0; +} + +char* getHttpUserAgent(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->useragent; + return nullptr; +} + +char* getHttpHost(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->host; + return nullptr; +} + +char* getHttpUrl(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->url; + return nullptr; +} + +char* getHttpReferer(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->referer; + return nullptr; +} + +char* getHttpNewUrl(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->new_field[REQ_URI_FID]; + return nullptr; +} + +char* getHttpUri(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->uri; + return nullptr; +} + +char* getHttpResponseCode(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->response_code; + return nullptr; +} + +char* getHttpCookie(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->cookie; + return nullptr; +} + +char* getHttpNewCookie(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->new_field[REQ_COOKIE_FID]; + return nullptr; +} + +char* getHttpNewField(AppIdData* appIdData, HTTP_FIELD_ID fieldId) +{ + if (appIdData && appIdData->hsession && fieldId >= 0 && fieldId <= HTTP_FIELD_MAX) + return appIdData->hsession->new_field[fieldId]; + return nullptr; +} + +void freeHttpNewField(AppIdData* appIdData, HTTP_FIELD_ID fieldId) +{ + if (appIdData && appIdData->hsession && fieldId >= 0 && fieldId <= HTTP_FIELD_MAX && + nullptr != appIdData->hsession->new_field[fieldId]) + { + snort_free(appIdData->hsession->new_field[fieldId]); + appIdData->hsession->new_field[fieldId] = nullptr; + } +} + +char* getHttpContentType(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->content_type; + return nullptr; +} + +char* getHttpLocation(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->location; + return nullptr; +} + +char* getHttpBody(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->body; + return nullptr; +} + +char* getHttpReqBody(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->req_body; + return nullptr; +} + +uint16_t getHttpUriOffset(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->uriOffset; + return 0; +} + +uint16_t getHttpUriEndOffset(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->uriEndOffset; + return 0; +} + +uint16_t getHttpCookieOffset(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->cookieOffset; + return 0; +} + +uint16_t getHttpCookieEndOffset(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->cookieEndOffset; + return 0; +} + +SEARCH_SUPPORT_TYPE getHttpSearch(AppIdData* appIdData) +{ + if (appIdData) + return (appIdData->search_support_type != SEARCH_SUPPORT_TYPE_UNKNOWN) ? + appIdData->search_support_type : NOT_A_SEARCH_ENGINE; + return NOT_A_SEARCH_ENGINE; +} + +// FIXIT used to be sfaddr_t +sfip_t* getHttpXffAddr(AppIdData* appIdData) +{ + if (appIdData && appIdData->hsession) + return appIdData->hsession->xffAddr; + return nullptr; +} + +char* getTlsHost(AppIdData* appIdData) +{ + if (appIdData && appIdData->tsession) + return appIdData->tsession->tls_host; + return nullptr; +} + +AppId getPorServiceAppId(AppIdData* appIdData) +{ + if (appIdData) + return appIdData->portServiceAppId; + return APP_ID_NONE; +} + +// FIXIT used to be sfaddr_t +sfip_t* getServiceIp(AppIdData* appIdData) +{ + if (appIdData) + return &appIdData->service_ip; + return nullptr; +} + +// FIXIT used to be sfaddr_t +sfip_t* getInitiatorIp(AppIdData* appIdData) +{ + return appIdData ? &appIdData->common.initiator_ip : nullptr; +} + +DhcpFPData* getDhcpFpData(AppIdData* appIdData) +{ + if (appIdData && getAppIdFlag(appIdData, APPID_SESSION_HAS_DHCP_FP)) + return static_cast( + AppIdFlowdataRemove(appIdData, APPID_SESSION_DATA_DHCP_FP_DATA)); + + return nullptr; +} + +void freeDhcpFpData(AppIdData* appIdData, DhcpFPData* data) +{ + if (appIdData) + { + clearAppIdFlag(appIdData, APPID_SESSION_HAS_DHCP_FP); + AppIdFreeDhcpData(data); + } +} + +DHCPInfo* getDhcpInfo(AppIdData* appIdData) +{ + if (appIdData && getAppIdFlag(appIdData, APPID_SESSION_HAS_DHCP_INFO)) + return static_cast( + AppIdFlowdataRemove(appIdData, APPID_SESSION_DATA_DHCP_INFO)); + + return nullptr; +} + +void freeDhcpInfo(AppIdData* appIdData, DHCPInfo* data) +{ + if (appIdData) + { + clearAppIdFlag(appIdData, APPID_SESSION_HAS_DHCP_INFO); + AppIdFreeDhcpInfo(data); + } +} + +FpSMBData* getSmbFpData(AppIdData* appIdData) +{ + if (appIdData && getAppIdFlag(appIdData, APPID_SESSION_HAS_SMB_INFO)) + return static_cast( + AppIdFlowdataRemove(appIdData, APPID_SESSION_DATA_SMB_DATA)); + + return nullptr; +} + +void freeSmbFpData(AppIdData* appIdData, FpSMBData* data) +{ + if (appIdData) + { + clearAppIdFlag(appIdData, APPID_SESSION_HAS_SMB_INFO); + AppIdFreeSMBData(data); + } +} + +char* getNetbiosName(AppIdData* appIdData) +{ + if (appIdData) + { + char* netbiosName = appIdData->netbios_name; + appIdData->netbios_name = nullptr; //transfer ownership to caller. + return netbiosName; + } + return nullptr; +} + +#define APPID_HA_FLAGS_APP (1<<0) +#define APPID_HA_FLAGS_TP_DONE (1<<1) +#define APPID_HA_FLAGS_SVC_DONE (1<<2) +#define APPID_HA_FLAGS_HTTP (1<<3) + +uint32_t produceHAState(void* lwssn, uint8_t* buf) +{ + AppIdSessionHA* appHA = (AppIdSessionHA*)buf; + AppIdData* appIdData = (AppIdData*)(((Flow*)lwssn)->get_application_data(AppIdData::flow_id)); + + // FIXIT - getFlowType should be a class member + if (appIdData && getFlowType(appIdData) != APPID_FLOW_TYPE_NORMAL) + appIdData = nullptr; + if (appIdData) + { + appHA->flags = APPID_HA_FLAGS_APP; + if (TPIsAppIdAvailable(appIdData->tpsession)) + appHA->flags |= APPID_HA_FLAGS_TP_DONE; + if (getAppIdFlag(appIdData, APPID_SESSION_SERVICE_DETECTED)) + appHA->flags |= APPID_HA_FLAGS_SVC_DONE; + if (getAppIdFlag(appIdData, APPID_SESSION_HTTP_SESSION)) + appHA->flags |= APPID_HA_FLAGS_HTTP; + appHA->appId[0] = appIdData->tpAppId; + appHA->appId[1] = appIdData->serviceAppId; + appHA->appId[2] = appIdData->ClientServiceAppId; + appHA->appId[3] = appIdData->portServiceAppId; + appHA->appId[4] = appIdData->payloadAppId; + appHA->appId[5] = appIdData->tpPayloadAppId; + appHA->appId[6] = appIdData->ClientAppId; + appHA->appId[7] = appIdData->miscAppId; + } + else + { + memset(appHA->appId, 0, sizeof(appHA->appId)); + } + return sizeof(*appHA); +} + +// FIXIT last arg used to be sfaddr_t +uint32_t consumeHAState(void* lwssn, const uint8_t* buf, uint8_t, IpProtocol proto, + sfip_t* ip) +{ + AppIdSessionHA* appHA = (AppIdSessionHA*)buf; + if (appHA->flags & APPID_HA_FLAGS_APP) + { + AppIdData* appIdData = (AppIdData*)(((Flow*)lwssn)->get_application_data( + AppIdData::flow_id)); + + if (!appIdData) + { + appIdData = appSharedDataAlloc(proto, ip); + ((Flow*)lwssn)->set_application_data(appIdData); + if (appIdData->serviceAppId == APP_ID_FTP_CONTROL) + { + setAppIdFlag(appIdData, APPID_SESSION_CLIENT_DETECTED | + APPID_SESSION_NOT_A_SERVICE | APPID_SESSION_SERVICE_DETECTED); + if (!AddFTPServiceState(appIdData)) + { + setAppIdFlag(appIdData, APPID_SESSION_CONTINUE); + } + appIdData->rnaServiceState = RNA_STATE_STATEFUL; + } + else + appIdData->rnaServiceState = RNA_STATE_FINISHED; + appIdData->rnaClientState = RNA_STATE_FINISHED; + if (thirdparty_appid_module) + thirdparty_appid_module->session_state_set(appIdData->tpsession, TP_STATE_HA); + } + + if (appHA->flags & APPID_HA_FLAGS_TP_DONE && thirdparty_appid_module) + { + thirdparty_appid_module->session_state_set(appIdData->tpsession, TP_STATE_TERMINATED); + setAppIdFlag(appIdData, APPID_SESSION_NO_TPI); + } + if (appHA->flags & APPID_HA_FLAGS_SVC_DONE) + setAppIdFlag(appIdData, APPID_SESSION_SERVICE_DETECTED); + if (appHA->flags & APPID_HA_FLAGS_HTTP) + setAppIdFlag(appIdData, APPID_SESSION_HTTP_SESSION); + + appIdData->tpAppId = appHA->appId[0]; + appIdData->serviceAppId = appHA->appId[1]; + appIdData->ClientServiceAppId = appHA->appId[2]; + appIdData->portServiceAppId = appHA->appId[3]; + appIdData->payloadAppId = appHA->appId[4]; + appIdData->tpPayloadAppId = appHA->appId[5]; + appIdData->ClientAppId = appHA->appId[6]; + appIdData->miscAppId = appHA->appId[7]; + } + return sizeof(*appHA); +} + +char* getDNSQuery(AppIdData* appIdData, uint8_t* query_len) +{ + if (appIdData && appIdData->dsession) + { + if (query_len) + { + if (appIdData->dsession->host) + *query_len = appIdData->dsession->host_len; + + else + *query_len = 0; + } + + return appIdData->dsession->host; + } + if (query_len) + *query_len = 0; + return nullptr; +} + +uint16_t getDNSQueryoffset(AppIdData* appIdData) +{ + if (appIdData && appIdData->dsession) + return appIdData->dsession->host_offset; + return 0; +} + +uint16_t getDNSRecordType(AppIdData* appIdData) +{ + if (appIdData && appIdData->dsession) + return appIdData->dsession->record_type; + return 0; +} + +uint8_t getDNSResponseType(AppIdData* appIdData) +{ + if (appIdData && appIdData->dsession) + return appIdData->dsession->response_type; + return 0; +} + +uint32_t getDNSTTL(AppIdData* appIdData) +{ + if (appIdData && appIdData->dsession) + return appIdData->dsession->ttl; + return 0; +} + +static AppIdApi appIdDispatchTable = +{ + appGetAppName, + appGetAppId, + + getServiceAppId, + getPorServiceAppId, + getOnlyServiceAppId, + getMiscAppId, + getClientAppId, + getPayloadAppId, + getReferredAppId, + getFwServiceAppId, + getFwMiscAppId, + getFwClientAppId, + getFwPayloadAppId, + getFwReferredAppId, + + isSessionSslDecrypted, + IsAppIdInspectingSession, + isAppIdAvailable, + + getUserName, + getClientVersion, + + getAppIdSessionAttribute, + + getFlowType, + getServiceInfo, + getServicePort, + getServiceIp, + getInitiatorIp, + + getHttpUserAgent, + getHttpHost, + getHttpUrl, + getHttpReferer, + getHttpNewUrl, + getHttpUri, + getHttpResponseCode, + getHttpCookie, + getHttpNewCookie, + getHttpContentType, + getHttpLocation, + getHttpBody, + getHttpReqBody, + getHttpUriOffset, + getHttpUriEndOffset, + getHttpCookieOffset, + getHttpCookieEndOffset, + getHttpSearch, + getHttpXffAddr, + + getTlsHost, + + getDhcpFpData, + freeDhcpFpData, + getDhcpInfo, + freeDhcpInfo, + getSmbFpData, + freeSmbFpData, + getNetbiosName, + produceHAState, + consumeHAState, + + getAppIdData, + + getDNSQuery, + getDNSQueryoffset, + getDNSRecordType, + getDNSResponseType, + getDNSTTL, + + getHttpNewField, + freeHttpNewField, +}; + +void appIdApiInit(struct AppIdApi* api) +{ + *api = appIdDispatchTable; +} + diff --git a/src/network_inspectors/appid/appid_api.h b/src/network_inspectors/appid/appid_api.h new file mode 100644 index 000000000..098680a45 --- /dev/null +++ b/src/network_inspectors/appid/appid_api.h @@ -0,0 +1,274 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_api.h author Sourcefire Inc. + +#ifndef APPID_API_H +#define APPID_API_H + +#include + +enum class IpProtocol : uint8_t; + +#define APPID_SESSION_RESPONDER_MONITORED (1ULL << 0) +#define APPID_SESSION_INITIATOR_MONITORED (1ULL << 1) +#define APPID_SESSION_SPECIAL_MONITORED (1ULL << 2) +#define APPID_SESSION_INITIATOR_SEEN (1ULL << 3) +#define APPID_SESSION_RESPONDER_SEEN (1ULL << 4) +#define APPID_SESSION_DISCOVER_USER (1ULL << 5) +#define APPID_SESSION_HAS_DHCP_FP (1ULL << 6) +#define APPID_SESSION_HAS_DHCP_INFO (1ULL << 7) +#define APPID_SESSION_HAS_SMB_INFO (1ULL << 8) +#define APPID_SESSION_MID (1ULL << 9) +#define APPID_SESSION_OOO (1ULL << 10) +#define APPID_SESSION_SYN_RST (1ULL << 11) + + /**Service missed the first UDP packet in a flow. This causes detectors to see traffic in reverse direction. + * Detectors should set this flag by verifying that packet from initiator is indeed a packet from responder. + * Setting this flag without this check will cause RNA to not try other detectors in some cases (see bug 77551).*/ +#define APPID_SESSION_UDP_REVERSED (1ULL << 12) +#define APPID_SESSION_HTTP_SESSION (1ULL << 13) + + /**Service protocol was detected */ +#define APPID_SESSION_SERVICE_DETECTED (1ULL << 14) + + /**Finsihed with client app detection */ +#define APPID_SESSION_CLIENT_DETECTED (1ULL << 15) + /**Flow is a data connection not a service */ +#define APPID_SESSION_NOT_A_SERVICE (1ULL << 16) + +#define APPID_SESSION_DECRYPTED (1ULL << 17) +#define APPID_SESSION_SERVICE_DELETED (1ULL << 18) + + //The following attributes are references only with appId + /**Continue calling the routine after the service has been identified. */ +#define APPID_SESSION_CONTINUE (1ULL << 19) + /**Call service detection even if the host does not exist */ +#define APPID_SESSION_IGNORE_HOST (1ULL << 20) + /**Service protocol had incompatible client data */ +#define APPID_SESSION_INCOMPATIBLE (1ULL << 21) + /**we are ready to see out of network Server packets */ +#define APPID_SESSION_CLIENT_GETS_SERVER_PACKETS (1ULL << 22) + +#define APPID_SESSION_DISCOVER_APP (1ULL << 23) + +#define APPID_SESSION_PORT_SERVICE_DONE (1ULL << 24) +#define APPID_SESSION_ADDITIONAL_PACKET (1ULL << 25) +#define APPID_SESSION_RESPONDER_CHECKED (1ULL << 26) +#define APPID_SESSION_INITIATOR_CHECKED (1ULL << 27) +#define APPID_SESSION_SSL_SESSION (1ULL << 28) +#define APPID_SESSION_LOGIN_SUCCEEDED (1ULL << 29) + +#define APPID_SESSION_SPDY_SESSION (1ULL << 30) +#define APPID_SESSION_ENCRYPTED (1ULL << 31) + +#define APPID_SESSION_APP_REINSPECT (1ULL << 32) +#define APPID_SESSION_RESPONSE_CODE_CHECKED (1ULL << 33) +#define APPID_SESSION_REXEC_STDERR (1ULL << 34) +#define APPID_SESSION_CHP_INSPECTING (1ULL << 35) +#define APPID_SESSION_STICKY_SERVICE (1ULL << 36) +#define APPID_SESSION_APP_REINSPECT_SSL (1ULL << 37) + +#define APPID_SESSION_NO_TPI (1ULL << 38) +#define APPID_SESSION_IGNORE_FLOW (1ULL << 39) +#define APPID_SESSION_IGNORE_FLOW_LOGGED (1ULL << 40) + +#define APPID_SESSION_OOO_LOGGED (1ULL << 41) +#define APPID_SESSION_TPI_OOO_LOGGED (1ULL << 42) + +#define APPID_SESSION_EXPECTED_EVALUATE (1ULL << 43) + +#define APPID_SESSION_IGNORE_ID_FLAGS (APPID_SESSION_IGNORE_FLOW | \ + APPID_SESSION_NOT_A_SERVICE | \ + APPID_SESSION_NO_TPI | \ + APPID_SESSION_SERVICE_DETECTED | \ + APPID_SESSION_PORT_SERVICE_DONE) + + +using AppId = int32_t; +class AppIdData; + +enum APPID_FLOW_TYPE +{ + APPID_FLOW_TYPE_IGNORE, + APPID_FLOW_TYPE_NORMAL, + APPID_FLOW_TYPE_TMP +}; + +struct RNAServiceSubtype +{ + RNAServiceSubtype *next; + const char *service; + const char *vendor; + const char *version; +}; + +#define DHCP_OP55_MAX_SIZE 64 +#define DHCP_OP60_MAX_SIZE 64 + +struct DhcpFPData +{ + DhcpFPData *next; + unsigned op55_len; + unsigned op60_len; + uint8_t op55[DHCP_OP55_MAX_SIZE]; + uint8_t op60[DHCP_OP60_MAX_SIZE]; + // FIXIT-L J should be using eth address type here + uint8_t mac[6]; +} ; + +// FIXIT-L J inconsistently named structs (DHCPInfo vs DhcpFPData, note the "DHCP") +struct DHCPInfo +{ + DHCPInfo *next; + uint32_t ipAddr; + // FIXIT-L J should be using eth address type here + // FIXIT-L J inconsistently named fields (macAddr here, mac in DhcpFPData) + uint8_t macAddr[6]; + uint32_t subnetmask; + uint32_t leaseSecs; + uint32_t router; +}; + +struct FpSMBData +{ + FpSMBData *next; + unsigned major; + unsigned minor; + uint32_t flags; +}; + +//maximum number of appIds replicated for a flow/session +#define APPID_HA_SESSION_APP_NUM_MAX 8 + +struct AppIdSessionHA +{ + uint16_t flags; + AppId appId[APPID_HA_SESSION_APP_NUM_MAX]; +}; + +enum SEARCH_SUPPORT_TYPE +{ + // FIXIT-L J enums are inconsistently named + NOT_A_SEARCH_ENGINE, + SUPPORTED_SEARCH_ENGINE, + UNSUPPORTED_SEARCH_ENGINE, + SEARCH_SUPPORT_TYPE_UNKNOWN, +}; + +// FIXIT-M J probable duplication from nhttp +enum HTTP_FIELD_ID +{ + REQ_AGENT_FID = 0, + REQ_HOST_FID = 1, + REQ_REFERER_FID = 2, + REQ_URI_FID = 3, + REQ_COOKIE_FID = 4, + REQ_BODY_FID = 5, + RSP_CONTENT_TYPE_FID = 6, + RSP_LOCATION_FID = 7, + RSP_BODY_FID = 8, + HTTP_FIELD_MAX = RSP_BODY_FID +}; + +// ----------------------------------------------------------------------------- +// AppId API +// ----------------------------------------------------------------------------- + +struct sfip_t; +// FIXIT-H J should be class with static methods +struct AppIdApi +{ + const char * (*geApplicationName)(int32_t appId); + AppId (*geApplicationId)(const char* appName); + + AppId (*geServiceAppId)(AppIdData*); + AppId (*getPorServiceAppId)(AppIdData*); + AppId (*getOnlyServiceAppId)(AppIdData*); + AppId (*getMiscAppId)(AppIdData*); + AppId (*getClientAppId)(AppIdData*); + AppId (*getPayloadAppId)(AppIdData*); + AppId (*getReferredAppId)(AppIdData*); + AppId (*getFwServiceAppId)(AppIdData*); + AppId (*getFwMiscAppId)(AppIdData*); + AppId (*getFwClientAppId)(AppIdData*); + AppId (*getFwPayloadAppId)(AppIdData*); + AppId (*getFwReferredAppId)(AppIdData*); + + bool (*isSessionSslDecrypted)(AppIdData*); + bool (*isAppIdInspectingSession)(AppIdData*); + bool (*isAppIdAvailable)(AppIdData*); + + char* (*getUserName)(AppIdData*, AppId* service, bool* isLoginSuccessful); + char* (*geClientVersion)(AppIdData*); + + uint64_t (*getAppIdSessionAttribute)(AppIdData*, uint64_t flag); + + APPID_FLOW_TYPE (*getFlowType)(AppIdData*); + void (*geServiceInfo)(AppIdData*, char **serviceVendor, char** serviceVersion, RNAServiceSubtype** subtype); + short (*geServicePort)(AppIdData*); + sfip_t* (*geServiceIp)(AppIdData*); + sfip_t* (*getInitiatorIp)(AppIdData*); + + char* (*getHttpUserAgent)(AppIdData*); + char* (*getHttpHost)(AppIdData*); + char* (*getHttpUrl)(AppIdData*); + char* (*getHttpReferer)(AppIdData*); + char* (*getHttpNewUrl)(AppIdData*); + char* (*getHttpUri)(AppIdData*); + char* (*getHttpResponseCode)(AppIdData*); + char* (*getHttpCookie)(AppIdData*); + char* (*getHttpNewCookie)(AppIdData*); + char* (*getHttpContentType)(AppIdData*); + char* (*getHttpLocation)(AppIdData*); + char* (*getHttpBody)(AppIdData*); + char* (*getHttpReqBody)(AppIdData*); + uint16_t (*getHttpUriOffset)(AppIdData*); + uint16_t (*getHttpUriEndOffset)(AppIdData*); + uint16_t (*getHttpCookieOffset)(AppIdData*); + uint16_t (*getHttpCookieEndOffset)(AppIdData*); + SEARCH_SUPPORT_TYPE (*getHttpSearch)(AppIdData*); + sfip_t* (*getHttpXffAddr)(AppIdData*); + + char* (*getTlsHost)(AppIdData*); + + DhcpFPData* (*getDhcpFpData)(AppIdData*); + void (*freeDhcpFpData)(AppIdData*, DhcpFPData*); + DHCPInfo* (*getDhcpInfo)(AppIdData*); + void (*freeDhcpInfo)(AppIdData*, DHCPInfo*); + FpSMBData* (*getSmbFpData)(AppIdData*); + void (*freeSmbFpData)(AppIdData*, FpSMBData*); + char* (*getNetbiosName)(AppIdData*); + uint32_t (*produceHAState)(void* lwssn, uint8_t* buf); + uint32_t (*consumeHAState)(void* lwssn, const uint8_t* buf, uint8_t length, IpProtocol proto, sfip_t* ip); + AppIdData* (*getAppIdData)(void* lwssn); + + char* (*getDNSQuery)(AppIdData*, uint8_t* query_len); + uint16_t (*getDNSQueryoffset)(AppIdData*); + uint16_t (*getDNSRecordType)(AppIdData*); + uint8_t (*getDNSResponseType)(AppIdData*); + uint32_t (*getDNSTTL)(AppIdData*); + char* (*getHttpNewField)(AppIdData*, HTTP_FIELD_ID); + void (*freeHttpNewField)(AppIdData*, HTTP_FIELD_ID); +}; + +// For access when including header +extern AppIdApi appIdApi; + +#endif diff --git a/src/network_inspectors/appid/appid_config.cc b/src/network_inspectors/appid/appid_config.cc new file mode 100644 index 000000000..18ea5c626 --- /dev/null +++ b/src/network_inspectors/appid/appid_config.cc @@ -0,0 +1,1098 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_config.cc author Sourcefire Inc. +#include +#include + +#include "appid_config.h" +#include "appid_stats.h" +#include "app_info_table.h" +#include "application_ids.h" +#include "app_forecast.h" +#include "fw_appid.h" +#include "host_port_app_cache.h" +#include "thirdparty_appid_utils.h" +#include "client_plugins/client_app_base.h" +#include "service_plugins/service_base.h" +#include "service_plugins/service_ssl.h" +#include "detector_plugins/detector_base.h" +#include "detector_plugins/detector_http.h" +#include "detector_plugins/detector_dns.h" + +#include "util/network_set.h" +#include "util/ip_funcs.h" +#include "util/common_util.h" +#include "util/sfutil.h" + +#include "lua_detector_api.h" +#include "lua_detector_module.h" + +#include "main/snort_debug.h" +#include "log/messages.h" +#include "utils/util.h" + +#define ODP_PORT_DETECTORS "odp/port/*" +#define CUSTOM_PORT_DETECTORS "custom/port/*" + +/*static const char * const MODULE_NAME = "AppMatcher"; */ +#define MAX_DISPLAY_SIZE 65536 + +// FIXIT - M this global needs to go asap... just here now to compile while doing some major config +// refactoring +AppIdConfig* pAppidActiveConfig = nullptr; + +unsigned appIdPolicyId; +uint32_t app_id_netmasks[33]; + +struct PortList +{ + PortList* next; + uint16_t port; +}; + +AppIdModuleConfig::~AppIdModuleConfig() +{ + snort_free((void*)conf_file); + snort_free((void*)app_stats_filename); + snort_free((void*)app_detector_dir); + snort_free((void*)thirdparty_appid_dir); +} + +void AppIdConfig::add_generic_config_element(const char* name, void* pData) +{ + AppidGenericConfigItem* pConfigItem; + + pConfigItem = (AppidGenericConfigItem*)snort_calloc(sizeof(AppidGenericConfigItem)); + pConfigItem->name = snort_strdup(name); + pConfigItem->pData = pData; + sflist_add_tail(&genericConfigList, pConfigItem); +} + +void* AppIdConfig::find_generic_config_element(const char* name) +{ + AppidGenericConfigItem* pConfigItem; + SF_LNODE* next; + + // Search a module's configuration by its name + for (pConfigItem = (AppidGenericConfigItem*)sflist_first( + (SF_LIST*)&genericConfigList, &next); + pConfigItem != nullptr; + pConfigItem = (AppidGenericConfigItem*)sflist_next(&next)) + { + if (strcmp(pConfigItem->name, name) == 0) + { + return pConfigItem->pData; + } + } + + return nullptr; +} + +void AppIdConfig::remove_generic_config_element(const char* name) +{ + SF_LNODE* iter; + AppidGenericConfigItem* cfg; + + // Search a module's configuration by its name + for (cfg = (AppidGenericConfigItem*)sflist_first(&genericConfigList, &iter); + cfg != nullptr; + cfg = (AppidGenericConfigItem*)sflist_next(&iter)) + { + if (strcmp(cfg->name, name) == 0) + { + snort_free(cfg->name); + snort_free(cfg); + sflist_remove_node(&genericConfigList, iter); + break; + } + } +} + +void AppIdConfig::read_port_detectors(const char* files) +{ + int rval; + glob_t globs; + char pattern[PATH_MAX]; + uint32_t n; + + snprintf(pattern, sizeof(pattern), "%s/%s", mod_config->app_detector_dir, files); + + memset(&globs, 0, sizeof(globs)); + rval = glob(pattern, 0, nullptr, &globs); + if (rval != 0 && rval != GLOB_NOMATCH) + { + ErrorMessage("Unable to read directory '%s'\n",pattern); + return; + } + + for (n = 0; n < globs.gl_pathc; n++) + { + FILE* file; + unsigned proto = 0; + AppId appId = APP_ID_NONE; + char line[1024]; + PortList* port = nullptr; + PortList* tmp_port; + + if ((file = fopen(globs.gl_pathv[n], "r")) == nullptr) + { + ErrorMessage("Unable to read port service '%s'\n",globs.gl_pathv[n]); + continue; + } + + while (fgets(line, sizeof(line), file)) + { + char* key, * value, * p; + size_t len; + + len = strlen(line); + for (; len && (line[len - 1] == '\n' || line[len - 1] == '\r'); len--) + line[len - 1] = 0; + + /* find key/value for lines of the format "key: value\n" */ + if ((value = strchr(line, ':'))) + { + key = line; + *value = '\0'; + value++; + for (; *value && *value == ' '; value++) + ; + + if (strcasecmp(key, "ports") == 0) + { + char* context = nullptr; + char* ptr; + unsigned long tmp; + + for (ptr = strtok_r(value, ",", &context); ptr; ptr = strtok_r(nullptr, ",", + &context)) + { + for (; *ptr && *ptr == ' '; ptr++) + ; + len = strlen(ptr); + for (; len && ptr[len - 1] == ' '; len--) + ptr[len - 1] = 0; + tmp = strtoul(ptr, &p, 10); + if (!*ptr || *p || !tmp || tmp > 65535) + { + ErrorMessage("Invalid port, '%s', in lua detector '%s'\n",ptr, + globs.gl_pathv[n]); + goto next; + } + tmp_port = (PortList*)snort_calloc(sizeof(PortList)); + tmp_port->port = (uint16_t)tmp; + tmp_port->next = port; + port = tmp_port; + } + } + else if (strcasecmp(key, "protocol") == 0) + { + if (strcasecmp(value, "tcp") == 0) + proto = 1; + else if (strcasecmp(value, "udp") == 0) + proto = 2; + else if (strcasecmp(value, "tcp/udp") == 0) + proto = 3; + else + { + ErrorMessage("Invalid protocol, '%s', in port service '%s'\n",value, + globs.gl_pathv[n]); + goto next; + } + } + else if (strcasecmp(key, "appId") == 0) + { + appId = (AppId)strtoul(value, &p, 10); + if (!*value || *p || appId <= APP_ID_NONE) + { + ErrorMessage("Invalid app ID, '%s', in port service '%s'\n",value, + globs.gl_pathv[n]); + goto next; + } + } + } + } + if (port && proto && appId > APP_ID_NONE) + { + while ((tmp_port = port)) + { + port = tmp_port->next; + if (proto & 1) + tcp_port_only[tmp_port->port] = appId; + if (proto & 2) + udp_port_only[tmp_port->port] = appId; + + snort_free(tmp_port); + appInfoSetActive(appId, true); + } + appInfoSetActive(appId, true); + } + else + ErrorMessage("Missing parameter(s) in port service '%s'\n",globs.gl_pathv[n]); + +next:; + while ((tmp_port = port)) + { + port = tmp_port->next; + snort_free(tmp_port); + } + fclose(file); + } + + globfree(&globs); +} + +void AppIdConfig::configure_analysis_networks(char* toklist[], uint32_t flag) +{ + int zone; + NetworkSet* my_net_list; + RNAIpAddrSet* ias; + RNAIpv6AddrSet* ias6; + char* p; + long tmp; + + if (toklist[0]) + { + if (strchr(toklist[0], ':')) + { + ias6 = ParseIpv6Cidr(toklist[0]); + if (ias6) + { + NSIPv6Addr six; + char min_ip[INET6_ADDRSTRLEN]; + char max_ip[INET6_ADDRSTRLEN]; + + if (toklist[1]) + { + tmp = strtol(toklist[1], &p, 10); + if (!*toklist[1] || *p != 0 || tmp >= MAX_ZONES || tmp < -1) + { + ErrorMessage("Invalid Analyze: %s '%s'", toklist[0], toklist[1]); + zone = -1; + } + else + zone = (int)tmp; + } + else + zone = -1; + ias6->addr_flags |= flag; + six = ias6->range_min; + NSIPv6AddrHtoN(&six); + inet_ntop(AF_INET6, (struct in6_addr*)&six, min_ip, sizeof(min_ip)); + six = ias6->range_max; + NSIPv6AddrHtoN(&six); + inet_ntop(AF_INET6, (struct in6_addr*)&six, max_ip, sizeof(max_ip)); + LogMessage("Adding %s-%s (0x%08X) with zone %d\n", min_ip, max_ip, + ias6->addr_flags, zone); + if (zone >= 0) + { + if (!(my_net_list = net_list_by_zone[zone])) + { + if (NetworkSet_New(&my_net_list)) + ErrorMessage("%s", "Failed to create a network set"); + else + { + my_net_list->next = net_list_list; + net_list_list = my_net_list; + } + net_list_by_zone[zone] = my_net_list; + } + } + else + my_net_list = net_list; + if (my_net_list && NetworkSet_AddCidrBlock6Ex(my_net_list, &ias6->range_min, + ias6->netmask, + ias6->addr_flags & IPFUNCS_EXCEPT_IP, 0, + ias6->addr_flags & (~IPFUNCS_EXCEPT_IP))) + { + ErrorMessage( + "Failed to add an IP address set to the list of monitored networks"); + } + snort_free(ias6); + } + else + ErrorMessage("Invalid analysis parameter: %s", toklist[0]); + } + else + { + ias = ParseIpCidr(toklist[0], app_id_netmasks); + if (ias) + { + if (toklist[1]) + { + tmp = strtol(toklist[1], &p, 10); + if (!*toklist[1] || *p != 0 || tmp >= MAX_ZONES || tmp < -1) + { + ErrorMessage("Invalid Analyze: %s '%s'", toklist[0], toklist[1]); + zone = -1; + } + else + zone = (int)tmp; + } + else + zone = -1; + ias->addr_flags |= flag; + LogMessage("Adding 0x%08X-0x%08X (0x%08X) with zone %d\n", ias->range_min, + ias->range_max, + ias->addr_flags, zone); + if (zone >= 0) + { + if (!(my_net_list = net_list_by_zone[zone])) + { + if (NetworkSet_New(&my_net_list)) + ErrorMessage("%s", "Failed to create a network set"); + else + { + my_net_list->next = net_list_list; + net_list_list = my_net_list; + } + net_list_by_zone[zone] = my_net_list; + } + } + else + my_net_list = net_list; + if (my_net_list && NetworkSet_AddCidrBlockEx(my_net_list, ias->range_min, + ias->netmask, + ias->addr_flags & IPFUNCS_EXCEPT_IP, 0, + ias->addr_flags & (~IPFUNCS_EXCEPT_IP))) + { + ErrorMessage( + "Failed to add an IP address set to the list of monitored networks"); + } + snort_free(ias); + } + else + ErrorMessage("Invalid analysis parameter: %s", toklist[0]); + } + } +} + +int AppIdConfig::add_port_exclusion(SF_LIST* port_exclusions[], const ip::snort_in6_addr* ip, + const ip::snort_in6_addr* netmask, int family, uint16_t port) +{ + PortExclusion* port_ex; + SF_LIST* pe_list; + + port_ex = (PortExclusion*)snort_calloc(sizeof(PortExclusion)); + port_ex->ip = *ip; + if (family == AF_INET) + { + port_ex->netmask.u6_addr32[0] = port_ex->netmask.u6_addr32[1] = + port_ex->netmask.u6_addr32[2] = ~0; + port_ex->netmask.u6_addr32[3] = netmask->u6_addr32[3]; + } + else + port_ex->netmask = *netmask; + + if ((pe_list = port_exclusions[port]) == nullptr) + { + pe_list = port_exclusions[port] = sflist_new(); + if (pe_list == nullptr) + { + snort_free(port_ex); + ErrorMessage("Config: Failed to allocate memory for port exclusion list"); + return -1; + } + } + + /* add this PortExclusion to the sflist for this port */ + if (sflist_add_tail(pe_list, port_ex) ) + { + snort_free(port_ex); + ErrorMessage("Config: Failed to add an port exclusion to the list"); + return -1; + } + return 0; +} + +void AppIdConfig::process_port_exclusion(char* toklist[]) +{ + int i = 1; + char* p; + RNAIpAddrSet* ias; + RNAIpv6AddrSet* ias6; + SF_LIST** port_exclusions; + IpProtocol proto; + unsigned long dir; + unsigned long port; + ip::snort_in6_addr ip; + ip::snort_in6_addr netmask; + int family; + + if (!toklist[i]) + { + ErrorMessage("Config: Port exclusion direction omitted"); + return; + } + + if (strcasecmp(toklist[i], "dst") == 0) + dir = 2; + else if (strcasecmp(toklist[i], "src") == 0) + dir = 1; + else if (strcasecmp(toklist[i], "both") == 0) + dir = 3; + else + { + ErrorMessage("Config: Invalid port exclusion direction specified"); + return; + } + + i++; + if (!toklist[i]) + { + ErrorMessage("Config: Port exclusion protocol omitted"); + return; + } + + if (strcasecmp(toklist[i], "tcp") == 0) + proto = IpProtocol::TCP; + else if (strcasecmp(toklist[i], "udp") == 0) + proto = IpProtocol::UDP; + else + { + ErrorMessage("Config: Invalid port exclusion protocol specified"); + return; + } + + i++; + if (!toklist[i]) + { + ErrorMessage("Config: Port exclusion port omitted"); + return; + } + + port = strtoul(toklist[i], &p, 10); + if (!*toklist[i] || *p || port >= APP_ID_PORT_ARRAY_SIZE) + { + ErrorMessage("Config: Invalid port exclusion port specified"); + return; + } + + i++; + if (!toklist[i]) + { + ErrorMessage("Config: Port exclusion address omitted"); + return; + } + + if (strchr(toklist[i], ':')) + { + ias6 = ParseIpv6Cidr(toklist[i]); + if (!ias6 || ias6->addr_flags) + { + if (ias6) + snort_free(ias6); + ErrorMessage("Config: Invalid port exclusion address specified"); + return; + } + NSIPv6AddrHtoNConv(&ias6->range_min, &ip); + NSIPv6AddrHtoNConv(&ias6->netmask_mask, &netmask); + family = AF_INET6; + snort_free(ias6); + } + else + { + ias = ParseIpCidr(toklist[i], app_id_netmasks); + if (!ias || ias->addr_flags) + { + if (ias) + snort_free(ias); + ErrorMessage("Config: Invalid port exclusion address specified"); + return; + } + family = AF_INET; + copyIpv4ToIpv6Network(&ip, htonl(ias->range_min)); + copyIpv4ToIpv6Network(&netmask, htonl(ias->netmask_mask)); + snort_free(ias); + } + + if (dir & 1) + { + if (proto == IpProtocol::TCP) + port_exclusions = tcp_port_exclusions_src; + else + port_exclusions = udp_port_exclusions_src; + add_port_exclusion(port_exclusions, &ip, &netmask, family, (uint16_t)port); + } + if (dir & 2) + { + if (proto == IpProtocol::TCP) + port_exclusions = tcp_port_exclusions_dst; + else + port_exclusions = udp_port_exclusions_dst; + add_port_exclusion(port_exclusions, &ip, &netmask, family, (uint16_t)port); + } +} + +void AppIdConfig::process_config_directive(char* toklist[], int /* reload */) +{ + char* curtok; + int i; + + /* the first tok is "config" or we wouldn't be here now */ + i = 1; + curtok = toklist[i]; + i++; + + if (!strcasecmp(curtok, "Analyze")) + { + configure_analysis_networks(&toklist[i], IPFUNCS_HOSTS_IP | IPFUNCS_APPLICATION); + } + else if (!strcasecmp(curtok, "AnalyzeHost")) + { + configure_analysis_networks(&toklist[i], IPFUNCS_HOSTS_IP | IPFUNCS_APPLICATION); + } + else if (!strcasecmp(curtok, "AnalyzeUser")) + { + configure_analysis_networks(&toklist[i], IPFUNCS_USER_IP | IPFUNCS_APPLICATION); + } + else if (!strcasecmp(curtok, "AnalyzeHostUser")) + { + configure_analysis_networks(&toklist[i], + IPFUNCS_HOSTS_IP | IPFUNCS_USER_IP | IPFUNCS_APPLICATION); + } + else if (!strcasecmp(curtok, "AnalyzeApplication")) + { + configure_analysis_networks(&toklist[i], IPFUNCS_APPLICATION); + } +} + +int AppIdConfig::load_analysis_config(const char* config_file, int reload, int instance_id) +{ + FILE* fp; + char linebuffer[MAX_LINE]; + char* cptr; + char* toklist[MAX_TOKS]; + int num_toks; + unsigned line = 0; + NetworkSet* my_net_list; + + if (NetworkSet_New(&net_list)) + { + FatalError("Failed to allocate a network set"); + exit(1); + } + net_list_list = net_list; + + if (!config_file || (!config_file[0])) + { + char addrString[sizeof("0.0.0.0/0")]; + LogMessage("Defaulting to monitoring all Snort traffic for AppID.\n"); + toklist[1] = nullptr; + toklist[0] = addrString; + strcpy(addrString,"0.0.0.0/0"); + configure_analysis_networks(toklist, IPFUNCS_HOSTS_IP | IPFUNCS_USER_IP | + IPFUNCS_APPLICATION); + strcpy(addrString,"::/0"); + configure_analysis_networks(toklist, IPFUNCS_HOSTS_IP | IPFUNCS_USER_IP | + IPFUNCS_APPLICATION); + toklist[0] = nullptr; + } + else + { + DebugFormat(DEBUG_APPID, "Loading configuration file: %s", config_file); + + if (!(fp = fopen(config_file, "r"))) + { + ErrorMessage("Unable to open %s", config_file); + return -1; + } + + while (fgets(linebuffer, MAX_LINE, fp) != nullptr) + { + line++; + + strip(linebuffer); + + cptr = linebuffer; + + while (isspace((int)*cptr)) + cptr++; + + if (*cptr && (*cptr != '#') && (*cptr != 0x0a)) + { + memset(toklist, 0, sizeof(toklist)); + /* tokenize the line */ + num_toks = Tokenize(cptr, toklist); + if (num_toks < 2) + { + fclose(fp); + ErrorMessage("Invalid configuration file line %u", line); + return -1; + } + if (!(strcasecmp(toklist[0], "config"))) + { + process_config_directive(toklist, reload); + } + else if (!(strcasecmp(toklist[0], "portexclusion"))) + { + process_port_exclusion(toklist); + } + } + } + + fclose(fp); + } + + if (instance_id) + { + char* instance_toklist[2]; + char addrString[sizeof("0.0.0.0/0")]; + LogMessage("Defaulting to monitoring all Snort traffic for AppID.\n"); + instance_toklist[0] = addrString; + instance_toklist[1] = nullptr; + strcpy(addrString,"0.0.0.0/0"); + configure_analysis_networks(instance_toklist, IPFUNCS_APPLICATION); + strcpy(addrString,"::/0"); + configure_analysis_networks(instance_toklist, IPFUNCS_APPLICATION); + } + + for (my_net_list = net_list_list; my_net_list; my_net_list = net_list->next) + { + if (my_net_list != net_list) + { + if (NetworkSet_AddSet(my_net_list, net_list)) + ErrorMessage("Failed to add any network list to a zone network list"); + } + } + net_list_count = 0; + for (my_net_list = net_list_list; my_net_list; my_net_list = net_list->next) + { + if (NetworkSet_Reduce(my_net_list)) + ErrorMessage("Failed to reduce the IP address sets"); + net_list_count += NetworkSet_CountEx(my_net_list) + NetworkSet_Count6Ex(my_net_list); + } + + return 0; +} + +void AppIdConfig::load_modules(uint32_t instance_id) +{ + if (LoadServiceModules(nullptr, instance_id, this)) + exit(-1); + + if (LoadClientAppModules(this)) + exit(-1); + + if (LoadDetectorModules(nullptr)) + exit(-1); +} + +void AppIdConfig::finalize_pattern_modules() +{ + unsigned int i; + ServicePatternData* curr; + ServicePatternData* lists[] = { serviceConfig.tcp_pattern_data, + serviceConfig.udp_pattern_data }; + for (i = 0; i < (sizeof(lists) / sizeof(*lists)); i++) + { + curr = lists[i]; + while (curr != nullptr) + { + if (curr->svc != nullptr) + { + bool isActive = true; + if (curr->svc->userdata && !curr->svc->userdata->isActive) + { + /* C detectors don't have userdata here, but they're always + * active. So, this check is really just for Lua + * detectors. */ + isActive = false; + } + if (isActive) + { + curr->svc->current_ref_count = curr->svc->ref_count; + } + } + curr = curr->next; + } + } +} + +int AppIdConfig::init_AF_indicators() +{ + if (!(AF_indicators = sfxhash_new(1024, sizeof(AppId), sizeof(AFElement), 0, + 0, nullptr, nullptr, 0))) + { + ErrorMessage("Config: failed to allocate memory for an sfxhash."); + return 0; + } + else + return 1; +} + +int AppIdConfig::init_AF_actives() +{ + if (!(AF_actives = sfxhash_new(1024, sizeof(AFActKey), sizeof(AFActVal), + (sizeof(SFXHASH_NODE)*2048), 1, nullptr, nullptr, 1))) + { + ErrorMessage("Config: failed to allocate memory for an sfxhash."); + return 0; + } + else + return 1; +} + +static int genericDataFree(void* /* key */, void* data) +{ + if (data) + snort_free(data); + return 0; +} + +int AppIdConfig::init_CHP_glossary() +{ + if (!(CHP_glossary = sfxhash_new(1024, sizeof(AppId), 0, 0, 0, nullptr, &genericDataFree, 0))) + { + ErrorMessage("Config: failed to allocate memory for an sfxhash."); + return 0; + } + else + return 1; +} + +void AppIdConfig::set_safe_search_enforcement(int enabled) +{ + DEBUG_WRAP(DebugFormat(DEBUG_APPID, " Safe Search Enforcement enabled = %d.\n",enabled); ); + mod_config->disable_safe_search = enabled ? 0 : 1; +} + +bool AppIdConfig::init_appid( ) +{ + fwAppIdInit(); + + if (config_state == RNA_FW_CONFIG_STATE_UNINIT) + { + appIdPolicyId = 53; + // FIXIT - active config must be per Inspector instance...not this global... + pAppidActiveConfig = this; + config_state = RNA_FW_CONFIG_STATE_PENDING; + + InitNetmasks(app_id_netmasks); + sflist_init(&pAppidActiveConfig->client_app_args); + load_analysis_config(mod_config->conf_file, 0, mod_config->instance_id); + if (!init_CHP_glossary( )) + return false; + if (!init_AF_indicators( )) + return false; + if (!init_AF_actives( )) + return false; + LuaDetectorModuleManager::luaModuleInit(); + appInfoTableInit(mod_config->app_detector_dir, pAppidActiveConfig); + read_port_detectors(ODP_PORT_DETECTORS); + read_port_detectors(CUSTOM_PORT_DETECTORS); + load_modules(mod_config->instance_id); + hostPortAppCacheInit(pAppidActiveConfig); + lengthAppCacheInit(pAppidActiveConfig); + + LuaDetectorModuleManager::LoadLuaModules(pAppidActiveConfig); + ClientAppInit(pAppidActiveConfig); + ServiceInit(pAppidActiveConfig); + LuaDetectorModuleManager::FinalizeLuaModules(pAppidActiveConfig); + + finalize_pattern_modules(); + + http_detector_finalize(pAppidActiveConfig); +#ifdef REMOVED_WHILE_NOT_IN_USE + sipUaFinalize(&pAppidActiveConfig->detectorSipConfig); + ssl_detector_process_patterns(&pAppidActiveConfig->serviceSslConfig); +#endif + dns_host_detector_process_patterns(&pAppidActiveConfig->serviceDnsConfig); + portPatternFinalize(pAppidActiveConfig); + ClientAppFinalize(pAppidActiveConfig); + ServiceFinalize(pAppidActiveConfig); + appIdStatsInit(mod_config); + ThirdPartyAppIDInit(mod_config); + + show(); + + // FIXIT - do we still need to support this? + if (pAppidActiveConfig->mod_config->dump_ports) + { + dumpPorts(stdout, pAppidActiveConfig); + appInfoTableDump(pAppidActiveConfig); + exit(0); + } + +#ifdef DEBUG_APP_COMMON + DisplayPortConfig(pAppidActiveConfig); +#endif + if (AppIdServiceStateInit(mod_config->memcap)) + exit(-1); + config_state = RNA_FW_CONFIG_STATE_INIT; + return true; + } + + return false; +} + +static void ConfigItemFree(ConfigItem* ci) +{ + if (ci) + { + if (ci->name) + snort_free(ci->name); + if (ci->value) + snort_free(ci->value); + snort_free(ci); + } +} + +static void cleanup_config(AppIdConfig* pConfig) +{ + NetworkSet* net_list; ///< list of network sets + unsigned int i; + while ((net_list = pConfig->net_list_list)) + { + pConfig->net_list_list = net_list->next; + NetworkSet_Destroy(net_list); + } + + /* clean up any port exclusions that have been allocated */ + for ( i=0; itcp_port_exclusions_src[i] != nullptr ) + { + sflist_free_all(pConfig->tcp_port_exclusions_src[i], &free); + pConfig->tcp_port_exclusions_src[i] = nullptr; + } + if ( pConfig->tcp_port_exclusions_dst[i] != nullptr ) + { + sflist_free_all(pConfig->tcp_port_exclusions_dst[i], &free); + pConfig->tcp_port_exclusions_dst[i] = nullptr; + } + if ( pConfig->udp_port_exclusions_src[i] != nullptr ) + { + sflist_free_all(pConfig->udp_port_exclusions_src[i], &free); + pConfig->udp_port_exclusions_src[i] = nullptr; + } + if ( pConfig->udp_port_exclusions_dst[i] != nullptr ) + { + sflist_free_all(pConfig->udp_port_exclusions_dst[i], &free); + pConfig->udp_port_exclusions_dst[i] = nullptr; + } + } + + pConfig->net_list = nullptr; + + if (pConfig->CHP_glossary) + { + sfxhash_delete(pConfig->CHP_glossary); + pConfig->CHP_glossary = nullptr; + } + + if (pConfig->AF_indicators) + { + sfxhash_delete(pConfig->AF_indicators); + pConfig->AF_indicators = nullptr; + } + + if (pConfig->AF_actives) + { + sfxhash_delete(pConfig->AF_actives); + pConfig->AF_actives = nullptr; + } + + memset(pConfig->net_list_by_zone, 0, sizeof(pConfig->net_list_by_zone)); + + sflist_static_free_all(&pConfig->client_app_args, (void (*)(void*))ConfigItemFree); +} + +int AppIdConfig::cleanup(void) +{ + if (config_state == RNA_FW_CONFIG_STATE_INIT) + { + config_state = RNA_FW_CONFIG_STATE_PENDING; + if (thirdparty_appid_module != nullptr) + { + thirdparty_appid_module->print_stats(); + } + ThirdPartyAppIDFini(); + + cleanup_config(pAppidActiveConfig); + CleanupServices(pAppidActiveConfig); + CleanupClientApp(pAppidActiveConfig); + LuaDetectorModuleManager::luaModuleFini(); + hostPortAppCacheFini(pAppidActiveConfig); + AppIdServiceStateCleanup(); + appIdStatsFini(); + fwAppIdFini(pAppidActiveConfig); + http_detector_clean(&pAppidActiveConfig->detectorHttpConfig); +#ifdef REMOVED_WHILE_NOT_IN_USE + service_ssl_clean(&pAppidActiveConfig->serviceSslConfig); +#endif + service_dns_host_clean(&pAppidActiveConfig->serviceDnsConfig); + config_state = RNA_FW_CONFIG_STATE_UNINIT; + snort_free(pAppidActiveConfig); + pAppidActiveConfig = nullptr; + return 0; + } + return -1; +} + +static void DisplayPortExclusionList(SF_LIST* pe_list, uint16_t port) +{ + const char* p; + const char* p2; + char inet_buffer[INET6_ADDRSTRLEN]; + char inet_buffer2[INET6_ADDRSTRLEN]; + PortExclusion* pe; + SF_LNODE* lnext; + + if (!pe_list) + return; + + for (pe = (PortExclusion*)sflist_first(pe_list, &lnext); + pe; + pe = (PortExclusion*)sflist_next(&lnext)) + { + p = inet_ntop(pe->family, &pe->ip, inet_buffer, sizeof(inet_buffer)); + p2 = inet_ntop(pe->family, &pe->netmask, inet_buffer2, sizeof(inet_buffer2)); + LogMessage(" %d on %s/%s\n", port, p ? p : "ERROR", p2 ? p2 : "ERROR"); + } +} + +void AppIdConfig::show() +{ + unsigned i; + int j; + struct in_addr ia; + char inet_buffer[INET6_ADDRSTRLEN]; + char inet_buffer2[INET6_ADDRSTRLEN]; + NSIPv6Addr six; + const char* p; + const char* p2; + NetworkSet* my_net_list; + + if (mod_config->thirdparty_appid_dir) + LogMessage(" 3rd Party Dir: %s\n", mod_config->thirdparty_appid_dir); + + my_net_list = net_list; + LogMessage(" Monitoring Networks for any zone:\n"); + for (i = 0; i < my_net_list->count; i++) + { + ia.s_addr = htonl(my_net_list->pnetwork[i]->range_min); + p = inet_ntop(AF_INET, &ia, inet_buffer, sizeof(inet_buffer)); + ia.s_addr = htonl(my_net_list->pnetwork[i]->range_max); + p2 = inet_ntop(AF_INET, &ia, inet_buffer2, sizeof(inet_buffer2)); + LogMessage(" %s%s-%s %04X\n", (my_net_list->pnetwork[i]->info.ip_not) ? "!" : "", + p ? + p : "ERROR", + p2 ? p2 : "ERROR", my_net_list->pnetwork[i]->info.type); + } + for (i = 0; i < my_net_list->count6; i++) + { + six = my_net_list->pnetwork6[i]->range_min; + NSIPv6AddrHtoN(&six); + p = inet_ntop(AF_INET6, (struct in6_addr*)&six, inet_buffer, sizeof(inet_buffer)); + six = my_net_list->pnetwork6[i]->range_max; + NSIPv6AddrHtoN(&six); + p2 = inet_ntop(AF_INET6, (struct in6_addr*)&six, inet_buffer2, sizeof(inet_buffer2)); + LogMessage(" %s%s-%s %04X\n", (my_net_list->pnetwork6[i]->info.ip_not) ? "!" : "", + p ? + p : "ERROR", + p2 ? p2 : "ERROR", my_net_list->pnetwork6[i]->info.type); + } + + for (j=0; j < MAX_ZONES; j++) + { + if (!(my_net_list = net_list_by_zone[j])) + continue; + LogMessage(" Monitoring Networks for zone %d:\n", j); + for (i = 0; i < my_net_list->count; i++) + { + ia.s_addr = htonl(my_net_list->pnetwork[i]->range_min); + p = inet_ntop(AF_INET, &ia, inet_buffer, sizeof(inet_buffer)); + ia.s_addr = htonl(my_net_list->pnetwork[i]->range_max); + p2 = inet_ntop(AF_INET, &ia, inet_buffer2, sizeof(inet_buffer2)); + LogMessage(" %s%s-%s %04X\n", (my_net_list->pnetwork[i]->info.ip_not) ? "!" : + "", + p ? p : "ERROR", + p2 ? p2 : "ERROR", my_net_list->pnetwork[i]->info.type); + } + for (i = 0; i < my_net_list->count6; i++) + { + six = my_net_list->pnetwork6[i]->range_min; + NSIPv6AddrHtoN(&six); + p = inet_ntop(AF_INET6, (struct in6_addr*)&six, inet_buffer, sizeof(inet_buffer)); + six = my_net_list->pnetwork6[i]->range_max; + NSIPv6AddrHtoN(&six); + p2 = inet_ntop(AF_INET6, (struct in6_addr*)&six, inet_buffer2, sizeof(inet_buffer2)); + LogMessage(" %s%s-%s %04X\n", (my_net_list->pnetwork6[i]->info.ip_not) ? "!" : + "", + p ? p : "ERROR", + p2 ? p2 : "ERROR", my_net_list->pnetwork6[i]->info.type); + } + } + + LogMessage(" Excluded TCP Ports for Src:\n"); + for (i = 0; i < APP_ID_PORT_ARRAY_SIZE; i++) + DisplayPortExclusionList(tcp_port_exclusions_src[i], i); + + LogMessage(" Excluded TCP Ports for Dst:\n"); + for (i = 0; i < APP_ID_PORT_ARRAY_SIZE; i++) + DisplayPortExclusionList(tcp_port_exclusions_dst[i], i); + + LogMessage(" Excluded UDP Ports Src:\n"); + for (i = 0; i < APP_ID_PORT_ARRAY_SIZE; i++) + DisplayPortExclusionList(udp_port_exclusions_src[i], i); + + LogMessage(" Excluded UDP Ports Dst:\n"); + for (i = 0; i < APP_ID_PORT_ARRAY_SIZE; i++) + DisplayPortExclusionList(udp_port_exclusions_dst[i], i); +} + +#ifdef DEBUG_APP_COMMON +static void DisplayPortConfig(AppIdConfig* aic) +{ + unsigned i; + int first; + + first = 1; + for (i=0; ireferer) + { + snort_free(hsession->referer); + hsession->referer = nullptr; + } + if (hsession->cookie) + { + snort_free(hsession->cookie); + hsession->cookie = nullptr; + } + if (hsession->url) + { + snort_free(hsession->url); + hsession->url = nullptr; + } + if (hsession->useragent) + { + snort_free(hsession->useragent); + hsession->useragent = nullptr; + } + if (hsession->host) + { + snort_free(hsession->host); + hsession->host = nullptr; + } + if (hsession->uri) + { + snort_free(hsession->uri); + hsession->uri = nullptr; + } + if (hsession->content_type) + { + snort_free(hsession->content_type); + hsession->content_type = nullptr; + } + if (hsession->location) + { + snort_free(hsession->location); + hsession->location = nullptr; + } + if (hsession->body) + { + snort_free(hsession->body); + hsession->body = nullptr; + } + if (hsession->req_body) + { + snort_free(hsession->req_body); + hsession->req_body = nullptr; + } + if (hsession->xffAddr) + { + sfip_free(hsession->xffAddr); + hsession->xffAddr = nullptr; + } +} + +void AppIdData::appHttpSessionDataFree() +{ + int i; + + if (hsession == nullptr) + return; + + appHttpFieldClear(); + + for (i = 0; i < NUMBER_OF_PTYPES; i++) + { + if (nullptr != hsession->new_field[i]) + { + snort_free(hsession->new_field[i]); + hsession->new_field[i] = nullptr; + } + } + if (hsession->fflow) + { + snort_free(hsession->fflow); + hsession->fflow = nullptr; + } + if (hsession->via) + { + snort_free(hsession->via); + hsession->via = nullptr; + } + if (hsession->content_type) + { + snort_free(hsession->content_type); + hsession->content_type = nullptr; + } + if (hsession->response_code) + { + snort_free(hsession->response_code); + hsession->response_code = nullptr; + } + + snort_free(hsession); + hsession = nullptr; +} + +void AppIdData::appDNSSessionDataFree() +{ + if (dsession ) + { + if (dsession->host) + { + snort_free(dsession->host); + dsession->host = nullptr; + } + snort_free(dsession); + dsession = nullptr; + } +} + +void AppIdData::appTlsSessionDataFree() +{ + if (tsession ) + { + if (tsession->tls_host) + snort_free(tsession->tls_host); + if (tsession->tls_cname) + snort_free(tsession->tls_cname); + if (tsession->tls_orgUnit) + snort_free(tsession->tls_orgUnit); + snort_free(tsession); + tsession = nullptr; + } +} + +void AppIdData::AppIdFlowdataFree() +{ + AppIdFlowData* tmp_fd; + + while ((tmp_fd = flowData)) + { + flowData = tmp_fd->next; + if (tmp_fd->fd_data && tmp_fd->fd_free) + tmp_fd->fd_free(tmp_fd->fd_data); + tmp_fd->next = fd_free_list; + fd_free_list = tmp_fd; + } +} + +void AppIdData::appSharedDataDelete() +{ + RNAServiceSubtype* rna_service_subtype; + + /*check daq flag */ + appIdStatsUpdate(this); + + if (ssn) + FailInProcessService(this, pAppidActiveConfig); + AppIdFlowdataFree(); + + if (thirdparty_appid_module) + { + thirdparty_appid_module->session_delete(tpsession, 0); + tpsession = nullptr; + } + + snort_free(clientVersion); + snort_free(serviceVendor); + snort_free(serviceVersion); + snort_free(netbios_name); + while ((rna_service_subtype = subtype)) + { + subtype = rna_service_subtype->next; + snort_free(*(void**)&rna_service_subtype->service); + snort_free(*(void**)&rna_service_subtype->vendor); + snort_free(*(void**)&rna_service_subtype->version); + snort_free(rna_service_subtype); + } + if (candidate_service_list) + { + sflist_free(candidate_service_list); + candidate_service_list = nullptr; + } + + if (candidate_client_list) + { + sflist_free(candidate_client_list); + candidate_client_list = nullptr; + } + snort_free(username); + snort_free(netbiosDomain); + snort_free(payloadVersion); + appHttpSessionDataFree(); + appTlsSessionDataFree(); + appDNSSessionDataFree(); + tsession = nullptr; + + snort_free(firewallEarlyData); + firewallEarlyData = nullptr; + + // should be freed by flow + // appSharedDataFree(sharedData); +} + +void AppIdFlowdataFini() +{ + AppIdFlowData* tmp_fd; + + while ((tmp_fd = fd_free_list)) + { + fd_free_list = fd_free_list->next; + snort_free(tmp_fd); + } +} + +void* AppIdFlowdataGet(AppIdData* flowp, unsigned id) +{ + AppIdFlowData* tmp_fd; + + for (tmp_fd = flowp->flowData; tmp_fd && tmp_fd->fd_id != id; tmp_fd = tmp_fd->next) + ; + return tmp_fd ? tmp_fd->fd_data : nullptr; +} + +void* AppIdFlowdataRemove(AppIdData* flowp, unsigned id) +{ + AppIdFlowData** pfd; + AppIdFlowData* fd; + + for (pfd = &flowp->flowData; *pfd && (*pfd)->fd_id != id; pfd = &(*pfd)->next) + ; + if ((fd = *pfd)) + { + *pfd = fd->next; + fd->next = fd_free_list; + fd_free_list = fd; + return fd->fd_data; + } + return nullptr; +} + +void AppIdFlowdataDelete(AppIdData* flowp, unsigned id) +{ + AppIdFlowData** pfd; + AppIdFlowData* fd; + + for (pfd = &flowp->flowData; *pfd && (*pfd)->fd_id != id; pfd = &(*pfd)->next) + ; + if ((fd = *pfd)) + { + *pfd = fd->next; + if (fd->fd_data && fd->fd_free) + fd->fd_free(fd->fd_data); + fd->next = fd_free_list; + fd_free_list = fd; + } +} + +void AppIdFlowdataDeleteAllByMask(AppIdData* flowp, unsigned mask) +{ + AppIdFlowData** pfd; + AppIdFlowData* fd; + + pfd = &flowp->flowData; + while (*pfd) + { + if ((*pfd)->fd_id & mask) + { + fd = *pfd; + *pfd = fd->next; + if (fd->fd_data && fd->fd_free) + fd->fd_free(fd->fd_data); + fd->next = fd_free_list; + fd_free_list = fd; + } + else + { + pfd = &(*pfd)->next; + } + } +} + +int AppIdFlowdataAdd(AppIdData* flowp, void* data, unsigned id, AppIdFreeFCN fcn) +{ + AppIdFlowData* tmp_fd; + + if (fd_free_list) + { + tmp_fd = fd_free_list; + fd_free_list = tmp_fd->next; + } + else + tmp_fd = (AppIdFlowData*)snort_alloc(sizeof(AppIdFlowData)); + + tmp_fd->fd_id = id; + tmp_fd->fd_data = data; + tmp_fd->fd_free = fcn; + tmp_fd->next = flowp->flowData; + flowp->flowData = tmp_fd; + return 0; +} + +int AppIdFlowdataAddId(AppIdData* flowp, uint16_t port, const RNAServiceElement* svc_element) +{ + if (flowp->serviceData) + return -1; + flowp->serviceData = svc_element; + flowp->service_port = port; + return 0; +} + +#ifdef RNA_DEBUG_EXPECTED_FLOWS +static void flowAppSharedDataDelete(AppIdData* sharedData) +{ + _dpd.errMsg("Deleting %p\n",sharedData); + appSharedDataDelete(sharedData); +} + +#endif + +AppIdData* AppIdEarlySessionCreate( + AppIdData*, const Packet* /*ctrlPkt*/, const sfip_t* cliIp, uint16_t cliPort, const + sfip_t* srvIp, + uint16_t srvPort, IpProtocol proto, int16_t app_id, int /*flags*/) +{ + char src_ip[INET6_ADDRSTRLEN]; + char dst_ip[INET6_ADDRSTRLEN]; + // FIXIT - not needed until crtlPkt expectedSession is supported + //struct _ExpectNode** node; + AppIdData* data; + enum PktType protocol = ( enum PktType )proto; + + if (app_id_debug_session_flag) + { + sfip_ntop(cliIp, src_ip, sizeof(src_ip)); + sfip_ntop(srvIp, dst_ip, sizeof(dst_ip)); + } + + data = appSharedDataAlloc(proto, cliIp); + if (data) + data->common.policyId = appIdPolicyId; + + // FIXIT - expect session control packet support not ported to snort3 yet + //node = (flags & APPID_EARLY_SESSION_FLAG_FW_RULE) ? &ctrlPkt->expectedSession : nullptr; + + // FIXIT - 2.9.x set_application_protocol_id_expected has several new parameters, need to look + // into what is required to support those here. + if (stream.set_application_protocol_id_expected(/*crtlPkt,*/ cliIp, cliPort, srvIp, srvPort, + protocol, app_id, data) ) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s failed to create a related flow for %s-%u -> %s-%u %u\n", + app_id_debug_session, + src_ip, (unsigned)cliPort, dst_ip, (unsigned)srvPort, (unsigned)proto); + data->appSharedDataDelete(); + return nullptr; + } + else if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s created a related flow for %s-%u -> %s-%u %u\n", + app_id_debug_session, + src_ip, (unsigned)cliPort, dst_ip, (unsigned)srvPort, (unsigned)proto); + + return data; +} + diff --git a/src/network_inspectors/appid/appid_flow_data.h b/src/network_inspectors/appid/appid_flow_data.h new file mode 100644 index 000000000..45c562119 --- /dev/null +++ b/src/network_inspectors/appid/appid_flow_data.h @@ -0,0 +1,355 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_flow_data.h author Sourcefire Inc. + +#ifndef APPID_SESSION_H +#define APPID_SESSION_H + +// AppId configuration data structures and access methods + +#include +#include + +#include "protocols/packet.h" +#include "utils/sflsq.h" + +#include "appid.h" +#include "appid_api.h" +#include "application_ids.h" +#include "flow_error.h" +#include "length_app_cache.h" +#include "service_state.h" +#include "thirdparty_appid_api.h" +#include "thirdparty_appid_types.h" + +#define SF_DEBUG_FILE stdout +#define NUMBER_OF_PTYPES 9 + +#define APPID_SESSION_DATA_NONE 0 + +#define APPID_SESSION_DATA_DHCP_FP_DATA 2 +#define APPID_SESSION_DATA_SMB_DATA 4 +#define APPID_SESSION_DATA_DHCP_INFO 5 + +#define APPID_SESSION_DATA_SERVICE_MODSTATE_BIT 0x20000000 +#define APPID_SESSION_DATA_CLIENT_MODSTATE_BIT 0x40000000 +#define APPID_SESSION_DATA_DETECTOR_MODSTATE_BIT 0x80000000 + +#define APPID_SESSION_BIDIRECTIONAL_CHECKED \ + (APPID_SESSION_INITIATOR_CHECKED | \ + APPID_SESSION_RESPONDER_CHECKED) +#define APPID_SESSION_DO_RNA \ + (APPID_SESSION_RESPONDER_MONITORED | \ + APPID_SESSION_INITIATOR_MONITORED | APPID_SESSION_DISCOVER_USER | \ + APPID_SESSION_SPECIAL_MONITORED) + +struct RNAServiceElement; +struct RNAServiceSubtype; +struct RNAClientAppModule; + +enum RNA_INSPECTION_STATE +{ + RNA_STATE_NONE = 0, + RNA_STATE_DIRECT, + RNA_STATE_STATEFUL, + RNA_STATE_FINISHED +}; + +using AppIdFreeFCN = void(*)(void*); + +#define FINGERPRINT_UDP_FLAGS_XENIX 0x00000800 +#define FINGERPRINT_UDP_FLAGS_NT 0x00001000 +#define FINGERPRINT_UDP_FLAGS_MASK (FINGERPRINT_UDP_FLAGS_XENIX | FINGERPRINT_UDP_FLAGS_NT) + +struct AppIdFlowData +{ + AppIdFlowData* next; + unsigned fd_id; + void* fd_data; + AppIdFreeFCN fd_free; +}; + +#define APPID_SESSION_TYPE_IGNORE APPID_FLOW_TYPE_IGNORE +#define APPID_SESSION_TYPE_NORMAL APPID_FLOW_TYPE_NORMAL +#define APPID_SESSION_TYPE_TMP APPID_FLOW_TYPE_TMP + +struct APPID_SESSION_STRUCT_FLAG +{ + APPID_FLOW_TYPE flow_type; +}; + +struct CommonAppIdData +{ + APPID_SESSION_STRUCT_FLAG fsf_type; /* This must be first. */ + unsigned policyId; + //flags shared with other preprocessor via session attributes. + uint64_t flags; + sfip_t initiator_ip; + uint16_t initiator_port; +}; + +#define SCAN_HTTP_VIA_FLAG (1<<0) +#define SCAN_HTTP_USER_AGENT_FLAG (1<<1) +#define SCAN_HTTP_HOST_URL_FLAG (1<<2) +#define SCAN_SSL_HOST_FLAG (1<<4) +#define SCAN_HOST_PORT_FLAG (1<<5) +#define SCAN_HTTP_VENDOR_FLAG (1<<6) +#define SCAN_HTTP_XWORKINGWITH_FLAG (1<<7) +#define SCAN_HTTP_CONTENT_TYPE_FLAG (1<<8) + +struct fflow_info +{ + uint32_t sip; + uint32_t dip; + uint16_t sport; + uint16_t dport; + IpProtocol protocol; + AppId appId; + int flow_prepared; +}; + +struct HttpRewriteableFields +{ + char* str; +}; + +struct httpSession +{ + char* host; + char* url; + char* uri; + char* via; + char* useragent; + char* response_code; + char* referer; + char* cookie; + char* content_type; + char* location; + char* body; + char* req_body; + char* server; + char* x_working_with; + char* new_field[HTTP_FIELD_MAX+1]; + + uint16_t uriOffset; + uint16_t uriEndOffset; + uint16_t cookieOffset; + uint16_t cookieEndOffset; + + fflow_info* fflow; + + int chp_finished; + AppId chp_candidate; + AppId chp_alt_candidate; + int chp_hold_flow; + int ptype_req_counts[NUMBER_OF_PTYPES]; + int total_found; + unsigned app_type_flags; + int num_matches; + int num_scans; + int get_offsets_from_rebuilt; + bool skip_simple_detect; // Flag to indicate if simple detection of client ID, payload ID, + // etc + // should be skipped + sfip_t* xffAddr; + const char** xffPrecedence; + int numXffFields; + +#if RESPONSE_CODE_PACKET_THRESHHOLD + unsigned response_code_packets; +#endif +}; + +// For dnsSession.state: +#define DNS_GOT_QUERY 0x01 +#define DNS_GOT_RESPONSE 0x02 + +struct dnsSession +{ + uint8_t state; // state + uint8_t host_len; // for host + uint8_t response_type; // response: RCODE + uint16_t id; // DNS msg ID + uint16_t host_offset; // for host + uint16_t record_type; // query: QTYPE + uint32_t ttl; // response: TTL + char* host; // host (usually query, but could be response for reverse lookup) +}; + +struct _RNAServiceSubtype; + +struct tlsSession +{ + char* tls_host; + int tls_host_strlen; + char* tls_cname; + int tls_cname_strlen; + char* tls_orgUnit; + int tls_orgUnit_strlen; +}; + +class AppIdData : public FlowData +{ +public: + AppIdData() : FlowData(flow_id) { service_ip.clear(); } + ~AppIdData(); + + void reset() + { *this = AppIdData(); } + + + CommonAppIdData common; + AppIdData* next = nullptr; + Flow* ssn = nullptr; + + sfip_t service_ip; + uint16_t service_port = 0; + IpProtocol proto = IpProtocol::PROTO_NOT_SET; + uint8_t previous_tcp_flags = 0; + + AppIdFlowData* flowData = nullptr; + + // AppId matching service side + AppId serviceAppId = APP_ID_NONE; + AppId portServiceAppId = APP_ID_NONE; + // RNAServiceElement for identifying detector + const RNAServiceElement* serviceData = nullptr; + RNA_INSPECTION_STATE rnaServiceState = RNA_STATE_NONE; + char* serviceVendor = nullptr; + char* serviceVersion = nullptr; + RNAServiceSubtype* subtype = nullptr; + AppIdServiceIDState* id_state = nullptr; + char* netbios_name = nullptr; + SF_LIST* candidate_service_list = nullptr; + unsigned int num_candidate_services_tried = 0; + int got_incompatible_services = 0; + + /**AppId matching client side */ + AppId ClientAppId = APP_ID_NONE; + AppId ClientServiceAppId = APP_ID_NONE; + char* clientVersion = nullptr; + /**RNAClientAppModule for identifying client detector*/ + const RNAClientAppModule* clientData = nullptr; + RNA_INSPECTION_STATE rnaClientState = RNA_STATE_NONE; + SF_LIST* candidate_client_list = nullptr; + unsigned int num_candidate_clients_tried = 0; + bool tried_reverse_service = false; + + /**AppId matching payload*/ + AppId payloadAppId = APP_ID_NONE; + AppId referredPayloadAppId = APP_ID_NONE; + AppId miscAppId = APP_ID_NONE; + + //appId determined by 3rd party library + AppId tpAppId = APP_ID_NONE; + AppId tpPayloadAppId = APP_ID_NONE; + + char* username = nullptr; + AppId usernameService = APP_ID_NONE; + + char* netbiosDomain = nullptr; + + uint32_t id = 0; + + httpSession* hsession = nullptr; + tlsSession* tsession = nullptr; + + unsigned scan_flags = 0; +#if RESPONSE_CODE_PACKET_THRESHHOLD + unsigned response_code_packets = 0; +#endif + + AppId referredAppId = APP_ID_NONE; + + AppId tmpAppId = APP_ID_NONE; + void* tpsession = nullptr; + uint16_t init_tpPackets = 0; + uint16_t resp_tpPackets = 0; + uint8_t tpReinspectByInitiator = 0; + char* payloadVersion = nullptr; + + uint16_t session_packet_count = 0; + int16_t snortId = 0; + + /* Length-based detectors. */ + LengthKey length_sequence; + + struct + { + uint32_t firstPktsecond; + uint32_t lastPktsecond; + uint64_t initiatorBytes; + uint64_t responderBytes; + } stats = {0, 0, 0, 0}; + + // Policy and rule ID for related flows (e.g. ftp-data) + AppIdData* expectedFlow = nullptr; + + //appIds picked from encrypted session. + struct + { + AppId serviceAppId; + AppId ClientAppId; + AppId payloadAppId; + AppId miscAppId; + AppId referredAppId; + } encrypted = { APP_ID_NONE, APP_ID_NONE, APP_ID_NONE, APP_ID_NONE, APP_ID_NONE }; + + // New fields introduced for DNS Blacklisting + dnsSession* dsession = nullptr; + + void* firewallEarlyData = nullptr; + AppId pastIndicator = APP_ID_NONE; + AppId pastForecast = APP_ID_NONE; + + bool is_http2 = false; + SEARCH_SUPPORT_TYPE search_support_type = SEARCH_SUPPORT_TYPE_UNKNOWN; + + static unsigned flow_id; + static void init() { flow_id = FlowData::get_flow_id(); } + + void appHttpFieldClear(); + void appHttpSessionDataFree(); + void appDNSSessionDataFree(); + void appTlsSessionDataFree(); + void AppIdFlowdataFree(); + void appSharedDataDelete(); +}; + +inline void setAppIdFlag(AppIdData* flow, uint64_t flags) +{ flow->common.flags |= flags; } + +inline void clearAppIdFlag(AppIdData* flow, uint64_t flags) +{ flow->common.flags &= ~flags; } + +inline uint64_t getAppIdFlag(AppIdData* flow, uint64_t flags) +{ return (flow->common.flags & flags); } + +void AppIdFlowdataFini(); +void* AppIdFlowdataGet(AppIdData*, unsigned id); +int AppIdFlowdataAdd(AppIdData*, void* data, unsigned id, AppIdFreeFCN); +void* AppIdFlowdataRemove(AppIdData*, unsigned id); +void AppIdFlowdataDelete(AppIdData*, unsigned id); +void AppIdFlowdataDeleteAllByMask(AppIdData*, unsigned mask); +AppIdData* AppIdEarlySessionCreate(AppIdData*, const Packet* ctrlPkt, const sfip_t* cliIp, + uint16_t cliPort, const sfip_t* srvIp, uint16_t srvPort, IpProtocol proto, int16_t app_id, int flags); +int AppIdFlowdataAddId(AppIdData*, uint16_t port, const RNAServiceElement*); + +#endif diff --git a/src/network_inspectors/appid/appid_inspector.cc b/src/network_inspectors/appid/appid_inspector.cc new file mode 100644 index 000000000..ca7d969b2 --- /dev/null +++ b/src/network_inspectors/appid/appid_inspector.cc @@ -0,0 +1,149 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_inspector.cc author davis mcpherson +// Created on: May 10, 2016 + +#include "appid_inspector.h" +#include "profiler/profiler.h" +#include "fw_appid.h" + +//------------------------------------------------------------------------- +// class stuff +//------------------------------------------------------------------------- + +AppIdInspector::AppIdInspector(const AppIdModuleConfig* pc) +{ + assert(pc); + config = pc; +} + +AppIdInspector::~AppIdInspector() +{ + delete config; +} + +bool AppIdInspector::configure(SnortConfig*) +{ + active_config = new AppIdConfig( ( AppIdModuleConfig* )config); + return active_config->init_appid(); + + // FIXIT some of this stuff may be needed in some fashion... +#ifdef REMOVED_WHILE_NOT_IN_USE + _dpd.registerGeAppId(getOpenAppId); + if (!thirdparty_appid_module) + _dpd.streamAPI->register_http_header_callback(httpHeaderCallback); + _dpd.registerSslAppIdLookup(sslAppGroupIdLookup); + + // FIXIT AppID will need to register for SIP events for sip detection to work... + if (_dpd.streamAPI->service_event_subscribe(PP_SIP, SIP_EVENT_TYPE_SIP_DIALOG, + SipSessionSnortCallback) == false) + DynamicPreprocessorFatalMessage("failed to subscribe to SIP_DIALOG\n"); +#endif +} + +void AppIdInspector::show(SnortConfig*) +{ + LogMessage("AppId Configuration\n"); + + LogMessage(" Detector Path: %s\n", config->app_detector_dir); + LogMessage(" appStats Files: %s\n", config->app_stats_filename); + LogMessage(" appStats Period: %lu secs\n", config->app_stats_period); + LogMessage(" appStats Rollover Size: %lu bytes\n", + config->app_stats_rollover_size); + LogMessage(" appStats Rollover time: %lu secs\n", + config->app_stats_rollover_time); + LogMessage("\n"); +} + +void AppIdInspector::eval(Packet* pkt) +{ + Profile profile(appidPerfStats); + + appid_stats.packet_count++; + fwAppIdSearch(pkt); +} + +//------------------------------------------------------------------------- +// api stuff +//------------------------------------------------------------------------- + +static Module* mod_ctor() +{ + return new AppIdModule; +} + +static void mod_dtor(Module* m) +{ + delete m; +} + +static void appid_inspector_init() +{ + AppIdData::init(); +} + +static Inspector* appid_inspector_ctor(Module* m) +{ + AppIdModule* mod = (AppIdModule*)m; + return new AppIdInspector(mod->get_data()); +} + +static void appid_inspector_dtor(Inspector* p) +{ + delete p; +} + +const InspectApi appid_inspector_api = +{ + { + PT_INSPECTOR, + sizeof(InspectApi), + INSAPI_VERSION, + 0, + API_RESERVED, + API_OPTIONS, + MOD_NAME, + MOD_HELP, + mod_ctor, + mod_dtor + }, + IT_NETWORK, + (uint16_t)PktType::ANY_IP, + nullptr, // buffers + nullptr, // service + appid_inspector_init, // pinit + nullptr, // pterm + nullptr, // tinit + nullptr, // tterm + appid_inspector_ctor, + appid_inspector_dtor, + nullptr, // ssn + nullptr // reset +}; + +#ifdef BUILDING_SO +SO_PUBLIC const BaseApi* snort_plugins[] = +{ + &appid_inspector_api.base, + nullptr +}; +#else +const BaseApi* nin_appid_inspector = &appid_inspector_api.base; +#endif + diff --git a/src/network_inspectors/appid/appid_inspector.h b/src/network_inspectors/appid/appid_inspector.h new file mode 100644 index 000000000..1baab27b7 --- /dev/null +++ b/src/network_inspectors/appid/appid_inspector.h @@ -0,0 +1,52 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_inspector.h author davis mcpherson +// Created on: May 10, 2016 + +#ifndef APPID_INSPECTOR_H +#define APPID_INSPECTOR_H + +#include "main/snort_config.h" +#include "protocols/packet.h" + +#include "appid_module.h" + +class AppIdInspector : public Inspector +{ +public: + + AppIdInspector(const AppIdModuleConfig*); + ~AppIdInspector(); + + bool configure(SnortConfig*) override; + void show(SnortConfig*) override; + void eval(Packet*) override; + + AppIdConfig* get_active_config() + { + return active_config; + } + +private: + + const AppIdModuleConfig* config = nullptr; + AppIdConfig* active_config = nullptr; +}; + +#endif diff --git a/src/network_inspectors/appid/appid_module.cc b/src/network_inspectors/appid/appid_module.cc new file mode 100644 index 000000000..e0d2dd95d --- /dev/null +++ b/src/network_inspectors/appid/appid_module.cc @@ -0,0 +1,160 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_module.cc author davis mcpherson +// Created on: May 10, 2016 + +#include + +#include "appid_module.h" +#include "profiler/profiler.h" +#include "utils/util.h" + +using namespace std; + +//------------------------------------------------------------------------- +// appid module +//------------------------------------------------------------------------- + +THREAD_LOCAL ProfileStats appidPerfStats; + +const PegInfo appid_pegs[] = +{ + { "packet_count", "count of packets processed by appid" }, + { "dns_udp_count", "count of dns flows over udp discovered" }, + { "dns_tcp_count", "count of dns flows over tcp discovered" }, + { "smtp_count", "count of smtp flows discovered" }, + { "smtps_count", "count of smtps flows discovered" }, + { nullptr, nullptr } +}; + +THREAD_LOCAL AppIdStats appid_stats; + +static const Parameter s_params[] = +{ + { "conf", Parameter::PT_STRING, nullptr, nullptr, + "RNA configuration file" }, + { "memcap", Parameter::PT_INT, "1048576:3221225472", "268435456", + "time period for collecting and logging AppId statistics" }, + { "app_stats_filename", Parameter::PT_STRING, nullptr, nullptr, + "Filename for logging AppId statistics" }, + { "app_stats_period", Parameter::PT_INT, "0:", "300", + "time period for collecting and logging AppId statistics" }, + { "app_stats_rollover_size", Parameter::PT_INT, "0:", "20971520", + "max file size for AppId stats before rolling over the log file" }, + { "app_stats_rollover_time", Parameter::PT_INT, "0:", "86400", + "max time period for collection AppId stats before rolling over the log file" }, + { "app_detector_dir", Parameter::PT_STRING, nullptr, nullptr, + "directory to load AppId detectors from" }, + { "instance_id", Parameter::PT_INT, "0:", "0", + "instance id - need more details for what this is" }, + { "debug", Parameter::PT_BOOL, nullptr, "false", + "enable AppId debug logging" }, + { "dump_ports", Parameter::PT_BOOL, nullptr, "false", + "enable dump of AppId port information" }, + { "thirdparty_appid_dir", Parameter::PT_STRING, nullptr, nullptr, + "directory to load thirdparty AppId detectors from" }, + + { nullptr, Parameter::PT_MAX, nullptr, nullptr, nullptr } +}; + +// FIXIT-M: Add appid_rules back in once we start using it. +#ifdef REMOVED_WHILE_NOT_IN_USE +static const RuleMap appid_rules[] = +{ + { 0 /* rule id */, "description" }, + { 0, nullptr } +}; +#endif + +AppIdModule::AppIdModule() : + Module(MOD_NAME, MOD_HELP, s_params) +{ + config = nullptr; +} + +AppIdModule::~AppIdModule() +{ +} + +ProfileStats* AppIdModule::get_profile() const +{ + return &appidPerfStats; +} + +const AppIdModuleConfig* AppIdModule::get_data() +{ + AppIdModuleConfig* temp = config; + config = nullptr; + return temp; +} + +bool AppIdModule::set(const char*, Value& v, SnortConfig*) +{ + if ( v.is("conf") ) + config->conf_file = snort_strdup(v.get_string()); + else if ( v.is("memcap") ) + config->memcap = v.get_long(); + else if ( v.is("app_stats_filename") ) + config->app_stats_filename = snort_strdup(v.get_string()); + else if ( v.is("app_stats_period") ) + config->app_stats_period = v.get_long(); + else if ( v.is("app_stats_rollover_size") ) + config->app_stats_rollover_size = v.get_long(); + else if ( v.is("app_stats_rollover_time") ) + config->app_stats_rollover_time = v.get_long(); + else if ( v.is("app_detector_dir") ) + config->app_detector_dir = snort_strdup(v.get_string()); + else if ( v.is("thirdparty_appid_dir") ) + config->thirdparty_appid_dir = snort_strdup(v.get_string()); + else if ( v.is("instance_id") ) + config->instance_id = v.get_long(); + else if ( v.is("debug") ) + config->debug = v.get_bool(); + else if ( v.is("dump_ports") ) + config->dump_ports = v.get_bool(); + else + return false; + + return true; +} + +bool AppIdModule::begin(const char* /*fqn*/, int, SnortConfig*) +{ + if ( config ) + return false; + + config = new AppIdModuleConfig; + return true; +} + +bool AppIdModule::end(const char*, int, SnortConfig*) +{ + return true; +} + +const PegInfo* AppIdModule::get_pegs() const +{ + return appid_pegs; +} + +PegCount* AppIdModule::get_counts() const +{ + return (PegCount*)&appid_stats; +} + diff --git a/src/network_inspectors/appid/appid_module.h b/src/network_inspectors/appid/appid_module.h new file mode 100644 index 000000000..fe49cd981 --- /dev/null +++ b/src/network_inspectors/appid/appid_module.h @@ -0,0 +1,69 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_module.h author davis mcpherson +// Created on: May 10, 2016 + +#ifndef APPID_MODULE_H +#define APPID_MODULE_H + +#include "main/snort_types.h" +#include "framework/module.h" +#include "appid_config.h" + +extern THREAD_LOCAL ProfileStats appidPerfStats; + +//------------------------------------------------------------------------- +// stream module +//------------------------------------------------------------------------- + +#define MOD_NAME "appid" +#define MOD_HELP "application and service identification" + +struct AppIdStats +{ + PegCount packet_count; + PegCount dns_udp_count; + PegCount dns_tcp_count; + PegCount smtp_count; + PegCount smtps_count; +}; + +extern THREAD_LOCAL AppIdStats appid_stats; + +class AppIdModule : public Module +{ +public: + AppIdModule(); + ~AppIdModule(); + + bool begin(const char*, int, SnortConfig*) override; + bool set(const char*, Value&, SnortConfig*) override; + bool end(const char*, int, SnortConfig*) override; + + const PegInfo* get_pegs() const override; + PegCount* get_counts() const override; + ProfileStats* get_profile() const override; + + const AppIdModuleConfig* get_data(); + +private: + AppIdModuleConfig* config; +}; + +#endif diff --git a/src/network_inspectors/appid/appid_stats.cc b/src/network_inspectors/appid/appid_stats.cc new file mode 100644 index 000000000..352268726 --- /dev/null +++ b/src/network_inspectors/appid/appid_stats.cc @@ -0,0 +1,528 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_stats.cc author Sourcefire Inc. + +#include "appid_stats.h" + +#include +#include +#include +#include + +#include "log/messages.h" +#include "loggers/unified2_common.h" +#include "utils/sflsq.h" +#include "utils/util.h" + +#include "appid_api.h" +#include "appid_flow_data.h" +#include "fw_appid.h" +#include "util/fw_avltree.h" +#include "util/output_file.h" + +#define URLCATBUCKETS 100 +#define URLREPBUCKETS 5 + +// FIXIT - find out where this is defined in snort 2.x and define appropriately here +#if 1 +#define UNIFIED2_IDS_EVENT_APPSTAT 1 +#endif + +static time_t bucketStart; +static time_t bucketInterval; +static time_t bucketEnd; + +struct AppIdStatRecord +{ + uint32_t app_id; + uint32_t initiatorBytes; + uint32_t responderBytes; +}; + +#ifdef WIN32 +#pragma pack(push,app_stats,1) +#else +#pragma pack(1) +#endif + +struct AppIdStatOutputRecord +{ + char appName[MAX_EVENT_APPNAME_LEN]; + uint32_t initiatorBytes; + uint32_t responderBytes; +}; + +#ifdef WIN32 +#pragma pack(pop,app_stats) +#else +#pragma pack() +#endif + +struct StatsBucket +{ + uint32_t startTime; + FwAvlTree* appsTree; + struct + { + size_t txByteCnt; + size_t rxByteCnt; + } totalStats; + uint32_t appRecordCnt; +}; + +static SF_LIST* currBuckets; +static SF_LIST* logBuckets; + +static const char* appFilePath; + +static FILE* appfp; + +static size_t appSize; + +static time_t appTime; + +Serial_Unified2_Header header; + +static size_t rollSize; +static time_t rollPeriod; +static bool enableAppStats; + +static void endStats2Period(void); +static void startStats2Period(time_t startTime); +static struct StatsBucket* getStatsBucket(time_t startTime); +static void dumpStats2(void); + +static void deleteRecord(void* record) +{ snort_free(record); } + +static inline time_t get_time() +{ + auto now = time(nullptr); + return now - (now % bucketInterval); +} + +void appIdStatsUpdate(AppIdData* session) +{ + if ( !enableAppStats ) + return; + + time_t now = get_time(); + + if (now >= bucketEnd) + { + endStats2Period(); + dumpStats2(); + startStats2Period(now); + } + + time_t bucketTime = session->stats.firstPktsecond - + (session->stats.firstPktsecond % bucketInterval); + + StatsBucket* bucket = getStatsBucket(bucketTime); + if ( !bucket ) + return; + + bucket->totalStats.txByteCnt += session->stats.initiatorBytes; + bucket->totalStats.rxByteCnt += session->stats.responderBytes; + + const uint32_t web_app_id = pickPayloadId(session); + if (web_app_id > APP_ID_NONE) + { + const uint32_t app_id = web_app_id; + AppIdStatRecord* record = (AppIdStatRecord*)fwAvlLookup(app_id, bucket->appsTree); + if ( !record ) + { + record = (AppIdStatRecord*)snort_calloc(sizeof(struct AppIdStatRecord)); + if (fwAvlInsert(app_id, record, bucket->appsTree) == 0) + { + record->app_id = app_id; + bucket->appRecordCnt += 1; +#ifdef DEBUG_STATS + fprintf(SF_DEBUG_FILE, "New App: %u Count %u\n", record->app_id, + bucket->appRecordCnt); +#endif + } + else + { + // FIXIT-M really? we just silently ignore an allocation failure? + snort_free(record); + record = nullptr; + } + } + + if (record) + { + record->initiatorBytes += session->stats.initiatorBytes; + record->responderBytes += session->stats.responderBytes; + } + } + + const uint32_t service_app_id = pickServiceAppId(session); + if ((service_app_id) && + (service_app_id != web_app_id)) + { + const uint32_t app_id = service_app_id; + AppIdStatRecord* record = (AppIdStatRecord*)fwAvlLookup(app_id, bucket->appsTree); + if ( !record ) + { + record = (AppIdStatRecord*)snort_calloc(sizeof(struct AppIdStatRecord)); + if (fwAvlInsert(app_id, record, bucket->appsTree) == 0) + { + record->app_id = app_id; + bucket->appRecordCnt += 1; +#ifdef DEBUG_STATS + fprintf(SF_DEBUG_FILE, "New App: %u Count %u\n", record->app_id, + bucket->appRecordCnt); +#endif + } + else + { + // FIXIT-M really? don't ignore insert failure? add a stat here + snort_free(record); + record = nullptr; + } + } + + if (record) + { + record->initiatorBytes += session->stats.initiatorBytes; + record->responderBytes += session->stats.responderBytes; + } + } + + const uint32_t client_app_id = pickClientAppId(session); + if (client_app_id > APP_ID_NONE + && client_app_id != service_app_id + && client_app_id != web_app_id) + { + const uint32_t app_id = client_app_id; + + AppIdStatRecord* record = (AppIdStatRecord*)fwAvlLookup(app_id, bucket->appsTree); + if ( !record ) + { + record = (AppIdStatRecord*)snort_calloc(sizeof(struct AppIdStatRecord)); + if (fwAvlInsert(app_id, record, bucket->appsTree) == 0) + { + record->app_id = app_id; + bucket->appRecordCnt += 1; +#ifdef DEBUG_STATS + fprintf(SF_DEBUG_FILE, "New App: %u Count %u\n", record->app_id, + bucket->appRecordCnt); +#endif + } + else + { + // FIXIT-M really? we just silently ignore an allocation failure? + snort_free(record); + record = nullptr; + } + } + + if (record) + { + record->initiatorBytes += session->stats.initiatorBytes; + record->responderBytes += session->stats.responderBytes; + } + } +} + +void appIdStatsInit(AppIdModuleConfig* config) +{ + if (config->app_stats_filename) + { + enableAppStats = true; + appFilePath = config->app_stats_filename; + + rollPeriod = config->app_stats_rollover_time; + rollSize = config->app_stats_rollover_size; + bucketInterval = config->app_stats_period; + + time_t now = get_time(); + startStats2Period(now); + appfp = nullptr; + } + else + enableAppStats = false; +} + +static void appIdStatsCloseFiles() +{ + if (appfp) + { + fclose(appfp); + appfp = nullptr; + } +} + +void appIdStatsReinit() +{ + // FIXIT-L J really should something like: + // if ( !stats_files_are_open() ) + // return; + if (!enableAppStats) + return; + + appIdStatsCloseFiles(); +} + +void appIdStatsIdleFlush() +{ + if (!enableAppStats) + return; + + time_t now = get_time(); + if (now >= bucketEnd) + { + endStats2Period(); + dumpStats2(); + startStats2Period(now); + } +} + +static void startStats2Period(time_t startTime) +{ + bucketStart = startTime; + bucketEnd = bucketStart + bucketInterval; +} + +static void endStats2Period(void) +{ + SF_LIST* bucketList = logBuckets; + logBuckets = currBuckets; + currBuckets = bucketList; +} + +static StatsBucket* getStatsBucket(time_t startTime) +{ + StatsBucket* bucket = nullptr; + + if ( !currBuckets ) + { + currBuckets = sflist_new(); +# ifdef DEBUG_STATS + fprintf(SF_DEBUG_FILE, "New Stats Bucket List\n"); +# endif + } + + if ( !currBuckets ) + return nullptr; + + SF_LNODE* lNode = nullptr; + StatsBucket* lBucket = nullptr; + + for ( lBucket = (StatsBucket*)sflist_first(currBuckets, &lNode); lNode && lBucket; + lBucket = (StatsBucket*)sflist_next(&lNode) ) + { + if (startTime == lBucket->startTime) + { + bucket = lBucket; + break; + } + else if (startTime < lBucket->startTime) + { + bucket = (StatsBucket*)snort_calloc(sizeof(StatsBucket)); + bucket->startTime = startTime; + bucket->appsTree = fwAvlInit(); + sflist_add_before(currBuckets, lNode, bucket); + +#ifdef DEBUG_STATS + fprintf(SF_DEBUG_FILE, "New Bucket Time: %u before %u\n", + bucket->startTime, lBucket->startTime); +#endif + break; + } + } + + if ( !lNode ) + { + bucket = (StatsBucket*)snort_calloc(sizeof(StatsBucket)); + bucket->startTime = startTime; + bucket->appsTree = fwAvlInit(); + sflist_add_tail(currBuckets, bucket); + +#ifdef DEBUG_STATS + fprintf(SF_DEBUG_FILE, "New Bucket Time: %u at tail\n", bucket->startTime); +#endif + } + + return bucket; +} + +static void dumpStats2() +{ + struct StatsBucket* bucket = nullptr; + uint8_t* buffer; + uint32_t* buffPtr; + struct FwAvlNode* node; + struct AppIdStatRecord* record; + size_t buffSize; + time_t currTime = time(nullptr); + + if (logBuckets == nullptr) + return; + + while ((bucket = (struct StatsBucket*)sflist_remove_head(logBuckets)) != nullptr) + { + if (bucket->appRecordCnt) + { + buffSize = bucket->appRecordCnt * sizeof(struct AppIdStatOutputRecord) + + 4 * sizeof(uint32_t); + header.type = UNIFIED2_IDS_EVENT_APPSTAT; + header.length = buffSize - 2*sizeof(uint32_t); + buffer = (uint8_t*)snort_calloc(buffSize); +# ifdef DEBUG_STATS + fprintf(SF_DEBUG_FILE, "Write App Records %u Size: %lu\n", + bucket->appRecordCnt, buffSize); +# endif + } + else + buffer = nullptr; + + if (buffer) + { + buffPtr = (uint32_t*)buffer; + *buffPtr++ = htonl(header.type); + *buffPtr++ = htonl(header.length); + *buffPtr++ = htonl(bucket->startTime); + *buffPtr++ = htonl(bucket->appRecordCnt); + + for (node = fwAvlFirst(bucket->appsTree); node != nullptr; node = fwAvlNext(node)) + { + struct AppIdStatOutputRecord* recBuffPtr; + const char* appName; + bool cooked_client = false; + AppId app_id; + char tmpBuff[MAX_EVENT_APPNAME_LEN]; + + record = (struct AppIdStatRecord*)node->data; + app_id = record->app_id; + + recBuffPtr = (struct AppIdStatOutputRecord*)buffPtr; + + if (app_id >= 2000000000) + { + cooked_client = true; + app_id -= 2000000000; + } + + AppInfoTableEntry* entry = appInfoEntryGet(app_id, pAppidActiveConfig); + if (entry) + { + appName = entry->appName; + if (cooked_client) + { + snprintf(tmpBuff, MAX_EVENT_APPNAME_LEN, "_cl_%s",appName); + tmpBuff[MAX_EVENT_APPNAME_LEN-1] = 0; + appName = tmpBuff; + } + } + else if (app_id == APP_ID_UNKNOWN || app_id == APP_ID_UNKNOWN_UI) + appName = "__unknown"; + else if (app_id == APP_ID_NONE) + appName = "__none"; + else + { + ErrorMessage("invalid appid in appStatRecord (%u)\n", record->app_id); + if (cooked_client) + { + snprintf(tmpBuff, MAX_EVENT_APPNAME_LEN, "_err_cl_%u",app_id); + } + else + { + snprintf(tmpBuff, MAX_EVENT_APPNAME_LEN, "_err_%u",app_id); // ODP out of + // sync? + } + tmpBuff[MAX_EVENT_APPNAME_LEN-1] = 0; + appName = tmpBuff; + } + + memcpy(recBuffPtr->appName, appName, MAX_EVENT_APPNAME_LEN); + + /**buffPtr++ = htonl(record->app_id); */ + recBuffPtr->initiatorBytes = htonl(record->initiatorBytes); + recBuffPtr->responderBytes = htonl(record->responderBytes); + + buffPtr += sizeof(*recBuffPtr)/sizeof(*buffPtr); + } + + if (appFilePath) + { + if (!appfp) + { + appfp = openOutputFile(appFilePath, currTime); + appTime = currTime; + appSize = 0; + } + else if (((currTime - appTime) > rollPeriod) || + ((appSize + buffSize) > rollSize)) + { + appfp = rolloverOutputFile(appFilePath, appfp, currTime); + appTime = currTime; + appSize = 0; + } + if (appfp) + { + if ((fwrite(buffer, buffSize, 1, appfp) == 1) && (fflush(appfp) == 0)) + { + appSize += buffSize; + } + else + { + ErrorMessage( + "NGFW Rule Engine Failed to write to statistics file (%s): %s\n", + appFilePath, strerror(errno)); + fclose(appfp); + appfp = nullptr; + } + } + } + snort_free(buffer); + } + fwAvlDeleteTree(bucket->appsTree, deleteRecord); + snort_free(bucket); + } +} + +void appIdStatsFini() +{ + if (!enableAppStats) + return; + + /*flush the last stats period. */ + endStats2Period(); + dumpStats2(); + + if (!currBuckets) + return; + + while (auto bucket = (StatsBucket*)sflist_remove_head(currBuckets)) + { + fwAvlDeleteTree(bucket->appsTree, deleteRecord); + snort_free(bucket); + } + + snort_free(currBuckets); + + if (logBuckets) + snort_free(logBuckets); + + appIdStatsCloseFiles(); +} + diff --git a/src/network_inspectors/appid/appid_stats.h b/src/network_inspectors/appid/appid_stats.h new file mode 100644 index 000000000..7cbbf8133 --- /dev/null +++ b/src/network_inspectors/appid/appid_stats.h @@ -0,0 +1,34 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_stats.h author Sourcefire Inc. + +#ifndef APPID_STATS_H +#define APPID_STATS_H + +class AppIdData; +class AppIdModuleConfig; + +void appIdStatsUpdate(AppIdData*); +void appIdStatsInit(AppIdModuleConfig* config); +void appIdStatsReinit(); +void appIdStatsIdleFlush(); +void appIdStatsFini(); + +#endif diff --git a/src/network_inspectors/appid/application_ids.h b/src/network_inspectors/appid/application_ids.h new file mode 100644 index 000000000..f93e076d7 --- /dev/null +++ b/src/network_inspectors/appid/application_ids.h @@ -0,0 +1,1013 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// application_ids.h author Sourcefire Inc. + +#ifndef APPLICATION_IDS_H +#define APPLICATION_IDS_H + +#include + +enum ApplicationId : int32_t +{ + APP_ID_UNKNOWN = -1, // searched and not found any matching app id + APP_ID_NONE = 0, // AppId not searched + + APP_ID_3COM_TSMUX = 2, + APP_ID_8021Q = 3, + APP_ID_914CG = 4, + APP_ID_ACA_SERVICES = 5, + APP_ID_ACI = 6, + APP_ID_ACR_NEMA = 7, + APP_ID_ACTIVE_DIRECTORY = 8, + APP_ID_ACTIVESYNC = 9, + APP_ID_AD_BACKUP = 10, + APP_ID_AD_DRS = 11, + APP_ID_AD_DSAOP = 12, + APP_ID_AD_DSROL = 13, + APP_ID_AD_NSP = 14, + APP_ID_ADOBE = 15, + APP_ID_AD_RESTORE = 16, + APP_ID_ADRIVE = 17, + APP_ID_AD_XDS = 18, + APP_ID_AED512 = 19, + APP_ID_AFP = 20, + APP_ID_AH = 21, + APP_ID_AJP = 22, + APP_ID_ALIAS = 23, + APP_ID_AMAZON = 24, + APP_ID_ANET = 25, + APP_ID_ANSA_NOTIFY = 26, + APP_ID_ANSA_REX_TRADER = 27, + APP_ID_APPLE_ARP = 28, + APP_ID_APPLEJUICE = 29, + APP_ID_APPLESHARE = 30, + APP_ID_APPLETALK = 31, + APP_ID_APPLE_UPDATE = 32, + APP_ID_ARCISDMS = 33, + APP_ID_ARIEL = 34, + APP_ID_ARNS = 35, + APP_ID_ARP = 36, + APP_ID_ASA = 37, + APP_ID_ASTRAWEB = 38, + APP_ID_ATM_FATE = 39, + APP_ID_ATM_MPOA = 40, + APP_ID_AUDITD = 41, + APP_ID_AUDIT = 42, + APP_ID_AURORA = 43, + APP_ID_AVG = 44, + APP_ID_AVIRA = 45, + APP_ID_AVOCENT = 46, + APP_ID_BACKBLAZE = 47, + APP_ID_BACKPACK = 48, + APP_ID_BATTLEFIELD = 49, + APP_ID_BATTLE_NET = 50, + APP_ID_BEETPH = 51, + APP_ID_BFTP = 52, + APP_ID_BGMP = 53, + APP_ID_BH611 = 54, + APP_ID_BHEVENT = 55, + APP_ID_BHFHS = 56, + APP_ID_BHMDS = 57, + APP_ID_BING = 58, + APP_ID_BITDEFENDER = 59, + APP_ID_BITS = 60, + APP_ID_BITTORRENT = 61, + APP_ID_BLACKBOARD = 62, + APP_ID_BLACKJACK = 63, + APP_ID_BLAZEFS = 64, + APP_ID_BLIDM = 65, + APP_ID_BNET = 66, + APP_ID_CABLEPORT_AX = 67, + APP_ID_CAICCI = 68, + APP_ID_CAILIC = 69, + APP_ID_CAP = 70, + APP_ID_CDC = 71, + APP_ID_CFDPTKT = 72, + APP_ID_CHARGEN = 73, + APP_ID_CHECK_POINT = 74, + APP_ID_CISCO_DRP = 76, + APP_ID_CISCO_FNATIVE = 77, + APP_ID_CISCO_GDP = 78, + APP_ID_CISCO_SLA = 79, + APP_ID_CISCO_SYSMAINT = 80, + APP_ID_CISCO_TNATIVE = 81, + APP_ID_CITRIX_CGP = 82, + APP_ID_CITRIX_ICA = 83, + APP_ID_CITRIX_IMA = 84, + APP_ID_CITRIX_JEDI = 85, + APP_ID_CITRIX_LICENSING = 86, + APP_ID_CITRIX_ONLINE = 87, + APP_ID_CITRIX_RTMP = 88, + APP_ID_CITRIX_SLG = 89, + APP_ID_CITRIX_WANSCALER = 90, + APP_ID_CL1 = 91, + APP_ID_CLEARCASE = 92, + APP_ID_CLOANTO = 93, + APP_ID_CMIP = 94, + APP_ID_CODA_AUTH = 95, + APP_ID_COMMVAULT = 96, + APP_ID_COMPRESSNET = 97, + APP_ID_COMSCM = 98, + APP_ID_CORBA = 99, + APP_ID_CORERJD = 100, + APP_ID_COVIA_CI = 101, + APP_ID_CSISGWP = 102, + APP_ID_CSNET_NS = 103, + APP_ID_CTF = 104, + APP_ID_CVCHOSTD = 105, + APP_ID_DASP = 106, + APP_ID_DATEX_ASN = 107, + APP_ID_DBASE = 108, + APP_ID_DCAP = 109, + APP_ID_DCCP = 110, + APP_ID_DCP = 111, + APP_ID_DEC_AUTH = 112, + APP_ID_DEC_DEBUG = 113, + APP_ID_DECVMS = 114, + APP_ID_DEOS = 115, + APP_ID_DHCPV6 = 116, + APP_ID_DIGG = 117, + APP_ID_DIRECT_CONNECT = 118, + APP_ID_DIRECT = 119, + APP_ID_DIXIE = 120, + APP_ID_DLS = 121, + APP_ID_DNA_CML = 122, + APP_ID_DNSIX = 123, + APP_ID_DPSI = 124, + APP_ID_DROPBOX = 125, + APP_ID_DSFGW = 126, + APP_ID_DSP3270 = 127, + APP_ID_DSP = 128, + APP_ID_DSSETUP = 129, + APP_ID_DTAG = 130, + APP_ID_DTK = 131, + APP_ID_EBAY = 132, + APP_ID_EBAY_BID = 133, + APP_ID_EBAY_SEARCH = 134, + APP_ID_EBAY_WATCH = 135, + APP_ID_EBUDDY = 136, + APP_ID_EGP = 137, + APP_ID_EMBLNDT = 138, + APP_ID_EMFIS = 139, + APP_ID_ENTRUSTTIME = 140, + APP_ID_EPMAP = 141, + APP_ID_ERPC = 142, + APP_ID_ESET = 143, + APP_ID_ESP = 144, + APP_ID_ESRO = 145, + APP_ID_ETH = 146, + APP_ID_ETOS = 147, + APP_ID_SAFARI_MOBILE_DUMMY = 148, + APP_ID_EXCHANGE = 1780, + APP_ID_FACEBOOK_APPS = 149, + APP_ID_FARK = 150, + APP_ID_FARMVILLE = 151, + APP_ID_FASP = 152, + APP_ID_FASTTRACK = 153, + APP_ID_FATMEN = 154, + APP_ID_FILEMAKER = 155, + APP_ID_FILER_CX = 156, + APP_ID_FILESTUBE = 157, + APP_ID_FLASHGET = 158, + APP_ID_FLICKR = 159, + APP_ID_FLIXSTER = 160, + APP_ID_FOGBUGZ = 161, + APP_ID_F_PROT = 162, + APP_ID_FREECAST = 163, + APP_ID_FRIENDFEED = 164, + APP_ID_FTP_CONTROL = 165, + APP_ID_FTP_DATA = 166, + APP_ID_FTPSDATA = 167, + APP_ID_FTPS = 168, + APP_ID_FXP = 169, + APP_ID_GACP = 170, + APP_ID_GANGLIA = 171, + APP_ID_GENESIS_PPP = 172, + APP_ID_GENIE = 173, + APP_ID_GENRAD = 174, + APP_ID_GIGANEWS = 175, + APP_ID_GIOP = 176, + APP_ID_GIST = 177, + APP_ID_GOOGLE_APIS = 178, + APP_ID_GOOGLE_APP_ENGINE = 179, + APP_ID_GOOGLE_DOCS = 180, + APP_ID_GOOGLE_TALK_GADGET = 182, + APP_ID_GOOGLE_TALK = 183, + APP_ID_GOOGLE = 184, + APP_ID_GOOGLE_TRANSLATE = 185, + APP_ID_GOOGLE_VIDEO = 186, + APP_ID_GOTOMEETING = 187, + APP_ID_GPFS = 188, + APP_ID_GRE = 189, + APP_ID_GROUPWISE = 190, + APP_ID_GSIFTP = 191, + APP_ID_GSS_LICENSE = 192, + APP_ID_H_225 = 193, + APP_ID_H_245 = 194, + APP_ID_H_248 = 195, + APP_ID_H_323 = 196, + APP_ID_HASSLE = 197, + APP_ID_HDAP = 198, + APP_ID_HEMS = 199, + APP_ID_HIVESTOR = 200, + APP_ID_HL7 = 201, + APP_ID_HOPSTER = 202, + APP_ID_HOSTNAME = 203, + APP_ID_HOTFILE = 204, + APP_ID_HOTMAIL = 205, + APP_ID_HP_PERF = 206, + APP_ID_HP_VMM = 207, + APP_ID_HTTP_AUDIO = 208, + APP_ID_HTTPMGT = 209, + APP_ID_HTTP_VIDEO = 210, + APP_ID_HYPER_G = 211, + APP_ID_IASD = 212, + APP_ID_IBM_OPC = 213, + APP_ID_ICA_BROWSER = 214, + APP_ID_ICAD = 215, + APP_ID_ICAP = 216, + APP_ID_ICA = 217, + APP_ID_ICESHARE = 218, + APP_ID_ICMP = 219, + APP_ID_ICMPV6 = 220, + APP_ID_ICP = 221, + APP_ID_ICQ2GO = 222, + APP_ID_IDP = 223, + APP_ID_IGMP = 224, + APP_ID_IKE = 225, + APP_ID_IMGAMES = 226, + APP_ID_IMSP = 227, + APP_ID_INBUSINESS = 228, + APP_ID_INFORMIX = 229, + APP_ID_INFOSEEK = 230, + APP_ID_INFOSTORE = 231, + APP_ID_INGRES_NET = 232, + APP_ID_IPCOMP = 233, + APP_ID_IPIP = 234, + APP_ID_IP = 235, + APP_ID_IPSEC = 236, + APP_ID_IPV6 = 237, + APP_ID_IPX = 238, + APP_ID_IRC = 239, + APP_ID_IRCU = 240, + APP_ID_IS_99 = 241, + APP_ID_ISAKMP = 242, + APP_ID_ISCHAT = 243, + APP_ID_ISI_GRAPHICS = 244, + APP_ID_ISOIP = 245, + APP_ID_ISO_TSAP = 246, + APP_ID_JARGON = 247, + APP_ID_KASPERSKY = 248, + APP_ID_KBLOCK = 249, + APP_ID_KFTPDATA = 250, + APP_ID_KFTP = 251, + APP_ID_KIS = 252, + APP_ID_KNETCMP = 253, + APP_ID_KRYPTOLAN = 254, + APP_ID_KTELNET = 255, + APP_ID_KUGOO = 256, + APP_ID_KVM = 257, + APP_ID_KWDB = 258, + APP_ID_L2TP = 259, + APP_ID_LA_MAINT = 260, + APP_ID_LAST_FM = 261, + APP_ID_LEGENT = 262, + APP_ID_LINK = 263, + APP_ID_LIVE365 = 264, + APP_ID_LIVEMEETING = 265, + APP_ID_LIVESTATION = 266, + APP_ID_LLMNR = 267, + APP_ID_LOCUS_CONN = 268, + APP_ID_LOCUS_MAP = 269, + APP_ID_LOGMEIN = 270, + APP_ID_LSARPC = 271, + APP_ID_MAFIAWARS = 272, + APP_ID_MAGENTA_LOGIC = 273, + APP_ID_MAGICJACK = 274, + APP_ID_MAILQ = 275, + APP_ID_MANET = 276, + APP_ID_MAPI = 277, + APP_ID_MASQDIALER = 278, + APP_ID_MATIP = 279, + APP_ID_MCAFEE = 280, + APP_ID_MC_FTP = 281, + APP_ID_MCIDAS = 282, + APP_ID_MCK_IVPIP = 283, + APP_ID_MEDIAFIRE = 285, + APP_ID_MEEBO = 286, + APP_ID_MEETING_MAKER = 287, + APP_ID_META5 = 288, + APP_ID_METAGRAM = 289, + APP_ID_MF_COBOL = 290, + APP_ID_MFTP = 291, + APP_ID_MINI_SQL = 292, + APP_ID_MIT_ML_DEV = 293, + APP_ID_MIT_SPOOLER = 294, + APP_ID_MIXI = 295, + APP_ID_MOBILEIP = 296, + APP_ID_MORTGAGEWARE = 297, + APP_ID_MPLS_MULTICAST = 298, + APP_ID_MPLS_UNICAST = 299, + APP_ID_MPM = 300, + APP_ID_MPP = 301, + APP_ID_MPTN = 302, + APP_ID_MS_CRS = 303, + APP_ID_MSDN = 304, + APP_ID_MSG = 305, + APP_ID_MSMQ = 306, + APP_ID_MSNP = 307, + APP_ID_MSN = 308, + APP_ID_MS_OLAP = 309, + APP_ID_MS_ONLINE = 310, + APP_ID_MSP = 311, + APP_ID_MS_SQL = 312, + APP_ID_MTA = 313, + APP_ID_MULTIPLEX = 314, + APP_ID_MUMPS = 315, + APP_ID_MYSPACE_CHAT = 316, + APP_ID_MYSPACE = 317, + APP_ID_NAMP = 318, + APP_ID_NAPSTER = 319, + APP_ID_NCED = 320, + APP_ID_NCLD = 321, + APP_ID_NDS_AUTH = 322, + APP_ID_NETBIOS = 323, + APP_ID_NETINFO = 324, + APP_ID_NETLOGON = 325, + APP_ID_NETMEETING = 326, + APP_ID_NETSC = 327, + APP_ID_NETSCOUT = 328, + APP_ID_NETWARE = 329, + APP_ID_NFA = 330, + APP_ID_NFS = 331, + APP_ID_NI_FTP = 332, + APP_ID_NI_MAIL = 333, + APP_ID_NIP = 334, + APP_ID_NNSP = 335, + APP_ID_NOVABACKUP = 336, + APP_ID_NPP = 337, + APP_ID_NSIIOPS = 338, + APP_ID_NSRMP = 339, + APP_ID_NSS = 340, + APP_ID_NSSTP = 341, + APP_ID_NXEDIT = 342, + APP_ID_NXTSTEP = 343, + APP_ID_OCBINDER = 344, + APP_ID_OCSERVER = 345, + APP_ID_OCS = 346, + APP_ID_ODMR = 347, + APP_ID_OFTP = 348, + APP_ID_OFTPS = 349, + APP_ID_ONMUX = 350, + APP_ID_OPALIS_ROBOT = 351, + APP_ID_OPENPORT = 352, + APP_ID_OPENVPN = 353, + APP_ID_ORACLE_SQLNET = 355, + APP_ID_ORKUT = 356, + APP_ID_OSCAR = 357, + APP_ID_OSUNMS = 358, + APP_ID_PANDA = 359, + APP_ID_PARTYPOKER = 360, + APP_ID_PAWSERV = 361, + APP_ID_PCMAIL = 362, + APP_ID_PDAP = 363, + APP_ID_PERSONALLINK = 364, + APP_ID_PFTP = 365, + APP_ID_PIM = 366, + APP_ID_PIP = 367, + APP_ID_PKIX_TIMESTAMP = 368, + APP_ID_PLAXO = 369, + APP_ID_POP2 = 370, + APP_ID_PPLIVE = 371, + APP_ID_PPP_DISCOVERY = 372, + APP_ID_PPP_SESSION = 373, + APP_ID_PPSTREAM = 374, + APP_ID_PPTP = 375, + APP_ID_PRINTSRV = 376, + APP_ID_PROFILE = 377, + APP_ID_PROSPERO = 378, + APP_ID_PTP = 379, + APP_ID_PUP = 380, + APP_ID_PWDGEN = 381, + APP_ID_QBIK = 382, + APP_ID_QFT = 383, + APP_ID_QMTP = 384, + APP_ID_QOTD = 385, + APP_ID_QQ = 386, + APP_ID_QUICKTIME = 387, + APP_ID_RAP = 388, + APP_ID_RARP = 389, + APP_ID_REMAIL = 390, + APP_ID_REMOTE_JOB_SERVICE = 391, + APP_ID_REMOTE_TELNET = 392, + APP_ID_RESCAP = 393, + APP_ID_RFR = 394, + APP_ID_RIP = 395, + APP_ID_RIS = 396, + APP_ID_RJE = 397, + APP_ID_RLOGIN = 398, + APP_ID_RLP = 399, + APP_ID_RMT = 400, + APP_ID_RPC2PMAP = 401, + APP_ID_RRP = 402, + APP_ID_RSH = 403, + APP_ID_RSVD = 404, + APP_ID_RSVP = 405, + APP_ID_RSVP_TUNNEL = 406, + APP_ID_RTCP = 407, + APP_ID_RTSPS = 408, + APP_ID_SAMR = 409, + APP_ID_SAP_HOSTCONTROL = 410, + APP_ID_SBNTBCST = 411, + APP_ID_SCOI2DLG = 412, + APP_ID_SCSI_ST = 413, + APP_ID_SCTP = 414, + APP_ID_SECOND_LIFE = 415, + APP_ID_SECURSIGHT = 416, + APP_ID_SEMANTIX = 417, + APP_ID_SEND = 418, + APP_ID_SET = 419, + APP_ID_SFTP = 420, + APP_ID_SGCP = 421, + APP_ID_SGMP = 422, + APP_ID_SHAREPOINT = 423, + APP_ID_SHRINKWRAP = 424, + APP_ID_SILVERPLATTER = 425, + APP_ID_SIP = 426, + APP_ID_SKYPE_AUTH = 428, + APP_ID_SKYPE_OUT = 429, + APP_ID_SKYPE_P2P = 430, + APP_ID_SKYPE_PROBE = 431, + APP_ID_SLINGBOX = 432, + APP_ID_SMAKYNET = 433, + APP_ID_SMART_SDP = 434, + APP_ID_SMPTE = 435, + APP_ID_SMSP = 436, + APP_ID_SMUX = 437, + APP_ID_SNA_GATEWAY = 438, + APP_ID_SNET = 439, + APP_ID_SNPP = 440, + APP_ID_SOFTPC = 441, + APP_ID_SOULSEEK = 442, + APP_ID_SQL_SERVICES = 443, + APP_ID_SRC = 444, + APP_ID_SRMP = 445, + APP_ID_SRS_SEND = 446, + APP_ID_SSDP = 447, + APP_ID_STATIONLAUNCHER = 448, + APP_ID_STATSRV = 449, + APP_ID_STORE_ADMIN = 450, + APP_ID_SU_MIT_TELNET = 451, + APP_ID_SUN_RPC = 452, + APP_ID_SUPDUP = 453, + APP_ID_SUPERNEWS = 454, + APP_ID_SURMEAS = 455, + APP_ID_SVRLOC = 456, + APP_ID_SWIFT_RVFP = 457, + APP_ID_SYBASE_SQL = 458, + APP_ID_SYMANTEC_SYSTEM_CENTER = 459, + APP_ID_SYNOPTICS = 460, + APP_ID_SYSATT = 461, + APP_ID_SYSLOG = 462, + APP_ID_SYSTAT = 463, + APP_ID_TACACS = 464, + APP_ID_TAC_NEWS = 465, + APP_ID_TCPMUX = 466, + APP_ID_TCP = 467, + APP_ID_TEXAR = 468, + APP_ID_TFTPS = 469, + APP_ID_TIME = 470, + APP_ID_TMOBILE = 471, + APP_ID_TOBIT = 472, + APP_ID_TOR = 473, + APP_ID_TRIPWIRE = 474, + APP_ID_TUMBLR = 475, + APP_ID_UAAC = 476, + APP_ID_UARPS = 477, + APP_ID_UC4 = 478, + APP_ID_UDP = 479, + APP_ID_UIS = 480, + APP_ID_ULSTPROC = 481, + APP_ID_UMA = 482, + APP_ID_UNICENTER = 483, + APP_ID_UNIDATA_LDM = 484, + APP_ID_UNIFY = 485, + APP_ID_UPS = 486, + APP_ID_USENET = 487, + APP_ID_UTMP = 489, + APP_ID_UUCP = 490, + APP_ID_VCHAT = 491, + APP_ID_VETTCP = 492, + APP_ID_VMNET = 493, + APP_ID_VMPWSCS = 494, + APP_ID_VONAGE = 495, + APP_ID_VSLMP = 496, + APP_ID_VUZE = 497, + APP_ID_WCCP = 498, + APP_ID_WEBFILTER = 499, + APP_ID_WEBLOGIC = 500, + APP_ID_WIKIPEDIA = 501, + APP_ID_WINDOWS_LIVE = 502, + APP_ID_WINDOWS_MEDIA = 503, + APP_ID_WINNY = 504, + APP_ID_WINS = 505, + APP_ID_WORDPRESS = 506, + APP_ID_WORLD_OF_WARCRAFT = 507, + APP_ID_X_224 = 508, + APP_ID_X_25 = 509, + APP_ID_XANGA = 510, + APP_ID_XBONE = 511, + APP_ID_XBOX_LIVE = 512, + APP_ID_XDMCP = 513, + APP_ID_XFER = 514, + APP_ID_XMPP = 515, + APP_ID_XNS_AUTHENTICATION = 516, + APP_ID_XNS_CLEARINGHOUSE = 517, + APP_ID_XNS_MAIL = 518, + APP_ID_XNS_TIME = 519, + APP_ID_XNS = 520, + APP_ID_XYPLEX = 521, + APP_ID_YAHOO_GAMES = 522, + APP_ID_YAHOO_MSG_FILE_TRANSFER = 523, + APP_ID_YAHOO = 524, + APP_ID_Z3950 = 525, + APP_ID_ZANNET = 526, + APP_ID_ZEBRA = 527, + APP_ID_ZOHO = 528, + APP_ID_ZOHO_CHAT = 529, + APP_ID_ZOHO_MAIL = 530, + APP_ID_ZOHO_SHARE = 531, + APP_ID_ZOHO_WIKI = 532, + APP_ID_ZYNGA = 533, + APP_ID_ZYNGA_POKER = 534, + + APP_ID_1_800_FLOWERS = 535, + APP_ID_100BAO = 536, + APP_ID_2CHANNEL = 537, + APP_ID_6_PM = 538, + APP_ID_ACE_HARDWARE_CORPORATION = 539, + APP_ID_ADDICTING_GAMES = 540, + APP_ID_ADOBE_UPDATE = 541, + APP_ID_ADORAMA = 542, + APP_ID_AIM_EXPRESS = 543, + APP_ID_AMERICAN_EXPRESS = 544, + APP_ID_ANDROID_BROWSER = 545, + APP_ID_AOL_EMAIL = 546, + APP_ID_AOL_INSTANT_MESSENGER = 547, + APP_ID_AOL_SOFTWARE = 549, + APP_ID_APPLE_EMAIL = 550, + APP_ID_APPLE_STORE = 551, + APP_ID_ARCSERVE = 552, + APP_ID_ARES = 553, + APP_ID_ARGOS = 554, + APP_ID_ATOM = 555, + APP_ID_ATOM_COM = 556, + APP_ID_AUTOBLOG = 557, + APP_ID_AUTOTRADER_COM = 558, + APP_ID_B_H_PHOTO_VIDEO = 559, + APP_ID_BANK_OF_AMERICA = 560, + APP_ID_BARNES_AND_NOBLE = 561, + APP_ID_BARNEYS_NEW_YORK = 562, + APP_ID_BASECAMP = 563, + APP_ID_BATTLENET = 564, + APP_ID_BEARSHARE = 565, + APP_ID_BEBO = 566, + APP_ID_BEST_BUY = 567, + APP_ID_BEWEEVEE = 568, + APP_ID_BGP = 569, + APP_ID_BITTORRENT_CLIENT = 570, + APP_ID_BITTRACKER_CLIENT = 571, + APP_ID_BLACK_DECKER_CORPORATION = 572, + APP_ID_BLACKBERRY_BROWSER = 573, + APP_ID_BLIP_TV = 574, + APP_ID_BLOCKBUSTER = 575, + APP_ID_BLOGGER = 576, + APP_ID_BLOOMINGDALES = 577, + APP_ID_BLUE_NILE = 578, + APP_ID_BLUEFLY = 579, + APP_ID_BOX_NET = 580, + APP_ID_CAMERASDIRECT_COM_AU = 581, + APP_ID_CAPITAL_ONE = 582, + APP_ID_CAR_AND_DRIVER = 583, + APP_ID_CARMAX = 584, + APP_ID_CDISCOUNT = 585, + APP_ID_CHARACTER_GENERATOR = 586, + APP_ID_CHASE = 587, + APP_ID_CHEAPTICKETS = 588, + APP_ID_CHROME = 589, + APP_ID_CITI = 590, + APP_ID_CITY_SPORTS = 591, + APP_ID_COLLABEDIT = 592, + APP_ID_COSTCO = 593, + APP_ID_CRAIGSLIST = 594, + APP_ID_CRUTCHFIELD = 595, + APP_ID_CURL = 596, + APP_ID_CVS = 597, + APP_ID_CVS_PSERVER = 598, + APP_ID_DAAP = 599, + APP_ID_DAILYMOTION = 600, + APP_ID_DAVID_JONES = 601, + APP_ID_DB2 = 602, + APP_ID_DCE_RPC = 603, + APP_ID_DEALS_DIRECT = 604, + APP_ID_DELICIOUS = 605, + APP_ID_DELL = 606, + APP_ID_DESTRUCTOID = 607, + APP_ID_DEVIANTART = 608, + APP_ID_DHCP = 609, + APP_ID_DHCPV6_SERVER = 610, + APP_ID_DICKS_SPORTING_GOODS = 611, + APP_ID_DIIGO = 612, + APP_ID_DILLARDS = 613, + APP_ID_DISCARD = 614, + APP_ID_DISCOVER = 615, + APP_ID_DNP3 = 616, + APP_ID_DNS = 617, + APP_ID_DRDA = 618, + APP_ID_DROPBEAR = 619, + APP_ID_DRUGSTORE_COM = 620, + APP_ID_E_TRADE = 621, + APP_ID_EDMUNDS_COM = 622, + APP_ID_EDONKEY = 623, + APP_ID_EUDORA = 624, + APP_ID_EUDORA_PRO = 625, + APP_ID_EVOLUTION = 626, + APP_ID_EXEC = 627, + APP_ID_EXPEDIA = 628, + APP_ID_FACEBOOK = 629, + APP_ID_FACEBOOK_CHAT = 630, + APP_ID_FACEBOOK_COMMENT = 631, + APP_ID_FACEBOOK_GAME_PREMIER_FOOTBALL = 632, + APP_ID_FACEBOOK_READ_EMAIL = 633, + APP_ID_FACEBOOK_SEND_EMAIL = 634, + APP_ID_FACEBOOK_STATUS_UPDATE = 635, + APP_ID_FIDELITY = 636, + APP_ID_FINGER = 637, + APP_ID_FIREFOX = 638, + APP_ID_FLASH_VIDEO = 639, + APP_ID_FNAC = 640, + APP_ID_FOXY = 641, + APP_ID_FRIENDSTER = 642, + APP_ID_FRYS_ELECTRONICS = 643, + APP_ID_FTD = 644, + APP_ID_FTP = 645, + APP_ID_G4 = 646, + APP_ID_GAME_INFORMER = 647, + APP_ID_GAMESPOT = 648, + APP_ID_GAMESPY = 649, + APP_ID_GAMESTOP = 650, + APP_ID_GAMETRAILERS = 651, + APP_ID_GAWKER = 652, + APP_ID_GENERIC = 653, + APP_ID_GIFT = 654, + APP_ID_GMAIL = 655, + APP_ID_GNUCLEUS = 656, + APP_ID_GNUCLEUSLAN = 657, + APP_ID_GNUTELLA = 658, + APP_ID_GNUTELLA2 = 659, + APP_ID_GOOGLE_ANALYTICS = 660, + APP_ID_GOOGLE_CALENDAR = 661, + APP_ID_GOOGLE_DESKTOP = 662, + APP_ID_GOOGLE_NEWS = 663, + APP_ID_GOOGLE_PRODUCT_SEARCH = 664, + APP_ID_GOOGLE_SAFEBROWSING = 665, + APP_ID_GOOGLE_TOOLBAR = 1146, + APP_ID_GOPHER = 667, + APP_ID_GTK_GNUTELLA = 668, + APP_ID_HAIKU_LEARNING_SYSTEMS = 669, + APP_ID_HOME_DEPOT = 670, + APP_ID_HOSTNAME_SERVER = 671, + APP_ID_GOOGLE_EARTH = 672, + APP_ID_HOTLINE = 673, + APP_ID_HOUSE_OF_FRASER = 674, + APP_ID_HSBC = 675, + APP_ID_HTTP = 676, + APP_ID_HULU = 677, + APP_ID_IBM_APP = 678, + APP_ID_ICQ = 679, + APP_ID_IGN = 680, + APP_ID_ILOVEIM = 681, + APP_ID_IMAGESHACK = 682, + APP_ID_IMAP = 683, + APP_ID_IMGUR = 684, + APP_ID_IMO_IM = 685, + APP_ID_INTERNET_EXPLORER = 686, + APP_ID_IRCD = 687, + APP_ID_ITU_H_323 = 688, + APP_ID_ITUNES = 689, + APP_ID_J_C_PENNEY = 690, + APP_ID_J_R = 691, + APP_ID_JABBER = 692, + APP_ID_JALOPNIK = 693, + APP_ID_JAVA_RMI = 694, + APP_ID_JIRA = 695, + APP_ID_JOYSTIQ = 696, + APP_ID_KAD = 697, + APP_ID_KAY_JEWELERS = 698, + APP_ID_KAZAA = 699, + APP_ID_KMAIL = 700, + APP_ID_KERBEROS = 701, + APP_ID_KMART = 702, + APP_ID_KOGAN_TECHNOLOGIES = 703, + APP_ID_KOHLS = 704, + APP_ID_KONGREGATE = 705, + APP_ID_KONQUEROR = 706, + APP_ID_KOTAKU = 707, + APP_ID_LAUNCHPAD = 708, + APP_ID_LBPS = 709, + APP_ID_LDAP = 710, + APP_ID_LIMELIGHT = 711, + APP_ID_LIMEWIRE = 712, + APP_ID_LINKEDIN = 713, + APP_ID_LINKEDIN_JOB_SEARCH = 714, + APP_ID_LINUXCONF = 715, + APP_ID_LIVEJOURNAL = 716, + APP_ID_LOGIN = 717, + APP_ID_LOKALISTEN = 718, + APP_ID_LORD_TAYLOR = 719, + APP_ID_LOTUS_NOTES = 720, + APP_ID_LOVEFILM = 721, + APP_ID_LOWES = 722, + APP_ID_LSH = 723, + APP_ID_MANOLITO = 724, + APP_ID_MEGACO = 725, + APP_ID_MEGAVIDEO = 726, + APP_ID_MENARDS = 727, + APP_ID_METACAFE = 728, + APP_ID_METAFILTER = 729, + APP_ID_MGCP = 730, + APP_ID_MICROSOFT_UPDATE = 731, + APP_ID_MICROSOFT_WINDOWS_MESSENGER = 732, + APP_ID_MINUS = 733, + APP_ID_MIXX = 734, + APP_ID_MMS = 735, + APP_ID_SAFARI_MOBILE = 736, + APP_ID_MODBUS = 737, + APP_ID_MORGAN_STANLEY = 738, + APP_ID_MORPHEUS = 739, + APP_ID_MOVENETWORKS = 740, + APP_ID_MP4 = 741, + APP_ID_MPEG = 742, + APP_ID_MSN_MESSENGER = 743, + APP_ID_MSN_MESSENGER_MAC = 744, + APP_ID_MUTE = 745, + APP_ID_MUTT = 746, + APP_ID_MYSQL = 747, + APP_ID_MYUDUTU = 748, + APP_ID_NCP = 749, + APP_ID_NECKERMANN = 750, + APP_ID_NEIMAN_MARCUS = 751, + APP_ID_NESSUS = 752, + APP_ID_NETBIOS_DGM = 753, + APP_ID_NETBIOS_NS = 754, + APP_ID_NETBIOS_SSN = 755, + APP_ID_NETFLIX = 756, + APP_ID_NETLOG = 757, + APP_ID_NETVIBES = 758, + APP_ID_NEWEGG = 759, + APP_ID_NEWSNOW = 760, + APP_ID_NEWSVINE = 761, + APP_ID_NICO_NICO_DOUGA = 762, + APP_ID_NNTP = 763, + APP_ID_NORSDTROM = 764, + APP_ID_NSPLAYER = 765, + APP_ID_NTALK = 766, + APP_ID_NTP = 767, + APP_ID_OFFICE_DEPOT = 768, + APP_ID_OFFICEMAX = 769, + APP_ID_OO_COM_AU = 770, + APP_ID_OPENSSH = 771, + APP_ID_OPERATING_SYSTEM = 772, + APP_ID_ORACLE_DATABASE = 773, + APP_ID_ORACLE_TNS = 774, + APP_ID_ORBITZ = 775, + APP_ID_OUTLOOK = 776, + APP_ID_OUTLOOK_EXPRESS = 777, + APP_ID_OVERSTOCK_COM = 778, + APP_ID_PANDORA = 779, + APP_ID_PC_DUO = 780, + APP_ID_PCANYWHERE = 781, + APP_ID_PEERCAST = 782, + APP_ID_PEERENABLER = 783, + APP_ID_PHOTOBUCKET = 784, + APP_ID_PICASA = 785, + APP_ID_POCO = 786, + APP_ID_POGO = 787, + APP_ID_POP3 = 788, + APP_ID_POPCAP_GAMES = 789, + APP_ID_POPURLS = 790, + APP_ID_POSTGRESQL = 791, + APP_ID_PRICELINE_COM = 792, + APP_ID_PROFLOWERS = 793, + APP_ID_PUTTY = 794, + APP_ID_QUAKE = 795, + APP_ID_QUICKFLIX = 796, + APP_ID_QUILL_CORPORATION = 797, + APP_ID_QVC = 798, + APP_ID_QZONE = 799, + APP_ID_RADIUS = 800, + APP_ID_RADIUS_ACCT = 801, + APP_ID_RAPIDSHARE = 802, + APP_ID_RDP = 803, + APP_ID_REDDIT = 804, + APP_ID_REDMINE = 805, + APP_ID_REI = 806, + APP_ID_REMOTE_DESKTOP_CLIENT = 807, + APP_ID_RENREN = 808, + APP_ID_REVOLVECLOTHING = 809, + APP_ID_RONA = 810, + APP_ID_RSS = 811, + APP_ID_RTMP = 812, + APP_ID_RTP = 813, + APP_ID_RTSP = 814, + APP_ID_SAFARI = 815, + APP_ID_SAKS_FIFTH_AVENUE = 816, + APP_ID_SAMS_CLUB = 817, + APP_ID_SCHUELERVZ = 818, + APP_ID_SCHWAB = 819, + APP_ID_SCOTTRADE = 820, + APP_ID_SEARS = 821, + APP_ID_SHAREAZA = 822, + APP_ID_SHELL = 823, + APP_ID_SHOCKWAVE = 824, + APP_ID_SHOPLET = 825, + APP_ID_SHOPNBC = 826, + APP_ID_SHOPPING_HP_COM = 827, + APP_ID_SHOPSTYLE = 828, + APP_ID_SHOUTCAST_RADIO = 829, + APP_ID_SHOWCLIX = 830, + APP_ID_SHOWDOCUMENT = 831, + APP_ID_SKYPE = 832, + APP_ID_SKYPE_MAC = 833, + APP_ID_SLASHDOT = 834, + APP_ID_SLOW = 835, + APP_ID_SMTP = 836, + APP_ID_SNMP = 837, + APP_ID_SNMP_TRAP = 838, + APP_ID_SOCKS = 839, + APP_ID_SORIBADA = 840, + APP_ID_SPIN_DE = 841, + APP_ID_SPORTS_AUTHORITY = 842, + APP_ID_SQL_SERVER = 843, + APP_ID_SQUID = 844, + APP_ID_SQUIRREL_EMAIL = 845, // deprecated + APP_ID_SSH = 846, + APP_ID_SSL = 847, + APP_ID_STAPLES = 848, + APP_ID_STAYFRIENDS = 849, + APP_ID_STUBHUB = 850, + APP_ID_STUDIVZ = 851, + APP_ID_STUMBLEUPON = 852, + APP_ID_STUN = 853, + APP_ID_SWAROVSKI = 854, + APP_ID_T_ROWE_PRICE = 855, + APP_ID_TABULAR_DATA_STREAM_TDS = 856, + APP_ID_TALK = 857, + APP_ID_TARGET = 858, + APP_ID_TCHIBO = 859, + APP_ID_TD_AMERITRADE = 860, + APP_ID_TELNET = 861, + APP_ID_TFTP = 862, + APP_ID_THE_GAP = 863, + APP_ID_THE_SHARPER_IMAGE = 864, + APP_ID_THINKGEEK = 865, + APP_ID_THUNDERBIRD = 866, + APP_ID_TICKETMASTER = 867, + APP_ID_TICKETS_COM = 868, + APP_ID_TICKETSNOW = 869, + APP_ID_TIFFANY_CO = 870, + APP_ID_TIGER_DIRECT = 871, + APP_ID_TIMBUKTU = 872, + APP_ID_TINYPIC = 873, + APP_ID_TIVOLI = 874, + APP_ID_TN3270 = 875, + APP_ID_TOC = 876, + APP_ID_TOP_GEAR = 877, + APP_ID_TRAC = 878, + APP_ID_TRACEROUTE = 879, + APP_ID_TRAVELOCITY = 880, + APP_ID_TRIPADVISOR = 881, + APP_ID_TWITTER = 882, + APP_ID_URBAN_OUTFITTERS = 883, + APP_ID_USTREAM_TV = 884, + APP_ID_VANGUARD = 885, + APP_ID_VCOM = 886, + APP_ID_VEHIX = 887, + APP_ID_VENTE_PRIVEE_COM = 888, + APP_ID_VEOH = 889, + APP_ID_VERIZON_EMAIL = 890, + APP_ID_VIADEO = 891, + APP_ID_VICTORIAS_SECRET = 892, + APP_ID_VIMEO = 893, + APP_ID_VNC = 894, + APP_ID_VNC_RFB = 895, + APP_ID_VNC_SERVER_RFB = 896, + APP_ID_VOIP_RTP = 897, + APP_ID_VOIP_SIP = 898, + APP_ID_VOYAGES_SNCF_COM = 899, + APP_ID_WACHOVIA = 900, + APP_ID_WALMART = 901, + APP_ID_WAV = 902, + APP_ID_WEB_OF_TRUST = 903, + APP_ID_WEBDAV = 904, + APP_ID_WEBEX = 905, + APP_ID_WEBSPHERE_MQ = 906, + APP_ID_WELLS_FARGO = 907, + APP_ID_WER_KENNT_WEN = 908, + APP_ID_WGET = 909, + APP_ID_WINDOWS_LIVE_HOTMAIL = 910, + APP_ID_WINDOWS_LIVE_SKYDRIVE = 911, + APP_ID_WINDOWS_MEDIA_PLAYER = 912, + APP_ID_WINMX = 913, + APP_ID_WIZIQ = 914, + APP_ID_WMA = 915, + APP_ID_WMV = 916, + APP_ID_WOOT = 917, + APP_ID_WX = 918, + APP_ID_X_FONT_SERVER = 919, + APP_ID_X11 = 920, + APP_ID_XBOX = 921, + APP_ID_XING = 922, + APP_ID_XM_RADIO_ONLINE = 923, + APP_ID_XUNLEI = 924, + APP_ID_XWINDOWS = 925, + APP_ID_YAHOO_VOICE = 926, + APP_ID_YET_ABC = 927, + APP_ID_YOUSENDIT = 928, + APP_ID_YOUTUBE = 929, + APP_ID_ZALES = 930, + APP_ID_ZAPPOS = 931, + APP_ID_ZIP_CA = 932, + APP_ID_ZOOOMR = 933, + APP_ID_YAHOO_MSG = 936, + APP_ID_YAHOOMAIL = 946, + APP_ID_YAHOO_TOOLBAR = 947, + APP_ID_RSYNC = 1097, + APP_ID_XSCPLS = 1098, + APP_ID_ROBUST_MPA = 1100, + APP_ID_VND_WAV = 1101, + APP_ID_GPP = 1102, + APP_ID_M4V = 1103, + APP_ID_X_WAV = 1104, + APP_ID_MPA = 1105, + APP_ID_MP4A = 1106, + APP_ID_AOL_NETSCAPE = 1107, + APP_ID_SMTP_IMO = 1108, + APP_ID_DDM_SSL = 1111, + APP_ID_SMTPS = 1112, + APP_ID_NNTPS = 1113, + APP_ID_IMAPS = 1114, + APP_ID_SSHELL = 1115, + APP_ID_LDAPS = 1116, + APP_ID_TELNETS = 1117, + APP_ID_IRCS = 1118, + APP_ID_POP3S = 1119, + APP_ID_MSFT_GC_SSL = 1120, + APP_ID_SF_APPLIANCE_MGMT = 1121, + APP_ID_HTTPS = 1122, + APP_ID_SKYPE_TUNNELING = 1126, + APP_ID_ASPROXY = 1145, + APP_ID_OPERA = 1288, + APP_ID_SSL_CLIENT = 1296, + APP_ID_AOL = 1419, + APP_ID_MDNS = 1755, + APP_ID_APPLE_CORE_MEDIA = 2253, + APP_ID_ULTRASURF = 2634, + APP_ID_LYCOS = 2775, + APP_ID_DOGPILE = 2804, + APP_ID_SPDY = 2886, + APP_ID_HTTP2 = 2889, // only used for some quick bookkeeping -- treat as HTTP + APP_ID_ANYCONNECT = 2921, + APP_ID_ANYCONNECT_SSL_CLIENT = 2922, + APP_ID_ANYCONNECT_IPSEC_CLIENT = 2923, + APP_ID_FTP_ACTIVE = 4002, + APP_ID_FTP_PASSIVE = 4003, + + // FIXIT - L Need better name since often there is no UI + APP_ID_UNKNOWN_UI = 65535 // this causes the UI to render Unknown instead of pending or blank +}; + +struct AppRegistryEntry +{ + ApplicationId appId; + uint32_t additionalInfo; +}; + +#endif diff --git a/src/network_inspectors/appid/client_plugins/CMakeLists.txt b/src/network_inspectors/appid/client_plugins/CMakeLists.txt new file mode 100644 index 000000000..668ac3040 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/CMakeLists.txt @@ -0,0 +1,34 @@ +set ( CLIENT_PLUGINS_SOURCES + client_app_aim.cc + client_app_aim.h + client_app_api.h + client_app_base.cc + client_app_base.h + client_app_bit.cc + client_app_bit_tracker.cc + client_app_config.h + client_app_msn.cc + client_app_msn.h + client_app_rtp.cc + client_app_smtp.cc + client_app_smtp.h + client_app_ssh.cc + client_app_timbuktu.cc + client_app_tns.cc + client_app_vnc.cc + client_app_ym.cc + client_app_ym.h + ) + +add_library ( appid_client_plugins STATIC + ${CLIENT_PLUGINS_SOURCES} +) + +target_include_directories ( appid_client_plugins PRIVATE ${APPID_INCLUDE_DIR} ) + +# FIXIT-H: Add unit tests + +#install (FILES ${CLIENT_PLUGINS_INCLUDES} +# DESTINATION "${INCLUDE_INSTALL_PATH}/appid/client_plugins" + +#) diff --git a/src/network_inspectors/appid/client_plugins/Makefile.am b/src/network_inspectors/appid/client_plugins/Makefile.am new file mode 100644 index 000000000..1ae8e9e2c --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/Makefile.am @@ -0,0 +1,33 @@ + +AM_CPPFLAGS+=-I$(top_srcdir)/src/network_inspectors/appid + +file_list = \ +client_app_aim.cc \ +client_app_aim.h \ +client_app_api.h \ +client_app_base.cc \ +client_app_base.h \ +client_app_bit.cc \ +client_app_bit_tracker.cc \ +client_app_config.h \ +client_app_msn.cc \ +client_app_msn.h \ +client_app_rtp.cc \ +client_app_smtp.cc \ +client_app_smtp.h \ +client_app_ssh.cc \ +client_app_timbuktu.cc \ +client_app_tns.cc \ +client_app_vnc.cc \ +client_app_ym.cc \ +client_app_ym.h + +noinst_LIBRARIES = libappid_client_plugins.a +libappid_client_plugins_a_SOURCES = $(file_list) + +# Uncomment once tests are ready. +#if BUILD_UNIT_TESTS +#SUBDIRS = test +#endif + + diff --git a/src/network_inspectors/appid/client_plugins/client_app_aim.cc b/src/network_inspectors/appid/client_plugins/client_app_aim.cc new file mode 100644 index 000000000..1ec758bf8 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_aim.cc @@ -0,0 +1,318 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_aim.cc author Sourcefire Inc. + +#include "client_app_api.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "main/snort_debug.h" +#include "utils/sflsq.h" + +#include "app_info_table.h" +#include "application_ids.h" + +#pragma pack(1) + +struct FLAPFNACSignOn +{ + uint16_t len; +}; + +struct FLAPFNAC +{ + uint16_t family; + uint16_t subtype; + uint16_t flags; + uint32_t id; +}; + +struct FLAPTLV +{ + uint16_t subtype; + uint16_t len; +}; + +struct FLAPHeader +{ + uint8_t start; + uint8_t channel; + uint16_t seq; + uint16_t len; +}; + +#pragma pack() + +struct AIM_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL AIM_CLIENT_APP_CONFIG aim_config; + +#define MAX_VERSION_SIZE 64 + +static CLIENT_APP_RETCODE aim_init(const IniClientAppAPI* const, SF_LIST* config); +static CLIENT_APP_RETCODE aim_validate( + const uint8_t* data, uint16_t size, const int dir, AppIdData*, Packet*, + Detector*, const AppIdConfig*); + +RNAClientAppModule aim_client_mod = +{ + "AIM", // name + IpProtocol::TCP, // proto + &aim_init, // init + nullptr, // clean + &aim_validate, // validate + 2, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const uint8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static const uint8_t NEW_CONNECTION[] = "\x02a\x001"; +static const uint8_t AIM_PROTOCOL_VERSION[] = "\x000\x004\x000\x000\x000\x001"; +static const uint8_t OLDER_AOL[] = "AOL Instant Messenger"; +static const uint8_t AOL[] = "imApp"; +static const uint8_t NETSCAPE_AOL[] = "Netscape 2000 an approved user of AOL Instant Messenger"; + +static Client_App_Pattern patterns[] = +{ + { NEW_CONNECTION, sizeof(NEW_CONNECTION)-1, 0, 0 }, + { AIM_PROTOCOL_VERSION, sizeof(AIM_PROTOCOL_VERSION)-1, 4, 0 }, + { OLDER_AOL, sizeof(OLDER_AOL)-1, -1, APP_ID_AOL_INSTANT_MESSENGER }, + { AOL, sizeof(AOL)-1, -1, APP_ID_AOL_INSTANT_MESSENGER }, + { NETSCAPE_AOL, sizeof(NETSCAPE_AOL), -1, APP_ID_AOL_NETSCAPE }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_AOL_NETSCAPE, APPINFO_FLAG_CLIENT_ADDITIONAL | APPINFO_FLAG_CLIENT_USER }, + { APP_ID_AOL_INSTANT_MESSENGER, APPINFO_FLAG_CLIENT_ADDITIONAL | APPINFO_FLAG_CLIENT_USER }, +}; + +static CLIENT_APP_RETCODE aim_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + aim_config.enabled = 1; + + if ( config ) + { + SF_LNODE* cursor = nullptr; + RNAClientAppModuleConfigItem* item = nullptr; + + for ( item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor) ) + { + DebugFormat(DEBUG_INSPECTOR, "Processing %s: %s\n", item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + aim_config.enabled = atoi(item->value); + } + } + + if (aim_config.enabled) + { + for (unsigned i = 0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_INSPECTOR, "registering pattern length %u at %d\n", + patterns[i].length, patterns[i].index); + + init_api->RegisterPattern(&aim_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + for (unsigned j = 0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_INSPECTOR, "registering appId: %d\n", + appIdRegistry[j].appId); + + init_api->RegisterAppId(&aim_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +template +static inline const Hdr* advance(const uint8_t*& cur, const uint8_t* const end) +{ + assert(end >= cur); + if ( (size_t)(end - cur) < sizeof(Hdr) ) + return nullptr; + + cur += sizeof(Hdr); + return reinterpret_cast(cur); +} + +static inline bool check_username( + const uint8_t* const data, const FLAPTLV* tlv, char* const buf, char* const buf_end) +{ + const uint8_t* const end = data + tlv->len; + char* ptr = buf; + *buf_end = '\0'; + + for ( const uint8_t* cur = data; cur < end; ++cur ) + { + if (isalnum(*cur) || *cur == '.' || *cur == '@' || *cur == '-' || *cur == '_') + { + if ( ptr < buf_end ) + *ptr++ = *cur; + } + else + return false; + } + + return true; +} + +static CLIENT_APP_RETCODE aim_validate( + const uint8_t* const data, uint16_t size, const int dir, AppIdData* flowp, + Packet*, Detector*, const AppIdConfig*) +{ + if ( dir != APP_ID_FROM_INITIATOR ) + return CLIENT_APP_INPROCESS; + + const uint8_t* const end = data + size; + const uint8_t* cur = data; + + while ( cur < end ) + { + auto fh = advance(cur, end); + if ( !fh ) + goto bail; + + if (fh->start != 0x2a || fh->channel < 1 || fh->channel > 5) + goto bail; + + uint16_t len = ntohs(fh->len); + + if (len > (end - cur)) + goto bail; + + bool check_user_name = false; + + if ( fh->channel == 0x02 ) + { + auto fnac = advance(cur, end); + if ( !fnac ) + goto bail; + + if (fnac->family == htons(0x0017) && fnac->subtype == htons(0x0006)) + check_user_name = true; + + len -= sizeof(*fnac); + } + else if ( fh->channel == 0x01 ) + { + if ( len < 4 || memcmp(cur, &AIM_PROTOCOL_VERSION[2], 4) != 0 ) + goto bail; + + len -= 4; + cur += 4; + } + + if ( len ) + { + bool got_id = false; + uint16_t major = 0; + uint16_t minor = 0; + uint16_t lesser = 0; + + const uint8_t* const frame_end = cur + len; + + while ( cur < frame_end ) + { + auto tlv = advance(cur, frame_end); + if ( !tlv ) + goto bail; + + if (frame_end - cur < tlv->len) + goto bail; + + switch ( ntohs(tlv->subtype) ) + { + case 0x0001: + if ( check_user_name ) + { + constexpr auto USERNAME_LEN = 256; + char username[USERNAME_LEN]; + + if ( check_username(cur, tlv, username, username + USERNAME_LEN) ) + aim_client_mod.api->add_user(flowp, username, + APP_ID_AOL_INSTANT_MESSENGER, 1); + } + break; + case 0x0003: + got_id = true; + break; + case 0x0017: + got_id = true; + major = ntohs(*(uint16_t*)cur); + break; + case 0x0018: + got_id = true; + minor = ntohs(*(uint16_t*)cur); + break; + case 0x0019: + got_id = true; + lesser = ntohs(*(uint16_t*)cur); + break; + default: + break; + } + + cur += tlv->len; + } + + if ( got_id ) + { + char version[MAX_VERSION_SIZE]; + + snprintf(version, sizeof(version), "%d.%d.%d", major, minor, lesser); + aim_client_mod.api->add_app( + flowp, APP_ID_AOL_INSTANT_MESSENGER, + APP_ID_AOL_INSTANT_MESSENGER, version); + } + } + } + + return CLIENT_APP_INPROCESS; + +bail: + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_aim.h b/src/network_inspectors/appid/client_plugins/client_app_aim.h new file mode 100644 index 000000000..aca99d29a --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_aim.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_aim.h author Sourcefire Inc. + +#ifndef CLIENT_APP_AIM_H +#define CLIENT_APP_AIM_H + +#include "client_app_api.h" + +extern RNAClientAppModule aim_client_mod; + +#endif + diff --git a/src/network_inspectors/appid/client_plugins/client_app_api.h b/src/network_inspectors/appid/client_plugins/client_app_api.h new file mode 100644 index 000000000..feb985c94 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_api.h @@ -0,0 +1,152 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_api.h author Sourcefire Inc. + +#ifndef CLIENT_APP_API_H +#define CLIENT_APP_API_H + +#include + +#include "appid_api.h" +#include "appid_flow_data.h" + +struct Packet; +struct Detector; + +// Forward declaration for AppId config. Cannot include appIdConfig.h because of +// circular dependency +class AppIdConfig; + +enum CLIENT_APP_RETCODE +{ + CLIENT_APP_SUCCESS = 0, + CLIENT_APP_INPROCESS = 10, + CLIENT_APP_ENULL = -10, + CLIENT_APP_EINVALID = -11, + CLIENT_APP_ENOMEM = -12 +}; + +struct RNAClientAppModuleConfig +{ + const char* name; + SF_LIST items; +}; + +struct RNAClientAppModuleConfigItem +{ + const char* name; + const char* value; +}; + +using RNAClientAppFCN = CLIENT_APP_RETCODE(*)( + const uint8_t* data, + uint16_t size, + const int dir, + AppIdData*, + Packet*, + Detector*, + const AppIdConfig* +); + +struct IniClientAppAPI +{ + void (* RegisterPattern)( + RNAClientAppFCN, IpProtocol proto, const uint8_t* const pattern, + unsigned size, int position, AppIdConfig*); + + void (* RegisterPatternEx)( + RNAClientAppFCN, IpProtocol proto, const uint8_t* const pattern, + unsigned size, int position, Detector*); + + void (* RegisterPatternNoCase)( + RNAClientAppFCN, IpProtocol proto, const uint8_t* const pattern, + unsigned size, int position, AppIdConfig*); + + void (* RegisterAppId)( + RNAClientAppFCN, AppId, uint32_t additionalInfo, AppIdConfig*); + + int debug; + uint32_t instance_id; + AppIdConfig* pAppidConfig; ///< AppId context for which this API should be used +}; + +struct CleanClientAppAPI +{ + AppIdConfig* pAppidConfig = nullptr; ///< AppId context for which this API should be used +}; + +struct FinalizeClientAppAPI +{ + void* data = nullptr; +}; + +using RNAClientAppInitFCN = CLIENT_APP_RETCODE(*)(const IniClientAppAPI* const, SF_LIST* config); +using RNAClientAppFinalizeFCN = CLIENT_APP_RETCODE (*)(const FinalizeClientAppAPI* const); +using RNAClientAppCleanFCN = void(*)(const CleanClientAppAPI* const); + +using ClientAppFlowdataGet = void*(*)(AppIdData*, unsigned); +using ClientAppFlowdataAdd = int(*)(AppIdData*, void*, unsigned, AppIdFreeFCN); +using ClientAppAddApp = void(*)(AppIdData*, AppId, AppId, const char*); +using ClientAppAddInfo = void(*)(AppIdData*, const char*); +using ClientAppAddUser = void(*)(AppIdData*, const char*, AppId, int); +using ClientAppAddPayload = void(*)(AppIdData*, AppId); + +struct ClientAppApi +{ + ClientAppFlowdataGet data_get; + ClientAppFlowdataAdd data_add; + ClientAppAddApp add_app; + ClientAppAddInfo add_info; + ClientAppAddUser add_user; + ClientAppAddPayload add_payload; +}; + +struct RNAClientAppRecord +{ + RNAClientAppRecord* next; + const struct RNAClientAppModule* module; +}; + +struct RNAClientAppModule +{ + const char* name; + IpProtocol proto; + RNAClientAppInitFCN init; + RNAClientAppCleanFCN clean; + RNAClientAppFCN validate; + unsigned minimum_matches; + const ClientAppApi* api; + Detector* userData; + + /**precedence of this detector.*/ + unsigned int precedence; + RNAClientAppFinalizeFCN finalize; + + int provides_user; + unsigned flow_data_index; +}; + +struct RNAClientAppFlowState +{ + RNAClientAppFlowState* next; + const RNAClientAppModule* ca; +}; + +#endif diff --git a/src/network_inspectors/appid/client_plugins/client_app_base.cc b/src/network_inspectors/appid/client_plugins/client_app_base.cc new file mode 100644 index 000000000..05ea6a717 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_base.cc @@ -0,0 +1,997 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_base.cc author Ron Dempster + +#include +#include +#include +#include +#include +#include +#include + +#include "client_app_base.h" + +#include "main/snort_debug.h" +#include "log/messages.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" +#include "utils/util.h" +#include "profiler/profiler.h" + +#include "appid_api.h" +#include "appid_config.h" +#include "fw_appid.h" +#include "client_app_api.h" +#include "client_app_base.h" +#include "client_app_smtp.h" +#include "client_app_msn.h" +#include "client_app_aim.h" +#include "client_app_ym.h" +#include "detector_plugins/detector_sip.h" +#include "lua_detector_module.h" +#include "lua_detector_api.h" +#include "http_common.h" +#include "service_plugins/service_ssl.h" +#include "detector_plugins/detector_dns.h" +#include "detector_plugins/detector_pattern.h" + +/*#define CLIENT_APP_DEBUG 1 */ + +#define BUFSIZE 512 + +/* If this is greater than 1, more than 1 client detector can be searched for + * and tried per flow based on pattern (if a valid detector doesn't + * already exist). */ +#define MAX_CANDIDATE_CLIENTS 10 + +static void* client_app_flowdata_get(AppIdData* flowp, unsigned client_id); +static int client_app_flowdata_add(AppIdData* flowp, void* data, unsigned client_id, AppIdFreeFCN + fcn); +static void AppIdAddClientAppInfo(AppIdData* flowp, const char* info); + +static const ClientAppApi client_app_api = +{ + &client_app_flowdata_get, + &client_app_flowdata_add, + &AppIdAddClientApp, + &AppIdAddClientAppInfo, + &AppIdAddUser, + &AppIdAddPayload +}; + +static void LuaClientAppRegisterPattern(RNAClientAppFCN fcn, IpProtocol proto, + const uint8_t* const pattern, unsigned size, + int position, struct Detector* userData); +static void CClientAppRegisterPattern(RNAClientAppFCN fcn, IpProtocol proto, + const uint8_t* const pattern, unsigned size, + int position, AppIdConfig* pConfig); +static void CClientAppRegisterPatternNoCase(RNAClientAppFCN fcn, IpProtocol proto, + const uint8_t* const pattern, unsigned size, + int position, AppIdConfig* pConfig); + +static IniClientAppAPI client_init_api = +{ + &CClientAppRegisterPattern, + &LuaClientAppRegisterPattern, + &CClientAppRegisterPatternNoCase, + &appSetClientValidator, + 0, + 0, + nullptr +}; + +static CleanClientAppAPI clean_api = +{ +}; + +static FinalizeClientAppAPI finalize_api = +{ +}; + +extern RNAClientAppModule bit_client_mod; +extern RNAClientAppModule bit_tracker_client_mod; +extern RNAClientAppModule rtp_client_mod; +extern RNAClientAppModule ssh_client_mod; +extern RNAClientAppModule timbuktu_client_mod; +extern RNAClientAppModule tns_client_mod; +extern RNAClientAppModule vnc_client_mod; +extern RNAClientAppModule pattern_udp_client_mod; +extern RNAClientAppModule pattern_tcp_client_mod; +extern RNAClientAppModule http_client_mod; + +static RNAClientAppModule* static_client_list[] = +{ + &smtp_client_mod, + +#ifdef REMOVED_WHILE_NOT_IN_USE + &msn_client_mod, + &aim_client_mod, + &ym_client_mod, + &sip_udp_client_mod, + &sip_tcp_client_mod, + &bit_client_mod, + &bit_tracker_client_mod, + &rtp_client_mod, + &ssh_client_mod, + &timbuktu_client_mod, + &tns_client_mod, + &vnc_client_mod, + &pattern_udp_client_mod, + &pattern_tcp_client_mod, +#endif + &dns_udp_client_mod, + &dns_tcp_client_mod, + &http_client_mod +}; + +/*static const char * const MODULE_NAME = "ClientApp"; */ + +const ClientAppApi* getClientApi(void) +{ + return &client_app_api; +} + +RNAClientAppModuleConfig* geClientAppModuleConfig(const char* moduleName, + ClientAppConfig* pClientAppConfig) +{ + SF_LNODE* cursor; + RNAClientAppModuleConfig* mod_config; + + for (mod_config = (RNAClientAppModuleConfig*)sflist_first(&pClientAppConfig->module_configs, + &cursor); + mod_config; + mod_config = (RNAClientAppModuleConfig*)sflist_next(&cursor)) + { + if (strcasecmp(mod_config->name, moduleName) == 0) + break; + } + return mod_config; +} + +const RNAClientAppModule* ClientAppGetClientAppModule(RNAClientAppFCN fcn, struct + Detector* userdata, + ClientAppConfig* pClientAppConfig) +{ + RNAClientAppRecord* li; + + for (li = pClientAppConfig->tcp_client_app_list; li; li=li->next) + { + if ((li->module->validate == fcn) && (li->module->userData == userdata)) + return li->module; + } + for (li=pClientAppConfig->udp_client_app_list; li; li=li->next) + { + if ((li->module->validate == fcn) && (li->module->userData == userdata)) + return li->module; + } + return nullptr; +} + +void add_pattern_data(SearchTool* st, const RNAClientAppModule* li, int position, const + uint8_t* const pattern, unsigned size, + unsigned nocase, int* count, ClientAppConfig* pClientAppConfig) +{ + ClientPatternData* pd = (ClientPatternData*)snort_calloc(sizeof(ClientPatternData)); + pd->ca = li; + pd->position = position; + (*count)++; + pd->next = pClientAppConfig->pattern_data_list; + pClientAppConfig->pattern_data_list = pd; + st->add((const char*)pattern, size, pd, nocase); +} + +static void clientCreatePattern(IpProtocol proto, const uint8_t* const pattern, unsigned size, + int position, unsigned nocase, const RNAClientAppModule* li, ClientAppConfig* pClientAppConfig) +{ + int* count; + + if (!li) + { + ErrorMessage("Invalid client app when registering a pattern"); + return; + } + + if (proto == IpProtocol::TCP) + { + if (pClientAppConfig->tcp_patterns) + delete pClientAppConfig->tcp_patterns; + pClientAppConfig->tcp_patterns = new SearchTool("ac_full"); + pClientAppConfig->udp_patterns = nullptr; + count = &pClientAppConfig->tcp_pattern_count; + add_pattern_data(pClientAppConfig->tcp_patterns, li, position, pattern, size, + nocase, count, pClientAppConfig); + } + else if (proto == IpProtocol::UDP) + { + if (pClientAppConfig->udp_patterns) + delete pClientAppConfig->udp_patterns; + pClientAppConfig->udp_patterns = new SearchTool("ac_full"); + pClientAppConfig->tcp_patterns = nullptr; + count = &pClientAppConfig->udp_pattern_count; + add_pattern_data(pClientAppConfig->udp_patterns, li, position, pattern, size, + nocase, count, pClientAppConfig); + } + else + { + pClientAppConfig->tcp_patterns = nullptr; + pClientAppConfig->udp_patterns = nullptr; + ErrorMessage("Invalid protocol when registering a pattern: %u\n",(unsigned)proto); + } +} + +static void CClientAppRegisterPattern(RNAClientAppFCN fcn, IpProtocol proto, + const uint8_t* const pattern, unsigned size, + int position, AppIdConfig* pConfig) +{ + ClientAppRegisterPattern(fcn, proto, pattern, size, position, 0, nullptr, + &pConfig->clientAppConfig); +} + +static void CClientAppRegisterPatternNoCase(RNAClientAppFCN fcn, IpProtocol proto, + const uint8_t* const pattern, unsigned size, + int position, AppIdConfig* pConfig) +{ + ClientAppRegisterPattern(fcn, proto, pattern, size, position, 1, nullptr, + &pConfig->clientAppConfig); +} + +static void LuaClientAppRegisterPattern(RNAClientAppFCN fcn, IpProtocol proto, + const uint8_t* const pattern, unsigned size, int position, struct Detector* userData) +{ + ClientAppRegisterPattern(fcn, proto, pattern, size, position, 0, userData, + &userData->pAppidNewConfig->clientAppConfig); +} + +void ClientAppRegisterPattern(RNAClientAppFCN fcn, IpProtocol proto, const uint8_t* const pattern, + unsigned size, int position, unsigned nocase, struct Detector* userData, + ClientAppConfig* pClientAppConfig) +{ + RNAClientAppRecord* list; + RNAClientAppRecord* li; + + if (proto == IpProtocol::TCP) + list = pClientAppConfig->tcp_client_app_list; + else if (proto == IpProtocol::UDP) + list = pClientAppConfig->udp_client_app_list; + else + { + ErrorMessage("Invalid protocol when registering a pattern: %u\n",(unsigned)proto); + return; + } + + for (li=list; li; li=li->next) + { + if ((li->module->validate == fcn) && (li->module->userData == userData)) + { + clientCreatePattern(proto, pattern, size, position, nocase, li->module, + pClientAppConfig); + break; + } + } +} + +int ClientAppLoadForConfigCallback(void* symbol, ClientAppConfig* pClientAppConfig) +{ + static unsigned client_module_index = 0; + RNAClientAppModule* cam = (RNAClientAppModule*)symbol; + RNAClientAppRecord** list = nullptr; + RNAClientAppRecord* li; + + DebugFormat(DEBUG_LOG,"Adding client %s for protocol %u\n",cam->name, (unsigned)cam->proto); + + if (client_module_index >= 65536) + { + ErrorMessage("Maximum number of client modules exceeded"); + return -1; + } + + if (cam->proto == IpProtocol::TCP) + { + list = &pClientAppConfig->tcp_client_app_list; + } + else if (cam->proto == IpProtocol::UDP) + { + list = &pClientAppConfig->udp_client_app_list; + } + else + { + ErrorMessage("Client %s did not have a valid protocol (%u)", + cam->name, (unsigned)cam->proto); + return -1; + } + + for (li = *list; li; li=li->next) + { + if (li->module == cam) + break; + } + if (!li) + { + li = (RNAClientAppRecord*)snort_calloc(sizeof(RNAClientAppRecord)); + li->next = *list; + *list = li; + li->module = cam; + cam->api = &client_app_api; + cam->flow_data_index = client_module_index | APPID_SESSION_DATA_CLIENT_MODSTATE_BIT; + client_module_index++; + } + /*Can't set cam->userData to nullptr because Lua detectors use it although C detectors don't + cam->userData = nullptr; */ + return 0; +} + +int ClientAppLoadCallback(void* symbol) +{ + return ClientAppLoadForConfigCallback(symbol, &pAppidActiveConfig->clientAppConfig); +} + +int LoadClientAppModules(AppIdConfig* pConfig) +{ + unsigned i; + + for (i=0; iclientAppConfig)) + return -1; + } + + return 0; +} + +static void AddModuleConfigItem(char* module_name, char* item_name, char* item_value, + ClientAppConfig* config) +{ + SF_LNODE* cursor; + RNAClientAppModuleConfig* mod_config; + RNAClientAppModuleConfigItem* item; + + for (mod_config = (RNAClientAppModuleConfig*)sflist_first(&config->module_configs, &cursor); + mod_config; + mod_config = (RNAClientAppModuleConfig*)sflist_next(&cursor)) + { + if (strcasecmp(mod_config->name, module_name) == 0) + break; + } + + if (!mod_config) + { + mod_config = (RNAClientAppModuleConfig*)snort_calloc(sizeof(RNAClientAppModuleConfig)); + mod_config->name = snort_strdup(module_name); + sflist_init(&mod_config->items); + if (sflist_add_tail(&config->module_configs, mod_config)) + { + ErrorMessage("Failed to add a module configuration"); + exit(-1); + } + } + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(&mod_config->items, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + if (strcasecmp(item->name, item_name) == 0) + break; + } + + if (!item) + { + item = (RNAClientAppModuleConfigItem*)snort_calloc(sizeof(RNAClientAppModuleConfigItem)); + item->name = snort_strdup(item_name); + if (sflist_add_tail(&mod_config->items, item)) + { + ErrorMessage("Failed to add a module configuration item"); + exit(-1); + } + } + + if (item->value) + snort_free((void*)item->value); + + item->value = snort_strdup(item_value); +} + +static void ClientAppParseOption(ClientAppConfig* config, + char* key, char* value) +{ + char* p; + + if (!strcasecmp(key, "enable")) + { + config->enabled = atoi(value); + } + else if ((p = strchr(key, ':')) && p[1]) + { + *p = 0; + AddModuleConfigItem(key, &p[1], value, config); + *p = ':'; + } + else + { + DebugFormat(DEBUG_LOG, "Unknown client app argument ignored: key(%s) value(%s)", + key, value); + } +} + +static int ClientAppParseArgs(ClientAppConfig* config, SF_LIST* args) +{ + ConfigItem* ci; + SF_LNODE* cursor; + + for (ci=(ConfigItem*)sflist_first(args, &cursor); + ci; + ci=(ConfigItem*)sflist_next(&cursor)) + { + ClientAppParseOption(config, ci->name, ci->value); + } + + return 0; +} + +#define MAX_DISPLAY_SIZE 65536 +static void DisplayClientAppConfig(ClientAppConfig* config) +{ + static char buffer[MAX_DISPLAY_SIZE]; + int position = 0; + int tmp; + RNAClientAppModuleConfig* mod_config; + RNAClientAppModuleConfigItem* item; + SF_LNODE* cursor; + + tmp = snprintf(&buffer[position], MAX_DISPLAY_SIZE-position, + "\n----------------------------------------------\nRNA Client App Config\n"); + if (tmp >= MAX_DISPLAY_SIZE-position) + position = MAX_DISPLAY_SIZE; + else if (tmp > 0) + position += tmp; + + tmp = snprintf(&buffer[position], MAX_DISPLAY_SIZE-position, + "Enabled: %s\n", config->enabled ? "Yes" : "No"); + if (tmp >= MAX_DISPLAY_SIZE-position) + position = MAX_DISPLAY_SIZE; + else if (tmp > 0) + position += tmp; + + for (mod_config = (RNAClientAppModuleConfig*)sflist_first(&config->module_configs, &cursor); + mod_config; + mod_config = (RNAClientAppModuleConfig*)sflist_next(&cursor)) + { + SF_LNODE* cursor; + + tmp = snprintf(&buffer[position], MAX_DISPLAY_SIZE-position, "%s\n", mod_config->name); + if (tmp >= MAX_DISPLAY_SIZE-position) + position = MAX_DISPLAY_SIZE; + else if (tmp > 0) + position += tmp; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(&mod_config->items, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + tmp = snprintf(&buffer[position], MAX_DISPLAY_SIZE-position, + " %s: %s\n", item->name, item->value); + if (tmp >= MAX_DISPLAY_SIZE-position) + position = MAX_DISPLAY_SIZE; + else if (tmp > 0) + position += tmp; + } + } + tmp = snprintf(&buffer[position], MAX_DISPLAY_SIZE-position, + "----------------------------------------------\n"); + if (tmp >= MAX_DISPLAY_SIZE-position) + position = MAX_DISPLAY_SIZE; + else if (tmp > 0) + position += tmp; + + DebugFormat(DEBUG_LOG,"%s\n",buffer); +} + +static void free_module_config_item(void* module_config_item) +{ + RNAClientAppModuleConfigItem* item = (RNAClientAppModuleConfigItem*)module_config_item; + + if (item) + { + if (item->name) + snort_free((void*)item->name); + if (item->value) + snort_free((void*)item->value); + snort_free(item); + } +} + +static void free_module_config(void* module_config) +{ + RNAClientAppModuleConfig* config = (RNAClientAppModuleConfig*)module_config; + if (config) + { + if (config->name) + snort_free((void*)config->name); + sflist_static_free_all(&config->items, &free_module_config_item); + snort_free(config); + } +} + +static void initialize_module(RNAClientAppRecord* li, ClientAppConfig* pClientAppConfig) +{ + RNAClientAppModuleConfig* mod_config; + SF_LNODE* cursor; + int rval; + + for (mod_config = (RNAClientAppModuleConfig*)sflist_first(&pClientAppConfig->module_configs, + &cursor); + mod_config; + mod_config = (RNAClientAppModuleConfig*)sflist_next(&cursor)) + { + if (strcasecmp(mod_config->name, li->module->name) == 0) + break; + } + if (li->module->init && (rval=li->module->init(&client_init_api, mod_config ? + &mod_config->items : nullptr)) != CLIENT_APP_SUCCESS) + { + ErrorMessage("Could not initialize the %s client app element: %d\n",li->module->name, + rval); + exit(-1); + } +} + +static void finalize_module(RNAClientAppRecord* li) +{ + int rval; + + if ( li->module->finalize && ( rval = (li->module->finalize)(&finalize_api) ) != + CLIENT_APP_SUCCESS ) + { + ErrorMessage("Could not finlize the %s client app element: %d\n",li->module->name, rval); + exit(-1); + } +} + +static void clean_module(RNAClientAppRecord* li) +{ + if (li->module->clean) + li->module->clean(&clean_api); +} + +void UnconfigureClientApp(AppIdConfig* pConfig) +{ + ClientPatternData* pd; + RNAClientAppRecord* li; + + clean_api.pAppidConfig = pConfig; + for (li = pConfig->clientAppConfig.tcp_client_app_list; li; li = li->next) + clean_module(li); + for (li = pConfig->clientAppConfig.udp_client_app_list; li; li = li->next) + clean_module(li); + + // FIXIT - should this be deleted here? or move this clean up to a dtor? + delete pConfig->clientAppConfig.tcp_patterns; + pConfig->clientAppConfig.tcp_patterns = nullptr; + + delete pConfig->clientAppConfig.udp_patterns; + pConfig->clientAppConfig.udp_patterns = nullptr; + + while (pConfig->clientAppConfig.pattern_data_list) + { + pd = pConfig->clientAppConfig.pattern_data_list; + pConfig->clientAppConfig.pattern_data_list = pd->next; + snort_free((void*)pd); + } + + CleanHttpPatternLists(pConfig); +#ifdef REMOVED_WHILE_NOT_IN_USE + ssl_detector_free_patterns(&pConfig->serviceSslConfig); +#endif + dns_detector_free_patterns(&pConfig->serviceDnsConfig); + CleanClientPortPatternList(pConfig); + + sflist_static_free_all(&pConfig->clientAppConfig.module_configs, &free_module_config); +} + +/** + * Initialize the configuration of the client app module + * + * @param args + */ +void ClientAppInit(AppIdConfig* pConfig) +{ + RNAClientAppRecord* li; + + sflist_init(&pConfig->clientAppConfig.module_configs); + pConfig->clientAppConfig.enabled = 1; + + ClientAppParseArgs(&pConfig->clientAppConfig, &pConfig->client_app_args); + DisplayClientAppConfig(&pConfig->clientAppConfig); + + if (pConfig->clientAppConfig.enabled) + { + client_init_api.debug = app_id_debug; + client_init_api.pAppidConfig = pConfig; + // FIXIT - active config global must go... + client_init_api.instance_id = pAppidActiveConfig->mod_config->instance_id; + + for (li = pConfig->clientAppConfig.tcp_client_app_list; li; li = li->next) + initialize_module(li, &pConfig->clientAppConfig); + for (li = pConfig->clientAppConfig.udp_client_app_list; li; li = li->next) + initialize_module(li, &pConfig->clientAppConfig); + + luaModuleInitAllClients(); + + for (li = pConfig->clientAppConfig.tcp_client_app_list; li; li = li->next) + finalize_module(li); + + for (li = pConfig->clientAppConfig.udp_client_app_list; li; li = li->next) + finalize_module(li); + } +} + +void ClientAppFinalize(AppIdConfig* pConfig) +{ + if (pConfig->clientAppConfig.enabled) + { + if ( pConfig->clientAppConfig.tcp_patterns ) + pConfig->clientAppConfig.tcp_patterns->prep(); + + if ( pConfig->clientAppConfig.udp_patterns ) + pConfig->clientAppConfig.udp_patterns->prep(); + } +} + +struct ClientAppMatch +{ + struct ClientAppMatch* next; + unsigned count; + const RNAClientAppModule* ca; +}; + +static ClientAppMatch* match_free_list; + +/** + * Clean up the configuration of the client app module + */ +void CleanupClientApp(AppIdConfig* /* pConfig */) +{ +#ifdef RNA_FULL_CLEANUP + ClientAppMatch* match; + ClientPatternData* pd; + RNAClientAppRecord* li; + + clean_api.pAppidConfig = pConfig; + if (pConfig->clientAppConfig.tcp_patterns) + { + delete pConfig->clientAppConfig.tcp_patterns; + pConfig->clientAppConfig.tcp_patterns = nullptr; + } + if (pConfig->clientAppConfig.udp_patterns) + { + delete pConfig->clientAppConfig.udp_patterns; + pConfig->clientAppConfig.udp_patterns = nullptr; + } + while ((pd = pConfig->clientAppConfig.pattern_data_list) != nullptr) + { + pConfig->clientAppConfig.pattern_data_list = pd->next; + snort_free(pd); + } + + while ((li=pConfig->clientAppConfig.tcp_client_app_list) != nullptr) + { + pConfig->clientAppConfig.tcp_client_app_list = li->next; + if (li->module->clean) + li->module->clean(&clean_api); + snort_free(li); + } + while ((li=pConfig->clientAppConfig.udp_client_app_list) != nullptr) + { + pConfig->clientAppConfig.udp_client_app_list = li->next; + if (li->module->clean) + li->module->clean(&clean_api); + snort_free(li); + } + + luaModuleCleanAllClients(); + + CleanHttpPatternLists(pConfig); + ssl_detector_free_patterns(&pConfig->serviceSslConfig); + dns_detector_free_patterns(&pConfig->serviceDnsConfig); + CleanClientPortPatternList(pConfig); + + sflist_static_free_all(&pConfig->clientAppConfig.module_configs, &free_module_config); + while ((match=match_free_list) != nullptr) + { + match_free_list = match->next; + snort_free(match); + } +#endif +} + +/* + * Callback function for string search + * + * @param id id in array of search strings from pop_config.cmds + * @param index index in array of search strings from pop_config.cmds + * @param data buffer passed in to search function + * + * @return response + * @retval 1 commands caller to stop searching + */ +static int pattern_match(void* id, void* /*unused_tree*/, int index, void* data, + void* /*unused_neg*/) +{ + ClientAppMatch** matches = (ClientAppMatch**)data; + ClientPatternData* pd = (ClientPatternData*)id; + ClientAppMatch* cam; + + if ( pd->position >= 0 && pd->position != index ) + return 0; + + for (cam = *matches; cam; cam = cam->next) + { + if (cam->ca == pd->ca) + break; + } + + if (cam) + cam->count++; + else + { + if (match_free_list) + { + cam = match_free_list; + match_free_list = cam->next; + memset(cam, 0, sizeof(*cam)); + } + else + cam = (ClientAppMatch*)snort_calloc(sizeof(ClientAppMatch)); + + cam->count = 1; + cam->ca = pd->ca; + cam->next = *matches; + *matches = cam; + } + return 0; +} + +void AppIdAddClientApp(AppIdData* flowp, AppId service_id, AppId id, const char* version) +{ + if (version) + { + if (flowp->clientVersion) + { + if (strcmp(version, flowp->clientVersion)) + { + snort_free(flowp->clientVersion); + flowp->clientVersion = snort_strdup(version); + } + } + else + flowp->clientVersion = snort_strdup(version); + } + + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + flowp->ClientServiceAppId = service_id; + flowp->ClientAppId = id; + checkSandboxDetection(id); +} + +static void AppIdAddClientAppInfo(AppIdData* flowp, const char* info) +{ + if (flowp->hsession && !flowp->hsession->url) + flowp->hsession->url = snort_strdup(info); +} + +static ClientAppMatch* BuildClientPatternList(const Packet* pkt, IpProtocol protocol, + const ClientAppConfig* pClientAppConfig) +{ + ClientAppMatch* match_list = nullptr; + SearchTool* patterns; + + if (protocol == IpProtocol::TCP) + patterns = pClientAppConfig->tcp_patterns; + else + patterns = pClientAppConfig->udp_patterns; + + if (!patterns) + return nullptr; + + patterns->find_all((char*)pkt->data, pkt->dsize, &pattern_match, false, (void*)&match_list); + + return match_list; +} + +static const RNAClientAppModule* GetNextFromClientPatternList(ClientAppMatch** match_list) +{ + ClientAppMatch* curr = nullptr; + ClientAppMatch* prev = nullptr; + ClientAppMatch* max_curr = nullptr; + ClientAppMatch* max_prev = nullptr; + unsigned max_count; + unsigned max_precedence; + + curr = *match_list; + max_count = 0; + max_precedence = 0; + while (curr) + { + if (curr->count >= curr->ca->minimum_matches + && ((curr->count > max_count) + || (curr->count == max_count && curr->ca->precedence > max_precedence))) + { + max_count = curr->count; + max_precedence = curr->ca->precedence; + max_curr = curr; + max_prev = prev; + } + prev = curr; + curr = curr->next; + } + + if (max_curr != nullptr) + { + if (max_prev == nullptr) + { + *match_list = (*match_list)->next; + } + else + { + max_prev->next = max_curr->next; + } + max_curr->next = match_free_list; + match_free_list = max_curr; + return max_curr->ca; + } + else + { + return nullptr; + } +} + +static void FreeClientPatternList(ClientAppMatch** match_list) +{ + ClientAppMatch* cam; + ClientAppMatch* tmp; + + cam = *match_list; + while (cam) + { + tmp = cam; + cam = tmp->next; + tmp->next = match_free_list; + match_free_list = tmp; + } + + *match_list = nullptr; +} + +/** + * The process to determine the running client app given the packet data. + * + * @param p packet to process + */ +static void ClientAppID(Packet* p, const int /*direction*/, AppIdData* flowp, const + AppIdConfig* pConfig) +{ + const RNAClientAppModule* client = nullptr; + ClientAppMatch* match_list; + +#ifdef CLIENT_APP_DEBUG + _dpd.logMsg("Client"); +#endif + + if (!p->dsize) + return; + + if (flowp->clientData != nullptr) + return; + + if (flowp->candidate_client_list != nullptr) + { + if (flowp->num_candidate_clients_tried > 0) + return; + } + else + { + flowp->candidate_client_list = (SF_LIST*) snort_calloc(sizeof(SF_LIST)); + sflist_init(flowp->candidate_client_list); + flowp->num_candidate_clients_tried = 0; + } + + match_list = BuildClientPatternList(p, flowp->proto, &pConfig->clientAppConfig); + while (flowp->num_candidate_clients_tried < MAX_CANDIDATE_CLIENTS) + { + const RNAClientAppModule* tmp = GetNextFromClientPatternList(&match_list); + if (tmp != nullptr) + { + SF_LNODE* cursor; + + client = (RNAClientAppModule*)sflist_first(flowp->candidate_client_list, &cursor); + while (client && (client != tmp)) + client = (RNAClientAppModule*)sflist_next(&cursor); + if (client == nullptr) + { + sflist_add_tail(flowp->candidate_client_list, (void*)tmp); + flowp->num_candidate_clients_tried++; +#ifdef CLIENT_APP_DEBUG + _dpd.logMsg("Using %s from pattern match", tmp ? tmp->name : \ n ",ULL"); +#endif + } + } + else + { + break; + } + } + FreeClientPatternList(&match_list); + + if (sflist_count(flowp->candidate_client_list) == 0) + { + client = nullptr; + switch (p->ptrs.dp) + { + case 465: + if (getAppIdFlag(flowp, APPID_SESSION_DECRYPTED)) + client = &smtp_client_mod; + break; + default: + break; + } + if (client != nullptr) + { + sflist_add_tail(flowp->candidate_client_list, (void*)client); + flowp->num_candidate_clients_tried++; + } + } +} + +int AppIdDiscoverClientApp(Packet* p, int direction, AppIdData* rnaData, const + AppIdConfig* pConfig) +{ + if (!pConfig->clientAppConfig.enabled) + return APPID_SESSION_SUCCESS; + + if (direction == APP_ID_FROM_INITIATOR) + { + /* get out if we've already tried to validate a client app */ + if (!getAppIdFlag(rnaData, APPID_SESSION_CLIENT_DETECTED)) + ClientAppID(p, direction, rnaData, pConfig); + } + else if (rnaData->rnaServiceState != RNA_STATE_STATEFUL && getAppIdFlag(rnaData, + APPID_SESSION_CLIENT_GETS_SERVER_PACKETS)) + ClientAppID(p, direction, rnaData, pConfig); + + return APPID_SESSION_SUCCESS; +} + +DetectorAppUrlList* geAppUrlList(AppIdConfig* pConfig) +{ + HttpPatternLists* patternLists = &pConfig->httpPatternLists; + return (&patternLists->appUrlList); +} + +static void* client_app_flowdata_get(AppIdData* flowp, unsigned client_id) +{ + return AppIdFlowdataGet(flowp, client_id); +} + +static int client_app_flowdata_add(AppIdData* flowp, void* data, unsigned client_id, AppIdFreeFCN + fcn) +{ + return AppIdFlowdataAdd(flowp, data, client_id, fcn); +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_base.h b/src/network_inspectors/appid/client_plugins/client_app_base.h new file mode 100644 index 000000000..ea6b28d5a --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_base.h @@ -0,0 +1,65 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_base.h author Sourcefire Inc. + +#ifndef CLIENT_APP_BASE_H +#define CLIENT_APP_BASE_H + +#include "appid_api.h" + +#include "client_app_api.h" + +#define GENERIC_APP_OFFSET 2000000000 + +class AppIdData; +class AppIdConfig; +struct Detector; +struct DetectorSipConfig; +struct ClientAppConfig; +struct RNAClientAppModule; +struct DetectorAppUrlList; +struct Packet; +struct ClientAppApi; +struct RNAClientAppModuleConfig; + +void ClientAppInit(AppIdConfig*); +void ClientAppFinalize(AppIdConfig*); +void UnconfigureClientApp(AppIdConfig*); +void CleanupClientApp(AppIdConfig*); +int ClientAppLoadCallback(void* symbol); +int ClientAppLoadForConfigCallback(void* symbol, ClientAppConfig*); +int LoadClientAppModules(AppIdConfig*); +void ClientAppRegisterPattern(RNAClientAppFCN, IpProtocol proto, const uint8_t* const pattern, + unsigned size, int position, unsigned nocase, Detector*, ClientAppConfig*); +const ClientAppApi* getClientApi(); +RNAClientAppModuleConfig* getClientAppModuleConfig(const char* moduleName, ClientAppConfig*); +int AppIdDiscoverClientApp(Packet* p, int direction, AppIdData*, const AppIdConfig*); +void AppIdAddClientApp(AppIdData*, AppId service_id, AppId id, const char* version); +DetectorAppUrlList* getAppUrlList(AppIdConfig*); +const RNAClientAppModule* ClientAppGetClientAppModule(RNAClientAppFCN, Detector*, + ClientAppConfig*); +int sipUaPatternAdd( AppId, const char* clientVersion, const char* uaPattern, DetectorSipConfig*); +int sipServerPatternAdd(AppId, const char* clientVersion, const char* uaPattern, + DetectorSipConfig*); +int sipUaFinalize(DetectorSipConfig*); +int sipServerFinalize(); +int portPatternFinalize(AppIdConfig*); + +#endif diff --git a/src/network_inspectors/appid/client_plugins/client_app_bit.cc b/src/network_inspectors/appid/client_plugins/client_app_bit.cc new file mode 100644 index 000000000..286b00538 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_bit.cc @@ -0,0 +1,229 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_bit.cc author Sourcefire Inc. + +#include "client_app_api.h" +#include "application_ids.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +static const char BIT_BANNER[] = "\023BitTorrent protocol"; + +#define BIT_BANNER_LEN (sizeof(BIT_BANNER)-1) +#define RES_LEN 8 +#define SHA_LEN 20 +#define MAX_STR_LEN 20 +#define PEER_ID_LEN 20 +#define MAX_VER_LEN 4 +#define LAST_BANNER_OFFSET (BIT_BANNER_LEN+RES_LEN+SHA_LEN+PEER_ID_LEN - 1) + +enum BITState +{ + BIT_STATE_BANNER = 0, + BIT_STATE_BANNER_DC, + BIT_STATE_MESSAGE_LEN, + BIT_STATE_MESSAGE_DATA +}; + +struct ClientBITData +{ + BITState state; + unsigned stringlen; + unsigned pos; + union + { + uint32_t len; + uint8_t raw_len[4]; + } l; +}; + +#pragma pack(1) +struct ClientBITMsg +{ + uint32_t len; + uint8_t code; +}; +#pragma pack() + +struct BIT_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL BIT_CLIENT_APP_CONFIG bit_config; + +static CLIENT_APP_RETCODE bit_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE bit_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, + const AppIdConfig* pConfig); + +SO_PUBLIC RNAClientAppModule bit_client_mod = +{ + "BIT", + IpProtocol::TCP, + &bit_init, + nullptr, + &bit_validate, + 1, + nullptr, + nullptr, + 0, + nullptr, + 0, + 0 +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern patterns[] = +{ + { (const uint8_t*)BIT_BANNER, sizeof(BIT_BANNER)-1, 0, APP_ID_BITTORRENT }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_BITTORRENT, 0 } +}; + +static CLIENT_APP_RETCODE bit_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + RNAClientAppModuleConfigItem* item; + + bit_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor = nullptr; + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + bit_config.enabled = atoi(item->value); + } + } + } + + if (bit_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&bit_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&bit_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static CLIENT_APP_RETCODE bit_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig*) +{ + ClientBITData* fd; + uint16_t offset; + + if (dir != APP_ID_FROM_INITIATOR) + return CLIENT_APP_INPROCESS; + + fd = (ClientBITData*)bit_client_mod.api->data_get(flowp, bit_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientBITData*)snort_calloc(sizeof(ClientBITData)); + bit_client_mod.api->data_add(flowp, fd, bit_client_mod.flow_data_index, &snort_free); + fd->state = BIT_STATE_BANNER; + } + + offset = 0; + while (offset < size) + { + switch (fd->state) + { + case BIT_STATE_BANNER: + if (data[offset] != BIT_BANNER[fd->pos]) + return CLIENT_APP_EINVALID; + if (fd->pos == BIT_BANNER_LEN-1) + fd->state = BIT_STATE_BANNER_DC; + fd->pos++; + break; + case BIT_STATE_BANNER_DC: + if (fd->pos == LAST_BANNER_OFFSET) + { + fd->pos = 0; + fd->state = BIT_STATE_MESSAGE_LEN; + break; + } + fd->pos++; + break; + case BIT_STATE_MESSAGE_LEN: + fd->l.raw_len[fd->pos] = data[offset]; + fd->pos++; + if (fd->pos >= offsetof(ClientBITMsg, code)) + { + fd->stringlen = ntohl(fd->l.len); + fd->state = BIT_STATE_MESSAGE_DATA; + if (!fd->stringlen) + { + if (offset == size-1) + goto done; + return CLIENT_APP_EINVALID; + } + fd->pos = 0; + } + break; + + case BIT_STATE_MESSAGE_DATA: + fd->pos++; + if (fd->pos == fd->stringlen) + goto done; + break; + default: + goto inprocess; + } + offset++; + } +inprocess: + return CLIENT_APP_INPROCESS; + +done: + bit_client_mod.api->add_app(flowp, APP_ID_BITTORRENT, APP_ID_BITTORRENT, nullptr); + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_bit_tracker.cc b/src/network_inspectors/appid/client_plugins/client_app_bit_tracker.cc new file mode 100644 index 000000000..0d96299be --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_bit_tracker.cc @@ -0,0 +1,274 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_bit_tracker.cc author Sourcefire Inc. + +#include "main/snort_debug.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_app_api.h" + +static const char UDP_BIT_QUERY[] = "d1:a"; +static const char UDP_BIT_RESPONSE[] = "d1:r"; +static const char UDP_BIT_ERROR[] = "d1:e"; +static const char UDP_BIT_FIRST[] = "d1:"; +static const char UDP_BIT_COMMON_END[] = "1:y1:"; + +#define UDP_BIT_FIRST_LEN (sizeof(UDP_BIT_FIRST)-1) +#define UDP_BIT_COMMON_END_LEN (sizeof(UDP_BIT_COMMON_END)-1) +#define UDP_BIT_END_LEN (UDP_BIT_COMMON_END_LEN+2) + +enum BITState +{ + BIT_STATE_BANNER = 0, + BIT_STATE_TYPES, + BIT_STATE_DC, + BIT_STATE_CHECK_END, + BIT_STATE_CHECK_END_TYPES, + BIT_STATE_CHECK_LAST +}; + +enum BITType +{ + BIT_TYPE_REQUEST = 1, + BIT_TYPE_RESPONSE, + BIT_TYPE_ERROR +}; + +struct ClientBITData +{ + BITState state; + BITType type; + unsigned pos; +}; + +struct BIT_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL BIT_CLIENT_APP_CONFIG udp_bit_config; + +static CLIENT_APP_RETCODE udp_bit_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE udp_bit_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, const AppIdConfig* pConfig); + +SO_PUBLIC RNAClientAppModule bit_tracker_client_mod = +{ + "BIT-UDP", // name + IpProtocol::UDP, // proto + &udp_bit_init, // init + nullptr, // clean + &udp_bit_validate, // validate + 1, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern udp_patterns[] = +{ + { (const uint8_t*)UDP_BIT_QUERY, sizeof(UDP_BIT_QUERY), 0, APP_ID_BITTRACKER_CLIENT }, + { (const uint8_t*)UDP_BIT_RESPONSE, sizeof(UDP_BIT_RESPONSE), 0, APP_ID_BITTRACKER_CLIENT }, + { (const uint8_t*)UDP_BIT_ERROR, sizeof(UDP_BIT_ERROR), 0, APP_ID_BITTRACKER_CLIENT }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_BITTRACKER_CLIENT, 0 } +}; + +static CLIENT_APP_RETCODE udp_bit_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + + udp_bit_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor; + RNAClientAppModuleConfigItem* item; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + udp_bit_config.enabled = atoi(item->value); + } + } + } + + if (udp_bit_config.enabled) + { + for (i=0; i < sizeof(udp_patterns)/sizeof(*udp_patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)udp_patterns[i].pattern, udp_patterns[i].index); + init_api->RegisterPattern(&udp_bit_validate, IpProtocol::UDP, udp_patterns[i].pattern, + udp_patterns[i].length, udp_patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&udp_bit_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static CLIENT_APP_RETCODE udp_bit_validate(const uint8_t* data, uint16_t size, const int /*dir*/, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig*) +{ + ClientBITData* fd; + uint16_t offset; + + if (size < (UDP_BIT_FIRST_LEN + UDP_BIT_END_LEN + 3)) + return CLIENT_APP_EINVALID; + + fd = (ClientBITData*)bit_tracker_client_mod.api->data_get(flowp, + bit_tracker_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientBITData*)snort_calloc(sizeof(ClientBITData)); + bit_tracker_client_mod.api->data_add(flowp, fd, + bit_tracker_client_mod.flow_data_index, &snort_free); + fd->state = BIT_STATE_BANNER; + } + + offset = 0; + while (offset < size) + { + switch (fd->state) + { + case BIT_STATE_BANNER: + if (data[offset] != UDP_BIT_FIRST[fd->pos]) + return CLIENT_APP_EINVALID; + if (fd->pos == UDP_BIT_FIRST_LEN-1) + fd->state = BIT_STATE_TYPES; + fd->pos++; + break; + case BIT_STATE_TYPES: + switch (data[offset]) + { + case 'a': + fd->type = BIT_TYPE_REQUEST; + fd->state = BIT_STATE_DC; + break; + case 'r': + fd->type = BIT_TYPE_RESPONSE; + fd->state = BIT_STATE_DC; + break; + case 'e': + fd->type = BIT_TYPE_ERROR; + fd->state = BIT_STATE_DC; + break; + default: + return CLIENT_APP_EINVALID; + } + break; + + case BIT_STATE_DC: + if (offset < (size - UDP_BIT_END_LEN)) + break; + else if (offset == (size - UDP_BIT_END_LEN) && data[offset] == UDP_BIT_COMMON_END[0]) + { + fd->state = BIT_STATE_CHECK_END; + fd->pos = 0; + } + else + return CLIENT_APP_EINVALID; + /*fall through */ + case BIT_STATE_CHECK_END: + if (data[offset] != UDP_BIT_COMMON_END[fd->pos]) + return CLIENT_APP_EINVALID; + if (fd->pos == UDP_BIT_COMMON_END_LEN-1) + fd->state = BIT_STATE_CHECK_END_TYPES; + fd->pos++; + break; + + case BIT_STATE_CHECK_END_TYPES: + switch (data[offset]) + { + case 'q': + if (fd->type != BIT_TYPE_REQUEST) + return CLIENT_APP_EINVALID; + fd->state = BIT_STATE_CHECK_LAST; + break; + case 'r': + if (fd->type != BIT_TYPE_RESPONSE) + return CLIENT_APP_EINVALID; + fd->state = BIT_STATE_CHECK_LAST; + break; + case 'e': + if (fd->type != BIT_TYPE_ERROR) + return CLIENT_APP_EINVALID; + fd->state = BIT_STATE_CHECK_LAST; + break; + default: + return CLIENT_APP_EINVALID; + } + break; + + case BIT_STATE_CHECK_LAST: + switch (data[offset]) + { + case 'e': + goto done; + default: + return CLIENT_APP_EINVALID; + } + break; + + default: + goto inprocess; + } + offset++; + } +inprocess: + return CLIENT_APP_INPROCESS; + +done: + bit_tracker_client_mod.api->add_app(flowp, APP_ID_BITTORRENT, APP_ID_BITTRACKER_CLIENT, + nullptr); + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_config.h b/src/network_inspectors/appid/client_plugins/client_app_config.h new file mode 100644 index 000000000..bd178e55a --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_config.h @@ -0,0 +1,51 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_config.h author Sourcefire Inc. + +#ifndef CLIENT_APP_CONFIG_H +#define CLIENT_APP_CONFIG_H + +#include "utils/sflsq.h" +#include "search_engines/search_tool.h" + +struct RNAClientAppModule; +struct RNAClientAppRecord; + +struct ClientPatternData +{ + ClientPatternData* next; + int position; + const RNAClientAppModule* ca; +}; + +struct ClientAppConfig +{ + RNAClientAppRecord* tcp_client_app_list; // List of all TCP client apps (C and Lua) + RNAClientAppRecord* udp_client_app_list; // List of all UDP client apps (C and Lua) + int enabled; + SF_LIST module_configs; + ClientPatternData* pattern_data_list; + SearchTool* tcp_patterns; + int tcp_pattern_count; + SearchTool* udp_patterns; + int udp_pattern_count; +}; + +#endif diff --git a/src/network_inspectors/appid/client_plugins/client_app_msn.cc b/src/network_inspectors/appid/client_plugins/client_app_msn.cc new file mode 100644 index 000000000..05de85fde --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_msn.cc @@ -0,0 +1,216 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_msn.cc author Sourcefire Inc. + +#include "client_app_msn.h" +#include "main/snort_debug.h" +#include "app_info_table.h" +#include "application_ids.h" + +#define MAX_VERSION_SIZE 64 + +struct MSN_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL MSN_CLIENT_APP_CONFIG msn_config; + +static CLIENT_APP_RETCODE msn_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, const AppIdConfig* pConfig); + +struct Client_App_Pattern +{ + const uint8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static const uint8_t VER[] = "VER "; +static const uint8_t CVRMAIN[] = "CVR0\x00d\x00a"; +static const uint8_t CVR[] = "CVR"; +static const uint8_t MSNMSGR[] = "MSNMSGR"; +static const uint8_t MACMSGS[] = "macmsgs"; +static const uint8_t MSMSGS[] = "MSMSGS"; + +static Client_App_Pattern patterns[] = +{ + { VER, sizeof(VER)-1, 0, APP_ID_MSN }, + { CVRMAIN, sizeof(CVRMAIN)-1, -1, APP_ID_MSN }, + { MSNMSGR, sizeof(MSNMSGR)-1, -1, APP_ID_MSN_MESSENGER }, + { MACMSGS, sizeof(MACMSGS)-1, -1, APP_ID_MSN_MESSENGER }, + { MSMSGS, sizeof(MSMSGS)-1, -1, APP_ID_MICROSOFT_WINDOWS_MESSENGER } +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_MICROSOFT_WINDOWS_MESSENGER, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_MSN_MESSENGER, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_MSN, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_MSNP, APPINFO_FLAG_CLIENT_ADDITIONAL } +}; + +static CLIENT_APP_RETCODE msn_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + RNAClientAppModuleConfigItem* item; + msn_config.enabled = 1; + + if (config) + { + SF_LNODE* iter = nullptr; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &iter); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&iter)) + { + DebugFormat(DEBUG_APPID,"Processing %s: %s\n",item->name, item->value); + + if (strcasecmp(item->name, "enabled") == 0) + { + msn_config.enabled = atoi(item->value); + } + } + } + + if (msn_config.enabled) + { + for ( unsigned i=0; i < sizeof(patterns)/sizeof(*patterns); i++ ) + { + DebugFormat(DEBUG_APPID,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&msn_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + for ( unsigned j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++ ) + { + DebugFormat(DEBUG_APPID,"registering appId: %d\n",appIdRegistry[j].appId); + + init_api->RegisterAppId(&msn_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static CLIENT_APP_RETCODE msn_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector*, const AppIdConfig*) +{ + const u_int8_t* end; + u_int8_t version[MAX_VERSION_SIZE]; + u_int8_t* v; + u_int8_t* v_end; + uint32_t product_id; + + product_id = APP_ID_MSN_MESSENGER; + memset(&version,0,sizeof(version)); + + if (!data || !msn_client_mod.api || !flowp || !pkt) + return CLIENT_APP_ENULL; + + if (dir != APP_ID_FROM_INITIATOR) + return CLIENT_APP_INPROCESS; + + if (size >= sizeof(CVR) && memcmp(data, CVR, sizeof(CVR)-1) == 0) + { + int space_count = 0; + + end = data + size; + + while ( data < end && space_count < 6 ) /* Skip to the product and version strings */ + { + if ( *data == ' ' ) + space_count++; + + data++; + } + + /* Get the product */ + if ( end-data >= (int)sizeof(MSNMSGR) && memcmp(data, MSNMSGR, sizeof(MSNMSGR)-1) == 0 ) + { + product_id = APP_ID_MSN_MESSENGER; + data += sizeof(MSNMSGR) - 1; + + data++; /* skip the space */ + } + else if ( end-data >= (int)sizeof(MACMSGS) && memcmp(data, MACMSGS, sizeof(MACMSGS)-1) == + 0 ) + { + product_id = APP_ID_MSN_MESSENGER; + data += sizeof(MACMSGS) - 1; + + data++; /* skip the space */ + } + else if ( end-data >= (int)sizeof(MSMSGS) && memcmp(data, MSMSGS, sizeof(MSMSGS)-1) == 0 ) + { + product_id = APP_ID_MICROSOFT_WINDOWS_MESSENGER; + data += sizeof(MSMSGS) - 1; + + data++; /* skip the space */ + } + else /* advance past the unknown product name */ + { + while ( data < end && *data != ' ') + data++; + + data++; /* skip the space */ + } + + v = version; + + v_end = v + (MAX_VERSION_SIZE - 1); + + /* Get the version */ + while ( data < end && *data != ' ' && v < v_end ) + { + *v = *data; + v++; + data++; + } + + goto done; + } + + return CLIENT_APP_INPROCESS; + +done: + msn_client_mod.api->add_app(flowp, APP_ID_MSN_MESSENGER, product_id, (char*)version); + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + +RNAClientAppModule msn_client_mod = +{ + "MSN", + IpProtocol::TCP, + msn_init, + nullptr, // clean + msn_validate, + 2, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize + 0, // provides_user + 0 // flow_data_index +}; + diff --git a/src/network_inspectors/appid/client_plugins/client_app_msn.h b/src/network_inspectors/appid/client_plugins/client_app_msn.h new file mode 100644 index 000000000..82b5555bc --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_msn.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_msn.h author Sourcefire Inc. + +#ifndef CLIENT_APP_MSN_H +#define CLIENT_APP_MSN_H + +#include "client_app_api.h" + +extern RNAClientAppModule msn_client_mod; + +#endif + diff --git a/src/network_inspectors/appid/client_plugins/client_app_rtp.cc b/src/network_inspectors/appid/client_plugins/client_app_rtp.cc new file mode 100644 index 000000000..ae424ac3e --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_rtp.cc @@ -0,0 +1,358 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_rtp.cc author Sourcefire Inc. + +#include "main/snort_debug.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" +#include "utils/util.h" + +#include "application_ids.h" +#include "client_app_api.h" + +enum RTPState +{ + RTP_STATE_CONNECTION, + RTP_STATE_CONTINUE +}; + +#define MAX_REMOTE_SIZE 128 +#define NUMBER_OF_PACKETS 3 + +struct ClientRTPData +{ + RTPState state; + uint8_t pos; + uint16_t init_seq; + uint16_t resp_seq; + uint8_t init_count; + uint8_t resp_count; + uint32_t init_timestamp; + uint32_t resp_timestamp; + uint32_t init_ssrc; + uint32_t resp_ssrc; +}; + +#pragma pack(1) +struct ClientRTPMsg +{ +#if defined(WORDS_BIGENDIAN) + uint8_t vers : 2, + padding : 1, + extension : 1, + count : 4; + uint8_t marker : 1, + payloadtype : 7; +#else + uint8_t count : 4, + extension : 1, + padding : 1, + vers : 2; + uint8_t payloadtype : 7, + marker : 1; +#endif + uint16_t seq; + uint32_t timestamp; + uint32_t ssrc; +}; +#pragma pack() + +struct RTP_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL RTP_CLIENT_APP_CONFIG rtp_config; + +static CLIENT_APP_RETCODE rtp_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE rtp_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, const AppIdConfig* pConfig); + +SO_PUBLIC RNAClientAppModule rtp_client_mod = +{ + "RTP", // name + IpProtocol::UDP, // proto + &rtp_init, // init + nullptr, // clean + &rtp_validate, // validate + 1, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern patterns[] = +{ + { (const uint8_t*)"\x000\x000", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x001", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x002", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x003", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x004", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x005", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x006", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x007", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x008", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x009", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x00a", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x00b", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x00c", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x00d", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x00e", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x00f", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x010", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x011", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x012", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x013", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x019", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x01a", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x01b", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x01c", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x01f", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x020", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x021", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x022", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x080", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x081", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x082", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x083", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x084", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x085", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x086", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x087", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x088", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x089", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x08a", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x08b", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x08c", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x08d", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x08e", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x08f", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x090", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x091", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x092", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x093", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x099", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x09a", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x09b", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x09c", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x09f", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x0a0", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x0a1", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x000\x0a2", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x000", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x001", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x002", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x003", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x004", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x005", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x006", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x007", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x008", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x009", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x00a", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x00b", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x00c", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x00d", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x00e", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x00f", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x010", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x011", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x012", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x013", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x019", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x01a", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x01b", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x01c", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x01f", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x020", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x021", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x022", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x080", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x081", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x082", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x083", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x084", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x085", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x086", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x087", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x088", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x089", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x08a", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x08b", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x08c", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x08d", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x08e", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x08f", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x090", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x091", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x092", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x093", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x099", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x09a", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x09b", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x09c", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x09f", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x0a0", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x0a1", 2, 0, APP_ID_RTP }, + { (const uint8_t*)"\x080\x0a2", 2, 0, APP_ID_RTP }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_RTP, 0 } +}; + +static CLIENT_APP_RETCODE rtp_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + + rtp_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor; + RNAClientAppModuleConfigItem* item; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + rtp_config.enabled = atoi(item->value); + } + } + } + + if (rtp_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&rtp_validate, IpProtocol::UDP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&rtp_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static CLIENT_APP_RETCODE rtp_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig*) +{ + ClientRTPData* fd; + ClientRTPMsg* hdr; + + if (!size) + return CLIENT_APP_INPROCESS; + + fd = (ClientRTPData*)rtp_client_mod.api->data_get(flowp, rtp_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientRTPData*)snort_calloc(sizeof(ClientRTPData)); + rtp_client_mod.api->data_add(flowp, fd, rtp_client_mod.flow_data_index, &snort_free); + fd->state = RTP_STATE_CONNECTION; + } + + switch (fd->state) + { + case RTP_STATE_CONNECTION: + if (size < sizeof(ClientRTPMsg)) + return CLIENT_APP_EINVALID; + hdr = (ClientRTPMsg*)data; + if (hdr->vers > 2 || hdr->payloadtype > 34) + return CLIENT_APP_EINVALID; + if (dir == APP_ID_FROM_INITIATOR) + { + fd->init_seq = ntohs(hdr->seq); + fd->init_timestamp = ntohl(hdr->timestamp); + fd->init_ssrc = ntohl(hdr->ssrc); + fd->init_count++; + } + else + { + fd->resp_seq = ntohs(hdr->seq); + fd->resp_timestamp = ntohl(hdr->timestamp); + fd->resp_ssrc = ntohl(hdr->ssrc); + fd->resp_count++; + } + fd->state = RTP_STATE_CONTINUE; + return CLIENT_APP_INPROCESS; + + case RTP_STATE_CONTINUE: + if (size < sizeof(ClientRTPMsg)) + return CLIENT_APP_EINVALID; + hdr = (ClientRTPMsg*)data; + if (hdr->vers > 2) + return CLIENT_APP_EINVALID; + if (hdr->payloadtype > 34) + return CLIENT_APP_EINVALID; + if (dir == APP_ID_FROM_INITIATOR) + { + if ((ntohs(hdr->seq) != ++fd->init_seq) || + (ntohl(hdr->ssrc) != fd->init_ssrc) || + (ntohl(hdr->timestamp) < fd->init_timestamp)) + return CLIENT_APP_EINVALID; + fd->init_timestamp = ntohl(hdr->timestamp); + if (++fd->init_count < NUMBER_OF_PACKETS) + return CLIENT_APP_INPROCESS; + } + else + { + if ((ntohs(hdr->seq) != ++fd->resp_seq) || + (ntohl(hdr->ssrc) != fd->resp_ssrc) || + (ntohl(hdr->timestamp) < fd->resp_timestamp)) + return CLIENT_APP_EINVALID; + fd->resp_timestamp = ntohl(hdr->timestamp); + if (++fd->resp_count < NUMBER_OF_PACKETS) + return CLIENT_APP_INPROCESS; + } + break; + + default: + return CLIENT_APP_INPROCESS; + } + + rtp_client_mod.api->add_app(flowp, APP_ID_RTP, APP_ID_RTP, nullptr); + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_smtp.cc b/src/network_inspectors/appid/client_plugins/client_app_smtp.cc new file mode 100644 index 000000000..a9dc1bed8 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_smtp.cc @@ -0,0 +1,751 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_smtp.cc author Sourcefire Inc. + +#include "client_app_smtp.h" + +#include "main/snort_debug.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "appid_api.h" +#include "application_ids.h" + +#define UNIT_TESTING 0 + +#if UNIT_TESTING +#include "fw_appid.h" +#endif + +enum SMTPState +{ + SMTP_STATE_NONE, + SMTP_STATE_HELO, + SMTP_STATE_MAIL_FROM, + SMTP_STATE_RCPT_TO, + SMTP_STATE_DATA, + SMTP_STATE_MESSAGE, + SMTP_STATE_GET_PRODUCT_VERSION, + SMTP_STATE_SKIP_LINE, + SMTP_STATE_CONNECTION_ERROR, + SMTP_STATE_STARTTLS +}; + +#define MAX_VERSION_SIZE 64 +#define MAX_HEADER_LINE_SIZE 1024 + +#if UNIT_TESTING +char* stateName [] = +{ + "SMTP_STATE_NONE", + "SMTP_STATE_HELO", + "SMTP_STATE_MAIL_FROM", + "SMTP_STATE_RCPT_TO", + "SMTP_STATE_DATA", + "SMTP_STATE_MESSAGE", + "SMTP_STATE_GET_PRODUCT_VERSION", + "SMTP_STATE_SKIP_LINE", + "SMTP_STATE_CONNECTION_ERROR", + "SMTP_STATE_STARTTLS" +}; +#endif + +/* flag values for ClientSMTPData */ +#define CLIENT_FLAG_STARTTLS_SENT 0x01 +#define CLIENT_FLAG_SMTPS 0x02 + +#define MAX_VERSION_SIZE 64 +struct ClientSMTPData +{ + int flags; + SMTPState state; + SMTPState nextstate; + uint8_t version[MAX_VERSION_SIZE]; + unsigned pos; + uint8_t* headerline; +}; + +struct SMTP_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL SMTP_CLIENT_APP_CONFIG smtp_config; + +static CLIENT_APP_RETCODE smtp_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE smtp_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, const AppIdConfig* pConfig); + +SO_PUBLIC RNAClientAppModule smtp_client_mod = +{ + "SMTP", // name + IpProtocol::TCP, // proto + &smtp_init, // init + nullptr, // clean + &smtp_validate, // validate + 1, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +#define HELO "HELO " +#define EHLO "EHLO " +#define MAILFROM "MAIL FROM:" +#define RCPTTO "RCPT TO:" +#define DATA "DATA" +#define RSET "RSET" +#define AUTH "AUTH PLAIN" +#define STARTTLS "STARTTLS" + +#define STARTTLS_COMMAND_SUCCESS "220 " + +#define MICROSOFT "Microsoft " +#define OUTLOOK "Outlook" +#define EXPRESS "Express " +#define IMO "IMO, " + +#define XMAILER "X-Mailer: " +#define USERAGENT "User-Agent: " + +static const uint8_t APP_SMTP_OUTLOOK[] = "Microsoft Outlook"; +static const uint8_t APP_SMTP_OUTLOOK_EXPRESS[] = "Microsoft Outlook Express "; +static const uint8_t APP_SMTP_IMO[] = "IMO, "; +static const uint8_t APP_SMTP_EVOLUTION[] = "Ximian Evolution "; +static const uint8_t APP_SMTP_LOTUSNOTES[] = "Lotus Notes "; +static const uint8_t APP_SMTP_APPLEMAIL[] = "Apple Mail ("; +static const uint8_t APP_SMTP_EUDORA[] = "QUALCOMM Windows Eudora Version "; +static const uint8_t APP_SMTP_EUDORAPRO[] = "Windows Eudora Pro Version "; +static const uint8_t APP_SMTP_AOL[] = "AOL "; +static const uint8_t APP_SMTP_MUTT[] = "Mutt/"; +static const uint8_t APP_SMTP_KMAIL[] = "KMail/"; +static const uint8_t APP_SMTP_MTHUNDERBIRD[] = "Mozilla Thunderbird "; +static const uint8_t APP_SMTP_THUNDERBIRD[] = "Thunderbird "; +static const uint8_t APP_SMTP_MOZILLA[] = "Mozilla"; +static const uint8_t APP_SMTP_THUNDERBIRD_SHORT[] = "Thunderbird/"; + +static Client_App_Pattern patterns[] = +{ + { (uint8_t*)HELO, sizeof(HELO)-1, 0, APP_ID_SMTP }, + { (uint8_t*)EHLO, sizeof(EHLO)-1, 0, APP_ID_SMTP }, + { APP_SMTP_OUTLOOK, sizeof(APP_SMTP_OUTLOOK)-1, -1, APP_ID_OUTLOOK }, + { APP_SMTP_OUTLOOK_EXPRESS, sizeof(APP_SMTP_OUTLOOK_EXPRESS)-1,-1, APP_ID_OUTLOOK_EXPRESS }, + { APP_SMTP_IMO, sizeof(APP_SMTP_IMO)-1, -1, APP_ID_SMTP_IMO }, + { APP_SMTP_EVOLUTION, sizeof(APP_SMTP_EVOLUTION)-1, -1, APP_ID_EVOLUTION }, + { APP_SMTP_LOTUSNOTES, sizeof(APP_SMTP_LOTUSNOTES)-1, -1, APP_ID_LOTUS_NOTES }, + { APP_SMTP_APPLEMAIL, sizeof(APP_SMTP_APPLEMAIL)-1, -1, APP_ID_APPLE_EMAIL }, + { APP_SMTP_EUDORA, sizeof(APP_SMTP_EUDORA)-1, -1, APP_ID_EUDORA }, + { APP_SMTP_EUDORAPRO, sizeof(APP_SMTP_EUDORAPRO)-1, -1, APP_ID_EUDORA_PRO }, + { APP_SMTP_AOL, sizeof(APP_SMTP_AOL)-1, -1, APP_ID_AOL_EMAIL }, + { APP_SMTP_MUTT, sizeof(APP_SMTP_MUTT)-1, -1, APP_ID_MUTT }, + { APP_SMTP_KMAIL, sizeof(APP_SMTP_KMAIL)-1, -1, APP_ID_KMAIL }, + { APP_SMTP_MTHUNDERBIRD, sizeof(APP_SMTP_MTHUNDERBIRD)-1, -1, APP_ID_THUNDERBIRD }, + { APP_SMTP_THUNDERBIRD, sizeof(APP_SMTP_THUNDERBIRD)-1, -1, APP_ID_THUNDERBIRD }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_THUNDERBIRD, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_OUTLOOK, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_KMAIL, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_EUDORA_PRO, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_EVOLUTION, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_SMTP_IMO, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_EUDORA, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_LOTUS_NOTES, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_APPLE_EMAIL, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_AOL_EMAIL, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_MUTT, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_SMTP, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_OUTLOOK_EXPRESS, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_SMTPS, APPINFO_FLAG_CLIENT_ADDITIONAL } +}; + +static CLIENT_APP_RETCODE smtp_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + + smtp_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor; + RNAClientAppModuleConfigItem* item; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + smtp_config.enabled = atoi(item->value); + } + } + } + + if (smtp_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + init_api->RegisterPattern(&smtp_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&smtp_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static int ExtractVersion(ClientSMTPData* const fd, const uint8_t* product, + const uint8_t* data, AppIdData* flowp, Packet*) +{ + const u_int8_t* p; + u_int8_t* v; + u_int8_t* v_end; + unsigned len; + unsigned sublen; + AppId appId = (fd->flags & CLIENT_FLAG_SMTPS) ? APP_ID_SMTPS : APP_ID_SMTP; + + v_end = fd->version; + v_end += MAX_VERSION_SIZE - 1; + len = data - product; + if (len >= sizeof(MICROSOFT) && memcmp(product, MICROSOFT, sizeof(MICROSOFT)-1) == 0) + { + p = product + sizeof(MICROSOFT) - 1; + + if (data-p >= (int)sizeof(OUTLOOK) && memcmp(p, OUTLOOK, sizeof(OUTLOOK)-1) == 0) + { + p += sizeof(OUTLOOK) - 1; + if (p >= data) + return 1; + if (*p == ',') + { + p++; + if (p >= data || *p != ' ') + return 1; + p++; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_OUTLOOK, (char*)fd->version); + return 0; + } + else if (*p == ' ') + { + p++; + if (data-p >= (int)sizeof(EXPRESS) && memcmp(p, EXPRESS, sizeof(EXPRESS)-1) == 0) + { + p += sizeof(EXPRESS) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_OUTLOOK_EXPRESS, + (char*)fd->version); + return 0; + } + else if (data-p >= (int)sizeof(IMO) && memcmp(p, IMO, sizeof(IMO)-1) == 0) + { + p += sizeof(IMO) - 1; + if (p >= data) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_OUTLOOK, (char*)fd->version); + return 0; + } + } + } + } + else if (len >= sizeof(APP_SMTP_EVOLUTION) && memcmp(product, APP_SMTP_EVOLUTION, + sizeof(APP_SMTP_EVOLUTION)-1) == 0) + { + p = product + sizeof(APP_SMTP_EVOLUTION) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_EVOLUTION, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_LOTUSNOTES) && memcmp(product, APP_SMTP_LOTUSNOTES, + sizeof(APP_SMTP_LOTUSNOTES)-1) == 0) + { + p = product + sizeof(APP_SMTP_LOTUSNOTES) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_LOTUS_NOTES, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_APPLEMAIL) && memcmp(product, APP_SMTP_APPLEMAIL, + sizeof(APP_SMTP_APPLEMAIL)-1) == 0) + { + p = product + sizeof(APP_SMTP_APPLEMAIL) - 1; + if (p >= data || *(data - 1) != ')' || *p == ')' || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_APPLE_EMAIL, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_EUDORA) && memcmp(product, APP_SMTP_EUDORA, + sizeof(APP_SMTP_EUDORA)-1) == 0) + { + p = product + sizeof(APP_SMTP_EUDORA) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_EUDORA, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_EUDORAPRO) && memcmp(product, APP_SMTP_EUDORAPRO, + sizeof(APP_SMTP_EUDORAPRO)-1) == 0) + { + p = product + sizeof(APP_SMTP_EUDORAPRO) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_EUDORA_PRO, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_AOL) && memcmp(product, APP_SMTP_AOL, sizeof(APP_SMTP_AOL)- + 1) == 0) + { + p = product + sizeof(APP_SMTP_AOL) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_AOL_EMAIL, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_MUTT) && memcmp(product, APP_SMTP_MUTT, sizeof(APP_SMTP_MUTT)- + 1) == 0) + { + p = product + sizeof(APP_SMTP_MUTT) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_MUTT, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_KMAIL) && memcmp(product, APP_SMTP_KMAIL, + sizeof(APP_SMTP_KMAIL)-1) == 0) + { + p = product + sizeof(APP_SMTP_KMAIL) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, appId /*KMAIL_ID*/, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_THUNDERBIRD) && memcmp(product, APP_SMTP_THUNDERBIRD, + sizeof(APP_SMTP_THUNDERBIRD)-1) == 0) + { + p = product + sizeof(APP_SMTP_THUNDERBIRD) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_THUNDERBIRD, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_MTHUNDERBIRD) && memcmp(product, APP_SMTP_MTHUNDERBIRD, + sizeof(APP_SMTP_MTHUNDERBIRD)-1) == 0) + { + p = product + sizeof(APP_SMTP_MTHUNDERBIRD) - 1; + if (p >= data || isspace(*p)) + return 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_THUNDERBIRD, (char*)fd->version); + return 0; + } + else if (len >= sizeof(APP_SMTP_MOZILLA) && memcmp(product, APP_SMTP_MOZILLA, + sizeof(APP_SMTP_MOZILLA)-1) == 0) + { + for (p = product + sizeof(APP_SMTP_MOZILLA) - 1; p < data; p++) + { + if (*p == 'T') + { + sublen = data - p; + if (sublen >= sizeof(APP_SMTP_THUNDERBIRD_SHORT) && memcmp(p, + APP_SMTP_THUNDERBIRD_SHORT, sizeof(APP_SMTP_THUNDERBIRD_SHORT)-1) == 0) + { + p = p + sizeof(APP_SMTP_THUNDERBIRD_SHORT) - 1; + for (v=fd->version; vadd_app(flowp, appId, APP_ID_THUNDERBIRD, + (char*)fd->version); + return 0; + } + } + } + } + + return 1; +} + +static void freeData(void* data) +{ + ClientSMTPData* fd = (ClientSMTPData*)data; + snort_free(fd->headerline); + snort_free(fd); +} + +static CLIENT_APP_RETCODE smtp_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector*, const AppIdConfig*) +{ + ClientSMTPData* fd; + const uint8_t* end; + unsigned len; +#if UNIT_TESTING + SMTPState currState = SMTP_STATE_NONE; + +#endif + + fd = (ClientSMTPData*)smtp_client_mod.api->data_get(flowp, smtp_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientSMTPData*)snort_calloc(sizeof(ClientSMTPData)); + if (!fd) + return CLIENT_APP_ENOMEM; + if (smtp_client_mod.api->data_add(flowp, fd, smtp_client_mod.flow_data_index, &freeData)) + { + snort_free(fd); + return CLIENT_APP_ENOMEM; + } + fd->state = SMTP_STATE_HELO; + } + + if (dir != APP_ID_FROM_INITIATOR) + { + if ( (fd->flags & CLIENT_FLAG_STARTTLS_SENT) && + !memcmp(data,STARTTLS_COMMAND_SUCCESS,sizeof(STARTTLS_COMMAND_SUCCESS)-1) ) + { + fd->flags &= ~(CLIENT_FLAG_STARTTLS_SENT); + fd->flags |= CLIENT_FLAG_SMTPS; + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); // we no longer need + // to examine the + // response. + if (!getAppIdFlag(flowp, APPID_SESSION_DECRYPTED)) + { + /* Because we can't see any further info without decryption we settle for + plain APP_ID_SMTPS instead of perhaps finding data that would make calling + ExtractVersion() worthwhile, So set the appid and call it good. */ + smtp_client_mod.api->add_app(flowp, APP_ID_SMTPS, APP_ID_SMTPS, nullptr); + goto done; + } + } + return CLIENT_APP_INPROCESS; + } + if (getAppIdFlag(flowp, APPID_SESSION_ENCRYPTED)) + { + if (!getAppIdFlag(flowp, APPID_SESSION_DECRYPTED)) + return CLIENT_APP_INPROCESS; + } + + for (end = data + size; data < end; data++) + { +#if UNIT_TESTING + if (app_id_debug_session_flag && currState != fd->state) + { + DEBUG_WRAP(DebugMessage(DEBUG_APPID, "AppIdDbg %s SMTP client state %s\n", + app_id_debug_session, stateName[fd->state]); ); + currState = fd->state; + } +#endif + switch (fd->state) + { + case SMTP_STATE_HELO: + len = end - data; + if (*data == HELO[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(HELO)) + { + fd->pos = 0; + fd->nextstate = SMTP_STATE_MAIL_FROM; + fd->state = SMTP_STATE_SKIP_LINE; + } + } + else if (*data == EHLO[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(EHLO)) + { + fd->pos = 0; + fd->nextstate = SMTP_STATE_MAIL_FROM; + fd->state = SMTP_STATE_SKIP_LINE; + } + } + else + goto done; + break; + + case SMTP_STATE_MAIL_FROM: + if (*data == MAILFROM[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(MAILFROM)) + { + fd->pos = 0; + fd->nextstate = SMTP_STATE_RCPT_TO; + fd->state = SMTP_STATE_SKIP_LINE; + } + } + else if (*data == RSET[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(RSET)) + { + fd->pos = 0; + fd->nextstate = fd->state; + fd->state = SMTP_STATE_SKIP_LINE; + } + } + else if (*data == AUTH[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(AUTH)) + { + fd->pos = 0; + fd->nextstate = fd->state; + fd->state = SMTP_STATE_SKIP_LINE; + } + } + else if (*data == STARTTLS[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(STARTTLS)) + { + fd->flags |= CLIENT_FLAG_STARTTLS_SENT; + fd->pos = 0; + fd->nextstate = fd->state; + fd->state = SMTP_STATE_SKIP_LINE; + setAppIdFlag(flowp, APPID_SESSION_ENCRYPTED); + } + } + else + goto done; + break; + case SMTP_STATE_RCPT_TO: + if (*data == RCPTTO[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(RCPTTO)) + { + fd->pos = 0; + fd->nextstate = SMTP_STATE_DATA; + fd->state = SMTP_STATE_SKIP_LINE; + } + } + else + goto done; + break; + + case SMTP_STATE_DATA: + if (*data == DATA[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(DATA)) + { + fd->pos = 0; + fd->nextstate = SMTP_STATE_MESSAGE; + fd->state = SMTP_STATE_SKIP_LINE; + } + } + else if (*data == RCPTTO[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(RCPTTO)) + { + fd->pos = 0; + fd->nextstate = fd->state; + fd->state = SMTP_STATE_SKIP_LINE; + } + } + break; + case SMTP_STATE_MESSAGE: + if (*data == '.') + { + len = end - data; + if (len == 0 || + (len >= 1 && data[1] == 0x0A) || + (len >= 2 && data[1] == 0x0D && data[2] == 0x0A)) + { + AppId appId = (fd->flags & CLIENT_FLAG_SMTPS) ? APP_ID_SMTPS : APP_ID_SMTP; + smtp_client_mod.api->add_app(flowp, appId, appId, nullptr); + goto done; + } + } + else if (*data == XMAILER[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(XMAILER)) + { + fd->pos = 0; + fd->state = SMTP_STATE_GET_PRODUCT_VERSION; + } + } + else if (*data == USERAGENT[fd->pos]) + { + fd->pos++; + if (fd->pos == strlen(USERAGENT)) + { + fd->pos = 0; + fd->state = SMTP_STATE_GET_PRODUCT_VERSION; + } + } + else if (!isprint(*data) && *data != 0x09) + goto done; + else + { + fd->pos = 0; + fd->nextstate = fd->state; + fd->state = SMTP_STATE_SKIP_LINE; + } + break; + + case SMTP_STATE_GET_PRODUCT_VERSION: + if (*data == 0x0D) + { + if (fd->headerline && fd->pos) + { + ExtractVersion(fd, fd->headerline, fd->headerline + fd->pos, flowp, pkt); + snort_free(fd->headerline); + fd->headerline = nullptr; + } + goto done; + } + else if (!isprint(*data)) + { + snort_free(fd->headerline); + fd->headerline = nullptr; + goto done; + } + else + { + if (!fd->headerline) + { + if (!(fd->headerline = (uint8_t*)snort_calloc(MAX_HEADER_LINE_SIZE))) + goto done; + } + + if (fd->pos < (MAX_HEADER_LINE_SIZE-1)) + fd->headerline[fd->pos++] = *data; + } + break; + + case SMTP_STATE_SKIP_LINE: + if (*data == 0x0A) + { + fd->pos = 0; + fd->state = fd->nextstate; + fd->nextstate = SMTP_STATE_NONE; + } + else if (!(*data == 0x0D || isprint(*data))) + goto done; + break; + + default: + goto done; + } + } + return CLIENT_APP_INPROCESS; + +done: + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_smtp.h b/src/network_inspectors/appid/client_plugins/client_app_smtp.h new file mode 100644 index 000000000..fabcb6c06 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_smtp.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_smtp.h author Sourcefire Inc. + +#ifndef CLIENT_APP_SMTP_H +#define CLIENT_APP_SMTP_H + +#include "client_app_api.h" + +extern RNAClientAppModule smtp_client_mod; + +#endif + diff --git a/src/network_inspectors/appid/client_plugins/client_app_ssh.cc b/src/network_inspectors/appid/client_plugins/client_app_ssh.cc new file mode 100644 index 000000000..b4ad94729 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_ssh.cc @@ -0,0 +1,638 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_ssh.cc author Sourcefire Inc. + +#include "main/snort_debug.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_app_api.h" + +static const char SSH_CLIENT_BANNER[] = "SSH-"; +#define SSH_CLIENT_BANNER_LEN (sizeof(SSH_CLIENT_BANNER)-1) +#define SSH_CLIENT_BANNER_MAXPOS (sizeof(SSH_CLIENT_BANNER)-2) + +static const char DROPBEAR_BANNER[] = "dropbear"; +#define DROPBEAR_BANNER_MAXPOS (sizeof(DROPBEAR_BANNER)-2) + +static const char LSH_BANNER[] = "lsh"; +#define LSH_BANNER_MAXPOS (sizeof(LSH_BANNER)-2) + +static const char OPENSSH_BANNER[] = "OpenSSH"; +#define OPENSSH_BANNER_MAXPOS (sizeof(OPENSSH_BANNER)-2) + +static const char PUTTY_BANNER[] = "PuTTY"; +#define PUTTY_BANNER_MAXPOS (sizeof(PUTTY_BANNER)-2) + +#define SSH_MSG_KEYXINIT 20 +#define SSH_MSG_IGNORE 2 +#define SSH_MSG_SESSION_KEY 3 +#define SSH_MAX_BANNER_LEN 255 +#define SSH2 2 +#define SSH1 1 + +enum SSHClientState +{ + SSH_CLIENT_STATE_BANNER = 0, + SSH_CLIENT_STATE_ID_PROTO_VERSION, + SSH_CLIENT_STATE_LOOKING_FOR_DASH, + SSH_CLIENT_STATE_ID_CLIENT, + SSH_CLIENT_STATE_CHECK_OPENSSH, + SSH_CLIENT_STATE_CHECK_PUTTY, + SSH_CLIENT_STATE_CHECK_LSH, + SSH_CLIENT_STATE_CHECK_DROPBEAR, + SSH_CLIENT_STATE_ID_SOFTWARE_VERSION, + SSH_CLIENT_STATE_ID_REST_OF_LINE, + SSH_CLIENT_STATE_KEY +}; + +enum SSH2HeaderState +{ + SSH2_HEADER_BEGIN, + SSH2_HEADER_PLEN, + SSH2_HEADER_CODE, + SSH2_IGNORE, + SSH2_PADDING, + SSH2_KEYX_HEADER_FINISH, + SSH2_FIELD_LEN_BEGIN, + SSH2_FIELD_DATA_BEGIN, + SSH2_PAYLOAD_BEGIN +}; + +enum SSH1HeaderState +{ + SSH1_HEADER_BEGIN, + SSH1_HEADER_PLEN, + SSH1_HEADER_FIND_CODE, + SSH1_HEADER_CODE, + SSH1_SESSION_KEY +}; + +struct ClientSSHData +{ + SSHClientState state; + SSH2HeaderState hstate; + SSH1HeaderState oldhstate; + unsigned len; + unsigned pos; + unsigned field; + unsigned field_len; + unsigned read_data; + union + { + uint32_t len; + uint8_t raw_len[4]; + } l; + unsigned ssh_version; + uint8_t version[SSH_MAX_BANNER_LEN]; + uint8_t plen; + uint8_t code; + uint32_t client_id; +}; + +#pragma pack(1) + +struct ClientSSHKeyString +{ + uint32_t len; + uint8_t data; +}; + +struct ClientSSHMsg +{ + uint32_t len; + uint8_t plen; + uint8_t code; +}; + +struct ClientSSH2KeyExchange +{ + ClientSSHMsg msg; + uint8_t cookie[16]; +}; + +struct ClientSSH1KeyExchangeV1 +{ + uint32_t len; + uint8_t code; +}; + +struct ClientSSHKeyExchangeFinal +{ + uint8_t kex_pkt; + uint32_t future; +}; + +#pragma pack() + +struct SSH_CLIENT_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL SSH_CLIENT_CONFIG ssh_client_config; + +static CLIENT_APP_RETCODE ssh_client_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE ssh_client_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, + const AppIdConfig* pConfig); + +SO_PUBLIC RNAClientAppModule ssh_client_mod = +{ + "SSH", // name + IpProtocol::TCP, // proto + &ssh_client_init, // init + nullptr, // clean + &ssh_client_validate, // validate + 1, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern patterns[] = +{ + { (const uint8_t*)SSH_CLIENT_BANNER, sizeof(SSH_CLIENT_BANNER)-1, 0, APP_ID_SSH }, + { (const uint8_t*)OPENSSH_BANNER, sizeof(OPENSSH_BANNER)-1, 0, APP_ID_OPENSSH }, + { (const uint8_t*)PUTTY_BANNER, sizeof(PUTTY_BANNER)-1, 0, APP_ID_PUTTY }, + { (const uint8_t*)LSH_BANNER, sizeof(LSH_BANNER)-1, 0, APP_ID_LSH }, + { (const uint8_t*)DROPBEAR_BANNER, sizeof(DROPBEAR_BANNER)-1, 0, APP_ID_DROPBEAR }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_DROPBEAR, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_SSH, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_LSH, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_PUTTY, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_OPENSSH, APPINFO_FLAG_CLIENT_ADDITIONAL } +}; + +static CLIENT_APP_RETCODE ssh_client_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + + ssh_client_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor; + RNAClientAppModuleConfigItem* item; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n", item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + ssh_client_config.enabled = atoi(item->value); + } + } + } + + if (ssh_client_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG, "registering patterns: %s: %d", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&ssh_client_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n", appIdRegistry[j].appId); + init_api->RegisterAppId(&ssh_client_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static inline CLIENT_APP_RETCODE ssh_client_validate_keyx(uint16_t offset, const uint8_t* data, + uint16_t size, ClientSSHData* fd) +{ + const ClientSSHMsg* ckx; + const ClientSSHKeyString* cks; + const ClientSSH2KeyExchange* ckex; + + while (offset < size) + { + switch (fd->hstate) + { + case SSH2_HEADER_BEGIN: + fd->l.raw_len[fd->pos] = data[offset]; + fd->pos++; + if (fd->pos == sizeof(ckx->len)) + { + fd->len = ntohl(fd->l.len); + fd->hstate = SSH2_HEADER_PLEN; + } + break; + case SSH2_HEADER_PLEN: + fd->plen = data[offset]; + fd->hstate = SSH2_HEADER_CODE; + fd->pos++; + break; + case SSH2_HEADER_CODE: + fd->code = data[offset]; + if (fd->code == SSH_MSG_KEYXINIT) + { + fd->pos = 0; + fd->hstate = SSH2_KEYX_HEADER_FINISH; + fd->read_data = fd->plen + sizeof(ckex->cookie) + sizeof(ckx->len); + } + else if (fd->code == SSH_MSG_IGNORE) + { + fd->pos = sizeof(ckx->len) + 2; + fd->hstate = SSH2_IGNORE; + } + else + return CLIENT_APP_EINVALID; + fd->len = ntohl(fd->l.len) + sizeof(ckx->len); + if (fd->len > 35000) + return CLIENT_APP_EINVALID; + break; + case SSH2_IGNORE: + fd->pos++; + if (fd->pos >= fd->len) + { + fd->hstate = SSH2_HEADER_BEGIN; + fd->pos = 0; + } + break; + case SSH2_KEYX_HEADER_FINISH: + fd->pos++; + if (fd->pos >= sizeof(ckex->cookie)) + { + fd->hstate = SSH2_FIELD_LEN_BEGIN; + fd->pos = 0; + } + break; + case SSH2_FIELD_LEN_BEGIN: + fd->l.raw_len[fd->pos] = data[offset]; + fd->pos++; + if (fd->pos >= sizeof(cks->len)) + { + fd->pos = 0; + fd->field_len = ntohl(fd->l.len); + fd->read_data += fd->field_len + sizeof(cks->len); + if (fd->read_data > fd->len) + return CLIENT_APP_EINVALID; + if (fd->field_len) + fd->hstate = SSH2_FIELD_DATA_BEGIN; + else + { + fd->field++; + if (fd->field >= 10) + fd->hstate = SSH2_PAYLOAD_BEGIN; + } + } + break; + case SSH2_FIELD_DATA_BEGIN: + fd->pos++; + if (fd->pos >= fd->field_len) + { + fd->field++; + if (fd->field >= 10) + fd->hstate = SSH2_PAYLOAD_BEGIN; + else + fd->hstate = SSH2_FIELD_LEN_BEGIN; + fd->pos = 0; + } + break; + case SSH2_PAYLOAD_BEGIN: + if (fd->pos >= offsetof(ClientSSHKeyExchangeFinal, future)) + { + fd->l.raw_len[fd->pos - offsetof(ClientSSHKeyExchangeFinal, future)] = + data[offset]; + } + fd->pos++; + if (fd->pos >= sizeof(ClientSSHKeyExchangeFinal)) + { + if (fd->l.len != 0) + return CLIENT_APP_EINVALID; + fd->hstate = SSH2_PADDING; + fd->pos = 0; + } + break; + case SSH2_PADDING: + fd->pos++; + if (fd->pos >= fd->plen) + { + offset++; + if (offset == size) + return CLIENT_APP_SUCCESS; + return CLIENT_APP_EINVALID; + } + break; + + default: + assert(0); // All cases should be handled above. + } + offset++; + } + return CLIENT_APP_INPROCESS; +} + +static inline CLIENT_APP_RETCODE ssh_client_validate_pubkey(uint16_t offset, const uint8_t* data, + uint16_t size, ClientSSHData* fd) +{ + const ClientSSHMsg* ckx; + + while (offset < size) + { + switch (fd->oldhstate) + { + case SSH1_HEADER_BEGIN: + fd->l.raw_len[fd->pos] = data[offset]; + fd->pos++; + if (fd->pos == sizeof(ckx->len)) + { + fd->len = ntohl(fd->l.len); + fd->oldhstate = SSH1_HEADER_PLEN; + } + break; + case SSH1_HEADER_PLEN: + if (size > (fd->len + sizeof(ckx->len))) + fd->plen = size - (fd->len + sizeof(ckx->len)); + else + fd->plen = 0; + fd->oldhstate = SSH1_HEADER_FIND_CODE; + // Fall through to SSH1_HEADER_FIND_CODE state. + case SSH1_HEADER_FIND_CODE: + if (fd->pos == fd->plen + sizeof(ckx->len)) + { + fd->oldhstate = SSH1_HEADER_CODE; + fd->code = data[offset]; + } + fd->pos++; + break; + case SSH1_HEADER_CODE: + if (fd->code == SSH_MSG_SESSION_KEY) + { + fd->oldhstate = SSH1_SESSION_KEY; + fd->pos++; + } + else + return CLIENT_APP_EINVALID; + fd->len = fd->len + fd->plen + sizeof(ckx->len); + if (fd->len > 35000) + return CLIENT_APP_EINVALID; + break; + case SSH1_SESSION_KEY: + fd->pos++; + if (fd->pos >= fd->len) + { + offset++; + if (offset == size) + return CLIENT_APP_SUCCESS; + return CLIENT_APP_EINVALID; + } + break; + } + offset++; + } + return CLIENT_APP_INPROCESS; +} + +static inline CLIENT_APP_RETCODE ssh_client_sm(const uint8_t* data, uint16_t size, + ClientSSHData* fd) +{ + uint16_t offset = 0; + uint8_t d; + + while (offset < size) + { + d = data[offset]; + switch (fd->state) + { + case SSH_CLIENT_STATE_BANNER: + if (d != SSH_CLIENT_BANNER[fd->pos]) + return CLIENT_APP_EINVALID; + if (fd->pos >= SSH_CLIENT_BANNER_MAXPOS) + fd->state = SSH_CLIENT_STATE_ID_PROTO_VERSION; + else + fd->pos++; + break; + + case SSH_CLIENT_STATE_ID_PROTO_VERSION: + if (d == '1') + fd->ssh_version = SSH1; + else if (d == '2') + fd->ssh_version = SSH2; + else + return CLIENT_APP_EINVALID; + fd->state = SSH_CLIENT_STATE_LOOKING_FOR_DASH; + break; + + case SSH_CLIENT_STATE_LOOKING_FOR_DASH: + if (d == '-') + { + fd->state = SSH_CLIENT_STATE_ID_CLIENT; + break; + } + break; + + case SSH_CLIENT_STATE_ID_CLIENT: + switch (d) + { + case 'O': + fd->state = SSH_CLIENT_STATE_CHECK_OPENSSH; + break; + case 'P': + fd->state = SSH_CLIENT_STATE_CHECK_PUTTY; + break; + case 'l': + fd->state = SSH_CLIENT_STATE_CHECK_LSH; + break; + case 'd': + fd->state = SSH_CLIENT_STATE_CHECK_DROPBEAR; + break; + default: + fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; + fd->client_id = APP_ID_SSH; + } + /*the next thing we want to see is the SECOND character... */ + fd->pos = 1; + break; + + case SSH_CLIENT_STATE_CHECK_OPENSSH: + if (d != OPENSSH_BANNER[fd->pos]) + { + fd->client_id = APP_ID_SSH; + fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; + } + else if (fd->pos >= OPENSSH_BANNER_MAXPOS) + { + fd->client_id = APP_ID_OPENSSH; + fd->state = SSH_CLIENT_STATE_ID_SOFTWARE_VERSION; + fd->pos = 0; + } + else + fd->pos++; + break; + + case SSH_CLIENT_STATE_CHECK_PUTTY: + if (d != PUTTY_BANNER[fd->pos]) + { + fd->client_id = APP_ID_SSH; + fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; + } + else if (fd->pos >= PUTTY_BANNER_MAXPOS) + { + fd->client_id = APP_ID_PUTTY; + fd->state = SSH_CLIENT_STATE_ID_SOFTWARE_VERSION; + fd->pos = 0; + } + else + fd->pos++; + break; + + case SSH_CLIENT_STATE_CHECK_LSH: + if (d != LSH_BANNER[fd->pos]) + { + fd->client_id = APP_ID_SSH; + fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; + } + else if (fd->pos >= LSH_BANNER_MAXPOS) + { + fd->client_id = APP_ID_LSH; + fd->state = SSH_CLIENT_STATE_ID_SOFTWARE_VERSION; + fd->pos = 0; + } + else + fd->pos++; + break; + + case SSH_CLIENT_STATE_CHECK_DROPBEAR: + if (d != DROPBEAR_BANNER[fd->pos]) + { + fd->client_id = APP_ID_SSH; + fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; + } + else if (fd->pos >= DROPBEAR_BANNER_MAXPOS) + { + fd->client_id = APP_ID_DROPBEAR; + fd->state = SSH_CLIENT_STATE_ID_SOFTWARE_VERSION; + fd->pos = 0; + } + else + fd->pos++; + break; + + case SSH_CLIENT_STATE_ID_SOFTWARE_VERSION: + if (d == '\n') + { + fd->version[fd->pos] = 0; + fd->pos = 0; + fd->state = SSH_CLIENT_STATE_KEY; + break; + } + if (d == ' ') + { + fd->version[fd->pos] = 0; + fd->state = SSH_CLIENT_STATE_ID_REST_OF_LINE; + break; + } + if (fd->pos < SSH_MAX_BANNER_LEN - 1 && d != '\r' && d != '-' && d != '_') + { + fd->version[fd->pos++] = d; + } + break; + + case SSH_CLIENT_STATE_ID_REST_OF_LINE: + if (d == '\n') + { + fd->pos = 0; + fd->state = SSH_CLIENT_STATE_KEY; + break; + } + break; + + case SSH_CLIENT_STATE_KEY: + switch (fd->ssh_version) + { + case SSH2: + return ssh_client_validate_keyx(offset, data, size, fd); + break; + case SSH1: + return ssh_client_validate_pubkey(offset, data, size, fd); + break; + default: + return CLIENT_APP_EINVALID; + break; + } + break; + + default: + return CLIENT_APP_EINVALID; + } + offset++; + } + return CLIENT_APP_INPROCESS; +} + +static CLIENT_APP_RETCODE ssh_client_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig*) +{ + ClientSSHData* fd; + CLIENT_APP_RETCODE sm_ret; + + if (!size || dir != APP_ID_FROM_INITIATOR) + return CLIENT_APP_INPROCESS; + + fd = ( ClientSSHData*)ssh_client_mod.api->data_get(flowp, ssh_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientSSHData*)snort_calloc(sizeof(ClientSSHData)); + ssh_client_mod.api->data_add(flowp, fd, ssh_client_mod.flow_data_index, &snort_free); + fd->state = SSH_CLIENT_STATE_BANNER; + fd->hstate = SSH2_HEADER_BEGIN; + fd->oldhstate = SSH1_HEADER_BEGIN; + } + + sm_ret = ssh_client_sm(data, size, fd); + if (sm_ret != CLIENT_APP_SUCCESS) + return sm_ret; + + ssh_client_mod.api->add_app(flowp, APP_ID_SSH, fd->client_id, (const char*)fd->version); + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_timbuktu.cc b/src/network_inspectors/appid/client_plugins/client_app_timbuktu.cc new file mode 100644 index 000000000..8a1e8ae37 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_timbuktu.cc @@ -0,0 +1,243 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_timbuktu.cc author Sourcefire Inc. + +#include "main/snort_debug.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_app_api.h" + +static const char TIMBUKTU_BANNER[] = "\000\001"; + +#define TIMBUKTU_BANNER_LEN (sizeof(TIMBUKTU_BANNER)-1) +#define MAX_ANY_SIZE 2 + +enum TIMBUKTUState +{ + TIMBUKTU_STATE_BANNER = 0, + TIMBUKTU_STATE_ANY_MESSAGE_LEN, + TIMBUKTU_STATE_MESSAGE_LEN, + TIMBUKTU_STATE_MESSAGE_DATA +}; + +struct ClientTIMBUKTUData +{ + TIMBUKTUState state; + uint16_t stringlen; + unsigned pos; + union + { + uint16_t len; + uint8_t raw_len[2]; + } l; +}; + +#pragma pack(1) +struct ClientTIMBUKTUMsg +{ + uint16_t len; + uint8_t message; +}; +#pragma pack() + +struct TIMBUKTU_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL TIMBUKTU_CLIENT_APP_CONFIG timbuktu_config; + +static CLIENT_APP_RETCODE timbuktu_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE timbuktu_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, const AppIdConfig* pConfig); + +SO_PUBLIC RNAClientAppModule timbuktu_client_mod = +{ + "TIMBUKTU", // name + IpProtocol::TCP, // proto + &timbuktu_init, // init + nullptr, // clean + &timbuktu_validate, // validate + 1, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern patterns[] = +{ + { (const uint8_t*)TIMBUKTU_BANNER, sizeof(TIMBUKTU_BANNER)-1, 0, APP_ID_TIMBUKTU }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_TIMBUKTU, 0 } +}; + +static CLIENT_APP_RETCODE timbuktu_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + + timbuktu_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor; + RNAClientAppModuleConfigItem* item; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + timbuktu_config.enabled = atoi(item->value); + } + } + } + + if (timbuktu_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&timbuktu_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&timbuktu_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static CLIENT_APP_RETCODE timbuktu_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig*) +{ + ClientTIMBUKTUData* fd; + uint16_t offset; + + if (dir != APP_ID_FROM_INITIATOR) + return CLIENT_APP_INPROCESS; + + fd = (ClientTIMBUKTUData*)timbuktu_client_mod.api->data_get(flowp, + timbuktu_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientTIMBUKTUData*)snort_calloc(sizeof(ClientTIMBUKTUData)); + timbuktu_client_mod.api->data_add(flowp, fd, + timbuktu_client_mod.flow_data_index, &snort_free); + fd->state = TIMBUKTU_STATE_BANNER; + } + + offset = 0; + while (offset < size) + { + switch (fd->state) + { + case TIMBUKTU_STATE_BANNER: + if (data[offset] != TIMBUKTU_BANNER[fd->pos]) + return CLIENT_APP_EINVALID; + if (fd->pos >= TIMBUKTU_BANNER_LEN-1) + { + fd->pos = 0; + fd->state = TIMBUKTU_STATE_ANY_MESSAGE_LEN; + break; + } + fd->pos++; + break; + /* cheeck any 2 bytes fisrt */ + case TIMBUKTU_STATE_ANY_MESSAGE_LEN: + fd->pos++; + if (fd->pos >= MAX_ANY_SIZE) + { + fd->pos = 0; + fd->state = TIMBUKTU_STATE_MESSAGE_LEN; + break; + } + break; + case TIMBUKTU_STATE_MESSAGE_LEN: + if (fd->pos < offsetof(ClientTIMBUKTUMsg, message)) + { + fd->l.raw_len[fd->pos] = data[offset]; + } + fd->pos++; + if (fd->pos >= offsetof(ClientTIMBUKTUMsg, message)) + { + fd->stringlen = ntohs(fd->l.len); + if (!fd->stringlen) + { + if (offset == size - 1) + goto done; + return CLIENT_APP_EINVALID; + } + else if ((fd->stringlen + TIMBUKTU_BANNER_LEN + MAX_ANY_SIZE + offsetof( + ClientTIMBUKTUMsg, message)) > size) + return CLIENT_APP_EINVALID; + fd->state = TIMBUKTU_STATE_MESSAGE_DATA; + fd->pos = 0; + } + break; + case TIMBUKTU_STATE_MESSAGE_DATA: + fd->pos++; + if (fd->pos == fd->stringlen) + { + if (offset == size - 1) + goto done; + return CLIENT_APP_EINVALID; + } + break; + default: + goto inprocess; + } + offset++; + } +inprocess: + return CLIENT_APP_INPROCESS; + +done: + timbuktu_client_mod.api->add_app(flowp, APP_ID_TIMBUKTU, APP_ID_TIMBUKTU, nullptr); + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_tns.cc b/src/network_inspectors/appid/client_plugins/client_app_tns.cc new file mode 100644 index 000000000..d42f666c5 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_tns.cc @@ -0,0 +1,436 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_tns.cc author Sourcefire Inc. + +#include "main/snort_debug.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_app_api.h" + +static const char TNS_BANNER[] = "\000\000"; +#define TNS_BANNER_LEN (sizeof(TNS_BANNER)-1) + +#define TNS_TYPE_CONNECT 1 +#define TNS_TYPE_ACCEPT 2 +#define TNS_TYPE_ACK 3 +#define TNS_TYPE_REFUSE 4 +#define TNS_TYPE_REDIRECT 5 +#define TNS_TYPE_DATA 6 +#define TNS_TYPE_NULL 7 +#define TNS_TYPE_ABORT 9 +#define TNS_TYPE_RESEND 11 +#define TNS_TYPE_MARKER 12 +#define TNS_TYPE_ATTENTION 13 +#define TNS_TYPE_CONTROL 14 +#define TNS_TYPE_MAX 19 + +#define CONNECT_VERSION_OFFSET 8 +#define CONNECT_DATA_OFFSET 26 + +#define USER_STRING "user=" +#define MAX_USER_POS ((int)sizeof(USER_STRING) - 2) + +enum TNSState +{ + TNS_STATE_MESSAGE_LEN = 0, + TNS_STATE_MESSAGE_CHECKSUM, + TNS_STATE_MESSAGE, + TNS_STATE_MESSAGE_RES, + TNS_STATE_MESSAGE_HD_CHECKSUM, + TNS_STATE_MESSAGE_DATA, + TNS_STATE_MESSAGE_CONNECT, + TNS_STATE_MESSAGE_CONNECT_OFFSET_DC, + TNS_STATE_MESSAGE_CONNECT_OFFSET, + TNS_STATE_MESSAGE_CONNECT_PREDATA, + TNS_STATE_MESSAGE_CONNECT_DATA, + TNS_STATE_COLLECT_USER +}; + +#define MAX_VERSION_SIZE 12 +struct ClientTNSData +{ + TNSState state; + unsigned stringlen; + unsigned offsetlen; + unsigned pos; + unsigned message; + union + { + uint16_t len; + uint8_t raw_len[2]; + } l; + const char* version; + uint8_t* data; +}; + +#pragma pack(1) +struct ClientTNSMsg +{ + uint16_t len; + uint16_t checksum; + uint8_t msg; + uint8_t res; + uint16_t hdchecksum; + uint8_t data; +}; +#pragma pack() + +struct TNS_CLIENT_APP_CONFIG +{ + int enabled; +}; + +#ifdef REMOVED_WHILE_NOT_IN_USE +static const char* msg_type[] = +{ + nullptr, + "Connect", + "Accept", + "Acknowledge", + "Refuse", + "Redirect", + "Data", + "Null", + nullptr, + "Abort", + nullptr, + "Resend", + "Marker", + "Attention", + "Control", + nullptr, + nullptr, + nullptr, + nullptr, + nullptr +}; +#endif +THREAD_LOCAL TNS_CLIENT_APP_CONFIG tns_config; + +static CLIENT_APP_RETCODE tns_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE tns_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, const AppIdConfig* pConfig); + +SO_PUBLIC RNAClientAppModule tns_client_mod = +{ + "TNS", // name + IpProtocol::TCP, // proto + &tns_init, // init + nullptr, // clean + &tns_validate, // validate + 1, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern patterns[] = +{ + { (const uint8_t*)TNS_BANNER, sizeof(TNS_BANNER)-1, 2, APP_ID_ORACLE_DATABASE }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_ORACLE_DATABASE, APPINFO_FLAG_CLIENT_ADDITIONAL | APPINFO_FLAG_CLIENT_USER } +}; + +static CLIENT_APP_RETCODE tns_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + + if (config) + { + SF_LNODE* cursor; + RNAClientAppModuleConfigItem* item; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + tns_config.enabled = atoi(item->value); + } + } + } + + tns_config.enabled = 1; + + if (tns_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&tns_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&tns_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +#define TNS_MAX_INFO_SIZE 63 +static CLIENT_APP_RETCODE tns_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig*) +{ + char username[TNS_MAX_INFO_SIZE+1]; + ClientTNSData* fd; + uint16_t offset; + int user_pos = 0; + int user_size = 0; + uint16_t user_start = 0; + uint16_t user_end = 0; + + if (dir != APP_ID_FROM_INITIATOR) + return CLIENT_APP_INPROCESS; + + fd = (ClientTNSData*)tns_client_mod.api->data_get(flowp, tns_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientTNSData*)snort_calloc(sizeof(ClientTNSData)); + tns_client_mod.api->data_add(flowp, fd, tns_client_mod.flow_data_index, &snort_free); + fd->state = TNS_STATE_MESSAGE_LEN; + } + + offset = 0; + while (offset < size) + { + switch (fd->state) + { + case TNS_STATE_MESSAGE_LEN: + fd->l.raw_len[fd->pos++] = data[offset]; + if (fd->pos >= offsetof(ClientTNSMsg, checksum)) + { + fd->stringlen = ntohs(fd->l.len); + if (fd->stringlen == 2) + { + if (offset == size - 1) + goto done; + return CLIENT_APP_EINVALID; + } + else if (fd->stringlen < 2) + return CLIENT_APP_EINVALID; + else if (fd->stringlen > size) + return CLIENT_APP_EINVALID; + else + fd->state = TNS_STATE_MESSAGE_CHECKSUM; + } + break; + + case TNS_STATE_MESSAGE_CHECKSUM: + if (data[offset] != 0) + return CLIENT_APP_EINVALID; + fd->pos++; + if (fd->pos >= offsetof(ClientTNSMsg, msg)) + fd->state = TNS_STATE_MESSAGE; + break; + + case TNS_STATE_MESSAGE: + fd->message = data[offset]; + if (fd->message < TNS_TYPE_CONNECT || fd->message > TNS_TYPE_MAX) + return CLIENT_APP_EINVALID; + fd->pos++; + fd->state = TNS_STATE_MESSAGE_RES; + break; + case TNS_STATE_MESSAGE_RES: + fd->state = TNS_STATE_MESSAGE_HD_CHECKSUM; + fd->pos++; + break; + case TNS_STATE_MESSAGE_HD_CHECKSUM: + fd->pos++; + if (fd->pos >= offsetof(ClientTNSMsg, data)) + { + switch (fd->message) + { + case TNS_TYPE_CONNECT: + fd->state = TNS_STATE_MESSAGE_CONNECT; + break; + case TNS_TYPE_ACK: + case TNS_TYPE_REFUSE: + case TNS_TYPE_DATA: + case TNS_TYPE_NULL: + case TNS_TYPE_ABORT: + case TNS_TYPE_RESEND: + case TNS_TYPE_MARKER: + case TNS_TYPE_ATTENTION: + case TNS_TYPE_CONTROL: + if (fd->pos >= fd->stringlen) + { + if (offset == (size - 1)) + goto done; + return CLIENT_APP_EINVALID; + } + fd->state = TNS_STATE_MESSAGE_DATA; + break; + case TNS_TYPE_ACCEPT: + case TNS_TYPE_REDIRECT: + default: + return CLIENT_APP_EINVALID; + } + } + break; + case TNS_STATE_MESSAGE_CONNECT: + fd->l.raw_len[fd->pos - CONNECT_VERSION_OFFSET] = data[offset]; + fd->pos++; + if (fd->pos >= (CONNECT_VERSION_OFFSET + 2)) + { + { + switch (ntohs(fd->l.len)) + { + case 0x136: + fd->version = "8"; + break; + case 0x137: + fd->version = "9i R1"; + break; + case 0x138: + fd->version = "9i R2"; + break; + case 0x139: + fd->version = "10g R1/R2"; + break; + case 0x13A: + fd->version = "11g R1"; + break; + default: + break; + } + } + fd->l.len = 0; + fd->state = TNS_STATE_MESSAGE_CONNECT_OFFSET_DC; + } + break; + case TNS_STATE_MESSAGE_CONNECT_OFFSET_DC: + fd->pos++; + if (fd->pos >= CONNECT_DATA_OFFSET) + fd->state = TNS_STATE_MESSAGE_CONNECT_OFFSET; + break; + case TNS_STATE_MESSAGE_CONNECT_OFFSET: + fd->l.raw_len[fd->pos - CONNECT_DATA_OFFSET] = data[offset]; + fd->pos++; + if (fd->pos >= (CONNECT_DATA_OFFSET + 2)) + { + fd->offsetlen = ntohs(fd->l.len); + if (fd->offsetlen > size) + { + return CLIENT_APP_EINVALID; + } + fd->state = TNS_STATE_MESSAGE_CONNECT_PREDATA; + } + break; + case TNS_STATE_MESSAGE_CONNECT_PREDATA: + fd->pos++; + if (fd->pos >= fd->offsetlen) + { + fd->state = TNS_STATE_MESSAGE_CONNECT_DATA; + } + break; + case TNS_STATE_MESSAGE_CONNECT_DATA: + if (tolower(data[offset]) != USER_STRING[user_pos]) + { + user_pos = 0; + if (tolower(data[offset]) == USER_STRING[user_pos]) + user_pos++; + } + else if (++user_pos > MAX_USER_POS) + { + user_start = offset+1; + fd->state = TNS_STATE_COLLECT_USER; + } + + fd->pos++; + if (fd->pos >= fd->stringlen) + { + if (offset == (size - 1)) + goto done; + return CLIENT_APP_EINVALID; + } + break; + case TNS_STATE_COLLECT_USER: + if (user_end == 0 && data[offset] == ')') + { + user_end = offset; + } + + fd->pos++; + if (fd->pos >= fd->stringlen) + { + if (offset == (size - 1)) + goto done; + return CLIENT_APP_EINVALID; + } + break; + case TNS_STATE_MESSAGE_DATA: + fd->pos++; + if (fd->pos >= fd->stringlen) + { + if (offset == (size - 1)) + goto done; + return CLIENT_APP_EINVALID; + } + break; + default: + goto inprocess; + } + offset++; + } +inprocess: + return CLIENT_APP_INPROCESS; + +done: + tns_client_mod.api->add_app(flowp, APP_ID_ORACLE_TNS, APP_ID_ORACLE_DATABASE, fd->version); + if (user_start && user_end && ((user_size = user_end - user_start) > 0)) + { + /* we truncate extra long usernames */ + if (user_size > TNS_MAX_INFO_SIZE) + user_size = TNS_MAX_INFO_SIZE; + memcpy(username, &data[user_start], user_size); + username[user_size] = 0; + tns_client_mod.api->add_user(flowp, username, APP_ID_ORACLE_DATABASE, 1); + } + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_vnc.cc b/src/network_inspectors/appid/client_plugins/client_app_vnc.cc new file mode 100644 index 000000000..c30e4d9b6 --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_vnc.cc @@ -0,0 +1,204 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_vnc.cc author Sourcefire Inc. + +#include "main/snort_debug.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_app_api.h" + +static const char VNC_BANNER[] = "RFB "; +static const char VNC_BANNER2[] = "."; + +#define VNC_BANNER_LEN (sizeof(VNC_BANNER)-1) + +enum VNCState +{ + VNC_STATE_BANNER = 0, + VNC_STATE_VERSION +}; + +#define MAX_VERSION_SIZE 8 +struct ClientVNCData +{ + VNCState state; + unsigned pos; + uint8_t version[MAX_VERSION_SIZE]; +}; + +struct VNC_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL VNC_CLIENT_APP_CONFIG vnc_config; + +static CLIENT_APP_RETCODE vnc_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE vnc_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, + const AppIdConfig* pConfig); + +SO_PUBLIC RNAClientAppModule vnc_client_mod = +{ + "RFB", // name + IpProtocol::TCP, // proto + &vnc_init, // init + nullptr, // clean + &vnc_validate, // validate + 2, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern patterns[] = +{ + { (const uint8_t*)VNC_BANNER, sizeof(VNC_BANNER)-1, 0, APP_ID_VNC }, + { (const uint8_t*)VNC_BANNER2, sizeof(VNC_BANNER2)-1, 7, APP_ID_VNC }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_VNC, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_VNC_RFB, APPINFO_FLAG_CLIENT_ADDITIONAL } +}; + +static CLIENT_APP_RETCODE vnc_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + + vnc_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor; + RNAClientAppModuleConfigItem* item; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + vnc_config.enabled = atoi(item->value); + } + } + } + + if (vnc_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&vnc_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&vnc_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static CLIENT_APP_RETCODE vnc_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig*) +{ + ClientVNCData* fd; + uint16_t offset; + + if (dir != APP_ID_FROM_INITIATOR) + return CLIENT_APP_INPROCESS; + + fd = (ClientVNCData*)vnc_client_mod.api->data_get(flowp, vnc_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientVNCData*)snort_calloc(sizeof(ClientVNCData)); + vnc_client_mod.api->data_add(flowp, fd, vnc_client_mod.flow_data_index, &snort_free); + fd->state = VNC_STATE_BANNER; + } + + offset = 0; + while (offset < size) + { + switch (fd->state) + { + case VNC_STATE_BANNER: + if (data[offset] != VNC_BANNER[fd->pos]) + return CLIENT_APP_EINVALID; + if (fd->pos >= VNC_BANNER_LEN-1) + { + fd->state = VNC_STATE_VERSION; + fd->pos = 0; + break; + } + fd->pos++; + break; + case VNC_STATE_VERSION: + if ((isdigit(data[offset]) || data[offset] == '.' || data[offset] == '\n') && fd->pos < + MAX_VERSION_SIZE) + { + fd->version[fd->pos] = data[offset]; + if (data[offset] == '\n' && fd->pos == 7) + { + fd->version[fd->pos] = 0; + goto done; + } + } + else + return CLIENT_APP_EINVALID; + fd->pos++; + break; + default: + goto inprocess; + } + offset++; + } +inprocess: + return CLIENT_APP_INPROCESS; + +done: + vnc_client_mod.api->add_app(flowp, APP_ID_VNC_RFB, APP_ID_VNC, (const char*)fd->version); + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_ym.cc b/src/network_inspectors/appid/client_plugins/client_app_ym.cc new file mode 100644 index 000000000..11714466e --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_ym.cc @@ -0,0 +1,227 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_ym.cc author Sourcefire Inc. + +#include "client_app_ym.h" + +#include "main/snort_debug.h" +#include "protocols/packet.h" +#include "utils/sflsq.h" + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_app_api.h" + +struct YM_CLIENT_APP_CONFIG +{ + int enabled; +}; + +THREAD_LOCAL YM_CLIENT_APP_CONFIG ym_config; + +#define MAX_VERSION_SIZE 64 + +static CLIENT_APP_RETCODE ym_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE ym_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, const AppIdConfig* pConfig); + +RNAClientAppModule ym_client_mod = +{ + "YM", // name + IpProtocol::TCP, // proto + &ym_init, // init + nullptr, // clean + &ym_validate, // validate + 1, // minimum_matches + nullptr, // api + nullptr, // userData + 0, // precedence + nullptr, // finalize, + 1, // provides_user + 0 // flow_data_index +}; + +struct Client_App_Pattern +{ + const u_int8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static const uint8_t APP_YMSG[] = "YMSG"; + +static Client_App_Pattern patterns[] = +{ + { APP_YMSG, sizeof(APP_YMSG)-1, 0, APP_ID_YAHOO_MSG }, +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_YAHOO, APPINFO_FLAG_CLIENT_ADDITIONAL }, + { APP_ID_YAHOO_MSG, APPINFO_FLAG_CLIENT_ADDITIONAL } +}; + +static CLIENT_APP_RETCODE ym_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + + ym_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor; + RNAClientAppModuleConfigItem* item; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + ym_config.enabled = atoi(item->value); + } + } + } + + if (ym_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&ym_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&ym_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static const uint8_t* skip_separator(const uint8_t* data, const uint8_t* end) +{ + while ( data + 1 < end ) + { + if ( data[0] == 0xc0 && data[1] == 0x80 ) + break; + + data++; + } + + data += 2; + + return data; +} + +static CLIENT_APP_RETCODE ym_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, Detector*, const AppIdConfig*) +{ +#define HEADERSIZE 20 +#define VERSIONID "135" +#define SEPARATOR 0xc080 + + const uint8_t* end; + uint16_t len; + uint8_t version[MAX_VERSION_SIZE]; + uint8_t* v; + uint8_t* v_end; + uint32_t product_id; + + product_id = APP_ID_YAHOO; + memset(&version,0,sizeof(version)); + + DebugFormat(DEBUG_LOG,"Found yahoo! client: %zu\n",sizeof(VERSIONID)); + + if (!data || !ym_client_mod.api || !flowp || !pkt) + return CLIENT_APP_ENULL; + + if (dir != APP_ID_FROM_INITIATOR) + return CLIENT_APP_INPROCESS; + + /* Validate the packet using the length field, otherwise abort. */ + if ( size < 10 ) + return CLIENT_APP_ENULL; + + len = *((uint16_t*)(data + 8)); + len = ntohs(len); + + if ( len != (size - HEADERSIZE) ) + return CLIENT_APP_ENULL; + + end = data + size; + + if ( size >= HEADERSIZE ) + { + data += HEADERSIZE; + } + + while ( data < end ) + { + if ( end-data >= (int)sizeof(VERSIONID) && memcmp(data, VERSIONID, sizeof(VERSIONID)-1) == + 0 ) + { + data += sizeof(VERSIONID)-1; + + if ( data + 2 >= end ) /* Skip the separator */ + goto done; + else + data += 2; + + product_id = APP_ID_YAHOO; + + v = version; + + v_end = v + (MAX_VERSION_SIZE - 1); + + /* Get the version */ + while ( data + 1 < end && v < v_end ) + { + if ( data[0] == 0xc0 && data[1] == 0x80 ) + break; + + *v = *data; + v++; + data++; + } + + goto done; + } + + data = skip_separator(data,end); /*skip to the command end separator */ + data = skip_separator(data,end); /* skip to the command data end separator */ + } + + return CLIENT_APP_INPROCESS; + +done: + ym_client_mod.api->add_app(flowp, APP_ID_YAHOO, product_id, (char*)version); + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; +} + diff --git a/src/network_inspectors/appid/client_plugins/client_app_ym.h b/src/network_inspectors/appid/client_plugins/client_app_ym.h new file mode 100644 index 000000000..265033b2c --- /dev/null +++ b/src/network_inspectors/appid/client_plugins/client_app_ym.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// client_app_ym.h author Sourcefire Inc. + +#ifndef CLIENT_APP_YM_H +#define CLIENT_APP_YM_H + +#include "client_app_api.h" + +extern RNAClientAppModule ym_client_mod; + +#endif + diff --git a/src/network_inspectors/appid/detector_plugins/CMakeLists.txt b/src/network_inspectors/appid/detector_plugins/CMakeLists.txt new file mode 100644 index 000000000..af40d3a30 --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/CMakeLists.txt @@ -0,0 +1,30 @@ +set ( DETECTOR_PLUGINS_SOURCES + detector_api.h + detector_base.cc + detector_base.h + detector_dns.cc + detector_dns.h + detector_http.cc + detector_http.h + detector_imap.cc + detector_kerberos.cc + detector_pattern.cc + detector_pattern.h + detector_pop3.cc + detector_sip.cc + detector_sip.h + http_url_patterns.cc + http_url_patterns.h + ) + +add_library ( appid_detector_plugins STATIC + ${DETECTOR_PLUGINS_SOURCES} +) + +target_include_directories ( appid_detector_plugins PRIVATE ${APPID_INCLUDE_DIR} ) + +# FIXIT-H: Add unit tests + +#install (FILES ${DETECTOR_PLUGINS_INCLUDES} +# DESTINATION "${INCLUDE_INSTALL_PATH}/appid/detector_plugins" +#) diff --git a/src/network_inspectors/appid/detector_plugins/Makefile.am b/src/network_inspectors/appid/detector_plugins/Makefile.am new file mode 100644 index 000000000..bd9c51598 --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/Makefile.am @@ -0,0 +1,30 @@ + +AM_CPPFLAGS+=-I$(top_srcdir)/src/network_inspectors/appid + +file_list = \ +detector_api.h \ +detector_base.cc \ +detector_base.h \ +detector_dns.cc \ +detector_dns.h \ +detector_http.cc \ +detector_http.h \ +detector_imap.cc \ +detector_kerberos.cc \ +detector_pattern.cc \ +detector_pattern.h \ +detector_pop3.cc \ +detector_sip.cc \ +detector_sip.h \ +http_url_patterns.cc \ +http_url_patterns.h + +noinst_LIBRARIES = libappid_detector_plugins.a +libappid_detector_plugins_a_SOURCES = $(file_list) + +# Uncomment once tests are ready. +#if BUILD_UNIT_TESTS +#SUBDIRS = test +#endif + + diff --git a/src/network_inspectors/appid/detector_plugins/detector_api.h b/src/network_inspectors/appid/detector_plugins/detector_api.h new file mode 100644 index 000000000..bfa873e60 --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_api.h @@ -0,0 +1,54 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_api.h author Sourcefire Inc. + +#ifndef DETECTOR_API_H +#define DETECTOR_API_H + +#include "appid_flow_data.h" + +struct RNAServiceValidationModule; +struct RNAClientAppModule; +struct StreamAPI; + +using DetectorFlowdataGet = void*(*)(AppIdData*, unsigned); +using DetectorFlowdataAdd = int(*)(AppIdData*, void*, unsigned, AppIdFreeFCN); + +struct DetectorApi +{ + DetectorFlowdataGet data_get; + DetectorFlowdataAdd data_add; +}; + +// compound detector with both service and client side. +struct RNADetectorValidationModule +{ + /**service side.*/ + RNAServiceValidationModule* service; + + /**client side.*/ + RNAClientAppModule* client; + + const DetectorApi* api; + unsigned flow_data_index; + StreamAPI* streamAPI; +}; + +#endif diff --git a/src/network_inspectors/appid/detector_plugins/detector_base.cc b/src/network_inspectors/appid/detector_plugins/detector_base.cc new file mode 100644 index 000000000..17a81f707 --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_base.cc @@ -0,0 +1,129 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_base.cc author Sourcefire Inc. + +#include "detector_base.h" + +#include "client_plugins/client_app_base.h" +#include "detector_api.h" +#include "log/messages.h" +#include "service_plugins/service_base.h" + +static void* detector_flowdata_get(AppIdData* flowp, unsigned detector_id); +static int detector_flowdata_add(AppIdData* flowp, void* data, unsigned detector_id, + AppIdFreeFCN fcn); + +static const DetectorApi detector_api +{ + &detector_flowdata_get, + &detector_flowdata_add, +}; + +extern RNADetectorValidationModule imap_detector_mod; +extern RNADetectorValidationModule pop3_detector_mod; +extern RNADetectorValidationModule kerberos_detector_mod; +extern RNADetectorValidationModule pattern_detector_mod; + +static RNADetectorValidationModule* static_detector_list[] +{ +#ifdef REMOVED_WHILE_NOT_IN_USE + &imap_detector_mod, + &pop3_detector_mod, + &kerberos_detector_mod +#endif + nullptr +}; + +/**callback function for initializing static and dynamic detectors. + */ +static int detectorLoadCallback(void* symbol) +{ + static THREAD_LOCAL unsigned detector_module_index = 0; + RNADetectorValidationModule* svm = (RNADetectorValidationModule*)symbol; + + if (detector_module_index >= 65536) + { + ErrorMessage("Maximum number of detector modules exceeded"); + return -1; + } + if (svm->service) + { + if (serviceLoadCallback(svm->service)) + { + return -1; + } + } + + if (svm->client) + { + if (ClientAppLoadCallback(svm->client)) + { + return -1; + } + } + + svm->api = &detector_api; + svm->flow_data_index = detector_module_index | APPID_SESSION_DATA_DETECTOR_MODSTATE_BIT; +// svm->streamAPI = _dpd.streamAPI; FIXIT - removed to permit compilation. + detector_module_index++; + + return 0; +} + +int LoadDetectorModules(const char** ) +{ + unsigned i; + + for (i=0; impattern = target; + cm->index = index; + cm->next = *matches; + *matches = cm; + + return 0; +} + +static int dns_host_detector_create_matcher(ServiceDnsConfig* pDnsConfig, + DetectorDNSHostPattern* list) +{ + DetectorDNSHostPattern* element = nullptr; + + if (pDnsConfig->dns_host_host_matcher) + delete pDnsConfig->dns_host_host_matcher; + + pDnsConfig->dns_host_host_matcher = new SearchTool("ac_full"); + if (!pDnsConfig->dns_host_host_matcher) + return 0; + + /* Add patterns from Lua API */ + for (element = list; element; element = element->next) + { + pDnsConfig->dns_host_host_matcher->add((char*)element->dpattern->pattern, + element->dpattern->pattern_size, element->dpattern, true); + } + + pDnsConfig->dns_host_host_matcher->prep(); + + return 1; +} + +int dns_host_detector_process_patterns(ServiceDnsConfig* pDnsConfig) +{ + int retVal = 1; + if (!dns_host_detector_create_matcher(pDnsConfig, pDnsConfig->DetectorDNSHostPatternList)) + retVal = 0; + return retVal; +} + +static int dns_service_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR, "registering appId: %d\n", appIdRegistry[i].appId); + init_api->RegisterAppId(&dns_udp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int dns_validate_label(const uint8_t* data, uint16_t* offset, uint16_t size, uint8_t* len, + unsigned* len_valid) +{ + const DNSLabel* lbl; + const DNSLabelPtr* lbl_ptr; + const DNSLabelBitfield* lbl_bit; + uint16_t tmp; + + *len = 0; + *len_valid = 1; + for (;; ) + { + if ((size <= *offset) || (size-(*offset)) < (int)offsetof(DNSLabel, name)) + return SERVICE_NOMATCH; + lbl = (DNSLabel*)(data + (*offset)); + switch (lbl->len & DNS_LENGTH_FLAGS) + { + case 0xC0: + *len_valid = 0; + lbl_ptr = (DNSLabelPtr*)lbl; + *offset += offsetof(DNSLabelPtr, data); + if (*offset >= size) + return SERVICE_NOMATCH; + tmp = (uint16_t)(ntohs(lbl_ptr->position) & 0x3FFF); + if (tmp > size - offsetof(DNSLabel, name)) + return SERVICE_NOMATCH; + return SERVICE_SUCCESS; + case 0x00: + *offset += offsetof(DNSLabel, name); + if (!lbl->len) + { + (*len)--; // take off the extra '.' at the end + return SERVICE_SUCCESS; + } + *offset += lbl->len; + *len += lbl->len + 1; // add 1 for '.' + break; + case 0x40: + *len_valid = 0; + if (lbl->len != 0x41) + return SERVICE_NOMATCH; + *offset += offsetof(DNSLabelBitfield, data); + if (*offset >= size) + return SERVICE_NOMATCH; + lbl_bit = (DNSLabelBitfield*)lbl; + if (lbl_bit->len) + { + *offset += ((lbl_bit->len - 1) / 8) + 1; + } + else + { + *offset += 32; + } + break; + default: + *len_valid = 0; + return SERVICE_NOMATCH; + } + } + return SERVICE_NOMATCH; +} + +static int dns_validate_query(const uint8_t* data, uint16_t* offset, uint16_t size, + uint16_t id, unsigned host_reporting, AppIdData* flowp) +{ + int ret; + const uint8_t* host; + uint8_t host_len; + unsigned host_len_valid; + uint16_t host_offset; + DNSQueryFixed* query; + uint16_t record_type; + + host = data + *offset; + host_offset = *offset; + ret = dns_validate_label(data, offset, size, &host_len, &host_len_valid); + if (ret == SERVICE_SUCCESS) + { + query = (DNSQueryFixed*)(data + *offset); + *offset += sizeof(DNSQueryFixed); + if (host_reporting) + { + record_type = ntohs(query->QType); + if ((host_len == 0) || (!host_len_valid)) + { + host = nullptr; + host_len = 0; + host_offset = 0; + } + switch (record_type) + { + case PATTERN_A_REC: + case PATTERN_AAAA_REC: + case PATTERN_CNAME_REC: + case PATTERN_SRV_REC: + case PATTERN_TXT_REC: + case PATTERN_MX_REC: + case PATTERN_SOA_REC: + case PATTERN_NS_REC: + dns_service_mod.api->add_dns_query_info(flowp, id, host, host_len, host_offset, + record_type); + break; + case PATTERN_PTR_REC: + dns_service_mod.api->add_dns_query_info(flowp, id, nullptr, 0, 0, record_type); + break; + default: + break; + } + } + } + return ret; +} + +static int dns_validate_answer(const uint8_t* data, uint16_t* offset, uint16_t size, + uint16_t id, uint8_t rcode, unsigned host_reporting, AppIdData* flowp) +{ + int ret; + const uint8_t* host; + uint8_t host_len; + unsigned host_len_valid; + uint16_t host_offset; + uint16_t record_type; + uint32_t ttl; + uint16_t r_data_offset; + + ret = dns_validate_label(data, offset, size, &host_len, &host_len_valid); + if (ret == SERVICE_SUCCESS) + { + DNSAnswerData* ad = (DNSAnswerData*)(data + (*offset)); + *offset += sizeof(DNSAnswerData); + if (*offset > size) + return SERVICE_NOMATCH; + r_data_offset = *offset; + *offset += ntohs(ad->r_len); + if (*offset > size) + return SERVICE_NOMATCH; + if (host_reporting) + { + record_type = ntohs(ad->type); + ttl = ntohl(ad->ttl); + switch (record_type) + { + case PATTERN_A_REC: + case PATTERN_AAAA_REC: + case PATTERN_CNAME_REC: + case PATTERN_SRV_REC: + case PATTERN_TXT_REC: + case PATTERN_MX_REC: + case PATTERN_SOA_REC: + case PATTERN_NS_REC: + dns_service_mod.api->add_dns_response_info(flowp, id, nullptr, 0, 0, rcode, ttl); + break; + case PATTERN_PTR_REC: + host = data + r_data_offset; + host_offset = r_data_offset; + ret = dns_validate_label(data, &r_data_offset, size, &host_len, &host_len_valid); + if ((host_len == 0) || (!host_len_valid)) + { + host = nullptr; + host_len = 0; + host_offset = 0; + } + dns_service_mod.api->add_dns_response_info(flowp, id, host, host_len, host_offset, + rcode, ttl); + break; + default: + break; + } + } + } + return ret; +} + +static int dns_validate_header(const int dir, DNSHeader* hdr, + unsigned host_reporting, AppIdData* flowp) +{ + if (hdr->Opcode > MAX_OPCODE || hdr->Opcode == INVALID_OPCODE) + { + return SERVICE_NOMATCH; + } + if (hdr->Z) + { + return SERVICE_NOMATCH; + } + if (hdr->RCODE > MAX_RCODE) + { + return SERVICE_NOMATCH; + } + if (!hdr->QR) + { + // Query. + if (host_reporting) + dns_service_mod.api->reset_dns_info(flowp); + return dir == APP_ID_FROM_INITIATOR ? SERVICE_SUCCESS : SERVICE_REVERSED; + } + + // Response. + return dir == APP_ID_FROM_INITIATOR ? SERVICE_REVERSED : SERVICE_SUCCESS; +} + +static int validate_packet(const uint8_t* data, uint16_t size, const int, + unsigned host_reporting, AppIdData* flowp) +{ + uint16_t i; + uint16_t count; + const DNSHeader* hdr = (const DNSHeader*)data; + uint16_t offset; + + if (hdr->TC && size == 512) + return SERVICE_SUCCESS; + + offset = sizeof(DNSHeader); + + if (hdr->QDCount) + { + count = ntohs(hdr->QDCount); + for (i=0; iid), host_reporting, flowp) != + SERVICE_SUCCESS) + { + return SERVICE_NOMATCH; + } + } + } + + if (hdr->ANCount) + { + count = ntohs(hdr->ANCount); + for (i=0; iid), hdr->RCODE, + host_reporting, flowp) != SERVICE_SUCCESS) + { + return SERVICE_NOMATCH; + } + } + } + + if (hdr->NSCount) + { + count = ntohs(hdr->NSCount); + for (i=0; iid), hdr->RCODE, + host_reporting, flowp) != SERVICE_SUCCESS) + { + return SERVICE_NOMATCH; + } + } + } + + if (hdr->ARCount) + { + count = ntohs(hdr->ARCount); + for (i=0; iid), hdr->RCODE, + host_reporting, flowp) != SERVICE_SUCCESS) + { + return SERVICE_NOMATCH; + } + } + } + + if (hdr->QR && (hdr->RCODE != 0)) // error response + dns_service_mod.api->add_dns_response_info(flowp, ntohs(hdr->id), nullptr, 0, 0, + hdr->RCODE, + 0); + + return SERVICE_SUCCESS; +} + +static int dns_udp_validate(ServiceValidationArgs* args) +{ + int rval; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + return SERVICE_INPROCESS; + + if (size < sizeof(DNSHeader)) + { + rval = (dir == APP_ID_FROM_INITIATOR) ? SERVICE_INVALID_CLIENT : SERVICE_NOMATCH; + goto udp_done; + } + if ((rval = dns_validate_header(dir, (DNSHeader*)data, + pAppidActiveConfig->mod_config->dns_host_reporting, flowp)) != SERVICE_SUCCESS) + { + if (rval == SERVICE_REVERSED) + { + if (dir == APP_ID_FROM_RESPONDER) + { + if (getAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED)) + { + // To get here, we missed the initial query, got a + // response, and now we've got another query. + rval = validate_packet(data, size, dir, + pAppidActiveConfig->mod_config->dns_host_reporting, flowp); + if (rval == SERVICE_SUCCESS) + goto inprocess; + } + goto invalid; + } + else + { + // To get here, we missed the initial query, but now we've got + // a response. + rval = validate_packet(data, size, dir, + pAppidActiveConfig->mod_config->dns_host_reporting, flowp); + if (rval == SERVICE_SUCCESS) + { + setAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED); + goto success; + } + goto nomatch; + } + } + rval = (dir == APP_ID_FROM_INITIATOR) ? SERVICE_INVALID_CLIENT : SERVICE_NOMATCH; + goto udp_done; + } + + rval = validate_packet(data, size, dir, + pAppidActiveConfig->mod_config->dns_host_reporting, flowp); + if ((rval == SERVICE_SUCCESS) && (dir == APP_ID_FROM_INITIATOR)) + goto inprocess; + +udp_done: + switch (rval) + { + case SERVICE_SUCCESS: +success: + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + dns_service_mod.api->add_service(flowp, args->pkt, dir, &udp_svc_element, + APP_ID_DNS, nullptr, nullptr, nullptr); + appid_stats.dns_udp_count++; + return SERVICE_SUCCESS; + case SERVICE_INVALID_CLIENT: +invalid: + dns_service_mod.api->incompatible_data(flowp, args->pkt, dir, &udp_svc_element, + dns_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOT_COMPATIBLE; + case SERVICE_NOMATCH: +nomatch: + dns_service_mod.api->fail_service(flowp, args->pkt, dir, &udp_svc_element, + dns_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOMATCH; + case SERVICE_INPROCESS: +inprocess: + dns_udp_client_mod.api->add_app(flowp, APP_ID_NONE, APP_ID_DNS, nullptr); + dns_service_mod.api->service_inprocess(flowp, args->pkt, dir, &udp_svc_element); + return SERVICE_INPROCESS; + default: + return rval; + } +} + +static int dns_tcp_validate(ServiceValidationArgs* args) +{ + ServiceDNSData* dd; + const DNSTCPHeader* hdr; + uint16_t tmp; + int rval; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (size < sizeof(DNSTCPHeader)) + { + if (dir == APP_ID_FROM_INITIATOR) + goto not_compatible; + else + goto fail; + } + hdr = (DNSTCPHeader*)data; + data += sizeof(DNSTCPHeader); + size -= sizeof(DNSTCPHeader); + tmp = ntohs(hdr->length); + if (tmp < sizeof(DNSHeader) || dns_validate_header(dir, (DNSHeader*)data, + pAppidActiveConfig->mod_config->dns_host_reporting, flowp)) + { + if (dir == APP_ID_FROM_INITIATOR) + goto not_compatible; + else + goto fail; + } + + if (tmp > size) + goto not_compatible; + rval = validate_packet(data, size, dir, pAppidActiveConfig->mod_config->dns_host_reporting, + flowp); + if (rval != SERVICE_SUCCESS) + goto tcp_done; + + dd = static_cast(dns_service_mod.api->data_get(flowp, + dns_service_mod.flow_data_index)); + if (!dd) + { + dd = static_cast(snort_calloc(sizeof(ServiceDNSData))); + if (dns_service_mod.api->data_add(flowp, dd, dns_service_mod.flow_data_index, &snort_free)) + dd->state = DNS_STATE_QUERY; + } + + if (dd->state == DNS_STATE_QUERY) + { + if (dir != APP_ID_FROM_INITIATOR) + goto fail; + dd->id = ((DNSHeader*)data)->id; + dd->state = DNS_STATE_RESPONSE; + goto inprocess; + } + else + { + if (dir != APP_ID_FROM_RESPONDER || dd->id != ((DNSHeader*)data)->id) + goto fail; + } + +tcp_done: + switch (rval) + { + case SERVICE_SUCCESS: + goto success; + case SERVICE_INVALID_CLIENT: + goto not_compatible; + case SERVICE_NOMATCH: + goto fail; + case SERVICE_INPROCESS: + goto inprocess; + default: + return rval; + } + +success: + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + dns_service_mod.api->add_service(flowp, args->pkt, dir, &tcp_svc_element, + APP_ID_DNS, nullptr, nullptr, nullptr); + appid_stats.dns_tcp_count++; + return SERVICE_SUCCESS; + +not_compatible: + dns_service_mod.api->incompatible_data(flowp, args->pkt, dir, &tcp_svc_element, + dns_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOT_COMPATIBLE; + +fail: + dns_service_mod.api->fail_service(flowp, args->pkt, dir, &tcp_svc_element, + dns_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOMATCH; + +inprocess: + dns_tcp_client_mod.api->add_app(flowp, APP_ID_NONE, APP_ID_DNS, nullptr); + dns_service_mod.api->service_inprocess(flowp, args->pkt, dir, &tcp_svc_element); + return SERVICE_INPROCESS; +} + +static int dns_host_scan_patterns(SearchTool* matcher, const u_int8_t* pattern, size_t size, + AppId* ClientAppId, AppId* payloadId) +{ + MatchedDNSPatterns* mp = nullptr; + MatchedDNSPatterns* tmpMp; + DNSHostPattern* best_match; + + if (!matcher) + return 0; + + matcher->find_all((char*)pattern, size, dns_host_pattern_match, false, &mp); + + if (!mp) + return 0; + + best_match = mp->mpattern; + tmpMp = mp->next; + snort_free(mp); + + while ((mp = tmpMp)) + { + tmpMp = mp->next; + if (mp->mpattern->pattern_size > best_match->pattern_size) + { + best_match = mp->mpattern; + } + snort_free(mp); + } + + switch (best_match->type) + { + // type 0 means WEB APP + case 0: + *ClientAppId = APP_ID_DNS; + *payloadId = best_match->appId; + break; + // type 1 means CLIENT + case 1: + *ClientAppId = best_match->appId; + *payloadId = 0; + break; + default: + return 0; + } + + return 1; +} + +int dns_host_scan_hostname(const u_int8_t* pattern, size_t size, AppId* ClientAppId, + AppId* payloadId, const ServiceDnsConfig* pDnsConfig) +{ + return dns_host_scan_patterns(pDnsConfig->dns_host_host_matcher, pattern, size, ClientAppId, + payloadId); +} + +void service_dns_host_clean(ServiceDnsConfig* pDnsConfig) +{ + if (pDnsConfig->dns_host_host_matcher ) + { + delete pDnsConfig->dns_host_host_matcher; + pDnsConfig->dns_host_host_matcher = nullptr; + } +} + +static int dns_add_pattern(DetectorDNSHostPattern** list, uint8_t* pattern_str, size_t + pattern_size, uint8_t type, AppId app_id) +{ + DetectorDNSHostPattern* new_dns_host_pattern; + + new_dns_host_pattern = static_cast(snort_calloc( + sizeof(DetectorDNSHostPattern))); + new_dns_host_pattern->dpattern = static_cast(snort_calloc( + sizeof(DNSHostPattern))); + + new_dns_host_pattern->dpattern->type = type; + new_dns_host_pattern->dpattern->appId = app_id; + new_dns_host_pattern->dpattern->pattern = pattern_str; + new_dns_host_pattern->dpattern->pattern_size = pattern_size; + + new_dns_host_pattern->next = *list; + *list = new_dns_host_pattern; + + return 1; +} + +int dns_add_host_pattern(uint8_t* pattern_str, size_t pattern_size, uint8_t type, AppId app_id, + ServiceDnsConfig* pDnsConfig) +{ + return dns_add_pattern(&pDnsConfig->DetectorDNSHostPatternList, pattern_str, pattern_size, + type, app_id); +} + +static void dns_patterns_free(DetectorDNSHostPattern** list) +{ + DetectorDNSHostPattern* tmp_pattern; + + while ((tmp_pattern = *list)) + { + *list = tmp_pattern->next; + if (tmp_pattern->dpattern) + { + if (tmp_pattern->dpattern->pattern) + snort_free(tmp_pattern->dpattern->pattern); + free (tmp_pattern->dpattern); + } + snort_free(tmp_pattern); + } +} + +void dns_detector_free_patterns(ServiceDnsConfig* pDnsConfig) +{ + dns_patterns_free(&pDnsConfig->DetectorDNSHostPatternList); +} + +char* dns_parse_host(const uint8_t* host, uint8_t host_len) +{ + char* str; + const uint8_t* src; + char* dst; + uint8_t len; + uint32_t dstLen = 0; + + str = static_cast(snort_calloc(host_len + 1)); // plus '\0' at end + src = host; + dst = str; + while (*src != 0) + { + len = *src; + src++; + if ((dstLen + len) <= host_len) + memcpy(dst, src, len); + else + { + // Malformed DNS host, return + snort_free(str); + return nullptr; + } + src += len; + dst += len; + *dst = '.'; + dstLen += len + 1; + dst++; + } + str[host_len] = '\0'; // nullptr term + return str; +} + diff --git a/src/network_inspectors/appid/detector_plugins/detector_dns.h b/src/network_inspectors/appid/detector_plugins/detector_dns.h new file mode 100644 index 000000000..5cc24c8ef --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_dns.h @@ -0,0 +1,43 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_dns.h author Sourcefire Inc. + +#ifndef DETECTOR_DNS_H +#define DETECTOR_DNS_H + +#include "service_plugins/service_api.h" + +struct RNAServiceValidationModule; +struct RNAClientAppModule; +struct ServiceDnsConfig; + +extern struct RNAServiceValidationModule dns_service_mod; +extern struct RNAClientAppModule dns_udp_client_mod; +extern struct RNAClientAppModule dns_tcp_client_mod; + +int dns_host_scan_hostname(const u_int8_t*, size_t, AppId*, AppId*, const ServiceDnsConfig*); +void service_dns_host_clean(ServiceDnsConfig* pConfig); +int dns_host_detector_process_patterns(ServiceDnsConfig* pConfig); +int dns_add_host_pattern(uint8_t*, size_t, uint8_t, AppId, ServiceDnsConfig*); +void dns_detector_free_patterns(ServiceDnsConfig* pConfig); +char* dns_parse_host(const uint8_t* host, uint8_t host_len); + +#endif + diff --git a/src/network_inspectors/appid/detector_plugins/detector_http.cc b/src/network_inspectors/appid/detector_plugins/detector_http.cc new file mode 100644 index 000000000..b23d86f09 --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_http.cc @@ -0,0 +1,2496 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_http.cc author Sourcefire Inc. + +#include "detector_http.h" + +#include "search_engines/search_tool.h" +#include "main/snort_debug.h" +#include "sfip/sf_ip.h" + +#include "service_plugins/service_api.h" +#include "service_plugins/service_util.h" +#include "util/sf_mlmp.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_plugins/client_app_base.h" +#include "http_url_patterns.h" + +/* URL line patterns for identifying client */ +#define HTTP_GET "GET " +#define HTTP_PUT "PUT " +#define HTTP_POST "POST " +#define HTTP_HEAD "HEAD " +#define HTTP_TRACE "TRACE " +#define HTTP_DELETE "DELETE " +#define HTTP_OPTIONS "OPTIONS " +#define HTTP_PROPFIND "PROPFIND " +#define HTTP_PROPPATCH "PROPPATCH " +#define HTTP_MKCOL "MKCOL " +#define HTTP_COPY "COPY " +#define HTTP_MOVE "MOVE " +#define HTTP_LOCK "LOCK " +#define HTTP_UNLOCK "UNLOCK " + +#define HTTP_GET_SIZE (sizeof(HTTP_GET)-1) +#define HTTP_PUT_SIZE (sizeof(HTTP_PUT)-1) +#define HTTP_POST_SIZE (sizeof(HTTP_POST)-1) +#define HTTP_HEAD_SIZE (sizeof(HTTP_HEAD)-1) +#define HTTP_TRACE_SIZE (sizeof(HTTP_GET)-1) +#define HTTP_DELETE_SIZE (sizeof(HTTP_DELETE)-1) +#define HTTP_OPTIONS_SIZE (sizeof(HTTP_OPTIONS)-1) +#define HTTP_PROPFIND_SIZE (sizeof(HTTP_PROPFIND)-1) +#define HTTP_PROPPATCH_SIZE (sizeof(HTTP_PROPPATCH)-1) +#define HTTP_MKCOL_SIZE (sizeof(HTTP_GET)-1) +#define HTTP_COPY_SIZE (sizeof(HTTP_COPY)-1) +#define HTTP_MOVE_SIZE (sizeof(HTTP_MOVE)-1) +#define HTTP_LOCK_SIZE (sizeof(HTTP_LOCK)-1) +#define HTTP_UNLOCK_SIZE (sizeof(HTTP_UNLOCK)-1) + +/* media type patterns*/ +#define VIDEO_BANNER "video/" +#define AUDIO_BANNER "audio/" +#define APPLICATION_BANNER "application/" +#define QUICKTIME_BANNER "quicktime" +#define MPEG_BANNER "mpeg" +#define MPA_BANNER "mpa" +#define ROBUST_MPA_BANNER "robust-mpa" +#define MP4A_BANNER "mp4a-latm" +#define SHOCKWAVE_BANNER "x-shockwave-flash" +#define RSS_BANNER "rss+xml" +#define ATOM_BANNER "atom+xml" +#define MP4_BANNER "mp4" +#define WMV_BANNER "x-ms-wmv" +#define WMA_BANNER "x-ms-wma" +#define WAV_BANNER "wav" +#define X_WAV_BANNER "x-wav" +#define VND_WAV_BANNER "vnd.wav" +#define FLV_BANNER "x-flv" +#define M4V_BANNER "x-m4v" +#define GPP_BANNER "3gpp" +#define XSCPLS_BANNER "x-scpls" + +#define VIDEO_BANNER_MAX_POS (sizeof(VIDEO_BANNER)-2) +#define AUDIO_BANNER_MAX_POS (sizeof(AUDIO_BANNER)-2) +#define APPLICATION_BANNER_MAX_POS (sizeof(APPLICATION_BANNER)-2) +#define QUICKTIME_BANNER_MAX_POS (sizeof(QUICKTIME_BANNER)-2) +#define MPEG_BANNER_MAX_POS (sizeof(MPEG_BANNER)-2) +#define MPA_BANNER_MAX_POS (sizeof(MPA_BANNER)-2) +#define ROBUST_MPA_BANNER_MAX_POS (sizeof(ROBUST_MPA_BANNER)-2) +#define MP4A_BANNER_MAX_POS (sizeof(MP4A_BANNER)-2) +#define SHOCKWAVE_BANNER_MAX_POS (sizeof(SHOCKWAVE_BANNER)-2) +#define RSS_BANNER_MAX_POS (sizeof(RSS_BANNER)-2) +#define ATOM_BANNER_MAX_POS (sizeof(ATOM_BANNER)-2) +#define MP4_BANNER_MAX_POS (sizeof(MP4_BANNER)-2) +#define WMV_BANNER_MAX_POS (sizeof(WMV_BANNER)-2) +#define WMA_BANNER_MAX_POS (sizeof(WMA_BANNER)-2) +#define WAV_BANNER_MAX_POS (sizeof(WAV_BANNER)-2) +#define X_WAV_BANNER_MAX_POS (sizeof(X_WAV_BANNER)-2) +#define VND_WAV_BANNER_MAX_POS (sizeof(VND_WAV_BANNER)-2) +#define FLV_BANNER_MAX_POS (sizeof(FLV_BANNER)-2) +#define M4V_BANNER_MAX_POS (sizeof(M4V_BANNER)-2) +#define GPP_BANNER_MAX_POS (sizeof(GPP_BANNER)-2) +#define XSCPLS_BANNER_MAX_POS (sizeof(XSCPLS_BANNER)-2) + +/* version patterns*/ +static const char MSIE_PATTERN[] = "MSIE"; +static const char KONQUEROR_PATTERN[] = "Konqueror"; +static const char SKYPE_PATTERN[] = "Skype"; +static const char BITTORRENT_PATTERN[] = "BitTorrent"; +static const char FIREFOX_PATTERN[] = "Firefox"; +static const char WGET_PATTERN[] = "Wget/"; +static const char CURL_PATTERN[] = "curl"; +static const char GOOGLE_DESKTOP_PATTERN[] = "Google Desktop"; +static const char PICASA_PATTERN[] = "Picasa"; +static const char SAFARI_PATTERN[] = "Safari"; +static const char CHROME_PATTERN[] = "Chrome"; +static const char MOBILE_PATTERN[] = "Mobile"; +static const char BLACKBERRY_PATTERN[] = "BlackBerry"; +static const char ANDROID_PATTERN[] = "Android"; +static const char MEDIAPLAYER_PATTERN[] = "Windows-Media-Player"; +static const char APPLE_EMAIL_PATTERN[] = "Maci"; +static const char* APPLE_EMAIL_PATTERNS[] = { "Mozilla/5.0","AppleWebKit","(KHTML, like Gecko)" }; + +/* "fake" patterns for user-agent matching */ +static const char VERSION_PATTERN[] = "Version"; +#define VERSION_PATTERN_SIZE (sizeof(VERSION_PATTERN)-1) +#define FAKE_VERSION_APP_ID 3 + +/* proxy patterns*/ +static const char SQUID_PATTERN[] = "squid"; +#define SQUID_PATTERN_SIZE (sizeof(SQUID_PATTERN)-1) + +static const char MYSPACE_PATTERN[] = "myspace.com"; +static const char GMAIL_PATTERN[] = "gmail.com"; +static const char GMAIL_PATTERN2[] = "mail.google.com"; +static const char AOL_PATTERN[] = "webmail.aol.com"; +static const char MSUP_PATTERN[] = "update.microsoft.com"; +static const char MSUP_PATTERN2[] = "windowsupdate.com"; +static const char YAHOO_MAIL_PATTERN[] = "mail.yahoo.com"; +static const char YAHOO_TB_PATTERN[] = "rd.companion.yahoo.com"; +static const char ADOBE_UP_PATTERN[] = "swupmf.adobe.com"; +static const char HOTMAIL_PATTERN1[] = "hotmail.com"; +static const char HOTMAIL_PATTERN2[] = "mail.live.com"; +static const char GOOGLE_TB_PATTERN[] = "toolbarqueries.google.com"; +#define MYSPACE_PATTERN_SIZE (sizeof(MYSPACE_PATTERN)-1) +#define GMAIL_PATTERN_SIZE (sizeof(GMAIL_PATTERN)-1) +#define GMAIL_PATTERN2_SIZE (sizeof(GMAIL_PATTERN2)-1) +#define AOL_PATTERN_SIZE (sizeof(AOL_PATTERN)-1) +#define MSUP_PATTERN_SIZE (sizeof(MSUP_PATTERN)-1) +#define MSUP_PATTERN2_SIZE (sizeof(MSUP_PATTERN2)-1) +#define YAHOO_MAIL_PATTERN_SIZE (sizeof(YAHOO_MAIL_PATTERN)-1) +#define YAHOO_TB_PATTERN_SIZE (sizeof(YAHOO_TB_PATTERN)-1) +#define ADOBE_UP_PATTERN_SIZE (sizeof(ADOBE_UP_PATTERN)-1) +#define HOTMAIL_PATTERN1_SIZE (sizeof(HOTMAIL_PATTERN1)-1) +#define HOTMAIL_PATTERN2_SIZE (sizeof(HOTMAIL_PATTERN2)-1) +#define GOOGLE_TB_PATTERN_SIZE (sizeof(GOOGLE_TB_PATTERN)-1) + +#define COMPATIBLE_BROWSER_STRING " (Compat)" + +struct MatchedPatterns +{ + DetectorHTTPPattern* mpattern; + int index; + MatchedPatterns* next; +}; + +static DetectorHTTPPattern content_type_patterns[] = +{ + { SINGLE, 0, APP_ID_QUICKTIME, 0, + sizeof(QUICKTIME_BANNER)-1, (uint8_t*)QUICKTIME_BANNER, APP_ID_QUICKTIME }, + { SINGLE, 0, APP_ID_MPEG, 0, + sizeof(MPEG_BANNER)-1, (uint8_t*)MPEG_BANNER, APP_ID_MPEG }, + { SINGLE, 0, APP_ID_MPEG, 0, + sizeof(MPA_BANNER)-1, (uint8_t*)MPA_BANNER, APP_ID_MPEG }, + { SINGLE, 0, APP_ID_MPEG, 0, + sizeof(MP4A_BANNER)-1, (uint8_t*)MP4A_BANNER, APP_ID_MPEG }, + { SINGLE, 0, APP_ID_MPEG, 0, + sizeof(ROBUST_MPA_BANNER)-1, (uint8_t*)ROBUST_MPA_BANNER, APP_ID_MPEG }, + { SINGLE, 0, APP_ID_MPEG, 0, + sizeof(XSCPLS_BANNER)-1, (uint8_t*)XSCPLS_BANNER, APP_ID_MPEG }, + { SINGLE, 0, APP_ID_SHOCKWAVE, 0, + sizeof(SHOCKWAVE_BANNER)-1, (uint8_t*)SHOCKWAVE_BANNER, APP_ID_SHOCKWAVE }, + { SINGLE, 0, APP_ID_RSS, 0, + sizeof(RSS_BANNER)-1, (uint8_t*)RSS_BANNER, APP_ID_RSS }, + { SINGLE, 0, APP_ID_ATOM, 0, + sizeof(ATOM_BANNER)-1, (uint8_t*)ATOM_BANNER, APP_ID_ATOM }, + { SINGLE, 0, APP_ID_MP4, 0, + sizeof(MP4_BANNER)-1, (uint8_t*)MP4_BANNER, APP_ID_MP4 }, + { SINGLE, 0, APP_ID_WMV, 0, + sizeof(WMV_BANNER)-1, (uint8_t*)WMV_BANNER, APP_ID_WMV }, + { SINGLE, 0, APP_ID_WMA, 0, + sizeof(WMA_BANNER)-1, (uint8_t*)WMA_BANNER, APP_ID_WMA }, + { SINGLE, 0, APP_ID_WAV, 0, + sizeof(WAV_BANNER)-1, (uint8_t*)WAV_BANNER, APP_ID_WAV }, + { SINGLE, 0, APP_ID_WAV, 0, + sizeof(X_WAV_BANNER)-1, (uint8_t*)X_WAV_BANNER, APP_ID_WAV }, + { SINGLE, 0, APP_ID_WAV, 0, + sizeof(VND_WAV_BANNER)-1, (uint8_t*)VND_WAV_BANNER, APP_ID_WAV }, + { SINGLE, 0, APP_ID_FLASH_VIDEO, 0, + sizeof(FLV_BANNER)-1, (uint8_t*)FLV_BANNER, APP_ID_FLASH_VIDEO }, + { SINGLE, 0, APP_ID_FLASH_VIDEO, 0, + sizeof(M4V_BANNER)-1, (uint8_t*)M4V_BANNER, APP_ID_FLASH_VIDEO }, + { SINGLE, 0, APP_ID_FLASH_VIDEO, 0, + sizeof(GPP_BANNER)-1, (uint8_t*)GPP_BANNER, APP_ID_FLASH_VIDEO }, + { SINGLE, 0, APP_ID_GENERIC, 0, + sizeof(VIDEO_BANNER)-1, (uint8_t*)VIDEO_BANNER, APP_ID_GENERIC }, + { SINGLE, 0, APP_ID_GENERIC, 0, + sizeof(AUDIO_BANNER)-1, (uint8_t*)AUDIO_BANNER, APP_ID_GENERIC }, +}; + +static DetectorHTTPPattern via_http_detector_patterns[] = +{ + { SINGLE, APP_ID_SQUID, 0, 0, + SQUID_PATTERN_SIZE, (uint8_t*)SQUID_PATTERN, APP_ID_SQUID }, +}; + +static DetectorHTTPPattern host_payload_http_detector_patterns[] = +{ + { SINGLE, 0, 0, APP_ID_MYSPACE, + MYSPACE_PATTERN_SIZE, (uint8_t*)MYSPACE_PATTERN, APP_ID_MYSPACE }, + { SINGLE, 0, 0, APP_ID_GMAIL, + GMAIL_PATTERN_SIZE, (uint8_t*)GMAIL_PATTERN, APP_ID_GMAIL,}, + { SINGLE, 0, 0, APP_ID_GMAIL, + GMAIL_PATTERN2_SIZE, (uint8_t*)GMAIL_PATTERN2, APP_ID_GMAIL,}, + { SINGLE, 0, 0, APP_ID_AOL_EMAIL, + AOL_PATTERN_SIZE, (uint8_t*)AOL_PATTERN, APP_ID_AOL_EMAIL,}, + { SINGLE, 0, 0, APP_ID_MICROSOFT_UPDATE, + MSUP_PATTERN_SIZE, (uint8_t*)MSUP_PATTERN, APP_ID_MICROSOFT_UPDATE,}, + { SINGLE, 0, 0, APP_ID_MICROSOFT_UPDATE, + MSUP_PATTERN2_SIZE, (uint8_t*)MSUP_PATTERN2, APP_ID_MICROSOFT_UPDATE,}, + { SINGLE, 0, 0, APP_ID_YAHOOMAIL, + YAHOO_MAIL_PATTERN_SIZE, (uint8_t*)YAHOO_MAIL_PATTERN, APP_ID_YAHOOMAIL,}, + { SINGLE, 0, 0, APP_ID_YAHOO_TOOLBAR, + YAHOO_TB_PATTERN_SIZE, (uint8_t*)YAHOO_TB_PATTERN, APP_ID_YAHOO_TOOLBAR,}, + { SINGLE, 0, 0, APP_ID_ADOBE_UPDATE, + ADOBE_UP_PATTERN_SIZE, (uint8_t*)ADOBE_UP_PATTERN, APP_ID_ADOBE_UPDATE,}, + { SINGLE, 0, 0, APP_ID_HOTMAIL, + HOTMAIL_PATTERN1_SIZE, (uint8_t*)HOTMAIL_PATTERN1, APP_ID_HOTMAIL,}, + { SINGLE, 0, 0, APP_ID_HOTMAIL, + HOTMAIL_PATTERN2_SIZE, (uint8_t*)HOTMAIL_PATTERN2, APP_ID_HOTMAIL,}, + { SINGLE, 0, 0, APP_ID_GOOGLE_TOOLBAR, + GOOGLE_TB_PATTERN_SIZE, (uint8_t*)GOOGLE_TB_PATTERN, APP_ID_GOOGLE_TOOLBAR,}, +}; + +static DetectorHTTPPattern client_agent_patterns[] = +{ + { USER_AGENT_HEADER, 0, FAKE_VERSION_APP_ID, 0, + VERSION_PATTERN_SIZE, (uint8_t*)VERSION_PATTERN, FAKE_VERSION_APP_ID,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_INTERNET_EXPLORER, 0, + sizeof(MSIE_PATTERN)-1, (uint8_t*)MSIE_PATTERN, APP_ID_INTERNET_EXPLORER,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_KONQUEROR, 0, + sizeof(KONQUEROR_PATTERN)-1, (uint8_t*)KONQUEROR_PATTERN, APP_ID_KONQUEROR,}, + { USER_AGENT_HEADER, APP_ID_SKYPE_AUTH, APP_ID_SKYPE, 0, + sizeof(SKYPE_PATTERN)-1, (uint8_t*)SKYPE_PATTERN, APP_ID_SKYPE,}, + { USER_AGENT_HEADER, APP_ID_BITTORRENT, APP_ID_BITTORRENT, 0, + sizeof(BITTORRENT_PATTERN)-1, (uint8_t*)BITTORRENT_PATTERN, APP_ID_BITTORRENT,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_FIREFOX, 0, + sizeof(FIREFOX_PATTERN)-1, (uint8_t*)FIREFOX_PATTERN, APP_ID_FIREFOX,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_WGET, 0, + sizeof(WGET_PATTERN)-1, (uint8_t*)WGET_PATTERN, APP_ID_WGET,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_CURL, 0, + sizeof(CURL_PATTERN)-1, (uint8_t*)CURL_PATTERN, APP_ID_CURL,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_GOOGLE_DESKTOP, 0, + sizeof(GOOGLE_DESKTOP_PATTERN)-1, (uint8_t*)GOOGLE_DESKTOP_PATTERN, APP_ID_GOOGLE_DESKTOP,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_PICASA, 0, + sizeof(PICASA_PATTERN)-1, (uint8_t*)PICASA_PATTERN, APP_ID_PICASA,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_SAFARI, 0, + sizeof(SAFARI_PATTERN)-1, (uint8_t*)SAFARI_PATTERN, APP_ID_SAFARI,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_CHROME, 0, + sizeof(CHROME_PATTERN)-1, (uint8_t*)CHROME_PATTERN, APP_ID_CHROME,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_SAFARI_MOBILE_DUMMY, 0, + sizeof(MOBILE_PATTERN)-1, (uint8_t*)MOBILE_PATTERN, APP_ID_SAFARI_MOBILE_DUMMY,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_BLACKBERRY_BROWSER, 0, + sizeof(BLACKBERRY_PATTERN)-1, (uint8_t*)BLACKBERRY_PATTERN, APP_ID_BLACKBERRY_BROWSER,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_ANDROID_BROWSER, 0, + sizeof(ANDROID_PATTERN)-1, (uint8_t*)ANDROID_PATTERN, APP_ID_ANDROID_BROWSER,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_WINDOWS_MEDIA_PLAYER, 0, + sizeof(MEDIAPLAYER_PATTERN)-1, (uint8_t*)MEDIAPLAYER_PATTERN, APP_ID_WINDOWS_MEDIA_PLAYER,}, + { USER_AGENT_HEADER, APP_ID_HTTP, APP_ID_APPLE_EMAIL, 0, + sizeof(APPLE_EMAIL_PATTERN)-1, (uint8_t*)APPLE_EMAIL_PATTERN, APP_ID_APPLE_EMAIL,}, +}; + +struct HeaderPattern +{ + int id; + uint8_t* data; + unsigned length; +}; + +static const char HTTP_HEADER_CONTENT_TYPE[] = "Content-Type: "; +static const char HTTP_HEADER_SERVER_FIELD[] = "Server: "; +static const char HTTP_HEADER_X_WORKING_WITH[] = "X-Working-With: "; +static const char HTTP_HEADER_CRLF[] = "\r"; +static const char HTTP_HEADER_LF[] = "\n"; + +#define HTTP_HEADER_CONTENT_TYPE_SIZE (sizeof(HTTP_HEADER_CONTENT_TYPE)-1) +#define HTTP_HEADER_SERVER_FIELD_SIZE (sizeof(HTTP_HEADER_SERVER_FIELD)-1) +#define HTTP_HEADER_X_WORKING_WITH_SIZE (sizeof(HTTP_HEADER_X_WORKING_WITH)-1) +#define HTTP_HEADER_CRLF_SIZE (sizeof(HTTP_HEADER_CRLF)-1) +#define HTTP_HEADER_LF_SIZE (sizeof(HTTP_HEADER_LF)-1) + +static HeaderPattern header_patterns[] = +{ + { HTTP_ID_CONTENT_TYPE, (uint8_t*)HTTP_HEADER_CONTENT_TYPE,HTTP_HEADER_CONTENT_TYPE_SIZE }, + { HTTP_ID_SERVER, (uint8_t*)HTTP_HEADER_SERVER_FIELD,HTTP_HEADER_SERVER_FIELD_SIZE }, + { HTTP_ID_COPY, (uint8_t*)HTTP_COPY, HTTP_COPY_SIZE }, + { HTTP_ID_DELETE, (uint8_t*)HTTP_DELETE, HTTP_DELETE_SIZE }, + { HTTP_ID_GET, (uint8_t*)HTTP_GET, HTTP_GET_SIZE }, + { HTTP_ID_HEAD, (uint8_t*)HTTP_HEAD, HTTP_HEAD_SIZE }, + { HTTP_ID_OPTIONS, (uint8_t*)HTTP_OPTIONS, HTTP_OPTIONS_SIZE }, + { HTTP_ID_PROPFIND, (uint8_t*)HTTP_PROPFIND, HTTP_PROPFIND_SIZE }, + { HTTP_ID_PROPPATCH, (uint8_t*)HTTP_PROPPATCH, HTTP_PROPPATCH_SIZE }, + { HTTP_ID_MKCOL, (uint8_t*)HTTP_MKCOL, HTTP_MKCOL_SIZE }, + { HTTP_ID_LOCK, (uint8_t*)HTTP_LOCK, HTTP_LOCK_SIZE }, + { HTTP_ID_MOVE, (uint8_t*)HTTP_MOVE, HTTP_MOVE_SIZE }, + { HTTP_ID_PUT, (uint8_t*)HTTP_PUT, HTTP_PUT_SIZE }, + { HTTP_ID_TRACE, (uint8_t*)HTTP_TRACE, HTTP_TRACE_SIZE }, + { HTTP_ID_UNLOCK, (uint8_t*)HTTP_UNLOCK, HTTP_UNLOCK_SIZE }, + { HTTP_ID_X_WORKING_WITH, (uint8_t*)HTTP_HEADER_X_WORKING_WITH, + HTTP_HEADER_X_WORKING_WITH_SIZE }, + { HTTP_ID_LEN, (uint8_t*)HTTP_HEADER_CRLF, HTTP_HEADER_CRLF_SIZE }, + { HTTP_ID_LEN, (uint8_t*)HTTP_HEADER_LF, HTTP_HEADER_LF_SIZE } +}; + +static int content_pattern_match(void* id, void*, int index, void* data, void*) +{ + MatchedPatterns* cm; + MatchedPatterns** matches = (MatchedPatterns**)data; + DetectorHTTPPattern* target = (DetectorHTTPPattern*)id; + + cm = (MatchedPatterns*)snort_calloc(sizeof(MatchedPatterns)); + cm->mpattern = target; + cm->index = index; + cm->next = *matches; + *matches = cm; + + return 0; +} + +static int chp_pattern_match(void* id, void*, int index, void* data, void*) +{ + MatchedCHPAction* new_match; + MatchedCHPAction* current_search; + MatchedCHPAction* prev_search; + MatchedCHPAction** matches = (MatchedCHPAction**)data; + CHPAction* target = (CHPAction*)id; + + new_match = (MatchedCHPAction*)snort_calloc(sizeof(MatchedCHPAction)); + new_match->mpattern = target; + new_match->index = index; + + // preserving order is required: sort by appIdInstance, then by precedence + for (current_search = *matches, prev_search = nullptr; + nullptr != current_search; + prev_search = current_search, current_search = current_search->next) + { + CHPAction* match_data = current_search->mpattern; + if (target->appIdInstance < match_data->appIdInstance) + break; + if (target->appIdInstance == match_data->appIdInstance) + { + if (target->precedence < match_data->precedence) + break; + } + } + + if (prev_search) + { + new_match->next = prev_search->next; + prev_search->next = new_match; + } + else + { + // insert at head of list. + new_match->next = *matches; + *matches = new_match; + } + return 0; +} + +#define CHP_TALLY_GROWTH_FACTOR 10 +static inline void chp_add_candidate_to_tally(CHPMatchTally** ppTally, CHPApp* chpapp) +{ + int index; + CHPMatchTally* pTally = *ppTally; + if (!pTally) + { + pTally = (CHPMatchTally*)snort_calloc(sizeof(CHPMatchTally) + + ( CHP_TALLY_GROWTH_FACTOR * sizeof(CHPMatchCandidate))); + pTally->in_use_elements = 0; + pTally->allocated_elements = CHP_TALLY_GROWTH_FACTOR; + *ppTally = pTally; + } + for (index=0; index < pTally->in_use_elements; index++ ) + { + if (chpapp == pTally->item[index].chpapp) + { + pTally->item[index].key_pattern_countdown--; + return; + } + } + // Not found. Add to array + if (pTally->in_use_elements == pTally->allocated_elements) + { + int newCount = pTally->allocated_elements + CHP_TALLY_GROWTH_FACTOR; + CHPMatchTally* pNewTally = (CHPMatchTally*)realloc(pTally, sizeof(CHPMatchTally)+newCount* + sizeof(CHPMatchCandidate)); + if (pNewTally) + { + *ppTally = pTally = pNewTally; + pTally->allocated_elements = newCount; + } + else + return; // failed to allocate a bigger chunk + } + // index == pTally->in_use_elements + pTally->in_use_elements++; + pTally->item[index].chpapp = chpapp; + pTally->item[index].key_pattern_length_sum = chpapp->key_pattern_length_sum; + pTally->item[index].key_pattern_countdown = chpapp->key_pattern_count - 1; // the count would + // have included + // this find. +} + +struct CHPTallyAndActions +{ + CHPMatchTally* pTally; + MatchedCHPAction* matches; +}; + +// In addition to creating the linked list of matching actions this function will +// create the CHPMatchTally needed to find the longest matching pattern. +static int chp_key_pattern_match(void* id, void*, int index, void* data, void*) +{ + CHPTallyAndActions* pTallyAndActions = (CHPTallyAndActions*)data; + CHPAction* target = (CHPAction*)id; + + if (target->key_pattern) + { + // We have a match from a key pattern. We need to have it's parent chpapp represented in + // the tally. + // If the chpapp has never been seen then add an item to the tally's array + // else decrement the count of expected key_patterns until zero so that we know when we + // have them all. + chp_add_candidate_to_tally(&pTallyAndActions->pTally, target->chpapp); + } + return chp_pattern_match(id, nullptr, index, &pTallyAndActions->matches, nullptr); +} + +static int http_pattern_match(void* id, void*, int index, void* data, void*) +{ + MatchedPatterns* cm = nullptr; + MatchedPatterns** tmp; + MatchedPatterns** matches = (MatchedPatterns**)data; + DetectorHTTPPattern* target = (DetectorHTTPPattern*)id; + + /* make sure we haven't already seen this pattern */ + for (tmp = matches; + *tmp; + tmp = &(*tmp)->next) + { + cm = *tmp; + } + + if (!*tmp) + { + cm = (MatchedPatterns*)snort_calloc(sizeof(MatchedPatterns)); + cm->mpattern = target; + cm->index = index; + cm->next = nullptr; + *tmp = cm; + } + + /* if its one of the host patterns, return after first match*/ + if (cm->mpattern->seq == SINGLE) + return 1; + else + return 0; +} + +static SearchTool* processPatterns(DetectorHTTPPattern* patternList, + size_t patternListCount, size_t*, HTTPListElement* luaPatternList) +{ + SearchTool* patternMatcher = new SearchTool("ac_full"); + + for (uint32_t i = 0; i < patternListCount; i++) + patternMatcher->add(patternList[i].pattern, patternList[i].pattern_size, + &patternList[i], false); + + /* Add patterns from Lua API */ + HTTPListElement* element; + for (element = luaPatternList; element != 0; element = element->next) + patternMatcher->add(element->detectorHTTPPattern.pattern, + element->detectorHTTPPattern.pattern_size, &element->detectorHTTPPattern, false); + + patternMatcher->prep(); + return patternMatcher; +} + +static int processHostPatterns( + DetectorHTTPPattern* patternList, + size_t patternListCount, + HTTPListElement* luaPatternList, + DetectorAppUrlList* urlPatternList, + DetectorAppUrlList* RTMPUrlList, + DetectorHttpConfig* pHttpConfig + ) +{ + HTTPListElement* element; + DetectorAppUrlPattern* appUrlPattern; + + if (!pHttpConfig->hosUrlMatcher) + pHttpConfig->hosUrlMatcher = mlmpCreate(); + + if (!pHttpConfig->RTMPHosUrlMatcher) + pHttpConfig->RTMPHosUrlMatcher = mlmpCreate(); + + for (uint32_t i = 0; i < patternListCount; i++) + { + if (addMlmpPattern(pHttpConfig->hosUrlMatcher, &pHttpConfig->hosUrlPatternsList, + patternList[i].pattern, patternList[i].pattern_size, + nullptr, 0, nullptr, 0, patternList[i].appId, patternList[i].payload, + patternList[i].service_id, + patternList[i].client_app, patternList[i].seq) < 0) + return -1; + } + + for (element = luaPatternList; element != 0; element = element->next) + { + if (addMlmpPattern(pHttpConfig->hosUrlMatcher, &pHttpConfig->hosUrlPatternsList, + element->detectorHTTPPattern.pattern, element->detectorHTTPPattern.pattern_size, + nullptr, 0, nullptr, 0, element->detectorHTTPPattern.appId, + element->detectorHTTPPattern.payload, element->detectorHTTPPattern.service_id, + element->detectorHTTPPattern.client_app, element->detectorHTTPPattern.seq) < 0) + return -1; + } + + for (uint32_t i = 0; i < RTMPUrlList->usedCount; i++) + { + appUrlPattern = RTMPUrlList->urlPattern[i]; + if (addMlmpPattern(pHttpConfig->RTMPHosUrlMatcher, &pHttpConfig->hosUrlPatternsList, + appUrlPattern->patterns.host.pattern, appUrlPattern->patterns.host.patternSize, + appUrlPattern->patterns.path.pattern, appUrlPattern->patterns.path.patternSize, + appUrlPattern->userData.query.pattern, appUrlPattern->userData.query.patternSize, + appUrlPattern->userData.appId, appUrlPattern->userData.payload, + appUrlPattern->userData.service_id, appUrlPattern->userData.client_app, + SINGLE) < 0) + return -1; + } + + for (uint32_t i = 0; i < urlPatternList->usedCount; i++) + { + appUrlPattern = urlPatternList->urlPattern[i]; + if (addMlmpPattern(pHttpConfig->hosUrlMatcher, &pHttpConfig->hosUrlPatternsList, + appUrlPattern->patterns.host.pattern, appUrlPattern->patterns.host.patternSize, + appUrlPattern->patterns.path.pattern, appUrlPattern->patterns.path.patternSize, + appUrlPattern->userData.query.pattern, appUrlPattern->userData.query.patternSize, + appUrlPattern->userData.appId, appUrlPattern->userData.payload, + appUrlPattern->userData.service_id, appUrlPattern->userData.client_app, + SINGLE) < 0) + return -1; + } + + mlmpProcessPatterns(pHttpConfig->hosUrlMatcher); + mlmpProcessPatterns(pHttpConfig->RTMPHosUrlMatcher); + return 0; +} + +static SearchTool* processContentTypePatterns(DetectorHTTPPattern* patternList, + size_t patternListCount, HTTPListElement* luaPatternList, size_t*) +{ + SearchTool* patternMatcher = new SearchTool("ac_full"); + HTTPListElement* element; + + for (uint32_t i = 0; i < patternListCount; i++) + { + patternMatcher->add(patternList[i].pattern, + patternList[i].pattern_size, + &patternList[i], + false); + } + + /* Add patterns from Lua API */ + for (element = luaPatternList; element; element = element->next) + { + patternMatcher->add(element->detectorHTTPPattern.pattern, + element->detectorHTTPPattern.pattern_size, + &element->detectorHTTPPattern, + false); + } + + patternMatcher->prep(); + + return patternMatcher; +} + +static int processCHPList(CHPListElement* chplist, DetectorHttpConfig* pHttpConfig) +{ + CHPListElement* chpe; + + for (size_t i = 0; i < sizeof(pHttpConfig->chp_matchers)/sizeof(pHttpConfig->chp_matchers[0]); + i++) + { + pHttpConfig->chp_matchers[i] = new SearchTool("ac_full"); + if (!pHttpConfig->chp_matchers[i]) + return 0; + } + + for (chpe = chplist; chpe; chpe = chpe->next) + { + pHttpConfig->chp_matchers[chpe->chp_action.ptype]->add(chpe->chp_action.pattern, + chpe->chp_action.psize, + &chpe->chp_action, + true); + } + + for (size_t i = 0; i < sizeof(pHttpConfig->chp_matchers)/sizeof(pHttpConfig->chp_matchers[0]); + i++) + pHttpConfig->chp_matchers[i]->prep(); + + return 1; +} + +static SearchTool* registerHeaderPatterns( + HeaderPattern* patternList, + size_t patternListCount) +{ + SearchTool* patternMatcher = new SearchTool("ac_full"); + + for (uint32_t i = 0; i < patternListCount; i++) + patternMatcher->add(patternList[i].data, patternList[i].length, &patternList[i], true); + + patternMatcher->prep(); + + return patternMatcher; +} + +int http_detector_finalize(AppIdConfig* pConfig) +{ + size_t upc = 0; + size_t apc = 0; + size_t ctc = 0; + size_t vpc = 0; + + DetectorHttpConfig* pHttpConfig = &pConfig->detectorHttpConfig; + HttpPatternLists* patternLists = &pConfig->httpPatternLists; + uint32_t numPatterns; + + /*create via pattern matcher */ + numPatterns = sizeof(via_http_detector_patterns)/sizeof(*via_http_detector_patterns); + pHttpConfig->via_matcher = processPatterns(via_http_detector_patterns, numPatterns, &vpc, + nullptr); + if (!pHttpConfig->via_matcher) + return -1; + + /*create url pattern matcher */ + pHttpConfig->url_matcher = processPatterns(nullptr, 0, &upc, + patternLists->urlPatternList); + if (!pHttpConfig->url_matcher) + return -1; + + /*create client agent pattern matcher */ + numPatterns = sizeof(client_agent_patterns)/sizeof(*client_agent_patterns); + pHttpConfig->client_agent_matcher = processPatterns(client_agent_patterns,numPatterns, &apc, + patternLists->clientAgentPatternList); + if (!pHttpConfig->client_agent_matcher) + return -1; + + numPatterns = sizeof(header_patterns)/sizeof(*header_patterns); + pHttpConfig->header_matcher = registerHeaderPatterns(header_patterns,numPatterns); + if (!pHttpConfig->header_matcher) + return -1; + + numPatterns = sizeof(host_payload_http_detector_patterns)/ + sizeof(*host_payload_http_detector_patterns); + if (processHostPatterns(host_payload_http_detector_patterns, numPatterns, + patternLists->hostPayloadPatternList, &patternLists->appUrlList, + &patternLists->RTMPUrlList, pHttpConfig) < 0) + return -1; + + numPatterns = sizeof(content_type_patterns)/sizeof(*content_type_patterns); + pHttpConfig->content_type_matcher = processContentTypePatterns(content_type_patterns, + numPatterns, patternLists->contentTypePatternList, &ctc); + if (!pHttpConfig->content_type_matcher) + return -1; + + if (!processCHPList(patternLists->chpList, pHttpConfig)) + return -1; + + pHttpConfig->chp_user_agent_matcher = pHttpConfig->chp_matchers[AGENT_PT]; + pHttpConfig->chp_host_matcher = pHttpConfig->chp_matchers[HOST_PT]; + pHttpConfig->chp_referer_matcher = pHttpConfig->chp_matchers[REFERER_PT]; + pHttpConfig->chp_uri_matcher = pHttpConfig->chp_matchers[URI_PT]; + pHttpConfig->chp_cookie_matcher = pHttpConfig->chp_matchers[COOKIE_PT]; + pHttpConfig->chp_req_body_matcher = pHttpConfig->chp_matchers[REQ_BODY_PT]; + pHttpConfig->chp_content_type_matcher = pHttpConfig->chp_matchers[CONTENT_TYPE_PT]; + pHttpConfig->chp_location_matcher = pHttpConfig->chp_matchers[LOCATION_PT]; + pHttpConfig->chp_body_matcher = pHttpConfig->chp_matchers[BODY_PT]; + + return 0; +} + +void http_detector_clean(DetectorHttpConfig* pHttpConfig) +{ + if (pHttpConfig->via_matcher) + { + delete(pHttpConfig->via_matcher); + pHttpConfig->via_matcher = nullptr; + } + if (pHttpConfig->url_matcher) + { + delete(pHttpConfig->url_matcher); + pHttpConfig->url_matcher = nullptr; + } + if (pHttpConfig->client_agent_matcher) + { + delete(pHttpConfig->client_agent_matcher); + pHttpConfig->client_agent_matcher = nullptr; + } + if (pHttpConfig->header_matcher) + { + delete(pHttpConfig->header_matcher); + pHttpConfig->header_matcher = nullptr; + } + if (pHttpConfig->content_type_matcher) + { + delete(pHttpConfig->content_type_matcher); + pHttpConfig->content_type_matcher = nullptr; + } + if (pHttpConfig->chp_user_agent_matcher) + { + delete(pHttpConfig->chp_user_agent_matcher); + pHttpConfig->chp_user_agent_matcher = nullptr; + } + if (pHttpConfig->chp_host_matcher) + { + delete(pHttpConfig->chp_host_matcher); + pHttpConfig->chp_host_matcher = nullptr; + } + if (pHttpConfig->chp_uri_matcher) + { + delete(pHttpConfig->chp_uri_matcher); + pHttpConfig->chp_uri_matcher = nullptr; + } + if (pHttpConfig->chp_cookie_matcher) + { + delete(pHttpConfig->chp_cookie_matcher); + pHttpConfig->chp_cookie_matcher = nullptr; + } + if (pHttpConfig->chp_content_type_matcher) + { + delete(pHttpConfig->chp_content_type_matcher); + pHttpConfig->chp_content_type_matcher = nullptr; + } + if (pHttpConfig->chp_location_matcher) + { + delete(pHttpConfig->chp_location_matcher); + pHttpConfig->chp_location_matcher = nullptr; + } + if (pHttpConfig->chp_body_matcher) + { + delete(pHttpConfig->chp_body_matcher); + pHttpConfig->chp_body_matcher = nullptr; + } + if (pHttpConfig->chp_referer_matcher) + { + delete(pHttpConfig->chp_referer_matcher); + pHttpConfig->chp_referer_matcher = nullptr; + } + + destroyHosUrlMatcher(&pHttpConfig->hosUrlMatcher); + destroyHosUrlMatcher(&pHttpConfig->RTMPHosUrlMatcher); + destroyHosUrlPatternList(&pHttpConfig->hosUrlPatternsList); +} + +static inline void FreeMatchStructures(MatchedPatterns* mp) +{ + MatchedPatterns* tmp; + + while (mp) + { + tmp = mp; + mp = mp->next; + snort_free(tmp); + } +} + +static void rewriteCHP(const char* buf, int bs, int start, + int psize, char* adata, char** outbuf, + int insert) +{ + int maxs, bufcont, as; + char* copyPtr; + + // special behavior for insert vs. rewrite + if (insert) + { + // we don't want to insert a string that is already present + if (!adata || strcasestr((const char*)buf, adata)) + return; + + start += psize; + bufcont = start; + as = strlen(adata); + maxs = bs+as; + } + else + { + if (adata) + { + // we also don't want to replace a string with an identical one. + if (!strncmp(buf+start,adata,psize)) + return; + + as = strlen(adata); + } + else + as = 0; + + bufcont = start+psize; + maxs = bs+(as-psize); + } + + *outbuf = copyPtr = (char*)snort_calloc(maxs + 1); + memcpy(copyPtr, buf, start); + copyPtr += start; + if (adata) + { + memcpy(copyPtr, adata, as); + copyPtr += as; + } + memcpy(copyPtr, buf+bufcont, bs-bufcont); +} + +static char* normalize_userid(char* user) +{ + int i, old_size; + //int new_size; + int percent_count = 0; + char a, b; + char* tmp_ret, * tmp_user; + + old_size = strlen(user); + + // find number of '%' + for (i = 0; i < old_size; i++) + { + if (*(user+i) == '%') + percent_count++; + } + if (0 == percent_count) + { + /* no change allows an early out */ + return user; + } + + /* Shrink user string in place */ + //new_size = old_size - percent_count*2; // FIXIT-L new_size was never used + tmp_ret = user; + tmp_user = user; + + while (*tmp_user) + { + if ((*tmp_user == '%') && + ((a = tmp_user[1]) && (b = tmp_user[2])) && + (isxdigit(a) && isxdigit(b))) + { + if (a >= 'a') + a -= 'a'-'A'; + + if (a >= 'A') + a -= ('A' - 10); + else + a -= '0'; + + if (b >= 'a') + b -= 'a'-'A'; + + if (b >= 'A') + b -= ('A' - 10); + else + b -= '0'; + + *tmp_ret++ = 16*a+b; + tmp_user+=3; + } + else + { + *tmp_ret++ = *tmp_user++; + } + } + *tmp_ret++ = '\0'; + + return user; +} + +static void extractCHP(char* buf, int bs, int start, + int psize, char* adata, + char** outbuf) +{ + char* begin = buf+start+psize; + char* end = nullptr; + char* tmp; + int i, as; + + if (adata) + as = strlen(adata); + else + as = 0; + + // find where the pattern ends so we can allocate a buffer + for (i = 0; i < as; i++) + { + tmp = strchr(begin, *(adata+i)); + if (tmp) + { + if ((!end) || (end && tmp < end)) + end = tmp; + } + } + if (!end) + { + if ((tmp = strchr(begin, 0x0d))) + { + end = tmp; + } + if ((tmp = strchr(begin, 0x0a))) + { + if ((!end) || (end && tmp < end)) + end = tmp; + } + } + + if (!end) + end = begin+bs; + + *outbuf = strndup(begin, end-begin); +} + +static uint32_t ddToIp(char* start, int size) +{ + uint32_t ret_addr = 0; + char* p; + int tmp = 0; + int octet = 3; + int digit_count = 1; + int done = 0; + + for (p = start; + p < start+size; + p++) + { + if (isdigit(*p)) + { + // if there are more than three digits in a row + if (digit_count > 3) + { + // this might be a spurrious digit after the IP address + if (octet == 0 && tmp && tmp <= 255) + { + ret_addr += tmp; + done = 1; + break; + } + else + return 0; + } + // otherwise, increase the value of tmp + tmp *= 10; + tmp += *p - '0'; + digit_count++; + } + // 0x2e is '.' + else if (*p == 0x2e) + { + // make sure we don't have random dots in there + if (!tmp) + return 0; + // otherwise, increase the return value + else + { + // octet value must fit in 8-bit boundary + if (tmp > 255) + return 0; + ret_addr += tmp < 255) + return 0; + if (!done) + ret_addr += tmp; + return htonl(ret_addr); +} + +static uint32_t ffSetIp(char* buf, int buf_size, int start, int psize) +{ + uint32_t ret_address; + + ret_address = ddToIp(buf+start+psize, buf_size); + + return ret_address; +} + +static uint16_t ffSetPort(char* buf, int buf_size, int start, int psize) +{ + uint16_t temp_port = 0; + uint16_t new_digit; + char* p; + int i; + + for (p = buf+start+psize, i = 1; p < buf+buf_size && isdigit(*p); p++, i++) + { + new_digit = *p -'0'; + // we don't want to try to put a value gt 65535 into a uint_16t + if ((i > 5) || (temp_port > 6535 || (temp_port == 6535 && new_digit > 5))) + return 0; + temp_port *= 10; + temp_port += *p - '0'; + } + + return temp_port; +} + +static IpProtocol ffSetProtocol(char* buf, int buf_size, int start, int psize) +{ + uint8_t temp_protocol = 0; + uint8_t new_digit; + char* p; + int i; + + for (p = buf+start+psize, i = 1; p < buf+buf_size && isdigit(*p); p++, i++) + { + new_digit = *p - '0'; + // we don't want to try to put a value gt 255 into a uint8_t + if ((i > 3) || (temp_protocol > 25 || (temp_protocol == 25 && new_digit > 5))) + return IpProtocol::PROTO_NOT_SET; + + temp_protocol *= 10; + temp_protocol += new_digit; + } + + return (IpProtocol)temp_protocol; +} + +static void fflowCreate(char* adata, fflow_info* fflow, + Packet* p, AppId target_appid) +{ + char* saddr_string = nullptr; + char* daddr_string = nullptr; + char* sport_string = nullptr; + char* dport_string = nullptr; + char* protocol_string = nullptr; + char* appid = nullptr; + const sfip_t* sip; + const sfip_t* dip; + int temp_port = 0; + char* brk; + + /* + The Action Data for this action is special + THE SEQUENCE MUST BE + source_address source_port dest_address dest_port protocol appid + DELIMITED BY A SPACE + if any value is '*', that means we should have already set this value with a previous action + */ + if (!(saddr_string = strtok_r(adata, " ", &brk))) + return; + if (!(sport_string = strtok_r(nullptr, " ", &brk))) + return; + if (!(daddr_string = strtok_r(nullptr, " ", &brk))) + return; + if (!(dport_string = strtok_r(nullptr, " ", &brk))) + return; + if (!(protocol_string = strtok_r(nullptr, " ", &brk))) + return; + if (!(appid = strtok_r(nullptr, " ", &brk))) + return; + + switch (*saddr_string) + { + case 'S': + sip = p->ptrs.ip_api.get_src(); + fflow->sip = sip->ip32[0]; + break; + case 'D': + sip = p->ptrs.ip_api.get_dst(); + fflow->sip = sip->ip32[0]; + break; + case '0': + sip = 0; + break; + case '*': + if (!fflow->sip) + return; + break; + default: + if ((!fflow->sip) && (!(fflow->sip = ddToIp(saddr_string, strlen(saddr_string))))) + return; + } + + switch (*sport_string) + { + case 'S': + if (strlen(sport_string) > 2) + { + if ((temp_port = strtol(sport_string+1, nullptr, 10))) + fflow->sport = p->ptrs.sp + temp_port; + else + return; + } + else + fflow->sport = p->ptrs.sp; + break; + case 'D': + if (strlen(sport_string) > 2) + { + if ((temp_port = strtol(sport_string+1, nullptr, 10))) + fflow->sport = p->ptrs.dp + temp_port; + else + return; + } + else + fflow->sport = p->ptrs.dp; + break; + case '0': + fflow->sport = 0; + break; + case '*': + if (!fflow->sport) + return; + break; + default: + if ((!fflow->sport) && (!(fflow->sport = ffSetPort(sport_string, strlen(sport_string), 0, + 0)))) + return; + } + + switch (*daddr_string) + { + case 'S': + dip = p->ptrs.ip_api.get_src(); + fflow->dip = dip->ip32[0]; + break; + case 'D': + dip = p->ptrs.ip_api.get_dst(); + fflow->dip = dip->ip32[0]; + break; + case '0': + fflow->dip = 0; + break; + case '*': + if (!fflow->dip) + return; + break; + default: + if ((!fflow->dip) && (!(fflow->dip = ddToIp(daddr_string, strlen(daddr_string))))) + return; + } + + switch (*dport_string) + { + case 'S': + if (strlen(dport_string) > 2) + { + if ((temp_port = strtol(dport_string+1, nullptr, 10))) + fflow->dport = p->ptrs.dp + temp_port; + else + return; + } + else + fflow->dport = p->ptrs.sp; + break; + case 'D': + if (strlen(dport_string) > 2) + { + if ((temp_port = strtol(dport_string+1, nullptr, 10))) + fflow->dport = p->ptrs.dp + temp_port; + else + return; + } + else + fflow->dport = p->ptrs.dp; + break; + case '0': + fflow->dport = 0; + break; + case '*': + if (!fflow->dport) + return; + break; + default: + if ((!fflow->dport) && (!(fflow->dport = ffSetPort(dport_string, strlen(dport_string), 0, + 0)))) + return; + } + + switch (*protocol_string) + { + case 'T': + fflow->protocol = IpProtocol::TCP; + break; + case 'U': + fflow->protocol = IpProtocol::UDP; + break; + case '0': + fflow->protocol = IpProtocol::PROTO_NOT_SET; + break; + case 'S': + case 'D': + fflow->protocol = p->is_tcp() ? IpProtocol::TCP : IpProtocol::UDP; + break; + case '*': + if ( fflow->protocol == IpProtocol::PROTO_NOT_SET ) + return; + break; + default: + if ( fflow->protocol == IpProtocol::PROTO_NOT_SET ) + { + fflow->protocol = ffSetProtocol( + protocol_string, strlen(protocol_string), 0, 0); + + if ( fflow->protocol == IpProtocol::PROTO_NOT_SET ) + return; + } + break; + } + + switch (*appid) + { + case '*': + fflow->appId = target_appid; + break; + default: + fflow->appId = strtol(appid, nullptr, 10); + } + + fflow->flow_prepared = 1; +} + +void finalizeFflow(fflow_info* fflow, unsigned app_type_flags, AppId target_appId, Packet* p) +{ + AppIdData* fp; + sfip_t saddr, daddr; + + sfip_set_raw(&saddr, &fflow->sip, AF_INET); + sfip_set_raw(&daddr, &fflow->dip, AF_INET); + + if (!(fp = AppIdEarlySessionCreate(nullptr, p, &saddr, fflow->sport, &daddr, fflow->dport, + fflow->protocol, target_appId, 0))) + return; + + if (app_type_flags & APP_TYPE_SERVICE) + { + fp->serviceAppId = target_appId; + fp->rnaServiceState = RNA_STATE_FINISHED; + fp->rnaClientState = RNA_STATE_FINISHED; + } + if (app_type_flags & APP_TYPE_CLIENT) + { + fp->ClientAppId = target_appId; + fp->rnaClientState = RNA_STATE_FINISHED; + } + if (app_type_flags & APP_TYPE_PAYLOAD) + { + fp->payloadAppId = target_appId; + } +} + +int scanKeyCHP(PatternType ptype, char* buf, int buf_size, CHPMatchTally** ppTally, + MatchedCHPAction** ppmatches, const DetectorHttpConfig* pHttpConfig) +{ + CHPTallyAndActions tallyAndActions; + tallyAndActions.pTally = *ppTally; + tallyAndActions.matches = *ppmatches; + + //FIXIT-H + pHttpConfig->chp_matchers[ptype]->find_all(buf, buf_size, &chp_key_pattern_match, + false, (void*)(&tallyAndActions)); + + *ppTally = tallyAndActions.pTally; + *ppmatches = tallyAndActions.matches; + return (int)(tallyAndActions.pTally != nullptr); +} + +AppId scanCHP(PatternType ptype, char* buf, int buf_size, MatchedCHPAction* mp, + char** version, char** user, char** new_field, + int* total_found, httpSession* hsession, Packet* p, const + DetectorHttpConfig* pHttpConfig) +{ + MatchedCHPAction* second_sweep_for_inserts = nullptr; + int do_not_further_modify_field = 0; + CHPAction* match = nullptr; + AppId ret = APP_ID_NONE; + MatchedCHPAction* tmp; + + if (ptype > MAX_KEY_PATTERN) + { + // There is no previous attempt to match generated by scanKeyCHP() + mp = nullptr; + + // FIXIT-H + pHttpConfig->chp_matchers[ptype]->find_all(buf, buf_size, &chp_pattern_match, + false, (void*)(&mp)); + } + if (!mp) + return APP_ID_NONE; + + if (pAppidActiveConfig->mod_config->disable_safe_search) + { + new_field = nullptr; + } + + for (tmp = mp; tmp; tmp = tmp->next) + { + match = (CHPAction*)tmp->mpattern; + if (match->appIdInstance > hsession->chp_candidate) + break; // because the list is sorted we know there are no more + else if (match->appIdInstance == hsession->chp_candidate) + { + switch (match->action) + { + default: + (*total_found)++; + break; + case ALTERNATE_APPID: // an "optional" action that doesn't count towards totals + case REWRITE_FIELD: // handled when the action completes successfully + case INSERT_FIELD: // handled when the action completes successfully + break; + } + if (!ret) + ret = hsession->chp_candidate; + } + else + continue; // keep looking + + switch (match->action) + { + case COLLECT_VERSION: + if (!*version) + extractCHP(buf, buf_size, tmp->index, match->psize, + match->action_data, version); + hsession->skip_simple_detect = true; + break; + case EXTRACT_USER: + if (!*user && !pAppidActiveConfig->mod_config->chp_userid_disabled) + { + extractCHP(buf, buf_size, tmp->index, match->psize, + match->action_data, user); + if (*user) + *user = normalize_userid(*user); + } + break; + case REWRITE_FIELD: + if (!do_not_further_modify_field && + nullptr != new_field && + nullptr == *new_field) + { + // The field supports rewrites, and a rewrite hasn't happened. + rewriteCHP(buf, buf_size, tmp->index, match->psize, + match->action_data, new_field, 0); + (*total_found)++; + do_not_further_modify_field = 1; + } + break; + case FUTURE_APPID_SESSION_SIP: + if (pAppidActiveConfig->mod_config->chp_fflow_disabled) + break; + if (!hsession->fflow) + hsession->fflow = (fflow_info*)snort_calloc(sizeof(fflow_info)); + if (!hsession->fflow->sip) + hsession->fflow->sip = ffSetIp(buf, buf_size, tmp->index, match->psize); + break; + + case FUTURE_APPID_SESSION_DIP: + if (pAppidActiveConfig->mod_config->chp_fflow_disabled) + break; + if (!hsession->fflow) + hsession->fflow = (fflow_info*)snort_calloc(sizeof(fflow_info)); + if (!hsession->fflow->dip) + hsession->fflow->dip = ffSetIp(buf, buf_size, tmp->index, match->psize); + break; + + case FUTURE_APPID_SESSION_SPORT: + if (pAppidActiveConfig->mod_config->chp_fflow_disabled) + break; + if (!hsession->fflow) + hsession->fflow = (fflow_info*)snort_calloc(sizeof(fflow_info)); + if (!hsession->fflow->sport) + hsession->fflow->sport = ffSetPort(buf, buf_size, tmp->index, match->psize); + break; + + case FUTURE_APPID_SESSION_DPORT: + if (pAppidActiveConfig->mod_config->chp_fflow_disabled) + break; + if (!hsession->fflow) + hsession->fflow = (fflow_info*)snort_calloc(sizeof(fflow_info)); + if (!hsession->fflow->dport) + hsession->fflow->dport = ffSetPort(buf, buf_size, tmp->index, match->psize); + break; + + case FUTURE_APPID_SESSION_PROTOCOL: + if (pAppidActiveConfig->mod_config->chp_fflow_disabled) + break; + if (!hsession->fflow) + hsession->fflow = (fflow_info*)snort_calloc(sizeof(fflow_info)); + if (hsession->fflow->protocol == IpProtocol::PROTO_NOT_SET) + hsession->fflow->protocol = ffSetProtocol(buf, buf_size, tmp->index, match->psize); + break; + + case FUTURE_APPID_SESSION_CREATE: + if (pAppidActiveConfig->mod_config->chp_fflow_disabled) + break; + if (!hsession->fflow) + hsession->fflow = (fflow_info*)snort_calloc(sizeof(fflow_info)); + fflowCreate(match->action_data, hsession->fflow, p, hsession->chp_candidate); + break; + + case INSERT_FIELD: + if (!do_not_further_modify_field && second_sweep_for_inserts == nullptr) + { + if (match->action_data) + { + // because this insert is the first one we have come across + // we only need to remember this ONE for later. + second_sweep_for_inserts = tmp; + } + else + { + // This is an attempt to "insert nothing"; call it a match + // The side effect is to set the do_not_further_modify_field to 1 (true) + + // Note that an attempt to "rewrite with identical string" + // is NOT equivalent to an "insert nothing" because of case- + // insensitive pattern matching + + do_not_further_modify_field = 1; + (*total_found)++; + } + } + break; + + case ALTERNATE_APPID: + hsession->chp_alt_candidate = strtol(match->action_data, nullptr, 10); + hsession->skip_simple_detect = true; + break; + + case HOLD_FLOW: + hsession->chp_hold_flow = 1; + break; + + case GET_OFFSETS_FROM_REBUILT: + hsession->get_offsets_from_rebuilt = 1; + hsession->chp_hold_flow = 1; + break; + + case SEARCH_UNSUPPORTED: + case NO_ACTION: + hsession->skip_simple_detect = true; + break; + } + } + // non-nullptr second_sweep_for_inserts indicates the insert action we will use. + if (!do_not_further_modify_field && second_sweep_for_inserts && + nullptr != new_field && + nullptr == *new_field) + { + // We will take the first INSERT_FIELD with an action string, + // which was decided with the setting of second_sweep_for_inserts. + rewriteCHP(buf, buf_size, second_sweep_for_inserts->index, + second_sweep_for_inserts->mpattern->psize, + second_sweep_for_inserts->mpattern->action_data, + new_field, 1); // insert + (*total_found)++; + } + + FreeMatchedCHPActions(mp); + return ret; +} + +static inline int optionallyReplaceWithStrdup(char** optionalStr, const char* strToDup) +{ + if (optionalStr) + { + if (*optionalStr) + snort_free(*optionalStr); + + *optionalStr = snort_strdup(strToDup); + } + return 0; +} + +void identifyUserAgent(const uint8_t* start, int size, AppId* serviceAppId, AppId* ClientAppId, + char** version, + const DetectorHttpConfig* pHttpConfig) +{ + int skypeDetect; + int mobileDetect; + int safariDetect; + unsigned int appleEmailDetect; + int firefox_detected, android_browser_detected; + int dominant_pattern_detected; + int longest_misc_match; + const uint8_t* end; + MatchedPatterns* mp = nullptr; + MatchedPatterns* tmp; + DetectorHTTPPattern* match; + uint8_t* buffPtr; + unsigned int i; + char temp_ver[MAX_VERSION_SIZE]; + temp_ver[0] = 0; + + // FIXIT-H + pHttpConfig->client_agent_matcher->find_all((const char*)start, size, &http_pattern_match, + false, (void*)&mp); + + if (mp) + { + end = start + size; + temp_ver[0] = 0; + skypeDetect = 0; + mobileDetect = 0; + safariDetect = 0; + firefox_detected = 0; + android_browser_detected = 0; + dominant_pattern_detected = 0; + longest_misc_match = 0; + i = 0; + *ClientAppId = APP_ID_NONE; + *serviceAppId = APP_ID_HTTP; + for (tmp = mp; tmp; tmp = tmp->next) + { + match = (DetectorHTTPPattern*)tmp->mpattern; + switch (match->client_app) + { + case APP_ID_INTERNET_EXPLORER: + case APP_ID_FIREFOX: + if (dominant_pattern_detected) + break; + buffPtr = (uint8_t*)start + tmp->index + match->pattern_size; + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != '/') + break; + buffPtr++; + while (i < MAX_VERSION_SIZE-1 && buffPtr < end) + { + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != ';' && *buffPtr != ')') + temp_ver[i++] = *buffPtr++; + else + break; + } + if (i == 0) + break; + + temp_ver[i] = 0; + + /*compatibility check */ + if (match->client_app == APP_ID_INTERNET_EXPLORER + && strstr((char*)buffPtr, "SLCC2")) + { + if ((MAX_VERSION_SIZE-i) >= (sizeof(COMPATIBLE_BROWSER_STRING) - 1)) + { + strcat(temp_ver, COMPATIBLE_BROWSER_STRING); + } + } + // Pick firefox over some things, but pick a misc app over Firefox. + if (match->client_app == APP_ID_FIREFOX) + firefox_detected = 1; + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + break; + + case APP_ID_CHROME: + if (dominant_pattern_detected) + break; + buffPtr = (uint8_t*)start + tmp->index + match->pattern_size; + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != '/') + break; + buffPtr++; + while (i < MAX_VERSION_SIZE-1 && buffPtr < end) + { + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != ';' && *buffPtr != ')') + temp_ver[i++] = *buffPtr++; + else + break; + } + if (i == 0) + break; + + dominant_pattern_detected = 1; + temp_ver[i] = 0; + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + break; + + case APP_ID_ANDROID_BROWSER: + if (dominant_pattern_detected) + break; + buffPtr = (uint8_t*)start + tmp->index + match->pattern_size; + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != '/') + break; + buffPtr++; + while (i < MAX_VERSION_SIZE-1 && buffPtr < end) + { + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != ';' && *buffPtr != ')') + temp_ver[i++] = *buffPtr++; + else + break; + } + if (i == 0) + break; + + temp_ver[i] = 0; + android_browser_detected = 1; + break; + + case APP_ID_KONQUEROR: + case APP_ID_CURL: + case APP_ID_PICASA: + if (dominant_pattern_detected) + break; + case APP_ID_WINDOWS_MEDIA_PLAYER: + case APP_ID_BITTORRENT: + buffPtr = (uint8_t*)start + tmp->index + match->pattern_size; + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != '/') + break; + buffPtr++; + while (i < MAX_VERSION_SIZE-1 && buffPtr < end) + { + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != ';' && *buffPtr != ')') + temp_ver[i++] = *buffPtr++; + else + break; + } + if (i == 0) + break; + + temp_ver[i] = 0; + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + goto done; + + case APP_ID_GOOGLE_DESKTOP: + buffPtr = (uint8_t*)start + tmp->index + match->pattern_size; + if (*buffPtr != ')') + { + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != '/') + break; + buffPtr++; + while (i < MAX_VERSION_SIZE-1 && buffPtr < end) + { + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != ';') + temp_ver[i++] = *buffPtr++; + else + break; + } + if (i == 0) + break; + temp_ver[i] = 0; + } + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + goto done; + + case APP_ID_SAFARI_MOBILE_DUMMY: + mobileDetect = 1; + break; + + case APP_ID_SAFARI: + if (dominant_pattern_detected) + break; + safariDetect = 1; + break; + + case APP_ID_APPLE_EMAIL: + appleEmailDetect = 1; + for (i = 0; i < 3 && appleEmailDetect; i++) + { + buffPtr = (uint8_t*)strstr((char*)start, (char*)APPLE_EMAIL_PATTERNS[i]); + appleEmailDetect = ((uint8_t*)buffPtr && (i != 0 || (i == 0 && buffPtr == + ((uint8_t*)start)))); + } + if (appleEmailDetect) + { + dominant_pattern_detected = !(buffPtr && strstr((char*)buffPtr, + SAFARI_PATTERN) != nullptr); + temp_ver[0] = 0; + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + } + i = 0; + break; + + case APP_ID_WGET: + buffPtr = (uint8_t*)start + tmp->index + match->pattern_size; + while (i < MAX_VERSION_SIZE - 1 && buffPtr < end) + { + temp_ver[i++] = *buffPtr++; + } + temp_ver[i] = 0; + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + goto done; + + case APP_ID_BLACKBERRY_BROWSER: + while ( start < end && *start != '/' ) + start++; + if (start >= end) + break; + start++; + while (i < MAX_VERSION_SIZE -1 && start < end) + { + if (*start != ' ' && *start != 0x09 && *start != ';') + temp_ver[i++] = *start++; + else + break; + } + if (i == 0) + break; + temp_ver[i] = 0; + + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + goto done; + + case APP_ID_SKYPE: + skypeDetect = 1; + break; + + case APP_ID_HTTP: + break; + + case APP_ID_OPERA: + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + break; + + case FAKE_VERSION_APP_ID: + if (temp_ver[0]) + { + temp_ver[0] = 0; + i = 0; + } + buffPtr = (uint8_t*)start + tmp->index + match->pattern_size; + if (*buffPtr == (uint8_t)'/') + { + buffPtr++; + while (i < MAX_VERSION_SIZE - 1 && buffPtr < end) + { + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != ';' && *buffPtr != + ')') + temp_ver[i++] = *buffPtr++; + else + break; + } + } + temp_ver[i] = 0; + break; + + default: + if (match->client_app) + { + if (match->pattern_size <= longest_misc_match) + break; + longest_misc_match = match->pattern_size; + i =0; + /* if we already collected temp_ver information after seeing 'Version', let's + use that*/ + buffPtr = (uint8_t*)start + tmp->index + match->pattern_size; + /* we may have to enter the pattern with the / in it. */ + if (*buffPtr == (uint8_t)'/' || *buffPtr == (uint8_t)' ') + buffPtr++; + if (buffPtr-1 > start && buffPtr < end && (*(buffPtr-1) == (uint8_t)'/' || + *(buffPtr-1) == (uint8_t)' ')) + { + while (i < MAX_VERSION_SIZE -1 && buffPtr < end) + { + if (*buffPtr != ' ' && *buffPtr != 0x09 && *buffPtr != ';' && + *buffPtr != ')') + temp_ver[i++] = *buffPtr++; + else + break; + } + temp_ver[i] = 0; + } + dominant_pattern_detected = 1; + *serviceAppId = APP_ID_HTTP; + *ClientAppId = match->client_app; + } + } + } + if (mobileDetect && safariDetect && !dominant_pattern_detected) + { + *serviceAppId = APP_ID_HTTP; + *ClientAppId = APP_ID_SAFARI_MOBILE; + } + else if (safariDetect && !dominant_pattern_detected) + { + *serviceAppId = APP_ID_HTTP; + *ClientAppId = APP_ID_SAFARI; + } + else if (firefox_detected && !dominant_pattern_detected) + { + *serviceAppId = APP_ID_HTTP; + *ClientAppId = APP_ID_FIREFOX; + } + else if (android_browser_detected && !dominant_pattern_detected) + { + *serviceAppId = APP_ID_HTTP; + *ClientAppId = APP_ID_ANDROID_BROWSER; + } + /* Better to choose Skype over any other ID */ + else if (skypeDetect) + { + *serviceAppId = APP_ID_SKYPE_AUTH; + *ClientAppId = APP_ID_SKYPE; + } + } + +done: + optionallyReplaceWithStrdup(version,temp_ver); + FreeMatchStructures(mp); +} + +int geAppidByViaPattern(const uint8_t* data, unsigned size, char** version, const + DetectorHttpConfig* pHttpConfig) +{ + unsigned i; + const uint8_t* data_ptr; + const uint8_t* end = data + size; + MatchedPatterns* mp = nullptr; + DetectorHTTPPattern* match = nullptr; + char temp_ver[MAX_VERSION_SIZE]; + + if (pHttpConfig->via_matcher) + { + // FIXIT-H + pHttpConfig->via_matcher->find_all((const char*)data, size, &http_pattern_match, + false, (void*)&mp); + } + + if (mp) + { + match = (DetectorHTTPPattern*)mp->mpattern; + switch (match->service_id) + { + case APP_ID_SQUID: + data_ptr = (uint8_t*)data + mp->index + match->pattern_size; + if (*data_ptr == '/') + { + data_ptr++; + for (i = 0; + data_ptr < end && i < (MAX_VERSION_SIZE-1) && *data_ptr != ')' && isprint( + *data_ptr); + data_ptr++) + { + temp_ver[i++] = (char)*data_ptr; + } + } + else + i = 0; + temp_ver[i] = 0; + optionallyReplaceWithStrdup(version,temp_ver); + FreeMatchStructures(mp); + return APP_ID_SQUID; + + default: + FreeMatchStructures(mp); + return APP_ID_NONE; + } + } + return APP_ID_NONE; +} + +#define HTTP_HEADER_WORKINGWITH_ASPROXY "ASProxy/" + +AppId scan_header_x_working_with(const uint8_t* data, uint32_t size, char** version) +{ + uint32_t i; + const uint8_t* end; + char temp_ver[MAX_VERSION_SIZE]; + + temp_ver[0] = 0; + + if (size >= (sizeof(HTTP_HEADER_WORKINGWITH_ASPROXY)-1) + && memcmp(data,HTTP_HEADER_WORKINGWITH_ASPROXY,sizeof(HTTP_HEADER_WORKINGWITH_ASPROXY)- + 1) == 0) + { + end = data+size; + data += sizeof(HTTP_HEADER_WORKINGWITH_ASPROXY)-1; + for (i = 0; + data < end && i < (MAX_VERSION_SIZE-1) && *data != ')' && isprint(*data); + data++) + { + temp_ver[i++] = (char)*data; + } + temp_ver[i] = 0; + optionallyReplaceWithStrdup(version,temp_ver); + return APP_ID_ASPROXY; + } + return APP_ID_NONE; +} + +AppId geAppidByContentType(const uint8_t* data, int size, const DetectorHttpConfig* pHttpConfig) +{ + MatchedPatterns* mp = nullptr; + DetectorHTTPPattern* match; + AppId payloadId; + + if (pHttpConfig->content_type_matcher) + { + // FIXIT-H + pHttpConfig->content_type_matcher->find_all((const char*)data, size, + &content_pattern_match, false, (void*)&mp); + } + + if (!mp) + return APP_ID_NONE; + + match = mp->mpattern; + payloadId = match->appId; + + FreeMatchStructures(mp); + + return payloadId; +} + +static int http_header_pattern_match(void* id, void*, int index, void* data, void*) +{ + HeaderMatchedPatterns* matches = (HeaderMatchedPatterns*)data; + HeaderPattern* target = (HeaderPattern*)id; + HTTPHeaderIndices* headers = matches->headers; + + if (matches->last_match >= 0) + { + headers[matches->last_match].end = index; + matches->last_match = -1; + } + + if (target->id < HTTP_ID_LEN) + { + if (index == 0) + { + goto store_index; + } + else if (index == matches->last_index_end) + { + /* This checks if the last match was \r or \n + It is still possible to have nefarious payloads + that have HTTP Headers in "" */ + goto store_index; + } + } + + goto done; + +store_index: + headers[target->id].start = index + target->length; + headers[target->id].end = 0; + matches->last_match = target->id; +done: + matches->last_index_end = index + target->length; + return 0; +} + +int getHTTPHeaderLocation(const uint8_t* data, unsigned size, HttpId id, int* start, int* end, + HeaderMatchedPatterns* hmp, + const DetectorHttpConfig* pHttpConfig) +{ + HTTPHeaderIndices* match; + + if (hmp->headers[id].start > 0) + { + *start = hmp->headers[id].start; + *end = hmp->headers[id].end; + return 1; + } + + if (hmp->searched) + return 0; + + if (pHttpConfig->header_matcher) + { + //FIXIT-H + pHttpConfig->header_matcher->find_all((const char*)data, size, + &http_header_pattern_match, false, (void*)hmp); + } + + hmp->searched = 1; + + /*Close out search space for last matched if needed */ + if (hmp->last_match > 0 && hmp->headers[hmp->last_match].end <= 0) + hmp->headers[hmp->last_match].end = size; + + match = &(hmp->headers[id]); + if (match->start > 0) + { + *start = match->start; + *end = match->end; + return 1; + } + + return 0; +} + +AppId getAppIdFromUrl(char* host, char* url, char** version, + char* referer, AppId* ClientAppId, AppId* serviceAppId, + AppId* payloadAppId, AppId* referredPayloadAppId, unsigned from_rtmp, + const DetectorHttpConfig* pHttpConfig) +{ + char* path; + char* referer_start; + char* temp_host = nullptr; + const char* referer_path = nullptr; + int host_len; + int referer_len = 0; + int referer_path_len = 0; + int path_len; + tMlmpPattern patterns[3]; + tMlpPattern query; + HosUrlDetectorPattern* data; + char* q; + int payload_found = 0; + int url_len; + static tMlmpTree* matcher; + +#define RTMP_MEDIA_STREAM_OFFSET 50000000 +#define URL_SCHEME_END_PATTERN "://" +#define URL_SCHEME_MAX_LEN (sizeof("https://")-1) + + matcher = (from_rtmp ? pHttpConfig->RTMPHosUrlMatcher : pHttpConfig->hosUrlMatcher); + + if (!host && !url) + return 0; + + if (url) + { + size_t scheme_len = strlen(url); + if (scheme_len > URL_SCHEME_MAX_LEN) + scheme_len = URL_SCHEME_MAX_LEN; // only need to search the first few bytes for + // scheme + char* url_offset = (char*)service_strstr((uint8_t*)url, scheme_len, + (uint8_t*)URL_SCHEME_END_PATTERN, sizeof(URL_SCHEME_END_PATTERN)-1); + if (url_offset) + url_offset += sizeof(URL_SCHEME_END_PATTERN)-1; + else + return 0; + + url = url_offset; + url_len = strlen(url); + } + else + { + url_len = 0; + } + + if (!host) + { + host_len = url_len; + + temp_host = host = snort_strdup(url); + host = strchr(host, '/'); + if (host != nullptr) + { + *host = '\0'; + } + host = temp_host; + } + host_len = strlen(host); + + if (url_len) + { + if (url_len < host_len) + { + snort_free(temp_host); + return 0; + } + path_len = url_len - host_len; + path = url + host_len; + } + else + { + path = nullptr; + path_len = 0; + } + + patterns[0].pattern = (uint8_t*)host; + patterns[0].patternSize = host_len; + patterns[1].pattern = (uint8_t*)path; + patterns[1].patternSize = path_len; + patterns[2].pattern = nullptr; + + data = (HosUrlDetectorPattern*)mlmpMatchPatternUrl(matcher, patterns); + + if (data) + { + payload_found = 1; + if (url) + { + q = strchr(url, '?'); + if (q != nullptr) + { + char temp_ver[MAX_VERSION_SIZE]; + temp_ver[0] = 0; + query.pattern = (uint8_t*)++q; + query.patternSize = strlen(q); + + matchQueryElements(&query, &data->query, temp_ver, MAX_VERSION_SIZE); + + if (temp_ver[0] != 0) + { + optionallyReplaceWithStrdup(version,temp_ver); + } + } + } + + *ClientAppId = data->client_id; + *serviceAppId = data->service_id; + *payloadAppId = data->payload_id; + } + + snort_free(temp_host); + + /* if referred_id feature id disabled, referer will be null */ + if (referer && (!payload_found || appInfoEntryFlagGet(data->payload_id, APPINFO_FLAG_REFERRED, + pAppidActiveConfig))) + { + referer_start = referer; + + char* referer_offset = (char*)service_strstr((uint8_t*)referer_start, URL_SCHEME_MAX_LEN, + (uint8_t*)URL_SCHEME_END_PATTERN, sizeof(URL_SCHEME_END_PATTERN)-1); + if (referer_offset) + { + referer_offset += sizeof(URL_SCHEME_END_PATTERN)-1; + } + else + return 0; + + referer_start = referer_offset; + referer_len = strlen(referer_start); + referer_path = strchr(referer_start, '/'); + + if (referer_path) + { + referer_path_len = strlen(referer_path); + referer_len -= referer_path_len; + } + else + { + referer_path = "/"; + referer_path_len = 1; + } + + if (referer_start && referer_len > 0) + { + data = nullptr; + patterns[0].pattern = (uint8_t*)referer_start; + patterns[0].patternSize = referer_len; + patterns[1].pattern = (uint8_t*)referer_path; + patterns[1].patternSize = referer_path_len; + patterns[2].pattern = nullptr; + data = (HosUrlDetectorPattern*)mlmpMatchPatternUrl(matcher, patterns); + if (data != nullptr) + { + if (payload_found) + *referredPayloadAppId = *payloadAppId; + else + payload_found = 1; + *payloadAppId = data->payload_id; + } + } + } + return payload_found; +} + +void getServerVendorVersion(const uint8_t* data, int len, char** version, char** vendor, + RNAServiceSubtype** subtype) +{ + const uint8_t* subname; + const uint8_t* subver; + int subname_len; + int subver_len; + const uint8_t* paren; + const uint8_t* ver; + const uint8_t* p; + const uint8_t* end = data + len; + RNAServiceSubtype* sub; + int vendor_len; + int version_len; + char* tmp; + + ver = (const uint8_t*)memchr(data, '/', len); + if (ver) + { + version_len = 0; + vendor_len = ver - data; + ver++; + subname = nullptr; + subname_len = 0; + subver = nullptr; + paren = nullptr; + for (p=ver; *p && p < end; p++) + { + if (*p == '(') + { + subname = nullptr; + paren = p; + } + else if (*p == ')') + { + subname = nullptr; + paren = nullptr; + } + /* some admins put tags in their http response lines. + the anchors will cause problems for adaptive profiles in snort, + so let's just get rid of them */ + else if (*p == '<') + break; + else if (!paren) + { + if (*p == ' ' || *p == '\t') + { + if (subname && subname_len > 0 && subver && *subname) + { + sub = (RNAServiceSubtype*)snort_calloc(sizeof(RNAServiceSubtype)); + tmp = (char*)snort_calloc(subname_len + 1); + memcpy(tmp, subname, subname_len); + tmp[subname_len] = 0; + sub->service = tmp; + subver_len = p - subver; + if (subver_len > 0 && *subver) + { + tmp = (char*)snort_calloc(subver_len + 1); + memcpy(tmp, subver, subver_len); + tmp[subver_len] = 0; + sub->version = tmp; + } + sub->next = *subtype; + *subtype = sub; + } + subname = p + 1; + subname_len = 0; + subver = nullptr; + } + else if (*p == '/' && subname) + { + if (version_len <= 0) + version_len = subname - ver - 1; + subname_len = p - subname; + subver = p + 1; + } + } + } + if (subname && subname_len > 0 && subver && *subname) + { + sub = (RNAServiceSubtype*)snort_calloc(sizeof(RNAServiceSubtype)); + tmp = (char*)snort_calloc(subname_len + 1); + memcpy(tmp, subname, subname_len); + tmp[subname_len] = 0; + sub->service = tmp; + + subver_len = p - subver; + if (subver_len > 0 && *subver) + { + tmp = (char*)snort_calloc(subver_len + 1); + memcpy(tmp, subver, subver_len); + tmp[subver_len] = 0; + sub->version = tmp; + } + sub->next = *subtype; + *subtype = sub; + } + + if (version_len <= 0) + version_len = p - ver; + if (version_len >= MAX_VERSION_SIZE) + version_len = MAX_VERSION_SIZE - 1; + *version = (char*)snort_calloc(sizeof(char) * (version_len + 1)); + memcpy(*version, ver, version_len); + *(*version + version_len) = '\0'; + } + else + { + vendor_len = len; + } + + if (vendor_len >= MAX_VERSION_SIZE) + vendor_len = MAX_VERSION_SIZE - 1; + *vendor = (char*)snort_calloc(sizeof(char) * (vendor_len + 1)); + memcpy(*vendor, data, vendor_len); + *(*vendor+vendor_len) = '\0'; +} + +int webdav_found(HeaderMatchedPatterns* hmp) +{ + // to check for webdav, look for one of the special methods + int found = 0; + if (hmp->headers[HTTP_ID_COPY].start > 0) + found = 1; + else if (hmp->headers[HTTP_ID_MOVE].start > 0) + found = 1; + else if (hmp->headers[HTTP_ID_LOCK].start > 0) + found = 1; + else if (hmp->headers[HTTP_ID_UNLOCK].start > 0) + found = 1; + else if (hmp->headers[HTTP_ID_MKCOL].start > 0) + found = 1; + else if (hmp->headers[HTTP_ID_PROPPATCH].start > 0) + found = 1; + else if (hmp->headers[HTTP_ID_PROPFIND].start > 0) + found = 1; + return found; +} + +// Start of HTTP/2 detection logic. +// +// This is intended to simply detect the presence of HTTP version 2 as a +// service protocol if it is seen (unencrypted) on non-std ports. That way, we +// can notify Snort for future reference. this covers the "with prior +// knowledge" case for HTTP/2 (i.e., the client knows the server supports +// HTTP/2 and jumps right in with the preface). + +static CLIENT_APP_RETCODE http_client_init(const IniClientAppAPI* const init_api, + SF_LIST* config); +static CLIENT_APP_RETCODE http_client_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, + const AppIdConfig* pConfig); + +static int http_service_init(const IniServiceAPI* const init_api); +static int http_service_validate(ServiceValidationArgs* args); + +static AppRegistryEntry appIdRegistry[] = +{ + { + APP_ID_HTTP, + 0 + } +}; + +static const char HTTP2_PREFACE[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; +#define HTTP2_PREFACE_LEN (sizeof(HTTP2_PREFACE)-1) +#define HTTP2_PREFACE_MAXPOS (sizeof(HTTP2_PREFACE)-2) + +struct Client_App_Pattern +{ + const uint8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern patterns[] = +{ + { + (const uint8_t*)HTTP2_PREFACE, + sizeof(HTTP2_PREFACE)-1, + 0, + APP_ID_HTTP + }, +}; + +SO_PUBLIC RNAClientAppModule http_client_mod = +{ + "HTTP", + IpProtocol::TCP, + &http_client_init, + nullptr, + &http_client_validate, + 1, + nullptr, + nullptr, + 0, + nullptr, + 0, + 0 +}; + +static RNAServiceValidationPort pp[] = +{ + { + nullptr, + 0, + IpProtocol::PROTO_NOT_SET, + 0 + } +}; + +static RNAServiceElement http_service_element = +{ + nullptr, + &http_service_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "http" +}; + +RNAServiceValidationModule http_service_mod = +{ + "HTTP", + &http_service_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static CLIENT_APP_RETCODE http_client_init(const IniClientAppAPI* const init_api, SF_LIST*) +{ + if (pAppidActiveConfig->mod_config->http2_detection_enabled) + { + for (unsigned i = 0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG, "registering patterns: %s: %d", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&http_client_validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + for (unsigned j = 0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG, "registering appId: %d\n", appIdRegistry[j].appId); + init_api->RegisterAppId(&http_client_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static CLIENT_APP_RETCODE http_client_validate(const uint8_t*, uint16_t, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector*, const AppIdConfig*) +{ + http_client_mod.api->add_app(flowp, APP_ID_HTTP, APP_ID_HTTP + GENERIC_APP_OFFSET, nullptr); + flowp->rnaClientState = RNA_STATE_FINISHED; + http_service_mod.api->add_service(flowp, pkt, dir, &http_service_element, + APP_ID_HTTP, nullptr, nullptr, nullptr); + flowp->rnaServiceState = RNA_STATE_FINISHED; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED | APPID_SESSION_SERVICE_DETECTED); + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + flowp->is_http2 = true; + + return CLIENT_APP_SUCCESS; +} + +static int http_service_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG, "registering appId: %d\n", appIdRegistry[i].appId); + init_api->RegisterAppId(&http_service_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int http_service_validate(ServiceValidationArgs*) +{ + return SERVICE_INPROCESS; +} + +// End of HTTP/2 detection logic. + diff --git a/src/network_inspectors/appid/detector_plugins/detector_http.h b/src/network_inspectors/appid/detector_plugins/detector_http.h new file mode 100644 index 000000000..7494ab34a --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_http.h @@ -0,0 +1,195 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_http.h author Sourcefire Inc. + +#ifndef DETECTOR_HTTP_H +#define DETECTOR_HTTP_H + +#include "detector_api.h" +#include "http_common.h" + +#include "utils/util.h" + +struct CHPAction; +struct CHPApp; +struct DetectorHttpConfig; +class AppIdConfig; + +#define MAX_VERSION_SIZE 64 + +enum HttpId +{ + /* Only store Content-Type, Server, User-Agent & Via headers now. */ + HTTP_ID_CONTENT_TYPE, + HTTP_ID_SERVER, + /* HTTP Responses */ + HTTP_ID_COPY, + HTTP_ID_DELETE, + HTTP_ID_GET, + HTTP_ID_HEAD, + HTTP_ID_OPTIONS, + HTTP_ID_PROPFIND, + HTTP_ID_PROPPATCH, + HTTP_ID_MKCOL, + HTTP_ID_LOCK, + HTTP_ID_MOVE, + HTTP_ID_PUT, + HTTP_ID_POST, + HTTP_ID_TRACE, + HTTP_ID_UNLOCK, + HTTP_ID_X_WORKING_WITH, + /* When others are needed, move value to be above HTTP_ID_LEN */ + HTTP_ID_ACCEPT, + HTTP_ID_ACCEPT_CHARSET, + HTTP_ID_ACCEPT_ENCODING, + HTTP_ID_ACCEPT_LANGUAGE, + HTTP_ID_ACCEPT_RANGES, + HTTP_ID_AGE, + HTTP_ID_ALLOW, + HTTP_ID_AUTHORIZATION, + HTTP_ID_CACHE_CONTROL, + HTTP_ID_CONNECTION, + HTTP_ID_COOKIE, + HTTP_ID_CONTENT_DISPOSITION, + HTTP_ID_CONTENT_ENCODING, + HTTP_ID_CONTENT_LANGUAGE, + HTTP_ID_CONTENT_LENGTH, + HTTP_ID_CONTENT_LOCATION, + HTTP_ID_CONTENT_MD5, + HTTP_ID_CONTENT_RANGE, + HTTP_ID_DATE, + HTTP_ID_ETAG, + HTTP_ID_EXPECT, + HTTP_ID_EXPIRES, + HTTP_ID_FROM, + HTTP_ID_HOST, + HTTP_ID_IF_MATCH, + HTTP_ID_IF_MODIFIED_SINCE, + HTTP_ID_IF_NONE_MATCH, + HTTP_ID_IF_RANGE, + HTTP_ID_IF_UNMODIFIED_SINCE, + HTTP_ID_LAST_MODIFIED, + HTTP_ID_LINK, + HTTP_ID_LOCATION, + HTTP_ID_MAX_FORWARDS, + HTTP_ID_P3P, + HTTP_ID_PRAGMA, + HTTP_ID_PROXY_AUTHORIZATION, + HTTP_ID_PROXY_AUTHENTICATE, + HTTP_ID_RANGE, + HTTP_ID_REFERER, + HTTP_ID_REFRESH, + HTTP_ID_RETRY_AFTER, + HTTP_ID_SET_COOKIE, + HTTP_ID_STRICT_TRANSPORT_SECURITY, + HTTP_ID_TE, + HTTP_ID_TRAILER, + HTTP_ID_TRANSFER_ENCODING, + HTTP_ID_UPGRADE, + HTTP_ID_USER_AGENT, + HTTP_ID_VARY, + HTTP_ID_VIA, + HTTP_ID_WARNING, + HTTP_ID_WWW_AUTHENTICATE, + HTTP_ID_LEN +}; + +struct HTTPHeaderIndices +{ + int start; + int end; +}; + +struct HeaderMatchedPatterns +{ + HTTPHeaderIndices headers[HTTP_ID_LEN]; + int last_match; + int last_index_end; + int searched; +}; + +struct MatchedCHPAction +{ + CHPAction* mpattern; + int index; + MatchedCHPAction* next; +}; + +// This is an array element for the dynamically growing tally below +struct CHPMatchCandidate +{ + CHPApp* chpapp; + int key_pattern_length_sum; + int key_pattern_countdown; +}; + +// This is a structure which will grow using realloc as needed to keep all candidates +struct CHPMatchTally +{ + int allocated_elements; + int in_use_elements; + CHPMatchCandidate item[1]; // FIXIT-H: Was item[0]; must account for this in allocation and freeing. +}; + +int geAppidByViaPattern(const u_int8_t* data, unsigned size, char** version, const + DetectorHttpConfig* pHttpConfig); +int getHTTPHeaderLocation(const uint8_t* data, unsigned size, HttpId id, int* start, int* end, + HeaderMatchedPatterns* hmp, + const DetectorHttpConfig* pHttpConfig); + +inline void FreeMatchedCHPActions(MatchedCHPAction* ma) +{ + MatchedCHPAction* tmp; + + while (ma) + { + tmp = ma; + ma = ma->next; + snort_free(tmp); + } +} + +int scanKeyCHP(PatternType ptype, char* buf, int buf_size, + CHPMatchTally** ppTally, MatchedCHPAction** ppmatches, const DetectorHttpConfig* pHttpConfig); + +AppId scanCHP(PatternType ptype, char* buf, int buf_size, MatchedCHPAction* mp, + char** version, char** user, char** new_field, + int* total_found, httpSession* hsession, Packet* p, const + DetectorHttpConfig* pHttpConfig); +AppId getAppIdFromUrl(char* host, char* url, char** version, + char* referer, AppId* ClientAppId, AppId* serviceAppId, + AppId* payloadAppId, AppId* referredPayloadAppId, unsigned from_rtmp, + const DetectorHttpConfig* pHttpConfig); +AppId geAppidByContentType(const uint8_t* data, int size, const + DetectorHttpConfig* pHttpConfig); +AppId scan_header_x_working_with(const uint8_t* data, uint32_t size, char** version); +void identifyUserAgent(const u_int8_t* start, int size, AppId* serviceAppId, AppId* ClientAppId, + char** version, + const DetectorHttpConfig* pHttpConfig); +void getServerVendorVersion(const uint8_t* data, int len, char** version, char** vendor, + RNAServiceSubtype** subtype); +int webdav_found(HeaderMatchedPatterns* hmp); +int http_detector_finalize(AppIdConfig* pConfig); +void http_detector_clean(DetectorHttpConfig* pHttpConfig); +void finalizeFflow(fflow_info* fflow, unsigned app_type_flags, AppId target_appId, + Packet* p); + +#endif + diff --git a/src/network_inspectors/appid/detector_plugins/detector_imap.cc b/src/network_inspectors/appid/detector_plugins/detector_imap.cc new file mode 100644 index 000000000..7cfccf773 --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_imap.cc @@ -0,0 +1,1054 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_imap.cc author Sourcefire Inc. + +#include +#include +#include +#include +#include +#include + +#include "detector_api.h" +#include "app_info_table.h" +#include "application_ids.h" +#include "appid_api.h" +#include "appid_config.h" +#include "client_plugins/client_app_api.h" +#include "service_plugins/service_api.h" + +#include "main/snort_debug.h" +#include "search_engines/search_tool.h" +#include "utils/util.h" + +/*#define DEBUG_IMAP_DETECTOR 1 */ + +static const unsigned IMAP_USER_NAME_MAX_LEN = 32; +static const unsigned IMAP_TAG_MAX_LEN = 6; +static const unsigned MIN_CMDS = 3; + +// static const char* const OK_LOGIN = " LOGIN Ok."; +static const char* const NO_LOGIN = " Login failed."; + +struct CLIENT_APP_CONFIG +{ + int enabled; +}; + +enum IMAPClientState +{ + IMAP_CLIENT_STATE_NON_AUTH, // IMAP - Non-Authenticated state + IMAP_CLIENT_STATE_AUTH, // IMAP - Authenticated state + IMAP_CLIENT_STATE_AUTHENTICATE_CMD, // IMAP - authentication-in-progress state + IMAP_CLIENT_STATE_STARTTLS_CMD, // IMAP - authentication-in-progress state (probable IMAPS) +}; + +struct ClientAppData +{ + IMAPClientState state; + unsigned count; + int detected; + int got_user; + int auth; + int set_flags; + char username[IMAP_USER_NAME_MAX_LEN+1]; + char imapCmdTag[IMAP_TAG_MAX_LEN+1]; +}; + +// FIXIT-L THREAD_LOCAL? +static CLIENT_APP_CONFIG ca_config; + +static CLIENT_APP_RETCODE init(const IniClientAppAPI* const init_api, SF_LIST* config); +static void clean(const CleanClientAppAPI* const clean_api); +static CLIENT_APP_RETCODE validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, Detector* userData, + const AppIdConfig* pConfig); + +static RNAClientAppModule client_app_mod = +{ + "IMAP", + IpProtocol::TCP, + &init, + &clean, + &validate, + 1, + nullptr, + nullptr, + 0, + nullptr, + 1, + 0 +}; + +struct Client_App_Pattern +{ + const uint8_t* pattern; + unsigned length; + int eoc; +}; + +static const uint8_t CAPA[] = "CAPABILITY\x00d\x00a"; +static const uint8_t CAPA2[] = "CAPABILITY\x00a"; +static const uint8_t NOOP[] = "NOOP\x00d\x00a"; +static const uint8_t NOOP2[] = "NOOP\x00a"; +static const uint8_t LOGOUT[] = "LOGOUT\x00d\x00a"; +static const uint8_t LOGOUT2[] = "LOGOUT\x00a"; +static const uint8_t AUTHENTICATE[] = "AUTHENTICATE "; +static const uint8_t LOGIN[] = "LOGIN "; +static const uint8_t SELECT[] = "SELECT "; +/*static const uint8_t EXAMINE[] = "EXAMINE "; */ +static const uint8_t CREATE[] = "CREATE "; +static const uint8_t DELETE[] = "DELETE "; +static const uint8_t RENAME[] = "RENAME "; +static const uint8_t SUBSCRIBE[] = "SUBSCRIBE "; +static const uint8_t UNSUBSCRIBE[] = "UNSUBSCRIBE "; +static const uint8_t LISTC[] = "LIST "; +static const uint8_t LSUB[] = "LSUB "; +static const uint8_t APPEND[] = "APPEND "; +static const uint8_t CHECK[] = "CHECK\x00d\x00a"; +static const uint8_t CHECK2[] = "CHECK\x00a"; +static const uint8_t CLOSE[] = "CLOSE\x00d\x00a"; +static const uint8_t CLOSE2[] = "CLOSE\x00a"; +static const uint8_t EXPUNGE[] = "EXPUNGE\x00d\x00a"; +static const uint8_t EXPUNGE2[] = "EXPUNGE\x00a"; +static const uint8_t SEARCH[] = "SEARCH "; +static const uint8_t FETCH[] = "FETCH "; +static const uint8_t PARTIAL[] = "PARTIAL "; +static const uint8_t STORE[] = "STORE "; +static const uint8_t COPY[] = "COPY "; +static const uint8_t UID[] = "UID "; +static const uint8_t STARTTLS[] = "STARTTLS\x00d\x00a"; +static const uint8_t STARTTLS2[] = "STARTTLS\x00a"; + +enum Client_App_Pattern_Index +{ + /* order MUST correspond to that in the array, patterns[], below */ + PATTERN_LOGIN, + PATTERN_AUTHENTICATE, + PATTERN_STARTTLS, + PATTERN_STARTTLS2, + PATTERN_IMAP_OTHER // always last +}; + +static Client_App_Pattern patterns[] = +{ + { LOGIN, sizeof(LOGIN)-1, 0 }, + { AUTHENTICATE, sizeof(AUTHENTICATE)-1, 0 }, + { STARTTLS, sizeof(STARTTLS)-1, 1 }, + { STARTTLS2, sizeof(STARTTLS2)-1, 1 }, + /* These are represented by index >= PATTERN_IMAP_OTHER */ + { CAPA, sizeof(CAPA)-1, 1 }, + { CAPA2, sizeof(CAPA2)-1, 1 }, + { NOOP, sizeof(NOOP)-1, 1 }, + { NOOP2, sizeof(NOOP2)-1, 1 }, + { LOGOUT, sizeof(LOGOUT)-1, 1 }, + { LOGOUT2, sizeof(LOGOUT2)-1, 1 }, + { SELECT, sizeof(SELECT)-1, 0 }, + { CREATE, sizeof(CREATE)-1, 0 }, + { DELETE, sizeof(DELETE)-1, 0 }, + { RENAME, sizeof(RENAME)-1, 0 }, + { SUBSCRIBE, sizeof(SUBSCRIBE)-1, 0 }, + { UNSUBSCRIBE, sizeof(UNSUBSCRIBE)-1, 0 }, + { LISTC, sizeof(LISTC)-1, 0 }, + { LSUB, sizeof(LSUB)-1, 0 }, + { APPEND, sizeof(APPEND)-1, 0 }, + { CHECK, sizeof(CHECK)-1, 1 }, + { CHECK2, sizeof(CHECK2)-1, 1 }, + { CLOSE, sizeof(CLOSE)-1, 1 }, + { CLOSE2, sizeof(CLOSE2)-1, 1 }, + { EXPUNGE, sizeof(EXPUNGE)-1, 1 }, + { EXPUNGE2, sizeof(EXPUNGE2)-1, 1 }, + { SEARCH, sizeof(SEARCH)-1, 0 }, + { FETCH, sizeof(FETCH)-1, 0 }, + { PARTIAL, sizeof(PARTIAL)-1, 0 }, + { STORE, sizeof(STORE)-1, 0 }, + { COPY, sizeof(COPY)-1, 0 }, + { UID, sizeof(UID)-1, 0 }, +}; + +// FIXIT-L THREAD_LOCAL? +static size_t longest_pattern; + +static const unsigned IMAP_PORT = 143; + +static const unsigned IMAP_COUNT_THRESHOLD = 2; + +static const char* const OK = "OK"; +static const char* const BAD = "BAD"; +static const char* const NO = "NO"; + +#define IMAP_FLAG_ALNUM 0x01 +#define IMAP_FLAG_FIRST_PACKET 0x02 +#define IMAP_FLAG_RESULT_OK 0x04 +#define IMAP_FLAG_RESULT_NO 0x08 +#define IMAP_FLAG_RESULT_BAD 0x10 +#define IMAP_FLAG_RESULT_ALL (IMAP_FLAG_RESULT_OK | IMAP_FLAG_RESULT_NO | IMAP_FLAG_RESULT_BAD) + +// static const unsigned IMAP_MAX_BANNER = 192; + +enum IMAPState +{ + IMAP_STATE_BEGIN, + IMAP_STATE_BANNER_SPACE, + IMAP_STATE_BANNER_OK, + IMAP_STATE_BANNER_WHITE_SPACE, + IMAP_STATE_BANNER, + IMAP_STATE_MID_LINE, + IMAP_STATE_MID_ALNUM, + IMAP_STATE_ALNUM_CODE, + IMAP_STATE_ALNUM_CODE_TERM, + IMAP_STATE_MID_OK, + IMAP_STATE_MID_NO, + IMAP_STATE_MID_BAD, + IMAP_STATE_MID_TERM, + IMAP_STATE_MID_OK_LOGIN, + IMAP_STATE_MID_NO_LOGIN, + IMAP_STATE_ALNUM_TAG +}; + +struct ServiceIMAPData +{ + IMAPState state; + unsigned pos; + unsigned flags; + unsigned count; + unsigned parens; + char tagValue[IMAP_TAG_MAX_LEN+1]; +#ifdef DEBUG_IMAP_DETECTOR + IMAPState last_state; +#endif +}; + +static int imap_init(const IniServiceAPI* const init_api); +static int imap_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &imap_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "imap", +}; + +static RNAServiceValidationPort pp[] = +{ + { &imap_validate, IMAP_PORT, IpProtocol::TCP, 0 }, + { &imap_validate, 220, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +static RNAServiceValidationModule service_mod = +{ + "IMAP", + &imap_init, + pp, + nullptr, + nullptr, + 1, + nullptr, + 0 +}; + +static const char IMAP_PATTERN[] = "* OK"; + +struct DetectorData +{ + ClientAppData client; + ServiceIMAPData server; + int need_continue; +}; + +SO_PUBLIC RNADetectorValidationModule imap_detector_mod = +{ + &service_mod, + &client_app_mod, + nullptr, + 0, + nullptr +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_IMAP, APPINFO_FLAG_CLIENT_USER }, + { APP_ID_IMAPS, APPINFO_FLAG_CLIENT_USER } +}; + +static CLIENT_APP_RETCODE init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + RNAClientAppModuleConfigItem* item; + SearchTool* cmd_matcher = new SearchTool("ac_full"); + + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + cmd_matcher->add(patterns[i].pattern, patterns[i].length, &patterns[i]); + if (patterns[i].length > longest_pattern) + longest_pattern = patterns[i].length; + } + cmd_matcher->prep(); + + pAppidActiveConfig->add_generic_config_element(client_app_mod.name, cmd_matcher); + + ca_config.enabled = 1; + + if (config) + { + SF_LNODE* cursor = nullptr; + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &cursor); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&cursor)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + ca_config.enabled = atoi(item->value); + } + } + } + + if (ca_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering pattern: %s\n",(const char*)patterns[i].pattern); + init_api->RegisterPatternNoCase(&validate, IpProtocol::TCP, patterns[i].pattern, + patterns[i].length, -1, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&validate, appIdRegistry[j].appId, appIdRegistry[j].additionalInfo, + init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static int imap_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPatternUser(&imap_validate, IpProtocol::TCP, (uint8_t*)IMAP_PATTERN, + sizeof(IMAP_PATTERN)-1, 0, "imap", init_api->pAppidConfig); + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&imap_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static void clean(const CleanClientAppAPI* const clean_api) +{ + SearchTool* cmd_matcher = + (SearchTool*)clean_api->pAppidConfig->find_generic_config_element(client_app_mod.name); + if (cmd_matcher) + delete cmd_matcher; + + clean_api->pAppidConfig->remove_generic_config_element(client_app_mod.name); +} + +static int pattern_match(void* id, void*, int index, void* data, void*) +{ + Client_App_Pattern** pcmd = (Client_App_Pattern**)data; + unsigned long idx = (unsigned long)id; + + if (index) + return 0; + pcmd = (Client_App_Pattern**)data; + *pcmd = &patterns[idx]; + return 1; +} + +inline static int isImapTagChar(uint8_t tag) +{ + /* Per RFC 3501 + tag char's cannot consist of ", %, { */ + if ((tag == 0x7B) || (tag == 0x22) || (tag == 0x25)) + return 0; + + /* Alpha Numeric's */ + if (isalnum(tag) /* valid tag chars: 0-9, A-Z, a-z */ + || (tag >=0x2C && tag <=0x2F) /* valid tag chars: , - . / */ + || (tag >=0x5D && tag <= 0x60) /* valid tag chars: ] ^ _ ` */ + || (tag >= 0x21 && tag <= 0x27) /* valid tag chars: ! # $ & , */ + /* 0x22 " and 0x25 % invalid as above */ + || (tag >= 0x3a && tag <= 0x40) /*valid tag chars: : ; < = > ? @ */ + || (tag == 0x5b) /*valid tag chars: [ */ + || (tag >= 0x7c && tag <= 0x7e) /* valid tag chars: | } ~ */ + ) + return 1; + + return 0; +} + +static int imap_server_validate(DetectorData* dd, const uint8_t* data, uint16_t size, + AppIdData* flowp) +{ + const uint8_t* end = data + size; + ServiceIMAPData* id = &dd->server; + + id->flags &= ~IMAP_FLAG_RESULT_ALL; // when we are done these flags will tell us OK vs. NO vs. + // BAD + for (; data < end; data++) + { +#ifdef DEBUG_IMAP_DETECTOR + if (id->state != id->last_state) + { + DebugFormat(DEBUG_INSPECTOR,"%p State %d\n",flowp, id->state); + id->last_state = id->state; + } +#endif + switch (id->state) + { + case IMAP_STATE_BEGIN: + if (id->flags & IMAP_FLAG_FIRST_PACKET) + { + id->flags &= ~IMAP_FLAG_FIRST_PACKET; + if (*data == '*') + { + id->state = IMAP_STATE_BANNER_SPACE; + break; + } + } + if (*data == '+' || *data == '*') + { + id->state = IMAP_STATE_MID_LINE; + id->flags &= ~IMAP_FLAG_ALNUM; + } + else if (isImapTagChar(*data)) + { + id->flags |= IMAP_FLAG_ALNUM; + id->tagValue[0] = *data; + id->pos = 1; + id->state = IMAP_STATE_ALNUM_TAG; + } + else + return -1; + break; + case IMAP_STATE_BANNER_SPACE: + if (*data == ' ') + { + id->state = IMAP_STATE_BANNER_OK; + id->pos = 0; + } + else + id->state = IMAP_STATE_MID_LINE; + break; + case IMAP_STATE_BANNER_OK: + if (*data == OK[id->pos]) + { + id->pos++; + if (id->pos >= sizeof(OK)-1) + id->state = IMAP_STATE_BANNER_WHITE_SPACE; + } + else + id->state = IMAP_STATE_MID_LINE; + break; + case IMAP_STATE_BANNER_WHITE_SPACE: + if (*data==' ' || *data=='\t') + break; + else if (*data == 0x0D) + id->state = IMAP_STATE_MID_TERM; + else if (*data == 0x0A) + id->state = IMAP_STATE_BEGIN; + else if (!isprint(*data)) + return -1; + else + id->state = IMAP_STATE_BANNER; + break; + case IMAP_STATE_BANNER: + if (*data == 0x0D) + id->state = IMAP_STATE_MID_TERM; + else if (*data == 0x0A) + id->state = IMAP_STATE_BEGIN; + else if (!isprint(*data)) + return -1; + break; + case IMAP_STATE_MID_LINE: + if (*data == 0x0D) + { + if (!id->parens) + id->state = IMAP_STATE_MID_TERM; + } + else if (*data == 0x0A) + { + if (!id->parens) + { + id->state = IMAP_STATE_BEGIN; + if (id->flags & IMAP_FLAG_ALNUM) + id->count++; + } + } + else if (*data == '(') + id->parens++; + else if (*data == ')') + { + if (id->parens) + id->parens--; + } + else if (!isprint(*data) && *data != 0x09) + return -1; + break; + case IMAP_STATE_MID_TERM: + if (*data == 0x0A) + { + id->state = IMAP_STATE_BEGIN; + if (id->flags & IMAP_FLAG_ALNUM) + id->count++; + } + else + return -1; + break; + case IMAP_STATE_MID_ALNUM: + if (*data == ' ') + id->state = IMAP_STATE_ALNUM_CODE; + else + return -1; + break; + case IMAP_STATE_ALNUM_TAG: + if ((id->pos < (sizeof(id->tagValue)-1)) + && (isImapTagChar(*data))) + { + id->tagValue[id->pos] = *data; + } + else + { + id->tagValue[id->pos] = '\0'; + id->state = IMAP_STATE_ALNUM_CODE; + } + break; + + case IMAP_STATE_ALNUM_CODE: + if (*data == OK[0]) + { + id->state = IMAP_STATE_MID_OK; + id->pos = 1; + } + else if (*data == NO[0]) + { + id->state = IMAP_STATE_MID_NO; + id->pos = 1; + } + else if (*data == BAD[0]) + { + id->state = IMAP_STATE_MID_BAD; + id->pos = 1; + } + else + return -1; + break; + case IMAP_STATE_MID_OK: + if (*data == OK[id->pos]) + { + id->pos++; + if (id->pos >= sizeof(OK)-1) + { + id->pos = 0; + id->state = IMAP_STATE_MID_OK_LOGIN; + if (!strcasecmp(id->tagValue, dd->client.imapCmdTag)) + { + dd->client.imapCmdTag[0] = '\0'; + id->flags |= IMAP_FLAG_RESULT_OK; + } + } + } + else + return -1; + break; + + case IMAP_STATE_MID_OK_LOGIN: + /*add user successful */ + if ((id->flags & IMAP_FLAG_RESULT_OK) && dd->client.username[0]) + { + service_mod.api->add_user(flowp, dd->client.username, APP_ID_IMAP, 1); // use of + // LOGIN + // cmd + // implies + // no IMAPS + } + id->state = IMAP_STATE_MID_LINE; + break; + case IMAP_STATE_MID_NO: + if (*data == NO[id->pos]) + { + id->pos++; + if (id->pos >= sizeof(NO)-1) + { + id->pos = 0; + id->state = IMAP_STATE_MID_NO_LOGIN; + if (!strcasecmp(id->tagValue, dd->client.imapCmdTag)) + { + dd->client.imapCmdTag[0] = '\0'; + id->flags |= IMAP_FLAG_RESULT_NO; + } + } + } + else + return -1; + break; + case IMAP_STATE_MID_NO_LOGIN: + if (*data == NO_LOGIN[id->pos]) + { + id->pos++; + if (id->pos >= sizeof(NO_LOGIN)-1) + { + id->state = IMAP_STATE_ALNUM_CODE_TERM; + /*add user login failed */ + if ((id->flags & IMAP_FLAG_RESULT_NO) && dd->client.username[0]) + { + service_mod.api->add_user(flowp, dd->client.username, APP_ID_IMAP, 0); // use + // of + // LOGIN + // cmd + // implies + // no + // IMAPS + } + } + } + else + id->state = IMAP_STATE_MID_LINE; + break; + + case IMAP_STATE_MID_BAD: + if (*data == BAD[id->pos]) + { + id->pos++; + if (id->pos >= sizeof(BAD)-1) + { + id->state = IMAP_STATE_ALNUM_CODE_TERM; + if (!strcasecmp(id->tagValue, dd->client.imapCmdTag)) + { + dd->client.imapCmdTag[0] = '\0'; + id->flags |= IMAP_FLAG_RESULT_BAD; + } + } + } + else + return -1; + break; + case IMAP_STATE_ALNUM_CODE_TERM: + if (*data == 0x0D) + id->state = IMAP_STATE_MID_TERM; + else if (*data == 0x0A) + { + id->state = IMAP_STATE_BEGIN; + id->count++; + } + else if (*data == ' ') + id->state = IMAP_STATE_MID_LINE; + else + return -1; + break; + } + } + if (dd->client.state == IMAP_CLIENT_STATE_STARTTLS_CMD) + { + if (id->flags & IMAP_FLAG_RESULT_OK) + { + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + /* we are potentially overriding any APP_ID_IMAP assessment that was made earlier. */ + client_app_mod.api->add_app(flowp, APP_ID_IMAPS, APP_ID_IMAPS, nullptr); // sets + // APPID_SESSION_CLIENT_DETECTED + } + else + { + /* We failed to transition to IMAPS - fall back to normal IMAP state, Non-Authenticated + */ + dd->client.state = IMAP_CLIENT_STATE_NON_AUTH; + } + } + else if (dd->client.state == IMAP_CLIENT_STATE_AUTHENTICATE_CMD) + { + dd->client.auth = 0; // stop discarding intervening command packets (part of the + // authenticate) + /* Change state as appropriate */ + dd->client.state = (id->flags & IMAP_FLAG_RESULT_OK) ? + IMAP_CLIENT_STATE_AUTH : + IMAP_CLIENT_STATE_NON_AUTH; + } + return 0; +} + +static CLIENT_APP_RETCODE validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, + const AppIdConfig* pConfig) +{ + const uint8_t* s = data; + const uint8_t* end = (data + size); + unsigned length; + Client_App_Pattern* cmd = nullptr; + DetectorData* dd; + ClientAppData* fd; + char tag[IMAP_TAG_MAX_LEN+1] = { 0 }; + SearchTool* cmd_matcher = + (SearchTool*)( ( AppIdConfig*)pConfig)->find_generic_config_element(client_app_mod.name); + +#ifdef APP_ID_USES_REASSEMBLED + imap_detector_mod.streamAPI->response_flush_stream(pkt); +#endif + + if (!size) + return CLIENT_APP_INPROCESS; + + dd = (DetectorData*)imap_detector_mod.api->data_get(flowp, imap_detector_mod.flow_data_index); + if (!dd) + { + dd = (DetectorData*)snort_calloc(sizeof(DetectorData)); + imap_detector_mod.api->data_add(flowp, dd, imap_detector_mod.flow_data_index, &snort_free); + dd->server.flags = IMAP_FLAG_FIRST_PACKET; + fd = &dd->client; + } + else + fd = &dd->client; + + if (!fd->set_flags) + { + dd->need_continue = 1; + fd->set_flags = 1; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + + if (dir == APP_ID_FROM_RESPONDER) + { + if (imap_server_validate(dd, data, size, flowp)) + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + return CLIENT_APP_INPROCESS; + } + + while ((length = (end - s)) > 0) + { + unsigned pattern_index; + if (fd->auth) + { + /* authentication exchange in progress ignore all client-side + packets until server-side OK/BAD/NO received */ + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + continue; + } + + { + /*processing tags */ + char* p = tag; + char* p_end = p + sizeof(tag) - 1; + for (; (s < end) && isImapTagChar(*s); s++) + { + if (p < p_end) + { + *p++ = *s; + } + } + for (; (s < end) && !isspace(*s); s++) + ; + *p = '\0'; + } + + if (end == s || !isblank(*s)) + { + dd->need_continue = 0; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + return CLIENT_APP_SUCCESS; + } + for (; (s < end) && isblank(*s); s++) + ; + + /*s is now at command beginning */ + if ((length = (end - s)) <= 0) + { + dd->need_continue = 0; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + return CLIENT_APP_SUCCESS; + } + cmd = nullptr; + cmd_matcher->find_all((char*)s, (length > longest_pattern ? longest_pattern : length), + &pattern_match, false, (void*)&cmd); + + if (!cmd) + { + if ( (s[0] >= 'A' && s[0] <= 'Z') || (s[0] >= 'a' && s[0] <= 'z') ) + { + // Command was not in the recognized list. Keep searching. + return CLIENT_APP_INPROCESS; + } + else + { + // IMAP commands are English words, or at least start with X. + return CLIENT_APP_ENULL; // anything but CLIENT_APP_SUCCESS or CLIENT_APP_INPROCESS + } + } + s += cmd->length; + + pattern_index = cmd - patterns; // diff of ptr into array and its base addr is the + // corresponding index. + switch (fd->state) + { + case IMAP_CLIENT_STATE_AUTHENTICATE_CMD: + case IMAP_CLIENT_STATE_STARTTLS_CMD: + /* The command we received was rejected by the server side - + fall back to normal IMAP Non-Authorized state */ + fd->state = IMAP_CLIENT_STATE_NON_AUTH; + // fall through + + case IMAP_CLIENT_STATE_NON_AUTH: + switch (pattern_index) + { + case PATTERN_LOGIN: + strncpy(fd->imapCmdTag, tag, sizeof(fd->imapCmdTag)); + { + char* p = fd->username; + char* p_end = p + sizeof(fd->username) - 1; + int found_tick = 0; + + if (*s == '"') + { + s++; + for (; s < end && p < p_end; s++) + { + if (*s == '"') + { + fd->count++; + if (fd->count == MIN_CMDS) + { + client_app_mod.api->add_app(flowp, APP_ID_IMAP, APP_ID_IMAP, + nullptr); + fd->detected = 1; + if (fd->got_user) + { + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + clearAppIdFlag(flowp, + APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + fd->state = IMAP_CLIENT_STATE_AUTH; + } + *p = 0; + fd->got_user = 1; + break; + } + else if (isalnum(*s) || *s == '.' || *s == '@' || *s == '-' || *s == + '_' || *s == '`' || *s == ' ') + { + *p = *s; + p++; + } + else + break; + } + } + else + { + for (; s < end && p < p_end; s++) + { + if (isalnum(*s) || *s == '.' || *s == '@' || *s == '-' || *s == '_') + { + if (!found_tick) + { + *p = *s; + p++; + } + } + else if (*s == '`') + found_tick = 1; + else if (*s == ' ') + { + fd->count++; + if (fd->count == MIN_CMDS) + { + client_app_mod.api->add_app(flowp, APP_ID_IMAP, APP_ID_IMAP, + nullptr); + fd->detected = 1; + if (fd->got_user) + { + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + clearAppIdFlag(flowp, + APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + } + *p = 0; + fd->got_user = 1; + break; + } + else + break; + } + } + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + } + break; + + case PATTERN_STARTTLS: + case PATTERN_STARTTLS2: + strncpy(fd->imapCmdTag, tag, sizeof(fd->imapCmdTag)); + fd->state = IMAP_CLIENT_STATE_STARTTLS_CMD; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; // all we need because + // cmd->eoc == 1 + /* No other commands will be coming until the result from this one. */ + break; + + case PATTERN_AUTHENTICATE: + strncpy(fd->imapCmdTag, tag, sizeof(fd->imapCmdTag)); + fd->auth = 1; // gobble additional client packets until the server OK/BAD/NO + // response + fd->state = IMAP_CLIENT_STATE_AUTHENTICATE_CMD; + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + break; + + default: + { + fd->count++; + if (fd->count == MIN_CMDS) + { + client_app_mod.api->add_app(flowp, APP_ID_IMAP, APP_ID_IMAP, nullptr); + fd->detected = 1; + if (fd->got_user) + { + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + } + if (!cmd->eoc) + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + } + break; + } + break; + case IMAP_CLIENT_STATE_AUTH: + { + fd->count++; + if (fd->count == MIN_CMDS) + { + client_app_mod.api->add_app(flowp, APP_ID_IMAP, APP_ID_IMAP, nullptr); + fd->detected = 1; + if (fd->got_user) + { + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + } + if (!cmd->eoc) + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + } + break; + } // end switch(fd->state) + } // end 'while' + return CLIENT_APP_INPROCESS; +} + +static int imap_validate(ServiceValidationArgs* args) +{ + DetectorData* dd; + ServiceIMAPData* id; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + +#ifdef APP_ID_USES_REASSEMBLED + imap_detector_mod.streamAPI->response_flush_stream(pkt); +#endif + + if (!size) + goto inprocess; + + dd = (DetectorData*)imap_detector_mod.api->data_get(flowp, imap_detector_mod.flow_data_index); + if (!dd) + { + dd = (DetectorData*)snort_calloc(sizeof(DetectorData)); + imap_detector_mod.api->data_add(flowp, dd, imap_detector_mod.flow_data_index, &snort_free); + id = &dd->server; + id->state = IMAP_STATE_BEGIN; + id->flags = IMAP_FLAG_FIRST_PACKET; + } + else + id = &dd->server; + + if (dd->need_continue) + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + else + { + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + if (getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + return SERVICE_SUCCESS; + } + + if (!imap_server_validate(dd, data, size, flowp)) + { + if ((id->flags & IMAP_FLAG_RESULT_OK) && dd->client.state == + IMAP_CLIENT_STATE_STARTTLS_CMD) + { + /* IMAP server response to STARTTLS command from client was OK */ + service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_IMAPS, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + } + if (id->count >= IMAP_COUNT_THRESHOLD && !getAppIdFlag(flowp, + APPID_SESSION_SERVICE_DETECTED)) + { + service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_IMAP, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + } + } + else if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + } + else + { + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_SUCCESS; + } + +inprocess: + service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; +} + diff --git a/src/network_inspectors/appid/detector_plugins/detector_kerberos.cc b/src/network_inspectors/appid/detector_plugins/detector_kerberos.cc new file mode 100644 index 000000000..e45a8d799 --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_kerberos.cc @@ -0,0 +1,1134 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_kerberos.cc author Sourcefire Inc. + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_plugins/client_app_api.h" +#include "service_plugins/service_api.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +/*#define DEBUG_KERBEROS 1 */ + +enum KerberosState +{ + KRB_STATE_TCP_LENGTH, + KRB_STATE_APP, + KRB_STATE_SEQ, + KRB_STATE_VERSION, + KRB_STATE_VERSION_2, + KRB_STATE_VERSION_VALUE, + KRB_STATE_TYPE, + KRB_STATE_TYPE_VALUE, + KRB_STATE_ERROR, + KRB_STATE_ERROR_VALUE, + KRB_STATE_FIELD, + KRB_STATE_FIELD_DATA, + KRB_STATE_FIELD_DATA_2, + KRB_STATE_CNAME_SEQ, + KRB_STATE_CNAME_TYPE, + KRB_STATE_CNAME_TYPE_2, + KRB_STATE_CNAME_TYPE_VALUE, + KRB_STATE_CNAME, + KRB_STATE_CNAME_PRINCIPAL_SEQ, + KRB_STATE_CNAME_PRINCIPAL_KS, + KRB_STATE_CNAME_PRINCIPAL_DATA, + KRB_STATE_CNAME_PRINCIPAL_DATA_2, + KRB_STATE_LEN, + KRB_STATE_LEN_2, + KRB_STATE_REQBODY_SEQ, + KRB_STATE_REQBODY_TYPE, + KRB_STATE_FIELD_LEVEL2, + KRB_STATE_FIELD_DATA_LEVEL2, + KRB_STATE_FIELD_DATA_2_LEVEL2, + KRB_STATE_INVALID, +}; + +/*error codes from RFC 4120 */ +static const unsigned KDC_ERR_PREAUTH_FAILED = 24; + +#define KRB_FLAG_AUTH_FAILED 0x01 +#define KRB_FLAG_USER_DETECTED 0x02 +#define KRB_FLAG_SERVICE_DETECTED 0x04 + +enum KRB_RETCODE +{ + KRB_INPROCESS, + KRB_FAILED, +}; + +struct KRBState +{ + KerberosState state; + KerberosState next_state; + uint8_t msg_type; + unsigned msg_len; + uint8_t tag; + unsigned len; + unsigned pos; + int added; + unsigned cname_len; + char cname[256]; + char ver[2]; +#ifdef DEBUG_KERBEROS + KerberosState last_state; +#endif + unsigned flags; +}; + +struct KRB_CLIENT_APP_CONFIG +{ + int enabled; + int failedLogin; +}; + +struct DetectorData +{ + KRBState clnt_state; + KRBState svr_state; + int set_flags; + int need_continue; +}; + +// FIXIT-L THREAD_LOCAL? +static KRB_CLIENT_APP_CONFIG krb_client_config; + +static CLIENT_APP_RETCODE krb_client_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE krb_client_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, + const AppIdConfig* pConfig); + +static RNAClientAppModule client_app_mod = +{ + "KERBEROS", + IpProtocol::UDP, + &krb_client_init, + nullptr, + &krb_client_validate, + 1, + nullptr, + nullptr, + 0, + nullptr, + 1, + 0 +}; + +struct Detector_Pattern +{ + const uint8_t* pattern; + unsigned length; +}; + +static const uint8_t AS_REQ[] = "\x0a1\x003\x002\x001\x005\x0a2\x003\x002\x001\x00a"; +static const uint8_t TGS_REQ[] = "\x0a1\x003\x002\x001\x005\x0a2\x003\x002\x001\x00c"; +static const uint8_t AS_REQ_4[] = "\x0a1\x003\x002\x001\x004\x0a2\x003\x002\x001\x00a"; +static const uint8_t TGS_REQ_4[] = "\x0a1\x003\x002\x001\x004\x0a2\x003\x002\x001\x00c"; + +static Detector_Pattern client_patterns[] = +{ + { AS_REQ, sizeof(AS_REQ)-1 }, + { TGS_REQ, sizeof(TGS_REQ)-1 }, + { AS_REQ_4, sizeof(AS_REQ_4)-1 }, + { TGS_REQ_4, sizeof(TGS_REQ_4)-1 }, +}; + +static int krb_server_init(const IniServiceAPI* const init_api); +static int krb_server_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &krb_server_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "kerberos" +}; + +static RNAServiceValidationPort pp[] = +{ + { &krb_server_validate, 88, IpProtocol::TCP, 0 }, + { &krb_server_validate, 88, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +static RNAServiceValidationModule service_mod = +{ + "KRB", + &krb_server_init, + pp, + nullptr, + nullptr, + 1, + nullptr, + 0 +}; + +static const uint8_t AS_REP[] = "\x0a0\x003\x002\x001\x005\x0a1\x003\x002\x001\x00b"; +static const uint8_t TGS_REP[] = "\x0a0\x003\x002\x001\x005\x0a1\x003\x002\x001\x00d"; +static const uint8_t AS_REP_4[] = "\x0a0\x003\x002\x001\x004\x0a1\x003\x002\x001\x00b"; +static const uint8_t TGS_REP_4[] = "\x0a0\x003\x002\x001\x004\x0a1\x003\x002\x001\x00d"; + +static Detector_Pattern service_patterns[] = +{ + { AS_REP, sizeof(AS_REP)-1 }, + { TGS_REP, sizeof(TGS_REP)-1 }, + { AS_REP_4, sizeof(AS_REP_4)-1 }, + { TGS_REP_4, sizeof(TGS_REP_4)-1 }, +}; + +SO_PUBLIC RNADetectorValidationModule kerberos_detector_mod = +{ + &service_mod, + &client_app_mod, + nullptr, + 0, + nullptr +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_KERBEROS, APPINFO_FLAG_CLIENT_USER | APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static CLIENT_APP_RETCODE krb_client_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + RNAClientAppModuleConfigItem* item; + + krb_client_config.enabled = 1; + krb_client_config.failedLogin = 0; + + if (config) + { + SF_LNODE* iter = nullptr; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &iter); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&iter)) + { + DebugFormat(DEBUG_INSPECTOR,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + krb_client_config.enabled = atoi(item->value); + } + if (strcasecmp(item->name, "failed-login") == 0) + { + krb_client_config.failedLogin = atoi(item->value); + } + } + } + + if (krb_client_config.enabled) + { + for (i=0; i < sizeof(client_patterns)/sizeof(*client_patterns); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering pattern with length %u\n", + client_patterns[i].length); + init_api->RegisterPattern(&krb_client_validate, IpProtocol::UDP, + client_patterns[i].pattern, client_patterns[i].length, -1, init_api->pAppidConfig); + init_api->RegisterPattern(&krb_client_validate, IpProtocol::TCP, + client_patterns[i].pattern, client_patterns[i].length, -1, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&krb_client_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static int krb_server_init(const IniServiceAPI* const init_api) +{ + unsigned i; + + for (i=0; i < sizeof(service_patterns)/sizeof(*service_patterns); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering pattern with length %u\n", + service_patterns[i].length); + init_api->RegisterPatternUser(&krb_server_validate, IpProtocol::UDP, + service_patterns[i].pattern, + service_patterns[i].length, -1, "kerberos", init_api->pAppidConfig); + init_api->RegisterPatternUser(&krb_server_validate, IpProtocol::TCP, + service_patterns[i].pattern, + service_patterns[i].length, -1, "kerberos", init_api->pAppidConfig); + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&krb_server_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +#define ASN_1_APPLICATION 0x40 +#define ASN_1_CONSTRUCT 0x20 +#define ASN_1_TYPE_MASK 0xe0 +#define AS_REQ_MSG_TYPE 0x0a +#define AS_REP_MSG_TYPE 0x0b +#define TGS_REQ_MSG_TYPE 0x0c +#define TGS_REP_MSG_TYPE 0x0d +#define ERROR_MSG_TYPE 0x1e + +static KRB_RETCODE krb_walk_client_packet(KRBState* krbs, const uint8_t* s, const uint8_t* end, + AppIdData* flowp) +{ + static const uint8_t KRB_CLIENT_VERSION[] = "\x0a1\x003\x002\x001"; + static const uint8_t KRB_CLIENT_TYPE[] = "\x0a2\x003\x002\x001"; + static const uint8_t KRB_CNAME_TYPE[] = "\x0a0\x003\x002\x001"; +#define KRB_CNAME_TYPE_SIZE (sizeof(KRB_CNAME_TYPE) - 1) + + while (s < end) + { +#ifdef DEBUG_KERBEROS + if (krbs->state != krbs->last_state) + { + DebugFormat(DEBUG_INSPECTOR,"%p State %d\n",flowp, krbs->state); + krbs->last_state = krbs->state; + } +#endif + switch (krbs->state) + { + case KRB_STATE_TCP_LENGTH: + if (krbs->pos >= 3) + krbs->state = KRB_STATE_APP; + else + krbs->pos++; + break; + case KRB_STATE_APP: +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Type %u (%02X)\n",flowp, *s & (~ASN_1_TYPE_MASK), *s); +#endif + if ((*s & ASN_1_TYPE_MASK) != (ASN_1_APPLICATION|ASN_1_CONSTRUCT)) + return KRB_FAILED; + krbs->msg_type = *s & (~ASN_1_TYPE_MASK); + switch (krbs->msg_type) + { + case AS_REQ_MSG_TYPE: + case TGS_REQ_MSG_TYPE: + case ERROR_MSG_TYPE: + case 14: + case 16: + case 20: + case 21: + case 22: + krbs->next_state = KRB_STATE_SEQ; + break; + default: + return KRB_FAILED; + } + krbs->state = KRB_STATE_LEN; + krbs->msg_len = 0xFFFFFFFF; + break; + case KRB_STATE_SEQ: + if (krbs->len < 2 || *s != 0x30) + return KRB_FAILED; + krbs->msg_len = krbs->len; + krbs->next_state = KRB_STATE_VERSION; + krbs->state = KRB_STATE_LEN; + krbs->pos = 0; + break; + case KRB_STATE_VERSION: + if (krbs->len < 10 || krbs->len != krbs->msg_len) + return KRB_FAILED; + krbs->state = KRB_STATE_VERSION_2; + krbs->pos = 0; + case KRB_STATE_VERSION_2: + if (*s != KRB_CLIENT_VERSION[krbs->pos]) + return KRB_FAILED; + krbs->pos++; + if (krbs->pos >= sizeof(KRB_CLIENT_VERSION) - 1) + krbs->state = KRB_STATE_VERSION_VALUE; + break; + case KRB_STATE_VERSION_VALUE: + if (*s != 5 && *s != 4) + return KRB_FAILED; + krbs->state = KRB_STATE_TYPE; + krbs->pos = 0; + krbs->ver[0] = *s + '0'; + break; + case KRB_STATE_TYPE: + if (*s != KRB_CLIENT_TYPE[krbs->pos]) + return KRB_FAILED; + if (krbs->pos >= (sizeof(KRB_CLIENT_TYPE) - 1) - 1) + { + krbs->state = KRB_STATE_TYPE_VALUE; + break; + } + krbs->pos++; + break; + case KRB_STATE_TYPE_VALUE: + if (*s != krbs->msg_type) + return KRB_FAILED; + krbs->state = KRB_STATE_FIELD; + krbs->tag = 0xa2; + break; + case KRB_STATE_FIELD: +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Tag %02X\n",flowp, *s); +#endif + if (krbs->msg_len < 2 || *s <= krbs->tag || (*s & ASN_1_TYPE_MASK) != 0xa0) + return KRB_FAILED; + krbs->tag = *s; + if (krbs->tag == 0xa4 + && (krbs->msg_type == AS_REQ_MSG_TYPE || krbs->msg_type == TGS_REQ_MSG_TYPE) + && krb_client_config.failedLogin) + { + krbs->next_state = KRB_STATE_REQBODY_SEQ; + } + else + krbs->next_state = KRB_STATE_FIELD_DATA; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_FIELD_DATA: + if (krbs->msg_len < krbs->len) + return KRB_FAILED; + krbs->state = KRB_STATE_FIELD_DATA_2; + case KRB_STATE_FIELD_DATA_2: + if (krbs->len <= 1) + { + if (krbs->msg_len <= 1) + { +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Valid\n",flowp); +#endif + if (!krbs->added) + { + client_app_mod.api->add_app(flowp, APP_ID_KERBEROS, APP_ID_KERBEROS, + krbs->ver); + krbs->added = 1; + } + krbs->state = KRB_STATE_APP; + if (!krbs->msg_len) + continue; + break; + } + krbs->state = KRB_STATE_FIELD; + if (!krbs->len) + continue; + break; + } + krbs->len--; + break; + case KRB_STATE_REQBODY_SEQ: + /*REQ_BODY is the last level 1 element in AS-REQ and TSG-REQ messages therefore + a. its length is not maintained, remaining msg_len is assumed to be req_body length + b. krbs->rtag is reused at level 2 */ + + if (*s != 0x30) + return KRB_FAILED; + + krbs->next_state = KRB_STATE_FIELD_LEVEL2; + krbs->state = KRB_STATE_LEN; + krbs->tag = 0; + break; + + case KRB_STATE_FIELD_LEVEL2: +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Tag %02X\n",flowp, *s); +#endif + if (krbs->msg_len <= 1) + { + krbs->state = KRB_STATE_APP; + if (!krbs->msg_len) + continue; + break; + } + + if (krbs->msg_len < 2 || *s <= krbs->tag || (*s & ASN_1_TYPE_MASK) != 0xa0) + return KRB_FAILED; + krbs->tag = *s; + if (krbs->tag == 0xa1) + krbs->next_state = KRB_STATE_CNAME_SEQ; + else + krbs->next_state = KRB_STATE_FIELD_DATA_LEVEL2; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_FIELD_DATA_LEVEL2: + if (krbs->msg_len < krbs->len) + return KRB_FAILED; + krbs->next_state = KRB_STATE_FIELD_DATA_2_LEVEL2; + krbs->state = KRB_STATE_LEN; + break; + + case KRB_STATE_FIELD_DATA_2_LEVEL2: + if (krbs->len <= 1) + { + krbs->state = KRB_STATE_FIELD_LEVEL2; + if (!krbs->len) + continue; + break; + } + krbs->len--; + break; + + case KRB_STATE_CNAME_SEQ: + if (krbs->len < (KRB_CNAME_TYPE_SIZE + 5) || krbs->len > krbs->msg_len || *s != 0x30) + return KRB_FAILED; + krbs->cname_len = krbs->len; + krbs->next_state = KRB_STATE_CNAME_TYPE; + krbs->state = KRB_STATE_LEN; + break; + + case KRB_STATE_CNAME_TYPE: + if (krbs->len > krbs->cname_len || krbs->len < (KRB_CNAME_TYPE_SIZE + 3)) + return KRB_FAILED; + krbs->state = KRB_STATE_CNAME_TYPE_2; + krbs->pos = 0; + case KRB_STATE_CNAME_TYPE_2: + if (*s != KRB_CNAME_TYPE[krbs->pos]) + return KRB_FAILED; + krbs->pos++; + if (krbs->pos >= KRB_CNAME_TYPE_SIZE) + krbs->state = KRB_STATE_CNAME_TYPE_VALUE; + break; + case KRB_STATE_CNAME_TYPE_VALUE: + if (krbs->cname_len < 3 || (*s > 7 && *s != 10)) + return KRB_FAILED; + if (*s != 1) + { + krbs->len = krbs->cname_len; + krbs->state = KRB_STATE_FIELD_DATA_2; + break; + } + krbs->state = KRB_STATE_CNAME; + break; + case KRB_STATE_CNAME: + if (krbs->cname_len < 3 || *s != 0xa1) + return KRB_FAILED; + krbs->next_state = KRB_STATE_CNAME_PRINCIPAL_SEQ; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_CNAME_PRINCIPAL_SEQ: + if (krbs->len != krbs->cname_len || krbs->cname_len < 3 || *s != 0x30) + return KRB_FAILED; + krbs->next_state = KRB_STATE_CNAME_PRINCIPAL_KS; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_CNAME_PRINCIPAL_KS: + if (krbs->len != krbs->cname_len || krbs->cname_len < 3 || *s != 0x1b) + return KRB_FAILED; + krbs->next_state = KRB_STATE_CNAME_PRINCIPAL_DATA; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_CNAME_PRINCIPAL_DATA: + if (krbs->len != krbs->cname_len) + return KRB_FAILED; + krbs->state = KRB_STATE_CNAME_PRINCIPAL_DATA_2; + krbs->pos = 0; + case KRB_STATE_CNAME_PRINCIPAL_DATA_2: + if (krbs->len) + { + if (krbs->pos < (sizeof(krbs->cname) - 2)) + { + if (isalnum(*s) || *s == '.' || *s == '@' || *s == '-' || *s == '_' || *s == + '`' || *s == ' ') + { + krbs->cname[krbs->pos] = *s; + krbs->pos++; + } + else + { + krbs->len = krbs->cname_len; + krbs->state = KRB_STATE_FIELD_DATA_2; + break; + } + } + } + if (krbs->len <= 1) + { + if (krbs->pos) + { +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Name %u\n",flowp, krbs->pos); +#endif + krbs->cname[krbs->pos] = 0; + } + if (krbs->msg_len <= 1) + { + krbs->state = KRB_STATE_APP; + if (!krbs->msg_len) + continue; + } + krbs->state = KRB_STATE_FIELD_LEVEL2; + if (!krbs->len) + continue; + break; + } + krbs->len--; + break; + case KRB_STATE_LEN: + if (*s & 0x80) + { + krbs->pos = *s & 0x7F; + if (!krbs->pos || krbs->pos > 4) + { + /* Not handling indeterminate length or length greater than 32 bits */ + return KRB_FAILED; + } + krbs->len = 0; + krbs->state = KRB_STATE_LEN_2; + } + else + { + krbs->len = *s; + krbs->state = krbs->next_state; + } + break; + case KRB_STATE_LEN_2: + if (krbs->msg_len) + { + krbs->len <<= 8; + krbs->len |= *s; + if (krbs->pos <= 1) + { + krbs->state = krbs->next_state; + break; + } + krbs->pos--; + } + else + return KRB_FAILED; + break; + default: + /* This should never happen */ + return KRB_FAILED; + } + krbs->msg_len--; + krbs->cname_len--; + s++; + } + return KRB_INPROCESS; +} + +static KRB_RETCODE krb_walk_server_packet(KRBState* krbs, const uint8_t* s, const uint8_t* end, + AppIdData* flowp, Packet* pkt, const int dir, + const char* reqCname) +{ + static const uint8_t KRB_SERVER_VERSION[] = "\x0a0\x003\x002\x001"; + static const uint8_t KRB_SERVER_TYPE[] = "\x0a1\x003\x002\x001"; + static const uint8_t KRB_CNAME_TYPE[] = "\x0a0\x003\x002\x001"; + static const uint8_t KRB_ERROR[] = "\x003\x002\x001"; +#define KRB_CNAME_TYPE_SIZE (sizeof(KRB_CNAME_TYPE) - 1) + + while (s < end) + { +#ifdef DEBUG_KERBEROS + if (krbs->state != krbs->last_state) + { + DebugFormat(DEBUG_INSPECTOR,"%p State %d\n",flowp, krbs->state); + krbs->last_state = krbs->state; + } +#endif + switch (krbs->state) + { + case KRB_STATE_TCP_LENGTH: + if (krbs->pos >= 3) + krbs->state = KRB_STATE_APP; + else + krbs->pos++; + break; + case KRB_STATE_APP: +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Type %u (%02X)\n",flowp, *s & (~ASN_1_TYPE_MASK), *s); +#endif + if ((*s & ASN_1_TYPE_MASK) != (ASN_1_APPLICATION|ASN_1_CONSTRUCT)) + return KRB_FAILED; + krbs->msg_type = *s & (~ASN_1_TYPE_MASK); + switch (krbs->msg_type) + { + case AS_REP_MSG_TYPE: + case TGS_REP_MSG_TYPE: + case ERROR_MSG_TYPE: + case 15: + case 17: + case 20: + case 21: + case 22: + krbs->next_state = KRB_STATE_SEQ; + break; + default: + return KRB_FAILED; + } + krbs->state = KRB_STATE_LEN; + krbs->msg_len = 0xFFFFFFFF; + break; + case KRB_STATE_SEQ: + if (krbs->len < 2 || *s != 0x30) + return KRB_FAILED; + krbs->msg_len = krbs->len; + krbs->next_state = KRB_STATE_VERSION; + krbs->state = KRB_STATE_LEN; + krbs->pos = 0; + break; + case KRB_STATE_VERSION: + if (krbs->len < 10 || krbs->len != krbs->msg_len) + return KRB_FAILED; + krbs->state = KRB_STATE_VERSION_2; + krbs->pos = 0; + case KRB_STATE_VERSION_2: + if (*s != KRB_SERVER_VERSION[krbs->pos]) + return KRB_FAILED; + krbs->pos++; + if (krbs->pos >= sizeof(KRB_SERVER_VERSION) - 1) + krbs->state = KRB_STATE_VERSION_VALUE; + break; + case KRB_STATE_VERSION_VALUE: + if (*s != 5 && *s != 4) + return KRB_FAILED; + krbs->state = KRB_STATE_TYPE; + krbs->pos = 0; + krbs->ver[0] = *s + '0'; + break; + case KRB_STATE_TYPE: + if (*s != KRB_SERVER_TYPE[krbs->pos]) + return KRB_FAILED; + if (krbs->pos >= (sizeof(KRB_SERVER_TYPE) - 1) - 1) + { + krbs->state = KRB_STATE_TYPE_VALUE; + break; + } + krbs->pos++; + break; + case KRB_STATE_TYPE_VALUE: + if (*s != krbs->msg_type) + return KRB_FAILED; + krbs->state = KRB_STATE_FIELD; + krbs->tag = 0xa1; + break; + case KRB_STATE_ERROR: + if (*s != KRB_ERROR[krbs->pos]) + return KRB_FAILED; + if (krbs->pos >= (sizeof(KRB_ERROR) - 1) - 1) + { + krbs->state = KRB_STATE_ERROR_VALUE; + break; + } + krbs->pos++; + break; + case KRB_STATE_ERROR_VALUE: +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Error %u\n",flowp, *s); +#endif + if (krbs->msg_len <= 1) + { + krbs->flags |= KRB_FLAG_SERVICE_DETECTED; + krbs->state = KRB_STATE_APP; + if (!krbs->msg_len) + continue; + break; + } + + if (*s == KDC_ERR_PREAUTH_FAILED) + { +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p unAuthorized\n",flowp); +#endif + krbs->flags |= KRB_FLAG_AUTH_FAILED; + } + krbs->state = KRB_STATE_FIELD; + break; + case KRB_STATE_FIELD: +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Tag %02X\n",flowp, *s); +#endif + if (krbs->msg_len < 2 || *s <= krbs->tag || (*s & ASN_1_TYPE_MASK) != 0xa0) + return KRB_FAILED; + krbs->tag = *s; + if ((krbs->tag == 0xa4 && (krbs->msg_type == AS_REP_MSG_TYPE || krbs->msg_type == + TGS_REP_MSG_TYPE)) + || (krbs->tag == 0xa8 && (krbs->msg_type == ERROR_MSG_TYPE))) + { + krbs->next_state = KRB_STATE_CNAME_SEQ; + } + else if (krbs->tag == 0xa6 && krbs->msg_type == ERROR_MSG_TYPE) + { + krbs->state = KRB_STATE_ERROR; + krbs->pos = 0; + break; + } + else + krbs->next_state = KRB_STATE_FIELD_DATA; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_FIELD_DATA: + if (krbs->msg_len < krbs->len) + return KRB_FAILED; + krbs->state = KRB_STATE_FIELD_DATA_2; + case KRB_STATE_FIELD_DATA_2: + if (krbs->len <= 1) + { + if (krbs->msg_len <= 1) + { + krbs->flags |= KRB_FLAG_SERVICE_DETECTED; + krbs->state = KRB_STATE_APP; + if (!krbs->msg_len) + continue; + break; + } + krbs->state = KRB_STATE_FIELD; + if (!krbs->len) + continue; + break; + } + krbs->len--; + break; + case KRB_STATE_CNAME_SEQ: + if (krbs->len < (KRB_CNAME_TYPE_SIZE + 5) || krbs->len > krbs->msg_len || *s != 0x30) + return KRB_FAILED; + krbs->cname_len = krbs->len; + krbs->next_state = KRB_STATE_CNAME_TYPE; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_CNAME_TYPE: + if (krbs->len > krbs->cname_len || krbs->len < (KRB_CNAME_TYPE_SIZE + 3)) + return KRB_FAILED; + krbs->state = KRB_STATE_CNAME_TYPE_2; + krbs->pos = 0; + case KRB_STATE_CNAME_TYPE_2: + if (*s != KRB_CNAME_TYPE[krbs->pos]) + return KRB_FAILED; + krbs->pos++; + if (krbs->pos >= KRB_CNAME_TYPE_SIZE) + krbs->state = KRB_STATE_CNAME_TYPE_VALUE; + break; + case KRB_STATE_CNAME_TYPE_VALUE: + if (krbs->cname_len < 3 || (*s > 7 && *s != 10)) + return KRB_FAILED; + if (*s != 1) + { + krbs->len = krbs->cname_len; + krbs->state = KRB_STATE_FIELD_DATA_2; + break; + } + krbs->state = KRB_STATE_CNAME; + break; + case KRB_STATE_CNAME: + if (krbs->cname_len < 3 || *s != 0xa1) + return KRB_FAILED; + krbs->next_state = KRB_STATE_CNAME_PRINCIPAL_SEQ; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_CNAME_PRINCIPAL_SEQ: + if (krbs->len != krbs->cname_len || krbs->cname_len < 3 || *s != 0x30) + return KRB_FAILED; + krbs->next_state = KRB_STATE_CNAME_PRINCIPAL_KS; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_CNAME_PRINCIPAL_KS: + if (krbs->len != krbs->cname_len || krbs->cname_len < 3 || *s != 0x1b) + return KRB_FAILED; + krbs->next_state = KRB_STATE_CNAME_PRINCIPAL_DATA; + krbs->state = KRB_STATE_LEN; + break; + case KRB_STATE_CNAME_PRINCIPAL_DATA: + if (krbs->len != krbs->cname_len) + return KRB_FAILED; + krbs->state = KRB_STATE_CNAME_PRINCIPAL_DATA_2; + krbs->pos = 0; + case KRB_STATE_CNAME_PRINCIPAL_DATA_2: + if (krbs->len) + { + if (krbs->pos < (sizeof(krbs->cname) - 2)) + { + if (isalnum(*s) || *s == '.' || *s == '@' || *s == '-' || *s == '_' || *s == + '`' || *s == ' ') + { + krbs->cname[krbs->pos] = *s; + krbs->pos++; + } + else + { + krbs->len = krbs->cname_len; + krbs->state = KRB_STATE_FIELD_DATA_2; + break; + } + } + } + if (krbs->len <= 1) + { + if (krbs->pos) + { +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Name %u\n",flowp, krbs->pos); +#endif + krbs->cname[krbs->pos] = 0; + krbs->flags |= KRB_FLAG_USER_DETECTED; + } + if (krbs->msg_len <= 1) + { + krbs->flags |= KRB_FLAG_SERVICE_DETECTED; + krbs->state = KRB_STATE_APP; + if (!krbs->msg_len) + continue; + } + krbs->state = KRB_STATE_FIELD; + if (!krbs->len) + continue; + break; + } + krbs->len--; + break; + case KRB_STATE_LEN: + if (*s & 0x80) + { + krbs->pos = *s & 0x7F; + if (!krbs->pos || krbs->pos > 4) + { + /* Not handling indeterminate length or length greater than 32 bits */ + return KRB_FAILED; + } + krbs->len = 0; + krbs->state = KRB_STATE_LEN_2; + } + else + { + krbs->len = *s; + krbs->state = krbs->next_state; + } + break; + case KRB_STATE_LEN_2: + if (krbs->msg_len) + { + krbs->len <<= 8; + krbs->len |= *s; + if (krbs->pos <= 1) + { + krbs->state = krbs->next_state; + break; + } + krbs->pos--; + } + else + return KRB_FAILED; + break; + default: + /* This should never happen */ + return KRB_FAILED; + } + krbs->msg_len--; + krbs->cname_len--; + s++; + } + + if (krbs->msg_len <= 1) + { + /*end of server response message */ +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Valid\n",flowp); +#endif + if (krbs->flags & KRB_FLAG_SERVICE_DETECTED) + { + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED) && pkt) + { + service_mod.api->add_service(flowp, pkt, dir, &svc_element, APP_ID_KERBEROS, + nullptr, krbs->ver, nullptr); + setAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED); + } + } + + if (krbs->flags & KRB_FLAG_AUTH_FAILED) + { + if (krb_client_config.failedLogin + && ((krbs->flags & KRB_FLAG_USER_DETECTED) || reqCname)) + { + service_mod.api->add_user(flowp, + (krbs->flags & KRB_FLAG_USER_DETECTED) ? krbs->cname : reqCname, + APP_ID_LDAP, 0); + } + } + else if (krbs->flags & KRB_FLAG_USER_DETECTED) + { + service_mod.api->add_user(flowp, krbs->cname, APP_ID_LDAP, 1); + } + + krbs->flags = 0; + } + + return KRB_INPROCESS; +} + +static CLIENT_APP_RETCODE krb_client_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig*) +{ + const uint8_t* s = data; + const uint8_t* end = (data + size); + DetectorData* fd; + +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR, "%p Processing %u %u->%u %u %d", flowp, flowp->proto, + pkt->src_port, + pkt->dst_port, size, dir); +#endif + +#ifdef APP_ID_USES_REASSEMBLED + kerberos_detector_mod.streamAPI->response_flush_stream(pkt); +#endif + + if (!size) + return CLIENT_APP_INPROCESS; + + fd = (DetectorData*)kerberos_detector_mod.api->data_get(flowp, + kerberos_detector_mod.flow_data_index); + if (!fd) + { + fd = (DetectorData*)snort_calloc(sizeof(DetectorData)); + kerberos_detector_mod.api->data_add(flowp, fd, + kerberos_detector_mod.flow_data_index, &snort_free); + if (flowp->proto == IpProtocol::TCP) + { + fd->clnt_state.state = KRB_STATE_TCP_LENGTH; + fd->svr_state.state = KRB_STATE_TCP_LENGTH; + } + else + { + fd->clnt_state.state = KRB_STATE_APP; + fd->svr_state.state = KRB_STATE_APP; + } +#ifdef DEBUG_KERBEROS + fd->clnt_state.last_state = KRB_STATE_INVALID; + fd->svr_state.last_state = KRB_STATE_INVALID; +#endif + } + + if (!fd->set_flags) + { + fd->need_continue = 1; + fd->set_flags = 1; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + + if (dir == APP_ID_FROM_INITIATOR) + { + if (krb_walk_client_packet(&fd->clnt_state, s, end, flowp) == KRB_FAILED) + { +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Failed\n",flowp); +#endif + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + return CLIENT_APP_SUCCESS; + } + } + else if (krb_walk_server_packet(&fd->svr_state, s, end, flowp, nullptr, dir, + fd->clnt_state.cname) == KRB_FAILED) + { +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Server Failed\n",flowp); +#endif + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + return CLIENT_APP_INPROCESS; +} + +static int krb_server_validate(ServiceValidationArgs* args) +{ + DetectorData* fd; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + const uint8_t* s = data; + const uint8_t* end = (data + size); + +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR, "%p Processing %u %u->%u %u %d", flowp, flowp->proto, + pkt->src_port, + pkt->dst_port, size, dir); +#endif + + if (dir != APP_ID_FROM_RESPONDER) + goto inprocess; + +#ifdef APP_ID_USES_REASSEMBLED + kerberos_detector_mod.streamAPI->response_flush_stream(pkt); +#endif + + if (!size) + goto inprocess; + + fd = (DetectorData*)kerberos_detector_mod.api->data_get(flowp, + kerberos_detector_mod.flow_data_index); + if (!fd) + { + fd = (DetectorData*)snort_calloc(sizeof(DetectorData)); + kerberos_detector_mod.api->data_add(flowp, fd, + kerberos_detector_mod.flow_data_index, &snort_free); + if (flowp->proto == IpProtocol::TCP) + { + fd->clnt_state.state = KRB_STATE_TCP_LENGTH; + fd->svr_state.state = KRB_STATE_TCP_LENGTH; + } + else + { + fd->clnt_state.state = KRB_STATE_APP; + fd->svr_state.state = KRB_STATE_APP; + } +#ifdef DEBUG_KERBEROS + fd->clnt_state.last_state = KRB_STATE_INVALID; + fd->svr_state.last_state = KRB_STATE_INVALID; +#endif + } + + if (fd->need_continue) + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + else + { + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + if (getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + return SERVICE_SUCCESS; + } + + if (krb_walk_server_packet(&fd->svr_state, s, end, flowp, pkt, dir, fd->clnt_state.cname) == + KRB_FAILED) + { +#ifdef DEBUG_KERBEROS + DebugFormat(DEBUG_INSPECTOR,"%p Failed\n",flowp); +#endif + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_SUCCESS; + } + +inprocess: + service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + return SERVICE_INPROCESS; +} + diff --git a/src/network_inspectors/appid/detector_plugins/detector_pattern.cc b/src/network_inspectors/appid/detector_plugins/detector_pattern.cc new file mode 100644 index 000000000..f6b6d454a --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_pattern.cc @@ -0,0 +1,760 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_pattern.cc author Sourcefire Inc. + +#include "detector_pattern.h" +#include "app_info_table.h" +#include "client_plugins/client_app_api.h" +#include "service_plugins/service_api.h" + +#include "log/messages.h" +#include "main/snort_debug.h" +#include "utils/util.h" + +static int service_validate(ServiceValidationArgs* args); +static int csdPatternTreeSearch(const uint8_t* data, uint16_t size, IpProtocol protocol, + Packet* pkt, + const RNAServiceElement** serviceData, bool isClient, + const AppIdConfig* pConfig); +static int pattern_service_init(const IniServiceAPI* const iniServiceApi); +static void pattern_service_clean(const CleanServiceAPI* const clean_api); +static CLIENT_APP_RETCODE client_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE client_init_tcp(const IniClientAppAPI* const init_api, SF_LIST* config); +static CLIENT_APP_RETCODE client_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, + const AppIdConfig* pConfig); +static void client_clean(const CleanClientAppAPI* const clean_api); +static const IniServiceAPI* iniServiceApi; +static const IniClientAppAPI* iniClientApi; + +static RNAServiceElement svc_element = +{ + nullptr, + &service_validate, + nullptr, + DETECTOR_TYPE_PATTERN, + 1, + 1, + 0, + "pattern", +}; + +RNAServiceValidationModule pattern_service_mod = +{ + "pattern", + &pattern_service_init, + nullptr, + nullptr, + nullptr, + 0, + &pattern_service_clean, + 0 +}; + +// client side +RNAClientAppModule pattern_udp_client_mod = +{ + "pattern", + IpProtocol::UDP, + &client_init, + &client_clean, + &client_validate, + 0, + nullptr, + nullptr, + 0, + nullptr, + 0, + 0 +}; + +RNAClientAppModule pattern_tcp_client_mod = +{ + "pattern", + IpProtocol::TCP, + &client_init_tcp, + nullptr, + &client_validate, + 0, + nullptr, + nullptr, + 0, + nullptr, + 0, + 0 +}; + +static RNAServiceValidationPort pp = +{ + &service_validate, + 0, + IpProtocol::PROTO_NOT_SET, + 0 +}; + +static void FreePattern(Pattern* pattern) +{ + if (pattern) + { + if (pattern->data) + snort_free(pattern->data); + snort_free(pattern); + } +} + +static void FreePatternService(PatternService* ps) +{ + Pattern* pattern; + PortNode* port; + + if (ps) + { + while ((pattern = ps->pattern)) + { + ps->pattern = pattern->next; + FreePattern(pattern); + } + while ((port = ps->port)) + { + ps->port = port->next; + snort_free(port); + } + snort_free(ps); + } +} + +static void read_patterns(PortPatternNode* portPatternList, PatternService** serviceList) +{ + PatternService* ps = nullptr; + Pattern* pattern; + PortNode* port; + PortPatternNode* pNode; + char* lastName = nullptr; + short lastPort = 0; + IpProtocol lastProto = IpProtocol::PROTO_NOT_SET; + bool newPs; + + for (pNode = portPatternList; pNode; pNode = pNode->next) + { + newPs = false; + if (!ps || !lastName || strcmp(lastName, pNode->detectorName) + || lastProto != pNode->protocol) + { + ps = (PatternService*)snort_calloc(sizeof(PatternService)); + lastName = pNode->detectorName; + lastProto = pNode->protocol; + newPs = true; + ps->id = pNode->appId; + ps->proto = pNode->protocol; + ps->next = *serviceList; + *serviceList = ps; + } + + if (pNode->port && (newPs || lastPort != pNode->port)) + { + port = (PortNode*)snort_calloc(sizeof(PortNode)); + port->port = pNode->port; + port->next = ps->port; + lastPort = pNode->port; + ps->port = port; + } + + pattern = (Pattern*)snort_calloc(sizeof(Pattern)); + pattern->data = (uint8_t*)snort_calloc(pNode->length); + memcpy(pattern->data, pNode->pattern, pNode->length); + pattern->length = pNode->length; + if (pattern->length > ps->longest) + ps->longest = pattern->length; + pattern->ps = ps; + pattern->offset = pNode->offset; + pattern->next = ps->pattern; + ps->pattern = pattern; + + appInfoSetActive(ps->id, true); + } +} + +// Register ports for detectors which have a pattern associated with it. +static void install_ports(PatternService* serviceList, const IniServiceAPI* const iniServiceApi) +{ + PatternService* ps; + PortNode* port; + + for (ps = serviceList; ps; ps = ps->next) + { + if (!ps->port) + continue; + + for (port = ps->port; port; port = port->next) + { + pp.port = port->port; + pp.proto = (IpProtocol)ps->proto; + if (iniServiceApi->AddPort(&pp, &pattern_service_mod, iniServiceApi->pAppidConfig)) + ErrorMessage("Failed to add port - %d:%u:%d\n",ps->id, + (unsigned)pp.port, (uint8_t)pp.proto); + else + { + DebugFormat(DEBUG_LOG,"Installed ports - %d:%u:%u\n", + ps->id, (unsigned)pp.port, (unsigned)pp.proto); + } + } + } +} + +static void RegisterPattern(SearchTool** patterns, Pattern* pattern) +{ + if (!*patterns) + { + *patterns = new SearchTool("ac_full"); + if (!*patterns) + { + ErrorMessage("Error initializing the pattern table\n"); + return; + } + } + + (*patterns)->add((char*)pattern->data, pattern->length, pattern, false); +} + +// Creates unique subset of services registered on ports, and then creates pattern trees. +static void createServicePatternTrees(AppIdConfig* pConfig) +{ + PatternService* ps; + Pattern* pattern; + PortNode* port; + unsigned i; + + for (ps = pConfig->servicePortPattern->servicePortPattern; ps; ps = ps->next) + { + for (port = ps->port; port; port = port->next) + { + for (pattern = ps->pattern; pattern; pattern = pattern->next) + { + if (ps->proto == IpProtocol::TCP) + RegisterPattern(&pConfig->servicePortPattern->tcpPortPatternTree[port->port], + pattern); + else + RegisterPattern(&pConfig->servicePortPattern->udpPortPatternTree[port->port], + pattern); + } + } + } + for (i = 0; i < 65536; i++) + { + if (pConfig->servicePortPattern->tcpPortPatternTree[i]) + { + for (ps = pConfig->servicePortPattern->servicePortPattern; ps; ps = ps->next) + { + if (ps->port || (ps->proto != IpProtocol::TCP)) + continue; + + for (pattern = ps->pattern; pattern; pattern = pattern->next) + RegisterPattern(&pConfig->servicePortPattern->tcpPortPatternTree[i], pattern); + } + + pConfig->servicePortPattern->tcpPortPatternTree[i]->prep(); + } + if (pConfig->servicePortPattern->udpPortPatternTree[i]) + { + for (ps = pConfig->servicePortPattern->servicePortPattern; ps; ps = ps->next) + { + if (ps->port || (ps->proto != IpProtocol::UDP)) + continue; + + for (pattern = ps->pattern; pattern; pattern = pattern->next) + RegisterPattern(&pConfig->servicePortPattern->udpPortPatternTree[i], pattern); + } + + pConfig->servicePortPattern->udpPortPatternTree[i]->prep(); + } + } +} + +static void createClientPatternTrees( + AppIdConfig* pConfig + ) +{ + PatternService* ps; + Pattern* pattern; + + for (ps = pConfig->clientPortPattern->servicePortPattern; ps; ps = ps->next) + { + for (pattern = ps->pattern; pattern; pattern = pattern->next) + { + if (ps->proto == IpProtocol::TCP) + RegisterPattern(&pConfig->clientPortPattern->tcp_patterns, pattern); + else + RegisterPattern(&pConfig->clientPortPattern->udp_patterns, pattern); + } + } +} + +void registerServicePatterns( + AppIdConfig* pConfig + ) +{ + PatternService* ps; + Pattern* pattern; + + /**Register patterns with no associated ports, to RNA and local + * pattern tree. Register patterns with ports with local pattern + * tree only. + */ + for (ps = pConfig->servicePortPattern->servicePortPattern; ps; ps = ps->next) + { + if (!ps->port) + { + for (pattern = ps->pattern; pattern; pattern = pattern->next) + { + if (pattern->data && pattern->length) + { + if (ps->proto == IpProtocol::TCP) + { + DebugFormat(DEBUG_LOG,"Adding pattern with length %u\n",pattern->length); + iniServiceApi->RegisterPattern(&service_validate, IpProtocol::TCP, + pattern->data, pattern->length, + pattern->offset, "pattern", iniServiceApi->pAppidConfig); + RegisterPattern(&pConfig->servicePortPattern->tcp_patterns, pattern); + } + else + { + DebugFormat(DEBUG_LOG,"Adding pattern with length %u\n",pattern->length); + iniServiceApi->RegisterPattern(&service_validate, IpProtocol::UDP, + pattern->data, pattern->length, + pattern->offset, "pattern", iniServiceApi->pAppidConfig); + RegisterPattern(&pConfig->servicePortPattern->udp_patterns, pattern); + } + } + } + } + else + { + for (pattern = ps->pattern; pattern; pattern = pattern->next) + ps->count++; + } + } + if (pConfig->servicePortPattern->tcp_patterns) + { + pConfig->servicePortPattern->tcp_patterns->prep(); + } + if (pConfig->servicePortPattern->udp_patterns) + { + pConfig->servicePortPattern->udp_patterns->prep(); + } +} + +void registerClientPatterns( + AppIdConfig* pConfig + ) +{ + PatternService* ps; + Pattern* pattern; + + /**Register patterns with no associated ports, to RNA and local + * pattern tree. Register patterns with ports with local pattern + * tree only. + */ + for (ps = pConfig->clientPortPattern->servicePortPattern; ps; ps = ps->next) + { + for (pattern = ps->pattern; pattern; pattern = pattern->next) + { + if (pattern->data && pattern->length) + { + if (ps->proto == IpProtocol::TCP) + { + DebugFormat(DEBUG_LOG,"Adding pattern with length %u\n",pattern->length); + iniClientApi->RegisterPattern(&client_validate, IpProtocol::TCP, pattern->data, + pattern->length, + pattern->offset, iniClientApi->pAppidConfig); + RegisterPattern(&pConfig->clientPortPattern->tcp_patterns, pattern); + } + else + { + DebugFormat(DEBUG_LOG,"Adding pattern with length %u\n",pattern->length); + iniClientApi->RegisterPattern(&client_validate, IpProtocol::UDP, pattern->data, + pattern->length, pattern->offset, iniClientApi->pAppidConfig); + RegisterPattern(&pConfig->clientPortPattern->udp_patterns, pattern); + } + } + ps->count++; + } + } + if (pConfig->clientPortPattern->tcp_patterns) + { + pConfig->clientPortPattern->tcp_patterns->prep(); + } + if (pConfig->clientPortPattern->udp_patterns) + { + pConfig->clientPortPattern->udp_patterns->prep(); + } +} + +void dumpPatterns(const char* name, PatternService* pList) +{ + UNUSED(name); + PatternService* ps; + Pattern* pattern; + + /**Register patterns with no associated ports, to RNA and local + * pattern tree. Register patterns with ports with local pattern + * tree only. + */ + + DebugFormat(DEBUG_LOG,"Adding pattern for \"%s\"\n",name); + for (ps = pList; ps; ps = ps->next) + { + for (pattern = ps->pattern; pattern; pattern = pattern->next) + { + DebugFormat(DEBUG_LOG,"\t%s, %d\n",pattern->data, pattern->length); + if (pattern->data && pattern->length) + { + DebugFormat(DEBUG_LOG,"\t\t%s, %d\n",pattern->data, pattern->length); + } + } + } +} + +int portPatternFinalize(AppIdConfig* pConfig) +{ + if (pConfig->clientPortPattern) + { + read_patterns(pConfig->clientPortPattern->luaInjectedPatterns, + &pConfig->clientPortPattern->servicePortPattern); + createClientPatternTrees(pConfig); + registerClientPatterns(pConfig); + dumpPatterns("Client", pConfig->clientPortPattern->servicePortPattern); + } + if (pConfig->servicePortPattern) + { + read_patterns(pConfig->servicePortPattern->luaInjectedPatterns, + &pConfig->servicePortPattern->servicePortPattern); + install_ports(pConfig->servicePortPattern->servicePortPattern, iniServiceApi); + createServicePatternTrees(pConfig); + registerServicePatterns(pConfig); + dumpPatterns("Server", pConfig->servicePortPattern->servicePortPattern); + } + + return 0; +} + +static int pattern_service_init(const IniServiceAPI* const init_api) +{ + iniServiceApi = init_api; + + DebugFormat(DEBUG_LOG,"Initializing with instance %u\n",iniServiceApi->instance_id); + + return 0; +} + +static void pattern_service_clean(const CleanServiceAPI* const clean_api) +{ + PatternService* ps; + AppIdConfig* pConfig = clean_api->pAppidConfig; + + if (pConfig->servicePortPattern && pConfig->servicePortPattern->servicePortPattern) + { + unsigned i; + + if (pConfig->servicePortPattern->tcp_patterns) + { + delete pConfig->servicePortPattern->tcp_patterns; + pConfig->servicePortPattern->tcp_patterns = nullptr; + } + if (pConfig->servicePortPattern->udp_patterns) + { + delete pConfig->servicePortPattern->udp_patterns; + pConfig->servicePortPattern->udp_patterns = nullptr; + } + for (i = 0; i < 65536; i++) + { + if (pConfig->servicePortPattern->tcpPortPatternTree[i]) + { + delete pConfig->servicePortPattern->tcpPortPatternTree[i]; + pConfig->servicePortPattern->tcpPortPatternTree[i] = nullptr; + } + if (pConfig->servicePortPattern->udpPortPatternTree[i]) + { + delete pConfig->servicePortPattern->udpPortPatternTree[i]; + pConfig->servicePortPattern->udpPortPatternTree[i] = nullptr; + } + } + while (pConfig->servicePortPattern->servicePortPattern) + { + ps = pConfig->servicePortPattern->servicePortPattern; + pConfig->servicePortPattern->servicePortPattern = ps->next; + FreePatternService(ps); + } + } +} + +struct PServiceMatch +{ + /**Matches are aggregated by PatternService first and then by patterns. next is used to walk + matches by PatternService*/ + PServiceMatch* next; + + /**Walks matches by pattern within a PatternService. */ + PServiceMatch* ps_next; + + Pattern* data; +}; + +static PServiceMatch* free_servicematch_list; + +static int pattern_match(void* id, void*, int index, void* data, void*) +{ + PServiceMatch** matches = (PServiceMatch**)data; + Pattern* pd = (Pattern*)id; + PServiceMatch* psm; + PServiceMatch* sm; + + if (pd->offset >= 0 && pd->offset != index) + return 0; + + /*find if previously this PS was matched. */ + for (psm=*matches; psm; psm=psm->next) + if (psm->data->ps == pd->ps) + break; + + if (psm) + { + /*walks patterns within a PatternService. */ + for (sm=psm; sm; sm=sm->ps_next) + if (sm->data == pd) + return 0; + + if (free_servicematch_list) + { + sm = free_servicematch_list; + free_servicematch_list = sm->next; + memset(sm, 0, sizeof(*sm)); + } + else + sm = (PServiceMatch*)snort_calloc(sizeof(PServiceMatch)); + + sm->data = pd; + sm->ps_next = psm->ps_next; + psm->ps_next = sm; + return 0; + } + else if (free_servicematch_list) + { + sm = free_servicematch_list; + free_servicematch_list = sm->next; + memset(sm, 0, sizeof(*sm)); + } + else + sm = (PServiceMatch*)snort_calloc(sizeof(PServiceMatch)); + + sm->data = pd; + sm->next = *matches; + *matches = sm; + return 0; +} + +static int csdPatternTreeSearch(const uint8_t* data, uint16_t size, IpProtocol protocol, + Packet* pkt, + const RNAServiceElement** serviceData, bool isClient, + const AppIdConfig* pConfig) +{ + SearchTool* patternTree = nullptr; + PatternService* ps; + PServiceMatch* matches = nullptr; + PServiceMatch* sm; + PServiceMatch* psm; + Pattern* pattern; + + if (!data || !pkt || !size) + return 0; + + *serviceData = nullptr; + + if (!isClient) + { + if (protocol == IpProtocol::UDP) + patternTree = pConfig->servicePortPattern->udpPortPatternTree[pkt->ptrs.sp]; + else + patternTree = pConfig->servicePortPattern->tcpPortPatternTree[pkt->ptrs.sp]; + } + + if (!patternTree) + { + if (protocol == IpProtocol::UDP) + patternTree = (isClient) ? pConfig->clientPortPattern->udp_patterns : + pConfig->servicePortPattern->udp_patterns; + else + patternTree = (isClient) ? pConfig->clientPortPattern->tcp_patterns : + pConfig->servicePortPattern->tcp_patterns; + } + + if (patternTree) + { + patternTree->find_all((char*)data, size, &pattern_match, false, (void*)&matches); + } + + if (matches == nullptr) + return 0; + + /* match highest count and then longest pattern. */ + ps = nullptr; + for (sm = matches; sm; sm = sm->next) + { + /* walk all patterns in PatternService */ + for (pattern = sm->data->ps->pattern; pattern; pattern = pattern->next) + { + for (psm = sm; psm; psm = psm->ps_next) + if (pattern == psm->data) + break; + if (psm == nullptr) + break; + } + + if (pattern == nullptr) /*all patterns in PatternService were matched */ + { + if (ps) + { + if (sm->data->ps->count > ps->count) + ps = sm->data->ps; + else if (sm->data->ps->count == ps->count && sm->data->ps->longest > ps->longest) + ps = sm->data->ps; + } + else + ps = sm->data->ps; + } + } + + /*free match list */ + while (matches) + { + while (matches->ps_next) + { + sm = matches->ps_next; + matches->ps_next = sm->ps_next; + sm->next = free_servicematch_list; + free_servicematch_list = sm; + } + sm = matches; + matches = sm->next; + sm->next = free_servicematch_list; + free_servicematch_list = sm; + } + + if (ps == nullptr) + return 0; + *serviceData = &svc_element; + return ps->id; +} + +static int service_validate(ServiceValidationArgs* args) +{ + uint32_t id; + const RNAServiceElement* service = nullptr; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + + if (!data || !pattern_service_mod.api || !flowp || !pkt) + return SERVICE_ENULL; + if (!size) + goto inprocess; + if (dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + id = csdPatternTreeSearch(data, size, flowp->proto, pkt, &service, false, args->pConfig); + if (!id) + goto fail; + + pattern_service_mod.api->add_service(flowp, pkt, dir, &svc_element, id, nullptr, nullptr, + nullptr); + return SERVICE_SUCCESS; + +inprocess: + pattern_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +fail: + pattern_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + pattern_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOMATCH; +} + +static CLIENT_APP_RETCODE client_init(const IniClientAppAPI* const init_api, SF_LIST*) +{ + iniClientApi = init_api; + + return CLIENT_APP_SUCCESS; +} + +static CLIENT_APP_RETCODE client_init_tcp(const IniClientAppAPI* const, SF_LIST*) +{ + return CLIENT_APP_SUCCESS; +} + +static void client_clean(const CleanClientAppAPI* const clean_api) +{ + AppIdConfig* pConfig = clean_api->pAppidConfig; + + if (pConfig->clientPortPattern && pConfig->clientPortPattern->servicePortPattern) + { + if (pConfig->clientPortPattern->tcp_patterns) + { + delete pConfig->clientPortPattern->tcp_patterns; + pConfig->clientPortPattern->tcp_patterns = nullptr; + } + + if (pConfig->clientPortPattern->udp_patterns) + { + delete pConfig->clientPortPattern->udp_patterns; + pConfig->clientPortPattern->udp_patterns = nullptr; + } + } +} + +static CLIENT_APP_RETCODE client_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector*, const AppIdConfig* pConfig) +{ + AppId id; + const RNAServiceElement* service = nullptr; + + if (!data || !flowp || !pkt) + return CLIENT_APP_ENULL; + if (!size) + goto inprocess; + if (dir == APP_ID_FROM_RESPONDER) + goto inprocess; + + id = csdPatternTreeSearch(data, size, flowp->proto, pkt, &service, true, + (AppIdConfig*)pConfig); + if (!id) + goto fail; + + pattern_tcp_client_mod.api->add_app(flowp, id, id, nullptr); + return CLIENT_APP_SUCCESS; + +inprocess: + return CLIENT_APP_INPROCESS; + +fail: + return CLIENT_APP_EINVALID; +} + diff --git a/src/network_inspectors/appid/detector_plugins/detector_pattern.h b/src/network_inspectors/appid/detector_plugins/detector_pattern.h new file mode 100644 index 000000000..a7a51ec5d --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_pattern.h @@ -0,0 +1,90 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_pattern.h author Sourcefire Inc. + +#ifndef DETECTOR_PATTERN_H +#define DETECTOR_PATTERN_H + +#include "appid_api.h" +#include "detector_api.h" + +extern RNAServiceValidationModule pattern_service_mod; + +struct PortPatternNode +{ + AppId appId; + IpProtocol protocol; + unsigned short port; + unsigned char* pattern; + unsigned length; + int32_t offset; + char* detectorName; + PortPatternNode* next; +}; + +struct PatternService; +struct Pattern +{ + Pattern* next; + unsigned length; + int offset; + uint8_t* data; + PatternService* ps; +}; + +struct PortNode // FIXIT this name changed from "Port" which is already in use by Snort++ +{ + PortNode* next; + uint16_t port; +}; + +/**list for pattern services. Each pattern service is unique for a given uuid. */ +struct PatternService +{ + PatternService* next; + AppId id; + Pattern* pattern; + PortNode* port; + IpProtocol proto; + unsigned count; + unsigned longest; +}; + +class SearchTool; +struct ServicePortPattern +{ + PortPatternNode* luaInjectedPatterns; + PatternService* servicePortPattern; + SearchTool* tcp_patterns; + SearchTool* udp_patterns; + SearchTool* tcpPortPatternTree[65536]; + SearchTool* udpPortPatternTree[65536]; +}; + +struct ClientPortPattern +{ + PortPatternNode* luaInjectedPatterns; + PatternService* servicePortPattern; + SearchTool* tcp_patterns; + SearchTool* udp_patterns; +}; + +#endif + diff --git a/src/network_inspectors/appid/detector_plugins/detector_pop3.cc b/src/network_inspectors/appid/detector_plugins/detector_pop3.cc new file mode 100644 index 000000000..3e2089d1b --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_pop3.cc @@ -0,0 +1,959 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_pop3.cc author Sourcefire Inc. + +#include "app_info_table.h" +#include "application_ids.h" +#include "client_plugins/client_app_api.h" +#include "detector_plugins/detector_api.h" +#include "service_plugins/service_api.h" +#include "service_plugins/service_util.h" + +#include "log/messages.h" +#include "main/snort_debug.h" +#include "search_engines/search_tool.h" +#include "utils/util.h" + +/*#define DEBUG_POP3 1 */ + +struct POP3_CLIENT_APP_CONFIG +{ + int enabled; +}; + +enum POP3ClientState +{ + POP3_CLIENT_STATE_AUTH, // POP3 - AUTHORIZATION state + POP3_CLIENT_STATE_TRANS, // POP3 - TRANSACTION state + POP3_CLIENT_STATE_STLS_CMD // POP3 - AUTHORIZATION hybrid state (probable POP3S) +}; + +struct ClientPOP3Data +{ + int auth; + char* username; + POP3ClientState state; + int set_flags; + int detected; + int got_user; +}; + +// static const unsigned MIN_POP3_CMDS = 3; + +// FIXIT-L THREAD_LOCAL? +static POP3_CLIENT_APP_CONFIG pop3_config; + +static CLIENT_APP_RETCODE pop3_ca_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static void pop3_ca_clean(const CleanClientAppAPI* const clean_api); +static CLIENT_APP_RETCODE pop3_ca_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, + const AppIdConfig* pConfig); + +static RNAClientAppModule client_app_mod = +{ + "POP3", + IpProtocol::TCP, + &pop3_ca_init, + &pop3_ca_clean, + &pop3_ca_validate, + 1, + nullptr, + nullptr, + 0, + nullptr, + 1, + 0 +}; + +struct Client_App_Pattern +{ + const uint8_t* pattern; + unsigned length; + int eoc; +}; + +static const uint8_t APOP[] = "APOP "; +static const uint8_t DELE[] = "DELE "; +static const uint8_t LISTC[] = "LIST "; +static const uint8_t LISTEOC[] = "LIST\x00d\x00a"; +static const uint8_t LISTEOC2[] = "LIST\x00a"; +static const uint8_t NOOP[] = "NOOP\x00d\x00a"; +static const uint8_t NOOP2[] = "NOOP\x00a"; +static const uint8_t QUIT[] = "QUIT\x00d\x00a"; +static const uint8_t QUIT2[] = "QUIT\x00a"; +static const uint8_t RETR[] = "RETR "; +static const uint8_t STAT[] = "STAT\x00d\x00a"; +static const uint8_t STAT2[] = "STAT\x00a"; +static const uint8_t RSET[] = "RSET\x00d\x00a"; +static const uint8_t RSET2[] = "RSET\x00a"; +static const uint8_t TOP[] = "TOP "; +static const uint8_t UIDL[] = "UIDL "; +static const uint8_t UIDLEOC[] = "UIDL\x00d\x00a"; +static const uint8_t UIDLEOC2[] = "UIDL\x00a"; +static const uint8_t USER[] = "USER "; +static const uint8_t PASS[] = "PASS "; +static const uint8_t CAPA[] = "CAPA\x00d\x00a"; +static const uint8_t CAPA2[] = "CAPA\x00a"; +static const uint8_t AUTH[] = "AUTH "; +static const uint8_t AUTHEOC[] = "AUTH\x00d\x00a"; +static const uint8_t AUTHEOC2[] = "AUTH\x00a"; +static const uint8_t AUTHEOC3[] = "AUTH \x00d\x00a"; +static const uint8_t AUTHEOC4[] = "AUTH \x00a"; +static const uint8_t STLSEOC[] = "STLS\x00d\x00a"; +static const uint8_t STLSEOC2[] = "STLS\x00a"; + +enum Client_App_Pattern_Index +{ + /* order MUST correspond to that in the array, patterns[], below */ + PATTERN_USER, + PATTERN_PASS, + PATTERN_APOP, + PATTERN_AUTH, + PATTERN_AUTHEOC, + PATTERN_AUTHEOC2, + PATTERN_AUTHEOC3, + PATTERN_AUTHEOC4, + PATTERN_STLSEOC, + PATTERN_STLSEOC2, + PATTERN_POP3_OTHER // always last +}; + +static Client_App_Pattern patterns[] = +{ + { USER, sizeof(USER)-1, 0 }, + { PASS, sizeof(PASS)-1, 0 }, + { APOP, sizeof(APOP)-1, 0 }, + { AUTH, sizeof(AUTH)-1, 0 }, + { AUTHEOC, sizeof(AUTHEOC)-1, 1 }, + { AUTHEOC2, sizeof(AUTHEOC2)-1, 1 }, + { AUTHEOC3, sizeof(AUTHEOC3)-1, 1 }, + { AUTHEOC4, sizeof(AUTHEOC4)-1, 1 }, + { STLSEOC, sizeof(STLSEOC)-1, 1 }, + { STLSEOC2, sizeof(STLSEOC2)-1, 1 }, + /* These are represented by index >= PATTERN_POP3_OTHER */ + { DELE, sizeof(DELE)-1, 0 }, + { LISTC, sizeof(LISTC)-1, 0 }, + { LISTEOC, sizeof(LISTEOC)-1, 1 }, + { LISTEOC2, sizeof(LISTEOC2)-1, 1 }, + { NOOP, sizeof(NOOP)-1, 1 }, + { NOOP2, sizeof(NOOP2)-1, 1 }, + { QUIT, sizeof(QUIT)-1, 1 }, + { QUIT2, sizeof(QUIT2)-1, 1 }, + { RETR, sizeof(RETR)-1, 0 }, + { STAT, sizeof(STAT)-1, 1 }, + { STAT2, sizeof(STAT2)-1, 1 }, + { RSET, sizeof(RSET)-1, 1 }, + { RSET2, sizeof(RSET2)-1, 1 }, + { TOP, sizeof(TOP)-1, 0 }, + { UIDL, sizeof(UIDL)-1, 0 }, + { UIDLEOC, sizeof(UIDLEOC)-1, 1 }, + { UIDLEOC2, sizeof(UIDLEOC2)-1, 1 }, + { CAPA, sizeof(CAPA)-1, 1 }, + { CAPA2, sizeof(CAPA2)-1, 1 }, +}; + +// FIXIT-L THREAD_LOCAL? +static size_t longest_pattern; + +static const unsigned POP3_PORT = 110; + +static const unsigned POP3_COUNT_THRESHOLD = 4; + +static const char* const POP3_OK = "+OK"; +static const char* const POP3_ERR = "-ERR"; +static const char* const POP3_TERM = ".\x00D\x00A"; + +enum POP3State +{ + POP3_STATE_CONNECT, + POP3_STATE_RESPONSE, + POP3_STATE_CONTINUE +}; + +static const unsigned MAX_VERSION_SIZE = 64; +struct ServicePOP3Data +{ + POP3State state; + unsigned count; + const char* vendor; + char version[MAX_VERSION_SIZE]; + RNAServiceSubtype* subtype; + int error; +}; + +static int pop3_init(const IniServiceAPI* const init_api); +static int pop3_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &pop3_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "pop3" +}; + +static RNAServiceValidationPort pp[] = +{ + { &pop3_validate, POP3_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +static RNAServiceValidationModule service_mod = +{ + "POP3", + &pop3_init, + pp, + nullptr, + nullptr, + 1, + nullptr, + 0 +}; + +struct POP3DetectorData +{ + ClientPOP3Data client; + ServicePOP3Data server; + int need_continue; +}; + +SO_PUBLIC RNADetectorValidationModule pop3_detector_mod = +{ + &service_mod, + &client_app_mod, + nullptr, + 0, + nullptr +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_POP3, APPINFO_FLAG_SERVICE_ADDITIONAL | APPINFO_FLAG_CLIENT_USER }, + { APP_ID_POP3S, APPINFO_FLAG_SERVICE_ADDITIONAL | APPINFO_FLAG_CLIENT_USER } +}; + +static CLIENT_APP_RETCODE pop3_ca_init(const IniClientAppAPI* const init_api, SF_LIST* config) +{ + unsigned i; + RNAClientAppModuleConfigItem* item; + SearchTool* cmd_matcher = new SearchTool("ac_full"); + + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + cmd_matcher->add(patterns[i].pattern, patterns[i].length, &patterns[i]); + if (patterns[i].length > longest_pattern) + longest_pattern = patterns[i].length; + } + cmd_matcher->prep(); + + init_api->pAppidConfig->add_generic_config_element(client_app_mod.name, cmd_matcher); + + pop3_config.enabled = 1; + + if (config) + { + SF_LNODE* iter = nullptr; + + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &iter); + item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&iter)) + { + DebugFormat(DEBUG_INSPECTOR,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + pop3_config.enabled = atoi(item->value); + } + } + } + + if (pop3_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering pattern: %s\n", + (const char*)patterns[i].pattern); + init_api->RegisterPatternNoCase(&pop3_ca_validate, IpProtocol::TCP, + patterns[i].pattern, + patterns[i].length, 0, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&pop3_ca_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static int pop3_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPatternUser(&pop3_validate, IpProtocol::TCP, (uint8_t*)POP3_OK, + sizeof(POP3_OK)-1, 0, "pop3", init_api->pAppidConfig); + init_api->RegisterPatternUser(&pop3_validate, IpProtocol::TCP, (uint8_t*)POP3_ERR, + sizeof(POP3_ERR)-1, 0, "pop3", init_api->pAppidConfig); + + unsigned j; + for (j=0; j < sizeof(appIdRegistry)/sizeof(*appIdRegistry); j++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[j].appId); + init_api->RegisterAppId(&pop3_validate, appIdRegistry[j].appId, + appIdRegistry[j].additionalInfo, init_api->pAppidConfig); + } + return 0; +} + +static void pop3_ca_clean(const CleanClientAppAPI* const clean_api) +{ + SearchTool* cmd_matcher = + (SearchTool*)clean_api->pAppidConfig->find_generic_config_element(client_app_mod.name); + if (cmd_matcher) + delete cmd_matcher; + clean_api->pAppidConfig->remove_generic_config_element(client_app_mod.name); +} + +static int pop3_pattern_match(void* id, void*, int index, void* data, void*) +{ + Client_App_Pattern** pcmd; + unsigned long idx = (unsigned long)id; + + if (index) + return 0; + pcmd = (Client_App_Pattern**)data; + *pcmd = &patterns[idx]; + return 1; +} + +static void pop3_free_state(void* data) +{ + POP3DetectorData* dd = (POP3DetectorData*)data; + ClientPOP3Data* cd; + ServicePOP3Data* sd; + RNAServiceSubtype* sub; + + if (dd) + { + sd = &dd->server; + while (sd->subtype) + { + sub = sd->subtype; + sd->subtype = sub->next; + if (sub->service) + snort_free((void*)sub->service); + if (sub->version) + snort_free((void*)sub->version); + snort_free(sub); + } + cd = &dd->client; + if (cd->username) + snort_free(cd->username); + snort_free(dd); + } +} + +static int pop3_check_line(const uint8_t** data, const uint8_t* end) +{ + /* Line in the form (textCRLF) */ + for (; (*data)server; + const uint8_t* begin = nullptr; + const uint8_t* end; + const uint8_t* line_end; + const uint8_t* p; + const uint8_t* p2; + const uint8_t* ver; + const uint8_t* rel; + const uint8_t* s; + unsigned len; + char* v; + char* v_end; + + end = data + size; + v_end = pd->version; + v_end += MAX_VERSION_SIZE - 1; + switch (pd->state) + { + case POP3_STATE_CONNECT: + pd->state = POP3_STATE_RESPONSE; + begin = data; + case POP3_STATE_RESPONSE: + if (!begin && data[0] == '+' && data[1] == ' ') + { + data += 2; + if (pop3_check_line(&data, end)) + return -1; + if (data != end) + return -1; + return 0; + } + if (size < sizeof(POP3_ERR)) + return -1; + + if (!strncmp((char*)data, POP3_OK, sizeof(POP3_OK)-1)) + { + data += sizeof(POP3_OK) - 1; + pd->error = 0; + } + else if (!strncmp((char*)data, POP3_ERR, sizeof(POP3_ERR)-1)) + { + begin = nullptr; + data += sizeof(POP3_ERR) - 1; + pd->error = 1; + } + else + return -1; + if (pop3_check_line(&data, end) < 0) + return -1; + if (dd->client.state == POP3_CLIENT_STATE_STLS_CMD) + { + if (pd->error) + { + /* We failed to transition to POP3S - fall back to normal POP3 state, AUTHORIZATION + */ + dd->client.state = POP3_CLIENT_STATE_AUTH; + } + else + { + setAppIdFlag(flowp, APPID_SESSION_ENCRYPTED); + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + /* we are potentially overriding the APP_ID_POP3 assessment that was made earlier. + */ + client_app_mod.api->add_app(flowp, APP_ID_POP3S, APP_ID_POP3S, nullptr); // sets + // APPID_SESSION_CLIENT_DETECTED + } + } + else if (dd->client.username) // possible only with non-TLS authentication therefor: + // APP_ID_POP3 + { + if (pd->error) + { + service_mod.api->add_user(flowp, dd->client.username, APP_ID_POP3, 0); + snort_free(dd->client.username); + dd->client.username = nullptr; + } + else + { + service_mod.api->add_user(flowp, dd->client.username, APP_ID_POP3, 1); + snort_free(dd->client.username); + dd->client.username = nullptr; + dd->need_continue = 0; + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + dd->client.got_user = 1; + if (dd->client.detected) + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + } + } + if (server && begin) + { + line_end = &data[-1]; + len = line_end - begin; + if ((p=service_strstr(begin, len, (unsigned char*)ven_cppop, sizeof(ven_cppop)-1))) + { + pd->vendor = ven_cppop; + p += (sizeof(ven_cppop) - 1); + if (*p == ' ') + { + p++; + v = pd->version; + for (; p < line_end && *p && *p != ']'; p++) + { + if (v < v_end) + { + *v = *p; + v++; + } + } + if (p < line_end && *p) + *v = 0; + else + pd->version[0] = 0; + } + } + else if ((p=service_strstr(begin, len, (unsigned char*)ven_cc, sizeof(ven_cc)-1))) + { + pd->vendor = ven_cc; + p += (sizeof(ven_cc) - 1); + if (line_end-p >= (int)sizeof(ver_cc)-1 && memcmp(p, ver_cc, sizeof(ver_cc)-1) == + 0) + { + p += sizeof(ver_cc) - 1; + v = pd->version; + for (; p < line_end && *p && *p != ' '; p++) + { + if (v < v_end) + { + *v = *p; + v++; + } + } + if (p < line_end && *p) + *v = 0; + else + pd->version[0] = 0; + } + } + else if (service_strstr(begin, len, (unsigned char*)ven_im, sizeof(ven_im)-1)) + pd->vendor = ven_im; + else if ((p=service_strstr(begin, len, (unsigned char*)ven_po, sizeof(ven_po)-1))) + { + RNAServiceSubtype* sub; + + pd->vendor = ven_po; + p += (sizeof(ven_po) - 1); + if (line_end-p < (int)sizeof(ver_po)-1 || memcmp(p, ver_po, sizeof(ver_po)-1) != 0) + goto ven_ver_done; + p += sizeof(ver_po) - 1; + ver = p; + for (; p < line_end && *p && *p != ' '; p++) + ; + if (p == ver || p >= line_end || !(*p)) + goto ven_ver_done; + if (line_end-p < (int)sizeof(ver_po2)-1 || memcmp(p, ver_po2, sizeof(ver_po2)-1) != + 0) + { + /* Does not have release */ + v = pd->version; + for (; ver < p && *ver; ver++) + { + if (v < v_end) + { + *v = *ver; + v++; + } + else + break; + } + *v = 0; + goto ven_ver_done; + } + /* Move past release and look for number followed by a space */ + p2 = p + sizeof(ver_po2) - 1; + rel = p2; + for (; p2 < line_end && *p2 && *p2 != ' '; p2++) + ; + if (p2 >= line_end || p2 == rel || !(*p2)) + { + v = pd->version; + for (; ver < p && *ver; ver++) + { + if (v < v_end) + { + *v = *ver; + v++; + } + else + break; + } + *v = 0; + goto ven_ver_done; + } + v = pd->version; + for (; ver < p2 && *ver; ver++) + { + if (v < v_end) + { + *v = *ver; + v++; + } + else + break; + } + *v = 0; + if (line_end-p2 < (int)sizeof(sub_po)-1 || memcmp(p2, sub_po, sizeof(sub_po)-1) != + 0) + goto ven_ver_done; + s = p2 + (sizeof(sub_po) - 1); + for (p=s; p < line_end && *p && *p != ' '; p++) + ; + if (p == s || p >= line_end || !(*p)) + goto ven_ver_done; + sub = (RNAServiceSubtype*)snort_calloc(sizeof(RNAServiceSubtype)); + unsigned sub_len; + + sub_len = p - s; + sub->service = (const char*)snort_calloc(sub_len+1); + memcpy((char*)sub->service, s, sub_len); + ((char*)sub->service)[sub_len] = 0; + sub->next = pd->subtype; + pd->subtype = sub; + if (line_end-p > (int)sizeof(subver_po)-1 + && memcmp(p, subver_po, sizeof(subver_po)-1) == 0) + { + s = p + (sizeof(subver_po) - 1); + for (p=s; p < line_end && *p && *p != ' '; p++) + ; + if (p != s && p < line_end && *p) + { + sub_len = p - s; + sub->version = (const char*)snort_calloc(sub_len+1); + memcpy((char*)sub->version, s, sub_len); + ((char*)sub->version)[sub_len] = 0; + } + } + } +ven_ver_done:; + } + if (data >= end) + { + pd->count++; + return 0; + } + pd->state = POP3_STATE_CONTINUE; + /* Fall through */ + + case POP3_STATE_CONTINUE: + while (data < end) + { + if ((end-data) == (sizeof(POP3_TERM)-1) && + !strncmp((char*)data, POP3_TERM, sizeof(POP3_TERM)-1)) + { + pd->count++; + pd->state = POP3_STATE_RESPONSE; + return 0; + } + if (pop3_check_line(&data, end) < 0) + return -1; + } + return 0; + } + return 0; +} + +static CLIENT_APP_RETCODE pop3_ca_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet*, struct Detector*, const AppIdConfig* pConfig) +{ + const uint8_t* s = data; + const uint8_t* end = (data + size); + unsigned length; + Client_App_Pattern* cmd; + POP3DetectorData* dd; + ClientPOP3Data* fd; + + if (!size) + return CLIENT_APP_INPROCESS; + +#ifdef APP_ID_USES_REASSEMBLED + pop3_detector_mod.streamAPI->response_flush_stream(pkt); +#endif + + dd = (POP3DetectorData*)pop3_detector_mod.api->data_get(flowp, + pop3_detector_mod.flow_data_index); + if (!dd) + { + dd = (POP3DetectorData*)snort_calloc(sizeof(POP3DetectorData)); + pop3_detector_mod.api->data_add(flowp, dd, + pop3_detector_mod.flow_data_index, &pop3_free_state); + dd->server.state = POP3_STATE_CONNECT; + fd = &dd->client; + fd->state = POP3_CLIENT_STATE_AUTH; + } + else + fd = &dd->client; + + if (!fd->set_flags) + { + dd->need_continue = 1; + fd->set_flags = 1; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + + if (dir == APP_ID_FROM_RESPONDER) + { +#ifdef DEBUG_POP3 + DebugFormat(DEBUG_INSPECTOR,"%p Calling server\n",flowp); + DumpHex(SF_DEBUG_FILE, data, size); +#endif + + if (pop3_server_validate(dd, data, size, flowp, 0)) + clearAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + return CLIENT_APP_INPROCESS; + } + +#ifdef DEBUG_POP3 + DebugFormat(DEBUG_INSPECTOR,"%p Client\n",flowp); + DumpHex(SF_DEBUG_FILE, data, size); +#endif + + while ((length = (end - s))) + { + unsigned pattern_index; + SearchTool* cmd_matcher = + (SearchTool*)((AppIdConfig*)pConfig)->find_generic_config_element(client_app_mod.name); + + cmd = nullptr; + cmd_matcher->find_all((char*)s, (length > longest_pattern ? longest_pattern : length), + &pop3_pattern_match, false, (void*)&cmd); + + if (!cmd) + { + dd->need_continue = 0; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); + return CLIENT_APP_SUCCESS; + } + s += cmd->length; + + pattern_index = cmd - patterns; // diff of ptr into array and its base addr is the + // corresponding index. + switch (fd->state) + { + case POP3_CLIENT_STATE_STLS_CMD: + /* We failed to transition to POP3S - fall back to normal POP3 AUTHORIZATION state */ + fd->state = POP3_CLIENT_STATE_AUTH; + // fall through + + case POP3_CLIENT_STATE_AUTH: + switch (pattern_index) + { + case PATTERN_STLSEOC: + case PATTERN_STLSEOC2: + { + /* If the STLS command succeeds we will be in a TLS negotiation state. + Wait for the "+OK" from the server using this STLS hybrid state. */ + fd->state = POP3_CLIENT_STATE_STLS_CMD; + /* skip extra CRLFs */ + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + } + break; + case PATTERN_APOP: + case PATTERN_USER: + { + char username[((255 - (sizeof(USER) - 1)) - 2) + 1]; + char* p = username; + char* p_end = p + sizeof(username) - 1; + int found_tick = 0; + + for (; s < end && p < p_end; s++) + { + if (isalnum(*s) || *s == '.' || *s == '@' || *s == '-' || *s == '_') + { + if (!found_tick) + { + *p = *s; + p++; + } + } + else if (*s == '`') + found_tick = 1; + else if (*s == '\r' || *s == '\n' || *s == ' ') // test for space for APOP + // case + { + *p = 0; + if (username[0]) + { + if (fd->username) + snort_free(fd->username); + fd->username = snort_strdup(username); + } + break; + } + else + break; + } + if (pattern_index == PATTERN_APOP) + { + /* the APOP command contains the user AND the equivalent of a password. */ + fd->state = POP3_CLIENT_STATE_TRANS; + } + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + } + break; + + case PATTERN_AUTH: + /* the AUTH command, containing a parameter implies non-TLS security + negotiation */ + fd->state = POP3_CLIENT_STATE_TRANS; // look ahead for normal POP3 commands + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + // having skipped to the end of the line, fall through for the empty-line skip + + case PATTERN_AUTHEOC: // used with subsequent CAPA; no state change; + case PATTERN_AUTHEOC2: + case PATTERN_AUTHEOC3: // AUTH with nothing after, Mircosoft ext., is query-only + // behavior; no state change; + case PATTERN_AUTHEOC4: + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + break; + + case PATTERN_PASS: + if (fd->got_user) + { + fd->state = POP3_CLIENT_STATE_TRANS; + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + break; + } + // fall through because we are not changing to TRANSACTION state, yet + default: + { + if (!cmd->eoc) + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + } + break; + } // end of switch(pattern_index) + break; + + case POP3_CLIENT_STATE_TRANS: + if (pattern_index >= PATTERN_POP3_OTHER) + { + /* We stayed in non-secure mode and received a TRANSACTION-state command: POP3 + found */ + client_app_mod.api->add_app(flowp, APP_ID_POP3, APP_ID_POP3, nullptr); // sets + // APPID_SESSION_CLIENT_DETECTED + fd->detected = 1; + } + else + { + // ignore AUTHORIZATION-state commands while in TRANSACTION state + } + if (!cmd->eoc) + for (; (s < end) && *s != '\r' && *s != '\n'; s++) + ; + for (; (s < end) && (*s == '\r' || *s == '\n'); s++) + ; + break; + } + } + return CLIENT_APP_INPROCESS; +} + +static int pop3_validate(ServiceValidationArgs* args) +{ + POP3DetectorData* dd; + ServicePOP3Data* pd; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + +#ifdef APP_ID_USES_REASSEMBLED + pop3_detector_mod.streamAPI->response_flush_stream(pkt); +#endif + + if (dir != APP_ID_FROM_RESPONDER) + goto inprocess; + +#ifdef DEBUG_POP3 + DebugFormat(DEBUG_INSPECTOR,"%p Dir %d\n",flowp, dir); + DumpHex(SF_DEBUG_FILE, data, size); +#endif + + dd = (POP3DetectorData*)pop3_detector_mod.api->data_get(flowp, + pop3_detector_mod.flow_data_index); + if (!dd) + { + dd = (POP3DetectorData*)snort_calloc(sizeof(POP3DetectorData)); + pop3_detector_mod.api->data_add(flowp, dd, + pop3_detector_mod.flow_data_index, &pop3_free_state); + dd->client.state = POP3_CLIENT_STATE_AUTH; + pd = &dd->server; + pd->state = POP3_STATE_CONNECT; + } + else + pd = &dd->server; + + if (dd->need_continue) + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + else + { + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + if (getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + return SERVICE_SUCCESS; + } + + if (!pop3_server_validate(dd, data, size, flowp, 1)) + { + if (pd->count >= POP3_COUNT_THRESHOLD && !getAppIdFlag(flowp, + APPID_SESSION_SERVICE_DETECTED)) + { + service_mod.api->add_service_consume_subtype(flowp, pkt, dir, &svc_element, + dd->client.state == POP3_CLIENT_STATE_STLS_CMD ? APP_ID_POP3S : APP_ID_POP3, + pd->vendor, + pd->version[0] ? pd->version : nullptr, pd->subtype); + pd->subtype = nullptr; + return SERVICE_SUCCESS; + } + } + else if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + } + else + { + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_SUCCESS; + } + +inprocess:; + service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + return SERVICE_INPROCESS; +} + diff --git a/src/network_inspectors/appid/detector_plugins/detector_sip.cc b/src/network_inspectors/appid/detector_plugins/detector_sip.cc new file mode 100644 index 000000000..a2276dc9a --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_sip.cc @@ -0,0 +1,799 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_sip.cc author Sourcefire Inc. + +#include "detector_sip.h" + +#include "http_url_patterns.h" +#include "fw_appid.h" +#include "service_plugins/service_base.h" +#include "util/sf_mlmp.h" +#include "utils/util.h" + +#include "log/messages.h" +#include "main/snort_debug.h" +#include "service_inspectors/sip/sip_common.h" + +static const char SIP_REGISTER_BANNER[] = "REGISTER "; +static const char SIP_INVITE_BANNER[] = "INVITE "; +static const char SIP_CANCEL_BANNER[] = "CANCEL "; +static const char SIP_ACK_BANNER[] = "ACK "; +static const char SIP_BYE_BANNER[] = "BYE "; +static const char SIP_OPTIONS_BANNER[] = "OPTIONS "; +static const char SIP_BANNER[] = "SIP/2.0 "; +static const char SIP_BANNER_END[] = "SIP/2.0\x00d\x00a"; +#define SIP_REGISTER_BANNER_LEN (sizeof(SIP_REGISTER_BANNER)-1) +#define SIP_INVITE_BANNER_LEN (sizeof(SIP_INVITE_BANNER)-1) +#define SIP_CANCEL_BANNER_LEN (sizeof(SIP_CANCEL_BANNER)-1) +#define SIP_ACK_BANNER_LEN (sizeof(SIP_ACK_BANNER)-1) +#define SIP_BYE_BANNER_LEN (sizeof(SIP_BYE_BANNER)-1) +#define SIP_OPTIONS_BANNER_LEN (sizeof(SIP_OPTIONS_BANNER)-1) +#define SIP_BANNER_LEN (sizeof(SIP_BANNER)-1) +#define SIP_BANNER_END_LEN (sizeof(SIP_BANNER_END)-1) +#define SIP_BANNER_LEN (sizeof(SIP_BANNER)-1) + +#define USER_STRING "from: " +#define MAX_USER_POS ((int)sizeof(USER_STRING) - 2) + +static const char svc_name[] = "sip"; +static const unsigned SIP_PORT = 5060; + +// static const unsigned MAX_ADDRESS_SIZE = 16; +// static const unsigned MAX_CALLID_SIZE = 64; +static const unsigned MAX_VENDOR_SIZE = 64; +// static const unsigned MAX_PORT_SIZE = 6; + +enum SIPState +{ + SIP_STATE_INIT=0, + SIP_STATE_REGISTER, + SIP_STATE_CALL +}; + +// static const unsigned SIP_STATUS_OK = 200; + +// static const unsigned SIP_MAX_INFO_SIZE = 63; + +enum tSIP_FLAGS +{ + SIP_FLAG_SERVER_CHECKED = (1<< 0) +}; + +struct ClientSIPData +{ + void* owner; + SIPState state; + uint32_t flags; + char* userName; + char* clientUserAgent; + char* from; +}; + +struct SIP_CLIENT_APP_CONFIG +{ + int enabled; +}; + +// FIXIT-L THREAD_LOCAL? +static SIP_CLIENT_APP_CONFIG sip_config; + +static CLIENT_APP_RETCODE sip_client_init(const IniClientAppAPI* const init_api, SF_LIST* config); +static void sip_clean(const CleanClientAppAPI* const clean_api); +static CLIENT_APP_RETCODE sip_client_validate(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, Packet* pkt, Detector* userData, + const AppIdConfig* pConfig); +static CLIENT_APP_RETCODE sip_tcp_client_init(const IniClientAppAPI* const init_api, + SF_LIST* config); +static CLIENT_APP_RETCODE sip_tcp_client_validate(const uint8_t* data, uint16_t size, const int + dir, + AppIdData* flowp, Packet* pkt, Detector* userData, + const AppIdConfig* pConfig); +static int sipAppGeClientApp(void* patternMatcher, char* pattern, uint32_t patternLen, + AppId* ClientAppId, char** clientVersion); +static void sipUaClean(DetectorSipConfig* pConfig); +static void sipServerClean(DetectorSipConfig* pConfig); + +RNAClientAppModule sip_udp_client_mod = +{ + "SIP", + IpProtocol::UDP, + &sip_client_init, + &sip_clean, + &sip_client_validate, + 2, + nullptr, + nullptr, + 0, + nullptr, + 1, + 0 +}; +RNAClientAppModule sip_tcp_client_mod = +{ + "SIP", + IpProtocol::TCP, + &sip_tcp_client_init, + nullptr, + &sip_tcp_client_validate, + 2, + nullptr, + nullptr, + 0, + nullptr, + 1, + 0 +}; + +struct Client_App_Pattern +{ + const uint8_t* pattern; + unsigned length; + int index; + unsigned appId; +}; + +static Client_App_Pattern patterns[] = +{ + { (const uint8_t*)SIP_REGISTER_BANNER, sizeof(SIP_REGISTER_BANNER)-1, 0, APP_ID_SIP }, + { (const uint8_t*)SIP_INVITE_BANNER, sizeof(SIP_INVITE_BANNER)-1, 0, APP_ID_SIP }, + { (const uint8_t*)SIP_CANCEL_BANNER, sizeof(SIP_CANCEL_BANNER)-1, 0, APP_ID_SIP }, + { (const uint8_t*)SIP_ACK_BANNER, sizeof(SIP_ACK_BANNER)-1, 0, APP_ID_SIP }, + { (const uint8_t*)SIP_BYE_BANNER, sizeof(SIP_BYE_BANNER)-1, 0, APP_ID_SIP }, + { (const uint8_t*)SIP_OPTIONS_BANNER, sizeof(SIP_OPTIONS_BANNER)-1, 0, APP_ID_SIP }, + { (const uint8_t*)SIP_BANNER, sizeof(SIP_BANNER)-1, 0, APP_ID_SIP }, + { (const uint8_t*)SIP_BANNER_END, sizeof(SIP_BANNER_END)-1, -1, APP_ID_SIP }, +}; + +static AppRegistryEntry appIdClientRegistry[] = +{ + { APP_ID_SIP, APPINFO_FLAG_CLIENT_ADDITIONAL|APPINFO_FLAG_CLIENT_USER }, +}; + +static AppRegistryEntry appIdServiceRegistry[] = +{ + { APP_ID_SIP, APPINFO_FLAG_SERVICE_ADDITIONAL|APPINFO_FLAG_CLIENT_USER }, + { APP_ID_RTP, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +//service side +struct ServiceSIPData +{ + uint8_t serverPkt; + char vendor[MAX_VENDOR_SIZE]; +}; + +static int sip_service_init(const IniServiceAPI* const init_api); +static int sip_service_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &sip_service_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "sip" +}; + +static RNAServiceValidationPort pp[] = +{ + { &sip_service_validate, SIP_PORT, IpProtocol::TCP, 0 }, + { &sip_service_validate, SIP_PORT, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +SO_PUBLIC RNAServiceValidationModule sip_service_mod = +{ + svc_name, + &sip_service_init, + pp, + nullptr, + nullptr, + 1, + nullptr, + 0 +}; + +static CLIENT_APP_RETCODE sip_client_init(const IniClientAppAPI* const init_api, SF_LIST*) +{ + unsigned i; + + /*configuration is read by sip_tcp_init(), which is called first */ + + if (sip_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&sip_client_validate, IpProtocol::UDP, patterns[i].pattern, + patterns[i].length, patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdClientRegistry)/sizeof(*appIdClientRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdClientRegistry[j].appId); + init_api->RegisterAppId(&sip_client_validate, appIdClientRegistry[j].appId, + appIdClientRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + if (init_api->pAppidConfig->detectorSipConfig.sipUaMatcher) + { + sipUaClean(&init_api->pAppidConfig->detectorSipConfig); + } + if (init_api->pAppidConfig->detectorSipConfig.sipServerMatcher) + { + sipServerClean(&init_api->pAppidConfig->detectorSipConfig); + } + return CLIENT_APP_SUCCESS; +} + +static void sip_clean(const CleanClientAppAPI* const clean_api) +{ + if (clean_api->pAppidConfig->detectorSipConfig.sipUaMatcher) + { + sipUaClean(&clean_api->pAppidConfig->detectorSipConfig); + } + if (clean_api->pAppidConfig->detectorSipConfig.sipServerMatcher) + { + sipServerClean(&clean_api->pAppidConfig->detectorSipConfig); + } +} + +static CLIENT_APP_RETCODE sip_tcp_client_init(const IniClientAppAPI* const init_api, + SF_LIST* config) +{ + unsigned i; + RNAClientAppModuleConfigItem* item; + + sip_config.enabled = 1; + + if (config) + { + SF_LNODE* next; + for (item = (RNAClientAppModuleConfigItem*)sflist_first(config, &next); item; + item = (RNAClientAppModuleConfigItem*)sflist_next(&next)) + { + DebugFormat(DEBUG_LOG,"Processing %s: %s\n",item->name, item->value); + if (strcasecmp(item->name, "enabled") == 0) + { + sip_config.enabled = atoi(item->value); + } + } + } + + if (sip_config.enabled) + { + for (i=0; i < sizeof(patterns)/sizeof(*patterns); i++) + { + DebugFormat(DEBUG_LOG,"registering patterns: %s: %d\n", + (const char*)patterns[i].pattern, patterns[i].index); + init_api->RegisterPattern(&sip_tcp_client_validate, IpProtocol::TCP, + patterns[i].pattern, patterns[i].length, + patterns[i].index, init_api->pAppidConfig); + } + } + + unsigned j; + for (j=0; j < sizeof(appIdClientRegistry)/sizeof(*appIdClientRegistry); j++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdClientRegistry[j].appId); + init_api->RegisterAppId(&sip_tcp_client_validate, appIdClientRegistry[j].appId, + appIdClientRegistry[j].additionalInfo, init_api->pAppidConfig); + } + + return CLIENT_APP_SUCCESS; +} + +static void clientDataFree(void* data) +{ + ClientSIPData* fd = (ClientSIPData*)data; + snort_free(fd->from); + snort_free(fd->clientUserAgent); + snort_free(fd->userName); + free (fd); +} + +// static const char* const SIP_USRNAME_BEGIN_MARKER = "data_get(flowp, + sip_udp_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientSIPData*)snort_calloc(sizeof(ClientSIPData)); + sip_udp_client_mod.api->data_add(flowp, fd, + sip_udp_client_mod.flow_data_index, &clientDataFree); + fd->owner = &sip_udp_client_mod; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + + return CLIENT_APP_INPROCESS; +} + +static CLIENT_APP_RETCODE sip_tcp_client_validate(const uint8_t* data, uint16_t size, const int + dir, + AppIdData* flowp, Packet* pkt, struct Detector* userData, + const AppIdConfig* pConfig) +{ + return sip_client_validate(data, size, dir, flowp, pkt, userData, pConfig); +} + +static int sipAppAddPattern(DetectorAppSipPattern** patternList, AppId ClientAppId, + const char* clientVersion, const char* serverPattern) +{ + /* Allocate memory for data structures */ + DetectorAppSipPattern* pattern = (DetectorAppSipPattern*)snort_calloc( + sizeof(DetectorAppSipPattern)); + pattern->userData.ClientAppId = ClientAppId; + pattern->userData.clientVersion = snort_strdup(clientVersion); + pattern->pattern.pattern = (uint8_t*)snort_strdup(serverPattern); + pattern->pattern.patternSize = (int)strlen(serverPattern); + pattern->next = *patternList; + *patternList = pattern; + + return 0; +} + +int sipUaPatternAdd( + AppId ClientAppId, + const char* clientVersion, + const char* pattern, + DetectorSipConfig* pSipConfig + ) +{ + return sipAppAddPattern(&pSipConfig->appSipUaList, ClientAppId, clientVersion, pattern); +} + +int sipServerPatternAdd( + AppId ClientAppId, + const char* clientVersion, + const char* pattern, + DetectorSipConfig* pSipConfig + ) +{ + return sipAppAddPattern(&pSipConfig->appSipServerList, ClientAppId, clientVersion, pattern); +} + +int sipUaFinalize(DetectorSipConfig* pSipConfig) +{ + const int PATTERN_PART_MAX=10; + static tMlmpPattern patterns[PATTERN_PART_MAX]; + int num_patterns; + DetectorAppSipPattern* patternNode; + + pSipConfig->sipUaMatcher = mlmpCreate(); + if (!pSipConfig->sipUaMatcher) + return -1; + + pSipConfig->sipServerMatcher = mlmpCreate(); + if (!pSipConfig->sipServerMatcher) + { + mlmpDestroy((tMlmpTree*)pSipConfig->sipUaMatcher); + pSipConfig->sipUaMatcher = nullptr; + return -1; + } + + for (patternNode = pSipConfig->appSipUaList; patternNode; patternNode = patternNode->next) + { + num_patterns = parseMultipleHTTPPatterns((const char*)patternNode->pattern.pattern, + patterns, PATTERN_PART_MAX, 0); + patterns[num_patterns].pattern = nullptr; + + mlmpAddPattern((tMlmpTree*)pSipConfig->sipUaMatcher, patterns, patternNode); + } + + for (patternNode = pSipConfig->appSipServerList; patternNode; patternNode = patternNode->next) + { + num_patterns = parseMultipleHTTPPatterns((const char*)patternNode->pattern.pattern, + patterns, PATTERN_PART_MAX, 0); + patterns[num_patterns].pattern = nullptr; + + mlmpAddPattern((tMlmpTree*)pSipConfig->sipServerMatcher, patterns, patternNode); + } + + mlmpProcessPatterns((tMlmpTree*)pSipConfig->sipUaMatcher); + mlmpProcessPatterns((tMlmpTree*)pSipConfig->sipServerMatcher); + return 0; +} + +static void sipUaClean(DetectorSipConfig* pSipConfig) +{ + DetectorAppSipPattern* node; + + if (pSipConfig->sipUaMatcher) + { + mlmpDestroy((tMlmpTree*)pSipConfig->sipUaMatcher); + pSipConfig->sipUaMatcher = nullptr; + } + + for (node = pSipConfig->appSipUaList; node; node = pSipConfig->appSipUaList) + { + pSipConfig->appSipUaList = node->next; + snort_free((void*)node->pattern.pattern); + snort_free(node->userData.clientVersion); + snort_free(node); + } +} + +static void sipServerClean(DetectorSipConfig* pSipConfig) +{ + DetectorAppSipPattern* node; + + if (pSipConfig->sipServerMatcher) + { + mlmpDestroy((tMlmpTree*)pSipConfig->sipServerMatcher); + pSipConfig->sipServerMatcher = nullptr; + } + + for (node = pSipConfig->appSipServerList; node; node = pSipConfig->appSipServerList) + { + pSipConfig->appSipServerList = node->next; + snort_free((void*)node->pattern.pattern); + snort_free(node->userData.clientVersion); + snort_free(node); + } +} + +static int sipAppGeClientApp( + void* patternMatcher, + char* pattern, + uint32_t patternLen, + AppId* ClientAppId, + char** clientVersion) +{ + tMlmpPattern patterns[3]; + DetectorAppSipPattern* data; + + if (!pattern) + return 0; + + patterns[0].pattern = (uint8_t*)pattern; + patterns[0].patternSize = patternLen; + patterns[1].pattern = nullptr; + + data = (DetectorAppSipPattern*)mlmpMatchPatternGeneric((tMlmpTree*)patternMatcher, patterns); + + if (data == nullptr) + return 0; + + *ClientAppId = data->userData.ClientAppId; + *clientVersion = data->userData.clientVersion; + + return 1; +} + +static void createRtpFlow(AppIdData* flowp, const Packet* pkt, const sfip_t* cliIp, uint16_t + cliPort, + const sfip_t* srvIp, uint16_t srvPort, IpProtocol proto, int16_t app_id) +{ + AppIdData* fp, * fp2; + + fp = sip_service_mod.api->flow_new(flowp, pkt, cliIp, cliPort, srvIp, srvPort, + proto, app_id, APPID_EARLY_SESSION_FLAG_FW_RULE); + if (fp) + { + fp->ClientAppId = flowp->ClientAppId; + fp->payloadAppId = flowp->payloadAppId; + fp->serviceAppId = APP_ID_RTP; + PopulateExpectedFlow(flowp, fp, APPID_SESSION_IGNORE_ID_FLAGS); + } + + // create an RTCP flow as well + fp2 = sip_service_mod.api->flow_new(flowp, pkt, cliIp, cliPort+1, srvIp, srvPort+1, + proto, app_id, APPID_EARLY_SESSION_FLAG_FW_RULE); + if (fp2) + { + fp2->ClientAppId = flowp->ClientAppId; + fp2->payloadAppId = flowp->payloadAppId; + fp2->serviceAppId = APP_ID_RTCP; + PopulateExpectedFlow(flowp, fp2, APPID_SESSION_IGNORE_ID_FLAGS); + } +} + +static int addFutureRtpFlows(AppIdData* flowp, const SipDialog* dialog, const Packet* p) +{ + SIP_MediaData* mdataA,* mdataB; + + // check the first media session + if (nullptr == dialog->mediaSessions) + return -1; + // check the second media session + if (nullptr == dialog->mediaSessions->nextS) + return -1; + + DebugFormat(DEBUG_SIP, "Adding future media sessions ID: %u and %u\n", + dialog->mediaSessions->sessionID, dialog->mediaSessions->nextS->sessionID); + + mdataA = dialog->mediaSessions->medias; + mdataB = dialog->mediaSessions->nextS->medias; + while ((nullptr != mdataA)&&(nullptr != mdataB)) + { + DebugFormat(DEBUG_SIP, "Adding future channels Source IP: %s Port: %u\n", + sfip_to_str(&mdataA->maddress), mdataA->mport); + DebugFormat(DEBUG_SIP, "Adding future channels Destine IP: %s Port: %u\n", + sfip_to_str(&mdataB->maddress), mdataB->mport); + + // FIXIT All of the casts in these two function calls are flagrantly wrong. These + // signatures don't line up and it doesn't seem to be a simple fix. + createRtpFlow(flowp, p, &mdataA->maddress, mdataA->mport, &mdataB->maddress, + mdataB->mport, IpProtocol::UDP, APP_ID_RTP); + createRtpFlow(flowp, p, &mdataB->maddress, mdataB->mport, &mdataA->maddress, + mdataA->mport, IpProtocol::UDP, APP_ID_RTP); + + mdataA = mdataA->nextM; + mdataB = mdataB->nextM; + } + return 0; +} + +static void SipSessionCbClientProcess(const Packet* p, const SipHeaders* headers, const + SipDialog* dialog, AppIdData* flowp) +{ + ClientSIPData* fd; + AppId ClientAppId = APP_ID_SIP; + char* clientVersion = nullptr; + int direction; + + fd = (ClientSIPData*)sip_udp_client_mod.api->data_get(flowp, + sip_udp_client_mod.flow_data_index); + if (!fd) + { + fd = (ClientSIPData*)snort_calloc(sizeof(ClientSIPData)); + sip_udp_client_mod.api->data_add(flowp, fd, + sip_udp_client_mod.flow_data_index, &clientDataFree); + fd->owner = &sip_udp_client_mod; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + + if (fd->owner != &sip_udp_client_mod && fd->owner != &sip_tcp_client_mod) + return; + + direction = (p->is_from_client()) ? + APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER; + + if (headers->methodFlag == SIP_METHOD_INVITE && direction == APP_ID_FROM_INITIATOR) + { + if (headers->from && headers->fromLen) + { + snort_free(fd->from); + fd->from = strndup(headers->from, headers->fromLen); + } + + if (headers->userName && headers->userNameLen) + { + snort_free(fd->userName); + fd->userName = strndup(headers->userName, headers->userNameLen); + } + if (headers->userAgent && headers->userAgentLen) + { + snort_free(fd->clientUserAgent); + fd->clientUserAgent = strndup(headers->userAgent, headers->userAgentLen); + } + } + + if (fd->clientUserAgent) + { + if (sipAppGeClientApp(pAppidActiveConfig->detectorSipConfig.sipUaMatcher, + fd->clientUserAgent, strlen(fd->clientUserAgent), &ClientAppId, &clientVersion)) + goto success; + } + + if ( fd->from && !(fd->flags & SIP_FLAG_SERVER_CHECKED)) + { + fd->flags |= SIP_FLAG_SERVER_CHECKED; + + if (sipAppGeClientApp(pAppidActiveConfig->detectorSipConfig.sipServerMatcher, + (char*)fd->from, strlen(fd->from), &ClientAppId, &clientVersion)) + goto success; + } + + if (!dialog || dialog->state != SIP_DLG_ESTABLISHED) + return; + +success: + //client detection successful + sip_udp_client_mod.api->add_app(flowp, APP_ID_SIP, ClientAppId, clientVersion); + + if (fd->userName) + sip_udp_client_mod.api->add_user(flowp, (char*)fd->userName, APP_ID_SIP, 1); + + setAppIdFlag(flowp, APPID_SESSION_CLIENT_DETECTED); +} + +static void SipSessionCbServiceProcess(const Packet* p, const SipHeaders* headers, const + SipDialog* dialog, AppIdData* flowp) +{ + ServiceSIPData* ss; + int direction; + + ss = (ServiceSIPData*)sip_service_mod.api->data_get(flowp, sip_service_mod.flow_data_index); + if (!ss) + { + ss = (ServiceSIPData*)snort_calloc(sizeof(ServiceSIPData)); + sip_service_mod.api->data_add(flowp, ss, sip_service_mod.flow_data_index, &snort_free); + } + + ss->serverPkt = 0; + + direction = p->is_from_client() ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER; + + if (direction == APP_ID_FROM_RESPONDER) + { + if (headers->userAgent && headers->userAgentLen) + { + memcpy(ss->vendor, headers->userAgent, + headers->userAgentLen > (MAX_VENDOR_SIZE - 1) ? (MAX_VENDOR_SIZE - 1) : + headers->userAgentLen); + } + else if (headers->server && headers->serverLen) + { + memcpy(ss->vendor, headers->server, + headers->serverLen > (MAX_VENDOR_SIZE - 1) ? (MAX_VENDOR_SIZE - 1) : + headers->serverLen); + } + } + + if (!dialog) + return; + + if (dialog->mediaUpdated) + addFutureRtpFlows(flowp, dialog, p); + + if (dialog->state == SIP_DLG_ESTABLISHED) + { + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + sip_service_mod.api->add_service(flowp, p, direction, &svc_element, + APP_ID_SIP, ss->vendor[0] ? ss->vendor : nullptr, nullptr, nullptr); + } + } +} + +void SipSessionSnortCallback(void*, ServiceEventType, void* data) +{ + AppIdData* flowp = nullptr; + SipEventData* eventData = (SipEventData*)data; + + const Packet* p = eventData->packet; + const SipHeaders* headers = eventData->headers; + const SipDialog* dialog = eventData->dialog; + +#ifdef DEBUG_APP_ID_SESSIONS + { + char src_ip[INET6_ADDRSTRLEN]; + char dst_ip[INET6_ADDRSTRLEN]; + const sfip_t* ip; + + src_ip[0] = 0; + ip = p->ptrs.ip_api.get_src(); + inet_ntop(sfaddr_family(ip), (void*)sfaddr_get_ptr(ip), src_ip, sizeof(src_ip)); + dst_ip[0] = 0; + ip = p->ptrs.ip_api.get_dst(); + inet_ntop(sfaddr_family(ip), (void*)sfaddr_get_ptr(ip), dst_ip, sizeof(dst_ip)); + fprintf(SF_DEBUG_FILE, "AppId Sip Snort Callback Session %s-%u -> %s-%u %d\n", src_ip, + (unsigned)p->src_port, dst_ip, (unsigned)p->dst_port, IsTCP(p) ? IpProtocol::TCP : + IpProtocol::UDP); + } +#endif + if (p->flow) + flowp = getAppIdData(p->flow); + + if (!flowp) + { + ErrorMessage("Missing session\n"); + return; + } + + SipSessionCbClientProcess(p, headers, dialog, flowp); + SipSessionCbServiceProcess(p, headers, dialog, flowp); +} + +static int sip_service_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&sip_service_validate, IpProtocol::UDP, (const uint8_t*)SIP_BANNER, + SIP_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::TCP, (const uint8_t*)SIP_BANNER, + SIP_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::UDP, (const + uint8_t*)SIP_INVITE_BANNER, SIP_INVITE_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::TCP, (const + uint8_t*)SIP_INVITE_BANNER, SIP_INVITE_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::UDP, (const + uint8_t*)SIP_ACK_BANNER, + SIP_ACK_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::TCP, (const + uint8_t*)SIP_ACK_BANNER, + SIP_ACK_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::UDP, (const + uint8_t*)SIP_REGISTER_BANNER, SIP_REGISTER_BANNER_LEN, 0, svc_name, + init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::TCP, (const + uint8_t*)SIP_REGISTER_BANNER, SIP_REGISTER_BANNER_LEN, 0, svc_name, + init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::UDP, (const + uint8_t*)SIP_CANCEL_BANNER, SIP_CANCEL_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::TCP, (const + uint8_t*)SIP_CANCEL_BANNER, SIP_CANCEL_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::UDP, (const + uint8_t*)SIP_BYE_BANNER, + SIP_BYE_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::TCP, (const + uint8_t*)SIP_BYE_BANNER, + SIP_BYE_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::UDP, (const + uint8_t*)SIP_OPTIONS_BANNER, SIP_OPTIONS_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + init_api->RegisterPattern(&sip_service_validate, IpProtocol::TCP, (const + uint8_t*)SIP_OPTIONS_BANNER, SIP_OPTIONS_BANNER_LEN, 0, svc_name, init_api->pAppidConfig); + unsigned i; + for (i=0; i < sizeof(appIdServiceRegistry)/sizeof(*appIdServiceRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdServiceRegistry[i].appId); + init_api->RegisterAppId(&sip_service_validate, appIdServiceRegistry[i].appId, + appIdServiceRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int sip_service_validate(ServiceValidationArgs* args) +{ + ServiceSIPData* ss; + AppIdData* flowp = args->flowp; + + ss = (ServiceSIPData*)sip_service_mod.api->data_get(flowp, sip_service_mod.flow_data_index); + if (!ss) + { + ss = (ServiceSIPData*)snort_calloc(sizeof(ServiceSIPData)); + sip_service_mod.api->data_add(flowp, ss, sip_service_mod.flow_data_index, &snort_free); + } + + if (args->size && args->dir == APP_ID_FROM_RESPONDER) + { + ss->serverPkt++; + } + + if (ss->serverPkt > 10) + { + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + sip_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + sip_service_mod.flow_data_index, args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOMATCH; + } + + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + sip_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + } + + return SERVICE_INPROCESS; +} + diff --git a/src/network_inspectors/appid/detector_plugins/detector_sip.h b/src/network_inspectors/appid/detector_plugins/detector_sip.h new file mode 100644 index 000000000..8e267f9dd --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/detector_sip.h @@ -0,0 +1,58 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// detector_sip.h author Sourcefire Inc. + +#ifndef DETECTOR_SIP_H +#define DETECTOR_SIP_H + +// AppId structures for SIP detection + +#include "detector_api.h" +#include "util/sf_multi_mpse.h" + +struct RNAServiceValidationModule; + +struct SipUaUserData +{ + AppId ClientAppId; + char* clientVersion; +}; + +struct DetectorAppSipPattern +{ + tMlpPattern pattern; + SipUaUserData userData; + DetectorAppSipPattern* next; +}; + +struct DetectorSipConfig +{ + void* sipUaMatcher; + DetectorAppSipPattern* appSipUaList; + void* sipServerMatcher; + DetectorAppSipPattern* appSipServerList; +}; + +extern struct RNAClientAppModule sip_udp_client_mod; +extern struct RNAClientAppModule sip_tcp_client_mod; +extern struct RNAServiceValidationModule sip_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/detector_plugins/http_url_patterns.cc b/src/network_inspectors/appid/detector_plugins/http_url_patterns.cc new file mode 100644 index 000000000..32dccd2f8 --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/http_url_patterns.cc @@ -0,0 +1,249 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// http_url_patterns.cc author Sourcefire Inc. + +#include "http_url_patterns.h" + +#include + +#include "log/messages.h" + +#include "application_ids.h" +#include "http_common.h" +#include "util/sf_multi_mpse.h" +#include "util/sf_mlmp.h" +#include "utils/util.h" + +static const char* const FP_OPERATION_AND = "%&%"; +static const unsigned PATTERN_PART_MAX = 10; + +static void destroyHosUrlDetectorPattern(HosUrlDetectorPattern* pattern) +{ + if (!pattern) + return; + + destroyHosUrlDetectorPattern(pattern->next); + + if (pattern->host.pattern) + snort_free(*(void**)&pattern->host.pattern); + if (pattern->path.pattern) + snort_free(*(void**)&pattern->path.pattern); + if (pattern->query.pattern) + snort_free(*(void**)&pattern->query.pattern); + snort_free(pattern); +} + +static int addHosUrlPatternToList(HosUrlDetectorPattern* detector, + HosUrlPatternsList** hosUrlPatternsList) +{ + if (!detector) + return -1; + + if (!(*hosUrlPatternsList)) + { + *hosUrlPatternsList = (HosUrlPatternsList*)snort_calloc(sizeof(HosUrlPatternsList)); + (*hosUrlPatternsList)->head = detector; + (*hosUrlPatternsList)->tail = detector; + } + else + { + (*hosUrlPatternsList)->tail->next = detector; + (*hosUrlPatternsList)->tail = detector; + } + + return 0; +} + +void destroyHosUrlPatternList(HosUrlPatternsList** pHosUrlPatternsList) +{ + if (!(*pHosUrlPatternsList)) + return; + + destroyHosUrlDetectorPattern((*pHosUrlPatternsList)->head); + snort_free(*pHosUrlPatternsList); + *pHosUrlPatternsList = nullptr; +} + +int addMlmpPattern(void* hosUrlMatcher, HosUrlPatternsList** hosUrlPatternsList, + const uint8_t* host_pattern, int host_pattern_size, + const uint8_t* path_pattern, int path_pattern_size, const uint8_t* query_pattern, int + query_pattern_size, + AppId appId, uint32_t payload_id, uint32_t service_id, uint32_t client_id, DHPSequence seq) +{ + static tMlmpPattern patterns[PATTERN_PART_MAX]; + int num_patterns; + + if (!host_pattern) + return -1; + + if (!hosUrlMatcher) + return -1; + + HosUrlDetectorPattern* detector = (HosUrlDetectorPattern*)snort_calloc( + sizeof(HosUrlDetectorPattern)); + detector->host.pattern = (uint8_t*)snort_strdup((char*)host_pattern); + + if (path_pattern) + detector->path.pattern = (uint8_t*)snort_strdup((char*)path_pattern); + else + detector->path.pattern = nullptr; + + if (query_pattern) + detector->query.pattern = (uint8_t*)snort_strdup((char*)query_pattern); + else + detector->query.pattern = nullptr; + + detector->host.patternSize = host_pattern_size; + detector->path.patternSize = path_pattern_size; + detector->query.patternSize = query_pattern_size; + detector->payload_id = payload_id; + detector->service_id = service_id; + detector->client_id = client_id; + detector->seq = seq; + detector->next = nullptr; + if (appId > APP_ID_NONE) + detector->appId = appId; + else if (payload_id > APP_ID_NONE) + detector->appId = payload_id; + else if (client_id > APP_ID_NONE) + detector->appId = client_id; + else + detector->appId = service_id; + + num_patterns = parseMultipleHTTPPatterns((const char*)host_pattern, patterns, + PATTERN_PART_MAX, 0); + if (path_pattern) + num_patterns += parseMultipleHTTPPatterns((const char*)path_pattern, patterns+num_patterns, + PATTERN_PART_MAX-num_patterns, 1); + + patterns[num_patterns].pattern = nullptr; + + if (addHosUrlPatternToList(detector, hosUrlPatternsList)) + return -1; + + return mlmpAddPattern((tMlmpTree*)hosUrlMatcher, patterns, detector); +} + +uint32_t parseMultipleHTTPPatterns(const char* pattern, tMlmpPattern* parts, uint32_t + numPartLimit, int level) +{ + uint32_t partNum = 0; + const char* tmp; + uint32_t i; + + if (!pattern) + return 0; + + tmp = pattern; + while (tmp && (partNum < numPartLimit)) + { + const char* tmp2 = strstr(tmp, FP_OPERATION_AND); + if (tmp2) + { + parts[partNum].pattern = (uint8_t*)strndup(tmp, tmp2-tmp); + if (parts[partNum].pattern) + { + parts[partNum].patternSize = strlen((const char*)parts[partNum].pattern); + tmp = tmp2+strlen(FP_OPERATION_AND); + } + } + else + { + parts[partNum].pattern = (uint8_t*)snort_strdup(tmp); + parts[partNum].patternSize = strlen((const char*)parts[partNum].pattern); + tmp = nullptr; + } + parts[partNum].level = level; + + if (!parts[partNum].pattern) + { + for (i = 0; i <= partNum; i++) + snort_free((void*)parts[i].pattern); + + ErrorMessage("Failed to allocate memory"); + return 0; + } + partNum++; + } + + return partNum; +} + +/**recursively destroy matcher. + */ +void destroyHosUrlMatcher(tMlmpTree** hosUrlMatcher) +{ + if (hosUrlMatcher && *hosUrlMatcher) + { + mlmpDestroy(*hosUrlMatcher); + *hosUrlMatcher = nullptr; + } +} + +int matchQueryElements( + tMlpPattern* packetData, + tMlpPattern* userPattern, + char* appVersion, + size_t appVersionSize + ) +{ + const uint8_t* index, * endKey; + const uint8_t* queryEnd; + uint32_t extractedSize; + uint32_t copySize = 0; + + if (appVersion == nullptr) + return 0; + + appVersion[0] = '\0'; + + if (!userPattern->pattern || !packetData->pattern) + return 0; + + /*queryEnd is 1 past the end. */ + queryEnd = packetData->pattern + packetData->patternSize; + index = packetData->pattern; + endKey = queryEnd; + + /*?key1=value1&key2=value2 */ + for (index = packetData->pattern; index < queryEnd; index = endKey+1) + { + /*find end of query tuple */ + endKey = (const uint8_t*)memchr (index, '&', queryEnd - index); + if (!endKey) + endKey = queryEnd; + + if (userPattern->patternSize < (uint32_t)(endKey-index)) + { + if (memcmp(index, userPattern->pattern, userPattern->patternSize) == 0) + { + index += userPattern->patternSize; + extractedSize = (endKey-index); + appVersionSize--; + copySize = (extractedSize < appVersionSize) ? extractedSize : appVersionSize; + memcpy(appVersion, index, copySize); + appVersion[copySize] = '\0'; + break; + } + } + } + return copySize; +} + diff --git a/src/network_inspectors/appid/detector_plugins/http_url_patterns.h b/src/network_inspectors/appid/detector_plugins/http_url_patterns.h new file mode 100644 index 000000000..3a880f13e --- /dev/null +++ b/src/network_inspectors/appid/detector_plugins/http_url_patterns.h @@ -0,0 +1,47 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// http_url_patterns.h author Sourcefire Inc. + +#ifndef HTTP_URL_PATTERNS_H +#define HTTP_URL_PATTERNS_H + +// List of URL pattern match functions + +#include "http_common.h" + +struct tMlmpPattern; + +int addMlmpPattern(void* mlpMatcher, HosUrlPatternsList** hosUrlPatternsList, const + uint8_t* host_pattern, int host_pattern_size, + const uint8_t* path_pattern, int path_pattern_size, const uint8_t* query_pattern, int + query_pattern_size, + AppId appId, uint32_t payload_id, uint32_t service_id, uint32_t client_id, DHPSequence seq); +void destroyHosUrlMatcher(tMlmpTree** mlpMatcher); +int matchQueryElements(tMlpPattern* packetData, + tMlpPattern* userPattern, + char* appVersion, + size_t appVersionSize); + +uint32_t parseMultipleHTTPPatterns(const char* pattern, tMlmpPattern* parts, u_int32_t + numPartLimit, int level); +void destroyHosUrlPatternList(HosUrlPatternsList** pHosUrlPatternsList); + +#endif + diff --git a/src/network_inspectors/appid/dns_defs.h b/src/network_inspectors/appid/dns_defs.h new file mode 100644 index 000000000..4d45121e0 --- /dev/null +++ b/src/network_inspectors/appid/dns_defs.h @@ -0,0 +1,122 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// dns_defs.h author Sourcefire Inc. + +#ifndef DNS_DEFS_H +#define DNS_DEFS_H + +#include + +#define MAX_OPCODE 5 +#define INVALID_OPCODE 3 + +#define MAX_RCODE 10 + +#define RCODE_NXDOMAIN 3 + +#define DNS_LENGTH_FLAGS 0xC0 + +#define PATTERN_A_REC 1 +#define PATTERN_AAAA_REC 28 +#define PATTERN_CNAME_REC 5 +#define PATTERN_SRV_REC 33 +#define PATTERN_TXT_REC 16 +#define PATTERN_MX_REC 15 +#define PATTERN_SOA_REC 6 +#define PATTERN_NS_REC 2 +#define PATTERN_PTR_REC 12 + +#pragma pack(1) + +// FIXIT-H J bitfields should be replaced with getters/setters +struct DNSHeader +{ + uint16_t id; +#if defined(SF_BIGENDIAN) + uint8_t QR : 1, + Opcode : 4, + AA : 1, + TC : 1, + RD : 1; + uint8_t RA : 1, + Z : 1, + AD : 1, + CD : 1, + RCODE : 4; +#else + uint8_t RD : 1, + TC : 1, + AA : 1, + Opcode : 4, + QR : 1; + uint8_t RCODE : 4, + CD : 1, + AD : 1, + Z : 1, + RA : 1; +#endif + uint16_t QDCount; + uint16_t ANCount; + uint16_t NSCount; + uint16_t ARCount; +}; + +struct DNSTCPHeader +{ + uint16_t length; +}; + +struct DNSLabel +{ + uint8_t len; + uint8_t name; +}; + +struct DNSLabelPtr +{ + uint16_t position; + uint8_t data; +}; + +struct DNSLabelBitfield +{ + uint8_t id; + uint8_t len; + uint8_t data; +}; + +struct DNSQueryFixed +{ + uint16_t QType; + uint16_t QClass; +}; + +struct DNSAnswerData +{ + uint16_t type; + uint16_t klass; + uint32_t ttl; + uint16_t r_len; +}; + +#pragma pack() + +#endif + diff --git a/src/network_inspectors/appid/errors.sh b/src/network_inspectors/appid/errors.sh new file mode 100755 index 000000000..f340defc3 --- /dev/null +++ b/src/network_inspectors/appid/errors.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# run make on selected files from MANIFEST.txt + +USAGE="$0 [-h] [] []" + +if [[ $1 == -h ]]; then + echo $USAGE >&2 + exit +fi + +file_status=$1 +cisco_username=$2 +filter= + +if [[ -n "$file_status" ]]; then + filter="$filter *$file_status" +fi + +if [[ -n "$cisco_username" ]]; then + filter="$filter *$USER" +fi + +grep -v ^# MANIFEST.txt | while IFS='' read -r line || [[ -n "$line" ]]; do + echo $line | grep "$filter" >/dev/null && { + filename=$(echo $line | awk '{print $1}') + make "${filename%.*}_${filename##*.}" >/dev/null || echo $filename + } +done diff --git a/src/network_inspectors/appid/flow_error.h b/src/network_inspectors/appid/flow_error.h new file mode 100644 index 000000000..223acf740 --- /dev/null +++ b/src/network_inspectors/appid/flow_error.h @@ -0,0 +1,36 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// flow_error.h author Sourcefire Inc. + +#ifndef FLOW_ERROR_H +#define FLOW_ERROR_H + +#define APPID_SESSION_SUCCESS 0 +#define APPID_SESSION_ENULL 1 +#define APPID_SESSION_EINVALID 2 +#define APPID_SESSION_ENOMEM 3 +#define APPID_SESSION_NOTFOUND 4 +#define APPID_SESSION_BADJUJU 5 +#define APPID_SESSION_DISABLED 6 +#define APPID_SESSION_EUNSUPPORTED 7 +#define APPID_SESSION_STOP_PROCESSING 8 +#define APPID_SESSION_EEXISTS 9 + +#endif diff --git a/src/network_inspectors/appid/fw_appid.cc b/src/network_inspectors/appid/fw_appid.cc new file mode 100644 index 000000000..8c57ce007 --- /dev/null +++ b/src/network_inspectors/appid/fw_appid.cc @@ -0,0 +1,4108 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// fw_appid.cc author Sourcefire Inc. + +#include "fw_appid.h" + +#include "log/messages.h" +#include "profiler/profiler.h" +#include "protocols/tcp.h" +#include "target_based/snort_protocols.h" +#include "utils/util.h" + +#include "appid_stats.h" +#include "app_forecast.h" +#include "app_info_table.h" + +#include "host_port_app_cache.h" +#include "lua_detector_module.h" +#include "client_plugins/client_app_base.h" +#include "detector_plugins/detector_http.h" +#include "detector_plugins/detector_dns.h" +#include "service_plugins/service_base.h" +#include "service_plugins/service_ssl.h" +#include "service_plugins/service_util.h" +#include "util/common_util.h" +#include "util/ip_funcs.h" +#include "util/network_set.h" +#include "time/packet_time.h" +#include "sfip/sf_ip.h" +#include "stream/stream_api.h" + +#if 1 // FIXIT-M hacks +struct HttpParsedHeaders +{ + struct HttpBuf + { + char* start; + int len; + }; + + HttpBuf host; + HttpBuf url; + HttpBuf userAgent; + HttpBuf referer; + HttpBuf via; + HttpBuf contentType; + HttpBuf responseCode; +}; + +#define HTTP_XFF_FIELD_X_FORWARDED_FOR "" +#define HTTP_XFF_FIELD_TRUE_CLIENT_IP "" + +#endif + +#define MAX_ATTR_LEN 1024 +#define HTTP_PREFIX "http://" + +#define APP_MAPPING_FILE "appMapping.data" + +#ifdef RNA_DEBUG_PE +static const char* MODULE_NAME = "fw_appid"; +#endif + +static volatile int app_id_debug_flag; +static FWDebugSessionConstraints app_id_debug_info; +char app_id_debug_session[FW_DEBUG_SESSION_ID_SIZE]; +bool app_id_debug_session_flag; + +ProfileStats tpPerfStats; +ProfileStats tpLibPerfStats; +ProfileStats httpPerfStats; +ProfileStats clientMatchPerfStats; +ProfileStats serviceMatchPerfStats; + +#define HTTP_PATTERN_MAX_LEN 1024 +#define PORT_MAX 65535 + +unsigned long app_id_raw_packet_count = 0; +unsigned long app_id_processed_packet_count = 0; +unsigned long app_id_ignored_packet_count = 0; +int app_id_debug; +static int ptype_scan_counts[NUMBER_OF_PTYPES]; + +static void ProcessThirdPartyResults(Packet* p, AppIdData* appIdSession, int confidence, + AppId* proto_list, ThirdPartyAppIDAttributeData* attribute_data); +static void ExamineRtmpMetadata(AppIdData* appIdSession); + +void dump_appid_stats() +{ + LogMessage("Application Identification Preprocessor:\n"); + LogMessage(" Total packets received : %lu\n", app_id_raw_packet_count); + LogMessage(" Total packets processed : %lu\n", app_id_processed_packet_count); + if (thirdparty_appid_module) + thirdparty_appid_module->print_stats(); + LogMessage(" Total packets ignored : %lu\n", app_id_ignored_packet_count); + AppIdServiceStateDumpStats(); + RNAPndDumpLuaStats(); +} + +void reset_appid_stats(int, void*) +{ + app_id_raw_packet_count = 0; + app_id_processed_packet_count = 0; + app_id_ignored_packet_count = 0; + if (thirdparty_appid_module) + thirdparty_appid_module->reset_stats(); +} + + +/* The UNSYNCED_SNORT_ID value is to cheaply insure we get + the value from snort rather than assume */ +#define UNSYNCED_SNORT_ID 0x5555 + +AppIdData* appSharedDataAlloc(IpProtocol proto, const sfip_t* ip) +{ + // FIXIT - should appid_flow_data_id be global to all threads? + static THREAD_LOCAL uint32_t appid_flow_data_id = 0; + AppIdData* data = new AppIdData; + + // should be freed by flow + // if (app_id_free_list) + // { + // data = app_id_free_list; + // app_id_free_list = data->next; + // data->reset(); + // } + + if ( !data ) + FatalError("Could not allocate AppIdData data"); + + if (thirdparty_appid_module) + if (!(data->tpsession = thirdparty_appid_module->session_create())) + FatalError("Could not allocate AppIdData->tpsession data"); + + data->id = ++appid_flow_data_id; + data->common.fsf_type.flow_type = APPID_SESSION_TYPE_NORMAL; + data->proto = proto; + data->common.initiator_ip = *ip; + data->snortId = UNSYNCED_SNORT_ID; + data->search_support_type = SEARCH_SUPPORT_TYPE_UNKNOWN; + return data; +} + +static inline AppIdData* appSharedCreateData(const Packet* p, IpProtocol proto, int direction) +{ + AppIdData* data; + const sfip_t* ip; + + ip = (direction == APP_ID_FROM_INITIATOR) + ? p->ptrs.ip_api.get_src() : p->ptrs.ip_api.get_dst(); + data = appSharedDataAlloc(proto, ip); + + if ( ( proto == IpProtocol::TCP || proto == IpProtocol::UDP ) && ( p->ptrs.sp != p->ptrs.dp ) ) + data->common.initiator_port = + (direction == APP_ID_FROM_INITIATOR) ? p->ptrs.sp : p->ptrs.dp; + data->ssn = p->flow; + data->stats.firstPktsecond = p->pkth->ts.tv_sec; + + p->flow->set_application_data(data); + return data; +} + +static inline void appSharedReInitData(AppIdData* session) +{ + session->miscAppId = APP_ID_NONE; + +#ifdef REMOVED_WHILE_NOT_IN_USE + //data + if (isSslServiceAppId(session->tpAppId)) + { + session->payloadAppId = session->referredPayloadAppId = session->tpPayloadAppId = + APP_ID_NONE; + clearAppIdFlag(session, APPID_SESSION_CONTINUE); + if (session->payloadVersion) + { + snort_free(session->payloadVersion); + session->payloadVersion = nullptr; + } + if (session->hsession && session->hsession->url) + { + snort_free(session->hsession->url); + session->hsession->url = nullptr; + } + } +#endif + + //service + if (!getAppIdFlag(session, APPID_SESSION_STICKY_SERVICE)) + { + clearAppIdFlag(session, APPID_SESSION_STICKY_SERVICE); + + session->tpAppId = session->serviceAppId = session->portServiceAppId = APP_ID_NONE; + if (session->serviceVendor) + { + snort_free(session->serviceVendor); + session->serviceVendor = nullptr; + } + if (session->serviceVersion) + { + snort_free(session->serviceVersion); + session->serviceVersion = nullptr; + } + + session->service_ip.clear(); + session->service_port = 0; + session->rnaServiceState = RNA_STATE_NONE; + session->serviceData = nullptr; + AppIdFlowdataDeleteAllByMask(session, APPID_SESSION_DATA_SERVICE_MODSTATE_BIT); + } + + //client + session->ClientAppId = session->ClientServiceAppId = APP_ID_NONE; + if (session->clientVersion) + { + snort_free(session->clientVersion); + session->clientVersion = nullptr; + } + session->rnaClientState = RNA_STATE_NONE; + AppIdFlowdataDeleteAllByMask(session, APPID_SESSION_DATA_CLIENT_MODSTATE_BIT); + + //3rd party cleaning + if (thirdparty_appid_module) + thirdparty_appid_module->session_delete(session->tpsession, 1); + session->init_tpPackets = 0; + session->resp_tpPackets = 0; + + session->scan_flags &= ~SCAN_HTTP_HOST_URL_FLAG; + clearAppIdFlag(session, APPID_SESSION_SERVICE_DETECTED|APPID_SESSION_CLIENT_DETECTED| + APPID_SESSION_SSL_SESSION|APPID_SESSION_HTTP_SESSION|APPID_SESSION_APP_REINSPECT); +} + +void fwAppIdFini(AppIdConfig* pConfig) +{ +#ifdef RNA_FULL_CLEANUP + AppIdData* app_id; + TmpAppIdData* tmp_app_id; + + while ((app_id = app_id_free_list)) + { + app_id_free_list = app_id->next; + snort_free(app_id); + } + + while ((tmp_app_id = tmp_app_id_free_list)) + { + tmp_app_id_free_list = tmp_app_id->next; + snort_free(tmp_app_id); + } + AppIdFlowdataFini(); +#endif + + appInfoTableFini(pConfig); +} + +static inline int PENetworkMatch(const sfip_t* pktAddr, const PortExclusion* pe) +{ + const uint32_t* pkt = pktAddr->ip32; + const uint32_t* nm = pe->netmask.u6_addr32; + const uint32_t* peIP = pe->ip.u6_addr32; + return (((pkt[0] & nm[0]) == peIP[0]) + && ((pkt[1] & nm[1]) == peIP[1]) + && ((pkt[2] & nm[2]) == peIP[2]) + && ((pkt[3] & nm[3]) == peIP[3])); +} + +static inline int checkPortExclusion(const Packet* pkt, int reversed) +{ + SF_LIST** src_port_exclusions; + SF_LIST** dst_port_exclusions; + SF_LIST* pe_list; + PortExclusion* pe; + const sfip_t* s_ip; + uint16_t port; + AppIdConfig* pConfig = pAppidActiveConfig; + + if ( pkt->is_tcp() ) + { + src_port_exclusions = pConfig->tcp_port_exclusions_src; + dst_port_exclusions = pConfig->tcp_port_exclusions_dst; + } + else if ( pkt->is_udp() ) + { + src_port_exclusions = pConfig->udp_port_exclusions_src; + dst_port_exclusions = pConfig->udp_port_exclusions_dst; + } + else + return 0; + + /* check the source port */ + port = reversed ? pkt->ptrs.dp : pkt->ptrs.sp; + if ( port && (pe_list=src_port_exclusions[port]) != nullptr ) + { + s_ip = reversed ? pkt->ptrs.ip_api.get_dst() : pkt->ptrs.ip_api.get_src(); + + SF_LNODE* node; + + /* walk through the list of port exclusions for this port */ + for ( pe = (PortExclusion*)sflist_first(pe_list, &node); + pe; + pe = (PortExclusion*)sflist_next(&node) ) + { + if ( PENetworkMatch(s_ip, pe)) + { +#ifdef RNA_DEBUG_PE + char inetBuffer[INET6_ADDRSTRLEN]; + inetBuffer[0] = 0; + inet_ntop(sfaddr_family(s_ip), (void*)sfaddr_get_ptr(s_ip), inetBuffer, + sizeof(inetBuffer)); + + SFDEBUG(MODULE_NAME, "excluding src port: %d",port); + SFDEBUG(MODULE_NAME, "for addresses src: %s", inetBuffer); +#endif + return 1; + } + } + } + + /* check the dest port */ + port = reversed ? pkt->ptrs.sp : pkt->ptrs.dp; + if ( port && (pe_list=dst_port_exclusions[port]) != nullptr ) + { + s_ip = reversed ? pkt->ptrs.ip_api.get_src() : pkt->ptrs.ip_api.get_dst(); + + SF_LNODE* node; + /* walk through the list of port exclusions for this port */ + for ( pe = (PortExclusion*)sflist_first(pe_list, &node); + pe; + pe = (PortExclusion*)sflist_next(&node) ) + { + if ( PENetworkMatch(s_ip, pe)) + { +#ifdef RNA_DEBUG_PE + char inetBuffer[INET6_ADDRSTRLEN]; + inetBuffer[0] = 0; + inet_ntop(sfaddr_family(s_ip), (void*)sfaddr_get_ptr(s_ip), inetBuffer, + sizeof(inetBuffer)); + SFDEBUG(MODULE_NAME, "excluding dst port: %d",port); + SFDEBUG(MODULE_NAME, "for addresses dst: %s", inetBuffer); +#endif + return 1; + } + } + } + + return 0; +} + +static inline bool fwAppIdDebugCheck(Flow* flow, AppIdData* session, volatile int debug_flag, + FWDebugSessionConstraints* info, char* debug_session, int direction) +{ + if (debug_flag) + { + assert(flow); + const FlowKey* key = flow->key; + + if (((info->protocol == PktType::NONE) || info->protocol == key->pkt_type) && + (((!info->sport || info->sport == key->port_l) && + (!info->sip_flag || memcmp(&info->sip, key->ip_l, sizeof(info->sip)) == 0) && + (!info->dport || info->dport == key->port_h) && + (!info->dip_flag || memcmp(&info->dip, key->ip_h, sizeof(info->dip)) == 0)) || + ((!info->sport || info->sport == key->port_h) && + (!info->sip_flag || memcmp(&info->sip, key->ip_h, sizeof(info->sip)) == 0) && + (!info->dport || info->dport == key->port_l) && + (!info->dip_flag || memcmp(&info->dip, key->ip_l, sizeof(info->dip)) == 0)))) + { + int af; + sfip_t sip; + sfip_t dip; + unsigned offset; + uint16_t sport; + uint16_t dport; + char sipstr[INET6_ADDRSTRLEN]; + char dipstr[INET6_ADDRSTRLEN]; + if (session && session->common.fsf_type.flow_type != APPID_SESSION_TYPE_IGNORE) + { + if (session->common.initiator_port) + { + if (session->common.initiator_port == key->port_l) + { + sfip_set_raw(&sip, key->ip_l, AF_INET); + sfip_set_raw(&dip, key->ip_l, AF_INET); + sport = key->port_l; + dport = key->port_h; + } + else + { + sfip_set_raw(&sip, key->ip_l, AF_INET); + sfip_set_raw(&dip, key->ip_l, AF_INET); + sport = key->port_h; + dport = key->port_l; + } + } + // FIXIT - sfip_fast_eq6 is stub macro + else if (sfip_fast_eq6(&session->common.initiator_ip, key->ip_l) == 0) + { + sfip_set_raw(&sip, key->ip_l, AF_INET); + sfip_set_raw(&dip, key->ip_h, AF_INET); + sport = key->port_l; + dport = key->port_h; + } + else + { + sfip_set_raw(&sip, key->ip_l, AF_INET); + sfip_set_raw(&dip, key->ip_h, AF_INET); + sport = key->port_h; + dport = key->port_l; + } + } + else + { + sfip_set_raw(&sip, key->ip_l, AF_INET); + sfip_set_raw(&dip, key->ip_h, AF_INET); + sport = key->port_l; + dport = key->port_h; + } + sipstr[0] = 0; + if ( sip.ip32[0] || sip.ip32[1] || sip.ip16[4] || + (sip.ip16[5] && sip.ip16[5] != 0xffff) ) + { + af = AF_INET6; + offset = 0; + } + else + { + af = AF_INET; + offset = 12; + } + + inet_ntop(af, &sip.ip8[offset], sipstr, sizeof(sipstr)); + dipstr[0] = 0; + if ( dip.ip32[0] || dip.ip32[1] || dip.ip16[4] || + (dip.ip16[5] && dip.ip16[5] != 0xFFFF) ) + { + af = AF_INET6; + offset = 0; + } + else + { + af = AF_INET; + offset = 12; + } + + inet_ntop(af, &dip.ip8[offset], dipstr, sizeof(dipstr)); +#ifdef HAVE_DAQ_ADDRESS_SPACE_ID + snprintf(debug_session, FW_DEBUG_SESSION_ID_SIZE, "%s-%u -> %s-%u %u%s AS %u I %u", + sipstr, (unsigned)sport, dipstr, (unsigned)dport, (unsigned)key->pkt_type, + (direction == APP_ID_FROM_INITIATOR) ? "" : " R", + (unsigned)key->addressSpaceId, get_instance_id()); +#else + snprintf(debug_session, FW_DEBUG_SESSION_ID_SIZE, "%s-%u -> %s-%u %u%s I %u", + sipstr, (unsigned)sport, dipstr, (unsigned)dport, (unsigned)key->pkt_type, + (direction == APP_ID_FROM_INITIATOR) ? "" : " R", get_instance_id()); +#endif + return true; + } + } + return false; +} + +static inline void appIdDebugParse(const char* desc, const uint8_t* data, uint32_t length, + volatile int* debug_flag, FWDebugSessionConstraints* info) +{ + *debug_flag = 0; + memset(info, 0, sizeof(*info)); + do + { + if (length >= sizeof(info->protocol)) + { + info->protocol = static_cast(*data); + length -= sizeof(info->protocol); + data += sizeof(info->protocol); + } + else + break; + + if (length >= sizeof(info->sip)) + { + memcpy(&info->sip, data, sizeof(info->sip)); + if (info->sip.u6_addr32[1] || info->sip.u6_addr32[2] || info->sip.u6_addr32[3]) + info->sip_flag = 1; + else if (info->sip.u6_addr32[0]) + { + info->sip.u6_addr32[3] = info->sip.u6_addr32[0]; + info->sip.u6_addr32[0] = 0; + info->sip.u6_addr16[5] = 0xFFFF; + info->sip_flag = 1; + } + length -= sizeof(info->sip); + data += sizeof(info->sip); + } + else + break; + + if (length >= sizeof(info->sport)) + { + memcpy(&info->sport, data, sizeof(info->sport)); + length -= sizeof(info->sport); + data += sizeof(info->sport); + } + else + break; + + if (length >= sizeof(info->dip)) + { + memcpy(&info->dip, data, sizeof(info->dip)); + if (info->dip.u6_addr32[1] || info->dip.u6_addr32[2] || info->dip.u6_addr32[3]) + info->dip_flag = 1; + else if (info->dip.u6_addr32[0]) + { + info->dip.u6_addr32[3] = info->dip.u6_addr32[0]; + info->dip.u6_addr32[0] = 0; + info->dip.u6_addr16[5] = 0xFFFF; + info->dip_flag = 1; + } + length -= sizeof(info->dip); + data += sizeof(info->dip); + } + else + break; + + if (length >= sizeof(info->dport)) + { + memcpy(&info->dport, data, sizeof(info->dport)); + length -= sizeof(info->dport); + data += sizeof(info->dport); + } + else + break; + } + while (0); + + if (info->protocol != PktType::NONE || info->sip_flag || info->sport || info->dip_flag || + info->dport) + { + int saf; + int daf; + char sipstr[INET6_ADDRSTRLEN]; + char dipstr[INET6_ADDRSTRLEN]; + + if (!info->sip.u6_addr32[0] && !info->sip.u6_addr32[0] && !info->sip.u6_addr16[4] && + info->sip.u6_addr16[5] == 0xFFFF) + { + saf = AF_INET; + } + else + saf = AF_INET6; + if (!info->dip.u6_addr32[0] && !info->dip.u6_addr32[0] && !info->dip.u6_addr16[4] && + info->dip.u6_addr16[5] == 0xFFFF) + { + daf = AF_INET; + } + else + daf = AF_INET6; + if (!info->sip_flag) + saf = daf; + if (!info->dip_flag) + daf = saf; + sipstr[0] = 0; + inet_ntop(saf, saf == AF_INET ? &info->sip.u6_addr32[3] : info->sip.u6_addr32, sipstr, + sizeof(sipstr)); + dipstr[0] = 0; + inet_ntop(daf, daf == AF_INET ? &info->dip.u6_addr32[3] : info->dip.u6_addr32, dipstr, + sizeof(dipstr)); + LogMessage("Debugging %s with %s-%u and %s-%u %u\n", desc, + sipstr, (unsigned)info->sport, + dipstr, (unsigned)info->dport, + (unsigned)info->protocol); + *debug_flag = 1; + } + else + LogMessage("Debugging %s disabled\n", desc); +} + +int AppIdDebug(uint16_t, const uint8_t* const data, uint32_t length, void**, char*, int) +{ + appIdDebugParse("appId", data, length, &app_id_debug_flag, &app_id_debug_info); + return 0; +} + +unsigned isIPv4HostMonitored(uint32_t ip4, int32_t zone) +{ + NetworkSet* net_list; + unsigned flags; + AppIdConfig* pConfig = pAppidActiveConfig; + + if (zone >= 0 && zone < MAX_ZONES && pConfig->net_list_by_zone[zone]) + net_list = pConfig->net_list_by_zone[zone]; + else + net_list = pConfig->net_list; + + NetworkSet_ContainsEx(net_list, ip4, &flags); + return flags; +} + +static inline unsigned isIPMonitored(const Packet* p, int dst) +{ + const sfip_t* sf_ip; + NetworkSet* net_list; + unsigned flags; + int32_t zone; + NSIPv6Addr ip6; + AppIdConfig* pConfig = pAppidActiveConfig; + + if (!dst) + { + zone = p->pkth->ingress_group; + sf_ip = p->ptrs.ip_api.get_src(); + } + else + { + zone = (p->pkth->egress_index == DAQ_PKTHDR_UNKNOWN) ? p->pkth->ingress_group + : + p->pkth->egress_group; + if (zone == DAQ_PKTHDR_FLOOD) + return 0; + sf_ip = p->ptrs.ip_api.get_dst(); + } + + if (zone >= 0 && zone < MAX_ZONES && pConfig->net_list_by_zone[zone]) + net_list = pConfig->net_list_by_zone[zone]; + else + net_list = pConfig->net_list; + + if ( sf_ip->is_ip4() ) + { + if (sf_ip->ip32[0] == 0xFFFFFFFF) + return IPFUNCS_CHECKED; + NetworkSet_ContainsEx(net_list, ntohl(sf_ip->ip32[0]), &flags); + } + else + { + // FIXIT - the sfaddr_get_ptr macro is NULL, not good for memcpy + // memcpy(&ip6, sfaddr_get_ptr(sf_ip), sizeof(ip6)); + NSIPv6AddrNtoH(&ip6); + NetworkSet_Contains6Ex(net_list, &ip6, &flags); + } + + return flags | IPFUNCS_CHECKED; +} + +static inline bool isSpecialSessionMonitored(const Packet* p) +{ + if ( p->is_ip4() ) + { + if (p->is_udp() && ((p->ptrs.sp == 68 && p->ptrs.dp == 67) + || (p->ptrs.sp == 67 && p->ptrs.dp == 68))) + { + return true; + } + } + return false; +} + +static inline uint64_t isSessionMonitored(const Packet* p, int dir, AppIdData* session) +{ + uint64_t flags; +// FIXIT-M: Determine correct replacement for this _dpd call +#ifdef REMOVED_WHILE_NOT_IN_USE + uint64_t flow_flags = _dpd.isAppIdRequired() ? APPID_SESSION_DISCOVER_APP : 0; +#else + uint64_t flow_flags = APPID_SESSION_DISCOVER_APP; +#endif + + flow_flags |= (dir == APP_ID_FROM_INITIATOR) ? APPID_SESSION_INITIATOR_SEEN : + APPID_SESSION_RESPONDER_SEEN; + if (session) + { + flow_flags |= session->common.flags; + if (session->common.policyId != appIdPolicyId) + { + if (checkPortExclusion(p, dir == APP_ID_FROM_RESPONDER)) + { + flow_flags |= APPID_SESSION_INITIATOR_SEEN | APPID_SESSION_RESPONDER_SEEN | + APPID_SESSION_INITIATOR_CHECKED | APPID_SESSION_RESPONDER_CHECKED; + flow_flags &= ~(APPID_SESSION_INITIATOR_MONITORED | + APPID_SESSION_RESPONDER_MONITORED); + return flow_flags; + } + if (dir == APP_ID_FROM_INITIATOR) + { + if (getAppIdFlag(session, APPID_SESSION_INITIATOR_CHECKED)) + { + flags = isIPMonitored(p, 0); + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_INITIATOR_MONITORED; + else + flow_flags &= ~APPID_SESSION_INITIATOR_MONITORED; + } + + if (getAppIdFlag(session, APPID_SESSION_RESPONDER_CHECKED)) + { + flags = isIPMonitored(p, 1); + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_RESPONDER_MONITORED; + else + flow_flags &= ~APPID_SESSION_RESPONDER_MONITORED; + } + } + else + { + if (getAppIdFlag(session, APPID_SESSION_RESPONDER_CHECKED)) + { + flags = isIPMonitored(p, 0); + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_RESPONDER_MONITORED; + else + flow_flags &= ~APPID_SESSION_RESPONDER_MONITORED; + } + + if (getAppIdFlag(session, APPID_SESSION_INITIATOR_CHECKED)) + { + flags = isIPMonitored(p, 1); + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_INITIATOR_MONITORED; + else + flow_flags &= ~APPID_SESSION_INITIATOR_MONITORED; + } + } + } + + if (getAppIdFlag(session, APPID_SESSION_BIDIRECTIONAL_CHECKED) == + APPID_SESSION_BIDIRECTIONAL_CHECKED) + return flow_flags; + + if (dir == APP_ID_FROM_INITIATOR) + { + if (!getAppIdFlag(session, APPID_SESSION_INITIATOR_CHECKED)) + { + flags = isIPMonitored(p, 0); + flow_flags |= APPID_SESSION_INITIATOR_CHECKED; + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_INITIATOR_MONITORED; + if (flags & IPFUNCS_USER_IP) + flow_flags |= APPID_SESSION_DISCOVER_USER; + if (flags & IPFUNCS_APPLICATION) + flow_flags |= APPID_SESSION_DISCOVER_APP; + + if (isSpecialSessionMonitored(p)) + { + flow_flags |= APPID_SESSION_SPECIAL_MONITORED; + } + } + if (!(flow_flags & APPID_SESSION_DISCOVER_APP) && !getAppIdFlag(session, + APPID_SESSION_RESPONDER_CHECKED)) + { + flags = isIPMonitored(p, 1); + if (flags & IPFUNCS_CHECKED) + flow_flags |= APPID_SESSION_RESPONDER_CHECKED; + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_RESPONDER_MONITORED; + if (flags & IPFUNCS_APPLICATION) + flow_flags |= APPID_SESSION_DISCOVER_APP; + if (isSpecialSessionMonitored(p)) + { + flow_flags |= APPID_SESSION_SPECIAL_MONITORED; + } + } + } + else + { + if (!getAppIdFlag(session, APPID_SESSION_RESPONDER_CHECKED)) + { + flags = isIPMonitored(p, 0); + flow_flags |= APPID_SESSION_RESPONDER_CHECKED; + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_RESPONDER_MONITORED; + if (flags & IPFUNCS_APPLICATION) + flow_flags |= APPID_SESSION_DISCOVER_APP; + if (isSpecialSessionMonitored(p)) + { + flow_flags |= APPID_SESSION_SPECIAL_MONITORED; + } + } + if (!(flow_flags & APPID_SESSION_DISCOVER_APP) && !getAppIdFlag(session, + APPID_SESSION_INITIATOR_CHECKED)) + { + flags = isIPMonitored(p, 1); + if (flags & IPFUNCS_CHECKED) + flow_flags |= APPID_SESSION_INITIATOR_CHECKED; + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_INITIATOR_MONITORED; + if (flags & IPFUNCS_USER_IP) + flow_flags |= APPID_SESSION_DISCOVER_USER; + if (flags & IPFUNCS_APPLICATION) + flow_flags |= APPID_SESSION_DISCOVER_APP; + if (isSpecialSessionMonitored(p)) + { + flow_flags |= APPID_SESSION_SPECIAL_MONITORED; + } + } + } + } + else if (checkPortExclusion(p, 0)) + { + flow_flags |= APPID_SESSION_INITIATOR_SEEN | APPID_SESSION_RESPONDER_SEEN | + APPID_SESSION_INITIATOR_CHECKED | APPID_SESSION_RESPONDER_CHECKED; + } + else if (dir == APP_ID_FROM_INITIATOR) + { + flags = isIPMonitored(p, 0); + flow_flags |= APPID_SESSION_INITIATOR_CHECKED; + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_INITIATOR_MONITORED; + if (flags & IPFUNCS_USER_IP) + flow_flags |= APPID_SESSION_DISCOVER_USER; + if (flags & IPFUNCS_APPLICATION) + flow_flags |= APPID_SESSION_DISCOVER_APP; + if (!(flow_flags & APPID_SESSION_DISCOVER_APP)) + { + flags = isIPMonitored(p, 1); + if (flags & IPFUNCS_CHECKED) + flow_flags |= APPID_SESSION_RESPONDER_CHECKED; + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_RESPONDER_MONITORED; + if (flags & IPFUNCS_APPLICATION) + flow_flags |= APPID_SESSION_DISCOVER_APP; + } + if (isSpecialSessionMonitored(p)) + { + flow_flags |= APPID_SESSION_SPECIAL_MONITORED; + } + } + else + { + flags = isIPMonitored(p, 0); + flow_flags |= APPID_SESSION_RESPONDER_CHECKED; + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_RESPONDER_MONITORED; + if (flags & IPFUNCS_APPLICATION) + flow_flags |= APPID_SESSION_DISCOVER_APP; + if (!(flow_flags & APPID_SESSION_DISCOVER_APP)) + { + flags = isIPMonitored(p, 1); + if (flags & IPFUNCS_CHECKED) + flow_flags |= APPID_SESSION_INITIATOR_CHECKED; + if (flags & IPFUNCS_HOSTS_IP) + flow_flags |= APPID_SESSION_INITIATOR_MONITORED; + if (flags & IPFUNCS_USER_IP) + flow_flags |= APPID_SESSION_DISCOVER_USER; + if (flags & IPFUNCS_APPLICATION) + flow_flags |= APPID_SESSION_DISCOVER_APP; + } + + if (isSpecialSessionMonitored(p)) + { + flow_flags |= APPID_SESSION_SPECIAL_MONITORED; + } + } + + return flow_flags; +} + +static inline void seServiceAppIdData(AppIdData* session, AppId serviceAppId, char* vendor, + char** version) +{ + if (serviceAppId <= APP_ID_NONE) + return; + + //in drambuie, 3rd party is in INIT state after processing first GET requuest. + if (serviceAppId == APP_ID_HTTP) + { + if (session->ClientServiceAppId == APP_ID_NONE) + { + session->ClientServiceAppId = serviceAppId; + } + return; + } + + if (session->serviceAppId != serviceAppId) + { + session->serviceAppId = serviceAppId; + + if (pAppidActiveConfig->mod_config->instance_id) + checkSandboxDetection(serviceAppId); + + /* Clear out previous values of vendor & version */ + if (session->serviceVendor) + { + snort_free(session->serviceVendor); + session->serviceVendor = nullptr; + } + if (session->serviceVersion) + { + snort_free(session->serviceVersion); + session->serviceVersion = nullptr; + } + + if (vendor) + session->serviceVendor = vendor; + + if (version && *version) + { + session->serviceVersion = *version; + *version = nullptr; + } + } + else + { + if (vendor || version) + { + /* Clear previous values */ + if (session->serviceVendor) + snort_free(session->serviceVendor); + if (session->serviceVersion) + snort_free(session->serviceVersion); + + /* set vendor */ + if (vendor) + session->serviceVendor = vendor; + else + session->serviceVendor = nullptr; + + /* set version */ + if (version && *version) + { + session->serviceVersion = *version; + *version = nullptr; + } + else + session->serviceVersion = nullptr; + } + } +} + +static inline void setClientAppIdData(AppIdData* session, AppId ClientAppId, char** version) +{ + AppIdConfig* pConfig = pAppidActiveConfig; + if (ClientAppId <= APP_ID_NONE || ClientAppId == APP_ID_HTTP) + return; + + if (session->ClientAppId != ClientAppId) + { + unsigned prev_priority = appInfoEntryPriorityGet(session->ClientAppId, pConfig); + unsigned curr_priority = appInfoEntryPriorityGet(ClientAppId, pConfig); + + if (pAppidActiveConfig->mod_config->instance_id) + checkSandboxDetection(ClientAppId); + + if ((session->ClientAppId) && (prev_priority > curr_priority )) + return; + session->ClientAppId = ClientAppId; + + if (session->clientVersion) + snort_free(session->clientVersion); + + if (version && *version) + { + session->clientVersion = *version; + *version = nullptr; + } + else + session->clientVersion = nullptr; + } + else if (version && *version) + { + if (session->clientVersion) + snort_free(session->clientVersion); + session->clientVersion = *version; + *version = nullptr; + } +} + +static inline void setReferredPayloadAppIdData(AppIdData* session, AppId referredPayloadAppId) +{ + if (referredPayloadAppId <= APP_ID_NONE) + return; + + if (session->referredPayloadAppId != referredPayloadAppId) + { + if (pAppidActiveConfig->mod_config->instance_id) + checkSandboxDetection(referredPayloadAppId); + + session->referredPayloadAppId = referredPayloadAppId; + } +} + +static inline void setPayloadAppIdData(AppIdData* session, ApplicationId payloadAppId, + char** version) +{ + AppIdConfig* pConfig = pAppidActiveConfig; + + if (payloadAppId <= APP_ID_NONE) + return; + + if (session->payloadAppId != payloadAppId) + { + unsigned prev_priority = appInfoEntryPriorityGet(session->payloadAppId, pConfig); + unsigned curr_priority = appInfoEntryPriorityGet(payloadAppId, pConfig); + + if (pAppidActiveConfig->mod_config->instance_id) + checkSandboxDetection(payloadAppId); + + if ((session->payloadAppId ) && (prev_priority > curr_priority )) + return; + + session->payloadAppId = payloadAppId; + + if (session->payloadVersion) + snort_free(session->payloadVersion); + + if (version && *version) + { + session->payloadVersion = *version; + *version = nullptr; + } + else + session->payloadVersion = nullptr; + } + else if (version && *version) + { + if (session->payloadVersion) + snort_free(session->payloadVersion); + session->payloadVersion = *version; + *version = nullptr; + } +} + +static inline void clearSessionAppIdData(AppIdData* session) +{ + session->payloadAppId = APP_ID_UNKNOWN; + session->serviceAppId = APP_ID_UNKNOWN; + session->tpPayloadAppId = APP_ID_UNKNOWN; + session->tpAppId = APP_ID_UNKNOWN; + if (session->payloadVersion) + { + snort_free(session->payloadVersion); + session->payloadVersion = nullptr; + } + if (session->serviceVendor) + { + snort_free(session->serviceVendor); + session->serviceVendor = nullptr; + } + if (session->serviceVersion) + { + snort_free(session->serviceVersion); + session->serviceVersion = nullptr; + } + + if (session->tsession) + session->appTlsSessionDataFree(); + + if (session->hsession) + session->appHttpSessionDataFree(); + + if (session->dsession) + session->appDNSSessionDataFree(); + + if (thirdparty_appid_module) + thirdparty_appid_module->session_delete(session->tpsession, 1); +} + +static inline int initial_CHP_sweep(char** chp_buffers, MatchedCHPAction** ppmatches, + AppIdData* session, const DetectorHttpConfig* pHttpConfig) +{ + CHPApp* cah = nullptr; + int longest = 0; + int size, i; + httpSession* hsession; + int scanKeyFoundSomething=0; + CHPMatchTally* pTally = nullptr; // scanKeyCHP allocates a pointer, but we free it when ready + + hsession = session->hsession; + + for (i = 0; i <= MAX_KEY_PATTERN; i++) + { + ppmatches[i] = nullptr; + if (chp_buffers[i] && (size = strlen(chp_buffers[i])) && + scanKeyCHP((PatternType)i, chp_buffers[i], size, &pTally, &ppmatches[i], pHttpConfig)) + scanKeyFoundSomething=1; + } + if (!scanKeyFoundSomething) + return 0; + + for (i = 0; i < pTally->in_use_elements; i++) + { + // Only those items which have had their key_pattern_countdown field reduced to zero are a + // full match + if (pTally->item[i].key_pattern_countdown) + continue; + if (longest < pTally->item[i].key_pattern_length_sum) + { + // We've found a new longest pattern set + longest = pTally->item[i].key_pattern_length_sum; + cah = pTally->item[i].chpapp; + } + } + // either we have a candidate or we don't so we can free the tally structure either way. + free(pTally); + + if (cah == nullptr) + { + // We were planning to pass along the content of ppmatches to the second phase and let + // them be freed inside scanCHP, but we have no candidate so we free here + for (i = 0; i <= MAX_KEY_PATTERN; i++) + { + if (ppmatches[i]) + { + FreeMatchedCHPActions(ppmatches[i]); + ppmatches[i] = nullptr; + } + } + return 0; + } + + /*************************************************************** + candidate has been chosen and it is pointed to by cah + we will preserve any match sets until the calls to scanCHP() + ***************************************************************/ + for (i = 0; i < NUMBER_OF_PTYPES; i++) + { + ptype_scan_counts[i] = cah->ptype_scan_counts[i]; + hsession->ptype_req_counts[i] = cah->ptype_req_counts[i] + + cah->ptype_rewrite_insert_used[i]; + if (i > 3 && !cah->ptype_scan_counts[i] && !getAppIdFlag(session, + APPID_SESSION_SPDY_SESSION)) + { + clearAppIdFlag(session, APPID_SESSION_CHP_INSPECTING); + if (thirdparty_appid_module) + thirdparty_appid_module->session_attr_clear(session->tpsession, + TP_ATTR_CONTINUE_MONITORING); + } + } + hsession->chp_candidate = cah->appIdInstance; + hsession->app_type_flags = cah->app_type_flags; + hsession->num_matches = cah->num_matches; + hsession->num_scans = cah->num_scans; + + if (thirdparty_appid_module) + { + if ((ptype_scan_counts[CONTENT_TYPE_PT])) + thirdparty_appid_module->session_attr_set(session->tpsession, + TP_ATTR_COPY_RESPONSE_CONTENT); + else + thirdparty_appid_module->session_attr_clear(session->tpsession, + TP_ATTR_COPY_RESPONSE_CONTENT); + + if ((ptype_scan_counts[LOCATION_PT])) + thirdparty_appid_module->session_attr_set(session->tpsession, + TP_ATTR_COPY_RESPONSE_LOCATION); + else + thirdparty_appid_module->session_attr_clear(session->tpsession, + TP_ATTR_COPY_RESPONSE_LOCATION); + + if ((ptype_scan_counts[BODY_PT])) + thirdparty_appid_module->session_attr_set(session->tpsession, + TP_ATTR_COPY_RESPONSE_BODY); + else + thirdparty_appid_module->session_attr_clear(session->tpsession, + TP_ATTR_COPY_RESPONSE_BODY); + } + + return 1; +} + +static const char* httpFieldName[ NUMBER_OF_PTYPES ] = // for use in debug messages +{ + "useragent", + "host", + "referer", + "uri", + "cookie", + "req_body", + "content_type", + "location", + "body", +}; + +static inline void processCHP(AppIdData* session, char** version, Packet* p, const + AppIdConfig* pConfig) +{ + int i, size; + int found_in_buffer = 0; + char* user = nullptr; + AppId chp_final; + AppId ret = 0; + httpSession* http_session = session->hsession; + + char* chp_buffers[NUMBER_OF_PTYPES] = + { + http_session->useragent, + http_session->host, + http_session->referer, + http_session->uri, + http_session->cookie, + http_session->req_body, + http_session->content_type, + http_session->location, + http_session->body, + }; + + char* chp_rewritten[NUMBER_OF_PTYPES] = + { + nullptr,nullptr,nullptr, + nullptr,nullptr,nullptr, + nullptr,nullptr,nullptr + }; + + MatchedCHPAction* chp_matches[NUMBER_OF_PTYPES] = + { + nullptr,nullptr,nullptr, + nullptr,nullptr,nullptr, + nullptr,nullptr,nullptr + }; + + if (http_session->chp_hold_flow) + http_session->chp_finished = 0; + + if (!http_session->chp_candidate) + { + // remove artifacts from previous matches before we start again. + for (i = 0; i < NUMBER_OF_PTYPES; i++) + { + if (http_session->new_field[i]) + { + snort_free(http_session->new_field[i]); + http_session->new_field[i] = nullptr; + } + } + + if (!initial_CHP_sweep(chp_buffers, chp_matches, session, &pConfig->detectorHttpConfig)) + http_session->chp_finished = 1; // this is a failure case. + } + if (!http_session->chp_finished && http_session->chp_candidate) + { + for (i = 0; i < NUMBER_OF_PTYPES; i++) + { + if (ptype_scan_counts[i] && chp_buffers[i] && (size = strlen(chp_buffers[i])) > 0) + { + found_in_buffer = 0; + ret = scanCHP((PatternType)i, chp_buffers[i], size, chp_matches[i], version, + &user, &chp_rewritten[i], &found_in_buffer, + http_session, p, &pConfig->detectorHttpConfig); + chp_matches[i] = nullptr; // freed by scanCHP() + http_session->total_found += found_in_buffer; + http_session->num_scans--; + ptype_scan_counts[i] = 0; + // Give up if scanCHP returns nothing, OR + // (if we did not match the right numbher of patterns in this field AND EITHER + // (there is no match quota [all must match]) OR + // (the total number of matches is less than our match quota)) + if (!ret || + (found_in_buffer < http_session->ptype_req_counts[i] && + (!http_session->num_matches || + http_session->total_found < http_session->num_matches))) + { + http_session->chp_candidate = 0; + break; + } + /* We are finished if we have a num_matches target and we've met it or + if we have done all the scans */ + if (!http_session->num_scans || + (http_session->num_matches && http_session->total_found >= + http_session->num_matches)) + { + http_session->chp_finished = 1; + break; + } + } + else if (ptype_scan_counts[i] && !http_session->chp_hold_flow) + { + /* we have a scan count, but nothing in the buffer, so we should drop out of CHP */ + http_session->chp_candidate = 0; + break; + } + } + if (!http_session->chp_candidate) + { + http_session->chp_finished = 1; + if (*version) + { + snort_free(*version); + *version = nullptr; + } + if (user) + { + snort_free(user); + user = nullptr; + } + for (i = 0; i < NUMBER_OF_PTYPES; i++) + { + if (nullptr != chp_rewritten[i]) + { + snort_free(chp_rewritten[i]); + chp_rewritten[i] = nullptr; + } + } + memset(ptype_scan_counts, 0, 7 * sizeof(ptype_scan_counts[0])); + + // Make it possible for other detectors to run. + http_session->skip_simple_detect = false; + return; + } + if (http_session->chp_candidate && http_session->chp_finished) + { + chp_final = http_session->chp_alt_candidate ? + http_session->chp_alt_candidate : + http_session->chp_candidate >> CHP_APPID_BITS_FOR_INSTANCE; // transform the + // instance into the + // appId. + if (http_session->app_type_flags & APP_TYPE_SERVICE) + { + seServiceAppIdData(session, chp_final, nullptr, version); + } + + if (http_session->app_type_flags & APP_TYPE_CLIENT) + { + setClientAppIdData(session, chp_final, version); + } + + if (http_session->app_type_flags & APP_TYPE_PAYLOAD) + { + setPayloadAppIdData(session, (ApplicationId)chp_final, version); + } + + if (http_session->fflow && http_session->fflow->flow_prepared) + { + finalizeFflow(http_session->fflow, http_session->app_type_flags, + (http_session->fflow->appId ? http_session->fflow->appId : chp_final), p); + snort_free(http_session->fflow); + http_session->fflow = nullptr; + } + if (*version) + *version = nullptr; + if (user) + { + session->username = user; + user = nullptr; + if (http_session->app_type_flags & APP_TYPE_SERVICE) + session->usernameService = chp_final; + else + session->usernameService = session->serviceAppId; + setAppIdFlag(session, APPID_SESSION_LOGIN_SUCCEEDED); + } + for (i = 0; i < NUMBER_OF_PTYPES; i++) + { + if (nullptr != chp_rewritten[i]) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s rewritten %s: %s\n", app_id_debug_session, + httpFieldName[i], chp_rewritten[i]); + if (http_session->new_field[i]) + snort_free(http_session->new_field[i]); + http_session->new_field[i] = chp_rewritten[i]; + chp_rewritten[i] = nullptr; + } + } + http_session->chp_candidate = 0; + //if we're doing safesearch rewrites, we want to continue to hold the flow + if (!http_session->get_offsets_from_rebuilt) + http_session->chp_hold_flow = 0; + session->scan_flags &= ~SCAN_HTTP_VIA_FLAG; + session->scan_flags &= ~SCAN_HTTP_USER_AGENT_FLAG; + session->scan_flags &= ~SCAN_HTTP_HOST_URL_FLAG; + memset(ptype_scan_counts, 0, 7 * sizeof(ptype_scan_counts[0])); + } + else /* if we have a candidate, but we're not finished */ + { + if (user) + { + snort_free(user); + user = nullptr; + } + for (i = 0; i < NUMBER_OF_PTYPES; i++) + { + if (nullptr != chp_rewritten[i]) + { + snort_free(chp_rewritten[i]); + chp_rewritten[i] = nullptr; + } + } + } + } +} + +static inline bool payloadAppIdIsSet(AppIdData* session) +{ + return ( session->payloadAppId || session->tpPayloadAppId ); +} + +static inline void clearMiscHttpFlags(AppIdData* session) +{ + if (!getAppIdFlag(session, APPID_SESSION_SPDY_SESSION)) + { + clearAppIdFlag(session, APPID_SESSION_CHP_INSPECTING); + if (thirdparty_appid_module) + thirdparty_appid_module->session_attr_clear(session->tpsession, + TP_ATTR_CONTINUE_MONITORING); + } +} + +static inline int processHTTPPacket(Packet* p, AppIdData* session, int direction, + HttpParsedHeaders* const, const AppIdConfig* pConfig) +{ + Profile http_profile_context(httpPerfStats); + constexpr auto RESPONSE_CODE_LENGTH = 3; + HeaderMatchedPatterns hmp; + httpSession* http_session; + int start, end, size; + char* version = nullptr; + char* vendorVersion = nullptr; + char* vendor = nullptr; + AppId serviceAppId = 0; + AppId ClientAppId = 0; + AppId payloadAppId = 0; + AppId referredPayloadAppId = 0; + char* host; + char* url; + char* useragent; + char* referer; + char* via; + AppInfoTableEntry* entry; + + http_session = session->hsession; + if (!http_session) + { + clearSessionAppIdData(session); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s attempt to process HTTP packet with no HTTP data\n", + app_id_debug_session); + + return 0; + } + + // For fragmented HTTP headers, do not process if none of the fields are set. + // These fields will get set when the HTTP header is reassembled. + if ((!http_session->useragent) && (!http_session->host) && (!http_session->referer) && + (!http_session->uri)) + { + if (!http_session->skip_simple_detect) + clearMiscHttpFlags(session); + + return 0; + } + + if (direction == APP_ID_FROM_RESPONDER && !getAppIdFlag(session, + APPID_SESSION_RESPONSE_CODE_CHECKED)) + { + if (http_session->response_code) + { + setAppIdFlag(session, APPID_SESSION_RESPONSE_CODE_CHECKED); + if (strlen(http_session->response_code) != RESPONSE_CODE_LENGTH) + { + /* received bad response code. Stop processing this session */ + clearSessionAppIdData(session); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s bad http response code\n", app_id_debug_session); + + return 0; + } + } +#if RESPONSE_CODE_PACKET_THRESHHOLD + else if (++(http_session->response_code_packets) == RESPONSE_CODE_PACKET_THRESHHOLD) + { + setAppIdFlag(session, APPID_SESSION_RESPONSE_CODE_CHECKED); + /* didn't receive response code in first X packets. Stop processing this session */ + clearSessionAppIdData(session); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s no response code received\n", app_id_debug_session); + PREPROC_PROFILE_END(httpPerfStats); + return 0; + } +#endif + } + host = http_session->host; + url = http_session->url; + via = http_session->via; + useragent = http_session->useragent; + referer = http_session->referer; + memset(&hmp, 0, sizeof(hmp)); + + if (session->serviceAppId == APP_ID_NONE) + { + session->serviceAppId = APP_ID_HTTP; + if (pAppidActiveConfig->mod_config->instance_id) + checkSandboxDetection(APP_ID_HTTP); + } + + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s chp_finished %d chp_hold_flow %d\n", app_id_debug_session, + http_session->chp_finished, http_session->chp_hold_flow); + + if (!http_session->chp_finished || http_session->chp_hold_flow) + processCHP(session, &version, p, pConfig); + + if (!http_session->skip_simple_detect) // false unless a match happened with a call to + // processCHP(). + { + if (!getAppIdFlag(session, APPID_SESSION_APP_REINSPECT)) + { + // Scan Server Header for Vendor & Version + if ((thirdparty_appid_module && (session->scan_flags & SCAN_HTTP_VENDOR_FLAG) && + session->hsession->server) || + (!thirdparty_appid_module && getHTTPHeaderLocation(p->data, p->dsize, + HTTP_ID_SERVER, &start, &end, &hmp, &pConfig->detectorHttpConfig) == 1)) + { + if (session->serviceAppId == APP_ID_NONE || session->serviceAppId == APP_ID_HTTP) + { + RNAServiceSubtype* subtype = nullptr; + RNAServiceSubtype** tmpSubtype; + + if (thirdparty_appid_module) + getServerVendorVersion((uint8_t*)session->hsession->server, strlen( + session->hsession->server), &vendorVersion, &vendor, &subtype); + else + getServerVendorVersion(p->data + start, end - start, &vendorVersion, + &vendor, &subtype); + if (vendor || vendorVersion) + { + if (session->serviceVendor) + { + snort_free(session->serviceVendor); + session->serviceVendor = nullptr; + } + if (session->serviceVersion) + { + snort_free(session->serviceVersion); + session->serviceVersion = nullptr; + } + if (vendor) + session->serviceVendor = vendor; + if (vendorVersion) + session->serviceVersion = vendorVersion; + session->scan_flags &= ~SCAN_HTTP_VENDOR_FLAG; + } + if (subtype) + { + for (tmpSubtype = &session->subtype; *tmpSubtype; tmpSubtype = + &(*tmpSubtype)->next) + ; + + *tmpSubtype = subtype; + } + } + } + + if (webdav_found(&hmp)) + { + if (app_id_debug_session_flag && payloadAppId > APP_ID_NONE && + session->payloadAppId != payloadAppId) + LogMessage("AppIdDbg %s data is webdav\n", app_id_debug_session); + setPayloadAppIdData(session, APP_ID_WEBDAV, nullptr); + } + + // Scan User-Agent for Browser types or Skype + if ((session->scan_flags & SCAN_HTTP_USER_AGENT_FLAG) && session->ClientAppId <= + APP_ID_NONE && useragent && (size = strlen(useragent)) > 0) + { + if (version) + { + snort_free(version); + version = nullptr; + } + identifyUserAgent((uint8_t*)useragent, size, &serviceAppId, &ClientAppId, &version, + &pConfig->detectorHttpConfig); + if (app_id_debug_session_flag && serviceAppId > APP_ID_NONE && serviceAppId != + APP_ID_HTTP && session->serviceAppId != serviceAppId) + LogMessage("AppIdDbg %s User Agent is service %d\n", app_id_debug_session, + serviceAppId); + seServiceAppIdData(session, serviceAppId, nullptr, nullptr); + if (app_id_debug_session_flag && ClientAppId > APP_ID_NONE && ClientAppId != + APP_ID_HTTP && session->ClientAppId != ClientAppId) + LogMessage("AppIdDbg %s User Agent is client %d\n", app_id_debug_session, + ClientAppId); + setClientAppIdData(session, ClientAppId, &version); + session->scan_flags &= ~SCAN_HTTP_USER_AGENT_FLAG; + } + + /* Scan Via Header for squid */ + if (!payloadAppIdIsSet(session) && (session->scan_flags & SCAN_HTTP_VIA_FLAG) && via && + (size = strlen(via)) > 0) + { + if (version) + { + snort_free(version); + version = nullptr; + } + payloadAppId = geAppidByViaPattern((uint8_t*)via, size, &version, + &pConfig->detectorHttpConfig); + if (app_id_debug_session_flag && payloadAppId > APP_ID_NONE && + session->payloadAppId != payloadAppId) + LogMessage("AppIdDbg %s VIA is data %d\n", app_id_debug_session, + payloadAppId); + setPayloadAppIdData(session, (ApplicationId)payloadAppId, nullptr); + session->scan_flags &= ~SCAN_HTTP_VIA_FLAG; + } + } + + /* Scan X-Working-With HTTP header */ + if ((thirdparty_appid_module && (session->scan_flags & SCAN_HTTP_XWORKINGWITH_FLAG) && + session->hsession->x_working_with) || + (!thirdparty_appid_module && getHTTPHeaderLocation(p->data, p->dsize, + HTTP_ID_X_WORKING_WITH, &start, &end, &hmp, &pConfig->detectorHttpConfig) == 1)) + { + AppId appId; + + if (thirdparty_appid_module) + appId = scan_header_x_working_with((uint8_t*)session->hsession->x_working_with, + strlen(session->hsession->x_working_with), &version); + else + appId = scan_header_x_working_with(p->data + start, end - start, &version); + + if (appId) + { + if (direction == APP_ID_FROM_INITIATOR) + { + if (app_id_debug_session_flag && ClientAppId > APP_ID_NONE && ClientAppId != + APP_ID_HTTP && session->ClientAppId != ClientAppId) + LogMessage("AppIdDbg %s X is client %d\n", app_id_debug_session, appId); + setClientAppIdData(session, appId, &version); + } + else + { + if (app_id_debug_session_flag && serviceAppId > APP_ID_NONE && serviceAppId != + APP_ID_HTTP && session->serviceAppId != serviceAppId) + LogMessage("AppIdDbg %s X is service %d\n", app_id_debug_session, appId); + seServiceAppIdData(session, appId, nullptr, &version); + } + session->scan_flags &= ~SCAN_HTTP_XWORKINGWITH_FLAG; + } + } + + // Scan Content-Type Header for multimedia types and scan contents + if ((thirdparty_appid_module && (session->scan_flags & SCAN_HTTP_CONTENT_TYPE_FLAG) + && session->hsession->content_type && !payloadAppIdIsSet(session)) || + (!thirdparty_appid_module && !payloadAppIdIsSet(session) && + getHTTPHeaderLocation(p->data, p->dsize, HTTP_ID_CONTENT_TYPE, &start, &end, + &hmp, &pConfig->detectorHttpConfig) == 1)) + { + if (thirdparty_appid_module) + payloadAppId = geAppidByContentType((uint8_t*)session->hsession->content_type, + strlen(session->hsession->content_type), &pConfig->detectorHttpConfig); + else + payloadAppId = geAppidByContentType(p->data + start, end - start, + &pConfig->detectorHttpConfig); + if (app_id_debug_session_flag && payloadAppId > APP_ID_NONE && session->payloadAppId != + payloadAppId) + LogMessage("AppIdDbg %s Content-Type is data %d\n", app_id_debug_session, + payloadAppId); + setPayloadAppIdData(session, (ApplicationId)payloadAppId, nullptr); + session->scan_flags &= ~SCAN_HTTP_CONTENT_TYPE_FLAG; + } + + if (session->scan_flags & SCAN_HTTP_HOST_URL_FLAG) + { + if (version) + { + snort_free(version); + version = nullptr; + } + if (getAppIdFromUrl(host, url, &version, referer, &ClientAppId, &serviceAppId, + &payloadAppId, &referredPayloadAppId, 0, &pConfig->detectorHttpConfig) == 1) + { + // do not overwrite a previously-set client or service + if (session->ClientAppId <= APP_ID_NONE) + { + if (app_id_debug_session_flag && ClientAppId > APP_ID_NONE && ClientAppId != + APP_ID_HTTP && session->ClientAppId != ClientAppId) + LogMessage("AppIdDbg %s URL is client %d\n", app_id_debug_session, + ClientAppId); + setClientAppIdData(session, ClientAppId, nullptr); + } + if (session->serviceAppId <= APP_ID_NONE) + { + if (app_id_debug_session_flag && serviceAppId > APP_ID_NONE && serviceAppId != + APP_ID_HTTP && session->serviceAppId != serviceAppId) + LogMessage("AppIdDbg %s URL is service %d\n", app_id_debug_session, + serviceAppId); + seServiceAppIdData(session, serviceAppId, nullptr, nullptr); + } + // DO overwrite a previously-set data + if (app_id_debug_session_flag && payloadAppId > APP_ID_NONE && + session->payloadAppId != payloadAppId) + LogMessage("AppIdDbg %s URL is data %d\n", app_id_debug_session, + payloadAppId); + setPayloadAppIdData(session, (ApplicationId)payloadAppId, &version); + setReferredPayloadAppIdData(session, referredPayloadAppId); + } + session->scan_flags &= ~SCAN_HTTP_HOST_URL_FLAG; + } + + if (session->ClientAppId == APP_ID_APPLE_CORE_MEDIA) + { + if (session->tpPayloadAppId > APP_ID_NONE) + { + entry = appInfoEntryGet(session->tpPayloadAppId, pConfig); + // only move tpPayloadAppId to client if its got a ClientAppId + if (entry->clientId > APP_ID_NONE) + { + session->miscAppId = session->ClientAppId; + session->ClientAppId = session->tpPayloadAppId; + } + } + else if (session->payloadAppId > APP_ID_NONE) + { + entry = appInfoEntryGet(session->payloadAppId, pConfig); + // only move payloadAppId to client if it has a ClientAppid + if (entry->clientId > APP_ID_NONE) + { + session->miscAppId = session->ClientAppId; + session->ClientAppId = session->payloadAppId; + } + } + } + + clearMiscHttpFlags(session); + } // end DON'T skip_simple_detect + + return 0; +} + +static inline void stopRnaServiceInspection(Packet* p, AppIdData* session, int direction) +{ + if (direction == APP_ID_FROM_INITIATOR) + { + session->service_ip = *p->ptrs.ip_api.get_dst(); + session->service_port = p->ptrs.dp; + } + else + { + session->service_ip = *p->ptrs.ip_api.get_src(); + session->service_port = p->ptrs.sp; + } + + session->rnaServiceState = RNA_STATE_FINISHED; + setAppIdFlag(session, APPID_SESSION_SERVICE_DETECTED); + clearAppIdFlag(session, APPID_SESSION_CONTINUE); +} + +static inline bool isSslDecryptionEnabled(AppIdData* session) +{ + if (getAppIdFlag(session, APPID_SESSION_DECRYPTED)) + return 1; +// FIXIT-M J bad bad bad +// #ifdef UNIT_TEST +// if (session->session_packet_count >= 12) +// return 1; +// return 0; +// #else + return session->ssn->is_proxied(); +// #endif +} + +static inline void checkRestartAppDetection(AppIdData* session) +{ + if (getAppIdFlag(session, APPID_SESSION_DECRYPTED)) + return; + if (!isSslDecryptionEnabled(session)) + return; + + AppId serviceAppId = pickServiceAppId(session); +#ifdef REMOVED_WHILE_NOT_IN_USE + isSslServiceAppId(serviceAppId); +#else + bool isSsl = false; +#endif + + // A session could either: + // 1. Start of as SSL - captured with isSsl flag, OR + // 2. It could start of as a non-SSL session and later change to SSL. For example, FTP->FTPS. + // In this case APPID_SESSION_ENCRYPTED flag is set by the protocol state machine. + if (getAppIdFlag(session, APPID_SESSION_ENCRYPTED) || isSsl) + { + setAppIdFlag(session, APPID_SESSION_DECRYPTED); + session->encrypted.serviceAppId = serviceAppId; + session->encrypted.payloadAppId = pickPayloadId(session); + session->encrypted.ClientAppId = pickClientAppId(session); + session->encrypted.miscAppId = pickMiscAppId(session); + session->encrypted.referredAppId = pickReferredPayloadId(session); + appSharedReInitData(session); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s SSL decryption is available, restarting app Detection\n", + app_id_debug_session); + + // APPID_SESSION_ENCRYPTED is set upon receiving a command which upgrades the session to + // SSL. + // Next packet after the command will have encrypted traffic. + // In the case of a session which starts as SSL, current packet itself is encrypted. Set + // the special flag + // APPID_SESSION_APP_REINSPECT_SSL which allows reinspection of this pcaket. + if (isSsl) + setAppIdFlag(session, APPID_SESSION_APP_REINSPECT_SSL); + } +} + +static inline void updateEncryptedAppId(AppIdData* session, AppId serviceAppId) +{ + switch (serviceAppId) + { + case APP_ID_HTTP: + if (session->miscAppId == APP_ID_NSIIOPS || session->miscAppId == APP_ID_DDM_SSL + || session->miscAppId == APP_ID_MSFT_GC_SSL || session->miscAppId == + APP_ID_SF_APPLIANCE_MGMT) + { + break; + } + session->miscAppId = APP_ID_HTTPS; + break; + case APP_ID_SMTP: + session->miscAppId = APP_ID_SMTPS; + break; + case APP_ID_NNTP: + session->miscAppId = APP_ID_NNTPS; + break; + case APP_ID_IMAP: + session->miscAppId = APP_ID_IMAPS; + break; + case APP_ID_SHELL: + session->miscAppId = APP_ID_SSHELL; + break; + case APP_ID_LDAP: + session->miscAppId = APP_ID_LDAPS; + break; + case APP_ID_FTP_DATA: + session->miscAppId = APP_ID_FTPSDATA; + break; + case APP_ID_FTP: + session->miscAppId = APP_ID_FTPS; + break; + case APP_ID_TELNET: + session->miscAppId = APP_ID_TELNET; + break; + case APP_ID_IRC: + session->miscAppId = APP_ID_IRCS; + break; + case APP_ID_POP3: + session->miscAppId = APP_ID_POP3S; + break; + default: + break; + } +} + +// FIXIT - H Refactor to get this to work with SSL in snort++ environment +//static inline void ExamineSslMetadata(Packet* p, AppIdData* session, AppIdConfig* pConfig) +static inline void ExamineSslMetadata(Packet*, AppIdData*, AppIdConfig*) +{ +#ifdef REMOVED_WHILE_NOT_IN_USE + size_t size; + int ret; + AppId ClientAppId = 0; + AppId payloadAppId = 0; + + if ((session->scan_flags & SCAN_SSL_HOST_FLAG) && session->tsession->tls_host) + { + size = strlen(session->tsession->tls_host); + if ((ret = ssl_scan_hostname((const u_int8_t*)session->tsession->tls_host, size, + &ClientAppId, &payloadAppId, &pConfig->serviceSslConfig))) + { + setClientAppIdData(session, ClientAppId, nullptr); + setPayloadAppIdData(session, (ApplicationId)payloadAppId, nullptr); + setSSLSquelch(p, ret, (ret == 1 ? payloadAppId : ClientAppId)); + } + session->scan_flags &= ~SCAN_SSL_HOST_FLAG; + // ret = 0; + } + if (session->tsession->tls_cname) + { + size = strlen(session->tsession->tls_cname); + if ((ret = ssl_scan_cname((const u_int8_t*)session->tsession->tls_cname, size, + &ClientAppId, &payloadAppId, &pConfig->serviceSslConfig))) + { + setClientAppIdData(session, ClientAppId, nullptr); + setPayloadAppIdData(session, (ApplicationId)payloadAppId, nullptr); + setSSLSquelch(p, ret, (ret == 1 ? payloadAppId : ClientAppId)); + } + snort_free(session->tsession->tls_cname); + session->tsession->tls_cname = nullptr; + // ret = 0; + } + if (session->tsession->tls_orgUnit) + { + size = strlen(session->tsession->tls_orgUnit); + if ((ret = ssl_scan_cname((const u_int8_t*)session->tsession->tls_orgUnit, size, + &ClientAppId, &payloadAppId, &pConfig->serviceSslConfig))) + { + setClientAppIdData(session, ClientAppId, nullptr); + setPayloadAppIdData(session, (ApplicationId)payloadAppId, nullptr); + setSSLSquelch(p, ret, (ret == 1 ? payloadAppId : ClientAppId)); + } + snort_free(session->tsession->tls_orgUnit); + session->tsession->tls_orgUnit = nullptr; + // ret = 0; + } +#endif +} + +static inline int RunClientDetectors(AppIdData* session, + Packet* p, + int direction, + AppIdConfig* pConfig) +{ + int ret = CLIENT_APP_INPROCESS; + + if (session->clientData != nullptr) + { + ret = session->clientData->validate(p->data, p->dsize, direction, + session, p, session->clientData->userData, pConfig); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s %s client detector returned %d\n", app_id_debug_session, + session->clientData->name ? session->clientData->name : "UNKNOWN", ret); + } + else if ( (session->candidate_client_list != nullptr) + && (sflist_count(session->candidate_client_list) > 0) ) + { + SF_LNODE* node; + RNAClientAppModule* client; + + ret = CLIENT_APP_INPROCESS; + (void)sflist_first(session->candidate_client_list, &node); + while (node != nullptr) + { + int result; + SF_LNODE* node_tmp; + + client = (RNAClientAppModule*)node->ndata; + result = client->validate(p->data, p->dsize, direction, + session, p, client->userData, pConfig); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s %s client detector returned %d\n", app_id_debug_session, + client->name ? client->name : "UNKNOWN", result); + + node_tmp = node; + sflist_next(&node); + if (result == CLIENT_APP_SUCCESS) + { + ret = CLIENT_APP_SUCCESS; + session->clientData = client; + sflist_free(session->candidate_client_list); + session->candidate_client_list = nullptr; + break; /* done */ + } + else if (result != CLIENT_APP_INPROCESS) /* fail */ + { + sflist_remove_node(session->candidate_client_list, node_tmp); + } + } + } + + return ret; +} + +static inline void getOffsetsFromRebuilt(Packet* pkt, httpSession* hsession) +{ +// size of "GET /\r\n\r\n" +#define MIN_HTTP_REQ_HEADER_SIZE 9 + const uint8_t cookieStr[] = "Cookie: "; + unsigned cookieStrLen = sizeof(cookieStr)-1; + const uint8_t crlf[] = "\r\n"; + unsigned crlfLen = sizeof(crlf)-1; + const uint8_t crlfcrlf[] = "\r\n\r\n"; + unsigned crlfcrlfLen = sizeof(crlfcrlf)-1; + const uint8_t* p; + uint8_t* headerEnd; + uint16_t headerSize; + + if (!pkt || !pkt->data || pkt->dsize < MIN_HTTP_REQ_HEADER_SIZE) + return; + + p = pkt->data; + + if (!(headerEnd = (uint8_t*)service_strstr(p, pkt->dsize, crlfcrlf, crlfcrlfLen))) + return; + + headerEnd += crlfcrlfLen; + + headerSize = headerEnd - p; + + //uri offset is the index of the first char after the first space in the data + if (!(p = (uint8_t*)memchr(pkt->data, ' ', headerSize))) + return; + hsession->uriOffset = ++p - pkt->data; + headerSize = headerEnd - p; + + //uri end offset is the index of the first CRLF sequence after uri offset + if (!(p = (uint8_t*)service_strstr(p, headerSize, crlf, crlfLen))) + { + // clear uri offset if we can't find an end offset + hsession->uriOffset = 0; + return; + } + hsession->uriEndOffset = p - pkt->data; + headerSize = headerEnd - p; + + //cookie offset is the index of the first char after the cookie header, "Cookie: " + if (!(p = (uint8_t*)service_strstr(p, headerSize, cookieStr, cookieStrLen))) + return; + hsession->cookieOffset = p + cookieStrLen - pkt->data; + headerSize = headerEnd - p; + + //cookie end offset is the index of the first CRLF sequence after cookie offset + if (!(p = (uint8_t*)service_strstr(p, headerSize, crlf, crlfLen))) + { + // clear cookie offset if we can't find a cookie end offset + hsession->cookieOffset = 0; + return; + } + hsession->cookieEndOffset = p - pkt->data; +} + +static int16_t snortId_for_ftp; +static int16_t snortId_for_ftp_data; +static int16_t snortId_for_imap; +static int16_t snortId_for_pop3; +static int16_t snortId_for_smtp; +static int16_t snortId_for_http2; + +static inline void synchAppIdWithSnortId(AppId newAppId, Packet* p, AppIdData* session, + AppIdConfig* pConfig) +{ + AppInfoTableEntry* entry; + int16_t tempSnortId = session->snortId; + + if (tempSnortId == UNSYNCED_SNORT_ID) + { + tempSnortId = session->snortId = p->flow->ssn_state.application_protocol; + } + + if (tempSnortId == snortId_for_ftp || tempSnortId == snortId_for_ftp_data || + tempSnortId == snortId_for_imap || tempSnortId == snortId_for_pop3 || + tempSnortId == snortId_for_smtp || tempSnortId == snortId_for_http2) + { + return; // These preprocessors, in snort proper, already know and expect these to remain + // unchanged. + } + if ((entry = appInfoEntryGet(newAppId, pConfig)) && (tempSnortId = entry->snortId)) + { + // Snort has a separate protocol ID for HTTP/2. We don't. So, when we + // talk to them about it, we have to play by their rules. + if ((newAppId == APP_ID_HTTP) && (session->is_http2)) + tempSnortId = snortId_for_http2; + + if (tempSnortId != session->snortId) + { + if (app_id_debug_session_flag) + if (tempSnortId == snortId_for_http2) + LogMessage("AppIdDbg %s Telling Snort that it's HTTP/2\n", + app_id_debug_session); + + p->flow->ssn_state.application_protocol = tempSnortId; + session->snortId = tempSnortId; + } + } +} + +static inline void checkTerminateTpModule(uint16_t tpPktCount, AppIdData* session) +{ + if ((tpPktCount >= pAppidActiveConfig->mod_config->max_tp_flow_depth) || + (getAppIdFlag(session, APPID_SESSION_HTTP_SESSION | APPID_SESSION_APP_REINSPECT) == + (APPID_SESSION_HTTP_SESSION | APPID_SESSION_APP_REINSPECT) && + session->hsession && session->hsession->uri && + (!session->hsession->chp_candidate || session->hsession->chp_finished))) + { + if (session->tpAppId == APP_ID_NONE) + session->tpAppId = APP_ID_UNKNOWN; + if (session->payloadAppId == APP_ID_NONE) + session->payloadAppId = APP_ID_UNKNOWN; + if (thirdparty_appid_module) + thirdparty_appid_module->session_delete(session->tpsession, 1); + } +} + +//#define DEBUG_PACKETS +#ifdef DEBUG_PACKETS +#define printSnortPacket(SFSnortPacket_ptr) debug_printSnortPacket(SFSnortPacket_ptr) + +#define CHAR_DUMP_WIDTH 60 +static inline void debug_printSnortPacket(SFSnortPacket* p) +{ + if (app_id_debug_flag) + { + char* tweakedPayload; + char* hexPayload; + LogMessage("AppIdDbg \n"); + LogMessage("AppIdDbg ------------------------------------------------\n"); + LogMessage("AppIdDbg \n"); + if (p->data != nullptr && p->dsize) + { + tweakedPayload= (char*)snort_calloc((CHAR_DUMP_WIDTH*2)+1); // room for hex + if (tweakedPayload) + { + int j; + int i; + LogMessage("AppIdDbg data: (%d chars per line)\n",CHAR_DUMP_WIDTH); + for (j=0; jdsize; j+=CHAR_DUMP_WIDTH) + { + for (i=j; idsize && i<(j+CHAR_DUMP_WIDTH); i++) + { + if ((int)p->data[i] >= 32 && (int)p->data[i] <=126) + tweakedPayload[i-j] = p->data[i]; + else + tweakedPayload[i-j] = '.'; + } + tweakedPayload[i-j] = '\0'; + LogMessage("AppIdDbg %s\n", tweakedPayload); + } +//#define DUMP_IN_HEX +#ifdef DUMP_IN_HEX + LogMessage("AppIdDbg HEX data: (%d chars per line)\n",CHAR_DUMP_WIDTH); + for (j=0; jdsize; j+=CHAR_DUMP_WIDTH) + { + for (i=j; idsize && i<(j+CHAR_DUMP_WIDTH); i++) + { + sprintf(&tweakedPayload[(i-j)*2], "%02x", (p->data)[i]); + } + // terminating '\0' provided by sprintf() + LogMessage("AppIdDbg %s\n", tweakedPayload); + } +#endif + snort_free(tweakedPayload); tweakedPayload = nullptr; + } + } + if (p->flow) + { + LogMessage( + "AppIdDbg \nAppIdDbg for p->flow=%p is_session_decrypted=%d direction=%d\n", + p->flow, _dpd.streamAPI->is_session_decrypted(p->flow), + _dpd.sessionAPI->get_ignore_direction(p->flow)); + } + + LogMessage("AppIdDbg ptrs.sp: %d\n", p->ptrs.sp); + LogMessage("AppIdDbg ptrs.dp: %d\n", p->ptrs.dp); + LogMessage("AppIdDbg orig_src_port: %d\n", p->orig_src_port); + LogMessage("AppIdDbg rig_dst_port: %d\n", p->orig_dst_port); + LogMessage("AppIdDbg payloadsize: %d\n", p->dsize); + + if ((p->packet_flags) & 0x00000080) + { + LogMessage("AppIdDbg direction: client\n"); + } + else if ((p->packet_flags) & 0x00000040) + { + LogMessage("AppIdDbg direction: server\n"); + } + else + { + LogMessage("AppIdDbg direction: unknown\n"); + } + + if ((p->packet_flags) & 0x00000001) + LogMessage("AppIdDbg A rebuilt fragment\n"); + if ((p->packet_flags) & 0x00000002) + LogMessage("AppIdDbg A rebuilt stream\n"); + if ((p->packet_flags) & 0x00000004) + LogMessage( + "AppIdDbg From an unestablished stream, traffic send one direction only\n"); + if ((p->packet_flags) & 0x00000008) + LogMessage("AppIdDbg From an established stream\n"); + if ((p->packet_flags) & 0x00000010) + LogMessage("AppIdDbg this packet has been queued for stream reassembly\n"); + if ((p->packet_flags) & 0x00000020) + LogMessage("AppIdDbg packet completes the 3 way handshake\n"); + if ((p->packet_flags) & 0x00000040) + LogMessage("AppIdDbg packet come from server side of a connection(tcp)\n"); + if ((p->packet_flags) & 0x00000080) + LogMessage("AppIdDbg packet come from client side of a connection(tcp)\n"); + if ((p->packet_flags) & 0x00000100) + LogMessage("AppIdDbg start of PDU\n"); + if ((p->packet_flags) & 0x00000200) + LogMessage("AppIdDbg end of PDU\n"); + if ((p->packet_flags) & 0x00800000) + LogMessage("AppIdDbg packet has new size\n"); + } +} + +#else +#define printSnortPacket(SFSnortPacket_ptr) +#endif + +void fwAppIdInit() +{ + /* init globals for snortId compares */ + snortId_for_ftp = FindProtocolReference("ftp"); + snortId_for_ftp_data = FindProtocolReference("ftp-data"); + snortId_for_imap = FindProtocolReference("imap"); + snortId_for_pop3 = FindProtocolReference("pop3"); + snortId_for_smtp = FindProtocolReference("smtp"); + snortId_for_http2 = FindProtocolReference("http2"); +} + +void fwAppIdSearch(Packet* p) +{ + AppIdData* session; + IpProtocol protocol; + AppId tpAppId = 0; + AppId serviceAppId = 0; + AppId ClientAppId = 0; + AppId payloadAppId = 0; + bool isTpAppidDiscoveryDone = false; + uint64_t flow_flags; + int direction; + const sfip_t* ip; + uint16_t port; + size_t size; + int tp_confidence; + AppId* tp_proto_list; + ThirdPartyAppIDAttributeData* tp_attribute_data; + AppInfoTableEntry* entry; + AppIdConfig* pConfig = pAppidActiveConfig; + bool is_decrypted; + bool is_rebuilt; + bool is_http2; + + app_id_raw_packet_count++; + + if (!p->flow) + { + app_id_ignored_packet_count++; + return; + } + + is_decrypted = p->flow->is_proxied(); + is_rebuilt = p->is_rebuilt(); +// FIXIT-M - Need to convert this _dpd stream api call to the correct snort++ method +#ifdef REMOVED_WHILE_NOT_IN_USE + is_http2 = _dpd.streamAPI->is_session_http2(p->flow); +#else + is_http2 = false; +#endif + if (is_http2) + { + session = getAppIdData(p->flow); + if (session) + session->is_http2 = true; + if (!is_rebuilt) + { + // For HTTP/2, we only want to look at the ones that are rebuilt from + // Stream / HTTP Inspect as HTTP/1 packets. + app_id_ignored_packet_count++; + return; + } + } + else // not HTTP/2 + { + if (is_rebuilt && !is_decrypted) + { + session = getAppIdData(p->flow); + if (session && session->hsession && session->hsession->get_offsets_from_rebuilt) + { + getOffsetsFromRebuilt(p, session->hsession); + if (app_id_debug_session_flag) + LogMessage( + "AppIdDbg %s offsets from rebuilt packet: uri: %u-%u cookie: %u-%u\n", + app_id_debug_session, session->hsession->uriOffset, + session->hsession->uriEndOffset, session->hsession->cookieOffset, + session->hsession->cookieEndOffset); + } + app_id_ignored_packet_count++; + return; + } + } + + session = appSharedGetData(p); + if (session) + { + if (session->common.fsf_type.flow_type == APPID_SESSION_TYPE_IGNORE) + return; + if (session->common.fsf_type.flow_type == APPID_SESSION_TYPE_NORMAL) + { + protocol = session->proto; + session->ssn = p->flow; + } + else if (p->is_tcp()) + protocol = IpProtocol::TCP; + else + protocol = IpProtocol::UDP; + + ip = p->ptrs.ip_api.get_src(); + if (session->common.initiator_port) + direction = (session->common.initiator_port == p->ptrs.sp) ? APP_ID_FROM_INITIATOR : + APP_ID_FROM_RESPONDER; + else + direction = (sfip_fast_equals_raw(ip, &session->common.initiator_ip)) ? + APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER; + } + else + { + if (p->is_tcp()) + protocol = IpProtocol::TCP; + + else if (p->is_udp()) + protocol = IpProtocol::UDP; + + else if ( p->is_ip4() || p->is_ip6() ) + protocol = p->get_ip_proto_next(); + + else + return; + + // FIXIT - L Refactor to use direction symbols defined by snort proper + direction = p->is_from_client() ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER; + } + + app_id_debug_session_flag = fwAppIdDebugCheck(p->flow, session, app_id_debug_flag, + &app_id_debug_info, app_id_debug_session, direction); + //if (app_id_debug_session_flag) + // LogMessage("AppIdDbg %s flag:%08x,decrypt:%s,http2:%s\n", app_id_debug_session, + // p->packet_flags, is_decrypted?"T":"F", is_http2?"T":"F"); + + // fwAppIdSearch() is a top-level function that is called by AppIdProcess(). + // At this point, we know that we need to use the current active config - + // pAppidActiveConfig. This function uses pAppidActiveConfig and passes it + // to all the functions that need to look at AppId config. + flow_flags = isSessionMonitored(p, direction, session); + if (!(flow_flags & (APPID_SESSION_DISCOVER_APP | APPID_SESSION_SPECIAL_MONITORED))) + { + if (!session) + { + if ((flow_flags & APPID_SESSION_BIDIRECTIONAL_CHECKED) == + APPID_SESSION_BIDIRECTIONAL_CHECKED) + { + // FIXIT-M: This _dpd call needs to be convert to correct snort++ call + // static THREAD_LOCAL APPID_SESSION_STRUCT_FLAG ignore_fsf { + // APPID_SESSION_TYPE_IGNORE }; + + // _dpd.sessionAPI->set_application_data(p->flow, PP_APP_ID, &ignore_fsf, + // nullptr); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s not monitored\n", app_id_debug_session); + } + else + { + AppIdData* tmp_session; + + tmp_session = (decltype(tmp_session))snort_calloc(sizeof(AppIdData)); + + tmp_session->common.fsf_type.flow_type = APPID_SESSION_TYPE_TMP; + tmp_session->common.flags = flow_flags; + ip = (direction == APP_ID_FROM_INITIATOR) ? + p->ptrs.ip_api.get_src() : p->ptrs.ip_api.get_dst(); + tmp_session->common.initiator_ip = *ip; + if ( (protocol == IpProtocol::TCP || protocol == IpProtocol::UDP ) && + p->ptrs.sp != p->ptrs.dp ) + { + tmp_session->common.initiator_port = (direction == APP_ID_FROM_INITIATOR) ? + p->ptrs.sp : p->ptrs.dp; + } + else + tmp_session->common.initiator_port = 0; + tmp_session->common.policyId = appIdPolicyId; + p->flow->set_application_data(tmp_session); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s unknown monitoring\n", app_id_debug_session); + } + } + else + { + session->common.flags = flow_flags; + if ((flow_flags & APPID_SESSION_BIDIRECTIONAL_CHECKED) == + APPID_SESSION_BIDIRECTIONAL_CHECKED) + session->common.fsf_type.flow_type = APPID_SESSION_TYPE_IGNORE; + session->common.policyId = appIdPolicyId; + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s not monitored\n", app_id_debug_session); + } + return; + } + + if (!session || session->common.fsf_type.flow_type == APPID_SESSION_TYPE_TMP) + { + /* This call will free the existing temporary session, if there is one */ + session = appSharedCreateData(p, protocol, direction); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s new session\n", app_id_debug_session); + } + + app_id_processed_packet_count++; + session->session_packet_count++; + + if (direction == APP_ID_FROM_INITIATOR) + session->stats.initiatorBytes += p->pkth->pktlen; + else + session->stats.responderBytes += p->pkth->pktlen; + + session->common.flags = flow_flags; + session->common.policyId = appIdPolicyId; + + tpAppId = session->tpAppId; + + session->common.policyId = appIdPolicyId; + + if (getAppIdFlag(session, APPID_SESSION_IGNORE_FLOW)) + { + if (app_id_debug_session_flag && !getAppIdFlag(session, APPID_SESSION_IGNORE_FLOW_LOGGED)) + { + setAppIdFlag(session, APPID_SESSION_IGNORE_FLOW_LOGGED); + LogMessage("AppIdDbg %s Ignoring connection with service %d\n", + app_id_debug_session, session->serviceAppId); + } + return; + } + + if (p->packet_flags & PKT_STREAM_ORDER_BAD) + setAppIdFlag(session, APPID_SESSION_OOO); + + else if ( p->is_tcp() && p->ptrs.tcph ) + { + const auto* tcph = p->ptrs.tcph; + if ( tcph->is_rst() && session->previous_tcp_flags == TH_SYN ) + { + AppIdServiceIDState* id_state; + + setAppIdFlag(session, APPID_SESSION_SYN_RST); + if (sfip_is_set(&session->service_ip)) + { + ip = &session->service_ip; + port = session->service_port; + } + else + { + ip = p->ptrs.ip_api.get_src(); + port = p->ptrs.sp; + } + + id_state = AppIdGetServiceIDState(ip, IpProtocol::TCP, port, + AppIdServiceDetectionLevel( + session)); + + if (id_state) + { + if (!id_state->reset_time) + id_state->reset_time = packet_time(); + else if ((packet_time() - id_state->reset_time) >= 60) + { + // FIXIT-H ip's on the packet are const + // AppIdRemoveServiceIDState(ip, IpProtocol::TCP, port, + // AppIdServiceDetectionLevel(session)); + setAppIdFlag(session, APPID_SESSION_SERVICE_DELETED); + } + } + } + + session->previous_tcp_flags = p->ptrs.tcph->th_flags; + } + + /*HostPort based AppId. */ + if (!(session->scan_flags & SCAN_HOST_PORT_FLAG)) + { + HostPortVal* hv; + + session->scan_flags |= SCAN_HOST_PORT_FLAG; + if (direction == APP_ID_FROM_INITIATOR) + { + ip = p->ptrs.ip_api.get_dst(); + port = p->ptrs.dp; + } + else + { + ip = p->ptrs.ip_api.get_src(); + port = p->ptrs.sp; + } + + if ((hv = hostPortAppCacheFind(ip, port, protocol, pConfig))) + { + switch (hv->type) + { + case 1: + session->ClientAppId = hv->appId; + session->rnaClientState = RNA_STATE_FINISHED; + break; + case 2: + session->payloadAppId = hv->appId; + break; + default: + session->serviceAppId = hv->appId; + synchAppIdWithSnortId(hv->appId, p, session, pConfig); + session->rnaServiceState = RNA_STATE_FINISHED; + session->rnaClientState = RNA_STATE_FINISHED; + setAppIdFlag(session, APPID_SESSION_SERVICE_DETECTED); + if (thirdparty_appid_module) + thirdparty_appid_module->session_delete(session->tpsession, 1); + session->tpsession = nullptr; + } + } + } + + checkRestartAppDetection(session); + + //restart inspection by 3rd party + if (!getAppIdFlag(session, APPID_SESSION_NO_TPI)) + { + if (TPIsAppIdDone(session->tpsession) && getAppIdFlag(session, + APPID_SESSION_HTTP_SESSION) && p->dsize) + { + if (session->tpReinspectByInitiator) + { + clearAppIdFlag(session, APPID_SESSION_APP_REINSPECT); + if (direction == APP_ID_FROM_RESPONDER) + session->tpReinspectByInitiator = 0; //toggle at OK response + } + else if (direction == APP_ID_FROM_INITIATOR) + { + session->tpReinspectByInitiator = 1; //once per request + setAppIdFlag(session, APPID_SESSION_APP_REINSPECT); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s 3rd party allow reinspect http\n", + app_id_debug_session); + session->appHttpFieldClear(); + } + } + } + + if (session->tpAppId == APP_ID_SSH && session->payloadAppId != APP_ID_SFTP && + session->session_packet_count >= MIN_SFTP_PACKET_COUNT && session->session_packet_count < + MAX_SFTP_PACKET_COUNT) + { + if ( p->ptrs.ip_api.tos() == 8 ) + { + session->payloadAppId = APP_ID_SFTP; + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s data is SFTP\n", app_id_debug_session); + } + } + + Profile tpPerfStats_profile_context(tpPerfStats); + + /*** Start of third-party processing. ***/ + if (thirdparty_appid_module && + !getAppIdFlag(session, APPID_SESSION_NO_TPI) && + (!TPIsAppIdDone(session->tpsession) || + getAppIdFlag(session, APPID_SESSION_APP_REINSPECT | APPID_SESSION_APP_REINSPECT_SSL))) + { + // First SSL decrypted packet is now being inspected. Reset the flag so that SSL decrypted + // traffic + // gets processed like regular traffic from next packet onwards + if (getAppIdFlag(session, APPID_SESSION_APP_REINSPECT_SSL)) + { + clearAppIdFlag(session, APPID_SESSION_APP_REINSPECT_SSL); + } + if (p->dsize || pAppidActiveConfig->mod_config->tp_allow_probes) + { + if (protocol != IpProtocol::TCP || !p->dsize || (p->packet_flags & + PKT_STREAM_ORDER_OK) || + pAppidActiveConfig->mod_config->tp_allow_probes) + { + { + Profile tpLibPerfStats_profile_context(tpLibPerfStats); + if (!session->tpsession) + { + if (!(session->tpsession = thirdparty_appid_module->session_create())) + FatalError( + "Could not allocate AppIdData->tpsession data"); + } + + printSnortPacket(p); // debug output of packet content + thirdparty_appid_module->session_process(session->tpsession, p, direction, + &tpAppId, &tp_confidence, &tp_proto_list, &tp_attribute_data); + } + + isTpAppidDiscoveryDone = true; + if (thirdparty_appid_module->session_state_get(session->tpsession) == + TP_STATE_CLASSIFIED) + clearAppIdFlag(session, APPID_SESSION_APP_REINSPECT); + + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s 3rd party returned %d\n", app_id_debug_session, + tpAppId); + + if (appInfoEntryFlagGet(tpAppId, APPINFO_FLAG_IGNORE, pConfig)) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s 3rd party ignored\n", app_id_debug_session); + if (getAppIdFlag(session, APPID_SESSION_HTTP_SESSION)) + tpAppId = APP_ID_HTTP; + else + tpAppId = APP_ID_NONE; + } + + // For now, third party can detect HTTP/2 (w/o metadata) for + // some cases. Treat it like HTTP w/ is_http2 flag set. + if ((tpAppId == APP_ID_HTTP2) && (tp_confidence == 100)) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s 3rd party saw HTTP/2\n", app_id_debug_session); + tpAppId = APP_ID_HTTP; + session->is_http2 = true; + } + + // if the third-party appId must be treated as a client, do it now + if (appInfoEntryFlagGet(tpAppId, APPINFO_FLAG_TP_CLIENT, pAppidActiveConfig)) + session->ClientAppId = tpAppId; + + ProcessThirdPartyResults(p, session, tp_confidence, tp_proto_list, + tp_attribute_data); +#ifdef REMOVED_WHILE_NOT_IN_USE + if (getAppIdFlag(session, APPID_SESSION_SSL_SESSION) && + !(session->scan_flags & SCAN_SSL_HOST_FLAG)) + { + setSSLSquelch(p, 1, tpAppId); + } +#endif + } + else + { + tpAppId = APP_ID_NONE; + if (app_id_debug_session_flag && !getAppIdFlag(session, + APPID_SESSION_TPI_OOO_LOGGED)) + { + setAppIdFlag(session, APPID_SESSION_TPI_OOO_LOGGED); + LogMessage("AppIdDbg %s 3rd party packet out-of-order\n", + app_id_debug_session); + } + } + + if (thirdparty_appid_module->session_state_get(session->tpsession) == + TP_STATE_MONITORING) + { + thirdparty_appid_module->disable_flags(session->tpsession, + TP_SESSION_FLAG_ATTRIBUTE | TP_SESSION_FLAG_TUNNELING | + TP_SESSION_FLAG_FUTUREFLOW); + } + + if (tpAppId == APP_ID_SSL && + (stream.get_application_protocol_id(p->flow) == snortId_for_ftp_data)) + { + // If we see SSL on an FTP data channel set tpAppId back + // to APP_ID_NONE so the FTP preprocessor picks up the flow. + tpAppId = APP_ID_NONE; + } + + if (tpAppId > APP_ID_NONE && (!getAppIdFlag(session, APPID_SESSION_APP_REINSPECT) || + session->payloadAppId > APP_ID_NONE)) + { + AppId snorAppId; + + // if the packet is HTTP, then search for via pattern + if (getAppIdFlag(session, APPID_SESSION_HTTP_SESSION) && session->hsession) + { + snorAppId = APP_ID_HTTP; + //data should never be APP_ID_HTTP + if (tpAppId != APP_ID_HTTP) + session->tpPayloadAppId = tpAppId; + + session->tpAppId = APP_ID_HTTP; + + processHTTPPacket(p, session, direction, nullptr, pAppidActiveConfig); + + if (TPIsAppIdAvailable(session->tpsession) && session->tpAppId == APP_ID_HTTP + && !getAppIdFlag(session, APPID_SESSION_APP_REINSPECT)) + { + session->rnaClientState = RNA_STATE_FINISHED; + setAppIdFlag(session, APPID_SESSION_CLIENT_DETECTED | + APPID_SESSION_SERVICE_DETECTED); + session->rnaServiceState = RNA_STATE_FINISHED; + clearAppIdFlag(session, APPID_SESSION_CONTINUE); + if (direction == APP_ID_FROM_INITIATOR) + { + ip = p->ptrs.ip_api.get_dst(); + session->service_ip = *ip; + session->service_port = p->ptrs.dp; + } + else + { + ip = p->ptrs.ip_api.get_src(); + session->service_ip = *ip; + session->service_port = p->ptrs.sp; + } + } + } + else if (getAppIdFlag(session, APPID_SESSION_SSL_SESSION) && session->tsession) + { + ExamineSslMetadata(p, session, pConfig); + + uint16_t serverPort; + AppId porAppId; + + serverPort = (direction == APP_ID_FROM_INITIATOR) ? p->ptrs.dp : p->ptrs.sp; + +#ifdef REMOVED_WHILE_NOT_IN_USE + porAppId = getSslServiceAppId(serverPort); +#else + porAppId = serverPort; +#endif + if (tpAppId == APP_ID_SSL ) + { + tpAppId = porAppId; + + //SSL policy needs to determine IMAPS/POP3S etc before appId sees first + // server packet + session->portServiceAppId = porAppId; + + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s SSL is service %d, portServiceAppId %d\n", + app_id_debug_session, tpAppId, session->portServiceAppId); + } + else + { + session->tpPayloadAppId = tpAppId; + tpAppId = porAppId; + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s SSL is %d\n", app_id_debug_session, tpAppId); + } + session->tpAppId = tpAppId; + snorAppId = APP_ID_SSL; + } + else + { + //for non-http protocols, tp id is treated like serviceId + + snorAppId = tpAppId; + session->tpAppId = tpAppId; + } + + synchAppIdWithSnortId(snorAppId, p, session, pConfig); + } + else + { + if ( protocol != IpProtocol::TCP || !p->dsize || + (p->packet_flags & (PKT_STREAM_ORDER_OK | PKT_STREAM_ORDER_BAD))) + { + if (direction == APP_ID_FROM_INITIATOR) + { + session->init_tpPackets++; + checkTerminateTpModule(session->init_tpPackets, session); + } + else + { + session->resp_tpPackets++; + checkTerminateTpModule(session->resp_tpPackets, session); + } + } + } + } + } + + if (direction == APP_ID_FROM_RESPONDER && !getAppIdFlag(session, + APPID_SESSION_PORT_SERVICE_DONE|APPID_SESSION_SYN_RST)) + { + setAppIdFlag(session, APPID_SESSION_PORT_SERVICE_DONE); + session->portServiceAppId = getPortServiceId(protocol, p->ptrs.sp, pConfig); + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s port service %d\n", app_id_debug_session, + session->portServiceAppId); + } + + /* Length-based detectors. */ + /* Only check if: + * - Port service didn't find anything (and we haven't yet either). + * - We haven't hit the max packets allowed for detector sequence matches. + * - Packet has data (we'll ignore 0-sized packets in sequencing). */ + if ( (session->portServiceAppId <= APP_ID_NONE) + && (session->length_sequence.sequence_cnt < LENGTH_SEQUENCE_CNT_MAX) + && (p->dsize > 0)) + { + uint8_t index = session->length_sequence.sequence_cnt; + session->length_sequence.proto = protocol; + session->length_sequence.sequence_cnt++; + session->length_sequence.sequence[index].direction = direction; + session->length_sequence.sequence[index].length = p->dsize; + session->portServiceAppId = lengthAppCacheFind(&session->length_sequence, pConfig); + if (session->portServiceAppId > APP_ID_NONE) + { + setAppIdFlag(session, APPID_SESSION_PORT_SERVICE_DONE); + } + } + + /* exceptions for rexec and any other service detector that needs to see SYN and SYN/ACK */ + if (getAppIdFlag(session, APPID_SESSION_REXEC_STDERR)) + { + AppIdDiscoverService(p, direction, session, pConfig); + if (session->serviceAppId == APP_ID_DNS && + pAppidActiveConfig->mod_config->dns_host_reporting && + session->dsession && session->dsession->host ) + { + size = session->dsession->host_len; + dns_host_scan_hostname((const u_int8_t*)session->dsession->host, size, &ClientAppId, + &payloadAppId, &pConfig->serviceDnsConfig); + setClientAppIdData(session, ClientAppId, nullptr); + } + else if (session->serviceAppId == APP_ID_RTMP) + ExamineRtmpMetadata(session); + else if (getAppIdFlag(session, APPID_SESSION_SSL_SESSION) && session->tsession) + ExamineSslMetadata(p, session, pConfig); + } + else if (protocol != IpProtocol::TCP || !p->dsize || (p->packet_flags & PKT_STREAM_ORDER_OK)) + { + /*** Start of service discovery. ***/ + if (session->rnaServiceState != RNA_STATE_FINISHED) + { + Profile serviceMatchPerfStats_profile_context(serviceMatchPerfStats); + + uint32_t prevRnaServiceState; + + tpAppId = session->tpAppId; + prevRnaServiceState = session->rnaServiceState; + + //decision to directly call validator or go through elaborate service_state tracking + //is made once at the beginning of sesssion. + if (session->rnaServiceState == RNA_STATE_NONE && p->dsize) + { + if ( p->flow->get_session_flags() & SSNFLAG_MIDSTREAM ) + { + // Unless it could be ftp control + if ( protocol == IpProtocol::TCP && (p->ptrs.sp == 21 || p->ptrs.dp == 21) && + !(p->ptrs.tcph->is_fin() || p->ptrs.tcph->is_rst()) ) + { + setAppIdFlag(session, APPID_SESSION_CLIENT_DETECTED | + APPID_SESSION_NOT_A_SERVICE | APPID_SESSION_SERVICE_DETECTED); + if (!AddFTPServiceState(session)) + { + setAppIdFlag(session, APPID_SESSION_CONTINUE); + if (p->ptrs.dp != 21) + setAppIdFlag(session, APPID_SESSION_RESPONDER_SEEN); + } + session->rnaServiceState = RNA_STATE_STATEFUL; + } + else + { + setAppIdFlag(session, APPID_SESSION_MID | APPID_SESSION_SERVICE_DETECTED); + session->rnaServiceState = RNA_STATE_FINISHED; + } + } + else if (TPIsAppIdAvailable(session->tpsession)) + { + if (tpAppId > APP_ID_NONE) + { + //tp has positively identified appId, Dig deeper only if sourcefire + // detector + //identifies additional information or flow is UDP reveresed. + if ((entry = appInfoEntryGet(tpAppId, pConfig)) + && entry->svrValidator && + ((entry->flags & APPINFO_FLAG_SERVICE_ADDITIONAL) || + ((entry->flags & APPINFO_FLAG_SERVICE_UDP_REVERSED) && protocol == + IpProtocol::UDP && + getAppIdFlag(session, APPID_SESSION_INITIATOR_MONITORED | + APPID_SESSION_RESPONDER_MONITORED)))) + { + AppIdFlowdataDeleteAllByMask(session, + APPID_SESSION_DATA_SERVICE_MODSTATE_BIT); + + session->serviceData = entry->svrValidator; + session->rnaServiceState = RNA_STATE_STATEFUL; + } + else + { + stopRnaServiceInspection(p, session, direction); + } + } + else + session->rnaServiceState = RNA_STATE_STATEFUL; + } + else + session->rnaServiceState = RNA_STATE_STATEFUL; + } + + //stop rna inspection as soon as tp has classified a valid AppId later in the session + if (session->rnaServiceState == RNA_STATE_STATEFUL && + prevRnaServiceState == RNA_STATE_STATEFUL && + !getAppIdFlag(session, APPID_SESSION_NO_TPI) && + TPIsAppIdAvailable(session->tpsession) && + tpAppId > APP_ID_NONE && tpAppId < SF_APPID_MAX) + { + entry = appInfoEntryGet(tpAppId, pConfig); + + if (entry && entry->svrValidator && !(entry->flags & + APPINFO_FLAG_SERVICE_ADDITIONAL)) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s Stopping service detection\n", + app_id_debug_session); + stopRnaServiceInspection(p, session, direction); + } + } + + if (session->rnaServiceState == RNA_STATE_STATEFUL) + { + AppIdDiscoverService(p, direction, session, pConfig); + isTpAppidDiscoveryDone = true; + //to stop executing validator after service has been detected by RNA. + if (getAppIdFlag(session, APPID_SESSION_SERVICE_DETECTED | + APPID_SESSION_CONTINUE) == APPID_SESSION_SERVICE_DETECTED) + session->rnaServiceState = RNA_STATE_FINISHED; + + if (session->serviceAppId == APP_ID_DNS && + pAppidActiveConfig->mod_config->dns_host_reporting && + session->dsession && session->dsession->host ) + { + size = session->dsession->host_len; + dns_host_scan_hostname((const u_int8_t*)session->dsession->host, size, + &ClientAppId, &payloadAppId, &pConfig->serviceDnsConfig); + setClientAppIdData(session, ClientAppId, nullptr); + } + else if (session->serviceAppId == APP_ID_RTMP) + ExamineRtmpMetadata(session); + else if (getAppIdFlag(session, APPID_SESSION_SSL_SESSION) && session->tsession) + ExamineSslMetadata(p, session, pConfig); + + if (tpAppId <= APP_ID_NONE && + getAppIdFlag(session, APPID_SESSION_SERVICE_DETECTED | + APPID_SESSION_NOT_A_SERVICE | APPID_SESSION_IGNORE_HOST) == + APPID_SESSION_SERVICE_DETECTED) + { + synchAppIdWithSnortId(session->serviceAppId, p, session, pConfig); + } + } + } + /*** End of service discovery. ***/ + + /*** Start of client discovery. ***/ + if (session->rnaClientState != RNA_STATE_FINISHED) + { + Profile clientMatchPerfStats_profile_context(clientMatchPerfStats); + uint32_t prevRnaClientState = session->rnaClientState; + bool was_http2 = session->is_http2; + bool was_service = getAppIdFlag(session, APPID_SESSION_SERVICE_DETECTED) ? true : + false; + + //decision to directly call validator or go through elaborate service_state tracking + //is made once at the beginning of sesssion. + if (session->rnaClientState == RNA_STATE_NONE && p->dsize && direction == + APP_ID_FROM_INITIATOR) + { + if ( p->flow->get_session_flags() & SSNFLAG_MIDSTREAM ) + session->rnaClientState = RNA_STATE_FINISHED; + + else if (TPIsAppIdAvailable(session->tpsession) && (tpAppId = session->tpAppId) > + APP_ID_NONE && tpAppId < SF_APPID_MAX) + { + AppInfoTableEntry* entry; + + if ((entry = appInfoEntryGet(tpAppId, pConfig)) && entry->clntValidator && + ((entry->flags & APPINFO_FLAG_CLIENT_ADDITIONAL) || + ((entry->flags & APPINFO_FLAG_CLIENT_USER) && + getAppIdFlag(session, APPID_SESSION_DISCOVER_USER)))) + { + //tp has positively identified appId, Dig deeper only if sourcefire + // detector + //identifies additional information + session->clientData = entry->clntValidator; + session->rnaClientState = RNA_STATE_DIRECT; + } + else + { + setAppIdFlag(session, APPID_SESSION_CLIENT_DETECTED); + session->rnaClientState = RNA_STATE_FINISHED; + } + } + else if (getAppIdFlag(session, APPID_SESSION_HTTP_SESSION)) + session->rnaClientState = RNA_STATE_FINISHED; + else + session->rnaClientState = RNA_STATE_STATEFUL; + } + + //stop rna inspection as soon as tp has classified a valid AppId later in the session + if ((session->rnaClientState == RNA_STATE_STATEFUL || + session->rnaClientState == RNA_STATE_DIRECT) && + session->rnaClientState == prevRnaClientState && + !getAppIdFlag(session, APPID_SESSION_NO_TPI) && + TPIsAppIdAvailable(session->tpsession) && + tpAppId > APP_ID_NONE && tpAppId < SF_APPID_MAX) + { + entry = appInfoEntryGet(tpAppId, pConfig); + + if (!(entry && entry->clntValidator && entry->clntValidator == + session->clientData && (entry->flags & (APPINFO_FLAG_CLIENT_ADDITIONAL| + APPINFO_FLAG_CLIENT_USER)))) + { + session->rnaClientState = RNA_STATE_FINISHED; + setAppIdFlag(session, APPID_SESSION_CLIENT_DETECTED); + } + } + + if (session->rnaClientState == RNA_STATE_DIRECT) + { + int ret = CLIENT_APP_INPROCESS; + + if (direction == APP_ID_FROM_INITIATOR) + { + /* get out if we've already tried to validate a client app */ + if (!getAppIdFlag(session, APPID_SESSION_CLIENT_DETECTED)) + { + ret = RunClientDetectors(session, p, direction, pConfig); + } + } + else if (session->rnaServiceState != RNA_STATE_STATEFUL && + getAppIdFlag(session, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS)) + { + ret = RunClientDetectors(session, p, direction, pConfig); + } + + switch (ret) + { + case CLIENT_APP_INPROCESS: + break; + default: + session->rnaClientState = RNA_STATE_FINISHED; + break; + } + } + else if (session->rnaClientState == RNA_STATE_STATEFUL) + { + AppIdDiscoverClientApp(p, direction, session, pConfig); + isTpAppidDiscoveryDone = true; + if (session->candidate_client_list != nullptr) + { + if (sflist_count(session->candidate_client_list) > 0) + { + int ret = 0; + if (direction == APP_ID_FROM_INITIATOR) + { + /* get out if we've already tried to validate a client app */ + if (!getAppIdFlag(session, APPID_SESSION_CLIENT_DETECTED)) + { + ret = RunClientDetectors(session, p, direction, pConfig); + } + } + else if (session->rnaServiceState != RNA_STATE_STATEFUL && + getAppIdFlag(session, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS)) + { + ret = RunClientDetectors(session, p, direction, pConfig); + } + if (ret < 0) + setAppIdFlag(session, APPID_SESSION_CLIENT_DETECTED); + } + else + { + setAppIdFlag(session, APPID_SESSION_CLIENT_DETECTED); + } + } + } + if (app_id_debug_session_flag) + if (!was_http2 && session->is_http2) + LogMessage("AppIdDbg %s Got a preface for HTTP/2\n", app_id_debug_session); + + if (!was_service && getAppIdFlag(session, APPID_SESSION_SERVICE_DETECTED)) + synchAppIdWithSnortId(session->serviceAppId, p, session, pConfig); + } + /*** End of client discovery. ***/ + + setAppIdFlag(session, APPID_SESSION_ADDITIONAL_PACKET); + } + else + { + if (app_id_debug_session_flag && p->dsize && + !getAppIdFlag(session, APPID_SESSION_OOO_LOGGED)) + { + setAppIdFlag(session, APPID_SESSION_OOO_LOGGED); + LogMessage("AppIdDbg %s packet out-of-order\n", app_id_debug_session); + } + } + + serviceAppId = pickServiceAppId(session); + payloadAppId = pickPayloadId(session); + + if (serviceAppId > APP_ID_NONE) + { + if (getAppIdFlag(session, APPID_SESSION_DECRYPTED)) + { + if (session->miscAppId == APP_ID_NONE) + updateEncryptedAppId(session, serviceAppId); + } +// FIXIT-M: Need to determine what api to use for this _dpd function +#if 1 + UNUSED(isTpAppidDiscoveryDone); +#else + else if (isTpAppidDiscoveryDone && isSslServiceAppId(serviceAppId) && + _dpd.isSSLPolicyEnabled(nullptr)) + setAppIdFlag(session, APPID_SESSION_CONTINUE); +#endif + } + +// FIXIT-M: Need to determine what api to use for this _dpd function +#ifdef REMOVED_WHILE_NOT_IN_USE + _dpd.streamAPI->set_application_id(p->flow, serviceAppId, pickClientAppId(session), + payloadAppId, pickMiscAppId(session)); +#endif + + /* Set the field that the Firewall queries to see if we have a search engine. */ + if (session->search_support_type == SEARCH_SUPPORT_TYPE_UNKNOWN && payloadAppId > APP_ID_NONE) + { + uint flags = appInfoEntryFlagGet(payloadAppId, APPINFO_FLAG_SEARCH_ENGINE | + APPINFO_FLAG_SUPPORTED_SEARCH, pConfig); + session->search_support_type = + (flags & APPINFO_FLAG_SEARCH_ENGINE) ? + ((flags & APPINFO_FLAG_SUPPORTED_SEARCH) ? SUPPORTED_SEARCH_ENGINE : + UNSUPPORTED_SEARCH_ENGINE ) + : NOT_A_SEARCH_ENGINE; + if (app_id_debug_session_flag) + { + const char* typeString; + switch ( session->search_support_type ) + { + case NOT_A_SEARCH_ENGINE: typeString = "NOT_A_SEARCH_ENGINE"; break; + case SUPPORTED_SEARCH_ENGINE: typeString = "SUPPORTED_SEARCH_ENGINE"; break; + case UNSUPPORTED_SEARCH_ENGINE: typeString = "UNSUPPORTED_SEARCH_ENGINE"; break; + default: typeString = "unknown"; break; + } + + LogMessage("AppIdDbg %s appId: %u (safe)search_support_type=%s\n", + app_id_debug_session, payloadAppId, typeString); + } + } + + if ( serviceAppId != APP_ID_NONE ) + { + if ( payloadAppId != APP_ID_NONE && payloadAppId != session->pastIndicator) + { + session->pastIndicator = payloadAppId; + checkSessionForAFIndicator(p, direction, pConfig, (ApplicationId)payloadAppId); + } + + if (session->payloadAppId == APP_ID_NONE && session->pastForecast != serviceAppId && + session->pastForecast != APP_ID_UNKNOWN) + { + session->pastForecast = checkSessionForAFForecast(session, p, direction, pConfig, + (ApplicationId)serviceAppId); + } + } +} + +static inline void pickHttpXffAddress(Packet*, AppIdData* appIdSession, + ThirdPartyAppIDAttributeData* attribute_data) +{ + int i; + static const char* defaultXffPrecedence[] = + { + HTTP_XFF_FIELD_X_FORWARDED_FOR, + HTTP_XFF_FIELD_TRUE_CLIENT_IP + }; + + // XFF precedence configuration cannot change for a session. Do not get it again if we already + // got it. +// FIXIT-M: +#ifdef REMOVED_WHILE_NOT_IN_USE + if (!appIdSession->hsession->xffPrecedence) + appIdSession->hsession->xffPrecedence = _dpd.sessionAPI->get_http_xff_precedence( + p->flow, p->packet_flags, &appIdSession->hsession->numXffFields); +#endif + + if (!appIdSession->hsession->xffPrecedence) + { + appIdSession->hsession->xffPrecedence = defaultXffPrecedence; + appIdSession->hsession->numXffFields = sizeof(defaultXffPrecedence) / + sizeof(defaultXffPrecedence[0]); + } + + if (app_id_debug_session_flag) + { + for (i = 0; i < attribute_data->numXffFields; i++) + LogMessage("AppIdDbg %s %s : %s\n", app_id_debug_session, + attribute_data->xffFieldValue[i].field, attribute_data->xffFieldValue[i].value); + } + + // xffPrecedence array is sorted based on precedence + for (i = 0; (i < appIdSession->hsession->numXffFields) && + appIdSession->hsession->xffPrecedence[i]; i++) + { + int j; + for (j = 0; j < attribute_data->numXffFields; j++) + { + if (appIdSession->hsession->xffAddr) + sfip_free(appIdSession->hsession->xffAddr); + + if (strncasecmp(attribute_data->xffFieldValue[j].field, + appIdSession->hsession->xffPrecedence[i], UINT8_MAX) == 0) + { + char* tmp = strchr(attribute_data->xffFieldValue[j].value, ','); + SFIP_RET status; + + if (!tmp) + { + appIdSession->hsession->xffAddr = sfip_alloc( + attribute_data->xffFieldValue[j].value, &status); + } + // For a comma-separated list of addresses, pick the first address + else + { + attribute_data->xffFieldValue[j].value[tmp - + attribute_data->xffFieldValue[j].value] = '\0'; + appIdSession->hsession->xffAddr = sfip_alloc( + attribute_data->xffFieldValue[j].value, &status); + } + break; + } + } + if (appIdSession->hsession->xffAddr) + break; + } +} + +static inline void ProcessThirdPartyResults(Packet* p, AppIdData* appIdSession, int + confidence, AppId* proto_list, ThirdPartyAppIDAttributeData* attribute_data) +{ + int size; + AppId serviceAppId = 0; + AppId ClientAppId = 0; + AppId payloadAppId = 0; + AppId referredPayloadAppId = 0; + AppIdConfig* pConfig = pAppidActiveConfig; + + if (ThirdPartyAppIDFoundProto(APP_ID_EXCHANGE, proto_list)) + { + if (!appIdSession->payloadAppId) + appIdSession->payloadAppId = APP_ID_EXCHANGE; + } + + if (ThirdPartyAppIDFoundProto(APP_ID_HTTP, proto_list)) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s flow is HTTP\n", app_id_debug_session); + setAppIdFlag(appIdSession, APPID_SESSION_HTTP_SESSION); + } + if (ThirdPartyAppIDFoundProto(APP_ID_SPDY, proto_list)) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s flow is SPDY\n", app_id_debug_session); + + setAppIdFlag(appIdSession, APPID_SESSION_HTTP_SESSION | APPID_SESSION_SPDY_SESSION); + } + + if (getAppIdFlag(appIdSession, APPID_SESSION_HTTP_SESSION)) + { + if (!appIdSession->hsession) + { + appIdSession->hsession = (httpSession*)snort_calloc(sizeof(httpSession)); + memset(ptype_scan_counts, 0, 7 * sizeof(ptype_scan_counts[0])); + } + + if (getAppIdFlag(appIdSession, APPID_SESSION_SPDY_SESSION)) + { + if (attribute_data->spdyRequesHost) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s SPDY host is %s\n", app_id_debug_session, + attribute_data->spdyRequesHost); + if (appIdSession->hsession->host) + { + snort_free(appIdSession->hsession->host); + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->host = snort_strdup(attribute_data->spdyRequesHost); + appIdSession->scan_flags |= SCAN_HTTP_HOST_URL_FLAG; + } + if (attribute_data->spdyRequestPath) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s SPDY URI is %s\n", app_id_debug_session, + attribute_data->spdyRequestPath); + if (appIdSession->hsession->uri) + { + snort_free(appIdSession->hsession->uri); + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->uri = snort_strdup(attribute_data->spdyRequestPath); + } + if (attribute_data->spdyRequestScheme && + attribute_data->spdyRequesHost && + attribute_data->spdyRequestPath) + { + static const char httpsScheme[] = "https"; + static const char httpScheme[] = "http"; + const char* scheme; + + if (appIdSession->hsession->url) + { + snort_free(appIdSession->hsession->url); + appIdSession->hsession->chp_finished = 0; + } + if (getAppIdFlag(appIdSession, APPID_SESSION_DECRYPTED) + && memcmp(attribute_data->spdyRequestScheme, httpScheme, sizeof(httpScheme)- + 1) == 0) + { + scheme = httpsScheme; + } + else + { + scheme = attribute_data->spdyRequestScheme; + } + + size = strlen(scheme) + + strlen(attribute_data->spdyRequesHost) + + strlen(attribute_data->spdyRequestPath) + + sizeof("://"); // see sprintf() format + appIdSession->hsession->url = (char*)snort_calloc(size); + sprintf(appIdSession->hsession->url, "%s://%s%s", + scheme, attribute_data->spdyRequesHost, attribute_data->spdyRequestPath); + appIdSession->scan_flags |= SCAN_HTTP_HOST_URL_FLAG; + } + if (attribute_data->spdyRequestScheme) + { + snort_free(attribute_data->spdyRequestScheme); + attribute_data->spdyRequestScheme = nullptr; + } + if (attribute_data->spdyRequesHost) + { + snort_free(attribute_data->spdyRequesHost); + attribute_data->spdyRequesHost = nullptr; + } + if (attribute_data->spdyRequestPath) + { + snort_free(attribute_data->spdyRequestPath); + attribute_data->spdyRequestPath = nullptr; + } + } + else + { + if (attribute_data->httpRequesHost) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s HTTP host is %s\n", app_id_debug_session, + attribute_data->httpRequesHost); + if (appIdSession->hsession->host) + { + snort_free(appIdSession->hsession->host); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->host = attribute_data->httpRequesHost; + attribute_data->httpRequesHost = nullptr; + appIdSession->scan_flags |= SCAN_HTTP_HOST_URL_FLAG; + } + if (attribute_data->httpRequesUrl) + { + static const char httpScheme[] = "http://"; + + if (appIdSession->hsession->url) + { + snort_free(appIdSession->hsession->url); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + + //change http to https if session was decrypted. + if (getAppIdFlag(appIdSession, APPID_SESSION_DECRYPTED) + && + memcmp(attribute_data->httpRequesUrl, httpScheme, sizeof(httpScheme)-1) == 0) + { + appIdSession->hsession->url = + (char*)snort_calloc(strlen(attribute_data->httpRequesUrl) + 2); + + if (appIdSession->hsession->url) + sprintf(appIdSession->hsession->url, "https://%s", + attribute_data->httpRequesUrl + sizeof(httpScheme)-1); + + snort_free(attribute_data->httpRequesUrl); + attribute_data->httpRequesUrl = nullptr; + } + else + { + appIdSession->hsession->url = attribute_data->httpRequesUrl; + attribute_data->httpRequesUrl = nullptr; + } + + appIdSession->scan_flags |= SCAN_HTTP_HOST_URL_FLAG; + } + if (attribute_data->httpRequestUri) + { + if (appIdSession->hsession->uri) + { + snort_free(appIdSession->hsession->uri); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->uri = attribute_data->httpRequestUri; + appIdSession->hsession->uriOffset = attribute_data->httpRequestUriOffset; + appIdSession->hsession->uriEndOffset = attribute_data->httpRequestUriEndOffset; + attribute_data->httpRequestUri = nullptr; + attribute_data->httpRequestUriOffset = 0; + attribute_data->httpRequestUriEndOffset = 0; + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s uri (%u-%u) is %s\n", app_id_debug_session, + appIdSession->hsession->uriOffset, appIdSession->hsession->uriEndOffset, + appIdSession->hsession->uri); + } + } + if (attribute_data->httpRequestVia) + { + if (appIdSession->hsession->via) + { + snort_free(appIdSession->hsession->via); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->via = attribute_data->httpRequestVia; + attribute_data->httpRequestVia = nullptr; + appIdSession->scan_flags |= SCAN_HTTP_VIA_FLAG; + } + else if (attribute_data->httpResponseVia) + { + if (appIdSession->hsession->via) + { + snort_free(appIdSession->hsession->via); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->via = attribute_data->httpResponseVia; + attribute_data->httpResponseVia = nullptr; + appIdSession->scan_flags |= SCAN_HTTP_VIA_FLAG; + } + if (attribute_data->httpRequestUserAgent) + { + if (appIdSession->hsession->useragent) + { + snort_free(appIdSession->hsession->useragent); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->useragent = attribute_data->httpRequestUserAgent; + attribute_data->httpRequestUserAgent = nullptr; + appIdSession->scan_flags |= SCAN_HTTP_USER_AGENT_FLAG; + } + // Check to see if third party discovered HTTP/2. + // - once it supports it... + if (attribute_data->httpResponseVersion) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s HTTP response version is %s\n", app_id_debug_session, + attribute_data->httpResponseVersion); + if (strncmp(attribute_data->httpResponseVersion, "HTTP/2", 6) == 0) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s 3rd party detected and parsed HTTP/2\n", + app_id_debug_session); + appIdSession->is_http2 = true; + } + snort_free(attribute_data->httpResponseVersion); + attribute_data->httpResponseVersion = nullptr; + } + if (attribute_data->httpResponseCode) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s HTTP response code is %s\n", app_id_debug_session, + attribute_data->httpResponseCode); + if (appIdSession->hsession->response_code) + { + snort_free(appIdSession->hsession->response_code); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->response_code = attribute_data->httpResponseCode; + attribute_data->httpResponseCode = nullptr; + } + // Check to see if we've got an upgrade to HTTP/2 (if enabled). + // - This covers the "without prior knowledge" case (i.e., the client + // asks the server to upgrade to HTTP/2). + if (attribute_data->httpResponseUpgrade) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s HTTP response upgrade is %s\n", app_id_debug_session, + attribute_data->httpResponseUpgrade); + if (pAppidActiveConfig->mod_config->http2_detection_enabled) + if (appIdSession->hsession->response_code && (strncmp( + appIdSession->hsession->response_code, "101", 3) == 0)) + if (strncmp(attribute_data->httpResponseUpgrade, "h2c", 3) == 0) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s Got an upgrade to HTTP/2\n", + app_id_debug_session); + appIdSession->is_http2 = true; + } + snort_free(attribute_data->httpResponseUpgrade); + attribute_data->httpResponseUpgrade = nullptr; + } + if (!pAppidActiveConfig->mod_config->referred_appId_disabled && + attribute_data->httpRequestReferer) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s referrer is %s\n", app_id_debug_session, + attribute_data->httpRequestReferer); + if (appIdSession->hsession->referer) + { + snort_free(appIdSession->hsession->referer); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->referer = attribute_data->httpRequestReferer; + attribute_data->httpRequestReferer = nullptr; + } + if (attribute_data->httpRequestCookie) + { + if (appIdSession->hsession->cookie) + { + snort_free(appIdSession->hsession->cookie); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->cookie = attribute_data->httpRequestCookie; + appIdSession->hsession->cookieOffset = attribute_data->httpRequestCookieOffset; + appIdSession->hsession->cookieEndOffset = attribute_data->httpRequestCookieEndOffset; + attribute_data->httpRequestCookie = nullptr; + attribute_data->httpRequestCookieOffset = 0; + attribute_data->httpRequestCookieEndOffset = 0; + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s cookie (%u-%u) is %s\n", app_id_debug_session, + appIdSession->hsession->cookieOffset, appIdSession->hsession->cookieEndOffset, + appIdSession->hsession->cookie); + } + if (attribute_data->httpResponseContent) + { + if (appIdSession->hsession->content_type) + { + snort_free(appIdSession->hsession->content_type); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->content_type = attribute_data->httpResponseContent; + attribute_data->httpResponseContent = nullptr; + appIdSession->scan_flags |= SCAN_HTTP_CONTENT_TYPE_FLAG; + } + if (ptype_scan_counts[LOCATION_PT] && attribute_data->httpResponseLocation) + { + if (appIdSession->hsession->location) + { + snort_free(appIdSession->hsession->location); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->location = attribute_data->httpResponseLocation; + attribute_data->httpResponseLocation = nullptr; + } + if (attribute_data->httpRequestBody) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s got a request body %s\n", app_id_debug_session, + attribute_data->httpRequestBody); + if (appIdSession->hsession->req_body) + { + snort_free(appIdSession->hsession->req_body); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->req_body = attribute_data->httpRequestBody; + attribute_data->httpRequestBody = nullptr; + } + if (ptype_scan_counts[BODY_PT] && attribute_data->httpResponseBody) + { + if (appIdSession->hsession->body) + { + snort_free(appIdSession->hsession->body); + if (!getAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT)) + appIdSession->hsession->chp_finished = 0; + } + appIdSession->hsession->body = attribute_data->httpResponseBody; + attribute_data->httpResponseBody = nullptr; + } + if (attribute_data->numXffFields) + { + pickHttpXffAddress(p, appIdSession, attribute_data); + } + if (!appIdSession->hsession->chp_finished || appIdSession->hsession->chp_hold_flow) + { + setAppIdFlag(appIdSession, APPID_SESSION_CHP_INSPECTING); + if (thirdparty_appid_module) + thirdparty_appid_module->session_attr_set(appIdSession->tpsession, + TP_ATTR_CONTINUE_MONITORING); + } + if (attribute_data->httpResponseServer) + { + if (appIdSession->hsession->server) + snort_free(appIdSession->hsession->server); + appIdSession->hsession->server = attribute_data->httpResponseServer; + attribute_data->httpResponseServer = nullptr; + appIdSession->scan_flags |= SCAN_HTTP_VENDOR_FLAG; + } + if (attribute_data->httpRequestXWorkingWith) + { + if (appIdSession->hsession->x_working_with) + snort_free(appIdSession->hsession->x_working_with); + appIdSession->hsession->x_working_with = attribute_data->httpRequestXWorkingWith; + attribute_data->httpRequestXWorkingWith = nullptr; + appIdSession->scan_flags |= SCAN_HTTP_XWORKINGWITH_FLAG; + } + } + else if (ThirdPartyAppIDFoundProto(APP_ID_RTMP, proto_list) || + ThirdPartyAppIDFoundProto(APP_ID_RTSP, proto_list)) + { + if (!appIdSession->hsession) + appIdSession->hsession = (httpSession*)snort_calloc(sizeof(httpSession)); + + if (!appIdSession->hsession->url) + { + if (attribute_data->httpRequesUrl) + { + appIdSession->hsession->url = attribute_data->httpRequesUrl; + attribute_data->httpRequesUrl = nullptr; + appIdSession->scan_flags |= SCAN_HTTP_HOST_URL_FLAG; + } + } + + if (!pAppidActiveConfig->mod_config->referred_appId_disabled && + !appIdSession->hsession->referer) + { + if (attribute_data->httpRequestReferer) + { + appIdSession->hsession->referer = attribute_data->httpRequestReferer; + attribute_data->httpRequestReferer = nullptr; + } + } + + if (appIdSession->hsession->url || (confidence == 100 && + appIdSession->session_packet_count > pAppidActiveConfig->mod_config->rtmp_max_packets)) + { + if (appIdSession->hsession->url) + { + if (((getAppIdFromUrl(nullptr, appIdSession->hsession->url, nullptr, + appIdSession->hsession->referer, &ClientAppId, &serviceAppId, + &payloadAppId, &referredPayloadAppId, 1, &pConfig->detectorHttpConfig)) || + (getAppIdFromUrl(nullptr, appIdSession->hsession->url, nullptr, + appIdSession->hsession->referer, &ClientAppId, &serviceAppId, + &payloadAppId, &referredPayloadAppId, 0, &pConfig->detectorHttpConfig))) == 1) + { + // do not overwrite a previously-set client or service + if (appIdSession->ClientAppId <= APP_ID_NONE) + setClientAppIdData(appIdSession, ClientAppId, nullptr); + if (appIdSession->serviceAppId <= APP_ID_NONE) + seServiceAppIdData(appIdSession, serviceAppId, nullptr, nullptr); + + // DO overwrite a previously-set data + setPayloadAppIdData(appIdSession, (ApplicationId)payloadAppId, nullptr); + setReferredPayloadAppIdData(appIdSession, referredPayloadAppId); + } + } + + if (thirdparty_appid_module) + { + thirdparty_appid_module->disable_flags(appIdSession->tpsession, + TP_SESSION_FLAG_ATTRIBUTE | TP_SESSION_FLAG_TUNNELING | + TP_SESSION_FLAG_FUTUREFLOW); + thirdparty_appid_module->session_delete(appIdSession->tpsession, 1); + } + appIdSession->tpsession = nullptr; + clearAppIdFlag(appIdSession, APPID_SESSION_APP_REINSPECT); + } + } + else if (ThirdPartyAppIDFoundProto(APP_ID_SSL, proto_list)) + { + AppId tmpAppId = APP_ID_NONE; + + if (thirdparty_appid_module && appIdSession->tpsession) + tmpAppId = thirdparty_appid_module->session_appid_get(appIdSession->tpsession); + + setAppIdFlag(appIdSession, APPID_SESSION_SSL_SESSION); + + if (!appIdSession->tsession) + appIdSession->tsession = (tlsSession*)snort_calloc(sizeof(tlsSession)); + + if (!appIdSession->ClientAppId) + setClientAppIdData(appIdSession, APP_ID_SSL_CLIENT, nullptr); + + if (attribute_data->tlsHost) + { + if (appIdSession->tsession->tls_host) + snort_free(appIdSession->tsession->tls_host); + appIdSession->tsession->tls_host = attribute_data->tlsHost; + attribute_data->tlsHost = nullptr; + if (testSSLAppIdForReinspect(tmpAppId)) + appIdSession->scan_flags |= SCAN_SSL_HOST_FLAG; + } + if (testSSLAppIdForReinspect(tmpAppId)) + { + if (attribute_data->tlsCname) + { + if (appIdSession->tsession->tls_cname) + snort_free(appIdSession->tsession->tls_cname); + appIdSession->tsession->tls_cname = attribute_data->tlsCname; + attribute_data->tlsCname = nullptr; + } + if (attribute_data->tlsOrgUnit) + { + if (appIdSession->tsession->tls_orgUnit) + snort_free(appIdSession->tsession->tls_orgUnit); + appIdSession->tsession->tls_orgUnit = attribute_data->tlsOrgUnit; + attribute_data->tlsOrgUnit = nullptr; + } + } + } + else if (ThirdPartyAppIDFoundProto(APP_ID_FTP_CONTROL, proto_list)) + { + if (!pAppidActiveConfig->mod_config->ftp_userid_disabled && attribute_data->ftpCommandUser) + { + if (appIdSession->username) + snort_free(appIdSession->username); + appIdSession->username = attribute_data->ftpCommandUser; + attribute_data->ftpCommandUser = nullptr; + appIdSession->usernameService = APP_ID_FTP_CONTROL; + setAppIdFlag(appIdSession, APPID_SESSION_LOGIN_SUCCEEDED); + } + } +} + +void appSetServiceValidator(RNAServiceValidationFCN fcn, AppId appId, unsigned extractsInfo, + AppIdConfig* pConfig) +{ + AppInfoTableEntry* pEntry = appInfoEntryGet(appId, pConfig); + if (!pEntry) + { + ErrorMessage("AppId: invalid direct service AppId, %d, for %p", appId, (void*)fcn); + return; + } + extractsInfo &= (APPINFO_FLAG_SERVICE_ADDITIONAL | APPINFO_FLAG_SERVICE_UDP_REVERSED); + if (!extractsInfo) + { + DebugFormat(DEBUG_APPID, "Ignoring direct service without info for %p with AppId %d", + (void*)fcn, + appId); + return; + } + pEntry->svrValidator = ServiceGetServiceElement(fcn, nullptr, pConfig); + if (pEntry->svrValidator) + pEntry->flags |= extractsInfo; + else + ErrorMessage("AppId: failed to find a service element for %p with AppId %d", (void*)fcn, + appId); +} + +void appSetLuaServiceValidator(RNAServiceValidationFCN fcn, AppId appId, unsigned extractsInfo, + struct Detector* data) +{ + AppInfoTableEntry* entry; + AppIdConfig* pConfig = pAppidActiveConfig; + + if ((entry = appInfoEntryGet(appId, pConfig))) + { + entry->flags |= APPINFO_FLAG_ACTIVE; + + extractsInfo &= (APPINFO_FLAG_SERVICE_ADDITIONAL | APPINFO_FLAG_SERVICE_UDP_REVERSED); + if (!extractsInfo) + { + DebugFormat(DEBUG_LOG, + "Ignoring direct service without info for %p %p with AppId %d\n", + (void*)fcn, (void*)data, appId); + return; + } + + entry->svrValidator = ServiceGetServiceElement(fcn, data, pConfig); + if (entry->svrValidator) + entry->flags |= extractsInfo; + else + ErrorMessage("AppId: Failed to find a service element for %p %p with AppId %d", + (void*)fcn, (void*)data, appId); + } + else + { + ErrorMessage("Invalid direct service AppId, %d, for %p %p\n", + appId, (void*)fcn, (void*)data); + } +} + +void appSetClientValidator(RNAClientAppFCN fcn, AppId appId, unsigned extractsInfo, + AppIdConfig* pConfig) +{ + AppInfoTableEntry* pEntry = appInfoEntryGet(appId, pConfig); + if (!pEntry) + { + ErrorMessage("AppId: invalid direct client application AppId, %d, for %p\n", + appId, (void*)fcn); + return; + } + extractsInfo &= (APPINFO_FLAG_CLIENT_ADDITIONAL | APPINFO_FLAG_CLIENT_USER); + if (!extractsInfo) + { + DebugFormat(DEBUG_LOG, + "Ignoring direct client application without info for %p with AppId %d", + (void*)fcn, appId); + return; + } + pEntry->clntValidator = ClientAppGetClientAppModule(fcn, nullptr, &pConfig->clientAppConfig); + if (pEntry->clntValidator) + pEntry->flags |= extractsInfo; + else + ErrorMessage("Appid: Failed to find a client application module for %p with AppId %d\n", + (void*)fcn, appId); +} + +void appSetLuaClientValidator(RNAClientAppFCN fcn, AppId appId, unsigned extractsInfo, struct + Detector* data) +{ + AppInfoTableEntry* entry; + AppIdConfig* pConfig = pAppidActiveConfig; + + if ((entry = appInfoEntryGet(appId, pConfig))) + { + entry->flags |= APPINFO_FLAG_ACTIVE; + extractsInfo &= (APPINFO_FLAG_CLIENT_ADDITIONAL | APPINFO_FLAG_CLIENT_USER); + if (!extractsInfo) + { + DebugFormat(DEBUG_LOG, + "Ignoring direct client application without info for %p %p with AppId %d\n", + (void*)fcn, (void*)data, appId); + return; + } + + entry->clntValidator = ClientAppGetClientAppModule(fcn, data, &pConfig->clientAppConfig); + if (entry->clntValidator) + entry->flags |= extractsInfo; + else + ErrorMessage( + "AppId: Failed to find a client application module for %p %p with AppId %d", + (void*)fcn, (void*)data, appId); + } + else + { + ErrorMessage("Invalid direct client application AppId, %d, for %p %p\n", + appId, (void*)fcn, (void*)data); + return; + } +} + +void AppIdAddUser(AppIdData* flowp, const char* username, AppId appId, int success) +{ + if (flowp->username) + snort_free(flowp->username); + flowp->username = snort_strdup(username); + flowp->usernameService = appId; + if (success) + setAppIdFlag(flowp, APPID_SESSION_LOGIN_SUCCEEDED); + else + clearAppIdFlag(flowp, APPID_SESSION_LOGIN_SUCCEEDED); +} + +void AppIdAddDnsQueryInfo(AppIdData* flow, + uint16_t id, + const uint8_t* host, uint8_t host_len, uint16_t host_offset, + uint16_t record_type) +{ + if ( flow->dsession ) + { + if ( ( flow->dsession->state != 0 ) && ( flow->dsession->id != id ) ) + AppIdResetDnsInfo(flow); + } + else + flow->dsession = (dnsSession*)snort_calloc(sizeof(dnsSession)); + + if (flow->dsession->state & DNS_GOT_QUERY) + return; + flow->dsession->state |= DNS_GOT_QUERY; + + flow->dsession->id = id; + flow->dsession->record_type = record_type; + + if (!flow->dsession->host) + { + if ((host != nullptr) && (host_len > 0) && (host_offset > 0)) + { + flow->dsession->host_len = host_len; + flow->dsession->host_offset = host_offset; + flow->dsession->host = dns_parse_host(host, host_len); + } + } +} + +void AppIdAddDnsResponseInfo(AppIdData* flow, + uint16_t id, + const uint8_t* host, uint8_t host_len, uint16_t host_offset, + uint8_t response_type, uint32_t ttl) +{ + if ( flow->dsession ) + { + if ( ( flow->dsession->state != 0 ) && ( flow->dsession->id != id ) ) + AppIdResetDnsInfo(flow); + } + else + flow->dsession = (dnsSession*)snort_calloc(sizeof(*flow->dsession)); + + if (flow->dsession->state & DNS_GOT_RESPONSE) + return; + flow->dsession->state |= DNS_GOT_RESPONSE; + + flow->dsession->id = id; + flow->dsession->response_type = response_type; + flow->dsession->ttl = ttl; + + if (!flow->dsession->host) + { + if ((host != nullptr) && (host_len > 0) && (host_offset > 0)) + { + flow->dsession->host_len = host_len; + flow->dsession->host_offset = host_offset; + flow->dsession->host = dns_parse_host(host, host_len); + } + } +} + +void AppIdResetDnsInfo(AppIdData* flow) +{ + if (flow->dsession) + { + snort_free(flow->dsession->host); + memset(flow->dsession, 0, sizeof(*(flow->dsession))); + } +} + +void AppIdAddPayload(AppIdData* flow, AppId payload_id) +{ + if (pAppidActiveConfig->mod_config->instance_id) + checkSandboxDetection(payload_id); + flow->payloadAppId = payload_id; +} + +AppId getOpenAppId(void* ssnptr) +{ + AppIdData* session; + AppId payloadAppId = APP_ID_NONE; + if (ssnptr && (session = getAppIdData(ssnptr))) + { + payloadAppId = session->payloadAppId; + } + + return payloadAppId; +} + +/** + * @returns 1 if some appid is found, 0 otherwise. + */ +//int sslAppGroupIdLookup(void* ssnptr, const char* serverName, const char* commonName, +// AppId* serviceAppId, AppId* ClientAppId, AppId* payloadAppId) +int sslAppGroupIdLookup(void*, const char*, const char*, AppId*, AppId*, AppId*) +{ +#ifdef REMOVED_WHILE_NOT_IN_USE + AppIdData* session; + *serviceAppId = *ClientAppId = *payloadAppId = APP_ID_NONE; + + if (commonName) + { + ssl_scan_cname((const uint8_t*)commonName, strlen(commonName), ClientAppId, payloadAppId, + &pAppidActiveConfig->serviceSslConfig); + } + if (serverName) + { + ssl_scan_hostname((const uint8_t*)serverName, strlen(serverName), ClientAppId, + payloadAppId, &pAppidActiveConfig->serviceSslConfig); + } + + if (ssnptr && (session = getAppIdData(ssnptr))) + { + *serviceAppId = pickServiceAppId(session); + if (*ClientAppId == APP_ID_NONE) + { + *ClientAppId = pickClientAppId(session); + } + if (*payloadAppId == APP_ID_NONE) + { + *payloadAppId = pickPayloadId(session); + } + } + if (*serviceAppId != APP_ID_NONE || + *ClientAppId != APP_ID_NONE || + *payloadAppId != APP_ID_NONE) + { + return 1; + } +#endif + + return 0; +} + +void httpHeaderCallback(Packet* p, HttpParsedHeaders* const headers) +{ + AppIdData* session; + int direction; + AppIdConfig* pConfig = pAppidActiveConfig; + + if (thirdparty_appid_module) + return; + if (!p || !(session = getAppIdData(p->flow))) + return; + + direction = p->is_from_client() ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER; + + if (!session->hsession) + session->hsession = (decltype(session->hsession))snort_calloc(sizeof(httpSession)); + + if (direction == APP_ID_FROM_INITIATOR) + { + if (headers->host.start) + { + snort_free(session->hsession->host); + session->hsession->host = strndup((char*)headers->host.start, headers->host.len); + session->scan_flags |= SCAN_HTTP_HOST_URL_FLAG; + + if (headers->url.start) + { + snort_free(session->hsession->url); + session->hsession->url = (char*)snort_calloc(sizeof(HTTP_PREFIX) + + headers->host.len + headers->url.len); + strcpy(session->hsession->url, HTTP_PREFIX); + strncat(session->hsession->url, (char*)headers->host.start, headers->host.len); + strncat(session->hsession->url, (char*)headers->url.start, headers->url.len); + session->scan_flags |= SCAN_HTTP_HOST_URL_FLAG; + } + } + if (headers->userAgent.start) + { + snort_free(session->hsession->useragent); + session->hsession->useragent = strndup((char*)headers->userAgent.start, + headers->userAgent.len); + session->scan_flags |= SCAN_HTTP_USER_AGENT_FLAG; + } + if (headers->referer.start) + { + snort_free(session->hsession->referer); + session->hsession->referer = strndup((char*)headers->referer.start, + headers->referer.len); + } + if (headers->via.start) + { + snort_free(session->hsession->via); + session->hsession->via = strndup((char*)headers->via.start, headers->via.len); + session->scan_flags |= SCAN_HTTP_VIA_FLAG; + } + } + else + { + if (headers->via.start) + { + snort_free(session->hsession->via); + session->hsession->via = strndup((char*)headers->via.start, headers->via.len); + session->scan_flags |= SCAN_HTTP_VIA_FLAG; + } + if (headers->contentType.start) + { + snort_free(session->hsession->content_type); + session->hsession->content_type = strndup((char*)headers->contentType.start, + headers->contentType.len); + } + if (headers->responseCode.start) + { + long responseCodeNum; + responseCodeNum = strtoul((char*)headers->responseCode.start, nullptr, 10); + if (responseCodeNum > 0 && responseCodeNum < 700) + { + snort_free(session->hsession->response_code); + session->hsession->response_code = strndup((char*)headers->responseCode.start, + headers->responseCode.len); + } + } + } + processHTTPPacket(p, session, direction, headers, pConfig); + + setAppIdFlag(session, APPID_SESSION_SERVICE_DETECTED | APPID_SESSION_HTTP_SESSION); + +// FIXIT-M: +#ifdef REMOVED_WHILE_NOT_IN_USE + _dpd.streamAPI->set_application_id(p->flow, pickServiceAppId(session), + pickClientAppId(session), pickPayloadId(session), pickMiscAppId(session)); +#endif +} + +static inline void ExamineRtmpMetadata(AppIdData* appIdSession) +{ + AppId serviceAppId = 0; + AppId ClientAppId = 0; + AppId payloadAppId = 0; + AppId referredPayloadAppId = 0; + char* version = nullptr; + httpSession* hsession; + AppIdConfig* pConfig = pAppidActiveConfig; + + if (!appIdSession->hsession) + appIdSession->hsession = (httpSession*)snort_calloc(sizeof(httpSession)); + + hsession = appIdSession->hsession; + + if (hsession->url) + { + if (((getAppIdFromUrl(nullptr, hsession->url, &version, + hsession->referer, &ClientAppId, &serviceAppId, + &payloadAppId, &referredPayloadAppId, 1, &pConfig->detectorHttpConfig)) || + (getAppIdFromUrl(nullptr, hsession->url, &version, + hsession->referer, &ClientAppId, &serviceAppId, + &payloadAppId, &referredPayloadAppId, 0, &pConfig->detectorHttpConfig))) == 1) + { + /* do not overwrite a previously-set client or service */ + if (appIdSession->ClientAppId <= APP_ID_NONE) + setClientAppIdData(appIdSession, ClientAppId, nullptr); + if (appIdSession->serviceAppId <= APP_ID_NONE) + seServiceAppIdData(appIdSession, serviceAppId, nullptr, nullptr); + + /* DO overwrite a previously-set data */ + setPayloadAppIdData(appIdSession, (ApplicationId)payloadAppId, nullptr); + setReferredPayloadAppIdData(appIdSession, referredPayloadAppId); + } + } +} + +void checkSandboxDetection(AppId appId) +{ + AppInfoTableEntry* entry; + AppIdConfig* pConfig = pAppidActiveConfig; + + if (pAppidActiveConfig->mod_config->instance_id && pConfig) + { + entry = appInfoEntryGet(appId, pConfig); + if (entry && entry->flags & APPINFO_FLAG_ACTIVE) + { + fprintf(SF_DEBUG_FILE, "add service\n"); + fprintf(SF_DEBUG_FILE, "Detected AppId %d\n", entry->appId); + } + } +} + diff --git a/src/network_inspectors/appid/fw_appid.h b/src/network_inspectors/appid/fw_appid.h new file mode 100644 index 000000000..8395952f2 --- /dev/null +++ b/src/network_inspectors/appid/fw_appid.h @@ -0,0 +1,374 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// fw_appid.h author Sourcefire Inc. + +#ifndef FW_APPID_H +#define FW_APPID_H + +#include "client_plugins/client_app_api.h" +#include "util/common_util.h" + +#include "appid.h" +#include "appid_api.h" +#include "appid_config.h" +#include "appid_flow_data.h" +#include "application_ids.h" +#include "app_info_table.h" +#include "service_plugins/service_api.h" +#include "thirdparty_appid_utils.h" + +#define PP_APP_ID 1 + +#define MIN_SFTP_PACKET_COUNT 30 +#define MAX_SFTP_PACKET_COUNT 55 + +struct HttpParsedHeaders; +struct Packet; +class AppIdConfig; + +extern uint8_t appIdPriorityArray[SF_APPID_MAX+1]; + +enum ServiceEventType {}; + +AppIdData* getAppIdData(void* lwssn); + +void fwAppIdInit(); +void fwAppIdFini(AppIdConfig*); +void fwAppIdSearch(Packet*); +void httpHeaderCallback(Packet*, HttpParsedHeaders* const); +void SipSessionSnortCallback(void* ssnptr, ServiceEventType, void* eventData); + +void readRnaAppMappingTable(const char* path, AppIdConfig*); +AppId appGetAppFromServiceId(uint32_t serviceId, AppIdConfig*); +AppId appGetAppFromClientId(uint32_t clientId, AppIdConfig*); +AppId appGetAppFromPayloadId(uint32_t payloadId, AppIdConfig*); +void appSharedDataDelete(AppIdData*); +void AppIdAddUser(AppIdData*, const char* username, AppId, int success); +void AppIdAddDnsQueryInfo( + AppIdData*, + uint16_t id, + const uint8_t* host, uint8_t host_len, uint16_t host_offset, + uint16_t record_type +); + +void AppIdAddDnsResponseInfo( + AppIdData*, + uint16_t id, + const uint8_t* host, uint8_t host_len, uint16_t host_offset, + uint8_t response_type, uint32_t ttl +); + +void AppIdResetDnsInfo(AppIdData*); + +void AppIdAddPayload(AppIdData*, AppId); +AppIdData* appSharedDataAlloc(IpProtocol proto, const sfip_t*); +AppId getOpenAppId(void* ssnptr); + +void appSetServiceValidator( + RNAServiceValidationFCN, AppId, unsigned extractsInfo, AppIdConfig*); + +void appSetLuaServiceValidator( + RNAServiceValidationFCN, AppId, unsigned extractsInfo, Detector* dat); + +void appSetClientValidator( + RNAClientAppFCN, AppId, unsigned extractsInfo, AppIdConfig*); + +void appSetLuaClientValidator( + RNAClientAppFCN, AppId, unsigned extractsInfo, Detector* data); + +int sslAppGroupIdLookup( + void* ssnptr, + const char* serverName, + const char* commonName, + AppId* serviceAppId, + AppId* ClientAppId, + AppId* payloadAppId +); + +AppId getAppId(void* ssnptr); + +#ifdef FW_TRACKER_DEBUG +void logAppIdInfo(SFSnortPacket* p, char* message, AppId id); +#endif +int AppIdDebug( + uint16_t type, + const uint8_t* data, + uint32_t length, + void** new_context, + char* statusBuf, + int statusBuf_len +); + +extern char app_id_debug_session[FW_DEBUG_SESSION_ID_SIZE]; +extern bool app_id_debug_session_flag; + +extern ProfileStats httpPerfStats; +extern ProfileStats clientMatchPerfStats; +extern ProfileStats serviceMatchPerfStats; +extern ProfileStats luaDetectorsPerfStats; +extern ProfileStats tpPerfStats; +extern ProfileStats tpLibPerfStats; + +extern unsigned dhcp_fp_table_size; +extern unsigned long app_id_raw_packet_count; +extern unsigned long app_id_processed_packet_count; +extern unsigned long app_id_ignored_packet_count; +extern int app_id_debug; +extern unsigned isIPv4HostMonitored(uint32_t ip4, int32_t zone); +extern void checkSandboxDetection(AppId appId); + +inline void initializePriorityArray() +{ + for ( int i=0; i < SF_APPID_MAX; ++i ) + appIdPriorityArray[i] = 2; +} + +inline void setAppPriority(AppId app_id, uint8_t bit_val) +{ + if ( app_id < SF_APPID_MAX && bit_val <= APPID_MAX_PRIORITY ) + appIdPriorityArray[app_id] = bit_val; +} + +inline int getAppPriority(AppId app_id) +{ + if (app_id > APP_ID_NONE && app_id < SF_APPID_MAX) + return appIdPriorityArray[app_id]; + + return -1; +} + +inline int ThirdPartyAppIDFoundProto(AppId proto, AppId* proto_list) +{ + unsigned int proto_cnt = 0; + while (proto_list[proto_cnt] != APP_ID_NONE) + if (proto_list[proto_cnt++] == proto) + return 1; // found + + return 0; // not found +} + +inline int TPIsAppIdDone(void* tpSession) +{ + if (thirdparty_appid_module) + { + unsigned state; + + if (tpSession) + state = thirdparty_appid_module->session_state_get(tpSession); + else + state = TP_STATE_INIT; + return (state == TP_STATE_CLASSIFIED || state == TP_STATE_TERMINATED || state == + TP_STATE_HA); + } + return true; +} + +inline int TPIsAppIdAvailable(void* tpSession) +{ + if (thirdparty_appid_module) + { + unsigned state; + + if (tpSession) + state = thirdparty_appid_module->session_state_get(tpSession); + else + state = TP_STATE_INIT; + return (state == TP_STATE_CLASSIFIED || state == TP_STATE_TERMINATED || state == + TP_STATE_MONITORING); + } + return true; +} + +inline AppId isAppDetectionDone(AppIdData* flow) +{ return getAppIdFlag(flow, APPID_SESSION_SERVICE_DETECTED); } + +inline AppId pickServiceAppId(AppIdData* flow) +{ + AppId rval; + + if (!flow || flow->common.fsf_type.flow_type != APPID_SESSION_TYPE_NORMAL) + return APP_ID_NONE; + + if (getAppIdFlag(flow, APPID_SESSION_SERVICE_DETECTED)) + { + bool deferred = appInfoEntryFlagGet(flow->serviceAppId, APPINFO_FLAG_DEFER, pAppidActiveConfig) + || appInfoEntryFlagGet(flow->tpAppId, APPINFO_FLAG_DEFER, pAppidActiveConfig); + + if (flow->serviceAppId > APP_ID_NONE && !deferred) + return flow->serviceAppId; + if (TPIsAppIdAvailable(flow->tpsession)) + { + if (flow->tpAppId > APP_ID_NONE) + return flow->tpAppId; + else if (deferred) + return flow->serviceAppId; + else + rval = APP_ID_UNKNOWN_UI; + } + else + rval = flow->tpAppId; + } + else if (flow->tpAppId > APP_ID_NONE) + return flow->tpAppId; + else + rval = APP_ID_NONE; + + if (flow->ClientServiceAppId > APP_ID_NONE) + return flow->ClientServiceAppId; + + if (flow->portServiceAppId > APP_ID_NONE) + return flow->portServiceAppId; + + return rval; +} + +inline AppId pickOnlyServiceAppId(AppIdData* flow) +{ + if (!flow || flow->common.fsf_type.flow_type != APPID_SESSION_TYPE_NORMAL) + return APP_ID_NONE; + + bool deferred = appInfoEntryFlagGet(flow->serviceAppId, APPINFO_FLAG_DEFER, pAppidActiveConfig) + || appInfoEntryFlagGet(flow->tpAppId, APPINFO_FLAG_DEFER, pAppidActiveConfig); + + if (flow->serviceAppId > APP_ID_NONE && !deferred) + return flow->serviceAppId; + + if (TPIsAppIdAvailable(flow->tpsession) && flow->tpAppId > APP_ID_NONE) + return flow->tpAppId; + else if (deferred) + return flow->serviceAppId; + + if (flow->serviceAppId < APP_ID_NONE) + return APP_ID_UNKNOWN_UI; + + return APP_ID_NONE; +} + +inline AppId pickMiscAppId(AppIdData* flow) +{ + if (!flow || flow->common.fsf_type.flow_type != APPID_SESSION_TYPE_NORMAL) + return APP_ID_NONE; + if (flow->miscAppId > APP_ID_NONE) + return flow->miscAppId; + return APP_ID_NONE; +} + +inline AppId pickClientAppId(AppIdData* flow) +{ + if (!flow || flow->common.fsf_type.flow_type != APPID_SESSION_TYPE_NORMAL) + return APP_ID_NONE; + if (flow->ClientAppId > APP_ID_NONE) + return flow->ClientAppId; + return APP_ID_NONE; +} + +inline AppId pickPayloadId(AppIdData* flow) +{ + if (!flow || flow->common.fsf_type.flow_type != APPID_SESSION_TYPE_NORMAL) + return APP_ID_NONE; + + // if we have a deferred payload, just use it. + // we are not worried about the APP_ID_UNKNOWN case here + if (appInfoEntryFlagGet(flow->tpPayloadAppId, APPINFO_FLAG_DEFER_PAYLOAD, pAppidActiveConfig)) + return flow->tpPayloadAppId; + else if (flow->payloadAppId > APP_ID_NONE) + return flow->payloadAppId; + else if (flow->tpPayloadAppId > APP_ID_NONE) + return flow->tpPayloadAppId; + + return APP_ID_NONE; +} + +inline AppId pickReferredPayloadId(AppIdData* flow) +{ + if (!flow || flow->common.fsf_type.flow_type != APPID_SESSION_TYPE_NORMAL) + return APP_ID_NONE; + if (flow->referredPayloadAppId > APP_ID_NONE) + return flow->referredPayloadAppId; + return APP_ID_NONE; +} + +inline AppId fwPickServiceAppId(AppIdData* session) +{ + AppId appId; + appId = pickServiceAppId(session); + if (appId == APP_ID_NONE) + appId = session->encrypted.serviceAppId; + return appId; +} + +inline AppId fwPickMiscAppId(AppIdData* session) +{ + AppId appId; + appId = pickMiscAppId(session); + if (appId == APP_ID_NONE) + appId = session->encrypted.miscAppId; + return appId; +} + +inline AppId fwPickClientAppId(AppIdData* session) +{ + AppId appId; + appId = pickClientAppId(session); + return appId; +} + +inline AppId fwPickPayloadAppId(AppIdData* session) +{ + AppId appId; + appId = pickPayloadId(session); + if (appId == APP_ID_NONE) + appId = session->encrypted.payloadAppId; + return appId; +} + +inline AppId fwPickReferredPayloadAppId(AppIdData* session) +{ + AppId appId; + appId = pickReferredPayloadId(session); + if (appId == APP_ID_NONE) + appId = session->encrypted.referredAppId; + return appId; +} + +inline AppIdData* appSharedGetData(const Packet* p) +{ + if ( p && p->flow ) + return (AppIdData*)p->flow->get_application_data(AppIdData::flow_id); + + return nullptr; +} + +inline unsigned int isFwSessionSslDecrypted(AppIdData* session) +{ + return getAppIdFlag(session, APPID_SESSION_DECRYPTED); +} + +inline int testSSLAppIdForReinspect(AppId app_id) +{ + if (app_id <= SF_APPID_MAX && + (app_id == APP_ID_SSL || + appInfoEntryFlagGet(app_id, APPINFO_FLAG_SSL_INSPECT, pAppidActiveConfig))) + return 1; + else + return 0; +} + +#endif diff --git a/src/network_inspectors/appid/host_port_app_cache.cc b/src/network_inspectors/appid/host_port_app_cache.cc new file mode 100644 index 000000000..2640967b0 --- /dev/null +++ b/src/network_inspectors/appid/host_port_app_cache.cc @@ -0,0 +1,93 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// host_port_app_cache.cc author Sourcefire Inc. + +#include "host_port_app_cache.h" + +#include "appid_config.h" +#include "hash/sfxhash.h" +#include "log/messages.h" +#include "sfip/sf_ip.h" + +void hostPortAppCacheInit(AppIdConfig* pConfig) +{ + auto hash = sfxhash_new( + 2048, sizeof(HostPortKey), sizeof(HostPortVal), 0, 0, nullptr, nullptr, 0); + + if ( hash ) + pConfig->hostPortCache = hash; + + else + ErrorMessage("failed to allocate HostPort map"); +} + +void hostPortAppCacheFini(AppIdConfig* pConfig) +{ + if ( pConfig->hostPortCache ) + { + sfxhash_delete(pConfig->hostPortCache); + pConfig->hostPortCache = nullptr; + } +} + +HostPortVal* hostPortAppCacheFind( + const sfip_t* snort_ip, uint16_t port, IpProtocol protocol, const AppIdConfig* pConfig) +{ + HostPortKey hk; + sfip_set_ip(&hk.ip, snort_ip); + hk.port = port; + hk.proto = protocol; + + return (HostPortVal*)sfxhash_find(pConfig->hostPortCache, &hk); +} + +int hostPortAppCacheAdd(const in6_addr* ip, uint16_t port, IpProtocol proto, unsigned type, + AppId appId, AppIdConfig* pConfig) +{ + HostPortKey hk; + HostPortVal hv; + memcpy(&hk.ip, ip, sizeof(hk.ip)); + hk.port = port; + hk.proto = proto; + hv.appId = appId; + hv.type = type; + + return sfxhash_add(pConfig->hostPortCache, &hk, &hv) ? 0 : 1; +} + +void hostPortAppCacheDump(const AppIdConfig* pConfig) +{ + for ( SFXHASH_NODE* node = sfxhash_findfirst(pConfig->hostPortCache); + node; + node = sfxhash_findnext(pConfig->hostPortCache)) + { + char inet_buffer[INET6_ADDRSTRLEN]; + HostPortKey* hk; + HostPortVal* hv; + + hk = (HostPortKey*)node->key; + hv = (HostPortVal*)node->data; + + inet_ntop(AF_INET6, &hk->ip, inet_buffer, sizeof(inet_buffer)); + printf("\tip=%s, \tport %d, \tip_proto %d, \ttype=%u, \tappId=%d\n", inet_buffer, hk->port, + to_utype(hk->proto), hv->type, hv->appId); + } +} + diff --git a/src/network_inspectors/appid/host_port_app_cache.h b/src/network_inspectors/appid/host_port_app_cache.h new file mode 100644 index 000000000..20af9667d --- /dev/null +++ b/src/network_inspectors/appid/host_port_app_cache.h @@ -0,0 +1,55 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// host_port_app_cache.h author Sourcefire Inc. + +#ifndef HOST_PORT_APP_CACHE_H +#define HOST_PORT_APP_CACHE_H + +#include "sfip/sfip_t.h" +#include "appid_api.h" + +class AppIdConfig; +enum class IpProtocol : uint8_t; + +struct HostPortKey +{ + sfip_t ip; + uint16_t port; + IpProtocol proto; +}; + +struct HostPortVal +{ + AppId appId; + unsigned type; +}; + +void hostPortAppCacheInit(AppIdConfig*); +void hostPortAppCacheFini(AppIdConfig*); +// FIXIT-M: Should proto be IpProtocol or ProtocolId? +HostPortVal* hostPortAppCacheFind( + const sfip_t*, uint16_t port, IpProtocol proto, const AppIdConfig*); + +int hostPortAppCacheAdd( + const in6_addr*, uint16_t port, IpProtocol proto, unsigned type, AppId, AppIdConfig*); +void hostPortAppCacheDump(const AppIdConfig*); + +#endif + diff --git a/src/network_inspectors/appid/http_common.h b/src/network_inspectors/appid/http_common.h new file mode 100644 index 000000000..c8da1ddc0 --- /dev/null +++ b/src/network_inspectors/appid/http_common.h @@ -0,0 +1,260 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// http_common.h author Sourcefire Inc. + +#ifndef HTTP_COMMON_H +#define HTTP_COMMON_H + +#include "appid.h" +#include "appid_api.h" +#include "utils/sflsq.h" + +// FIXIT-H rename util/ so we don't confuse it with src/utils +#include "util/sf_multi_mpse.h" + +#define MAX_USERNAME_SIZE 64 +#define MAX_URL_SIZE 65535 + +class SearchTool; +struct tMlmpTree; + +// These values are used in Lua code as raw numbers. Do NOT reassign new values. +enum DHPSequence +{ + SINGLE = 0, + SKYPE_URL = 1, + SKYPE_VERSION = 2, + BT_ANNOUNCE = 3, + BT_OTHER = 4, + USER_AGENT_HEADER = 5 +}; + +struct DetectorHTTPPattern +{ + DHPSequence seq; + AppId service_id; + AppId client_app; + AppId payload; + int pattern_size; + uint8_t* pattern; + AppId appId; +}; + +struct HTTPListElement +{ + DetectorHTTPPattern detectorHTTPPattern; + HTTPListElement* next; +}; + +#define APPL_VERSION_LENGTH 40 + +struct UrlUserData +{ + uint32_t service_id; + uint32_t client_app; + uint32_t payload; + AppId appId; + tMlpPattern query; +}; + +struct DetectorAppUrlPattern +{ + struct + { + tMlpPattern host; + tMlpPattern path; + tMlpPattern scheme; + } patterns; + + UrlUserData userData; +}; + +struct DetectorAppUrlList +{ + DetectorAppUrlPattern** urlPattern; + size_t usedCount; + size_t allocatedCount; +}; + +// These values are used in Lua code as raw numbers. Do NOT reassign new values. +#define APP_TYPE_SERVICE 0x1 +#define APP_TYPE_CLIENT 0x2 +#define APP_TYPE_PAYLOAD 0x4 + +// These values are used in Lua code as raw numbers. Do NOT reassign new values. +enum ActionType +{ + NO_ACTION, //0 + COLLECT_VERSION, //1 + EXTRACT_USER, //2 + REWRITE_FIELD, //3 + INSERT_FIELD, //4 + ALTERNATE_APPID, //5 + FUTURE_APPID_SESSION_SIP, //6 + FUTURE_APPID_SESSION_DIP, //7 + FUTURE_APPID_SESSION_SPORT, //8 + FUTURE_APPID_SESSION_DPORT, //9 + FUTURE_APPID_SESSION_PROTOCOL, //10 + FUTURE_APPID_SESSION_CREATE, //11 + HOLD_FLOW, //12 + GET_OFFSETS_FROM_REBUILT, //13 + SEARCH_UNSUPPORTED, //14 + MAX_ACTION_TYPE = SEARCH_UNSUPPORTED, +}; + +// These values are used in Lua code as raw numbers. Do NOT reassign new values. +enum PatternType +{ + // Request-side headers + AGENT_PT, // 0 + HOST_PT, // 1 + REFERER_PT, // 2 + URI_PT, // 3 + COOKIE_PT, // 4 + REQ_BODY_PT, // 5 + // Response-side headers + CONTENT_TYPE_PT, // 6 + LOCATION_PT, // 7 + BODY_PT, // 8 + MAX_PATTERN_TYPE = BODY_PT, + MAX_KEY_PATTERN = URI_PT, +}; + +#define CHP_APPID_BITS_FOR_INSTANCE 7 +#define CHP_APPID_INSTANCE_MAX (1 << CHP_APPID_BITS_FOR_INSTANCE) +#define CHP_APPIDINSTANCE_TO_ID(_appIdInstance) \ + (_appIdInstance >> CHP_APPID_BITS_FOR_INSTANCE) +#define CHP_APPIDINSTANCE_TO_INSTANCE(_appIdInstance) \ + (_appIdInstance & (CHP_APPID_INSTANCE_MAX-1)) +/* + NOTE: The following structures have a field called appIdInstance. + The low-order CHP_APPID_BITS_FOR_INSTANCE bits of appIdInstance field are used + for the instance value while the remaining bits are used for the appId, shifted left + that same number of bits. The legacy value for older apis is generated with the + macro below. +*/ +#define CHP_APPID_SINGLE_INSTANCE(_appId) \ + ((_appId << CHP_APPID_BITS_FOR_INSTANCE) + (CHP_APPID_INSTANCE_MAX-1)) + +struct CHPApp +{ + AppId appIdInstance; // * see note above + unsigned app_type_flags; + int num_matches; + int num_scans; + int key_pattern_count; + int key_pattern_length_sum; + int ptype_scan_counts[NUMBER_OF_PTYPES]; + int ptype_req_counts[NUMBER_OF_PTYPES]; + int ptype_rewrite_insert_used[NUMBER_OF_PTYPES]; // boolean +}; + +struct CHPAction +{ + AppId appIdInstance; // * see note above + unsigned precedence; // order of creation + int key_pattern; + PatternType ptype; + int psize; + char* pattern; + ActionType action; + char* action_data; + CHPApp* chpapp; +}; + +struct CHPListElement +{ + CHPAction chp_action; + CHPListElement* next; +}; + +struct HttpPatternLists +{ + HTTPListElement* hostPayloadPatternList; + HTTPListElement* urlPatternList; + HTTPListElement* clientAgentPatternList; + HTTPListElement* contentTypePatternList; + CHPListElement* chpList; + DetectorAppUrlList appUrlList; + DetectorAppUrlList RTMPUrlList; +}; + +// url parts extracted from http headers. +struct UrlStruct +{ + tMlpPattern host; /*from host header */ + tMlpPattern path; /*from GET/POST request */ + tMlpPattern scheme; /*hardcoded to "http:" */ + tMlpPattern query; /*query match for version number */ +}; + +struct HosUrlDetectorPattern +{ + tMlpPattern host; + tMlpPattern path; + tMlpPattern query; + uint32_t payload_id; + uint32_t service_id; + uint32_t client_id; + AppId appId; + DHPSequence seq; + HosUrlDetectorPattern* next; +}; + +struct HosUrlPatternsList +{ + HosUrlDetectorPattern* head; + HosUrlDetectorPattern* tail; +}; + +struct DetectorHttpConfig +{ + SearchTool* url_matcher; + SearchTool* client_agent_matcher; + SearchTool* via_matcher; + tMlmpTree* hosUrlMatcher; + tMlmpTree* RTMPHosUrlMatcher; + SearchTool* header_matcher; + SearchTool* content_type_matcher; + + // CHP matchers + // TODO: Is there a need for these variables? They just point to the pointers in the + // array chp_matchers[]. They are used only in the function http_detector_clean(). But + // there we could easily traverse through the members of chp_matchers instead of using + // these variables. + SearchTool* chp_user_agent_matcher; + SearchTool* chp_host_matcher; + SearchTool* chp_referer_matcher; + SearchTool* chp_uri_matcher; + SearchTool* chp_cookie_matcher; + SearchTool* chp_content_type_matcher; + SearchTool* chp_location_matcher; + SearchTool* chp_body_matcher; + // TODO: chp_req_body_matcher is not being used anywhere in the code, should it be removed? + SearchTool* chp_req_body_matcher; + + SearchTool* chp_matchers[MAX_PATTERN_TYPE+1]; + + HosUrlPatternsList* hosUrlPatternsList; +}; + +extern AppId getAppIdByHttpUrl(UrlStruct* url, UrlUserData** rnaData); +#endif + diff --git a/src/network_inspectors/appid/length_app_cache.cc b/src/network_inspectors/appid/length_app_cache.cc new file mode 100644 index 000000000..edfaa62fa --- /dev/null +++ b/src/network_inspectors/appid/length_app_cache.cc @@ -0,0 +1,79 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// length_app_cache.cc author Sourcefire Inc. + +#include "length_app_cache.h" + +#include "hash/sfxhash.h" +#include "log/messages.h" + +#include "appid_config.h" +#include "application_ids.h" + +#define HASH_NUM_ROWS (1024) + +void lengthAppCacheInit(AppIdConfig* pConfig) +{ + if (!(pConfig->lengthCache = sfxhash_new(HASH_NUM_ROWS, + sizeof(LengthKey), + sizeof(AppId), + 0, + 0, + nullptr, + nullptr, + 0))) + { + ErrorMessage("lengthAppCache: Failed to allocate length cache!"); + } +} + +void lengthAppCacheFini(AppIdConfig* pConfig) +{ + if (pConfig->lengthCache) + { + sfxhash_delete(pConfig->lengthCache); + pConfig->lengthCache = nullptr; + } +} + +AppId lengthAppCacheFind(const LengthKey* key, const AppIdConfig* pConfig) +{ + AppId* val; + + val = (AppId*)sfxhash_find(pConfig->lengthCache, (void*)key); + if (val == nullptr) + { + return APP_ID_NONE; /* no match */ + } + else + { + return *val; /* match found */ + } +} + +bool lengthAppCacheAdd(const LengthKey* key, AppId val, AppIdConfig* pConfig) +{ + if (sfxhash_add(pConfig->lengthCache, (void*)key, (void*)&val)) + { + return false; + } + return true; +} + diff --git a/src/network_inspectors/appid/length_app_cache.h b/src/network_inspectors/appid/length_app_cache.h new file mode 100644 index 000000000..2df3a81f3 --- /dev/null +++ b/src/network_inspectors/appid/length_app_cache.h @@ -0,0 +1,55 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// length_app_cache.h author Sourcefire Inc. + +#ifndef LENGTH_APP_CACHE_H +#define LENGTH_APP_CACHE_H + +#include "appid_api.h" + +#define LENGTH_SEQUENCE_CNT_MAX (5) + +#pragma pack(1) + +class AppIdConfig; +enum class IpProtocol : uint8_t; + +struct LengthSequenceEntry +{ + uint8_t direction; /* APP_ID_FROM_INITIATOR or APP_ID_FROM_RESPONDER */ + uint16_t length; /* payload size (bytes) */ +}; + +struct LengthKey +{ + IpProtocol proto; /* IpProtocol::TCP or IpProtocol::UDP */ + uint8_t sequence_cnt; /* num valid entries in sequence */ + LengthSequenceEntry sequence[LENGTH_SEQUENCE_CNT_MAX]; +}; + +#pragma pack() + +void lengthAppCacheInit(AppIdConfig*); +void lengthAppCacheFini(AppIdConfig*); +AppId lengthAppCacheFind(const LengthKey*, const AppIdConfig*); +bool lengthAppCacheAdd(const LengthKey*, AppId, AppIdConfig*); + +#endif + diff --git a/src/network_inspectors/appid/lua_detector_api.cc b/src/network_inspectors/appid/lua_detector_api.cc new file mode 100644 index 000000000..3d20ecadd --- /dev/null +++ b/src/network_inspectors/appid/lua_detector_api.cc @@ -0,0 +1,3392 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lua_detector_api.cc author Sourcefire Inc. + +#include "lua_detector_api.h" + +#include + +#include +#include + +#include "hash/sfxhash.h" +#include "log/messages.h" +#include "main/snort_debug.h" +#include "profiler/profiler.h" +#include "sfip/sf_ip.h" +#include "utils/util.h" + +#include "app_forecast.h" +#include "app_info_table.h" +#include "fw_appid.h" +#include "host_port_app_cache.h" +#include "http_common.h" +#include "lua_detector_flow_api.h" +#include "lua_detector_module.h" +#include "lua_detector_util.h" +#include "service_plugins/service_base.h" +#include "service_plugins/service_ssl.h" +#include "client_plugins/client_app_base.h" +#include "detector_plugins/detector_dns.h" +#include "detector_plugins/detector_pattern.h" + +#define DETECTOR "Detector" +#define OVECCOUNT 30 /* should be a multiple of 3 */ +#define URL_LIST_STEP_SIZE 5000 + +#define CHECK_INPUTS() \ + if ( !checkServiceElement(ud) || !ud->validateParams.pkt ) \ + { \ + lua_pushnumber(L, SERVICE_ENULL); \ + return 1; \ + } + +enum +{ + LUA_LOG_CRITICAL = 0, + LUA_LOG_ERR = 1, + LUA_LOG_WARN = 2, + LUA_LOG_NOTICE = 3, + LUA_LOG_INFO = 4, + LUA_LOG_DEBUG = 5, +}; + +/*static const char * LuaLogLabel = "luaDetectorApi"; */ + +ProfileStats luaDetectorsPerfStats; +ProfileStats luaCiscoPerfStats; +ProfileStats luaCustomPerfStats; + +static void FreeDetectorAppUrlPattern(DetectorAppUrlPattern* pattern); + +// FIXIT-H J lifetime of detector is easy to misuse with this idiom +// Leaves 1 value (the Detector userdata) at the top of the stack +Detector* createDetector(lua_State* L, const char* detectorName) +{ + auto detector = new Detector(); + detector->myLuaState = L; + detector->name = detectorName; + + UserData::push(L, DETECTOR, detector); + + // add a lua reference so the detector doesn't get garbage-collected + // FIXIT-M J should go in a different table maybe? + lua_pushvalue(L, -1); + detector->detectorUserDataRef = luaL_ref(L, LUA_REGISTRYINDEX); + + return detector; +} + +// must be called only when RNA is exitting. +static void freeDetector(Detector* detector) +{ delete detector; } + +// check service element, Allocate if necessary +int checkServiceElement(Detector* detector) +{ + if ( !detector->server.pServiceElement ) + { + detector->server.pServiceElement = new RNAServiceElement; + assert(detector->server.pServiceElement); + detector->server.pServiceElement->name = detector->server.serviceModule.name; + } + + return 1; +} + +// Creates a new detector instance. Creates a new detector instance and leaves the instance +// on stack. This is the first call by a lua detector to create and instance. Later calls +// provide the detector instance. +// +// lua params: +// #1 - serviceName/stack - name of service +// #2 - pValidator/stack - service validator function name +// #3 - pFini/stack - service clean exit function name +// return - a detector instance or none +static int service_init(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + // auto pServiceName = luaL_checkstring(L, 2); + auto pValidator = luaL_checkstring(L, 3); + auto pFini = luaL_checkstring(L, 4); + + lua_getglobal(L, pValidator); + lua_getglobal(L, pFini); + + if ( lua_isfunction(L, -1) && lua_isfunction(L, -2) ) + { + if ( checkServiceElement(ud) ) + { + ud->server.pServiceElement->validate = validateAnyService; + ud->server.pServiceElement->userdata = ud.ptr; + ud->server.pServiceElement->detectorType = DETECTOR_TYPE_DECODER; + } + + lua_pop(L, 2); + return 1; + } + else + { + ErrorMessage("%s: attempted setting validator/fini to non-function\n", + ud->server.serviceModule.name); + + lua_pop(L, 2); + return 0; + } +} + +// Register a pattern for fast pattern matching. Lua detector calls this function to register a +// pattern +// for fast pattern matching. This is similar to registerPattern in traditional C detector. +// +// lua params: +// #1 - detector/stack - detector object +// #2 - protocol/stack - protocol type. Values can be {tcp=6, udp=17 } +// #3 - pattern/stack - pattern string. +// #4 - size/stack - number of bytes in pattern +// #5 - position/stack - position offset where to start matching pattern. +// return - status/stack - 0 if successful, -1 otherwise. +static int service_registerPattern(lua_State* L) +{ + int index = 1; + + auto& ud = *UserData::check(L, DETECTOR, index++); + + // FIXIT-H J none of these params check for signedness casting issues + // FIXIT-M: May want to create a lua_toipprotocol() so we can handle + // error checking in that function. + int protocol = lua_tonumber(L, index++); + if (protocol > UINT8_MAX) + { + ErrorMessage("Invalid protocol value %d\n", protocol); + return -1; + } + + const char* pattern = lua_tostring(L, index++); + size_t size = lua_tonumber(L, index++); + unsigned int position = lua_tonumber(L, index++); + + /*Note: we can not give callback into lua directly so we have to + give a local callback function, which will do demuxing and + then call lua callback function. */ + + /*mpse library does not hold reference to pattern therefore we dont need to allocate it. */ + + ServiceRegisterPatternDetector(validateAnyService, (IpProtocol)protocol, (uint8_t*)pattern, + size, position, ud, ud->server.serviceModule.name); + + lua_pushnumber(L, 0); + return 1; +} + +static int common_registerAppId(lua_State* L) +{ + unsigned int appId; + int index = 1; + + auto& ud = *UserData::check(L, DETECTOR, index++); + appId = lua_tonumber(L, index++); + + if ( !ud->packageInfo.server.initFunctionName.empty() ) + appSetLuaServiceValidator( + validateAnyService, appId, APPINFO_FLAG_SERVICE_ADDITIONAL, ud.ptr); + + if ( !ud->packageInfo.client.initFunctionName.empty() ) + appSetLuaClientValidator( + validateAnyClientApp, appId, APPINFO_FLAG_CLIENT_ADDITIONAL, ud.ptr); + + appInfoSetActive(appId, true); + + lua_pushnumber(L, 0); + return 1; +} + +static int Detector_htons(lua_State* L) +{ + // FIXIT-L J ignoring arg #1, as it is unused + // auto* ud = UserData::check(L, DETECTOR, 1); + + unsigned short aShort = lua_tonumber(L, 2); + + lua_pushnumber(L, htons(aShort)); + return 1; +} + +static int Detector_htonl(lua_State* L) +{ + // FIXIT-L J ignoring arg #1, as it is unused + // auto* ud = UserData::check(L, DETECTOR, 1); + + unsigned int anInt = lua_tonumber(L, 2); + + lua_pushnumber(L, htonl(anInt)); + return 1; +} + +// Logs messages from detectors into wherever /etc/syslog.conf directs them. +// examples are: +// detector:log(DC.logLevel.warning, 'a warning') +// lua params: +// #1 - level - level of message. See DetectorCommon for enumeration. +// #2 - message - message to be logged. +static int Detector_logMessage(lua_State* L) +{ + const auto& name = (*UserData::check(L, DETECTOR, 1))->server.serviceModule.name; + + unsigned int level = lua_tonumber(L, 2); + const char* message = lua_tostring(L, 3); + + switch ( level ) + { + case LUA_LOG_CRITICAL: + FatalError("%s:%s\n", name, message); + break; + + case LUA_LOG_ERR: + case LUA_LOG_WARN: + // FIXIT-L J should WARN do a WarningMessage instead? + ErrorMessage("%s:%s\n", name, message); + break; + + case LUA_LOG_NOTICE: + case LUA_LOG_INFO: + LogMessage("%s:%s\n", name, message); + break; + + case LUA_LOG_DEBUG: + DebugFormat(DEBUG_APPID, "%s:%s\n", name, message); + break; + + default: + break; + } + + return 0; +} + +// Analyze application payload +// lua params: +// 1 - detector/stack - detector object +// 2 - major/stack - major number of application +// 3 - minor/stack - minor number of application +// 4 - flags/stack - any flags +static int service_analyzePayload(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + unsigned int payloadId = lua_tonumber(L, 2); + + assert(ud->validateParams.pkt); + + ud->validateParams.flowp->payloadAppId = payloadId; + + return 0; +} + +// Design notes: Due to following two design limitations: +// a. lua validate functions, known only at runtime, can not be wrapped inside unique +// C functions at runtime and +// b. core engine can not call lua functions directly. +// +// There must be a common validate function in C that in turn calls relevent Lua functions. +// Right now there is only one detector so there is a one-to-one mapping, but the framework +// will have to support multiple detectors in production environment. Core engine API will be +// changed to take an additional void* that will be used to call only a unique detector. +int validateAnyService(ServiceValidationArgs* args) +{ + Profile lua_detector_context(luaCustomPerfStats); + + int retValue; + auto detector = args->userdata; + + if ( !detector ) + { + // FIXIT-M J unhelpful error message + ErrorMessage("invalid LUA parameters"); + return SERVICE_ENULL; + } + + auto L = detector->myLuaState; + + detector->validateParams.data = args->data; + detector->validateParams.size = args->size; + detector->validateParams.dir = args->dir; + detector->validateParams.flowp = args->flowp; + detector->validateParams.pkt = args->pkt; + + const auto& serverName = detector->name; + + /*Note: Some frequently used header fields may be extracted and stored in detector for + better performance. */ + + if ( detector->packageInfo.server.validateFunctionName.empty() || !lua_checkstack(L, 1) ) + { + ErrorMessage("server %s: invalid LUA %s\n", serverName.c_str(), lua_tostring(L, -1)); + detector->validateParams.pkt = nullptr; + return SERVICE_ENULL; + } + + lua_getglobal(L, detector->packageInfo.server.validateFunctionName.c_str()); + + DebugFormat(DEBUG_APPID, "server %s: Lua Memory usage %d\n",serverName.c_str(), lua_gc(L, + LUA_GCCOUNT, 0)); + DebugFormat(DEBUG_APPID,"server %s: validating\n", serverName.c_str()); + + if ( lua_pcall(L, 0, 1, 0) ) + { + /*Runtime Lua errors are suppressed in production code since detectors are written for + efficiency + and with defensive minimum checks. Errors are dealt as exceptions that dont impact + processing + by other detectors or future packets by the same detector. */ + ErrorMessage("server %s: error validating %s\n", serverName.c_str(), lua_tostring(L, -1)); + detector->validateParams.pkt = nullptr; + return SERVICE_ENULL; + } + + /**detectorFlows must be destroyed after each packet is processed.*/ + sflist_static_free_all(&allocatedFlowList, freeDetectorFlow); + + /* retrieve result */ + if ( !lua_isnumber(L, -1) ) + { + ErrorMessage("server %s: validator returned non-numeric value\n", serverName.c_str()); + detector->validateParams.pkt = nullptr; + return SERVICE_ENULL; + } + + retValue = lua_tonumber(L, -1); + lua_pop(L, 1); /* pop returned value */ + + DebugFormat(DEBUG_APPID, "server %s: Validator returned %d\n", serverName.c_str(), retValue); + + detector->validateParams.pkt = nullptr; + + return retValue; +} + +/**design: dont store serviceId in detector structure since a single detector + * can get serviceId for multiple protocols. For example SIP which gets Id for RTP and + * SIP services. + */ + +// Get service id from database, given service name. Lua detectors call this function at init time +// get get a service Id (an integer) from database. +// @param serviceName/stack - Name of service +// @return serviceId/stack - serviceId if successful, -1 otherwise. +static int service_getServiceId(lua_State* L) +{ + auto ud = *UserData::check(L, DETECTOR, 1); + + lua_pushnumber(L, ud->server.serviceId); + return 1; +} + +/** + * Design Notes: In these APIs, three different AppID contexts - pAppidNewConfig, pAppidOldConfig + * and pAppidActiveConfig are used. pAppidNewConfig is used in APIs related to the loading of the + * detector such as service_addPorts(), client_registerPattern(), etc. A detector is loaded either + * during reload or at initialization. Use of pAppidNewConfig will cause the data structures related + * to the detector such as service ports, patterns, etc to be saved in the new AppID context. + * + * The new AppID context becomes active at the end of initialization or at reload swap. + * FinalizeLuaModules() is called at this time, which changes all the detectors' pAppidActiveConfig + * references to the new context. Also, pAppidOldConfig will be changed to point to the previous + * AppID context. In the packet processing APIs such as service_addService(), client_addUser(), etc. + * pAppidActiveConfig is used. + * + * In the cleanup APIs such as service_removePorts(), Detector_fini(), etc., data structures in the + * old AppID conext need to be freed. Therefore, pAppidOldConfig is used in these APIs. + */ + +// Add port for a given service. Lua detectors call this function to register ports on which a +// given service is expected to run. +// @param protocol/stack - protocol type. Values can be {tcp=6, udp=17 } +// @param port/stack - port number to register. +// @return status/stack - 0 if successful, -1 otherwise. +static int service_addPorts(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + RNAServiceValidationPort pp; + pp.proto = (IpProtocol)lua_tonumber(L, 2); + pp.port = lua_tonumber(L, 3); + pp.reversed_validation = lua_tonumber(L, 5); + pp.validate = &validateAnyService; + + if ( ((pp.proto != IpProtocol::UDP) && (pp.proto != IpProtocol::TCP)) || !pp.port ) + { + lua_pushnumber(L, -1); + return 1; + } + + if ( ServiceAddPort(&pp, &ud->server.serviceModule, ud, ud->pAppidNewConfig) ) + { + lua_pushnumber(L, -1); + return 1; + } + + ++ud->server.pServiceElement->ref_count; + + lua_pushnumber(L, 0); + return 1; +} + +// Remove all ports for a given service. Lua detectors call this function to remove ports for this +// service +// when exiting. This function is not used currently by any detectors. +// @return status/stack - 0 if successful, -1 otherwise. +static int service_removePorts(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + detectorRemoveAllPorts(ud, ud->pAppidOldConfig); + + lua_pushnumber(L, 0); + return 1; +} + +// Shared function between Lua API and RNA core. +void detectorRemoveAllPorts(Detector* detector, AppIdConfig* pConfig) +{ ServiceRemovePorts(&validateAnyService, detector, pConfig); } + +// Set service name. Lua detectors call this function to set service name. It is preferred to set +// service name +// when a detector is created. Afterwards there is rarely a need to change service name. +// @param serviceName/stack - Name of service +// @return status/stack - 0 if successful, -1 otherwise. +static int service_setServiceName(lua_State* L) +{ + lua_pushnumber(L, 0); + return 1; +} + +/**Get service name. Lua detectors call this function to get service name. There is + * rarely a need to change service name. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is always 1. + * @return serviceName/stack - service name if successful, nil otherwise. + */ +static int service_getServiceName( + lua_State* L + ) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + lua_pushstring(L, ud->server.serviceModule.name); + return 1; +} + +/**Is this a customer defined detector. Lua detectors can call this function to verify if the detector + * was created by Sourcefire or not. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is always 1. + * @return integer/stack - -1 if failed, 0 if sourcefire created, 1 otherwise. + */ +static int service_isCustomDetector(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + lua_pushnumber(L, ud->isCustom); + return 1; +} + +/**Set service validator Lua function name. Lua detectors use this function to set a lua function name + * as service validator function. It is preferred to set validatorname when a detector is created. + * Afterwards there is rarely a need to change service name. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @param validatorName/stack - Name of service validator + * @return int - Number of elements on stack, which is always 0. + */ +static int service_setValidator(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + const char* pValidator = lua_tostring(L, 2); + lua_getglobal(L, pValidator); + + if (!lua_isfunction(L, -1)) + { + ErrorMessage("%s: attempted setting validator to non-function\n", + ud->server.serviceModule.name); + + lua_pop(L, 1); + lua_pushnumber(L, -1); + return 1; + } + + lua_pop(L, 1); + + ud->packageInfo.server.validateFunctionName = pValidator; + + lua_pushnumber(L, 0); + return 1; +} + +/** Add data (validator function name) to a flow. Detector use this function when confirming a flow + * belongs to this service. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @param sourcePort/stack - Source port number. + * @return int - Number of elements on stack, which is always 0. + */ +static int service_addDataId(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + uint16_t sport = lua_tonumber(L, 2); + + /*check inputs and whether this function is called in context of a + packet */ + if ( !checkServiceElement(ud) || !ud->validateParams.pkt ) + { + lua_pushnumber(L, -1); + return 1; + } + + AppIdFlowdataAddId(ud->validateParams.flowp, sport, ud->server.pServiceElement); + + lua_pushnumber(L, 0); + return 1; +} + +/** Add service id to a flow. Positive identification by a detector. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @param serviceId/stack - id of service postively identified on this flow. + * @param vendorName/stack - name of vendor of service. This is optional. + * @param version/stack - version of service. This is optional. + * @return int - Number of elements on stack, which is always 1. + * @return int/stack - values from enum SERVICE_RETCODE + */ +static int service_addService( + lua_State* L + ) +{ + char* vendor, * version; + unsigned int serviceId, retValue = SERVICE_ENULL; + auto& ud = *UserData::check(L, DETECTOR, 1); + + serviceId = lua_tonumber(L, 2); + vendor = (char*)luaL_optstring(L, 3, nullptr); + version = (char*)luaL_optstring(L, 4, nullptr); + + /*check inputs (vendor and version may be null) and whether this function is + called in context of a packet */ + if ( !checkServiceElement(ud) || !ud->validateParams.pkt ) + { + lua_pushnumber(L, SERVICE_ENULL); + return 1; + } + + /*Phase2 - discuss RNAServiceSubtype will be maintained on lua side therefore the last + parameter on the following call is nullptr. + Subtype is not displayed on DC at present. */ + retValue = AppIdServiceAddService(ud->validateParams.flowp, ud->validateParams.pkt, + ud->validateParams.dir, ud->server.pServiceElement, + appGetAppFromServiceId(serviceId, ud->pAppidActiveConfig), vendor, version, nullptr); + + lua_pushnumber(L, retValue); + return 1; +} + +/**Function confirms the flow is not running this service. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is always 1. + * @return int/stack - values from enum SERVICE_RETCODE + */ +static int service_failService(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + CHECK_INPUTS(); + + unsigned int retValue = AppIdServiceFailService(ud->validateParams.flowp, + ud->validateParams.pkt, + ud->validateParams.dir, ud->server.pServiceElement, APPID_SESSION_DATA_NONE, + ud->pAppidActiveConfig); + + lua_pushnumber(L, retValue); + return 1; +} + +/**Detector use this function to indicate the flow may belong to this flow. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is always 1. + * @return int/stack - values from enum SERVICE_RETCODE + */ +static int service_inProcessService(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + CHECK_INPUTS(); + + unsigned int retValue = AppIdServiceInProcess(ud->validateParams.flowp, ud->validateParams.pkt, + ud->validateParams.dir, ud->server.pServiceElement); + + lua_pushnumber(L, retValue); + return 1; +} + +/**Detector use this function to indicate error in service identification. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is always 1. + * @return int/stack - values from enum SERVICE_RETCODE + */ +static int service_inCompatibleData(lua_State* L) +{ + unsigned int retValue = SERVICE_ENULL; + auto& ud = *UserData::check(L, DETECTOR, 1); + + CHECK_INPUTS(); + + retValue = AppIdServiceIncompatibleData(ud->validateParams.flowp, + ud->validateParams.pkt, + ud->validateParams.dir, ud->server.pServiceElement, + APPID_SESSION_DATA_NONE, ud->pAppidActiveConfig); + + lua_pushnumber(L, retValue); + return 1; +} + +/** Get size of current packet. It should be noted that due to restrictions on sharing pointers + * between C and Lua, packet data is maintained on C side. Lua side can get specific fields, run + * memcmp and pattern matching on packet data. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is always 1 if successful, 0 otherwise. + * @return packetSize/stack - size of packet on stack, if successful. + */ +static int Detector_getPacketSize( + lua_State* L + ) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + lua_pushnumber(L, ud->validateParams.size); + return 1; +} + +/**Get packet direction. A flow/session maintains initiater and responder sides. A packet direction + * is determined wrt to the original initiater. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is always 1 if successful, 0 otherwise. + * @return packetDir/stack - direction of packet on stack, if successful. + */ +static int Detector_getPacketDir( + lua_State* L + ) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + lua_pushnumber(L, ud->validateParams.dir); + return 1; +} + +/**Perform a pcre match with grouping. A simple regular expression match with no grouping + * can also be performed. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of group matches. May be 0 or more. + * @return matchedStrings/stack - matched strings are pushed on stack starting with group 0. + * There may be 0 or more strings. + */ +static int Detector_getPcreGroups( + lua_State* L + ) +{ + char* pattern; + unsigned int offset; + pcre* re; + int ovector[OVECCOUNT]; + const char* error; + int erroffset; + int rc, i; + auto& ud = *UserData::check(L, DETECTOR, 1); + + pattern = (char*)lua_tostring(L, 2); + offset = lua_tonumber(L, 3); /*offset can be zero, no check necessary. */ + + { + /*compile the regular expression pattern, and handle errors */ + re = pcre_compile( + pattern, /*the pattern */ + PCRE_DOTALL, /*default options - dot matches everything including newline */ + &error, /*for error message */ + &erroffset, /*for error offset */ + nullptr); /*use default character tables */ + + if (re == nullptr) + { + ErrorMessage("PCRE compilation failed at offset %d: %s\n", erroffset, error); + return 0; + } + + /*pattern match against the subject string. */ + rc = pcre_exec( + re, /*compiled pattern */ + nullptr, /*no extra data */ + (char*)ud->validateParams.data, /*subject string */ + ud->validateParams.size, /*length of the subject */ + offset, /*offset 0 */ + 0, /*default options */ + ovector, /*output vector for substring information + */ + OVECCOUNT); /*number of elements in the output vector + */ + + if (rc < 0) + { + /*Matching failed: clubbing PCRE_ERROR_NOMATCH with other errors. */ + pcre_free(re); + return 0; + } + + /*Match succeded */ + + /*printf("\nMatch succeeded at offset %d", ovector[0]); */ + pcre_free(re); + + if (rc == 0) + { + /*overflow of matches */ + rc = OVECCOUNT/3; + /*printf("ovector only has room for %d captured substrings", rc - 1); */ + ErrorMessage("ovector only has room for %d captured substrings\n",rc - 1); + } + } + + lua_checkstack (L, rc); + for (i = 0; i < rc; i++) + { + /*printf("%2d: %.*s\n", i, , substring_start); */ + lua_pushlstring(L, (char*)ud->validateParams.data + ovector[2*i], ovector[2*i+1] - + ovector[2*i]); + } + + return rc; +} + +/**Performs a simple memory comparison. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @param pattern/stack - pattern to be matched. + * @param patternLenght/stack - length of pattern + * @param offset/stack - offset into packet payload where matching should start. + * + * @return int - Number of group matches. May be 1 if successful, and 0 if error is encountered. + * @return memCmpResult/stack - returns -1,0,1 based on memcmp result. + */ +static int Detector_memcmp( + lua_State* L + ) +{ + char* pattern; + unsigned int patternLen; + unsigned int offset; + int rc; + auto& ud = *UserData::check(L, DETECTOR, 1); + + pattern = (char*)lua_tostring(L, 2); + patternLen = lua_tonumber(L, 3); + offset = lua_tonumber(L, 4); /*offset can be zero, no check necessary. */ + + rc = memcmp((char*)ud->validateParams.data + offset, pattern, patternLen); + + lua_checkstack (L, 1); + lua_pushnumber(L, rc); + return 1; +} + +/**Get Packet Protocol Type + * + * @param Lua_State* - Lua state variable. + * @return int - Number of elements on stack, which is protocol type if successful, 0 otherwise. + * @return protocol type TCP or UDP + */ +static int Detector_getProtocolType(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + if ( !ud->validateParams.pkt || !ud->validateParams.pkt->has_ip() ) + { + // FIXIT-H J why the inconsistent use of checkstack? + lua_checkstack (L, 1); + lua_pushnumber(L, 0); + return 1; + } + + lua_checkstack (L, 1); + // FIXIT-H: is this conversion to double valid? + lua_pushnumber(L, (double)ud->validateParams.pkt->get_ip_proto_next() ); + return 1; +} + +/**Get source IP address from IP header. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return IPv4/stack - Source IPv4 addresss. + */ +static int Detector_getPktSrcIPAddr(lua_State* L) +{ + const sfip_t* ipAddr; + auto& ud = *UserData::check(L, DETECTOR, 1); + + ipAddr = ud->validateParams.pkt->ptrs.ip_api.get_src(); + + lua_checkstack (L, 1); + lua_pushnumber(L, ipAddr->ip32[0]); + return 1; +} + +/**Get source port number from IP header. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return portNumber/stack - source port number. + */ +static int Detector_getPktSrcPort(lua_State* L) +{ + unsigned int port; + auto& ud = *UserData::check(L, DETECTOR, 1); + + port = ud->validateParams.pkt->ptrs.sp; + + lua_checkstack (L, 1); + lua_pushnumber(L, port); + return 1; +} + +/**Get destination port number from IP header. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return portNumber/stack - destination Port number. + */ +static int Detector_getPktDstPort(lua_State* L) +{ + unsigned int port; + auto& ud = *UserData::check(L, DETECTOR, 1); + + port = ud->validateParams.pkt->ptrs.dp; + + lua_checkstack (L, 1); + lua_pushnumber(L, port); + return 1; +} + +/**Get destination IP address from IP header. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return IPv4/stack - destination IPv4 addresss. + */ +static int Detector_getPktDstIPAddr(lua_State* L) +{ + const sfip_t* ipAddr; + auto& ud = *UserData::check(L, DETECTOR, 1); + + ipAddr = ud->validateParams.pkt->ptrs.ip_api.get_dst(); + + lua_checkstack (L, 1); + lua_pushnumber(L, ipAddr->ip32[0]); + return 1; +} + +/**Get packet count. This is used mostly for printing packet sequence + * number when RNA is being tested with a pcap file. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return packetCount/stack - Total packet processed by RNA. + */ +static int Detector_getPktCount(lua_State* L) +{ + lua_checkstack (L, 1); + lua_pushnumber(L, app_id_processed_packet_count); + return 1; +} + +CLIENT_APP_RETCODE validateAnyClientApp( + const uint8_t* data, + uint16_t size, + const int dir, + AppIdData* flowp, + Packet* pkt, + Detector* detector, + const AppIdConfig* + ) +{ + Profile lua_profile_context(luaCustomPerfStats); + + int retValue; + lua_State* myLuaState; + const char* validateFn; + const char* clientName; + + if (!data || !flowp || !pkt || !detector) + { + return CLIENT_APP_ENULL; + } + + myLuaState = detector->myLuaState; + detector->validateParams.data = data; + detector->validateParams.size = size; + detector->validateParams.dir = dir; + detector->validateParams.flowp = flowp; + detector->validateParams.pkt = (Packet*)pkt; + validateFn = detector->packageInfo.client.validateFunctionName.c_str(); + clientName = detector->name.c_str(); + + if ((!validateFn) || !(lua_checkstack(myLuaState, 1))) + { + ErrorMessage("client %s: invalid LUA %s\n",clientName, lua_tostring(myLuaState, -1)); + detector->validateParams.pkt = nullptr; + return CLIENT_APP_ENULL; + } + + lua_getglobal(myLuaState, validateFn); + + DebugFormat(DEBUG_APPID,"client %s: Lua Memory usage %d\n",clientName, lua_gc(myLuaState, + LUA_GCCOUNT,0)); + DebugFormat(DEBUG_APPID,"client %s: validating\n",clientName); + + if (lua_pcall(myLuaState, 0, 1, 0)) + { + ErrorMessage("client %s: error validating %s\n",clientName, lua_tostring(myLuaState, -1)); + detector->validateParams.pkt = nullptr; + return (CLIENT_APP_RETCODE)SERVICE_ENULL; + } + + /**detectorFlows must be destroyed after each packet is processed.*/ + sflist_static_free_all(&allocatedFlowList, freeDetectorFlow); + + /* retrieve result */ + if (!lua_isnumber(myLuaState, -1)) + { + ErrorMessage("client %s: validator returned non-numeric value\n",clientName); + detector->validateParams.pkt = nullptr; + retValue = SERVICE_ENULL; + } + + retValue = lua_tonumber(myLuaState, -1); + lua_pop(myLuaState, 1); /* pop returned value */ + /*lua_settop(myLuaState, 0); */ + + DebugFormat(DEBUG_APPID,"client %s: Validator returned %d\n",clientName, retValue); + + detector->validateParams.pkt = nullptr; + + return (CLIENT_APP_RETCODE)retValue; +} + +static int client_registerPattern(lua_State* L) +{ + IpProtocol protocol; + size_t size; + const char* pattern; + unsigned int position; + int index = 1; + + auto& ud = *UserData::check(L, DETECTOR, index++); + protocol = (IpProtocol)lua_tonumber(L, index++); + pattern = lua_tostring(L, index++); + size = lua_tonumber(L, index++); + position = lua_tonumber(L, index++); + + /*Note: we can not give callback into lua directly so we have to + give a local callback function, which will do demuxing and + then call lua callback function. */ + + /*mpse library does not hold reference to pattern therefore we dont need to allocate it. */ + + ud->client.appModule.userData = ud.ptr; + ClientAppLoadForConfigCallback((void*)&(ud->client.appModule), + &ud->pAppidNewConfig->clientAppConfig); + ClientAppRegisterPattern( + validateAnyClientApp, protocol, (const uint8_t*)pattern, size, + position, 0, ud, &ud->pAppidNewConfig->clientAppConfig); + + lua_pushnumber(L, 0); + return 1; /*number of results */ +} + +/**Creates a new detector instance. Creates a new detector instance and leaves the instance + * on stack. This is the first call by a lua detector to create and instance. Later calls + * provide the detector instance. + * + * @param Lua_State* - Lua state variable. + * @param serviceName/stack - name of service + * @param pValidator/stack - service validator function name + * @param pFini/stack - service clean exit function name + * @return int - Number of elements on stack, which should be 1 if success 0 otherwise. + * @return detector - a detector instance on stack if successful + */ + +static int client_init(lua_State*) +{ + /*nothing to do */ + return 0; +} + +static int service_addClient(lua_State* L) +{ + AppId clienAppId, serviceId; + const char* version; + + auto& ud = *UserData::check(L, DETECTOR, 1); + + clienAppId = lua_tonumber(L, 2); + serviceId = lua_tonumber(L, 3); + version = lua_tostring(L, 4); + + if ( !ud->validateParams.pkt || !version ) + { + lua_pushnumber(L, -1); + return 1; + } + + AppIdAddClientApp(ud->validateParams.flowp, serviceId, clienAppId, version); + + lua_pushnumber(L, 0); + return 1; +} + +static int client_addApp(lua_State* L) +{ + unsigned int serviceId, productId; + const char* version; + auto& ud = *UserData::check(L, DETECTOR, 1); + + serviceId = lua_tonumber(L, 2); + productId = lua_tonumber(L, 4); + version = lua_tostring(L, 5); + + CHECK_INPUTS(); + + if ( !ud->client.appModule.api ) + { + lua_pushnumber(L, -1); + return 1; + } + + ud->client.appModule.api->add_app(ud->validateParams.flowp, + appGetAppFromServiceId(serviceId, ud->pAppidActiveConfig), appGetAppFromClientId( + productId, ud->pAppidActiveConfig), version); + + lua_pushnumber(L, 0); + return 1; +} + +static int client_addInfo(lua_State* L) +{ + const char* info; + auto& ud = *UserData::check(L, DETECTOR, 1); + + info = lua_tostring(L, 2); + + CHECK_INPUTS(); + + if (!ud->client.appModule.api) + { + lua_pushnumber(L, -1); + return 1; + } + + ud->client.appModule.api->add_info(ud->validateParams.flowp, info); + + lua_pushnumber(L, 0); + return 1; +} + +static int client_addUser(lua_State* L) +{ + unsigned int serviceId; + const char* userName; + auto& ud = *UserData::check(L, DETECTOR, 1); + + userName = lua_tostring(L, 2); + serviceId = lua_tonumber(L, 3); + + CHECK_INPUTS(); + + if (!ud->client.appModule.api) + { + lua_pushnumber(L, -1); + return 1; + } + + ud->client.appModule.api->add_user(ud->validateParams.flowp, userName, + appGetAppFromServiceId(serviceId, ud->pAppidActiveConfig), 1); + + lua_pushnumber(L, 0); + return 1; +} + +static int client_addPayload(lua_State* L) +{ + unsigned int payloadId; + auto& ud = *UserData::check(L, DETECTOR, 1); + + payloadId = lua_tonumber(L, 2); + + CHECK_INPUTS(); + + if (!ud->client.appModule.api) + { + lua_pushnumber(L, -1); + return 1; + } + + ud->client.appModule.api->add_payload(ud->validateParams.flowp, + appGetAppFromPayloadId(payloadId, ud->pAppidActiveConfig)); + + lua_pushnumber(L, 0); + return 1; +} + +/**Get flow object from a detector object. The flow object is then used with flowApi. + * A new copy of flow object is provided with every call. This can be optimized by maintaining + * a single copy. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return packetCount/stack - Total packet processed by RNA. + * @todo maintain a single copy and return the same copy with every call to Detector_getFlow(). + */ +static int Detector_getFlow(lua_State* L) +{ + auto& ud = *UserData::check(L, DETECTOR, 1); + + CHECK_INPUTS(); + + auto df = new DetectorFlow(); + df->pFlow = ud->validateParams.flowp; + + UserData::push(L, "DetectorFlow", df); + + return 1; +} + +int Detector_addHttpPattern(lua_State* L) +{ + int index = 1; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + + /* Verify valid pattern type */ + enum httpPatternType pType = (enum httpPatternType)lua_tointeger(L, index++); + if (pType < HTTP_PAYLOAD || pType > HTTP_URL) + { + ErrorMessage("Invalid HTTP pattern type."); + return 0; + } + + /* Verify valid DHSequence */ + DHPSequence seq = (DHPSequence)lua_tointeger(L, index++); + if (seq < SINGLE || seq > USER_AGENT_HEADER) + { + ErrorMessage("Invalid HTTP DHP Sequence."); + return 0; + } + + uint32_t service_id = lua_tointeger(L, index++); + uint32_t client_app = lua_tointeger(L, index++); + /*uint32_t client_app_type =*/ lua_tointeger(L, index++); + uint32_t payload = lua_tointeger(L, index++); + /*uint32_t payload_type =*/ lua_tointeger(L, index++); + + // FIXIT-H J should this be inverted? + if (ud->validateParams.pkt) + { + ErrorMessage( + "Invalid detector context addHttpPattern: service_id %u; client_app %u; payload %u\n", + service_id, client_app, payload); + return 0; + } + + /* Verify that pattern is a valid string */ + size_t pattern_size = 0; + const char* tmpString = lua_tolstring(L, index++, &pattern_size); + if ( tmpString == nullptr || pattern_size == 0) + { + ErrorMessage("Invalid HTTP pattern string."); + return 0; + } + + uint8_t* pattern_str = (uint8_t*)snort_strdup(tmpString); + uint32_t appId = lua_tointeger(L, index++); + + HTTPListElement* element = (HTTPListElement*)snort_calloc(sizeof(HTTPListElement)); + DetectorHTTPPattern* pattern = &element->detectorHTTPPattern; + AppIdConfig* pConfig = ud->pAppidNewConfig; + + pattern->seq = seq; + pattern->service_id = appGetAppFromServiceId(service_id, pConfig); + pattern->client_app = appGetAppFromClientId(client_app, pConfig); + pattern->payload = appGetAppFromPayloadId(payload, pConfig); + pattern->pattern = pattern_str; + pattern->pattern_size = (int)pattern_size; + pattern->appId = appId; + + /* for apps that should not show up in 4.10 and ealier, we cannot include an entry in + the legacy client app or payload tables. We will use the appId instead. This is only for + user-agents that ID clients. if you want a user-agent to ID a payload, include it in the + payload database. If you want a host pattern ID, use the other API. */ + + if (!service_id && !client_app && !payload && pType == 2) + { + pattern->client_app = appId; + } + + switch (pType) + { + case HTTP_PAYLOAD: + element->next = pConfig->httpPatternLists.hostPayloadPatternList; + pConfig->httpPatternLists.hostPayloadPatternList = element; + break; + + case HTTP_URL: + element->next = pConfig->httpPatternLists.urlPatternList; + pConfig->httpPatternLists.urlPatternList = element; + break; + + case HTTP_USER_AGENT: + element->next = pConfig->httpPatternLists.clientAgentPatternList; + pConfig->httpPatternLists.clientAgentPatternList = element; + break; + } + + appInfoSetActive(pattern->service_id, true); + appInfoSetActive(pattern->client_app, true); + appInfoSetActive(pattern->payload, true); + appInfoSetActive(appId, true); + + return 0; +} + +/* On the lua side, this should look something like: + addSSLCertPattern(, '' ) +*/ +int Detector_addSSLCertPattern(lua_State* L) +{ + uint8_t* pattern_str; + size_t pattern_size; + int index = 1; + uint8_t type; + AppId app_id; + + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("Invalid SSL detector user data or context."); + return 0; + } + + type = lua_tointeger(L, index++); + app_id = (AppId)lua_tointeger(L, index++); + + pattern_size = 0; + const char* tmpString = lua_tolstring(L, index++, &pattern_size); + if (!tmpString || !pattern_size) + { + ErrorMessage("Invalid SSL Host pattern string"); + return 0; + } + pattern_str = (uint8_t*)snort_strdup(tmpString); + +#ifdef REMOVED_WHILE_NOT_IN_USE + if (!ssl_add_cert_pattern(pattern_str, pattern_size, type, app_id, + &ud->pAppidNewConfig->serviceSslConfig)) + { + snort_free(pattern_str); + ErrorMessage("Failed to add an SSL pattern list member"); + return 0; + } +#else + UNUSED(pattern_str); + UNUSED(type); +#endif + + appInfoSetActive(app_id, true); + return 0; +} + +/* On the lua side, this should look something like: + addDNSHostPattern(, '' ) +*/ +int Detector_addDNSHostPattern(lua_State* L) +{ + uint8_t* pattern_str; + size_t pattern_size; + int index = 1; + uint8_t type; + AppId app_id; + + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("LuaDetectorApi:Invalid DNS detector user data or context."); + return 0; + } + + type = lua_tointeger(L, index++); + app_id = (AppId)lua_tointeger(L, index++); + + pattern_size = 0; + const char* tmpString = lua_tolstring(L, index++, &pattern_size); + if (!tmpString || !pattern_size) + { + ErrorMessage("LuaDetectorApi:Invalid DNS Host pattern string"); + return 0; + } + pattern_str = (uint8_t*)snort_strdup(tmpString); + if (!dns_add_host_pattern(pattern_str, pattern_size, type, app_id, + &ud->pAppidNewConfig->serviceDnsConfig)) + { + snort_free(pattern_str); + ErrorMessage("LuaDetectorApi:Failed to add an SSL pattern list member"); + } + + return 0; +} + +static int Detector_addSSLCnamePattern(lua_State* L) +{ + uint8_t* pattern_str; + size_t pattern_size; + int index = 1; + uint8_t type; + AppId app_id; + + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("Invalid SSL detector user data or context."); + return 0; + } + + type = lua_tointeger(L, index++); + app_id = (AppId)lua_tointeger(L, index++); + + pattern_size = 0; + const char* tmpString = lua_tolstring(L, index++, &pattern_size); + if (!tmpString || !pattern_size) + { + ErrorMessage("Invalid SSL Host pattern string"); + return 0; + } + pattern_str = (uint8_t*)snort_strdup(tmpString); + +#ifdef REMOVED_WHILE_NOT_IN_USE + if (!ssl_add_cname_pattern(pattern_str, pattern_size, type, app_id, + &ud->pAppidNewConfig->serviceSslConfig)) + { + snort_free(pattern_str); + ErrorMessage("Failed to add an SSL pattern list member"); + return 0; + } +#else + UNUSED(pattern_str); + UNUSED(type); +#endif + + appInfoSetActive(app_id, true); + return 0; +} + +static int Detector_addHostPortApp(lua_State* L) +{ + /*uint8_t *ipaddr_str; */ + size_t ipaddr_size; + int index = 1; + uint8_t type; + AppId app_id; + in6_addr ip6Addr; + + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("%s: Invalid detector user data or context.\n",__func__); + return 0; + } + + type = lua_tointeger(L, index++); + app_id = (AppId)lua_tointeger(L, index++); + + ipaddr_size = 0; + const char* tmpString = lua_tolstring(L, index++, &ipaddr_size); + if (!tmpString || !ipaddr_size) + { + ErrorMessage("%s:Invalid ipaddr string\n",__func__); + return 0; + } + if (!strchr(tmpString, ':')) + { + if (inet_pton(AF_INET, tmpString, &ip6Addr) <= 0) + { + ErrorMessage("%s: Invalid IP address: %s\n",__func__, tmpString); + return 0; + } + + // FIXIT-H J ip6Addr type is struct in6_addr, so... + // ip6Addr.u6_addr32[0] = ip6Addr.u6_addr32[1] = 0; + // ip6Addr.u6_addr32[2] = ntohl(0x0000ffff); + } + else + { + if (inet_pton(AF_INET6, tmpString, &ip6Addr) <= 0) + { + ErrorMessage("%s: Invalid IP address: %s\n",__func__, tmpString); + return 0; + } + } + unsigned port = lua_tointeger(L, index++); + unsigned proto = lua_tointeger(L, index++); + + if (proto > UINT8_MAX) + { + ErrorMessage("%s:Invalid protocol value %d\n",__func__, proto); + return 0; + } + + if (!hostPortAppCacheAdd(&ip6Addr, (uint16_t)port, (IpProtocol)proto, type, app_id, + ud->pAppidNewConfig)) + { + ErrorMessage("%s:Failed to backend call\n",__func__); + } + + return 0; +} + +static int Detector_addContentTypePattern(lua_State* L) +{ + uint8_t* pattern; + AppId appId; + int index = 1; + + auto& ud = *UserData::check(L, DETECTOR, index++); + + size_t stringSize = 0; + const char* tmpString = lua_tolstring(L, index++, &stringSize); + if (!tmpString || !stringSize) + { + ErrorMessage("Invalid HTTP Header string"); + return 0; + } + pattern = (uint8_t*)snort_strdup(tmpString); + appId = lua_tointeger(L, index++); + + if (ud->validateParams.pkt) + { + ErrorMessage("Invalid detector context addSipUserAgent: appId %d\n",appId); + snort_free(pattern); + return 0; + } + + HTTPListElement* element = (HTTPListElement*)snort_calloc(sizeof(HTTPListElement)); + DetectorHTTPPattern* detector = &element->detectorHTTPPattern; + AppIdConfig* pConfig = ud->pAppidNewConfig; + + detector->pattern = pattern; + detector->pattern_size = strlen((char*)pattern); + detector->appId = appId; + + element->next = pConfig->httpPatternLists.contentTypePatternList; + pConfig->httpPatternLists.contentTypePatternList = element; + + appInfoSetActive(appId, true); + + return 0; +} + +static inline int GetDetectorUserData(lua_State* L, int index, + UserData** detector_user_data, const char* errorString) +{ + // Verify detector user data and that we are not in packet context + *detector_user_data = UserData::check(L, DETECTOR, index); + if (!*detector_user_data || (**detector_user_data)->validateParams.pkt) + { + ErrorMessage("%s", errorString); + return -1; + } + + return 0; +} + +static int detector_create_chp_app(UserData* ud, AppId appIdInstance, + unsigned app_type_flags, int num_matches) +{ + CHPApp* new_app = (CHPApp*)snort_calloc(sizeof(CHPApp)); + new_app->appIdInstance = appIdInstance; + new_app->app_type_flags = app_type_flags; + new_app->num_matches = num_matches; + + if (sfxhash_add((*ud)->pAppidNewConfig->CHP_glossary, + &(new_app->appIdInstance), new_app)) + { + ErrorMessage("LuaDetectorApi:Failed to add CHP for appId %d, instance %d", + CHP_APPIDINSTANCE_TO_ID(appIdInstance), CHP_APPIDINSTANCE_TO_INSTANCE(appIdInstance)); + snort_free(new_app); + return -1; + } + return 0; +} + +static int Detector_CHPCreateApp(lua_State* L) +{ + UserData* ud; + AppId appId; + unsigned app_type_flags; + int num_matches; + + AppId appIdInstance; + + int index = 1; + + if (GetDetectorUserData(L, index++, &ud, + "LuaDetectorApi:Invalid HTTP detector user data in CHPCreateApp.")) + return 0; + + appId = lua_tointeger(L, index++); + appIdInstance = CHP_APPID_SINGLE_INSTANCE(appId); // Last instance for the old API + + app_type_flags = lua_tointeger(L, index++); + num_matches = lua_tointeger(L, index++); + + // We only want one of these for each appId. + if (sfxhash_find((*ud)->pAppidNewConfig->CHP_glossary, &appIdInstance)) + { + ErrorMessage( + "LuaDetectorApi:Attempt to add more than one CHP for appId %d - use CHPMultiCreateApp", + appId); + return 0; + } + + detector_create_chp_app(ud, appIdInstance, app_type_flags, num_matches); + return 0; +} + +static inline int CHPGetKeyPatternBoolean(lua_State* L, int index) +{ + return (0 != lua_tointeger(L, index)); +} + +static inline int CHPGetPatternType(lua_State* L, int index, PatternType* pattern_type) +{ + *pattern_type = (PatternType)lua_tointeger(L, index); + if (*pattern_type < AGENT_PT || *pattern_type > MAX_PATTERN_TYPE) + { + ErrorMessage("LuaDetectorApi:Invalid CHP Action pattern type."); + return -1; + } + return 0; +} + +static inline int CHPGetPatternDataAndSize(lua_State* L, int index, char** pattern_data, + size_t* pattern_size) +{ + const char* tmpString = nullptr; // Lua owns this pointer + *pattern_size = 0; + *pattern_data = nullptr; + tmpString = lua_tolstring(L, index, &*pattern_size); + // FIXIT-M: recode all this to something elegant since snort_strdup can't fail (just like Rudy) + // non-empty pattern required + if (!tmpString || !*pattern_size || !(*pattern_data = snort_strdup(tmpString))) + { + if (*pattern_size) // implies snort_strdup() failed + ErrorMessage("LuaDetectorApi:CHP Action PATTERN string mem alloc failed."); + else + ErrorMessage("LuaDetectorApi:Invalid CHP Action PATTERN string."); // empty string in + // Lua code - bad + return -1; + } + return 0; +} + +static inline int CHPGetActionType(lua_State* L, int index, ActionType* action_type) +{ + *action_type = (ActionType)lua_tointeger(L, index); + if (*action_type < NO_ACTION || *action_type > MAX_ACTION_TYPE) + { + ErrorMessage("LuaDetectorApi:Incompatible CHP Action type, might be for a later version."); + return -1; + } + return 0; +} + +static inline int CHPGetActionData(lua_State* L, int index, char** action_data) +{ + // An empty string is translated into a nullptr pointer because the action data is optional + const char* tmpString = nullptr; // Lua owns this pointer + size_t action_data_size = 0; + tmpString = lua_tolstring(L, index, &action_data_size); + if (action_data_size) + *action_data = snort_strdup(tmpString); + else + *action_data = nullptr; + + return 0; +} + +static int detector_add_chp_action(UserData* ud, + AppId appIdInstance, int isKeyPattern, PatternType patternType, + size_t patternSize, char* patternData, ActionType actionType, char* optionalActionData) +{ + uint precedence; + CHPListElement* tmp_chpa, * chpa; + CHPApp* chpapp; + + //find the CHP App for this + if (!(chpapp = (decltype(chpapp))sfxhash_find((*ud)->pAppidNewConfig->CHP_glossary, + &appIdInstance))) + { + ErrorMessage( + "LuaDetectorApi:Invalid attempt to add a CHP action for unknown appId %d, instance %d. - pattern:\"%s\" - action \"%s\"", + CHP_APPIDINSTANCE_TO_ID(appIdInstance), CHP_APPIDINSTANCE_TO_INSTANCE(appIdInstance), + patternData, optionalActionData ? optionalActionData : ""); + snort_free(patternData); + if (optionalActionData) + snort_free(optionalActionData); + return 0; + } + + if (isKeyPattern) + { + chpapp->key_pattern_count++; + chpapp->key_pattern_length_sum += patternSize; + } + + if (chpapp->ptype_scan_counts[patternType] == 0) + chpapp->num_scans++; + precedence = chpapp->ptype_scan_counts[patternType]++; + // at runtime we'll want to know how many of each type of pattern we are looking for. + if (actionType == REWRITE_FIELD || actionType == INSERT_FIELD) + chpapp->ptype_rewrite_insert_used[patternType]=1; // true. + else if (actionType != ALTERNATE_APPID) + chpapp->ptype_req_counts[patternType]++; + + chpa = (CHPListElement*)snort_calloc(sizeof(CHPListElement)); + chpa->chp_action.appIdInstance = appIdInstance; + chpa->chp_action.precedence = precedence; + chpa->chp_action.key_pattern = isKeyPattern; + chpa->chp_action.ptype = patternType; + chpa->chp_action.psize = patternSize; + chpa->chp_action.pattern = patternData; + chpa->chp_action.action = actionType; + chpa->chp_action.action_data = optionalActionData; + chpa->chp_action.chpapp = chpapp; // link this struct to the Glossary entry + + AppIdConfig* pConfig = (*ud)->pAppidNewConfig; + + tmp_chpa = pConfig->httpPatternLists.chpList; + if (!tmp_chpa) + pConfig->httpPatternLists.chpList = chpa; + else + { + while (tmp_chpa->next) + tmp_chpa = tmp_chpa->next; + tmp_chpa->next = chpa; + } + + /* Set the safe-search bits in the appId entry */ + if (actionType == GET_OFFSETS_FROM_REBUILT) + { + /* This is a search engine and it is SUPPORTED for safe-search packet rewrite */ + appInfoEntryFlagSet(CHP_APPIDINSTANCE_TO_ID(appIdInstance), APPINFO_FLAG_SEARCH_ENGINE | + APPINFO_FLAG_SUPPORTED_SEARCH, pConfig); + } + else if (actionType == SEARCH_UNSUPPORTED) + { + /* This is a search engine and it is UNSUPPORTED for safe-search packet rewrite */ + appInfoEntryFlagSet(CHP_APPIDINSTANCE_TO_ID(appIdInstance), APPINFO_FLAG_SEARCH_ENGINE, + pConfig); + } + return 0; +} + +static int Detector_CHPAddAction(lua_State* L) +{ + UserData* ud; + int key_pattern; + PatternType ptype; + size_t psize; + char* pattern; + ActionType action; + char* action_data; + + AppId appIdInstance; + AppId appId; + + int index = 1; + + if (GetDetectorUserData(L, index++, &ud, + "LuaDetectorApi:Invalid HTTP detector user data in CHPAddAction.")) + return 0; + + // Parameter 1 + appId = lua_tointeger(L, index++); + appIdInstance = CHP_APPID_SINGLE_INSTANCE(appId); // Last instance for the old API + + // Parameter 2 + key_pattern = CHPGetKeyPatternBoolean(L, index++); + + // Parameter 3 + if (CHPGetPatternType(L, index++, &ptype)) + return 0; + + // Parameter 4 + if (CHPGetPatternDataAndSize(L, index++, &pattern, &psize)) + return 0; + + // Parameter 5 + if (CHPGetActionType(L, index++, &action)) + { + snort_free(pattern); + return 0; + } + + // Parameter 6 + if (CHPGetActionData(L, index++, &action_data)) + { + snort_free(pattern); + return 0; + } + + return detector_add_chp_action(ud, appIdInstance, key_pattern, ptype, + psize, pattern, action, action_data); +} + +static int Detector_CHPMultiCreateApp(lua_State* L) +{ + UserData* ud; + AppId appId; + unsigned app_type_flags; + int num_matches; + + AppId appIdInstance; + int instance; + + int index = 1; + + if (GetDetectorUserData(L, index++, &ud, + "LuaDetectorApi:Invalid HTTP detector user data in CHPMultiCreateApp.")) + return 0; + + appId = lua_tointeger(L, index++); + app_type_flags = lua_tointeger(L, index++); + num_matches = lua_tointeger(L, index++); + + for (instance=0; instance < CHP_APPID_INSTANCE_MAX; instance++ ) + { + appIdInstance = (appId << CHP_APPID_BITS_FOR_INSTANCE) + instance; + if (sfxhash_find((*ud)->pAppidNewConfig->CHP_glossary, + &appIdInstance)) + continue; + break; + } + + // We only want a maximum of these for each appId. + if (instance == CHP_APPID_INSTANCE_MAX) + { + ErrorMessage("LuaDetectorApi:Attempt to create more than %d CHP for appId %d", + CHP_APPID_INSTANCE_MAX, appId); + return 0; + } + + if (detector_create_chp_app(ud, appIdInstance, app_type_flags, num_matches)) + return 0; + + lua_pushnumber(L, appIdInstance); + return 1; +} + +static int Detector_CHPMultiAddAction(lua_State* L) +{ + UserData* ud; + int key_pattern; + PatternType ptype; + size_t psize; + char* pattern; + ActionType action; + char* action_data; + + AppId appIdInstance; + + int index = 1; + + if (GetDetectorUserData(L, index++, &ud, + "LuaDetectorApi:Invalid HTTP detector user data in CHPMultiAddAction.")) + return 0; + + // Parameter 1 + appIdInstance = lua_tointeger(L, index++); + + // Parameter 2 + key_pattern = CHPGetKeyPatternBoolean(L, index++); + + // Parameter 3 + if (CHPGetPatternType(L, index++, &ptype)) + return 0; + + // Parameter 4 + if (CHPGetPatternDataAndSize(L, index++, &pattern, &psize)) + return 0; + + // Parameter 5 + if (CHPGetActionType(L, index++, &action)) + { + snort_free(pattern); + return 0; + } + + // Parameter 6 + if (CHPGetActionData(L, index++, &action_data)) + { + snort_free(pattern); + return 0; + } + + return detector_add_chp_action(ud, appIdInstance, key_pattern, ptype, + psize, pattern, action, action_data); +} + +static int Detector_portOnlyService(lua_State* L) +{ + int index = 1; + + // Verify detector user data and that we are not in packet context + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("LuaDetectorApi:Invalid HTTP detector user data in addPortOnlyService."); + return 0; + } + + AppId appId = lua_tointeger(L, index++); + u_int16_t port = lua_tointeger(L, index++); + u_int8_t protocol = lua_tointeger(L, index++); + + if (port == 0) + ud->pAppidNewConfig->ip_protocol[protocol] = appId; + else if (protocol == 6) + ud->pAppidNewConfig->tcp_port_only[port] = appId; + else if (protocol == 17) + ud->pAppidNewConfig->udp_port_only[port] = appId; + + return 0; +} + +/* Add a length-based detector. This is done by adding a new length sequence + * to the cache. Note that this does not require a validate and is only used + * as a fallback identification. + * + * @param lua_State* - Lua state variable. + * @param appId/stack - App ID to use for this detector. + * @param proto/stack - Protocol (IPPROTO_TCP/DC.ipproto.tcp (6) or + * IPPROTO_UDP/DC.ipproto.udp (17)). + * @param sequence_cnt/stack - Number of elements in sequence below (max of + * LENGTH_SEQUENCE_CNT_MAX). + * @param sequence_str/stack - String that defines direction/length sequence. + * - Example: "I/8,R/512,I/512,R/1024,I/1024" + * - Direction: I(nitiator) or R(esponder). + * - Length : Payload size (bytes) (> 0). + * @return int - Number of elements on stack, which is always 1. + * @return status/stack - 0 if successful, -1 otherwise. + */ +static int Detector_lengthAppCacheAdd(lua_State* L) +{ + int i; + const char* str_ptr; + uint16_t length; + LengthKey length_sequence; + int index = 1; + + auto& ud = *UserData::check(L, DETECTOR, index++); + + AppId appId = lua_tonumber(L, index++); + IpProtocol proto = (IpProtocol)lua_tonumber(L, index++); + uint8_t sequence_cnt = lua_tonumber(L, index++); + const char* sequence_str = lua_tostring(L, index++); + + if (((proto != IpProtocol::TCP) && (proto != IpProtocol::UDP)) + || ((sequence_cnt == 0) || (sequence_cnt > LENGTH_SEQUENCE_CNT_MAX)) + || ((sequence_str == nullptr) || (strlen(sequence_str) == 0))) + { + ErrorMessage("LuaDetectorApi:Invalid input (%d,%u,%u,\"%s\")!", + appId, (unsigned)proto, (unsigned)sequence_cnt, sequence_str ? sequence_str : ""); + lua_pushnumber(L, -1); + return 1; + } + + memset(&length_sequence, 0, sizeof(length_sequence)); + + length_sequence.proto = proto; + length_sequence.sequence_cnt = sequence_cnt; + + str_ptr = sequence_str; + for (i = 0; i < sequence_cnt; i++) + { + int last_one; + + switch (*str_ptr) + { + case 'I': + length_sequence.sequence[i].direction = APP_ID_FROM_INITIATOR; + break; + case 'R': + length_sequence.sequence[i].direction = APP_ID_FROM_RESPONDER; + break; + default: + ErrorMessage("LuaDetectorApi:Invalid sequence string (\"%s\")!", + sequence_str); + lua_pushnumber(L, -1); + return 1; + } + str_ptr++; + + if (*str_ptr != '/') + { + ErrorMessage("LuaDetectorApi:Invalid sequence string (\"%s\")!", + sequence_str); + lua_pushnumber(L, -1); + return 1; + } + str_ptr++; + + length = (uint16_t)atoi(str_ptr); + if (length == 0) + { + ErrorMessage("LuaDetectorApi:Invalid sequence string (\"%s\")!", + sequence_str); + lua_pushnumber(L, -1); + return 1; + } + length_sequence.sequence[i].length = length; + + while ((*str_ptr != ',') && (*str_ptr != 0)) + { + str_ptr++; + } + + last_one = (i == (sequence_cnt - 1)); + if ( (!last_one && (*str_ptr != ',')) + || (last_one && (*str_ptr != 0))) + { + ErrorMessage("LuaDetectorApi:Invalid sequence string (\"%s\")!", + sequence_str); + lua_pushnumber(L, -1); + return 1; + } + str_ptr++; + } + + if (!lengthAppCacheAdd(&length_sequence, appId, ud->pAppidNewConfig)) + { + ErrorMessage("LuaDetectorApi:Could not add entry to cache!"); + lua_pushnumber(L, -1); + return 1; + } + + lua_pushnumber(L, 0); + return 1; +} + +static int Detector_AFAddApp(lua_State* L) +{ + int index = 1; + AFElement val; + + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("LuaDetectorApi:Invalid HTTP detector user data in AFAddApp."); + return 0; + } + + ApplicationId indicator = (ApplicationId)lua_tointeger(L, index++); + ApplicationId forecast = (ApplicationId)lua_tointeger(L, index++); + ApplicationId target = (ApplicationId)lua_tointeger(L, index++); + + if (sfxhash_find(ud->pAppidNewConfig->AF_indicators, &indicator)) + { + ErrorMessage("LuaDetectorApi:Attempt to add more than one AFElement per appId %d", + indicator); + return 0; + } + + val.indicator = indicator; + val.forecast = forecast; + val.target = target; + + if (sfxhash_add(ud->pAppidNewConfig->AF_indicators, &indicator, &val)) + { + ErrorMessage("LuaDetectorApi:Failed to add AFElement for appId %d", indicator); + return 0; + } + + return 0; +} + +static int Detector_addAppUrl(lua_State* L) +{ + int index = 1; + DetectorAppUrlPattern** tmp; + const char* tmpString; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("Invalid HTTP detector user data in addAppUrl."); + return 0; + } + + u_int32_t service_id = lua_tointeger(L, index++); + u_int32_t client_app = lua_tointeger(L, index++); + /*u_int32_t client_app_type =*/ lua_tointeger(L, index++); + u_int32_t payload = lua_tointeger(L, index++); + /*u_int32_t payload_type =*/ lua_tointeger(L, index++); + + if (ud->validateParams.pkt) + { + ErrorMessage( + "Invalid HTTP detector context addAppUrl: service_id %u; client_app %u; payload %u\n", + service_id, client_app, payload); + return 0; + } + + /* Verify that host pattern is a valid string */ + size_t hostPatternSize = 0; + u_int8_t* hostPattern = nullptr; + tmpString = lua_tolstring(L, index++, &hostPatternSize); + if (!tmpString || !hostPatternSize) + { + ErrorMessage("Invalid host pattern string."); + return 0; + } + else + hostPattern = (u_int8_t*)snort_strdup(tmpString); + + /* Verify that path pattern is a valid string */ + size_t pathPatternSize = 0; + u_int8_t* pathPattern = nullptr; + tmpString = lua_tolstring(L, index++, &pathPatternSize); + if (!tmpString || !pathPatternSize ) + { + ErrorMessage("Invalid path pattern string."); + snort_free(hostPattern); + return 0; + } + else + pathPattern = (u_int8_t*)snort_strdup(tmpString); + + /* Verify that scheme pattern is a valid string */ + size_t schemePatternSize; + u_int8_t* schemePattern = nullptr; + tmpString = lua_tolstring(L, index++, &schemePatternSize); + if (!tmpString || !schemePatternSize ) + { + ErrorMessage("Invalid scheme pattern string."); + snort_free(pathPattern); + snort_free(hostPattern); + return 0; + } + else + schemePattern = (u_int8_t*)snort_strdup(tmpString); + + /* Verify that query pattern is a valid string */ + size_t queryPatternSize; + u_int8_t* queryPattern = nullptr; + tmpString = lua_tolstring(L, index++, &queryPatternSize); + if (tmpString && queryPatternSize) + queryPattern = (u_int8_t*)snort_strdup(tmpString); + else + { + ErrorMessage("Invalid query pattern string."); + snort_free(hostPattern); + snort_free(pathPattern); + snort_free(schemePattern); + return 0; + } + + u_int32_t appId = lua_tointeger(L, index++); + + /* Allocate memory for data structures */ + DetectorAppUrlPattern* pattern = (DetectorAppUrlPattern*)snort_calloc( + sizeof(DetectorAppUrlPattern)); + AppIdConfig* pConfig = ud->pAppidNewConfig; + + pattern->userData.service_id = appGetAppFromServiceId(service_id, pConfig); + pattern->userData.client_app = appGetAppFromClientId(client_app, pConfig); + pattern->userData.payload = appGetAppFromPayloadId(payload, pConfig); + pattern->userData.appId = appId; + pattern->userData.query.pattern = queryPattern; + pattern->userData.query.patternSize = queryPatternSize; + pattern->patterns.host.pattern = hostPattern; + pattern->patterns.host.patternSize = (int)hostPatternSize; + pattern->patterns.path.pattern = pathPattern; + pattern->patterns.path.patternSize = (int)pathPatternSize; + pattern->patterns.scheme.pattern = schemePattern; + pattern->patterns.scheme.patternSize = (int)schemePatternSize; + + DetectorAppUrlList* urlList = &pConfig->httpPatternLists.appUrlList; + + /**first time usedCount and allocatedCount are both 0, urlPattern will be nullptr. + * This case is same as malloc. In case of error, realloc will return nullptr, and + * original urlPattern buffer is left untouched. + */ + if (urlList->usedCount == urlList->allocatedCount) + { + tmp = (decltype(tmp))realloc(urlList->urlPattern, (urlList->allocatedCount+ + URL_LIST_STEP_SIZE)* + sizeof(*tmp)); + if (!tmp) + { + FreeDetectorAppUrlPattern(pattern); + return 0; + } + urlList->urlPattern = tmp; + urlList->allocatedCount += URL_LIST_STEP_SIZE; + } + + urlList->urlPattern[urlList->usedCount++] = pattern; + + appInfoSetActive(pattern->userData.service_id, true); + appInfoSetActive(pattern->userData.client_app, true); + appInfoSetActive(pattern->userData.payload, true); + appInfoSetActive(appId, true); + + return 0; +} + +static int Detector_addRTMPUrl(lua_State* L) +{ + int index = 1; + DetectorAppUrlPattern** tmp; + const char* tmpString; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("Invalid HTTP detector user data in addRTMPUrl."); + return 0; + } + + u_int32_t service_id = lua_tointeger(L, index++); + u_int32_t client_app = lua_tointeger(L, index++); + /*u_int32_t client_app_type =*/ lua_tointeger(L, index++); + u_int32_t payload = lua_tointeger(L, index++); + /*u_int32_t payload_type =*/ lua_tointeger(L, index++); + + if (ud->validateParams.pkt) + { + ErrorMessage( + "Invalid HTTP detector context addRTMPUrl: service_id %u; client_app %u; payload %u\n", + service_id, client_app, payload); + return 0; + } + + /* Verify that host pattern is a valid string */ + size_t hostPatternSize = 0; + u_int8_t* hostPattern = nullptr; + tmpString = lua_tolstring(L, index++, &hostPatternSize); + // FIXIT - recode all this to something elegant since snort_strdup can't fail (just like Rudy) + if (!tmpString || !hostPatternSize || !(hostPattern = (u_int8_t*)snort_strdup(tmpString))) + { + ErrorMessage("Invalid host pattern string."); + return 0; + } + + /* Verify that path pattern is a valid string */ + size_t pathPatternSize = 0; + u_int8_t* pathPattern = nullptr; + tmpString = lua_tolstring(L, index++, &pathPatternSize); + // FIXIT - recode all this to something elegant since snort_strdup can't fail (just like Rudy) + if (!tmpString || !pathPatternSize || !(pathPattern = (u_int8_t*)snort_strdup(tmpString))) + { + ErrorMessage("Invalid path pattern string."); + snort_free(hostPattern); + return 0; + } + + /* Verify that scheme pattern is a valid string */ + size_t schemePatternSize; + u_int8_t* schemePattern = nullptr; + tmpString = lua_tolstring(L, index++, &schemePatternSize); + // FIXIT - recode all this to something elegant since snort_strdup can't fail (just like Rudy) + if (!tmpString || !schemePatternSize || !(schemePattern = (u_int8_t*)snort_strdup(tmpString))) + { + ErrorMessage("Invalid scheme pattern string."); + snort_free(pathPattern); + snort_free(hostPattern); + return 0; + } + + /* Verify that query pattern is a valid string */ + size_t queryPatternSize; + u_int8_t* queryPattern = nullptr; + tmpString = lua_tolstring(L, index++, &queryPatternSize); + if (tmpString && queryPatternSize) + queryPattern = (u_int8_t*)snort_strdup(tmpString); + + u_int32_t appId = lua_tointeger(L, index++); + + /* Allocate memory for data structures */ + DetectorAppUrlPattern* pattern = (DetectorAppUrlPattern*)snort_calloc( + sizeof(DetectorAppUrlPattern)); + + /* we want to put these patterns in just like for regular Urls, but we do NOT need legacy IDs for them. + * so just use the appID for service, client, or payload ID */ + pattern->userData.service_id = service_id; + pattern->userData.client_app = client_app; + pattern->userData.payload = payload; + pattern->userData.appId = appId; + pattern->userData.query.pattern = queryPattern; + pattern->userData.query.patternSize = queryPatternSize; + pattern->patterns.host.pattern = hostPattern; + pattern->patterns.host.patternSize = (int)hostPatternSize; + pattern->patterns.path.pattern = pathPattern; + pattern->patterns.path.patternSize = (int)pathPatternSize; + pattern->patterns.scheme.pattern = schemePattern; + pattern->patterns.scheme.patternSize = (int)schemePatternSize; + + AppIdConfig* pConfig = ud->pAppidNewConfig; + DetectorAppUrlList* urlList = &pConfig->httpPatternLists.RTMPUrlList; + + /**first time usedCount and allocatedCount are both 0, urlPattern will be nullptr. + * This case is same as malloc. In case of error, realloc will return nullptr, and + * original urlPattern buffer is left untouched. + */ + if (urlList->usedCount == urlList->allocatedCount) + { + tmp = (decltype(tmp))realloc(urlList->urlPattern, (urlList->allocatedCount+ + URL_LIST_STEP_SIZE)* + sizeof(*tmp)); + if (!tmp) + { + FreeDetectorAppUrlPattern(pattern); + return 0; + } + urlList->urlPattern = tmp; + urlList->allocatedCount += URL_LIST_STEP_SIZE; + } + + urlList->urlPattern[urlList->usedCount++] = pattern; + + appInfoSetActive(pattern->userData.service_id, true); + appInfoSetActive(pattern->userData.client_app, true); + appInfoSetActive(pattern->userData.payload, true); + appInfoSetActive(appId, true); + + return 0; +} + +/*Lua should inject patterns in format. */ +static int Detector_addSipUserAgent(lua_State* L) +{ + int index = 1; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + + u_int32_t client_app = lua_tointeger(L, index++); + const char* clientVersion = lua_tostring(L, index++); + if (!clientVersion ) + { + ErrorMessage("Invalid sip client version string."); + return 0; + } + + if (ud->validateParams.pkt) + { + ErrorMessage("Invalid detector context addSipUserAgent: client_app %u\n",client_app); + return 0; + } + + /* Verify that ua pattern is a valid string */ + const char* uaPattern = lua_tostring(L, index++); + if (!uaPattern) + { + ErrorMessage("Invalid sip ua pattern string."); + return 0; + } + +#ifdef REMOVED_WHILE_NOT_IN_USE + sipUaPatternAdd(client_app, clientVersion, uaPattern, + &ud->pAppidNewConfig->detectorSipConfig); +#endif + + appInfoSetActive(client_app, true); + + return 0; +} + +static int openCreateApp(lua_State* L) +{ + int index = 1; + const char* tmpString; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("Invalid HTTP detector user data in addAppUrl."); + return 0; + } + + /* Verify that host pattern is a valid string */ + size_t appNameLen = 0; + tmpString = lua_tolstring(L, index++, &appNameLen); + if (!tmpString || !appNameLen) + { + ErrorMessage("Invalid appName string."); + lua_pushnumber(L, APP_ID_NONE); + return 1; /*number of results */ + } + + AppInfoTableEntry* entry = appInfoEntryCreate(tmpString, + ud->pAppidNewConfig); + + if (entry) + { + lua_pushnumber(L, entry->appId); + return 1; /*number of results */ + } + + lua_pushnumber(L, APP_ID_NONE); + return 1; /*number of results */ +} + +static int openAddClientApp(lua_State* L) +{ + unsigned int serviceAppId, clienAppId; + auto& ud = *UserData::check(L, DETECTOR, 1); + + serviceAppId = lua_tonumber(L, 2); + clienAppId = lua_tonumber(L, 3); + + /*check inputs and whether this function is called in context of a + packet */ + if ( !ud->validateParams.pkt ) + { + lua_pushnumber(L, -1); + return 1; + } + + if (!ud->client.appModule.api) + { + lua_pushnumber(L, -1); + return 1; + } + + ud->client.appModule.api->add_app(ud->validateParams.flowp, serviceAppId, + clienAppId, ""); + + lua_pushnumber(L, 0); + return 1; +} + +/** Add service id to a flow. Positive identification by a detector. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @param serviceId/stack - id of service postively identified on this flow. + * @param vendorName/stack - name of vendor of service. This is optional. + * @param version/stack - version of service. This is optional. + * @return int - Number of elements on stack, which is always 1. + * @return int/stack - values from enum SERVICE_RETCODE + */ +static int openAddServiceApp(lua_State* L) +{ + unsigned int serviceId, retValue = SERVICE_ENULL; + auto& ud = *UserData::check(L, DETECTOR, 1); + + serviceId = lua_tonumber(L, 2); + + CHECK_INPUTS(); + + /*Phase2 - discuss RNAServiceSubtype will be maintained on lua side therefore the last + parameter on the following call is nullptr. + Subtype is not displayed on DC at present. */ + retValue = AppIdServiceAddService(ud->validateParams.flowp, ud->validateParams.pkt, + ud->validateParams.dir, ud->server.pServiceElement, + serviceId, nullptr, nullptr, nullptr); + + lua_pushnumber(L, retValue); + return 1; +} + +static int openAddPayloadApp(lua_State* L) +{ + unsigned int payloadAppId; + + auto& ud = *UserData::check(L, DETECTOR, 1); + + payloadAppId = lua_tonumber(L, 2); + + /*check inputs and whether this function is called in context of a + packet */ + if ( !ud->validateParams.pkt ) + { + lua_pushnumber(L, -1); + return 1; + } + + if (!ud->client.appModule.api) + { + lua_pushnumber(L, -1); + return 1; + } + + ud->client.appModule.api->add_payload(ud->validateParams.flowp, payloadAppId); + + lua_pushnumber(L, 0); + return 1; +} + +int openAddHttpPattern(lua_State* L) +{ + int index = 1; + AppIdConfig* pConfig; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + + pConfig = ud->pAppidNewConfig; + + /* Verify valid pattern type */ + enum httpPatternType pType = (enum httpPatternType)lua_tointeger(L, index++); + if (pType < HTTP_PAYLOAD || pType > HTTP_URL) + { + ErrorMessage("Invalid HTTP pattern type."); + return 0; + } + + /* Verify valid DHSequence */ + DHPSequence seq = (DHPSequence)lua_tointeger(L, index++); + if (seq < SINGLE || seq > USER_AGENT_HEADER) + { + ErrorMessage("Invalid HTTP DHP Sequence."); + return 0; + } + + uint32_t serviceAppId = lua_tointeger(L, index++); + uint32_t clienAppId = lua_tointeger(L, index++); + uint32_t payloadAppId = lua_tointeger(L, index++); + + if (ud->validateParams.pkt) + { + ErrorMessage( + "Invalid detector context addHttpPattern: serviceAppId %u; clienAppId %u; payloadAppId %u\n", + serviceAppId, clienAppId, payloadAppId); + return 0; + } + + /* Verify that pattern is a valid string */ + size_t pattern_size = 0; + uint8_t* pattern_str = (uint8_t*)snort_strdup(lua_tolstring(L, index++, &pattern_size)); + if (pattern_str == nullptr || pattern_size == 0) + { + ErrorMessage("Invalid HTTP pattern string."); + snort_free(pattern_str); + return 0; + } + + HTTPListElement* element = (HTTPListElement*)snort_calloc(sizeof(HTTPListElement)); + DetectorHTTPPattern* pattern = &element->detectorHTTPPattern; + pattern->seq = seq; + pattern->service_id = serviceAppId; + pattern->client_app = clienAppId; + pattern->payload = payloadAppId; + pattern->pattern = pattern_str; + pattern->pattern_size = (int)pattern_size; + pattern->appId = APP_ID_NONE; + + switch (pType) + { + case HTTP_PAYLOAD: + element->next = pConfig->httpPatternLists.hostPayloadPatternList; + pConfig->httpPatternLists.hostPayloadPatternList = element; + break; + + case HTTP_URL: + element->next = pConfig->httpPatternLists.urlPatternList; + pConfig->httpPatternLists.urlPatternList = element; + break; + + case HTTP_USER_AGENT: + element->next = pConfig->httpPatternLists.clientAgentPatternList; + pConfig->httpPatternLists.clientAgentPatternList = element; + break; + } + + appInfoSetActive(serviceAppId, true); + appInfoSetActive(clienAppId, true); + appInfoSetActive(payloadAppId, true); + + return 0; +} + +static int openAddUrlPattern(lua_State* L) +{ + int index = 1; + DetectorAppUrlPattern** tmp; + const char* tmpString; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + if ( ud->validateParams.pkt ) + { + ErrorMessage("Invalid HTTP detector user data in addAppUrl."); + return 0; + } + + AppIdConfig* pConfig = ud->pAppidNewConfig; + u_int32_t serviceAppId = lua_tointeger(L, index++); + u_int32_t clienAppId = lua_tointeger(L, index++); + u_int32_t payloadAppId = lua_tointeger(L, index++); + + if (ud->validateParams.pkt) + { + ErrorMessage( + "Invalid HTTP detector context addAppUrl: serviceAppId %u; clienAppId %u; payloadAppId %u\n", + serviceAppId, clienAppId, payloadAppId); + return 0; + } + + /* Verify that host pattern is a valid string */ + size_t hostPatternSize = 0; + u_int8_t* hostPattern = nullptr; + tmpString = lua_tolstring(L, index++, &hostPatternSize); + if (!tmpString || !hostPatternSize || !(hostPattern = (u_int8_t* )snort_strdup(tmpString))) + { + ErrorMessage("Invalid host pattern string."); + return 0; + } + + /* Verify that path pattern is a valid string */ + size_t pathPatternSize = 0; + u_int8_t* pathPattern = nullptr; + tmpString = lua_tolstring(L, index++, &pathPatternSize); + if (!tmpString || !pathPatternSize || !(pathPattern = (u_int8_t*)snort_strdup(tmpString))) + { + ErrorMessage("Invalid path pattern string."); + snort_free(hostPattern); + return 0; + } + + /* Verify that scheme pattern is a valid string */ + size_t schemePatternSize; + u_int8_t* schemePattern = nullptr; + tmpString = lua_tolstring(L, index++, &schemePatternSize); + if (!tmpString || !schemePatternSize || !(schemePattern = (u_int8_t*)snort_strdup(tmpString))) + { + ErrorMessage("Invalid scheme pattern string."); + snort_free(pathPattern); + snort_free(hostPattern); + return 0; + } + + /* Allocate memory for data structures */ + DetectorAppUrlPattern* pattern = (DetectorAppUrlPattern*)snort_calloc( + sizeof(DetectorAppUrlPattern)); + pattern->userData.service_id = serviceAppId; + pattern->userData.client_app = clienAppId; + pattern->userData.payload = payloadAppId; + pattern->userData.appId = APP_ID_NONE; + pattern->userData.query.pattern = nullptr; + pattern->userData.query.patternSize = 0; + pattern->patterns.host.pattern = hostPattern; + pattern->patterns.host.patternSize = (int)hostPatternSize; + pattern->patterns.path.pattern = pathPattern; + pattern->patterns.path.patternSize = (int)pathPatternSize; + pattern->patterns.scheme.pattern = schemePattern; + pattern->patterns.scheme.patternSize = (int)schemePatternSize; + + DetectorAppUrlList* urlList = &pConfig->httpPatternLists.appUrlList; + + /**first time usedCount and allocatedCount are both 0, urlPattern will be nullptr. + * This case is same as malloc. In case of error, realloc will return nullptr, and + * original urlPattern buffer is left untouched. + */ + if (urlList->usedCount == urlList->allocatedCount) + { + tmp = (decltype(tmp))realloc(urlList->urlPattern, + (urlList->allocatedCount + URL_LIST_STEP_SIZE) * sizeof(*tmp)); + if (!tmp) + { + FreeDetectorAppUrlPattern(pattern); + return 0; + } + urlList->urlPattern = tmp; + urlList->allocatedCount += URL_LIST_STEP_SIZE; + } + + urlList->urlPattern[urlList->usedCount++] = pattern; + + appInfoSetActive(serviceAppId, true); + appInfoSetActive(clienAppId, true); + appInfoSetActive(payloadAppId, true); + + return 0; +} + +void CleanClientPortPatternList(AppIdConfig* pConfig) +{ + PortPatternNode* tmp; + + if ( pConfig->clientPortPattern) + { + while ((tmp = pConfig->clientPortPattern->luaInjectedPatterns)) + { + pConfig->clientPortPattern->luaInjectedPatterns = tmp->next; + snort_free(tmp->pattern); + snort_free(tmp->detectorName); + snort_free(tmp); + } + + snort_free(pConfig->clientPortPattern); + } +} + +void CleanServicePortPatternList(AppIdConfig* pConfig) +{ + PortPatternNode* tmp; + + if ( pConfig->servicePortPattern) + { + while ((tmp = pConfig->servicePortPattern->luaInjectedPatterns)) + { + pConfig->servicePortPattern->luaInjectedPatterns = tmp->next; + snort_free(tmp->pattern); + snort_free(tmp->detectorName); + snort_free(tmp); + } + + snort_free(pConfig->servicePortPattern); + } +} + +/* Add a port and pattern based detection for client application. Both port and pattern criteria + * must be met before client application is deemed detected. + * + * @param lua_State* - Lua state variable. + * @param proto/stack - Protocol (IPPROTO_TCP/DC.ipproto.tcp (6) or + * IPPROTO_UDP/DC.ipproto.udp (17)). + * @param port/stack - port number to register. + * @param pattern/stack - pattern to be matched. + * @param patternLenght/stack - length of pattern + * @param offset/stack - offset into packet payload where matching should start. + * @param appId/stack - App ID to use for this detector. + * @return int - Number of elements on stack, which is always 0. + */ +static int addPortPatternClient(lua_State* L) +{ + int index = 1; + AppIdConfig* pConfig; + PortPatternNode* pPattern; + IpProtocol protocol; + uint16_t port; + const char* pattern; + size_t patternSize = 0; + unsigned position; + AppId appId; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + + pConfig = ud->pAppidNewConfig; + protocol = (IpProtocol)lua_tonumber(L, index++); + //port = lua_tonumber(L, index++); + port = 0; + pattern = lua_tolstring(L, index++, &patternSize); + position = lua_tonumber(L, index++); + appId = lua_tointeger(L, index++); + + if (!pConfig->clientPortPattern) + pConfig->clientPortPattern = + (decltype(pConfig->clientPortPattern))snort_calloc(sizeof(ClientPortPattern)); + + if (appId <= APP_ID_NONE || !pattern || !patternSize || (protocol != IpProtocol::TCP && + protocol != + IpProtocol::UDP)) + { + ErrorMessage("addPortPatternClient(): Invalid input in %s\n", + ud->name.c_str()); + return 0; + } + pPattern = (decltype(pPattern))snort_calloc(sizeof(PortPatternNode)); + pPattern->pattern = (decltype(pPattern->pattern))snort_calloc(patternSize); + pPattern->appId = appId; + pPattern->protocol = protocol; + pPattern->port = port; + memcpy(pPattern->pattern, pattern, patternSize); + pPattern->length = patternSize; + pPattern->offset = position; + pPattern->detectorName = snort_strdup(ud->name.c_str()); + + //insert ports in order. + { + PortPatternNode** prev; + PortPatternNode** curr; + + prev = nullptr; + for (curr = &pConfig->clientPortPattern->luaInjectedPatterns; + *curr; + prev = curr, curr = &((*curr)->next)) + { + if (strcmp(pPattern->detectorName, (*curr)->detectorName) || pPattern->protocol < + (*curr)->protocol + || pPattern->port < (*curr)->port) + break; + } + if (prev) + { + pPattern->next = (*prev)->next; + (*prev)->next = pPattern; + } + else + { + pPattern->next = *curr; + *curr = pPattern; + } + } + + appInfoSetActive(appId, true); + + return 0; +} + +/* Add a port and pattern based detection for service application. Both port and pattern criteria + * must be met before service application is deemed detected. + * + * @param lua_State* - Lua state variable. + * @param proto/stack - Protocol (IPPROTO_TCP/DC.ipproto.tcp (6) or + * IPPROTO_UDP/DC.ipproto.udp (17)). + * @param port/stack - port number to register. + * @param pattern/stack - pattern to be matched. + * @param patternLenght/stack - length of pattern + * @param offset/stack - offset into packet payload where matching should start. + * @param appId/stack - App ID to use for this detector. + * @return int - Number of elements on stack, which is always 0. + */ +static int addPortPatternService(lua_State* L) +{ + int index = 1; + size_t patternSize = 0; + AppIdConfig* pConfig; + PortPatternNode* pPattern; + IpProtocol protocol; + uint16_t port; + const char* pattern; + unsigned position; + AppId appId; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + + pConfig = ud->pAppidNewConfig; + protocol = (IpProtocol)lua_tonumber(L, index++); + port = lua_tonumber(L, index++); + pattern = lua_tolstring(L, index++, &patternSize); + position = lua_tonumber(L, index++); + appId = lua_tointeger(L, index++); + + if (!pConfig->servicePortPattern) + pConfig->servicePortPattern = + (decltype(pConfig->servicePortPattern))snort_calloc(sizeof(ServicePortPattern)); + + pPattern = (decltype(pPattern))snort_calloc(sizeof(PortPatternNode)); + pPattern->pattern = (decltype(pPattern->pattern))snort_calloc(patternSize); + pPattern->appId = appId; + pPattern->protocol = protocol; + pPattern->port = port; + memcpy(pPattern->pattern, pattern, patternSize); + pPattern->length = patternSize; + pPattern->offset = position; + pPattern->detectorName = snort_strdup(ud->name.c_str()); + + //insert ports in order. + { + PortPatternNode** prev; + PortPatternNode** curr; + + prev = nullptr; + for (curr = &pConfig->servicePortPattern->luaInjectedPatterns; + *curr; + prev = curr, curr = &((*curr)->next)) + { + if (strcmp(pPattern->detectorName, (*curr)->detectorName) || pPattern->protocol < + (*curr)->protocol + || pPattern->port < (*curr)->port) + break; + } + if (prev) + { + pPattern->next = (*prev)->next; + (*prev)->next = pPattern; + } + else + { + pPattern->next = *curr; + *curr = pPattern; + } + } + + appInfoSetActive(appId, true); + + return 0; +} + +/*Lua should inject patterns in format. */ +static int Detector_addSipServer(lua_State* L) +{ + int index = 1; + + /* Verify detector user data and that we are not in packet context */ + auto& ud = *UserData::check(L, DETECTOR, index++); + + u_int32_t client_app = lua_tointeger(L, index++); + const char* clientVersion = lua_tostring(L, index++); + if (!clientVersion ) + { + ErrorMessage("Invalid sip client version string."); + return 0; + } + + if (ud->validateParams.pkt) + { + ErrorMessage("Invalid detector context addSipServer: client_app %u\n",client_app); + return 0; + } + + /* Verify that ua pattern is a valid string */ + const char* uaPattern = lua_tostring(L, index++); + if (!uaPattern) + { + ErrorMessage("Invalid sip ua pattern string."); + return 0; + } + + // FIXIT - uncomment when sip detector is included in the build +#ifdef REMOVED_WHILE_NOT_IN_USE + sipServerPatternAdd(client_app, clientVersion, uaPattern, + &ud->pAppidNewConfig->detectorSipConfig); +#endif + appInfoSetActive(client_app, true); + + return 0; +} + +static inline int ConvertStringToAddress(const char* string, sfip_t* address) +{ + int af; + struct in6_addr buf; + + if (strchr(string, ':')) + af = AF_INET6; + else if (strchr(string, '.')) + af = AF_INET; + else + return 0; + + if (inet_pton(af, string, &buf)) + { + if (sfip_set_raw(address, &buf, af) != SFIP_SUCCESS) + return 0; + } + else + return 0; + + return 1; // success +} + +/**Creates a future flow based on the current flow. When the future flow is + * seen, the app ID will simply be declared with the info given here. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object. + * @param client_addr/stack - client address of the future flow + * @param client_port/stack - client port of the the future flow (can use 0 for wildcard here) + * @param server_addr/stack - server address of the future flow + * @param server_port/stack - server port of the future flow + * @param proto/stack - protocol type (see define IPPROTO_xxxx in /usr/include/netinet/in.h) + * @param service_app_id/stack - service app ID to declare for future flow (can be 0 for none) + * @param client_app_id/stack - client app ID to declare for future flow (can be 0 for none) + * @param payload_app_id/stack - payload app ID to declare for future flow (can be 0 for none) + * @param app_id_to_snort/stack - AppID's app ID entry to convert to Snort app ID (see note below) + * @return int - number of elements on stack, which is 1 if successful, 0 otherwise. + * + * Notes: For app_id_to_snort, use the app ID that AppID knows about (it'll + * probably be a repeat of one of the other 3 app IDs given here). For + * example, for "FTP Data", use 166. Internally, this'll be converted to the + * app ID that Snort recognizes ("ftp-data"). For this to really mean + * anything, the app IDs entry in appMapping.data should have a Snort app ID + * defined. + * + * Example: createFutureFlow("192.168.0.200", 0, "192.168.0.100", 20, 6, 166, 0, 0, 166) + */ +static int createFutureFlow(lua_State* L) +{ + sfip_t client_addr; + sfip_t server_addr; + IpProtocol proto; + uint16_t client_port, server_port; + char* pattern; + AppId service_app_id, client_app_id, payload_app_id, app_id_to_snort; + int16_t snort_app_id; + AppIdData* fp; + + auto& ud = *UserData::check(L, DETECTOR, 1); + + /*check inputs and whether this function is called in context of a packet */ + if ( !ud->validateParams.pkt ) + { + return 0; + } + + pattern = (char*)lua_tostring(L, 2); + if (!ConvertStringToAddress(pattern, &client_addr)) + return 0; + + client_port = lua_tonumber(L, 3); + + pattern = (char*)lua_tostring(L, 4); + if (!ConvertStringToAddress(pattern, &server_addr)) + return 0; + + server_port = lua_tonumber(L, 5); + + proto = (IpProtocol)lua_tonumber(L, 6); + + service_app_id = lua_tointeger(L, 7); + client_app_id = lua_tointeger(L, 8); + payload_app_id = lua_tointeger(L, 9); + + app_id_to_snort = lua_tointeger(L, 10); + if (app_id_to_snort > APP_ID_NONE) + { + AppInfoTableEntry* entry = appInfoEntryGet(app_id_to_snort, pAppidActiveConfig); + if (nullptr == entry) + return 0; + snort_app_id = entry->snortId; + } + else + { + snort_app_id = 0; + } + + fp = AppIdEarlySessionCreate(ud->validateParams.flowp, + ud->validateParams.pkt, + &client_addr, client_port, &server_addr, server_port, proto, + snort_app_id, + APPID_EARLY_SESSION_FLAG_FW_RULE); + if (fp) + { + fp->serviceAppId = service_app_id; + fp->ClientAppId = client_app_id; + fp->payloadAppId = payload_app_id; + setAppIdFlag(fp, APPID_SESSION_SERVICE_DETECTED | APPID_SESSION_NOT_A_SERVICE | + APPID_SESSION_PORT_SERVICE_DONE); + fp->rnaServiceState = RNA_STATE_FINISHED; + fp->rnaClientState = RNA_STATE_FINISHED; + + return 1; + } + else + return 0; +} + +static const luaL_reg Detector_methods[] = +{ + /* Obsolete API names. No longer use these! They are here for backward + * compatibility and will eventually be removed. */ + /* - "memcmp" is now "matchSimplePattern" (below) */ + { "memcmp", Detector_memcmp }, + /* - "getProtocolType" is now "getL4Protocol" (below) */ + { "getProtocolType", Detector_getProtocolType }, + /* - "inCompatibleData" is now "markIncompleteData" (below) */ + { "inCompatibleData", service_inCompatibleData }, + /* - "addDataId" is now "addAppIdDataToFlow" (below) */ + { "addDataId", service_addDataId }, + /* - "service_inCompatibleData" is now "service_markIncompleteData" (below) */ + { "service_inCompatibleData", service_inCompatibleData }, + /* - "service_addDataId" is now "service_addAppIdDataToFlow" (below) */ + { "service_addDataId", service_addDataId }, + + { "getPacketSize", Detector_getPacketSize }, + { "getPacketDir", Detector_getPacketDir }, + { "matchSimplePattern", Detector_memcmp }, + { "getPcreGroups", Detector_getPcreGroups }, + { "getL4Protocol", Detector_getProtocolType }, + { "getPktSrcAddr", Detector_getPktSrcIPAddr }, + { "getPktDstAddr", Detector_getPktDstIPAddr }, + { "getPktSrcPort", Detector_getPktSrcPort }, + { "getPktDstPort", Detector_getPktDstPort }, + { "getPktCount", Detector_getPktCount }, + { "getFlow", Detector_getFlow }, + { "htons", Detector_htons }, + { "htonl", Detector_htonl }, + { "log", Detector_logMessage }, + { "addHttpPattern", Detector_addHttpPattern }, + { "addAppUrl", Detector_addAppUrl }, + { "addRTMPUrl", Detector_addRTMPUrl }, + { "addContentTypePattern", Detector_addContentTypePattern }, + { "addSSLCertPattern", Detector_addSSLCertPattern }, + { "addSipUserAgent", Detector_addSipUserAgent }, + { "addSipServer", Detector_addSipServer }, + { "addSSLCnamePattern", Detector_addSSLCnamePattern }, + { "addHostPortApp", Detector_addHostPortApp }, + { "addDNSHostPattern", Detector_addDNSHostPattern }, + + /*Obsolete - new detectors should not use this API */ + { "init", service_init }, + { "registerPattern", service_registerPattern }, + { "getServiceID", service_getServiceId }, + { "addPort", service_addPorts }, + { "removePort", service_removePorts }, + { "setServiceName", service_setServiceName }, + { "getServiceName", service_getServiceName }, + { "isCustomDetector", service_isCustomDetector }, + { "setValidator", service_setValidator }, + { "addService", service_addService }, + { "failService", service_failService }, + { "inProcessService", service_inProcessService }, + { "markIncompleteData", service_inCompatibleData }, + { "analyzePayload", service_analyzePayload }, + { "addAppIdDataToFlow", service_addDataId }, + + /*service API */ + { "service_init", service_init }, + { "service_registerPattern", service_registerPattern }, + { "service_getServiceId", service_getServiceId }, + { "service_addPort", service_addPorts }, + { "service_removePort", service_removePorts }, + { "service_setServiceName", service_setServiceName }, + { "service_getServiceName", service_getServiceName }, + { "service_isCustomDetector", service_isCustomDetector }, + { "service_setValidator", service_setValidator }, + { "service_addService", service_addService }, + { "service_failService", service_failService }, + { "service_inProcessService", service_inProcessService }, + { "service_markIncompleteData", service_inCompatibleData }, + { "service_analyzePayload", service_analyzePayload }, + { "service_addAppIdDataToFlow", service_addDataId }, + { "service_addClient", service_addClient }, + + /*client init API */ + { "client_init", client_init }, + { "client_registerPattern", client_registerPattern }, + { "client_getServiceId", service_getServiceId }, + + /*client service API */ + { "client_addApp", client_addApp }, + { "client_addInfo", client_addInfo }, + { "client_addUser", client_addUser }, + { "client_addPayload", client_addPayload }, + + //HTTP Multi Pattern engine + { "CHPCreateApp", Detector_CHPCreateApp }, + { "CHPAddAction", Detector_CHPAddAction }, + { "CHPMultiCreateApp", Detector_CHPMultiCreateApp },// allows multiple detectors, same + // appId + { "CHPMultiAddAction", Detector_CHPMultiAddAction }, + + //App Forecasting engine + { "AFAddApp", Detector_AFAddApp }, + + { "portOnlyService", Detector_portOnlyService }, + + /* Length-based detectors. */ + { "AddLengthBasedDetector", Detector_lengthAppCacheAdd }, + + { "registerAppId", common_registerAppId }, + + { "open_createApp", openCreateApp }, + { "open_addClientApp", openAddClientApp }, + { "open_addServiceApp", openAddServiceApp }, + { "open_addPayloadApp", openAddPayloadApp }, + { "open_addHttpPattern", openAddHttpPattern }, + { "open_addUrlPattern", openAddUrlPattern }, + + { "addPortPatternClient", addPortPatternClient }, + { "addPortPatternService", addPortPatternService }, + + { "createFutureFlow", createFutureFlow }, + + { 0, 0 } +}; + +/**This function performs a clean exit on an api instance. It is called when RNA is performing + * a clean exit. + */ +void Detector_fini(void* data) +{ + lua_State* myLuaState; + Detector* detector = (Detector*)data; + + DebugFormat(DEBUG_APPID,"Finishing detector %s\n",detector->server.serviceModule.name); + + myLuaState = detector->myLuaState; + + if ( !detector->packageInfo.server.cleanFunctionName.empty() && lua_checkstack(myLuaState, 1)) + { + lua_getglobal(myLuaState, detector->packageInfo.server.cleanFunctionName.c_str()); + + if (lua_pcall(myLuaState, 0, 0, 0)) + { + /*See comment at first lua_pcall() */ + ErrorMessage("%s: error running %s in lua: %s", detector->server.serviceModule.name, + detector->packageInfo.server.cleanFunctionName.c_str(), lua_tostring(myLuaState, + -1)); + } + } + else if ( !detector->packageInfo.client.cleanFunctionName.empty() && lua_checkstack(myLuaState, + 1)) + { + lua_getglobal(myLuaState, detector->packageInfo.client.cleanFunctionName.c_str()); + + if (lua_pcall(myLuaState, 0, 0, 0)) + { + /*See comment at first lua_pcall() */ + ErrorMessage("%s: error running %s in lua: %s", detector->server.serviceModule.name, + detector->packageInfo.client.cleanFunctionName.c_str(), lua_tostring(myLuaState, + -1)); + } + } + else + { + ErrorMessage("%s: DetectorFini not provided\n", detector->name.c_str()); + } + + freeDetector(detector); + + /*lua_close will perform garbage collection after killing lua script. */ + /**Design: Lua_state does not allow me to store user variables so detectors store lua_state. + * There is one lua_state for each lua file, which can have only one + * detectors. So if lua detector creates a detector, registers a pattern + * and then loses reference then lua will garbage collect but we should not free the buffer. + * + */ + lua_close(myLuaState); +} + +/**Garbage collector hook function. Called when Lua side garbage collects detector api instance. Current design is to allocate + * one of each luaState, detector and detectorUserData buffers, and hold these buffers till RNA exits. SigHups processing + * reuses the buffers and calls DetectorInit to reinitialize. RNA ensures that UserData is not garbage collected, by + * creating a reference in LUA_REGISTRY table. The reference is released only on RNA exit. + * + * If in future, one needs to free any of these buffers then one should consider references to detector buffer in RNAServiceElement + * stored in flows and hostServices data structures. Other detectors at this time create one static instance for the lifetime of RNA, + * and therefore we have adopted the same principle for Lua Detecotors. + */ +static int Detector_gc(lua_State*) +{ + return 0; +} + +/*convert detector to string for printing */ +static int Detector_tostring(lua_State* L) +{ + lua_pushfstring(L, "Detector (%p)", UserData::check(L, DETECTOR, 1)); + return 1; +} + +static const luaL_reg Detector_meta[] = +{ + { "__gc", Detector_gc }, // FIXIT-M J As of right now, Detector_gc is a no-op + { "__tostring", Detector_tostring }, + { 0, 0 } +}; + +/**Registers C functions as an API, enabling Lua detector to call these functions. This function + * should be called once before loading any lua detectors. This function itself is not part of API + * and therefore can not be called by a Lua detection. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return methodArray/stack - array of newly created methods + */ +int Detector_register(lua_State* L) +{ + /* populates a new table with Detector_methods (method_table), add the table to the globals and + stack*/ + luaL_openlib(L, DETECTOR, Detector_methods, 0); + + /* create metatable for Foo, add it to the Lua registry, metatable on stack */ + luaL_newmetatable(L, DETECTOR); + + /* populates table on stack with Detector_meta methods, puts the metatable on stack*/ + luaL_openlib(L, nullptr, Detector_meta, 0); + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, -3); /* dup methods table*/ + lua_settable(L, -3); /* metatable.__index = methods */ + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, -3); /* dup methods table*/ + lua_settable(L, -3); /* hide metatable: + metatable.__metatable = methods */ + lua_pop(L, 1); /* drop metatable */ + return 1; /* return methods on the stack */ +} + +/** @} */ /* end of LuaDetectorBaseApi */ + +static void FreeHTTPListElement(HTTPListElement* element) +{ + if (element) + { + if (element->detectorHTTPPattern.pattern) + snort_free(element->detectorHTTPPattern.pattern); + snort_free(element); + } +} + +static void FreeCHPAppListElement(CHPListElement* element) +{ + if (element) + { + if (element->chp_action.pattern) + snort_free(element->chp_action.pattern); + if (element->chp_action.action_data) + snort_free(element->chp_action.action_data); + free (element); + } +} + +static void FreeDetectorAppUrlPattern(DetectorAppUrlPattern* pattern) +{ + if (pattern) + { + if (pattern->userData.query.pattern) + snort_free(*(void**)&pattern->userData.query.pattern); + if (pattern->patterns.host.pattern) + snort_free(*(void**)&pattern->patterns.host.pattern); + if (pattern->patterns.path.pattern) + snort_free(*(void**)&pattern->patterns.path.pattern); + if (pattern->patterns.scheme.pattern) + snort_free(*(void**)&pattern->patterns.scheme.pattern); + // FIXIT - pattern still allocated with calloc/realloc + free(pattern); + } +} + +void CleanHttpPatternLists(AppIdConfig* pConfig) +{ + HTTPListElement* element; + CHPListElement* chpe; + size_t i; + + for (i = 0; i < pConfig->httpPatternLists.appUrlList.usedCount; i++) + { + FreeDetectorAppUrlPattern(pConfig->httpPatternLists.appUrlList.urlPattern[i]); + pConfig->httpPatternLists.appUrlList.urlPattern[i] = nullptr; + } + for (i = 0; i < pConfig->httpPatternLists.RTMPUrlList.usedCount; i++) + { + FreeDetectorAppUrlPattern(pConfig->httpPatternLists.RTMPUrlList.urlPattern[i]); + pConfig->httpPatternLists.RTMPUrlList.urlPattern[i] = nullptr; + } + if (pConfig->httpPatternLists.appUrlList.urlPattern) + { + snort_free(pConfig->httpPatternLists.appUrlList.urlPattern); + pConfig->httpPatternLists.appUrlList.urlPattern = nullptr; + } + pConfig->httpPatternLists.appUrlList.allocatedCount = 0; + if (pConfig->httpPatternLists.RTMPUrlList.urlPattern) + { + snort_free(pConfig->httpPatternLists.RTMPUrlList.urlPattern); + pConfig->httpPatternLists.RTMPUrlList.urlPattern = nullptr; + } + pConfig->httpPatternLists.RTMPUrlList.allocatedCount = 0; + pConfig->httpPatternLists.appUrlList.usedCount = 0; + pConfig->httpPatternLists.RTMPUrlList.usedCount = 0; + while ((element = pConfig->httpPatternLists.clientAgentPatternList)) + { + pConfig->httpPatternLists.clientAgentPatternList = element->next; + FreeHTTPListElement(element); + } + while ((element = pConfig->httpPatternLists.hostPayloadPatternList)) + { + pConfig->httpPatternLists.hostPayloadPatternList = element->next; + FreeHTTPListElement(element); + } + while ((element = pConfig->httpPatternLists.urlPatternList)) + { + pConfig->httpPatternLists.urlPatternList = element->next; + FreeHTTPListElement(element); + } + while ((element = pConfig->httpPatternLists.contentTypePatternList)) + { + pConfig->httpPatternLists.contentTypePatternList = element->next; + FreeHTTPListElement(element); + } + while ((chpe = pConfig->httpPatternLists.chpList)) + { + pConfig->httpPatternLists.chpList = chpe->next; + FreeCHPAppListElement(chpe); + } +} + +// ----------------------------------------------------------------------------- +// Detector +// ----------------------------------------------------------------------------- + +Detector::~Detector() +{ + if ( server.pServiceElement ) + delete server.pServiceElement; + + // release the reference of the userdata on the lua side + if ( detectorUserDataRef != LUA_REFNIL ) + luaL_unref(myLuaState, LUA_REGISTRYINDEX, detectorUserDataRef); + + delete[] validatorBuffer; +} + diff --git a/src/network_inspectors/appid/lua_detector_api.h b/src/network_inspectors/appid/lua_detector_api.h new file mode 100644 index 000000000..b84fc7ff5 --- /dev/null +++ b/src/network_inspectors/appid/lua_detector_api.h @@ -0,0 +1,164 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lua_detector_api.h author Sourcefire Inc. + +#ifndef LUA_DETECTOR_API_H +#define LUA_DETECTOR_API_H + +// This module supports basic API towards Lua detectors. + +#include +#include + +#include "client_plugins/client_app_api.h" +#include "service_plugins/service_api.h" + +struct Packet; +struct ProfileStats; +struct ServiceValidationArgs; +struct lua_State; +class AppIdConfig; +class AppIdData; +struct RNAServiceElement; + +struct DetectorPackageInfo +{ + struct UniInfo + { + std::string initFunctionName = "DetectorInit"; // client init function + std::string cleanFunctionName = "DetectorClean"; // client clean function + std::string validateFunctionName = "DetectorValidate"; // client validate function + int minimum_matches = 0; + }; + + std::string name = "NoName"; + IpProtocol proto; + + UniInfo client; + UniInfo server; +}; + +struct Detector +{ + ~Detector(); + + /**Identifies customer created detectors using SDL. */ + bool isCustom; + bool isActive; + bool wasActive; + + struct + { + const uint8_t* data; + uint16_t size; + int dir; + AppIdData* flowp; + Packet* pkt; + uint8_t macAddress[6]; + } validateParams; + + /**Pointer to flow created by a validator. + */ + AppIdData* pFlow; + + struct + { + unsigned int serviceId; + + /**present only for server detectors*/ + RNAServiceValidationModule serviceModule; + + /**calloced buffer to satisfy internal flow API. + */ + RNAServiceElement* pServiceElement; + } server; + + /**constructed from packageInfo read from lua detector directly. Present + * only for client detectors. + */ + struct + { + /**application fingerprint id.*/ + unsigned int appFpId; + + /**Client Application Module. */ + RNAClientAppModule appModule; + } client; + + lua_State* myLuaState; + + /**Reference to lua userdata. This is a key into LUA_REGISTRYINDEX */ + int detectorUserDataRef; + + std::string name; // lua file name is used as detector name + + /**Package information retrieved from detector lua file. + */ + DetectorPackageInfo packageInfo; + + unsigned detector_version; + char* validatorBuffer; + unsigned char digest[16]; + + AppIdConfig* pAppidActiveConfig; ///< AppId context in which this detector should be used; + // used during packet processing + AppIdConfig* pAppidOldConfig; ///< AppId context in which this detector should be + // cleaned; used at reload free and exit + AppIdConfig* pAppidNewConfig; ///< AppId context in which this detector should be + // loaded; used at initialization and reload + + /**Snort profiling stats for individual Lua detector.*/ + ProfileStats* pPerfStats; +}; + +int Detector_register(lua_State*); +void Detector_fini(void* detector); +void detectorRemoveAllPorts(Detector*, AppIdConfig*); +Detector* createDetector(lua_State*, const char* filename); +CLIENT_APP_RETCODE validateAnyClientApp( + const uint8_t* data, + uint16_t size, + const int dir, + AppIdData*, + Packet*, + Detector*, + const AppIdConfig* +); + +enum httpPatternType +{ + HTTP_PAYLOAD = 1, + HTTP_USER_AGENT = 2, + HTTP_URL = 3 +}; + +int Detector_addSSLCertPattern(lua_State*); +int Detector_addDNSHostPattern(lua_State*); + +int Detector_addHttpPattern(lua_State*); + +void CleanHttpPatternLists(AppIdConfig*); +void CleanClientPortPatternList(AppIdConfig*); +void CleanServicePortPatternList(AppIdConfig*); + +int validateAnyService(ServiceValidationArgs*); +int checkServiceElement(Detector*); + +#endif diff --git a/src/network_inspectors/appid/lua_detector_flow_api.cc b/src/network_inspectors/appid/lua_detector_flow_api.cc new file mode 100644 index 000000000..22c4efc01 --- /dev/null +++ b/src/network_inspectors/appid/lua_detector_flow_api.cc @@ -0,0 +1,481 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lua_detector_flow_api.cc author Sourcefire Inc. + +#include "lua_detector_flow_api.h" + +#include + +#include "appid_api.h" +#include "log/messages.h" +#include "lua_detector_api.h" +#include "lua_detector_util.h" +#include "lua_detector_module.h" +#include "main/snort_debug.h" +#include "sfip/sf_ip.h" +#include "util/common_util.h" + +/*static const char * LuaLogLabel = "luaDetectorFlowApi"; */ + +#define DETECTOR "Detector" +#define DETECTORFLOW "DetectorFlow" + +#if 1 // FIXIT-M hacks +#endif + +/* Lua flag bit/index to C flag value (0 for invalid). */ +static const uint64_t FLAGS_TABLE_LUA_TO_C[32] +{ + 0, /* 0 */ + 0, /* 1 */ + 0, /* 2 */ + 0, /* 3 */ + 0, /* 4 */ + 0, /* 5 */ + 0, /* 6 */ + 0, /* 7 */ + 0, /* 8 */ + 0, /* 9 */ + 0, /* 10 */ + 0, /* 11 */ + 0, /* 12 */ + 0, /* 13 */ + 0, /* 14 */ + 0, /* 15 */ + 0, /* 16 */ + 0, /* 17 */ + 0, /* 18 */ + 0, /* 19 */ + 0, /* 20 */ + 0, /* 21 */ + APPID_SESSION_UDP_REVERSED, /* 22: udpReversed */ + APPID_SESSION_INCOMPATIBLE, /* 23: incompatible */ + APPID_SESSION_IGNORE_HOST, /* 24: ignoreHost */ + 0, /* 25: ignoreTcpSeq -- OBSOLETE */ + APPID_SESSION_CLIENT_DETECTED, /* 26: ClientAppDetected */ + 0, /* 27: gotBanner -- OBSOLETE */ + APPID_SESSION_NOT_A_SERVICE, /* 28: notAService */ + 0, /* 29: logUnknown -- OBSOLETE */ + APPID_SESSION_CONTINUE, /* 30: continue */ + APPID_SESSION_SERVICE_DETECTED /* 31: serviceDetected */ +}; + +/* C flag bit/index to Lua flag value (0 for invalid). */ +static const uint64_t FLAGS_TABLE_C_TO_LUA[32] +{ + 0, /* 0 */ + 0, /* 1 */ + 0, /* 2 */ + 0, /* 3 */ + 0, /* 4 */ + 0, /* 5 */ + 0, /* 6 */ + 0, /* 7 */ + 0, /* 8 */ + 0, /* 9 */ + 0, /* 10 */ + 0, /* 11 */ + 0x00400000, /* 12: APPID_SESSION_UDP_REVERSED */ + 0, /* 13 */ + 0x80000000, /* 14: APPID_SESSION_SERVICE_DETECTED */ + 0x04000000, /* 15: APPID_SESSION_CLIENT_DETECTED */ + 0x10000000, /* 16: APPID_SESSION_NOT_A_SERVICE */ + 0, /* 17 */ + 0, /* 18 */ + 0x40000000, /* 19: APPID_SESSION_CONTINUE */ + 0x01000000, /* 20: APPID_SESSION_IGNORE_HOST */ + 0x00800000, /* 21: APPID_SESSION_INCOMPATIBLE */ + 0, /* 22 */ + 0, /* 23 */ + 0, /* 24 */ + 0, /* 25 */ + 0, /* 26 */ + 0, /* 27 */ + 0, /* 28 */ + 0, /* 29 */ + 0, /* 30 */ + 0 /* 31 */ +}; + +/* Convert flag bits used by the Lua code into what the C code uses. */ +static inline uint64_t ConvertFlagsLuaToC(uint64_t in) +{ + uint64_t out = 0; + unsigned i; + uint64_t msk; + + msk = 1; + for (i = 0; i < 32; i++) + { + if (in & msk) + out |= FLAGS_TABLE_LUA_TO_C[i]; + msk <<= 1; + } + + return out; +} + +/* Convert flag bits used by the C code into what the Lua code uses. */ +static inline uint64_t ConvertFlagsCToLua(uint64_t in) +{ + uint64_t out = 0; + unsigned i; + uint64_t msk; + + msk = 1; + for (i = 0; i < 32; i++) + { + if (in & msk) + out |= FLAGS_TABLE_C_TO_LUA[i]; + msk <<= 1; + } + + return out; +} + +/**Creates a user data for a flow. + * + * @param Lua_State* - Lua state variable. + * @param detector/stack - detector object + * @param srcAddress/stack - source address of the flow + * @param srcPort/stack - source port of the the flow + * @param dstAddress/stack - destination address of the flow. + * @param dstPort/stack - detector port of the flow. + * @param proto/stack - protocol type. See defined IPPROTO_xxxx in /usr/include/netinet/in.h + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return UserData/stack - A userdata representing UserData. + */ +static int DetectorFlow_new(lua_State* L) +{ + sfip_t saddr; + sfip_t daddr; + IpProtocol proto; + uint16_t sport, dport; + char* pattern; + size_t patternLen; + + auto& detector_ud = *UserData::check(L, DETECTOR, 1); + + /*check inputs and whether this function is called in context of a packet */ + if ( !detector_ud->validateParams.pkt ) + return 0; /*number of results */ + + pattern = (char*)lua_tostring(L, 2); + patternLen = lua_strlen (L, 2); + + if (patternLen == 16) + { + if (sfip_set_raw(&saddr, pattern, AF_INET6) != SFIP_SUCCESS) + return 0; + } + else if (patternLen == 4) + { + if (sfip_set_raw(&saddr, pattern, AF_INET) != SFIP_SUCCESS) + return 0; + } + else + { + return 0; + } + pattern = (char*)lua_tostring(L, 3); + patternLen = lua_strlen (L, 3); + + if (patternLen == 16) + { + if (sfip_set_raw(&daddr, pattern, AF_INET6) != SFIP_SUCCESS) + return 0; + } + else if (patternLen == 4) + { + if (sfip_set_raw(&daddr, pattern, AF_INET) != SFIP_SUCCESS) + return 0; + } + else + { + return 0; + } + + sport = lua_tonumber(L, 4); + dport = lua_tonumber(L, 5); + proto = (IpProtocol)lua_tonumber(L, 6); + + auto detector_flow = new DetectorFlow(); + UserData::push(L, DETECTORFLOW, detector_flow); + + detector_flow->myLuaState = L; + lua_pushvalue(L, -1); + detector_flow->userDataRef = luaL_ref(L, LUA_REGISTRYINDEX); + + sflist_add_tail(&allocatedFlowList, detector_flow); + + detector_flow->pFlow = AppIdEarlySessionCreate((AppIdData*)detector_flow, + detector_ud->validateParams.pkt, + &saddr, sport, &daddr, dport, proto, 0, 0); + + if (!detector_flow->pFlow) + { + /*calloced buffer will be freed later after the current packet is processed. */ + lua_pop(L, 1); + return 0; + } + + return 1; +} + +/**free DetectorFlow and its corresponding user data. + */ +void freeDetectorFlow(void* userdata) +{ + DetectorFlow* pDetectorFlow = (DetectorFlow*)userdata; + + /*The detectorUserData itself is a userdata and therefore be freed by Lua side. */ + if (pDetectorFlow->userDataRef != LUA_REFNIL) + { + auto L = pDetectorFlow->myLuaState; + luaL_unref(L, LUA_REGISTRYINDEX, pDetectorFlow->userDataRef); + pDetectorFlow->userDataRef = LUA_REFNIL; + } + + delete pDetectorFlow; +} + +/**Sets a flow flag. + * + * @param Lua_State* - Lua state variable. + * @param detectorFlow/stack - UserData object + * @param flags/stack - flags to be set. + * @return int - Number of elements on stack, which is 0 + */ +static int DetectorFlow_setFlowFlag( + lua_State* L + ) +{ + uint64_t flags; + + auto& pLuaData = *UserData::check(L, DETECTORFLOW, 1); + assert(pLuaData.ptr); + + flags = lua_tonumber(L, 2); + flags = ConvertFlagsLuaToC(flags); + + setAppIdFlag(pLuaData->pFlow, flags); + + return 0; +} + +/**Gets a flow flag value. + * + * @param Lua_State* - Lua state variable. + * @param detectorFlow/stack - UserData object + * @param flags/stack - flags to get. + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return flagValue/stack - value of a given flag. + */ +static int DetectorFlow_getFlowFlag( + lua_State* L + ) +{ + uint64_t flags; + uint64_t ret; + + auto& pLuaData = *UserData::check(L, DETECTORFLOW, 1); + assert(pLuaData.ptr); + + flags = lua_tonumber(L, 2); + flags = ConvertFlagsLuaToC(flags); + + ret = getAppIdFlag(pLuaData->pFlow, flags); + ret = ConvertFlagsCToLua(ret); + lua_pushnumber(L, ret); + + return 1; +} + +/**Clear a flow flag. + * + * @param Lua_State* - Lua state variable. + * @param detectorFlow/stack - UserData object + * @param flags/stack - flags to be cleared. + * @return int - Number of elements on stack, which is 0. + */ +static int DetectorFlow_clearFlowFlag( + lua_State* L + ) +{ + uint64_t flags; + + auto& pLuaData = *UserData::check(L, DETECTORFLOW, 1); + assert(pLuaData.ptr); + + flags = lua_tonumber(L, 2); + flags = ConvertFlagsLuaToC(flags); + + clearAppIdFlag(pLuaData->pFlow, flags); + + return 0; +} + +/**Set service id on a flow. + * + * @param Lua_State* - Lua state variable. + * @param detectorFlow/stack - UserData object + * @param serviceId/stack - service Id to be set on a flow. + * @return int - Number of elements on stack, which is 0. + */ +static int DetectorFlow_setFlowServiceId(lua_State*) +{ return 0; } + +/**Set client application id on a flow. + * + * @param Lua_State* - Lua state variable. + * @param detectorFlow/stack - UserData object + * @param applId/stack - client application Id to be set on a flow. + * @return int - Number of elements on stack, which is 0. + */ +static int DetectorFlow_setFlowClnAppId( + lua_State* + ) +{ + return 0; +} + +/**Set client application type id on a flow. + * + * @param Lua_State* - Lua state variable. + * @param detectorFlow/stack - UserData object + * @param applTypeId/stack - client application type id to be set on a flow. + * @return int - Number of elements on stack, which is 0. + */ +static int DetectorFlow_setFlowClnAppType( + lua_State* + ) +{ + return 0; +} + +/**Design: For simplicity reason I am passing flowkey (20 bytes) to lua detectors. + * The key is used to index into local lua table and get any flow specific data that a detector needs. + * This approach avoids embedding lua detector data into core engine flow data structure. + * + * For optimization, I could have created an integer index on C side. This can be taken up in future. + */ + +/**Get flow key from a UserData object. + * + * @param Lua_State* - Lua state variable. + * @param detectorflow/stack - UserData object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return flowKey/stack - A 20 byte flow key + */ +static int DetectorFlow_getFlowKey( + lua_State* L + ) +{ + auto& pLuaData = *UserData::check(L, DETECTORFLOW, 1); + assert(pLuaData.ptr); + + lua_pushlstring(L, (char*)&pLuaData->pFlow->id, + sizeof(pLuaData->pFlow->id)); + + return 1; +} + +static const luaL_reg DetectorFlow_methods[] = +{ + /* Obsolete API names. No longer use these! They are here for backward + * compatibility and will eventually be removed. */ + /* - "new" is now "createFlow" (below) */ + { "new", DetectorFlow_new }, + + { "createFlow", DetectorFlow_new }, + { "setFlowFlag", DetectorFlow_setFlowFlag }, + { "getFlowFlag", DetectorFlow_getFlowFlag }, + { "clearFlowFlag", DetectorFlow_clearFlowFlag }, + { "setFlowServiceId", DetectorFlow_setFlowServiceId }, + { "setFlowClnAppId", DetectorFlow_setFlowClnAppId }, + { "setFlowClnAppType", DetectorFlow_setFlowClnAppType }, + { "getFlowKey", DetectorFlow_getFlowKey }, + { 0, 0 } +}; + +/** + * lua_close will ensure that all detectors and flows get _gc called. + */ +static int DetectorFlow_gc( + lua_State* + ) +{ + return 0; +} + +static int DetectorFlow_tostring( + lua_State* L + ) +{ + char buff[32]; + snprintf(buff, sizeof(buff), "%p", (void*)UserData::check(L, DETECTORFLOW, 1)); + lua_pushfstring(L, "UserData (%s)", buff); + return 1; +} + +static const luaL_reg DetectorFlow_meta[] = +{ + { "__gc", DetectorFlow_gc }, + { "__tostring", DetectorFlow_tostring }, + { 0, 0 } +}; + +/**Registers C functions as an API, enabling Lua detector to call these functions. This function + * should be called once before loading any lua detectors. This function itself is not part of API + * and therefore can not be called by a Lua detection. + * + * @param Lua_State* - Lua state variable. + * @param detectorFlow/stack - UserData object + * @return int - Number of elements on stack, which is 1 if successful, 0 otherwise. + * @return methodArray/stack - array of newly created methods + */ +int DetectorFlow_register( + lua_State* L + ) +{ + /* populates a new table with Detector_methods (method_table), add the table to the globals and + stack*/ + luaL_openlib(L, DETECTORFLOW, DetectorFlow_methods, 0); + + /* create metatable for Foo, add it to the Lua registry, metatable on stack */ + luaL_newmetatable(L, DETECTORFLOW); + + /* populates table on stack with Detector_meta methods, puts the metatable on stack*/ + luaL_openlib(L, nullptr, DetectorFlow_meta, 0); + + lua_pushliteral(L, "__index"); + lua_pushvalue(L, -3); /* dup methods table*/ + lua_settable(L, -3); /* metatable.__index = methods */ + + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, -3); /* dup methods table*/ + lua_settable(L, -3); /* hide metatable: + metatable.__metatable = methods */ + lua_pop(L, 1); /* drop metatable */ + return 1; /* return methods on the stack */ +} + +/** @} */ /* end of LuaDetectorFlowApi */ + diff --git a/src/network_inspectors/appid/lua_detector_flow_api.h b/src/network_inspectors/appid/lua_detector_flow_api.h new file mode 100644 index 000000000..32af90f6f --- /dev/null +++ b/src/network_inspectors/appid/lua_detector_flow_api.h @@ -0,0 +1,42 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lua_detector_flow_api.h author Sourcefire Inc. + +#ifndef LUA_DETECTOR_FLOW_API_H +#define LUA_DETECTOR_FLOW_API_H + +// This module supports API towards Lua detectors for performing specific operations on a flow object. +// The flow object on Lua side is a userData. + +struct lua_State; +class AppIdData; + +struct DetectorFlow +{ + // FIXIT-H J why is the lua state and user data ref on this object? + lua_State* myLuaState; + AppIdData* pFlow; + int userDataRef; +}; + +int DetectorFlow_register(lua_State*); +void freeDetectorFlow(void* userdata); + +#endif diff --git a/src/network_inspectors/appid/lua_detector_module.cc b/src/network_inspectors/appid/lua_detector_module.cc new file mode 100644 index 000000000..f62f61dfc --- /dev/null +++ b/src/network_inspectors/appid/lua_detector_module.cc @@ -0,0 +1,961 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lua_detector_module.cc author Sourcefire Inc. + +// supporting Lua detectors in core engine. + +#include "lua_detector_module.h" + +#include +#include +#include +#include +#include + +#include "appid_config.h" +#include "client_plugins/client_app_base.h" +#include "fw_appid.h" // for lua*PerfStats +#include "hash/sfghash.h" +#include "log/messages.h" +#include "lua/lua.h" +#include "lua_detector_api.h" +#include "lua_detector_flow_api.h" +#include "main/snort_debug.h" +#include "utils/util.h" + +#define MD5CONTEXT MD5_CTX + +#define MD5INIT MD5_Init +#define MD5UPDATE MD5_Update +#define MD5FINAL MD5_Final +#define MD5DIGEST MD5 + +#define MAXPD 1024 +#define LUA_DETECTOR_FILENAME_MAX 1024 + +// This data structure is shared in the main and the reload threads. However, the detectors +// in this list could be using different AppID contexts (pAppidOldConfig, pAppidActiveConfig +// and pAppidActiveConfig) based on which context the detector is being used. For example, +// a detector could simultaneously be loaded in the reload thread while the same detector +// could be used in the packet processing thread. Since allocatedDetectorList is used only +// during loading, we don't need to use synchronization measures to access it. +static std::list allocatedDetectorList; + +SF_LIST allocatedFlowList; /*list of flows allocated. */ +static uint32_t gLuaTrackerSize = 0; +static unsigned gNumDetectors = 0; +static unsigned gNumActiveDetectors; + +inline bool match_char_set(char c, const char* set) +{ + while ( *set && *set != c ) + ++set; + + return *set != '\0'; +} + +inline const char* find_first_not_of(const char* s, const char* const set) +{ + while ( *s && match_char_set(*s, set) ) + ++s; + + return s; +} + +inline const char* find_first_of(const char* s, const char* const set) +{ + while ( *s && !match_char_set(*s, set) ) + ++s; + + return s; +} + +const char* tokenize(const char* const delim, const char*& save, size_t& len) +{ + if ( !save || !*save ) + return nullptr; + + save = find_first_not_of(save, delim); + + if ( !*save ) + return nullptr; + + const char* end = find_first_of(save, delim); + + const char* tmp = save; + + len = end - save; + save = end; + + return tmp; +} + +static inline bool get_lua_ns(lua_State* L, const char* const ns) +{ + const char* save = ns; + size_t len = 0; + + lua_pushvalue(L, LUA_GLOBALSINDEX); + + while ( const char* s = tokenize(". ", save, len) ) + { + if ( !lua_istable(L, -1) ) + return false; + + lua_pushlstring(L, s, len); + lua_gettable(L, -2); + } + + return true; +} + +static inline bool get_lua_field( + lua_State* L, int table, const char* field, std::string& out) +{ + lua_getfield(L, table, field); + bool result = lua_isstring(L, -1); + if ( result ) + out = lua_tostring(L, -1); + + lua_pop(L, 1); + return result; +} + +static inline bool get_lua_field( + lua_State* L, int table, const char* field, int& out) +{ + lua_getfield(L, table, field); + bool result = lua_isnumber(L, -1); + if ( result ) + out = lua_tointeger(L, -1); + + lua_pop(L, 1); + return result; +} + +static inline bool get_lua_field( + lua_State* L, int table, const char* field, IpProtocol& out) +{ + lua_getfield(L, table, field); + bool result = lua_isnumber(L, -1); + if ( result ) + out = (IpProtocol)lua_tointeger(L, -1); + + lua_pop(L, 1); + return result; +} + +static lua_State* createLuaState() +{ + // FIXIT-H J should obtain lua states from lua state factory + auto L = luaL_newstate(); + luaL_openlibs(L); + +// FIXIT-M J this is stupid, remove it +#ifdef HAVE_LIBLUAJIT + /*linked in during compilation */ + luaopen_jit(myLuaState); + + { + static unsigned once = 0; + if (!once) + { + lua_getfield(myLuaState, LUA_REGISTRYINDEX, "_LOADED"); + lua_getfield(myLuaState, -1, "jit"); /* Get jit.* module table. */ + lua_getfield (myLuaState, -1, "version"); + if (lua_isstring(myLuaState, -1)) + DEBUG_WRAP(DebugMessage(DEBUG_APPID, "LuaJIT: Version %s\n", lua_tostring( + myLuaState, -1)); ); + lua_pop(myLuaState, 1); + once = 1; + } + } + +#endif /*HAVE_LIBLUAJIT */ + + Detector_register(L); + // After detector register the methods are still on the stack, remove them + lua_pop(L, 1); + + DetectorFlow_register(L); + lua_pop(L, 1); + +#ifdef REMOVED_WHILE_NOT_IN_USE + /*The garbage-collector pause controls how long the collector waits before + starting a new cycle. Larger values make the collector less aggressive. + Values smaller than 100 mean the collector will not wait to start a new + cycle. A value of 200 means that the collector waits for the total memory + in use to double before starting a new cycle. */ + + lua_gc(myLuaState, LUA_GCSETPAUSE, 100); + + /*The step multiplier controls the relative speed of the collector relative + to memory allocation. Larger values make the collector more aggressive + but also increase the size of each incremental step. Values smaller than + 100 make the collector too slow and can result in the collector never + finishing a cycle. The default, 200, means that the collector runs at + "twice" the speed of memory allocation. */ + + lua_gc(myLuaState, LUA_GCSETSTEPMUL, 200); +#endif + + // set lua library paths + char extra_path_buffer[PATH_MAX]; + + snprintf( + extra_path_buffer, PATH_MAX-1, "%s/odp/libs/?.lua;%s/custom/libs/?.lua", + pAppidActiveConfig->mod_config->app_detector_dir, + pAppidActiveConfig->mod_config->app_detector_dir); + + const int save_top = lua_gettop(L); + if ( get_lua_ns(L, "package.path") ) + { + lua_pushstring(L, extra_path_buffer); + lua_concat(L, 2); + lua_setfield(L, -2, "path"); + } + else + ErrorMessage("Could not set lua package.path\n"); + + lua_settop(L, save_top); + + return L; +} + +#ifdef REMOVED_WHILE_NOT_IN_USE +static void getDetectorPackageInfo(lua_State* L, Detector* detector, int fillDefaults) +{ + tDetectorPackageInfo* pkg = &detector->packageInfo; + lua_getglobal (L, "DetectorPackageInfo"); + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + + if (fillDefaults) + { + /*set default values first */ + pkg->name = snort_strdup("NoName"); + pkg->server.initFunctionName = snort_strdup("DetectorInit"); + pkg->server.cleanFunctionName = snort_strdup("DetectorClean"); + pkg->server.validateFunctionName = snort_strdup("DetectorValidate"); + if (!pkg->name || !pkg->server.initFunctionName || !pkg->server.cleanFunctionName || + !pkg->server.validateFunctionName) + _dpd.errMsg("failed to allocate package"); + } + return; + } + + /* Get all the variables */ + lua_getfield(L, -1, "name"); /* string */ + if (lua_isstring(L, -1)) + { + pkg->name = snort_strdup(lua_tostring(L, -1)); + if (!pkg->name) + _dpd.errMsg("failed to allocate package name"); + } + else if (fillDefaults) + { + pkg->name = snort_strdup("NoName"); + if (!pkg->name) + _dpd.errMsg("failed to allocate package name"); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "proto"); /* integer? */ + if (lua_isnumber(L, -1)) + { + pkg->proto = lua_tointeger(L, -1); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "client"); + if (lua_istable(L, -1)) + { + lua_getfield(L, -1, "init"); /* string*/ + if (lua_isstring(L, -1)) + { + pkg->client.initFunctionName = snort_strdup(lua_tostring(L, -1)); + if (!pkg->client.initFunctionName) + _dpd.errMsg("failed to allocate client init function name"); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "clean"); /* string*/ + if (lua_isstring(L, -1)) + { + pkg->client.cleanFunctionName = snort_strdup(lua_tostring(L, -1)); + if (!pkg->client.cleanFunctionName) + lua_getfield(L, -1, "validate"); /* string*/ + if (lua_isstring(L, -1)) + { + pkg->client.validateFunctionName = snort_strdup(lua_tostring(L, -1)); + if (!pkg->client.validateFunctionName) + _dpd.errMsg("failed to allocate client validate function name"); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "minimum_matches"); /* integer*/ + if (lua_isnumber(L, -1)) + { + pkg->client.minMatches = lua_tointeger(L, -1); + } + lua_pop(L, 1); + } + lua_pop(L, 1); /*pop client table */ + + lua_getfield(L, -1, "server"); + if (lua_istable(L, -1)) + { + lua_getfield(L, -1, "init"); /* string*/ + if (lua_isstring(L, -1)) + { + pkg->server.initFunctionName = snort_strdup(lua_tostring(L, -1)); + if (!pkg->server.initFunctionName) + _dpd.errMsg("failed to allocate server init function name"); + } + else if (fillDefaults) + { + pkg->server.initFunctionName = snort_strdup("DetectorInit"); + if (!pkg->server.initFunctionName) + _dpd.errMsg("failed to allocate server init function name"); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "clean"); /* string*/ + if (lua_isstring(L, -1)) + { + pkg->server.cleanFunctionName = snort_strdup(lua_tostring(L, -1)); + if (!pkg->server.cleanFunctionName) + _dpd.errMsg("failed to allocate server clean function name"); + } + else if (fillDefaults) + { + pkg->server.cleanFunctionName = snort_strdup("DetectorClean"); + if (!pkg->server.cleanFunctionName) + _dpd.errMsg("failed to allocate server clean function name"); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "validate"); /* string*/ + if (lua_isstring(L, -1)) + { + pkg->server.validateFunctionName = snort_strdup(lua_tostring(L, -1)); + if (!pkg->server.validateFunctionName) + _dpd.errMsg("failed to allocate server validate function name"); + } + else if (fillDefaults) + { + pkg->server.validateFunctionName = snort_strdup("DetectorValidate"); + if (!pkg->server.validateFunctionName) + _dpd.errMsg("failed to allocate server validate function name"); + } + lua_pop(L, 1); + } + lua_pop(L, 1); /*pop server table */ + + lua_pop(L, 1); /*pop DetectorPackageInfo table */ + } +} + +#endif + +// fetch or create packageInfo defined inside lua detector +static void getDetectorPackageInfo(Detector* detector) +{ + auto L = detector->myLuaState; + Lua::ManageStack mgr(L); + + auto& pkg = detector->packageInfo; + lua_getglobal(L, "DetectorPackageInfo"); + + // use defaults + if ( lua_isnil(L, -1) ) + return; + + // get name + get_lua_field(L, -1, "name", pkg.name); + + // get proto + if ( !get_lua_field(L, -1, "proto", pkg.proto) ) + { + // FIXIT-M J error messages should use source info + ErrorMessage("DetectorPackageInfo field 'proto' is not a number\n"); + } + + // get client + lua_getfield(L, -1, "client"); + if ( !lua_istable(L, -1) ) + { + // FIXIT-M J error messages should use source info + ErrorMessage("DetectorPackageInfo field 'client' is not a table\n"); + } + else + { + get_lua_field(L, -1, "init", pkg.client.initFunctionName); + get_lua_field(L, -1, "clean", pkg.client.cleanFunctionName); + get_lua_field(L, -1, "validate", pkg.client.validateFunctionName); + get_lua_field(L, -1, "minimum_matches", pkg.client.minimum_matches); + } + + // pop client table + lua_pop(L, 1); + + // get server + lua_getfield(L, -1, "server"); + if ( !lua_istable(L, -1) ) + { + // FIXIT-M J error messages should use source info + ErrorMessage("DetectorPackageInfo field 'server' is not a table\n"); + } + else + { + get_lua_field(L, -1, "init", pkg.server.initFunctionName); + get_lua_field(L, -1, "clean", pkg.server.cleanFunctionName); + get_lua_field(L, -1, "validate", pkg.server.validateFunctionName); + get_lua_field(L, -1, "minimum_matches", pkg.server.minimum_matches); + } +} + +/**Calls DetectorInit function inside lua detector. + * Calls initialization function as defined in packageInfo, which reads either user defined name + * or DetectorInit symbol. Pushes detectorUserData on stack as input parameter and the calls the + * function. Notice * that on error, lua_state is not closed. This keeps faulty detectors around + * without using it, but it keeps wrapping functions simpler. + */ +static void luaServerInit(Detector* detector) +{ + const auto& name = detector->name; + auto L = detector->myLuaState; + const auto& server = detector->packageInfo.server; + + if ( server.initFunctionName.empty() ) + { + ErrorMessage("Detector %s: DetectorInit() is not provided for server\n", name.c_str()); + return; + } + + lua_getglobal(L, server.initFunctionName.c_str()); + + if (!lua_isfunction(L, -1)) + { + ErrorMessage("Detector %s: does not contain DetectorInit() function\n", name.c_str()); + return; + } + + /*first parameter is DetectorUserData */ + lua_rawgeti(L, LUA_REGISTRYINDEX, detector->detectorUserDataRef); + + if ( lua_pcall(L, 1, 1, 0) ) + { + ErrorMessage("error loading lua Detector %s, error %s\n", + name.c_str(), lua_tostring(L, -1)); + return; + } + else + { + if ( detector->server.pServiceElement ) + detector->server.pServiceElement->ref_count = 1; + + DebugFormat(DEBUG_APPID, "Initialized %s\n", name.c_str()); + } +} + +/**Calls init function inside lua detector. + * Calls initialization function as defined in packageInfo. Pushes detectorUserData on stack + * as input parameter and the calls the function. Notice * that on error, lua_state is not + * closed. This keeps faulty detectors around without using it, but it keeps wrapping functions + * simpler. + */ +static void luaClientInit(Detector* detector) +{ + auto L = detector->myLuaState; + const auto& client = detector->packageInfo.client; + + assert(!client.initFunctionName.empty()); + + lua_getglobal(L, client.initFunctionName.c_str()); + if (!lua_isfunction(L, -1)) + { + ErrorMessage("Detector %s: does not contain DetectorInit() function\n", + detector->name.c_str()); + return; + } + + /*first parameter is DetectorUserData */ + lua_rawgeti(L, LUA_REGISTRYINDEX, detector->detectorUserDataRef); + + /*second parameter is a table containing configuration stuff. */ + // ... which is empty.??? + lua_newtable(L); + + if ( lua_pcall(L, 2, 1, 0) ) + { + // FIXIT shouldn't this be using detector->name? + ErrorMessage("Could not initialize the %s client app element: %s\n", + detector->name.c_str(), lua_tostring(L, -1)); + return; + } + else + { + DebugFormat(DEBUG_APPID, "Initialized %s\n", detector->name.c_str()); + } +} + +static void luaClientFini(Detector* detector) +{ + auto L = detector->myLuaState; + const auto& client = detector->packageInfo.client; + + assert(!client.cleanFunctionName.empty() ); + + lua_getglobal(L, client.cleanFunctionName.c_str()); + if (!lua_isfunction(L, -1)) + { + ErrorMessage("Detector %s: does not contain DetectorFini() function\n", + detector->name.c_str()); + return; + } + + /*first parameter is DetectorUserData */ + lua_rawgeti(L, LUA_REGISTRYINDEX, detector->detectorUserDataRef); + + if ( lua_pcall(L, 1, 1, 0) ) + { + ErrorMessage("Could not cleanup the %s client app element: %s\n", + detector->name.c_str(), lua_tostring(L, -1)); + } +} + +/**set tracker sizes on Lua detector sizes. Uses global module names to access functions. + */ +static inline void setLuaTrackerSize(lua_State* L, uint32_t numTrackers) +{ + /*change flow tracker size according to available memory calculation */ + lua_getglobal(L, "hosServiceTrackerModule"); + if (lua_istable(L, -1)) + { + lua_getfield(L, -1, "setHosServiceTrackerSize"); + if (lua_isfunction(L, -1)) + { + lua_pushinteger (L, numTrackers); + if (lua_pcall(L, 1, 0, 0) != 0) + { + ErrorMessage("error setting tracker size"); + } + } + } + else + { +#ifdef LUA_DETECTOR_DEBUG + DebugFormat(DEBUG_LOG, "hosServiceTrackerModule.setHosServiceTrackerSize not found"); +#endif + } + lua_pop(L, 1); + + /*change flow tracker size according to available memory calculation */ + lua_getglobal(L, "flowTrackerModule"); + if (lua_istable(L, -1)) + { + lua_getfield(L, -1, "setFlowTrackerSize"); + if (lua_isfunction(L, -1)) + { + lua_pushinteger (L, numTrackers); + if (lua_pcall(L, 1, 0, 0) != 0) + { + ErrorMessage("error setting tracker size"); + } + } + } + else + { +#ifdef LUA_DETECTOR_DEBUG + DebugFormat(DEBUG_LOG, "flowTrackerModule.setFlowTrackerSize not found"); +#endif + } + lua_pop(L, 1); +} + +static void luaCustomLoad( char* detectorName, char* validator, unsigned int validatorLen, + unsigned char* const digest, AppIdConfig* pConfig, bool isCustom) +{ + Detector* detector; + RNAClientAppModule* cam = nullptr; + + lua_State* L = createLuaState(); + if ( !L ) + { + ErrorMessage("can not create new luaState"); + snort_free(validator); + return; + } + + if ( luaL_loadbuffer(L, validator, validatorLen, "") || + lua_pcall(L, 0, 0, 0) ) + { + ErrorMessage("cannot run validator %s, error: %s\n", + detectorName, lua_tostring(L, -1)); + + lua_close(L); + snort_free(validator); + + return; + } + + detector = createDetector(L, detectorName); + if ( !detector ) + { + ErrorMessage("cannot allocate detector %s\n", detectorName); + lua_close(L); + snort_free(validator); + + return; + } + + getDetectorPackageInfo(detector); + detector->validatorBuffer = validator; + detector->isActive = true; + detector->pAppidNewConfig = detector->pAppidActiveConfig = detector->pAppidOldConfig = pConfig; + detector->isCustom = isCustom; + + if ( detector->packageInfo.server.initFunctionName.empty() ) + { + assert(false); // FIXIT-H J cam is null at this point so... WOMP + detector->client.appFpId = APP_ID_UNKNOWN; + cam = &detector->client.appModule; + // cam->name = detector->packageInfo.name; + cam->proto = detector->packageInfo.proto; + cam->validate = validateAnyClientApp; + cam->minimum_matches = detector->packageInfo.client.minimum_matches; + cam->userData = detector; + cam->api = getClientApi(); + } + else + { + /*add to active service list */ + detector->server.serviceModule.next = pConfig->serviceConfig.active_service_list; + pConfig->serviceConfig.active_service_list = &detector->server.serviceModule; + + detector->server.serviceId = APP_ID_UNKNOWN; + + /*create a ServiceElement */ + if (checkServiceElement(detector)) + { + detector->server.pServiceElement->validate = validateAnyService; + detector->server.pServiceElement->userdata = detector; + + detector->server.pServiceElement->detectorType = DETECTOR_TYPE_DECODER; + } + } + + memcpy(detector->digest, digest, sizeof(detector->digest)); + allocatedDetectorList.push_front(detector); + gNumDetectors++; + + DebugFormat(DEBUG_LOG,"Loaded detector %s\n", detectorName); +} + +void LuaDetectorModuleManager::luaModuleInit() +{ + sflist_init(&allocatedFlowList); + allocatedDetectorList.clear(); +} + +/**calculates Number of flow and host tracker entries for Lua detectors, given amount + * of memory allocated to RNA (fraction of total system memory) and number of detectors + * loaded in database. Calculations are based on CAICCI detector and observing memory + * consumption per tracker. + * @param rnaMemory - total memory RNA is allowed to use. This is calculated as a fraction of + * total system memory. + * @param numDetectors - number of lua detectors present in database. + */ +#define LUA_TRACKERS_MAX 10000 +#define LUA_TRACKER_AVG_MEM_BYTES 740 + +static inline uint32_t calculateLuaTrackerSize(u_int64_t rnaMemory, uint32_t numDetectors) +{ + u_int64_t detectorMemory = (rnaMemory/8); + unsigned numTrackers; + if (!numDetectors) + numDetectors = 1; + numTrackers = (detectorMemory/LUA_TRACKER_AVG_MEM_BYTES)/numDetectors; + return (numTrackers > LUA_TRACKERS_MAX) ? LUA_TRACKERS_MAX : numTrackers; +} + +static void loadCustomLuaModules(char* path, AppIdConfig* pConfig, bool isCustom) +{ + unsigned n; + FILE* file; + char pattern[PATH_MAX]; + snprintf(pattern, sizeof(pattern), "%s/*", path); + + glob_t globs; + memset(&globs, 0, sizeof(globs)); + int rval = glob(pattern, 0, nullptr, &globs); + if (rval != 0 && rval != GLOB_NOMATCH) + { + ErrorMessage("Unable to read directory '%s'\n",pattern); + return; + } + + // Open each RNA detector file and gather detector information from it + for (n = 0; n < globs.gl_pathc; n++) + { + unsigned char digest[16]; + MD5CONTEXT context; + char detectorName[LUA_DETECTOR_FILENAME_MAX]; + char* basename; + + basename = strrchr(globs.gl_pathv[n], '/'); + if (!basename) + { + basename = globs.gl_pathv[n]; + } + basename++; + + snprintf(detectorName, LUA_DETECTOR_FILENAME_MAX, "%s_%s", (isCustom ? "custom" : "cisco"), + basename); + + if ((file = fopen(globs.gl_pathv[n], "r")) == nullptr) + { + ErrorMessage("Unable to read lua detector '%s'\n",globs.gl_pathv[n]); + continue; + } + + /*Load lua file as a detector. */ + if (fseek(file, 0, SEEK_END)) + { + ErrorMessage("Unable to seek lua detector '%s'\n",globs.gl_pathv[n]); + continue; + } + + auto validatorBufferLen = ftell(file); + + if (validatorBufferLen == -1) + { + ErrorMessage("Unable to return offset on lua detector '%s'\n",globs.gl_pathv[n]); + continue; + } + if (fseek(file, 0, SEEK_SET)) + { + ErrorMessage("Unable to seek lua detector '%s'\n",globs.gl_pathv[n]); + continue; + } + + auto validatorBuffer = new uint8_t[validatorBufferLen + 1](); + + if (fread(validatorBuffer, validatorBufferLen, 1, file) == 0) + { + ErrorMessage("Failed to read lua detector %s\n",globs.gl_pathv[n]); + delete[] validatorBuffer; + continue; + } + + validatorBuffer[validatorBufferLen] = '\0'; + + MD5INIT(&context); + MD5UPDATE(&context, validatorBuffer, validatorBufferLen); + MD5FINAL(digest, &context); + + // FIXIT-H J this finds the wrong detector -- it should be find_last_of + auto it = std::find_if( + allocatedDetectorList.begin(), + allocatedDetectorList.end(), + [&detectorName](const Detector* d) { + return d->name == detectorName; + }); + + if ( it != allocatedDetectorList.end() ) + { + Detector* detector = *it; + if ( !memcmp(digest, detector->digest, sizeof(digest)) ) + { + detector->isActive = true; + detector->pAppidNewConfig = pConfig; + delete[] validatorBuffer; + } + } + + luaCustomLoad(detectorName, (char*)validatorBuffer, validatorBufferLen, digest, pConfig, + isCustom); + } + + globfree(&globs); +} + +void LuaDetectorModuleManager::FinalizeLuaModules(AppIdConfig* pConfig) +{ + gNumActiveDetectors = 0; + + for ( auto& detector : allocatedDetectorList ) + { + detector->pAppidOldConfig = detector->pAppidActiveConfig; + detector->pAppidActiveConfig = pConfig; + if ( detector->isActive ) + { + ++gNumActiveDetectors; + + if ( detector->server.pServiceElement ) + detector->server.pServiceElement->current_ref_count = + detector->server.pServiceElement->ref_count; + } + } + + luaDetectorsSetTrackerSize(); +} + +void LuaDetectorModuleManager::LoadLuaModules(AppIdConfig* pConfig) +{ + for ( auto& detector : allocatedDetectorList ) + { + detector->wasActive = detector->isActive; + detector->isActive = 0; + + if ( detector->server.pServiceElement ) + detector->server.pServiceElement->ref_count = 0; + } + + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "%s/odp/lua", pAppidActiveConfig->mod_config->app_detector_dir); + loadCustomLuaModules(path, pConfig, 0); + snprintf(path, sizeof(path), "%s/custom/lua", + pAppidActiveConfig->mod_config->app_detector_dir); + loadCustomLuaModules(path, pConfig, 1); + // luaDetectorsCleanInactive(); +} + +void luaDetectorsUnload(AppIdConfig* pConfig) +{ + for ( auto& detector : allocatedDetectorList ) + { + if ( detector->isActive && !detector->packageInfo.server.initFunctionName.empty()) + detectorRemoveAllPorts(detector, pConfig); + + if ( detector->isActive && !detector->packageInfo.client.initFunctionName.empty() ) + luaClientFini(detector); + + detector->isActive = false; + + if (detector->server.pServiceElement) + detector->server.pServiceElement->ref_count = 0; + } + + gNumActiveDetectors = 0; +} + +void luaDetectorsSetTrackerSize() +{ + gLuaTrackerSize = calculateLuaTrackerSize(512*1024*1024, gNumActiveDetectors); + + DebugFormat(DEBUG_APPID, " Setting tracker size to %u\n", gLuaTrackerSize); + + for ( auto& detector : allocatedDetectorList ) + { + if ( detector->isActive ) + setLuaTrackerSize(detector->myLuaState, gLuaTrackerSize); + } +} + +void LuaDetectorModuleManager::UnloadLuaModules(AppIdConfig*) +{ + for ( auto& detector : allocatedDetectorList ) + { + if ( detector->wasActive ) + { + if ( detector->client.appFpId ) + luaClientFini(detector); + + detector->wasActive = false; + } + + // Detector cleanup is done. Move pAppidOldConfig to the current + // AppID context. + detector->pAppidOldConfig = detector->pAppidActiveConfig; + } +} + +/**Reconfigure all Lua modules. + * Iterates over all Lua detectors in system and reconfigures them. This + * will however not read rna_csd_validator_map table again to check for + * newly activated or deactivate detectors. Current design calls for restarting + * RNA whenever detectors are activated/deactivated. + */ +void luaModuleInitAllServices() +{ + for ( auto& detector : allocatedDetectorList ) + luaServerInit(detector); +} + +/**Reconfigure all Lua modules. + * Iterates over all Lua detectors in system and reconfigures them. This + * will however not read rna_csd_validator_map table again to check for + * newly activated or deactivate detectors. Current design calls for restarting + * RNA whenever detectors are activated/deactivated. + */ +void luaModuleInitAllClients() +{ + for ( auto& detector : allocatedDetectorList ) + if ( detector->isActive && !detector->packageInfo.client.initFunctionName.empty() ) + luaClientInit(detector); +} + +void luaModuleCleanAllClients() +{ + for ( auto& detector : allocatedDetectorList ) + if ( !detector->packageInfo.client.initFunctionName.empty() ) + luaClientFini(detector); + + /*dont free detector. Lua side reclaims the memory. */ +} + +/**Finish routine for DetectorCore module. It release all Lua sessions and frees any memory. + * @warn This function should be called once and that too when RNA is performing clean exit. + * @return void. + */ +void LuaDetectorModuleManager::luaModuleFini() +{ + DebugMessage(DEBUG_APPID, "luaModuleFini(): entered"); + + /*flow can be freed during garbage collection */ + + sflist_static_free_all(&allocatedFlowList, freeDetectorFlow); + allocatedDetectorList.clear(); +} + +void RNAPndDumpLuaStats() +{ + size_t totalMem = 0; + size_t mem; + + if ( allocatedDetectorList.empty() ) + return; + + LogMessage("Lua detector Stats"); + + for ( auto& detector : allocatedDetectorList ) + { + mem = lua_gc(detector->myLuaState, LUA_GCCOUNT, 0); + totalMem += mem; + LogMessage(" Detector %s: Lua Memory usage %zu kb", detector->name.c_str(), mem); + } + + LogMessage("Lua Stats total memory usage %zu kb", totalMem); +} + diff --git a/src/network_inspectors/appid/lua_detector_module.h b/src/network_inspectors/appid/lua_detector_module.h new file mode 100644 index 000000000..ed46c893e --- /dev/null +++ b/src/network_inspectors/appid/lua_detector_module.h @@ -0,0 +1,70 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lua_detector_module.h author Sourcefire Inc. + +#ifndef LUA_DETECTOR_MODULE_H +#define LUA_DETECTOR_MODULE_H + +#include + +#include "utils/sflsq.h" + +class AppIdConfig; + +class LuaDetectorModuleManager +{ +public: + // Initializes Lua modules. Open lua and if available LuaJIT libraries, and registers all API modules. + static void luaModuleInit(); + static void luaModuleFini(); + + // Load all Lua modules into a detector list + // + // Each RNA detector file in the folder app_id_detector_path is parsed for + // detector information. If it is a valid detector, a detector data structure + // is created for it and stored in allocatedDetectorList. + static void LoadLuaModules(AppIdConfig*); + + // Finalize Lua modules + // This function should be called after LoadLuaModules(). It sets up proper AppId references + // and tracker size for all the detectors. + static void FinalizeLuaModules(AppIdConfig*); + + // Unload Lua modules + // + // This function cleans up all the data structures that were created for the Lua detectors + // in a given AppId context. It should be called after FinalizeLuaModules(). + static void UnloadLuaModules(AppIdConfig*); + + static void add_chunk(const std::string&); +}; + +void luaModuleInitAllServices(); +void luaModuleCleanAllClients(); +void luaModuleInitAllClients(); +void RNAPndDumpLuaStats(); + +void luaDetectorsUnload(AppIdConfig*); +void luaDetectorsSetTrackerSize(); + +extern SF_LIST allocatedFlowList; + +#endif + diff --git a/src/network_inspectors/appid/lua_detector_util.h b/src/network_inspectors/appid/lua_detector_util.h new file mode 100644 index 000000000..caafe6db8 --- /dev/null +++ b/src/network_inspectors/appid/lua_detector_util.h @@ -0,0 +1,72 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// lua_detector_util.h author Joel Cornett + +#ifndef LUA_DETECTOR_UTIL_H +#define LUA_DETECTOR_UTIL_H + +// encapsulate Lua interface boilerplate to get sane, identical behavior across users + +#include + +#include + +template +struct UserData +{ + T* ptr; + + T* operator->() + { return ptr; } + + const T* operator->() const + { return ptr; } + + operator T*() const + { return ptr; } + + operator T*() + { return ptr; } + + static UserData* push(lua_State* L, const char* const meta, T* ptr = nullptr) + { + auto ud = static_cast*>(lua_newuserdata(L, sizeof(UserData))); + assert(ud); + + ud->ptr = ptr; + + luaL_getmetatable(L, meta); + // metatable should already be in registry at this point + assert(lua_istable(L, -1)); + + lua_setmetatable(L, -2); + return ud; + } + + static UserData* check(lua_State* L, const char* const meta, int n) + { + luaL_checktype(L, n, LUA_TUSERDATA); + auto ud = static_cast*>(luaL_checkudata(L, n, meta)); + assert(ud); + return ud; + } +}; + +#endif diff --git a/src/network_inspectors/appid/service_plugins/CMakeLists.txt b/src/network_inspectors/appid/service_plugins/CMakeLists.txt new file mode 100644 index 000000000..691f6f471 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/CMakeLists.txt @@ -0,0 +1,81 @@ + +set ( SERVICE_PLUGINS_SOURCES + dcerpc.cc + dcerpc.h + service_api.h + service_base.cc + service_base.h + service_battle_field.cc + service_battle_field.h + service_bgp.cc + service_bgp.h + service_bit.cc + service_bootp.cc + service_bootp.h + service_config.h + service_dcerpc.cc + service_dcerpc.h + service_direct_connect.cc + service_direct_connect.h + service_flap.cc + service_flap.h + service_ftp.cc + service_ftp.h + service_irc.cc + service_irc.h + service_lpr.cc + service_lpr.h + service_mdns.cc + service_mdns.h + service_mysql.cc + service_mysql.h + service_netbios.cc + service_netbios.h + service_nntp.cc + service_nntp.h + service_ntp.cc + service_ntp.h + service_radius.cc + service_radius.h + service_rexec.cc + service_rexec.h + service_rfb.cc + service_rfb.h + service_rlogin.cc + service_rlogin.h + service_rpc.cc + service_rpc.h + service_rshell.cc + service_rshell.h + service_rsync.cc + service_rsync.h + service_rtmp.cc + service_rtmp.h + service_smtp.cc + service_smtp.h + service_snmp.cc + service_snmp.h + service_ssh.cc + service_ssh.h + service_ssl.cc + service_ssl.h + service_telnet.cc + service_telnet.h + service_tftp.cc + service_tftp.h + service_timbuktu.cc + service_tns.cc + service_util.h + ) + +add_library ( appid_service_plugins STATIC + ${SERVICE_PLUGINS_SOURCES} +) + +target_include_directories ( appid_service_plugins PRIVATE ${APPID_INCLUDE_DIR} ) + +# FIXIT-H: Add unit tests + +#install (FILES ${SERVICE_PLUGINS_INCLUDES} +# DESTINATION "${INCLUDE_INSTALL_PATH}/appid/service_plugins" +#) diff --git a/src/network_inspectors/appid/service_plugins/Makefile.am b/src/network_inspectors/appid/service_plugins/Makefile.am new file mode 100644 index 000000000..e7497a135 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/Makefile.am @@ -0,0 +1,78 @@ + +AM_CPPFLAGS+=-I$(top_srcdir)/src/network_inspectors/appid + +file_list = \ +dcerpc.cc \ +dcerpc.h \ +service_api.h \ +service_base.cc \ +service_base.h \ +service_battle_field.cc \ +service_battle_field.h \ +service_bgp.cc \ +service_bgp.h \ +service_bit.cc \ +service_bootp.cc \ +service_bootp.h \ +service_config.h \ +service_dcerpc.cc \ +service_dcerpc.h \ +service_direct_connect.cc \ +service_direct_connect.h \ +service_flap.cc \ +service_flap.h \ +service_ftp.cc \ +service_ftp.h \ +service_irc.cc \ +service_irc.h \ +service_lpr.cc \ +service_lpr.h \ +service_mdns.cc \ +service_mdns.h \ +service_mysql.cc \ +service_mysql.h \ +service_netbios.cc \ +service_netbios.h \ +service_nntp.cc \ +service_nntp.h \ +service_ntp.cc \ +service_ntp.h \ +service_radius.cc \ +service_radius.h \ +service_rexec.cc \ +service_rexec.h \ +service_rfb.cc \ +service_rfb.h \ +service_rlogin.cc \ +service_rlogin.h \ +service_rpc.cc \ +service_rpc.h \ +service_rshell.cc \ +service_rshell.h \ +service_rsync.cc \ +service_rsync.h \ +service_rtmp.cc \ +service_rtmp.h \ +service_smtp.cc \ +service_smtp.h \ +service_snmp.cc \ +service_snmp.h \ +service_ssh.cc \ +service_ssh.h \ +service_ssl.cc \ +service_ssl.h \ +service_telnet.cc \ +service_telnet.h \ +service_tftp.cc \ +service_tftp.h \ +service_timbuktu.cc \ +service_tns.cc \ +service_util.h + +noinst_LIBRARIES = libappid_service_plugins.a +libappid_service_plugins_a_SOURCES = $(file_list) + +# Uncomment once tests are ready. +#if BUILD_UNIT_TESTS +#SUBDIRS = test +#endif diff --git a/src/network_inspectors/appid/service_plugins/dcerpc.cc b/src/network_inspectors/appid/service_plugins/dcerpc.cc new file mode 100644 index 000000000..1f6f373ae --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/dcerpc.cc @@ -0,0 +1,74 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// dcerpc.cc author Sourcefire Inc. + +#include "dcerpc.h" + +#include + +#define min(x,y) ((x)<(y) ? (x) : (y)) + +#define DCERPC_LE_FLAG 0x10 + +#pragma pack(1) + +struct DCERPCHeader +{ + uint8_t version; + uint8_t minor_version; + uint8_t type; + uint8_t flags; + uint8_t drep[4]; + uint16_t frag_length; + uint16_t auth_length; + uint32_t id; +}; + +#pragma pack() + +int dcerpc_validate(const uint8_t* data, int size) +{ + DCERPCHeader* hdr; + uint16_t len; + + if (size < (int)sizeof(DCERPCHeader)) + return -1; + hdr = (DCERPCHeader*)data; + if (hdr->version != 5) + return -1; + if (hdr->minor_version > 1) + return -1; + if (hdr->type > 19) + return -1; + if (hdr->drep[0] & DCERPC_LE_FLAG) + { + len = hdr->frag_length; + } + else + { + len = ntohs(hdr->frag_length); + } + if (len < sizeof(DCERPCHeader)) + return -1; + if (size < len) + return -1; + return (int)len; +} + diff --git a/src/network_inspectors/appid/service_plugins/dcerpc.h b/src/network_inspectors/appid/service_plugins/dcerpc.h new file mode 100644 index 000000000..89c77632b --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/dcerpc.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// dcerpc.h author Sourcefire Inc. + +#ifndef DCERPC_H +#define DCERPC_H + +#include + +int dcerpc_validate(const uint8_t* data, int size); + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_api.h b/src/network_inspectors/appid/service_plugins/service_api.h new file mode 100644 index 000000000..50363d779 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_api.h @@ -0,0 +1,213 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_api.h author Sourcefire Inc. + +#ifndef SERVICE_API_H +#define SERVICE_API_H + +#include "appid_flow_data.h" + +class AppIdConfig; +class AppIdData; +struct Detector; +struct RNAServiceSubtype; +struct Packet; +struct DynamicPreprocessorData; + +enum SERVICE_RETCODE +{ + SERVICE_SUCCESS = 0, + SERVICE_INPROCESS = 10, + SERVICE_NEED_REASSEMBLY = 11, + SERVICE_NOT_COMPATIBLE = 12, + SERVICE_INVALID_CLIENT = 13, + SERVICE_REVERSED = 14, + SERVICE_NOMATCH = 100, + SERVICE_ENULL = -10, + SERVICE_EINVALID = -11, + SERVICE_ENOMEM = -12 +}; + +struct ServiceValidationArgs +{ + const uint8_t* data; + uint16_t size; + int dir; + AppIdData* flowp; + Packet* pkt; + struct Detector* userdata; + const AppIdConfig* pConfig; + bool app_id_debug_session_flag; + char* app_id_debug_session; +}; + +using RNAServiceValidationFCN = int(*)(ServiceValidationArgs*); + +#define MakeRNAServiceValidationPrototype(name) static int name(ServiceValidationArgs* args) + +struct CleanServiceAPI +{ + AppIdConfig* pAppidConfig; ///< AppId context for which this API should be used +}; + +struct IniServiceAPI; +using RNAServiceValidationInitFCN = int(*)(const IniServiceAPI* const); +using RNAServiceValidationCleanFCN = void(*)(const CleanServiceAPI* const); + +struct RNAServiceValidationPort; +struct RNAServiceValidationModule; +struct IniServiceAPI +{ + void (* RegisterPattern)( + RNAServiceValidationFCN, IpProtocol proto, const uint8_t* pattern, + unsigned size, int position, const char* name, AppIdConfig*); + + int (* AddPort)( + RNAServiceValidationPort*, RNAServiceValidationModule*, AppIdConfig*); + + void (* RemovePorts)(RNAServiceValidationFCN, AppIdConfig*); + void (* RegisterPatternUser)( + RNAServiceValidationFCN, IpProtocol proto, const uint8_t* pattern, + unsigned size, int position, const char* name, AppIdConfig*); + + void (* RegisterAppId)( + RNAServiceValidationFCN, AppId, uint32_t additionalInfo, AppIdConfig*); + + int debug; + uint32_t instance_id; + AppIdConfig* pAppidConfig; ///< AppId context for which this API should be used +}; + +struct RNAServicePerf +{ + /*time to validate */ + uint64_t totalValidateTime; +}; + +struct RNAServiceElement +{ + RNAServiceElement* next; + RNAServiceValidationFCN validate; + // Value of userdata pointer and validate pointer forms key for comparison. + Detector* userdata; + + /**type of detector - pattern based, Sourcefire (validator) or User (Validator). */ + unsigned detectorType; + + /**Number of resources registered */ + unsigned ref_count; + unsigned current_ref_count; + int provides_user; + const char* name; +}; + +typedef void*(* ServiceFlowdataGet)(AppIdData*, unsigned); +typedef int (* ServiceFlowdataAdd)(AppIdData*, void*, unsigned, AppIdFreeFCN); +typedef int (* ServiceFlowdataAddId)(AppIdData*, uint16_t, const RNAServiceElement* const); +typedef int (* ServiceFlowdataAddDHCP)(AppIdData*, unsigned, const uint8_t*, unsigned, const + uint8_t*, const uint8_t*); +#define APPID_EARLY_SESSION_FLAG_FW_RULE 1 +typedef AppIdData*(* ServiceCreateNewFlow)(AppIdData* flowp, const Packet*, const sfip_t*, uint16_t, + const sfip_t*, uint16_t, IpProtocol, int16_t, int flags); +typedef void (* ServiceDhcpNewLease)(AppIdData* flow, const uint8_t* mac, uint32_t ip, int32_t + zone, uint32_t subnetmask, uint32_t leaseSecs, uint32_t router); +typedef void (* ServiceAnalyzeFP)(AppIdData*, unsigned, unsigned, uint32_t); + +typedef int (* AddService)(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, AppId service, const char* vendor, + const char* version, const RNAServiceSubtype* subtype); +typedef int (* AddServiceConsumeSubtype)(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, AppId service, const char* vendor, const char* version, + RNAServiceSubtype* subtype); +typedef int (* ServiceInProcess)(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element); +typedef int (* FailService)(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, unsigned flow_data_index, const AppIdConfig* pConfig); +typedef int (* IncompatibleData)(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, unsigned flow_data_index, const AppIdConfig*); +typedef void (* AddHostInfo)(AppIdData* flow, SERVICE_HOST_INFO_CODE code, const void* info); +typedef void (* AddPayload)(AppIdData*, AppId); +typedef void (* AddUser)(AppIdData*, const char*, AppId, int); +typedef void (* AddMisc)(AppIdData*, AppId); +typedef void (* AddDnsQueryInfo)(AppIdData* flow, uint16_t id, const uint8_t* host, + uint8_t host_len, uint16_t host_offset, uint16_t record_type); +typedef void (* AddDnsResponseInfo)(AppIdData* flow, uint16_t id, const uint8_t* host, + uint8_t host_len, uint16_t host_offset, uint8_t response_type, uint32_t ttl); +typedef void (* ResetDnsInfo)(AppIdData* flow); + +struct ServiceApi +{ + ServiceFlowdataGet data_get; + ServiceFlowdataAdd data_add; + ServiceCreateNewFlow flow_new; + ServiceFlowdataAddId data_add_id; + ServiceFlowdataAddDHCP data_add_dhcp; + ServiceDhcpNewLease dhcpNewLease; + ServiceAnalyzeFP analyzefp; + AddService add_service; + FailService fail_service; + ServiceInProcess service_inprocess; + IncompatibleData incompatible_data; + AddHostInfo add_host_info; + AddPayload add_payload; + AddUser add_user; + AddServiceConsumeSubtype add_service_consume_subtype; + AddMisc add_misc; + AddDnsQueryInfo add_dns_query_info; + AddDnsResponseInfo add_dns_response_info; + ResetDnsInfo reset_dns_info; +}; + +struct RNAFlowState +{ + RNAFlowState* next; + const RNAServiceElement* svc; + uint16_t port; +}; + +struct RNAServiceValidationPort +{ + RNAServiceValidationFCN validate; + uint16_t port; + IpProtocol proto; + uint8_t reversed_validation; +}; + +struct RNAServiceValidationModule +{ + const char* name; + RNAServiceValidationInitFCN init; + RNAServiceValidationPort* pp; + const ServiceApi* api; + RNAServiceValidationModule* next; + int provides_user; + RNAServiceValidationCleanFCN clean; + unsigned flow_data_index; +}; + +#if defined(WORDS_BIGENDIAN) +#define LETOHS(p) BYTE_SWAP_16(*((uint16_t*)(p))) +#define LETOHL(p) BYTE_SWAP_32(*((uint32_t*)(p))) +#else +#define LETOHS(p) (*((uint16_t*)(p))) +#define LETOHL(p) (*((uint32_t*)(p))) +#endif + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_base.cc b/src/network_inspectors/appid/service_plugins/service_base.cc new file mode 100644 index 000000000..b46d6457c --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_base.cc @@ -0,0 +1,2456 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_base.cc author Ron Dempster + +#include "service_base.h" + +#include + +#include "service_api.h" +#include "service_battle_field.h" +#include "service_bgp.h" +#include "service_bootp.h" +#include "service_dcerpc.h" +#include "service_direct_connect.h" +#include "service_flap.h" +#include "service_ftp.h" +#include "service_irc.h" +#include "service_lpr.h" +#include "service_mdns.h" +#include "service_mysql.h" +#include "service_netbios.h" +#include "service_nntp.h" +#include "service_ntp.h" +#include "service_radius.h" +#include "service_rexec.h" +#include "service_rfb.h" +#include "service_rlogin.h" +#include "service_rpc.h" +#include "service_rshell.h" +#include "service_rsync.h" +#include "service_rtmp.h" +#include "service_smtp.h" +#include "service_snmp.h" +#include "service_ssh.h" +#include "service_ssl.h" +#include "service_telnet.h" +#include "service_tftp.h" +#include "appid_flow_data.h" +#include "fw_appid.h" +#include "lua_detector_api.h" +#include "lua_detector_module.h" +#include "util/ip_funcs.h" +#include "detector_plugins/detector_dns.h" +#include "detector_plugins/detector_pattern.h" +#include "detector_plugins/detector_sip.h" +#include "log/messages.h" +#include "main/snort_debug.h" +#include "search_engines/search_tool.h" +#include "utils/util.h" + +/*#define SERVICE_DEBUG 1 + #define SERVICE_DEBUG_PORT 0 */ + +#define BUFSIZE 512 + +#define STATE_ID_INCONCLUSIVE_SERVICE_WEIGHT 3 +#define STATE_ID_INVALID_CLIENT_THRESHOLD 9 +#define STATE_ID_MAX_VALID_COUNT 5 +#define STATE_ID_NEEDED_DUPE_DETRACT_COUNT 3 + +/* If this is greater than 1, more than 1 service detector can be searched for + * and tried per flow based on port/pattern (if a valid detector doesn't + * already exist). */ +#define MAX_CANDIDATE_SERVICES 10 + +static void* service_flowdata_get(AppIdData* flow, unsigned service_id); +static int service_flowdata_add(AppIdData* flow, void* data, unsigned service_id, AppIdFreeFCN + fcn); +static void AppIdAddHostInfo(AppIdData* flow, SERVICE_HOST_INFO_CODE code, const void* info); +static int AppIdAddDHCP(AppIdData* flowp, unsigned op55_len, const uint8_t* op55, unsigned + op60_len, const uint8_t* op60, const uint8_t* mac); +static void AppIdAddHostIP(AppIdData* flow, const uint8_t* mac, uint32_t ip4, + int32_t zone, uint32_t subnetmask, uint32_t leaseSecs, uint32_t router); +static void AppIdAddSMBData(AppIdData* flow, unsigned major, unsigned minor, uint32_t flags); +static void AppIdServiceAddMisc(AppIdData* flow, AppId miscId); + +const ServiceApi serviceapi = +{ + &service_flowdata_get, + &service_flowdata_add, + &AppIdEarlySessionCreate, + &AppIdFlowdataAddId, + &AppIdAddDHCP, + &AppIdAddHostIP, + &AppIdAddSMBData, + &AppIdServiceAddService, + &AppIdServiceFailService, + &AppIdServiceInProcess, + &AppIdServiceIncompatibleData, + &AppIdAddHostInfo, + &AppIdAddPayload, + &AppIdAddUser, + &AppIdServiceAddServiceSubtype, + &AppIdServiceAddMisc, + &AppIdAddDnsQueryInfo, + &AppIdAddDnsResponseInfo, + &AppIdResetDnsInfo, +}; + +#ifdef SERVICE_DEBUG +static const char* serviceIdStateName[] = +{ + "NEW", + "VALID", + "PORT", + "PATTERN", + "BRUTE_FORCE" +}; +#endif + +static RNAServiceElement* ftp_service = nullptr; + +static ServicePatternData* free_pattern_data; + +/*C service API */ +static void ServiceRegisterPattern(RNAServiceValidationFCN fcn, + IpProtocol proto, const uint8_t* pattern, unsigned size, + int position, struct Detector* userdata, int provides_user, + const char* name, ServiceConfig* pServiceConfig); +static void CServiceRegisterPattern(RNAServiceValidationFCN fcn, IpProtocol proto, + const uint8_t* pattern, unsigned size, + int position, const char* name, AppIdConfig* pConfig); +static void ServiceRegisterPatternUser(RNAServiceValidationFCN fcn, IpProtocol proto, + const uint8_t* pattern, unsigned size, + int position, const char* name, AppIdConfig* pConfig); +static int CServiceAddPort(RNAServiceValidationPort* pp, RNAServiceValidationModule* svm, + AppIdConfig* pConfig); +static void CServiceRemovePorts(RNAServiceValidationFCN validate, AppIdConfig* pConfig); + +static IniServiceAPI svc_init_api = +{ + &CServiceRegisterPattern, + &CServiceAddPort, + &CServiceRemovePorts, + &ServiceRegisterPatternUser, + &appSetServiceValidator, + 0, + 0, + nullptr +}; + +static CleanServiceAPI svc_clean_api = +{ + nullptr +}; + +extern RNAServiceValidationModule timbuktu_service_mod; +extern RNAServiceValidationModule bit_service_mod; +extern RNAServiceValidationModule tns_service_mod; +extern RNAServiceValidationModule http_service_mod; + +static RNAServiceValidationModule* static_service_list[] = +{ +#ifdef REMOVED_WHILE_NOT_IN_USE + &bgp_service_mod, + &bootp_service_mod, + &dcerpc_service_mod, +#endif + &dns_service_mod, +#ifdef REMOVED_WHILE_NOT_IN_USE + &flap_service_mod, + &ftp_service_mod, + &irc_service_mod, + &lpr_service_mod, + &mysql_service_mod, + &netbios_service_mod, + &nntp_service_mod, + &ntp_service_mod, + &radius_service_mod, + &rexec_service_mod, + &rfb_service_mod, + &rlogin_service_mod, + &rpc_service_mod, + &rshell_service_mod, + &rsync_service_mod, + &rtmp_service_mod, +#endif + &smtp_service_mod, +#ifdef REMOVED_WHILE_NOT_IN_USE + &snmp_service_mod, + &ssh_service_mod, + &ssl_service_mod, + &telnet_service_mod, + &tftp_service_mod, + &sip_service_mod, + &directconnect_service_mod, + &battlefield_service_mod, + &mdns_service_mod, + &timbuktu_service_mod, + &bit_service_mod, + &tns_service_mod, +#endif + &pattern_service_mod, + &http_service_mod +}; + +struct ServiceMatch +{ + struct ServiceMatch* next; + unsigned count; + unsigned size; + RNAServiceElement* svc; +}; + +static DHCPInfo* dhcp_info_free_list; +static FpSMBData* smb_data_free_list; +static unsigned smOrderedListSize = 0; +static ServiceMatch** smOrderedList = nullptr; +static ServiceMatch* free_service_match; +static const uint8_t zeromac[6] = { 0, 0, 0, 0, 0, 0 }; + +/**free ServiceMatch List. + */ +void AppIdFreeServiceMatchList(ServiceMatch* sm) +{ + ServiceMatch* tmpSm; + + if (!sm) + return; + + for (tmpSm = sm; tmpSm->next; tmpSm = tmpSm->next) + ; + tmpSm->next = free_service_match; + free_service_match = sm; +} + +void cleanupFreeServiceMatch(void) +{ + ServiceMatch* match; + while ((match=free_service_match) != nullptr) + { + free_service_match = match->next; + snort_free(match); + } +} + +int AddFTPServiceState(AppIdData* fp) +{ + if (!ftp_service) + return -1; + return AppIdFlowdataAddId(fp, 21, ftp_service); +} + +/**allocate one ServiceMatch element. + */ +static inline ServiceMatch* allocServiceMatch(void) +{ + ServiceMatch* sm; + + if ((sm = free_service_match)) + { + free_service_match = sm->next; + memset(sm, 0, sizeof(*sm)); + return sm; + } + return (ServiceMatch*)snort_calloc(sizeof(ServiceMatch)); +} + +static int pattern_match(void* id, void*, int index, void* data, void*) +{ + ServiceMatch** matches = (ServiceMatch**)data; + ServicePatternData* pd = (ServicePatternData*)id; + ServiceMatch* sm; + + if (pd->position >= 0 && pd->position != index) + return 0; + + for (sm=*matches; sm; sm=sm->next) + if (sm->svc == pd->svc) + break; + if (sm) + sm->count++; + else + { + sm=allocServiceMatch(); + sm->count++; + sm->svc = pd->svc; + sm->size = pd->size; + sm->next = *matches; + *matches = sm; + } + return 0; +} + +AppId getPortServiceId(IpProtocol proto, uint16_t port, const AppIdConfig* pConfig) +{ + AppId appId; + + if (proto == IpProtocol::TCP) + appId = pConfig->tcp_port_only[port]; + else if (proto == IpProtocol::UDP) + appId = pConfig->udp_port_only[port]; + else + appId = pConfig->ip_protocol[(uint16_t)proto]; + + checkSandboxDetection(appId); + + return appId; +} + +static inline uint16_t sslPortRemap( + uint16_t port + ) +{ + switch (port) + { + case 465: + return 25; + case 563: + return 119; + case 585: + case 993: + return 143; + case 990: + return 21; + case 992: + return 23; + case 994: + return 6667; + case 995: + return 110; + default: + return 0; + } +} + +static inline RNAServiceElement* AppIdGetNexServiceByPort( + IpProtocol protocol, + uint16_t port, + const RNAServiceElement* const lasService, + AppIdData* rnaData, + const AppIdConfig* pConfig + ) +{ + RNAServiceElement* service = nullptr; + SF_LIST* list = nullptr; + + if (AppIdServiceDetectionLevel(rnaData) == 1) + { + unsigned remappedPort = sslPortRemap(port); + if (remappedPort) + list = pConfig->serviceConfig.tcp_services[remappedPort]; + } + else if (protocol == IpProtocol::TCP) + { + list = pConfig->serviceConfig.tcp_services[port]; + } + else + { + list = pConfig->serviceConfig.udp_services[port]; + } + + if (list) + { + SF_LNODE* iter = nullptr; + + service = (RNAServiceElement*)sflist_first(list, &iter); + if (lasService) + { + while ( service && ((service->validate != lasService->validate) || + (service->userdata != lasService->userdata))) + service = (RNAServiceElement*)sflist_next(&iter); + if (service) + service = (RNAServiceElement*)sflist_next(&iter); + } + } + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (port == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "Port service for protocol %u port %u, service %s\n", + (unsigned)protocol, (unsigned)port, (service && service->name) ? service->name : + "UNKNOWN"); +#endif + + return service; +} + +static inline RNAServiceElement* AppIdNexServiceByPattern(AppIdServiceIDState* id_state +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + , uint16_t port +#endif +#endif + ) +{ + RNAServiceElement* service = nullptr; + + while (id_state->currenService) + { + id_state->currenService = id_state->currenService->next; + if (id_state->currenService && id_state->currenService->svc->current_ref_count) + { + service = id_state->currenService->svc; + break; + } + } + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (port == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "Next pattern service %s\n", + (service && service->name) ? service->name : "UNKNOWN"); +#endif + + return service; +} + +const RNAServiceElement* ServiceGetServiceElement(RNAServiceValidationFCN fcn, struct + Detector* userdata, + AppIdConfig* pConfig) +{ + RNAServiceElement* li; + + for (li=pConfig->serviceConfig.tcp_service_list; li; li=li->next) + { + if ((li->validate == fcn) && (li->userdata == userdata)) + return li; + } + + for (li=pConfig->serviceConfig.udp_service_list; li; li=li->next) + { + if ((li->validate == fcn) && (li->userdata == userdata)) + return li; + } + return nullptr; +} + +static void ServiceRegisterPattern(RNAServiceValidationFCN fcn, + IpProtocol proto, const uint8_t* pattern, unsigned size, + int position, struct Detector* userdata, int provides_user, + const char* name, ServiceConfig* pServiceConfig) +{ + SearchTool** patterns; + ServicePatternData** pd_list; + int* count; + ServicePatternData* pd; + RNAServiceElement** list; + RNAServiceElement* li; + + if ((IpProtocol)proto == IpProtocol::TCP) + { + patterns = &pServiceConfig->tcp_patterns; + pd_list = &pServiceConfig->tcp_pattern_data; + + count = &pServiceConfig->tcp_pattern_count; + list = &pServiceConfig->tcp_service_list; + } + else if ((IpProtocol)proto == IpProtocol::UDP) + { + patterns = &pServiceConfig->udp_patterns; + pd_list = &pServiceConfig->udp_pattern_data; + + count = &pServiceConfig->udp_pattern_count; + list = &pServiceConfig->udp_service_list; + } + else + { + ErrorMessage("Invalid protocol when registering a pattern: %u\n",(unsigned)proto); + return; + } + + for (li=*list; li; li=li->next) + { + if ((li->validate == fcn) && (li->userdata == userdata)) + break; + } + if (!li) + { + li = (RNAServiceElement*)snort_calloc(sizeof(RNAServiceElement)); + li->next = *list; + *list = li; + li->validate = fcn; + li->userdata = userdata; + li->detectorType = UINT_MAX; + li->provides_user = provides_user; + li->name = name; + } + + if (!(*patterns)) + { + *patterns = new SearchTool("ac_full"); + if (!(*patterns)) + { + ErrorMessage("Error initializing the pattern table for protocol %u\n",(unsigned)proto); + return; + } + } + + if (free_pattern_data) + { + pd = free_pattern_data; + free_pattern_data = pd->next; + memset(pd, 0, sizeof(*pd)); + } + else + pd = (ServicePatternData*)snort_calloc(sizeof(ServicePatternData)); + + pd->svc = li; + pd->size = size; + pd->position = position; + (*patterns)->add(pattern, size, pd, false); + (*count)++; + pd->next = *pd_list; + *pd_list = pd; + li->ref_count++; +} + +void ServiceRegisterPatternDetector(RNAServiceValidationFCN fcn, + IpProtocol proto, const uint8_t* pattern, unsigned size, + int position, struct Detector* userdata, const char* name) +{ + ServiceRegisterPattern(fcn, proto, pattern, size, position, userdata, 0, name, + &userdata->pAppidNewConfig->serviceConfig); +} + +static void ServiceRegisterPatternUser(RNAServiceValidationFCN fcn, IpProtocol proto, + const uint8_t* pattern, unsigned size, int position, const char* name, AppIdConfig* pConfig) +{ + ServiceRegisterPattern(fcn, proto, pattern, size, position, nullptr, 1, name, + &pConfig->serviceConfig); +} + +static void CServiceRegisterPattern(RNAServiceValidationFCN fcn, IpProtocol proto, + const uint8_t* pattern, unsigned size, + int position, const char* name, + AppIdConfig* pConfig) +{ + ServiceRegisterPattern(fcn, proto, pattern, size, position, nullptr, 0, name, + &pConfig->serviceConfig); +} + +static void RemoveServicePortsByType(RNAServiceValidationFCN validate, SF_LIST** services, + RNAServiceElement* list, struct Detector* userdata) +{ + RNAServiceElement* li; + unsigned i; + + for (li=list; li; li=li->next) + { + if (li->validate == validate && li->userdata == userdata) + break; + } + if (li == nullptr) + return; + + for (i=0; i < RNA_SERVICE_MAX_PORT; i++) + { + SF_LIST* listTmp; + + if ( ( listTmp = services[i] ) ) + { + SF_LNODE* iter; + RNAServiceElement* liTmp; + + liTmp = (RNAServiceElement*)sflist_first(listTmp, &iter); + while (liTmp) + { + if (liTmp == li) + { + li->ref_count--; + sflist_remove_node(listTmp, iter); + // FIXIT - M Revisit this for better solution to calling sflist_first after + // deleting a node... ultimate solution for use of sflist would be move + // to STL + liTmp = (RNAServiceElement*)sflist_first(listTmp, &iter); + } + else + liTmp = (RNAServiceElement*)sflist_next(&iter); + } + } + } +} + +/** + * \brief Remove all ports registered for all services + * + * This function takes care of removing ports for all services including C service modules, + * Lua detector modules and services associated with C detector modules. + * + * @param pServiceConfig - Service configuration from which all ports need to be removed + * @return void + */ +static void RemoveAllServicePorts(ServiceConfig* pServiceConfig) +{ + int i; + + for (i=0; itcp_services[i]) + { + sflist_free(pServiceConfig->tcp_services[i]); + pServiceConfig->tcp_services[i] = nullptr; + } + } + for (i=0; iudp_services[i]) + { + sflist_free(pServiceConfig->udp_services[i]); + pServiceConfig->udp_services[i] = nullptr; + } + } + for (i=0; iudp_reversed_services[i]) + { + sflist_free(pServiceConfig->udp_reversed_services[i]); + pServiceConfig->udp_reversed_services[i] = nullptr; + } + } +} + +void ServiceRemovePorts(RNAServiceValidationFCN validate, struct Detector* userdata, + AppIdConfig* pConfig) +{ + RemoveServicePortsByType(validate, pConfig->serviceConfig.tcp_services, + pConfig->serviceConfig.tcp_service_list, userdata); + RemoveServicePortsByType(validate, pConfig->serviceConfig.udp_services, + pConfig->serviceConfig.udp_service_list, userdata); + RemoveServicePortsByType(validate, pConfig->serviceConfig.udp_reversed_services, + pConfig->serviceConfig.udp_reversed_service_list, userdata); +} + +static void CServiceRemovePorts(RNAServiceValidationFCN validate, AppIdConfig* pConfig) +{ + ServiceRemovePorts(validate, nullptr, pConfig); +} + +int ServiceAddPort(RNAServiceValidationPort* pp, RNAServiceValidationModule* svm, + struct Detector* userdata, AppIdConfig* pConfig) +{ + SF_LIST** services; + RNAServiceElement** list = nullptr; + RNAServiceElement* li; + RNAServiceElement* serviceElement; + uint8_t isAllocated = 0; + + DebugFormat(DEBUG_INSPECTOR, "Adding service %s for protocol %u on port %u, %p", + svm->name, (unsigned)pp->proto, (unsigned)pp->port, (void*)pp->validate); + if (pp->proto == IpProtocol::TCP) + { + services = pConfig->serviceConfig.tcp_services; + list = &pConfig->serviceConfig.tcp_service_list; + } + else if (pp->proto == IpProtocol::UDP) + { + if (!pp->reversed_validation) + { + services = pConfig->serviceConfig.udp_services; + list = &pConfig->serviceConfig.udp_service_list; + } + else + { + services = pConfig->serviceConfig.udp_reversed_services; + list = &pConfig->serviceConfig.udp_reversed_service_list; + } + } + else + { + ErrorMessage("Service %s did not have a valid protocol (%u)\n", + svm->name, (unsigned)pp->proto); + return 0; + } + + for (li=*list; li; li=li->next) + { + if (li->validate == pp->validate && li->userdata == userdata) + break; + } + if (!li) + { + li = new RNAServiceElement; + isAllocated = 1; + li->next = *list; + *list = li; + li->validate = pp->validate; + li->provides_user = svm->provides_user; + li->userdata = userdata; + li->detectorType = UINT_MAX; + li->name = svm->name; + } + + if (pp->proto == IpProtocol::TCP && pp->port == 21 && !ftp_service) + { + ftp_service = li; + li->ref_count++; + } + + /*allocate a new list if this is first detector for this port. */ + if (!services[pp->port]) + { + services[pp->port] = (SF_LIST*)snort_alloc(sizeof(SF_LIST)); + sflist_init(services[pp->port]); + } + + /*search and add if not present. */ + SF_LNODE* iter = nullptr; + for (serviceElement = (RNAServiceElement*)sflist_first(services[pp->port], &iter); + serviceElement && (serviceElement != li); + serviceElement = (RNAServiceElement*)sflist_next(&iter)) + ; + + if (!serviceElement) + { + if (sflist_add_tail(services[pp->port], li)) + { + ErrorMessage("Could not add %s, service for protocol %u on port %u", svm->name, + (unsigned)pp->proto, (unsigned)pp->port); + if (isAllocated) + { + *list = li->next; + snort_free(li); + } + return -1; + } + } + + li->ref_count++; + return 0; +} + +static int CServiceAddPort(RNAServiceValidationPort* pp, RNAServiceValidationModule* svm, + AppIdConfig* pConfig) +{ + return ServiceAddPort(pp, svm, nullptr, pConfig); +} + +int serviceLoadForConfigCallback(void* symbol, AppIdConfig* pConfig) +{ + static unsigned service_module_index = 0; + RNAServiceValidationModule* svm = (RNAServiceValidationModule*)symbol; + RNAServiceValidationPort* pp; + + if (service_module_index >= 65536) + { + ErrorMessage("Maximum number of service modules exceeded"); + return -1; + } + + svm->api = &serviceapi; + pp = svm->pp; + for (pp=svm->pp; pp && pp->validate; pp++) + { + if (CServiceAddPort(pp, svm, pConfig)) + return -1; + } + + if (svm->init(&svc_init_api)) + { + ErrorMessage("Error initializing service %s\n",svm->name); + } + + svm->next = pConfig->serviceConfig.active_service_list; + pConfig->serviceConfig.active_service_list = svm; + + svm->flow_data_index = service_module_index | APPID_SESSION_DATA_SERVICE_MODSTATE_BIT; + service_module_index++; + + return 0; +} + +int serviceLoadCallback(void* symbol) +{ + return serviceLoadForConfigCallback(symbol, pAppidActiveConfig); +} + +int LoadServiceModules(const char**, uint32_t instance_id, AppIdConfig* pConfig) +{ + unsigned i; + + svc_init_api.instance_id = instance_id; + svc_init_api.debug = pAppidActiveConfig->mod_config->debug; + svc_init_api.pAppidConfig = pConfig; + + for (i=0; iserviceConfig.active_service_list; svm; svm=svm->next) + { + // processing only non-lua service detectors. + if (svm->init) + { + pp = svm->pp; + for (pp=svm->pp; pp && pp->validate; pp++) + { + if (CServiceAddPort(pp, svm, pConfig)) + return -1; + } + } + } + + return 0; +} + +void ServiceInit(AppIdConfig*) +{ + luaModuleInitAllServices(); +} + +void ServiceFinalize(AppIdConfig* pConfig) +{ + if (pConfig->serviceConfig.tcp_patterns) + pConfig->serviceConfig.tcp_patterns->prep(); + if (pConfig->serviceConfig.udp_patterns) + pConfig->serviceConfig.udp_patterns->prep(); +} + +void UnconfigureServices(AppIdConfig* pConfig) +{ + RNAServiceElement* li; + ServicePatternData* pd; + RNAServiceValidationModule* svm; + + svc_clean_api.pAppidConfig = pConfig; + + if (pConfig->serviceConfig.tcp_patterns) + { + delete pConfig->serviceConfig.tcp_patterns; + pConfig->serviceConfig.tcp_patterns = nullptr; + } + // Do not free memory for the pattern; this can be later reclaimed when a + // new pattern needs to be created. Memory for these patterns will be freed + // on exit. + while (pConfig->serviceConfig.tcp_pattern_data) + { + pd = pConfig->serviceConfig.tcp_pattern_data; + if ((li = pd->svc) != nullptr) + li->ref_count--; + pConfig->serviceConfig.tcp_pattern_data = pd->next; + pd->next = free_pattern_data; + free_pattern_data = pd; + } + if (pConfig->serviceConfig.udp_patterns) + { + delete pConfig->serviceConfig.udp_patterns; + pConfig->serviceConfig.udp_patterns = nullptr; + } + while (pConfig->serviceConfig.udp_pattern_data) + { + pd = pConfig->serviceConfig.udp_pattern_data; + if ((li = pd->svc) != nullptr) + li->ref_count--; + pConfig->serviceConfig.udp_pattern_data = pd->next; + pd->next = free_pattern_data; + free_pattern_data = pd; + } + + RemoveAllServicePorts(&pConfig->serviceConfig); + + for (svm=pConfig->serviceConfig.active_service_list; svm; svm=svm->next) + { + if (svm->clean) + svm->clean(&svc_clean_api); + } + + CleanServicePortPatternList(pConfig); +} + +void ReconfigureServices(AppIdConfig* pConfig) +{ + RNAServiceValidationModule* svm; + + for (svm=pConfig->serviceConfig.active_service_list; svm; svm=svm->next) + { + /*processing only non-lua service detectors. */ + if (svm->init) + { + if (svm->init(&svc_init_api)) + { + ErrorMessage("Error initializing service %s\n",svm->name); + } + else + { + DebugFormat(DEBUG_INSPECTOR,"Initialized service %s\n",svm->name); + } + } + } + + ServiceInit(pConfig); +} + +void CleanupServices(AppIdConfig* /*pConfig*/) +{ +// FIXIT-H: Without this defined, pConfig is unused parameter +#ifdef RNA_FULL_CLEANUP + ServicePatternData* pattern; + RNAServiceElement* se; + ServiceMatch* sm; + RNAServiceValidationModule* svm; + FpSMBData* sd; + DHCPInfo* info; + + svc_clean_api.pAppidConfig = pConfig; + + if (pConfig->serviceConfig.tcp_patterns) + { + delete pConfig->serviceConfig.tcp_patterns; + pConfig->serviceConfig.tcp_patterns = nullptr; + } + if (pConfig->serviceConfig.udp_patterns) + { + delete pConfig->serviceConfig.udp_patterns; + pConfig->serviceConfig.udp_patterns = nullptr; + } + while ((pattern=pConfig->serviceConfig.tcp_pattern_data)) + { + pConfig->serviceConfig.tcp_pattern_data = pattern->next; + snort_free(pattern); + } + while ((pattern=pConfig->serviceConfig.udp_pattern_data)) + { + pConfig->serviceConfig.udp_pattern_data = pattern->next; + snort_free(pattern); + } + while ((pattern=free_pattern_data)) + { + free_pattern_data = pattern->next; + snort_free(pattern); + } + while ((se=pConfig->serviceConfig.tcp_service_list)) + { + pConfig->serviceConfig.tcp_service_list = se->next; + snort_free(se); + } + while ((se=pConfig->serviceConfig.udp_service_list)) + { + pConfig->serviceConfig.udp_service_list = se->next; + snort_free(se); + } + while ((se=pConfig->serviceConfig.udp_reversed_service_list)) + { + pConfig->serviceConfig.udp_reversed_service_list = se->next; + snort_free(se); + } + while ((sd = smb_data_free_list)) + { + smb_data_free_list = sd->next; + snort_free(sd); + } + while ((info = dhcp_info_free_list)) + { + dhcp_info_free_list = info->next; + snort_free(info); + } + while ((sm = free_service_match)) + { + free_service_match = sm->next; + snort_free(sm); + } + cleanupFreeServiceMatch(); + if (smOrderedList) + { + // FIXIT - M - still allocated with calloc/realloc - vector coming soon... + free(smOrderedList); + smOrderedListSize = 0; + } + + RemoveAllServicePorts(&pConfig->serviceConfig); + + for (svm=pConfig->serviceConfig.active_service_list; svm; svm=svm->next) + { + if (svm->clean) + svm->clean(&svc_clean_api); + } + + CleanServicePortPatternList(pConfig); +#endif +} + +static int AppIdPatternPrecedence(const void* a, const void* b) +{ + const ServiceMatch* sm1 = (ServiceMatch*)a; + const ServiceMatch* sm2 = (ServiceMatch*)b; + + /*higher precedence should be before lower precedence */ + if (sm1->count != sm2->count) + return (sm2->count - sm1->count); + else + return (sm2->size - sm1->size); +} + +/**Perform pattern match of a packet and construct a list of services sorted in order of + * precedence criteria. Criteria is count and then size. The first service in the list is + * returned. The list itself is saved in AppIdServiceIDState. If + * appId is already identified, then use it instead of searching again. RNA will capability + * to try out other inferior matches. If appId is unknown i.e. searched and not found by FRE then + * dont do any pattern match. This is a way degrades RNA detector selection if FRE is running on + * this sensor. +*/ +static inline RNAServiceElement* AppIdGetServiceByPattern(const Packet* pkt, IpProtocol proto, + const int, AppIdServiceIDState* id_state, const ServiceConfig* pServiceConfig) +{ + SearchTool* patterns = nullptr; + ServiceMatch* match_list; + ServiceMatch* sm; + uint32_t count; + uint32_t i; + RNAServiceElement* service = nullptr; + + if (proto == IpProtocol::TCP) + patterns = pServiceConfig->tcp_patterns; + else + patterns = pServiceConfig->udp_patterns; + + if (!patterns) + { +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "Pattern bailing due to no patterns\n"); +#endif + return nullptr; + } + + if (!smOrderedList) + { + smOrderedListSize = 32; + // FIXIT - H - using calloc because this may be realloc'ed later, change to vector asap + smOrderedList = (ServiceMatch**)calloc(smOrderedListSize, sizeof(ServiceMatch*)); + if (!smOrderedList) + { + ErrorMessage("Pattern bailing due to failed allocation"); + return nullptr; + } + } + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) + { +#endif + fprintf(SF_DEBUG_FILE, "Matching\n"); + DumpHex(SF_DEBUG_FILE, pkt->data, pkt->dsize); +#if SERVICE_DEBUG_PORT +} + +#endif +#endif + /*FRE didn't search */ + match_list = nullptr; + patterns->find_all((char*)pkt->data, pkt->dsize, &pattern_match, false, (void*)&match_list); + + count = 0; + for (sm=match_list; sm; sm=sm->next) + { + if (count >= smOrderedListSize) + { + ServiceMatch** tmp; + smOrderedListSize *= 2; + tmp = (ServiceMatch**)realloc(smOrderedList, smOrderedListSize * + sizeof(*smOrderedList)); + if (!tmp) + { + ErrorMessage("Realloc failure %u\n",smOrderedListSize); + smOrderedListSize /= 2; + + /*free the remaining elements. */ + AppIdFreeServiceMatchList(sm); + + break; + } + ErrorMessage("Realloc %u\n",smOrderedListSize); + + smOrderedList = tmp; + } + + smOrderedList[count++] = sm; + } + + if (!count) + return nullptr; + + qsort(smOrderedList, count, sizeof(*smOrderedList), AppIdPatternPrecedence); + + /*rearrange the matchlist now */ + for (i = 0; i < (count-1); i++) + smOrderedList[i]->next = smOrderedList[i+1]; + smOrderedList[i]->next = nullptr; + + service = smOrderedList[0]->svc; + + if (id_state) + { + id_state->svc = service; + if (id_state->serviceList != nullptr) + { + AppIdFreeServiceMatchList(id_state->serviceList); + } + id_state->serviceList = smOrderedList[0]; + id_state->currenService = smOrderedList[0]; + } + else + AppIdFreeServiceMatchList(smOrderedList[0]); + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "Pattern service for protocol %u (%u->%u), %s\n", + (unsigned)proto, (unsigned)pkt->ptrs.sp, (unsigned)pkt->ptrs.dp, + (service && service->name) ? service->name.c_str() : "UNKNOWN"); +#endif + return service; +} + +static inline RNAServiceElement* AppIdGetServiceByBruteForce( + IpProtocol protocol, + const RNAServiceElement* lasService, + const AppIdConfig* pConfig + ) +{ + RNAServiceElement* service; + + if (lasService) + service = lasService->next; + else + service = ((protocol == IpProtocol::TCP) ? pConfig->serviceConfig.tcp_service_list : + pConfig->serviceConfig.udp_service_list); + + while (service && !service->current_ref_count) + service = service->next; + + return service; +} + +static void AppIdAddHostInfo(AppIdData*, SERVICE_HOST_INFO_CODE, const void*) +{ +} + +void AppIdFreeDhcpData(DhcpFPData* dd) +{ + snort_free(dd); +} + +static int AppIdAddDHCP(AppIdData* flowp, unsigned op55_len, const uint8_t* op55, unsigned + op60_len, const uint8_t* op60, const uint8_t* mac) +{ + if (op55_len && op55_len <= DHCP_OPTION55_LEN_MAX && !getAppIdFlag(flowp, + APPID_SESSION_HAS_DHCP_FP)) + { + DhcpFPData* rdd; + + rdd = (DhcpFPData*)snort_calloc(sizeof(*rdd)); + if (AppIdFlowdataAdd(flowp, rdd, APPID_SESSION_DATA_DHCP_FP_DATA, + (AppIdFreeFCN)AppIdFreeDhcpData)) + { + AppIdFreeDhcpData(rdd); + return -1; + } + + setAppIdFlag(flowp, APPID_SESSION_HAS_DHCP_FP); + rdd->op55_len = (op55_len > DHCP_OP55_MAX_SIZE) ? DHCP_OP55_MAX_SIZE : op55_len; + memcpy(rdd->op55, op55, rdd->op55_len); + rdd->op60_len = (op60_len > DHCP_OP60_MAX_SIZE) ? DHCP_OP60_MAX_SIZE : op60_len; + if (op60_len) + memcpy(rdd->op60, op60, rdd->op60_len); + memcpy(rdd->mac, mac, sizeof(rdd->mac)); + } + return 0; +} + +void AppIdFreeDhcpInfo(DHCPInfo* dd) +{ + if (dd) + { + dd->next = dhcp_info_free_list; + dhcp_info_free_list = dd; + } +} + +static void AppIdAddHostIP(AppIdData* flow, const uint8_t* mac, uint32_t ip, int32_t zone, + uint32_t subnetmask, uint32_t leaseSecs, uint32_t router) +{ + DHCPInfo* info; + unsigned flags; + + if (memcmp(mac, zeromac, 6) == 0 || ip == 0) + return; + + if (!getAppIdFlag(flow, APPID_SESSION_DO_RNA) || getAppIdFlag(flow, + APPID_SESSION_HAS_DHCP_INFO)) + return; + + flags = isIPv4HostMonitored(ntohl(ip), zone); + if (!(flags & IPFUNCS_HOSTS_IP)) + return; + + if (dhcp_info_free_list) + { + info = dhcp_info_free_list; + dhcp_info_free_list = info->next; + } + else + info = (DHCPInfo*)snort_calloc(sizeof(DHCPInfo)); + + if (AppIdFlowdataAdd(flow, info, APPID_SESSION_DATA_DHCP_INFO, + (AppIdFreeFCN)AppIdFreeDhcpInfo)) + { + AppIdFreeDhcpInfo(info); + return; + } + setAppIdFlag(flow, APPID_SESSION_HAS_DHCP_INFO); + info->ipAddr = ip; + memcpy(info->macAddr, mac, sizeof(info->macAddr)); + info->subnetmask = subnetmask; + info->leaseSecs = leaseSecs; + info->router = router; +} + +void AppIdFreeSMBData(FpSMBData* sd) +{ + if (sd) + { + sd->next = smb_data_free_list; + smb_data_free_list = sd; + } +} + +static void AppIdAddSMBData(AppIdData* flow, unsigned major, unsigned minor, uint32_t flags) +{ + FpSMBData* sd; + + if (flags & FINGERPRINT_UDP_FLAGS_XENIX) + return; + + if (smb_data_free_list) + { + sd = smb_data_free_list; + smb_data_free_list = sd->next; + } + else + sd = (FpSMBData*)snort_calloc(sizeof(FpSMBData)); + + if (AppIdFlowdataAdd(flow, sd, APPID_SESSION_DATA_SMB_DATA, (AppIdFreeFCN)AppIdFreeSMBData)) + { + AppIdFreeSMBData(sd); + return; + } + + setAppIdFlag(flow, APPID_SESSION_HAS_SMB_INFO); + sd->major = major; + sd->minor = minor; + sd->flags = flags & FINGERPRINT_UDP_FLAGS_MASK; +} + +static int AppIdServiceAddServiceEx(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, + AppId appId, const char* vendor, const char* version) +{ + AppIdServiceIDState* id_state; + uint16_t port; + const sfip_t* ip; + + if (!flow || !pkt || !svc_element) + { + ErrorMessage("Invalid arguments to absinthe_add_appId"); + return SERVICE_EINVALID; + } + + flow->serviceData = svc_element; + + if (vendor) + { + if (flow->serviceVendor) + snort_free(flow->serviceVendor); + flow->serviceVendor = snort_strdup(vendor); + } + if (version) + { + if (flow->serviceVersion) + snort_free(flow->serviceVersion); + flow->serviceVersion = snort_strdup(version); + } + setAppIdFlag(flow, APPID_SESSION_SERVICE_DETECTED); + flow->serviceAppId = appId; + + checkSandboxDetection(appId); + + if (getAppIdFlag(flow, APPID_SESSION_IGNORE_HOST)) + return SERVICE_SUCCESS; + + if (!getAppIdFlag(flow, APPID_SESSION_UDP_REVERSED)) + { + if (dir == APP_ID_FROM_INITIATOR) + { + ip = pkt->ptrs.ip_api.get_dst(); + port = pkt->ptrs.dp; + } + else + { + ip = pkt->ptrs.ip_api.get_src(); + port = pkt->ptrs.sp; + } + if (flow->service_port) + port = flow->service_port; + } + else + { + if (dir == APP_ID_FROM_INITIATOR) + { + ip = pkt->ptrs.ip_api.get_src(); + port = pkt->ptrs.sp; + } + else + { + ip = pkt->ptrs.ip_api.get_dst(); + port = pkt->ptrs.dp; + } + } + + /* If we ended up with UDP reversed, make sure we're pointing to the + * correct host tracker entry. */ + if (getAppIdFlag(flow, APPID_SESSION_UDP_REVERSED)) + { + flow->id_state = AppIdGetServiceIDState(ip, flow->proto, port, + AppIdServiceDetectionLevel(flow)); + } + + if (!(id_state = flow->id_state)) + { + if (!(id_state = AppIdAddServiceIDState(ip, flow->proto, port, AppIdServiceDetectionLevel( + flow)))) + { + ErrorMessage("Add service failed to create state"); + return SERVICE_ENOMEM; + } + flow->id_state = id_state; + flow->service_ip = *ip; + flow->service_port = port; + } + else + { + if (id_state->serviceList) + { + AppIdFreeServiceMatchList(id_state->serviceList); + id_state->serviceList = nullptr; + id_state->currenService = nullptr; + } + if (!sfip_is_set(&flow->service_ip)) + { + flow->service_ip = *ip; + flow->service_port = port; + } +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "Service %d for protocol %u on port %u (%u->%u) is valid\n", + (int)appId, (unsigned)flow->proto, (unsigned)flow->service_port, + (unsigned)pkt->ptrs.sp, (unsigned)pkt->ptrs.dp); +#endif + } + id_state->reset_time = 0; + if (id_state->state != SERVICE_ID_VALID) + { + id_state->state = SERVICE_ID_VALID; + id_state->valid_count = 0; + id_state->detract_count = 0; + id_state->last_detract.clear(); + id_state->invalid_client_count = 0; + id_state->last_invalid_client.clear(); + } + id_state->svc = svc_element; + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + { + char ipstr[INET6_ADDRSTRLEN]; + + ipstr[0] = 0; + inet_ntop(sfip_family(&flow->service_ip), (void*)sfaddr_get_ptr(&flow->service_ip), + ipstr, sizeof(ipstr)); + fprintf(SF_DEBUG_FILE, "Valid: %s:%u:%u %p %d\n", ipstr, (unsigned)flow->proto, + (unsigned)flow->service_port, id_state, (int)id_state->state); + } +#endif + + if (!id_state->valid_count) + { + id_state->valid_count++; + id_state->invalid_client_count = 0; + id_state->last_invalid_client.clear(); + id_state->detract_count = 0; + id_state->last_detract.clear(); + } + else if (id_state->valid_count < STATE_ID_MAX_VALID_COUNT) + id_state->valid_count++; + + /* Done looking for this session. */ + id_state->searching = false; + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "Service %d for protocol %u on port %u (%u->%u) is valid\n", + (int)appId, (unsigned)flow->proto, (unsigned)flow->service_port, (unsigned)pkt->ptrs.sp, + (unsigned)pkt->ptrs.dp); +#endif + return SERVICE_SUCCESS; +} + +int AppIdServiceAddServiceSubtype(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, + AppId appId, const char* vendor, const char* version, + RNAServiceSubtype* subtype) +{ + flow->subtype = subtype; + if (!svc_element->current_ref_count) + { +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, + "Service %d for protocol %u on port %u (%u->%u) is valid, but skipped\n", + (int)appId, (unsigned)flow->proto, (unsigned)flow->service_port, + (unsigned)pkt->ptrs.sp, (unsigned)pkt->ptrs.dp); +#endif + return SERVICE_SUCCESS; + } + return AppIdServiceAddServiceEx(flow, pkt, dir, svc_element, appId, vendor, version); +} + +int AppIdServiceAddService(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, + AppId appId, const char* vendor, const char* version, + const RNAServiceSubtype* subtype) +{ + RNAServiceSubtype* new_subtype = nullptr; + RNAServiceSubtype* tmp_subtype; + + if (!svc_element->current_ref_count) + { +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, + "Service %d for protocol %u on port %u (%u->%u) is valid, but skipped\n", + (int)appId, (unsigned)flow->proto, (unsigned)flow->service_port, + (unsigned)pkt->ptrs.sp, (unsigned)pkt->ptrs.dp); +#endif + return SERVICE_SUCCESS; + } + + for (; subtype; subtype = subtype->next) + { + tmp_subtype = (RNAServiceSubtype*)snort_calloc(sizeof(RNAServiceSubtype)); + if (subtype->service) + tmp_subtype->service = snort_strdup(subtype->service); + + if (subtype->vendor) + tmp_subtype->vendor = snort_strdup(subtype->vendor); + + if (subtype->version) + tmp_subtype->version = snort_strdup(subtype->version); + + tmp_subtype->next = new_subtype; + new_subtype = tmp_subtype; + } + flow->subtype = new_subtype; + return AppIdServiceAddServiceEx(flow, pkt, dir, svc_element, appId, vendor, version); +} + +int AppIdServiceInProcess(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element) +{ + AppIdServiceIDState* id_state; + + if (!flow || !pkt) + { + ErrorMessage("Invalid arguments to service_in_process"); + return SERVICE_EINVALID; + } + + if (dir == APP_ID_FROM_INITIATOR || getAppIdFlag(flow, APPID_SESSION_IGNORE_HOST| + APPID_SESSION_UDP_REVERSED)) + return SERVICE_SUCCESS; + + if (!(id_state = flow->id_state)) + { + uint16_t port; + const sfip_t* ip; + + ip = pkt->ptrs.ip_api.get_src(); + port = flow->service_port ? flow->service_port : pkt->ptrs.sp; + + if (!(id_state = AppIdAddServiceIDState(ip, flow->proto, port, AppIdServiceDetectionLevel( + flow)))) + { + ErrorMessage("In-process service failed to create state"); + return SERVICE_ENOMEM; + } + flow->id_state = id_state; + flow->service_ip = *ip; + flow->service_port = port; + id_state->state = SERVICE_ID_NEW; + id_state->svc = svc_element; + } + else + { + if (!sfip_is_set(&flow->service_ip)) + { + const sfip_t* ip = pkt->ptrs.ip_api.get_src(); + flow->service_ip = *ip; + if (!flow->service_port) + flow->service_port = pkt->ptrs.sp; + } +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "Service for protocol %u on port %u is in process (%u->%u), %p %s", + (unsigned)flow->proto, (unsigned)flow->service_port, (unsigned)pkt->ptrs.sp, + (unsigned)pkt->ptrs.dp, + svc_element->validate, svc_element->name ? : "UNKNOWN"); +#endif + } + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + { + char ipstr[INET6_ADDRSTRLEN]; + + ipstr[0] = 0; + inet_ntop(sfip_family(&flow->service_ip), (void*)sfaddr_get_ptr(&flow->service_ip), + ipstr, sizeof(ipstr)); + fprintf(SF_DEBUG_FILE, "Inprocess: %s:%u:%u %p %d\n", ipstr, (unsigned)flow->proto, + (unsigned)flow->service_port, + id_state, (int)id_state->state); + } +#endif + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "Service for protocol %u on port %u is in process (%u->%u), %s\n", + (unsigned)flow->proto, (unsigned)flow->service_port, (unsigned)pkt->ptrs.sp, + (unsigned)pkt->ptrs.dp, + svc_element->name ? : "UNKNOWN"); +#endif + + return SERVICE_SUCCESS; +} + +/**Called when service can not be identified on a flow but the checks failed on client request + * rather than server response. When client request fails a check, it may be specific to a client + * therefore we should not fail the service right away. If the same behavior is seen from the same + * client ultimately we will have to fail the service. If the same behavior is seen from different + * clients going to same service then this most likely the service is something else. + */ +int AppIdServiceIncompatibleData(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, unsigned flow_data_index, const AppIdConfig*) +{ + AppIdServiceIDState* id_state; + + if (!flow || !pkt) + { + ErrorMessage("Invalid arguments to service_incompatible_data"); + return SERVICE_EINVALID; + } + + if (flow_data_index != APPID_SESSION_DATA_NONE) + AppIdFlowdataDelete(flow, flow_data_index); + + /* If we're still working on a port/pattern list of detectors, then ignore + * individual fails until we're done looking at everything. */ + if ( (flow->serviceData == nullptr) /* we're + working + on a list + of + detectors, + and... */ + && (flow->candidate_service_list != nullptr) + && (flow->id_state != nullptr) ) + { + if (sflist_count(flow->candidate_service_list) != 0) /* it's + not empty + */ + { + return SERVICE_SUCCESS; + } + else if ( (flow->num_candidate_services_tried >= MAX_CANDIDATE_SERVICES) /* or + it's + empty, + but we're + tried + everything + we're + going to + try */ + || (flow->id_state->state == SERVICE_ID_BRUTE_FORCE) ) + { + return SERVICE_SUCCESS; + } + } + + setAppIdFlag(flow, APPID_SESSION_SERVICE_DETECTED); + clearAppIdFlag(flow, APPID_SESSION_CONTINUE); + + flow->serviceAppId = APP_ID_NONE; + + if (getAppIdFlag(flow, APPID_SESSION_IGNORE_HOST|APPID_SESSION_UDP_REVERSED) || (svc_element && + !svc_element->current_ref_count)) + return SERVICE_SUCCESS; + + if (dir == APP_ID_FROM_INITIATOR) + { + setAppIdFlag(flow, APPID_SESSION_INCOMPATIBLE); + return SERVICE_SUCCESS; + } + + if (!(id_state = flow->id_state)) + { + uint16_t port; + const sfip_t* ip; + + ip = pkt->ptrs.ip_api.get_src(); + port = flow->service_port ? flow->service_port : pkt->ptrs.sp; + + if (!(id_state = AppIdAddServiceIDState(ip, flow->proto, port, AppIdServiceDetectionLevel( + flow)))) + { + ErrorMessage("Incompatible service failed to create state"); + return SERVICE_ENOMEM; + } + flow->id_state = id_state; + flow->service_ip = *ip; + flow->service_port = port; + id_state->state = SERVICE_ID_NEW; + id_state->svc = svc_element; + } + else + { + if (!sfip_is_set(&flow->service_ip)) + { + const sfip_t* ip = pkt->ptrs.ip_api.get_src(); + flow->service_ip = *ip; + if (!flow->service_port) + flow->service_port = pkt->ptrs.sp; +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, + "service_IC: Changed State to %s for protocol %u on port %u (%u->%u), count %u, %s\n", + serviceIdStateName[id_state->state], (unsigned)flow->proto, + (unsigned)flow->service_port, + (unsigned)pkt->ptrs.sp, (unsigned)pkt->ptrs.dp, id_state->invalid_client_count, + (id_state->svc && id_state->svc->name) ? id_state->svc->name : "UNKNOWN"); +#endif + } + id_state->reset_time = 0; + } + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, + "service_IC: State %s for protocol %u on port %u (%u->%u), count %u, %s\n", + serviceIdStateName[id_state->state], (unsigned)flow->proto, (unsigned)flow->service_port, + (unsigned)pkt->ptrs.sp, (unsigned)pkt->ptrs.dp, id_state->invalid_client_count, + (id_state->svc && id_state->svc->name) ? id_state->svc->name : "UNKNOWN"); +#endif + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + { + char ipstr[INET6_ADDRSTRLEN]; + + ipstr[0] = 0; + inet_ntop(sfip_family(&flow->service_ip), (void*)sfaddr_get_ptr(&flow->service_ip), + ipstr, sizeof(ipstr)); + fprintf(SF_DEBUG_FILE, "Incompat: %s:%u:%u %p %d %s\n", ipstr, (unsigned)flow->proto, + (unsigned)flow->service_port, + id_state, (int)id_state->state, + (id_state->svc && id_state->svc->name) ? id_state->svc->name : "UNKNOWN"); + } +#endif + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + { + char ipstr[INET6_ADDRSTRLEN]; + + ipstr[0] = 0; + inet_ntop(sfip_family(&flow->service_ip), (void*)sfaddr_get_ptr(&flow->service_ip), + ipstr, sizeof(ipstr)); + fprintf(SF_DEBUG_FILE, "Incompat End: %s:%u:%u %p %d %s\n", ipstr, (unsigned)flow->proto, + (unsigned)flow->service_port, + id_state, (int)id_state->state, (id_state->svc && id_state->svc->name) ? + id_state->svc->name : "UNKNOWN"); + } +#endif + + return SERVICE_SUCCESS; +} + +int AppIdServiceFailService(AppIdData* flow, const Packet* pkt, int dir, + const RNAServiceElement* svc_element, unsigned flow_data_index, const AppIdConfig*) +{ + AppIdServiceIDState* id_state; + + if (flow_data_index != APPID_SESSION_DATA_NONE) + AppIdFlowdataDelete(flow, flow_data_index); + + /* If we're still working on a port/pattern list of detectors, then ignore + * individual fails until we're done looking at everything. */ + if ( (flow->serviceData == nullptr) /* we're + working + on a list + of + detectors, + and... */ + && (flow->candidate_service_list != nullptr) + && (flow->id_state != nullptr) ) + { + if (sflist_count(flow->candidate_service_list) != 0) /* it's + not empty + */ + { + return SERVICE_SUCCESS; + } + else if ( (flow->num_candidate_services_tried >= MAX_CANDIDATE_SERVICES) /* or + it's + empty, + but we're + tried + everything + we're + going to + try */ + || (flow->id_state->state == SERVICE_ID_BRUTE_FORCE) ) + { + return SERVICE_SUCCESS; + } + } + + flow->serviceAppId = APP_ID_NONE; + + setAppIdFlag(flow, APPID_SESSION_SERVICE_DETECTED); + clearAppIdFlag(flow, APPID_SESSION_CONTINUE); + + /* detectors should be careful in marking flow UDP_REVERSED otherwise the same detector + * gets all future flows. UDP_REVERSE should be marked only when detector positively + * matches opposite direction patterns. */ + + if (getAppIdFlag(flow, APPID_SESSION_IGNORE_HOST|APPID_SESSION_UDP_REVERSED) || (svc_element && + !svc_element->current_ref_count)) + return SERVICE_SUCCESS; + + /* For subsequent packets, avoid marking service failed on client packet, + * otherwise the service will show up on client side. */ + if (dir == APP_ID_FROM_INITIATOR) + { + setAppIdFlag(flow, APPID_SESSION_INCOMPATIBLE); + return SERVICE_SUCCESS; + } + + if (!(id_state = flow->id_state)) + { + uint16_t port; + const sfip_t* ip; + + ip = pkt->ptrs.ip_api.get_src(); + port = flow->service_port ? flow->service_port : pkt->ptrs.sp; + + if (!(id_state = AppIdAddServiceIDState(ip, flow->proto, port, AppIdServiceDetectionLevel( + flow)))) + { + ErrorMessage("Fail service failed to create state"); + return SERVICE_ENOMEM; + } + flow->id_state = id_state; + flow->service_ip = *ip; + flow->service_port = port; + id_state->state = SERVICE_ID_NEW; + id_state->svc = svc_element; + } + else + { + if (!sfip_is_set(&flow->service_ip)) + { + const sfip_t* ip = pkt->ptrs.ip_api.get_src(); + flow->service_ip = *ip; + if (!flow->service_port) + flow->service_port = pkt->ptrs.sp; + } +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, + "service_fail: State %s for protocol %u on port %u (%u->%u), count %u, valid count %u, currSvc %s\n", + serviceIdStateName[id_state->state], (unsigned)flow->proto, + (unsigned)flow->service_port, + (unsigned)pkt->ptrs.sp, (unsigned)pkt->ptrs.dp, id_state->invalid_client_count, + id_state->valid_count, + (svc_element && svc_element->name) ? svc_element->name : "UNKNOWN"); +#endif + } + id_state->reset_time = 0; + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, + "service_fail: State %s for protocol %u on port %u (%u->%u), count %u, valid count %u, currSvc %s\n", + serviceIdStateName[id_state->state], (unsigned)flow->proto, (unsigned)flow->service_port, + (unsigned)pkt->ptrs.sp, (unsigned)pkt->ptrs.dp, id_state->invalid_client_count, + id_state->valid_count, + (svc_element && svc_element->name) ? svc_element->name : "UNKNOWN"); +#endif + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + { + char ipstr[INET6_ADDRSTRLEN]; + + ipstr[0] = 0; + inet_ntop(sfip_family(&flow->service_ip), (void*)sfaddr_get_ptr(&flow->service_ip), + ipstr, sizeof(ipstr)); + fprintf(SF_DEBUG_FILE, "Fail: %s:%u:%u %p %d %s\n", ipstr, (unsigned)flow->proto, + (unsigned)flow->service_port, + id_state, (int)id_state->state, + (id_state->svc && id_state->svc->name) ? id_state->svc->name : "UNKNOWN"); + } +#endif + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (pkt->ptrs.dp == SERVICE_DEBUG_PORT || pkt->ptrs.sp == SERVICE_DEBUG_PORT) +#endif + { + char ipstr[INET6_ADDRSTRLEN]; + + ipstr[0] = 0; + inet_ntop(sfip_family(&flow->service_ip), (void*)sfaddr_get_ptr(&flow->service_ip), + ipstr, sizeof(ipstr)); + fprintf(SF_DEBUG_FILE, "Fail End: %s:%u:%u %p %d %s\n", ipstr, (unsigned)flow->proto, + (unsigned)flow->service_port, + id_state, (int)id_state->state, (id_state->svc && id_state->svc->name) ? + id_state->svc->name : "UNKNOWN"); + } +#endif + + return SERVICE_SUCCESS; +} + +/* Handle some exception cases on failure: + * - valid_count: If we have a detector that should be valid, but it keeps + * failing, consider restarting the detector search. + * - invalid_client_count: If our service detector search had trouble + * simply because of unrecognized client data, then consider retrying + * the search again. */ +static void HandleFailure(AppIdData* flowp, + AppIdServiceIDState* id_state, + const sfip_t* client_ip, + unsigned timeout) +{ + /* If we had a valid detector, check for too many fails. If so, start + * search sequence again. */ + if (id_state->state == SERVICE_ID_VALID) + { + /* Too many invalid clients? If so, count it as an invalid detect. */ + if (id_state->invalid_client_count >= STATE_ID_INVALID_CLIENT_THRESHOLD) + { + if (id_state->valid_count <= 1) + { + id_state->state = SERVICE_ID_NEW; + id_state->invalid_client_count = 0; + id_state->last_invalid_client.clear(); + id_state->valid_count = 0; + id_state->detract_count = 0; + id_state->last_detract.clear(); + } + else + { + id_state->valid_count--; + id_state->last_invalid_client = *client_ip; + id_state->invalid_client_count = 0; + } + } + /* Just a plain old fail. If too many of these happen, start + * search process over. */ + else if (id_state->invalid_client_count == 0) + { + if (sfip_fast_eq6(&id_state->last_detract, client_ip)) + id_state->detract_count++; + else + id_state->last_detract = *client_ip; + + if (id_state->detract_count >= STATE_ID_NEEDED_DUPE_DETRACT_COUNT) + { + if (id_state->valid_count <= 1) + { + id_state->state = SERVICE_ID_NEW; + id_state->invalid_client_count = 0; + id_state->last_invalid_client.clear(); + id_state->valid_count = 0; + id_state->detract_count = 0; + id_state->last_detract.clear(); + } + else + id_state->valid_count--; + } + } + } + /* If we were port/pattern searching and timed out, just restart over next + * time. */ + else if (timeout && (flowp->candidate_service_list != nullptr)) + { + id_state->state = SERVICE_ID_NEW; + } + /* If we were working on a port/pattern list of detectors, see if we + * should restart search (because of invalid clients) or just let it + * naturally continue onto brute force next. */ + else if ( (flowp->candidate_service_list != nullptr) + && (id_state->state == SERVICE_ID_BRUTE_FORCE) ) + { + /* If we're getting some invalid clients, keep retrying + * port/pattern search until we either find something or until we + * just see too many invalid clients. */ + if ( (id_state->invalid_client_count > 0) + && (id_state->invalid_client_count < STATE_ID_INVALID_CLIENT_THRESHOLD) ) + { + id_state->state = SERVICE_ID_NEW; + } + } + + /* Done looking for this session. */ + id_state->searching = false; +} + +/**Changes in_process service state to failed state when a flow is terminated. + * + * RNA used to repeat the same service detector if the detector remained in process till the flow terminated. Thus RNA + * got stuck on this one detector and never tried another service detector. This function will treat such a detector + * as returning incompatibleData when the flow is terminated. The intent here to make RNA try other service detectors but + * unlike incompatibleData status, we dont want to undermine confidence in the service. + * + * @note Packet may be nullptr when this function is called upon session timeout. + */ +void FailInProcessService(AppIdData* flowp, const AppIdConfig*) +{ + AppIdServiceIDState* id_state; + + if (getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED|APPID_SESSION_UDP_REVERSED)) + return; + + id_state = AppIdGetServiceIDState(&flowp->service_ip, flowp->proto, flowp->service_port, + AppIdServiceDetectionLevel(flowp)); + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (flowp->service_port == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "FailInProcess %" PRIx64 ", %08X:%u proto %u\n", + flowp->common.flags, sfaddr_get_ip4_value(&flowp->common.initiator_ip), + (unsigned)flowp->service_port, (unsigned)flowp->proto); +#endif + + if (!id_state || (id_state->svc && !id_state->svc->current_ref_count)) + return; + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (flowp->service_port == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, "FailInProcess: State %s for protocol %u on port %u, count %u, %s\n", + serviceIdStateName[id_state->state], (unsigned)flowp->proto, (unsigned)flowp->service_port, + id_state->invalid_client_count, (id_state->svc && id_state->svc->name) ? + id_state->svc->name : "UNKNOWN"); +#endif + + id_state->invalid_client_count += STATE_ID_INCONCLUSIVE_SERVICE_WEIGHT; + + // FIXIT - we need a Flow to get the ip address of client/server... +#ifdef REMOVED_WHILE_NOT_IN_USE + sfip_t* tmp_ip = _dpd.sessionAPI->get_session_ip_address(flowp->ssn, SSN_DIR_FROM_SERVER); + if (sfip_fast_eq6(tmp_ip, &flowp->service_ip)) + tmp_ip = _dpd.sessionAPI->get_session_ip_address(flowp->ssn, SSN_DIR_FROM_CLIENT); + + HandleFailure(flowp, id_state, tmp_ip, 1); +#endif + +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + if (flowp->service_port == SERVICE_DEBUG_PORT) +#endif + fprintf(SF_DEBUG_FILE, + "FailInProcess: Changed State to %s for protocol %u on port %u, count %u, %s\n", + serviceIdStateName[id_state->state], (unsigned)flowp->proto, (unsigned)flowp->service_port, + id_state->invalid_client_count, (id_state->svc && id_state->svc->name) ? + id_state->svc->name : "UNKNOWN"); +#endif +} + +/* This function should be called to find the next service detector to try when + * we have not yet found a valid detector in the host tracker. It will try + * both port and/or pattern (but not brute force - that should be done outside + * of this function). This includes UDP reversed services. A valid id_state + * (even if just initialized to the NEW state) should exist before calling this + * function. The state coming out of this function will reflect the state in + * which the next detector was found. If nothing is found, it'll indicate that + * brute force should be tried next as a state (and return nullptr). This + * function can be called once or multiple times (to run multiple detectors in + * parallel) per flow. Do not call this function if a detector has already + * been specified (serviceData). Basically, this function handles going + * through the main port/pattern search (and returning which detector to add + * next to the list of detectors to try (even if only 1)). */ +static const RNAServiceElement* AppIdGetNexService(const Packet* p, const int dir, + AppIdData* rnaData, const AppIdConfig* pConfig, AppIdServiceIDState* id_state) +{ + auto proto = rnaData->proto; + + /* If NEW, just advance onto trying ports. */ + if (id_state->state == SERVICE_ID_NEW) + { + id_state->state = SERVICE_ID_PORT; + id_state->svc = nullptr; + } + + /* See if there are any port detectors to try. If not, move onto patterns. */ + if (id_state->state == SERVICE_ID_PORT) + { + id_state->svc = AppIdGetNexServiceByPort(proto, (uint16_t)((dir == + APP_ID_FROM_RESPONDER) ? p->ptrs.sp : p->ptrs.dp), id_state->svc, rnaData, pConfig); + if (id_state->svc != nullptr) + { + return id_state->svc; + } + else + { + id_state->state = SERVICE_ID_PATTERN; + id_state->svc = nullptr; + if (id_state->serviceList != nullptr) + { + id_state->currenService = id_state->serviceList; + } + else + { + id_state->serviceList = nullptr; + id_state->currenService = nullptr; + } + } + } + + if (id_state->state == SERVICE_ID_PATTERN) + { + /* If we haven't found anything yet, try to see if we get any hits + * first with UDP reversed services before moving onto pattern matches. */ + if (dir == APP_ID_FROM_INITIATOR) + { + if (!getAppIdFlag(rnaData, APPID_SESSION_ADDITIONAL_PACKET) && (proto == + IpProtocol::UDP) + && !rnaData->tried_reverse_service ) + { + SF_LNODE* iter; + AppIdServiceIDState* reverse_id_state; + const RNAServiceElement* reverse_service = nullptr; + const sfip_t* reverse_ip = p->ptrs.ip_api.get_src(); + rnaData->tried_reverse_service = true; + if ((reverse_id_state = AppIdGetServiceIDState(reverse_ip, proto, p->ptrs.sp, + AppIdServiceDetectionLevel(rnaData)))) + { + reverse_service = reverse_id_state->svc; + } + if ( reverse_service + || (pConfig->serviceConfig.udp_reversed_services[p->ptrs.sp] && + (reverse_service = ( RNAServiceElement*)sflist_first( + pConfig->serviceConfig.udp_reversed_services[p->ptrs.sp], &iter))) + || (p->dsize && (reverse_service = AppIdGetServiceByPattern(p, proto, + dir, nullptr, &pConfig->serviceConfig))) ) + { + id_state->svc = reverse_service; + return id_state->svc; + } + } + return nullptr; + } + /* Try pattern match detectors. If not, give up, and go to brute + * force. */ + else /* APP_ID_FROM_RESPONDER */ + { + if (id_state->serviceList == nullptr) /* no list yet (need to make one) */ + { + id_state->svc = AppIdGetServiceByPattern(p, proto, dir, id_state, + &pConfig->serviceConfig); + } + else /* already have a pattern service list (just use it) */ + { + id_state->svc = AppIdNexServiceByPattern(id_state +#ifdef SERVICE_DEBUG +#if SERVICE_DEBUG_PORT + , flow->service_port +#endif +#endif + ); + } + + if (id_state->svc != nullptr) + { + return id_state->svc; + } + else + { + id_state->state = SERVICE_ID_BRUTE_FORCE; + id_state->svc = nullptr; + return nullptr; + } + } + } + + /* Don't do anything if it was in VALID or BRUTE FORCE. */ + return nullptr; +} + +int AppIdDiscoverService(Packet* p, const int dir, AppIdData* rnaData, const + AppIdConfig* pConfig) +{ + const sfip_t* ip; + int ret = SERVICE_NOMATCH; + const RNAServiceElement* service; + AppIdServiceIDState* id_state; + uint16_t port; + ServiceValidationArgs args; + + /* Get packet info. */ + auto proto = rnaData->proto; + if (sfip_is_set(&rnaData->service_ip)) + { + ip = &rnaData->service_ip; + port = rnaData->service_port; + } + else + { + if (dir == APP_ID_FROM_RESPONDER) + { + ip = p->ptrs.ip_api.get_src(); + port = p->ptrs.sp; + } + else + { + ip = p->ptrs.ip_api.get_src(); + port = p->ptrs.dp; + } + } + + /* Get host tracker state. */ + id_state = rnaData->id_state; + if (id_state == nullptr) + { + id_state = AppIdGetServiceIDState(ip, proto, port, AppIdServiceDetectionLevel(rnaData)); + + /* Create it if it doesn't exist yet. */ + if (id_state == nullptr) + { + if (!(id_state = AppIdAddServiceIDState(ip, proto, port, + AppIdServiceDetectionLevel(rnaData)))) + { + ErrorMessage("Discover service failed to create state"); + return SERVICE_ENOMEM; + } + memset(id_state, 0, sizeof(*id_state)); + } + rnaData->id_state = id_state; + } + + if (rnaData->serviceData == nullptr) + { + /* If a valid service already exists in host tracker, give it a try. */ + if ((id_state->svc != nullptr) && (id_state->state == SERVICE_ID_VALID)) + { + rnaData->serviceData = id_state->svc; + } + /* If we've gotten to brute force, give next detector a try. */ + else if ( (id_state->state == SERVICE_ID_BRUTE_FORCE) + && (rnaData->num_candidate_services_tried == 0) + && !id_state->searching ) + { + rnaData->serviceData = AppIdGetServiceByBruteForce(proto, id_state->svc, pConfig); + id_state->svc = rnaData->serviceData; + } + } + + args.data = p->data; + args.size = p->dsize; + args.dir = dir; + args.flowp = rnaData; + args.pkt = p; + args.pConfig = pConfig; + args.app_id_debug_session_flag = app_id_debug_session_flag; + args.app_id_debug_session = app_id_debug_session; + + /* If we already have a service to try, then try it out. */ + if (rnaData->serviceData != nullptr) + { + service = rnaData->serviceData; + args.userdata = service->userdata; + ret = service->validate(&args); + if (ret == SERVICE_NOT_COMPATIBLE) + rnaData->got_incompatible_services = 1; + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s %s returned %d\n", app_id_debug_session, + service->name ? service->name : "UNKNOWN", ret); + } + /* Else, try to find detector(s) to use based on ports and patterns. */ + else + { + if (rnaData->candidate_service_list == nullptr) + { + rnaData->candidate_service_list = (SF_LIST*)snort_calloc(sizeof(SF_LIST)); + sflist_init(rnaData->candidate_service_list); + rnaData->num_candidate_services_tried = 0; + + /* This is our first time in for this session, and we're about to + * search for a service, because we don't have any solid history on + * this IP/port yet. If some other session is also currently + * searching on this host tracker entry, reset state here, so that + * we can start search over again with this session. */ + if (id_state->searching) + id_state->state = SERVICE_ID_NEW; + id_state->searching = true; + } + + /* See if we've got more detector(s) to add to the candidate list. */ + if ( (id_state->state == SERVICE_ID_NEW) + || (id_state->state == SERVICE_ID_PORT) + || ((id_state->state == SERVICE_ID_PATTERN) && (dir == APP_ID_FROM_RESPONDER)) ) + { + while (rnaData->num_candidate_services_tried < MAX_CANDIDATE_SERVICES) + { + const RNAServiceElement* tmp = AppIdGetNexService(p, dir, rnaData, pConfig, + id_state); + if (tmp != nullptr) + { + SF_LNODE* iter = nullptr; + /* Add to list (if not already there). */ + service = (RNAServiceElement*)sflist_first(rnaData->candidate_service_list, + &iter); + while (service && (service != tmp)) + service = (RNAServiceElement*)sflist_next(&iter); + if (service == nullptr) + { + sflist_add_tail(rnaData->candidate_service_list, (void*)tmp); + rnaData->num_candidate_services_tried++; + } + } + else + { + break; + } + } + } + + /* Run all of the detectors that we currently have. */ + ret = SERVICE_INPROCESS; + SF_LNODE* iter; + service = (RNAServiceElement*)sflist_first(rnaData->candidate_service_list, &iter); + while (service) + { + int result; + + args.userdata = service->userdata; + result = service->validate(&args); + if (result == SERVICE_NOT_COMPATIBLE) + rnaData->got_incompatible_services = 1; + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s %s returned %d\n", app_id_debug_session, + service->name ? service->name : "UNKNOWN", result); + + if (result == SERVICE_SUCCESS) + { + ret = SERVICE_SUCCESS; + rnaData->serviceData = service; + sflist_free(rnaData->candidate_service_list); + rnaData->candidate_service_list = nullptr; + break; /* done */ + } + else if (result != SERVICE_INPROCESS) /* fail */ + { + sflist_remove_node(rnaData->candidate_service_list, iter); + service = (RNAServiceElement*)sflist_first(rnaData->candidate_service_list, &iter); + } + else + service = (RNAServiceElement*)sflist_next(&iter); + } + + /* If we tried everything and found nothing, then fail. */ + if (ret != SERVICE_SUCCESS) + { + if ( (sflist_count(rnaData->candidate_service_list) == 0) + && ( (rnaData->num_candidate_services_tried >= MAX_CANDIDATE_SERVICES) + || (id_state->state == SERVICE_ID_BRUTE_FORCE) ) ) + { + AppIdServiceFailService(rnaData, p, dir, nullptr, APPID_SESSION_DATA_NONE, + pConfig); + ret = SERVICE_NOMATCH; + } + } + } + + if (service != nullptr) + { + id_state->reset_time = 0; + } + else if (dir == APP_ID_FROM_RESPONDER) /* we have seen bidirectional exchange and have not + identified any service */ + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s no RNA service detector\n", app_id_debug_session); + AppIdServiceFailService(rnaData, p, dir, nullptr, APPID_SESSION_DATA_NONE, pConfig); + ret = SERVICE_NOMATCH; + } + + /* Handle failure exception cases in states. */ + if ((ret != SERVICE_INPROCESS) && (ret != SERVICE_SUCCESS)) + { + const sfip_t* tmp_ip; + if (dir == APP_ID_FROM_RESPONDER) + tmp_ip = p->ptrs.ip_api.get_dst(); + else + tmp_ip = p->ptrs.ip_api.get_src(); + + if (rnaData->got_incompatible_services) + { + if (id_state->invalid_client_count < STATE_ID_INVALID_CLIENT_THRESHOLD) + { + if (sfip_fast_equals_raw(&id_state->last_invalid_client, tmp_ip)) + id_state->invalid_client_count++; + else + { + id_state->invalid_client_count += 3; + id_state->last_invalid_client = *tmp_ip; + } + } + } + + HandleFailure(rnaData, id_state, tmp_ip, 0); + } + + /* Can free up any pattern match lists if done with them. */ + if ( (id_state->state == SERVICE_ID_BRUTE_FORCE) + || (id_state->state == SERVICE_ID_VALID) ) + { + if (id_state->serviceList != nullptr) + { + AppIdFreeServiceMatchList(id_state->serviceList); + } + id_state->serviceList = nullptr; + id_state->currenService = nullptr; + } + + return ret; +} + +static void* service_flowdata_get(AppIdData* flow, unsigned service_id) +{ + return AppIdFlowdataGet(flow, service_id); +} + +static int service_flowdata_add(AppIdData* flow, void* data, unsigned service_id, AppIdFreeFCN + fcn) +{ + return AppIdFlowdataAdd(flow, data, service_id, fcn); +} + +/** GUS: 2006 09 28 10:10:54 + * A simple function that prints the + * ports that have decoders registered. + */ +static void dumpServices(FILE* stream, SF_LIST* const* parray) +{ + int i,n = 0; + for (i = 0; i < RNA_SERVICE_MAX_PORT; i++) + { + if (parray[i] && (sflist_count(parray[i]) != 0)) + { + if ( n != 0) + { + fprintf(stream," "); + } + n++; + fprintf(stream,"%d",i); + } + } +} + +void dumpPorts(FILE* stream, const AppIdConfig* pConfig) +{ + fprintf(stream,"(tcp "); + dumpServices(stream,pConfig->serviceConfig.tcp_services); + fprintf(stream,") \n"); + fprintf(stream,"(udp "); + dumpServices(stream,pConfig->serviceConfig.udp_services); + fprintf(stream,") \n"); +} + +static void AppIdServiceAddMisc(AppIdData* flow, AppId miscId) +{ + if (flow != nullptr) + flow->miscAppId = miscId; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_base.h b/src/network_inspectors/appid/service_plugins/service_base.h new file mode 100644 index 000000000..6b2673e9c --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_base.h @@ -0,0 +1,138 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_base.h author Sourcefire Inc. + +#ifndef SERVICE_BASE_H +#define SERVICE_BASE_H + +#include + +// FIXIT - there are conflicts with sfaddr_temp.h - sf_ip.h must be first address those asap +#include "appid_api.h" +#include "appid_flow_data.h" +#include "service_api.h" +#include "sfip/sf_ip.h" + +class AppIdConfig; +class AppIdData; +struct RNAServiceElement; +struct DhcpFPData; +struct FpSMBData; +struct Packet; +struct ServiceMatch; +struct Detector; +struct RNAServiceValidationPort; +struct RNAServiceValidationModule; + +void CleanupServices(AppIdConfig*); +void ReconfigureServices(AppIdConfig*); +void UnconfigureServices(AppIdConfig*); +void ServiceInit(AppIdConfig*); +void ServiceFinalize(AppIdConfig*); +void FailInProcessService(AppIdData*, const AppIdConfig*); +int LoadServiceModules(const char** dir_list, uint32_t instance_id, AppIdConfig*); + +// This function is called during reload/reconfiguration. It registers service ports in the given +// AppId configuration. This function also takes care of services associated with detector modules. +int ReloadServiceModules(AppIdConfig*); + +int serviceLoadCallback(void* symbol); +int serviceLoadForConfigCallback(void* symbol, AppIdConfig*); +int ServiceAddPort( + RNAServiceValidationPort*, RNAServiceValidationModule*, Detector*, AppIdConfig*); +void ServiceRemovePorts(RNAServiceValidationFCN, Detector*, AppIdConfig*); +void ServiceRegisterPatternDetector( + RNAServiceValidationFCN, IpProtocol proto, const uint8_t* pattern, + unsigned size, int position, Detector*, const char* name); +int AppIdDiscoverService( + Packet*, int direction, AppIdData*, const AppIdConfig*); +AppId getPortServiceId(IpProtocol proto, uint16_t port, const AppIdConfig*); + +void AppIdFreeServiceIDState(AppIdServiceIDState*); + +int AppIdServiceAddService( + AppIdData*, const Packet*, int dir, const RNAServiceElement*, + AppId appId, const char* vendor, const char* version, const RNAServiceSubtype*); + +int AppIdServiceAddServiceSubtype( + AppIdData*, const Packet*, int dir, const RNAServiceElement*, AppId, + const char* vendor, const char* version, RNAServiceSubtype*); + +int AppIdServiceInProcess( + AppIdData*, const Packet*, int dir, const RNAServiceElement*); + +int AppIdServiceIncompatibleData( + AppIdData*, const Packet*, int dir, const RNAServiceElement*, + unsigned flow_data_index, const AppIdConfig*); + +int AppIdServiceFailService( + AppIdData*, const Packet*, int dir, const RNAServiceElement*, + unsigned flow_data_index, const AppIdConfig*); + +int AddFTPServiceState(AppIdData*); +void AppIdFreeDhcpInfo(DHCPInfo*); +void AppIdFreeSMBData(FpSMBData*); +void AppIdFreeDhcpData(DhcpFPData*); + +void dumpPorts(FILE*, const AppIdConfig*); + +const RNAServiceElement* ServiceGetServiceElement( + RNAServiceValidationFCN, Detector*, AppIdConfig*); + +extern RNAServiceValidationModule* active_service_list; + +extern uint32_t app_id_instance_id; + +void cleanupFreeServiceMatch(); +void AppIdFreeServiceMatchList(ServiceMatch* sm); + +inline bool compareServiceElements( + const RNAServiceElement* first, const RNAServiceElement* second) +{ + if (first == second) + return 0; + if (first == nullptr || second == nullptr) + return 1; + return (first->validate != second->validate || first->userdata != second->userdata); +} + +inline uint32_t AppIdServiceDetectionLevel(AppIdData* session) +{ + if (getAppIdFlag(session, APPID_SESSION_DECRYPTED)) + return 1; + return 0; +} + +inline void PopulateExpectedFlow(AppIdData* parent, AppIdData* expected, uint64_t flags) +{ + setAppIdFlag(expected, flags | + getAppIdFlag(parent, + APPID_SESSION_RESPONDER_MONITORED | + APPID_SESSION_INITIATOR_MONITORED | + APPID_SESSION_SPECIAL_MONITORED | + APPID_SESSION_RESPONDER_CHECKED | + APPID_SESSION_INITIATOR_CHECKED | + APPID_SESSION_DISCOVER_APP | + APPID_SESSION_DISCOVER_USER)); + expected->rnaServiceState = RNA_STATE_FINISHED; + expected->rnaClientState = RNA_STATE_FINISHED; +} + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_battle_field.cc b/src/network_inspectors/appid/service_plugins/service_battle_field.cc new file mode 100644 index 000000000..487f23a6c --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_battle_field.cc @@ -0,0 +1,233 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_battle_field.cc author Sourcefire Inc. + +#include "service_battle_field.h" +#include "application_ids.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +enum CONNECTION_STATES +{ + CONN_STATE_INIT, + CONN_STATE_HELLO_DETECTED, + CONN_STATE_SERVICE_DETECTED, + CONN_STATE_MESSAGE_DETECTED, + CONN_STATE_MAX +}; + +static const unsigned MAX_PACKET_INSPECTION_COUNT = 10; + +struct ServiceData +{ + uint32_t state; + uint32_t messageId; + uint32_t packetCount; +}; + +static int battle_field_init(const IniServiceAPI* const init_api); +static int battle_field_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &battle_field_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "battle_field" +}; + +static RNAServiceValidationPort pp[] = +{ + { &battle_field_validate, 4711, IpProtocol::TCP, 0 }, + { &battle_field_validate, 16567, IpProtocol::UDP, 0 }, + { &battle_field_validate, 27900, IpProtocol::UDP, 0 }, + { &battle_field_validate, 27900, IpProtocol::TCP, 0 }, + { &battle_field_validate, 29900, IpProtocol::UDP, 0 }, + { &battle_field_validate, 29900, IpProtocol::TCP, 0 }, + { &battle_field_validate, 27901, IpProtocol::TCP, 0 }, + { &battle_field_validate, 28910, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +static const char PATTERN_HELLO[] = "battlefield2\x00"; +static const char PATTERN_2[] = "\xfe\xfd"; +static const char PATTERN_3[] = "\x11\x20\x00\x01\x00\x00\x50\xb9\x10\x11"; +static const char PATTERN_4[] = "\x11\x20\x00\x01\x00\x00\x30\xb9\x10\x11"; +static const char PATTERN_5[] = "\x11\x20\x00\x01\x00\x00\xa0\x98\x00\x11"; +static const char PATTERN_6[] = "\xfe\xfd\x09\x00\x00\x00\x00"; + +RNAServiceValidationModule battlefield_service_mod = +{ + "BattleField", + &battle_field_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_BATTLEFIELD, 0 } +}; + +static int battle_field_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&battle_field_validate, IpProtocol::TCP, (uint8_t*)PATTERN_HELLO, + sizeof(PATTERN_HELLO)-1, 5, "battle_field", init_api->pAppidConfig); + init_api->RegisterPattern(&battle_field_validate, IpProtocol::TCP, (uint8_t*)PATTERN_2, + sizeof(PATTERN_2)-1, 0, "battle_field", init_api->pAppidConfig); + init_api->RegisterPattern(&battle_field_validate, IpProtocol::TCP, (uint8_t*)PATTERN_3, + sizeof(PATTERN_3)-1, 0, "battle_field", init_api->pAppidConfig); + init_api->RegisterPattern(&battle_field_validate, IpProtocol::TCP, (uint8_t*)PATTERN_4, + sizeof(PATTERN_4)-1, 0, "battle_field", init_api->pAppidConfig); + init_api->RegisterPattern(&battle_field_validate, IpProtocol::TCP, (uint8_t*)PATTERN_5, + sizeof(PATTERN_5)-1, 0, "battle_field", init_api->pAppidConfig); + init_api->RegisterPattern(&battle_field_validate, IpProtocol::TCP, (uint8_t*)PATTERN_6, + sizeof(PATTERN_6)-1, 0, "battle_field", init_api->pAppidConfig); + + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&battle_field_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int battle_field_validate(ServiceValidationArgs* args) +{ + ServiceData* fd; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + uint16_t size = args->size; + + if (!size) + { + goto inprocess_nofd; + } + + fd = (ServiceData*)battlefield_service_mod.api->data_get(flowp, + battlefield_service_mod.flow_data_index); + if (!fd) + { + fd = (ServiceData*)snort_calloc(sizeof(ServiceData)); + battlefield_service_mod.api->data_add(flowp, fd, + battlefield_service_mod.flow_data_index, &snort_free); + } + + switch (fd->state) + { + case CONN_STATE_INIT: + if ((pkt->ptrs.sp >= 27000 || pkt->ptrs.dp >= 27000) && size >= 4) + { + if (data[0] == 0xfe && data[1] == 0xfd) + { + fd->messageId = (data[2]<<8) | data[3]; + fd->state = CONN_STATE_MESSAGE_DETECTED; + goto inprocess; + } + } + + if (size == 18 && memcmp(data+5, PATTERN_HELLO, sizeof(PATTERN_HELLO)-1) == 0) + { + fd->state = CONN_STATE_HELLO_DETECTED; + goto inprocess; + } + break; + + case CONN_STATE_MESSAGE_DETECTED: + if (size > 8) + { + if ((uint32_t)(data[0]<<8 | data[1]) == fd->messageId) + { + goto success; + } + + if (data[0] == 0xfe && data[1] == 0xfd) + { + fd->messageId = (data[2]<<8) | data[3]; + goto inprocess; + } + } + + fd->state = CONN_STATE_INIT; + goto inprocess; + break; + + case CONN_STATE_HELLO_DETECTED: + if ((size == 7) && (memcmp(data, PATTERN_6, sizeof(PATTERN_6)-1) == 0)) + { + goto success; + } + + if ((size > 10) + && ((memcmp(data, PATTERN_3, sizeof(PATTERN_3)-1) == 0) + || (memcmp(data, PATTERN_4, sizeof(PATTERN_4)-1) == 0) + || (memcmp(data, PATTERN_5, sizeof(PATTERN_5)-1) == 0))) + { + goto success; + } + break; + case CONN_STATE_SERVICE_DETECTED: + goto success; + } + + battlefield_service_mod.api->fail_service(flowp, pkt, args->dir, &svc_element, + battlefield_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOMATCH; + +inprocess: + fd->packetCount++; + if (fd->packetCount >= MAX_PACKET_INSPECTION_COUNT) + goto fail; +inprocess_nofd: + battlefield_service_mod.api->service_inprocess(flowp, pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + if (args->dir != APP_ID_FROM_RESPONDER) + { + fd->state = CONN_STATE_SERVICE_DETECTED; + goto inprocess; + } + + battlefield_service_mod.api->add_service(flowp, pkt, args->dir, &svc_element, + APP_ID_BATTLEFIELD, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + battlefield_service_mod.api->fail_service(flowp, pkt, args->dir, &svc_element, + battlefield_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_battle_field.h b/src/network_inspectors/appid/service_plugins/service_battle_field.h new file mode 100644 index 000000000..b5d4870a4 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_battle_field.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_battle_field.h author Sourcefire Inc. + +#ifndef SERVICE_BATTLEFIELD_H +#define SERVICE_BATTLEFIELD_H + +#include "service_api.h" + +extern RNAServiceValidationModule battlefield_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_bgp.cc b/src/network_inspectors/appid/service_plugins/service_bgp.cc new file mode 100644 index 000000000..cefa73b9d --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_bgp.cc @@ -0,0 +1,256 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_bgp.cc author Sourcefire Inc. + +#include "service_bgp.h" +#include "application_ids.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +static const unsigned BGP_PORT = 179; + +static const unsigned BGP_V1_TYPE_OPEN = 1; +static const unsigned BGP_V1_TYPE_OPEN_CONFIRM = 5; +static const unsigned BGP_TYPE_OPEN = 1; +static const unsigned BGP_TYPE_KEEPALIVE = 4; + +static const unsigned BGP_OPEN_LINK_MAX = 3; + +static const unsigned BGP_VERSION_MAX = 4; +static const unsigned BGP_VERSION_MIN = 2; + +enum BGPState +{ + BGP_STATE_CONNECTION, + BGP_STATE_OPENSENT +}; + +#pragma pack(1) + +struct ServiceBGPData +{ + BGPState state; + int v1; +}; + +union ServiceBGPHeader +{ + struct + { + uint16_t marker; + uint16_t len; + uint8_t version; + uint8_t type; + uint16_t hold; + } v1; + struct + { + uint32_t marker[4]; + uint16_t len; + uint8_t type; + } v; +}; + +struct ServiceBGPOpen +{ + uint8_t version; + uint16_t as; + uint16_t holdtime; +}; + +struct ServiceBGPV1Open +{ + uint16_t system; + uint8_t link; + uint8_t auth; +}; + +#pragma pack() + +static int bgp_init(const IniServiceAPI* const init_api); +static int bgp_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &bgp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "bgp" +}; + +static RNAServiceValidationPort pp[] = +{ + { &bgp_validate, BGP_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule bgp_service_mod = +{ + "BGP", + &bgp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static uint8_t BGP_PATTERN[] = +{ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_BGP, 0 } +}; + +static int bgp_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&bgp_validate, IpProtocol::TCP, BGP_PATTERN, sizeof(BGP_PATTERN), 0, + "bgp", init_api->pAppidConfig); + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&bgp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int bgp_validate(ServiceValidationArgs* args) +{ + ServiceBGPData* bd; + const ServiceBGPHeader* bh; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + uint16_t len; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + if (size < sizeof(ServiceBGPHeader)) + goto fail; + + bd = (ServiceBGPData*)bgp_service_mod.api->data_get(flowp, bgp_service_mod.flow_data_index); + if (!bd) + { + bd = (ServiceBGPData*)snort_calloc(sizeof(ServiceBGPData)); + bgp_service_mod.api->data_add(flowp, bd, bgp_service_mod.flow_data_index, &snort_free); + bd->state = BGP_STATE_CONNECTION; + } + + bh = (const ServiceBGPHeader*)data; + switch (bd->state) + { + case BGP_STATE_CONNECTION: + if (size >= sizeof(bh->v1) + sizeof(ServiceBGPV1Open) && + bh->v1.marker == 0xFFFF && + bh->v1.version == 0x01 && bh->v1.type == BGP_V1_TYPE_OPEN) + { + ServiceBGPV1Open* open; + + len = ntohs(bh->v1.len); + if (len > 1024) + goto fail; + open = (ServiceBGPV1Open*)(data + sizeof(bh->v1)); + if (open->link > BGP_OPEN_LINK_MAX) + goto fail; + bd->v1 = 1; + } + else if (size >= sizeof(bh->v) + sizeof(ServiceBGPOpen) && + bh->v.marker[0] == 0xFFFFFFFF && + bh->v.marker[1] == 0xFFFFFFFF && + bh->v.marker[2] == 0xFFFFFFFF && + bh->v.marker[3] == 0xFFFFFFFF && + bh->v.type == BGP_TYPE_OPEN) + { + ServiceBGPOpen* open; + + len = ntohs(bh->v.len); + if (len > 4096) + goto fail; + open = (ServiceBGPOpen*)(data + sizeof(bh->v)); + if (open->version > BGP_VERSION_MAX || + open->version < BGP_VERSION_MIN) + { + goto fail; + } + bd->v1 = 0; + } + else + goto fail; + bd->state = BGP_STATE_OPENSENT; + break; + case BGP_STATE_OPENSENT: + if (bd->v1) + { + if (size >= sizeof(bh->v1) && bh->v1.marker == 0xFFFF && + bh->v1.version == 0x01 && + bh->v1.type == BGP_V1_TYPE_OPEN_CONFIRM) + { + len = ntohs(bh->v1.len); + if (len != sizeof(bh->v1)) + goto fail; + goto success; + } + } + else + { + if (size >= sizeof(bh->v) && + bh->v.type == BGP_TYPE_KEEPALIVE) + { + len = ntohs(bh->v.len); + if (len != sizeof(bh->v)) + goto fail; + goto success; + } + } + default: + goto fail; + } + +inprocess: + bgp_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +fail: + bgp_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + bgp_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + +success: + bgp_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_BGP, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_bgp.h b/src/network_inspectors/appid/service_plugins/service_bgp.h new file mode 100644 index 000000000..e6a843c56 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_bgp.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_bgp.h author Sourcefire Inc. + +#ifndef SERVICE_BGP_H +#define SERVICE_BGP_H + +#include "service_api.h" + +extern RNAServiceValidationModule bgp_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_bit.cc b/src/network_inspectors/appid/service_plugins/service_bit.cc new file mode 100644 index 000000000..0afabc95d --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_bit.cc @@ -0,0 +1,212 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_bit.cc author Sourcefire Inc. + +#include "main/snort_debug.h" +#include "utils/util.h" +#include "application_ids.h" +#include "service_api.h" + +static const char svc_name[] = "bt"; +static const uint8_t BIT_BANNER[] = "\023BitTorrent protocol"; + +#define BIT_PORT 6881 + +#define BIT_BANNER_LEN (sizeof(BIT_BANNER)-1) +#define RES_LEN 8 +#define SHA_LEN 20 +#define PEER_ID_LEN 20 +#define LAST_BANNER_OFFSET (BIT_BANNER_LEN+RES_LEN+SHA_LEN+PEER_ID_LEN - 1) + +enum BITState +{ + BIT_STATE_BANNER, + BIT_STATE_BANNER_DC, + BIT_STATE_MESSAGE_LEN, + BIT_STATE_MESSAGE_DATA +}; + +struct ServiceBITData +{ + BITState state; + unsigned stringlen; + unsigned pos; + union + { + uint32_t len; + uint8_t raw_len[4]; + } l; +}; + +#pragma pack(1) +struct ServiceBITMsg +{ + uint32_t len; + uint8_t code; +}; +#pragma pack() + +static int bit_init(const IniServiceAPI* const init_api); +static int bit_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &bit_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "bit" +}; + +static RNAServiceValidationPort pp[] = +{ + { &bit_validate, BIT_PORT, IpProtocol::TCP, 0 }, + { &bit_validate, BIT_PORT+1, IpProtocol::TCP, 0 }, + { &bit_validate, BIT_PORT+2, IpProtocol::TCP, 0 }, + { &bit_validate, BIT_PORT+3, IpProtocol::TCP, 0 }, + { &bit_validate, BIT_PORT+4, IpProtocol::TCP, 0 }, + { &bit_validate, BIT_PORT+5, IpProtocol::TCP, 0 }, + { &bit_validate, BIT_PORT+6, IpProtocol::TCP, 0 }, + { &bit_validate, BIT_PORT+7, IpProtocol::TCP, 0 }, + { &bit_validate, BIT_PORT+8, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +// FIXIT - Why is there no service_bit.h that declares this extern like all the others? +SO_PUBLIC RNAServiceValidationModule bit_service_mod = +{ + svc_name, + &bit_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_BITTORRENT, 0 } +}; + +static int bit_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&bit_validate, IpProtocol::TCP, (const uint8_t*)BIT_BANNER, + sizeof(BIT_BANNER)-1, 0, svc_name, init_api->pAppidConfig); + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&bit_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int bit_validate(ServiceValidationArgs* args) +{ + ServiceBITData* ss; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + uint16_t offset; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + ss = (ServiceBITData*)bit_service_mod.api->data_get(flowp, bit_service_mod.flow_data_index); + if (!ss) + { + ss = (ServiceBITData*)snort_calloc(sizeof(ServiceBITData)); + bit_service_mod.api->data_add(flowp, ss, bit_service_mod.flow_data_index, &snort_free); + ss->state = BIT_STATE_BANNER; + } + + offset = 0; + while (offset < size) + { + switch (ss->state) + { + case BIT_STATE_BANNER: + if (data[offset] != BIT_BANNER[ss->pos]) + goto fail; + if (ss->pos == BIT_BANNER_LEN-1) + ss->state = BIT_STATE_BANNER_DC; + ss->pos++; + break; + case BIT_STATE_BANNER_DC: + if (ss->pos == LAST_BANNER_OFFSET) + { + ss->pos = 0; + ss->state = BIT_STATE_MESSAGE_LEN; + break; + } + ss->pos++; + break; + case BIT_STATE_MESSAGE_LEN: + ss->l.raw_len[ss->pos] = data[offset]; + ss->pos++; + if (ss->pos >= offsetof(ServiceBITMsg, code)) + { + ss->stringlen = ntohl(ss->l.len); + ss->state = BIT_STATE_MESSAGE_DATA; + if (!ss->stringlen) + { + if (offset == size-1) + goto success; + goto fail; + } + ss->pos = 0; + } + break; + + case BIT_STATE_MESSAGE_DATA: + ss->pos++; + if (ss->pos == ss->stringlen) + goto success; + break; + default: + goto fail; + } + offset++; + } + +inprocess: + bit_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + bit_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_BITTORRENT, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + bit_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + bit_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_bootp.cc b/src/network_inspectors/appid/service_plugins/service_bootp.cc new file mode 100644 index 000000000..1e78d81ce --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_bootp.cc @@ -0,0 +1,340 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_bootp.cc author Sourcefire Inc. + +#include "service_bootp.h" +#include "main/snort_debug.h" +#include "protocols/eth.h" +#include "app_info_table.h" +#include "application_ids.h" + +#define DHCP_MAGIC_COOKIE 0x63825363 + +#pragma pack(1) + +struct ServiceBOOTPHeader +{ + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; +}; + +enum DHCP_OPTIONS +{ + DHCP_OPT_SUBNET_MASK = 1, + DHCP_OPT_ROUTER = 3, + DHCP_OPT_DOMAIN_NAME_SERVER = 6, + DHCP_OPT_DOMAIN_NAME = 15, + DHCP_OPT_IPADDR_LEASE_TIME = 51, + DHCP_OPT_DHCP_MESSAGE_TYPE =53 +}; + +struct ServiceDHCPOption +{ + uint8_t option; + uint8_t len; +}; + +#pragma pack() + +static int bootp_init(const IniServiceAPI* const init_api); +static int bootp_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &bootp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "bootp" +}; + +static RNAServiceValidationPort pp[] = +{ + { &bootp_validate, 67, IpProtocol::UDP, 0 }, + { &bootp_validate, 67, IpProtocol::UDP, 1 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule bootp_service_mod = +{ + "DHCP", + &bootp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_DHCP, APPINFO_FLAG_SERVICE_ADDITIONAL | APPINFO_FLAG_SERVICE_UDP_REVERSED } +}; + +static int bootp_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&bootp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int bootp_validate(ServiceValidationArgs* args) +{ + const ServiceBOOTPHeader* bh; + const ServiceDHCPOption* op; + unsigned i; + unsigned op55_len=0; + unsigned op60_len=0; + const uint8_t* op55=nullptr; + const uint8_t* op60=nullptr; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + + if (size < sizeof(ServiceBOOTPHeader)) + goto fail; + + bh = (const ServiceBOOTPHeader*)data; + + if (bh->htype != 0x01) + goto fail; + if (bh->hlen != 0x06) + goto fail; + + for (i=0; isname); i++) + { + if (!bh->sname[i]) + break; + } + if (i >= sizeof(bh->sname)) + goto fail; + + for (i=0; ifile); i++) + { + if (!bh->file[i]) + break; + } + if (i >= sizeof(bh->file)) + goto fail; + + if (bh->op == 0x01) + { + if (size > sizeof(ServiceBOOTPHeader) + 4) + { + if (ntohl(*((uint32_t*)(data + sizeof(ServiceBOOTPHeader)))) == + DHCP_MAGIC_COOKIE) + { + int option53 = 0; + for (i=sizeof(ServiceBOOTPHeader)+sizeof(uint32_t); ioption == 0xff) + { + const eth::EtherHdr* eh = layer::get_eth_layer(pkt); + + if (!eh) + goto fail; + + if (option53 && op55_len && (memcmp(eh->ether_src, bh->chaddr, 6) == 0)) + { + if (bootp_service_mod.api->data_add_dhcp(flowp, op55_len, op55, + op60_len, op60, + bh->chaddr)) + { + return SERVICE_ENOMEM; + } + } + goto inprocess; + } + i += sizeof(ServiceDHCPOption); + if (i >= size) + goto not_compatible; + if (op->option == 53 && op->len == 1 && i + 1 < size && data[i] == 3) + { + option53 = 1; + } + else if (op->option == 55 && op->len >= 1) + { + if (option53) + { + op55_len = op->len; + op55 = &data[i]; + } + } + else if (op->option == 60 && op->len >= 1) + { + if (option53) + { + op60_len = op->len; + op60 = &data[i]; + } + } + i += op->len; + if (i >= size) + goto not_compatible; + } + goto not_compatible; + } + } + goto not_compatible; + } + + if (bh->op != 0x02) + goto fail; + + if (dir == APP_ID_FROM_INITIATOR) + { + setAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED); + } + else + { + clearAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED); + } + + if (size > sizeof(ServiceBOOTPHeader) + 4) + { + if (ntohl(*((uint32_t*)(data + sizeof(ServiceBOOTPHeader)))) == + DHCP_MAGIC_COOKIE) + { + int option53 = 0; + uint32_t subnet = 0; + uint32_t router = 0; + uint32_t leaseTime = 0; + + for (i=sizeof(ServiceBOOTPHeader)+sizeof(uint32_t); + ioption == 0xff) + { + const eth::EtherHdr* eh = layer::get_eth_layer(pkt); + + if (!eh) + goto fail; + + if (option53 && (memcmp(eh->ether_dst, bh->chaddr, 6) == 0)) + bootp_service_mod.api->dhcpNewLease(flowp, bh->chaddr, bh->yiaddr, + pkt->pkth->ingress_group, ntohl(subnet), ntohl(leaseTime), + router); + goto success; + } + i += sizeof(ServiceDHCPOption); + if (i + op->len > size) + goto fail; + + switch (op->option) + { + case DHCP_OPT_DHCP_MESSAGE_TYPE: + if (op->len == 1 && data[i] == 5) + { + option53 = 1; + } + break; + case DHCP_OPT_SUBNET_MASK: + if (op->len == 4) + { + memcpy(&subnet, &data[i], sizeof(subnet)); + } + break; + case DHCP_OPT_ROUTER: + if (op->len == 4) + { + memcpy(&router, &data[i], sizeof(router)); + } + break; + case DHCP_OPT_IPADDR_LEASE_TIME: + if (op->len == 4 ) + { + memcpy(&leaseTime, &data[i], sizeof(leaseTime)); + } + break; + default: + ; + } + i += op->len; + if (i >= size) + goto fail; + } + goto fail; + } + } + +success: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + bootp_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_DHCP, nullptr, nullptr, nullptr); + } + return SERVICE_SUCCESS; + +inprocess: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + bootp_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + } + return SERVICE_INPROCESS; + +fail: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + bootp_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + bootp_service_mod.flow_data_index, args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOMATCH; + +not_compatible: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + bootp_service_mod.api->incompatible_data(flowp, args->pkt, args->dir, &svc_element, + bootp_service_mod.flow_data_index, args->pConfig); + } + return SERVICE_NOT_COMPATIBLE; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_bootp.h b/src/network_inspectors/appid/service_plugins/service_bootp.h new file mode 100644 index 000000000..96ee5d21c --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_bootp.h @@ -0,0 +1,32 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_bootp.h author Sourcefire Inc. + +#ifndef SERVICE_BOOTP_H +#define SERVICE_BOOTP_H + +// Service detector for BOOTP + +#include "service_api.h" + +extern RNAServiceValidationModule bootp_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_config.h b/src/network_inspectors/appid/service_plugins/service_config.h new file mode 100644 index 000000000..437f79b8d --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_config.h @@ -0,0 +1,109 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_config.h author Sourcefire Inc. + +#ifndef SERVICE_CONFIG_H +#define SERVICE_CONFIG_H + +// Service detector configuration + +#include + +#include +#include "service_api.h" + +#define RNA_SERVICE_MAX_PORT 65536 + +struct RNAServiceElement; +struct RNAServiceValidationModule; +class SearchTool; + +struct SSLCertPattern +{ + uint8_t type; + AppId appId; + uint8_t* pattern; + int pattern_size; +}; + +struct DetectorSSLCertPattern +{ + SSLCertPattern* dpattern; + DetectorSSLCertPattern* next; +}; + +struct ServiceSslConfig +{ + DetectorSSLCertPattern* DetectorSSLCertPatternList; + DetectorSSLCertPattern* DetectorSSLCnamePatternList; + SearchTool* ssl_host_matcher; + SearchTool* ssl_cname_matcher; +}; + +// DNS host pattern structure +struct DNSHostPattern +{ + uint8_t type; + AppId appId; + uint8_t* pattern; + int pattern_size; +}; + +struct DetectorDNSHostPattern +{ + DNSHostPattern* dpattern; + DetectorDNSHostPattern* next; +}; + +struct ServiceDnsConfig +{ + DetectorDNSHostPattern* DetectorDNSHostPatternList; + SearchTool* dns_host_host_matcher; +}; + +struct ServicePatternData +{ + ServicePatternData* next; + int position; + unsigned size; + RNAServiceElement* svc; +}; + +struct ServiceConfig +{ + RNAServiceValidationModule* active_service_list; // List of all services (Lua and C) + RNAServiceElement* tcp_service_list; // List of all TCP services (Lua and C) + RNAServiceElement* udp_service_list; // List of all UDP services (Lua and C) + RNAServiceElement* udp_reversed_service_list; // List of all UDP reversed services (Lua and C) + + //list nodes are RNAServiceElement*. + SF_LIST* tcp_services[RNA_SERVICE_MAX_PORT]; + SF_LIST* udp_services[RNA_SERVICE_MAX_PORT]; + SF_LIST* udp_reversed_services[RNA_SERVICE_MAX_PORT]; + + SearchTool* tcp_patterns; + ServicePatternData* tcp_pattern_data; + int tcp_pattern_count; + SearchTool* udp_patterns; + ServicePatternData* udp_pattern_data; + int udp_pattern_count; +}; + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_dcerpc.cc b/src/network_inspectors/appid/service_plugins/service_dcerpc.cc new file mode 100644 index 000000000..b5baaa5ac --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_dcerpc.cc @@ -0,0 +1,203 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_dcerpc.cc author Sourcefire Inc. + +#include "service_dcerpc.h" +#include "application_ids.h" +#include "dcerpc.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#define DCERPC_THRESHOLD 3 + +#define min(x,y) ((x)<(y) ? (x) : (y)) + +struct ServiceDCERPCData +{ + unsigned count; +}; + +static int dcerpc_init(const IniServiceAPI* const init_api); +static int dcerpc_tcp_validate(ServiceValidationArgs* args); +static int dcerpc_udp_validate(ServiceValidationArgs* args); + +static RNAServiceElement tcp_svc_element = +{ + nullptr, + &dcerpc_tcp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "dcerpc" +}; +static RNAServiceElement udp_svc_element = +{ + nullptr, + &dcerpc_udp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "udp dcerpc" +}; + +static RNAServiceValidationPort pp[] = +{ + { &dcerpc_tcp_validate, 135, IpProtocol::TCP, 0 }, + { &dcerpc_udp_validate, 135, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule dcerpc_service_mod = +{ + "DCERPC", + &dcerpc_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_DCE_RPC, 0 } +}; + +static int dcerpc_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&dcerpc_udp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int dcerpc_tcp_validate(ServiceValidationArgs* args) +{ + ServiceDCERPCData* dd; + int retval = SERVICE_INPROCESS; + int length; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + if (!size) + goto inprocess; + + dd = (ServiceDCERPCData*)dcerpc_service_mod.api->data_get(flowp, + dcerpc_service_mod.flow_data_index); + if (!dd) + { + dd = (ServiceDCERPCData*)snort_calloc(sizeof(ServiceDCERPCData)); + dcerpc_service_mod.api->data_add(flowp, dd, dcerpc_service_mod.flow_data_index, + &snort_free); + } + + while (size) + { + length = dcerpc_validate(data, size); + if (length < 0) + goto fail; + dd->count++; + if (dd->count >= DCERPC_THRESHOLD) + retval = SERVICE_SUCCESS; + data += length; + size -= length; + } + if (retval == SERVICE_SUCCESS) + { + dcerpc_service_mod.api->add_service(flowp, args->pkt, args->dir, &tcp_svc_element, + APP_ID_DCE_RPC, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + } + +inprocess: + dcerpc_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &tcp_svc_element); + return SERVICE_INPROCESS; + +fail: + dcerpc_service_mod.api->fail_service(flowp, args->pkt, args->dir, &tcp_svc_element, + dcerpc_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + +static int dcerpc_udp_validate(ServiceValidationArgs* args) +{ + ServiceDCERPCData* dd; + int retval = SERVICE_NOMATCH; + int length; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + if (!size) + goto inprocess; + + dd = (ServiceDCERPCData*)dcerpc_service_mod.api->data_get(flowp, + dcerpc_service_mod.flow_data_index); + if (!dd) + { + dd = (ServiceDCERPCData*)snort_calloc(sizeof(ServiceDCERPCData)); + dcerpc_service_mod.api->data_add(flowp, dd, dcerpc_service_mod.flow_data_index, + &snort_free); + } + + while (size) + { + length = dcerpc_validate(data, size); + if (length < 0) + goto fail; + dd->count++; + if (dd->count >= DCERPC_THRESHOLD) + retval = SERVICE_SUCCESS; + data += length; + size -= length; + } + if (retval == SERVICE_SUCCESS) + { + dcerpc_service_mod.api->add_service(flowp, args->pkt, args->dir, &udp_svc_element, + APP_ID_DCE_RPC, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + } + +inprocess: + dcerpc_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &udp_svc_element); + return SERVICE_INPROCESS; + +fail: + dcerpc_service_mod.api->fail_service(flowp, args->pkt, args->dir, &udp_svc_element, + dcerpc_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_dcerpc.h b/src/network_inspectors/appid/service_plugins/service_dcerpc.h new file mode 100644 index 000000000..6c32f455f --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_dcerpc.h @@ -0,0 +1,32 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_dcerpc.h author Sourcefire Inc. + +#ifndef SERVICE_DCERPC_H +#define SERVICE_DCERPC_H + +// Service detector for DCE/RPC + +#include "service_api.h" + +extern RNAServiceValidationModule dcerpc_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_direct_connect.cc b/src/network_inspectors/appid/service_plugins/service_direct_connect.cc new file mode 100644 index 000000000..453cd86da --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_direct_connect.cc @@ -0,0 +1,324 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_direct_connect.cc author Sourcefire Inc. + +#include "service_direct_connect.h" +#include "application_ids.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +enum CONNECTION_STATES +{ + CONN_STATE_INIT, + CONN_STATE_1, + CONN_STATE_2, + CONN_STATE_SERVICE_DETECTED, + CONN_STATE_MAX +}; + +#define MAX_PACKET_INSPECTION_COUNT 10 + +struct ServiceData +{ + uint32_t state; + uint32_t packetCount; +}; + +static int direct_connect_init(const IniServiceAPI* const init_api); +static int direct_connect_validate(ServiceValidationArgs* args); +static int validateDirectConnectTcp(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, const Packet* pkt, ServiceData* serviceData, + const AppIdConfig* pConfig); +static int validateDirectConnectUdp(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, const Packet* pkt, ServiceData* serviceData, + const AppIdConfig* pConfig); + +static RNAServiceElement svc_element = +{ + nullptr, + &direct_connect_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "direct_connect" +}; + +static RNAServiceValidationPort pp[] = +{ + { &direct_connect_validate, 411, IpProtocol::TCP, 0 }, + { &direct_connect_validate, 411, IpProtocol::UDP, 0 }, + { &direct_connect_validate, 412, IpProtocol::TCP, 0 }, + { &direct_connect_validate, 412, IpProtocol::UDP, 0 }, + { &direct_connect_validate, 413, IpProtocol::TCP, 0 }, + { &direct_connect_validate, 413, IpProtocol::UDP, 0 }, + { &direct_connect_validate, 414, IpProtocol::TCP, 0 }, + { &direct_connect_validate, 414, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +#define PATTERN1 "$Lock " +#define PATTERN2 "$MyNick " +#define PATTERN3 "HSUP ADBAS0" +#define PATTERN4 "HSUP ADBASE" +#define PATTERN5 "CSUP ADBAS0" +#define PATTERN6 "CSUP ADBASE" +#define PATTERN7 "$SR " + +RNAServiceValidationModule directconnect_service_mod = +{ + "DirectConnect", + &direct_connect_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_DIRECT_CONNECT, 0 } +}; + +static int direct_connect_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&direct_connect_validate, IpProtocol::TCP, (uint8_t*)PATTERN1, + sizeof(PATTERN1)-1, 0, "direct_connect", init_api->pAppidConfig); + init_api->RegisterPattern(&direct_connect_validate, IpProtocol::TCP, (uint8_t*)PATTERN2, + sizeof(PATTERN2)-1, 0, "direct_connect", init_api->pAppidConfig); + init_api->RegisterPattern(&direct_connect_validate, IpProtocol::TCP, (uint8_t*)PATTERN3, + sizeof(PATTERN3)-1, 0, "direct_connect", init_api->pAppidConfig); + init_api->RegisterPattern(&direct_connect_validate, IpProtocol::TCP, (uint8_t*)PATTERN4, + sizeof(PATTERN4)-1, 0, "direct_connect", init_api->pAppidConfig); + init_api->RegisterPattern(&direct_connect_validate, IpProtocol::TCP, (uint8_t*)PATTERN5, + sizeof(PATTERN5)-1, 0, "direct_connect", init_api->pAppidConfig); + init_api->RegisterPattern(&direct_connect_validate, IpProtocol::TCP, (uint8_t*)PATTERN6, + sizeof(PATTERN6)-1, 0, "direct_connect", init_api->pAppidConfig); + init_api->RegisterPattern(&direct_connect_validate, IpProtocol::UDP, (uint8_t*)PATTERN7, + sizeof(PATTERN7)-1, 0, "direct_connect", init_api->pAppidConfig); + + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&direct_connect_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int direct_connect_validate(ServiceValidationArgs* args) +{ + ServiceData* fd; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + { + directconnect_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, + &svc_element); + return SERVICE_INPROCESS; + } + + fd = (ServiceData*)directconnect_service_mod.api->data_get(flowp, + directconnect_service_mod.flow_data_index); + if (!fd) + { + fd = (ServiceData*)snort_calloc(sizeof(ServiceData)); + directconnect_service_mod.api->data_add(flowp, fd, + directconnect_service_mod.flow_data_index, &snort_free); + } + + if (flowp->proto == IpProtocol::TCP) + return validateDirectConnectTcp(data, size, args->dir, flowp, args->pkt, fd, + args->pConfig); + else + return validateDirectConnectUdp(data, size, args->dir, flowp, args->pkt, fd, + args->pConfig); +} + +static int validateDirectConnectTcp(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, const Packet* pkt, ServiceData* serviceData, + const AppIdConfig* pConfig) +{ + switch (serviceData->state) + { + case CONN_STATE_INIT: + if (size > 6 + && data[size-2] == '|' + && data[size-1] == '$') + { + if (memcmp(data, PATTERN1, sizeof(PATTERN1)-1) == 0) + { + printf("maybe first directconnect to hub detected\n"); + serviceData->state = CONN_STATE_1; + goto inprocess; + } + + if (memcmp(data, PATTERN2, sizeof(PATTERN2)-1) == 0) + { + printf("maybe first dc connect between peers detected\n"); + serviceData->state = CONN_STATE_2; + goto inprocess; + } + } + + if (size >= 11) + { + if (memcmp(data, PATTERN3, sizeof(PATTERN3)-1) == 0 + || memcmp(data, PATTERN4, sizeof(PATTERN4)-1) == 0 + || memcmp(data, PATTERN5, sizeof(PATTERN5)-1) == 0 + || memcmp(data, PATTERN6, sizeof(PATTERN6)-1) == 0) + { + goto success; + } + } + break; + + case CONN_STATE_1: + printf ("ValidateDirectConnectTcp(): state 1 size %d\n", size); + if (size >= 11) + { + if (memcmp(data, PATTERN3, sizeof(PATTERN3)-1) == 0 + || memcmp(data, PATTERN4, sizeof(PATTERN4)-1) == 0 + || memcmp(data, PATTERN5, sizeof(PATTERN5)-1) == 0 + || memcmp(data, PATTERN6, sizeof(PATTERN6)-1) == 0) + { + printf("found directconnect HSUP ADBAS E in second packet\n"); + goto success; + } + } + + if (size > 6) + { + if ((data[0] == '$' || data[0] == '<') + && data[size-2] == '|' + && data[size-1] == '$') + { + goto success; + } + else + { + goto inprocess; + } + } + break; + + case CONN_STATE_2: + if (size > 6) + { + if (data[0] == '$' && data[size-2] == '|' && data[size-1] == '$') + { + goto success; + } + else + { + goto inprocess; + } + } + break; + + case CONN_STATE_SERVICE_DETECTED: + goto success; + } + +inprocess: + serviceData->packetCount++; + if (serviceData->packetCount >= MAX_PACKET_INSPECTION_COUNT) + goto fail; + + directconnect_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +success: + if (dir != APP_ID_FROM_RESPONDER) + { + serviceData->state = CONN_STATE_SERVICE_DETECTED; + goto inprocess; + } + + directconnect_service_mod.api->add_service(flowp, pkt, dir, &svc_element, + APP_ID_DIRECT_CONNECT, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + directconnect_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + directconnect_service_mod.flow_data_index, pConfig); + return SERVICE_NOMATCH; +} + +static int validateDirectConnectUdp(const uint8_t* data, uint16_t size, const int dir, + AppIdData* flowp, const Packet* pkt, ServiceData* serviceData, + const AppIdConfig* pConfig) +{ + if (dir == APP_ID_FROM_RESPONDER && serviceData->state == CONN_STATE_SERVICE_DETECTED) + { + goto reportSuccess; + } + + if (size > 58) + { + if (memcmp(data, PATTERN7, sizeof(PATTERN7)-1) == 0 + && data[size-3] == ')' + && data[size-2] == '|' + && data[size-1] == '$') + { + goto success; + } + serviceData->state += 1; + + if (serviceData->state != CONN_STATE_SERVICE_DETECTED) + goto inprocess; + else + goto fail; + } + +inprocess: + serviceData->packetCount++; + if (serviceData->packetCount >= MAX_PACKET_INSPECTION_COUNT) + goto fail; + + directconnect_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +success: + if (dir != APP_ID_FROM_RESPONDER) + { + serviceData->state = CONN_STATE_SERVICE_DETECTED; + goto inprocess; + } + +reportSuccess: + directconnect_service_mod.api->add_service(flowp, pkt, dir, &svc_element, + APP_ID_DIRECT_CONNECT, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + directconnect_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + directconnect_service_mod.flow_data_index, pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_direct_connect.h b/src/network_inspectors/appid/service_plugins/service_direct_connect.h new file mode 100644 index 000000000..6bf2e2ba0 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_direct_connect.h @@ -0,0 +1,32 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_direct_connect.h author Sourcefire Inc. + +#ifndef SERVICE_DIRECTCONNECT_H +#define SERVICE_DIRECTCONNECT_H + +// Service detector for Direct Connect + +#include "service_api.h" + +extern RNAServiceValidationModule directconnect_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_flap.cc b/src/network_inspectors/appid/service_plugins/service_flap.cc new file mode 100644 index 000000000..562b754f8 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_flap.cc @@ -0,0 +1,242 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_flap.cc author Sourcefire Inc. + +#include "service_flap.h" +#include "application_ids.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#define FLAP_PORT 5190 + +enum FLAPState +{ + FLAP_STATE_ACK, + FLAP_STATE_COOKIE +}; + +#define FNAC_SIGNON 0x0017 +#define FNAC_GENERIC 0x0001 +#define FNAC_SUB_SIGNON_REPLY 0x0007 +#define FNAC_SUB_SERVER_READY 0x0003 + +struct ServiceFLAPData +{ + FLAPState state; +}; + +#pragma pack(1) + +struct FLAPFNACSignOn +{ + uint16_t len; +}; + +struct FLAPFNAC +{ + uint16_t family; + uint16_t subtype; + uint16_t flags; + uint32_t id; +}; + +struct FLAPTLV +{ + uint16_t subtype; + uint16_t len; +}; + +struct FLAPHeader +{ + uint8_t start; + uint8_t type; + uint16_t seq; + uint16_t len; +}; + +#pragma pack() + +static int flap_init(const IniServiceAPI* const init_api); +static int flap_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &flap_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "flap" +}; + +static RNAServiceValidationPort pp[] = +{ + { &flap_validate, 5190, IpProtocol::TCP, 0 }, + { &flap_validate, 9898, IpProtocol::TCP, 0 }, + { &flap_validate, 4443, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule flap_service_mod = +{ + "FLAP", + &flap_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static uint8_t FLAP_PATTERN[] = { 0x2A, 0x01 }; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_AOL_INSTANT_MESSENGER, 0 } +}; + +static int flap_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&flap_validate, IpProtocol::TCP, FLAP_PATTERN, + sizeof(FLAP_PATTERN), 0, "flap", init_api->pAppidConfig); + //unsigned i; + for (unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&flap_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int flap_validate(ServiceValidationArgs* args) +{ + ServiceFLAPData* sf; + const uint8_t* data = args->data; + const FLAPHeader* hdr = (const FLAPHeader*)args->data; + const FLAPFNAC* ff; + const FLAPTLV* tlv; + AppIdData* flowp = args->flowp; + uint16_t size = args->size; + uint16_t len; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + sf = (ServiceFLAPData*)flap_service_mod.api->data_get(flowp, flap_service_mod.flow_data_index); + if (!sf) + { + sf = (ServiceFLAPData*)snort_calloc(sizeof(ServiceFLAPData)); + flap_service_mod.api->data_add(flowp, sf, flap_service_mod.flow_data_index, &snort_free); + sf->state = FLAP_STATE_ACK; + } + + switch (sf->state) + { + case FLAP_STATE_ACK: + sf->state = FLAP_STATE_COOKIE; + if (size < sizeof(FLAPHeader)) + goto fail; + if (hdr->start != 0x2A) + goto fail; + if (hdr->type != 0x01) + goto fail; + if (ntohs(hdr->len) != 4) + goto fail; + if (size - sizeof(FLAPHeader) != 4) + goto fail; + if (ntohl(*((uint32_t*)(data + sizeof(FLAPHeader)))) != 0x00000001) + goto fail; + goto inprocess; + case FLAP_STATE_COOKIE: + if (size < sizeof(FLAPHeader) + sizeof(FLAPFNAC)) + goto fail; + if (hdr->start != 0x2A) + goto fail; + if ((uint16_t)ntohs(hdr->len) != (uint16_t)(size - sizeof(FLAPHeader))) + goto fail; + if (hdr->type == 0x02) + { + ff = (FLAPFNAC*)(data + sizeof(FLAPHeader)); + if (ntohs(ff->family) == FNAC_SIGNON) + { + FLAPFNACSignOn* ffs = (FLAPFNACSignOn*)((uint8_t*)ff + sizeof(FLAPFNAC)); + + if (ntohs(ff->subtype) != FNAC_SUB_SIGNON_REPLY) + goto fail; + if ((uint16_t)ntohs(ffs->len) != (uint16_t)(size - + (sizeof(FLAPHeader) + + sizeof(FLAPFNAC) + + sizeof(FLAPFNACSignOn)))) + goto fail; + } + else if (ntohs(ff->family) == FNAC_GENERIC) + { + if (ntohs(ff->subtype) != FNAC_SUB_SERVER_READY) + goto fail; + } + else + goto fail; + goto success; + } + if (hdr->type == 0x04) + { + data += sizeof(FLAPHeader); + size -= sizeof(FLAPHeader); + while (size >= sizeof(FLAPTLV)) + { + tlv = (FLAPTLV*)data; + data += sizeof(FLAPTLV); + size -= sizeof(FLAPTLV); + len = ntohs(tlv->len); + if (size < len) + goto fail; + size -= len; + data += len; + } + if (size) + goto fail; + goto success; + } + goto fail; + } + +fail: + flap_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + flap_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + +success: + flap_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_AOL_INSTANT_MESSENGER, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +inprocess: + flap_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_flap.h b/src/network_inspectors/appid/service_plugins/service_flap.h new file mode 100644 index 000000000..63577e21a --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_flap.h @@ -0,0 +1,32 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_flap.h author Sourcefire Inc. + +#ifndef SERVICE_FLAP_H +#define SERVICE_FLAP_H + +// Service detector for FLAP + +#include "service_api.h" + +extern RNAServiceValidationModule flap_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_ftp.cc b/src/network_inspectors/appid/service_plugins/service_ftp.cc new file mode 100644 index 000000000..99bca70c3 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_ftp.cc @@ -0,0 +1,1322 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_ftp.cc author Sourcefire Inc. + +#include "service_ftp.h" + +#include "main/snort_debug.h" +#include "sfip/sf_ip.h" +#include "target_based/snort_protocols.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "appid_api.h" +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_base.h" +#include "service_util.h" + +// FIXIT-H This needs to use a real SFIP function +static SFIP_RET sfip_convert_ip_text_to_binary(const int, const char*, void*) +{ return SFIP_SUCCESS; } + +#define FTP_PORT 21 +/*#define RNA_FTP_EXPECTED_ON_PORT 1 */ + +enum FTPState +{ + FTP_STATE_CONNECTION, + FTP_STATE_LOGIN, + FTP_STATE_PASSWORD, + FTP_STATE_ACCOUNT, + FTP_STATE_CONNECTION_ERROR, + FTP_STATE_MONITOR +}; + +enum FTPReplyState +{ + FTP_REPLY_BEGIN, + FTP_REPLY_MULTI, + FTP_REPLY_MID +}; + +enum FTPCmd +{ + FTP_CMD_NONE, + FTP_CMD_PORT_EPRT, + FTP_CMD_PASV_EPSV +}; + +#define MAX_STRING_SIZE 64 +struct ServiceFTPData +{ + FTPState state; + FTPReplyState rstate; + int code; + char vendor[MAX_STRING_SIZE]; + char version[MAX_STRING_SIZE]; + FTPCmd cmd; + sfip_t address; + uint16_t port; +}; + +#pragma pack(1) + +struct ServiceFTPCode +{ + uint8_t code[3]; + uint8_t sp; +}; + +#pragma pack() + +static int ftp_init(const IniServiceAPI* const init_api); +static int ftp_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &ftp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "ftp" +}; + +static RNAServiceValidationPort pp[] = +{ + { &ftp_validate, FTP_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule ftp_service_mod = +{ + "FTP", + &ftp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +#define FTP_PATTERN1 "220 " +#define FTP_PATTERN2 "220-" +#define FTP_PATTERN3 "FTP" +#define FTP_PATTERN4 "ftp" + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_FTP_CONTROL, APPINFO_FLAG_SERVICE_ADDITIONAL }, + { APP_ID_FTP_ACTIVE, APPINFO_FLAG_SERVICE_ADDITIONAL }, + { APP_ID_FTP_PASSIVE, APPINFO_FLAG_SERVICE_ADDITIONAL }, + { APP_ID_FTPS, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int16_t ftp_data_app_id = 0; + +static int ftp_init(const IniServiceAPI* const init_api) +{ + ftp_data_app_id = AddProtocolReference("ftp-data"); + + init_api->RegisterPattern(&ftp_validate, IpProtocol::TCP, (uint8_t*)FTP_PATTERN1, + sizeof(FTP_PATTERN1)-1, 0, "ftp", init_api->pAppidConfig); + init_api->RegisterPattern(&ftp_validate, IpProtocol::TCP, (uint8_t*)FTP_PATTERN2, + sizeof(FTP_PATTERN2)-1, 0, "ftp", init_api->pAppidConfig); + init_api->RegisterPattern(&ftp_validate, IpProtocol::TCP, (uint8_t*)FTP_PATTERN3, + sizeof(FTP_PATTERN3)-1, -1, "ftp", init_api->pAppidConfig); + init_api->RegisterPattern(&ftp_validate, IpProtocol::TCP, (uint8_t*)FTP_PATTERN4, + sizeof(FTP_PATTERN4)-1, -1, "ftp", init_api->pAppidConfig); + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&ftp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static inline void CopyVendorString(ServiceFTPData* fd, const uint8_t* vendor, unsigned int + vendorLen) +{ + unsigned int copyLen = vendorLen < sizeof(fd->vendor)-1 ? vendorLen : sizeof(fd->vendor)-1; + memcpy(fd->vendor, vendor, copyLen); + fd->vendor[copyLen] = '\0'; +} + +static inline void CopyVersionString(ServiceFTPData* fd, const uint8_t* version, unsigned int + versionLen) +{ + unsigned int copyLen = versionLen < sizeof(fd->version)-1 ? versionLen : sizeof(fd->version)-1; + while (copyLen > 0 && !isalnum(version[copyLen-1])) + { + copyLen--; + } + memcpy(fd->version, version, copyLen); + fd->version[copyLen] = '\0'; +} + +enum VVP_PARSE_ENUM +{ + VVP_PARSE_HP = 1, + VVP_PARSE_FILEZILLA = 2, + VVP_PARSE_MS = 3, + VVP_PARSE_WU = 4, + VVP_PARSE_PRO_FTPD = 5, + VVP_PARSE_PURE_FTPD = 6, + VVP_PARSE_NC_FTPD = 7 +}; + +static int VendorVersionParse(const uint8_t* data, uint16_t init_offset, + uint16_t offset, ServiceFTPData* fd, + const uint8_t* vendorCandidate, unsigned int vendorCandidateLen, + const uint8_t* optionalVersion, unsigned int versionLen, + VVP_PARSE_ENUM vvp_parse_type) +{ + const unsigned char* p; + const unsigned char* end; + const unsigned char* ver; + unsigned int verlen; + int ret = 0; // no match + + p = &data[init_offset]; + end = &data[offset-1]; + /* Search for the vendorCandidate string */ + if (vvp_parse_type == VVP_PARSE_WU) + { + /* Search for the version string */ + if ((p = service_strstr(p, end-p, optionalVersion, versionLen))) + { + /* If we like the version we will just assign the vendor */ + CopyVendorString(fd, vendorCandidate, vendorCandidateLen); + ret = 1; + + /* Found the version string. Move just past the version string */ + ver = p + versionLen; + p = ver; + verlen = 0; + while (p < end && *p && *p != ' ' ) + { + p++; verlen++; + } + CopyVersionString(fd, ver, verlen); + } + } + else if ((p=service_strstr(p, end-p, vendorCandidate, vendorCandidateLen))) + { + /* Found vendorCandidate string */ + CopyVendorString(fd, vendorCandidate, vendorCandidateLen); + ret = 1; + /* Move just past the vendor string */ + p += vendorCandidateLen; + if (optionalVersion) + { + /* Search for the version string */ + if ((p = service_strstr(p, end-p, optionalVersion, versionLen))) + { + /* Found the version string. Move just past the version string */ + ver = p + versionLen; + p = ver; + verlen = 0; + switch (vvp_parse_type) + { + case VVP_PARSE_HP: + while (p < end && *p && (isalnum(*p) || *p == '.')) + { + p++; verlen++; + } + break; + case VVP_PARSE_FILEZILLA: + while (p < end && *p && (isalnum(*p) || *p == '.' || *p == ' ')) + { + p++; verlen++; + } + break; + case VVP_PARSE_MS: + while (p < end && *p && *p != ')' ) + { + p++; verlen++; + } + break; + case VVP_PARSE_PRO_FTPD: + while (p < end && *p && *p != ' ' ) + { + p++; verlen++; + } + break; + default: + break; + } + CopyVersionString(fd, ver, verlen); + } + } + } + return ret; +} + +static int CheckVendorVersion(const uint8_t* data, uint16_t init_offset, + uint16_t offset, ServiceFTPData* fd, VVP_PARSE_ENUM vvp_parse_type) +{ + static const unsigned char ven_hp[] = "Hewlett-Packard FTP Print Server"; + static const unsigned char ver_hp[] = "Version "; + static const unsigned char ven_fzilla[] = "FileZilla Server"; + static const unsigned char ver_fzilla[] = "version "; + static const unsigned char ven_ms[] = "Microsoft FTP Service"; + static const unsigned char ver_ms[] = "(Version "; + static const unsigned char ven_wu[] = "wu"; + static const unsigned char ver_wu[] = "(Version wu-"; + static const unsigned char ven_proftpd[] = "ProFTPD"; + static const unsigned char ven_pureftpd[] = "Pure-FTPd"; + static const unsigned char ven_ncftpd[] = "NcFTPd"; + + if (!data || init_offset >= offset) + return 0; + + switch (vvp_parse_type) + { + case VVP_PARSE_HP: + return VendorVersionParse(data, init_offset, offset,fd, + ven_hp, sizeof(ven_hp)-1, + ver_hp, sizeof(ver_hp)-1, + VVP_PARSE_HP); + case VVP_PARSE_FILEZILLA: + return VendorVersionParse(data, init_offset, offset,fd, + ven_fzilla, sizeof(ven_fzilla)-1, + ver_fzilla, sizeof(ver_fzilla)-1, + VVP_PARSE_FILEZILLA); + case VVP_PARSE_MS: + return VendorVersionParse(data, init_offset, offset,fd, + ven_ms, sizeof(ven_ms)-1, + ver_ms, sizeof(ver_ms)-1, + VVP_PARSE_MS); + case VVP_PARSE_WU: + return VendorVersionParse(data, init_offset, offset,fd, + ven_wu, sizeof(ven_wu)-1, + ver_wu, sizeof(ver_wu)-1, + VVP_PARSE_WU); + case VVP_PARSE_PRO_FTPD: + return VendorVersionParse(data, init_offset, offset,fd, + ven_proftpd, sizeof(ven_proftpd)-1, + (const uint8_t*)" ", sizeof(" ")-1, + VVP_PARSE_PRO_FTPD); + case VVP_PARSE_PURE_FTPD: + return VendorVersionParse(data, init_offset, offset,fd, + ven_pureftpd, sizeof(ven_pureftpd)-1, + nullptr, 0, + VVP_PARSE_PURE_FTPD); + case VVP_PARSE_NC_FTPD: + return VendorVersionParse(data, init_offset, offset,fd, + ven_ncftpd, sizeof(ven_ncftpd)-1, + nullptr, 0, + VVP_PARSE_NC_FTPD); + } + return 0; +} + +static int ftp_validate_reply(const uint8_t* data, uint16_t* offset, + uint16_t size, ServiceFTPData* fd) +{ + const ServiceFTPCode* code_hdr; + int tmp; + FTPReplyState tmp_state; + + for (; *offset < size; (*offset)++) + { + /* Trim any blank lines (be a little tolerant) */ + for (; *offsetrstate) + { + case FTP_REPLY_BEGIN: + if (size - (*offset) < (int)sizeof(ServiceFTPCode)) + return -1; + + code_hdr = (ServiceFTPCode*)(data + *offset); + + if (code_hdr->sp == '-') + fd->rstate = FTP_REPLY_MULTI; + else if (code_hdr->sp != ' ' && code_hdr->sp != 0x09) + return -1; + + if (code_hdr->code[0] < '1' || code_hdr->code[0] > '5') + return -1; + fd->code = (code_hdr->code[0] - '0') * 100; + + if (code_hdr->code[1] < '0' || code_hdr->code[1] > '5') + return -1; + fd->code += (code_hdr->code[1] - '0') * 10; + + if (!isdigit(code_hdr->code[2])) + return -1; + fd->code += code_hdr->code[2] - '0'; + + *offset += sizeof(ServiceFTPCode); + tmp_state = fd->rstate; + + if (!fd->vendor[0] && !fd->version[0]) + { + if (fd->code == 220) + { + // These vendor strings are present on the first "220" whether that is the + // "220-" or "220 " + if (!CheckVendorVersion(data, *offset, size, fd, VVP_PARSE_MS) && + !CheckVendorVersion(data, *offset, size, fd, VVP_PARSE_WU) && + !CheckVendorVersion(data, *offset, size, fd, VVP_PARSE_PRO_FTPD) && + !CheckVendorVersion(data, *offset, size, fd, VVP_PARSE_PURE_FTPD) && + !CheckVendorVersion(data, *offset, size, fd, VVP_PARSE_NC_FTPD) && + !CheckVendorVersion(data, *offset, size, fd, VVP_PARSE_FILEZILLA) + ) + { + /* Look for (Vendor Version: or (Vendor Version) */ + const unsigned char* end; + const unsigned char* p; + const unsigned char* ven; + const unsigned char* ver; + end = &data[size-1]; + for (p=&data[*offset]; p=end || !(*p)) + { + for (p=ver; pcode == 230) + { + // These vendor strings are present on the first "230" whether that is the + // "230-" or "230 " + CheckVendorVersion(data, *offset, size, fd, VVP_PARSE_HP); + } + } + + fd->rstate = FTP_REPLY_MID; + for (; *offset < size; (*offset)++) + { + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + } + if (data[*offset] != 0x0A) + return -1; + fd->rstate = tmp_state; + break; + } + if (data[*offset] == 0x0A) + { + fd->rstate = tmp_state; + break; + } + } + if (fd->rstate == FTP_REPLY_MID) + return -1; + break; + case FTP_REPLY_MULTI: + if (size - *offset < (int)sizeof(ServiceFTPCode)) + { + fd->rstate = FTP_REPLY_MID; + for (; *offset < size; (*offset)++) + { + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + } + if (data[*offset] != 0x0A) + return -1; + fd->rstate = FTP_REPLY_MULTI; + break; + } + if (data[*offset] == 0x0A) + { + fd->rstate = FTP_REPLY_MULTI; + break; + } + } + if (fd->rstate == FTP_REPLY_MID) + return -1; + } + else + { + code_hdr = (ServiceFTPCode*)(data + *offset); + if (size - (*offset) >= (int)sizeof(ServiceFTPCode) && + (code_hdr->sp == ' ' || code_hdr->sp == 0x09) && + code_hdr->code[0] >= '1' && code_hdr->code[0] <= '5' && + code_hdr->code[1] >= '1' && code_hdr->code[1] <= '5' && + isdigit(code_hdr->code[2])) + { + tmp = (code_hdr->code[0] - '0') * 100; + tmp += (code_hdr->code[1] - '0') * 10; + tmp += code_hdr->code[2] - '0'; + if (tmp == fd->code) + { + *offset += sizeof(ServiceFTPCode); + fd->rstate = FTP_REPLY_BEGIN; + } + } + tmp_state = fd->rstate; + fd->rstate = FTP_REPLY_MID; + for (; *offset < size; (*offset)++) + { + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + } + if (data[*offset] != 0x0A) + return -1; + fd->rstate = tmp_state; + break; + } + if (data[*offset] == 0x0A) + { + fd->rstate = tmp_state; + break; + } + } + if (fd->rstate == FTP_REPLY_MID) + return -1; + } + break; + default: + return -1; + } + if (fd->rstate == FTP_REPLY_BEGIN) + { + for (; *offset < size; (*offset)++) + { + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + if (data[*offset] != 0x0A) + return -1; + } + else if (!isspace(data[*offset])) + break; + } + return fd->code; + } + } + return 0; +} + +static inline int _ftp_decode_number32(const uint8_t** data, const uint8_t* end, uint8_t delimiter, + uint32_t* number) +{ + const uint8_t* local_data; + uint32_t local_number = 0; + for (local_data = *data; local_data < end && *local_data == ' '; local_data++) + ; + if (local_data < end && *local_data == delimiter) + { + *number = 0; + return -1; + } + while (local_data < end && *local_data != delimiter) + { + if (!isdigit(*local_data)) + { + *number = 0; + return -1; + } + local_number *= 10; + local_number += *local_data - '0'; + local_data++; + } + if (local_data >= end || *local_data != delimiter) + { + *number = 0; + return -1; + } + *number = local_number; + *data = local_data+1; + return 0; +} + +static int ftp_decode_octet(const uint8_t** data, const uint8_t* end, uint8_t delimiter, + uint32_t* number) +{ + if (_ftp_decode_number32(data, end, delimiter, number) == -1) + return -1; + if (*number > 255) + { + *number = 0; + return -1; + } + return 0; +} + +static int ftp_decode_port_number(const uint8_t** data, const uint8_t* end, uint8_t delimiter, + uint32_t* number) +{ + if (_ftp_decode_number32(data, end, delimiter, number) == -1) + return -1; + if (*number > 65535) + { + *number = 0; + return -1; + } + return 0; +} + +static int ftp_validate_pasv(const uint8_t* data, uint16_t size, + uint32_t* address, uint16_t* port) +{ + const uint8_t* end; + uint32_t tmp; + + *address = 0; + *port = 0; + + end = data + size; + data += sizeof(ServiceFTPCode); + + for (; data= end) + return 1; + + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + *address = tmp << 24; + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + *address += tmp << 16; + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + *address += tmp << 8; + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + *address += tmp; + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + *port = (uint16_t)(tmp << 8); + if (ftp_decode_octet(&data, end, ')', &tmp)) + return -1; + *port += tmp; + return 0; +} + +static int ftp_validate_epsv(const uint8_t* data, uint16_t size, + uint16_t* port) +{ + const uint8_t* end; + uint8_t delimiter; + + *port = 0; + + end = data + size; + data += sizeof(ServiceFTPCode); + + for (; data= end) + return 1; + + delimiter = *data++; + if (data >= end) + return 1; + + for (; data= end) + return 1; + + for (; data= end) + return 1; + + while (data < end && *data != delimiter) + { + if (!isdigit(*data)) + return -1; + *port *= 10; + *port += *data - '0'; + data++; + } + + return 0; +} + +static int ftp_validate_port(const uint8_t* data, uint16_t size, + sfip_t* address, uint16_t* port) +{ + const uint8_t* end; + const uint8_t* p; + uint32_t tmp; + uint32_t addr; + uint32_t addr2; + + memset(address,0,sizeof(sfip_t)); + *port = 0; + + end = data + size; + + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + addr = tmp << 24; + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + addr += tmp << 16; + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + addr += tmp << 8; + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + addr += tmp; + addr2 = htonl(addr); // make it network order before calling sfip_set_raw() + sfip_set_raw(address, &addr2, AF_INET); + + if (ftp_decode_octet(&data, end, ',', &tmp)) + return -1; + *port = (uint16_t)(tmp << 8); + p = end - 1; + if (p > data) + { + if (*p == 0x0a) + { + p--; + if (*p == 0x0d) + { + if (ftp_decode_octet(&data, end, 0x0d, &tmp)) + return -1; + *port += tmp; + return 0; + } + } + } + if (ftp_decode_octet(&data, end, 0x0a, &tmp)) + return -1; + *port += tmp; + return 0; +} + +/* RFC 2428 support */ +struct addr_family_map +{ + uint16_t eprt_fam; + uint16_t sfaddr_fam; +}; + +static addr_family_map RFC2428_known_address_families[] = +{ + { 1, AF_INET }, + { 2, AF_INET6 }, + { 0, 0 } +}; + +static int ftp_validate_eprt(const uint8_t* data, uint16_t size, + sfip_t* address, uint16_t* port) +{ + int index; + int addrFamilySupported = 0; + uint8_t delimiter; + const uint8_t* end; + uint32_t tmp; + char tmp_str[INET6_ADDRSTRLEN+1]; + + memset(address,0,sizeof(sfip_t)); + *port = 0; + + end = data + size; + + delimiter = *data++; // all delimiters will match this one. + if (ftp_decode_octet(&data, end, delimiter, &tmp)) + return -1; + + // Look up the address family in the table. + for (index = 0; !addrFamilySupported && RFC2428_known_address_families[index].eprt_fam != 0; + index++) + { + if ( RFC2428_known_address_families[index].eprt_fam == (uint16_t)tmp ) + { + addrFamilySupported = RFC2428_known_address_families[index].sfaddr_fam; + } + } + if (!addrFamilySupported) // not an ipv4 or ipv6 address being provided. + return -1; + + for (index = 0; + index < INET6_ADDRSTRLEN && data < end && *data != delimiter; + index++, data++ ) + { + tmp_str[index] = *data; + } + tmp_str[index] = '\0'; // make the copied portion be nul terminated. + + if (sfip_convert_ip_text_to_binary(addrFamilySupported, tmp_str, &address) != SFIP_SUCCESS) + return -1; + + address->family = addrFamilySupported; + + data++; // skip the delimiter at the end of the address substring. + if (ftp_decode_port_number(&data, end, delimiter, &tmp)) // an error is returned if port was + // greater than 65535 + return -1; + + *port = (uint16_t)tmp; + return 0; +} + +static inline void WatchForCommandResult(ServiceFTPData* fd, AppIdData* flowp, FTPCmd command) +{ + if (fd->state != FTP_STATE_MONITOR) + { + setAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED | APPID_SESSION_CONTINUE); + fd->state = FTP_STATE_MONITOR; + } + fd->cmd = command; +} + +static inline void InitializeDataSession(AppIdData* flowp,AppIdData* fp) +{ + unsigned encryptedFlag = getAppIdFlag(flowp, APPID_SESSION_ENCRYPTED | + APPID_SESSION_DECRYPTED); + if (encryptedFlag == APPID_SESSION_ENCRYPTED) + { + fp->serviceAppId = APP_ID_FTPSDATA; + } + else + { + encryptedFlag = 0; // change (APPID_SESSION_ENCRYPTED | APPID_SESSION_DECRYPTED) case to + // zeroes. + fp->serviceAppId = APP_ID_FTP_DATA; + } + PopulateExpectedFlow(flowp, fp, APPID_SESSION_IGNORE_ID_FLAGS | encryptedFlag); +} + +static int ftp_validate(ServiceValidationArgs* args) +{ + static const char FTP_PASV_CMD[] = "PASV"; + static const char FTP_EPSV_CMD[] = "EPSV"; + static const char FTP_PORT_CMD[] = "PORT "; + static const char FTP_EPRT_CMD[] = "EPRT "; + ServiceFTPData* fd; + uint16_t offset; + uint16_t init_offset; + int code; + int code_index; + uint32_t address; + uint16_t port; + AppIdData* fp; + int retval = SERVICE_INPROCESS; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + + //ignore packets while encryption is on in explicit mode. In future, this will be changed + //to direct traffic to SSL detector to extract payload from certs. This will require + // manintaining + //two detector states at the same time. + if (getAppIdFlag(flowp, APPID_SESSION_ENCRYPTED)) + { + if (!getAppIdFlag(flowp, APPID_SESSION_DECRYPTED)) + { + goto inprocess; + } + } + + fd = (ServiceFTPData*)ftp_service_mod.api->data_get(flowp, ftp_service_mod.flow_data_index); + if (!fd) + { + fd = (ServiceFTPData*)snort_calloc(sizeof(ServiceFTPData)); + ftp_service_mod.api->data_add(flowp, fd, ftp_service_mod.flow_data_index, &snort_free); + fd->state = FTP_STATE_CONNECTION; + fd->rstate = FTP_REPLY_BEGIN; + fd->cmd = FTP_CMD_NONE; + } + + if (dir != APP_ID_FROM_RESPONDER) + { + if (data[size-1] != 0x0a) + goto inprocess; + + if (size > sizeof(FTP_PORT_CMD)-1 && + strncasecmp((char*)data, FTP_PORT_CMD, sizeof(FTP_PORT_CMD)-1) == 0) + { + if (ftp_validate_port(data+(sizeof(FTP_PORT_CMD)-1), + size-(sizeof(FTP_PORT_CMD)-1), + &fd->address, &fd->port) == 0) + { + WatchForCommandResult(fd, flowp, FTP_CMD_PORT_EPRT); + } + } + else if (size > sizeof(FTP_EPRT_CMD)-1 && + strncasecmp((char*)data, FTP_EPRT_CMD, sizeof(FTP_EPRT_CMD)-1) == 0) + { + if (ftp_validate_eprt(data+(sizeof(FTP_EPRT_CMD)-1), + size-(sizeof(FTP_EPRT_CMD)-1), + &fd->address, &fd->port) == 0) + { + WatchForCommandResult(fd, flowp, FTP_CMD_PORT_EPRT); + } + } + else if ( size > sizeof(FTP_PASV_CMD)-1 && + ( strncasecmp((char*)data, FTP_PASV_CMD, sizeof(FTP_PASV_CMD)-1) == 0 || + strncasecmp((char*)data, FTP_EPSV_CMD, sizeof(FTP_EPSV_CMD)-1) == 0 ) + ) + { + WatchForCommandResult(fd, flowp, FTP_CMD_PASV_EPSV); + } + goto inprocess; + } + + offset = 0; + while (offset < size) + { + init_offset = offset; + if ((code=ftp_validate_reply(data, &offset, size, fd)) < 0) + goto fail; + if (!code) + goto inprocess; + + switch (fd->state) + { + case FTP_STATE_CONNECTION: + switch (code) + { + case 120: /*system will be ready in nn minutes */ + break; + case 220: /*service ready for new user */ + fd->state = FTP_STATE_LOGIN; + break; + case 110: /* restart mark reply */ + case 125: /* connection is open start transferring file */ + case 150: /* Opening command */ + case 200: /*command ok */ + case 202: /*command not implemented */ + case 211: /* system status */ + case 212: /* directory status */ + case 213: /* file status */ + case 214: /* help message */ + case 215: /* name system type */ + case 225: /* data connection open */ + case 226: /* Transfer complete */ + case 227: /*entering passive mode */ + case 230: /*user loggined */ + case 250: /* CWD command successful */ + case 257: /* PATHNAME created */ + case 331: /* login ok need password */ + case 332: /*new account for login */ + case 350: /*requested file action pending futher information */ + case 450: /*requested file action not taken */ + case 451: /*requested file action aborted */ + case 452: /*requested file action not taken not enough space */ + case 500: /*syntax error */ + case 501: /*not recognozed */ + case 502: /*not recognozed */ + case 503: /*bad sequence of commands */ + case 504: /*command not implemented */ + case 530: /*login incorrect */ + case 532: /*new account for storing file */ + case 550: /*requested action not taken */ + case 551: /*requested action aborted :page type unknown */ + case 552: /*requested action aborted */ + case 553: /*requested action not taken file name is not allowed */ + setAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED | APPID_SESSION_CONTINUE); + fd->state = FTP_STATE_MONITOR; + break; + case 221: /*good bye */ + case 421: /*service not available closing connection */ + fd->state = FTP_STATE_CONNECTION_ERROR; + break; + default: + goto fail; + } + break; + case FTP_STATE_LOGIN: + code_index = code / 100; + switch (code_index) + { + case 2: + switch (code) + { + case 221: + fd->state = FTP_STATE_CONNECTION_ERROR; + break; + case 230: + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + fd->state = FTP_STATE_MONITOR; + retval = SERVICE_SUCCESS; + break; + case 234: + { + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + retval = SERVICE_SUCCESS; + /* + // we do not set the state to FTP_STATE_MONITOR here because we don't know + // if there will be SSL decryption to allow us to see what we are interested in. + // Let the WatchForCommandResult() usage elsewhere take care of it. + */ + setAppIdFlag(flowp, + APPID_SESSION_CONTINUE | + APPID_SESSION_ENCRYPTED | + APPID_SESSION_STICKY_SERVICE); + } + break; + default: + break; + } + break; + case 3: + switch (code) + { + case 331: + fd->state = FTP_STATE_PASSWORD; + break; + case 332: + fd->state = FTP_STATE_ACCOUNT; + break; + default: + break; + } + break; + case 4: + switch (code) + { + case 421: + fd->state = FTP_STATE_CONNECTION_ERROR; + break; + case 431: + break; + default: + goto fail; + } + break; + case 5: + break; + default: + goto fail; + } + break; + case FTP_STATE_PASSWORD: + code_index = code / 100; + switch (code_index) + { + case 2: + switch (code) + { + case 221: + fd->state = FTP_STATE_CONNECTION_ERROR; + break; + case 202: + case 230: + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + fd->state = FTP_STATE_MONITOR; + retval = SERVICE_SUCCESS; + default: + break; + } + break; + case 3: + switch (code) + { + case 332: + fd->state = FTP_STATE_ACCOUNT; + break; + default: + break; + } + break; + case 4: + switch (code) + { + case 421: + fd->state = FTP_STATE_CONNECTION_ERROR; + break; + default: + goto fail; + } + break; + case 5: + switch (code) + { + case 500: + case 501: + case 503: + case 530: + fd->state = FTP_STATE_LOGIN; + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case FTP_STATE_ACCOUNT: + code_index = code / 100; + switch (code_index) + { + case 2: + switch (code) + { + case 202: + case 230: + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + fd->state = FTP_STATE_MONITOR; + retval = SERVICE_SUCCESS; + default: + break; + } + break; + case 3: + switch (code) + { + case 332: + fd->state = FTP_STATE_ACCOUNT; + break; + default: + break; + } + break; + case 4: + switch (code) + { + case 421: + fd->state = FTP_STATE_CONNECTION_ERROR; + break; + default: + goto fail; + } + break; + case 5: + switch (code) + { + case 500: + case 501: + case 503: + case 530: + fd->state = FTP_STATE_LOGIN; + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case FTP_STATE_MONITOR: // looking for the DATA channel info in the result + switch (code) + { + case 227: + { + code = ftp_validate_pasv(data + init_offset, + (uint16_t)(offset-init_offset), + &address, &port); + if (!code) + { + sfip_t ip; + const sfip_t* sip; + const sfip_t* dip; + uint32_t addr; + + dip = pkt->ptrs.ip_api.get_dst(); + sip = pkt->ptrs.ip_api.get_src(); + addr = htonl(address); + sfip_set_raw(&ip, &addr, AF_INET); + fp = ftp_service_mod.api->flow_new(flowp, pkt, dip, 0, &ip, port, flowp->proto, + ftp_data_app_id, + APPID_EARLY_SESSION_FLAG_FW_RULE); + if (fp) + { + InitializeDataSession(flowp,fp); + } + if (!sfip_fast_eq6(&ip, sip)) + { + fp = ftp_service_mod.api->flow_new(flowp, pkt, dip, 0, sip, port, + flowp->proto, ftp_data_app_id, + APPID_EARLY_SESSION_FLAG_FW_RULE); + if (fp) + { + InitializeDataSession(flowp,fp); + } + } + ftp_service_mod.api->add_payload(flowp, APP_ID_FTP_PASSIVE); // Passive + // mode FTP is + // reported as + // a payload + // id + } + else if (code < 0) + { + goto fail; + } + } + break; + case 229: + { + code = ftp_validate_epsv(data + init_offset, + (uint16_t)(offset-init_offset), + &port); + + if (!code) + { + const sfip_t* sip; + const sfip_t* dip; + dip = pkt->ptrs.ip_api.get_dst(); + sip = pkt->ptrs.ip_api.get_src(); + fp = ftp_service_mod.api->flow_new(flowp, pkt, dip, 0, sip, port, flowp->proto, + ftp_data_app_id, + APPID_EARLY_SESSION_FLAG_FW_RULE); + if (fp) + { + InitializeDataSession(flowp,fp); + } + ftp_service_mod.api->add_payload(flowp, APP_ID_FTP_PASSIVE); // Passive + // mode FTP is + // reported as + // a payload + // id + } + else if (code < 0) + { + goto fail; + } + } + break; + case 200: + if (fd->cmd == FTP_CMD_PORT_EPRT) + { + const sfip_t* sip; + sip = pkt->ptrs.ip_api.get_src(); + fp = ftp_service_mod.api->flow_new(flowp, pkt, sip, 0, &fd->address, fd->port, + flowp->proto, ftp_data_app_id, + APPID_EARLY_SESSION_FLAG_FW_RULE); + if (fp) + { + InitializeDataSession(flowp,fp); + } + ftp_service_mod.api->add_payload(flowp, APP_ID_FTP_ACTIVE); // Active mode FTP + // is reported as a + // payload id + } + break; + default: + break; + } + fd->cmd = FTP_CMD_NONE; + break; + case FTP_STATE_CONNECTION_ERROR: + default: + goto fail; + } + } + + switch (retval) + { + default: + case SERVICE_INPROCESS: +inprocess: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + ftp_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + } + return SERVICE_INPROCESS; + + case SERVICE_SUCCESS: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + uint64_t encryptedFlag = getAppIdFlag(flowp, APPID_SESSION_ENCRYPTED | + APPID_SESSION_DECRYPTED); + ftp_service_mod.api->add_service(flowp, pkt, dir, &svc_element, + encryptedFlag == APPID_SESSION_ENCRYPTED ? // FTPS + // only + // when + // encrypted==1 + // decrypted==0 + APP_ID_FTPS : APP_ID_FTP_CONTROL, + fd->vendor[0] ? fd->vendor : nullptr, + fd->version[0] ? fd->version : nullptr, nullptr); + } + return SERVICE_SUCCESS; + + case SERVICE_NOMATCH: +fail: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + ftp_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + ftp_service_mod.flow_data_index, args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOMATCH; + } +} + diff --git a/src/network_inspectors/appid/service_plugins/service_ftp.h b/src/network_inspectors/appid/service_plugins/service_ftp.h new file mode 100644 index 000000000..ffa13ddce --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_ftp.h @@ -0,0 +1,32 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_ftp.h author Sourcefire Inc. + +#ifndef SERVICE_FTP_H +#define SERVICE_FTP_H + +// Service detector for FTP + +#include "service_api.h" + +extern RNAServiceValidationModule ftp_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_irc.cc b/src/network_inspectors/appid/service_plugins/service_irc.cc new file mode 100644 index 000000000..7706ddf50 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_irc.cc @@ -0,0 +1,338 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_irc.cc author Sourcefire Inc. + +#include "service_irc.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#include "appid_flow_data.h" +#include "application_ids.h" + +#define IRC_COUNT_THRESHOLD 10 + +static const char* const IRC_USER="USER "; +static const char* const IRC_NOTICE="NOTICE "; +static const char* const IRC_ERROR="ERROR "; +static const char* const IRC_PONG="PONG "; +static const char* const IRC_PING="PING "; + +enum IRCState +{ + IRC_STATE_BEGIN, + IRC_STATE_MID_PREFIX, + IRC_STATE_COMMAND_BEGIN, + IRC_STATE_MID_COMMAND, + IRC_STATE_MID_NUMERIC_COMMAND, + IRC_STATE_LINE, + IRC_STATE_MID_TERM, + IRC_STATE_FOUND_USER, + IRC_STATE_USER_USERNAME, + IRC_STATE_USER_HOSTNAME, + IRC_STATE_USER_SERVERNAME, + IRC_STATE_USER_REALNAME_BEGIN, + IRC_STATE_USER_REALNAME, + IRC_STATE_USER_MID_TERM +}; + +struct ServiceIRCData +{ + IRCState state; + unsigned pos; + const char* command; + IRCState initiator_state; + unsigned initiator_pos; + const char* initiator_command; + unsigned count; +}; + +static int irc_init(const IniServiceAPI* const init_api); +static int irc_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &irc_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "irc" +}; + +static RNAServiceValidationPort pp[] = +{ + { &irc_validate, 6667, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule irc_service_mod = +{ + "IRC", + &irc_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_IRCD, 0 } +}; + +static int irc_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&irc_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int irc_validate(ServiceValidationArgs* args) +{ + ServiceIRCData* id; + const uint8_t* end; + IRCState* state; + unsigned* pos; + const char** command; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + + id = (ServiceIRCData*)irc_service_mod.api->data_get(flowp, irc_service_mod.flow_data_index); + if (!id) + { + id = (ServiceIRCData*)snort_calloc(sizeof(ServiceIRCData)); + irc_service_mod.api->data_add(flowp, id, irc_service_mod.flow_data_index, &snort_free); + id->initiator_state = IRC_STATE_BEGIN; + id->state = IRC_STATE_BEGIN; + } + + end = (const uint8_t*)(data + size); + + if (dir == APP_ID_FROM_RESPONDER) + { + state = &id->state; + pos = &id->pos; + command = &id->command; + } + else + { + state = &id->initiator_state; + pos = &id->initiator_pos; + command = &id->initiator_command; + } + + for (; datacount++; + if (id->count >= IRC_COUNT_THRESHOLD && id->initiator_state == + IRC_STATE_FOUND_USER) + goto success; + } + } + break; + case IRC_STATE_MID_TERM: + if (*data != 0x0A) + goto fail; + *state = IRC_STATE_BEGIN; + if (dir == APP_ID_FROM_RESPONDER) + { + id->count++; + if (id->count >= IRC_COUNT_THRESHOLD && id->initiator_state == + IRC_STATE_FOUND_USER) + goto success; + } + break; + case IRC_STATE_MID_NUMERIC_COMMAND: + if (*pos < 3) + { + if (!isdigit(*data)) + goto fail; + (*pos)++; + } + else + { + if (*data != ' ') + goto fail; + *state = IRC_STATE_LINE; + } + break; + case IRC_STATE_MID_PREFIX: + if (*data == ' ') + *state = IRC_STATE_COMMAND_BEGIN; + else if (!isprint(*data)) + goto fail; + break; + case IRC_STATE_USER_USERNAME: + if (*data == ' ') + *state = IRC_STATE_USER_HOSTNAME; + else if (*data == 0x0D || *data == 0x0A) + goto fail; + break; + case IRC_STATE_USER_HOSTNAME: + if (*data == ' ') + *state = IRC_STATE_USER_SERVERNAME; + else if (*data == 0x0D || *data == 0x0A) + goto fail; + break; + case IRC_STATE_USER_SERVERNAME: + if (*data == ' ') + *state = IRC_STATE_USER_REALNAME_BEGIN; + else if (*data == 0x0D || *data == 0x0A) + goto fail; + break; + case IRC_STATE_USER_REALNAME_BEGIN: + if (*data == ':') + *state = IRC_STATE_USER_REALNAME; + else + goto fail; + break; + case IRC_STATE_USER_REALNAME: + if (*data == 0x0D) + *state = IRC_STATE_USER_MID_TERM; + else if (*data == 0x0A) + *state = IRC_STATE_FOUND_USER; + break; + case IRC_STATE_USER_MID_TERM: + if (*data != 0x0A) + goto fail; + *state = IRC_STATE_FOUND_USER; + break; + case IRC_STATE_FOUND_USER: + goto inprocess; + default: + goto fail; + } + } +inprocess: + irc_service_mod.api->service_inprocess(flowp, args->pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +success: + irc_service_mod.api->add_service(flowp, args->pkt, dir, &svc_element, + APP_ID_IRCD, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + if (dir == APP_ID_FROM_RESPONDER) + { + irc_service_mod.api->fail_service(flowp, args->pkt, dir, &svc_element, + irc_service_mod.flow_data_index, args->pConfig); + } + else + { + irc_service_mod.api->incompatible_data(flowp, args->pkt, dir, &svc_element, + irc_service_mod.flow_data_index, args->pConfig); + } + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_irc.h b/src/network_inspectors/appid/service_plugins/service_irc.h new file mode 100644 index 000000000..ccdbbc9e8 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_irc.h @@ -0,0 +1,32 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_irc.h author Sourcefire Inc. + +#ifndef SERVICE_IRC_H +#define SERVICE_IRC_H + +// Service detector for IRC + +#include "service_api.h" + +extern RNAServiceValidationModule irc_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_lpr.cc b/src/network_inspectors/appid/service_plugins/service_lpr.cc new file mode 100644 index 000000000..8a2f86da5 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_lpr.cc @@ -0,0 +1,261 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_lpr.cc author Sourcefire Inc. + +#include "main/snort_debug.h" +#include "utils/util.h" + +#include "application_ids.h" +#include "appid_flow_data.h" +#include "service_api.h" + +#define LPR_COUNT_THRESHOLD 5 + +enum LPRState +{ + LPR_STATE_COMMAND, + LPR_STATE_RECEIVE, + LPR_STATE_REPLY1, + LPR_STATE_REPLY, + LPR_STATE_IGNORE +}; + +enum LPRCommand +{ + LPR_CMD_PRINT = 1, + LPR_CMD_RECEIVE, + LPR_CMD_SHORT_STATE, + LPR_CMD_LONG_STATE, + LPR_CMD_REMOVE +}; + +enum LPRSubCommand +{ + LPR_SUBCMD_ABORT = 1, + LPR_SUBCMD_CONTROL, + LPR_SUBCMD_DATA +}; + +struct ServiceLPRData +{ + LPRState state; + unsigned no_data_count; + unsigned count; +}; + +static int lpr_init(const IniServiceAPI* const init_api); +static int lpr_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &lpr_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "lpr" +}; + +static RNAServiceValidationPort pp[] = +{ + { &lpr_validate, 515, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule lpr_service_mod = +{ + "LPR", + &lpr_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_PRINTSRV, 0 } +}; + +static int lpr_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&lpr_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int lpr_validate(ServiceValidationArgs* args) +{ + ServiceLPRData* ld; + int i; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + + ld = (ServiceLPRData*)lpr_service_mod.api->data_get(flowp, lpr_service_mod.flow_data_index); + if (!ld) + { + ld = (ServiceLPRData*)snort_calloc(sizeof(ServiceLPRData)); + lpr_service_mod.api->data_add(flowp, ld, lpr_service_mod.flow_data_index, &snort_free); + ld->state = LPR_STATE_COMMAND; + } + + switch (ld->state) + { + case LPR_STATE_COMMAND: + if (dir != APP_ID_FROM_INITIATOR) + goto bail; + if (size < 3) + goto bail; + switch (*data) + { + case LPR_CMD_RECEIVE: + if (data[size-1] != 0x0A) + goto bail; + size--; + for (i=1; istate = LPR_STATE_REPLY; + break; + case LPR_CMD_PRINT: + ld->state = LPR_STATE_IGNORE; + break; + case LPR_CMD_SHORT_STATE: + ld->state = LPR_STATE_IGNORE; + break; + case LPR_CMD_LONG_STATE: + ld->state = LPR_STATE_IGNORE; + break; + case LPR_CMD_REMOVE: + ld->state = LPR_STATE_IGNORE; + break; + default: + goto bail; + } + break; + case LPR_STATE_RECEIVE: + if (dir != APP_ID_FROM_INITIATOR) + goto inprocess; + if (size < 2) + goto bail; + switch (*data) + { + case LPR_SUBCMD_ABORT: + if (size != 2) + goto bail; + if (data[1] != 0x0A) + goto bail; + ld->state = LPR_STATE_REPLY; + break; + case LPR_SUBCMD_CONTROL: + case LPR_SUBCMD_DATA: + if (size < 5) + goto bail; + if (data[size-1] != 0x0A) + goto bail; + if (!isdigit(data[1])) + goto bail; + for (i=2; i= size) + goto bail; + for (; istate = LPR_STATE_REPLY1; + break; + default: + goto bail; + } + break; + case LPR_STATE_REPLY1: + if (dir != APP_ID_FROM_RESPONDER) + goto inprocess; + if (size != 1) + goto fail; + ld->count++; + if (ld->count >= LPR_COUNT_THRESHOLD) + { + ld->state = LPR_STATE_IGNORE; + goto success; + } + ld->state = LPR_STATE_REPLY; + break; + case LPR_STATE_REPLY: + if (dir != APP_ID_FROM_RESPONDER) + goto inprocess; + if (size != 1) + goto fail; + ld->count++; + if (ld->count >= LPR_COUNT_THRESHOLD) + { + ld->state = LPR_STATE_IGNORE; + goto success; + } + ld->state = LPR_STATE_RECEIVE; + break; + case LPR_STATE_IGNORE: + break; + default: + goto bail; + } +inprocess: + lpr_service_mod.api->service_inprocess(flowp, args->pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +success: + lpr_service_mod.api->add_service(flowp, args->pkt, dir, &svc_element, + APP_ID_PRINTSRV, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + lpr_service_mod.api->fail_service(flowp, args->pkt, dir, &svc_element, + lpr_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + +bail: + lpr_service_mod.api->incompatible_data(flowp, args->pkt, dir, &svc_element, + lpr_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOT_COMPATIBLE; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_lpr.h b/src/network_inspectors/appid/service_plugins/service_lpr.h new file mode 100644 index 000000000..7dc7344fe --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_lpr.h @@ -0,0 +1,32 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_lpr.h author Sourcefire Inc. + +#ifndef SERVICE_LPR_H +#define SERVICE_LPR_H + +// Service detector for LPR + +#include "service_api.h" + +extern RNAServiceValidationModule lpr_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_mdns.cc b/src/network_inspectors/appid/service_plugins/service_mdns.cc new file mode 100644 index 000000000..28876984e --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_mdns.cc @@ -0,0 +1,600 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_mdns.cc author Sourcefire Inc. + +#include "service_mdns.h" + +#include "search_engines/search_tool.h" +#include "client_plugins/client_app_base.h" +#include "detector_plugins/http_url_patterns.h" +#include "detector_plugins/detector_http.h" +#include "util/common_util.h" +#include "appid_config.h" +#include "appid_flow_data.h" +#include "fw_appid.h" +#include "http_common.h" +#include "lua_detector_api.h" +#include "service_api.h" +#include "service_base.h" +#include "service_ssl.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#define MDNS_PORT 5353 +#define PATTERN_REFERENCE_PTR 3 +#define PATTERN_STR_LOCAL_1 "\005local" +#define PATTERN_STR_LOCAL_2 "\005LOCAL" +#define PATTERN_STR_ARPA_1 "\004arpa" +#define PATTERN_STR_ARPA_2 "\004ARPA" +#define PATTERN_USERNAME_1 '@' +#define MDNS_PATTERN1 "\x00\x00\x84\x00\x00\x00" +#define MDNS_PATTERN2 "\x00\x00\x08\x00\x00\x00" +#define MDNS_PATTERN3 "\x00\x00\x04\x00\x00\x00" +#define MDNS_PATTERN4 "\x00\x00\x00\x00" +#define SRV_RECORD "\x00\x21" +#define SRV_RECORD_OFFSET 6 +#define LENGTH_OFFSET 8 +#define NEXT_MESSAGE_OFFSET 10 +#define QUERY_OFFSET 4 +#define ANSWER_OFFSET 6 +#define RECORD_OFFSET 12 +#define SHIFT_BITS 8 +#define SHIFT_BITS_REFERENCE_PTR 6 +#define REFERENCE_PTR_LENGTH 2 +#define MAX_LENGTH_SERVICE_NAME 256 + +enum MDNSState +{ + MDNS_STATE_CONNECTION, + MDNS_STATE_CONNECTION_ERROR +}; + +struct ServiceMDNSData +{ + MDNSState state; +}; + +struct MdnsPattern +{ + const uint8_t* pattern; + unsigned length; +}; + +struct MatchedPatterns +{ + MdnsPattern* mpattern; + int index; + MatchedPatterns* next; +}; + +struct MdnsConfig +{ + SearchTool* mdnsMatcher; + MatchedPatterns* patternList; +}; + +static int MDNS_init(const IniServiceAPI* const init_api); +static int ReferencePointer(const char* start_ptr,const char** resp_endptr, int* start_index, + uint16_t data_size, uint8_t* user_name_len, unsigned offset, const AppIdConfig* pConfig); +static int MDNS_validate(ServiceValidationArgs* args); +static int mdnsMatcherCreate(AppIdConfig* pConfig); +static void mdnsMatcherDestroy(AppIdConfig* pConfig); +static unsigned mdnsMatchListCreate(const char* data, uint16_t dataSize, const + AppIdConfig* pConfig); +static void mdnsMatchListFind(const char* dataPtr, uint16_t index, const char** resp_endptr, + int* pattern_length, const AppIdConfig* pConfig); +static void mdnsMatchListDestroy(const AppIdConfig* pConfig); +static void MDNS_clean(const CleanServiceAPI* const clean_api); + +static RNAServiceElement svc_element = +{ + nullptr, + &MDNS_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "MDNS" +}; + +static RNAServiceValidationPort pp[] = +{ + { &MDNS_validate, 5353, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule mdns_service_mod = +{ + "MDNS", + &MDNS_init, + pp, + nullptr, + nullptr, + 0, + MDNS_clean, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_MDNS, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int MDNS_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&MDNS_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + mdnsMatcherCreate(init_api->pAppidConfig); + return 0; +} + +static int MDNS_validate_reply(const uint8_t* data, uint16_t size) +{ + int ret_val; + + /* Check for the pattern match*/ + if (size >= 6 && memcmp(data, MDNS_PATTERN1, sizeof(MDNS_PATTERN1)-1) == 0) + ret_val = 1; + else if (size >= 6 && memcmp(data, MDNS_PATTERN2, sizeof(MDNS_PATTERN2)-1) == 0) + ret_val = 1; + else if (size >= 6 && memcmp(data,MDNS_PATTERN3, sizeof(MDNS_PATTERN3)-1) == 0) + ret_val = 1; + else if (size >= 4 && memcmp(data,MDNS_PATTERN4, sizeof(MDNS_PATTERN4)-1) == 0) + ret_val = 1; + else + ret_val = 0; + + return ret_val; +} + +/* Input to this function is start_ptr and data_size. + Output is resp_endptr, start_index and user_name_len + Returns 0 or 1 for successful/unsuccessful hit for pattern '@' + Returns -1 for invalid address pointer or past the data_size */ +static int ReferencePointer(const char* start_ptr, const char** resp_endptr, int* start_index, + uint16_t data_size, uint8_t* user_name_len, unsigned size, const AppIdConfig* pConfig) +{ + int index = 0; + int pattern_length = 0; + + while (index< data_size && (start_ptr[index] == ' ' )) + index++; + + if (index >= data_size) + return -1; + *start_index = index; + + const char* temp_start_ptr; + temp_start_ptr = start_ptr+index; + + mdnsMatchListFind(start_ptr, size - data_size + index, resp_endptr, &pattern_length, pConfig); + /* Contains reference pointer */ + while ((index < data_size) && !(*resp_endptr) && ((uint8_t )temp_start_ptr[index] >> + SHIFT_BITS_REFERENCE_PTR != PATTERN_REFERENCE_PTR)) + { + if (temp_start_ptr[index] == PATTERN_USERNAME_1) + { + *user_name_len = index - *start_index; + index++; + break; + } + index++; + mdnsMatchListFind(start_ptr, size - data_size + index, resp_endptr, &pattern_length, + pConfig); + } + if (index >= data_size) + *user_name_len = 0; + else if ((uint8_t )temp_start_ptr[index] >> SHIFT_BITS_REFERENCE_PTR == PATTERN_REFERENCE_PTR) + pattern_length = REFERENCE_PTR_LENGTH; + else if (!(*resp_endptr) && ((uint8_t )temp_start_ptr[index] >>SHIFT_BITS_REFERENCE_PTR != + PATTERN_REFERENCE_PTR )) + { + while ((index < data_size) && !(*resp_endptr) && ((uint8_t )temp_start_ptr[index] >> + SHIFT_BITS_REFERENCE_PTR != PATTERN_REFERENCE_PTR)) + { + index++; + mdnsMatchListFind(start_ptr, size - data_size + index, resp_endptr, &pattern_length, + pConfig); + } + if (index >= data_size) + *user_name_len = 0; + else if ((uint8_t )temp_start_ptr[index] >> SHIFT_BITS_REFERENCE_PTR == + PATTERN_REFERENCE_PTR) + pattern_length = REFERENCE_PTR_LENGTH; + } + + /* Add reference pointer bytes */ + if ( index+ pattern_length < data_size) + *resp_endptr = start_ptr + index+ pattern_length; + else + return -1; + + if (*user_name_len > 0) + return 1; + else + return 0; +} + +/* Input to this Function is pkt and size + Processing: 1. Parses Multiple MDNS response packet + 2. Calls the function which scans for pattern to identify the user + 3. Calls the function which does the Username reporting along with the host + MDNS User Analysis*/ +static int MDNSUserAnalyser(AppIdData* flowp, const Packet* pkt, uint16_t size, const + AppIdConfig* pConfig) +{ + const char* query_val; + const char* answers; + char user_name[MAX_LENGTH_SERVICE_NAME] = ""; + char* user_name_bkp = nullptr; + const char* resp_endptr; + const char* srv_original; + const char* end_srv_original; + const char* user_original; + int query_val_int; + int ans_count = 0; + int start_index =0; + int processed_ans =0; + uint16_t data_len = 0; + uint8_t* data_len_str; + uint8_t user_name_len = 0; + uint16_t data_size = size; + + /* Scan for MDNS response, decided on Query value */ + query_val = (char*)pkt->data + QUERY_OFFSET; + query_val_int = (short)(query_val[0]<data + ANSWER_OFFSET; + ans_count = (short)(answers[0]<< SHIFT_BITS | (answers[1] )); + + if ( query_val_int ==0) + { + srv_original = (char*)pkt->data + RECORD_OFFSET; + mdnsMatchListCreate(srv_original, size-RECORD_OFFSET, pConfig); + end_srv_original = (char*)pkt->data + RECORD_OFFSET+data_size; + for (processed_ans =0; processed_ans< ans_count && data_size <= size && size > 0; + processed_ans++ ) + { + /* Call Decode Reference pointer function if referenced value instead of direct value + */ + user_name_len = 0; + int ret_value = ReferencePointer(srv_original, &resp_endptr, &start_index, data_size, + &user_name_len, size, pConfig); + int user_index =0; + int user_printable_index =0; + + if (ret_value == -1) + { + return -1; + } + else if (ret_value) + { + while (start_index < data_size && (!isprint(srv_original[start_index]) || + srv_original[start_index] == '"' || srv_original[start_index] =='\'')) + { + start_index++; + user_index++; + } + user_name_len -=user_index; + + memcpy(user_name, srv_original+start_index, user_name_len); + user_name[user_name_len] = '\0'; + + user_index =0; + while (user_index < user_name_len) + { + if (!isprint(user_name[user_index])) + { + return 1; + } + user_index++; + } + + AppIdAddUser(flowp, user_name, APP_ID_MDNS, 1); + break; + } + /* Find the length to Jump to the next response */ + + if ((resp_endptr + NEXT_MESSAGE_OFFSET ) < (srv_original + data_size)) + { + data_len_str = (uint8_t*)(resp_endptr+ LENGTH_OFFSET); + data_len = 0; + data_len = (short)( data_len_str[0]<< SHIFT_BITS | ( data_len_str[1] )); + + data_size = data_size - (resp_endptr + NEXT_MESSAGE_OFFSET + data_len - + srv_original); + /* Check if user name is available in the Domain Name field */ + if (data_size < size) + { + if (memcmp(resp_endptr, SRV_RECORD, sizeof(SRV_RECORD)-1)==0) + start_index = SRV_RECORD_OFFSET; + else + start_index =0; + + srv_original = resp_endptr + NEXT_MESSAGE_OFFSET; + user_original = (char*)memchr((const uint8_t*)srv_original, PATTERN_USERNAME_1, + data_len); + + if (user_original ) + { + user_name_len = user_original - srv_original - start_index; + user_name_bkp = (char*)(srv_original + start_index); + /* Non-Printable characters in the begining */ + + while (user_index < user_name_len) + { + if (isprint(user_name_bkp[user_index])) + { + break; + } + user_index++; + } + + user_printable_index = user_index; + /* Non-Printable characters in the between */ + + while (user_printable_index < user_name_len) + { + if (!isprint(user_name_bkp [user_printable_index ])) + { + return 0; + } + user_printable_index++; + } + /* Copy the user name if available */ + if (( user_name_len - user_index ) < MAX_LENGTH_SERVICE_NAME ) + { + memcpy(user_name, user_name_bkp + user_index, user_name_len - + user_index); + user_name[ user_name_len - user_index ] = '\0'; + AppIdAddUser(flowp, user_name, APP_ID_MDNS, 1); + return 1; + } + else + return 0; + } + + srv_original = srv_original + data_len; + if (srv_original > end_srv_original) + return 0; + } + else + { + return 0; + } + } + else + { + return 0; + } + } + } + else + return 0; + + return 1; +} + +static int MDNS_validate(ServiceValidationArgs* args) +{ + ServiceMDNSData* fd; + int ret_val; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + uint16_t size = args->size; + + fd = (ServiceMDNSData*)mdns_service_mod.api->data_get(flowp, mdns_service_mod.flow_data_index); + if (!fd) + { + fd = (ServiceMDNSData*)snort_calloc(sizeof(ServiceMDNSData)); + mdns_service_mod.api->data_add(flowp, fd, mdns_service_mod.flow_data_index, &snort_free); + fd->state = MDNS_STATE_CONNECTION; + } + + if (pkt->ptrs.dp == MDNS_PORT || pkt->ptrs.sp == MDNS_PORT ) + { + ret_val = MDNS_validate_reply(data, size); + if (ret_val == 1) + { + if (pAppidActiveConfig->mod_config->mdns_user_reporting) + { + ret_val = MDNSUserAnalyser(flowp, pkt, size, args->pConfig); + mdnsMatchListDestroy(args->pConfig); + goto success; + } + goto success; + } + else + goto fail; + } + else + goto fail; + +success: + mdns_service_mod.api->add_service(flowp, pkt, args->dir, &svc_element, + APP_ID_MDNS, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + mdns_service_mod.api->fail_service(flowp, pkt, args->dir, &svc_element, + mdns_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + +static MatchedPatterns* patternFreeList; + +static MdnsPattern patterns[] = +{ + { (uint8_t*)PATTERN_STR_LOCAL_1, sizeof(PATTERN_STR_LOCAL_1) }, + { (uint8_t*)PATTERN_STR_LOCAL_2, sizeof(PATTERN_STR_LOCAL_2) }, + { (uint8_t*)PATTERN_STR_ARPA_1, sizeof(PATTERN_STR_ARPA_1) }, + { (uint8_t*)PATTERN_STR_ARPA_2, sizeof(PATTERN_STR_ARPA_2) }, +}; + +static int mdnsMatcherCreate(AppIdConfig* pConfig) +{ + MdnsConfig* pMdnsConfig = (MdnsConfig*)snort_calloc(sizeof(MdnsConfig)); + + if (!(pMdnsConfig->mdnsMatcher = new SearchTool("ac_full"))) + { + snort_free(pMdnsConfig); + return 0; + } + + for (unsigned i = 0; i < sizeof(patterns) / sizeof(*patterns); i++) + pMdnsConfig->mdnsMatcher->add( + (char*)patterns[i].pattern, patterns[i].length, &patterns[i]); + + pMdnsConfig->mdnsMatcher->prep(); + pConfig->add_generic_config_element(svc_element.name, pMdnsConfig); + return 1; +} + +static void mdnsMatcherDestroy(AppIdConfig* pConfig) +{ + MdnsConfig* pMdnsConfig = (MdnsConfig*)pConfig->find_generic_config_element(svc_element.name); + MatchedPatterns* node; + if (pMdnsConfig->mdnsMatcher) + delete pMdnsConfig->mdnsMatcher; + pMdnsConfig->mdnsMatcher = nullptr; + + mdnsMatchListDestroy(pConfig); + + while ((node = patternFreeList)) + { + patternFreeList = node->next; + snort_free(node); + } + snort_free(pMdnsConfig); + pConfig->remove_generic_config_element(svc_element.name); +} + +static int mdns_pattern_match(void* id, void*, int index, void* data, void*) +{ + MatchedPatterns* cm; + MatchedPatterns** matches = (MatchedPatterns**)data; + MdnsPattern* target = (MdnsPattern*)id; + MatchedPatterns* element; + MatchedPatterns* prevElement; + + if (patternFreeList) + { + cm = patternFreeList; + patternFreeList = cm->next; + } + else + cm = (MatchedPatterns*)snort_calloc(sizeof(MatchedPatterns)); + + cm->mpattern = target; + cm->index = index; + for (prevElement = nullptr, element = *matches; + element; + prevElement = element, element = element->next) + { + if (element->index > index) + break; + } + if (prevElement) + { + cm->next = prevElement->next; + prevElement->next = cm; + } + else + { + cm->next = *matches; + *matches = cm; + } + + return 0; +} + +static unsigned mdnsMatchListCreate(const char* data, uint16_t dataSize, const + AppIdConfig* pConfig) +{ + MdnsConfig* pMdnsConfig = (MdnsConfig*)((AppIdConfig*)pConfig)->find_generic_config_element( + svc_element.name); + + if (pMdnsConfig->patternList) + mdnsMatchListDestroy(pConfig); + + pMdnsConfig->mdnsMatcher->find_all( + (char*)data, dataSize, mdns_pattern_match, false, (void*)&pMdnsConfig->patternList); + + if (pMdnsConfig->patternList) + return 1; + return 0; +} + +static void mdnsMatchListFind(const char* dataPtr, uint16_t index, const char** resp_endptr, + int* pattern_length, const AppIdConfig* pConfig) +{ + MdnsConfig* pMdnsConfig = (MdnsConfig*)((AppIdConfig*)pConfig)->find_generic_config_element( + svc_element.name); + + while (pMdnsConfig->patternList) + { + if (pMdnsConfig->patternList->index == index) + { + *resp_endptr = dataPtr+pMdnsConfig->patternList->index-index; + *pattern_length = pMdnsConfig->patternList->mpattern->length; + return; + } + if (pMdnsConfig->patternList->index > index) + break; + MatchedPatterns* element; + element = pMdnsConfig->patternList; + pMdnsConfig->patternList = pMdnsConfig->patternList->next; + + element->next = patternFreeList; + patternFreeList = element; + } + *resp_endptr = nullptr; + *pattern_length = 0; +} + +static void mdnsMatchListDestroy(const AppIdConfig* pConfig) +{ + MatchedPatterns* element; + + MdnsConfig* pMdnsConfig = (MdnsConfig*)((AppIdConfig*)pConfig)->find_generic_config_element( + svc_element.name); + while (pMdnsConfig->patternList) + { + element = pMdnsConfig->patternList; + pMdnsConfig->patternList = pMdnsConfig->patternList->next; + + element->next = patternFreeList; + patternFreeList = element; + } +} + +static void MDNS_clean(const CleanServiceAPI* const clean_api) +{ + mdnsMatcherDestroy(clean_api->pAppidConfig); +} + diff --git a/src/network_inspectors/appid/service_plugins/service_mdns.h b/src/network_inspectors/appid/service_plugins/service_mdns.h new file mode 100644 index 000000000..54c5a3763 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_mdns.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_mdns.h author Sourcefire Inc. + +#ifndef SERVICE_MDNS_H +#define SERVICE_MDNS_H + +#include "service_api.h" + +extern RNAServiceValidationModule mdns_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_mysql.cc b/src/network_inspectors/appid/service_plugins/service_mysql.cc new file mode 100644 index 000000000..dfce57755 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_mysql.cc @@ -0,0 +1,164 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_mysql.cc author Sourcefire Inc. + +#include "service_mysql.h" + +#include "main/snort_debug.h" + +#include "application_ids.h" +#include "app_info_table.h" +#include "appid_flow_data.h" + +#include "service_api.h" + +#pragma pack(1) + +struct ServiceMYSQLHdr +{ + union + { + uint32_t len; + struct + { + uint8_t len[3]; + uint8_t packet; + } p; + } l; + IpProtocol proto; +}; + +#pragma pack() + +static int svc_mysql_init(const IniServiceAPI* const init_api); +static int svc_mysql_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element +{ + nullptr, + &svc_mysql_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "mysql" +}; + +static RNAServiceValidationPort pp[] +{ + { &svc_mysql_validate, 3306, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule mysql_service_mod +{ + "MYSQL", + &svc_mysql_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] +{ + { APP_ID_MYSQL, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int svc_mysql_init(const IniServiceAPI* const init_api) +{ + for ( unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++ ) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&svc_mysql_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int svc_mysql_validate(ServiceValidationArgs* args) +{ + const uint8_t* data = args->data; + const ServiceMYSQLHdr* hdr = (const ServiceMYSQLHdr*)data; + uint32_t len; + const uint8_t* end; + const uint8_t* p = nullptr; + AppIdData* flowp = args->flowp; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + if (size < sizeof(ServiceMYSQLHdr)) + goto fail; + + len = hdr->l.p.len[0]; + len |= hdr->l.p.len[1] << 8; + len |= hdr->l.p.len[2] << 16; + len += 4; + if (len > size) + goto fail; + if (hdr->l.p.packet) + goto fail; + if (hdr->proto != (IpProtocol)0x0A) + goto fail; + + end = data + len; + data += sizeof(ServiceMYSQLHdr); + p = data; + for (; data= end) + goto fail; + if (data == p) + p = nullptr; + data += 5; + if (data >= end) + goto fail; + for (; data= end) + goto fail; + mysql_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_MYSQL, nullptr, (char*)p, nullptr); + return SERVICE_SUCCESS; + +inprocess: + mysql_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +fail: + mysql_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + mysql_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_mysql.h b/src/network_inspectors/appid/service_plugins/service_mysql.h new file mode 100644 index 000000000..d44236048 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_mysql.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_mysql.h author Sourcefire Inc. + +#ifndef SERVICE_MYSQL_H +#define SERVICE_MYSQL_H + +#include "service_api.h" + +extern RNAServiceValidationModule mysql_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_netbios.cc b/src/network_inspectors/appid/service_plugins/service_netbios.cc new file mode 100644 index 000000000..c2057dce4 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_netbios.cc @@ -0,0 +1,1243 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_netbios.cc author Sourcefire Inc. + +#include "service_netbios.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" /* for WORDS_BIGENDIAN */ +#endif + +#include "log/messages.h" +#include "main/snort_debug.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "appid_api.h" +#include "appid_flow_data.h" +#include "application_ids.h" +#include "dcerpc.h" +#include "service_api.h" + +/*#define RNA_DEBUG_NETBIOS 1 */ + +#define NBSS_PORT 139 + +#define NBNS_NB 32 +#define NBNS_NBSTAT 33 +#define NBNS_LENGTH_FLAGS 0xC0 + +#define NBNS_OPCODE_QUERY 0 +#define NBNS_OPCODE_REGISTRATION 5 +#define NBNS_OPCODE_RELEASE 6 +#define NBNS_OPCODE_WEACK 7 +#define NBNS_OPCODE_REFRESH 8 +#define NBNS_OPCODE_REFRESHALT 9 +#define NBNS_OPCODE_MHREGISTRATION 15 + +#define NBSS_COUNT_THRESHOLD 4 + +#define NBNS_REPLYCODE_MAX 7 + +#define NBSS_TYPE_MESSAGE 0x00 +#define NBSS_TYPE_REQUEST 0x81 +#define NBSS_TYPE_RESP_POSITIVE 0x82 +#define NBSS_TYPE_RESP_NEGATIVE 0x83 +#define NBSS_TYPE_RESP_RETARGET 0x84 +#define NBSS_TYPE_KEEP_ALIVE 0x85 + +enum NBSSState +{ + NBSS_STATE_CONNECTION, + NBSS_STATE_FLOW, + NBSS_STATE_CONT, + NBSS_STATE_ERROR +}; + +#define NBDGM_TYPE_DIRECT_UNIQUE 0x10 +#define NBDGM_TYPE_DIRECT_GROUP 0x11 +#define NBDGM_TYPE_BROADCAST 0x12 +#define NBDGM_TYPE_ERROR 0x13 +#define NBDGM_TYPE_REQUEST 0x14 +#define NBDGM_TYPE_POSITIVE_REPSONSE 0x15 +#define NBDGM_TYPE_NEGATIVE_RESPONSE 0x16 + +#define NBDGM_ERROR_CODE_MIN 0x82 +#define NBDGM_ERROR_CODE_MAX 0x84 + +#define min(x,y) ((x)<(y) ? (x) : (y)) + +#pragma pack(1) + +struct NBNSHeader +{ + uint16_t id; +#if defined(WORDS_BIGENDIAN) + uint8_t response : 1, + Opcode : 4, + auth : 1, + trunc : 1, + RD : 1; + uint8_t RA : 1, + unused : 2, + broadcast : 1, + replycode : 4; +#else + uint8_t RD : 1, + trunc : 1, + auth : 1, + Opcode : 4, + response : 1; + uint8_t replycode : 4, + broadcast : 1, + unused : 2, + RA : 1; +#endif + uint16_t QCount; + uint16_t ACount; + uint16_t NSCount; + uint16_t ARCount; +}; + +#define NBNS_NAME_LEN 0x20 +struct NBNSLabelLength +{ + uint8_t len; +}; + +struct NBNSLabelData +{ + uint8_t len; + uint8_t data[NBNS_NAME_LEN]; + uint8_t zero; +}; + +struct NBNSLabel +{ + uint16_t type; + uint16_t class_id; +}; + +struct NBNSLabelPtr +{ + uint8_t flag; + uint8_t position; +}; + +struct NBNSAnswerData +{ + uint32_t ttl; + uint16_t data_len; +}; + +struct NBSSHeader +{ + uint8_t type; + uint8_t flags; + uint16_t length; +}; + +uint8_t NB_SMB_BANNER[] = +{ + 0xFF, 'S', 'M', 'B' +}; + +struct ServiceSMBHeader +{ + uint8_t command; + uint32_t status; + uint8_t flags[3]; + uint16_t pid_high; + uint8_t signature[8]; + uint16_t reserved2; + uint16_t tid; + uint16_t pid; + uint16_t uid; + uint16_t mid; +}; + +struct ServiceSMBAndXResponse +{ + uint8_t wc; + uint8_t cmd; + uint8_t reserved; + uint16_t offset; + uint16_t action; + uint16_t sec_len; +}; + +struct ServiceSMBNegotiateProtocolResponse +{ + uint8_t wc; + uint16_t dialect_index; + uint8_t security_mode; + uint16_t max_mpx_count; + uint16_t max_vcs; + uint32_t max_buffer_size; + uint32_t max_raw_buffer; + uint32_t session_key; + uint32_t capabilities; + uint32_t system_time[2]; + uint16_t time_zone; + uint8_t sec_len; +}; + +struct ServiceSMBTransactionHeader +{ + uint8_t wc; + uint16_t total_pc; + uint16_t total_dc; + uint16_t max_pc; + uint16_t max_dc; + uint8_t max_sc; + uint8_t reserved; + uint16_t flags; + uint32_t timeout; + uint16_t reserved2; + uint16_t pc; + uint16_t po; + uint16_t dc; + uint16_t offset; + uint8_t sc; + uint8_t reserved3; +}; +/* sc * 2 to get to the transaction name */ + +#define SERVICE_SMB_STATUS_SUCCESS 0x00000000 +#define SERVICE_SMB_TRANSACTION_COMMAND 0x25 +#define SERVICE_SMB_COMMAND_SESSION_SETUP_ANDX_RESPONSE 0x73 +#define SERVICE_SMB_COMMAND_NEGOTIATE_PROTOCOL 0x72 +#define SERVICE_SMB_CAPABILITIES_EXTENDED_SECURITY 0x80000000 +#define SERVICE_SMB_CAPABILITIES_UNICODE 0x00000004 +#define SERVICE_SMB_FLAGS_RESPONSE 0x80 +#define SERVICE_SMB_FLAGS_UNICODE 0x80 +#define SERVICE_SMB_NOT_TRANSACTION_WC 8 +#define SERVICE_SMB_MAILSLOT_HOST 0x01 +#define SERVICE_SMB_MAILSLOT_LOCAL_MASTER 0x0f +#define SERVICE_SMB_MAILSLOT_SERVER_TYPE_XENIX 0x00000800 +#define SERVICE_SMB_MAILSLOT_SERVER_TYPE_NT 0x00001000 +static char mailslot[] = "\\MAILSLOT\\BROWSE"; + +struct ServiceSMBBrowserHeader +{ + uint8_t command; + uint8_t count; + uint32_t period; + uint8_t hostname[16]; + uint8_t major; + uint8_t minor; + uint32_t server_type; +}; + +struct ServiceNBSSData +{ + NBSSState state; + unsigned count; + uint32_t length; + AppId serviceAppId; + AppId miscAppId; +}; + +struct NBDgmHeader +{ + uint8_t type; +#if defined(WORDS_BIGENDIAN) + uint8_t zero : 4, + SNT : 2, + first : 1, + more : 1; +#else + uint8_t more : 1, + first : 1, + SNT : 2, + zero : 4; +#endif + uint16_t id; + uint32_t src_ip; + uint16_t src_port; +}; + +struct NBDgmError +{ + uint8_t code; +}; + +#pragma pack() + +static int netbios_init(const IniServiceAPI* const init_api); +static int nbns_validate(ServiceValidationArgs* args); +static int nbss_validate(ServiceValidationArgs* args); +static int nbdgm_validate(ServiceValidationArgs* args); + +static RNAServiceElement nbns_svc_element +{ + nullptr, + &nbns_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "nbns" +}; +static RNAServiceElement nbdgm_svc_element +{ + nullptr, + &nbdgm_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "nbdgm" +}; +static RNAServiceElement nbss_svc_element +{ + nullptr, + &nbss_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "nbss" +}; + +static RNAServiceValidationPort pp[] +{ + { &nbns_validate, 137, IpProtocol::TCP, 0 }, + { &nbns_validate, 137, IpProtocol::UDP, 0 }, + { &nbns_validate, 137, IpProtocol::UDP, 1 }, + { &nbdgm_validate, 138, IpProtocol::UDP, 0 }, + { &nbss_validate, 139, IpProtocol::TCP, 0 }, + { &nbss_validate, 445, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule netbios_service_mod +{ + "NETBIOS", + &netbios_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static int netbios_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&nbss_validate, IpProtocol::TCP, NB_SMB_BANNER, + sizeof(NB_SMB_BANNER), + -1, "netbios", init_api->pAppidConfig); + + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d for NetBIOS-ns\n",APP_ID_NETBIOS_NS); + init_api->RegisterAppId(&nbns_validate, APP_ID_NETBIOS_NS, APPINFO_FLAG_SERVICE_UDP_REVERSED, + init_api->pAppidConfig); + + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d for NetBIOS-dgm\n",APP_ID_NETBIOS_DGM); + init_api->RegisterAppId(&nbdgm_validate, APP_ID_NETBIOS_DGM, APPINFO_FLAG_SERVICE_ADDITIONAL, + init_api->pAppidConfig); + + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d for NetBIOS-ssn\n",APP_ID_NETBIOS_SSN); + init_api->RegisterAppId(&nbss_validate, APP_ID_NETBIOS_SSN, APPINFO_FLAG_SERVICE_ADDITIONAL, + init_api->pAppidConfig); + + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",APP_ID_DCE_RPC); + init_api->RegisterAppId(&nbss_validate, APP_ID_DCE_RPC, 0, init_api->pAppidConfig); + + return 0; +} + +static int netbios_validate_name_and_decode(const uint8_t** data, + const uint8_t* const begin, + const uint8_t* const end, + char* name) +{ + const NBNSLabelLength* lbl_len; + const NBNSLabelData* lbl_data; + const NBNSLabelPtr* lbl_ptr; + int i; + int j; + + if (end - *data < (int)sizeof(NBNSLabelLength)) + return -1; + lbl_len = (NBNSLabelLength*)(*data); + switch (lbl_len->len & NBNS_LENGTH_FLAGS) + { + case 0x00: + lbl_data = (NBNSLabelData*)(*data); + if (end - *data < (int)sizeof(NBNSLabelData)) + return -1; + *data += sizeof(NBNSLabelData); + break; + case 0xC0: + lbl_ptr = (NBNSLabelPtr*)(*data); + *data += sizeof(NBNSLabelPtr); + if (begin + lbl_ptr->position + sizeof(NBNSLabelData) > end) + return -1; + lbl_data = (NBNSLabelData*)(begin + lbl_ptr->position); + break; + default: + return -1; + } + if (lbl_data->len != NBNS_NAME_LEN) + return -1; + if (lbl_data->zero) + return -1; + for (i=0; i<(NBNS_NAME_LEN/2); i++) + { + j = 2 * i; + if (lbl_data->data[j] < 'A' || lbl_data->data[j] > 'Z') + return -1; + name[i] = (uint8_t)(((uint8_t)(lbl_data->data[j] - 'A')) << 4); + j++; + if (lbl_data->data[i] < 'A' || lbl_data->data[i] > 'Z') + return -1; + name[i] |= (uint8_t)(lbl_data->data[j] - 'A'); + } + name[(NBNS_NAME_LEN/2)] = 0; + for (i=(NBNS_NAME_LEN/2)-1; i >= 0; i--) + { + if (name[i] == ' ') + name[i] = 0; + else if (name[i]) + break; + } + return 0; +} + +static int netbios_validate_name(const uint8_t** data, + const uint8_t* const begin, + const uint8_t* const end) +{ + const NBNSLabelLength* lbl_len; + const NBNSLabelData* lbl_data; + const NBNSLabelPtr* lbl_ptr; + int i; + + if (end - *data < (int)sizeof(NBNSLabelLength)) + return -1; + lbl_len = (NBNSLabelLength*)(*data); + switch (lbl_len->len & NBNS_LENGTH_FLAGS) + { + case 0x00: + lbl_data = (NBNSLabelData*)(*data); + if (end - *data < (int)sizeof(NBNSLabelData)) + return -1; + *data += sizeof(NBNSLabelData); + break; + case 0xC0: + lbl_ptr = (NBNSLabelPtr*)(*data); + *data += sizeof(NBNSLabelPtr); + if (begin + lbl_ptr->position + sizeof(NBNSLabelData) > end) + return -1; + lbl_data = (NBNSLabelData*)(begin + lbl_ptr->position); + break; + default: + return -1; + } + if (lbl_data->len != NBNS_NAME_LEN) + return -1; + if (lbl_data->zero) + return -1; + for (i=0; idata[i] < 'A' || lbl_data->data[i] > 'Z') + return -1; + return 0; +} + +static int netbios_validate_label(const uint8_t** data, + const uint8_t* const end) +{ + const NBNSLabel* lbl; + uint16_t tmp; + + if (end - *data < (int)sizeof(NBNSLabel)) + return -1; + lbl = (NBNSLabel*)(*data); + *data += sizeof(NBNSLabel); + tmp = ntohs(lbl->type); + if (tmp != NBNS_NB && tmp != NBNS_NBSTAT) + return -1; + return 0; +} + +static int nbns_validate_query(const uint8_t** data, const uint8_t* const begin, + const uint8_t* const end) +{ + int ret; + + ret = netbios_validate_name(data, begin, end); + if (!ret) + { + return netbios_validate_label(data, end); + } + return ret; +} + +static int nbns_validate_answer(const uint8_t** data, const uint8_t* const begin, + const uint8_t* const end) +{ + int ret; + uint16_t tmp; + + ret = netbios_validate_name(data, begin, end); + if (ret) + return ret; + ret = netbios_validate_label(data, end); + if (!ret) + { + const NBNSAnswerData* ad = (const NBNSAnswerData*)(*data); + if (end - *data < (int)sizeof(NBNSAnswerData)) + return -1; + *data += sizeof(NBNSAnswerData); + tmp = ntohs(ad->data_len); + if (end - *data < tmp) + return -1; + *data += tmp; + } + return ret; +} + +static int nbns_validate(ServiceValidationArgs* args) +{ + uint16_t i; + uint16_t count; + const NBNSHeader* hdr; + const uint8_t* begin; + const uint8_t* end; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (size < sizeof(NBNSHeader)) + goto fail; + hdr = (NBNSHeader*)data; + if ((hdr->Opcode > NBNS_OPCODE_QUERY && + hdr->Opcode < NBNS_OPCODE_REGISTRATION) || + (hdr->Opcode > NBNS_OPCODE_REFRESHALT && + hdr->Opcode < NBNS_OPCODE_MHREGISTRATION)) + { + goto fail; + } + if (hdr->trunc) + goto not_compatible; + if (hdr->broadcast) + goto not_compatible; + + begin = data; + end = data + size; + data += sizeof(NBNSHeader); + + if (!hdr->response) + { + if (dir == APP_ID_FROM_RESPONDER) + { + if (getAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED)) + goto success; + goto fail; + } + goto inprocess; + } + + if (hdr->replycode > NBNS_REPLYCODE_MAX) + goto fail; + + if (hdr->QCount) + { + count = ntohs(hdr->QCount); + for (i=0; iACount) + { + count = ntohs(hdr->ACount); + for (i=0; iNSCount) + { + count = ntohs(hdr->NSCount); + for (i=0; iARCount) + { + count = ntohs(hdr->ARCount); + for (i=0; iadd_service(flowp, args->pkt, dir, &nbns_svc_element, + APP_ID_NETBIOS_NS, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +inprocess: + netbios_service_mod.api->service_inprocess(flowp, args->pkt, dir, &nbns_svc_element); + return SERVICE_INPROCESS; + +fail: + netbios_service_mod.api->fail_service(flowp, args->pkt, dir, &nbns_svc_element, + netbios_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOMATCH; + +not_compatible: + netbios_service_mod.api->incompatible_data(flowp, args->pkt, dir, &nbns_svc_element, + netbios_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOT_COMPATIBLE; +} + +static void nbss_free_state(void* data) +{ + ServiceNBSSData* nd = (ServiceNBSSData*)data; + + if (nd) + { + snort_free(nd); + } +} + +static inline void smb_domain_skip_string(const uint8_t** data, uint16_t* size, uint16_t* offset, + const uint8_t unicode) +{ + if (unicode) + { + if (*size != 0 && ((*offset) % 2)) + { + (*offset)++; + (*data)++; + (*size)--; + } + while (*size > 1) + { + *size -= 2; + *offset += 2; + if (**data == 0) + { + *data += 2; + break; + } + else + { + *data += 2; + } + } + } + else + { + while (*size) + { + (*size)--; + (*offset)++; + if (**data == 0) + { + (*data)++; + break; + } + else + { + (*data)++; + } + } + } +} + +static inline void smb_find_domain(const uint8_t* data, uint16_t size, const int, + AppIdData* flowp, const Packet* pkt) +{ + const ServiceSMBHeader* smb; + const ServiceSMBAndXResponse* resp; + const ServiceSMBNegotiateProtocolResponse* np; + char domain[NBNS_NAME_LEN+1]; + unsigned pos = 0; + uint16_t byte_count; + uint16_t sec_len; + uint16_t wc; + uint8_t unicode; + uint32_t capabilities; + uint16_t offset; + + if (size < sizeof(*smb) + sizeof(wc)) + return; + smb = (ServiceSMBHeader*)data; + if (smb->status != SERVICE_SMB_STATUS_SUCCESS) + return; + if (!(smb->flags[0] & SERVICE_SMB_FLAGS_RESPONSE)) + return; + unicode = smb->flags[2] & SERVICE_SMB_FLAGS_UNICODE; + data += sizeof(*smb); + size -= sizeof(*smb); + resp = (ServiceSMBAndXResponse*)data; + np = (ServiceSMBNegotiateProtocolResponse*)data; + wc = 2 * (uint16_t)*data; + offset = 1; + data++; + size--; + if (size < (wc + sizeof(byte_count))) + return; + data += wc; + size -= wc; + byte_count = LETOHS(data); + data += sizeof(byte_count); + size -= sizeof(byte_count); + if (size < byte_count) + return; + offset += sizeof(byte_count); + offset += wc; + if (smb->command == SERVICE_SMB_COMMAND_SESSION_SETUP_ANDX_RESPONSE) + { + if (wc == 8) + { + sec_len = LETOHS(&resp->sec_len); + if (sec_len >= byte_count) + return; + data += sec_len; + byte_count -= sec_len; + } + else if (wc != 6) + return; + smb_domain_skip_string(&data, &byte_count, &offset, unicode); + smb_domain_skip_string(&data, &byte_count, &offset, unicode); + if (byte_count != 0 && (offset % 2)) + { + data++; + byte_count--; + } + } + else if (smb->command == SERVICE_SMB_COMMAND_NEGOTIATE_PROTOCOL) + { + if (wc == 34) + { + capabilities = LETOHL(&np->capabilities); + if (capabilities & SERVICE_SMB_CAPABILITIES_EXTENDED_SECURITY) + return; + unicode = (capabilities & SERVICE_SMB_CAPABILITIES_UNICODE) || unicode; + } + else if (wc != 26) + return; + if (np->sec_len >= byte_count) + return; + data += np->sec_len; + byte_count -= np->sec_len; + } + else + return; + if (unicode) + { + int found = 0; + while (byte_count > 1) + { + byte_count -= 2; + if (*data == 0) + { + data += 2; + found = 1; + break; + } + else + { + if (pos < NBNS_NAME_LEN) + { + domain[pos] = *data; + pos++; + domain[pos] = 0; + } + data++; + if (*data != 0) + { +#ifdef RNA_DEBUG_NETBIOS + _dpd.errMsg("Failed command %02X %u 0x%08X:%u->0x%08X:%u", + smb->command, byte_count, pkt->src_ip.s_addr, pkt->ptrs.sp, + pkt->dst_ip.s_addr, pkt->ptrs.dp); +#else + UNUSED(pkt); +#endif + return; + } + data++; + } + } + if (!found && byte_count == 1 && *data == 0) + { + byte_count--; + } + if (byte_count && smb->command != SERVICE_SMB_COMMAND_NEGOTIATE_PROTOCOL) + { +#ifdef RNA_DEBUG_NETBIOS + _dpd.errMsg("Failed command %02X %u 0x%08X:%u->0x%08X:%u", + smb->command, byte_count, pkt->src_ip.s_addr, pkt->src_port, pkt->dst_ip.s_addr, + pkt->dst_port); +#endif + return; + } + } + else + { + while (byte_count) + { + byte_count--; + if (*data == 0) + { + data++; + break; + } + else + { + if (pos < NBNS_NAME_LEN) + { + domain[pos] = *data; + pos++; + domain[pos] = 0; + } + data++; + } + } + if (byte_count && smb->command != SERVICE_SMB_COMMAND_NEGOTIATE_PROTOCOL) + { +#ifdef RNA_DEBUG_NETBIOS + _dpd.errMsg("Failed command %02X %u 0x%08X:%u->0x%08X:%u", + smb->command, byte_count, pkt->src_ip.s_addr, pkt->src_port, pkt->dst_ip.s_addr, + pkt->dst_port); +#endif + return; + } + } + if (pos) + { +#ifdef RNA_DEBUG_NETBIOS + _dpd.debugMsg(DEBUG_LOG, "Found domain %s for command %02X 0x%08X:%u->0x%08X:%u", + domain, smb->command, pkt->src_ip.s_addr, pkt->src_port, pkt->dst_ip.s_addr, + pkt->dst_port); +#endif + if (!flowp->netbiosDomain) + flowp->netbiosDomain = snort_strdup(domain); + } +} + +static int nbss_validate(ServiceValidationArgs* args) +{ + ServiceNBSSData* nd; + const NBSSHeader* hdr; + const uint8_t* end; + uint32_t tmp; + int retval = -1; + AppIdData* flowp = args->flowp; + Packet* pkt = args->pkt; + const uint8_t* data = args->data; + const int dir = args->dir; + uint16_t size = args->size; + + if (dir != APP_ID_FROM_RESPONDER) + goto inprocess; + if (!size) + goto inprocess; + + nd = (ServiceNBSSData*)netbios_service_mod.api->data_get(flowp, + netbios_service_mod.flow_data_index); + if (!nd) + { + nd = (ServiceNBSSData*)snort_calloc(sizeof(ServiceNBSSData)); + netbios_service_mod.api->data_add(flowp, nd, + netbios_service_mod.flow_data_index, &nbss_free_state); + nd->state = NBSS_STATE_CONNECTION; + nd->serviceAppId = APP_ID_NETBIOS_SSN; + nd->miscAppId = APP_ID_NONE; + } + + end = data + size; + while (data < end) + { + switch (nd->state) + { + case NBSS_STATE_CONNECTION: + if (size < sizeof(NBSSHeader)) + goto fail; + hdr = (NBSSHeader*)data; + data += sizeof(NBSSHeader); + nd->state = NBSS_STATE_ERROR; + switch (hdr->type) + { + case NBSS_TYPE_RESP_POSITIVE: + if (hdr->flags || hdr->length) + goto fail; + nd->state = NBSS_STATE_FLOW; + break; + case NBSS_TYPE_RESP_NEGATIVE: + if (hdr->flags || ntohs(hdr->length) != 1) + goto fail; + if (data >= end) + goto fail; + if (*data < 0x80 || (*data > 0x83 && *data < 0x8F) || *data > 0x8F) + goto fail; + data++; + break; + case NBSS_TYPE_MESSAGE: + if (hdr->flags & 0xFE) + goto fail; + nd->length = ((uint32_t)(hdr->flags & 0x01)) << 16; + nd->length |= (uint32_t)ntohs(hdr->length); + tmp = end - data; + if (tmp >= sizeof(NB_SMB_BANNER) && + nd->length >= sizeof(NB_SMB_BANNER) && + !memcmp(data, NB_SMB_BANNER, sizeof(NB_SMB_BANNER))) + { + if (nd->serviceAppId != APP_ID_DCE_RPC) + { + nd->serviceAppId = APP_ID_NETBIOS_SSN; + } + + if (nd->length <= tmp) + { + smb_find_domain(data + sizeof(NB_SMB_BANNER), + nd->length - sizeof(NB_SMB_BANNER), + dir, flowp, pkt); + } + } + else if (tmp >= 4 && nd->length >= 4 && + !(*((uint32_t*)data)) && + dcerpc_validate(data+4, ((int)min(tmp, nd->length)) - 4) > 0) + { + nd->serviceAppId = APP_ID_DCE_RPC; + nd->miscAppId = APP_ID_NETBIOS_SSN; + } + + if (tmp < nd->length) + { + data = end; + nd->length -= tmp; + nd->state = NBSS_STATE_CONT; + } + else + { + data += nd->length; + nd->count++; + nd->state = NBSS_STATE_FLOW; + } + break; + case NBSS_TYPE_RESP_RETARGET: + if (hdr->flags || ntohs(hdr->length) != 6) + goto fail; + if (end - data < 6) + goto fail; + data += 6; + break; + default: + goto fail; + } + break; + case NBSS_STATE_FLOW: + if (size < sizeof(NBSSHeader)) + goto fail; + hdr = (NBSSHeader*)data; + data += sizeof(NBSSHeader); + switch (hdr->type) + { + case NBSS_TYPE_KEEP_ALIVE: + if (hdr->flags || hdr->length) + goto fail; + break; + case NBSS_TYPE_MESSAGE: + if (hdr->flags & 0xFE) + goto fail; + nd->length = ((uint32_t)(hdr->flags & 0x01)) << 16; + nd->length += (uint32_t)ntohs(hdr->length); + tmp = end - data; + if (tmp >= sizeof(NB_SMB_BANNER) && + nd->length >= sizeof(NB_SMB_BANNER) && + !memcmp(data, NB_SMB_BANNER, sizeof(NB_SMB_BANNER))) + { + if (nd->serviceAppId != APP_ID_DCE_RPC) + { + nd->serviceAppId = APP_ID_NETBIOS_SSN; + } + if (nd->length <= tmp) + { + smb_find_domain(data + sizeof(NB_SMB_BANNER), nd->length, dir, flowp, pkt); + } + } + else if (tmp >= 4 && nd->length >= 4 && + !(*((uint32_t*)data)) && + !(dcerpc_validate(data+4, ((int)min(tmp, nd->length)) - 4) > 0)) + { + nd->serviceAppId = APP_ID_DCE_RPC; + nd->miscAppId = APP_ID_NETBIOS_SSN; + } + + if (tmp < nd->length) + { + data = end; + nd->length -= tmp; + nd->state = NBSS_STATE_CONT; + } + else + { + data += nd->length; + if (nd->count < NBSS_COUNT_THRESHOLD) + { + nd->count++; + if (nd->count >= NBSS_COUNT_THRESHOLD) + { + retval = SERVICE_SUCCESS; + } + } + } + break; + default: + goto fail; + } + break; + case NBSS_STATE_CONT: + tmp = end - data; + if (tmp < nd->length) + { + data = end; + nd->length -= tmp; + } + else + { + data += nd->length; + nd->state = NBSS_STATE_FLOW; + if (nd->count < NBSS_COUNT_THRESHOLD) + { + nd->count++; + if (nd->count >= NBSS_COUNT_THRESHOLD) + { + retval = SERVICE_SUCCESS; + } + } + } + break; + default: + goto fail; + } + } + if (retval == -1) + goto inprocess; + + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + if (netbios_service_mod.api->add_service(flowp, pkt, dir, &nbss_svc_element, + nd->serviceAppId, nullptr, nullptr, nullptr) == SERVICE_SUCCESS) + { + netbios_service_mod.api->add_misc(flowp, nd->miscAppId); + } + } + return SERVICE_SUCCESS; + +inprocess: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + netbios_service_mod.api->service_inprocess(flowp, pkt, dir, &nbss_svc_element); + } + return SERVICE_INPROCESS; + +fail: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + netbios_service_mod.api->fail_service(flowp, pkt, dir, &nbss_svc_element, + netbios_service_mod.flow_data_index, args->pConfig); + } + return SERVICE_NOMATCH; +} + +static int nbdgm_validate(ServiceValidationArgs* args) +{ + const NBDgmHeader* hdr; + const NBDgmError* err; + const uint8_t* end; + const ServiceSMBHeader* smb; + const ServiceSMBTransactionHeader* trans; + const ServiceSMBBrowserHeader* browser; + uint16_t len; + char source_name[(NBNS_NAME_LEN/2)+1]; + uint32_t server_type; + AppId serviceAppId = APP_ID_NETBIOS_DGM; + AppId miscAppId = APP_ID_NONE; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (size < sizeof(NBDgmHeader)) + goto fail; + if (pkt->ptrs.sp != pkt->ptrs.dp) + goto fail; + + source_name[0] = 0; + end = data + size; + + hdr = (NBDgmHeader*)data; + data += sizeof(NBDgmHeader); + if (hdr->zero) + goto fail; + if (!hdr->first || hdr->more) + goto fail; + + switch (hdr->type) + { + case NBDGM_TYPE_POSITIVE_REPSONSE: + case NBDGM_TYPE_NEGATIVE_RESPONSE: + case NBDGM_TYPE_REQUEST: + if (netbios_validate_name(&data, data, end)) + goto fail; + if (end != data) + goto fail; + goto success; + case NBDGM_TYPE_DIRECT_UNIQUE: + case NBDGM_TYPE_DIRECT_GROUP: + case NBDGM_TYPE_BROADCAST: + data += sizeof(uint16_t) + sizeof(uint16_t); /* dgm_length and packet_offset */ + if (data >= end) + goto fail; + if (netbios_validate_name_and_decode(&data, data, end, source_name)) + goto fail; + if (data >= end) + goto fail; + if (netbios_validate_name(&data, data, end)) + goto fail; + if (data >= end) + goto fail; + if (end-data >= (int)sizeof(NB_SMB_BANNER) && + !memcmp(data, NB_SMB_BANNER, sizeof(NB_SMB_BANNER))) + { + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + serviceAppId = APP_ID_NETBIOS_DGM; + } + data += sizeof(NB_SMB_BANNER); + if (end-data < (int)sizeof(ServiceSMBHeader)) + goto not_mailslot; + smb = (ServiceSMBHeader*)data; + data += sizeof(ServiceSMBHeader); + if (smb->command != SERVICE_SMB_TRANSACTION_COMMAND) + goto not_mailslot; + if (end-data < (int)sizeof(ServiceSMBTransactionHeader)) + goto not_mailslot; + trans = (ServiceSMBTransactionHeader*)data; + data += sizeof(ServiceSMBTransactionHeader); + if (trans->wc == SERVICE_SMB_NOT_TRANSACTION_WC) + goto not_mailslot; + if ((unsigned)(end-data) < (trans->sc*2)+sizeof(uint16_t)+sizeof(mailslot)+ + sizeof(ServiceSMBBrowserHeader)) + goto not_mailslot; + data += (trans->sc*2); + len = *((uint16_t*)data); + data += sizeof(uint16_t); + if (end-data < len) + goto not_mailslot; + if (memcmp(data, mailslot, sizeof(mailslot))) + goto not_mailslot; + data += sizeof(mailslot); + browser = (ServiceSMBBrowserHeader*)data; + if (browser->command != SERVICE_SMB_MAILSLOT_HOST && + browser->command != SERVICE_SMB_MAILSLOT_LOCAL_MASTER) + { + goto not_mailslot; + } + server_type = LETOHL(&browser->server_type); + netbios_service_mod.api->analyzefp(flowp, browser->major, browser->minor, server_type); + } +not_mailslot: + if (source_name[0]) + netbios_service_mod.api->add_host_info(flowp, SERVICE_HOST_INFO_NETBIOS_NAME, + source_name); + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + goto success; + case NBDGM_TYPE_ERROR: + if (end-data < (int)sizeof(NBDgmError)) + goto fail; + err = (NBDgmError*)data; + data += sizeof(NBDgmError); + if (end != data) + goto fail; + if (err->code < NBDGM_ERROR_CODE_MIN || + err->code > NBDGM_ERROR_CODE_MAX) + { + goto fail; + } + goto success; + default: + break; + } + +fail: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + netbios_service_mod.api->fail_service(flowp, pkt, dir, &nbdgm_svc_element, + netbios_service_mod.flow_data_index, + args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOMATCH; + +success: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + if (dir == APP_ID_FROM_RESPONDER) + { + if (netbios_service_mod.api->add_service(flowp, pkt, dir, &nbdgm_svc_element, + serviceAppId, nullptr, nullptr, nullptr) == SERVICE_SUCCESS) + { + netbios_service_mod.api->add_misc(flowp, miscAppId); + } + } + } + return SERVICE_SUCCESS; + +inprocess: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + netbios_service_mod.api->service_inprocess(flowp, pkt, dir, &nbdgm_svc_element); + } + return SERVICE_INPROCESS; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_netbios.h b/src/network_inspectors/appid/service_plugins/service_netbios.h new file mode 100644 index 000000000..e886ae220 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_netbios.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_netbios.h author Sourcefire Inc. + +#ifndef SERVICE_NETBIOS_H +#define SERVICE_NETBIOS_H + +#include "service_api.h" + +extern RNAServiceValidationModule netbios_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_nntp.cc b/src/network_inspectors/appid/service_plugins/service_nntp.cc new file mode 100644 index 000000000..f823a4613 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_nntp.cc @@ -0,0 +1,390 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_nntp.cc author Sourcefire Inc. + +#include "service_nntp.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_api.h" + +#define NNTP_PORT 119 + +#define NNTP_COUNT_THRESHOLD 4 + +enum NNTPState +{ + NNTP_STATE_CONNECTION, + NNTP_STATE_TRANSFER, + NNTP_STATE_DATA, + NNTP_STATE_CONNECTION_ERROR +}; + +#define NNTP_CR_RECEIVED 0x0001 +#define NNTP_MID_LINE 0x0002 +#define NNTP_MID_TERM 0x0004 + +struct ServiceNNTPData +{ + NNTPState state; + uint32_t flags; + unsigned count; +}; + +#pragma pack(1) + +struct ServiceNNTPCode +{ + uint8_t code[3]; + uint8_t sp; +}; + +#pragma pack() + +static int nntp_init(const IniServiceAPI* const init_api); +static int nntp_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &nntp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "nntp" +}; + +static RNAServiceValidationPort pp[] = +{ + { &nntp_validate, NNTP_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule nntp_service_mod = +{ + "NNTP", + &nntp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +#define NNTP_PATTERN1 "200 " +#define NNTP_PATTERN2 "201 " + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_NNTP, 0 } +}; + +static int nntp_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&nntp_validate, IpProtocol::TCP, (uint8_t*)NNTP_PATTERN1, + sizeof(NNTP_PATTERN1)-1, 0, "nntp", init_api->pAppidConfig); + init_api->RegisterPattern(&nntp_validate, IpProtocol::TCP, (uint8_t*)NNTP_PATTERN2, + sizeof(NNTP_PATTERN2)-1, 0, "nntp", init_api->pAppidConfig); + + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&nntp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int nntp_validate_reply(const uint8_t* data, uint16_t* offset, + uint16_t size) +{ + const ServiceNNTPCode* code_hdr; + int code; + + /* Trim any blank lines (be a little tolerant) */ + for (; *offsetsp != ' ') + return -1; + + if (code_hdr->code[0] < '1' || code_hdr->code[0] > '5') + return -1; + code = (code_hdr->code[0] - '0') * 100; + + if (code_hdr->code[1] < '0' || + (code_hdr->code[1] > '5' && code_hdr->code[1] < '8') || + code_hdr->code[1] > '9') + { + return -1; + } + code += (code_hdr->code[1] - '0') * 10; + + if (!isdigit(code_hdr->code[2])) + return -1; + code += code_hdr->code[2] - '0'; + + /* We have a valid code, now we need to see if the rest of the line + is okay */ + + *offset += sizeof(ServiceNNTPCode); + for (; *offset < size; (*offset)++) + { + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + if (data[*offset] != 0x0A) + return -1; + } + if (data[*offset] == 0x0A) + { + (*offset)++; + return code; + } + else if (!isprint(data[*offset])) + return -1; + } + + return 0; +} + +static int nntp_validate_data(const uint8_t* data, uint16_t* offset, + uint16_t size, int* flags) +{ + if (*flags & NNTP_CR_RECEIVED) + { + if (data[*offset] != 0x0A) + return -1; + if (*flags & NNTP_MID_TERM) + { + *flags = 0; + (*offset)++; + return 1; + } + *flags &= ~NNTP_CR_RECEIVED; + (*offset)++; + } + if (*flags & NNTP_MID_TERM) + { + if (*offset >= size) + return 0; + if (data[*offset] == 0x0D) + { + *flags |= NNTP_CR_RECEIVED; + (*offset)++; + if (*offset >= size) + return 0; + if (data[*offset] != 0x0A) + return -1; + *flags = 0; + (*offset)++; + return 1; + } + else if (data[*offset] == 0x0A) + { + *flags = 0; + (*offset)++; + return 1; + } + else if (data[*offset] != '.') + return -1; + *flags = NNTP_MID_LINE; + (*offset)++; + } + for (; *offset < size; (*offset)++) + { + if (!(*flags & NNTP_MID_LINE)) + { + if (data[*offset] == '.') + { + *flags |= NNTP_MID_TERM; + (*offset)++; + if (*offset >= size) + return 0; + if (data[*offset] == 0x0D) + { + *flags |= NNTP_CR_RECEIVED; + (*offset)++; + if (*offset >= size) + return 0; + if (data[*offset] != 0x0A) + return -1; + *flags = 0; + (*offset)++; + return 1; + } + else if (data[*offset] == 0x0A) + { + *flags = 0; + (*offset)++; + return 1; + } + else if (data[*offset] != '.') + return -1; + (*offset)++; + } + } + *flags = NNTP_MID_LINE; + for (; *offset < size; (*offset)++) + { + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + { + *flags |= NNTP_CR_RECEIVED; + return 0; + } + if (data[*offset] != 0x0A) + return -1; + *flags = 0; + break; + } + if (data[*offset] == 0x0A) + { + *flags = 0; + break; + } + } + } + return 0; +} + +static int nntp_validate(ServiceValidationArgs* args) +{ + ServiceNNTPData* nd; + uint16_t offset; + int code; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + nd = (ServiceNNTPData*)nntp_service_mod.api->data_get(flowp, nntp_service_mod.flow_data_index); + if (!nd) + { + nd = (ServiceNNTPData*)snort_calloc(sizeof(ServiceNNTPData)); + nntp_service_mod.api->data_add(flowp, nd, nntp_service_mod.flow_data_index, &snort_free); + nd->state = NNTP_STATE_CONNECTION; + } + + offset = 0; + while (offset < size) + { + if (nd->state == NNTP_STATE_DATA) + { + if ((code=nntp_validate_data(data, &offset, size, (int*)&nd->flags)) < 0) + goto fail; + if (!code) + goto inprocess; + nd->state = NNTP_STATE_TRANSFER; + } + if ((code=nntp_validate_reply(data, &offset, size)) < 0) + goto fail; + if (!code) + goto inprocess; + if (code == 400 || code == 502) + { + nd->state = NNTP_STATE_CONNECTION_ERROR; + } + else + { + switch (nd->state) + { + case NNTP_STATE_CONNECTION: + switch (code) + { + case 201: + case 200: + nd->state = NNTP_STATE_TRANSFER; + break; + default: + goto fail; + } + break; + case NNTP_STATE_TRANSFER: + nd->count++; + if (nd->count >= NNTP_COUNT_THRESHOLD) + goto success; + switch (code) + { + case 100: + case 215: + case 220: + case 221: + case 222: + case 224: + case 230: + case 231: + nd->state = NNTP_STATE_DATA; + break; + } + break; + case NNTP_STATE_CONNECTION_ERROR: + default: + goto fail; + } + } + } + +inprocess: + nntp_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + nntp_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_NNTP, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + nntp_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + nntp_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_nntp.h b/src/network_inspectors/appid/service_plugins/service_nntp.h new file mode 100644 index 000000000..477109014 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_nntp.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_nntp.h author Sourcefire Inc. + +#ifndef SERVICE_NNTP_H +#define SERVICE_NNTP_H + +#include "service_api.h" + +extern RNAServiceValidationModule nntp_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_ntp.cc b/src/network_inspectors/appid/service_plugins/service_ntp.cc new file mode 100644 index 000000000..f922f7dd0 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_ntp.cc @@ -0,0 +1,178 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_ntp.cc author Sourcefire Inc. + +#include "service_ntp.h" + +#include "main/snort_debug.h" + +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_api.h" + +#pragma pack(1) + +struct ServiceNTPTimestamp +{ + uint32_t sec; + uint32_t frac; +}; + +struct ServiceNTPHeader +{ + uint8_t LVM; + uint8_t stratum; + uint8_t poll; + int8_t precision; + uint32_t delay; + uint32_t dispersion; + uint32_t id; + ServiceNTPTimestamp ref; + ServiceNTPTimestamp orig; + ServiceNTPTimestamp recv; + ServiceNTPTimestamp xmit; +}; + +struct ServiceNTPOptional +{ + uint32_t keyid; + uint32_t digest[4]; +}; + +#pragma pack() + +static int ntp_init(const IniServiceAPI* const init_api); +static int ntp_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &ntp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "ntp" +}; + +static RNAServiceValidationPort pp[] = +{ + { &ntp_validate, 123, IpProtocol::UDP, 0 }, + { &ntp_validate, 123, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule ntp_service_mod = +{ + "NTP", + &ntp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_NTP, 0 } +}; + +static int ntp_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&ntp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int ntp_validate(ServiceValidationArgs* args) +{ + const ServiceNTPHeader* nh; + uint8_t ver; + uint8_t mode; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + nh = (ServiceNTPHeader*)data; + + mode = nh->LVM & 0x07; + if (mode == 0 || mode == 7 || mode == 3) + goto fail; + ver = nh->LVM & 0x38; + if (ver > 0x20 || ver < 0x08) + goto fail; + if (mode != 6) + { + if (ver < 0x18) + { + if (size != sizeof(ServiceNTPHeader)) + goto fail; + } + else if (size < sizeof(ServiceNTPHeader) || + size > sizeof(ServiceNTPHeader)+sizeof(ServiceNTPOptional)) + { + goto fail; + } + + if (nh->stratum > 15) + goto fail; + if (nh->poll && (nh->poll < 4 || nh->poll > 14)) + goto fail; + if (nh->precision > -6 || nh->precision < -20) + goto fail; + } + else + { + if (size < 2) + goto fail; + if (!(nh->stratum & 0x80)) + goto fail; + if (!(nh->stratum & 0x1F)) + goto fail; + } + + ntp_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_NTP, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +inprocess: + ntp_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +fail: + ntp_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + ntp_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_ntp.h b/src/network_inspectors/appid/service_plugins/service_ntp.h new file mode 100644 index 000000000..523f7e3b9 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_ntp.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_ntp.h author Sourcefire Inc. + +#ifndef SERVICE_NTP_H +#define SERVICE_NTP_H + +#include "service_api.h" + +extern RNAServiceValidationModule ntp_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_radius.cc b/src/network_inspectors/appid/service_plugins/service_radius.cc new file mode 100644 index 000000000..f26408e01 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_radius.cc @@ -0,0 +1,340 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_radius.cc author Sourcefire Inc. + +#include "service_radius.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_api.h" + +#define RADIUS_CODE_ACCESS_REQUEST 1 +#define RADIUS_CODE_ACCESS_ACCEPT 2 +#define RADIUS_CODE_ACCESS_REJECT 3 +#define RADIUS_CODE_ACCOUNTING_REQUEST 4 +#define RADIUS_CODE_ACCOUNTING_RESPONSE 5 +#define RADIUS_CODE_ACCESS_CHALLENGE 11 + +enum RADIUSState +{ + RADIUS_STATE_REQUEST, + RADIUS_STATE_RESPONSE +}; + +struct ServiceRADIUSData +{ + RADIUSState state; + uint8_t id; +}; + +#pragma pack(1) + +struct RADIUSHeader +{ + uint8_t code; + uint8_t id; + uint16_t length; + uint8_t auth[16]; +}; + +#pragma pack() + +static int radius_init(const IniServiceAPI* const init_api); +static int radius_validate(ServiceValidationArgs* args); +static int radius_validate_accounting(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &radius_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "radius" +}; + +static RNAServiceElement acct_svc_element = +{ + nullptr, + &radius_validate_accounting, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "radacct" +}; + +static RNAServiceValidationPort pp[] = +{ + { &radius_validate, 1812, IpProtocol::UDP, 0 }, + { &radius_validate, 1812, IpProtocol::UDP, 1 }, + { &radius_validate_accounting, 1813, IpProtocol::UDP, 0 }, + { &radius_validate_accounting, 1813, IpProtocol::UDP, 1 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule radius_service_mod = +{ + "RADIUS", + &radius_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_RADIUS_ACCT, APPINFO_FLAG_SERVICE_UDP_REVERSED }, + { APP_ID_RADIUS, APPINFO_FLAG_SERVICE_UDP_REVERSED } +}; + +static int radius_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&radius_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int radius_validate(ServiceValidationArgs* args) +{ + ServiceRADIUSData* rd; + const RADIUSHeader* hdr = (const RADIUSHeader*)args->data; + uint16_t len; + int new_dir; + AppIdData* flowp = args->flowp; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (size < sizeof(RADIUSHeader)) + goto fail; + + rd = (ServiceRADIUSData*)radius_service_mod.api->data_get(flowp, + radius_service_mod.flow_data_index); + if (!rd) + { + rd = (ServiceRADIUSData*)snort_calloc(sizeof(ServiceRADIUSData)); + radius_service_mod.api->data_add(flowp, rd, radius_service_mod.flow_data_index, + &snort_free); + rd->state = RADIUS_STATE_REQUEST; + } + + new_dir = dir; + if (rd->state == RADIUS_STATE_REQUEST) + { + if (hdr->code == RADIUS_CODE_ACCESS_ACCEPT || + hdr->code == RADIUS_CODE_ACCESS_REJECT || + hdr->code == RADIUS_CODE_ACCESS_CHALLENGE) + { + setAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED); + rd->state = RADIUS_STATE_RESPONSE; + new_dir = APP_ID_FROM_RESPONDER; + } + } + else if (getAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED)) + { + new_dir = (dir == APP_ID_FROM_RESPONDER) ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER; + } + + switch (rd->state) + { + case RADIUS_STATE_REQUEST: + if (new_dir != APP_ID_FROM_INITIATOR) + goto inprocess; + if (hdr->code != RADIUS_CODE_ACCESS_REQUEST) + { + goto not_compatible; + } + len = ntohs(hdr->length); + if (len > size) + { + goto not_compatible; + } + /* Must contain a username attribute */ + if (len < sizeof(RADIUSHeader)+3) + { + goto not_compatible; + } + rd->id = hdr->id; + rd->state = RADIUS_STATE_RESPONSE; + break; + case RADIUS_STATE_RESPONSE: + if (new_dir != APP_ID_FROM_RESPONDER) + goto inprocess; + if (hdr->code != RADIUS_CODE_ACCESS_ACCEPT && + hdr->code != RADIUS_CODE_ACCESS_REJECT && + hdr->code != RADIUS_CODE_ACCESS_CHALLENGE) + { + goto fail; + } + len = ntohs(hdr->length); + if (len > size) + goto fail; + /* Must contain a username attribute */ + if (len < sizeof(RADIUSHeader)) + goto fail; + if (hdr->id != rd->id) + { + rd->state = RADIUS_STATE_REQUEST; + goto inprocess; + } + goto success; + default: + goto fail; + } +inprocess: + radius_service_mod.api->service_inprocess(flowp, args->pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +success: + radius_service_mod.api->add_service(flowp, args->pkt, dir, &svc_element, + APP_ID_RADIUS, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +not_compatible: + radius_service_mod.api->incompatible_data(flowp, args->pkt, dir, &svc_element, + radius_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOT_COMPATIBLE; + +fail: + radius_service_mod.api->fail_service(flowp, args->pkt, dir, &svc_element, + radius_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + +static int radius_validate_accounting(ServiceValidationArgs* args) +{ + ServiceRADIUSData* rd; + const RADIUSHeader* hdr = (const RADIUSHeader*)args->data; + uint16_t len; + int new_dir; + AppIdData* flowp = args->flowp; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (size < sizeof(RADIUSHeader)) + goto fail; + + rd = (ServiceRADIUSData*)radius_service_mod.api->data_get(flowp, + radius_service_mod.flow_data_index); + if (!rd) + { + rd = (ServiceRADIUSData*)snort_calloc(sizeof(ServiceRADIUSData)); + radius_service_mod.api->data_add(flowp, rd, radius_service_mod.flow_data_index, + &snort_free); + rd->state = RADIUS_STATE_REQUEST; + } + + new_dir = dir; + if (rd->state == RADIUS_STATE_REQUEST) + { + if (hdr->code == RADIUS_CODE_ACCOUNTING_RESPONSE) + { + setAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED); + rd->state = RADIUS_STATE_RESPONSE; + new_dir = APP_ID_FROM_RESPONDER; + } + } + else if (getAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED)) + { + new_dir = (dir == APP_ID_FROM_RESPONDER) ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER; + } + + switch (rd->state) + { + case RADIUS_STATE_REQUEST: + if (new_dir != APP_ID_FROM_INITIATOR) + goto inprocess; + if (hdr->code != RADIUS_CODE_ACCOUNTING_REQUEST) + { + goto not_compatible; + } + len = ntohs(hdr->length); + if (len > size) + { + goto not_compatible; + } + /* Must contain a username attribute */ + if (len < sizeof(RADIUSHeader)+3) + { + goto not_compatible; + } + rd->id = hdr->id; + rd->state = RADIUS_STATE_RESPONSE; + break; + case RADIUS_STATE_RESPONSE: + if (new_dir != APP_ID_FROM_RESPONDER) + goto inprocess; + if (hdr->code != RADIUS_CODE_ACCOUNTING_RESPONSE) + goto fail; + len = ntohs(hdr->length); + if (len > size) + goto fail; + /* Must contain a NAS-IP-Address or NAS-Identifier attribute */ + if (len < sizeof(RADIUSHeader)) + goto fail; + if (hdr->id != rd->id) + { + rd->state = RADIUS_STATE_REQUEST; + goto inprocess; + } + goto success; + default: + goto fail; + } +inprocess: + radius_service_mod.api->service_inprocess(flowp, args->pkt, dir, &acct_svc_element); + return SERVICE_INPROCESS; + +success: + radius_service_mod.api->add_service(flowp, args->pkt, dir, &acct_svc_element, + APP_ID_RADIUS_ACCT, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +not_compatible: + radius_service_mod.api->incompatible_data(flowp, args->pkt, dir, &acct_svc_element, + radius_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOT_COMPATIBLE; + +fail: + radius_service_mod.api->fail_service(flowp, args->pkt, dir, &acct_svc_element, + radius_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_radius.h b/src/network_inspectors/appid/service_plugins/service_radius.h new file mode 100644 index 000000000..4912c895c --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_radius.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_radius.h author Sourcefire Inc. + +#ifndef SERVICE_RADIUS_H +#define SERVICE_RADIUS_H + +#include "service_api.h" + +extern RNAServiceValidationModule radius_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_rexec.cc b/src/network_inspectors/appid/service_plugins/service_rexec.cc new file mode 100644 index 000000000..6d54d9814 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rexec.cc @@ -0,0 +1,355 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rexec.cc author Sourcefire Inc. + +#include "service_rexec.h" + +#include +#include +#include +#include +#include +#include + +#include "protocols/packet.h" +#include "main/snort_debug.h" +#include "target_based/snort_protocols.h" +#include "utils/util.h" + +#include "appid_api.h" +#include "app_info_table.h" +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_api.h" +#include "service_base.h" + +#define REXEC_PORT 512 +#define REXEC_MAX_PORT_PACKET 6 + +enum REXECState +{ + REXEC_STATE_PORT, + REXEC_STATE_SERVER_CONNECT, + REXEC_STATE_USERNAME, + REXEC_STATE_PASSWORD, + REXEC_STATE_COMMAND, + REXEC_STATE_REPLY, + REXEC_STATE_DONE, + REXEC_STATE_STDERR_CONNECT_SYN, + REXEC_STATE_STDERR_CONNECT_SYN_ACK +}; + +struct ServiceREXECData +{ + REXECState state; + struct ServiceREXECData* parent; + struct ServiceREXECData* child; +}; + +static int rexec_init(const IniServiceAPI* const init_api); +static int rexec_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &rexec_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "rexec" +}; + +static RNAServiceValidationPort pp[] = +{ + { &rexec_validate, REXEC_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule rexec_service_mod = +{ + "REXEC", + &rexec_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_EXEC, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int16_t app_id = 0; + +static int rexec_init(const IniServiceAPI* const init_api) +{ + unsigned i; + + app_id = AddProtocolReference("rexec"); + + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&rexec_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static void rexec_free_state(void* data) +{ + ServiceREXECData* rd = (ServiceREXECData*)data; + + if (rd) + { + if (rd->parent) + { + rd->parent->child = nullptr; + rd->parent->parent = nullptr; + } + if (rd->child) + { + rd->child->parent = nullptr; + rd->child->child = nullptr; + } + snort_free(rd); + } +} + +static int rexec_validate(ServiceValidationArgs* args) +{ + int i; + uint32_t port; + AppIdData* pf; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + + ServiceREXECData* rd = (ServiceREXECData*)rexec_service_mod.api->data_get( + flowp, rexec_service_mod.flow_data_index); + + if (!rd) + { + if (!size) + goto inprocess; + rd = (ServiceREXECData*)snort_calloc(sizeof(ServiceREXECData)); + rexec_service_mod.api->data_add(flowp, rd, + rexec_service_mod.flow_data_index, &rexec_free_state); + rd->state = REXEC_STATE_PORT; + } + + switch (rd->state) + { + case REXEC_STATE_PORT: + if (dir != APP_ID_FROM_INITIATOR) + goto bail; + if (size > REXEC_MAX_PORT_PACKET) + goto bail; + if (data[size-1]) + goto bail; + port = 0; + for (i=0; i 65535) + goto bail; + if (port && pkt) + { + const sfip_t* sip; + const sfip_t* dip; + + dip = pkt->ptrs.ip_api.get_dst(); + sip = pkt->ptrs.ip_api.get_src(); + pf = rexec_service_mod.api->flow_new(flowp, pkt, dip, 0, sip, (uint16_t)port, + IpProtocol::TCP, app_id, + APPID_EARLY_SESSION_FLAG_FW_RULE); + if (pf) + { + ServiceREXECData* tmp_rd = (ServiceREXECData*)snort_calloc( + sizeof(ServiceREXECData)); + tmp_rd->state = REXEC_STATE_STDERR_CONNECT_SYN; + tmp_rd->parent = rd; + + rexec_service_mod.api->data_add(pf, tmp_rd, + rexec_service_mod.flow_data_index, &rexec_free_state); + if (rexec_service_mod.api->data_add_id(pf, (uint16_t)port, &svc_element)) + { + pf->rnaServiceState = RNA_STATE_FINISHED; + tmp_rd->state = REXEC_STATE_DONE; + tmp_rd->parent = nullptr; + return SERVICE_ENULL; + } + rd->child = tmp_rd; + rd->state = REXEC_STATE_SERVER_CONNECT; + pf->rnaServiceState = RNA_STATE_STATEFUL; + pf->scan_flags |= SCAN_HOST_PORT_FLAG; + PopulateExpectedFlow(flowp, pf, + APPID_SESSION_CONTINUE | + APPID_SESSION_REXEC_STDERR | + APPID_SESSION_NO_TPI | + APPID_SESSION_SERVICE_DETECTED | + APPID_SESSION_NOT_A_SERVICE | + APPID_SESSION_PORT_SERVICE_DONE); + pf->rnaServiceState = RNA_STATE_STATEFUL; + } + else + rd->state = REXEC_STATE_USERNAME; + } + else + rd->state = REXEC_STATE_USERNAME; + break; + case REXEC_STATE_SERVER_CONNECT: + if (!size) + break; + /* The only valid way out of this state is for the child flow to change it. */ + goto fail; + case REXEC_STATE_USERNAME: + if (!size) + break; + if (dir != APP_ID_FROM_INITIATOR) + goto bail; + for (i=0; istate = REXEC_STATE_PASSWORD; + if (i >= size) + goto bail; + i++; + data += i; + size -= i; + /* Fall through */ + case REXEC_STATE_PASSWORD: + if (!size) + break; + if (dir != APP_ID_FROM_INITIATOR) + goto bail; + for (i=0; istate = REXEC_STATE_COMMAND; + if (i >= size) + goto bail; + i++; + data += i; + size -= i; + /* Fall through */ + case REXEC_STATE_COMMAND: + if (!size) + break; + if (dir != APP_ID_FROM_INITIATOR) + goto bail; + for (i=0; istate = REXEC_STATE_COMMAND; + if (i >= size) + goto bail; + i++; + data += i; + size -= i; + if (!size) + { + rd->state = REXEC_STATE_REPLY; + break; + } + if (data[size-1]) + goto bail; + /* stdin */ + for (i=0; istate = REXEC_STATE_REPLY; + break; + case REXEC_STATE_REPLY: + if (!size) + goto inprocess; + if (dir != APP_ID_FROM_RESPONDER) + goto fail; + if (size != 1) + goto fail; + goto success; + break; + case REXEC_STATE_STDERR_CONNECT_SYN: + rd->state = REXEC_STATE_STDERR_CONNECT_SYN_ACK; + break; + case REXEC_STATE_STDERR_CONNECT_SYN_ACK: + if (rd->parent && rd->parent->state == REXEC_STATE_SERVER_CONNECT) + { + rd->parent->state = REXEC_STATE_USERNAME; + clearAppIdFlag(flowp, APPID_SESSION_REXEC_STDERR); + } + goto bail; + default: + goto bail; + } + +inprocess: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rexec_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + } + return SERVICE_INPROCESS; + +success: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rexec_service_mod.api->add_service(flowp, pkt, dir, &svc_element, + APP_ID_EXEC, nullptr, nullptr, nullptr); + } + return SERVICE_SUCCESS; + +bail: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rexec_service_mod.api->incompatible_data(flowp, pkt, dir, &svc_element, + rexec_service_mod.flow_data_index, + args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOT_COMPATIBLE; + +fail: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rexec_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + rexec_service_mod.flow_data_index, + args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_rexec.h b/src/network_inspectors/appid/service_plugins/service_rexec.h new file mode 100644 index 000000000..e3b2b03f5 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rexec.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rexec.h author Sourcefire Inc. + +#ifndef SERVICE_REXEC_H +#define SERVICE_REXEC_H + +#include "service_api.h" + +extern RNAServiceValidationModule rexec_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_rfb.cc b/src/network_inspectors/appid/service_plugins/service_rfb.cc new file mode 100644 index 000000000..a0845c726 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rfb.cc @@ -0,0 +1,143 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rfb.cc author Sourcefire Inc. + +#include "service_rfb.h" + +#include "service_api.h" +#include "app_info_table.h" +#include "application_ids.h" + +#include "main/snort_debug.h" + +#define RFB_BANNER_SIZE 12 + +#define RFB_BANNER "RFB " + +static int rfb_init(const IniServiceAPI* const init_api); +static int rfb_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &rfb_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "rfb" +}; + +static RNAServiceValidationPort pp[] = +{ + { &rfb_validate, 5900, IpProtocol::TCP, 0 }, + { &rfb_validate, 5901, IpProtocol::TCP, 0 }, + { &rfb_validate, 5902, IpProtocol::TCP, 0 }, + { &rfb_validate, 5903, IpProtocol::TCP, 0 }, + { &rfb_validate, 5904, IpProtocol::TCP, 0 }, + { &rfb_validate, 5905, IpProtocol::TCP, 0 }, + { &rfb_validate, 5906, IpProtocol::TCP, 0 }, + { &rfb_validate, 5907, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule rfb_service_mod = +{ + "RFB", + &rfb_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_VNC, APPINFO_FLAG_SERVICE_ADDITIONAL }, + { APP_ID_VNC_RFB, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int rfb_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&rfb_validate, IpProtocol::TCP, (uint8_t*)RFB_BANNER, + sizeof(RFB_BANNER) - 1, 0, "rfb", init_api->pAppidConfig); + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&rfb_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int rfb_validate(ServiceValidationArgs* args) +{ + char version[RFB_BANNER_SIZE-4]; + unsigned i; + char* v; + const unsigned char* p; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + if (size != RFB_BANNER_SIZE) + goto fail; + if (strncmp(RFB_BANNER, (char*)data, sizeof(RFB_BANNER)-1)) + goto fail; + if (data[7] != '.' || data[RFB_BANNER_SIZE-1] != 0x0A) + goto fail; + if (!isdigit(data[4]) || !isdigit(data[5]) || !isdigit(data[6]) || + !isdigit(data[8]) || !isdigit(data[9]) || !isdigit(data[10])) + { + goto fail; + } + v = version; + p = &data[4]; + for (i=4; iadd_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_VNC_RFB, nullptr, version, nullptr); + return SERVICE_SUCCESS; + +inprocess: + rfb_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +fail: + rfb_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + rfb_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_rfb.h b/src/network_inspectors/appid/service_plugins/service_rfb.h new file mode 100644 index 000000000..ad847c7b8 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rfb.h @@ -0,0 +1,28 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rfb.h author Sourcefire Inc. + +#ifndef SERVICE_RFB_H +#define SERVICE_RFB_H + +struct RNAServiceValidationModule; +extern RNAServiceValidationModule rfb_service_mod; + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_rlogin.cc b/src/network_inspectors/appid/service_plugins/service_rlogin.cc new file mode 100644 index 000000000..d7c9b78bf --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rlogin.cc @@ -0,0 +1,175 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rlogin.cc author Sourcefire Inc. + +#include "service_rlogin.h" + +#include "service_api.h" +#include "application_ids.h" + +#include "main/snort_debug.h" +#include "protocols/tcp.h" +#include "utils/util.h" + +#define RLOGIN_PASSWORD "Password: " +enum RLOGINState +{ + RLOGIN_STATE_HANDSHAKE, + RLOGIN_STATE_PASSWORD, + RLOGIN_STATE_CRLF, + RLOGIN_STATE_DATA, + RLOGIN_STATE_DONE +}; + +struct ServiceRLOGINData +{ + RLOGINState state; +}; + +static int rlogin_init(const IniServiceAPI* const init_api); +static int rlogin_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &rlogin_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "rlogin" +}; + +static RNAServiceValidationPort pp[] = +{ + { &rlogin_validate, 513, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule rlogin_service_mod = +{ + "RLOGIN", + &rlogin_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_RLOGIN, 0 } +}; + +static int rlogin_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&rlogin_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int rlogin_validate(ServiceValidationArgs* args) +{ + ServiceRLOGINData* rd; + AppIdData* flowp = args->flowp; + Packet* pkt = args->pkt; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + rd = (ServiceRLOGINData*)rlogin_service_mod.api->data_get(flowp, + rlogin_service_mod.flow_data_index); + if (!rd) + { + rd = (ServiceRLOGINData*)snort_calloc(sizeof(ServiceRLOGINData)); + rlogin_service_mod.api->data_add(flowp, rd, rlogin_service_mod.flow_data_index, + &snort_free); + rd->state = RLOGIN_STATE_HANDSHAKE; + } + + switch (rd->state) + { + case RLOGIN_STATE_HANDSHAKE: + if (size != 1) + goto fail; + if (*data) + goto fail; + rd->state = RLOGIN_STATE_PASSWORD; + break; + case RLOGIN_STATE_PASSWORD: + if (pkt->ptrs.tcph->are_flags_set(TH_URG) && size >= pkt->ptrs.tcph->urp()) + { + if (size != 1) + goto fail; + if (*data != 0x80) + goto fail; + rd->state = RLOGIN_STATE_DATA; + } + else + { + if (size != sizeof(RLOGIN_PASSWORD)-1) + goto fail; + if (strncmp((char*)data, RLOGIN_PASSWORD, sizeof(RLOGIN_PASSWORD)-1)) + goto fail; + rd->state = RLOGIN_STATE_CRLF; + } + break; + case RLOGIN_STATE_CRLF: + if (size != 2) + goto fail; + if (*data != 0x0A || *(data+1) != 0x0D) + goto fail; + rd->state = RLOGIN_STATE_DATA; + break; + case RLOGIN_STATE_DATA: + rd->state = RLOGIN_STATE_DONE; + goto success; + default: + goto fail; + } + +inprocess: + rlogin_service_mod.api->service_inprocess(flowp, pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + rlogin_service_mod.api->add_service(flowp, pkt, args->dir, &svc_element, + APP_ID_RLOGIN, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + rlogin_service_mod.api->fail_service(flowp, pkt, args->dir, &svc_element, + rlogin_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_rlogin.h b/src/network_inspectors/appid/service_plugins/service_rlogin.h new file mode 100644 index 000000000..fb684a39c --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rlogin.h @@ -0,0 +1,28 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rlogin.h author Sourcefire Inc. + +#ifndef SERVICE_RLOGIN_H +#define SERVICE_RLOGIN_H + +struct RNAServiceValidationModule; +extern RNAServiceValidationModule rlogin_service_mod; + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_rpc.cc b/src/network_inspectors/appid/service_plugins/service_rpc.cc new file mode 100644 index 000000000..3a160bd8e --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rpc.cc @@ -0,0 +1,952 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rpc.cc author Sourcefire Inc. + +#include "service_rpc.h" + +#include + +#if defined(FREEBSD) || defined(OPENBSD) +#include "rpc/rpc.h" +#endif + +#include "service_api.h" +#include "app_info_table.h" + +#include "log/messages.h" +#include "main/snort_debug.h" +#include "application_ids.h" +#include "target_based/snort_protocols.h" +#include "utils/util.h" + +/*#define RNA_DEBUG_RPC 1 */ + +enum RPCState +{ + RPC_STATE_CALL, + RPC_STATE_REPLY, + RPC_STATE_DONE +}; + +enum RPCTCPState +{ + RPC_TCP_STATE_FRAG, + RPC_TCP_STATE_HEADER, + RPC_TCP_STATE_CRED, + RPC_TCP_STATE_CRED_DATA, + RPC_TCP_STATE_VERIFY, + RPC_TCP_STATE_VERIFY_DATA, + RPC_TCP_STATE_REPLY_HEADER, + RPC_TCP_STATE_PARTIAL, + RPC_TCP_STATE_DONE +}; + +enum RPCReplyState +{ + RPC_REPLY_BEGIN, + RPC_REPLY_MULTI, + RPC_REPLY_MID +}; + +#define min(x,y) ((x)<(y) ? (x) : (y)) + +#define RPC_TYPE_CALL 0 +#define RPC_TYPE_REPLY 1 + +#define RPC_PROGRAM_PORTMAP 100000 +#define RPC_PORTMAP_GETPORT 3 + +#define RPC_REPLY_ACCEPTED 0 +#define RPC_REPLY_DENIED 1 + +#define RPC_MAX_ACCEPTED 4 +#define RPC_MAX_DENIED 5 + +#define RPC_TCP_FRAG_MASK 0x80000000 + +/* sizeof(ServiceRPCCall)+sizeof(_SERVICE_RPC_PORTMAP)==56 */ +#define RPC_MAX_TCP_PACKET_SIZE 56 + +#pragma pack(1) + +struct ServiceRPCFragment +{ + uint32_t length; +}; + +struct ServiceRPCAuth +{ + uint32_t flavor; + uint32_t length; +}; + +struct ServiceRPCPortmap +{ + uint32_t program; + uint32_t version; + uint32_t proto; + uint32_t port; +}; + +struct ServiceRPCPortmapReply +{ + uint32_t port; +}; + +struct ServiceRPC +{ + uint32_t xid; + uint32_t type; +}; + +struct ServiceRPCCall +{ + ServiceRPC header; + uint32_t version; + uint32_t program; + uint32_t program_version; + uint32_t procedure; + ServiceRPCAuth cred; + ServiceRPCAuth verify; +}; + +struct ServiceRPCReply +{ + ServiceRPC header; + uint32_t reply_state; + ServiceRPCAuth verify; + uint32_t state; +}; + +#pragma pack() + +struct ServiceRPCData +{ + RPCState state; + RPCTCPState tcpstate[APP_ID_APPID_SESSION_DIRECTION_MAX]; + RPCTCPState tcpfragstate[APP_ID_APPID_SESSION_DIRECTION_MAX]; + uint32_t program; + uint32_t procedure; + uint32_t xid; + uint32_t proto; + uint32_t tcpsize[APP_ID_APPID_SESSION_DIRECTION_MAX]; + uint32_t tcpfragpos[APP_ID_APPID_SESSION_DIRECTION_MAX]; + uint32_t tcpauthsize[APP_ID_APPID_SESSION_DIRECTION_MAX]; + uint32_t tcppos[APP_ID_APPID_SESSION_DIRECTION_MAX]; + uint8_t tcpdata[APP_ID_APPID_SESSION_DIRECTION_MAX][RPC_MAX_TCP_PACKET_SIZE]; + int once; +}; + +static int rpc_init(const IniServiceAPI* const init_api); +static int rpc_validate(ServiceValidationArgs* args); +static int rpc_tcp_validate(ServiceValidationArgs* args); + +static RNAServiceElement svc_element = +{ + nullptr, + &rpc_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "rpc" +}; +static RNAServiceElement tcp_svc_element = +{ + nullptr, + &rpc_tcp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "tcp rpc" +}; + +#define RPC_PORT_PORTMAPPER 111 +#define RPC_PORT_NFS 2049 +#define RPC_PORT_MOUNTD 4046 +#define RPC_PORT_NLOCKMGR 4045 + +static RNAServiceValidationPort pp[] = +{ + { &rpc_validate, RPC_PORT_PORTMAPPER, IpProtocol::UDP, 0 }, + { &rpc_validate, RPC_PORT_PORTMAPPER, IpProtocol::UDP, 1 }, + { &rpc_tcp_validate, RPC_PORT_PORTMAPPER, IpProtocol::TCP, 0 }, + { &rpc_validate, RPC_PORT_NFS, IpProtocol::UDP, 0 }, + { &rpc_validate, RPC_PORT_NFS, IpProtocol::UDP, 1 }, + { &rpc_tcp_validate, RPC_PORT_NFS, IpProtocol::TCP, 0 }, + { &rpc_validate, RPC_PORT_MOUNTD, IpProtocol::UDP, 0 }, + { &rpc_validate, RPC_PORT_MOUNTD, IpProtocol::UDP, 1 }, + { &rpc_tcp_validate, RPC_PORT_MOUNTD, IpProtocol::TCP, 0 }, + { &rpc_validate, RPC_PORT_NLOCKMGR, IpProtocol::UDP, 0 }, + { &rpc_validate, RPC_PORT_NLOCKMGR, IpProtocol::UDP, 1 }, + { &rpc_tcp_validate, RPC_PORT_NLOCKMGR, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule rpc_service_mod = +{ + "RPC", + &rpc_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +struct RPCProgram +{ + RPCProgram* next; + uint32_t program; + char* name; +}; + +static RPCProgram* rpc_programs; + +static uint8_t rpc_reply_accepted_pattern[8] = { 0,0,0,1,0,0,0,0 }; +static uint8_t rpc_reply_denied_pattern[8] = { 0,0,0,1,0,0,0,1 }; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_SUN_RPC, APPINFO_FLAG_SERVICE_ADDITIONAL | APPINFO_FLAG_SERVICE_UDP_REVERSED } +}; + +static int16_t app_id = 0; + +static int rpc_init(const IniServiceAPI* const init_api) +{ + struct rpcent* rpc; + RPCProgram* prog; + + app_id = AddProtocolReference("sunrpc"); + + if (!rpc_programs) + { + while ((rpc = getrpcent())) + { + if (rpc->r_name) + { + prog = (RPCProgram*)snort_calloc(sizeof(RPCProgram)); + prog->program = rpc->r_number; + prog->next = rpc_programs; + rpc_programs = prog; + prog->name = snort_strdup(rpc->r_name); + } + } + endrpcent(); + } + + init_api->RegisterPattern(&rpc_tcp_validate, IpProtocol::TCP, rpc_reply_accepted_pattern, + sizeof(rpc_reply_accepted_pattern), 8, "rpc", init_api->pAppidConfig); + init_api->RegisterPattern(&rpc_tcp_validate, IpProtocol::TCP, rpc_reply_denied_pattern, + sizeof(rpc_reply_denied_pattern), 8, "rpc", init_api->pAppidConfig); + init_api->RegisterPattern(&rpc_validate, IpProtocol::UDP, rpc_reply_accepted_pattern, + sizeof(rpc_reply_accepted_pattern), 4, "rpc", init_api->pAppidConfig); + init_api->RegisterPattern(&rpc_validate, IpProtocol::UDP, rpc_reply_denied_pattern, + sizeof(rpc_reply_denied_pattern), 4, "rpc", init_api->pAppidConfig); + + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&rpc_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static const RPCProgram* FindRPCProgram(uint32_t program) +{ + RPCProgram* rpc; + + for (rpc=rpc_programs; rpc; rpc=rpc->next) + { + if (program == rpc->program) + break; + } + return rpc; +} + +static int validate_packet(const uint8_t* data, uint16_t size, int dir, + AppIdData* flowp, Packet* pkt, ServiceRPCData* rd, + const char** pname, uint32_t* program) +{ + const ServiceRPCCall* call; + const ServiceRPCReply* reply; + const ServiceRPC* rpc; + const ServiceRPCPortmap* pm; + const ServiceRPCAuth* a; + const ServiceRPCPortmapReply* pmr; + uint32_t tmp; + uint32_t val; + const uint8_t* end; + AppIdData* pf; + const RPCProgram* rprog; + + if (!size) + return SERVICE_INPROCESS; + + end = data + size; + + if (flowp->proto == IpProtocol::UDP) + { + if (!rd->once) + { + rd->once = 1; + if (size < sizeof(ServiceRPC)) + return SERVICE_NOMATCH; + rpc = (ServiceRPC*)data; + if (ntohl(rpc->type) == RPC_TYPE_REPLY) + { + setAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED); + rd->state = RPC_STATE_REPLY; + dir = APP_ID_FROM_RESPONDER; + } + } + else if (getAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED)) + { + dir = (dir == APP_ID_FROM_RESPONDER) ? APP_ID_FROM_INITIATOR : APP_ID_FROM_RESPONDER; + } + } + + switch (rd->state) + { + case RPC_STATE_CALL: + if (dir != APP_ID_FROM_INITIATOR) + return SERVICE_INPROCESS; + rd->state = RPC_STATE_DONE; + if (size < sizeof(ServiceRPCCall)) + return SERVICE_NOT_COMPATIBLE; + call = (ServiceRPCCall*)data; + if (ntohl(call->header.type) != RPC_TYPE_CALL) + return SERVICE_NOT_COMPATIBLE; + if (ntohl(call->version) != 2) + return SERVICE_NOT_COMPATIBLE; + rd->program = ntohl(call->program); + rd->procedure = ntohl(call->procedure); + tmp = ntohl(call->cred.length); + if (sizeof(ServiceRPCCall)+tmp > size) + return SERVICE_NOT_COMPATIBLE; + data += (sizeof(ServiceRPCCall) - sizeof(ServiceRPCAuth)) + tmp; + a = (ServiceRPCAuth*)data; + tmp = ntohl(a->length); + if (tmp+sizeof(ServiceRPCAuth) > (unsigned)(end-data)) + return SERVICE_NOT_COMPATIBLE; + data += sizeof(ServiceRPCAuth) + tmp; + if (rd->program >= 0x60000000) + return SERVICE_NOT_COMPATIBLE; + switch (rd->program) + { + case RPC_PROGRAM_PORTMAP: + switch (rd->procedure) + { + case RPC_PORTMAP_GETPORT: + if (end-data < (int)sizeof(ServiceRPCPortmap)) + return SERVICE_NOT_COMPATIBLE; + pm = (ServiceRPCPortmap*)data; + rd->proto = pm->proto; + break; + default: + break; + } + break; + default: + break; + } + rd->xid = call->header.xid; + rd->state = RPC_STATE_REPLY; + break; + case RPC_STATE_REPLY: + if (dir != APP_ID_FROM_RESPONDER) + return SERVICE_INPROCESS; + rd->state = RPC_STATE_DONE; + if (size < sizeof(ServiceRPCReply)) + return SERVICE_NOMATCH; + reply = (ServiceRPCReply*)data; + if (ntohl(reply->header.type) != RPC_TYPE_REPLY) + return SERVICE_NOMATCH; + if (rd->xid != reply->header.xid && rd->xid != 0xFFFFFFFF) + return SERVICE_NOMATCH; + tmp = ntohl(reply->verify.length); + if (sizeof(ServiceRPCReply)+tmp > size) + return SERVICE_NOMATCH; + data += sizeof(ServiceRPCReply) + tmp; + tmp = ntohl(reply->reply_state); + val = ntohl(reply->state); + if (tmp == RPC_REPLY_ACCEPTED) + { + if (val > RPC_MAX_ACCEPTED) + return SERVICE_NOMATCH; + if (rd->xid == 0xFFFFFFFF && reply->header.xid != 0xFFFFFFFF) + { + rd->state = RPC_STATE_CALL; + return SERVICE_INPROCESS; + } + *program = rd->program; + switch (rd->program) + { + case RPC_PROGRAM_PORTMAP: + switch (rd->procedure) + { + case RPC_PORTMAP_GETPORT: + if (end-data < (int)sizeof(ServiceRPCPortmapReply)) + return SERVICE_NOMATCH; + pmr = (ServiceRPCPortmapReply*)data; + if (pmr->port) + { + const sfip_t* sip; + const sfip_t* dip; + + dip = pkt->ptrs.ip_api.get_dst(); + sip = pkt->ptrs.ip_api.get_src(); + tmp = ntohl(pmr->port); + pf = rpc_service_mod.api->flow_new(flowp, pkt, dip, 0, sip, (uint16_t)tmp, + // FIXIT-H: Change rd->proto to be IpProtocol + (IpProtocol)ntohl(rd->proto), app_id, 0); + if (pf) + { + rpc_service_mod.api->data_add_id(pf, (uint16_t)tmp, + flowp->proto==IpProtocol::TCP ? &tcp_svc_element : &svc_element); + pf->rnaServiceState = RNA_STATE_STATEFUL; + setAppIdFlag(pf, + getAppIdFlag(flowp, + APPID_SESSION_RESPONDER_MONITORED | + APPID_SESSION_INITIATOR_MONITORED | + APPID_SESSION_SPECIAL_MONITORED | + APPID_SESSION_RESPONDER_CHECKED | + APPID_SESSION_INITIATOR_CHECKED | + APPID_SESSION_DISCOVER_APP | + APPID_SESSION_DISCOVER_USER)); + } + } + break; + default: + break; + } + *pname = "portmap"; + break; + default: + rprog = FindRPCProgram(rd->program); + if (rprog && rprog->name) + *pname = rprog->name; + break; + } + } + else if (tmp == RPC_REPLY_DENIED) + { + if (val > RPC_MAX_DENIED) + return SERVICE_NOMATCH; + } + else + return SERVICE_NOMATCH; + rd->state = RPC_STATE_CALL; + return SERVICE_SUCCESS; + default: + return SERVICE_NOMATCH; + } + return SERVICE_INPROCESS; +} + +static int rpc_validate(ServiceValidationArgs* args) +{ + static char subname[64]; + ServiceRPCData* rd; + RNAServiceSubtype sub; + RNAServiceSubtype* subtype; + uint32_t program = 0; + const char* pname = nullptr; + int rval; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + { + rval = SERVICE_INPROCESS; + goto done; + } + + rd = (ServiceRPCData*)rpc_service_mod.api->data_get(flowp, rpc_service_mod.flow_data_index); + if (!rd) + { + rd = (ServiceRPCData*)snort_calloc(sizeof(ServiceRPCData)); + rpc_service_mod.api->data_add(flowp, rd, rpc_service_mod.flow_data_index, &snort_free); + rd->state = (dir == APP_ID_FROM_INITIATOR) ? RPC_STATE_CALL : RPC_STATE_REPLY; + rd->xid = 0xFFFFFFFF; + } + +#ifdef RNA_DEBUG_RPC + fprintf(SF_DEBUG_FILE, "Begin %u -> %u %u %d state %d\n", pkt->src_port, pkt->dst_port, + flowp->proto, dir, rd->state); +#endif + + rval = validate_packet(data, size, dir, flowp, pkt, rd, &pname, &program); + +#ifdef RNA_DEBUG_RPC + fprintf(SF_DEBUG_FILE, "End %u -> %u %u %d state %d rval %d\n", pkt->src_port, pkt->dst_port, + flowp->proto, dir, rd->state, rval); +#endif + +done: + switch (rval) + { + case SERVICE_INPROCESS: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rpc_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + } + return SERVICE_INPROCESS; + + case SERVICE_SUCCESS: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + if (pname && *pname) + { + memset(&sub, 0, sizeof(sub)); + sub.service = pname; + subtype = ⊂ + } + else if (program) + { + snprintf(subname, sizeof(subname), "(%u)", program); + memset(&sub, 0, sizeof(sub)); + sub.service = subname; + subtype = ⊂ + } + else + subtype = nullptr; + rpc_service_mod.api->add_service(flowp, pkt, dir, &svc_element, + APP_ID_SUN_RPC, nullptr, nullptr, subtype); + } + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_SUCCESS; + + case SERVICE_NOT_COMPATIBLE: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rpc_service_mod.api->incompatible_data(flowp, pkt, dir, &svc_element, + rpc_service_mod.flow_data_index, + args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOT_COMPATIBLE; + + case SERVICE_NOMATCH: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rpc_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + rpc_service_mod.flow_data_index, + args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOMATCH; + default: + return rval; + } +} + +static int rpc_tcp_validate(ServiceValidationArgs* args) +{ + ServiceRPCData* rd; + const ServiceRPCFragment* frag; + uint32_t length; + uint32_t fragsize; + int ret; + int retval = -1; + const ServiceRPCCall* call; + const ServiceRPCReply* reply; + + static char subname[64]; + RNAServiceSubtype sub; + RNAServiceSubtype* subtype; + uint32_t program = 0; + const char* pname = nullptr; + + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + + rd = (ServiceRPCData*)rpc_service_mod.api->data_get(flowp, rpc_service_mod.flow_data_index); + if (!rd) + { + rd = (ServiceRPCData*)snort_calloc(sizeof(ServiceRPCData)); + rpc_service_mod.api->data_add(flowp, rd, rpc_service_mod.flow_data_index, &snort_free); + rd->state = RPC_STATE_CALL; + for (ret=0; rettcpstate[ret] = RPC_TCP_STATE_FRAG; + rd->tcpfragstate[ret] = RPC_TCP_STATE_HEADER; + } + } + + while (size) + { + fragsize = min(size, (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK) - + rd->tcpfragpos[dir]); + + switch (rd->tcpstate[dir]) + { + case RPC_TCP_STATE_FRAG: + if (size < sizeof(ServiceRPCFragment)) + goto bail; + frag = (ServiceRPCFragment*)data; + data += sizeof(ServiceRPCFragment); + size -= sizeof(ServiceRPCFragment); + + rd->tcpsize[dir] = ntohl(frag->length); + rd->tcpfragpos[dir] = 0; + rd->tcpstate[dir] = rd->tcpfragstate[dir]; + break; + case RPC_TCP_STATE_HEADER: + if (dir == APP_ID_FROM_INITIATOR) + { + length = min(fragsize, offsetof(ServiceRPCCall, cred) - + rd->tcppos[dir]); + memcpy(&rd->tcpdata[dir][rd->tcppos[dir]], data, length); + rd->tcppos[dir] += length; + rd->tcpfragpos[dir] += length; + data += length; + size -= length; + if (rd->tcppos[dir] >= offsetof(ServiceRPCCall, cred)) + { + call = (ServiceRPCCall*)rd->tcpdata[dir]; + if (ntohl(call->header.type) != RPC_TYPE_CALL) + goto bail; + if (ntohl(call->version) != 2) + goto bail; + rd->tcpstate[dir] = RPC_TCP_STATE_CRED; + rd->tcppos[dir] = 0; + } + } + else + { + length = min(fragsize, offsetof(ServiceRPCReply, verify) - + rd->tcppos[dir]); + memcpy(&rd->tcpdata[dir][rd->tcppos[dir]], data, length); + rd->tcppos[dir] += length; + rd->tcpfragpos[dir] += length; + data += length; + size -= length; + if (rd->tcppos[dir] >= offsetof(ServiceRPCReply, verify)) + { + reply = (ServiceRPCReply*)rd->tcpdata[dir]; + if (ntohl(reply->header.type) != RPC_TYPE_REPLY) + goto fail; + rd->tcpstate[dir] = RPC_TCP_STATE_VERIFY; + rd->tcppos[dir] = 0; + } + } + break; + case RPC_TCP_STATE_CRED: + if (dir != APP_ID_FROM_INITIATOR) + goto bail; + length = min(fragsize, sizeof(ServiceRPCAuth) - rd->tcppos[dir]); + memcpy(&rd->tcpdata[dir][offsetof(ServiceRPCCall, cred)+rd->tcppos[dir]], + data, length); + rd->tcppos[dir] += length; + rd->tcpfragpos[dir] += length; + data += length; + size -= length; + if (rd->tcppos[dir] >= sizeof(ServiceRPCAuth)) + { + length = ntohl(((ServiceRPCCall*)rd->tcpdata[dir])->cred.length); + if (length > (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK) || + rd->tcpfragpos[dir]+length > (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK)) + goto bail; + rd->tcpauthsize[dir] = length; + rd->tcpstate[dir] = RPC_TCP_STATE_CRED_DATA; + rd->tcppos[dir] = 0; + } + break; + case RPC_TCP_STATE_CRED_DATA: + if (dir != APP_ID_FROM_INITIATOR) + goto bail; + length = min(fragsize, rd->tcpauthsize[dir] - rd->tcppos[dir]); + rd->tcppos[dir] += length; + rd->tcpfragpos[dir] += length; + data += length; + size -= length; + if (rd->tcppos[dir] >= rd->tcpauthsize[dir]) + { + ((ServiceRPCCall*)rd->tcpdata[dir])->cred.flavor = 0; + ((ServiceRPCCall*)rd->tcpdata[dir])->cred.length = 0; + rd->tcpstate[dir] = RPC_TCP_STATE_VERIFY; + rd->tcppos[dir] = 0; + } + break; + case RPC_TCP_STATE_VERIFY: + length = min(fragsize, sizeof(ServiceRPCAuth) - rd->tcppos[dir]); + if (dir == APP_ID_FROM_INITIATOR) + memcpy(&rd->tcpdata[dir][offsetof(ServiceRPCCall, verify)+rd->tcppos[dir]], + data, length); + else + memcpy(&rd->tcpdata[dir][offsetof(ServiceRPCReply, verify)+rd->tcppos[dir]], + data, length); + rd->tcppos[dir] += length; + rd->tcpfragpos[dir] += length; + data += length; + size -= length; + fragsize -= length; + if (rd->tcppos[dir] >= sizeof(ServiceRPCAuth)) + { + if (dir == APP_ID_FROM_INITIATOR) + length = ntohl(((ServiceRPCCall*)rd->tcpdata[dir])->verify.length); + else + length = ntohl(((ServiceRPCReply*)rd->tcpdata[dir])->verify.length); + if (length > (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK) || + rd->tcpfragpos[dir]+length > (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK)) + goto bail; + rd->tcpauthsize[dir] = length; + rd->tcpstate[dir] = RPC_TCP_STATE_VERIFY_DATA; + rd->tcppos[dir] = 0; + } + else + { + break; + } + case RPC_TCP_STATE_VERIFY_DATA: + length = min(fragsize, rd->tcpauthsize[dir] - rd->tcppos[dir]); + rd->tcppos[dir] += length; + rd->tcpfragpos[dir] += length; + data += length; + size -= length; + if (rd->tcppos[dir] >= rd->tcpauthsize[dir]) + { + if (dir == APP_ID_FROM_INITIATOR) + { + ((ServiceRPCCall*)rd->tcpdata[dir])->verify.flavor = 0; + ((ServiceRPCCall*)rd->tcpdata[dir])->verify.length = 0; + rd->tcpstate[dir] = RPC_TCP_STATE_PARTIAL; + rd->tcppos[dir] = sizeof(ServiceRPCCall); + if (rd->tcpfragpos[dir] >= (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK)) + { + if (rd->tcpsize[dir] & RPC_TCP_FRAG_MASK) + { +#ifdef RNA_DEBUG_RPC + fprintf(SF_DEBUG_FILE, "V Begin %u -> %u %u %d state %d\n", + pkt->src_port, pkt->dst_port, flowp->proto, dir, rd->state); +#endif + + ret = validate_packet(rd->tcpdata[dir], rd->tcppos[dir], dir, flowp, + pkt, + rd, &pname, &program); + +#ifdef RNA_DEBUG_RPC + fprintf(SF_DEBUG_FILE, "V End %u -> %u %u %d state %d rval %d\n", + pkt->src_port, pkt->dst_port, flowp->proto, dir, rd->state, ret); +#endif + + if (retval == -1) + retval = ret; + rd->tcpfragstate[dir] = RPC_TCP_STATE_HEADER; + rd->tcppos[dir] = 0; + } + else + rd->tcpfragstate[dir] = rd->tcpstate[dir]; + rd->tcpstate[dir] = RPC_TCP_STATE_FRAG; + } + } + else + { + ((ServiceRPCReply*)rd->tcpdata[dir])->verify.flavor = 0; + ((ServiceRPCReply*)rd->tcpdata[dir])->verify.length = 0; + rd->tcpstate[dir] = RPC_TCP_STATE_REPLY_HEADER; + if (rd->tcpfragpos[dir]+sizeof(uint32_t) > (rd->tcpsize[dir] & + ~RPC_TCP_FRAG_MASK)) + goto bail; + rd->tcppos[dir] = 0; + } + } + break; + case RPC_TCP_STATE_REPLY_HEADER: + if (dir != APP_ID_FROM_RESPONDER) + goto bail; + length = min(fragsize, sizeof(uint32_t) - rd->tcppos[dir]); + memcpy(&rd->tcpdata[dir][offsetof(ServiceRPCReply, state)+rd->tcppos[dir]], + data, length); + rd->tcppos[dir] += length; + rd->tcpfragpos[dir] += length; + data += length; + size -= length; + + if (rd->tcppos[dir] >= sizeof(uint32_t)) + { + rd->tcpstate[dir] = RPC_TCP_STATE_PARTIAL; + rd->tcppos[dir] = sizeof(ServiceRPCReply); + } + if (rd->tcpfragpos[dir] >= (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK)) + { + fragsize = 0; + } + else + { + break; + } + case RPC_TCP_STATE_PARTIAL: + if (rd->tcppos[dir] < RPC_MAX_TCP_PACKET_SIZE && fragsize) + { + length = min(fragsize, RPC_MAX_TCP_PACKET_SIZE - rd->tcppos[dir]); + memcpy(&rd->tcpdata[dir][rd->tcppos[dir]], data, length); + rd->tcppos[dir] += length; + } + else + { + length = fragsize; + } + rd->tcpfragpos[dir] += length; + data += length; + size -= length; + if (rd->tcpfragpos[dir] >= (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK)) + { + if (rd->tcpsize[dir] & RPC_TCP_FRAG_MASK) + { +#ifdef RNA_DEBUG_RPC + fprintf(SF_DEBUG_FILE, "P Begin %u -> %u %u %d state %d\n", pkt->src_port, + pkt->dst_port, flowp->proto, dir, rd->state); +#endif + + ret = validate_packet(rd->tcpdata[dir], rd->tcppos[dir], dir, flowp, pkt, + rd, &pname, &program); + +#ifdef RNA_DEBUG_RPC + fprintf(SF_DEBUG_FILE, "P End %u -> %u %u %d state %d rval %d\n", + pkt->src_port, pkt->dst_port, flowp->proto, dir, rd->state, ret); +#endif + + if (retval == -1) + retval = ret; + rd->tcpfragstate[dir] = RPC_TCP_STATE_HEADER; + rd->tcppos[dir] = 0; + } + else + rd->tcpfragstate[dir] = rd->tcpstate[dir]; + rd->tcpstate[dir] = RPC_TCP_STATE_FRAG; + } + break; + default: + if (retval == -1) + goto fail; + else + { + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + goto done; + } + } + if (rd->tcpstate[dir] != RPC_TCP_STATE_FRAG && + rd->tcpstate[dir] != RPC_TCP_STATE_PARTIAL && + rd->tcpfragpos[dir] >= (rd->tcpsize[dir] & ~RPC_TCP_FRAG_MASK)) + { + if (rd->tcpsize[dir] & RPC_TCP_FRAG_MASK) + goto bail; + rd->tcpfragstate[dir] = rd->tcpstate[dir]; + rd->tcpstate[dir] = RPC_TCP_STATE_FRAG; + } + } + if (retval == -1) + retval = SERVICE_INPROCESS; + +done: + switch (retval) + { + case SERVICE_INPROCESS: +inprocess: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rpc_service_mod.api->service_inprocess(flowp, pkt, dir, &tcp_svc_element); + } + return SERVICE_INPROCESS; + + case SERVICE_SUCCESS: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + if (pname && *pname) + { + memset(&sub, 0, sizeof(sub)); + sub.service = pname; + subtype = ⊂ + } + else if (program) + { + sprintf(subname, "(%u)", program); + memset(&sub, 0, sizeof(sub)); + sub.service = subname; + subtype = ⊂ + } + else + subtype = nullptr; + rpc_service_mod.api->add_service(flowp, pkt, dir, &tcp_svc_element, + APP_ID_SUN_RPC, nullptr, nullptr, subtype); + } + setAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_SUCCESS; + + case SERVICE_NOT_COMPATIBLE: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rpc_service_mod.api->incompatible_data(flowp, pkt, dir, &tcp_svc_element, + rpc_service_mod.flow_data_index, + args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOT_COMPATIBLE; + + case SERVICE_NOMATCH: +fail: + if (!getAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED)) + { + rpc_service_mod.api->fail_service(flowp, pkt, dir, &tcp_svc_element, + rpc_service_mod.flow_data_index, + args->pConfig); + } + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + return SERVICE_NOMATCH; + default: + return retval; + } + +bail: + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + rd->tcpstate[APP_ID_FROM_INITIATOR] = RPC_TCP_STATE_DONE; + rd->tcpstate[APP_ID_FROM_RESPONDER] = RPC_TCP_STATE_DONE; + if (dir == APP_ID_FROM_INITIATOR) + { + if (retval == -1) + retval = SERVICE_NOT_COMPATIBLE; + } + else + { + if (retval == -1) + retval = SERVICE_NOMATCH; + } + goto done; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_rpc.h b/src/network_inspectors/appid/service_plugins/service_rpc.h new file mode 100644 index 000000000..b2e8c03e4 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rpc.h @@ -0,0 +1,28 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rpc.h author Sourcefire Inc. + +#ifndef SERVICE_RPC_H +#define SERVICE_RPC_H + +struct RNAServiceValidationModule; +extern RNAServiceValidationModule rpc_service_mod; + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_rshell.cc b/src/network_inspectors/appid/service_plugins/service_rshell.cc new file mode 100644 index 000000000..c69ca2d5c --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rshell.cc @@ -0,0 +1,345 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rshell.cc author Sourcefire Inc. + +#include "service_rshell.h" + +#include "application_ids.h" +#include "service_api.h" +#include "service_base.h" +#include "app_info_table.h" + +#include "log/messages.h" +#include "main/snort_debug.h" +#include "target_based/snort_protocols.h" +#include "utils/util.h" + +#define RSHELL_PORT 514 +#define RSHELL_MAX_PORT_PACKET 6 + +enum RSHELLState +{ + RSHELL_STATE_PORT, + RSHELL_STATE_SERVER_CONNECT, + RSHELL_STATE_USERNAME, + RSHELL_STATE_USERNAME2, + RSHELL_STATE_COMMAND, + RSHELL_STATE_REPLY, + RSHELL_STATE_DONE, + RSHELL_STATE_STDERR_CONNECT_SYN, + RSHELL_STATE_STDERR_CONNECT_SYN_ACK +}; + +struct ServiceRSHELLData +{ + RSHELLState state; + ServiceRSHELLData* parent; + ServiceRSHELLData* child; +}; + +static int rshell_init(const IniServiceAPI* const init_api); +static int rshell_validate(ServiceValidationArgs* args); + +// FIXIT-L: Make the globals const or, if necessary, thread-local. +static RNAServiceElement svc_element = +{ + nullptr, + &rshell_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "rshell" +}; + +static RNAServiceValidationPort pp[] = +{ + { &rshell_validate, RSHELL_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule rshell_service_mod = +{ + "RSHELL", + &rshell_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_SHELL, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int16_t app_id = 0; + +static int rshell_init(const IniServiceAPI* const init_api) +{ + unsigned i; + + app_id = AddProtocolReference("rsh-error"); + + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&rshell_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static void rshell_free_state(void* data) +{ + ServiceRSHELLData* rd = (ServiceRSHELLData*)data; + + if (rd) + { + if (rd->parent) + { + rd->parent->child = nullptr; + rd->parent->parent = nullptr; + } + if (rd->child) + { + rd->child->parent = nullptr; + rd->child->child = nullptr; + } + snort_free(rd); + } +} + +static int rshell_validate(ServiceValidationArgs* args) +{ + ServiceRSHELLData* rd = nullptr; + ServiceRSHELLData* tmp_rd; + int i; + uint32_t port; + AppIdData* pf; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + bool app_id_debug_session_flag = args->app_id_debug_session_flag; + char* app_id_debug_session = args->app_id_debug_session; + + rd = (ServiceRSHELLData*)rshell_service_mod.api->data_get(flowp, + rshell_service_mod.flow_data_index); + if (!rd) + { + if (!size) + goto inprocess; + rd = (ServiceRSHELLData*)snort_calloc(sizeof(ServiceRSHELLData)); + rshell_service_mod.api->data_add(flowp, rd, + rshell_service_mod.flow_data_index, &rshell_free_state); + rd->state = RSHELL_STATE_PORT; + } + + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s rshell state %d\n", app_id_debug_session, rd->state); + + switch (rd->state) + { + case RSHELL_STATE_PORT: + if (dir != APP_ID_FROM_INITIATOR) + goto fail; + if (size > RSHELL_MAX_PORT_PACKET) + goto bail; + if (data[size-1]) + goto bail; + port = 0; + for (i=0; i 65535) + goto bail; + if (port) + { + const sfip_t* sip; + const sfip_t* dip; + + tmp_rd = (ServiceRSHELLData*)snort_calloc(sizeof(ServiceRSHELLData)); + tmp_rd->state = RSHELL_STATE_STDERR_CONNECT_SYN; + tmp_rd->parent = rd; + dip = pkt->ptrs.ip_api.get_dst(); + sip = pkt->ptrs.ip_api.get_src(); + // FIXIT-M can flow_new return null? + pf = rshell_service_mod.api->flow_new(flowp, pkt, dip, 0, sip, (uint16_t)port, + IpProtocol::TCP, app_id, APPID_EARLY_SESSION_FLAG_FW_RULE); + if (pf) + { + pf->rnaClientState = RNA_STATE_FINISHED; + rshell_service_mod.api->data_add(pf, tmp_rd, + rshell_service_mod.flow_data_index, &rshell_free_state); + if (rshell_service_mod.api->data_add_id(pf, (uint16_t)port, &svc_element)) + { + pf->rnaServiceState = RNA_STATE_FINISHED; + tmp_rd->state = RSHELL_STATE_DONE; + tmp_rd->parent = nullptr; + return SERVICE_ENOMEM; + } + pf->scan_flags |= SCAN_HOST_PORT_FLAG; + PopulateExpectedFlow(flowp, pf, + APPID_SESSION_NO_TPI | + APPID_SESSION_IGNORE_HOST | + APPID_SESSION_NOT_A_SERVICE | + APPID_SESSION_PORT_SERVICE_DONE); + pf->rnaServiceState = RNA_STATE_STATEFUL; + } + else + { + snort_free(tmp_rd); + return SERVICE_ENOMEM; + } + rd->child = tmp_rd; + rd->state = RSHELL_STATE_SERVER_CONNECT; + } + else + rd->state = RSHELL_STATE_USERNAME; + break; + case RSHELL_STATE_SERVER_CONNECT: + if (!size) + break; + /* The only valid way out of this state is for the child flow to change it. */ + goto fail; + case RSHELL_STATE_USERNAME: + if (!size) + break; + if (dir != APP_ID_FROM_INITIATOR) + goto fail; + for (i=0; istate = RSHELL_STATE_USERNAME2; + if (i >= size) + goto bail; + i++; + data += i; + size -= i; + /* Fall through */ + case RSHELL_STATE_USERNAME2: + if (!size) + break; + if (dir != APP_ID_FROM_INITIATOR) + goto fail; + for (i=0; istate = RSHELL_STATE_COMMAND; + if (i >= size) + goto bail; + i++; + data += i; + size -= i; + /* Fall through */ + case RSHELL_STATE_COMMAND: + if (!size) + break; + if (dir != APP_ID_FROM_INITIATOR) + goto fail; + for (i=0; istate = RSHELL_STATE_COMMAND; + if (i >= size) + goto bail; + i++; + data += i; + size -= i; + if (!size) + { + rd->state = RSHELL_STATE_REPLY; + break; + } + if (data[size-1]) + goto bail; + /* stdin */ + for (i=0; istate = RSHELL_STATE_REPLY; + break; + case RSHELL_STATE_REPLY: + if (!size) + goto inprocess; + if (dir != APP_ID_FROM_RESPONDER) + goto fail; + if (size == 1) + goto success; + if (*data == 0x01) + { + data++; + size--; + for (i=0; istate = RSHELL_STATE_STDERR_CONNECT_SYN_ACK; + break; + case RSHELL_STATE_STDERR_CONNECT_SYN_ACK: + if (rd->parent && rd->parent->state == RSHELL_STATE_SERVER_CONNECT) + rd->parent->state = RSHELL_STATE_USERNAME; + setAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED); + return SERVICE_SUCCESS; + default: + goto bail; + } + +inprocess: + rshell_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +success: + rshell_service_mod.api->add_service(flowp, pkt, dir, &svc_element, + APP_ID_SHELL, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +bail: + rshell_service_mod.api->incompatible_data(flowp, pkt, dir, &svc_element, + rshell_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOT_COMPATIBLE; + +fail: + rshell_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + rshell_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_rshell.h b/src/network_inspectors/appid/service_plugins/service_rshell.h new file mode 100644 index 000000000..a1ebe5b94 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rshell.h @@ -0,0 +1,29 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rshell.h author Sourcefire Inc. + +#ifndef SERVICE_RSHELL_H +#define SERVICE_RSHELL_H + +struct RNAServiceValidationModule; +// FIXIT-L: Make the globals const or, if necessary, thread-local. +extern RNAServiceValidationModule rshell_service_mod; + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_rsync.cc b/src/network_inspectors/appid/service_plugins/service_rsync.cc new file mode 100644 index 000000000..fc59e5cb7 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rsync.cc @@ -0,0 +1,165 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rsync.cc author Sourcefire Inc. + +#include "service_rsync.h" + +#include "application_ids.h" +#include "service_api.h" +#include "app_info_table.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#define RSYNC_PORT 873 + +#define RSYNC_BANNER "@RSYNCD: " + +enum RSYNCState +{ + RSYNC_STATE_BANNER, + RSYNC_STATE_MOTD, + RSYNC_STATE_DONE +}; + +struct ServiceRSYNCData +{ + RSYNCState state; +}; + +static int rsync_init(const IniServiceAPI* const init_api); +static int rsync_validate(ServiceValidationArgs* args); + +// FIXIT-L: Make the globals const or, if necessary, thread-local. +static RNAServiceElement svc_element = +{ + nullptr, + &rsync_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "rsync" +}; + +static RNAServiceValidationPort pp[] = +{ + { &rsync_validate, RSYNC_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule rsync_service_mod = +{ + "RSYNC", + &rsync_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_RSYNC, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int rsync_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&rsync_validate, IpProtocol::TCP, (uint8_t*)RSYNC_BANNER, + sizeof(RSYNC_BANNER)-1, 0, "rsync", init_api->pAppidConfig); + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_INSPECTOR,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&rsync_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int rsync_validate(ServiceValidationArgs* args) +{ + ServiceRSYNCData* rd; + int i; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + rd = (ServiceRSYNCData*)rsync_service_mod.api->data_get(flowp, + rsync_service_mod.flow_data_index); + if (!rd) + { + rd = (ServiceRSYNCData*)snort_calloc(sizeof(ServiceRSYNCData)); + rsync_service_mod.api->data_add(flowp, rd, rsync_service_mod.flow_data_index, &snort_free); + rd->state = RSYNC_STATE_BANNER; + } + + switch (rd->state) + { + case RSYNC_STATE_BANNER: + if (size < sizeof(RSYNC_BANNER)-1) + goto fail; + if (data[size-1] != 0x0A) + goto fail; + if (strncmp((char*)data, RSYNC_BANNER, sizeof(RSYNC_BANNER)-1)) + goto fail; + data += sizeof(RSYNC_BANNER) - 1; + size -= sizeof(RSYNC_BANNER) - 1; + for (i=0; istate = RSYNC_STATE_MOTD; + break; + case RSYNC_STATE_MOTD: + if (data[size-1] != 0x0A) + goto fail; + for (i=0; istate = RSYNC_STATE_DONE; + goto success; + default: + goto fail; + } + +inprocess: + rsync_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + rsync_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_RSYNC, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + rsync_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + rsync_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_rsync.h b/src/network_inspectors/appid/service_plugins/service_rsync.h new file mode 100644 index 000000000..8782a1f77 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rsync.h @@ -0,0 +1,29 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rsync.h author Sourcefire Inc. + +#ifndef SERVICE_RSYNC_H +#define SERVICE_RSYNC_H + +struct RNAServiceValidationModule; +// FIXIT-L: Make the globals const or, if necessary, thread-local. +extern RNAServiceValidationModule rsync_service_mod; + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_rtmp.cc b/src/network_inspectors/appid/service_plugins/service_rtmp.cc new file mode 100644 index 000000000..35b6d04a7 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rtmp.cc @@ -0,0 +1,707 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rtmp.cc author Sourcefire Inc. + +#include "service_rtmp.h" + +#include "application_ids.h" +#include "service_api.h" +#include "app_info_table.h" + +#include "log/messages.h" +#include "main/snort_debug.h" +#include "utils/util.h" + +#define RTMP_PORT 1935 + +#define RTMP_VER_3 3 + +#define RTMP_HANDSHAKE1_SIZE 1536 /* C1/S1 */ +#define RTMP_HANDSHAKE2_SIZE 1536 /* C2/S2 */ + +#define RTMP_CHUNK_SIZE 128 + +#define RTMP_AMF0_COMMAND_MESSAGE_ID 20 + +#define RTMP_COMMAND_TYPE_CONNECT "connect" +#define RTMP_COMMAND_TYPE_CONNECT_LEN 7 + +#define RTMP_PROPERTY_KEY_SWFURL "swfUrl" +#define RTMP_PROPERTY_KEY_SWFURL_LEN 6 +#define RTMP_PROPERTY_KEY_PAGEURL "pageUrl" +#define RTMP_PROPERTY_KEY_PAGEURL_LEN 7 + +#define AMF0_TYPE_NUMBER 0x00 +#define AMF0_TYPE_BOOLEAN 0x01 +#define AMF0_TYPE_STRING 0x02 +#define AMF0_TYPE_OBJECT 0x03 +#define AMF0_TYPE_OBJECT_END 0x09 /* Preceded by 0x00,0x00. */ + +#define CHECK_SIZE(n) do { if (size < (n)) goto parse_rtmp_message_fail; } while (0) +#define ADVANCE_DATA(n) do { data += (n); size -= (n); } while (0) + +enum RTMPState +{ + RTMP_STATE_INIT = 0, /* Haven't seen anything yet. */ + RTMP_STATE_SENT_HANDSHAKE0, /* C0/S0 */ + RTMP_STATE_SENDING_HANDSHAKE1, /* C1/S1 -- client/server_bytes_left */ + RTMP_STATE_SENT_HANDSHAKE1, /* C1/S1 */ + RTMP_STATE_SENDING_HANDSHAKE2, /* C2/S2 -- client/server_bytes_left */ + RTMP_STATE_SENT_HANDSHAKE2, /* C2/S2 */ + RTMP_STATE_DONE /* As in "this detector is done watching the client or + server". */ +}; + +struct ServiceRTMPData +{ + RTMPState client_state; + RTMPState server_state; + uint16_t client_bytes_left; + uint16_t server_bytes_left; + char* swfUrl; + char* pageUrl; +}; + +static int rtmp_init(const IniServiceAPI* const api); +static int rtmp_validate(ServiceValidationArgs* args); + +// FIXIT-L: Make the globals const or, if necessary, thread-local. +static RNAServiceElement svc_element = +{ + nullptr, + &rtmp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "rtmp" +}; + +static RNAServiceValidationPort pp[] = +{ + { &rtmp_validate, 1935, IpProtocol::TCP, 0 }, + { &rtmp_validate, 1935, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule rtmp_service_mod = +{ + "rtmp", + &rtmp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_RTMP, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int rtmp_init(const IniServiceAPI* const init_api) +{ + unsigned i; + for (i = 0; i < (sizeof(appIdRegistry) / sizeof(*appIdRegistry)); i++) + { + DebugFormat(DEBUG_INSPECTOR, "registering appId: %d\n", appIdRegistry[i].appId); + init_api->RegisterAppId(&rtmp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + return 0; +} + +void rtmp_free(void* ss) /* AppIdFreeFCN */ +{ + ServiceRTMPData* ss_tmp = (ServiceRTMPData*)ss; + snort_free(ss_tmp->swfUrl); + snort_free(ss_tmp->pageUrl); + snort_free(ss_tmp); +} + +int parse_rtmp_chunk_basic_header(const uint8_t** data_inout, uint16_t* size_inout, + uint8_t* format, uint32_t* chunk_stream_id) +{ + const uint8_t* data = *data_inout; + uint16_t size = *size_inout; + + if (size < 1) + return 0; + *format = (data[0] & 0xC0) >> 6; + + *chunk_stream_id = (data[0] & 0x3F); + if (*chunk_stream_id == 0) + { + if (size < 2) + return 0; + *chunk_stream_id = data[1] + 64; + data += 2; + size -= 2; + } + else if (*chunk_stream_id == 1) + { + *chunk_stream_id = data[2] * 256 + data[1] + 64; + if (size < 3) + return 0; + data += 3; + size -= 3; + } + else + { + data += 1; + size -= 1; + } + + *data_inout = data; + *size_inout = size; + return 1; +} + +int parse_rtmp_messgage_header(const uint8_t** data_inout, uint16_t* size_inout, + uint32_t* chunk_stream_id, uint32_t* message_length, uint8_t* message_type_id) +{ + const uint8_t* data = *data_inout; + uint16_t size = *size_inout; + + uint8_t fmt; + unsigned hdr_len; + + if (!parse_rtmp_chunk_basic_header(&data, &size, &fmt, chunk_stream_id)) + return 0; + switch (fmt) + { + case 0: + hdr_len = 11; + break; + case 1: + hdr_len = 7; + break; + default: + return 0; + } + if (size < hdr_len) + return 0; + + *message_length = (data[3] << 16) + (data[4] << 8) + data[5]; + *message_type_id = data[6]; + + data += hdr_len; + size -= hdr_len; + + *data_inout = data; + *size_inout = size; + return 1; +} + +int unchunk_rtmp_message_body(const uint8_t** data_inout, uint16_t* size_inout, + uint32_t chunk_stream_id, uint32_t message_length, uint8_t* message_body) +{ + const uint8_t* data = *data_inout; + uint16_t size = *size_inout; + + while (message_length > 0) + { + uint32_t chunk_len; + + chunk_len = message_length; + if (message_length > RTMP_CHUNK_SIZE) + chunk_len = RTMP_CHUNK_SIZE; + if (size < chunk_len) + return 0; + + memcpy(message_body, data, chunk_len); + data += chunk_len; + size -= chunk_len; + message_body += chunk_len; + message_length -= chunk_len; + + if (message_length > 0) + { + uint8_t fmt; + uint32_t id; + + if (!parse_rtmp_chunk_basic_header(&data, &size, &fmt, &id)) + return 0; + if (fmt != 3) + return 0; + if (id != chunk_stream_id) + return 0; + } + } + + *data_inout = data; + *size_inout = size; + return 1; +} + +char* duplicate_string(const uint8_t** data_inout, uint16_t* size_inout) +{ + const uint8_t* data = *data_inout; + uint16_t size = *size_inout; + + uint16_t field_len; + char* str; + + if (size < (1 + 2)) + return nullptr; + if (data[0] != AMF0_TYPE_STRING) + return nullptr; + field_len = (data[1] << 8) + data[2]; + if (field_len == 0) + return nullptr; + data += 1 + 2; + size -= 1 + 2; + + if (size < field_len) + return nullptr; + str = (char*)snort_alloc(field_len + 1); + memcpy(str, data, field_len); + str[field_len] = '\0'; + data += field_len; + size -= field_len; + + *data_inout = data; + *size_inout = size; + return str; +} + +int skip_property_value(const uint8_t** data_inout, uint16_t* size_inout) +{ + const uint8_t* data = *data_inout; + uint16_t size = *size_inout; + + uint8_t type; + uint16_t field_len; + + if (size < 1) + return 0; + type = data[0]; + data += 1; + size -= 1; + + switch (type) + { + case AMF0_TYPE_NUMBER: + if (size < 8) + return 0; + data += 8; + size -= 8; + break; + + case AMF0_TYPE_BOOLEAN: + if (size < 1) + return 0; + data += 1; + size -= 1; + break; + + case AMF0_TYPE_STRING: + if (size < 2) + return 0; + field_len = (data[0] << 8) + data[1]; + data += 2; + size -= 2; + if (size < field_len) + return 0; + data += field_len; + size -= field_len; + break; + + default: + return 0; + } + + *data_inout = data; + *size_inout = size; + return 1; +} + +int parse_rtmp_message(const uint8_t** data_inout, uint16_t* size_inout, ServiceRTMPData* ss) +{ + const uint8_t* data = *data_inout; + uint16_t size = *size_inout; + int ret = 1; + + uint32_t id; + uint32_t msg_len; + uint8_t msg_type; + uint16_t field_len; + uint8_t* body = nullptr; + + if (!parse_rtmp_messgage_header(&data, &size, &id, &msg_len, &msg_type)) + goto parse_rtmp_message_fail; + if (msg_type != RTMP_AMF0_COMMAND_MESSAGE_ID) + goto parse_rtmp_message_fail; + + body = (uint8_t*)snort_alloc(msg_len); + if (!unchunk_rtmp_message_body(&data, &size, id, msg_len, body)) + goto parse_rtmp_message_fail; + *data_inout = data; + *size_inout = size; + + /* Now we have a message body of a command (hopefully a connect). */ + data = body; + size = msg_len; + + /* Make sure it's a connect command. */ + CHECK_SIZE(1 + 2); + if (data[0] != AMF0_TYPE_STRING) + goto parse_rtmp_message_fail; + field_len = (data[1] << 8) + data[2]; + if (field_len == 0) + goto parse_rtmp_message_fail; + ADVANCE_DATA(1 + 2); + CHECK_SIZE(field_len); + if (strncmp((const char*)data, RTMP_COMMAND_TYPE_CONNECT, field_len) != 0) + goto parse_rtmp_message_fail; + ADVANCE_DATA(field_len); + + /* Make sure transaction ID is next. */ + CHECK_SIZE(1 + 8); + if (data[0] != AMF0_TYPE_NUMBER) + goto parse_rtmp_message_fail; + ADVANCE_DATA(1 + 8); + + /* Make sure we have the command object next. */ + CHECK_SIZE(1); + if (data[0] != AMF0_TYPE_OBJECT) + goto parse_rtmp_message_fail; + ADVANCE_DATA(1); + + /* Search command object for desired metadata. */ + do + { + /* Check for end of object. */ + CHECK_SIZE(3); /* Need at least this much for full end of object. */ + field_len = (data[0] << 8) + data[1]; + if (field_len == 0) + { + if (data[2] == AMF0_TYPE_OBJECT_END) + break; + else + goto parse_rtmp_message_fail; + } + ADVANCE_DATA(2); /* Not at end, so just get to start of key string for continued + processing below. */ + + /* See if we're interested in this property key (or just skip it). */ + CHECK_SIZE(field_len); + if ( (ss->swfUrl == nullptr) + && (field_len == RTMP_PROPERTY_KEY_SWFURL_LEN) + && (strncmp((const char*)data, RTMP_PROPERTY_KEY_SWFURL, + RTMP_PROPERTY_KEY_SWFURL_LEN) == 0) ) + { + /* swfUrl */ + ADVANCE_DATA(field_len); + ss->swfUrl = duplicate_string(&data, &size); + if (ss->swfUrl == nullptr) + goto parse_rtmp_message_fail; + } + else if ( (ss->pageUrl == nullptr) + && (field_len == RTMP_PROPERTY_KEY_PAGEURL_LEN) + && (strncmp((const char*)data, RTMP_PROPERTY_KEY_PAGEURL, + RTMP_PROPERTY_KEY_PAGEURL_LEN) == 0) ) + { + /* pageUrl */ + ADVANCE_DATA(field_len); + ss->pageUrl = duplicate_string(&data, &size); + if (ss->pageUrl == nullptr) + goto parse_rtmp_message_fail; + } + else + { + /* Something we dont care about... */ + ADVANCE_DATA(field_len); + if (!skip_property_value(&data, &size)) + goto parse_rtmp_message_fail; + } + } + while (size > 0); + +parse_rtmp_message_done: + snort_free(body); + return ret; + +parse_rtmp_message_fail: + ret = 0; + goto parse_rtmp_message_done; +} + +static int rtmp_validate(ServiceValidationArgs* args) +{ + ServiceRTMPData* ss; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + + ss = (ServiceRTMPData*)rtmp_service_mod.api->data_get(flowp, rtmp_service_mod.flow_data_index); + if (!ss) + { + ss = (ServiceRTMPData*)snort_calloc(sizeof(ServiceRTMPData)); + rtmp_service_mod.api->data_add(flowp, ss, rtmp_service_mod.flow_data_index, &rtmp_free); + } + + /* Client -> Server */ + if (dir == APP_ID_FROM_INITIATOR) + { + /* Consume this packet. */ + while (size > 0) + { + switch (ss->client_state) + { + case RTMP_STATE_INIT: + /* C0 is just a version number. Must be valid. */ + if (*data != RTMP_VER_3) + { + goto fail; + } + ss->client_state = RTMP_STATE_SENT_HANDSHAKE0; + data += 1; + size -= 1; + break; + + case RTMP_STATE_SENT_HANDSHAKE0: + /* Just skip RTMP_HANDSHAKE1_SIZE bytes for C1. */ + ss->client_state = RTMP_STATE_SENDING_HANDSHAKE1; + ss->client_bytes_left = RTMP_HANDSHAKE1_SIZE; + /* fall through */ + + case RTMP_STATE_SENDING_HANDSHAKE1: + if (size < ss->client_bytes_left) + { + /* We've still got more to get next time around. */ + ss->client_bytes_left -= size; + size = 0; + } + else + { + /* We've gotten all of the bytes that we wanted. */ + ss->client_state = RTMP_STATE_SENT_HANDSHAKE1; + data += ss->client_bytes_left; + size -= ss->client_bytes_left; + } + break; + + case RTMP_STATE_SENT_HANDSHAKE1: + /* Client can't start sending C2 until it has received S1. */ + if (ss->server_state < RTMP_STATE_SENT_HANDSHAKE1) + { + goto fail; + } + /* Just skip RTMP_HANDSHAKE2_SIZE bytes for C2. */ + ss->client_state = RTMP_STATE_SENDING_HANDSHAKE2; + ss->client_bytes_left = RTMP_HANDSHAKE2_SIZE; + /* fall through */ + + case RTMP_STATE_SENDING_HANDSHAKE2: + if (size < ss->client_bytes_left) + { + /* We've still got more to get next time around. */ + ss->client_bytes_left -= size; + size = 0; + } + else + { + /* We've gotten all of the bytes that we wanted. */ + ss->client_state = RTMP_STATE_SENT_HANDSHAKE2; + data += ss->client_bytes_left; + size -= ss->client_bytes_left; + } + break; + + case RTMP_STATE_SENT_HANDSHAKE2: + if (parse_rtmp_message(&data, &size, ss)) + { + /* Got our connect command. We're done. */ + ss->client_state = RTMP_STATE_DONE; + } + else + { + /* No connect command found. Bail out. */ + goto fail; + } + /* fall through */ + + case RTMP_STATE_DONE: + /* We're done with client, so just blindly consume all data. */ + size = 0; + break; + + default: + goto fail; /* No reason to ever get here. */ + } + } + } + /* Server -> Client */ + else if (dir == APP_ID_FROM_RESPONDER) + { + /* Consume this packet. */ + while (size > 0) + { + switch (ss->server_state) + { + case RTMP_STATE_INIT: + /* Client must initiate. */ + if (ss->client_state < RTMP_STATE_SENT_HANDSHAKE0) + { + goto fail; + } + /* S0 is just a version number. Must be valid. */ + if (*data != RTMP_VER_3) + { + goto fail; + } + ss->server_state = RTMP_STATE_SENT_HANDSHAKE0; + data += 1; + size -= 1; + break; + + case RTMP_STATE_SENT_HANDSHAKE0: + /* Just skip RTMP_HANDSHAKE1_SIZE bytes for S1. */ + ss->server_state = RTMP_STATE_SENDING_HANDSHAKE1; + ss->server_bytes_left = RTMP_HANDSHAKE1_SIZE; + /* fall through */ + + case RTMP_STATE_SENDING_HANDSHAKE1: + if (size < ss->server_bytes_left) + { + /* We've still got more to get next time around. */ + ss->server_bytes_left -= size; + size = 0; + } + else + { + /* We've gotten all of the bytes that we wanted. */ + ss->server_state = RTMP_STATE_SENT_HANDSHAKE1; + data += ss->server_bytes_left; + size -= ss->server_bytes_left; + } + break; + + case RTMP_STATE_SENT_HANDSHAKE1: + /* Server can't start sending S2 until it has received C1. */ + if (ss->client_state < RTMP_STATE_SENT_HANDSHAKE1) + { + goto fail; + } + /* Just skip RTMP_HANDSHAKE2_SIZE bytes for S2. */ + ss->server_state = RTMP_STATE_SENDING_HANDSHAKE2; + ss->server_bytes_left = RTMP_HANDSHAKE2_SIZE; + /* fall through */ + + case RTMP_STATE_SENDING_HANDSHAKE2: + if (size < ss->server_bytes_left) + { + /* We've still got more to get next time around. */ + ss->server_bytes_left -= size; + size = 0; + break; /* Not done yet. */ + } + else + { + /* We've gotten all of the bytes that we wanted. */ + ss->server_state = RTMP_STATE_SENT_HANDSHAKE2; + data += ss->server_bytes_left; + size -= ss->server_bytes_left; + } + /* fall through */ + + case RTMP_STATE_SENT_HANDSHAKE2: + /* No more interest in watching server. */ + ss->server_state = RTMP_STATE_DONE; + /* fall through */ + + case RTMP_STATE_DONE: + /* We're done with server, so just blindly consume all data. */ + size = 0; + break; + + default: + goto fail; /* No reason to ever get here. */ + } + } + } + + /* Are we there yet? */ + if ( (ss->client_state == RTMP_STATE_DONE) + && (ss->server_state == RTMP_STATE_DONE) ) + { + goto success; + } + + /* Give up if it's taking us too long to figure out this thing. */ + if (flowp->session_packet_count >= pAppidActiveConfig->mod_config->rtmp_max_packets) + { + goto fail; + } + +inprocess: + rtmp_service_mod.api->service_inprocess(flowp, args->pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +fail: + snort_free(ss->swfUrl); + snort_free(ss->pageUrl); + ss->swfUrl = ss->pageUrl = nullptr; + rtmp_service_mod.api->fail_service(flowp, args->pkt, dir, &svc_element, + rtmp_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + +success: + if (ss->swfUrl != nullptr) + { + if (!flowp->hsession) + flowp->hsession = (httpSession*)snort_calloc(sizeof(httpSession)); + + if (flowp->hsession->url == nullptr) + { + flowp->hsession->url = ss->swfUrl; + flowp->scan_flags |= SCAN_HTTP_HOST_URL_FLAG; + } + else + { + snort_free(ss->swfUrl); + } + ss->swfUrl = nullptr; + } + if (ss->pageUrl != nullptr) + { + if (!flowp->hsession) + flowp->hsession = (httpSession*)snort_calloc(sizeof(httpSession)); + + if (!pAppidActiveConfig->mod_config->referred_appId_disabled && + (flowp->hsession->referer == nullptr)) + flowp->hsession->referer = ss->pageUrl; + else + snort_free(ss->pageUrl); + ss->pageUrl = nullptr; + } + rtmp_service_mod.api->add_service(flowp, args->pkt, dir, &svc_element, + APP_ID_RTMP, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_rtmp.h b/src/network_inspectors/appid/service_plugins/service_rtmp.h new file mode 100644 index 000000000..f2d00f14b --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_rtmp.h @@ -0,0 +1,29 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_rtmp.h author Sourcefire Inc. + +#ifndef SERVICE_RTMP_H +#define SERVICE_RTMP_H + +struct RNAServiceValidationModule; +// FIXIT-L: Make the globals const or, if necessary, thread-local. +extern RNAServiceValidationModule rtmp_service_mod; + +#endif diff --git a/src/network_inspectors/appid/service_plugins/service_smtp.cc b/src/network_inspectors/appid/service_plugins/service_smtp.cc new file mode 100644 index 000000000..b91606378 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_smtp.cc @@ -0,0 +1,350 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_smtp.cc author Sourcefire Inc. + +#include "service_smtp.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#include "service_util.h" +#include "application_ids.h" +#include "service_api.h" +#include "appid_module.h" + +#define SMTP_PORT 25 +#define SMTP_CLOSING_CONN "closing connection\x0d\x0a" + +enum SMTPState +{ + SMTP_STATE_CONNECTION, + SMTP_STATE_HELO, + SMTP_STATE_TRANSFER, + SMTP_STATE_CONNECTION_ERROR, + SMTP_STATE_STARTTLS +}; + +struct ServiceSMTPData +{ + SMTPState state; + int code; + int multiline; + int set_flags; +}; + +#pragma pack(1) + +struct ServiceSMTPCode +{ + uint8_t code[3]; + uint8_t sp; +}; + +#pragma pack() + +static int smtp_init(const IniServiceAPI* const init_api); +static int smtp_validate(ServiceValidationArgs* args); + +// FIXIT-L: Make the globals const or, if necessary, thread-local. +static RNAServiceElement svc_element = +{ + nullptr, + &smtp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "smtp" +}; + +static RNAServiceValidationPort pp[] = +{ + { &smtp_validate, 25, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule smtp_service_mod = +{ + "SMTP", + &smtp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_SMTP, 0 }, + { APP_ID_SMTPS, 0 } +}; + +static int smtp_init(const IniServiceAPI* const init_api) +{ + const char SMTP_PATTERN1[] = "220 "; + const char SMTP_PATTERN2[] = "220-"; + const char SMTP_PATTERN3[] = "SMTP"; + const char SMTP_PATTERN4[] = "smtp"; + + init_api->RegisterPattern(&smtp_validate, IpProtocol::TCP, (uint8_t*)SMTP_PATTERN1, + sizeof(SMTP_PATTERN1) - 1, 0, "smtp", init_api->pAppidConfig); + init_api->RegisterPattern(&smtp_validate, IpProtocol::TCP, (uint8_t*)SMTP_PATTERN2, + sizeof(SMTP_PATTERN2) - 1, 0, "smtp", init_api->pAppidConfig); + init_api->RegisterPattern(&smtp_validate, IpProtocol::TCP, (uint8_t*)SMTP_PATTERN3, + sizeof(SMTP_PATTERN3) - 1, -1, "smtp", init_api->pAppidConfig); + init_api->RegisterPattern(&smtp_validate, IpProtocol::TCP, (uint8_t*)SMTP_PATTERN4, + sizeof(SMTP_PATTERN4) - 1, -1, "smtp", init_api->pAppidConfig); + + unsigned i; + for (i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&smtp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int smtp_validate_reply(const uint8_t* data, uint16_t* offset, + uint16_t size, int* multi, int* code) +{ + const ServiceSMTPCode* code_hdr; + int tmp; + + /* Trim any blank lines (be a little tolerant) */ + for (; *offsetcode[0] < '1' || code_hdr->code[0] > '5') + return -1; + tmp = (code_hdr->code[0] - '0') * 100; + + if (code_hdr->code[1] < '0' || code_hdr->code[1] > '5') + return -1; + tmp += (code_hdr->code[1] - '0') * 10; + + if (!isdigit(code_hdr->code[2])) + return -1; + tmp += code_hdr->code[2] - '0'; + + if (*multi && tmp != *code) + return -1; + *code = tmp; + if (code_hdr->sp == '-') + *multi = 1; + else if (code_hdr->sp == ' ') + *multi = 0; + else + return -1; + + /* We have a valid code, now we need to see if the rest of the line + is okay */ + + *offset += sizeof(ServiceSMTPCode); + for (; *offset < size; (*offset)++) + { + if (data[*offset] == 0x0D) + { + (*offset)++; + if (*offset >= size) + return -1; + if (data[*offset] != 0x0A) + return -1; + } + if (data[*offset] == 0x0A) + { + if (*multi) + { + if ((*offset + 1) >= size) + return 0; + + if (size - (*offset + 1) < (int)sizeof(ServiceSMTPCode)) + return -1; + + code_hdr = (ServiceSMTPCode*)(data + *offset + 1); + + if (code_hdr->code[0] < '1' || code_hdr->code[0] > '5') + return -1; + tmp = (code_hdr->code[0] - '0') * 100; + + if (code_hdr->code[1] < '1' || code_hdr->code[1] > '5') + return -1; + tmp += (code_hdr->code[1] - '0') * 10; + + if (!isdigit(code_hdr->code[2])) + return -1; + tmp += code_hdr->code[2] - '0'; + + if (tmp != *code) + return -1; + + if (code_hdr->sp == ' ') + *multi = 0; + else if (code_hdr->sp != '-') + return -1; + + *offset += sizeof(ServiceSMTPCode); + } + else + { + (*offset)++; + return *code; + } + } + else if (!isprint(data[*offset])) + return -1; + } + + return 0; +} + +static int smtp_validate(ServiceValidationArgs* args) +{ + ServiceSMTPData* fd; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + uint16_t offset; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + fd = (ServiceSMTPData*)smtp_service_mod.api->data_get(flowp, smtp_service_mod.flow_data_index); + if (!fd) + { + fd = (ServiceSMTPData*)snort_calloc(sizeof(ServiceSMTPData)); + smtp_service_mod.api->data_add(flowp, fd, smtp_service_mod.flow_data_index, &snort_free); + fd->state = SMTP_STATE_CONNECTION; + } + else + { + if (getAppIdFlag(flowp, APPID_SESSION_ENCRYPTED) && fd->state == SMTP_STATE_TRANSFER) + { + fd->state = SMTP_STATE_STARTTLS; + } + } + + if (!fd->set_flags) + { + fd->set_flags = 1; + setAppIdFlag(flowp, APPID_SESSION_CLIENT_GETS_SERVER_PACKETS); + } + + offset = 0; + while (offset < size) + { + if (smtp_validate_reply(data, &offset, size, &fd->multiline, &fd->code) < 0) + goto fail; + if (!fd->code) + goto inprocess; + switch (fd->state) + { + case SMTP_STATE_CONNECTION: + switch (fd->code) + { + case 220: + fd->state = SMTP_STATE_HELO; + break; + case 421: + if (service_strstr(data, size, (const uint8_t*)SMTP_CLOSING_CONN, + sizeof(SMTP_CLOSING_CONN)-1)) + goto success; + case 520: + fd->state = SMTP_STATE_CONNECTION_ERROR; + break; + default: + goto fail; + } + break; + case SMTP_STATE_HELO: + switch (fd->code) + { + case 250: + fd->state = SMTP_STATE_TRANSFER; + break; + case 500: + case 501: + case 504: + break; + case 421: + case 553: + fd->state = SMTP_STATE_CONNECTION_ERROR; + break; + default: + goto fail; + } + break; + case SMTP_STATE_STARTTLS: + if (fd->code == 220) + goto success; + /* STARTTLS failed. */ + clearAppIdFlag(flowp, APPID_SESSION_ENCRYPTED); // not encrypted after all + fd->state = SMTP_STATE_HELO; // revert the state + break; + case SMTP_STATE_TRANSFER: + goto success; + case SMTP_STATE_CONNECTION_ERROR: + default: + goto fail; + } + } + +inprocess: + smtp_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + smtp_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + fd->state == SMTP_STATE_STARTTLS ? APP_ID_SMTPS : APP_ID_SMTP, + nullptr, nullptr, nullptr); + if (fd->state == SMTP_STATE_STARTTLS) + appid_stats.smtps_count++; + else + appid_stats.smtp_count++; + + return SERVICE_SUCCESS; + +fail: + smtp_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + smtp_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_smtp.h b/src/network_inspectors/appid/service_plugins/service_smtp.h new file mode 100644 index 000000000..4b9239f9e --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_smtp.h @@ -0,0 +1,31 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_smtp.h author Sourcefire Inc. + +#ifndef SERVICE_SMTP_H +#define SERVICE_SMTP_H + +#include "detector_plugins/detector_api.h" + +// FIXIT-L: Make the globals const or, if necessary, thread-local. +extern RNAServiceValidationModule smtp_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_snmp.cc b/src/network_inspectors/appid/service_plugins/service_snmp.cc new file mode 100644 index 000000000..aaad09e68 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_snmp.cc @@ -0,0 +1,632 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_snmp.cc author Sourcefire Inc. + +#include "service_snmp.h" + +#include "log/messages.h" +#include "target_based/snort_protocols.h" +#include "utils/util.h" + +#include "appid_api.h" +#include "app_info_table.h" +#include "service_base.h" +#include "application_ids.h" + +#define SNMP_PORT 161 + +#define SNMP_VERSION_1 0 +#define SNMP_VERSION_2c 1 +#define SNMP_VERSION_2u 2 +#define SNMP_VERSION_3 3 + +#define SNMP_VENDOR_STR "SNMP" +#define SNMP_VERSION_STR_1 "v1" +#define SNMP_VERSION_STR_2c "v2c" +#define SNMP_VERSION_STR_2u "v2u" +#define SNMP_VERSION_STR_3 "v3" + +enum SNMPState +{ + SNMP_STATE_CONNECTION, + SNMP_STATE_RESPONSE, + SNMP_STATE_REQUEST, + SNMP_STATE_R_RESPONSE, + SNMP_STATE_R_REQUEST, + SNMP_STATE_ERROR +}; + +struct ServiceSNMPData +{ + SNMPState state; +}; + +enum SNMPPDUType +{ + SNMP_PDU_GET_REQUEST, + SNMP_PDU_GET_NEXT_REQUEST, + SNMP_PDU_GET_RESPONSE, + SNMP_PDU_SET_REQUEST, + SNMP_PDU_TRAP, + SNMP_PDU_GET_BULK_REQUEST, + SNMP_PDU_INFORM_REQUEST, + SNMP_PDU_TRAPV2 +}; + +#pragma pack(1) + +struct ServiceSNMPHeader +{ + uint16_t opcode; + union + { + uint16_t block; + uint16_t errorcode; + } d; +}; + +#pragma pack() + +static int snmp_init(const IniServiceAPI* const init_api); +static int snmp_validate(ServiceValidationArgs* args); + +// FIXIT-L: Make the globals const or, if necessary, thread-local. +static RNAServiceElement svc_element = +{ + nullptr, + &snmp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "snmp", +}; + +static RNAServiceValidationPort pp[] = +{ + { &snmp_validate, SNMP_PORT, IpProtocol::TCP, 0 }, + { &snmp_validate, SNMP_PORT, IpProtocol::UDP, 0 }, + { &snmp_validate, 162, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +RNAServiceValidationModule snmp_service_mod = +{ + "SNMP", + &snmp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static const uint8_t SNMP_PATTERN_2[] = { 0x02, 0x01, 0x00, 0x04 }; +static const uint8_t SNMP_PATTERN_3[] = { 0x02, 0x01, 0x01, 0x04 }; +static const uint8_t SNMP_PATTERN_4[] = { 0x02, 0x01, 0x03, 0x30 }; + +static AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_SNMP, APPINFO_FLAG_SERVICE_UDP_REVERSED|APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int16_t app_id = 0; + +static int snmp_init(const IniServiceAPI* const init_api) +{ + app_id = AddProtocolReference("snmp"); + + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_2, + sizeof(SNMP_PATTERN_2), 2, "snmp", init_api->pAppidConfig); + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_3, + sizeof(SNMP_PATTERN_3), 2, "snmp", init_api->pAppidConfig); + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_4, + sizeof(SNMP_PATTERN_4), 2, "snmp", init_api->pAppidConfig); + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_2, + sizeof(SNMP_PATTERN_2), 3, "snmp", init_api->pAppidConfig); + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_3, + sizeof(SNMP_PATTERN_3), 3, "snmp", init_api->pAppidConfig); + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_4, + sizeof(SNMP_PATTERN_4), 3, "snmp", init_api->pAppidConfig); + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_2, + sizeof(SNMP_PATTERN_2), 4, "snmp", init_api->pAppidConfig); + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_3, + sizeof(SNMP_PATTERN_3), 4, "snmp", init_api->pAppidConfig); + init_api->RegisterPattern(&snmp_validate, IpProtocol::UDP, SNMP_PATTERN_4, + sizeof(SNMP_PATTERN_4), 4, "snmp", init_api->pAppidConfig); + + for (unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&snmp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int snmp_ans1_length(const uint8_t** const data, + const uint8_t* const end, + uint32_t* const length) +{ + *length = 0; + if (**data == 0x80) + return -1; + if (**data < 0x80) + { + *length = (uint32_t)**data; + (*data)++; + } + else + { + int cnt = (**data) & 0x7F; + (*data)++; + for (; *data= end) + return -1; + if (snmp_ans1_length(data, end, &overall_length)) + return -1; + if (overall_length < 3 || (int)overall_length > end-(*data)) + return -1; + if (**data != 0x02) + return -1; + (*data)++; + if (**data != 0x01) + return -1; + (*data)++; + version = **data; + (*data)++; + overall_length -= 3; + if (!overall_length) + return -1; + switch (version) + { + case SNMP_VERSION_1: + case SNMP_VERSION_2c: + if (**data != 0x04) + return -1; + (*data)++; + overall_length--; + if (!overall_length) + return -1; + p = *data; + if (snmp_ans1_length(data, *data+overall_length, &community_length)) + return -1; + overall_length -= *data - p; + if (overall_length < community_length) + return -1; + for (; + community_length; + (*data)++, community_length--, overall_length--) + { + if (!isprint(**data)) + return -1; + } + break; + case SNMP_VERSION_2u: + if (**data != 0x04) + return -1; + (*data)++; + overall_length--; + if (!overall_length) + return -1; + p = *data; + if (snmp_ans1_length(data, *data+overall_length, &community_length)) + return -1; + overall_length -= *data - p; + if (!community_length || overall_length < community_length) + return -1; + if (**data != 1) + return -1; + *data += community_length; + overall_length -= community_length; + break; + case SNMP_VERSION_3: + /* Global header */ + if (**data != 0x30) + return -1; + (*data)++; + overall_length--; + if (!overall_length) + return -1; + p = *data; + if (snmp_ans1_length(data, *data+overall_length, &global_length)) + return -1; + overall_length -= *data - p; + if (global_length < 2 || overall_length < global_length) + return -1; + + /* Message id */ + if (**data != 0x02) + return -1; + (*data)++; + global_length--; + overall_length--; + p = *data; + if (snmp_ans1_length(data, *data+global_length, &length)) + return -1; + global_length -= *data - p; + overall_length -= *data - p; + if (global_length < length || length > sizeof(uint32_t)) + return -1; + *data += length; + global_length -= length; + overall_length -= length; + + /* Max message size */ + if (global_length < 2) + return -1; + if (**data != 0x02) + return -1; + (*data)++; + global_length--; + overall_length--; + p = *data; + if (snmp_ans1_length(data, *data+global_length, &length)) + return -1; + global_length -= *data - p; + overall_length -= *data - p; + if (global_length < length || length > sizeof(uint32_t)) + return -1; + *data += length; + global_length -= length; + overall_length -= length; + + /* Message flags */ + if (global_length < 2) + return -1; + if (**data != 0x04) + return -1; + (*data)++; + global_length--; + overall_length--; + p = *data; + if (snmp_ans1_length(data, *data+global_length, &length)) + return -1; + global_length -= *data - p; + overall_length -= *data - p; + if (length != 1 || global_length < length) + return -1; + (*data)++; + global_length--; + overall_length--; + + /* Security model */ + if (global_length < 2) + return -1; + if (**data != 0x02) + return -1; + (*data)++; + global_length--; + overall_length--; + p = *data; + if (snmp_ans1_length(data, *data+global_length, &length)) + return -1; + global_length -= *data - p; + overall_length -= *data - p; + if (global_length < length || length > sizeof(uint32_t)) + return -1; + *data += length; + global_length -= length; + overall_length -= length; + + /* Security Parameters */ + if (overall_length < 2) + return -1; + if (**data != 0x04) + return -1; + (*data)++; + overall_length--; + p = *data; + if (snmp_ans1_length(data, *data+overall_length, &global_length)) + return -1; + overall_length -= *data - p; + if (overall_length < global_length) + return -1; + *data += global_length; + overall_length -= global_length; + + /* Message */ + if (overall_length < 2) + return -1; + if (**data != 0x30) + return -1; + (*data)++; + overall_length--; + p = *data; + if (snmp_ans1_length(data, *data+overall_length, &global_length)) + return -1; + overall_length -= *data - p; + if (overall_length < global_length) + return -1; + + /* Context Engine ID */ + if (global_length < 2) + return -1; + if (**data != 0x04) + return -1; + (*data)++; + global_length--; + overall_length--; + p = *data; + if (snmp_ans1_length(data, *data+global_length, &length)) + return -1; + global_length -= *data - p; + overall_length -= *data - p; + if (global_length < length) + return -1; + *data += length; + global_length -= length; + overall_length -= length; + + /* Context Name */ + if (global_length < 2) + return -1; + if (**data != 0x04) + return -1; + (*data)++; + global_length--; + overall_length--; + p = *data; + if (snmp_ans1_length(data, *data+global_length, &length)) + return -1; + global_length -= *data - p; + overall_length -= *data - p; + if (global_length < length) + return -1; + *data += length; + global_length -= length; + overall_length -= length; + break; + default: + return -1; + } + if (!overall_length) + return -1; + cls = (**data) & 0xC0; + if (cls != 0x80 && cls != 0x40) + return -1; + *pdu = (**data) & 0x1F; + *version_ret = version; + return 0; +} + +static int snmp_validate(ServiceValidationArgs* args) +{ + ServiceSNMPData* sd; + ServiceSNMPData* tmp_sd; + AppIdData* pf; + uint8_t pdu; + const sfip_t* sip; + const sfip_t* dip; + uint8_t version; + const char* version_str = nullptr; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + bool app_id_debug_session_flag = args->app_id_debug_session_flag; + char* app_id_debug_session = args->app_id_debug_session; + + if (!size) + goto inprocess; + + sd = (ServiceSNMPData*)snmp_service_mod.api->data_get(flowp, snmp_service_mod.flow_data_index); + if (!sd) + { + sd = (ServiceSNMPData*)snort_calloc(sizeof(ServiceSNMPData)); + snmp_service_mod.api->data_add(flowp, sd, snmp_service_mod.flow_data_index, &snort_free); + sd->state = SNMP_STATE_CONNECTION; + } + + if (snmp_verify_packet(&data, data+size, &pdu, &version)) + { + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s snmp payload verify failed\n", app_id_debug_session); + if (getAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED)) + { + if (dir == APP_ID_FROM_RESPONDER) + goto bail; + else + goto fail; + } + else + { + if (dir == APP_ID_FROM_RESPONDER) + goto fail; + else + goto bail; + } + } + + if (app_id_debug_session_flag) + LogMessage("AppIdDbg %s snmp state %d\n", app_id_debug_session, sd->state); + + switch (sd->state) + { + case SNMP_STATE_CONNECTION: + if (pdu != SNMP_PDU_GET_RESPONSE && dir == APP_ID_FROM_RESPONDER) + { + sd->state = SNMP_STATE_R_RESPONSE; + setAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED); + break; + } + if (pdu == SNMP_PDU_GET_RESPONSE && dir == APP_ID_FROM_INITIATOR) + { + sd->state = SNMP_STATE_R_REQUEST; + setAppIdFlag(flowp, APPID_SESSION_UDP_REVERSED); + break; + } + + if (dir == APP_ID_FROM_RESPONDER) + { + sd->state = SNMP_STATE_REQUEST; + break; + } + + if (pdu == SNMP_PDU_TRAP || pdu == SNMP_PDU_TRAPV2) + { + setAppIdFlag(flowp, APPID_SESSION_SERVICE_DETECTED | APPID_SESSION_NOT_A_SERVICE); + clearAppIdFlag(flowp, APPID_SESSION_CONTINUE); + flowp->serviceAppId = APP_ID_SNMP; + break; + } + sd->state = SNMP_STATE_RESPONSE; + + /*adding expected connection in case the server doesn't send from 161*/ + dip = pkt->ptrs.ip_api.get_dst(); + sip = pkt->ptrs.ip_api.get_src(); + pf = snmp_service_mod.api->flow_new(flowp, pkt, dip, 0, sip, pkt->ptrs.sp, flowp->proto, + app_id, 0); + if (pf) + { + tmp_sd = (ServiceSNMPData*)snort_calloc(sizeof(ServiceSNMPData)); + tmp_sd->state = SNMP_STATE_RESPONSE; + snmp_service_mod.api->data_add(pf, tmp_sd, + snmp_service_mod.flow_data_index, &snort_free); + if (snmp_service_mod.api->data_add_id(pf, pkt->ptrs.dp, &svc_element)) + { + setAppIdFlag(pf, APPID_SESSION_SERVICE_DETECTED); + clearAppIdFlag(pf, APPID_SESSION_CONTINUE); + tmp_sd->state = SNMP_STATE_ERROR; + return SERVICE_ENULL; + } + PopulateExpectedFlow(flowp, pf, APPID_SESSION_EXPECTED_EVALUATE); + pf->rnaServiceState = RNA_STATE_STATEFUL; + pf->scan_flags |= SCAN_HOST_PORT_FLAG; + pf->common.initiator_ip = *sip; + } + break; + case SNMP_STATE_RESPONSE: + if (pdu == SNMP_PDU_GET_RESPONSE) + { + if (dir == APP_ID_FROM_RESPONDER) + goto success; + goto fail; + } + if (dir == APP_ID_FROM_RESPONDER) + goto fail; + break; + case SNMP_STATE_REQUEST: + if (pdu != SNMP_PDU_GET_RESPONSE) + { + if (dir == APP_ID_FROM_INITIATOR) + goto success; + goto fail; + } + if (dir == APP_ID_FROM_INITIATOR) + goto fail; + break; + case SNMP_STATE_R_RESPONSE: + if (pdu == SNMP_PDU_GET_RESPONSE) + { + if (dir == APP_ID_FROM_INITIATOR) + goto success; + goto fail; + } + if (dir == APP_ID_FROM_INITIATOR) + goto fail; + break; + case SNMP_STATE_R_REQUEST: + if (pdu != SNMP_PDU_GET_RESPONSE) + { + if (dir == APP_ID_FROM_RESPONDER) + goto success; + goto fail; + } + if (dir == APP_ID_FROM_RESPONDER) + goto fail; + break; + default: + if (dir == APP_ID_FROM_RESPONDER) + goto fail; + else + goto bail; + } + +inprocess: + snmp_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +success: + switch (version) + { + case SNMP_VERSION_1: + version_str = SNMP_VERSION_STR_1; + break; + case SNMP_VERSION_2c: + version_str = SNMP_VERSION_STR_2c; + break; + case SNMP_VERSION_2u: + version_str = SNMP_VERSION_STR_2u; + break; + case SNMP_VERSION_3: + version_str = SNMP_VERSION_STR_3; + break; + default: + version_str = nullptr; + break; + } + snmp_service_mod.api->add_service(flowp, pkt, dir, &svc_element, + APP_ID_SNMP, + SNMP_VENDOR_STR, version_str, nullptr); + return SERVICE_SUCCESS; + +bail: + snmp_service_mod.api->incompatible_data(flowp, pkt, dir, &svc_element, + snmp_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOT_COMPATIBLE; + +fail: + snmp_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + snmp_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_snmp.h b/src/network_inspectors/appid/service_plugins/service_snmp.h new file mode 100644 index 000000000..5709119f3 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_snmp.h @@ -0,0 +1,33 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_snmp.h author Sourcefire Inc. + +#ifndef SERVICE_SNMP_H +#define SERVICE_SNMP_H + +// SNMP service + +#include "detector_plugins/detector_api.h" + +// FIXIT-L: Make the globals const or, if necessary, thread-local. +extern RNAServiceValidationModule snmp_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_ssh.cc b/src/network_inspectors/appid/service_plugins/service_ssh.cc new file mode 100644 index 000000000..34ae66d23 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_ssh.cc @@ -0,0 +1,587 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_ssh.cc author Sourcefire Inc. + +#include "service_ssh.h" +#include "app_info_table.h" +#include "service_base.h" +#include "application_ids.h" + +#include "utils/util.h" + +#define SSH_PORT 22 + +#define SSH_BANNER "SSH-" +#define SERVICE_SSH_MSG_KEYXINIT 20 +#define SERVICE_SSH_MSG_IGNORE 2 +#define SERVICE_SSH_MSG_PUBLIC_KEY 2 +#define SERVICE_SSH_KEY_STRINGS 10 +#define SSH_MAX_FIELDS 10 +#define SSH_MAX_BANNER_LENGTH 255 + +#define SSH_VERSION_2 2 +#define SSH_VERSION_1 1 +#define MINIMUM_SSH_VERS_LEN 4 + +enum SSHState +{ + SSH_STATE_BANNER, + SSH_STATE_KEY, + SSH_STATE_DONE +}; + +enum SSHHeaderState +{ + SSH_HEADER_BEGIN, + SSH_HEADER_PLEN, + SSH_HEADER_CODE, + SSH_IGNORE, + SSH_PADDING, + SSH_KEYX_HEADER_FINISH, + SSH_FIELD_LEN_BEGIN, + SSH_FIELD_DATA_BEGIN, + SSH_PAYLOAD_BEGIN +}; + +enum OldSSHHeaderState +{ + OLD_SSH_HEADER_BEGIN, + OLD_SSH_HEADER_PLEN, + OLD_SSH_HEADER_FIND_CODE, + OLD_SSH_HEADER_CODE, + OLD_SSH_PUBLIC_KEY +}; + +struct ServiceSSHData +{ + SSHState state; + SSHHeaderState hstate; + OldSSHHeaderState oldhstate; + unsigned len; + unsigned pos; + unsigned field; + unsigned field_len; + unsigned read_data; + union + { + uint32_t len; + uint8_t raw_len[4]; + } l; + char* vendor; + char* version; + unsigned ssh_version; + uint8_t plen; + uint8_t code; +}; + +#pragma pack(1) + +struct ServiceSSHKeyString +{ + uint32_t len; + uint8_t data; +}; + +struct ServiceSSHMsg +{ + uint32_t len; + uint8_t plen; + uint8_t code; +}; + +struct ServiceSSHKeyExchange +{ + ServiceSSHMsg msg; + uint8_t cookie[16]; +}; + +struct ServiceSSHKeyExchangeV1 +{ + uint32_t len; + uint8_t code; +}; + +struct ServiceSSHKeyExchangeFinal +{ + uint8_t kex_pkt; + uint32_t future; +}; + +#pragma pack() + +static int ssh_init(const IniServiceAPI* const init_api); +static int ssh_validate(ServiceValidationArgs* args); + +static const RNAServiceElement svc_element = +{ + nullptr, + &ssh_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "ssh", +}; + +// FIXIT can this be const? That would require that RNAServiceValidationModule.pp be const which +// I don't know about. Otherwise we have a thread safety issue here. +static RNAServiceValidationPort pp[] = +{ + { &ssh_validate, SSH_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +// FIXIT maybe this can be const, else thread safety issue +RNAServiceValidationModule ssh_service_mod = +{ + "SSH", + &ssh_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static const AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_SSH, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int ssh_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&ssh_validate, IpProtocol::TCP, (uint8_t*)SSH_BANNER, + sizeof(SSH_BANNER) - 1, 0, "ssh", init_api->pAppidConfig); + for (unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&ssh_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + return 0; +} + +static int ssh_validate_pubkey(const uint8_t* data, uint16_t size, + ServiceSSHData* ss) +{ + uint16_t offset = 0; + const ServiceSSHMsg* skx; + + while (offset < size) + { + switch (ss->oldhstate) + { + case OLD_SSH_HEADER_BEGIN: + ss->l.raw_len[ss->pos] = data[offset]; + ss->pos++; + if (ss->pos == sizeof(skx->len)) + { + ss->len = ntohl(ss->l.len); + ss->oldhstate = OLD_SSH_HEADER_PLEN; + } + break; + case OLD_SSH_HEADER_PLEN: + if (size > (ss->len + sizeof(skx->len))) + ss->plen = size - (ss->len + sizeof(skx->len)); + else + ss->plen = 0; + ss->oldhstate = OLD_SSH_HEADER_FIND_CODE; + case OLD_SSH_HEADER_FIND_CODE: + if (ss->pos == ss->plen + sizeof(skx->len)) + { + ss->oldhstate = OLD_SSH_HEADER_CODE; + ss->code = data[offset]; + } + ss->pos++; + break; + case OLD_SSH_HEADER_CODE: + if (ss->code == SERVICE_SSH_MSG_PUBLIC_KEY) + { + ss->oldhstate = OLD_SSH_PUBLIC_KEY; + ss->pos++; + } + else + return SERVICE_NOMATCH; + ss->len = ss->len + ss->plen + sizeof(skx->len); + if (ss->len > 35000) + return SERVICE_NOMATCH; + break; + case OLD_SSH_PUBLIC_KEY: + ss->pos++; + if (ss->pos >= ss->len) + { + offset++; + if (offset == size) + return SERVICE_SUCCESS; + return SERVICE_NOMATCH; + } + break; + } + offset++; + } + return SERVICE_INPROCESS; +} + +static int ssh_validate_keyx(const uint8_t* data, uint16_t size, + ServiceSSHData* ss) +{ + uint16_t offset = 0; + const ServiceSSHMsg* skx; + const ServiceSSHKeyString* sks; + const ServiceSSHKeyExchange* skex; + + while (offset < size) + { + switch (ss->hstate) + { + case SSH_HEADER_BEGIN: + ss->l.raw_len[ss->pos] = data[offset]; + ss->pos++; + if (ss->pos == sizeof(skx->len)) + { + ss->len = ntohl(ss->l.len); + ss->hstate = SSH_HEADER_PLEN; + } + break; + case SSH_HEADER_PLEN: + ss->plen = data[offset]; + ss->hstate = SSH_HEADER_CODE; + ss->pos++; + break; + case SSH_HEADER_CODE: + ss->code = data[offset]; + if (ss->code == SERVICE_SSH_MSG_KEYXINIT) + { + ss->pos = 0; + ss->hstate = SSH_KEYX_HEADER_FINISH; + ss->read_data = ss->plen + sizeof(skex->cookie) + sizeof(skx->len); + } + else if (ss->code == SERVICE_SSH_MSG_IGNORE) + { + ss->pos = sizeof(skx->len) + 2; + ss->hstate = SSH_IGNORE; + } + else + return SERVICE_NOMATCH; + ss->len = ntohl(ss->l.len) + sizeof(skx->len); + if (ss->len > 35000) + return SERVICE_NOMATCH; + break; + case SSH_IGNORE: + ss->pos++; + if (ss->pos >= ss->len) + { + ss->hstate = SSH_HEADER_BEGIN; + ss->pos = 0; + } + break; + case SSH_KEYX_HEADER_FINISH: + ss->pos++; + if (ss->pos >= sizeof(skex->cookie)) + { + ss->hstate = SSH_FIELD_LEN_BEGIN; + ss->pos = 0; + } + break; + case SSH_FIELD_LEN_BEGIN: + ss->l.raw_len[ss->pos] = data[offset]; + ss->pos++; + if (ss->pos >= sizeof(sks->len)) + { + ss->pos = 0; + ss->field_len = ntohl(ss->l.len); + ss->read_data += ss->field_len + sizeof(sks->len); + if (ss->read_data > ss->len) + return SERVICE_NOMATCH; + if (ss->field_len) + ss->hstate = SSH_FIELD_DATA_BEGIN; + else + { + ss->field++; + if (ss->field >= 10) + ss->hstate = SSH_PAYLOAD_BEGIN; + } + } + break; + case SSH_FIELD_DATA_BEGIN: + ss->pos++; + if (ss->pos >= ss->field_len) + { + ss->field++; + if (ss->field >= 10) + ss->hstate = SSH_PAYLOAD_BEGIN; + else + ss->hstate = SSH_FIELD_LEN_BEGIN; + ss->pos = 0; + } + break; + case SSH_PAYLOAD_BEGIN: + if (ss->pos >= offsetof(ServiceSSHKeyExchangeFinal, future)) + { + ss->l.raw_len[ss->pos - offsetof(ServiceSSHKeyExchangeFinal, future)] = + data[offset]; + } + ss->pos++; + if (ss->pos >= sizeof(ServiceSSHKeyExchangeFinal)) + { + if (ss->l.len != 0) + return SERVICE_NOMATCH; + ss->hstate = SSH_PADDING; + ss->pos = 0; + } + break; + case SSH_PADDING: + ss->pos++; + if (ss->pos >= ss->plen) + { + offset++; + if (offset == size) + return SERVICE_SUCCESS; + return SERVICE_NOMATCH; + } + break; + } + offset++; + } + return SERVICE_INPROCESS; +} + +static void ssh_free_state(void* data) +{ + ServiceSSHData* sd = (ServiceSSHData*)data; + + if (sd) + { + if (sd->vendor) + { + snort_free(sd->vendor); + sd->vendor = nullptr; + } + if (sd->version) + { + snort_free(sd->version); + sd->version = nullptr; + } + snort_free(sd); + } +} + +static int ssh_validate(ServiceValidationArgs* args) +{ + ServiceSSHData* ss; + uint16_t offset; + int retval; + const char* ven; + const char* ver; + const char* end; + unsigned len; + int client_major; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + goto inprocess; + + ss = (ServiceSSHData*)ssh_service_mod.api->data_get(flowp, ssh_service_mod.flow_data_index); + if (!ss) + { + ss = (ServiceSSHData*)snort_calloc(sizeof(ServiceSSHData)); + ssh_service_mod.api->data_add(flowp, ss, + ssh_service_mod.flow_data_index, &ssh_free_state); + ss->state = SSH_STATE_BANNER; + ss->hstate = SSH_HEADER_BEGIN; + ss->oldhstate = OLD_SSH_HEADER_BEGIN; + } + + if (args->dir != APP_ID_FROM_RESPONDER) + { + if (!ss->ssh_version) + { + if ((size_t)size > (sizeof(SSH_BANNER)-1+MINIMUM_SSH_VERS_LEN) && + !strncmp(SSH_BANNER, (char*)data, sizeof(SSH_BANNER)-1)) + { + data += (sizeof(SSH_BANNER)-1); + if (!isdigit(*data)) + goto not_compatible; + else + client_major = *data; + data++; + if (*data != '.') + goto not_compatible; + switch (client_major) + { + case 0x31: + if (*(data+1) == 0x39 && *(data+2) == 0x39) + ss->ssh_version = SSH_VERSION_2; + else + ss->ssh_version = SSH_VERSION_1; + break; + case 0x32: + ss->ssh_version = SSH_VERSION_2; + break; + default: + goto not_compatible; + } + } + } + goto inprocess; + } + + switch (ss->state) + { + case SSH_STATE_BANNER: + offset = 0; + ss->state = SSH_STATE_KEY; + for (;; ) + { + /* SSH-v-\n where v is at least 1 character */ + if ((size_t)(size-offset) < ((sizeof(SSH_BANNER)-1)+3)) + { + goto fail; + } + if (!strncmp(SSH_BANNER, (char*)data+offset, sizeof(SSH_BANNER)-1)) + { + unsigned blen = sizeof(SSH_BANNER)-1; + offset += sizeof(SSH_BANNER)-1; + for (; + offset= size || blen > SSH_MAX_BANNER_LENGTH) + { + goto fail; + } + ven = (char*)&data[offset]; + for (; + offset= size) + goto fail; + if (data[offset+1] != 0x0A) + goto fail; + } + end = (char*)&data[offset]; + if (ven == end) + goto inprocess; + for (ver=ven; ver < end && *ver && *ver != '_' && *ver != '-'; ver++) + ; + if (ver < (end - 1) && isdigit(*(ver+1))) + { + len = ver - ven; + ss->vendor = (char*)snort_alloc(len+1); + memcpy(ss->vendor, ven, len); + ss->vendor[len] = 0; + ver++; + len = end - ver; + ss->version = (char*)snort_alloc(len+1); + memcpy(ss->version, ver, len); + ss->version[len] = 0; + } + else + { + len = end - ven; + ss->version = (char*)snort_calloc(len+1); + memcpy(ss->version, ven, len); + ss->version[len] = 0; + } + goto inprocess; + } + else if (!isprint(data[offset])) + goto fail; + } + goto fail; + } + else + { + for (; offsetssh_version) + { + case SSH_VERSION_2: + retval = ssh_validate_keyx(data, size, ss); + break; + case SSH_VERSION_1: + retval = ssh_validate_pubkey(data, size, ss); + break; + default: + goto fail; + } + goto done; + default: + break; + } + goto fail; + +done: + switch (retval) + { + case SERVICE_INPROCESS: +inprocess: + ssh_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + + case SERVICE_SUCCESS: + ssh_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_SSH, ss->vendor, ss->version, nullptr); + return SERVICE_SUCCESS; + + case SERVICE_NOMATCH: +fail: + ssh_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + ssh_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + +not_compatible: + ssh_service_mod.api->incompatible_data(flowp, args->pkt, args->dir, &svc_element, + ssh_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOT_COMPATIBLE; + + default: + return retval; + } +} + diff --git a/src/network_inspectors/appid/service_plugins/service_ssh.h b/src/network_inspectors/appid/service_plugins/service_ssh.h new file mode 100644 index 000000000..194f9ef1e --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_ssh.h @@ -0,0 +1,31 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_ssh.h author Sourcefire Inc. + +#ifndef SERVICE_SSH_H +#define SERVICE_SSH_H + +#include "detector_plugins/detector_api.h" + +// FIXIT-L: Make the globals const or, if necessary, thread-local. +extern RNAServiceValidationModule ssh_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_ssl.cc b/src/network_inspectors/appid/service_plugins/service_ssl.cc new file mode 100644 index 000000000..5aff90d20 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_ssl.cc @@ -0,0 +1,1179 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_ssl.cc author Sourcefire Inc. + +#include +#include +#include +#include +#include +#include +#include + +#include "appid_flow_data.h" +#include "service_config.h" +#include "service_base.h" +#include "service_ssl.h" +#include "fw_appid.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +#define SSL_PORT 443 + +enum SSLContentType +{ + SSL_CHANGE_CIPHER = 20, + SSL_ALERT = 21, + SSL_HANDSHAKE = 22, + SSL_APPLICATION_DATA = 23 +}; + +#define SSL_CLIENT_HELLO 1 +#define SSL_SERVER_HELLO 2 +#define SSL_CERTIFICATE 11 +#define SSL_SERVER_KEY_XCHG 12 +#define SSL_SERVER_CERT_REQ 13 +#define SSL_SERVER_HELLO_DONE 14 +#define SSL2_SERVER_HELLO 4 +#define PCT_SERVER_HELLO 2 + +#define FIELD_SEPARATOR "/" +#define COMMON_NAME_STR "/CN=" +#define ORG_NAME_STR "/O=" + +/* Extension types. */ +#define SSL_EXT_SERVER_NAME 0 + +struct MatchedSSLPatterns +{ + SSLCertPattern* mpattern; + int index; + struct MatchedSSLPatterns* next; +}; + +enum SSLState +{ + SSL_STATE_INITIATE, /* Client initiates. */ + SSL_STATE_CONNECTION, /* Server responds... */ + SSL_STATE_HEADER, + SSL_STATE_DONE +}; + +struct ServiceSSLData +{ + SSLState state; + int pos; + int length; + int tot_length; + /* From client: */ + char* host_name; + int host_name_strlen; + /* While collecting certificates: */ + unsigned certs_len; /* (Total) length of certificate(s). */ + uint8_t* certs_data; /* Certificate(s) data (each proceeded by length (3 bytes)). */ + int in_certs; /* Currently collecting certificates? */ + int certs_curr_len; /* Current amount of collected certificate data. */ + /* Data collected from certificates afterwards: */ + char* common_name; + int common_name_strlen; + char* org_name; + int org_name_strlen; +}; + +struct ServiceSSLCertificate +{ + X509* cert; + uint8_t* common_name_ptr; + int common_name_len; + uint8_t* org_name_ptr; + int org_name_len; + struct ServiceSSLCertificate* next; +}; + +#pragma pack(1) + +struct ServiceSSLV3Hdr /* Actually a TLS Record. */ +{ + uint8_t type; + uint16_t version; + uint16_t len; +}; + +struct ServiceSSLV3Record /* Actually a Handshake. */ +{ + uint8_t type; + uint8_t length_msb; + uint16_t length; + uint16_t version; + struct + { + uint32_t time; + uint8_t data[28]; + } random; +}; + +struct ServiceSSLV3CertsRecord /* Actually a Certificate(s) Handshake. */ +{ + uint8_t type; + uint8_t length_msb; + uint16_t length; + uint8_t certs_len[3]; /* 3-byte length, network byte order. */ + /* Certificate(s) follow. + * For each: + * - Length: 3 bytes + * - Data : "Length" bytes */ +}; + +struct ServiceSSLV3ExtensionServerName +{ + uint16_t type; + uint16_t length; + uint16_t list_length; + uint8_t string_length_msb; + uint16_t string_length; + /* String follows. */ +}; + +struct ServiceSSLPCTHdr +{ + uint8_t len; + uint8_t len2; + uint8_t type; + uint8_t pad; + uint16_t version; + uint8_t restart; + uint8_t auth; + uint32_t cipher; + uint16_t hash; + uint16_t cert; + uint16_t exch; + uint8_t id[32]; + uint16_t cert_len; + uint16_t c_cert_len; + uint16_t c_sig_len; + uint16_t resp_len; +}; + +struct ServiceSSLV2Hdr +{ + uint8_t len; + uint8_t len2; + uint8_t type; + uint8_t id; + uint8_t cert; + uint16_t version; + uint16_t cert_len; + uint16_t cipher_len; + uint16_t conn_len; +}; + +#pragma pack() + +/* Convert 3-byte lengths in TLS headers to integers. */ +#define ntoh3(msb_ptr) \ + ((uint32_t)( (uint32_t)(((uint8_t*)msb_ptr)[0] << 16) \ + + (uint32_t)(((uint8_t*)msb_ptr)[1] << 8) \ + + (uint32_t)(((uint8_t*)msb_ptr)[2] ) )) + +static int ssl_cert_pattern_match(void* id, void*, int index, void* data, void*) +{ + MatchedSSLPatterns* cm; + MatchedSSLPatterns** matches = (MatchedSSLPatterns**)data; + SSLCertPattern* target = (SSLCertPattern*)id; + + cm = (MatchedSSLPatterns*)snort_alloc(sizeof(MatchedSSLPatterns)); + cm->mpattern = target; + cm->index = index; + cm->next = *matches; + *matches = cm; + + return 0; +} + +static int ssl_detector_create_matcher(SearchTool** matcher, DetectorSSLCertPattern* list) +{ + size_t* patternIndex; + size_t size = 0; + DetectorSSLCertPattern* element = nullptr; + + if (*matcher) + delete *matcher; + + if (!(*matcher = new SearchTool("ac_full"))) + return 0; + + patternIndex = &size; + + /* Add patterns from Lua API */ + for (element = list; element; element = element->next) + { + (*matcher)->add(element->dpattern->pattern, + element->dpattern->pattern_size, + element->dpattern, + true); + (*patternIndex)++; + } + + (*matcher)->prep(); + + return 1; +} + +int ssl_detector_process_patterns(ServiceSslConfig* pSslConfig) +{ + int retVal = 1; + if (!ssl_detector_create_matcher(&pSslConfig->ssl_host_matcher, + pSslConfig->DetectorSSLCertPatternList)) + retVal = 0; + if (!ssl_detector_create_matcher(&pSslConfig->ssl_cname_matcher, + pSslConfig->DetectorSSLCnamePatternList)) + retVal = 0; + return retVal; +} + +static int ssl_init(const IniServiceAPI* const api); +static int ssl_validate(ServiceValidationArgs* args); + +static const RNAServiceElement svc_element = +{ + nullptr, + &ssl_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "ssl", +}; + +// FIXIT thread safety, can this be const? +static RNAServiceValidationPort pp[] = +{ + { &ssl_validate, 261, IpProtocol::TCP, 0 }, + { &ssl_validate, 261, IpProtocol::UDP, 0 }, + { &ssl_validate, 443, IpProtocol::TCP, 0 }, + { &ssl_validate, 443, IpProtocol::UDP, 0 }, + { &ssl_validate, 448, IpProtocol::TCP, 0 }, + { &ssl_validate, 448, IpProtocol::UDP, 0 }, + { &ssl_validate, 465, IpProtocol::TCP, 0 }, + { &ssl_validate, 563, IpProtocol::TCP, 0 }, + { &ssl_validate, 563, IpProtocol::UDP, 0 }, + { &ssl_validate, 585, IpProtocol::TCP, 0 }, + { &ssl_validate, 585, IpProtocol::UDP, 0 }, + { &ssl_validate, 614, IpProtocol::TCP, 0 }, + { &ssl_validate, 636, IpProtocol::TCP, 0 }, + { &ssl_validate, 636, IpProtocol::UDP, 0 }, + { &ssl_validate, 989, IpProtocol::TCP, 0 }, + { &ssl_validate, 990, IpProtocol::TCP, 0 }, + { &ssl_validate, 992, IpProtocol::TCP, 0 }, + { &ssl_validate, 992, IpProtocol::UDP, 0 }, + { &ssl_validate, 993, IpProtocol::TCP, 0 }, + { &ssl_validate, 993, IpProtocol::UDP, 0 }, + { &ssl_validate, 994, IpProtocol::TCP, 0 }, + { &ssl_validate, 994, IpProtocol::UDP, 0 }, + { &ssl_validate, 995, IpProtocol::TCP, 0 }, + { &ssl_validate, 995, IpProtocol::UDP, 0 }, + { &ssl_validate, 3269, IpProtocol::TCP, 0 }, + { &ssl_validate, 8305, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +// FIXIT thread safety, can this be const? +RNAServiceValidationModule ssl_service_mod = +{ + "ssl", + &ssl_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static const uint8_t SSL_PATTERN_PCT[] = { 0x02, 0x00, 0x80, 0x01 }; +static const uint8_t SSL_PATTERN3_0[] = { 0x16, 0x03, 0x00 }; +static const uint8_t SSL_PATTERN3_1[] = { 0x16, 0x03, 0x01 }; +static const uint8_t SSL_PATTERN3_2[] = { 0x16, 0x03, 0x02 }; +static const uint8_t SSL_PATTERN3_3[] = { 0x16, 0x03, 0x03 }; + +static const AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_SSL, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int ssl_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&ssl_validate, IpProtocol::TCP, SSL_PATTERN_PCT, + sizeof(SSL_PATTERN_PCT), 2, "ssl", init_api->pAppidConfig); + init_api->RegisterPattern(&ssl_validate, IpProtocol::TCP, SSL_PATTERN3_0, + sizeof(SSL_PATTERN3_0), 0, "ssl", init_api->pAppidConfig); + init_api->RegisterPattern(&ssl_validate, IpProtocol::TCP, SSL_PATTERN3_1, + sizeof(SSL_PATTERN3_1), 0, "ssl", init_api->pAppidConfig); + init_api->RegisterPattern(&ssl_validate, IpProtocol::TCP, SSL_PATTERN3_2, + sizeof(SSL_PATTERN3_2), 0, "ssl", init_api->pAppidConfig); + init_api->RegisterPattern(&ssl_validate, IpProtocol::TCP, SSL_PATTERN3_3, + sizeof(SSL_PATTERN3_3), 0, "ssl", init_api->pAppidConfig); + for (unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&ssl_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +void ssl_free(void* ss) /* AppIdFreeFCN */ +{ + ServiceSSLData* ss_tmp = (ServiceSSLData*)ss; + snort_free(ss_tmp->certs_data); + snort_free(ss_tmp->host_name); + snort_free(ss_tmp->common_name); + snort_free(ss_tmp->org_name); + snort_free(ss_tmp); +} + +void parse_client_initiation(const uint8_t* data, uint16_t size, ServiceSSLData* ss) +{ + const ServiceSSLV3Hdr* hdr3; + const ServiceSSLV3Record* rec; + unsigned length; + uint16_t ver; + + /* Sanity check header stuff. */ + if (size < sizeof(ServiceSSLV3Hdr)) + return; + hdr3 = (ServiceSSLV3Hdr*)data; + ver = ntohs(hdr3->version); + if (hdr3->type != SSL_HANDSHAKE || + (ver != 0x0300 && + ver != 0x0301 && + ver != 0x0302 && + ver != 0x0303)) + { + return; + } + data += sizeof(ServiceSSLV3Hdr); + size -= sizeof(ServiceSSLV3Hdr); + + if (size < sizeof(ServiceSSLV3Record)) + return; + rec = (ServiceSSLV3Record*)data; + ver = ntohs(rec->version); + if (rec->type != SSL_CLIENT_HELLO || + (ver != 0x0300 && + ver != 0x0301 && + ver != 0x0302 && + ver != 0x0303) || + rec->length_msb) + { + return; + } + length = ntohs(rec->length) + offsetof(ServiceSSLV3Record, version); + if (size < length) + return; + data += sizeof(ServiceSSLV3Record); + size -= sizeof(ServiceSSLV3Record); + + /* Session ID (1-byte length). */ + if (size < 1) + return; + length = *((uint8_t*)data); + data += length + 1; + if (size < (length + 1)) + return; + size -= length + 1; + + /* Cipher Suites (2-byte length). */ + if (size < 2) + return; + length = ntohs(*((uint16_t*)data)); + data += length + 2; + if (size < (length + 2)) + return; + size -= length + 2; + + /* Compression Methods (1-byte length). */ + if (size < 1) + return; + length = *((uint8_t*)data); + data += length + 1; + if (size < (length + 1)) + return; + size -= length + 1; + + /* Extensions (2-byte length) */ + if (size < 2) + return; + length = ntohs(*((uint16_t*)data)); + data += 2; + size -= 2; + if (size < length) + return; + + // We need at least type (2 bytes) and length (2 bytes) fields in the extension + while (length >= 4) + { + ServiceSSLV3ExtensionServerName* ext = (ServiceSSLV3ExtensionServerName*)data; + if (ntohs(ext->type) == SSL_EXT_SERVER_NAME) + { + /* Found server host name. */ + if (length < sizeof(ServiceSSLV3ExtensionServerName)) + return; + + unsigned len = ntohs(ext->string_length); + if ((length - sizeof(ServiceSSLV3ExtensionServerName)) < len) + return; + + const uint8_t* str = data + + offsetof(ServiceSSLV3ExtensionServerName, string_length) + + sizeof(ext->string_length); + ss->host_name = (char*)snort_alloc(len + 1); /* Plus nullptr term. */ + memcpy(ss->host_name, str, len); + ss->host_name[len] = '\0'; + ss->host_name_strlen = len; + return; + } + + data += ntohs(ext->length) + offsetof(ServiceSSLV3ExtensionServerName, list_length); + length -= ntohs(ext->length) + offsetof(ServiceSSLV3ExtensionServerName, list_length); + } +} + +int parse_certificates(ServiceSSLData* ss) +{ + int success = 0; + if (ss->certs_data && ss->certs_len) + { + char* common_name; + char* org_name; + char* common_name_ptr; + char* org_name_ptr; + + /* Pull out certificates from block of data. */ + uint8_t* data = ss->certs_data; + int len = ss->certs_len; + ServiceSSLCertificate* certs_head = nullptr; + ServiceSSLCertificate* certs_curr = nullptr; + int common_name_tot_len = 0; + int org_name_tot_len = 0; + int num_certs = 0; + success = 1; + while (len > 0) + { + X509* cert; + char* start; + char* end; + int length; + + /* Get each certificate. */ + int cert_len = ntoh3(data); + data += 3; + len -= 3; + if (len < cert_len) + { + success = 0; + break; + } + cert = d2i_X509(nullptr, (const unsigned char**)&data, cert_len); + len -= cert_len; /* Above call increments data pointer already. */ + if (!cert) + { + success = 0; + break; + } + + /* Insert certificate entry into list. */ + certs_curr = (ServiceSSLCertificate*)snort_calloc(sizeof(ServiceSSLCertificate)); + certs_curr->cert = cert; + certs_curr->next = certs_head; + certs_head = certs_curr; + num_certs++; + + /* Find "common name" value. */ + start = strstr(cert->name, COMMON_NAME_STR); + if (start) + { + start += strlen(COMMON_NAME_STR); + certs_curr->common_name_ptr = (uint8_t*)start; + end = strstr(start, FIELD_SEPARATOR); + if (end) + { + length = end - start; + } + else + { + length = strlen(start); + } + certs_curr->common_name_len = length; + common_name_tot_len += length; + } + + /* Find "org name" value. */ + start = strstr(cert->name, ORG_NAME_STR); + if (start) + { + start += strlen(ORG_NAME_STR); + certs_curr->org_name_ptr = (uint8_t*)start; + end = strstr(start, FIELD_SEPARATOR); + if (end) + { + length = end - start; + } + else + { + length = strlen(start); + } + certs_curr->org_name_len = length; + org_name_tot_len += length; + } + } + if (!success) + { + goto parse_certificates_clean; + } + + // Build up concatenated string of fields. + common_name = nullptr; + org_name = nullptr; + if (common_name_tot_len) + { + common_name_tot_len += num_certs; /* Space between each and terminator at end. */ + common_name = (char*)snort_calloc(common_name_tot_len); + } + + if (org_name_tot_len) + { + org_name_tot_len += num_certs; /* Space between each and terminator at end. */ + org_name = (char*)snort_calloc(org_name_tot_len); + } + + common_name_ptr = common_name; + org_name_ptr = org_name; + certs_curr = certs_head; + while (certs_curr) + { + /* Grab this common name. */ + if (certs_curr->common_name_ptr && certs_curr->common_name_len) + { + memcpy(common_name_ptr, certs_curr->common_name_ptr, certs_curr->common_name_len); + common_name_ptr += certs_curr->common_name_len; + *common_name_ptr = ' '; + common_name_ptr += 1; + } + + /* Grab this org name. */ + if (certs_curr->org_name_ptr && certs_curr->org_name_len) + { + memcpy(org_name_ptr, certs_curr->org_name_ptr, certs_curr->org_name_len); + org_name_ptr += certs_curr->org_name_len; + *org_name_ptr = ' '; + org_name_ptr += 1; + } + + certs_curr = certs_curr->next; + } + if (common_name_tot_len) + { + common_name_ptr -= 1; + *common_name_ptr = '\0'; /* Put terminator at end rather than space. */ + } + if (org_name_tot_len) + { + org_name_ptr -= 1; + *org_name_ptr = '\0'; /* Put terminator at end rather than space. */ + } + ss->common_name = common_name; + ss->common_name_strlen = common_name_tot_len - 1; /* Minus terminator. */ + ss->org_name = org_name; + ss->org_name_strlen = org_name_tot_len - 1; /* Minus terminator. */ + +parse_certificates_clean: + + while (certs_head) + { + certs_curr = certs_head; + certs_head = certs_head->next; + X509_free(certs_curr->cert); + snort_free(certs_curr); + } + + /* No longer need entire certificates. We have what we came for. */ + snort_free(ss->certs_data); + ss->certs_data = nullptr; + ss->certs_len = 0; + } + return success; /* 1 is OK; 0 is fail. */ +} + +static int ssl_validate(ServiceValidationArgs* args) +{ + ServiceSSLData* ss; + const ServiceSSLPCTHdr* pct; + const ServiceSSLV2Hdr* hdr2; + const ServiceSSLV3Hdr* hdr3; + const ServiceSSLV3Record* rec; + const ServiceSSLV3CertsRecord* certs_rec; + uint16_t ver; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + const int dir = args->dir; + uint16_t size = args->size; + + if (!size) + goto inprocess; + + ss = (ServiceSSLData*)ssl_service_mod.api->data_get(flowp, ssl_service_mod.flow_data_index); + if (!ss) + { + ss = (ServiceSSLData*)snort_calloc(sizeof(ServiceSSLData)); + ssl_service_mod.api->data_add(flowp, ss, ssl_service_mod.flow_data_index, &ssl_free); + ss->state = SSL_STATE_INITIATE; + } + /* Start off with a Client Hello from client to server. */ + if (ss->state == SSL_STATE_INITIATE) + { + ss->state = SSL_STATE_CONNECTION; + + if (dir == APP_ID_FROM_INITIATOR) + { + parse_client_initiation(data, size, ss); + goto inprocess; + } + } + + if (dir != APP_ID_FROM_RESPONDER) + { + goto inprocess; + } + + switch (ss->state) + { + case SSL_STATE_CONNECTION: + ss->state = SSL_STATE_DONE; + pct = (ServiceSSLPCTHdr*)data; + hdr2 = (ServiceSSLV2Hdr*)data; + hdr3 = (ServiceSSLV3Hdr*)data; + if (size >= sizeof(ServiceSSLPCTHdr) && pct->len >= 0x80 && + pct->type == PCT_SERVER_HELLO && ntohs(pct->version) == 0x8001) + { + goto success; + } + if (size >= sizeof(ServiceSSLV2Hdr) && hdr2->len >= 0x80 && + hdr2->type == SSL2_SERVER_HELLO && !(hdr2->cert & 0xFE)) + { + switch (ntohs(hdr2->version)) + { + case 0x0002: + case 0x0300: + case 0x0301: + case 0x0303: + break; + default: + goto not_v2; + } + if (hdr2->cipher_len % 3) + goto not_v2; + + goto success; +not_v2:; + } + if (size < sizeof(ServiceSSLV3Hdr) || + hdr3->type != SSL_HANDSHAKE || + (ntohs(hdr3->version) != 0x0300 && + ntohs(hdr3->version) != 0x0301 && + ntohs(hdr3->version) != 0x0302 && + ntohs(hdr3->version) != 0x0303)) + { + goto fail; + } + data += sizeof(ServiceSSLV3Hdr); + size -= sizeof(ServiceSSLV3Hdr); + rec = (ServiceSSLV3Record*)data; + if (size < sizeof(ServiceSSLV3Record) || + rec->type != SSL_SERVER_HELLO || + (ntohs(rec->version) != 0x0300 && + ntohs(rec->version) != 0x0301 && + ntohs(rec->version) != 0x0302 && + ntohs(rec->version) != 0x0303) || + rec->length_msb) + { + goto fail; + } + ss->tot_length = ntohs(hdr3->len); + ss->length = ntohs(rec->length) + + offsetof(ServiceSSLV3Record, version); + if (size == ss->length) + goto success; /* Just a Server Hello. */ + if (ss->tot_length < ss->length) + goto fail; + ss->tot_length -= ss->length; + if (size < ss->length) + goto fail; + data += ss->length; + size -= ss->length; + ss->state = SSL_STATE_HEADER; + ss->pos = 0; + /* fall through */ + case SSL_STATE_HEADER: + ss->state = SSL_STATE_DONE; + while (size > 0) + { + if (!ss->pos) + { + /* Need to move onto (and past) next header (i.e., record) if + * previous was completely consumed. */ + if (ss->tot_length == 0) + { + hdr3 = (ServiceSSLV3Hdr*)data; + ver = ntohs(hdr3->version); + if (size < sizeof(ServiceSSLV3Hdr) || + hdr3->type != SSL_HANDSHAKE || + (ver != 0x0300 && + ver != 0x0301 && + ver != 0x0302 && + ver != 0x0303)) + { + goto fail; + } + data += sizeof(ServiceSSLV3Hdr); + size -= sizeof(ServiceSSLV3Hdr); + ss->tot_length = ntohs(hdr3->len); + } + + rec = (ServiceSSLV3Record*)data; + if (size < offsetof(ServiceSSLV3Record, version) || + rec->length_msb) + { + goto fail; + } + switch (rec->type) + { + case SSL_CERTIFICATE: + /* Start pulling out certificates. */ + if (!ss->certs_data) + { + certs_rec = (ServiceSSLV3CertsRecord*)data; + ss->certs_len = ntoh3(certs_rec->certs_len); + ss->certs_data = (uint8_t*)snort_alloc(ss->certs_len); + if ((size - sizeof(ServiceSSLV3CertsRecord)) < ss->certs_len) + { + /* Will have to get more next time around. */ + ss->in_certs = 1; + // Skip over header to data + ss->certs_curr_len = size - sizeof(ServiceSSLV3CertsRecord); + memcpy(ss->certs_data, data + sizeof(ServiceSSLV3CertsRecord), + ss->certs_curr_len); + } + else + { + /* Can get it all this time. */ + ss->in_certs = 0; + ss->certs_curr_len = ss->certs_len; + memcpy(ss->certs_data, data + sizeof(ServiceSSLV3CertsRecord), + ss->certs_curr_len); + goto success; /* We got everything we need. */ + } + } + /* fall through */ + case SSL_SERVER_KEY_XCHG: + case SSL_SERVER_CERT_REQ: + ss->length = ntohs(rec->length) + + offsetof(ServiceSSLV3Record, version); + if (size == ss->length) + goto success; + if (ss->tot_length < ss->length) + goto fail; + ss->tot_length -= ss->length; + if (size < ss->length) + { + ss->pos = size; + size = 0; + } + else + { + data += ss->length; + size -= ss->length; + ss->pos = 0; + } + ss->state = SSL_STATE_HEADER; + break; + case SSL_SERVER_HELLO_DONE: + if (rec->length) + goto fail; + if (ss->tot_length != offsetof(ServiceSSLV3Record, version)) + goto fail; + goto success; + default: + goto fail; + } + } + else + { + /* See if there's more certificate data to grab. */ + if (ss->in_certs && ss->certs_data) + { + if (size < (ss->certs_len - ss->certs_curr_len)) + { + /* Will have to get more next time around. */ + memcpy(ss->certs_data + ss->certs_curr_len, data, size); + ss->in_certs = 1; + ss->certs_curr_len += size; + } + else + { + /* Can get it all this time. */ + memcpy(ss->certs_data + ss->certs_curr_len, data, ss->certs_len - + ss->certs_curr_len); + ss->in_certs = 0; + ss->certs_curr_len = ss->certs_len; + goto success; /* We got everything we need. */ + } + } + + if (size+ss->pos < ss->length) + { + ss->pos += size; + size = 0; + } + else + { + data += ss->length - ss->pos; + size -= ss->length - ss->pos; + ss->pos = 0; + } + ss->state = SSL_STATE_HEADER; + } + } + break; + default: + goto fail; + } + +inprocess: + ssl_service_mod.api->service_inprocess(flowp, args->pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +fail: + snort_free(ss->certs_data); + snort_free(ss->host_name); + snort_free(ss->common_name); + snort_free(ss->org_name); + ss->certs_data = nullptr; + ss->host_name = ss->common_name = ss->org_name = nullptr; + ssl_service_mod.api->fail_service(flowp, args->pkt, dir, &svc_element, + ssl_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; + +success: + if (ss->certs_data && ss->certs_len) + { + if (!parse_certificates(ss)) + { + goto fail; + } + } + setAppIdFlag(flowp, APPID_SESSION_SSL_SESSION); + if (ss->host_name || ss->common_name || ss->org_name) + { + if (!flowp->tsession) + flowp->tsession = (tlsSession*)snort_calloc(sizeof(tlsSession)); + + /* TLS Host */ + if (ss->host_name) + { + if (flowp->tsession->tls_host) + snort_free(flowp->tsession->tls_host); + flowp->tsession->tls_host = ss->host_name; + flowp->tsession->tls_host_strlen = ss->host_name_strlen; + flowp->scan_flags |= SCAN_SSL_HOST_FLAG; + } + else if (ss->common_name) + { + // use common name (from server) if we didn't see host name (from client) + char* common_name = snort_strdup(ss->common_name); + + if (flowp->tsession->tls_host) + snort_free(flowp->tsession->tls_host); + flowp->tsession->tls_host = common_name; + flowp->tsession->tls_host_strlen = ss->common_name_strlen; + flowp->scan_flags |= SCAN_SSL_HOST_FLAG; + } + + /* TLS Common Name */ + if (ss->common_name) + { + if (flowp->tsession->tls_cname) + snort_free(flowp->tsession->tls_cname); + flowp->tsession->tls_cname = ss->common_name; + flowp->tsession->tls_cname_strlen = ss->common_name_strlen; + } + + /* TLS Org Unit */ + if (ss->org_name) + { + if (flowp->tsession->tls_orgUnit) + snort_free(flowp->tsession->tls_orgUnit); + flowp->tsession->tls_orgUnit = ss->org_name; + flowp->tsession->tls_orgUnit_strlen = ss->org_name_strlen; + } + + ss->host_name = ss->common_name = ss->org_name = nullptr; + } + ssl_service_mod.api->add_service(flowp, args->pkt, dir, &svc_element, + getSslServiceAppId(args->pkt->ptrs.sp), nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; +} + +AppId getSslServiceAppId(short srcPort) +{ + switch (srcPort) + { + case 261: + return APP_ID_NSIIOPS; + case 443: + return APP_ID_HTTPS; + case 448: + return APP_ID_DDM_SSL; + case 465: + return APP_ID_SMTPS; + case 563: + return APP_ID_NNTPS; + case 585: /*Currently 585 is de-registered at IANA but old implementation may still use it. */ + case 993: + return APP_ID_IMAPS; + case 614: + return APP_ID_SSHELL; + case 636: + return APP_ID_LDAPS; + case 989: + return APP_ID_FTPSDATA; + case 990: + return APP_ID_FTPS; + case 992: + return APP_ID_TELNETS; + case 994: + return APP_ID_IRCS; + case 995: + return APP_ID_POP3S; + case 3269: + return APP_ID_MSFT_GC_SSL; + case 8305: + return APP_ID_SF_APPLIANCE_MGMT; + default: + return APP_ID_SSL; + } +} + +bool isSslServiceAppId(AppId appId) +{ + switch (appId) + { + case APP_ID_NSIIOPS: + case APP_ID_HTTPS: + case APP_ID_DDM_SSL: + case APP_ID_SMTPS: + case APP_ID_NNTPS: + case APP_ID_IMAPS: + case APP_ID_SSHELL: + case APP_ID_LDAPS: + case APP_ID_FTPSDATA: + case APP_ID_FTPS: + case APP_ID_TELNETS: + case APP_ID_IRCS: + case APP_ID_POP3S: + case APP_ID_MSFT_GC_SSL: + case APP_ID_SF_APPLIANCE_MGMT: + case APP_ID_SSL: + return true; + } + + return false; +} + +static int ssl_scan_patterns(SearchTool* matcher, const u_int8_t* pattern, size_t size, + AppId* ClientAppId, AppId* payloadId) +{ + MatchedSSLPatterns* mp = nullptr; + MatchedSSLPatterns* tmpMp; + SSLCertPattern* best_match; + + if (!matcher) + return 0; + + matcher->find_all((char*)pattern, size, ssl_cert_pattern_match, false, &mp); + + if (!mp) + return 0; + + best_match = nullptr; + while (mp) + { + //only patterns that match start of payload, or patterns starting with '.' or patterns + // folowing '.' in payload + //are considered a match. + if (mp->index == 0 || *mp->mpattern->pattern == '.' || pattern[mp->index-1] == '.') + { + if (!best_match || mp->mpattern->pattern_size > best_match->pattern_size) + { + best_match = mp->mpattern; + } + } + tmpMp = mp; + mp = mp->next; + free (tmpMp); + } + if (!best_match) + return 0; + + switch (best_match->type) + { + /* type 0 means WEB APP */ + case 0: + *ClientAppId = APP_ID_SSL_CLIENT; + *payloadId = best_match->appId; + break; + /* type 1 means CLIENT */ + case 1: + *ClientAppId = best_match->appId; + *payloadId = 0; + break; + default: + return 0; + } + + return 1; +} + +int ssl_scan_hostname(const u_int8_t* pattern, size_t size, AppId* ClientAppId, AppId* payloadId, + ServiceSslConfig* pSslConfig) +{ + return ssl_scan_patterns(pSslConfig->ssl_host_matcher, pattern, size, ClientAppId, payloadId); +} + +int ssl_scan_cname(const u_int8_t* pattern, size_t size, AppId* ClientAppId, AppId* payloadId, + ServiceSslConfig* pSslConfig) +{ + return ssl_scan_patterns(pSslConfig->ssl_cname_matcher, pattern, size, ClientAppId, payloadId); +} + +void service_ssl_clean(ServiceSslConfig* pSslConfig) +{ + if (pSslConfig->ssl_host_matcher) + { + delete pSslConfig->ssl_host_matcher; + pSslConfig->ssl_host_matcher = nullptr; + } + if (pSslConfig->ssl_cname_matcher) + { + delete pSslConfig->ssl_cname_matcher; + pSslConfig->ssl_cname_matcher = nullptr; + } +} + +static int ssl_add_pattern(DetectorSSLCertPattern** list, uint8_t* pattern_str, size_t + pattern_size, uint8_t type, AppId app_id) +{ + DetectorSSLCertPattern* new_ssl_pattern; + + new_ssl_pattern = (DetectorSSLCertPattern*)snort_calloc(sizeof(DetectorSSLCertPattern)); + new_ssl_pattern->dpattern = (SSLCertPattern*)snort_calloc(sizeof(SSLCertPattern)); + new_ssl_pattern->dpattern->type = type; + new_ssl_pattern->dpattern->appId = app_id; + new_ssl_pattern->dpattern->pattern = pattern_str; + new_ssl_pattern->dpattern->pattern_size = pattern_size; + + new_ssl_pattern->next = *list; + *list = new_ssl_pattern; + + return 1; +} + +int ssl_add_cert_pattern(uint8_t* pattern_str, size_t pattern_size, uint8_t type, AppId app_id, + ServiceSslConfig* pSslConfig) +{ + return ssl_add_pattern(&pSslConfig->DetectorSSLCertPatternList, pattern_str, pattern_size, + type, app_id); +} + +int ssl_add_cname_pattern(uint8_t* pattern_str, size_t pattern_size, uint8_t type, AppId app_id, + ServiceSslConfig* pSslConfig) +{ + return ssl_add_pattern(&pSslConfig->DetectorSSLCnamePatternList, pattern_str, pattern_size, + type, app_id); +} + +static void ssl_patterns_free(DetectorSSLCertPattern** list) +{ + DetectorSSLCertPattern* tmp_pattern; + + while ((tmp_pattern = *list)) + { + *list = tmp_pattern->next; + if (tmp_pattern->dpattern) + { + if (tmp_pattern->dpattern->pattern) + snort_free(tmp_pattern->dpattern->pattern); + free (tmp_pattern->dpattern); + } + snort_free(tmp_pattern); + } +} + +void ssl_detector_free_patterns(ServiceSslConfig* pSslConfig) +{ + ssl_patterns_free(&pSslConfig->DetectorSSLCertPatternList); + ssl_patterns_free(&pSslConfig->DetectorSSLCnamePatternList); +} + +int setSSLSquelch(Packet* p, int type, AppId appId) +{ + const sfip_t* sip; + const sfip_t* dip; + AppIdData* f; + + if (!appInfoEntryFlagGet(appId, APPINFO_FLAG_SSL_SQUELCH, pAppidActiveConfig)) + return 0; + + dip = p->ptrs.ip_api.get_dst(); + sip = p->ptrs.ip_api.get_src(); + + if (!(f = AppIdEarlySessionCreate(nullptr, p, sip, 0, dip, p->ptrs.dp, + IpProtocol::TCP, appId, 0))) + return 0; + + switch (type) + { + case 1: + f->payloadAppId = appId; + break; + case 2: + f->ClientAppId = appId; + f->rnaClientState = RNA_STATE_FINISHED; + break; + default: + return 0; + } + + return 1; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_ssl.h b/src/network_inspectors/appid/service_plugins/service_ssl.h new file mode 100644 index 000000000..317b4d9f2 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_ssl.h @@ -0,0 +1,41 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_ssl.h author Sourcefire Inc. + +#ifndef SERVICE_SSL_H +#define SERVICE_SSL_H + +#include "detector_plugins/detector_api.h" +#include "service_config.h" + +extern struct RNAServiceValidationModule ssl_service_mod; +AppId getSslServiceAppId(short srcPort); +bool isSslServiceAppId(AppId appId); +void service_ssl_clean(ServiceSslConfig*); +int ssl_detector_process_patterns(ServiceSslConfig*); +int ssl_scan_hostname(const u_int8_t*, size_t, AppId*, AppId*, ServiceSslConfig*); +int ssl_scan_cname(const u_int8_t*, size_t, AppId*, AppId*, ServiceSslConfig*); +int ssl_add_cert_pattern(uint8_t*, size_t, uint8_t, AppId, ServiceSslConfig*); +int ssl_add_cname_pattern(uint8_t*, size_t, uint8_t, AppId, ServiceSslConfig*); +void ssl_detector_free_patterns(ServiceSslConfig*); +int setSSLSquelch(Packet* p, int type, AppId appId); + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_telnet.cc b/src/network_inspectors/appid/service_plugins/service_telnet.cc new file mode 100644 index 000000000..1a54c2677 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_telnet.cc @@ -0,0 +1,185 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_telnet.cc author Sourcefire Inc. + +#include +#include +#include +#include +#include +#include + +#include "main/snort_debug.h" +#include "utils/util.h" + +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_api.h" + +#define TELNET_COUNT_THRESHOLD 3 + +#define TELNET_IAC 255 +#define TELNET_MIN_CMD 236 +#define TELNET_MIN_DATA_CMD 250 +#define TELNET_SUB_NEG_CMD 250 +#define TELNET_SUB_NEG_END_CMD 240 +#define TELNET_CMD_MAX_OPTION 44 + +enum TELNET_COMMAND_VALUE +{ + TELNET_CMD_SE = 240, + TELNET_CMD_NOP, + TELNET_CMD_DMARK, + TELNET_CMD_BREAK, + TELNET_CMD_IP, + TELNET_CMD_AO, + TELNET_CMD_AYT, + TELNET_CMD_EC, + TELNET_CMD_EL, + TELNET_CMD_GA, + TELNET_CMD_SB, + TELNET_CMD_WILL, + TELNET_CMD_WONT, + TELNET_CMD_DO, + TELNET_CMD_DONT, + TELNET_CMD_IAC +}; + +struct ServiceTelnetData +{ + unsigned count; +}; + +static int telnet_init(const IniServiceAPI* const init_api); +static int telnet_validate(ServiceValidationArgs* args); + +static const RNAServiceElement svc_element = +{ + nullptr, + &telnet_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "telnet", +}; + +// FIXIT thread safety, can this be const? +static RNAServiceValidationPort pp[] = +{ + { &telnet_validate, 23, IpProtocol::TCP, 0 }, + { &telnet_validate, 23, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +// FIXIT thread safety, can this be const? +RNAServiceValidationModule telnet_service_mod = +{ + "TELNET", + &telnet_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static const AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_TELNET, 0 } +}; + +static int telnet_init(const IniServiceAPI* const init_api) +{ + for (unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&telnet_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int telnet_validate(ServiceValidationArgs* args) +{ + ServiceTelnetData* td; + const uint8_t* end; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + td = (ServiceTelnetData*)telnet_service_mod.api->data_get(flowp, + telnet_service_mod.flow_data_index); + if (!td) + { + td = (ServiceTelnetData*)snort_calloc(sizeof(ServiceTelnetData)); + telnet_service_mod.api->data_add(flowp, td, telnet_service_mod.flow_data_index, + &snort_free); + } + + for (end=(data+size); data= end) + goto fail; + switch (*data) + { + case TELNET_CMD_WILL: + case TELNET_CMD_WONT: + case TELNET_CMD_DO: + case TELNET_CMD_DONT: + data++; + if (data >= end) + goto fail; + td->count++; + if (td->count >= TELNET_COUNT_THRESHOLD) + goto success; + break; + default: + goto fail; + } + } +inprocess: + telnet_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + telnet_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_TELNET, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + telnet_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + telnet_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_telnet.h b/src/network_inspectors/appid/service_plugins/service_telnet.h new file mode 100644 index 000000000..1a929add1 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_telnet.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_telnet.h author Sourcefire Inc. + +#ifndef SERVICE_TELNET_H +#define SERVICE_TELNET_H + +#include "detector_plugins/detector_api.h" + +extern RNAServiceValidationModule telnet_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_tftp.cc b/src/network_inspectors/appid/service_plugins/service_tftp.cc new file mode 100644 index 000000000..e025cacdc --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_tftp.cc @@ -0,0 +1,382 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_tftp.cc author Sourcefire Inc. + +#include +#include +#include +#include +#include +#include + +#include "main/snort_debug.h" +#include "log/messages.h" +#include "target_based/snort_protocols.h" +#include "utils/util.h" + +#include "app_info_table.h" +#include "appid_api.h" +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_api.h" +#include "service_base.h" + +#define TFTP_PORT 69 + +#define TFTP_COUNT_THRESHOLD 1 +#define TFTP_MAX_PACKET_SIZE 512 + +enum TFTPState +{ + TFTP_STATE_CONNECTION, + TFTP_STATE_TRANSFER, + TFTP_STATE_ACK, + TFTP_STATE_DATA, + TFTP_STATE_ERROR +}; + +struct ServiceTFTPData +{ + TFTPState state; + unsigned count; + int last; + uint16_t block; +}; + +#pragma pack(1) + +struct ServiceTFTPHeader +{ + uint16_t opcode; + union + { + uint16_t block; + uint16_t errorcode; + } d; +}; + +#pragma pack() + +static int tftp_init(const IniServiceAPI* const api); +static int tftp_validate(ServiceValidationArgs* args); + +static const RNAServiceElement svc_element = +{ + nullptr, + &tftp_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "tftp", +}; + +// FIXIT thread safety, can this be const? +static RNAServiceValidationPort pp[] = +{ + { &tftp_validate, 69, IpProtocol::UDP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +// FIXIT thread safety, can this be const? +RNAServiceValidationModule tftp_service_mod = +{ + "TFTP", + &tftp_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static const AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_TFTP, APPINFO_FLAG_SERVICE_ADDITIONAL } +}; + +static int16_t app_id = 0; + +static int tftp_init(const IniServiceAPI* const init_api) +{ + app_id = AddProtocolReference("tftp"); + for (unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&tftp_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int tftp_verify_header(const uint8_t* data, uint16_t size, + uint16_t* block) +{ + if (size < sizeof(ServiceTFTPHeader)) + return -1; + const ServiceTFTPHeader* hdr = (ServiceTFTPHeader*)data; + switch (ntohs(hdr->opcode)) + { + case 3: + if (size > sizeof(ServiceTFTPHeader) + TFTP_MAX_PACKET_SIZE) + return -1; + *block = ntohs(hdr->d.block); + return TFTP_STATE_DATA; + case 4: + if (size != sizeof(ServiceTFTPHeader)) + return -1; + *block = ntohs(hdr->d.block); + return TFTP_STATE_ACK; + case 5: + if (ntohs(hdr->d.errorcode) > 7) + return -1; + if (size <= sizeof(ServiceTFTPHeader)) + return -1; + if (data[size-1] != 0) + return -1; + return TFTP_STATE_ERROR; + default: + return -1; + } +} + +static int tftp_validate(ServiceValidationArgs* args) +{ + ServiceTFTPData* td; + ServiceTFTPData* tmp_td; + int mode; + uint16_t block = 0; + uint16_t tmp; + AppIdData* pf; + const sfip_t* sip; + const sfip_t* dip; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + Packet* pkt = args->pkt; + const int dir = args->dir; + uint16_t size = args->size; + char* app_id_debug_session = args->app_id_debug_session; + + if (!size) + goto inprocess; + + td = (ServiceTFTPData*)tftp_service_mod.api->data_get(flowp, tftp_service_mod.flow_data_index); + if (!td) + { + td = (ServiceTFTPData*)snort_calloc(sizeof(ServiceTFTPData)); + tftp_service_mod.api->data_add(flowp, td, tftp_service_mod.flow_data_index, &snort_free); + td->state = TFTP_STATE_CONNECTION; + } + if (args->app_id_debug_session_flag) + LogMessage("AppIdDbg %s tftp state %d\n", app_id_debug_session, td->state); + + if (td->state == TFTP_STATE_CONNECTION && dir == APP_ID_FROM_RESPONDER) + goto fail; + if ((td->state == TFTP_STATE_TRANSFER || td->state == TFTP_STATE_DATA) && + dir == APP_ID_FROM_INITIATOR) + { + goto inprocess; + } + switch (td->state) + { + case TFTP_STATE_CONNECTION: + if (size < 6) + goto bail; + tmp = ntohs(*((uint16_t*)data)); + if (tmp != 0x0001 && tmp != 0x0002) + goto bail; + data += sizeof(uint16_t); + size -= sizeof(uint16_t); + if (!(*data)) + goto bail; + for (; *data && size; data++, size--) + { + if (!isprint(*data)) + goto bail; + } + if (!size) + goto bail; + size--; + data++; + if (!size || !(*data)) + goto bail; + if (data[size-1]) + goto bail; + if (strcasecmp((char*)data, "netascii") && strcasecmp((char*)data, "octet")) + goto bail; + + tmp_td = (ServiceTFTPData*)snort_calloc(sizeof(ServiceTFTPData)); + tmp_td->state = TFTP_STATE_TRANSFER; + dip = pkt->ptrs.ip_api.get_dst(); + sip = pkt->ptrs.ip_api.get_src(); + pf = tftp_service_mod.api->flow_new(flowp, pkt, dip, 0, sip, pkt->ptrs.sp, flowp->proto, + app_id, APPID_EARLY_SESSION_FLAG_FW_RULE); + if (pf) + { + tftp_service_mod.api->data_add(pf, tmp_td, + tftp_service_mod.flow_data_index, &snort_free); + if (tftp_service_mod.api->data_add_id(pf, pkt->ptrs.dp, &svc_element)) + { + setAppIdFlag(pf, APPID_SESSION_SERVICE_DETECTED); + clearAppIdFlag(pf, APPID_SESSION_CONTINUE); + tmp_td->state = TFTP_STATE_ERROR; + return SERVICE_ENOMEM; + } + PopulateExpectedFlow(flowp, pf, APPID_SESSION_EXPECTED_EVALUATE); + pf->common.initiator_ip = *sip; + pf->rnaServiceState = RNA_STATE_STATEFUL; + pf->scan_flags |= SCAN_HOST_PORT_FLAG; + } + else + { + snort_free(tmp_td); + goto inprocess; /* Assume that the flow already exists + as in a retransmit situation */ + } + break; + case TFTP_STATE_TRANSFER: + if ((mode=tftp_verify_header(data, size, &block)) < 0) + { + if (args->app_id_debug_session_flag) + LogMessage("AppIdDbg %s tftp failed to verify\n", app_id_debug_session); + goto fail; + } + if (args->app_id_debug_session_flag) + LogMessage("AppIdDbg %s tftp mode %d and block %u\n", app_id_debug_session, + mode, (unsigned)block); + if (mode == TFTP_STATE_ACK) + { + if (block != 0) + { + td->state = TFTP_STATE_ERROR; + goto fail; + } + td->last = 0; + td->block = 0; + td->state = TFTP_STATE_ACK; + } + else if (mode == TFTP_STATE_DATA) + { + if (block != 1) + { + td->state = TFTP_STATE_ERROR; + goto fail; + } + td->block = 1; + td->state = TFTP_STATE_DATA; + } + else if (mode == TFTP_STATE_ERROR) + break; + else + { + td->state = TFTP_STATE_ERROR; + goto fail; + } + break; + case TFTP_STATE_ACK: + if ((mode=tftp_verify_header(data, size, &block)) < 0) + { + if (dir == APP_ID_FROM_RESPONDER) + goto fail; + else + { + if (args->app_id_debug_session_flag) + LogMessage("AppIdDbg %s tftp failed to verify\n", app_id_debug_session); + goto bail; + } + } + if (args->app_id_debug_session_flag) + LogMessage("AppIdDbg %s tftp mode %d\n", app_id_debug_session, mode); + if (mode == TFTP_STATE_ERROR) + { + td->state = TFTP_STATE_TRANSFER; + break; + } + if (dir == APP_ID_FROM_INITIATOR && mode != TFTP_STATE_DATA) + { + if (args->app_id_debug_session_flag) + LogMessage("AppIdDbg %s tftp bad mode\n", app_id_debug_session); + goto bail; + } + if (dir == APP_ID_FROM_RESPONDER && mode != TFTP_STATE_ACK) + goto fail; + if (dir == APP_ID_FROM_INITIATOR) + { + if (size < sizeof(ServiceTFTPHeader) + TFTP_MAX_PACKET_SIZE) + td->last = 1; + break; + } + if (block == (uint16_t)(td->block + 1)) + td->block++; + else if (block != td->block) + goto fail; + td->count++; + if (td->count >= TFTP_COUNT_THRESHOLD) + goto success; + if (td->last) + td->state = TFTP_STATE_TRANSFER; + break; + case TFTP_STATE_DATA: + if ((mode=tftp_verify_header(data, size, &block)) < 0) + goto fail; + if (mode == TFTP_STATE_ERROR) + td->state = TFTP_STATE_TRANSFER; + else if (mode != TFTP_STATE_DATA) + goto fail; + if (block == (uint16_t)(td->block + 1)) + td->block++; + else if (block != td->block) + goto fail; + td->count++; + if (td->count >= TFTP_COUNT_THRESHOLD) + goto success; + if (size < sizeof(ServiceTFTPHeader) + TFTP_MAX_PACKET_SIZE) + td->state = TFTP_STATE_TRANSFER; + break; + case TFTP_STATE_ERROR: + default: + goto fail; + } + +inprocess: + tftp_service_mod.api->service_inprocess(flowp, pkt, dir, &svc_element); + return SERVICE_INPROCESS; + +success: + if (args->app_id_debug_session_flag) + LogMessage("AppIdDbg %s tftp success\n", app_id_debug_session); + tftp_service_mod.api->add_service(flowp, pkt, dir, &svc_element, + APP_ID_TFTP, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +bail: + tftp_service_mod.api->incompatible_data(flowp, pkt, dir, &svc_element, + tftp_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOT_COMPATIBLE; + +fail: + tftp_service_mod.api->fail_service(flowp, pkt, dir, &svc_element, + tftp_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_tftp.h b/src/network_inspectors/appid/service_plugins/service_tftp.h new file mode 100644 index 000000000..5685f28b8 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_tftp.h @@ -0,0 +1,30 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_tftp.h author Sourcefire Inc. + +#ifndef SERVICE_TFTP_H +#define SERVICE_TFTP_H + +#include "detector_plugins/detector_api.h" + +extern RNAServiceValidationModule tftp_service_mod; + +#endif + diff --git a/src/network_inspectors/appid/service_plugins/service_timbuktu.cc b/src/network_inspectors/appid/service_plugins/service_timbuktu.cc new file mode 100644 index 000000000..630f44c75 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_timbuktu.cc @@ -0,0 +1,200 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_timbuktu.cc author Sourcefire Inc. + +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_api.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +static const char svc_name[] = "timbuktu"; +static char TIMBUKTU_BANNER[] = "\001\001"; + +#define TIMBUKTU_PORT 407 + +#define TIMBUKTU_BANNER_LEN (sizeof(TIMBUKTU_BANNER)-1) + +enum TIMBUKTUState +{ + TIMBUKTU_STATE_BANNER, + TIMBUKTU_STATE_MESSAGE_LEN, + TIMBUKTU_STATE_MESSAGE_DATA +}; + +struct ServiceTIMBUKTUData +{ + TIMBUKTUState state; + unsigned stringlen; + unsigned pos; +}; + +#pragma pack(1) +struct ServiceTIMBUKTUMsg +{ + uint16_t any; + uint8_t res; + uint8_t len; + uint8_t message; +}; +#pragma pack() + +static int timbuktu_init(const IniServiceAPI* const init_api); +static int timbuktu_validate(ServiceValidationArgs* args); + +static const RNAServiceElement svc_element = +{ + nullptr, + &timbuktu_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "timbuktu" +}; + +// FIXIT thread safety, can this be const? +static RNAServiceValidationPort pp[] = +{ + { &timbuktu_validate, TIMBUKTU_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +// FIXIT thread safety, can this be const? +SO_PUBLIC RNAServiceValidationModule timbuktu_service_mod = +{ + svc_name, + &timbuktu_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static const AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_TIMBUKTU, 0 } +}; + +static int timbuktu_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&timbuktu_validate, IpProtocol::TCP, (const + u_int8_t*)TIMBUKTU_BANNER, + sizeof(TIMBUKTU_BANNER)-1, 0, svc_name, init_api->pAppidConfig); + for (unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&timbuktu_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int timbuktu_validate(ServiceValidationArgs* args) +{ + ServiceTIMBUKTUData* ss; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + uint16_t offset=0; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + ss = (ServiceTIMBUKTUData*)timbuktu_service_mod.api->data_get(flowp, + timbuktu_service_mod.flow_data_index); + if (!ss) + { + ss = (ServiceTIMBUKTUData*)snort_calloc(sizeof(ServiceTIMBUKTUData)); + timbuktu_service_mod.api->data_add(flowp, ss, + timbuktu_service_mod.flow_data_index, &snort_free); + ss->state = TIMBUKTU_STATE_BANNER; + } + + offset = 0; + while (offset < size) + { + switch (ss->state) + { + case TIMBUKTU_STATE_BANNER: + if (data[offset] != TIMBUKTU_BANNER[ss->pos]) + goto fail; + if (ss->pos >= TIMBUKTU_BANNER_LEN-1) + { + ss->pos = 0; + ss->state = TIMBUKTU_STATE_MESSAGE_LEN; + break; + } + ss->pos++; + break; + case TIMBUKTU_STATE_MESSAGE_LEN: + ss->pos++; + if (ss->pos >= offsetof(ServiceTIMBUKTUMsg, message)) + { + ss->stringlen = data[offset]; + ss->state = TIMBUKTU_STATE_MESSAGE_DATA; + if (!ss->stringlen) + { + if (offset == size-1) + goto success; + goto fail; + } + ss->pos = 0; + } + break; + + case TIMBUKTU_STATE_MESSAGE_DATA: + ss->pos++; + if (ss->pos == ss->stringlen) + { + if (offset == (size-1)) + goto success; + goto fail; + } + break; + default: + goto fail; + } + offset++; + } + +inprocess: + timbuktu_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + timbuktu_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, + APP_ID_TIMBUKTU, nullptr, nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + timbuktu_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + timbuktu_service_mod.flow_data_index, + args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_tns.cc b/src/network_inspectors/appid/service_plugins/service_tns.cc new file mode 100644 index 000000000..712371d76 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_tns.cc @@ -0,0 +1,317 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_tns.cc author Sourcefire Inc. + +#include "app_info_table.h" +#include "appid_flow_data.h" +#include "application_ids.h" +#include "service_api.h" + +#include "main/snort_debug.h" +#include "utils/util.h" + +static const char svc_name[] = "oracle"; +static const uint8_t TNS_BANNER[] = "\000\000"; + +#define TNS_BANNER_LEN (sizeof(TNS_BANNER)-1) +#define TNS_PORT 1521 + +#define TNS_TYPE_CONNECT 1 +#define TNS_TYPE_ACCEPT 2 +#define TNS_TYPE_ACK 3 +#define TNS_TYPE_REFUSE 4 +#define TNS_TYPE_REDIRECT 5 +#define TNS_TYPE_DATA 6 +#define TNS_TYPE_NULL 7 +#define TNS_TYPE_ABORT 9 +#define TNS_TYPE_RESEND 11 +#define TNS_TYPE_MARKER 12 +#define TNS_TYPE_ATTENTION 13 +#define TNS_TYPE_CONTROL 14 +#define TNS_TYPE_MAX 19 + +enum TNSState +{ + TNS_STATE_MESSAGE_LEN, + TNS_STATE_MESSAGE_CHECKSUM, + TNS_STATE_MESSAGE, + TNS_STATE_MESSAGE_RES, + TNS_STATE_MESSAGE_HD_CHECKSUM, + TNS_STATE_MESSAGE_ACCEPT, + TNS_STATE_MESSAGE_DATA +}; + +#define ACCEPT_VERSION_OFFSET 8 +#define MAX_VERSION_SIZE 12 +struct ServiceTNSData +{ + TNSState state; + unsigned stringlen; + unsigned pos; + unsigned message; + union + { + uint16_t len; + uint8_t raw_len[2]; + } l; + const char* version; +}; + +#pragma pack(1) +struct ServiceTNSMsg +{ + uint16_t len; + uint16_t checksum; + uint8_t msg; + uint8_t res; + uint16_t hdchecksum; + uint8_t data; +}; +#pragma pack() + +static int tns_init(const IniServiceAPI* const init_api); +static int tns_validate(ServiceValidationArgs* args); + +static const RNAServiceElement svc_element = +{ + nullptr, + &tns_validate, + nullptr, + DETECTOR_TYPE_DECODER, + 1, + 1, + 0, + "tns" +}; + +// FIXIT thread safety, can this be const? +static RNAServiceValidationPort pp[] = +{ + { &tns_validate, TNS_PORT, IpProtocol::TCP, 0 }, + { nullptr, 0, IpProtocol::PROTO_NOT_SET, 0 } +}; + +// FIXIT thread safety, can this be const? +SO_PUBLIC RNAServiceValidationModule tns_service_mod = +{ + svc_name, + &tns_init, + pp, + nullptr, + nullptr, + 0, + nullptr, + 0 +}; + +static const AppRegistryEntry appIdRegistry[] = +{ + { APP_ID_ORACLE_TNS, APPINFO_FLAG_SERVICE_ADDITIONAL }, +}; + +static int tns_init(const IniServiceAPI* const init_api) +{ + init_api->RegisterPattern(&tns_validate, IpProtocol::TCP, (const uint8_t*)TNS_BANNER, + TNS_BANNER_LEN, 2, svc_name, init_api->pAppidConfig); + for (unsigned i=0; i < sizeof(appIdRegistry)/sizeof(*appIdRegistry); i++) + { + DebugFormat(DEBUG_LOG,"registering appId: %d\n",appIdRegistry[i].appId); + init_api->RegisterAppId(&tns_validate, appIdRegistry[i].appId, + appIdRegistry[i].additionalInfo, init_api->pAppidConfig); + } + + return 0; +} + +static int tns_validate(ServiceValidationArgs* args) +{ + ServiceTNSData* ss; + uint16_t offset; + AppIdData* flowp = args->flowp; + const uint8_t* data = args->data; + uint16_t size = args->size; + + if (!size) + goto inprocess; + if (args->dir != APP_ID_FROM_RESPONDER) + goto inprocess; + + ss = (ServiceTNSData*)tns_service_mod.api->data_get(flowp, tns_service_mod.flow_data_index); + if (!ss) + { + ss = (ServiceTNSData*)snort_calloc(sizeof(ServiceTNSData)); + tns_service_mod.api->data_add(flowp, ss, tns_service_mod.flow_data_index, &snort_free); + ss->state = TNS_STATE_MESSAGE_LEN; + } + + offset = 0; + while (offset < size) + { + switch (ss->state) + { + case TNS_STATE_MESSAGE_LEN: + ss->l.raw_len[ss->pos++] = data[offset]; + if (ss->pos >= offsetof(ServiceTNSMsg, checksum)) + { + ss->stringlen = ntohs(ss->l.len); + if (ss->stringlen == 2) + { + if (offset == (size - 1)) + goto success; + goto fail; + } + else if (ss->stringlen < 2) + goto fail; + else + { + ss->state = TNS_STATE_MESSAGE_CHECKSUM; + } + } + break; + + case TNS_STATE_MESSAGE_CHECKSUM: + if (data[offset] != 0) + goto fail; + ss->pos++; + if (ss->pos >= offsetof(ServiceTNSMsg, msg)) + { + ss->state = TNS_STATE_MESSAGE; + } + break; + + case TNS_STATE_MESSAGE: + ss->message = data[offset]; + if (ss->message < TNS_TYPE_CONNECT || ss->message > TNS_TYPE_MAX) + goto fail; + ss->pos++; + ss->state = TNS_STATE_MESSAGE_RES; + break; + + case TNS_STATE_MESSAGE_RES: + ss->pos++; + ss->state = TNS_STATE_MESSAGE_HD_CHECKSUM; + break; + + case TNS_STATE_MESSAGE_HD_CHECKSUM: + ss->pos++; + if (ss->pos >= offsetof(ServiceTNSMsg, data)) + { + switch (ss->message) + { + case TNS_TYPE_ACCEPT: + ss->state = TNS_STATE_MESSAGE_ACCEPT; + break; + case TNS_TYPE_ACK: + case TNS_TYPE_REFUSE: + case TNS_TYPE_REDIRECT: + case TNS_TYPE_DATA: + case TNS_TYPE_NULL: + case TNS_TYPE_ABORT: + case TNS_TYPE_MARKER: + case TNS_TYPE_ATTENTION: + case TNS_TYPE_CONTROL: + if (ss->pos == ss->stringlen) + { + if (offset == (size - 1)) + goto success; + else + goto fail; + } + ss->state = TNS_STATE_MESSAGE_DATA; + break; + case TNS_TYPE_RESEND: + if (ss->pos == ss->stringlen) + { + if (offset == (size - 1)) + { + ss->state = TNS_STATE_MESSAGE_LEN; + ss->pos = 0; + goto inprocess; + } + else + goto fail; + } + break; + case TNS_TYPE_CONNECT: + default: + goto fail; + } + } + break; + + case TNS_STATE_MESSAGE_ACCEPT: + ss->l.raw_len[ss->pos - ACCEPT_VERSION_OFFSET] = data[offset]; + ss->pos++; + if (ss->pos >= (ACCEPT_VERSION_OFFSET + 2)) + { + switch (ntohs(ss->l.len)) + { + case 0x136: + ss->version = "8"; + break; + case 0x137: + ss->version = "9i R1"; + break; + case 0x138: + ss->version = "9i R2"; + break; + case 0x139: + ss->version = "10g R1/R2"; + break; + case 0x13A: + ss->version = "11g R1"; + break; + default: + break; + } + ss->state = TNS_STATE_MESSAGE_DATA; + } + break; + case TNS_STATE_MESSAGE_DATA: + ss->pos++; + if (ss->pos == ss->stringlen) + { + if (offset == (size - 1)) + goto success; + else + goto fail; + } + break; + default: + goto fail; + } + offset++; + } + +inprocess: + tns_service_mod.api->service_inprocess(flowp, args->pkt, args->dir, &svc_element); + return SERVICE_INPROCESS; + +success: + tns_service_mod.api->add_service(flowp, args->pkt, args->dir, &svc_element, APP_ID_ORACLE_TNS, + nullptr, ss->version ? ss->version : nullptr, nullptr); + return SERVICE_SUCCESS; + +fail: + tns_service_mod.api->fail_service(flowp, args->pkt, args->dir, &svc_element, + tns_service_mod.flow_data_index, args->pConfig); + return SERVICE_NOMATCH; +} + diff --git a/src/network_inspectors/appid/service_plugins/service_util.h b/src/network_inspectors/appid/service_plugins/service_util.h new file mode 100644 index 000000000..c99cbd2f1 --- /dev/null +++ b/src/network_inspectors/appid/service_plugins/service_util.h @@ -0,0 +1,43 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_util.h author Sourcefire Inc. + +#ifndef SERVICE_UTIL_H +#define SERVICE_UTIL_H + +#include +#include + +inline const uint8_t* service_strstr(const uint8_t* haystack, unsigned haystack_len, + const uint8_t* needle, unsigned needle_len) +{ + const uint8_t* h_end = haystack + haystack_len; + + for (const uint8_t* p = haystack; h_end-p >= (int)needle_len; p++) + { + if (memcmp(p, needle, needle_len) == 0) + { + return p; + } + } + return nullptr; +} + +#endif diff --git a/src/network_inspectors/appid/service_state.cc b/src/network_inspectors/appid/service_state.cc new file mode 100644 index 000000000..31511dd22 --- /dev/null +++ b/src/network_inspectors/appid/service_state.cc @@ -0,0 +1,228 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_state.cc author Sourcefire Inc. + +#include "service_state.h" + +#include "hash/sfxhash.h" +#include "log/messages.h" +#include "service_plugins/service_base.h" +#include "sfip/sf_ip.h" + +/*#define DEBUG_SERVICE_STATE 1*/ + +static SFXHASH* serviceStateCache4; +static SFXHASH* serviceStateCache6; + +#define SERVICE_STATE_CACHE_ROWS 65536 + +static int AppIdServiceStateFree(void*, void* data) +{ + AppIdServiceIDState* id_state = (AppIdServiceIDState*)data; + if (id_state->serviceList) + { + AppIdFreeServiceMatchList(id_state->serviceList); + id_state->serviceList = nullptr; + } + + return 0; +} + +int AppIdServiceStateInit(unsigned long memcap) +{ + serviceStateCache4 = sfxhash_new(SERVICE_STATE_CACHE_ROWS, + sizeof(AppIdServiceStateKey4), + sizeof(AppIdServiceIDState), + memcap >> 1, + 1, + &AppIdServiceStateFree, + &AppIdServiceStateFree, + 1); + if (!serviceStateCache4) + { + ErrorMessage("Failed to allocate a hash table"); + return -1; + } + serviceStateCache6 = sfxhash_new(SERVICE_STATE_CACHE_ROWS, + sizeof(AppIdServiceStateKey6), + sizeof(AppIdServiceIDState), + memcap >> 1, + 1, + &AppIdServiceStateFree, + &AppIdServiceStateFree, + 1); + if (!serviceStateCache6) + { + ErrorMessage("Failed to allocate a hash table"); + return -1; + } + return 0; +} + +void AppIdServiceStateCleanup(void) +{ + if (serviceStateCache4) + { + sfxhash_delete(serviceStateCache4); + serviceStateCache4 = nullptr; + } + if (serviceStateCache6) + { + sfxhash_delete(serviceStateCache6); + serviceStateCache6 = nullptr; + } +} + +void AppIdRemoveServiceIDState(const sfip_t* ip, IpProtocol proto, uint16_t port, uint32_t level) +{ + AppIdServiceStateKey k; + SFXHASH* cache; + + if (sfip_family(ip) == AF_INET6) + { + k.key6.proto = proto; + k.key6.port = port; + memcpy(k.key6.ip, ip->ip32, sizeof(k.key6.ip)); + k.key6.level = level; + cache = serviceStateCache6; + } + else + { + k.key4.proto = proto; + k.key4.port = port; + k.key4.ip = ip->ip32[0]; + k.key4.level = level; + cache = serviceStateCache4; + } + if (sfxhash_remove(cache, &k) != SFXHASH_OK) + { + char ipstr[INET6_ADDRSTRLEN]; + + ipstr[0] = 0; + sfip_ntop(ip, ipstr, sizeof(ipstr)); + ErrorMessage("Failed to remove from hash: %s:%u:%u\n",ipstr, (unsigned)proto, + (unsigned)port); + } +} + +AppIdServiceIDState* AppIdGetServiceIDState(const sfip_t* ip, IpProtocol proto, + uint16_t port, uint32_t level) +{ + AppIdServiceStateKey k; + SFXHASH* cache; + AppIdServiceIDState* ss; + + if (sfip_family(ip) == AF_INET6) + { + k.key6.proto = proto; + k.key6.port = port; + memcpy(k.key6.ip, ip->ip32, sizeof(k.key6.ip)); + k.key6.level = level; + cache = serviceStateCache6; + } + else + { + k.key4.proto = proto; + k.key4.port = port; + k.key4.ip = ip->ip32[0]; + k.key4.level = level; + cache = serviceStateCache4; + } + ss = (AppIdServiceIDState*)sfxhash_find(cache, &k); + +#ifdef DEBUG_SERVICE_STATE + char ipstr[INET6_ADDRSTRLEN]; + + ipstr[0] = 0; + sfip_ntop(ip, ipstr, sizeof(ipstr)); + LogMessage("ServiceState: Read from hash: %s:%u:%u:%u %p %u %p\n",ipstr, (unsigned)proto, + (unsigned)port, level, ss, ss ? ss->state : 0, ss ? ss->svc : nullptr); +#endif + + if (ss && ss->svc && !ss->svc->ref_count) + { + ss->svc = nullptr; + ss->state = SERVICE_ID_NEW; + } + + return ss; +} + +AppIdServiceIDState* AppIdAddServiceIDState(const sfip_t* ip, IpProtocol proto, uint16_t port, + uint32_t + level) +{ + AppIdServiceStateKey k; + AppIdServiceIDState* ss; + SFXHASH* cache; + char ipstr[INET6_ADDRSTRLEN]; + + if (sfip_family(ip) == AF_INET6) + { + k.key6.proto = proto; + k.key6.port = port; + memcpy(k.key6.ip, ip->ip32, sizeof(k.key6.ip)); + k.key6.level = level; + cache = serviceStateCache6; + } + else + { + k.key4.proto = proto; + k.key4.port = port; + k.key4.ip = ip->ip32[0]; + k.key4.level = level; + cache = serviceStateCache4; + } +#ifdef DEBUG_SERVICE_STATE + ipstr[0] = 0; + sfip_ntop(ip, ipstr, sizeof(ipstr)); +#endif + if ((sfxhash_add_return_data_ptr(cache, &k, (void**)&ss) < 0) || !ss) + { + ipstr[0] = 0; + sfip_ntop(ip, ipstr, sizeof(ipstr)); + ErrorMessage("ServiceState: Failed to add to hash: %s:%u:%u:%u\n",ipstr, (unsigned)proto, + (unsigned)port, level); + return nullptr; + } +#ifdef DEBUG_SERVICE_STATE + LogMessage("ServiceState: Added to hash: %s:%u:%u:%u %p\n",ipstr, (unsigned)proto, + (unsigned)port, level, ss); +#endif + return ss; +} + +void AppIdServiceStateDumpStats(void) +{ + LogMessage("Service State:\n"); + if (serviceStateCache4) + { + LogMessage(" IPv4 Count: %u\n", sfxhash_count(serviceStateCache4)); + LogMessage(" IPv4 Memory Limit: %lu\n", serviceStateCache4->mc.memcap); + LogMessage(" IPv4 Memory Used: %lu\n", serviceStateCache4->mc.memused); + } + if (serviceStateCache6) + { + LogMessage(" IPv6 Count: %u\n", sfxhash_count(serviceStateCache6)); + LogMessage(" IPv6 Memory Limit: %lu\n", serviceStateCache6->mc.memcap); + LogMessage(" IPv6 Memory Used: %lu\n", serviceStateCache6->mc.memused); + } +} + diff --git a/src/network_inspectors/appid/service_state.h b/src/network_inspectors/appid/service_state.h new file mode 100644 index 000000000..bb2747a5d --- /dev/null +++ b/src/network_inspectors/appid/service_state.h @@ -0,0 +1,135 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// service_state.h author Sourcefire Inc. + +#ifndef SERVICE_STATE_H +#define SERVICE_STATE_H + +#include "sfip/sfip_t.h" + +struct RNAServiceElement; +struct ServiceMatch; +enum class IpProtocol : uint8_t; + +// Service state stored in hosttracker for maintaining service matching states. +enum SERVICE_ID_STATE +{ + /**first search of service. The matching criteria is coded in ProtocolID funtion. + */ + SERVICE_ID_NEW = 0, + + /**service is already detected and valid. + */ + SERVICE_ID_VALID, + + /**match based on source or destination port in first packet in flow. + */ + SERVICE_ID_PORT, + + /**match based on pattern in first response from server or client in + * case of client_services. + */ + SERVICE_ID_PATTERN, + + /**match based on round-robin through tcpServiceList or UdpServiceList. RNA walks + * the list from first element to last. In a detector declares a flow incompatible + * or the flow closes earlier than expected by detector, then the next detector is + * tried. This can obviously delay detection under some scenarios. + */ + SERVICE_ID_BRUTE_FORCE, +}; + +#define DETECTOR_TYPE_PASSIVE 0 +#define DETECTOR_TYPE_DECODER 0 +#define DETECTOR_TYPE_NETFLOW 1 +#define DETECTOR_TYPE_PORT 2 +#define DETECTOR_TYPE_DERIVED 3 +#define DETECTOR_TYPE_CONFLICT 4 +#define DETECTOR_TYPE_PATTERN 5 + +// Service state saved in hosttracker, for identifying a service across multiple flow instances. +struct AppIdServiceIDState +{ + const RNAServiceElement* svc; + + /**State of service identification.*/ + SERVICE_ID_STATE state; + unsigned valid_count; + unsigned detract_count; + sfip_t last_detract; + + /**Number of consequetive flows that were declared incompatible by detectors. Incompatibility + * means client packet did not match. + */ + unsigned invalid_client_count; + + /**IP address of client in last flow that was declared incompatible. If client IP address is + * different everytime, then consequetive incompatible status indicate that flow is not using + * specific service. + */ + sfip_t last_invalid_client; + + /** Count for number of unknown sessions saved + */ + unsigned unknowns_logged; + time_t reset_time; + + /**List of ServiceMatch nodes which are sorted in order of pattern match. The list is contructed + * once on first packet from server and then used for subsequent flows. This saves repeat pattern + * matching, but has the disadvantage of making one flow match dependent on first instance of the + * same flow. + */ + ServiceMatch* serviceList; + ServiceMatch* currenService; + + /** Is this entry currently being used in an active session? */ + bool searching; +}; + +struct AppIdServiceStateKey4 +{ + uint16_t port; + IpProtocol proto; + uint32_t ip; + uint32_t level; +}; + +struct AppIdServiceStateKey6 +{ + uint16_t port; + IpProtocol proto; + uint8_t ip[16]; + uint32_t level; +}; + +union AppIdServiceStateKey +{ + AppIdServiceStateKey4 key4; + AppIdServiceStateKey6 key6; +}; + +int AppIdServiceStateInit(unsigned long memcap); +void AppIdServiceStateCleanup(); +void AppIdRemoveServiceIDState(sfip_t*, IpProtocol proto, uint16_t port, uint32_t level); +AppIdServiceIDState* AppIdGetServiceIDState( const sfip_t*, IpProtocol proto, uint16_t port, uint32_t level); +AppIdServiceIDState* AppIdAddServiceIDState( const sfip_t*, IpProtocol proto, uint16_t port, uint32_t level); +void AppIdServiceStateDumpStats(); + +#endif diff --git a/src/network_inspectors/appid/sfaddr_temp.h b/src/network_inspectors/appid/sfaddr_temp.h new file mode 100644 index 000000000..fa6bd08bd --- /dev/null +++ b/src/network_inspectors/appid/sfaddr_temp.h @@ -0,0 +1,38 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sfaddr_temp.h author Sourcefire Inc. + +#ifndef SFADDR_TEMP_H +#define SFADDR_TEMP_H + +#include "protocols/ipv6.h" + +#define WORKAROUND_UNTIL_SFIP_CHANGES_FROM_SNORT299_ARE_PORTED_TO_SNORT3 +#ifdef WORKAROUND_UNTIL_SFIP_CHANGES_FROM_SNORT299_ARE_PORTED_TO_SNORT3 + +#define sfaddr_get_ip4_value(x) (0) +#define sfaddr_get_ptr(x) (0) +#define sfip_fast_eq6(x,y) (0) +#define sfip_fast_equals_raw(x, y) (0) +#define sfaddr_family(x) ((x)->family) + +#endif + +#endif diff --git a/src/network_inspectors/appid/test/CMakeLists.txt b/src/network_inspectors/appid/test/CMakeLists.txt new file mode 100644 index 000000000..7a294db6d --- /dev/null +++ b/src/network_inspectors/appid/test/CMakeLists.txt @@ -0,0 +1,9 @@ +set ( + APPID_LIBS + appid +) + +add_cpputest(appid_simple_test appid) +#add_cpputest(host_cache_module_test host_tracker ${HOST_TRACKER_MODULE_LIBS} hash) +#add_cpputest(host_tracker_module_test host_tracker ${HOST_TRACKER_MODULE_LIBS}) +#add_cpputest(host_tracker_test host_tracker) diff --git a/src/network_inspectors/appid/test/Makefile.am b/src/network_inspectors/appid/test/Makefile.am new file mode 100644 index 000000000..ae1cfe391 --- /dev/null +++ b/src/network_inspectors/appid/test/Makefile.am @@ -0,0 +1,121 @@ + +AM_CPPFLAGS+=-I$(top_srcdir)/src/network_inspectors/appid +AM_DEFAULT_SOURCE_EXT = .cc + +check_PROGRAMS = \ +appid_simple_test \ +process_http_test + +TESTS = $(check_PROGRAMS) + +lib_list = \ +../../../network_inspectors/arp_spoof/libarp_spoof.a \ +../../../network_inspectors/packet_capture/libpacket_capture.a \ +../../../service_inspectors/back_orifice/libback_orifice.a \ +../../../service_inspectors/dce_rpc/libdce_rpc.a \ +../../../service_inspectors/dnp3/libdnp3.a \ +../../../service_inspectors/dns/libdns.a \ +../../../service_inspectors/ftp_telnet/libftp_telnet.a \ +../../../service_inspectors/gtp/libgtp_inspect.a \ +../../../service_inspectors/modbus/libmodbus.a \ +../../../service_inspectors/nhttp_inspect/libnhttp_inspect.a \ +../../../service_inspectors/rpc_decode/librpc_decode.a \ +../../../service_inspectors/sip/libsip.a \ +../../../service_inspectors/ssh/libssh.a \ +../../../service_inspectors/wizard/libwizard.a + +if STATIC_CODECS +codec_list = \ +../../../codecs/root/libroot_codecs.a \ +../../../codecs/link/liblink_codecs.a +endif + + +test_list = ../../../catch/libcatch_tests.a + +if ENABLE_PIGLET +pig_list = \ +piglet/libpiglet.a \ +piglet_plugins/libpiglet_plugins.a +endif + +# order libs to avoid undefined symbols +# from gnu linker +snort_LIBS = \ +$(test_list) \ +../../../managers/libmanagers.a \ +../../../loggers/libloggers.a \ +../../../codecs/libcodecs.a \ +../../../codecs/ip/libip_codecs.a \ +../../../codecs/misc/libmisc_codecs.a \ +$(codec_list) \ +../../../network_inspectors/libnetwork_inspectors.a \ +../../../network_inspectors/binder/libbinder.a \ +../../../network_inspectors/normalize/libnormalize.a \ +../../../network_inspectors/perf_monitor/libperf_monitor.a \ +../../../network_inspectors/reputation/libreputation.a \ +../../../network_inspectors/appid/libappid.a \ +../../../network_inspectors/appid/client_plugins/libclient_plugins.a \ +../../../network_inspectors/appid/detector_plugins/libdetector_plugins.a \ +../../../network_inspectors/appid/service_plugins/libservice_plugins.a \ +../../../network_inspectors/appid/util/libappidutil.a \ +../../../service_inspectors/libservice_inspectors.a \ +$(lib_list) \ +../../../service_inspectors/imap/libimap.a \ +../../../service_inspectors/pop/libpop.a \ +../../../service_inspectors/smtp/libsmtp.a \ +../../../service_inspectors/ssl/libssl.a \ +../../../network_inspectors/port_scan/libport_scan.a \ +../../../stream/libstream.a \ +../../../stream/base/libstream_base.a \ +../../../stream/ip/libstream_ip.a \ +../../../stream/icmp/libstream_icmp.a \ +../../../stream/tcp/libstream_tcp.a \ +../../../stream/libtcp/libstream_libtcp.a \ +../../../stream/udp/libstream_udp.a \ +../../../stream/user/libstream_user.a \ +../../../stream/file/libstream_file.a \ +../../../stream/libstream_paf.a \ +../../../file_api/libfile_api.a \ +../../../mime/libmime.a \ +../../../service_inspectors/http_inspect/libhttp_inspect.a \ +$(pig_list) \ +../../../ips_options/libips_options.a \ +../../../search_engines/libsearch_engines.a \ +../../../target_based/libtarget_based.a \ +../../../main/libmain.a \ +../../../codecs/libcodec_module.a \ +../../../memory/libmemory.a \ +../../../host_tracker/libhost_tracker.a \ +../../../parser/libparser.a \ +../../../flow/libflow.a \ +../../../control/libcontrol.a \ +../../../filters/libfilter.a \ +../../../detection/libdetection.a \ +../../../framework/libframework.a \ +../../../time/libtime.a \ +../../../latency/liblatency.a \ +../../../profiler/libprofiler.a \ +../../../actions/libips_actions.a \ +../../../events/libevents.a \ +../../../hash/libhash.a \ +../../../log/liblog.a \ +../../../packet_io/libpacket_io.a \ +../../../helpers/libhelpers.a \ +../../../lua/liblua.a \ +../../../decompress/libdecompress.a \ +../../../sfip/libsfip.a \ +../../../sfrt/libsfrt.a \ +../../../protocols/libprotocols.a \ +../../../connectors/libconnectors.a \ +../../../connectors/file_connector/libfile_connector.a \ +../../../side_channel/libside_channel.a \ +../../../ports/libports.a \ +../../../utils/libutils.a + +appid_simple_test_CPPFLAGS = $(AM_CPPFLAGS) @CPPUTEST_CPPFLAGS@ +appid_simple_test_LDADD = ${snort_LIBS} @CPPUTEST_LDFLAGS@ +process_http_test_CPPFLAGS = $(AM_CPPFLAGS) @CPPUTEST_CPPFLAGS@ +process_http_test_LDADD = ${snort_LIBS} @CPPUTEST_LDFLAGS@ + + diff --git a/src/network_inspectors/appid/test/appid_simple_test.cc b/src/network_inspectors/appid/test/appid_simple_test.cc new file mode 100644 index 000000000..9de2fa60f --- /dev/null +++ b/src/network_inspectors/appid/test/appid_simple_test.cc @@ -0,0 +1,44 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// appid_simple_test.cc author stechew + +// Make some API calls to demonstrate that we can link with appid libs. + +#include +#include "util/fw_avltree.h" +#include "protocols/protocol_ids.h" +#include "sfip/sfip_t.h" +#include "sfip/sf_ip.h" +#include "fw_appid.h" + +int main() +{ + IpProtocol proto=IpProtocol::TCP; + sfip_t* ip = nullptr; + SFIP_RET status; + + printf("Testing...\n"); + + ip = sfip_alloc("10.1.1.1", &status); + fwAvlInit(); + appSharedDataAlloc(proto, ip); + + return 0; +} + diff --git a/src/network_inspectors/appid/test/appid_tests.cc b/src/network_inspectors/appid/test/appid_tests.cc new file mode 100644 index 000000000..8822d4f69 --- /dev/null +++ b/src/network_inspectors/appid/test/appid_tests.cc @@ -0,0 +1,881 @@ +#include +#include +#include + +#include + +#include "parser/mstring.h" + +#include "appid_config.h" + +#include "external_apis.h" +#include "fw_appid.h" +#include "session_file.h" + +// FIXIT - this must go when snort2.9.x sf_ip.h changes are ported to snort++ +#include "sfaddr_temp.h" + +#if 1 // FIXIT-M hacks +// not sure where this is defined; outside the appid tree probably +using ControlDataSendFunc = void (*)(); +#endif + +extern void AppIdReload(struct _SnortConfig* sc, char* args, void** new_config); +extern void* AppIdReloadSwap(struct _SnortConfig* sc, void* swap_config); +extern void AppIdReloadFree(void* old_context); +extern int AppIdReconfigure(uint16_t type, const uint8_t* data, uint32_t length, + void** new_context, + char* statusBuf, int statusBuf_len); +extern int AppIdReconfigureSwap(uint16_t type, void* new_context, void** old_context); +extern void AppIdReconfigureFree(uint16_t type, void* old_context, struct _THREAD_ELEMENT* te, + ControlDataSendFunc f); + +extern int processHTTPPacket(Packet* p, AppIdData* session, int direction, + HttpParsedHeaders* const headers, const AppIdConfig* pConfig); +extern void appIdApiInit(struct AppIdApi*); +extern void sfiph_build(Packet* p, const void* hdr, int family); +extern void pickHttpXffAddress(Packet* p, AppIdData* appIdSession, + ThirdPartyAppIDAttributeData* attribute_data); + +AppIdApi appIdApi; + +// FIXIT: use APIs instead of using global +extern AppIdData* pAppIdData; + +static char* testFilesPath = nullptr; +static char rnaConfPath[PATH_MAX] = { 0 }; + +static void testProcessHttpPacket(const char* useragent, const char* host, const char* referer, + const char* trailer) +{ + // FIXIT-M J these need to be cleared, probably + Packet p; + AppIdData session; + + char buf[1024]; + int bufLen; + + session.common.flags = 0x311380; + session.hsession = (decltype(session.hsession))snort_calloc(sizeof(httpSession)); + if (host) + { + session.hsession->host = snort_strdup(host); + strcpy(buf, "http://"); + strcat(buf, host); + if (trailer) + strcat(buf, trailer); + else + strcat(buf, "/"); + session.hsession->url = snort_strdup(buf); + } + if (useragent) + session.hsession->useragent = snort_strdup(useragent); + if (referer) + session.hsession->referer = snort_strdup(referer); + session.hsession->uri = snort_strdup("/"); + session.hsession->cookie = snort_strdup( + "s_vi=[CS]v1|25B026B7851D124A-6000012D802520B2[CE]; CG=US:MD:Laurel; mbox=check#true#1336576860|session#1336576799559-724714#1336578660; SelectedEdition=www; rsi_segs_ttn=A09801_10001|A09801_10313; ug=" + "4faa8b240a5fef0aa5147448c8005347; ugs=1; tnr:usrvtstg01=1336576805411%7C0%7C0%7C1%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C1%7Cf%7C" + "1%7C4%7C1336576805411; tnr:sesctmp01=1336576805411; s_cc=true; s_sq=%5B%5BB%5D%5D; adDEmas=R08&broadband&gblx.net&73&gbr&826027&0&10198&-&-&-&15275&; adDEon=true; s_ppv=13"); + session.serviceAppId = APP_ID_NONE; + session.payloadAppId = APP_ID_NONE; + session.tpPayloadAppId = 1190; + session.scan_flags = 0x26; + session.ClientAppId = 0; + + strcpy(buf, "GET / HTTP/1.1\r\n"); + strcat(buf, "Host: "); + strcat(buf, host); + strcat(buf, "\r\nUser-Agent: "); + strcat(buf, useragent); + if (referer) + { + strcat(buf, "\r\nReferer: "); + strcat(buf, referer); + } + strcat(buf, "\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"); + strcat(buf, + "Accept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"); + strcat(buf, "Keep-Alive: 115\r\nConnection: keep-alive\r\n"); + bufLen = strlen(buf); + strncat(buf, + "Cookie: s_vi=[CS]v1|25B026B7851D124A-6000012D802520B2[CE]; CG=US:MD:Laurel; mbox=check#true#1336576860|session#1336576799559-724714#1336578660; SelectedEdition=www; rsi_segs_ttn=A09801_10001|A09801_10313; ug=4faa8b240a5fef0aa5147448c8005347; ugs=1; tnr:usrvtstg01=1336576805411%7C0%7C0%7C1%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C1%7Cf%7C1%7C4%7C1336576805411; tnr:sesctmp01=1336576805411; s_cc=true; s_sq=%5B%5BB%5D%5D; adDEmas=R08&broadband&gblx.net&73&gbr&826027&0&10198&-&-&-&15275&; adDEon=true; s_ppv=13\r\n\r\n", + sizeof(buf) - bufLen - 1); + + p.data = (decltype(p.data))snort_strdup(buf); + p.dsize = strlen((const char*)p.data); + + processHTTPPacket(&p, &session, APP_ID_FROM_INITIATOR, nullptr, pAppidActiveConfig); + + if (host) + { + snort_free(session.hsession->host); + snort_free(session.hsession->url); + } + if (referer) + snort_free(session.hsession->referer); + if (useragent) + snort_free(session.hsession->useragent); + snort_free(session.hsession->uri); + snort_free(session.hsession->cookie); + snort_free(session.hsession); + + snort_free((uint8_t*)p.data); +} + +void testFwAppIdSearch(const char* fileName) +{ + Packet pkt; + Flow flow; + + FILE* file; + HttpParsedHeaders* httpHeader = nullptr; + AppId service; + bool isLoginSuccessful; + char* userName; + char* serviceVendor; + char* serviceVersion; + RNAServiceSubtype* serviceSubtype; + int moreData = 0; + char filePath[PATH_MAX]; + + strcpy(filePath, testFilesPath); + strcat(filePath, "/sessions/"); + strcat(filePath, fileName); + + file = fopen(filePath, "r"); + assert(file != nullptr); + + do + { + sessionFileReadSession(file, &flow); + moreData = sessionFileReadPacket(file, &pkt, &httpHeader); + + pkt.flow = &flow; + + sfiph_build(&pkt, &pkt.ip4h, pkt.family); + + if (httpHeader) + { + httpHeaderCallback(&pkt, httpHeader); + } + else + { + fwAppIdSearch(&pkt); + } + + if (pkt.data) + snort_free((uint8_t*)pkt.data); + memset(&pkt, 0, sizeof(pkt)); + + if (httpHeader) + { + if (httpHeader->host.start) + snort_free((uint8_t*)httpHeader->host.start); + if (httpHeader->url.start) + snort_free((uint8_t*)httpHeader->url.start); + if (httpHeader->method.start) + snort_free((uint8_t*)httpHeader->method.start); + if (httpHeader->userAgent.start) + snort_free((uint8_t*)httpHeader->userAgent.start); + if (httpHeader->referer.start) + snort_free((uint8_t*)httpHeader->referer.start); + if (httpHeader->via.start) + snort_free((uint8_t*)httpHeader->via.start); + if (httpHeader->responseCode.start) + snort_free((uint8_t*)httpHeader->responseCode.start); + if (httpHeader->server.start) + snort_free((uint8_t*)httpHeader->server.start); + if (httpHeader->xWorkingWith.start) + snort_free((uint8_t*)httpHeader->xWorkingWith.start); + if (httpHeader->contentType.start) + snort_free((uint8_t*)httpHeader->contentType.start); + snort_free(httpHeader); + httpHeader = nullptr; + } + } + while (moreData != -1); + + LogMessage("==========================================================\n"); + LogMessage("App name = %s\n", appGeAppName(appIdApi.geServiceAppId(pAppIdData))); + LogMessage("AppId = %d\n", appGeAppId(appGeAppName(appIdApi.geServiceAppId(pAppIdData)))); + LogMessage("Service AppId = %d\n", appIdApi.geServiceAppId(pAppIdData)); + LogMessage("Only Service AppId = %d\n", appIdApi.getOnlyServiceAppId(pAppIdData)); + LogMessage("Misc AppId = %d\n", appIdApi.getMiscAppId(pAppIdData)); + LogMessage("Client AppId = %d\n", appIdApi.getClientAppId(pAppIdData)); + LogMessage("Payload AppId = %d\n", appIdApi.getPayloadAppId(pAppIdData)); + LogMessage("Referred AppId = %d\n", appIdApi.getReferredAppId(pAppIdData)); + LogMessage("Fw Service AppId = %d\n", appIdApi.getFwServiceAppId(pAppIdData)); + LogMessage("Fw Misc AppId = %d\n", appIdApi.getFwMiscAppId(pAppIdData)); + LogMessage("Fw Client AppId = %d\n", appIdApi.getFwClientAppId(pAppIdData)); + LogMessage("Fw Payload AppId = %d\n", appIdApi.getFwPayloadAppId(pAppIdData)); + LogMessage("Fw Referred AppId = %d\n", appIdApi.getFwReferredAppId(pAppIdData)); + LogMessage("Is Session SSL Decrypted = %d\n", appIdApi.isSessionSslDecrypted(pAppIdData)); + LogMessage("Is AppId Inspecting Session = %d\n", appIdApi.isAppIdInspectingSession( + pAppIdData)); + LogMessage("Is AppId Available = %d\n", appIdApi.isAppIdAvailable(pAppIdData)); + userName = appIdApi.getUserName(pAppIdData, &service, &isLoginSuccessful); + LogMessage("User name = %s, service = %d, isLoginSuccessful = %d\n", + userName, service, isLoginSuccessful); + LogMessage("Client version = %s\n", appIdApi.geClientVersion(pAppIdData)); + // TODO: Is the flag argument correct? + LogMessage("Session attribute = %" PRIx64 "\n", appIdApi.getAppIdSessionAttribute(pAppIdData, + 0)); + LogMessage("Flow type = %08X\n", appIdApi.getFlowType(pAppIdData)); + appIdApi.geServiceInfo(pAppIdData, &serviceVendor, &serviceVersion, &serviceSubtype); + LogMessage("Service vendor = %s, version = %s\n", + serviceVendor, serviceVersion); + LogMessage("Service port = %d\n", appIdApi.geServicePort(pAppIdData)); + LogMessage("Service IP = %s\n", inet_ntoa(appIdApi.geServiceIp(pAppIdData))); + LogMessage("HTTP user agent = %s\n", appIdApi.getHttpUserAgent(pAppIdData)); + LogMessage("HTTP host = %s\n", appIdApi.getHttpHost(pAppIdData)); + LogMessage("HTTP URL = %s\n", appIdApi.getHttpUrl(pAppIdData)); + LogMessage("HTTP referer = %s\n", appIdApi.getHttpReferer(pAppIdData)); + LogMessage("TLS host = %s\n", appIdApi.getTlsHost(pAppIdData)); + LogMessage("NetBIOS name = %s\n", appIdApi.getNetbiosName(pAppIdData)); + + fclose(file); +} + +START_TEST(ConfigParseTest) +{ + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + appIdConfigParse( + "conf rna.conf, debug yes, dump_ports, memcap 0, app_stats_filename stats, app_stats_period 60, app_stats_rollover_size 100000, app_stats_rollover_time 60, app_detector_dir appid, instance_id 1, thirdparty_appid_dir thirdparty_appid"); + + ck_assert_str_eq(appidStaticConfig.conf_file, "rna.conf"); + ck_assert_int_eq(appidStaticConfig.app_id_debug, 1); + ck_assert_int_eq(appidStaticConfig.app_id_dump_ports, 1); + ck_assert_uint_eq(appidStaticConfig.memcap, (32*1024*1024ULL)); + ck_assert_str_eq(appidStaticConfig.app_stats_filename, "stats"); + ck_assert_uint_eq(appidStaticConfig.app_stats_period, 60); + ck_assert_uint_eq(appidStaticConfig.app_stats_rollover_size, 100000); + ck_assert_uint_eq(appidStaticConfig.app_stats_rollover_time, 60); + ck_assert_str_eq(appidStaticConfig.app_id_detector_path, "appid"); + ck_assert_uint_eq(appidStaticConfig.instance_id, 1); + ck_assert_str_eq(appidStaticConfig.appid_thirdparty_dir, "thirdparty_appid"); +} +END_TEST START_TEST(InitFiniTest) +{ + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + AppIdCommonFini(); +} + +END_TEST START_TEST(ReloadTest) +{ + AppIdConfig* pNewConfig = nullptr; + AppIdConfig* pOldConfig = nullptr; + + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + AppIdReload(nullptr, nullptr, (void**)&pNewConfig); + pOldConfig = AppIdReloadSwap(nullptr, pNewConfig); + AppIdReloadFree(pOldConfig); + + AppIdCommonFini(); +} + +END_TEST START_TEST(ReconfigureTest) +{ + AppIdConfig* pNewConfig = nullptr; + AppIdConfig* pOldConfig = nullptr; + + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + AppIdReconfigure(0, nullptr, 0, (void**)&pNewConfig, nullptr, 0); + AppIdReconfigureSwap(0, pNewConfig, (void**)&pOldConfig); + AppIdReconfigureFree(0, pOldConfig, nullptr, nullptr); + + AppIdCommonFini(); +} + +END_TEST START_TEST(HttpTest) +{ + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + testProcessHttpPacket( + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100715 Ubuntu/9.04 (jaunty) Firefox/3.6.7", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/600.1.17 (KHTML, like Gecko) Version/7.1 Safari/537.85.10", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", + "www.cnn.com", + nullptr, nullptr); + + AppIdCommonFini(); +} + +END_TEST START_TEST(HttpAfterReloadTest) +{ + AppIdConfig* pNewConfig = nullptr; + AppIdConfig* pOldConfig = nullptr; + + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + AppIdReload(nullptr, nullptr, (void**)&pNewConfig); + pOldConfig = AppIdReloadSwap(nullptr, pNewConfig); + AppIdReloadFree(pOldConfig); + + testProcessHttpPacket( + "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36", + "www.cnn.com", + nullptr, nullptr); + + AppIdCommonFini(); +} + +END_TEST START_TEST(HttpAfterReconfigureTest) +{ + AppIdConfig* pNewConfig = nullptr; + AppIdConfig* pOldConfig = nullptr; + + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + AppIdReconfigure(0, nullptr, 0, (void**)&pNewConfig, nullptr, 0); + AppIdReconfigureSwap(0, pNewConfig, (void**)&pOldConfig); + AppIdReconfigureFree(0, pOldConfig, nullptr, nullptr); + + testProcessHttpPacket("Wget/1.9.1", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "www.cnn.com", + nullptr, nullptr); + + AppIdCommonFini(); +} + +END_TEST START_TEST(HttpAfterReloadReconfigureTest) +{ + AppIdConfig* pNewConfig = nullptr; + AppIdConfig* pOldConfig = nullptr; + + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + AppIdReload(nullptr, nullptr, (void**)&pNewConfig); + pOldConfig = AppIdReloadSwap(nullptr, pNewConfig); + AppIdReloadFree(pOldConfig); + + pNewConfig = nullptr; + pOldConfig = nullptr; + + AppIdReconfigure(0, nullptr, 0, (void**)&pNewConfig, nullptr, 0); + AppIdReconfigureSwap(0, pNewConfig, (void**)&pOldConfig); + AppIdReconfigureFree(0, pOldConfig, nullptr, nullptr); + + testProcessHttpPacket( + "Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "www.123.com", + "http://www.cnn.com", nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100715 Ubuntu/9.04 (jaunty) Firefox/3.6.7", + "www.cnn.com", + nullptr, "/tech?a=1&b=2"); + + AppIdCommonFini(); +} + +END_TEST START_TEST(HttpXffTest) +{ + Packet p = { 0 }; + AppIdData session = { 0 }; + httpSession hsession = { 0 }; + ThirdPartyAppIDAttributeData tpData = { 0 }; + SFIP_RET status; + sfaddr_t* xffAddr = sfaddr_alloc("1.1.1.1", &status); + + // Only X-Forwarded-For + session.hsession = &hsession; + tpData.numXffFields = 1; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_X_FORWARDED_FOR; + tpData.xffFieldValue[0].value = "1.1.1.1"; + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + sfaddr_free(session.hsession->xffAddr); + + // Only True-Client-IP + memset(&p, 0, sizeof(p)); + memset(&session, 0, sizeof(session)); + memset(&hsession, 0, sizeof(hsession)); + memset(&tpData, 0, sizeof(tpData)); + session.hsession = &hsession; + tpData.numXffFields = 1; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_TRUE_CLIENT_IP; + tpData.xffFieldValue[0].value = "1.1.1.1"; + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + sfaddr_free(session.hsession->xffAddr); + + // X-Forwarded-For and True-Client-IP + memset(&p, 0, sizeof(p)); + memset(&session, 0, sizeof(session)); + memset(&hsession, 0, sizeof(hsession)); + memset(&tpData, 0, sizeof(tpData)); + session.hsession = &hsession; + tpData.numXffFields = 2; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_TRUE_CLIENT_IP; + tpData.xffFieldValue[0].value = "2.2.2.2"; + tpData.xffFieldValue[1].field = HTTP_XFF_FIELD_X_FORWARDED_FOR; + tpData.xffFieldValue[1].value = "1.1.1.1"; + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + sfaddr_free(session.hsession->xffAddr); + + // Comma-separated list in X-Forwarded-For + memset(&p, 0, sizeof(p)); + memset(&session, 0, sizeof(session)); + memset(&hsession, 0, sizeof(hsession)); + memset(&tpData, 0, sizeof(tpData)); + session.hsession = &hsession; + tpData.numXffFields = 1; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_X_FORWARDED_FOR; + tpData.xffFieldValue[0].value = snort_strdup("1.1.1.1, 2.2.2.2"); + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + snort_free(tpData.xffFieldValue[0].value); + sfaddr_free(session.hsession->xffAddr); + + // Custom XFF + static char* defaultXffPrecedence[] = { "Custom-XFF", HTTP_XFF_FIELD_X_FORWARDED_FOR, + HTTP_XFF_FIELD_TRUE_CLIENT_IP }; + memset(&p, 0, sizeof(p)); + memset(&session, 0, sizeof(session)); + memset(&hsession, 0, sizeof(hsession)); + memset(&tpData, 0, sizeof(tpData)); + session.hsession = &hsession; + session.hsession->xffPrecedence = defaultXffPrecedence; + session.hsession->numXffFields = 3; + tpData.numXffFields = 2; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_X_FORWARDED_FOR; + tpData.xffFieldValue[0].value = "2.2.2.2"; + tpData.xffFieldValue[1].field = "Custom-XFF"; + tpData.xffFieldValue[1].value = "1.1.1.1"; + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + sfaddr_free(session.hsession->xffAddr); + + sfaddr_free(xffAddr); + + snort_free((uint8_t*)p.data); +} + +END_TEST START_TEST(AimSessionTest) +{ + testFwAppIdSearch("aim.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "AOL Instant Messenger"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_AOL_INSTANT_MESSENGER); + ck_assert_uint_eq(appIdApi.getClientAppId(pAppIdData), APP_ID_AOL_INSTANT_MESSENGER); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(CnnSessionTest) +{ + testFwAppIdSearch("cnn.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "HTTP"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_HTTP); + ck_assert_uint_eq(appIdApi.getClientAppId(pAppIdData), APP_ID_FIREFOX); + ck_assert_uint_eq(appIdApi.getPayloadAppId(pAppIdData), 1190); // CNN app + ck_assert_str_eq(appIdApi.getHttpHost(pAppIdData), "www.cnn.com"); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(DnsSessionTest) +{ + testFwAppIdSearch("dns.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "DNS"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_DNS); + ck_assert_uint_eq(appIdApi.getClientAppId(pAppIdData), APP_ID_DNS); + ck_assert_uint_eq(appIdApi.geServicePort(pAppIdData), 53); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(ImapSessionTest) +{ + testFwAppIdSearch("imap.ssn"); + + // TODO: Investigate why IMAP appids are not showing up + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(MdnsSessionTest) +{ + testFwAppIdSearch("mdns.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "MDNS"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_MDNS); + ck_assert_uint_eq(appIdApi.geServicePort(pAppIdData), 5353); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(MsnSessionTest) +{ + testFwAppIdSearch("msn.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "MSN Messenger"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_MSN_MESSENGER); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(NetbiosNsSessionTest) +{ + testFwAppIdSearch("netbios_ns.ssn"); + + // TODO: Investigate why NetBIOS name service appids are not showing up + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(NetbiosSsSessionTest) +{ + testFwAppIdSearch("netbios_ss.ssn"); + + // TODO: Investigate why NetBIOS ss appids are not showing up + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(PatternSessionTest) +{ + testFwAppIdSearch("pattern.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "3Com AMP3"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), 3000); + ck_assert_uint_eq(appIdApi.getClientAppId(pAppIdData), 3000); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(Pop3SessionTest) +{ + testFwAppIdSearch("pop3.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "POP3"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_POP3); + ck_assert_uint_eq(appIdApi.geServicePort(pAppIdData), 110); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(RfbSessionTest) +{ + testFwAppIdSearch("rfb.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "RFB"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_VNC_RFB); + ck_assert_uint_eq(appIdApi.getClientAppId(pAppIdData), APP_ID_VNC); + ck_assert_uint_eq(appIdApi.geServicePort(pAppIdData), 5900); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(RtpSessionTest) +{ + testFwAppIdSearch("rtp.ssn"); + + // TODO: Investigate why RTP appids are not showing up + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(SmtpSessionTest) +{ + testFwAppIdSearch("smtp.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "SMTP"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_SMTP); + ck_assert_uint_eq(appIdApi.geServicePort(pAppIdData), 25); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(TimbuktuSessionTest) +{ + testFwAppIdSearch("timbuktu.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "Timbuktu"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_TIMBUKTU); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_TIMBUKTU); + ck_assert_uint_eq(appIdApi.geServicePort(pAppIdData), 407); + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(WebexSessionTest) +{ + testFwAppIdSearch("webex.ssn"); + + ck_assert_str_eq(appGeAppName(appIdApi.geServiceAppId(pAppIdData)), "HTTP"); + ck_assert_uint_eq(appIdApi.geServiceAppId(pAppIdData), APP_ID_HTTP); + ck_assert_uint_eq(appIdApi.getClientAppId(pAppIdData), 2932); // WebEx + ck_assert_uint_eq(appIdApi.getPayloadAppId(pAppIdData), 2932); // WebEx + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST START_TEST(YmSessionTest) +{ + testFwAppIdSearch("ym.ssn"); + + // TODO: Investigate why Yahoo messenger appids are not showing up + + appSharedDataDelete(pAppIdData); + pAppIdData = nullptr; +} + +END_TEST + +static void appIdTestSetup(void) +{ + static SessionAPI sessionAPI = { 0 }; + static StreamAPI streamAPI = { 0 }; + + testFilesPath = getenv("APPID_TESTS_PATH"); + + if (testFilesPath == nullptr) + { + printf("Env variable APPID_TESTS_PATH is not set. Exiting ...\n"); + exit(-1); + } + + strcpy(rnaConfPath, testFilesPath); + strcat(rnaConfPath, "/rna.conf"); + + _dpd.tokenSplit = mSplit; + _dpd.tokenFree = mSplitFree; + LogMessage = logMsg; + _dpd.errMsg = errMsg; + _dpd.debugMsg = debugMsg; + _dpd.addProtocolReference = addProtocolReference; + _dpd.addPreproc = addPreproc; + _dpd.getParserPolicy = getParserPolicy; + _dpd.getDefaultPolicy = getDefaultPolicy; + _dpd.isAppIdRequired = isAppIdRequired; + _dpd.getSnortInstance = getSnortInstance; + _dpd.findProtocolReference = findProtocolReference; + + sessionAPI.enable_preproc_all_ports = enable_preproc_all_ports; + sessionAPI.get_application_data = get_application_data; + sessionAPI.set_application_data = set_application_data; + sessionAPI.get_packet_direction = get_packet_direction; + sessionAPI.get_session_flags = get_session_flags; + sessionAPI.get_session_ip_address = get_session_ip_address; + sessionAPI.get_application_protocol_id = get_application_protocol_id; + sessionAPI.get_http_xff_precedence = get_http_xff_precedence; + _dpd.sessionAPI = &sessionAPI; + + streamAPI.is_session_decrypted = is_session_decrypted; + streamAPI.set_application_id = set_application_id; + streamAPI.is_session_http2 = is_session_http2; + _dpd.streamAPI = &streamAPI; + + _dpd.searchAPI = &searchAPI; + + appIdApiInit(&appIdApi); +} + +static void sessionTcaseSetup(void) +{ + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); +} + +static void sessionTcaseClean(void) +{ + AppIdCommonFini(); +} + +static Suite* setupAppIdSuite(void) +{ + Suite* appIdSuite; + TCase* frameworkTcase; + TCase* httpTcase; + TCase* sessionTcase; + + appIdSuite = suite_create("AppId"); + + // Create Framework test case + frameworkTcase = tcase_create("FrameworkTestCase"); + tcase_add_checked_fixture(frameworkTcase, nullptr, nullptr); + + // Add tests to Framework test case + tcase_add_test(frameworkTcase, ConfigParseTest); + tcase_add_test(frameworkTcase, InitFiniTest); + tcase_add_test(frameworkTcase, ReloadTest); + tcase_add_test(frameworkTcase, ReconfigureTest); + + suite_add_tcase(appIdSuite, frameworkTcase); + + // Create Http test case + httpTcase = tcase_create("HttpTestCase"); + tcase_add_checked_fixture(httpTcase, nullptr, nullptr); + + // Add tests to Http test case + tcase_add_test(httpTcase, HttpTest); + tcase_add_test(httpTcase, HttpAfterReloadTest); + tcase_add_test(httpTcase, HttpAfterReconfigureTest); + tcase_add_test(httpTcase, HttpAfterReloadReconfigureTest); + tcase_add_test(httpTcase, HttpXffTest); + + suite_add_tcase(appIdSuite, httpTcase); + + // Create Session test case + sessionTcase = tcase_create("SessionTestCase"); + tcase_add_checked_fixture(sessionTcase, nullptr, nullptr); + + // Add tests to Session test case + tcase_add_test(sessionTcase, AimSessionTest); + tcase_add_test(sessionTcase, CnnSessionTest); + tcase_add_test(sessionTcase, DnsSessionTest); + tcase_add_test(sessionTcase, ImapSessionTest); + tcase_add_test(sessionTcase, MdnsSessionTest); + tcase_add_test(sessionTcase, MsnSessionTest); + tcase_add_test(sessionTcase, NetbiosNsSessionTest); + tcase_add_test(sessionTcase, NetbiosSsSessionTest); + tcase_add_test(sessionTcase, PatternSessionTest); + tcase_add_test(sessionTcase, Pop3SessionTest); + tcase_add_test(sessionTcase, RfbSessionTest); + tcase_add_test(sessionTcase, RtpSessionTest); + tcase_add_test(sessionTcase, SmtpSessionTest); + tcase_add_test(sessionTcase, TimbuktuSessionTest); + tcase_add_test(sessionTcase, WebexSessionTest); + tcase_add_test(sessionTcase, YmSessionTest); + + suite_add_tcase(appIdSuite, sessionTcase); + + return appIdSuite; +} + +int main(int argc, char* argv[]) +{ + int opt, debug = 0; + int numberFailed; + Suite* appIdSuite; + SRunner* appIdRunner; + + while ((opt = getopt(argc, argv, "dh")) != -1) + { + switch (opt) + { + case 'd': + debug = 1; + break; + case 'h': + printf( + "Usage:\n\ + -d: Run test in no fork mode for debugging in gdb.\n\ + -h: This text.\n"); + return EXIT_SUCCESS; + } + } + + appIdTestSetup(); + + // Create a test runner for AppId suite + appIdSuite = setupAppIdSuite(); + appIdRunner = srunner_create(appIdSuite); + + if (debug) + { + srunner_set_fork_status(appIdRunner, CK_NOFORK); + } + + // Set test results format to TAP and specify file name + srunner_set_tap(appIdRunner, "AppIdTests.tap~"); + + // Run test cases in AppId suite + srunner_run(appIdRunner, nullptr, "FrameworkTestCase", CK_NORMAL); + + system("cp AppIdTests.tap~ AppIdTests.tap"); + + srunner_run(appIdRunner, nullptr, "HttpTestCase", CK_NORMAL); + + system("cat AppIdTests.tap~ >> AppIdTests.tap"); + + sessionTcaseSetup(); + srunner_run(appIdRunner, nullptr, "SessionTestCase", CK_NORMAL); + sessionTcaseClean(); + + system("cat AppIdTests.tap~ >> AppIdTests.tap"); + + numberFailed = srunner_ntests_failed(appIdRunner); + srunner_free(appIdRunner); + return (numberFailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} + diff --git a/src/network_inspectors/appid/test/external_apis.cc b/src/network_inspectors/appid/test/external_apis.cc new file mode 100644 index 000000000..9a82c4f8b --- /dev/null +++ b/src/network_inspectors/appid/test/external_apis.cc @@ -0,0 +1,249 @@ +//#include +#include "external_apis.h" +#include "session_file.h" +#include "appid_flow_data.h" + +#include "protocols/packet.h" +#include "protocols/tcp.h" +#include "protocols/udp.h" + +AppIdData* pAppIdData = nullptr; + +/*********************************************************** + * Local functions + **********************************************************/ +static void determinePacketDirection(Packet* p, uint16_t p_port, uint16_t scb_port, int is_sport) +{ + if (is_sport) + p->packet_flags |= (p_port == scb_port) ? PKT_FROM_CLIENT : PKT_FROM_SERVER; + else + p->packet_flags |= (p_port == scb_port) ? PKT_FROM_SERVER : PKT_FROM_CLIENT; +} + +static void setPacketDirectionFlag(Packet* p, SessionControlBlock* session) +{ + if (p->is_ip4()) + { + if (sfip_fast_eq4(p->ptrs.ip_api.get_src(), &session->client_ip)) + { + if (p->is_tcp()) + determinePacketDirection(p, p->ptrs.tcph->src_port(), session->client_port, true); + else if (p->is_udp()) + determinePacketDirection(p, p->ptrs.udph->src_port(), session->client_port, true); + else + p->packet_flags |= PKT_FROM_CLIENT; + } + else if (sfip_fast_eq4(p->ptrs.ip_api.get_dst(), &session->client_ip)) + { + if (p->is_tcp()) + determinePacketDirection(p, p->ptrs.tcph->dst_port(), session->client_port, false); + else if (p->is_udp()) + determinePacketDirection(p, p->ptrs.udph->dst_port(), session->client_port, false); + else + p->packet_flags |= PKT_FROM_SERVER; + } + } + else + { + if (sfip_fast_eq6(p->ptrs.ip_api.get_src(), &session->client_ip)) + { + if (p->is_tcp()) + determinePacketDirection(p, p->ptrs.tcph->src_port(), session->client_port, true); + else if (p->is_udp()) + determinePacketDirection(p, p->ptrs.udph->src_port(), session->client_port, true); + else + p->packet_flags |= PKT_FROM_CLIENT; + } + else if (sfip_fast_eq6(p->ptrs.ip_api.get_dst(), &session->client_ip)) + { + if (p->is_tcp()) + determinePacketDirection(p, p->ptrs.tcph->dst_port(), session->client_port, false); + else if (p->is_udp()) + determinePacketDirection(p, p->ptrs.udph->dst_port(), session->client_port, false); + else + p->packet_flags |= PKT_FROM_SERVER; + } + } +} + +NORETURN void FatalError(const char* format,...) +{ + va_list arg; + + printf("FATAL ERROR: "); + + va_start (arg, format); + vfprintf (stdout, format, arg); + va_end (arg); + fflush(stdout); + + exit(-1); +} + +void LogMessage(const char* format,...) +{ + va_list arg; + + printf("LOG MESSAGE: "); + + va_start (arg, format); + vfprintf (stdout, format, arg); + va_end (arg); + fflush(stdout); +} + +/*********************************************************** + * _dpd APIs + **********************************************************/ +void logMsg(const char* format, ...) +{ + va_list arg; + + printf("LOG: "); + + va_start (arg, format); + vfprintf (stdout, format, arg); + va_end (arg); + fflush(stdout); +} + +void errMsg(const char* format, ...) +{ + va_list arg; + + printf("ERROR: "); + + va_start (arg, format); + vfprintf (stdout, format, arg); + va_end (arg); + fflush(stdout); +} + +void debugMsg(uint64_t, const char* format, ...) +{ + va_list arg; + + printf("DEBUG: "); + + va_start (arg, format); + vfprintf (stdout, format, arg); + va_end (arg); + fflush(stdout); +} + +int16_t addProtocolReference(const char*) +{ + return 0; +} + +void* addPreproc(struct _SnortConfig*, void (*)(void*, void*), uint16_t, uint32_t, uint32_t) +{ + return nullptr; +} + +tSfPolicyId getParserPolicy(struct _SnortConfig*) +{ + return 0; +} + +tSfPolicyId getDefaultPolicy(void) +{ + return 1; +} + +bool isAppIdRequired(void) +{ + return false; +} + +uint32_t getSnortInstance(void) +{ + return 0; +} + +int16_t findProtocolReference(const char*) +{ + return 0; +} + +/*********************************************************** + * Session APIs + **********************************************************/ +void enable_preproc_all_ports(struct _SnortConfig*, uint32_t, uint32_t) +{ +} + +void* get_application_data(void*, uint32_t) +{ + return pAppIdData; +} + +int set_application_data(void*, uint32_t, AppIdData* data, StreamAppDataFree) +{ + pAppIdData = data; + + return 0; +} + +uint32_t get_packet_direction(Packet* p) +{ + if ((p == nullptr) || (p->flow == nullptr)) + return 0; + + setPacketDirectionFlag(p, p->flow); + + return (p->packet_flags & (PKT_FROM_SERVER | PKT_FROM_CLIENT)); +} + +uint32_t get_session_flags(void* ssnptr) +{ + SessionControlBlock* scb = (SessionControlBlock*)ssnptr; + return scb->ha_state.session_flags; +} + +sfaddr_t* get_session_ip_address(void* scbptr, uint32_t direction) +{ + SessionControlBlock* scb = (SessionControlBlock*)scbptr; + + if (scb != nullptr) + { + switch (direction) + { + case SSN_DIR_FROM_SERVER: + return (sfaddr_t*)(&(scb)->server_ip); + + case SSN_DIR_FROM_CLIENT: + return (sfaddr_t*)(&(scb)->client_ip); + + default: + break; + } + } + + return nullptr; +} + +bool is_session_decrypted(void*) +{ + return true; +} + +void set_application_id(void*, int16_t, int16_t, int16_t, int16_t) +{ +} + +bool is_session_http2(void*) +{ + return false; +} + +int16_t get_application_protocol_id(void*) +{ + return 0; +} + +char** get_http_xff_precedence(void*, uint32_t, int*) +{ + return nullptr; +} + diff --git a/src/network_inspectors/appid/test/external_apis.h b/src/network_inspectors/appid/test/external_apis.h new file mode 100644 index 000000000..333651170 --- /dev/null +++ b/src/network_inspectors/appid/test/external_apis.h @@ -0,0 +1,72 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// external_apis.h author Sourcefire Inc. + +#ifndef EXTERNAL_APIS_H +#define EXTERNAL_APIS_H + +#include + +#include "flow/flow.h" + +struct sfaddr_t; +struct SnortConfig; +struct Packet; +using tSfPolicyId = int; + +#define PKT_FROM_SERVER 0x00000040 /* this packet came from the server + side of a connection (TCP) */ +#define PKT_FROM_CLIENT 0x00000080 /* this packet came from the client + side of a connection (TCP) */ +// _dpd APIs +void logMsg(const char*, ...); +void errMsg(const char*, ...); +void debugMsg(uint64_t type, const char*, ...); +int16_t addProtocolReference(const char* protocol); + +void* addPreproc( + SnortConfig*, void (* pp_func)(void*, void*), uint16_t priority, + uint32_t appId, uint32_t flags); + +tSfPolicyId getParserPolicy(SnortConfig*); +tSfPolicyId getDefaultPolicy(); +bool isAppIdRequired(); +uint32_t getSnortInstance(); +int16_t findProtocolReference(const char* app); + +// Session APIs +void enable_preproc_all_ports(SnortConfig*, uint32_t appId, uint32_t flags); +void* get_application_data(void* stream_session, uint32_t protocol); +int set_application_data(void* scbptr, uint32_t protocol, void* data, StreamAppDataFree); +uint32_t get_packet_direction(Packet*); +uint32_t get_session_flags(void* ssnptr); +sfaddr_t* get_session_ip_address(void* scbptr, uint32_t direction); +int16_t get_application_protocol_id(void* scbptr); +char** get_http_xff_precedence(void* ssn, uint32_t flags, int* nFields); + +// Stream APIs +bool is_session_decrypted(void* stream_session); +void set_application_id( + void* ssnptr, int16_t serviceAppid, int16_t ClientAppid, + int16_t payloadAppId, int16_t miscAppid); + +bool is_session_http2(void* ssn); + +#endif diff --git a/src/network_inspectors/appid/test/mpse.cc b/src/network_inspectors/appid/test/mpse.cc new file mode 100644 index 000000000..16f99bcd5 --- /dev/null +++ b/src/network_inspectors/appid/test/mpse.cc @@ -0,0 +1,761 @@ +/* +* $Id: mpse.c,v 1.2 2015/03/25 14:45:18 andrbake Exp $ +* +* mpse.c +* +* An abstracted interface to the Multi-Pattern Matching routines, +* thats why we're passing 'void *' objects around. +* +* Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. +* Copyright (C) 2002-2013 Sourcefire, Inc. +* Marc A Norton +* +* Updates: +* 3/06 - Added AC_BNFA search +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License Version 2 as +** published by the Free Software Foundation. You may not use, modify or +** distribute this program under any other version of the GNU General +** Public License. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software +** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "bitop.h" +#include "bnfa_search.h" +#include "acsmx.h" +#include "acsmx2.h" +#include "sfksearch.h" +#include "mpse.h" +#include "snort_debug.h" +#include "sf_types.h" +#include "util.h" + +#ifdef DYNAMIC_PREPROC_CONTEXT +#include "sf_dynamic_preprocessor.h" +#endif //DYNAMIC_PREPROC_CONTEXT + +#ifdef INTEL_SOFT_CPM +#include "intel-soft-cpm.h" +#endif +#include "profiler.h" +#ifndef DYNAMIC_PREPROC_CONTEXT +#include "snort.h" +#endif +#ifdef PERF_PROFILING +PreprocStats mpsePerfStats; +#endif + +static uint64_t s_bcnt=0; + +typedef struct _mpse_struct +{ + int method; + void* obj; + int verbose; + uint64_t bcnt; + char inc_global_counter; +} MPSE; + +void* mpseNew(int method, int use_global_counter_flag, + void (* userfree)(void* p), + void (* optiontreefree)(void** p), + void (* neg_list_free)(void** p)) +{ + MPSE* p; + + p = (MPSE*)snort_calloc(sizeof(MPSE) ); + p->method = method; + p->verbose = 0; + p->obj = nullptr; + p->bcnt = 0; + p->inc_global_counter = (char)use_global_counter_flag; + + switch ( method ) + { + case MPSE_AC_BNFA: + p->obj=bnfaNew(userfree, optiontreefree, neg_list_free); + if (p->obj) + ((bnfa_struct_t*)(p->obj))->bnfaMethod = 1; + break; + case MPSE_AC_BNFA_Q: + p->obj=bnfaNew(userfree, optiontreefree, neg_list_free); + if (p->obj) + ((bnfa_struct_t*)(p->obj))->bnfaMethod = 0; + break; + case MPSE_AC: + p->obj = acsmNew(userfree, optiontreefree, neg_list_free); + break; + case MPSE_ACF: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_FULL); + break; + case MPSE_ACF_Q: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_FULLQ); + break; + case MPSE_ACS: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_SPARSE); + break; + case MPSE_ACB: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_BANDED); + break; + case MPSE_ACSB: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_SPARSEBANDS); + break; + case MPSE_LOWMEM: + p->obj = KTrieNew(0,userfree, optiontreefree, neg_list_free); + break; + case MPSE_LOWMEM_Q: + p->obj = KTrieNew(1,userfree, optiontreefree, neg_list_free); + break; + default: + /* p is free'd below if no case */ + break; + } + + if ( !p->obj ) + { + snort_free(p); + p = nullptr; + } + + return (void*)p; +} + +#ifndef DYNAMIC_PREPROC_CONTEXT +void* mpseNewWithSnortConfig(struct _SnortConfig* sc, + int method, int use_global_counter_flag, + void (* userfree)(void* p), + void (* optiontreefree)(void** p), + void (* neg_list_free)(void** p)) +{ + MPSE* p; + + p = (MPSE*)snort_calloc(sizeof(MPSE) ); + p->method = method; + p->verbose = 0; + p->obj = nullptr; + p->bcnt = 0; + p->inc_global_counter = (char)use_global_counter_flag; + + switch ( method ) + { + case MPSE_AC_BNFA: + p->obj=bnfaNew(userfree, optiontreefree, neg_list_free); + if (p->obj) + ((bnfa_struct_t*)(p->obj))->bnfaMethod = 1; + break; + case MPSE_AC_BNFA_Q: + p->obj=bnfaNew(userfree, optiontreefree, neg_list_free); + if (p->obj) + ((bnfa_struct_t*)(p->obj))->bnfaMethod = 0; + break; + case MPSE_AC: + p->obj = acsmNew(userfree, optiontreefree, neg_list_free); + break; + case MPSE_ACF: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_FULL); + break; + case MPSE_ACF_Q: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_FULLQ); + break; + case MPSE_ACS: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_SPARSE); + break; + case MPSE_ACB: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_BANDED); + break; + case MPSE_ACSB: + p->obj = acsmNew2(userfree, optiontreefree, neg_list_free); + if (p->obj) + acsmSelectFormat2((ACSM_STRUCT2*)p->obj,ACF_SPARSEBANDS); + break; + case MPSE_LOWMEM: + p->obj = KTrieNew(0,userfree, optiontreefree, neg_list_free); + break; + case MPSE_LOWMEM_Q: + p->obj = KTrieNew(1,userfree, optiontreefree, neg_list_free); + break; +#ifdef INTEL_SOFT_CPM + case MPSE_INTEL_CPM: + p->obj=IntelPmNew(sc, userfree, optiontreefree, neg_list_free); + break; +#endif + default: + /* p is free'd below if no case */ + break; + } + + if ( !p->obj ) + { + snort_free(p); + p = nullptr; + } + + return (void*)p; +} + +#endif //DYNAMIC_PREPROC_CONTEXT + +void mpseVerbose(void* pvoid) +{ + MPSE* p = (MPSE*)pvoid; + p->verbose = 1; +} + +void mpseSetOpt(void* pvoid, int flag) +{ + MPSE* p = (MPSE*)pvoid; + + if (p == nullptr) + return; + switch ( p->method ) + { + case MPSE_AC_BNFA_Q: + case MPSE_AC_BNFA: + if (p->obj) + bnfaSetOpt((bnfa_struct_t*)p->obj,flag); + break; + case MPSE_ACF: + case MPSE_ACF_Q: + if (p->obj) + acsmCompressStates((ACSM_STRUCT2*)p->obj, flag); + break; + default: + break; + } +} + +void mpseFree(void* pvoid) +{ + MPSE* p = (MPSE*)pvoid; + + if (p == nullptr) + return; + + switch ( p->method ) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + if (p->obj) + bnfaFree((bnfa_struct_t*)p->obj); + snort_free(p); + return; + + case MPSE_AC: + if (p->obj) + acsmFree((ACSM_STRUCT*)p->obj); + snort_free(p); + return; + + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + if (p->obj) + acsmFree2((ACSM_STRUCT2*)p->obj); + snort_free(p); + return; + + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + if (p->obj) + KTrieDelete((KTRIE_STRUCT*)p->obj); + snort_free(p); + return; + +#ifdef INTEL_SOFT_CPM + case MPSE_INTEL_CPM: + if (p->obj) + IntelPmDelete((IntelPm*)p->obj); + snort_free(p); + break; +#endif + + default: + snort_free(p); + assert(false); + return; + } +} + +int mpseAddPattern(void* pvoid, void* P, int m, + unsigned noCase, unsigned offset, unsigned depth, + unsigned negative, void* ID, int IID) +{ + MPSE* p = (MPSE*)pvoid; + + switch ( p->method ) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + return bnfaAddPattern( (bnfa_struct_t*)p->obj, (unsigned char*)P, m, + noCase, negative, ID); + + case MPSE_AC: + return acsmAddPattern( (ACSM_STRUCT*)p->obj, (unsigned char*)P, m, + noCase, offset, depth, negative, ID, IID); + + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + return acsmAddPattern2( (ACSM_STRUCT2*)p->obj, (unsigned char*)P, m, + noCase, offset, depth, negative, ID, IID); + + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + return KTrieAddPattern( (KTRIE_STRUCT*)p->obj, (unsigned char*)P, m, + noCase, negative, ID); + default: + return -1; + } +} + +#ifndef DYNAMIC_PREPROC_CONTEXT +int mpseAddPatternWithSnortConfig(SnortConfig* sc, void* pvoid, void* P, int m, + unsigned noCase, unsigned offset, unsigned depth, + unsigned negative, void* ID, int IID) +{ + MPSE* p = (MPSE*)pvoid; + + switch ( p->method ) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + return bnfaAddPattern( (bnfa_struct_t*)p->obj, (unsigned char*)P, m, + noCase, negative, ID); + + case MPSE_AC: + return acsmAddPattern( (ACSM_STRUCT*)p->obj, (unsigned char*)P, m, + noCase, offset, depth, negative, ID, IID); + + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + return acsmAddPattern2( (ACSM_STRUCT2*)p->obj, (unsigned char*)P, m, + noCase, offset, depth, negative, ID, IID); + + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + return KTrieAddPattern( (KTRIE_STRUCT*)p->obj, (unsigned char*)P, m, + noCase, negative, ID); +#ifdef INTEL_SOFT_CPM + case MPSE_INTEL_CPM: + return IntelPmAddPattern(sc, (IntelPm*)p->obj, (unsigned char*)P, m, + noCase, negative, ID, IID); +#endif + default: + return -1; + } +} + +#endif // DYNAMIC_PREPROC_CONTEXT + +void mpseLargeShifts(void* pvoid, int flag) +{ + MPSE* p = (MPSE*)pvoid; + + switch ( p->method ) + { + default: + return; + } +} + +int mpsePrepPatterns(void* pvoid, + int ( * build_tree )(void* id, void** existing_tree), + int ( * neg_list_func )(void* id, void** list) ) +{ + int retv; + MPSE* p = (MPSE*)pvoid; + + switch ( p->method ) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + retv = bnfaCompile( (bnfa_struct_t*)p->obj, build_tree, neg_list_func); + break; + + case MPSE_AC: + retv = acsmCompile( (ACSM_STRUCT*)p->obj, build_tree, neg_list_func); + break; + + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + retv = acsmCompile2( (ACSM_STRUCT2*)p->obj, build_tree, neg_list_func); + break; + + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + return KTrieCompile( (KTRIE_STRUCT*)p->obj, build_tree, neg_list_func); + + default: + retv = 1; + break; + } + + return retv; +} + +#ifndef DYNAMIC_PREPROC_CONTEXT +int mpsePrepPatternsWithSnortConf(struct _SnortConfig* sc, void* pvoid, + int ( * build_tree )(struct _SnortConfig*, void* id, void** existing_tree), + int ( * neg_list_func )(void* id, void** list) ) +{ + int retv; + MPSE* p = (MPSE*)pvoid; + + switch ( p->method ) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + retv = bnfaCompileWithSnortConf(sc, (bnfa_struct_t*)p->obj, build_tree, neg_list_func); + break; + + case MPSE_AC: + retv = acsmCompileWithSnortConf(sc, (ACSM_STRUCT*)p->obj, build_tree, neg_list_func); + break; + + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + retv = acsmCompile2WithSnortConf(sc, (ACSM_STRUCT2*)p->obj, build_tree, neg_list_func); + break; + + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + return KTrieCompileWithSnortConf(sc, (KTRIE_STRUCT*)p->obj, build_tree, neg_list_func); + +#ifdef INTEL_SOFT_CPM + case MPSE_INTEL_CPM: + return IntelPmFinishGroup(sc, (IntelPm*)p->obj, build_tree, neg_list_func); +#endif + + default: + retv = 1; + break; + } + + return retv; +} + +#endif //DYNAMIC_PREPROC_CONTEXT + +void mpseSetRuleMask(void* pvoid, BITOP* rm) +{ + MPSE* p = (MPSE*)pvoid; + + switch ( p->method ) + { + default: + return; + } +} + +int mpsePrintInfo(void* pvoid) +{ + MPSE* p = (MPSE*)pvoid; + + fflush(stderr); + fflush(stdout); + switch ( p->method ) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + bnfaPrintInfo( (bnfa_struct_t*)p->obj); + break; + case MPSE_AC: + return acsmPrintDetailInfo( (ACSM_STRUCT*)p->obj); + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + return acsmPrintDetailInfo2( (ACSM_STRUCT2*)p->obj); + + default: + return 1; + } + fflush(stderr); + fflush(stdout); + + return 0; +} + +int mpsePrintSummary(int method) +{ + switch (method) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + bnfaPrintSummary(); + break; + case MPSE_AC: + acsmPrintSummaryInfo(); + break; + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + acsmPrintSummaryInfo2(); + break; + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + if ( KTrieMemUsed() ) + { + double x; + x = (double)KTrieMemUsed(); + LogMessage("[ LowMem Search-Method Memory Used : %g %s ]\n", + (x > 1.e+6) ? x/1.e+6 : x/1.e+3, + (x > 1.e+6) ? "MBytes" : "KBytes"); + } + break; + default: + break; + } + + return 0; +} + +#ifndef DYNAMIC_PREPROC_CONTEXT +int mpsePrintSummaryWithSnortConfig(SnortConfig* sc, int method) +{ + switch (method) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + bnfaPrintSummary(); + break; + case MPSE_AC: + acsmPrintSummaryInfo(); + break; + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + acsmPrintSummaryInfo2(); + break; + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + if ( KTrieMemUsed() ) + { + double x; + x = (double)KTrieMemUsed(); + LogMessage("[ LowMem Search-Method Memory Used : %g %s ]\n", + (x > 1.e+6) ? x/1.e+6 : x/1.e+3, + (x > 1.e+6) ? "MBytes" : "KBytes"); + } + break; + default: + break; + } + +#ifdef INTEL_SOFT_CPM + IntelPmPrintSummary(sc); +#endif + + return 0; +} + +#endif //DYNAMIC_PREPROC_CONTEXT + +void mpseInitSummary(void) +{ + acsm_init_summary(); + bnfaInitSummary(); + KTrieInitMemUsed(); +} + +int mpseSearch(void* pvoid, const unsigned char* T, int n, + int ( * action )(void* id, void* tree, int index, void* data, void* neg_list), + void* data, int* current_state) +{ + MPSE* p = (MPSE*)pvoid; + int ret; + PROFILE_VARS; + + PREPROC_PROFILE_START(mpsePerfStats); + + p->bcnt += n; + + if (p->inc_global_counter) + s_bcnt += n; + + switch ( p->method ) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + /* return is actually the state */ + ret = bnfaSearch((bnfa_struct_t*)p->obj, (unsigned char*)T, n, + action, data, 0 /* start-state */, current_state); + PREPROC_PROFILE_END(mpsePerfStats); + return ret; + + case MPSE_AC: + ret = acsmSearch( (ACSM_STRUCT*)p->obj, (unsigned char*)T, n, action, data, current_state); + PREPROC_PROFILE_END(mpsePerfStats); + return ret; + + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + ret = acsmSearch2( (ACSM_STRUCT2*)p->obj, (unsigned char*)T, n, action, data, + current_state); + PREPROC_PROFILE_END(mpsePerfStats); + return ret; + + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + ret = KTrieSearch( (KTRIE_STRUCT*)p->obj, (unsigned char*)T, n, action, data); + *current_state = 0; + PREPROC_PROFILE_END(mpsePerfStats); + return ret; + +#ifdef INTEL_SOFT_CPM + case MPSE_INTEL_CPM: + ret = IntelPmSearch((IntelPm*)p->obj, (unsigned char*)T, n, action, data); + *current_state = 0; + PREPROC_PROFILE_END(mpsePerfStats); + return ret; +#endif + + default: + PREPROC_PROFILE_END(mpsePerfStats); + return 1; + } +} + +int mpseSearchAll(void* pvoid, const unsigned char* T, int n, + int ( * action )(void* id, void* tree, int index, void* data, void* neg_list), + void* data, int* current_state) +{ + MPSE* p = (MPSE*)pvoid; + int ret; + PROFILE_VARS; + + PREPROC_PROFILE_START(mpsePerfStats); + + p->bcnt += n; + + if (p->inc_global_counter) + s_bcnt += n; + + switch ( p->method ) + { + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + ret = acsmSearchAll2( (ACSM_STRUCT2*)p->obj, (unsigned char*)T, n, action, data, + current_state); + PREPROC_PROFILE_END(mpsePerfStats); + return ret; + + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + case MPSE_AC: + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: +#ifdef INTEL_SOFT_CPM + case MPSE_INTEL_CPM: +#endif + default: + //search all not implemented. + PREPROC_PROFILE_END(mpsePerfStats); + return 1; + } +} + +int mpseGetPatternCount(void* pvoid) +{ + MPSE* p = (MPSE*)pvoid; + + if (p == nullptr) + return 0; + + switch ( p->method ) + { + case MPSE_AC_BNFA: + case MPSE_AC_BNFA_Q: + return bnfaPatternCount((bnfa_struct_t*)p->obj); + case MPSE_AC: + return acsmPatternCount((ACSM_STRUCT*)p->obj); + case MPSE_ACF: + case MPSE_ACF_Q: + case MPSE_ACS: + case MPSE_ACB: + case MPSE_ACSB: + return acsmPatternCount2((ACSM_STRUCT2*)p->obj); + case MPSE_LOWMEM: + case MPSE_LOWMEM_Q: + return KTriePatternCount((KTRIE_STRUCT*)p->obj); +#ifdef INTEL_SOFT_CPM + case MPSE_INTEL_CPM: + return IntelGetPatternCount((IntelPm*)p->obj); +#endif + } + return 0; +} + +uint64_t mpseGetPatByteCount(void) +{ + return s_bcnt; +} + +void mpseResetByteCount(void) +{ + s_bcnt = 0; +} + +void mpse_print_qinfo(void) +{ + sfksearch_print_qinfo(); + bnfa_print_qinfo(); + acsmx2_print_qinfo(); +} + diff --git a/src/network_inspectors/appid/test/process_http_test.cc b/src/network_inspectors/appid/test/process_http_test.cc new file mode 100644 index 000000000..52fa32e5f --- /dev/null +++ b/src/network_inspectors/appid/test/process_http_test.cc @@ -0,0 +1,548 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +#include "appid_api.h" +#include "fw_appid.h" + +extern void appIdApiInit(struct AppIdApi*); + +AppIdApi appIdApi; + +static void appIdTestSetup() +{ +#ifdef REMOVED_WHILE_NOT_IN_USE + static SessionAPI sessionAPI = { 0 }; + static StreamAPI streamAPI = { 0 }; + + testFilesPath = getenv("APPID_TESTS_PATH"); + + if (testFilesPath == nullptr) + { + printf("Env variable APPID_TESTS_PATH is not set. Exiting ...\n"); + exit(-1); + } + + strcpy(rnaConfPath, testFilesPath); + strcat(rnaConfPath, "/rna.conf"); + + _dpd.tokenSplit = mSplit; + _dpd.tokenFree = mSplitFree; + LogMessage = logMsg; + _dpd.errMsg = errMsg; + _dpd.debugMsg = debugMsg; + _dpd.addProtocolReference = addProtocolReference; + _dpd.addPreproc = addPreproc; + _dpd.getParserPolicy = getParserPolicy; + _dpd.getDefaultPolicy = getDefaultPolicy; + _dpd.isAppIdRequired = isAppIdRequired; + _dpd.getSnortInstance = getSnortInstance; + _dpd.findProtocolReference = findProtocolReference; + + sessionAPI.enable_preproc_all_ports = enable_preproc_all_ports; + sessionAPI.get_application_data = get_application_data; + sessionAPI.set_application_data = set_application_data; + sessionAPI.get_packet_direction = get_packet_direction; + sessionAPI.get_session_flags = get_session_flags; + sessionAPI.get_session_ip_address = get_session_ip_address; + sessionAPI.get_application_protocol_id = get_application_protocol_id; + sessionAPI.get_http_xff_precedence = get_http_xff_precedence; + _dpd.sessionAPI = &sessionAPI; + + streamAPI.is_session_decrypted = is_session_decrypted; + streamAPI.set_application_id = set_application_id; + streamAPI.is_session_http2 = is_session_http2; + _dpd.streamAPI = &streamAPI; + + _dpd.searchAPI = &searchAPI; +#endif + + appIdApiInit(&appIdApi); +} + +#ifdef REMOVED_WHILE_NOT_IN_USE +#include +#include +#include + +#include + +#include "parser/mstring.h" + +#include "appid_config.h" + +#include "external_apis.h" +#include "fw_appid.h" +#include "session_file.h" + +// FIXIT - this must go when snort2.9.x sf_ip.h changes are ported to snort++ +#include "sfaddr_temp.h" + +#if 1 // FIXIT-M hacks +// not sure where this is defined; outside the appid tree probably +using ControlDataSendFunc = void (*)(); +#endif + +extern void AppIdReload(struct _SnortConfig* sc, char* args, void** new_config); +extern void* AppIdReloadSwap(struct _SnortConfig* sc, void* swap_config); +extern void AppIdReloadFree(void* old_context); +extern int AppIdReconfigure(uint16_t type, const uint8_t* data, uint32_t length, + void** new_context, + char* statusBuf, int statusBuf_len); +extern int AppIdReconfigureSwap(uint16_t type, void* new_context, void** old_context); +extern void AppIdReconfigureFree(uint16_t type, void* old_context, struct _THREAD_ELEMENT* te, + ControlDataSendFunc f); +#endif + +#ifdef REMOVED_WHILE_NOT_IN_USE +extern int processHTTPPacket(Packet* p, AppIdData* session, int direction, + HttpParsedHeaders* const headers, const AppIdConfig* pConfig); +extern void sfiph_build(Packet* p, const void* hdr, int family); +extern void pickHttpXffAddress(Packet* p, AppIdData* appIdSession, + ThirdPartyAppIDAttributeData* attribute_data); + +// FIXIT: use APIs instead of using global +extern AppIdData* pAppIdData; + +static char* testFilesPath = nullptr; +static char rnaConfPath[PATH_MAX] = { 0 }; + +static void testProcessHttpPacket(const char* useragent, const char* host, const char* referer, + const char* trailer) +{ + // FIXIT-M J these need to be cleared, probably + Packet p; + AppIdData session; + + char buf[1024]; + int bufLen; + + session.common.flags = 0x311380; + session.hsession = (decltype(session.hsession))snort_calloc(sizeof(httpSession)); + if (host) + { + session.hsession->host = snort_strdup(host); + strcpy(buf, "http://"); + strcat(buf, host); + if (trailer) + strcat(buf, trailer); + else + strcat(buf, "/"); + session.hsession->url = snort_strdup(buf); + } + if (useragent) + session.hsession->useragent = snort_strdup(useragent); + if (referer) + session.hsession->referer = snort_strdup(referer); + session.hsession->uri = snort_strdup("/"); + session.hsession->cookie = snort_strdup( + "s_vi=[CS]v1|25B026B7851D124A-6000012D802520B2[CE]; CG=US:MD:Laurel; mbox=check#true#1336576860|session#1336576799559-724714#1336578660; SelectedEdition=www; rsi_segs_ttn=A09801_10001|A09801_10313; ug=" + "4faa8b240a5fef0aa5147448c8005347; ugs=1; tnr:usrvtstg01=1336576805411%7C0%7C0%7C1%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C1%7Cf%7C" + "1%7C4%7C1336576805411; tnr:sesctmp01=1336576805411; s_cc=true; s_sq=%5B%5BB%5D%5D; adDEmas=R08&broadband&gblx.net&73&gbr&826027&0&10198&-&-&-&15275&; adDEon=true; s_ppv=13"); + session.serviceAppId = APP_ID_NONE; + session.payloadAppId = APP_ID_NONE; + session.tpPayloadAppId = 1190; + session.scan_flags = 0x26; + session.ClientAppId = 0; + + strcpy(buf, "GET / HTTP/1.1\r\n"); + strcat(buf, "Host: "); + strcat(buf, host); + strcat(buf, "\r\nUser-Agent: "); + strcat(buf, useragent); + if (referer) + { + strcat(buf, "\r\nReferer: "); + strcat(buf, referer); + } + strcat(buf, "\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"); + strcat(buf, + "Accept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"); + strcat(buf, "Keep-Alive: 115\r\nConnection: keep-alive\r\n"); + bufLen = strlen(buf); + strncat(buf, + "Cookie: s_vi=[CS]v1|25B026B7851D124A-6000012D802520B2[CE]; CG=US:MD:Laurel; mbox=check#true#1336576860|session#1336576799559-724714#1336578660; SelectedEdition=www; rsi_segs_ttn=A09801_10001|A09801_10313; ug=4faa8b240a5fef0aa5147448c8005347; ugs=1; tnr:usrvtstg01=1336576805411%7C0%7C0%7C1%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C0%7C1%7Cf%7C1%7C4%7C1336576805411; tnr:sesctmp01=1336576805411; s_cc=true; s_sq=%5B%5BB%5D%5D; adDEmas=R08&broadband&gblx.net&73&gbr&826027&0&10198&-&-&-&15275&; adDEon=true; s_ppv=13\r\n\r\n", + sizeof(buf) - bufLen - 1); + + p.data = (decltype(p.data))snort_strdup(buf); + p.dsize = strlen((const char*)p.data); + + processHTTPPacket(&p, &session, APP_ID_FROM_INITIATOR, nullptr, pAppidActiveConfig); + + if (host) + { + snort_free(session.hsession->host); + snort_free(session.hsession->url); + } + if (referer) + snort_free(session.hsession->referer); + if (useragent) + snort_free(session.hsession->useragent); + snort_free(session.hsession->uri); + snort_free(session.hsession->cookie); + snort_free(session.hsession); + + snort_free((uint8_t*)p.data); +} + +START_TEST(HttpTest) +{ + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + testProcessHttpPacket( + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100715 Ubuntu/9.04 (jaunty) Firefox/3.6.7", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/600.1.17 (KHTML, like Gecko) Version/7.1 Safari/537.85.10", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", + "www.cnn.com", + nullptr, nullptr); + + AppIdCommonFini(); +} + +END_TEST + +#ifdef REMOVED_WHILE_NOT_IN_USE +START_TEST(HttpAfterReloadTest) +{ + AppIdConfig* pNewConfig = nullptr; + AppIdConfig* pOldConfig = nullptr; + + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + AppIdReload(nullptr, nullptr, (void**)&pNewConfig); + pOldConfig = AppIdReloadSwap(nullptr, pNewConfig); + AppIdReloadFree(pOldConfig); + + testProcessHttpPacket( + "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36", + "www.cnn.com", + nullptr, nullptr); + + AppIdCommonFini(); +} + +END_TEST START_TEST(HttpAfterReconfigureTest) +{ + AppIdConfig* pNewConfig = nullptr; + AppIdConfig* pOldConfig = nullptr; + + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + AppIdReconfigure(0, nullptr, 0, (void**)&pNewConfig, nullptr, 0); + AppIdReconfigureSwap(0, pNewConfig, (void**)&pOldConfig); + AppIdReconfigureFree(0, pOldConfig, nullptr, nullptr); + + testProcessHttpPacket("Wget/1.9.1", + "www.cnn.com", + nullptr, nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "www.cnn.com", + nullptr, nullptr); + + AppIdCommonFini(); +} + +END_TEST START_TEST(HttpAfterReloadReconfigureTest) +{ + AppIdConfig* pNewConfig = nullptr; + AppIdConfig* pOldConfig = nullptr; + + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); + + AppIdReload(nullptr, nullptr, (void**)&pNewConfig); + pOldConfig = AppIdReloadSwap(nullptr, pNewConfig); + AppIdReloadFree(pOldConfig); + + pNewConfig = nullptr; + pOldConfig = nullptr; + + AppIdReconfigure(0, nullptr, 0, (void**)&pNewConfig, nullptr, 0); + AppIdReconfigureSwap(0, pNewConfig, (void**)&pOldConfig); + AppIdReconfigureFree(0, pOldConfig, nullptr, nullptr); + + testProcessHttpPacket( + "Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + "www.123.com", + "http://www.cnn.com", nullptr); + testProcessHttpPacket( + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100715 Ubuntu/9.04 (jaunty) Firefox/3.6.7", + "www.cnn.com", + nullptr, "/tech?a=1&b=2"); + + AppIdCommonFini(); +} + +END_TEST START_TEST(HttpXffTest) +{ + Packet p = { 0 }; + AppIdData session = { 0 }; + httpSession hsession = { 0 }; + ThirdPartyAppIDAttributeData tpData = { 0 }; + SFIP_RET status; + sfaddr_t* xffAddr = sfaddr_alloc("1.1.1.1", &status); + + // Only X-Forwarded-For + session.hsession = &hsession; + tpData.numXffFields = 1; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_X_FORWARDED_FOR; + tpData.xffFieldValue[0].value = "1.1.1.1"; + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + sfaddr_free(session.hsession->xffAddr); + + // Only True-Client-IP + memset(&p, 0, sizeof(p)); + memset(&session, 0, sizeof(session)); + memset(&hsession, 0, sizeof(hsession)); + memset(&tpData, 0, sizeof(tpData)); + session.hsession = &hsession; + tpData.numXffFields = 1; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_TRUE_CLIENT_IP; + tpData.xffFieldValue[0].value = "1.1.1.1"; + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + sfaddr_free(session.hsession->xffAddr); + + // X-Forwarded-For and True-Client-IP + memset(&p, 0, sizeof(p)); + memset(&session, 0, sizeof(session)); + memset(&hsession, 0, sizeof(hsession)); + memset(&tpData, 0, sizeof(tpData)); + session.hsession = &hsession; + tpData.numXffFields = 2; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_TRUE_CLIENT_IP; + tpData.xffFieldValue[0].value = "2.2.2.2"; + tpData.xffFieldValue[1].field = HTTP_XFF_FIELD_X_FORWARDED_FOR; + tpData.xffFieldValue[1].value = "1.1.1.1"; + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + sfaddr_free(session.hsession->xffAddr); + + // Comma-separated list in X-Forwarded-For + memset(&p, 0, sizeof(p)); + memset(&session, 0, sizeof(session)); + memset(&hsession, 0, sizeof(hsession)); + memset(&tpData, 0, sizeof(tpData)); + session.hsession = &hsession; + tpData.numXffFields = 1; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_X_FORWARDED_FOR; + tpData.xffFieldValue[0].value = snort_strdup("1.1.1.1, 2.2.2.2"); + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + snort_free(tpData.xffFieldValue[0].value); + sfaddr_free(session.hsession->xffAddr); + + // Custom XFF + static char* defaultXffPrecedence[] = { "Custom-XFF", HTTP_XFF_FIELD_X_FORWARDED_FOR, + HTTP_XFF_FIELD_TRUE_CLIENT_IP }; + memset(&p, 0, sizeof(p)); + memset(&session, 0, sizeof(session)); + memset(&hsession, 0, sizeof(hsession)); + memset(&tpData, 0, sizeof(tpData)); + session.hsession = &hsession; + session.hsession->xffPrecedence = defaultXffPrecedence; + session.hsession->numXffFields = 3; + tpData.numXffFields = 2; + tpData.xffFieldValue[0].field = HTTP_XFF_FIELD_X_FORWARDED_FOR; + tpData.xffFieldValue[0].value = "2.2.2.2"; + tpData.xffFieldValue[1].field = "Custom-XFF"; + tpData.xffFieldValue[1].value = "1.1.1.1"; + pickHttpXffAddress(&p, &session, &tpData); + ck_assert_int_eq(sfip_compare(session.hsession->xffAddr, xffAddr), SFIP_EQUAL); + sfaddr_free(session.hsession->xffAddr); + + sfaddr_free(xffAddr); + + snort_free((uint8_t*)p.data); +} + +#endif + +static void sessionTcaseSetup(void) +{ + memset(&appidStaticConfig, 0, sizeof(appidStaticConfig)); + + strcpy(appidStaticConfig.conf_file, rnaConfPath); + strcpy(appidStaticConfig.app_id_detector_path, testFilesPath); + + AppIdCommonInit(&appidStaticConfig); +} + +static void sessionTcaseClean(void) +{ + AppIdCommonFini(); +} + +static Suite* setupAppIdSuite(void) +{ + Suite* appIdSuite; + TCase* frameworkTcase; + TCase* httpTcase; + TCase* sessionTcase; + + appIdSuite = suite_create("AppId"); + + // Create Framework test case + frameworkTcase = tcase_create("FrameworkTestCase"); + tcase_add_checked_fixture(frameworkTcase, nullptr, nullptr); + + // Add tests to Framework test case + tcase_add_test(frameworkTcase, ConfigParseTest); + tcase_add_test(frameworkTcase, InitFiniTest); + tcase_add_test(frameworkTcase, ReloadTest); + tcase_add_test(frameworkTcase, ReconfigureTest); + + suite_add_tcase(appIdSuite, frameworkTcase); + + // Create Http test case + httpTcase = tcase_create("HttpTestCase"); + tcase_add_checked_fixture(httpTcase, nullptr, nullptr); + + // Add tests to Http test case + tcase_add_test(httpTcase, HttpTest); + tcase_add_test(httpTcase, HttpAfterReloadTest); + tcase_add_test(httpTcase, HttpAfterReconfigureTest); + tcase_add_test(httpTcase, HttpAfterReloadReconfigureTest); + tcase_add_test(httpTcase, HttpXffTest); + + suite_add_tcase(appIdSuite, httpTcase); + + // Create Session test case + sessionTcase = tcase_create("SessionTestCase"); + tcase_add_checked_fixture(sessionTcase, nullptr, nullptr); + + // Add tests to Session test case + tcase_add_test(sessionTcase, AimSessionTest); + tcase_add_test(sessionTcase, CnnSessionTest); + tcase_add_test(sessionTcase, DnsSessionTest); + tcase_add_test(sessionTcase, ImapSessionTest); + tcase_add_test(sessionTcase, MdnsSessionTest); + tcase_add_test(sessionTcase, MsnSessionTest); + tcase_add_test(sessionTcase, NetbiosNsSessionTest); + tcase_add_test(sessionTcase, NetbiosSsSessionTest); + tcase_add_test(sessionTcase, PatternSessionTest); + tcase_add_test(sessionTcase, Pop3SessionTest); + tcase_add_test(sessionTcase, RfbSessionTest); + tcase_add_test(sessionTcase, RtpSessionTest); + tcase_add_test(sessionTcase, SmtpSessionTest); + tcase_add_test(sessionTcase, TimbuktuSessionTest); + tcase_add_test(sessionTcase, WebexSessionTest); + tcase_add_test(sessionTcase, YmSessionTest); + + suite_add_tcase(appIdSuite, sessionTcase); + + return appIdSuite; +} + +#endif + +int main() +{ +#ifdef REMOVED_WHILE_NOT_IN_USE + int opt, debug = 0; + int numberFailed; + Suite* appIdSuite; + SRunner* appIdRunner; + + while ((opt = getopt(argc, argv, "dh")) != -1) + { + switch (opt) + { + case 'd': + debug = 1; + break; + case 'h': + printf( + "Usage:\n\ + -d: Run test in no fork mode for debugging in gdb.\n\ + -h: This text.\n"); + return EXIT_SUCCESS; + } + } +#endif + + appIdTestSetup(); + +#ifdef REMOVED_WHILE_NOT_IN_USE + // Create a test runner for AppId suite + appIdSuite = setupAppIdSuite(); + appIdRunner = srunner_create(appIdSuite); + + if (debug) + { + srunner_set_fork_status(appIdRunner, CK_NOFORK); + } + + // Set test results format to TAP and specify file name + srunner_set_tap(appIdRunner, "AppIdTests.tap~"); + + // Run test cases in AppId suite + srunner_run(appIdRunner, nullptr, "FrameworkTestCase", CK_NORMAL); + + system("cp AppIdTests.tap~ AppIdTests.tap"); + + srunner_run(appIdRunner, nullptr, "HttpTestCase", CK_NORMAL); + + system("cat AppIdTests.tap~ >> AppIdTests.tap"); + + sessionTcaseSetup(); + srunner_run(appIdRunner, nullptr, "SessionTestCase", CK_NORMAL); + sessionTcaseClean(); + + system("cat AppIdTests.tap~ >> AppIdTests.tap"); + + numberFailed = srunner_ntests_failed(appIdRunner); + srunner_free(appIdRunner); + return (numberFailed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +#endif +} + diff --git a/src/network_inspectors/appid/test/rna.conf b/src/network_inspectors/appid/test/rna.conf new file mode 100644 index 000000000..ed8dc94e4 --- /dev/null +++ b/src/network_inspectors/appid/test/rna.conf @@ -0,0 +1,26 @@ +# +# Configuration generated for NetworkDiscovery +# +# FIXIT - Look into converting these configuration options to lua +# +config ModuleDirectory /var/sf/detection_engines/812d18e2-1851-11e6-9201-d085f23f50f8/libs/rna +config OutputMethod serial -f rna.bin -t 86400 -s 20 +config Database sfsnort correlator correlator + +config MaxHostClientApps 16 +config MaxHostServices 100 +config MaxPayloads 100 +config MaxHostServiceInfo 16 + +protoid BannerGrab 0 +pnd UpdateTimeout 3600 +userConfig captureFailedLoginAttempts 1 +# START Export Rule - 1 (5e30e278-1852-11e6-a7db-e1e48cc1c54d) +# Device Analyze +config AnalyzeApplication 0.0.0.0/0 -1 +config AnalyzeApplication ::/0 -1 +# Device Source Service Exclusion +# Device Destination Service Exclusion +# END + + diff --git a/src/network_inspectors/appid/test/session_file.cc b/src/network_inspectors/appid/test/session_file.cc new file mode 100644 index 000000000..f6f90bb88 --- /dev/null +++ b/src/network_inspectors/appid/test/session_file.cc @@ -0,0 +1,513 @@ +#include +#include "session_file.h" + +static void sessionFileAdd(void* session, SessionFileData* data); +static SessionFileData* sessionFileFind(void* session); +static void sessionDataDump(FILE* file, SessionControlBlock* scb); +static void packetDataDump(FILE* file, uint32_t packetCount, Packet* pkt); +static void sessionDataRead(FILE* file, SessionControlBlock* scb); +static int packetDataRead(FILE* file, Packet* pkt, HttpParsedHeaders** pHttpHeader); +static void readHttpHeaderItem(FILE* file, HEADER_LOCATION* headerLocation); + +static SFXHASH* sessionFiles = nullptr; + +void sessionFileInit(void) +{ + sessionFiles = sfxhash_new(2048, + sizeof(void*), + sizeof(SessionFileData), + 0, + 0, + nullptr, + nullptr, + 0); +} + +void sessionFileFini(void) +{ + SFXHASH_NODE* node; + SessionFileData* data; + + for (node = sfxhash_findfirst(sessionFiles); + node; + node = sfxhash_findnext(sessionFiles)) + { + data = (SessionFileData*)node->data; + fclose(data->file); + } + + sfxhash_delete(sessionFiles); + sessionFiles = nullptr; +} + +FILE* sessionFileProcess(Packet* pkt) +{ + static uint32_t packetCount = 0; + static uint32_t sessionCount = 0; + SessionFileData* pSessionFileData; + SessionFileData sessionFileData; + SessionControlBlock* scb = (SessionControlBlock*)pkt->stream_session; + + packetCount++; + + if (!scb) + { + printf("Ignoring packet %d\n", packetCount); + return nullptr; + } + + pSessionFileData = sessionFileFind(scb); + + if (!pSessionFileData) + { + pSessionFileData = &sessionFileData; + + sessionCount++; + sprintf(pSessionFileData->fileName, "session%d.ssn", sessionCount); + pSessionFileData->file = fopen(pSessionFileData->fileName, "w"); + pSessionFileData->packetCount = 1; + + sessionFileAdd(scb, pSessionFileData); + } + else + { + pSessionFileData->packetCount++; + } + + sessionDataDump(pSessionFileData->file, scb); + + packetDataDump(pSessionFileData->file, pSessionFileData->packetCount, pkt); + + return pSessionFileData->file; +} + +void sessionFileProcessHttp(Packet* pkt, HttpParsedHeaders* headers) +{ + FILE* file = sessionFileProcess(pkt); + + if (file == nullptr) + return; + + if (headers->host.len > 0) + { + fprintf(file, " %u %d ", PACKET_HTTP_HOST, headers->host.len); + fwrite(headers->host.start, 1, headers->host.len, file); + } + if (headers->url.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_URL, headers->url.len); + fwrite(headers->url.start, 1, headers->url.len, file); + } + if (headers->method.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_METHOD, headers->method.len); + fwrite(headers->method.start, 1, headers->method.len, file); + } + if (headers->userAgent.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_USER_AGENT, headers->userAgent.len); + fwrite(headers->userAgent.start, 1, headers->userAgent.len, file); + } + if (headers->referer.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_REFERER, headers->referer.len); + fwrite(headers->referer.start, 1, headers->referer.len, file); + } + if (headers->via.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_VIA, headers->via.len); + fwrite(headers->via.start, 1, headers->via.len, file); + } + if (headers->responseCode.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_RESPONSE_CODE, headers->responseCode.len); + fwrite(headers->responseCode.start, 1, headers->responseCode.len, file); + } + if (headers->server.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_SERVER, headers->server.len); + fwrite(headers->server.start, 1, headers->server.len, file); + } + if (headers->xWorkingWith.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_X_WORKING_WITH, headers->xWorkingWith.len); + fwrite(headers->xWorkingWith.start, 1, headers->xWorkingWith.len, file); + } + if (headers->contentType.len > 0) + { + fprintf(file, "\n %u %d ", PACKET_HTTP_CONTENT_TYPE, headers->contentType.len); + fwrite(headers->contentType.start, 1, headers->contentType.len, file); + } + fprintf(file, "\n"); +} + +void sessionFileReadSession(FILE* file, SessionControlBlock* scb) +{ + char buf[16]; + + while (true) + { + fscanf(file, "%s\n", buf); + + if (strcmp(buf, "Session:") == 0) + { + sessionDataRead(file, scb); + return; + } + } +} + +int sessionFileReadPacket(FILE* file, Packet* pkt, HttpParsedHeaders** pHttpHeader) +{ + char buf[16]; + + while (true) + { + fscanf(file, "%s\n", buf); + + if (strncmp(buf, "Packet", 6) == 0) + { + fgets(buf, 16, file); + return packetDataRead(file, pkt, pHttpHeader); + } + } +} + +static void sessionFileAdd(void* session, SessionFileData* data) +{ + sfxhash_add(sessionFiles, &session, data); +} + +static SessionFileData* sessionFileFind(void* session) +{ + return (SessionFileData*)sfxhash_find(sessionFiles, &session); +} + +static void sessionDataDump(FILE* file, SessionControlBlock* scb) +{ + fprintf(file, "Session:\n"); + fprintf(file, " %u %08X %08X %08X %08X\n", SESSION_CLIENT_IP_IA32, + scb->client_ip.ia32[0], + scb->client_ip.ia32[1], + scb->client_ip.ia32[2], + scb->client_ip.ia32[3]); + fprintf(file, " %u %u\n", SESSION_CLIENT_PORT, scb->client_port); + fprintf(file, " %u %u\n", SESSION_HA_STATE_SESSION_FLAGS, scb->ha_state.session_flags); +} + +static void packetDataDump(FILE* file, uint32_t packetCount, Packet* pkt) +{ + fprintf(file, "Packet %d:\n", packetCount); + + if (pkt->pkt_header) + { + fprintf(file, " %u %u\n", PACKET_PKT_HEADER_TS_TV_SEC, (unsigned + int)pkt->pkt_header->ts.tv_sec); + fprintf(file, " %u %d\n", PACKET_PKT_HEADER_INGRESS_GROUP, + pkt->pkt_header->ingress_group); + fprintf(file, " %u %u\n", PACKET_PKT_HEADER_PKTLEN, pkt->pkt_header->pktlen); + } + + if (pkt->tcp_header) + { + fprintf(file, " %u %u\n", PACKET_TCP_HEADER_SOURCE_PORT, pkt->tcp_header->source_port); + fprintf(file, " %u %u\n", PACKET_TCP_HEADER_FLAGS, pkt->tcp_header->flags); + } + + if (pkt->udp_header) + { + fprintf(file, " %u %u\n", PACKET_UDP_HEADER_SOURCE_PORT, pkt->udp_header->source_port); + } + + if (pkt->is_ip4()) + { + fprintf(file, " %u %08X %08X %08X %08X\n", PACKET_IP4H_IP_ADDRS_IP_SRC_IA32, + pkt->ip4h->ip_addrs->ip_src.ia32[0], + pkt->ip4h->ip_addrs->ip_src.ia32[1], + pkt->ip4h->ip_addrs->ip_src.ia32[2], + pkt->ip4h->ip_addrs->ip_src.ia32[3]); + fprintf(file, " %u %u\n", PACKET_IP4H_IP_ADDRS_IP_SRC_FAMILY, + pkt->ip4h->ip_addrs->ip_src.family); + fprintf(file, " %u %08X %08X %08X %08X\n", PACKET_IP4H_IP_ADDRS_IP_DST_IA32, + pkt->ip4h->ip_addrs->ip_dst.ia32[0], + pkt->ip4h->ip_addrs->ip_dst.ia32[1], + pkt->ip4h->ip_addrs->ip_dst.ia32[2], + pkt->ip4h->ip_addrs->ip_dst.ia32[3]); + fprintf(file, " %u %u\n", PACKET_IP4H_IP_ADDRS_IP_DST_FAMILY, + pkt->ip4h->ip_addrs->ip_dst.family); + fprintf(file, " %u %u\n", PACKET_IP4H_IP_PROTO, pkt->ip4h->ip_proto); + } + + if (pkt->ip6h) + { + fprintf(file, " %u %08X %08X %08X %08X\n", PACKET_IP6H_IP_ADDRS_IP_SRC_IA32, + pkt->ip6h->ip_addrs->ip_src.ia32[0], + pkt->ip6h->ip_addrs->ip_src.ia32[1], + pkt->ip6h->ip_addrs->ip_src.ia32[2], + pkt->ip6h->ip_addrs->ip_src.ia32[3]); + fprintf(file, " %u %u\n", PACKET_IP6H_IP_ADDRS_IP_SRC_FAMILY, + pkt->ip6h->ip_addrs->ip_src.family); + fprintf(file, " %u %08X %08X %08X %08X\n", PACKET_IP6H_IP_ADDRS_IP_DST_IA32, + pkt->ip6h->ip_addrs->ip_dst.ia32[0], + pkt->ip6h->ip_addrs->ip_dst.ia32[1], + pkt->ip6h->ip_addrs->ip_dst.ia32[2], + pkt->ip6h->ip_addrs->ip_dst.ia32[3]); + fprintf(file, " %u %u\n", PACKET_IP6H_IP_ADDRS_IP_DST_FAMILY, + pkt->ip6h->ip_addrs->ip_dst.family); + } + + fprintf(file, " %u %u\n", PACKET_FAMILY, pkt->family); + fprintf(file, " %u %08X\n", PACKET_FLAGS, pkt->flags); + fprintf(file, " %u %u\n", PACKET_SRC_PORT, pkt->src_port); + fprintf(file, " %u %u\n", PACKET_DST_PORT, pkt->dst_port); + if (pkt->payload_size) + { + fprintf(file, " %u %u ", PACKET_PAYLOAD, pkt->payload_size); + fwrite(pkt->payload, 1, pkt->payload_size, file); + fprintf(file, "\n"); + } +} + +void sessionDataRead(FILE* file, SessionControlBlock* scb) +{ + int match; + uint32_t type; + + while (true) + { + match = fscanf(file, "%u", &type); + + if ((match == EOF) || (match == 0)) + return; + + switch (type) + { + case SESSION_CLIENT_IP_IA32: + fscanf(file, "%x", &scb->client_ip.ia32[0]); + fscanf(file, "%x", &scb->client_ip.ia32[1]); + fscanf(file, "%x", &scb->client_ip.ia32[2]); + fscanf(file, "%x\n", &scb->client_ip.ia32[3]); + break; + case SESSION_CLIENT_PORT: + fscanf(file, "%u\n", (unsigned int*)&scb->client_port); + break; + case SESSION_HA_STATE_SESSION_FLAGS: + fscanf(file, "%u\n", &scb->ha_state.session_flags); + break; + default: + printf("Unknown session field\n"); + break; + } + } +} + +// FIXIT - M Must check to ensure memory allocated by snort_calloc's below is freed when +// the tests complete +static int packetDataRead(FILE* file, Packet* pkt, HttpParsedHeaders** pHttpHeader) +{ + static SFDAQ_PktHdr_t pkt_header = { 0 }; + static TCPHeader tcp_header = { 0 }; + static UDPHeader udp_header = { 0 }; + static IP4Hdr ip4h = { 0 }; + static IP6Hdr ip6h = { 0 }; + static IPAddresses ip4_addrs = { 0 }; + static IPAddresses ip6_addrs = { 0 }; + int match; + uint32_t type; + + memset(&pkt_header, 0, sizeof(pkt_header)); + memset(&tcp_header, 0, sizeof(tcp_header)); + memset(&udp_header, 0, sizeof(udp_header)); + memset(&ip4h, 0, sizeof(ip4h)); + memset(&ip6h, 0, sizeof(ip6h)); + memset(&ip4_addrs, 0, sizeof(ip4_addrs)); + memset(&ip6_addrs, 0, sizeof(ip6_addrs)); + + while (true) + { + match = fscanf(file, "%u", &type); + + if (match == EOF) + return -1; + if (match == 0) + return 0; + + switch (type) + { + case PACKET_PKT_HEADER_TS_TV_SEC: + pkt->pkt_header = &pkt_header; + fscanf(file, "%u\n", (unsigned int*)&pkt_header.ts.tv_sec); + break; + case PACKET_PKT_HEADER_INGRESS_GROUP: + pkt->pkt_header = &pkt_header; + fscanf(file, "%u\n", &pkt_header.ingress_group); + break; + case PACKET_PKT_HEADER_PKTLEN: + pkt->pkt_header = &pkt_header; + fscanf(file, "%u\n", &pkt_header.pktlen); + break; + case PACKET_TCP_HEADER_SOURCE_PORT: + pkt->tcp_header = &tcp_header; + fscanf(file, "%u\n", (unsigned int*)&tcp_header.source_port); + break; + case PACKET_TCP_HEADER_FLAGS: + pkt->tcp_header = &tcp_header; + fscanf(file, "%u\n", (unsigned int*)&tcp_header.flags); + break; + case PACKET_UDP_HEADER_SOURCE_PORT: + pkt->udp_header = &udp_header; + fscanf(file, "%u\n", (unsigned int*)&udp_header.source_port); + break; + case PACKET_IP4H_IP_ADDRS_IP_SRC_IA32: + ip4h.ip_addrs = &ip4_addrs; + pkt->ip4h = &ip4h; + + fscanf(file, "%x", &ip4_addrs.ip_src.ia32[0]); + fscanf(file, "%x", &ip4_addrs.ip_src.ia32[1]); + fscanf(file, "%x", &ip4_addrs.ip_src.ia32[2]); + fscanf(file, "%x\n", &ip4_addrs.ip_src.ia32[3]); + break; + case PACKET_IP4H_IP_ADDRS_IP_SRC_FAMILY: + ip4h.ip_addrs = &ip4_addrs; + pkt->ip4h = &ip4h; + + fscanf(file, "%u\n", (unsigned int*)&ip4_addrs.ip_src.family); + break; + case PACKET_IP4H_IP_ADDRS_IP_DST_IA32: + ip4h.ip_addrs = &ip4_addrs; + pkt->ip4h = &ip4h; + + fscanf(file, "%x", &ip4_addrs.ip_dst.ia32[0]); + fscanf(file, "%x", &ip4_addrs.ip_dst.ia32[1]); + fscanf(file, "%x", &ip4_addrs.ip_dst.ia32[2]); + fscanf(file, "%x\n", &ip4_addrs.ip_dst.ia32[3]); + break; + case PACKET_IP4H_IP_ADDRS_IP_DST_FAMILY: + ip4h.ip_addrs = &ip4_addrs; + pkt->ip4h = &ip4h; + + fscanf(file, "%u\n", (unsigned int*)&ip4_addrs.ip_dst.family); + break; + case PACKET_IP4H_IP_PROTO: + ip4h.ip_addrs = &ip4_addrs; + pkt->ip4h = &ip4h; + + fscanf(file, "%u\n", (unsigned int*)&ip4h.ip_proto); + break; + case PACKET_IP6H_IP_ADDRS_IP_SRC_IA32: + ip6h.ip_addrs = &ip6_addrs; + pkt->ip6h = &ip6h; + + fscanf(file, "%x", &ip6_addrs.ip_src.ia32[0]); + fscanf(file, "%x", &ip6_addrs.ip_src.ia32[1]); + fscanf(file, "%x", &ip6_addrs.ip_src.ia32[2]); + fscanf(file, "%x\n", &ip6_addrs.ip_src.ia32[3]); + break; + case PACKET_IP6H_IP_ADDRS_IP_SRC_FAMILY: + ip6h.ip_addrs = &ip6_addrs; + pkt->ip6h = &ip6h; + + fscanf(file, "%u\n", (unsigned int*)&ip6_addrs.ip_src.family); + break; + case PACKET_IP6H_IP_ADDRS_IP_DST_IA32: + ip6h.ip_addrs = &ip6_addrs; + pkt->ip6h = &ip6h; + + fscanf(file, "%x", &ip6_addrs.ip_dst.ia32[0]); + fscanf(file, "%x", &ip6_addrs.ip_dst.ia32[1]); + fscanf(file, "%x", &ip6_addrs.ip_dst.ia32[2]); + fscanf(file, "%x\n", &ip6_addrs.ip_dst.ia32[3]); + break; + case PACKET_IP6H_IP_ADDRS_IP_DST_FAMILY: + ip6h.ip_addrs = &ip6_addrs; + pkt->ip6h = &ip6h; + + fscanf(file, "%u\n", (unsigned int*)&ip6_addrs.ip_dst.family); + break; + case PACKET_FAMILY: + fscanf(file, "%u\n", &pkt->family); + break; + case PACKET_FLAGS: + fscanf(file, "%x\n", &pkt->flags); + break; + case PACKET_SRC_PORT: + fscanf(file, "%u\n", (unsigned int*)&pkt->src_port); + break; + case PACKET_DST_PORT: + fscanf(file, "%u\n", (unsigned int*)&pkt->dst_port); + break; + case PACKET_PAYLOAD: + fscanf(file, "%u", (unsigned int*)&pkt->payload_size); + fgetc(file); + pkt->payload = snort_calloc(sizeof(char)* pkt->payload_size); + fread((uint8_t*)pkt->payload, 1, pkt->payload_size, file); + break; + case PACKET_HTTP_HOST: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->host); + break; + case PACKET_HTTP_URL: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->url); + break; + case PACKET_HTTP_METHOD: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->method); + break; + case PACKET_HTTP_USER_AGENT: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->userAgent); + break; + case PACKET_HTTP_REFERER: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->referer); + break; + case PACKET_HTTP_VIA: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->via); + break; + case PACKET_HTTP_RESPONSE_CODE: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->responseCode); + break; + case PACKET_HTTP_SERVER: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->server); + break; + case PACKET_HTTP_X_WORKING_WITH: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->xWorkingWith); + break; + case PACKET_HTTP_CONTENT_TYPE: + if (!(*pHttpHeader)) + *pHttpHeader = snort_calloc(sizeof(HttpParsedHeaders)); + readHttpHeaderItem(file, &(*pHttpHeader)->contentType); + break; + default: + printf("Unknown packet field\n"); + break; + } + } +} + +static void readHttpHeaderItem(FILE* file, HEADER_LOCATION* headerLocation) +{ + uint8_t* start; + + fscanf(file, "%u", &headerLocation->len); + start = snort_calloc(headerLocation->len + 1); + fgetc(file); + fread(start, 1, headerLocation->len, file); + start[headerLocation->len] = '\0'; + headerLocation->start = start; +} + diff --git a/src/network_inspectors/appid/test/session_file.h b/src/network_inspectors/appid/test/session_file.h new file mode 100644 index 000000000..1ea1dfcf3 --- /dev/null +++ b/src/network_inspectors/appid/test/session_file.h @@ -0,0 +1,182 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2015-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// session_file.h author Shravan Rangarajuvenkata + +#ifndef SESSION_FILE_H +#define SESSION_FILE_H + +#include + +// FIXIT - this must go when snort2.9.x sf_ip.h changes are ported to snort++ +#include "sfaddr_temp.h" + +#define MAX_APP_PROTOCOL_ID 4 + +struct HttpParsedHeaders; +struct Packet; +struct SessionKey; +struct StreamAppData; + +enum SessionField +{ + SESSION_CLIENT_IP_IA32 = 1000, + SESSION_CLIENT_PORT, + SESSION_HA_STATE_SESSION_FLAGS +}; + +enum tPktField +{ + PACKET_PKT_HEADER_TS_TV_SEC = 1000, + PACKET_PKT_HEADER_INGRESS_GROUP, + PACKET_PKT_HEADER_PKTLEN, + + PACKET_TCP_HEADER_SOURCE_PORT = 2000, + PACKET_TCP_HEADER_FLAGS, + PACKET_UDP_HEADER_SOURCE_PORT, + + PACKET_IP4H_IP_ADDRS_IP_SRC_IA32 = 3000, + PACKET_IP4H_IP_ADDRS_IP_SRC_FAMILY, + PACKET_IP4H_IP_ADDRS_IP_DST_IA32, + PACKET_IP4H_IP_ADDRS_IP_DST_FAMILY, + PACKET_IP4H_IP_PROTO, + + PACKET_FAMILY = 4000, + PACKET_FLAGS, + PACKET_SRC_PORT, + PACKET_DST_PORT, + PACKET_PAYLOAD, + + PACKET_HTTP_HOST = 5000, + PACKET_HTTP_URL, + PACKET_HTTP_METHOD, + PACKET_HTTP_USER_AGENT, + PACKET_HTTP_REFERER, + PACKET_HTTP_VIA, + PACKET_HTTP_RESPONSE_CODE, + PACKET_HTTP_SERVER, + PACKET_HTTP_X_WORKING_WITH, + PACKET_HTTP_CONTENT_TYPE, + + PACKET_IP6H_IP_ADDRS_IP_SRC_IA32 = 6000, + PACKET_IP6H_IP_ADDRS_IP_SRC_FAMILY, + PACKET_IP6H_IP_ADDRS_IP_DST_IA32, + PACKET_IP6H_IP_ADDRS_IP_DST_FAMILY +}; + +struct SessionFileData +{ + uint32_t packetCount; + FILE* file; + char fileName[16]; +}; + +#ifdef MPLS +struct MPLS_Hdr +{ + uint16_t length; + uint8_t* start; +}; +#endif + +// FIXIT-M: Temporary structs and defines for initial appid port. +using tSfPolicyId = int; +#define SE_MAX 255 +struct StreamHAState { }; +// END FIXIT-M + +struct SessionControlBlock +{ + SessionKey* key; + + //MemBucket *proto_specific_data; + void* proto_specific_data; + StreamAppData* appDataList; + + //MemBucket *flowdata; /* add flowbits */ + void* flowdata; /* add flowbits */ + + long last_data_seen; + uint64_t expire_time; + + tSfPolicyId napPolicyId; + tSfPolicyId ipsPolicyId; + bool ips_os_selected; + //SessionConfiguration *session_config; + void* session_config; + void* stream_config; + void* proto_policy; + + //PreprocEnableMask enabled_pps; + uint32_t enabled_pps; + //PreprocEvalFuncNode *initial_pp; + void* initial_pp; + + uint16_t session_state; + uint8_t handler[SE_MAX]; + sfaddr_t client_ip; // FIXTHIS family and bits should be changed to uint16_t + sfaddr_t server_ip; // or uint8_t to reduce sizeof from 24 to 20 + uint16_t client_port; + uint16_t server_port; + bool port_guess; + + uint8_t protocol; + +#ifdef ACTIVE_RESPONSE + uint8_t response_count; +#endif + + uint8_t inner_client_ttl; + uint8_t inner_server_ttl; + uint8_t outer_client_ttl; + uint8_t outer_server_ttl; + + StreamHAState ha_state; + StreamHAState cached_ha_state; + +#ifdef ENABLE_HA + struct timeval ha_next_update; + uint8_t ha_pending_mask; + uint8_t ha_flags; +#endif + + bool session_established; + bool new_session; + + // pointers for linking into list of oneway sessions + struct _SessionControlBlock* ows_prev; + struct _SessionControlBlock* ows_next; + bool in_oneway_list; + + int16_t app_protocol_id[MAX_APP_PROTOCOL_ID]; + +#ifdef MPLS + MPLS_Hdr* clientMplsHeader; + MPLS_Hdr* serverMplsHeader; +#endif +}; + +void sessionFileInit(); +void sessionFileFini(); +FILE* sessionFileProcess(Packet*); +void sessionFileProcessHttp(Packet*, HttpParsedHeaders*); +void sessionFileReadSession(FILE*, SessionControlBlock*); +int sessionFileReadPacket(FILE*, Packet*, HttpParsedHeaders**); + +#endif + diff --git a/src/network_inspectors/appid/test/sf_iph.cc b/src/network_inspectors/appid/test/sf_iph.cc new file mode 100644 index 000000000..6f8e3d3f0 --- /dev/null +++ b/src/network_inspectors/appid/test/sf_iph.cc @@ -0,0 +1,494 @@ +/* $Id: sf_iph.c,v 1.2 2015/03/25 14:45:18 andrbake Exp $ */ +/**************************************************************************** + * + * Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved. + * Copyright (C) 2007-2013 Sourcefire, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. You may not use, modify or + * distribute this program under any other version of the GNU General + * Public License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + ****************************************************************************/ + +#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "decode.h" + +// FIXIT - this must go when snort2.9.x sf_ip.h changes are ported to snort++ +#include "sfaddr_temp.h" + +#define FAILURE -1 +#define SUCCESS 0 +#define IP6_HEADER_LEN 40 + +/* Version is the first four bits of the uint32_t passed in */ +#define IP6_VER(x) \ + (ntohl(x) >> 28) + +/* The 'Packet' structure is almost always allocated on the stack. + * Likewise, return buffers will almost always be aswell. + * So, for performance reasons, argument validation can be disabled + * and removed from the code at compile time to prevent unecessary extra + * conditionals from being checked at run-time. */ +#define ERR_CHK_LVL 0 +#if ERR_CHK_LVL == 2 +#define VALIDATE(x,y) if (!x || !y) return FAILURE; +#elif ERR_CHK_LVL == 1 +#define VALIDATE(x,y) if (!y) return FAILURE; +#else +#define VALIDATE(x,y) +#endif + +sfaddr_t* ip6_ret_src(const Packet* p) +{ + VALIDATE(p, 1); + + return &p->ip6h->ip_addrs->ip_src; +} + +sfaddr_t* orig_ip6_ret_src(const Packet* p) +{ + VALIDATE(p, 1); + + return &p->orig_ip6h->ip_addrs->ip_src; +} + +sfaddr_t* ip6_ret_dst(const Packet* p) +{ + VALIDATE(p, 1); + + return &p->ip6h->ip_addrs->ip_dst; +} + +sfaddr_t* orig_ip6_ret_dst(const Packet* p) +{ + VALIDATE(p, 1); + + return &p->orig_ip6h->ip_addrs->ip_dst; +} + +uint16_t ip6_ret_toc(const Packet* p) +{ + uint16_t toc; + VALIDATE(p,1); + + toc = (uint16_t)((ntohl(p->ip6h->vcl) & 0x0FF00000) >> 20); + + return toc; +} + +uint16_t orig_ip6_ret_toc(const Packet* p) +{ + uint16_t toc; + VALIDATE(p,1); + + toc = (uint16_t)((ntohl(p->orig_ip6h->vcl) & 0x0FF00000) >> 20); + return toc; +} + +uint8_t ip6_ret_hops(const Packet* p) +{ +// VALIDATE(p,1); + + return p->ip6h->hop_lmt; +} + +uint8_t orig_ip6_ret_hops(const Packet* p) +{ +// VALIDATE(p,1); + + return p->orig_ip6h->hop_lmt; +} + +uint16_t ip6_ret_len(const Packet* p) +{ + VALIDATE(p,1); + + /* The length field does not include the header in IPv6, but does in IPv4. + * To make this analogous to IPv4, for Snort's purposes, we need to tack + * on the difference. */ + return p->ip6h->len; +} + +uint16_t orig_ip6_ret_len(const Packet* p) +{ + VALIDATE(p,1); + + return p->orig_ip6h->len; +} + +uint32_t ip6_ret_id(const Packet* p) +{ + IP6Frag* frag_hdr; + if (p->ip6_extension_count == 0) + return 0; + + frag_hdr = (IP6Frag*)p->ip6_extensions[p->ip6_frag_index].data; + + return frag_hdr->ip6f_ident; +} + +uint32_t orig_ip6_ret_id(const Packet* p) +{ +// XXX-IPv6 "NOT YET IMPLEMENTED - IP6 identification" + return 0; +} + +uint8_t ip6_ret_next(const Packet* p) +{ + VALIDATE(p,1); + return p->ip6h->next; +} + +uint8_t orig_ip6_ret_next(const Packet* p) +{ + VALIDATE(p,1); + return p->orig_ip6h->next; +} + +uint16_t ip6_ret_off(const Packet* p) +{ + IP6Frag* frag_hdr; + if (p->ip6_extension_count == 0) + return 0; + + frag_hdr = (IP6Frag*)p->ip6_extensions[p->ip6_frag_index].data; + + return frag_hdr->ip6f_offlg; +} + +uint16_t orig_ip6_ret_off(const Packet* p) +{ +// XXX-IPv6 "NOT YET IMPLEMENTED - IP6 frag offset" + return 0; +} + +uint8_t ip6_ret_ver(const Packet* p) +{ + return (uint8_t)IP6_VER(p->ip6h->vcl); +} + +uint8_t orig_ip6_ret_ver(const Packet* p) +{ + return (uint8_t)IP6_VER(p->orig_ip6h->vcl); +} + +sfaddr_t* ip4_ret_dst(const Packet* p) +{ + VALIDATE(p,1); + return &p->ip4h->ip_addrs->ip_dst; +} + +sfaddr_t* orig_ip4_ret_dst(const Packet* p) +{ + VALIDATE(p,1); + return &p->orig_ip4h->ip_addrs->ip_dst; +} + +sfaddr_t* ip4_ret_src(const Packet* p) +{ + VALIDATE(p,1); + return &p->ip4h->ip_addrs->ip_src; +} + +sfaddr_t* orig_ip4_ret_src(const Packet* p) +{ + VALIDATE(p,1); + return &p->orig_ip4h->ip_addrs->ip_src; +} + +uint16_t ip4_ret_tos(const Packet* p) +{ + VALIDATE(p,1); + + return p->ip4h->ip_tos; +} + +uint16_t orig_ip4_ret_tos(const Packet* p) +{ + VALIDATE(p,1); + + return p->orig_ip4h->ip_tos; +} + +uint8_t ip4_ret_ttl(const Packet* p) +{ + VALIDATE(p,1); + + return p->ip4h->ip_ttl; +} + +uint8_t orig_ip4_ret_ttl(const Packet* p) +{ + VALIDATE(p,1); + + return p->orig_ip4h->ip_ttl; +} + +uint16_t ip4_ret_len(const Packet* p) +{ + VALIDATE(p,1); + + return p->ip4h->ip_len; +} + +uint16_t orig_ip4_ret_len(const Packet* p) +{ + VALIDATE(p,1); + + return p->orig_ip4h->ip_len; +} + +uint32_t ip4_ret_id(const Packet* p) +{ + VALIDATE(p,1); + + return (uint32_t)p->ip4h->ip_id; +} + +uint32_t orig_ip4_ret_id(const Packet* p) +{ + VALIDATE(p,1); + + return (uint32_t)p->orig_ip4h->ip_id; +} + +uint8_t ip4_ret_proto(const Packet* p) +{ + // VALIDATION() + + return p->ip4h->ip_proto; +} + +uint8_t orig_ip4_ret_proto(const Packet* p) +{ + // VALIDATION() + + return p->orig_ip4h->ip_proto; +} + +uint16_t ip4_ret_off(const Packet* p) +{ + return p->ip4h->ip_off; +} + +uint16_t orig_ip4_ret_off(const Packet* p) +{ + return p->orig_ip4h->ip_off; +} + +uint8_t ip4_ret_ver(const Packet* p) +{ + return IP_VER(p->iph); +} + +uint8_t orig_ip4_ret_ver(const Packet* p) +{ + return IP_VER(p->orig_iph); +} + +uint8_t ip4_ret_hlen(const Packet* p) +{ + return IP_HLEN(p->iph); +} + +uint8_t orig_ip4_ret_hlen(const Packet* p) +{ + return IP_HLEN(p->orig_iph); +} + +uint8_t ip6_ret_hlen(const Packet* p) +{ + /* Snort is expecting this number to be in terms of 32 bit words */ + return IP6_HDR_LEN / 4; +} + +uint8_t orig_ip6_ret_hlen(const Packet* p) +{ + return IP6_HDR_LEN / 4; +} + +IPH_API ip4 = +{ + ip4_ret_src, + ip4_ret_dst, + ip4_ret_tos, + ip4_ret_ttl, + ip4_ret_len, + ip4_ret_id, + ip4_ret_proto, + ip4_ret_off, + ip4_ret_ver, + ip4_ret_hlen, + + orig_ip4_ret_src, + orig_ip4_ret_dst, + orig_ip4_ret_tos, + orig_ip4_ret_ttl, + orig_ip4_ret_len, + orig_ip4_ret_id, + orig_ip4_ret_proto, + orig_ip4_ret_off, + orig_ip4_ret_ver, + orig_ip4_ret_hlen, + + IPH_API_V4 +}; + +IPH_API ip6 = +{ + ip6_ret_src, + ip6_ret_dst, + ip6_ret_toc, + ip6_ret_hops, + ip6_ret_len, + ip6_ret_id, + ip6_ret_next, + ip6_ret_off, + ip6_ret_ver, + ip6_ret_hlen, + + orig_ip6_ret_src, + orig_ip6_ret_dst, + orig_ip6_ret_toc, + orig_ip6_ret_hops, + orig_ip6_ret_len, + orig_ip6_ret_id, + orig_ip6_ret_next, + orig_ip6_ret_off, + orig_ip6_ret_ver, + orig_ip6_ret_hlen, + + IPH_API_V6 +}; + +static inline void _set_callbacks(struct _Packet* p, int family, char orig) +{ + if ( !orig ) + { + if (family == AF_INET) + p->iph_api = &ip4; + else + p->iph_api = &ip6; + + p->family = family; + } + else + { + if (family == AF_INET) + p->orig_iph_api = &ip4; + else + p->orig_iph_api = &ip6; + + p->orig_family = family; + } +} + +void set_callbacks(struct _Packet* p, int family, char orig) +{ + _set_callbacks(p, family, orig); +} + +void sfiph_build(Packet* p, const void* hdr, int family) +{ + if (!p) + return; + + _set_callbacks(p, family, CALLBACK_IP); +} + +void sfiph_orig_build(Packet* p, const void* hdr, int family) +{ + IP6RawHdr* hdr6; + IPHdr* hdr4; + + if (!p || !hdr) + return; + + /* If iph_api is already set, we've been here before. + * That means this is a nested IP. */ + if (p->orig_iph_api) + { + memcpy(&p->outer_orig_ips, &p->inner_orig_ips, sizeof(p->outer_orig_ips)); + if (p->orig_iph_api->ver == IPH_API_V4) + { + memcpy(&p->outer_orig_ip4h, &p->inner_orig_ip4h, + sizeof(p->outer_orig_ip4h) - sizeof(p->outer_orig_ip4h.ip_addrs)); + p->outer_orig_ip4h.ip_addrs = &p->outer_orig_ips; + p->outer_orig_iph_api = p->orig_iph_api; + } + else if (p->orig_iph_api->ver == IPH_API_V6) + { + memcpy(&p->outer_orig_ip6h, &p->inner_orig_ip6h, + sizeof(p->outer_orig_ip6h) - sizeof(p->outer_orig_ip6h.ip_addrs)); + p->outer_orig_ip6h.ip_addrs = &p->outer_orig_ips; + p->outer_orig_iph_api = p->orig_iph_api; + } + } + + _set_callbacks(p, family, CALLBACK_ICMP_ORIG); + + if (family == AF_INET) + { + hdr4 = (IPHdr*)hdr; + + /* The struct Snort uses is identical to the actual IP6 struct, + * with the exception of the IP addresses. Copy over everything but + * the IPs */ + memcpy(&p->inner_orig_ip4h, hdr4, sizeof(IPHdr) - 8); + sfip_set_raw(&p->inner_orig_ips.ip_src, &hdr4->ip_src, family); + sfip_set_raw(&p->inner_orig_ips.ip_dst, &hdr4->ip_dst, family); + p->inner_orig_ip4h.ip_addrs = &p->inner_orig_ips; + p->actual_ip_len = ntohs(p->inner_orig_ip4h.ip_len); + p->orig_ip4h = &p->inner_orig_ip4h; + } + else + { + hdr6 = (IP6RawHdr*)hdr; + + /* The struct Snort uses is identical to the actual IP6 struct, + * with the exception of the IP addresses. Copy over everything but + * the IPs*/ + memcpy(&p->inner_orig_ip6h, hdr6, sizeof(IP6RawHdr) - 32); + sfip_set_raw(&p->inner_orig_ips.ip_src, &hdr6->ip6_src, family); + sfip_set_raw(&p->inner_orig_ips.ip_dst, &hdr6->ip6_dst, family); + p->inner_orig_ip6h.ip_addrs = &p->inner_orig_ips; + p->actual_ip_len = ntohs(p->inner_orig_ip6h.len) + IP6_HDR_LEN; + p->orig_ip6h = &p->inner_orig_ip6h; + } +} + +#ifdef TESTER +int main() +{ + Packet p; + IP4Hdr i4; + IP6Hdr i6; + + /* This test assumes we get an IPv4 packet and verifies + * that the correct callbacks are setup, and they return + * the correct values. */ + + _set_callbacks(&p, AF_INET, CALLBACK_IP); + + /* Same test as above, but with IPv6 */ + _set_callbacks(&p, AF_INET6, CALLBACK_IP); + + return 0; +} + +#endif + diff --git a/src/network_inspectors/appid/test/snort.lua b/src/network_inspectors/appid/test/snort.lua new file mode 100644 index 000000000..5700dbf47 --- /dev/null +++ b/src/network_inspectors/appid/test/snort.lua @@ -0,0 +1,47 @@ +--------------------------------------------------------------------------- +-- Snort++ prototype configuration +--------------------------------------------------------------------------- + +--------------------------------------------------------------------------- +-- setup environment +--------------------------------------------------------------------------- +-- given: +-- export DIR=/install/path +-- configure --prefix=$DIR +-- make install +-- +-- then: +-- export LUA_PATH=$DIR/include/snort/lua/?.lua\;\; +-- export SNORT_LUA_PATH=$DIR/conf/ +--------------------------------------------------------------------------- + + + +require("snort_config") + +dir = os.getenv('SNORT_LUA_PATH') + +if ( not dir ) then + dir = '.' +end + +dofile(dir .. '/snort_defaults.lua') + + +appid = +{ + conf = 'rna.conf', + memcap = 15856401, + app_detector_dir = '/var/sf/appid', + thirdparty_appid_dir = '/var/sf/appid/thirdparty_appid', + app_stats_filename = '/var/sf/appid/appid_stats.log', + app_stats_period = 3600, + app_stats_rollover_size = 100000000, + app_stats_rollover_time = 12, + instance_id = 222, + debug = true, + dump_ports = true, +} + + + diff --git a/src/network_inspectors/appid/thirdparty_appid_api.h b/src/network_inspectors/appid/thirdparty_appid_api.h new file mode 100644 index 000000000..4d43ec787 --- /dev/null +++ b/src/network_inspectors/appid/thirdparty_appid_api.h @@ -0,0 +1,102 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// thirdparty_appid_api.h author Sourcefire Inc. + +#ifndef THIRDPARTY_APPID_API_H +#define THIRDPARTY_APPID_API_H + +#include "protocols/packet.h" + +#include "appid_api.h" +#include "thirdparty_appid_types.h" + +#define THIRD_PARTY_APP_ID_API_VERSION 1 + +#define TP_PATH_MAX 4096 + +struct ThirdPartyConfig +{ + unsigned chp_body_collection_max; + unsigned ftp_userid_disabled : 1; + unsigned chp_body_collection_disabled : 1; + unsigned tp_allow_probes : 1; + unsigned http_upgrade_reporting_enabled : 1; + char appid_tp_dir[TP_PATH_MAX]; + int numXffFields; + char** xffFields; + int oldNumXffFields; + char** oldXffFields; +}; + +struct ThirdPartyUtils +{ + void (*logMsg)(const char*, ...); + uint32_t (*getSnortInstance)(); +}; + +using ThirdPartyAppIDModInit = int(*)(ThirdPartyConfig*, ThirdPartyUtils*); +using ThirdPartyAppIDModReconfigure = int(*)(ThirdPartyConfig*); +using ThirdPartyAppIDModFini = int(*)(); +using ThirdPartyAppIDSessionCreate = void*(*)(); +using ThirdPartyAppIDSessionDelete = int(*)(void* tpsession, int just_reset_state); +// FIXIT-L J use references for output parameters +using ThirdPartyAppIDSessionProcess = int(*)( + void* tpsession, // in + Packet*, // in + int direction, // in + AppId*, // out + int* confidence, // out + AppId** proto_list, // out + ThirdPartyAppIDAttributeData** attribute_data // out +); +using ThirdPartyAppIDPrintStats = int(*)(); +using ThirdPartyAppIDResetStats = int(*)(); +using ThirdPartyAppIDDisableFlags = int(*)(void* tpsession, uint32_t session_flags); +using ThirdPartyAppIDSessionStateGet = TPState(*)(void* tpsession); +using ThirdPartyAppIDSessionStateSet = void(*)(void* tpsession, TPState); +using ThirdPartyAppIDSessionAttrSet = void(*)(void* tpsession, TPSessionAttr); +using ThirdPartyAppIDSessionAttrClear = void(*)(void* tpsession, TPSessionAttr); +using ThirdPartyAppIDSessionAttrGet = unsigned(*)(void* tpsession, TPSessionAttr); +using ThirdPartyAppIDSessionCurrenAppIdGet = AppId(*)(void* tpsession); + +// SO_PUBLIC const ThirdPartyAppIDModule thirdparty_appid_impl_module +struct ThirdPartyAppIDModule +{ + const uint32_t api_version; + const char* module_name; + ThirdPartyAppIDModInit init; + ThirdPartyAppIDModReconfigure reconfigure; + ThirdPartyAppIDModFini fini; + ThirdPartyAppIDSessionCreate session_create; + ThirdPartyAppIDSessionDelete session_delete; + ThirdPartyAppIDSessionProcess session_process; + ThirdPartyAppIDPrintStats print_stats; + ThirdPartyAppIDResetStats reset_stats; + ThirdPartyAppIDDisableFlags disable_flags; + + ThirdPartyAppIDSessionStateGet session_state_get; + ThirdPartyAppIDSessionStateSet session_state_set; + ThirdPartyAppIDSessionAttrSet session_attr_set; + ThirdPartyAppIDSessionAttrClear session_attr_clear; + ThirdPartyAppIDSessionAttrGet session_attr_get; + ThirdPartyAppIDSessionCurrenAppIdGet session_appid_get; +}; + +#endif diff --git a/src/network_inspectors/appid/thirdparty_appid_types.h b/src/network_inspectors/appid/thirdparty_appid_types.h new file mode 100644 index 000000000..8fa2b2f8b --- /dev/null +++ b/src/network_inspectors/appid/thirdparty_appid_types.h @@ -0,0 +1,96 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// thirdparty_appid_types.h author Sourcefire Inc. + +#ifndef THIRDPARTY_APPID_TYPES_H +#define THIRDPARTY_APPID_TYPES_H + +#include + +// FIXIT-H J pulled from session_api.h include tree. +// this belongs somewhere else... +#define HTTP_MAX_XFF_FIELDS 255 + +#define TP_SESSION_FLAG_DPI 0x00000001 +#define TP_SESSION_FLAG_MUTABLE 0x00000002 +#define TP_SESSION_FLAG_FUTUREFLOW 0x00000004 +#define TP_SESSION_FLAG_ATTRIBUTE 0x00000008 +#define TP_SESSION_FLAG_TUNNELING 0x00000010 + +enum TPState +{ + TP_STATE_INIT, + TP_STATE_TERMINATED, + TP_STATE_INSPECTING, + TP_STATE_MONITORING, + TP_STATE_CLASSIFIED, + TP_STATE_HA = 21 +}; + +enum TPSessionAttr +{ + TP_ATTR_CONTINUE_MONITORING = (1 << 0), + TP_ATTR_COPY_RESPONSE_CONTENT = (1 << 1), + TP_ATTR_COPY_RESPONSE_LOCATION = (1 << 2), + TP_ATTR_COPY_RESPONSE_BODY = (1 << 3), +}; + +struct XffFieldValue +{ + char* field; + char* value; +}; + +struct ThirdPartyAppIDAttributeData +{ + char* spdyRequestPath; + char* spdyRequestScheme; + char* spdyRequesHost; + char* httpRequesUrl; + char* httpRequestUri; + uint16_t httpRequestUriOffset; + uint16_t httpRequestUriEndOffset; + char* httpRequesHost; + char* httpRequestCookie; + uint16_t httpRequestCookieOffset; + uint16_t httpRequestCookieEndOffset; + char* httpRequestVia; + char* httpResponseVia; + char* httpResponseUpgrade; + char* httpRequestUserAgent; + char* httpResponseVersion; + char* httpResponseCode; + char* httpResponseContent; + char* httpResponseLocation; + char* httpResponseBody; + char* httpRequestBody; + char* httpResponseServer; + char* httpRequestXWorkingWith; + char* tlsHost; + char* tlsCname; + char* tlsOrgUnit; + char* httpRequestReferer; + char* ftpCommandUser; + XffFieldValue xffFieldValue[HTTP_MAX_XFF_FIELDS]; + uint8_t numXffFields; +}; + +#endif + diff --git a/src/network_inspectors/appid/thirdparty_appid_utils.cc b/src/network_inspectors/appid/thirdparty_appid_utils.cc new file mode 100644 index 000000000..a0b0ae685 --- /dev/null +++ b/src/network_inspectors/appid/thirdparty_appid_utils.cc @@ -0,0 +1,202 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// thirdparty_appid_utils.cc author Sourcefire Inc. + +#include "thirdparty_appid_utils.h" + +#include + +#include "main/snort_debug.h" +#include "log/messages.h" +#include "appid_config.h" +#include "thirdparty_appid_api.h" + +#define MODULE_SYMBOL "thirdparty_appid_impl_module" + +THREAD_LOCAL void* module_handle = nullptr; +THREAD_LOCAL struct ThirdPartyConfig thirdpartyConfig; +THREAD_LOCAL ThirdPartyAppIDModule* thirdparty_appid_module = nullptr; + +// FIXIT - these need to be define or otherwise obtained... +static char* defaultXffFields[] = { nullptr /* HTTP_XFF_FIELD_X_FORWARDED_FOR, */ + /* HTTP_XFF_FIELD_TRUE_CLIENT_IP */ }; + +int LoadCallback(const char* const path, int /* indent */) +{ + void* handle; + ThirdPartyAppIDModule* tp_module; + + if (thirdparty_appid_module != nullptr) + { + ErrorMessage("Ignoring additional 3rd party AppID module (%s)!\n", path); + return 0; + } + + handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); + if (handle == nullptr) + { + ErrorMessage("Failed to load 3rd party AppID module: %s - %s\n", path, dlerror()); + return 0; + } + + tp_module = (ThirdPartyAppIDModule*)dlsym(handle, MODULE_SYMBOL); + if (tp_module == nullptr) + { + ErrorMessage("Failed to fine symbol %s in library %s\n", MODULE_SYMBOL, path); + dlclose(handle); + return 0; + } + + if ( (tp_module->api_version != THIRD_PARTY_APP_ID_API_VERSION) + || ((tp_module->module_name == nullptr) || (tp_module->module_name[0] == 0)) + || (tp_module->init == nullptr) + || (tp_module->fini == nullptr) + || (tp_module->session_create == nullptr) + || (tp_module->session_delete == nullptr) + || (tp_module->session_process == nullptr) + || (tp_module->print_stats == nullptr) + || (tp_module->reset_stats == nullptr) + || (tp_module->disable_flags == nullptr) ) + { + ErrorMessage("Ignoring incomplete 3rd party AppID module (%s)!\n", path); + dlclose(handle); + return 0; + } + + DEBUG_WRAP(DebugFormat(DEBUG_APPID, "Found 3rd party AppID module (%s).\n", + tp_module->module_name ? tp_module->module_name : ""); ); + module_handle = handle; + thirdparty_appid_module = tp_module; + return 0; +} + +void ThirdPartyAppIDInit(AppIdModuleConfig* appidStaticConfig) +{ + const char* thirdparty_appid_dir = appidStaticConfig->thirdparty_appid_dir; + int ret; + struct ThirdPartyUtils thirdpartyUtils; + + if ( ( thirdparty_appid_module != nullptr ) || ( thirdparty_appid_dir == nullptr ) + || ( thirdparty_appid_dir[0] == 0 ) ) + return; + + // FIXIT - need to port loadAllLibs function to snort3 + // _dpd.loadAllLibs(thirdparty_appid_dir, LoadCallback); + if (thirdparty_appid_module == nullptr) + { + DEBUG_WRAP(DebugMessage(DEBUG_APPID, "No 3rd party AppID module loaded.\n"); ); + return; + } + + memset(&thirdpartyConfig, 0, sizeof(thirdpartyConfig)); + thirdpartyConfig.chp_body_collection_max = appidStaticConfig->chp_body_collection_max; + thirdpartyConfig.ftp_userid_disabled = appidStaticConfig->ftp_userid_disabled; + thirdpartyConfig.chp_body_collection_disabled = + appidStaticConfig->chp_body_collection_disabled; + thirdpartyConfig.tp_allow_probes = appidStaticConfig->tp_allow_probes; + if (appidStaticConfig->http2_detection_enabled) + thirdpartyConfig.http_upgrade_reporting_enabled = 1; + else + thirdpartyConfig.http_upgrade_reporting_enabled = 0; + thirdpartyConfig.appid_tp_dir[0] = '\0'; // use default path + + // FIXIT - need to provide log function and getSnortInstance function to 3rd party utils +#ifdef REMOVED_WHILE_NOT_IN_USE + thirdpartyUtils.logMsg = &DebugFormat; + thirdpartyUtils.getSnortInstance = _dpd.getSnortInstance; + + // FIXIT - need to get xff fields from http config + thirdpartyConfig.xffFields = _dpd.getHttpXffFields(&thirdpartyConfig.numXffFields); +#endif + + if (!thirdpartyConfig.xffFields) + { + thirdpartyConfig.xffFields = defaultXffFields; + thirdpartyConfig.numXffFields = sizeof(defaultXffFields) / sizeof(defaultXffFields[0]); + } + + ret = thirdparty_appid_module->init(&thirdpartyConfig, &thirdpartyUtils); + if (ret != 0) + { + ErrorMessage("Unable to initialize 3rd party AppID module (%d)!\n", ret); + dlclose(module_handle); + module_handle = nullptr; + thirdparty_appid_module = nullptr; + return; + } + + DEBUG_WRAP(DebugFormat(DEBUG_APPID, + "3rd party AppID module loaded and initialized OK (%s).\n", + thirdparty_appid_module->module_name ? thirdparty_appid_module->module_name : ""); ); +} + +void ThirdPartyAppIDReconfigure(void) +{ + int ret; + + if (thirdparty_appid_module == nullptr) + { + DEBUG_WRAP(DebugMessage(DEBUG_APPID, "No 3rd party AppID module loaded.\n"); ); + return; + } + + thirdpartyConfig.oldNumXffFields = thirdpartyConfig.numXffFields; + thirdpartyConfig.oldXffFields = thirdpartyConfig.xffFields; + + // FIXIT - need to get xff fields from http config + // thirdpartyConfig.xffFields = _dpd.getHttpXffFields(&thirdpartyConfig.numXffFields); + if (!thirdpartyConfig.xffFields) + { + thirdpartyConfig.xffFields = defaultXffFields; + thirdpartyConfig.numXffFields = sizeof(defaultXffFields) / sizeof(defaultXffFields[0]); + } + + ret = thirdparty_appid_module->reconfigure(&thirdpartyConfig); + if (ret != 0) + { + ErrorMessage("Unable to reconfigure 3rd party AppID module (%d)!\n", ret); + return; + } + + DEBUG_WRAP(DebugFormat(DEBUG_APPID, "3rd party AppID module reconfigured OK (%s).\n", + thirdparty_appid_module->module_name ? thirdparty_appid_module->module_name : ""); ); +} + +void ThirdPartyAppIDFini(void) +{ + int ret; + + if (thirdparty_appid_module != nullptr) + { + ret = thirdparty_appid_module->fini(); + if (ret != 0) + { + ErrorMessage("Could not finalize 3rd party AppID module (%d)!\n", ret); + } + + dlclose(module_handle); + module_handle = nullptr; + thirdparty_appid_module = nullptr; + + DEBUG_WRAP(DebugMessage(DEBUG_APPID, + "3rd party AppID module finalized and unloaded OK.\n"); ); + } +} + diff --git a/src/network_inspectors/appid/thirdparty_appid_utils.h b/src/network_inspectors/appid/thirdparty_appid_utils.h new file mode 100644 index 000000000..ad8b3df8c --- /dev/null +++ b/src/network_inspectors/appid/thirdparty_appid_utils.h @@ -0,0 +1,37 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// thirdparty_appid_utils.h author Sourcefire Inc. + +#ifndef THIRDPARTY_APPID_UTILS_H +#define THIRDPARTY_APPID_UTILS_H + +#include "main/thread.h" + +class AppIdModuleConfig; +struct ThirdPartyAppIDModule; + +extern THREAD_LOCAL ThirdPartyAppIDModule* thirdparty_appid_module; // nullptr means no 3rd party AppID module + +void ThirdPartyAppIDInit(AppIdModuleConfig*); +void ThirdPartyAppIDReconfigure(); +void ThirdPartyAppIDFini(); + +#endif + diff --git a/src/network_inspectors/appid/util/CMakeLists.txt b/src/network_inspectors/appid/util/CMakeLists.txt new file mode 100644 index 000000000..7e7447408 --- /dev/null +++ b/src/network_inspectors/appid/util/CMakeLists.txt @@ -0,0 +1,31 @@ +set ( UTIL_SOURCES + common_util.h + fw_avltree.cc + fw_avltree.h + ip_funcs.cc + ip_funcs.h + network_set.cc + network_set.h + output_file.cc + output_file.h + sfksearch.cc + sfksearch.h + sf_mlmp.cc + sf_mlmp.h + sf_multi_mpse.cc + sf_multi_mpse.h + sfutil.cc + sfutil.h + ) + +add_library ( appid_util STATIC + ${UTIL_SOURCES} +) + +target_include_directories ( appid_util PRIVATE ${APPID_INCLUDE_DIR} ) + +# FIXIT-H: Add unit tests + +#install (FILES ${UTIL_INCLUDES} +# DESTINATION "${INCLUDE_INSTALL_PATH}/appid/util" +#) diff --git a/src/network_inspectors/appid/util/Makefile.am b/src/network_inspectors/appid/util/Makefile.am new file mode 100644 index 000000000..f19498c0c --- /dev/null +++ b/src/network_inspectors/appid/util/Makefile.am @@ -0,0 +1,29 @@ + +AM_CPPFLAGS+=-I$(top_srcdir)/src/network_inspectors/appid + +file_list = \ +common_util.h \ +fw_avltree.cc \ +fw_avltree.h \ +ip_funcs.cc \ +ip_funcs.h \ +network_set.cc \ +network_set.h \ +output_file.cc \ +output_file.h \ +sfksearch.cc \ +sfksearch.h \ +sf_mlmp.cc \ +sf_mlmp.h \ +sf_multi_mpse.cc \ +sf_multi_mpse.h \ +sfutil.cc \ +sfutil.h + +noinst_LIBRARIES = libappid_util.a +libappid_util_a_SOURCES = $(file_list) + +# FIXIT - H Uncomment once tests are ready. +#if BUILD_UNIT_TESTS +#SUBDIRS = test +#endif \ No newline at end of file diff --git a/src/network_inspectors/appid/util/common_util.h b/src/network_inspectors/appid/util/common_util.h new file mode 100644 index 000000000..d0b9f6f73 --- /dev/null +++ b/src/network_inspectors/appid/util/common_util.h @@ -0,0 +1,103 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// common_util.h author Sourcefire Inc. + +#ifndef COMMON_UTIL_H +#define COMMON_UTIL_H + +#include +#include +#include +#include + +#include "framework/decode_data.h" + +struct FWDebugSessionConstraints +{ + ip::snort_in6_addr sip; + int sip_flag; + ip::snort_in6_addr dip; + int dip_flag; + uint16_t sport; + uint16_t dport; + PktType protocol; +}; + +#define FW_DEBUG_SESSION_ID_SIZE (39+1+5+4+39+1+5+1+3+1+1+1+2+1+10+1+1+1+10+1) + +struct ConfigItem +{ + char* name; /* name of the config item */ + char* value; /* config item value */ +}; + +#define MAX_LINE 2048 +#define MAX_TOKS 256 + +inline void DumpHex(FILE* fp, const uint8_t* data, unsigned len) +{ + char str[18]; + unsigned i; + unsigned pos; + char c; + + for (i=0, pos=0; i +#include + +#include "utils/util.h" + +static inline int is_root(FwAvlNode* node) { return (node->parent == nullptr); } +static inline int get_balance(FwAvlNode* node) { return node->balance; } +static inline void set_balance(int balance, FwAvlNode* node) { node->balance = balance; } +static inline int inc_balance(FwAvlNode* node) { return ++node->balance; } +static inline int dec_balance(FwAvlNode* node) { return --node->balance; } +static inline FwAvlNode* get_parent(FwAvlNode* node) { return node->parent; } + +static inline void set_parent(FwAvlNode* parent, FwAvlNode* node) +{ + node->parent = parent; +} + +static inline FwAvlNode* new_node(uint32_t key, void* data) +{ + FwAvlNode* node = (FwAvlNode*)snort_calloc(sizeof(FwAvlNode)); + + node->key = key; + node->data = data; + return node; +} + +static inline FwAvlNode* get_first(FwAvlNode* node) +{ + while (node->left != nullptr) + node = node->left; + return node; +} + +static inline FwAvlNode* get_last(FwAvlNode* node) +{ + while (node->right != nullptr) + node = node->right; + return node; +} + +FwAvlNode* fwAvlFirst(const FwAvlTree* tree) +{ + if ((tree != 0) && (tree->root != 0)) + return get_first(tree->root); + else + return nullptr; +} + +FwAvlNode* fwAvlLast(const FwAvlTree* tree) +{ + if ((tree != 0) && (tree->root != 0)) + return get_last(tree->root); + else + return nullptr; +} + +FwAvlNode* fwAvlNext(FwAvlNode* node) +{ + FwAvlNode* parent = nullptr; + FwAvlNode* tmp; + + if (node->right != nullptr) + { + return get_first(node->right); + } + else + { + tmp = node; + while ( ((parent = get_parent(tmp)) != nullptr) && (parent->right == tmp) ) + tmp = parent; + } + return parent; +} + +FwAvlNode* fwAvlPrev(FwAvlNode* node) +{ + FwAvlNode* parent; + FwAvlNode* tmp; + + if (node->left != nullptr) + { + tmp = get_first(node->left); + } + else + { + tmp = node; + while ( ((parent = get_parent(tmp)) != nullptr) && (parent->left == tmp) ) + tmp = parent; + } + return tmp; +} + +static void rotate_left(FwAvlNode* node, FwAvlTree* tree) +{ + FwAvlNode* p = node; + FwAvlNode* q = node->right; + FwAvlNode* parent = get_parent(node); + + if (!is_root(p)) + { + if (parent->left == p) + parent->left = q; + else + parent->right = q; + } + else + { + tree->root = q; + } + + set_parent(parent, q); + set_parent(q, p); + + p->right = q->left; + if (p->right != nullptr) + { + set_parent(p, p->right); + } + q->left = p; +} + +static void rotate_right(FwAvlNode* node, FwAvlTree* tree) +{ + FwAvlNode* p = node; + FwAvlNode* q = node->left; + FwAvlNode* parent = get_parent(node); + + if (!is_root(p)) + { + if (parent->left == p) + parent->left = q; + else + parent->right = q; + } + else + { + tree->root = q; + } + + set_parent(parent, q); + set_parent(q, p); + + p->left = q->right; + if (p->left != nullptr) + { + set_parent(p, p->left); + } + q->right = p; +} + +static inline FwAvlNode* do_lookup(const uint32_t key, + const FwAvlTree* tree, FwAvlNode** pparent, + FwAvlNode** unbalanced, int* is_left) +{ + FwAvlNode* node = tree->root; + + *pparent = nullptr; + *unbalanced = node; + *is_left = 0; + + while (node != nullptr) + { + if (get_balance(node) != 0) + { + *unbalanced = node; + } + + *pparent = node; + + if (key == node->key) + { + return node; + } + else + { + if ((*is_left = node->key > key) != 0) + node = node->left; + else + node = node->right; + } + } + return nullptr; +} + +void* fwAvlLookup(const uint32_t key, const FwAvlTree* tree) +{ + FwAvlNode* node = nullptr; + FwAvlNode* pparent; + FwAvlNode* unbalanced; + int is_left; + + if (tree == 0) + { + return nullptr; + } + + node = do_lookup(key, tree, &pparent, &unbalanced, &is_left); + + if (node != nullptr) + { + return node->data; + } + + return nullptr; +} + +static inline void set_child(FwAvlNode* child, FwAvlNode* node, + int left) +{ + if (left != 0) + node->left = child; + else + node->right = child; +} + +int fwAvlInsert(uint32_t key, void* data, FwAvlTree* tree) +{ + int is_left; + FwAvlNode* parent; + FwAvlNode* right; + FwAvlNode* left; + FwAvlNode* unbalanced; + FwAvlNode* node; + + if (do_lookup(key, tree, &parent, &unbalanced, &is_left) != nullptr) + return 1; + + if (!(node = new_node(key, data))) + return -1; + + tree->count++; + if (parent == nullptr) + { + tree->root = node; + tree->first = node; + tree->last = node; + return 0; + } + + if (is_left != 0) + { + if (parent == tree->first) + tree->first = node; + } + else + { + if (parent == tree->last) + tree->last = node; + } + set_parent(parent, node); + set_child(node, parent, is_left); + + for (;; ) + { + if (parent->left == node) + dec_balance(parent); + else + inc_balance(parent); + + if (parent == unbalanced) + break; + + node = parent; + parent = get_parent(node); + } + + switch (get_balance(unbalanced)) + { + case 1: + case -1: + tree->height++; + break; + case 0: + break; + case 2: + right = unbalanced->right; + + if (get_balance(right) == 1) + { + set_balance(0, unbalanced); + set_balance(0, right); + } + else + { + switch (get_balance(right->left)) + { + case 1: + set_balance(-1, unbalanced); + set_balance(0, right); + break; + case 0: + set_balance(0, unbalanced); + set_balance(0, right); + break; + case -1: + set_balance(0, unbalanced); + set_balance(1, right); + break; + } + set_balance(0, right->left); + rotate_right(right, tree); + } + rotate_left(unbalanced, tree); + break; + case -2: + left = unbalanced->left; + + if (get_balance(left) == -1) + { + set_balance(0, unbalanced); + set_balance(0, left); + } + else + { + switch (get_balance(left->right)) + { + case 1: + set_balance(0, unbalanced); + set_balance(-1, left); + break; + case 0: + set_balance(0, unbalanced); + set_balance(0, left); + break; + case -1: + set_balance(1, unbalanced); + set_balance(0, left); + break; + } + set_balance(0, left->right); + rotate_left(left, tree); + } + rotate_right(unbalanced, tree); + break; + } + return 0; +} + +FwAvlTree* fwAvlInit() +{ + return (FwAvlTree*)snort_calloc(sizeof(FwAvlTree)); +} + +FwQNode* newFwQNode(FwAvlNode* treeNode) +{ + FwQNode* q_node = (FwQNode*)snort_calloc(sizeof(FwQNode)); + + q_node->treeNode = treeNode; + q_node->next = nullptr; + return(q_node); +} + +FwQNode* fwAvlSerialize(FwAvlTree* tree) +{ + FwQNode* head; + FwQNode* node; + FwQNode* tail; + + if ((tree == nullptr) || (tree->root == nullptr)) + return nullptr; + + head = newFwQNode(tree->root); + node = head; + tail = head; + + while (node) + { + if (node->treeNode->left != nullptr) + { + tail->next = newFwQNode(node->treeNode->left); + tail = tail->next; + } + + if (node->treeNode->right != nullptr) + { + tail->next = newFwQNode(node->treeNode->right); + tail = tail->next; + } + + node = node->next; + } + return head; +} + +void fwAvlDeleteTree(FwAvlTree* tree, void (* dataDelete)(void* data)) +{ + FwQNode* node = fwAvlSerialize(tree); + FwQNode* tmp; + + while (node != nullptr) + { + if (dataDelete) + dataDelete(node->treeNode->data); + snort_free(node->treeNode); + tmp = node; + node = node->next; + snort_free(tmp); + } + snort_free(tree); +} + diff --git a/src/network_inspectors/appid/util/fw_avltree.h b/src/network_inspectors/appid/util/fw_avltree.h new file mode 100644 index 000000000..ac7853b1f --- /dev/null +++ b/src/network_inspectors/appid/util/fw_avltree.h @@ -0,0 +1,64 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// fw_avltree.h author Sourcefire Inc. + +#ifndef FW_AVL_TREE_H +#define FW_AVL_TREE_H + +#include +#include + +struct FwAvlNode +{ + uint32_t key; + void* data; + int balance; + FwAvlNode* left; + FwAvlNode* right; + FwAvlNode* parent; +}; + +struct FwAvlTree +{ + unsigned count; + size_t height; + FwAvlNode* root; + FwAvlNode* first; + FwAvlNode* last; +}; + +struct FwQNode +{ + FwAvlNode* treeNode; + FwQNode* next; +}; + +FwAvlTree* fwAvlInit(); +int fwAvlInsert(uint32_t key, void* data, FwAvlTree* tree); +void* fwAvlLookup(const uint32_t key, const FwAvlTree* tree); +FwAvlNode* fwAvlFirst(const FwAvlTree* tree); +FwAvlNode* fwAvlLast(const FwAvlTree* tree); +FwAvlNode* fwAvlNext(FwAvlNode* node); +FwAvlNode* fwAvlPrev(FwAvlNode* node); +FwQNode* fwAvlSerialize(FwAvlTree* tree); +void fwAvlDeleteTree(FwAvlTree* tree, void (* dataDelete)(void* data)); + +#endif + diff --git a/src/network_inspectors/appid/util/ip_funcs.cc b/src/network_inspectors/appid/util/ip_funcs.cc new file mode 100644 index 000000000..524f19f07 --- /dev/null +++ b/src/network_inspectors/appid/util/ip_funcs.cc @@ -0,0 +1,207 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// ip_funcs.cc author Sourcefire Inc. + +#include "ip_funcs.h" + +#include "sfutil.h" +#include "log/messages.h" +#include "utils/util.h" + +RNAIpAddrSet* ParseIpCidr(char* ipstring, uint32_t* netmasks) +{ + char* toks[2]; + int num_toks; + RNAIpAddrSet* ias; + char* cp; + struct in_addr ia; + + if (ipstring == nullptr) + return nullptr; + + ias = (RNAIpAddrSet*)snort_calloc(sizeof(RNAIpAddrSet)); + strip(ipstring); + cp = ipstring; + if (*cp == 'h') + { + ias->addr_flags |= IPFUNCS_HOSTS_IP; + cp++; + } + + if (*cp == 's') + { + ias->addr_flags |= IPFUNCS_APPLICATION; + cp++; + } + + if (*cp == '!') + { + ias->addr_flags |= IPFUNCS_EXCEPT_IP; + cp++; + } + + if (!strcasecmp(ipstring, "any")) + { + ias->range_max = ~0; + return ias; + } + + num_toks = Split(cp, toks, 2, "/"); + + if (inet_pton(AF_INET, toks[0], &ia) <= 0) + { + ErrorMessage("IPFunctions: %s failed to translate", toks[0]); + snort_free(ias); + return nullptr; + } + + ias->range_min = ntohl(ia.s_addr); + + if (num_toks > 1) + { + ias->netmask = (unsigned)strtoul(toks[1], nullptr, 0); + + if (ias->netmask < 32) + { + ias->netmask_mask = netmasks[ias->netmask]; + ias->range_min &= ias->netmask_mask; + ias->range_max = ias->range_min + ~ias->netmask_mask; + } + else + { + ias->netmask = 32; + ias->netmask_mask = netmasks[ias->netmask]; + ias->range_min &= ias->netmask_mask; + ias->range_max = ias->range_min; + } + } + else + { + ias->netmask = 32; + ias->netmask_mask = netmasks[ias->netmask]; + ias->range_min &= ias->netmask_mask; + ias->range_max = ias->range_min; + } + + return ias; +} + +RNAIpv6AddrSet* ParseIpv6Cidr(char* ipstring) +{ + char* toks[2]; + int num_toks; + RNAIpv6AddrSet* ias; + char* cp; + struct in6_addr ia; + + if (ipstring == nullptr) + return nullptr; + + ias = (RNAIpv6AddrSet*)snort_calloc(sizeof(*ias)); + strip(ipstring); + cp = ipstring; + if (*cp == 'h') + { + ias->addr_flags |= IPFUNCS_HOSTS_IP; + cp++; + } + + if (*cp == 's') + { + ias->addr_flags |= IPFUNCS_APPLICATION; + cp++; + } + + if (*cp == '!') + { + ias->addr_flags |= IPFUNCS_EXCEPT_IP; + cp++; + } + + if (!strcasecmp(ipstring, "any")) + { + ias->range_max.lo = ULLONG_MAX; + ias->range_max.hi = ULLONG_MAX; + return ias; + } + + num_toks = Split(cp, toks, 2, "/"); + + if (inet_pton(AF_INET6, toks[0], &ia) <= 0) + { + ErrorMessage("IPFunctions: %s failed to translate", toks[0]); + snort_free(ias); + return nullptr; + } + memcpy(&ias->range_min, (const void*)&ia, sizeof(ias->range_min)); + NSIPv6AddrNtoH(&ias->range_min); + + if (num_toks > 1) + { + ias->netmask = (unsigned)strtoul(toks[1], nullptr, 0); + + /* Convert cidr to netmask */ + if (!ias->netmask) + { + ias->range_max.hi = ULLONG_MAX; + ias->range_max.lo = ULLONG_MAX; + } + else if (ias->netmask < 64) + { + ias->netmask_mask.hi = ULLONG_MAX << (64 - ias->netmask); + ias->range_min.hi &= ias->netmask_mask.hi; + ias->range_min.lo = 0; + ias->range_max.hi = ias->range_min.hi + ~ias->netmask_mask.hi; + ias->range_max.lo = ULLONG_MAX; + } + else if (ias->netmask == 64) + { + ias->netmask_mask.hi = ULLONG_MAX; + ias->range_min.hi &= ias->netmask_mask.hi; + ias->range_min.lo = 0; + ias->range_max.hi = ias->range_min.hi + ~ias->netmask_mask.hi; + ias->range_max.lo = ULLONG_MAX; + } + else if (ias->netmask < 128) + { + ias->netmask_mask.hi = ULLONG_MAX; + ias->netmask_mask.lo = ULLONG_MAX << (128 - ias->netmask); + ias->range_min.lo &= ias->netmask_mask.lo; + ias->range_max.hi = ias->range_min.hi; + ias->range_max.lo = ias->range_min.lo + ~ias->netmask_mask.lo; + } + else + { + ias->netmask_mask.hi = ULLONG_MAX; + ias->netmask_mask.lo = ULLONG_MAX; + ias->range_max = ias->range_min; + } + } + else + { + ias->netmask = 128; + ias->netmask_mask.lo = ULLONG_MAX; + ias->netmask_mask.hi = ULLONG_MAX; + ias->range_max = ias->range_min; + } + + return ias; +} + diff --git a/src/network_inspectors/appid/util/ip_funcs.h b/src/network_inspectors/appid/util/ip_funcs.h new file mode 100644 index 000000000..dab9cfd1a --- /dev/null +++ b/src/network_inspectors/appid/util/ip_funcs.h @@ -0,0 +1,83 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// ip_funcs.h author Sourcefire Inc. + +#ifndef IP_FUNCS_H +#define IP_FUNCS_H + +#include +#include "protocols/ipv6.h" +#include "network_set.h" + +// FIXIT - this must go when snort2.9.x sf_ip.h changes are ported to snort++ +#include "sfaddr_temp.h" + +#define IPFUNCS_EXCEPT_IP 0x01 +#define IPFUNCS_SECONDARY_IP 0x02 +#define IPFUNCS_APPID_SESSION_EXCLUDE_IP 0x04 +#define IPFUNCS_USER_IP 0x08 +#define IPFUNCS_HOSTS_IP 0x10 +#define IPFUNCS_APPLICATION 0x20 +#define IPFUNCS_CHECKED 0x80000000 + +struct RNAIpAddrSet +{ + uint32_t range_min; + uint32_t range_max; + uint32_t addr_flags; + unsigned netmask; + uint32_t netmask_mask; +}; + +RNAIpAddrSet* ParseIpCidr(char*, uint32_t*); + +struct RNAIpv6AddrSet +{ + NSIPv6Addr range_min; + NSIPv6Addr range_max; + uint32_t addr_flags; + unsigned netmask; + NSIPv6Addr netmask_mask; +}; + +RNAIpv6AddrSet* ParseIpv6Cidr(char*); + +inline void copyIpv4ToIpv6Network(ip::snort_in6_addr* keyIp, const uint32_t ip) +{ + keyIp->u6_addr32[0] = keyIp->u6_addr32[1] = 0; + keyIp->u6_addr16[4] = 0; + keyIp->u6_addr16[5] = 0xFFFF; + keyIp->u6_addr32[3] = ip; +} + +//these functions are needed since snort does not store IPv4 address in highest 4 bytes +//of 16 byte ip. +inline void copySnortIpToIpv6Network(ip::snort_in6_addr* keyIp, const sfip_t* snortIp) +{ + memcpy(keyIp, snortIp->ip32, sizeof(*keyIp)); +} + +inline int cmpSnortIpToHostKey(const ip::snort_in6_addr* keyIp, const sfip_t* snortIp) +{ + return memcmp(keyIp, snortIp->ip32, sizeof(*keyIp)); +} + +#endif + diff --git a/src/network_inspectors/appid/util/network_set.cc b/src/network_inspectors/appid/util/network_set.cc new file mode 100644 index 000000000..fd27c17b5 --- /dev/null +++ b/src/network_inspectors/appid/util/network_set.cc @@ -0,0 +1,1252 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// network_set.cc author Sourcefire Inc. + +#include "network_set.h" + +#include "log/messages.h" +#include "utils/util.h" + +int NetworkSet_New(NetworkSet** network_set) +{ + NetworkSet* tmp = nullptr; + + if (!network_set) + return -1; + + tmp = (NetworkSet*)snort_calloc(sizeof(NetworkSet)); + sflist_init(&tmp->networks); + tmp->ids = sfxhash_new(64, sizeof(unsigned), 0, 0, 0, nullptr, nullptr, 1); + if (tmp->ids == nullptr) + { + ErrorMessage("NetworkSet:Out of memory (wanted %zu bytes)", sizeof(NetworkSet)); + NetworkSet_Destroy(tmp); + return -1; + } + + sflist_init(&tmp->networks6); + tmp->ids6 = sfxhash_new(64, sizeof(unsigned), 0, 0, 0, nullptr, nullptr, 1); + if (tmp->ids6 == nullptr) + { + ErrorMessage("NetworkSet:Out of memory (wanted %zu bytes)", sizeof(NetworkSet)); + NetworkSet_Destroy(tmp); + return -1; + } + + *network_set = tmp; + + return 0; +} + +int NetworkSet_Destroy(NetworkSet* network_set) +{ + if (!network_set) + return -1; + + if (network_set->pnetwork) + { + snort_free(network_set->pnetwork); + network_set->pnetwork = nullptr; + } + sflist_static_free_all(&network_set->networks, &snort_free); + sfxhash_delete(network_set->ids); + if (network_set->pnetwork6) + { + snort_free(network_set->pnetwork6); + network_set->pnetwork6 = nullptr; + } + sflist_static_free_all(&network_set->networks6, &snort_free); + sfxhash_delete(network_set->ids6); + snort_free(network_set); + + return 0; +} + +int NetworkSet_AddNetworkRangeEx(NetworkSet* network_set, uint32_t range_min, + uint32_t range_max, unsigned cidr_bits, int ip_not, unsigned id, unsigned type) +{ + Network* network; + Network* iNetwork; + int rval; + + if (!network_set) + return -1; + + network = (Network*)snort_calloc(sizeof(Network)); + network->info.id = id; + network->info.ip_not = ip_not; + network->info.type = type; + network->info.netmask = cidr_bits; + if (range_min <= range_max) + { + network->range_min = range_min; + network->range_max = range_max; + } + else + { + network->range_min = range_max; + network->range_max = range_min; + } + + if (!network->info.ip_not) + { + SF_LNODE* iter = nullptr; + + for (iNetwork = (Network*)sflist_first(&network_set->networks, &iter); + iNetwork; + iNetwork = (Network*)sflist_next(&iter)) + { + if (iNetwork->info.id == network->info.id && + iNetwork->range_min == network->range_min && + iNetwork->range_max == network->range_max) + { + iNetwork->info.type |= network->info.type; + snort_free(network); + return 0; + } + } + } + + if (sflist_add_tail(&network_set->networks, (void*)network)) + { + ErrorMessage("NetworkSet:Out of memory"); + snort_free(network); + return -1; + } + + rval = sfxhash_add(network_set->ids, &network->info.id, &network->info.id); + if (rval != SFXHASH_OK && rval != SFXHASH_INTABLE) + { + ErrorMessage("NetworkSet:Out of memory"); + snort_free(network); + return -1; + } + + return 0; +} + +int NetworkSet_AddNetworkRange(NetworkSet* network_set, uint32_t range_min, + uint32_t range_max, unsigned cidr_bits, int ip_not, unsigned id) +{ + return NetworkSet_AddNetworkRangeEx(network_set, range_min, range_max, cidr_bits, ip_not, id, + 0); +} + +int NetworkSet_AddNetworkRange6Ex(NetworkSet* network_set, NSIPv6Addr* range_min, + NSIPv6Addr* range_max, unsigned cidr_bits, int ip_not, unsigned id, unsigned type) +{ + Network6* network; + Network6* iNetwork; + int rval; + + if (!network_set) + return -1; + + network = (Network6*)snort_calloc(sizeof(Network6)); + network->info.id = id; + network->info.ip_not = ip_not; + network->info.type = type; + network->info.netmask = cidr_bits; + if (NSIPv6AddrCompare(range_min, range_max) <= 0) + { + network->range_min = *range_min; + network->range_max = *range_max; + } + else + { + network->range_min = *range_max; + network->range_max = *range_min; + } + + if (!network->info.ip_not) + { + SF_LNODE* iter = nullptr; + + for (iNetwork = (Network6*)sflist_first(&network_set->networks6, &iter); + iNetwork; + iNetwork = (Network6*)sflist_next(&iter)) + { + if (iNetwork->info.id == network->info.id && + !NSIPv6AddrCompare(&iNetwork->range_min, &network->range_min) && + !NSIPv6AddrCompare(&iNetwork->range_max, &network->range_max)) + { + iNetwork->info.type |= network->info.type; + snort_free(network); + return 0; + } + } + } + + // FIXIT - these sflist funcs probably should never fail now either since they use snort_calloc... + if (sflist_add_tail(&network_set->networks6, (void*)network)) + { + ErrorMessage("NetworkSet:Out of memory"); + snort_free(network); + return -1; + } + rval = sfxhash_add(network_set->ids6, &network->info.id, &network->info.id); + if (rval != SFXHASH_OK && rval != SFXHASH_INTABLE) + { + ErrorMessage("NetworkSet:Out of memory"); + snort_free(network); + return -1; + } + + return 0; +} + +int NetworkSet_AddNetworkRange6(NetworkSet* network_set, NSIPv6Addr* range_min, + NSIPv6Addr* range_max, unsigned cidr_bits, int ip_not, unsigned id) +{ + return NetworkSet_AddNetworkRange6Ex(network_set, range_min, range_max, cidr_bits, ip_not, id, + 0); +} + +int NetworkSet_AddNetworkRangeOnlyIPv6(NetworkSet* network_set, int ip_not, unsigned id, unsigned + type) +{ + // Use two ranges to represent all of IPv6, excluding the IPv4-mapped range, ::FFFF:*.*.*.* + int rval; + NSIPv6Addr range_min, range_max; + range_min.lo = 0; + range_min.hi = 0; + range_max.lo = 0x0000FFFEFFFFFFFFULL; // 0x0000FFFF00000000 - 1 + range_max.hi = 0; + rval = NetworkSet_AddNetworkRange6Ex(network_set, &range_min, &range_max, 0, ip_not, id, type); + range_min.lo = 0x0001000000000000ULL; // 0x0000FFFFFFFFFFFF + 1 + range_min.hi = 0; + range_max.lo = 0xFFFFFFFFFFFFFFFFULL; + range_max.hi = 0xFFFFFFFFFFFFFFFFULL; + return rval ? rval : NetworkSet_AddNetworkRange6Ex(network_set, &range_min, &range_max, 0, + ip_not, id, type); +} + +static inline int NetworkSet_AddNetwork(NetworkSet* network_set, uint32_t ip, + unsigned cidr_bits, uint32_t mask, int ip_not, unsigned id, unsigned type) +{ + uint32_t range_min; + uint32_t range_max; + + range_min = ip & mask; + range_max = range_min + ~mask; + return NetworkSet_AddNetworkRangeEx(network_set, range_min, range_max, cidr_bits, ip_not, id, + type); +} + +int NetworkSet_AddCidrBlockEx(NetworkSet* network_set, uint32_t ip, + unsigned cidr_bits, int ip_not, unsigned id, unsigned type) +{ + uint32_t mask; + + if (cidr_bits > 32) + return -1; + + /* Convert cidr to netmask */ + if (cidr_bits == 0) + mask = 0; + else + mask = 0xffffffff << (32 - cidr_bits); + + return NetworkSet_AddNetwork(network_set, ip, cidr_bits, mask, ip_not, id, type); +} + +int NetworkSet_AddCidrBlock(NetworkSet* network_set, uint32_t ip, + unsigned cidr_bits, int ip_not, unsigned id) +{ + return NetworkSet_AddCidrBlockEx(network_set, ip, cidr_bits, ip_not, id, 0); +} + +static inline int NetworkSet_AddNetwork6(NetworkSet* network_set, NSIPv6Addr* ip, + unsigned cidr_bits, NSIPv6Addr* mask, int ip_not, unsigned id, unsigned type) +{ + NSIPv6Addr range_min; + NSIPv6Addr range_max; + + range_min.lo = ip->lo & mask->lo; + range_min.hi = ip->hi & mask->hi; + range_max.lo = range_min.lo + ~mask->lo; + range_max.hi = range_min.hi + ~mask->hi; + return NetworkSet_AddNetworkRange6Ex(network_set, &range_min, &range_max, cidr_bits, ip_not, + id, type); +} + +int NetworkSet_AddCidrBlock6Ex(NetworkSet* network_set, NSIPv6Addr* ip, + unsigned cidr_bits, int ip_not, unsigned id, unsigned type) +{ + NSIPv6Addr mask; + + if (cidr_bits > 128) + return -1; + + /* Convert cidr to netmask */ + if (!cidr_bits) + { + mask.hi = 0; + mask.lo = 0; + } + else if (cidr_bits < 64) + { + mask.hi = ULLONG_MAX << (64 - cidr_bits); + mask.lo = 0; + } + else if (cidr_bits == 64) + { + mask.hi = ULLONG_MAX; + mask.lo = 0; + } + else + { + mask.hi = ULLONG_MAX; + mask.lo = ULLONG_MAX << (128 - cidr_bits); + } + + return NetworkSet_AddNetwork6(network_set, ip, cidr_bits, &mask, ip_not, id, type); +} + +int NetworkSet_AddCidrBlock6(NetworkSet* network_set, NSIPv6Addr* ip, + unsigned cidr_bits, int ip_not, unsigned id) +{ + return NetworkSet_AddCidrBlock6Ex(network_set, ip, cidr_bits, ip_not, id, 0); +} + +static inline void NetworkList_Fprintf(NetworkSet* network_set, const char* prefix, FILE* stream) +{ + Network* network; + Network6* network6; + struct in_addr four; + NSIPv6Addr six; + SF_LNODE* iter = nullptr; + char min_ip[INET6_ADDRSTRLEN]; + char max_ip[INET6_ADDRSTRLEN]; + + for (network = (Network*)sflist_first(&network_set->networks, &iter); + network; + network = (Network*)sflist_next(&iter)) + { + four.s_addr = htonl(network->range_min); + inet_ntop(AF_INET, &four, min_ip, sizeof(min_ip)); + four.s_addr = htonl(network->range_max); + inet_ntop(AF_INET, &four, max_ip, sizeof(max_ip)); + + /* check containment for this network */ + fprintf(stream, "%s%s%s-%s for %u with %08X\n", prefix, network->info.ip_not ? "!" : "", + min_ip, max_ip, + network->info.id, network->info.type); + } + + for (network6 = (Network6*)sflist_first(&network_set->networks6, &iter); + network6; + network6 = (Network6*)sflist_next(&iter)) + { + six = network6->range_min; + NSIPv6AddrHtoN(&six); + inet_ntop(AF_INET6, (struct in6_addr*)&six, min_ip, sizeof(min_ip)); + six = network6->range_max; + NSIPv6AddrHtoN(&six); + inet_ntop(AF_INET6, (struct in6_addr*)&six, max_ip, sizeof(max_ip)); + + /* check containment for this network */ + fprintf(stream, "%s%s%s-%s for %u with %08X\n", prefix, network6->info.ip_not ? "!" : "", + min_ip, max_ip, + network6->info.id, network6->info.type); + } +} + +int NetworkSet_Fprintf(NetworkSet* network_set, const char* prefix, FILE* stream) +{ + if (!network_set) + return -1; + + if (!prefix) + prefix = ""; + + if (!stream) + stream = stdout; + + NetworkList_Fprintf(network_set, prefix, stream); + + return 0; +} + +NODE_DATA sflist_first(SF_LIST*, SF_LNODE**); +NODE_DATA sflist_next(SF_LNODE**); +static inline int NetworkSet_OrderByNetmask(SF_LIST* ordered_networks, SF_LIST* networks, unsigned + id) +{ + SF_LNODE* node = nullptr; + NODE_DATA node_data; + NSNetworkInfo* network; + + sflist_init(ordered_networks); + do + { + SF_LNODE* iter = nullptr; + + node_data = nullptr; + for (network = (NSNetworkInfo*)sflist_first(networks, &iter); + network; + network = (NSNetworkInfo*)sflist_next(&iter)) + { + if ( network->id == id && (node_data == nullptr || + network->netmask < ((NSNetworkInfo*)node_data)->netmask || + ( ( network->netmask == ((NSNetworkInfo*)node_data)->netmask) && + !network->ip_not ) ) ) + { + node_data = network; + node = iter; + } + } + + if (node_data) + { + if (sflist_add_tail(ordered_networks, node_data) ) + return -1; + + sflist_remove_node(networks, node); + } + } + while (node_data); + + return 0; +} + +static inline int NetworkSet_AddList(SF_LIST* networks, SF_LIST* new_networks) +{ + void* network; + + while ((network = sflist_remove_head(new_networks))) + { + if (sflist_add_tail(networks, network)) + return -1; + } + return 0; +} + +static inline int NetworkSet_ReduceNetworkSet(SF_LIST* networks) +{ + Network* ias; + Network* i_ias; + Network* new_ias; + uint32_t tmp; + bool changed; + SF_LIST reduced_networks; + + if (!sflist_count(networks)) + return 0; + + sflist_init(&reduced_networks); + while ( ( ias = (Network*)sflist_remove_head(networks) ) ) + { + SF_LNODE* iter = nullptr; + + /* ias is lowest in the list, so it takes precedence */ + if (ias->info.ip_not) + { + i_ias = (Network*)sflist_first(&reduced_networks, &iter); + while (i_ias) + { + changed = false; + + /* + i_ias ****** + ias *************** + */ + if (ias->range_min <= i_ias->range_min && ias->range_max >= i_ias->range_max) + { + sflist_remove_node(&reduced_networks, iter); + changed = true; + } + /* + i_ias ************ + ias *** + or + i_ias ************ + ias ************ + */ + else if (ias->range_min > i_ias->range_min && ias->range_min <= i_ias->range_max) + { + tmp = i_ias->range_max; + i_ias->range_max = ias->range_min - 1; + if (ias->range_max < tmp) + { + new_ias = (Network*)snort_calloc(sizeof(Network)); + *new_ias = *i_ias; + new_ias->range_min = ias->range_max + 1; + new_ias->range_max = tmp; + if (sflist_add_tail(&reduced_networks, new_ias)) + { + snort_free(new_ias); + return -1; + } + changed = true; + } + } + /* + i_ias ************ + ias ************ + or + i_ias ************ + ias **** + */ + else if (ias->range_max >= i_ias->range_min && ias->range_max <= i_ias->range_max) + { + tmp = i_ias->range_min; + i_ias->range_min = ias->range_max + 1; + if (ias->range_min > tmp) + { + new_ias = (Network*)snort_calloc(sizeof(Network)); + *new_ias = *i_ias; + new_ias->range_min = tmp; + new_ias->range_max = ias->range_min - 1; + if (sflist_add_tail(&reduced_networks, new_ias)) + { + snort_free(new_ias); + return -1; + } + changed = true; + } + } + + if (changed) + i_ias = (Network*)sflist_first(&reduced_networks, &iter); + else + i_ias = (Network*)sflist_next(&iter); + } + + snort_free(ias); + } + else + { + i_ias = (Network*)sflist_first(&reduced_networks, &iter); + while (i_ias) + { + changed = false; + if (ias->info.type == i_ias->info.type) + { + /* + i_ias ****** + ias *************** + */ + if (ias->range_min <= i_ias->range_min && ias->range_max >= i_ias->range_max) + { + sflist_remove_node(&reduced_networks, iter); + changed = true; + snort_free(i_ias); + i_ias = nullptr; + } + /* + i_ias *************** + ias ****** + */ + else if (i_ias->range_min <= ias->range_min && i_ias->range_max >= + ias->range_max) + { + ias->range_min = i_ias->range_min; + ias->range_max = i_ias->range_max; + sflist_remove_node(&reduced_networks, iter); + changed = true; + snort_free(i_ias); + i_ias = nullptr; + } + /* + i_ias ************ + ias ************ + */ + else if (ias->range_min > i_ias->range_min && ias->range_min <= + i_ias->range_max) + { + i_ias->range_max = ias->range_min - 1; + } + /* + i_ias ************ + ias ************ + */ + else if (ias->range_max >= i_ias->range_min && ias->range_max < + i_ias->range_max) + { + i_ias->range_min = ias->range_max + 1; + } + } + else /* different types */ + { + /* + i_ias ****** + ias ****** + */ + if (ias->range_min == i_ias->range_min && ias->range_max == i_ias->range_max) + { + i_ias->info.type = ias->info.type; + snort_free(ias); + ias = nullptr; + break; + } + /* + i_ias ****** + ias *************** + */ + else if (ias->range_min < i_ias->range_min && ias->range_max >= + i_ias->range_max) + { + sflist_remove_node(&reduced_networks, iter); + snort_free(i_ias); + i_ias = nullptr; + changed = true; + } + /* + i_ias ************ + ias *** + or + i_ias ************ + ias ************ + or + i_ias ************ + ias ****** + */ + else if (ias->range_min > i_ias->range_min && ias->range_min <= + i_ias->range_max) + { + tmp = i_ias->range_max; + i_ias->range_max = ias->range_min - 1; + if (ias->range_max < tmp) + { + new_ias = (Network*)snort_calloc(sizeof(Network)); + *new_ias = *i_ias; + new_ias->range_min = ias->range_max + 1; + new_ias->range_max = tmp; + if (sflist_add_tail(&reduced_networks, new_ias)) + { + snort_free(new_ias); + return -1; + } + changed = true; + } + } + /* + i_ias ************ + ias ************ + or + i_ias ************ + ias **** + */ + else if (ias->range_max > i_ias->range_min && ias->range_max < + i_ias->range_max) + { + i_ias->range_min = ias->range_max + 1; + } + } + + if (changed) + i_ias = (Network*)sflist_first(&reduced_networks, &iter); + else + i_ias = (Network*)sflist_next(&iter); + } + + if (ias && sflist_add_tail(&reduced_networks, ias)) + { + return -1; + } + } + } + + /* Minimize the ranges */ + SF_LNODE* outer_iter; + ias = (Network*)sflist_first(&reduced_networks, &outer_iter); + while (ias) + { + /* i_ias is lowest in the list, so it takes precedence */ + changed = false; + SF_LNODE* inner_iter = outer_iter; + + i_ias = (Network*)sflist_next(&inner_iter); + while ( i_ias ) + { + if (ias->info.type == i_ias->info.type) + { + /* + i_ias ************ + ias *** + */ + if (ias->range_min && (i_ias->range_max+1) == ias->range_min) + { + i_ias->range_max = ias->range_max; + sflist_remove_node(&reduced_networks, outer_iter); + snort_free(ias); + changed = true; + break; + } + /* + i_ias ************ + ias ***** + */ + else if (i_ias->range_min && (ias->range_max+1) == i_ias->range_min) + { + i_ias->range_min = ias->range_min; + sflist_remove_node(&reduced_networks, outer_iter); + snort_free(ias); + changed = true; + break; + } + } + + i_ias = (Network*)sflist_next(&inner_iter); + } + + if (changed) + ias = (Network*)sflist_first(&reduced_networks, &outer_iter); + else + ias = (Network*)sflist_next(&outer_iter); + } + + sflist_static_free_all(networks, &snort_free); + while ((ias = (Network*)sflist_remove_head(&reduced_networks))) + { + if (sflist_add_tail(networks, ias)) + { + return -1; + } + } + return 0; +} + +static inline int NetworkSet_ReduceNetworkSet6(SF_LIST* networks) +{ + Network6* ias; + Network6* i_ias; + Network6* new_ias; + NSIPv6Addr tmp; + NSIPv6Addr tmp2; + bool changed; + SF_LIST reduced_networks; + + if (!sflist_count(networks)) + return 0; + + sflist_init(&reduced_networks); + while ((ias = (Network6*)sflist_remove_head(networks))) + { + SF_LNODE* iter = nullptr; + + /* ias is lowest in the list, so it takes precedence */ + if (ias->info.ip_not) + { + i_ias = (Network6*)sflist_first(&reduced_networks, &iter); + while (i_ias) + { + changed = false; + + /* + i_ias ****** + ias *************** + */ + if (NSIPv6AddrCompare(&ias->range_min, &i_ias->range_min) <= 0 && + NSIPv6AddrCompare(&ias->range_max, &i_ias->range_max) >= 0) + { + sflist_remove_node(&reduced_networks, iter); + changed = true; + } + /* + i_ias ************ + ias *** + or + i_ias ************ + ias ************ + */ + else if (NSIPv6AddrCompare(&ias->range_min, &i_ias->range_min) > 0 && + NSIPv6AddrCompare(&ias->range_min, &i_ias->range_max) <= 0) + { + tmp = i_ias->range_max; + i_ias->range_max = ias->range_min; + NSIPv6AddrDec(&i_ias->range_max); + if (NSIPv6AddrCompare(&ias->range_max, &tmp) < 0) + { + new_ias = (Network6*)snort_calloc(sizeof(Network6)); + *new_ias = *i_ias; + new_ias->range_min = ias->range_max; + NSIPv6AddrInc(&new_ias->range_min); + new_ias->range_max = tmp; + if (sflist_add_tail(&reduced_networks, new_ias)) + { + snort_free(new_ias); + return -1; + } + changed = true; + } + } + /* + i_ias ************ + ias ************ + or + i_ias ************ + ias **** + */ + else if (NSIPv6AddrCompare(&ias->range_max, &i_ias->range_min) >= 0 && + NSIPv6AddrCompare(&ias->range_max, &i_ias->range_max) <= 0) + { + tmp = i_ias->range_min; + i_ias->range_min = ias->range_max; + NSIPv6AddrInc(&i_ias->range_min); + if (NSIPv6AddrCompare(&ias->range_min, &tmp) > 0) + { + new_ias = (Network6*)snort_calloc(sizeof(Network6)); + *new_ias = *i_ias; + new_ias->range_min = tmp; + new_ias->range_max = ias->range_min; + NSIPv6AddrDec(&new_ias->range_max); + if (sflist_add_tail(&reduced_networks, new_ias)) + { + snort_free(new_ias); + return -1; + } + changed = true; + } + } + + if (changed) + i_ias = (Network6*)sflist_first(&reduced_networks, &iter); + else + i_ias = (Network6*)sflist_next(&iter); + } + snort_free(ias); + } + else + { + i_ias = (Network6*)sflist_first(&reduced_networks, &iter); + while (i_ias) + { + changed = false; + if (ias->info.type == i_ias->info.type) + { + /* + i_ias ****** + ias *************** + */ + if (NSIPv6AddrCompare(&ias->range_min, &i_ias->range_min) <= 0 && + NSIPv6AddrCompare(&ias->range_max, &i_ias->range_max) >= 0) + { + sflist_remove_node(&reduced_networks, iter); + changed = true; + snort_free(i_ias); + i_ias = nullptr; + } + /* + i_ias *************** + ias ****** + */ + else if (NSIPv6AddrCompare(&i_ias->range_min, &ias->range_min) <= 0 && + NSIPv6AddrCompare(&i_ias->range_max, &ias->range_max) >= 0) + { + ias->range_min = i_ias->range_min; + ias->range_max = i_ias->range_max; + sflist_remove_node(&reduced_networks, iter); + changed = true; + snort_free(i_ias); + i_ias = nullptr; + } + /* + i_ias ************ + ias ************ + */ + else if (NSIPv6AddrCompare(&ias->range_min, &i_ias->range_min) > 0 && + NSIPv6AddrCompare(&ias->range_min, &i_ias->range_max) <= 0) + { + i_ias->range_max = ias->range_min; + NSIPv6AddrDec(&i_ias->range_max); + } + /* + i_ias ************ + ias ************ + */ + else if (NSIPv6AddrCompare(&ias->range_max, &i_ias->range_min) >= 0 && + NSIPv6AddrCompare(&ias->range_max, &i_ias->range_max) < 0) + { + i_ias->range_min = ias->range_max; + NSIPv6AddrInc(&i_ias->range_min); + } + } + else /* different types */ + { + /* + i_ias ****** + ias ****** + */ + if (!NSIPv6AddrCompare(&ias->range_min, &i_ias->range_min) && + !NSIPv6AddrCompare(&ias->range_max, &i_ias->range_max)) + { + i_ias->info.type = ias->info.type; + snort_free(ias); + ias = nullptr; + break; + } + /* + i_ias ****** + ias *************** + */ + else if (NSIPv6AddrCompare(&ias->range_min, &i_ias->range_min) < 0 && + NSIPv6AddrCompare(&ias->range_max, &i_ias->range_max) >= 0) + { + sflist_remove_node(&reduced_networks, iter); + snort_free(i_ias); + i_ias = nullptr; + changed = true; + } + /* + i_ias ************ + ias *** + or + i_ias ************ + ias ************ + or + i_ias ************ + ias ****** + */ + else if (NSIPv6AddrCompare(&ias->range_min, &i_ias->range_min) > 0 && + NSIPv6AddrCompare(&ias->range_min, &i_ias->range_max) <= 0) + { + tmp = i_ias->range_max; + i_ias->range_max = ias->range_min; + NSIPv6AddrDec(&i_ias->range_max); + if (NSIPv6AddrCompare(&ias->range_max, &tmp) < 0) + { + new_ias = (Network6*)snort_calloc(sizeof(Network6)); + *new_ias = *i_ias; + new_ias->range_min = ias->range_max; + NSIPv6AddrInc(&new_ias->range_min); + new_ias->range_max = tmp; + if (sflist_add_tail(&reduced_networks, new_ias)) + { + snort_free(new_ias); + return -1; + } + changed = true; + } + } + /* + i_ias ************ + ias ************ + or + i_ias ************ + ias **** + */ + else if (NSIPv6AddrCompare(&ias->range_max, &i_ias->range_min) > 0 && + NSIPv6AddrCompare(&ias->range_max, &i_ias->range_max) < 0) + { + i_ias->range_min = ias->range_max; + NSIPv6AddrInc(&i_ias->range_min); + } + } + + if (changed) + i_ias = (Network6*)sflist_first(&reduced_networks, &iter); + else + i_ias = (Network6*)sflist_next(&iter); + } + + if (ias && sflist_add_tail(&reduced_networks, ias)) + { + return -1; + } + } + } + + /* Minimize the ranges */ + SF_LNODE* outer_iter; + ias = (Network6*)sflist_first(&reduced_networks, &outer_iter); + while (ias) + { + /* i_ias is lowest in the list, so it takes precedence */ + changed = false; + SF_LNODE* inner_iter = outer_iter; + i_ias = (Network6*)sflist_next(&inner_iter); + while ( i_ias ) + { + if (ias->info.type == i_ias->info.type) + { + /* + i_ias ************ + ias *** + */ + tmp = i_ias->range_max; + NSIPv6AddrInc(&tmp); + tmp2 = ias->range_max; + NSIPv6AddrInc(&tmp2); + if ((ias->range_min.lo || ias->range_min.hi) && !NSIPv6AddrCompare(&tmp, + &ias->range_min)) + { + i_ias->range_max = ias->range_max; + sflist_remove_node(&reduced_networks, outer_iter); + snort_free(ias); + changed = true; + break; + } + /* + i_ias ************ + ias ***** + */ + else if ((i_ias->range_min.lo || i_ias->range_min.hi) && + !NSIPv6AddrCompare(&tmp2, &i_ias->range_min)) + { + i_ias->range_min = ias->range_min; + sflist_remove_node(&reduced_networks, outer_iter); + snort_free(ias); + changed = true; + break; + } + } + + i_ias = (Network6*)sflist_next(&inner_iter); + } + + if (changed) + ias = (Network6*)sflist_first(&reduced_networks, &outer_iter); + else + ias = (Network6*)sflist_next(&outer_iter); + } + + sflist_static_free_all(networks, &snort_free); + while ((ias = (Network6*)sflist_remove_head(&reduced_networks))) + { + if (sflist_add_tail(networks, ias)) + { + return -1; + } + } + return 0; +} + +int NetworkSet_Reduce(NetworkSet* network_set) +{ + SFXHASH_NODE* hnode; + unsigned id; + int rval; + SF_LIST ordered_networks; + Network* network; + Network6* network6; + unsigned tmp; + int count; + int i; + int j; + + if (!network_set) + return -1; + + for (hnode=sfxhash_gfindfirst(network_set->ids); hnode; hnode=sfxhash_gfindnext( + network_set->ids)) + { + id = *(unsigned*)(hnode->data); + if ((rval = NetworkSet_OrderByNetmask(&ordered_networks, &network_set->networks, id)) != 0) + { + sflist_free_all(&ordered_networks, &snort_free); + return rval; + } + if ((rval = NetworkSet_ReduceNetworkSet(&ordered_networks)) != 0) + { + sflist_free_all(&ordered_networks, &snort_free); + return rval; + } + if ((rval = NetworkSet_AddList(&network_set->networks, &ordered_networks)) != 0) + { + sflist_free_all(&ordered_networks, &snort_free); + return rval; + } + } + if ((rval = NetworkSet_ReduceNetworkSet(&network_set->networks)) != 0) + { + sflist_free_all(&ordered_networks, &snort_free); + return rval; + } + + tmp = 0; + if ((rval = NetworkSet_Count(network_set, &tmp)) != 0) + return rval; + + count = (int)tmp; + if (count > 0) + { + network_set->count = count; + if (network_set->pnetwork) + { + snort_free(network_set->pnetwork); + network_set->pnetwork = nullptr; + } + network_set->pnetwork = (Network**)snort_calloc(count * sizeof(Network*)); + SF_LNODE* iter = nullptr; + for (network = (Network*)sflist_first(&network_set->networks, &iter), i = 0; + network && i < count; + network = (Network*)sflist_next(&iter)) + { + network_set->pnetwork[i++] = network; + } + /* bubble sort this array */ + for (i = (count - 1); i >= 0; i--) + { + for (j = 1; j <= i; j++ ) + { + if (network_set->pnetwork[j-1]->range_min > network_set->pnetwork[j]->range_min) + { + network = network_set->pnetwork[j-1]; + network_set->pnetwork[j-1] = network_set->pnetwork[j]; + network_set->pnetwork[j] = network; + } + } + } + } + + for (hnode=sfxhash_gfindfirst(network_set->ids6); hnode; hnode=sfxhash_gfindnext( + network_set->ids6)) + { + id = *(unsigned*)(hnode->data); + if ((rval = NetworkSet_OrderByNetmask(&ordered_networks, &network_set->networks6, id)) != + 0) + { + sflist_free_all(&ordered_networks, &snort_free); + return rval; + } + if ((rval = NetworkSet_ReduceNetworkSet6(&ordered_networks)) != 0) + { + sflist_free_all(&ordered_networks, &snort_free); + return rval; + } + if ((rval = NetworkSet_AddList(&network_set->networks6, &ordered_networks)) != 0) + { + sflist_free_all(&ordered_networks, &snort_free); + return rval; + } + } + if ((rval = NetworkSet_ReduceNetworkSet6(&network_set->networks6)) != 0) + { + sflist_free_all(&ordered_networks, &snort_free); + return rval; + } + + tmp = 0; + if ((rval = NetworkSet_Count6(network_set, &tmp)) != 0) + return rval; + + count = (int)tmp; + if (count > 0) + { + network_set->count6 = count; + if (network_set->pnetwork6) + { + snort_free(network_set->pnetwork6); + network_set->pnetwork6 = nullptr; + } + network_set->pnetwork6 = (Network6**)snort_calloc(count * sizeof(Network6*)); + SF_LNODE* iter = nullptr; + for (network6 = (Network6*)sflist_first(&network_set->networks6, &iter), i = 0; + network6 && i < count; + network6 = (Network6*)sflist_next(&iter)) + { + network_set->pnetwork6[i++] = network6; + } + /* bubble sort this array */ + for (i = (count - 1); i >= 0; i--) + { + for (j = 1; j <= i; j++ ) + { + if (NSIPv6AddrCompare(&network_set->pnetwork6[j-1]->range_min, + &network_set->pnetwork6[j]->range_min) > 0) + { + network6 = network_set->pnetwork6[j-1]; + network_set->pnetwork6[j-1] = network_set->pnetwork6[j]; + network_set->pnetwork6[j] = network6; + } + } + } + } + return 0; +} + +NetworkSet* NetworkSet_Copy(NetworkSet* network_set) +{ + NetworkSet* new_set; + Network* network; + Network6* network6; + SF_LNODE* iter; + + if (!network_set) + return nullptr; + + if (NetworkSet_New(&new_set) != 0) + return nullptr; + + for (network = (Network*)sflist_first(&network_set->networks, &iter); + network; + network = (Network*)sflist_next(&iter)) + { + if (NetworkSet_AddNetworkRangeEx(new_set, network->range_min, network->range_max, + network->info.netmask, network->info.ip_not, + network->info.id, network->info.type) != 0) + { + NetworkSet_Destroy(new_set); + return nullptr; + } + } + for (network6 = (Network6*)sflist_first(&network_set->networks6, &iter); + network6; + network6 = (Network6*)sflist_next(&iter)) + { + if (NetworkSet_AddNetworkRange6Ex(new_set, &network6->range_min, &network6->range_max, + network6->info.netmask, network6->info.ip_not, + network6->info.id, network6->info.type) != 0) + { + NetworkSet_Destroy(new_set); + return nullptr; + } + } + return new_set; +} + +int NetworkSet_AddSet(NetworkSet* dest_set, NetworkSet* src_set) +{ + Network* network; + Network6* network6; + SF_LNODE* iter; + int rval; + + if (!src_set || !dest_set) + return -1; + + for (network = (Network*)sflist_first(&src_set->networks, &iter); + network; + network = (Network*)sflist_next(&iter)) + { + if ((rval=NetworkSet_AddNetworkRangeEx(dest_set, network->range_min, network->range_max, + network->info.netmask, network->info.ip_not, + network->info.id, network->info.type)) != 0) + { + return rval; + } + } + for (network6 = (Network6*)sflist_first(&src_set->networks6, &iter); + network6; + network6 = (Network6*)sflist_next(&iter)) + { + if ((rval=NetworkSet_AddNetworkRange6Ex(dest_set, &network6->range_min, + &network6->range_max, + network6->info.netmask, network6->info.ip_not, + network6->info.id, network6->info.type)) != 0) + { + return rval; + } + } + return 0; +} + diff --git a/src/network_inspectors/appid/util/network_set.h b/src/network_inspectors/appid/util/network_set.h new file mode 100644 index 000000000..06587cbc2 --- /dev/null +++ b/src/network_inspectors/appid/util/network_set.h @@ -0,0 +1,400 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// network_set.h author Sourcefire Inc. + +#ifndef NETWORK_SET_H +#define NETWORK_SET_H + +/* System includes */ +#include +#include +#include +#include +#include + +#include "utils/sflsq.h" +#include "hash/sfxhash.h" +#include "protocols/ipv6.h" + +// network_set.h author Sourcefire Inc. + +#ifndef ULLONG_MAX +# define ULLONG_MAX 18446744073709551615ULL +#endif + +#define BYTE_SWAP_16(x) \ + ((uint16_t)((((uint16_t)(x) & 0xff00) >> 8) | \ + (((uint16_t)(x) & 0x00ff) << 8))) + +#define BYTE_SWAP_32(x) \ + ((uint32_t)((((uint32_t)(x) & 0xff000000) >> 24) | \ + (((uint32_t)(x) & 0x00ff0000) >> 8) | \ + (((uint32_t)(x) & 0x0000ff00) << 8) | \ + (((uint32_t)(x) & 0x000000ff) << 24))) + +#define BYTE_SWAP_64(x) \ + ((uint64_t)((((uint64_t)(x) & 0xff00000000000000ULL) >> 56) | \ + (((uint64_t)(x) & 0x00ff000000000000ULL) >> 40) | \ + (((uint64_t)(x) & 0x0000ff0000000000ULL) >> 24) | \ + (((uint64_t)(x) & 0x000000ff00000000ULL) >> 8) | \ + (((uint64_t)(x) & 0x00000000ff000000ULL) << 8) | \ + (((uint64_t)(x) & 0x0000000000ff0000ULL) << 24) | \ + (((uint64_t)(x) & 0x000000000000ff00ULL) << 40) | \ + (((uint64_t)(x) & 0x00000000000000ffULL) << 56))) + +#if defined(WORDS_BIGENDIAN) +struct NSIPv6Addr +{ + uint64_t hi; + uint64_t lo; +}; +#else +struct NSIPv6Addr +{ + uint64_t lo; + uint64_t hi; +}; +#endif + +//IPv6 address a must be in network order +#define NSIP_IS_ADDR_MULTICAST(a) \ + (IN6_IS_ADDR_MULTICAST(a) \ + || ((IN6_IS_ADDR_V4MAPPED(a) || IN6_IS_ADDR_V4COMPAT(a)) && (((__const uint32_t*)(a))[3] == \ + 0xffffffff))) + +inline void NSIPv6PackIpv4(NSIPv6Addr* ipv6Addr, uint32_t ipv4Addr) +{ + ipv6Addr->hi = 0ULL; + ipv6Addr->lo = (uint64_t)ipv4Addr | 0x0000FFFF00000000ULL; +} + +inline int NSIPv6UnpackIpv4(const NSIPv6Addr* ipv6Addr, uint32_t* ipv4Addr) +{ + if (!ipv6Addr->hi) + { + uint64_t lo = ipv6Addr->lo & 0xFFFFFFFF00000000ULL; + if (!lo || lo == 0x0000FFFF00000000ULL) + { + *ipv4Addr = (uint32_t)ipv6Addr->lo; + return 0; + } + } + return -1; +} + +inline void NSIPv6AddrCopy(const NSIPv6Addr* src, NSIPv6Addr* dst) +{ + dst->hi = src->hi; + dst->lo = src->lo; +} + +inline int NSIPv6AddrCompare(const NSIPv6Addr* a, const NSIPv6Addr* b) +{ + if (a->hi < b->hi) + return -1; + else if (a->hi > b->hi) + return 1; + if (a->lo < b->lo) + return -1; + else if (a->lo > b->lo) + return 1; + return 0; +} + +#if defined(WORDS_BIGENDIAN) + +#define NSIPv6AddrNtoH(ip6) do { } while (0) + +#else + +inline void NSIPv6AddrNtoH(NSIPv6Addr* ip6) +{ + uint64_t tmp; + + tmp = BYTE_SWAP_64(ip6->hi); + ip6->hi = BYTE_SWAP_64(ip6->lo); + ip6->lo = tmp; +} + +#endif + +#if defined(WORDS_BIGENDIAN) + +inline void _NSIPv6AddrConv(const NSIPv6Addr* ip6, NSIPv6Addr* ip6h) +{ + ip6h->hi = ip6->hi; + ip6h->lo = ip6->lo; +} + +#else + +inline void _NSIPv6AddrConv(const NSIPv6Addr* ip6, NSIPv6Addr* ip6h) +{ + ip6h->hi = BYTE_SWAP_64(ip6->lo); + ip6h->lo = BYTE_SWAP_64(ip6->hi); +} + +#endif + +inline void NSIPv6AddrNtoHConv(const ip::snort_in6_addr* ip6, NSIPv6Addr* ip6h) +{ + _NSIPv6AddrConv((const NSIPv6Addr*)ip6, ip6h); +} + +inline void NSIPv6AddrHtoNConv(const NSIPv6Addr* ip6, ip::snort_in6_addr* ip6h) +{ + _NSIPv6AddrConv(ip6, (NSIPv6Addr*)ip6h); +} + +#define NSIPv6AddrHtoN(ip6) NSIPv6AddrNtoH(ip6) + +inline void NSIPv6AddrInc(NSIPv6Addr* ip6) +{ + if (ip6->lo == ULLONG_MAX) + { + ip6->lo = 0; + ip6->hi++; + } + else + ip6->lo++; +} + +inline void NSIPv6AddrDec(NSIPv6Addr* ip6) +{ + if (!ip6->lo) + { + ip6->lo = ULLONG_MAX; + ip6->hi--; + } + else + ip6->lo--; +} + +struct NSNetworkInfo +{ + unsigned id; + unsigned netmask; + int ip_not; + unsigned type; +}; + +struct Network +{ + NSNetworkInfo info; + uint32_t range_min; + uint32_t range_max; +}; + +struct Network6 +{ + NSNetworkInfo info; + NSIPv6Addr range_min; + NSIPv6Addr range_max; +}; + +struct NetworkSet +{ + NetworkSet* next; + SF_LIST networks; + SFXHASH* ids; + Network** pnetwork; + unsigned count; + SF_LIST networks6; + SFXHASH* ids6; + Network6** pnetwork6; + unsigned count6; +}; + +// Create a new network set +int NetworkSet_New(NetworkSet** network_set); + +// Destroy a network set +int NetworkSet_Destroy(NetworkSet* network_set); + +// Copy a network set +NetworkSet* NetworkSet_Copy(NetworkSet* network_set); + +// Add a network set to another network set +int NetworkSet_AddSet(NetworkSet* dest_set, NetworkSet* src_set); + +// Add a network to the set using cidr block notation +int NetworkSet_AddCidrBlockEx(NetworkSet* network_set, uint32_t ip, + unsigned cidr_bits, int ip_not, unsigned id, unsigned type); + +// Add a network to the set using cidr block notation +int NetworkSet_AddCidrBlock6Ex(NetworkSet* network_set, NSIPv6Addr* ip, + unsigned cidr_bits, int ip_not, unsigned id, unsigned type); + +// Add a network to the set using cidr block notation +int NetworkSet_AddCidrBlock(NetworkSet* network_set, uint32_t ip, + unsigned cidr_bits, int ip_not, unsigned id); + +// Add a network to the set using cidr block notation +int NetworkSet_AddCidrBlock6(NetworkSet* network_set, NSIPv6Addr* ip, + unsigned cidr_bits, int ip_not, unsigned id); + +// Add a network to the set using a range +int NetworkSet_AddNetworkRangeEx(NetworkSet* network_set, uint32_t range_min, + uint32_t range_max, unsigned cidr_bits, int ip_not, unsigned id, unsigned type); + +// Add a network to the set using a range +int NetworkSet_AddNetworkRange6Ex(NetworkSet* network_set, NSIPv6Addr* range_min, + NSIPv6Addr* range_max, unsigned cidr_bits, int ip_not, unsigned id, unsigned type); + +// Add a network to the set using a range +int NetworkSet_AddNetworkRange(NetworkSet* network_set, uint32_t range_min, + uint32_t range_max, unsigned cidr_bits, int ip_not, unsigned id); + +// Add a network to the set using a range +int NetworkSet_AddNetworkRange6(NetworkSet* network_set, NSIPv6Addr* range_min, + NSIPv6Addr* range_max, unsigned cidr_bits, int ip_not, unsigned id); + +// Add a network to the set using a range of all IPv6, excluding IPv4 +int NetworkSet_AddNetworkRangeOnlyIPv6(NetworkSet* network_set, int ip_not, unsigned id, unsigned + type); + +// Reduce the networks to a list of existing ranges +int NetworkSet_Reduce(NetworkSet* network_set); + +// Print the network to the specified stream +int NetworkSet_Fprintf(NetworkSet* network_set, const char* prefix, FILE* stream); + +// Test is the set contains the specied address +inline int NetworkSet_ContainsEx(NetworkSet* network_set, uint32_t ipaddr, unsigned* type) +{ + int low=0; + int middle=0; + int high=0; + + *type = 0; + if (!network_set) + return 0; + if (!network_set->count) + return 0; + high = network_set->count - 1; + if (ipaddr < network_set->pnetwork[low]->range_min || ipaddr > + network_set->pnetwork[high]->range_max) + return 0; + while (low <= high) + { + middle = low + ((high - low)>>1); + if (ipaddr < network_set->pnetwork[middle]->range_min) + high = middle - 1; + else if (ipaddr > network_set->pnetwork[middle]->range_max) + low = middle + 1; + else + { + *type = network_set->pnetwork[middle]->info.type; + return 1; + } + } + return 0; +} + +// Test is the set contains the specied address +inline int NetworkSet_Contains6Ex(NetworkSet* network_set, NSIPv6Addr* ipaddr, + unsigned* type) +{ + int low=0; + int middle=0; + int high=0; + + *type = 0; + if (!network_set) + return 0; + if (!network_set->count6) + return 0; + high = network_set->count6 - 1; + if (NSIPv6AddrCompare(ipaddr, &network_set->pnetwork6[low]->range_min) < 0 || + NSIPv6AddrCompare(ipaddr, &network_set->pnetwork6[high]->range_max) > 0) + { + return 0; + } + while (low <= high) + { + middle = low + ((high - low)>>1); + if (NSIPv6AddrCompare(ipaddr, &network_set->pnetwork6[middle]->range_min) < 0) + high = middle - 1; + else if (NSIPv6AddrCompare(ipaddr, &network_set->pnetwork6[middle]->range_max) > 0) + low = middle + 1; + else + { + *type = network_set->pnetwork6[middle]->info.type; + return 1; + } + } + return 0; +} + +// Test is the set contains the specied address +inline int NetworkSet_Contains(NetworkSet* network_set, uint32_t ipaddr) +{ + unsigned type; + return NetworkSet_ContainsEx(network_set, ipaddr, &type); +} + +// Test is the set contains the specied address +inline int NetworkSet_Contains6(NetworkSet* network_set, NSIPv6Addr* ipaddr) +{ + unsigned type; + return NetworkSet_Contains6Ex(network_set, ipaddr, &type); +} + +// Get a count of the number of networks in the set +inline int NetworkSet_Count(NetworkSet* network_set, unsigned* count) +{ + if (!network_set || !count) + return -1; + + *count = sflist_count(&network_set->networks); + + return 0; +} + +// Get a count of the number of networks in the set +inline int NetworkSet_Count6(NetworkSet* network_set, unsigned* count) +{ + if (!network_set || !count) + return -1; + + *count = sflist_count(&network_set->networks6); + + return 0; +} + +// Get a count of the number of networks in the set +inline unsigned NetworkSet_CountEx(NetworkSet* network_set) +{ + if (!network_set) + return 0; + + return sflist_count(&network_set->networks); +} + +// Get a count of the number of networks in the set +inline unsigned NetworkSet_Count6Ex(NetworkSet* network_set) +{ + if (!network_set) + return 0; + + return sflist_count(&network_set->networks6); +} + +#endif diff --git a/src/network_inspectors/appid/util/output_file.cc b/src/network_inspectors/appid/util/output_file.cc new file mode 100644 index 000000000..51814b465 --- /dev/null +++ b/src/network_inspectors/appid/util/output_file.cc @@ -0,0 +1,53 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// output_file.cc author Sourcefire Inc. + +#include "output_file.h" + +#include +#include +#include "log/messages.h" + +FILE* openOutputFile(const char* const filename, time_t tstamp) +{ + FILE* fp; + char output_fullpath[512]; + time_t curr_time; + + if (tstamp) + curr_time = tstamp; + else + curr_time = time(nullptr); + snprintf(output_fullpath, sizeof(output_fullpath), "%s.%lu", filename, curr_time); + LogMessage("*** Opening %s for output\n",output_fullpath); + if ((fp = fopen(output_fullpath, "w")) == nullptr) + { + ErrorMessage("Unable to open output file \"%s\": %s\n",output_fullpath, strerror(errno)); + } + return fp; +} + +FILE* rolloverOutputFile(const char* const filename, FILE* const oldfp, time_t tstamp) +{ + fclose(oldfp); + + return openOutputFile(filename, tstamp); +} + diff --git a/src/network_inspectors/appid/util/output_file.h b/src/network_inspectors/appid/util/output_file.h new file mode 100644 index 000000000..b41b3d85b --- /dev/null +++ b/src/network_inspectors/appid/util/output_file.h @@ -0,0 +1,32 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// output_file.h author Sourcefire Inc. + +#ifndef OUTPUT_FILE +#define OUTPUT_FILE + +#include +#include + +FILE* openOutputFile(const char* const filename, time_t tstamp); +FILE* rolloverOutputFile(const char* const filename, FILE* const oldfp, time_t tstamp); + +#endif + diff --git a/src/network_inspectors/appid/util/sf_mlmp.cc b/src/network_inspectors/appid/util/sf_mlmp.cc new file mode 100644 index 000000000..f5ee4429d --- /dev/null +++ b/src/network_inspectors/appid/util/sf_mlmp.cc @@ -0,0 +1,653 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sf_mlmp.cc author Sourcefire Inc. + +#include "sf_mlmp.h" + +#include + +#include "main/snort_debug.h" +#include "search_engines/search_tool.h" +#include "utils/util.h" + +#define _MLMP_DEBUG 0 + +struct tPatternNode +{ + tMlmpPattern pattern; + void* userData; /*client/service info */ + + /**part number. Should start from 1. Ordering of parts does not matter in the sense + * part 1 may appear after part 2 in payload.*/ + uint32_t partNum; + + /**Total number of parts.*/ + uint32_t partTotal; + + /**Uniq non-zero identifier to tie parts of a multi-part patterns together. */ + uint32_t patternId; + + tPatternNode* nextPattern; +}; + +struct tPatternPrimaryNode +{ + tPatternNode patternNode; + + tPatternPrimaryNode* nextPrimaryNode; + + /*Tree node for next level. Present only in primary pattern node i.e. */ + tMlmpTree* nextLevelMatcher; +}; + +/*Node for mlmp tree */ +struct tMlmpTree +{ + SearchTool* patternTree; + tPatternPrimaryNode* patternList; + uint32_t level; +}; + +/*Used to track matched patterns. */ +struct tMatchedPatternList +{ + tPatternNode* patternNode; + size_t index; + /*uint32_t level; */ + tMatchedPatternList* next; +}; + +static int compareMlmpPatterns(const void* p1, const void* p2); +static int createTreesRecusively(tMlmpTree* root); +static void destroyTreesRecursively(tMlmpTree* root); +static void dumpTreesRecursively(tMlmpTree* root); +static int addPatternRecursively(tMlmpTree* root, const tMlmpPattern* inputPatternList, + void* metaData, uint32_t level); +static tPatternNode* urlPatternSelector(const tMatchedPatternList* matchList, const + uint8_t* payload); +static tPatternNode* genericPatternSelector(const tMatchedPatternList* matchList, const + uint8_t* payload); +static void* mlmpMatchPatternCustom(tMlmpTree* root, tMlmpPattern* inputPatternList, + tPatternNode* (*callback)(const tMatchedPatternList*, const uint8_t*)); +static int patternMatcherCallback(void* id, void* unused_tree, int index, void* data, + void* unused_neg); + +static uint32_t gPatternId = 1; + +tMlmpTree* mlmpCreate(void) +{ + tMlmpTree* root = (tMlmpTree*)snort_calloc(sizeof(tMlmpTree)); + root->level = 0; + return root; +} + +/*last pattern should be nullptr */ +int mlmpAddPattern(tMlmpTree* root, const tMlmpPattern* inputPatternList, void* metaData) +{ + return addPatternRecursively(root, inputPatternList, metaData, 0); +} + +int mlmpProcessPatterns(tMlmpTree* root) +{ + int rvalue; + + rvalue = createTreesRecusively(root); + if (rvalue) + destroyTreesRecursively(root); + return rvalue; +} + +void* mlmpMatchPatternUrl(tMlmpTree* root, tMlmpPattern* inputPatternList) +{ + return mlmpMatchPatternCustom(root, inputPatternList, urlPatternSelector); +} + +void* mlmpMatchPatternGeneric(tMlmpTree* root, tMlmpPattern* inputPatternList) +{ + return mlmpMatchPatternCustom(root, inputPatternList, genericPatternSelector); +} + +static inline int matchDomainPattern(const tMatchedPatternList* mp, const uint8_t* pattern) +{ + if (!pattern) + return -1; + + return (mp->patternNode->pattern.level == 0 && !(mp->index == 0 || pattern[mp->index-1] == + '.')); +} + +static void* mlmpMatchPatternCustom(tMlmpTree* rootNode, tMlmpPattern* inputPatternList, + tPatternNode* (*callback)(const tMatchedPatternList*, const uint8_t*)) +{ + tMatchedPatternList* mp = nullptr; + tMatchedPatternList* tmpMp; + void* data = nullptr; + void* tmpData = nullptr; + tPatternPrimaryNode* primaryNode; + tMlmpPattern* pattern = inputPatternList; + + if (!rootNode || !pattern || !pattern->pattern) + return nullptr; + + rootNode->patternTree->find_all((char*)pattern->pattern, pattern->patternSize, + patternMatcherCallback, false, (void*)&mp); + + primaryNode = (tPatternPrimaryNode*)callback(mp, pattern->pattern); + + while (mp) + { + tmpMp = mp; + mp = mp->next; + snort_free(tmpMp); + } + + if (primaryNode) + { + data = primaryNode->patternNode.userData; + tmpData = mlmpMatchPatternCustom(primaryNode->nextLevelMatcher, ++inputPatternList, + callback); + if (tmpData) + data = tmpData; + } + + return data; +} + +void mlmpDestroy(tMlmpTree* root) +{ + destroyTreesRecursively(root); +} + +void mlmpDump(tMlmpTree* root) +{ + dumpTreesRecursively(root); +} + +/**tMlmpPattern comparator: compares patterns based on pattern, patternSize. This will + * result in alphabatical order. Notice that patternId is ignored here. + */ +static int compareMlmpPatterns(const void* p1, const void* p2) +{ + tMlmpPattern* pat1 = (tMlmpPattern*)p1; + tMlmpPattern* pat2 = (tMlmpPattern*)p2; + int rValue; + size_t minSize; + + /*first compare patterns by the smaller pattern size, if same then size wins */ + minSize = (pat1->patternSize > pat2->patternSize) ? pat2->patternSize : pat1->patternSize; + + rValue = memcmp(pat1->pattern, pat2->pattern, minSize); + if (rValue) + return rValue; + + return ((int)pat1->patternSize - (int)pat2->patternSize); +} + +/*pattern trees are not freed on error because in case of error, caller should call + detroyTreesRecursively. */ +static int createTreesRecusively(tMlmpTree* rootNode) +{ + SearchTool* patternMatcher; + tPatternPrimaryNode* primaryPatternNode; + tPatternNode* ddPatternNode; + + /* set up the MPSE for url patterns */ + patternMatcher = rootNode->patternTree = new SearchTool("ac_full"); + + for (primaryPatternNode = rootNode->patternList; + primaryPatternNode; + primaryPatternNode = primaryPatternNode->nextPrimaryNode) + { + /*recursion into next lower level */ + if (primaryPatternNode->nextLevelMatcher) + { + if (createTreesRecusively(primaryPatternNode->nextLevelMatcher)) + return -1; + } + + for (ddPatternNode = &primaryPatternNode->patternNode; + ddPatternNode; + ddPatternNode = ddPatternNode->nextPattern) + { + patternMatcher->add(ddPatternNode->pattern.pattern, + ddPatternNode->pattern.patternSize, ddPatternNode, true); + } + } + + patternMatcher->prep(); + + return 0; +} + +static void destroyTreesRecursively(tMlmpTree* rootNode) +{ + tPatternPrimaryNode* primaryPatternNode; + uint32_t partNum; + + if (!rootNode) + return; + + while ((primaryPatternNode = rootNode->patternList)) + { + /*recursion into next lower level */ + destroyTreesRecursively(primaryPatternNode->nextLevelMatcher); + rootNode->patternList = primaryPatternNode->nextPrimaryNode; + + for (partNum = 2; + partNum <= primaryPatternNode->patternNode.partTotal; + partNum++) + { + tPatternNode* patternNode = primaryPatternNode->patternNode.nextPattern + (partNum -2); + snort_free((void*)patternNode->pattern.pattern); + } + snort_free(primaryPatternNode->patternNode.nextPattern); + snort_free((void*)primaryPatternNode->patternNode.pattern.pattern); + snort_free(primaryPatternNode); + } + + delete rootNode->patternTree; + snort_free(rootNode); +} + +static void dumpTreesRecursively(tMlmpTree* rootNode) +{ + tPatternPrimaryNode* primaryPatternNode; + tPatternNode* ddPatternNode; + char prefix[41]; + uint32_t prefixSize; + + prefixSize = 4*(rootNode->level)+2; + if (prefixSize > 40) + prefixSize = 40; + + memset(prefix, ' ', prefixSize); + prefix[prefixSize] = '\0'; + + for (primaryPatternNode = rootNode->patternList; + primaryPatternNode; + primaryPatternNode = primaryPatternNode->nextPrimaryNode) + { + DebugFormat(DEBUG_INSPECTOR, "%s%u. Primary id %u. partTotal %u, Data %p\n", prefix, + rootNode->level+1, + primaryPatternNode->patternNode.patternId, + primaryPatternNode->patternNode.partTotal, + primaryPatternNode->patternNode.userData); + + for (ddPatternNode = &primaryPatternNode->patternNode; + ddPatternNode; + ddPatternNode = ddPatternNode->nextPattern) + { + DebugFormat(DEBUG_INSPECTOR, "%s\t part %u/%u: Pattern %s, size %u\n", prefix, + ddPatternNode->partNum, + ddPatternNode->partTotal, + (char*)ddPatternNode->pattern.pattern, + (u_int32_t)ddPatternNode->pattern.patternSize); + } + + if (primaryPatternNode->nextLevelMatcher) + { + dumpTreesRecursively(primaryPatternNode->nextLevelMatcher); + } + } +} + +/*compares multipart patterns, and orders then according to . + Comparing multi-parts alphanumerically does not make sense. */ +static int compareMlmpPatternList(const tPatternNode* p1, const tPatternNode* p2) +{ + if (p1->patternId != p2->patternId) + return (p1->patternId - p2->patternId); + + return (p1->partNum - p2->partNum); +} + +static tPatternNode* patternSelector(const tMatchedPatternList* patternMatchList, const + uint8_t* payload, bool domain) +{ + tPatternNode* bestNode = nullptr; + tPatternNode* currentPrimaryNode = nullptr; + const tMatchedPatternList* tmpList; + uint32_t partNum, patternId, patternSize, maxPatternSize; + + /*partTotal = 0; */ + partNum = 0; + patternId = 0; + patternSize = maxPatternSize = 0; + +#if _MLMP_DEBUG + tPatternNode* ddPatternNode; + DebugMessage(DEBUG_INSPECTOR, "\tMatches found -------------------\n"); for (tmpList = + patternMatchList; + tmpList; + tmpList = tmpList->next) + { + ddPatternNode = tmpList->patternNode; + { + DebugFormat(DEBUG_INSPECTOR, + "\t\tid %d, Pattern %s, size %u, partNum %u, partTotal %u, userData %p\n", + ddPatternNode->patternId, + ddPatternNode->pattern.pattern, + (u_int32_t)ddPatternNode->pattern.patternSize, + ddPatternNode->partNum, + ddPatternNode->partTotal, + ddPatternNode->userData); + } + } +#endif + + for (tmpList = patternMatchList; + tmpList; + tmpList = tmpList->next) + { + if (tmpList->patternNode->patternId != patternId) + { + /*first pattern */ + + /*skip incomplete pattern */ + if (tmpList->patternNode->partNum != 1) + continue; + + /*new pattern started */ + patternId = tmpList->patternNode->patternId; + currentPrimaryNode = tmpList->patternNode; + partNum = 0; + patternSize = 0; + } + + if (tmpList->patternNode->partNum == (partNum+1)) + { + partNum++; + patternSize += tmpList->patternNode->pattern.patternSize; + } + + if (tmpList->patternNode->partTotal != partNum) + continue; + + /*backward compatibility */ + if ((tmpList->patternNode->partTotal == 1) + && domain && matchDomainPattern(tmpList, payload)) + continue; + + /*last pattern part is seen in sequence */ + if (patternSize >= maxPatternSize) + { + maxPatternSize = patternSize; + bestNode = currentPrimaryNode; + } + } + +#if _MLMP_DEBUG + if (bestNode) + { + ddPatternNode = bestNode; + { + DebugFormat(DEBUG_INSPECTOR, + "\t\tSELECTED Id %d, pattern %s, size %u, partNum %u, partTotal %u, userData %p\n", + ddPatternNode->patternId, + ddPatternNode->pattern.pattern, + (u_int32_t)ddPatternNode->pattern.patternSize, + ddPatternNode->partNum, + ddPatternNode->partTotal, + ddPatternNode->userData); + } + } + DebugMessage(DEBUG_INSPECTOR, "\tMatches end -------------------\n"); +#endif + return bestNode; +} + +static tPatternNode* urlPatternSelector(const tMatchedPatternList* patternMatchList, const + uint8_t* payload) +{ + return patternSelector (patternMatchList, payload, true); +} + +static tPatternNode* genericPatternSelector(const tMatchedPatternList* patternMatchList, const + uint8_t* payload) +{ + return patternSelector (patternMatchList, payload, false); +} + +static int patternMatcherCallback(void* id, void*, int index, void* data, void*) +{ + tPatternNode* target = (tPatternNode*)id; + tMatchedPatternList** matchList = (tMatchedPatternList**)data; + tMatchedPatternList* prevNode; + tMatchedPatternList* tmpList; + tMatchedPatternList* newNode; + int cmp; + + /*sort matches by patternId, and then by partId or pattern// */ + +#if _MLMP_DEBUG + DebugFormat(DEBUG_INSPECTOR, + "\tCallback id %d, Pattern %s, size %u, partNum %u, partTotal %u, userData %p\n", + target->patternId, + target->pattern.pattern, + (u_int32_t)target->pattern.patternSize, + target->partNum, + target->partTotal, + target->userData); +#endif + + for (prevNode = nullptr, tmpList = *matchList; + tmpList; + prevNode = tmpList, tmpList = tmpList->next) + { + cmp = compareMlmpPatternList (target, tmpList->patternNode); + if (cmp > 0 ) + continue; + if (cmp == 0) + return 0; + break; + } + + newNode = (tMatchedPatternList*)snort_calloc(sizeof(tMatchedPatternList)); + newNode->index = index; + newNode->patternNode = target; + + if (prevNode == nullptr) + { + /*first node */ + newNode->next = *matchList; + *matchList = newNode; + } + else + { + newNode->next = prevNode->next; + prevNode->next = newNode; + } + + return 0; +} + +/*find a match and insertion point if no match is found. Insertion point nullptr means */ +static tPatternPrimaryNode* findMatchPattern(tMlmpTree* rootNode, const + tMlmpPattern* inputPatternList, uint32_t partTotal, + tPatternPrimaryNode** prevPrimaryPatternNode) +{ + tPatternPrimaryNode* primaryPatternNode; + tPatternNode* ddPatternNode; + uint32_t partNum; + int retVal; + + *prevPrimaryPatternNode = nullptr; + + for (primaryPatternNode = rootNode->patternList; + primaryPatternNode; + *prevPrimaryPatternNode = primaryPatternNode, primaryPatternNode = + primaryPatternNode->nextPrimaryNode + ) + { + if (primaryPatternNode->patternNode.partTotal != partTotal) + { + continue; + } + + partNum = 1; + for (ddPatternNode = &primaryPatternNode->patternNode; + ddPatternNode; + ddPatternNode = ddPatternNode->nextPattern) + { + retVal = compareMlmpPatterns(inputPatternList+(partNum-1), &ddPatternNode->pattern); + if (retVal == 0) + { + /*all nodes matched */ + if (partNum == ddPatternNode->partTotal) + return primaryPatternNode; + else + continue; + } + else if (retVal < 0) + { + return nullptr; + } + break; + } + /**prevPrimaryPatternNode = primaryPatternNode; */ + } + return nullptr; +} + +/** + * @Note + * a. Patterns in each patternList must be unique. Multipart patterns should be unique i.e. no two multi-part patterns + * should have same ordered sub-parts. + * b. Patterns are add in alphabetical ordering of primary nodes. + */ +static int addPatternRecursively(tMlmpTree* rootNode, const tMlmpPattern* inputPatternList, + void* metaData, uint32_t level) +{ + tPatternNode* newNode; + tPatternPrimaryNode* prevPrimaryPatternNode = nullptr; + tPatternPrimaryNode* primaryNode = nullptr; + const tMlmpPattern* nextPattern; + const tMlmpPattern* patterns = inputPatternList; + uint32_t partTotal = 0; + uint32_t patternId = 0; + uint32_t i; + + if (!rootNode || !inputPatternList) + return -1; + + /*make it easier for user to add patterns by calculating partTotal and partNum */ + for ( i = 0, patterns = inputPatternList; + patterns->pattern && (patterns->level == level); + patterns = inputPatternList + (++i)) + { + partTotal++; + } + + /*see if pattern is present already. Multipart-messages are considered match only if all parts + match. */ + primaryNode = findMatchPattern(rootNode, inputPatternList, partTotal, &prevPrimaryPatternNode); + + /*pattern not found, insert it in order */ + if (!primaryNode) + { + tPatternPrimaryNode* tmpPrimaryNode; + uint32_t partNum; + + tmpPrimaryNode = (tPatternPrimaryNode*)snort_calloc(sizeof(tPatternPrimaryNode)); + if (partTotal > 1) + tmpPrimaryNode->patternNode.nextPattern = + (tPatternNode*)snort_calloc((partTotal - 1) * sizeof(tPatternNode)); + patternId = gPatternId++; + i = 0; + patterns = inputPatternList+i; + + /*initialize primary Node */ + tmpPrimaryNode->patternNode.pattern.pattern = patterns->pattern; + tmpPrimaryNode->patternNode.pattern.patternSize = patterns->patternSize; + tmpPrimaryNode->patternNode.pattern.level = patterns->level; + tmpPrimaryNode->patternNode.partNum = 1; + tmpPrimaryNode->patternNode.partTotal = partTotal; + tmpPrimaryNode->patternNode.patternId = patternId; + + if (prevPrimaryPatternNode) + { + tmpPrimaryNode->nextPrimaryNode = prevPrimaryPatternNode->nextPrimaryNode; + prevPrimaryPatternNode->nextPrimaryNode = tmpPrimaryNode; + } + else + { + /*insert as first node since either this is the only node, or this is lexically + smallest. */ + tmpPrimaryNode->nextPrimaryNode = rootNode->patternList; + rootNode->patternList = tmpPrimaryNode; + } + + i++; + patterns = inputPatternList + i; + + /*create list of remaining nodes */ + for (partNum = 2; + partNum <= partTotal; + partNum++) + { + newNode = tmpPrimaryNode->patternNode.nextPattern + (partNum -2); + newNode->pattern.pattern = patterns->pattern; + newNode->pattern.patternSize = patterns->patternSize; + newNode->pattern.level = patterns->level; + newNode->partNum = partNum; + newNode->partTotal = partTotal; + newNode->patternId = patternId; + if (partNum < partTotal) + newNode->nextPattern = newNode+1; + else + newNode->nextPattern = nullptr; + + i++; + patterns = inputPatternList + i; + } + primaryNode = tmpPrimaryNode; + } + else + { + for (i = 0; i < primaryNode->patternNode.partTotal; i++) + snort_free((void*)(inputPatternList+i)->pattern); + } + + if (primaryNode) + { + /*move down the new node */ + nextPattern = inputPatternList + partTotal; + if (!nextPattern || !nextPattern->pattern) + { + primaryNode->patternNode.userData = metaData; + } + else + { + if (!primaryNode->nextLevelMatcher) + { + tMlmpTree* tmpRootNode; + + tmpRootNode = (tMlmpTree*)snort_calloc(sizeof(tMlmpTree)); + primaryNode->nextLevelMatcher = tmpRootNode; + primaryNode->nextLevelMatcher->level = rootNode->level+1; + } + addPatternRecursively(primaryNode->nextLevelMatcher, inputPatternList+partTotal, + metaData, level+1); + } + } + + return 0; +} + diff --git a/src/network_inspectors/appid/util/sf_mlmp.h b/src/network_inspectors/appid/util/sf_mlmp.h new file mode 100644 index 000000000..2cadc18ac --- /dev/null +++ b/src/network_inspectors/appid/util/sf_mlmp.h @@ -0,0 +1,51 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sf_mlmp.h author Sourcefire Inc. + +#ifndef SF_MULTI_PART_MPSE_H +#define SF_MULTI_PART_MPSE_H + +#include +#include + +struct tMlmpPattern +{ + /*binary pattern */ + const uint8_t* pattern; + + /*binary pattern length in bytes */ + size_t patternSize; + + /**level of pattern. It should start from 0.*/ + uint32_t level; +}; + +struct tMlmpTree; + +tMlmpTree* mlmpCreate(); +int mlmpAddPattern(tMlmpTree* root, const tMlmpPattern* patterns, void* metaData); +int mlmpProcessPatterns(tMlmpTree* root); +void* mlmpMatchPatternUrl(tMlmpTree* root, tMlmpPattern* inputPatternList); +void* mlmpMatchPatternGeneric(tMlmpTree* root, tMlmpPattern* inputPatternList); +void mlmpDestroy(tMlmpTree* root); +void mlmpDump(tMlmpTree* root); + +#endif + diff --git a/src/network_inspectors/appid/util/sf_multi_mpse.cc b/src/network_inspectors/appid/util/sf_multi_mpse.cc new file mode 100644 index 000000000..23620ad86 --- /dev/null +++ b/src/network_inspectors/appid/util/sf_multi_mpse.cc @@ -0,0 +1,432 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sf_multi_mpse.cc author Sourcefire Inc. + +#include "sf_multi_mpse.h" + +#include +#include +#include + +#include "search_engines/search_tool.h" +#include "utils/util.h" + +struct tPatternRootNode; +struct tPatternList +{ + tMlpPattern pattern; + void* userData; /*client/service info */ + + tPatternList* nextPattern; + tPatternRootNode* nextLevelMatcher; +}; + +/*Root node */ +struct tPatternRootNode +{ + SearchTool* patternTree; + tPatternList* patternList; + tPatternList* lastPattern; + unsigned int level; /*some searches may be specific to levels. Increments from 1 at top + level, */ +}; + +/*Used to track matched patterns. */ +struct MatchedPattern +{ + tPatternList* patternNode; + size_t index; + unsigned int level; +}; + +static int compareAppUrlPatterns(const void* p1, const void* p2); +static int createTreesRecusively(void* root); +static void destroyTreesRecursively(void* root); +static void dumpTreesRecursively(void* root, int level); +static int addPatternRecursively(void* root, const tMlpPattern** inputPatternList, void* metaData, + int level); +static int longest_pattern_match(void* id, void*, int index, void* data, + void*); +static int url_pattern_match(void* id, void*, int index, void* data, void*); + +void* mlpCreate(void) +{ + tPatternRootNode* root = (tPatternRootNode*)snort_calloc(sizeof(tPatternRootNode)); + root->level = 0; + return root; +} + +/*last pattern should be nullptr */ +int mlpAddPattern(void* root, const tMlpPattern** inputPatternList, void* metaData) +{ + return addPatternRecursively(root, inputPatternList, metaData, 0); +} + +int mlpProcessPatterns(void* root) +{ + int rvalue; + + rvalue = createTreesRecusively(root); + if (rvalue) + destroyTreesRecursively(root); + return rvalue; +} + +void* mlpMatchPatternLongest(void* root, tMlpPattern** inputPatternList) +{ + return mlpMatchPatternCustom(root, inputPatternList, longest_pattern_match); +} + +void* mlpMatchPatternUrl(void* root, tMlpPattern** inputPatternList) +{ + return mlpMatchPatternCustom(root, inputPatternList, url_pattern_match); +} + +static inline int matchDomainPattern(MatchedPattern mp, const uint8_t* pattern) +{ + if (!pattern) + return -1; + + return (mp.level == 0 && !(mp.index == 0 || pattern[mp.index-1] == '.')); +} + +void* mlpMatchPatternCustom(void* root, tMlpPattern** inputPatternList, int (* callback)(void*, + void*, int, void*, void*)) +{ + MatchedPattern mp = { nullptr,0,0 }; + void* data = nullptr; + void* tmpData = nullptr; + tPatternList* patternNode; + tPatternRootNode* rootNode = (tPatternRootNode*)root; + tMlpPattern* pattern = *inputPatternList; + + if (!rootNode || !pattern || !pattern->pattern) + return nullptr; + + mp.level = rootNode->level; + + rootNode->patternTree->find_all((char*)pattern->pattern, + pattern->patternSize, + callback, + false, + &mp); + + patternNode = mp.patternNode; + if (patternNode) + { + if (matchDomainPattern(mp, pattern->pattern) != 0) + return nullptr; + + data = patternNode->userData; + tmpData = mlpMatchPatternCustom(patternNode->nextLevelMatcher, ++inputPatternList, + callback); + if (tmpData) + data = tmpData; + } + + return data; +} + +void mlpDestroy(void* root) +{ + destroyTreesRecursively(root); +} + +void mlpDump(void* root) +{ + dumpTreesRecursively(root, 0); +} + +/*alphabetically ordering */ +static int compareAppUrlPatterns(const void* p1, const void* p2) +{ + tMlpPattern* pat1 = (tMlpPattern*)p1; + tMlpPattern* pat2 = (tMlpPattern*)p2; + int rValue; + size_t minSize; + + /*first compare patterns by the smaller pattern size, if same then size wins */ + minSize = (pat1->patternSize > pat2->patternSize) ? pat2->patternSize : pat1->patternSize; + + rValue = memcmp(pat1->pattern, pat2->pattern, minSize); + if (rValue) + return rValue; + + return ((int)pat1->patternSize - (int)pat2->patternSize); +} + +/* Pattern trees are not freed on error because in case of error, caller + * should call detroyTreesRecursively. + */ +static int createTreesRecusively(void* root) +{ + tPatternRootNode* rootNode = (tPatternRootNode*)root; + SearchTool* patternMatcher; + tPatternList* patternNode; + + /* set up the MPSE for url patterns */ + if (!(patternMatcher = rootNode->patternTree = new SearchTool("ac_full"))) + return -1; + + for (patternNode = rootNode->patternList; + patternNode; + patternNode = patternNode->nextPattern) + { + /*recursion into next lower level */ + if (patternNode->nextLevelMatcher) + { + if (createTreesRecusively(patternNode->nextLevelMatcher)) + return -1; + } + + patternMatcher->add(patternNode->pattern.pattern, + patternNode->pattern.patternSize, + patternNode, + false); + } + + patternMatcher->prep(); + + return 0; +} + +static void destroyTreesRecursively(void* root) +{ + tPatternRootNode* rootNode = (tPatternRootNode*)root; + tPatternList* patternNode; + + while ((patternNode = rootNode->patternList)) + { + /*recursion into next lower level */ + if (patternNode->nextLevelMatcher) + { + destroyTreesRecursively(patternNode->nextLevelMatcher); + } + rootNode->patternList = patternNode->nextPattern; + snort_free(patternNode); + } + + delete rootNode->patternTree; + snort_free(rootNode); +} + +static void dumpTreesRecursively(void* root, int level) +{ + tPatternRootNode* rootNode = (tPatternRootNode*)root; + tPatternList* patternNode; + char* offset; + + offset = (char*)snort_calloc(4*level+2); + if (!offset) + return; + memset(offset, ' ', 4*level+1); + offset[4*level] = '\0'; + + for (patternNode = rootNode->patternList; + patternNode; + patternNode = patternNode->nextPattern) + { + printf("%sPattern %s, size %u, userData %p\n", offset, + (char*)patternNode->pattern.pattern, + (u_int32_t)patternNode->pattern.patternSize, + patternNode->userData); + + /*recursion into next lower level */ + if (patternNode->nextLevelMatcher) + { + dumpTreesRecursively(patternNode->nextLevelMatcher, (level+1)); + } + } + snort_free(offset); +} + +static int longest_pattern_match(void* id, void*, int index, void* data, + void*) +{ + tPatternList* target = (tPatternList*)id; + MatchedPattern* match = (MatchedPattern*)data; + int newMatchWins = 0; + + /*printf("LongestMatcher: level %d, index: %d, matched %s\n", matches->level, index, + target->pattern.pattern); */ + + /*first match */ + if (!match->patternNode) + newMatchWins = 1; + /*subsequent longer match */ + else if (match->patternNode->pattern.patternSize < target->pattern.patternSize) + newMatchWins = 1; + + if (newMatchWins) + { + /*printf("new pattern wins\n"); */ + match->patternNode = target; + match->index = index; + } + + return 0; +} + +static int url_pattern_match(void* id, void*, int index, void* data, void*) +{ + tPatternList* target = (tPatternList*)id; + MatchedPattern* match = (MatchedPattern*)data; + int newMatchWins = 0; + + /*printf("UrlMatcher: level %d, index: %d, matched %s\n", match->level, index, + target->pattern.pattern); + first match */ + if (!match->patternNode) + newMatchWins = 1; + + /*subsequent longer match */ + else if (match->patternNode->pattern.patternSize < target->pattern.patternSize) + newMatchWins = 1; + else if (match->patternNode->pattern.patternSize == target->pattern.patternSize) + { + /*host part matching towards later part is better. This is not designed to prevent + mis-identifying + url 'www.spoof_for_google.google.com.phishing.com' as google. */ + if ((match->level == 0) && (match->index < (unsigned int)index)) + newMatchWins = 1; + /*path part matching towards lower index is better */ + if ((match->level == 1) && (match->index > (unsigned int)index)) + newMatchWins = 1; + } + + if (newMatchWins) + { + /*printf("new pattern wins\n"); */ + match->patternNode = target; + match->index = index; + } + + return 0; +} + +static int addPatternRecursively(void* root, const tMlpPattern** inputPatternList, void* metaData, + int level) +{ + tPatternRootNode* rootNode = (tPatternRootNode*)root; + tPatternList* prevNode = nullptr; + tPatternList* patternList; + tPatternList* newNode; + const tMlpPattern* nextPattern; + const tMlpPattern* patterns = *inputPatternList; + int rvalue; + + if (!rootNode || !patterns || !patterns->pattern) + return -1; + + for (patternList = rootNode->patternList; + patternList; + prevNode = patternList, patternList = patternList->nextPattern) + { + rvalue = compareAppUrlPatterns(patterns, patternList); + if (rvalue < 0) + continue; + if (rvalue == 0) + { + nextPattern = *(inputPatternList+1); + + if (!nextPattern || !nextPattern->pattern) + { + /*overriding any previous userData. */ + patternList->userData = metaData; + return 0; + } + return addPatternRecursively(patternList->nextLevelMatcher, inputPatternList+1, + metaData, level+1); + } + break; + } + + /*allocate and initialize a new node */ + newNode = (tPatternList*)snort_calloc(sizeof(tPatternList)); + newNode->pattern.pattern = patterns->pattern; + newNode->pattern.patternSize = patterns->patternSize; + newNode->nextLevelMatcher = (tPatternRootNode*)snort_calloc(sizeof(tPatternRootNode)); + newNode->nextLevelMatcher->level = rootNode->level+1; + + /*insert the new node */ + if (!prevNode) + { + /*insert as first node since either this is the only node, or this is lexically smallest. + */ + newNode->nextPattern = rootNode->patternList; + rootNode->patternList = newNode; + } + else + { + /*insert after previous node since either there is either a biggest node after prevNode or + newNode is lexically largest. */ + newNode->nextPattern = prevNode->nextPattern; + prevNode->nextPattern = newNode; + } + + /*move down the new node */ + nextPattern = *(inputPatternList+1); + if (!nextPattern || !nextPattern->pattern) + { + newNode->userData = metaData; + } + else + { + addPatternRecursively(newNode->nextLevelMatcher, inputPatternList+1, metaData, level+1); + } + + return 0; +} + +/**returns pattern tree at the level where inputPatternList runs out. + */ +void* mlpGetPatternMatcherTree(void* root, tMlpPattern** inputPatternList) +{ + MatchedPattern mp = { nullptr,0,0 }; + tPatternList* patternNode; + tPatternRootNode* rootNode = (tPatternRootNode*)root; + tMlpPattern* pattern = *inputPatternList; + + if (!rootNode || !pattern || !pattern->pattern) + return nullptr; + + mp.level = rootNode->level; + + rootNode->patternTree->find_all((char*)pattern->pattern, + pattern->patternSize, + longest_pattern_match, + false, + &mp); + + patternNode = mp.patternNode; + if (patternNode) + { + ++inputPatternList; + if (*inputPatternList && (*inputPatternList)->pattern) + { + return mlpMatchPatternCustom(patternNode->nextLevelMatcher, inputPatternList, + longest_pattern_match); + } + return patternNode->nextLevelMatcher; + } + + return nullptr; +} + diff --git a/src/network_inspectors/appid/util/sf_multi_mpse.h b/src/network_inspectors/appid/util/sf_multi_mpse.h new file mode 100644 index 000000000..cdae5998d --- /dev/null +++ b/src/network_inspectors/appid/util/sf_multi_mpse.h @@ -0,0 +1,46 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sf_multi_mpse.h author Sourcefire Inc. + +#ifndef SF_MULTI_MPSE_H +#define SF_MULTI_MPSE_H + +#include +#include + +struct tMlpPattern +{ + const uint8_t* pattern; + size_t patternSize; +}; + +void* mlpCreate(); +int mlpAddPattern(void* root, const tMlpPattern** patterns, void* metaData); +int mlpProcessPatterns(void* root); +void* mlpMatchPatternLongest(void* root, tMlpPattern** inputPatternList); +void* mlpMatchPatternUrl(void* root, tMlpPattern** inputPatternList); +void* mlpMatchPatternCustom(void* root, tMlpPattern** inputPatternList, + int (* callback)(void*, void*, int, void*, void*)); +void mlpDestroy(void* root); +void mlpDump(void* root); +void* mlpGetPatternMatcherTree(void* root, tMlpPattern** inputPatternList); + +#endif + diff --git a/src/network_inspectors/appid/util/sfksearch.cc b/src/network_inspectors/appid/util/sfksearch.cc new file mode 100644 index 000000000..1f1c47e7c --- /dev/null +++ b/src/network_inspectors/appid/util/sfksearch.cc @@ -0,0 +1,951 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sfksearch.cc author Sourcefire Inc. + +/* +* +* Basic Keyword Search Trie - uses linked lists to build the finite automata +* +* Keyword-Match: Performs the equivalent of a multi-string strcmp() +* - use for token testing after parsing the language tokens using lex or the like. +* +* Keyword-Search: searches the input text for one of multiple keywords, +* and supports case sensitivite and case insensitive patterns. +* +*/ + +#include "sfksearch.h" + +#include +#include +#include +#include +#include + +#include "utils/util.h" + +static void KTrieFree(KTRIENODE* n); + +static unsigned int mtot = 0; + +unsigned int KTrieMemUsed(void) { return mtot; } + +void KTrieInitMemUsed(void) +{ + mtot = 0; +} + +/* +* Allocate Memory +*/ +static void* KTRIE_MALLOC(int n) +{ + void* p; + + if (n < 1) + return nullptr; + + p = snort_calloc(n); + mtot += n; + + return p; +} + +/* +* Free Memory +*/ +static void KTRIE_FREE(void* p) +{ + if (p == nullptr) + return; + + snort_free(p); +} + +/* +* Local/Tmp nocase array +*/ +static unsigned char Tnocase[65*1024]; + +/* +** Case Translation Table +*/ +static unsigned char xlatcase[256]; + +static void init_xlatcase(void) +{ + int i; + static int first=1; + + if ( !first ) + return; /* thread safe */ + + for (i=0; i<256; i++) + { + xlatcase[ i ] = (unsigned char)tolower(i); + } + + first=0; +} + +static inline void ConvertCaseEx(unsigned char* d, const unsigned char* s, int m) +{ + int i; + for ( i=0; i < m; i++ ) + { + d[i] = xlatcase[ s[i] ]; + } +} + +KTRIE_STRUCT* KTrieNew(int method, void (* userfree)(void* p), + void (* optiontreefree)(void** p), + void (* neg_list_free)(void** p)) +{ + KTRIE_STRUCT* ts = (KTRIE_STRUCT*)KTRIE_MALLOC(sizeof(KTRIE_STRUCT) ); + + if ( !ts ) + return 0; + + memset(ts, 0, sizeof(KTRIE_STRUCT)); + + init_xlatcase(); + + ts->memory = sizeof(KTRIE_STRUCT); + ts->nchars = 0; + ts->npats = 0; + ts->end_states = 0; + ts->method = method; /* - old method, 1 = queue */ + ts->userfree = userfree; + ts->optiontreefree = optiontreefree; + ts->neg_list_free = neg_list_free; + + return ts; +} + +int KTriePatternCount(KTRIE_STRUCT* k) +{ + return k->npats; +} + +/* + * Deletes memory that was used in creating trie + * and nodes + */ +void KTrieDelete(KTRIE_STRUCT* k) +{ + KTRIEPATTERN* p = nullptr; + KTRIEPATTERN* pnext = nullptr; + int i; + + if (k == nullptr) + return; + + p = k->patrn; + + while (p != nullptr) + { + pnext = p->next; + + if (k->userfree && p->id) + k->userfree(p->id); + + if (k->optiontreefree) + { + if (p && p->rule_option_tree) + k->optiontreefree(&p->rule_option_tree); + } + + if (k->neg_list_free) + { + if (p && p->neg_list) + k->neg_list_free(&p->neg_list); + } + + KTRIE_FREE(p->P); + KTRIE_FREE(p->Pcase); + KTRIE_FREE(p); + + p = pnext; + } + + for (i = 0; i < KTRIE_ROOT_NODES; i++) + KTrieFree(k->root[i]); + + KTRIE_FREE(k); +} + +/* + * Recursively delete all nodes in trie + */ +static void KTrieFree(KTRIENODE* n) +{ + if (n == nullptr) + return; + + KTrieFree(n->child); + KTrieFree(n->sibling); + + KTRIE_FREE(n); +} + +static KTRIEPATTERN* KTrieNewPattern(unsigned char* P, int n) +{ + KTRIEPATTERN* p; + + if (n < 1) + return nullptr; + + p = (KTRIEPATTERN*)KTRIE_MALLOC(sizeof(KTRIEPATTERN) ); + + if (p == nullptr) + return nullptr; + + /* Save as a nocase string */ + p->P = (unsigned char*)KTRIE_MALLOC(n); + if ( !p->P ) + { + KTRIE_FREE(p); + return nullptr; + } + + ConvertCaseEx(p->P, P, n); + + /* Save Case specific version */ + p->Pcase = (unsigned char*)KTRIE_MALLOC(n); + if ( !p->Pcase ) + { + KTRIE_FREE(p->P); + KTRIE_FREE(p); + return nullptr; + } + + memcpy(p->Pcase, P, n); + + p->n = n; + p->next = nullptr; + + return p; +} + +/* +* Add Pattern info to the list of patterns +*/ +int KTrieAddPattern(KTRIE_STRUCT* ts, unsigned char* P, int n, + int nocase, int negative, void* id) +{ + KTRIEPATTERN* pnew; + + if ( !ts->patrn ) + { + pnew = ts->patrn = KTrieNewPattern(P, n); + + if ( !pnew ) + return -1; + } + else + { + pnew = KTrieNewPattern(P, n); + + if ( !pnew ) + return -1; + + pnew->next = ts->patrn; /* insert at head of list */ + + ts->patrn = pnew; + } + + pnew->nocase = nocase; + pnew->negative = negative; + pnew->id = id; + pnew->mnext = nullptr; + + ts->npats++; + ts->memory += sizeof(KTRIEPATTERN) + 2 * n; /* Case and nocase */ + + return 0; +} + +static KTRIENODE* KTrieCreateNode(KTRIE_STRUCT* ts) +{ + KTRIENODE* t=(KTRIENODE*)KTRIE_MALLOC(sizeof(KTRIENODE) ); + + if (!t) + return 0; + + memset(t,0,sizeof(KTRIENODE)); + + ts->memory += sizeof(KTRIENODE); + + return t; +} + +/* +* Insert a Pattern in the Trie +*/ +static int KTrieInsert(KTRIE_STRUCT* ts, KTRIEPATTERN* px) +{ + int type = 0; + int n = px->n; + unsigned char* P = px->P; + KTRIENODE* root; + + /* Make sure we at least have a root character for the tree */ + if ( !ts->root[*P] ) + { + ts->root[*P] = root = KTrieCreateNode(ts); + if ( !root ) + return -1; + root->edge = *P; + } + else + { + root = ts->root[*P]; + } + + /* Walk existing Patterns */ + while ( n ) + { + if ( root->edge == *P ) + { + P++; + n--; + + if ( n && root->child ) + { + root=root->child; + } + else /* cannot continue */ + { + type = 0; /* Expand the tree via the child */ + break; + } + } + else + { + if ( root->sibling ) + { + root=root->sibling; + } + else /* cannot continue */ + { + type = 1; /* Expand the tree via the sibling */ + break; + } + } + } + + /* + * Add the next char of the Keyword, if any + */ + if ( n ) + { + if ( type == 0 ) + { + /* + * Start with a new child to finish this Keyword + */ + root->child= KTrieCreateNode(ts); + if ( !root->child ) + return -1; + root=root->child; + root->edge = *P; + P++; + n--; + ts->nchars++; + } + else + { + /* + * Start a new sibling bracnch to finish this Keyword + */ + root->sibling= KTrieCreateNode(ts); + if ( !root->sibling ) + return -1; + root=root->sibling; + root->edge = *P; + P++; + n--; + ts->nchars++; + } + } + + /* + * Finish the keyword as child nodes + */ + while ( n ) + { + root->child = KTrieCreateNode(ts); + if ( !root->child ) + return -1; + root=root->child; + root->edge = *P; + P++; + n--; + ts->nchars++; + } + + if ( root->pkeyword ) + { + px->mnext = root->pkeyword; /* insert duplicates at front of list */ + root->pkeyword = px; + ts->duplicates++; + } + else + { + root->pkeyword = px; + ts->end_states++; + } + + return 0; +} + +static void Build_Bad_Character_Shifts(KTRIE_STRUCT* kt) +{ + int i,k; + KTRIEPATTERN* plist; + + /* Calc the min pattern size */ + kt->bcSize = 32000; + + for ( plist=kt->patrn; plist!=nullptr; plist=plist->next ) + { + if ( plist->n < kt->bcSize ) + { + kt->bcSize = plist->n; /* smallest pattern size */ + } + } + + /* + * Initialze the Bad Character shift table. + */ + for (i = 0; i < KTRIE_ROOT_NODES; i++) + { + kt->bcShift[i] = (unsigned short)kt->bcSize; + } + + /* + * Finish the Bad character shift table + */ + for ( plist=kt->patrn; plist!=nullptr; plist=plist->next ) + { + int shift, cindex; + + for ( k=0; kbcSize; k++ ) + { + shift = kt->bcSize - 1 - k; + + cindex = plist->P[ k ]; + + if ( shift < kt->bcShift[ cindex ] ) + { + kt->bcShift[ cindex ] = (unsigned short)shift; + } + } + } +} + +static int KTrieBuildMatchStateNode(KTRIENODE* root, + int (* build_tree)(void* id, void** existing_tree), + int (* neg_list_func)(void* id, void** list)) +{ + int cnt = 0; + KTRIEPATTERN* p; + + if (!root) + return 0; + + /* each and every prefix match at this root*/ + if (root->pkeyword) + { + for (p = root->pkeyword; p; p = p->mnext) + { + if (p->id) + { + if (p->negative) + { + neg_list_func(p->id, &root->pkeyword->neg_list); + } + else + { + build_tree(p->id, &root->pkeyword->rule_option_tree); + } + } + + cnt++; + } + + /* Last call to finalize the tree for this root */ + build_tree(nullptr, &root->pkeyword->rule_option_tree); + } + + /* for child of this root */ + if (root->child) + { + cnt += KTrieBuildMatchStateNode(root->child, build_tree, neg_list_func); + } + + /* 1st sibling of this root -- other siblings will be processed from + * within the processing for root->sibling. */ + if (root->sibling) + { + cnt += KTrieBuildMatchStateNode(root->sibling, build_tree, neg_list_func); + } + + return cnt; +} + +static int KTrieBuildMatchStateTrees(KTRIE_STRUCT* ts, + int (* build_tree)(void* id, void** existing_tree), + int (* neg_list_func)(void* id, void** list)) +{ + int i, cnt = 0; + KTRIENODE* root; + + /* Find the states that have a MatchList */ + for (i = 0; i < KTRIE_ROOT_NODES; i++) + { + root = ts->root[i]; + /* each and every prefix match at this root*/ + if (root) + { + cnt += KTrieBuildMatchStateNode(root, build_tree, neg_list_func); + } + } + + return cnt; +} + +/* +* Build the Keyword TRIE +* +*/ +int KTrieCompile(KTRIE_STRUCT* ts, + int (* build_tree)(void* id, void** existing_tree), + int (* neg_list_func)(void* id, void** list)) +{ + KTRIEPATTERN* p; + /* + static int tmem=0; + */ + + /* + * Build the Keyword TRIE + */ + for ( p=ts->patrn; p; p=p->next ) + { + if ( KTrieInsert(ts, p) ) + return -1; + } + + /* + * Build A Setwise Bad Character Shift Table + */ + Build_Bad_Character_Shifts(ts); + + /* + tmem += ts->memory; + printf(" Compile stats: %d patterns, %d chars, %d duplicate patterns, %d bytes, %d total-bytes\n",ts->npats,ts->nchars,ts->duplicates,ts->memory,tmem); + */ + if (build_tree && neg_list_func) + { + KTrieBuildMatchStateTrees(ts, build_tree, neg_list_func); + } + + return 0; +} + +void sfksearch_print_qinfo(void) +{ +#ifdef SFKSEARCH_TRACK_Q + if ( snort_conf->max_inq ) + { + LogMessage("lowmem: queue size = %u, max = %u\n", snort_conf->max_inq,SFK_MAX_INQ); + LogMessage("lowmem: queue flushes = " STDu64 "\n", snort_conf->tot_inq_flush); + LogMessage("lowmem: queue inserts = " STDu64 "\n", snort_conf->tot_inq_inserts); + LogMessage("lowmem: queue uinserts = " STDu64 "\n", snort_conf->tot_inq_uinserts); + } +#endif +} + +static inline void _init_queue(SFK_PMQ* b) +{ + b->inq=0; + b->inq_flush=0; +} + +/* uniquely insert into q */ +static inline int _add_queue(SFK_PMQ* b, KTRIEPATTERN* p) +{ + int i; + +#ifdef SFKSEARCH_TRACK_Q + snort_conf->tot_inq_inserts++; +#endif + + for (i=(int)(b->inq)-1; i>=0; i--) + if ( p == b->q[i] ) + return 0; + +#ifdef SFKSEARCH_TRACK_Q + snort_conf->tot_inq_uinserts++; +#endif + + if ( b->inq < SFK_MAX_INQ ) + { + b->q[ b->inq++ ] = p; + } + + if ( b->inq == SFK_MAX_INQ ) + { +#ifdef SFKSEARCH_TRACK_Q + b->inq_flush++; +#endif + return 1; + } + return 0; +} + +static inline unsigned _process_queue(SFK_PMQ* q, + int (* match)(void* id, void* tree, int index, void* data, void* neg_list), + void* data) +{ + KTRIEPATTERN* pk; + unsigned int i; + +#ifdef SFKSEARCH_TRACK_Q + if ( q->inq > snort_conf->max_inq ) + snort_conf->max_inq = q->inq; + snort_conf->tot_inq_flush += q->inq_flush; +#endif + + for ( i=0; iinq; i++ ) + { + pk = q->q[i]; + if (pk) + { + if (match (pk->id, pk->rule_option_tree, 0, data, pk->neg_list) > 0) + { + q->inq=0; + return 1; + } + } + } + q->inq=0; + return 0; +} + +static inline int KTriePrefixMatchQ(KTRIE_STRUCT* kt, + const unsigned char* T, + int n, + int (* match)(void* id, void* tree, int index, void* data, void* neg_list), + void* data) +{ + KTRIENODE* root; + + root = kt->root[ xlatcase[*T] ]; + + if ( !root ) + return 0; + + while ( n ) + { + if ( root->edge == xlatcase[*T] ) + { + T++; + n--; + + if ( root->pkeyword ) + { + if ( _add_queue(&kt->q, root->pkeyword) ) + { + if ( _process_queue(&kt->q,match,data) ) + { + return 1; + } + } + } + + if ( n && root->child ) + { + root = root->child; + } + else /* cannot continue -- match is over */ + { + break; + } + } + else + { + if ( root->sibling ) + { + root = root->sibling; + } + else /* cannot continue */ + { + break; + } + } + } + + return 0; +} + +/* +* Search - Algorithm +* +* This routine will log any substring of T that matches a keyword, +* and processes all prefix matches. This is used for generic +* pattern searching with a set of keywords and a body of text. +* +* +* +* kt- Trie Structure +* T - nocase text +* Tc- case specific text +* n - text length +* +* returns: +* # pattern matches +*/ +static inline int KTriePrefixMatch(KTRIE_STRUCT* kt, + const unsigned char* T, + const unsigned char*, + const unsigned char* bT, + int n, + int (* match)(void* id, void* tree, int index, void* data, void* neg_list), + void* data) +{ + KTRIENODE* root = kt->root[ *T ]; + int nfound = 0; + KTRIEPATTERN* pk; + int index; + + /* Check if any keywords start with this character */ + if ( !root ) + return 0; + + while ( n ) + { + if ( root->edge == *T ) + { + T++; + n--; + + pk = root->pkeyword; + if (pk) + { + index = (int)(T - bT - pk->n ); + nfound++; + if (match (pk->id, pk->rule_option_tree, index, data, pk->neg_list) > 0) + { + return nfound; + } + } + + if ( n && root->child ) + { + root = root->child; + } + else /* cannot continue -- match is over */ + { + break; + } + } + else + { + if ( root->sibling ) + { + root = root->sibling; + } + else /* cannot continue */ + { + break; + } + } + } + + return nfound; +} + +static inline int KTrieSearchQ(KTRIE_STRUCT* ks, const unsigned char* T, int n, + int (* match)(void* id, void* tree, int index, void* data, void* neg_list), + void* data) +{ + _init_queue(&ks->q); + while ( n > 0 ) + { + if ( KTriePrefixMatchQ(ks, T++, n--, match, data) ) + return 0; + } + _process_queue(&ks->q,match,data); + + return 0; +} + +static inline int KTrieSearchNoBC(KTRIE_STRUCT* ks, const unsigned char* Tx, + int n, + int (* match)(void* id, void* tree, int index, void* data, void* neg_list), + void* data) +{ + int nfound = 0; + const unsigned char* T, * bT; + + ConvertCaseEx(Tnocase, Tx, n); + + T = Tnocase; + bT = T; + + for (; n>0; n--, T++, Tx++ ) + { + nfound += KTriePrefixMatch(ks, T, Tx, bT, n, match, data); + } + + return nfound; +} + +static inline int KTrieSearchBC(KTRIE_STRUCT* ks, const unsigned char* Tx, + int n, + int (* match)(void* id, void* tree, int index, void* data, void* neg_list), + void* data) +{ + int tshift; + const unsigned char* Tend; + const unsigned char* T, * bT; + int nfound = 0; + short* bcShift = (short*)ks->bcShift; + int bcSize = ks->bcSize; + + ConvertCaseEx(Tnocase, Tx, n); + + T = Tnocase; + bT = T; + + Tend = T + n - bcSize; + + bcSize--; + + for (; T <= Tend; n--, T++, Tx++ ) + { + while ( (tshift = bcShift[ *( T + bcSize ) ]) > 0 ) + { + T += tshift; + Tx += tshift; + if ( T > Tend ) + return nfound; + } + + nfound += KTriePrefixMatch(ks, T, Tx, bT, n, match, data); + } + + return nfound; +} + +int KTrieSearch(KTRIE_STRUCT* ks, const unsigned char* T, int n, + int (* match)(void* id, void* tree, int index, void* data, void* neg_list), + void* data) +{ + if ( ks->method == KTRIEMETHOD_QUEUE ) + { + /*if( ks->bcSize < 3) */ + return KTrieSearchQ(ks, T, n, match, data); + /*else + return KTrieSearchQBC( ks, T, n, match, data ); */ + } + else + { + if ( ks->bcSize < 3) + return KTrieSearchNoBC(ks, T, n, match, data); + else + return KTrieSearchBC(ks, T, n, match, data); + } +} + +/* +* +* TEST DRIVER FOR KEYWORD TRIE +* +*/ +#ifdef KTRIE_MAIN + +char** gargv; + +int trie_nmatches = 0; + +int match(unsigned id, int index, void* data) +{ + trie_nmatches++; + data = data; + printf("id=%d found at index=%d, %s\n",id,index,gargv[id]); + return 0; +} + +int main(int argc, char** argv) +{ + int i; + KTRIE_STRUCT* ts; + int nocase=1; /* don't care about case */ + + gargv = argv; + + ts = KTrieNew(); + + if ( argc < 3 ) + { + printf("%s text pat1 pat2 ... patn [-c(ase-sensitive)\n",argv[0]); + printf("search for keywords-default, or match keywords\n"); + exit(0); + } + + for (i=1; i %d characters, %d patterns, %d bytes allocated\n",ts->nchars,ts->npats,ts->memory); + + printf("Searching...\n"); + + KTrieSearch(ts, (unsigned char*)argv[1], strlen(argv[1]), match, 0); + + printf("%d matches found\n",trie_nmatches); + + printf("normal pgm finish.\n"); + + return 0; +} + +#endif + diff --git a/src/network_inspectors/appid/util/sfksearch.h b/src/network_inspectors/appid/util/sfksearch.h new file mode 100644 index 000000000..ef6ddb3df --- /dev/null +++ b/src/network_inspectors/appid/util/sfksearch.h @@ -0,0 +1,112 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2003-2013 Sourcefire, Inc. +// Copyright (C) 2001 Marc Norton +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sfksearch.h author Sourcefire Inc. + +#ifndef KTRIE_H +#define KTRIE_H + +// Trie based multi-pattern matcher + +#define ALPHABET_SIZE 256 + +#ifdef WIN32 +#define inline __inline +#endif + +#define KTRIEMETHOD_STD 0 +#define KTRIEMETHOD_QUEUE 1 + +struct KTRIEPATTERN +{ + KTRIEPATTERN* next; /* global list of all patterns */ + KTRIEPATTERN* mnext; /* matching list of duplicate keywords */ + + unsigned char* P; /* no case */ + unsigned char* Pcase; /* case sensitive */ + int n; + int nocase; + int negative; + void* id; + void* rule_option_tree; + void* neg_list; +}; + +struct KTRIENODE +{ + int edge; /* character */ + + KTRIENODE* sibling; + KTRIENODE* child; + + KTRIEPATTERN* pkeyword; +}; + +#define KTRIE_ROOT_NODES 256 + +#define SFK_MAX_INQ 32 +struct SFK_PMQ +{ + unsigned inq; + unsigned inq_flush; + KTRIEPATTERN* q[SFK_MAX_INQ]; +}; + +struct KTRIE_STRUCT +{ + KTRIEPATTERN* patrn; /* List of patterns, built as they are added */ + + KTRIENODE* root[KTRIE_ROOT_NODES]; /* KTrie nodes */ + + int memory; + int nchars; + int npats; + int duplicates; + int method; + int end_states; /* should equal npats - duplicates */ + + int bcSize; + unsigned short bcShift[KTRIE_ROOT_NODES]; + void (* userfree)(void* p); + void (* optiontreefree)(void** p); + void (* neg_list_free)(void** p); + SFK_PMQ q; +}; + +KTRIE_STRUCT* KTrieNew(int method, void (* userfree)(void* p), + void (* optiontreefree)(void** p), + void (* neg_list_free)(void** p)); +int KTrieAddPattern(KTRIE_STRUCT* ts, unsigned char* P, int n, + int nocase, int negative, void* id); +int KTrieCompile(KTRIE_STRUCT* ts, + int (* build_tree)(void* id, void** existing_tree), + int (* neg_list_func)(void* id, void** list)); +int KTrieSearch(KTRIE_STRUCT* ts, const unsigned char* T, int n, + int (* match)(void* id, void* tree, int index, void* data, void* neg_list), + void* data); +unsigned int KTrieMemUsed(); +void KTrieInitMemUsed(); +void KTrieDelete(KTRIE_STRUCT* k); +int KTriePatternCount(KTRIE_STRUCT* k); + +void sfksearch_print_qinfo(); + +#endif + diff --git a/src/network_inspectors/appid/util/sfutil.cc b/src/network_inspectors/appid/util/sfutil.cc new file mode 100644 index 000000000..f8acedd6a --- /dev/null +++ b/src/network_inspectors/appid/util/sfutil.cc @@ -0,0 +1,165 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sfutil.cc author Sourcefire Inc. + +#include "sfutil.h" +#include "common_util.h" + +#include +#include +#include + +#include "utils/util.h" + +void ConfigItemFree(ConfigItem* ci) +{ + if (ci) + { + if (ci->name) + snort_free(ci->name); + if (ci->value) + snort_free(ci->value); + snort_free(ci); + } +} + +int Split(char* data, char** toklist, int max_toks, const char* separator) +{ + char** ap; + int argcount = 0; + + memset(toklist, 0, max_toks * sizeof(*toklist)); + for (ap = (char**)toklist; + ap < &toklist[max_toks] && (*ap=strsep(&data, separator)) != nullptr; ) + { + if (**ap != '\0') + { + ap++; + argcount++; + } + } + + return argcount; +} + +void InitNetmasks(uint32_t netmasks[]) +{ + netmasks[0] = 0x0; + netmasks[1] = 0x80000000; + netmasks[2] = 0xC0000000; + netmasks[3] = 0xE0000000; + netmasks[4] = 0xF0000000; + netmasks[5] = 0xF8000000; + netmasks[6] = 0xFC000000; + netmasks[7] = 0xFE000000; + netmasks[8] = 0xFF000000; + netmasks[9] = 0xFF800000; + netmasks[10] = 0xFFC00000; + netmasks[11] = 0xFFE00000; + netmasks[12] = 0xFFF00000; + netmasks[13] = 0xFFF80000; + netmasks[14] = 0xFFFC0000; + netmasks[15] = 0xFFFE0000; + netmasks[16] = 0xFFFF0000; + netmasks[17] = 0xFFFF8000; + netmasks[18] = 0xFFFFC000; + netmasks[19] = 0xFFFFE000; + netmasks[20] = 0xFFFFF000; + netmasks[21] = 0xFFFFF800; + netmasks[22] = 0xFFFFFC00; + netmasks[23] = 0xFFFFFE00; + netmasks[24] = 0xFFFFFF00; + netmasks[25] = 0xFFFFFF80; + netmasks[26] = 0xFFFFFFC0; + netmasks[27] = 0xFFFFFFE0; + netmasks[28] = 0xFFFFFFF0; + netmasks[29] = 0xFFFFFFF8; + netmasks[30] = 0xFFFFFFFC; + netmasks[31] = 0xFFFFFFFE; + netmasks[32] = 0xFFFFFFFF; +} + +int strip(char* data) +{ + int size; + char* idx; + + idx = data; + size = 0; + + while (*idx) + { + if ((*idx == '\n') || (*idx == '\r')) + { + *idx = 0; + break; + } + if (*idx == '\t') + { + *idx = ' '; + } + size++; + idx++; + } + + return size; +} + +int Tokenize(char* data, char* toklist[]) +{ + char** ap; + int argcount = 0; + int i = 0; + char* tok; + int drop_further = 0; + + for (ap = (char**)toklist; ap < &toklist[MAX_TOKS] && (*ap = strsep(&data, " ")) != nullptr; ) + { + if (**ap != '\0') + { + ap++; + argcount++; + } + } + + *ap = nullptr; + + /* scan for comments */ + while (i < argcount) + { + tok = toklist[i]; + + if (tok[0] == '#' && !drop_further) + { + argcount = i; + drop_further = 1; + } + + if (drop_further) + { + toklist[i] = nullptr; + } + + i++; + } + + return argcount; +} + diff --git a/src/network_inspectors/appid/util/sfutil.h b/src/network_inspectors/appid/util/sfutil.h new file mode 100644 index 000000000..ac2409947 --- /dev/null +++ b/src/network_inspectors/appid/util/sfutil.h @@ -0,0 +1,34 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2014-2016 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2005-2013 Sourcefire, Inc. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// sfutil.h author Sourcefire Inc. + +#ifndef SFUTIL_H +#define SFUTIL_H + +#include + +int SFGetRelocatePathForFile(const char* const content_file, const char** const root_path); +extern int Tokenize(char* data, char* toklist[]); +extern int strip(char* data); +extern void InitNetmasks(uint32_t netmasks[]); +extern int Split(char* data, char** toklist, int max_toks, const char* separator); + +#endif + diff --git a/src/network_inspectors/network_inspectors.cc b/src/network_inspectors/network_inspectors.cc index 1db00e4fc..c32f4eb20 100644 --- a/src/network_inspectors/network_inspectors.cc +++ b/src/network_inspectors/network_inspectors.cc @@ -23,6 +23,7 @@ #endif #include "framework/inspector.h" +extern const BaseApi* nin_appid_inspector; extern const BaseApi* nin_binder; extern const BaseApi* nin_normalize; extern const BaseApi* nin_perf_monitor; @@ -37,6 +38,7 @@ extern const BaseApi* nin_packet_capture; const BaseApi* network_inspectors[] = { + nin_appid_inspector, nin_binder, nin_normalize, nin_perf_monitor, diff --git a/src/search_engines/ac_bnfa.cc b/src/search_engines/ac_bnfa.cc index c4a97f9ce..f429fabd3 100644 --- a/src/search_engines/ac_bnfa.cc +++ b/src/search_engines/ac_bnfa.cc @@ -85,6 +85,8 @@ public: obj, T, n, match, context, 0 /* start-state */, current_state); } + // FIXIT-L: Implement search_all method for AC_BNFA. + int print_info() override { bnfaPrintInfo(obj); diff --git a/src/search_engines/search_tool.cc b/src/search_engines/search_tool.cc index 8325210b6..9594edec1 100644 --- a/src/search_engines/search_tool.cc +++ b/src/search_engines/search_tool.cc @@ -27,9 +27,13 @@ #include "framework/mpse.h" #include "managers/mpse_manager.h" -SearchTool::SearchTool() +SearchTool::SearchTool() : SearchTool("ac_bnfa") { - mpse = MpseManager::get_search_engine("ac_bnfa"); +} + +SearchTool::SearchTool(const char* method) +{ + mpse = MpseManager::get_search_engine(method); max_len = 0; } @@ -43,12 +47,22 @@ void SearchTool::add(const char* pat, unsigned len, int id, bool no_case) add((uint8_t*)pat, len, id, no_case); } +void SearchTool::add(const char* pat, unsigned len, void* id, bool no_case) +{ + add((uint8_t*)pat, len, id, no_case); +} + void SearchTool::add(const uint8_t* pat, unsigned len, int id, bool no_case) +{ + add(pat, len, (void*)(long)id, no_case); +} + +void SearchTool::add(const uint8_t* pat, unsigned len, void* id, bool no_case) { Mpse::PatternDescriptor desc(no_case); if ( mpse ) - mpse->add_pattern(nullptr, pat, len, desc, (void*)(long)id); + mpse->add_pattern(nullptr, pat, len, desc, id); if ( len > max_len ) max_len = len; diff --git a/src/search_engines/search_tool.h b/src/search_engines/search_tool.h index 157c52865..02cd64eb3 100644 --- a/src/search_engines/search_tool.h +++ b/src/search_engines/search_tool.h @@ -26,10 +26,13 @@ class SearchTool { public: SearchTool(); + SearchTool(const char* method); ~SearchTool(); void add(const char* pattern, unsigned len, int s_id, bool no_case = true); void add(const uint8_t* pattern, unsigned len, int s_id, bool no_case = true); + void add(const char* pattern, unsigned len, void* s_context, bool no_case = true); + void add(const uint8_t* pattern, unsigned len, void* s_context, bool no_case = true); void prep(); diff --git a/src/search_engines/test/Makefile.am b/src/search_engines/test/Makefile.am index b0792ce62..a676debcd 100644 --- a/src/search_engines/test/Makefile.am +++ b/src/search_engines/test/Makefile.am @@ -1,12 +1,20 @@ AM_DEFAULT_SOURCE_EXT = .cc -if HAVE_HYPERSCAN check_PROGRAMS = \ -hyperscan_test +search_tool_test TESTS = $(check_PROGRAMS) +search_tool_test_CPPFLAGS = $(AM_CPPFLAGS) @CPPUTEST_CPPFLAGS@ +search_tool_test_LDADD = \ +../libsearch_engines.a \ +../../catch/unit_test.o \ +@CPPUTEST_LDFLAGS@ + +if HAVE_HYPERSCAN +check_PROGRAMS += hyperscan_test + hyperscan_test_CPPFLAGS = $(AM_CPPFLAGS) @CPPUTEST_CPPFLAGS@ hyperscan_test_LDADD = \ diff --git a/src/search_engines/test/search_tool_test.cc b/src/search_engines/test/search_tool_test.cc new file mode 100644 index 000000000..fad9fed1a --- /dev/null +++ b/src/search_engines/test/search_tool_test.cc @@ -0,0 +1,217 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2016-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- + +// search_tool_test.cc author Steve Chew + +// Change private to public to give access to private members. +#define private public +#include "search_engines/search_tool.h" +#undef private + +#include + +#include "framework/base_api.h" +#include "framework/mpse.h" +#include "managers/mpse_manager.h" +#include "main/snort_config.h" + +// must appear after snort_config.h to avoid broken c++ map include +#include +#include + + +//------------------------------------------------------------------------- +// base stuff +//------------------------------------------------------------------------- + +SnortConfig s_conf; +THREAD_LOCAL SnortConfig* snort_conf = &s_conf; + +static SnortState s_state; + +SnortConfig::SnortConfig() +{ + state = &s_state; + memset(state, 0, sizeof(*state)); + num_slots = 1; +} + +SnortConfig::~SnortConfig() { } + +unsigned get_instance_id() +{ return 0; } + +FileIdentifier::~FileIdentifier() { } + +FileVerdict FilePolicy::type_lookup(Flow*, FileContext*) +{ return FILE_VERDICT_UNKNOWN; } + +FileVerdict FilePolicy::type_lookup(Flow*, FileInfo*) +{ return FILE_VERDICT_UNKNOWN; } + +FileVerdict FilePolicy::signature_lookup(Flow*, FileContext*) +{ return FILE_VERDICT_UNKNOWN; } + +FileVerdict FilePolicy::signature_lookup(Flow*, FileInfo*) +{ return FILE_VERDICT_UNKNOWN; } + +void LogValue(const char*, const char*, FILE* = stdout) +{ +} + +SO_PUBLIC void LogMessage(const char*, ...) +{ +} + +void FatalError(const char*,...) +{ + exit(1); +} + +void LogCount(char const*, uint64_t, FILE*) +{ } + +void LogStat(const char*, double, FILE* = stdout) +{} + +static void* s_tree = (void*)"tree"; +static void* s_list = (void*)"list"; + +static MpseAgent s_agent = +{ + [](struct SnortConfig* sc, void*, void** ppt) + { + CHECK(sc == nullptr); + *ppt = s_tree; + return 0; + }, + [](void*, void** ppl) + { + *ppl = s_list; + return 0; + }, + + [](void*) { }, + [](void** ppt) { CHECK(*ppt == s_tree); }, + [](void** ppl) { CHECK(*ppl == s_list); } +}; + +extern const BaseApi* se_ac_full; +const MpseApi* mpse_api = (MpseApi*)se_ac_full; +Mpse* acf = nullptr; + +Mpse* MpseManager::get_search_engine(const char *type) +{ + acf = nullptr; + + if(strcmp(type, "ac_full") == 0) + { + CHECK(se_ac_full); + mpse_api->init(); + acf = mpse_api->ctor(snort_conf, nullptr, false, &s_agent); + CHECK(acf); + } + + return acf; +} + +void MpseManager::delete_search_engine(Mpse *) +{ + mpse_api->dtor(acf); +} + +Mpse::Mpse(const char*, bool) { } + +int Mpse::search( + const unsigned char* T, int n, MpseMatch match, + void* context, int* current_state) +{ + return _search(T, n, match, context, current_state); +} + +int Mpse::search_all( + const unsigned char* T, int n, MpseMatch match, + void* context, int* current_state) +{ + return _search(T, n, match, context, current_state); +} + +uint64_t Mpse::get_pattern_byte_count() +{ return 0; } + +void Mpse::reset_pattern_byte_count() +{ } + +int pattern_id = 0; +int Test_SearchStrFound(void* /* id */, void* /* tree */, int /* index */, void* /* context */, void* /* neg_list */) +{ + //printf("found str with id=%ld, index=%d\n", (long)id, index); + return 0; +} + +TEST_GROUP(search_tool_tests) +{ + void setup() + { + CHECK(se_ac_full); + } +}; + +TEST(search_tool_tests, ac_full) +{ + // get_search_engine returns nullptr any search engine other than ac_full. + SearchTool *stool = new SearchTool; + CHECK(!stool->mpse); + delete stool; + + stool = new SearchTool("ac_full"); + CHECK(stool->mpse); + + pattern_id = 1; + stool->add("the", 3, pattern_id); + CHECK(stool->max_len == 3); + + pattern_id = 77; + stool->add("uba", 3, pattern_id); + CHECK(stool->max_len == 3); + + pattern_id = 2112; + stool->add("away", 4, pattern_id); + CHECK(stool->max_len == 4); + + pattern_id = 1000; + stool->add("nothere", 7, pattern_id); + CHECK(stool->max_len == 7); + + stool->prep(); + + const char *datastr = "the tuba ran away"; + int result = stool->find(datastr, strlen(datastr), Test_SearchStrFound); + CHECK(result == 3); + delete stool; +} + +//------------------------------------------------------------------------- +// main +//------------------------------------------------------------------------- + +int main(int argc, char** argv) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +} + diff --git a/src/sfip/sfip_t.h b/src/sfip/sfip_t.h index f82d7e61f..2196366d2 100644 --- a/src/sfip/sfip_t.h +++ b/src/sfip/sfip_t.h @@ -55,6 +55,12 @@ struct sfip_t /* uint64_t ip64[2]; */ }; + inline void clear() + { + family = bits = 0; + ip32[0] = ip32[1] = ip32[2] = ip32[3] = 0; + } + inline bool is_ip6() const { return family == AF_INET6; } diff --git a/tools/snort2lua/preprocessor_states/CMakeLists.txt b/tools/snort2lua/preprocessor_states/CMakeLists.txt index 7610f4215..ba2830a87 100644 --- a/tools/snort2lua/preprocessor_states/CMakeLists.txt +++ b/tools/snort2lua/preprocessor_states/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(preprocessor_states + pps_appid.cc pps_arpspoof.cc pps_bo.cc pps_dcerpc.cc diff --git a/tools/snort2lua/preprocessor_states/Makefile.am b/tools/snort2lua/preprocessor_states/Makefile.am index eaf71105a..c621e55f8 100644 --- a/tools/snort2lua/preprocessor_states/Makefile.am +++ b/tools/snort2lua/preprocessor_states/Makefile.am @@ -2,6 +2,7 @@ noinst_LIBRARIES = libpreprocessor_states.a libpreprocessor_states_a_SOURCES = \ +pps_appid.cc \ pps_arpspoof.cc \ pps_bo.cc \ pps_dcerpc.cc \ diff --git a/tools/snort2lua/preprocessor_states/pps_appid.cc b/tools/snort2lua/preprocessor_states/pps_appid.cc new file mode 100644 index 000000000..ec429a8d1 --- /dev/null +++ b/tools/snort2lua/preprocessor_states/pps_appid.cc @@ -0,0 +1,173 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2015-2016 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// pps_appid.cc author davis mcpherson + +#include +#include + +#include "conversion_state.h" +#include "helpers/s2l_util.h" + +namespace preprocessors +{ +namespace +{ +class AppId : public ConversionState +{ +public: + AppId(Converter& c) : ConversionState(c) { } + virtual ~AppId() { } + virtual bool convert(std::istringstream& data_stream); +}; +} // namespace + +bool AppId::convert(std::istringstream& data_stream) +{ + std::string keyword; + bool retval = true; + + table_api.open_table("appid"); + + // parse the file configuration + while (util::get_string(data_stream, keyword, ",")) + { + bool tmpval = true; + std::istringstream arg_stream(keyword); + + // should be guaranteed to happen. Checking for error just cause + if (!(arg_stream >> keyword)) + tmpval = false; + else if (!keyword.compare("conf")) + { + std::string file_name; + if( arg_stream >> file_name) + { + tmpval = table_api.add_option("conf", file_name); + } + else + { + data_api.failed_conversion(arg_stream, "appid: conf "); + tmpval = false; + } + } + else if (!keyword.compare("memcap")) + { + tmpval = parse_int_option("memcap", arg_stream, false); + } + else if (!keyword.compare("debug")) + { + std::string val; + if (!(arg_stream >> val)) + data_api.failed_conversion(arg_stream, "appid: debug "); + else if (!val.compare("yes")) + table_api.add_option("debug", true); + else + table_api.add_option("debug", false); + } + else if (!keyword.compare("dump_ports")) + { + table_api.add_option("dump_ports", true); + } + else if (!keyword.compare("instance_id")) + { + tmpval = parse_int_option("instance_id", arg_stream, false); + } + else if (!keyword.compare("app_stats_filename")) + { + std::string file_name; + if (arg_stream >> file_name) + { + tmpval = table_api.add_option("app_stats_filename", file_name); + } + else + { + data_api.failed_conversion(arg_stream, "appid: app_stats_filename "); + tmpval = false; + } + } + else if (!keyword.compare("app_stats_period")) + { + tmpval = parse_int_option("app_stats_period", arg_stream, false); + } + else if (!keyword.compare("app_stats_rollover_size")) + { + tmpval = parse_int_option("app_stats_rollover_size", arg_stream, false); + } + else if (!keyword.compare("app_stats_rollover_time")) + { + tmpval = parse_int_option("app_stats_rollover_time", arg_stream, false); + } + else if (!keyword.compare("app_detector_dir")) + { + std::string file_name; + if (arg_stream >> file_name) + { + tmpval = table_api.add_option("app_detector_dir", file_name); + } + else + { + data_api.failed_conversion(arg_stream, "appid: app_detector_dir "); + tmpval = false; + } + } + else if (!keyword.compare("thirdparty_appid_dir")) + { + std::string file_name; + if (arg_stream >> file_name) + { + tmpval = table_api.add_option("thirdparty_appid_dir", file_name); + } + else + { + data_api.failed_conversion(arg_stream, "appid: thirdparty_appid_dir "); + tmpval = false; + } + } + else + { + tmpval = false; + } + + if (!tmpval) + { + data_api.failed_conversion(arg_stream, keyword); + retval = false; + } + } + + return retval; +} + +/************************** + ******* A P I *********** + **************************/ + +static ConversionState* ctor(Converter& c) +{ + return new AppId(c); +} + +static const ConvertMap preprocessor_appid = +{ + "appid", + ctor, +}; + +const ConvertMap* appid_map = &preprocessor_appid; +} + diff --git a/tools/snort2lua/preprocessor_states/preprocessor_api.cc b/tools/snort2lua/preprocessor_states/preprocessor_api.cc index ce4d91c3b..8aae72ce9 100644 --- a/tools/snort2lua/preprocessor_states/preprocessor_api.cc +++ b/tools/snort2lua/preprocessor_states/preprocessor_api.cc @@ -21,6 +21,7 @@ namespace preprocessors { +extern const ConvertMap* appid_map; extern const ConvertMap* arpspoof_map; extern const ConvertMap* arpspoof_host_map; extern const ConvertMap* bo_map; @@ -59,6 +60,7 @@ extern const ConvertMap* stream_udp_map; const std::vector preprocessor_api = { + appid_map, arpspoof_map, arpspoof_host_map, bo_map, -- 2.47.3