diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
commit | e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch) | |
tree | 68cb5ef9081156392f1dd62a00c6ccc1451b93df /extcap | |
parent | Initial commit. (diff) | |
download | wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip |
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'extcap')
-rw-r--r-- | extcap/.editorconfig | 9 | ||||
-rw-r--r-- | extcap/CMakeLists.txt | 387 | ||||
-rw-r--r-- | extcap/androiddump.c | 2801 | ||||
-rw-r--r-- | extcap/ciscodump.c | 2512 | ||||
-rw-r--r-- | extcap/dpauxmon.c | 589 | ||||
-rw-r--r-- | extcap/dpauxmon_user.h | 59 | ||||
-rw-r--r-- | extcap/etl.c | 784 | ||||
-rw-r--r-- | extcap/etl.h | 48 | ||||
-rw-r--r-- | extcap/etw_message.c | 419 | ||||
-rw-r--r-- | extcap/etw_message.h | 59 | ||||
-rw-r--r-- | extcap/etw_ndiscap.c | 709 | ||||
-rw-r--r-- | extcap/etwdump.c | 312 | ||||
-rw-r--r-- | extcap/extcap-base.c | 427 | ||||
-rw-r--r-- | extcap/extcap-base.h | 129 | ||||
-rw-r--r-- | extcap/falcodump.cpp | 1016 | ||||
-rw-r--r-- | extcap/randpktdump.c | 368 | ||||
-rw-r--r-- | extcap/sdjournal.c | 472 | ||||
-rw-r--r-- | extcap/ssh-base.c | 226 | ||||
-rw-r--r-- | extcap/ssh-base.h | 85 | ||||
-rw-r--r-- | extcap/sshdump.c | 694 | ||||
-rw-r--r-- | extcap/udpdump.c | 490 | ||||
-rw-r--r-- | extcap/wifidump.c | 747 |
22 files changed, 13342 insertions, 0 deletions
diff --git a/extcap/.editorconfig b/extcap/.editorconfig new file mode 100644 index 00000000..6f7bf689 --- /dev/null +++ b/extcap/.editorconfig @@ -0,0 +1,9 @@ +# +# Editor configuration +# +# https://editorconfig.org +# + +[{ciscodump,randpktdump,ssh-base,sshdump,udpdump,wifidump}.[ch]] +indent_style = tab +indent_size = tab diff --git a/extcap/CMakeLists.txt b/extcap/CMakeLists.txt new file mode 100644 index 00000000..48c45164 --- /dev/null +++ b/extcap/CMakeLists.txt @@ -0,0 +1,387 @@ +# CMakeLists.txt +# +# Wireshark - Network traffic analyzer +# By Gerald Combs <gerald@wireshark.org> +# Copyright 1998 Gerald Combs +# +# SPDX-License-Identifier: GPL-2.0-or-later +# + +if(EXTCAP_ANDROIDDUMP_LIBPCAP) + set(ANDROIDDUMP_USE_LIBPCAP 1) +endif() + +if(LIBSSH_FOUND) + include(CMakePushCheckState) + cmake_push_check_state() + set(CMAKE_REQUIRED_INCLUDES ${LIBSSH_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES ${LIBSSH_LIBRARIES}) + check_function_exists(ssh_userauth_agent LIBSSH_USERAUTH_AGENT_FOUND) + cmake_pop_check_state() +endif() + +# Ensure "run/extcap" exists +# add_custom_command(OUTPUT "${DATAFILE_DIR}/extcap" +# COMMAND ${CMAKE_COMMAND} -E make_directory +# "${DATAFILE_DIR}/extcap" +# ) +# list(APPEND copy_data_files_depends "${DATAFILE_DIR}/extcap") + + +macro(set_extcap_executable_properties _executable) + set_target_properties(${_executable} PROPERTIES + LINK_FLAGS "${WS_LINK_FLAGS}" + FOLDER "Executables/Extcaps" + INSTALL_RPATH "${EXTCAP_INSTALL_RPATH}" + ) + if(MSVC) + set_target_properties(${_executable} PROPERTIES LINK_FLAGS_DEBUG "${WS_MSVC_DEBUG_LINK_FLAGS}") + endif() + + set(PROGLIST ${PROGLIST} ${_executable}) + + if(CMAKE_CONFIGURATION_TYPES) + set_target_properties(${_executable} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/extcap + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/run/Debug/extcap + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/run/Release/extcap + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/run/MinSizeRel/extcap + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/run/RelWithDebInfo/extcap + ) + else() + set_target_properties(${_executable} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/extcap + ) + if(ENABLE_APPLICATION_BUNDLE) + if(NOT CMAKE_CFG_INTDIR STREQUAL ".") + # Xcode + set_target_properties(${_executable} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/$<CONFIG>/Wireshark.app/Contents/MacOS/extcap + ) + else() + set_target_properties(${_executable} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/Wireshark.app/Contents/MacOS/extcap + ) + # Create a convenience link from run/<name> to its respective + # target in the application bundle. + add_custom_target(${_executable}-symlink + COMMAND ln -s -f + Wireshark.app/Contents/MacOS/extcap/${_executable} + ${CMAKE_BINARY_DIR}/run/${_executable} + ) + add_dependencies(${_executable} ${_executable}-symlink) + endif() + endif() + endif() + + if (MINGW) + set_target_properties(${_executable} PROPERTIES + LINK_OPTIONS -municode + ) + endif() +endmacro() + +macro(set_extlog_executable_properties _executable) + set_target_properties(${_executable} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/extcap + ) + if(ENABLE_APPLICATION_BUNDLE) + if(NOT CMAKE_CFG_INTDIR STREQUAL ".") + # Xcode + set_target_properties(${_executable} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/$<CONFIG>/Logray.app/Contents/MacOS/extcap + ) + else() + set_target_properties(${_executable} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/run/Logray.app/Contents/MacOS/extcap + ) + # Create a convenience link from run/<name> to its respective + # target in the application bundle. + add_custom_target(${_executable}-symlink + COMMAND ln -s -f + Logray.app/Contents/MacOS/extcap/${_executable} + ${CMAKE_BINARY_DIR}/run/${_executable} + ) + add_dependencies(${_executable} ${_executable}-symlink) + endif() + endif() +endmacro() + +add_custom_target(extcaps) + +add_library(extcap-base OBJECT extcap-base.c) +if(LIBSSH_FOUND) + add_library(ssh-base OBJECT ssh-base.c) + target_include_directories(ssh-base SYSTEM + PRIVATE + ${LIBSSH_INCLUDE_DIRS} + ) +endif() + +if(BUILD_androiddump) + if(EXTCAP_ANDROIDDUMP_LIBPCAP) + if(HAVE_LIBPCAP) + set(androiddump_LIBS + ui + wiretap + wsutil + ${WIN_WS2_32_LIBRARY} + $<$<BOOL:${PCAP_FOUND}>:pcap::pcap> + ) + else() + message(FATAL_ERROR "You are trying to build androiddump with libpcap but do not have it") + endif() + else() + set(androiddump_LIBS + ui + wiretap + wsutil + ${CMAKE_DL_LIBS} + ${WIN_WS2_32_LIBRARY} + ) + endif() + set(androiddump_FILES + $<TARGET_OBJECTS:cli_main> + $<TARGET_OBJECTS:extcap-base> + androiddump.c + ) + + set_executable_resources(androiddump "Androiddump") + add_executable(androiddump ${androiddump_FILES}) + set_extcap_executable_properties(androiddump) + target_link_libraries(androiddump ${androiddump_LIBS}) + install(TARGETS androiddump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps androiddump) +endif() + +if(BUILD_sshdump AND LIBSSH_FOUND) + set(sshdump_LIBS + wsutil + ${CMAKE_DL_LIBS} + ${WIN_WS2_32_LIBRARY} + ${LIBSSH_LIBRARIES} + ) + set(sshdump_FILES + $<TARGET_OBJECTS:cli_main> + $<TARGET_OBJECTS:extcap-base> + $<TARGET_OBJECTS:ssh-base> + sshdump.c + ) + + set_executable_resources(sshdump "Sshdump") + add_executable(sshdump ${sshdump_FILES}) + set_extcap_executable_properties(sshdump) + target_link_libraries(sshdump ${sshdump_LIBS}) + target_include_directories(sshdump SYSTEM PRIVATE ${LIBSSH_INCLUDE_DIRS}) + install(TARGETS sshdump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps sshdump) +elseif (BUILD_sshdump) + #message( WARNING "Cannot find libssh, cannot build sshdump" ) +endif() + +if(BUILD_ciscodump AND LIBSSH_FOUND) + set(ciscodump_LIBS + writecap + wsutil + ${CMAKE_DL_LIBS} + ${WIN_WS2_32_LIBRARY} + ${LIBSSH_LIBRARIES} + ) + set(ciscodump_FILES + $<TARGET_OBJECTS:cli_main> + $<TARGET_OBJECTS:extcap-base> + $<TARGET_OBJECTS:ssh-base> + ciscodump.c + ) + + set_executable_resources(ciscodump "Ciscodump") + add_executable(ciscodump ${ciscodump_FILES}) + set_extcap_executable_properties(ciscodump) + target_link_libraries(ciscodump ${ciscodump_LIBS}) + target_include_directories(ciscodump SYSTEM PRIVATE ${LIBSSH_INCLUDE_DIRS}) + install(TARGETS ciscodump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps ciscodump) +elseif (BUILD_ciscodump) + #message( WARNING "Cannot find libssh, cannot build ciscodump" ) +endif() + +if(BUILD_wifidump AND LIBSSH_FOUND) + set(wifidump_LIBS + writecap + wsutil + ${CMAKE_DL_LIBS} + ${WIN_WS2_32_LIBRARY} + ${LIBSSH_LIBRARIES} + ) + set(wifidump_FILES + $<TARGET_OBJECTS:cli_main> + $<TARGET_OBJECTS:extcap-base> + $<TARGET_OBJECTS:ssh-base> + wifidump.c + ) + + set_executable_resources(wifidump "Wifidump") + add_executable(wifidump ${wifidump_FILES}) + set_extcap_executable_properties(wifidump) + target_link_libraries(wifidump ${wifidump_LIBS}) + target_include_directories(wifidump SYSTEM PRIVATE ${LIBSSH_INCLUDE_DIRS}) + install(TARGETS wifidump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps wifidump) +elseif (BUILD_wifidump) + #message( WARNING "Cannot find libssh, cannot build wifidump" ) +endif() + +if(BUILD_dpauxmon AND HAVE_LIBNL3) + set(dpauxmon_LIBS + wsutil + writecap + ${GLIB2_LIBRARIES} + ${CMAKE_DL_LIBS} + ${NL_LIBRARIES} + ) + set(dpauxmon_FILES + $<TARGET_OBJECTS:extcap-base> + dpauxmon.c + ) + + set_executable_resources(dpauxmon "dpauxmon") + add_executable(dpauxmon ${dpauxmon_FILES}) + set_extcap_executable_properties(dpauxmon) + target_link_libraries(dpauxmon ${dpauxmon_LIBS}) + target_include_directories(dpauxmon SYSTEM PRIVATE ${NL_INCLUDE_DIRS}) + install(TARGETS dpauxmon RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps dpauxmon) +elseif (BUILD_dpauxmon) + #message( WARNING "Cannot find libnl3, cannot build dpauxmon" ) +endif() + +if(BUILD_udpdump) + set(udpdump_LIBS + wsutil + ${CMAKE_DL_LIBS} + ${WIN_WS2_32_LIBRARY} + wsutil + writecap + ) + set(udpdump_FILES + $<TARGET_OBJECTS:cli_main> + $<TARGET_OBJECTS:extcap-base> + udpdump.c + ) + + set_executable_resources(udpdump "udpdump") + add_executable(udpdump ${udpdump_FILES}) + set_extcap_executable_properties(udpdump) + target_link_libraries(udpdump ${udpdump_LIBS}) + install(TARGETS udpdump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps udpdump) +endif() + +if(BUILD_randpktdump) + set(randpktdump_LIBS + randpkt_core + wiretap + wsutil + ${CMAKE_DL_LIBS} + ${WIN_WS2_32_LIBRARY} + ) + set(randpktdump_FILES + $<TARGET_OBJECTS:cli_main> + $<TARGET_OBJECTS:extcap-base> + randpktdump.c + ) + + set_executable_resources(randpktdump "randpktdump") + add_executable(randpktdump ${randpktdump_FILES}) + set_extcap_executable_properties(randpktdump) + target_link_libraries(randpktdump ${randpktdump_LIBS}) + install(TARGETS randpktdump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps randpktdump) +endif() + + +if(BUILD_etwdump AND WIN32) + set(etwdump_LIBS + wiretap + wsutil + tdh + wevtapi + rpcrt4 + ${CMAKE_DL_LIBS} + ${WIN_WS2_32_LIBRARY} + ) + set(etwdump_FILES + $<TARGET_OBJECTS:cli_main> + $<TARGET_OBJECTS:extcap-base> + etwdump.c + etl.c + etw_message.c + etw_ndiscap.c + ) + + set_executable_resources(etwdump "etwdump") + add_executable(etwdump ${etwdump_FILES}) + set_extcap_executable_properties(etwdump) + target_link_libraries(etwdump ${etwdump_LIBS}) + install(TARGETS etwdump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps etwdump) +endif() + +if(BUILD_sdjournal AND SYSTEMD_FOUND) + set(sdjournal_LIBS + writecap + wsutil + ${CMAKE_DL_LIBS} + ${SYSTEMD_LIBRARIES} + ) + set(sdjournal_FILES + $<TARGET_OBJECTS:extcap-base> + sdjournal.c + ) + + set_executable_resources(sdjournal "sdjournal") + add_executable(sdjournal ${sdjournal_FILES}) + set_extcap_executable_properties(sdjournal) + target_link_libraries(sdjournal ${sdjournal_LIBS}) + target_include_directories(sdjournal SYSTEM PRIVATE ${SYSTEMD_INCLUDE_DIRS}) + install(TARGETS sdjournal RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps sdjournal) +endif() + +if(BUILD_falcodump AND SINSP_FOUND) + set(falcodump_LIBS + wsutil + ${SINSP_LINK_LIBRARIES} + ${CMAKE_DL_LIBS} + ${GCRYPT_LIBRARIES} + ) + set(falcodump_FILES + $<TARGET_OBJECTS:extcap-base> + falcodump.cpp + ) + + set_executable_resources(falcodump "falcodump") + add_executable(falcodump ${falcodump_FILES}) + set_extlog_executable_properties(falcodump) + target_link_libraries(falcodump ${falcodump_LIBS}) + target_include_directories(falcodump SYSTEM PRIVATE ${SINSP_INCLUDE_DIRS}) + install(TARGETS falcodump RUNTIME DESTINATION ${EXTCAP_INSTALL_LIBDIR}) + add_dependencies(extcaps falcodump) + + # XXX Hack; We need to fix this in falcosecurity-libs. + target_compile_definitions(falcodump PRIVATE HAVE_STRLCPY=1) + +endif() + +# +# Editor modelines - https://www.wireshark.org/tools/modelines.html +# +# Local variables: +# c-basic-offset: 8 +# tab-width: 8 +# indent-tabs-mode: t +# End: +# +# vi: set shiftwidth=8 tabstop=8 noexpandtab: +# :indentSize=8:tabSize=8:noTabs=false: +# diff --git a/extcap/androiddump.c b/extcap/androiddump.c new file mode 100644 index 00000000..7ba573a5 --- /dev/null +++ b/extcap/androiddump.c @@ -0,0 +1,2801 @@ +/* androiddump.c + * androiddump is extcap tool used to capture Android specific stuff + * + * Copyright 2015, Michal Labedzki for Tieto Corporation + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" +#define WS_LOG_DOMAIN "androiddump" + +#include "extcap-base.h" + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <wsutil/strtoi.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/report_message.h> +#include <wsutil/please_report_bug.h> +#include <wsutil/wslog.h> +#include <wsutil/cmdarg_err.h> +#include <wsutil/inet_addr.h> +#include <wsutil/exported_pdu_tlvs.h> + +#include "ui/failure_message.h" + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#ifdef HAVE_SYS_SOCKET_H + #include <sys/socket.h> +#endif + +#ifdef HAVE_ARPA_INET_H + #include <arpa/inet.h> +#endif + +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#endif + +/* Configuration options */ +/* #define ANDROIDDUMP_USE_LIBPCAP */ + +#define PCAP_GLOBAL_HEADER_LENGTH 24 +#define PCAP_RECORD_HEADER_LENGTH 16 + +#ifdef ANDROIDDUMP_USE_LIBPCAP + #include <pcap.h> + #include <pcap-bpf.h> + #include <pcap/bluetooth.h> + + #ifndef DLT_BLUETOOTH_H4_WITH_PHDR + #define DLT_BLUETOOTH_H4_WITH_PHDR 201 + #endif + + #ifndef DLT_WIRESHARK_UPPER_PDU + #define DLT_WIRESHARK_UPPER_PDU 252 + #endif + + #ifndef PCAP_TSTAMP_PRECISION_MICRO + #define PCAP_TSTAMP_PRECISION_MICRO 0 + #endif + + #ifndef PCAP_TSTAMP_PRECISION_NANO + #define PCAP_TSTAMP_PRECISION_NANO 1 + #endif +#else + #include "wiretap/wtap.h" + #include "wiretap/pcap-encap.h" +#endif + +#include <cli_main.h> + +#ifdef ANDROIDDUMP_USE_LIBPCAP + #define EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR DLT_BLUETOOTH_H4_WITH_PHDR + #define EXTCAP_ENCAP_WIRESHARK_UPPER_PDU DLT_WIRESHARK_UPPER_PDU + #define EXTCAP_ENCAP_ETHERNET DLT_EN10MB + #define EXTCAP_ENCAP_LINUX_SLL DLT_LINUX_SLL + #define EXTCAP_ENCAP_IEEE802_11_RADIO DLT_IEEE802_11_RADIO + #define EXTCAP_ENCAP_NETLINK DLT_NETLINK +#else + #define EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR WTAP_ENCAP_BLUETOOTH_H4_WITH_PHDR + #define EXTCAP_ENCAP_WIRESHARK_UPPER_PDU WTAP_ENCAP_WIRESHARK_UPPER_PDU + #define EXTCAP_ENCAP_ETHERNET WTAP_ENCAP_ETHERNET + #define EXTCAP_ENCAP_LINUX_SLL WTAP_ENCAP_SLL + #define EXTCAP_ENCAP_IEEE802_11_RADIO WTAP_ENCAP_IEEE_802_11_RADIOTAP + #define EXTCAP_ENCAP_NETLINK WTAP_ENCAP_NETLINK +#endif + +#define INTERFACE_ANDROID_LOGCAT_MAIN "android-logcat-main" +#define INTERFACE_ANDROID_LOGCAT_SYSTEM "android-logcat-system" +#define INTERFACE_ANDROID_LOGCAT_RADIO "android-logcat-radio" +#define INTERFACE_ANDROID_LOGCAT_EVENTS "android-logcat-events" +#define INTERFACE_ANDROID_LOGCAT_TEXT_MAIN "android-logcat-text-main" +#define INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM "android-logcat-text-system" +#define INTERFACE_ANDROID_LOGCAT_TEXT_RADIO "android-logcat-text-radio" +#define INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS "android-logcat-text-events" +#define INTERFACE_ANDROID_LOGCAT_TEXT_CRASH "android-logcat-text-crash" +#define INTERFACE_ANDROID_BLUETOOTH_HCIDUMP "android-bluetooth-hcidump" +#define INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER "android-bluetooth-external-parser" +#define INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET "android-bluetooth-btsnoop-net" +#define INTERFACE_ANDROID_TCPDUMP "android-tcpdump" +#define INTERFACE_ANDROID_TCPDUMP_FORMAT INTERFACE_ANDROID_TCPDUMP "-%s" +#define INTERFACE_ANDROID_TCPDUMP_SERIAL_FORMAT INTERFACE_ANDROID_TCPDUMP_FORMAT "-%s" + +#define ANDROIDDUMP_VERSION_MAJOR "1" +#define ANDROIDDUMP_VERSION_MINOR "1" +#define ANDROIDDUMP_VERSION_RELEASE "0" + +#define SERIAL_NUMBER_LENGTH_MAX 512 +#define MODEL_NAME_LENGTH_MAX 64 + +#define PACKET_LENGTH 65535 + +#define SOCKET_RW_TIMEOUT_MS 2000 +#define SOCKET_CONNECT_TIMEOUT_TRIES 10 +#define SOCKET_CONNECT_DELAY_US 1000 /* (1000us = 1ms) * SOCKET_CONNECT_TIMEOUT_TRIES (10) = 10ms worst-case */ + +#define ADB_HEX4_FORMAT "%04zx" +#define ADB_HEX4_LEN 4 + +#define BTSNOOP_HDR_LEN 16 + +enum exit_code { + EXIT_CODE_SUCCESS = 0, + EXIT_CODE_CANNOT_GET_INTERFACES_LIST = 1, + EXIT_CODE_UNKNOWN_ENCAPSULATION_WIRETAP, + EXIT_CODE_UNKNOWN_ENCAPSULATION_LIBPCAP, + EXIT_CODE_CANNOT_SAVE_WIRETAP_DUMP, + EXIT_CODE_CANNOT_SAVE_LIBPCAP_DUMP, + EXIT_CODE_NO_INTERFACE_SPECIFIED, + EXIT_CODE_INVALID_INTERFACE, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_1, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_2, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_3, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_4, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_5, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_6, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_7, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_8, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_9, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_10, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_11, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_12, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_13, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_14, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_15, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_16, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_17, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_18, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_19, + EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_20, + EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_1, + EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_2, + EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_3, + EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_4, + EXIT_CODE_ERROR_WHILE_RECEIVING_ADB_PACKET_STATUS, + EXIT_CODE_ERROR_WHILE_RECEIVING_ADB_PACKET_DATA, + EXIT_CODE_INVALID_SOCKET_INTERFACES_LIST, + EXIT_CODE_INVALID_SOCKET_1, + EXIT_CODE_INVALID_SOCKET_2, + EXIT_CODE_INVALID_SOCKET_3, + EXIT_CODE_INVALID_SOCKET_4, + EXIT_CODE_INVALID_SOCKET_5, + EXIT_CODE_INVALID_SOCKET_6, + EXIT_CODE_INVALID_SOCKET_7, + EXIT_CODE_INVALID_SOCKET_8, + EXIT_CODE_INVALID_SOCKET_9, + EXIT_CODE_INVALID_SOCKET_10, + EXIT_CODE_INVALID_SOCKET_11, + EXIT_CODE_GENERIC = -1 +}; + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_CONFIG_ADB_SERVER_IP, + OPT_CONFIG_ADB_SERVER_TCP_PORT, + OPT_CONFIG_LOGCAT_TEXT, + OPT_CONFIG_LOGCAT_IGNORE_LOG_BUFFER, + OPT_CONFIG_LOGCAT_CUSTOM_OPTIONS, + OPT_CONFIG_BT_SERVER_TCP_PORT, + OPT_CONFIG_BT_FORWARD_SOCKET, + OPT_CONFIG_BT_LOCAL_IP, + OPT_CONFIG_BT_LOCAL_TCP_PORT +}; + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + { "adb-server-ip", ws_required_argument, NULL, OPT_CONFIG_ADB_SERVER_IP}, + { "adb-server-tcp-port", ws_required_argument, NULL, OPT_CONFIG_ADB_SERVER_TCP_PORT}, + { "logcat-text", ws_optional_argument, NULL, OPT_CONFIG_LOGCAT_TEXT}, + { "logcat-ignore-log-buffer", ws_optional_argument, NULL, OPT_CONFIG_LOGCAT_IGNORE_LOG_BUFFER}, + { "logcat-custom-options", ws_required_argument, NULL, OPT_CONFIG_LOGCAT_CUSTOM_OPTIONS}, + { "bt-server-tcp-port", ws_required_argument, NULL, OPT_CONFIG_BT_SERVER_TCP_PORT}, + { "bt-forward-socket", ws_required_argument, NULL, OPT_CONFIG_BT_FORWARD_SOCKET}, + { "bt-local-ip", ws_required_argument, NULL, OPT_CONFIG_BT_LOCAL_IP}, + { "bt-local-tcp-port", ws_required_argument, NULL, OPT_CONFIG_BT_LOCAL_TCP_PORT}, + { 0, 0, 0, 0 } +}; + +struct interface_t { + const char *display_name; + const char *interface_name; + struct interface_t *next; +}; + +struct exported_pdu_header { + uint16_t tag; + uint16_t length; +/* unsigned char value[0]; */ +}; + + +typedef struct _own_pcap_bluetooth_h4_header { + uint32_t direction; +} own_pcap_bluetooth_h4_header; + +typedef struct pcap_hdr_s { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +} pcap_hdr_t; + + +typedef struct pcaprec_hdr_s { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +} pcaprec_hdr_t; + +/* This fix compilator warning like "warning: cast from 'char *' to 'uint32_t *' (aka 'unsigned int *') increases required alignment from 1 to 4 " */ +typedef union { + char *value_char; + uint8_t *value_u8; + uint16_t *value_u16; + uint32_t *value_u32; + uint64_t *value_u64; + int8_t *value_i8; + int16_t *value_i16; + int32_t *value_i32; + int64_t *value_i64; + own_pcap_bluetooth_h4_header *value_own_pcap_bluetooth_h4_header; +} data_aligned_t; + +#define SET_DATA(dest, type, src) \ + { \ + data_aligned_t data_aligned; \ + \ + data_aligned.value_char = src; \ + dest = data_aligned.type; \ + } + +struct extcap_dumper { + int encap; + union { +#ifdef ANDROIDDUMP_USE_LIBPCAP + pcap_dumper_t *pcap; +#else + wtap_dumper *wtap; +#endif + } dumper; +}; + +/* Globals */ +static int endless_loop = 1; + +/* Functions */ +static inline int is_specified_interface(const char *interface, const char *interface_prefix) { + return !strncmp(interface, interface_prefix, strlen(interface_prefix)); +} + +static bool is_logcat_interface(const char *interface) { + return is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_MAIN) || + is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_SYSTEM) || + is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_RADIO) || + is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_EVENTS); +} + +static bool is_logcat_text_interface(const char *interface) { + return is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_MAIN) || + is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM) || + is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_RADIO) || + is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS) || + is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_CRASH); +} + +static char* get_serial_from_interface(char *interface) +{ + static const char* const iface_prefix[] = { + INTERFACE_ANDROID_LOGCAT_MAIN, + INTERFACE_ANDROID_LOGCAT_SYSTEM, + INTERFACE_ANDROID_LOGCAT_RADIO, + INTERFACE_ANDROID_LOGCAT_EVENTS, + INTERFACE_ANDROID_LOGCAT_TEXT_MAIN, + INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM, + INTERFACE_ANDROID_LOGCAT_TEXT_RADIO, + INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS, + INTERFACE_ANDROID_LOGCAT_TEXT_CRASH, + INTERFACE_ANDROID_BLUETOOTH_HCIDUMP, + INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER, + INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET, + NULL + }; + int i; + const char* curr = NULL; + for (i = 0; (curr = iface_prefix[i]); i++) { + if (is_specified_interface(interface, curr) && + strlen(interface) > strlen(curr) + 1) { + return interface + strlen(curr) + 1; + } + } + return NULL; +} + +static const char* interface_to_logbuf(char* interface) +{ + const char *const adb_log_main = "log:main"; + const char *const adb_log_system = "log:system"; + const char *const adb_log_radio = "log:radio"; + const char *const adb_log_events = "log:events"; + const char *logbuf = NULL; + + if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_MAIN)) + logbuf = adb_log_main; + else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_SYSTEM)) + logbuf = adb_log_system; + else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_RADIO)) + logbuf = adb_log_radio; + else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_EVENTS)) + logbuf = adb_log_events; + return logbuf; +} + +/* + * General errors and warnings are reported through ws_warning() in + * androiddump. + * + * Unfortunately, ws_warning() may be a macro, so we do it by calling + * g_logv() with the appropriate arguments. + */ +static void +androiddump_cmdarg_err(const char *msg_format, va_list ap) +{ + ws_logv(LOG_DOMAIN_CAPCHILD, LOG_LEVEL_WARNING, msg_format, ap); +} + +static void useSndTimeout(socket_handle_t sock) { + int res; +#ifdef _WIN32 + const DWORD socket_timeout = SOCKET_RW_TIMEOUT_MS; + + res = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *) &socket_timeout, (socklen_t)sizeof(socket_timeout)); +#else + const struct timeval socket_timeout = { + .tv_sec = SOCKET_RW_TIMEOUT_MS / 1000, + .tv_usec = (SOCKET_RW_TIMEOUT_MS % 1000) * 1000 + }; + + res = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &socket_timeout, (socklen_t)sizeof(socket_timeout)); +#endif + if (res != 0) + ws_debug("Can't set socket timeout, using default"); +} + +static void useNonBlockingConnectTimeout(socket_handle_t sock) { + int res_snd; + int res_rcv; +#ifdef _WIN32 + const DWORD socket_timeout = SOCKET_RW_TIMEOUT_MS; + unsigned long non_blocking = 1; + + res_snd = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *) &socket_timeout, sizeof(socket_timeout)); + res_rcv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *) &socket_timeout, sizeof(socket_timeout)); + + /* set socket to non-blocking */ + ioctlsocket(sock, FIONBIO, &non_blocking); +#else + const struct timeval socket_timeout = { + .tv_sec = SOCKET_RW_TIMEOUT_MS / 1000, + .tv_usec = (SOCKET_RW_TIMEOUT_MS % 1000) * 1000 + }; + + res_snd = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &socket_timeout, sizeof(socket_timeout)); + res_rcv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &socket_timeout, sizeof(socket_timeout)); +#endif + if (res_snd != 0) + ws_debug("Can't set socket timeout, using default"); + if (res_rcv != 0) + ws_debug("Can't set socket timeout, using default"); +} + +static void useNormalConnectTimeout(socket_handle_t sock) { + int res_rcv; +#ifdef _WIN32 + const DWORD socket_timeout = 0; + unsigned long non_blocking = 0; + + res_rcv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *) &socket_timeout, sizeof(socket_timeout)); + ioctlsocket(sock, FIONBIO, &non_blocking); +#else + const struct timeval socket_timeout = { + .tv_sec = SOCKET_RW_TIMEOUT_MS / 1000, + .tv_usec = (SOCKET_RW_TIMEOUT_MS % 1000) * 1000 + }; + + res_rcv = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &socket_timeout, sizeof(socket_timeout)); +#endif + if (res_rcv != 0) + ws_debug("Can't set socket timeout, using default"); +} + +static struct extcap_dumper extcap_dumper_open(char *fifo, int encap) { + struct extcap_dumper extcap_dumper; + +#ifdef ANDROIDDUMP_USE_LIBPCAP + pcap_t *pcap; + + pcap = pcap_open_dead_with_tstamp_precision(encap, PACKET_LENGTH, PCAP_TSTAMP_PRECISION_NANO); + extcap_dumper.dumper.pcap = pcap_dump_open(pcap, fifo); + if (!extcap_dumper.dumper.pcap) { + ws_warning("Can't open %s for saving packets: %s", fifo, pcap_geterr(pcap)); + pcap_close(pcap); + exit(EXIT_CODE_CANNOT_SAVE_LIBPCAP_DUMP); + } + extcap_dumper.encap = encap; + if (pcap_dump_flush(extcap_dumper.dumper.pcap) == -1) { + ws_warning("Write to %s failed: %s", fifo, g_strerror(errno)); + } +#else + wtap_dump_params params = WTAP_DUMP_PARAMS_INIT; + int file_type_subtype; + int err = 0; + char *err_info = NULL; + + wtap_init(false); + + params.encap = encap; + params.snaplen = PACKET_LENGTH; + file_type_subtype = wtap_pcap_nsec_file_type_subtype(); + extcap_dumper.dumper.wtap = wtap_dump_open(fifo, file_type_subtype, WTAP_UNCOMPRESSED, ¶ms, &err, &err_info); + if (!extcap_dumper.dumper.wtap) { + cfile_dump_open_failure_message(fifo, err, err_info, file_type_subtype); + exit(EXIT_CODE_CANNOT_SAVE_WIRETAP_DUMP); + } + extcap_dumper.encap = encap; + if (!wtap_dump_flush(extcap_dumper.dumper.wtap, &err)) { + cfile_dump_open_failure_message(fifo, err, NULL, file_type_subtype); + exit(EXIT_CODE_CANNOT_SAVE_WIRETAP_DUMP); + } +#endif + + return extcap_dumper; +} + +static bool extcap_dumper_dump(struct extcap_dumper extcap_dumper, + char *fifo, char *buffer, + ssize_t captured_length, ssize_t reported_length, + time_t seconds, int nanoseconds) { +#ifdef ANDROIDDUMP_USE_LIBPCAP + struct pcap_pkthdr pcap_header; + + pcap_header.caplen = (bpf_u_int32) captured_length; + pcap_header.len = (bpf_u_int32) reported_length; + pcap_header.ts.tv_sec = seconds; + pcap_header.ts.tv_usec = nanoseconds / 1000; + + pcap_dump((u_char *) extcap_dumper.dumper.pcap, &pcap_header, buffer); + if (pcap_dump_flush(extcap_dumper.dumper.pcap) == -1) { + ws_warning("Write to %s failed: %s", fifo, g_strerror(errno)); + } +#else + int err = 0; + char *err_info; + wtap_rec rec; + + rec.rec_type = REC_TYPE_PACKET; + rec.presence_flags = WTAP_HAS_TS; + rec.rec_header.packet_header.caplen = (uint32_t) captured_length; + rec.rec_header.packet_header.len = (uint32_t) reported_length; + + rec.ts.secs = seconds; + rec.ts.nsecs = (int) nanoseconds; + + rec.block = NULL; + +/* NOTE: Try to handle pseudoheaders manually */ + if (extcap_dumper.encap == EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR) { + uint32_t *direction; + + SET_DATA(direction, value_u32, buffer) + + rec.rec_header.packet_header.pseudo_header.bthci.sent = GINT32_FROM_BE(*direction) ? 0 : 1; + + rec.rec_header.packet_header.len -= (uint32_t)sizeof(own_pcap_bluetooth_h4_header); + rec.rec_header.packet_header.caplen -= (uint32_t)sizeof(own_pcap_bluetooth_h4_header); + + buffer += sizeof(own_pcap_bluetooth_h4_header); + } + rec.rec_header.packet_header.pkt_encap = extcap_dumper.encap; + + if (!wtap_dump(extcap_dumper.dumper.wtap, &rec, (const uint8_t *) buffer, &err, &err_info)) { + cfile_write_failure_message(NULL, fifo, err, err_info, 0, + wtap_dump_file_type_subtype(extcap_dumper.dumper.wtap)); + return false; + } + + if (!wtap_dump_flush(extcap_dumper.dumper.wtap, &err)) { + cfile_write_failure_message(NULL, fifo, err, NULL, 0, + wtap_dump_file_type_subtype(extcap_dumper.dumper.wtap)); + return false; + } +#endif + + return true; +} + + +static socket_handle_t adb_connect(const char *server_ip, unsigned short *server_tcp_port) { + socket_handle_t sock; + socklen_t length; + struct sockaddr_in server; + struct sockaddr_in client; + int status; + int tries = 0; + + memset(&server, 0x0, sizeof(server)); + + server.sin_family = AF_INET; + server.sin_port = GINT16_TO_BE(*server_tcp_port); + ws_inet_pton4(server_ip, (ws_in4_addr *)&(server.sin_addr.s_addr)); + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { + ws_warning("Cannot open system TCP socket: %s", strerror(errno)); + return INVALID_SOCKET; + } + + useNonBlockingConnectTimeout(sock); + while (tries < SOCKET_CONNECT_TIMEOUT_TRIES) { + status = connect(sock, (struct sockaddr *) &server, (socklen_t)sizeof(server)); + tries += 1; + +#ifdef _WIN32 + if ((status == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK)) { + struct timeval timeout = { + .tv_sec = 0, + .tv_usec = SOCKET_CONNECT_DELAY_US, + }; + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(sock, &fdset); + if ((select(0, NULL, &fdset, NULL, &timeout) != 0) && (FD_ISSET(sock, &fdset))) { + status = 0; + } + } +#endif + + if (status != SOCKET_ERROR) + break; + g_usleep(SOCKET_CONNECT_DELAY_US); + } + useNormalConnectTimeout(sock); + + if (status == SOCKET_ERROR) { +#if 0 +/* NOTE: This does not work well - make significant delay while initializing Wireshark. + Do fork() then call "adb" also does not make sense, because there is need to + do something like sleep(1) to ensure adb is started... system() cannot be used + on Windows, because open console window. This helper does not work as expected, + so disable it and user must ensure that adb is started (adb start-server, + but also all other command start-server automatically) +*/ +#ifdef _WIN32 + if (_execlp("adb", "adb", "start-server", NULL)) { +#else + if (execlp("adb", "adb", "start-server", NULL)) { +#endif + errmsg("WARNING: Cannot execute system command to start adb: %s", strerror(errno)); + closesocket(sock); + return INVALID_SOCKET; + }; + + if (connect(sock, (struct sockaddr *) &server, (socklen_t)sizeof(server)) == SOCKET_ERROR) { + ws_warning("Cannot connect to ADB: <%s> Please check that adb daemon is running.", strerror(errno)); + closesocket(sock); + return INVALID_SOCKET; + } +#else + ws_debug("Cannot connect to ADB: <%s> Please check that adb daemon is running.", strerror(errno)); + closesocket(sock); + return INVALID_SOCKET; +#endif + } + + length = sizeof(client); + if (getsockname(sock, (struct sockaddr *) &client, &length)) { + ws_warning("getsockname: %s", strerror(errno)); + closesocket(sock); + return INVALID_SOCKET; + } + + if (length != sizeof(client)) { + ws_warning("incorrect length"); + closesocket(sock); + return INVALID_SOCKET; + } + + ws_debug("Client port %u", GUINT16_FROM_BE(client.sin_port)); + + return sock; +} + + +static char *adb_send_and_receive(socket_handle_t sock, const char *adb_service, + char *buffer, size_t buffer_length, size_t *data_length) { + size_t used_buffer_length; + size_t bytes_to_read; + uint32_t length; + ssize_t result; + char status[4]; + char tmp_buffer; + size_t adb_service_length; + + adb_service_length = strlen(adb_service); + if (adb_service_length > INT_MAX) { + ws_warning("Service name too long when sending <%s> to ADB daemon", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + /* 8 bytes of hex length + terminating NUL */ + if (buffer_length < 9) { + ws_warning("Buffer for response too short while sending <%s> to ADB daemon", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + snprintf(buffer, buffer_length, ADB_HEX4_FORMAT, adb_service_length); + result = send(sock, buffer, ADB_HEX4_LEN, 0); + if (result < ADB_HEX4_LEN) { + ws_warning("Error while sending <%s> length to ADB daemon", adb_service); + return NULL; + } + + result = send(sock, adb_service, (int) adb_service_length, 0); + if (result != (ssize_t) adb_service_length) { + ws_warning("Error while sending <%s> to ADB daemon", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + used_buffer_length = 0; + while (used_buffer_length < 8) { + bytes_to_read = buffer_length - used_buffer_length; + if (bytes_to_read > INT_MAX) + bytes_to_read = INT_MAX; + result = recv(sock, buffer + used_buffer_length, (int)bytes_to_read, 0); + + if (result <= 0) { + ws_warning("Broken socket connection while fetching reply status for <%s>", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + used_buffer_length += result; + } + + memcpy(status, buffer, 4); + tmp_buffer = buffer[8]; + buffer[8] = '\0'; + if (!ws_hexstrtou32(buffer + 4, NULL, &length)) { + ws_warning("Invalid reply length <%s> while reading reply for <%s>", buffer + 4, adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + buffer[8] = tmp_buffer; + + if (buffer_length < length + 8) { + ws_warning("Buffer for response too short while sending <%s> to ADB daemon", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + while (used_buffer_length < length + 8) { + bytes_to_read = buffer_length - used_buffer_length; + if (bytes_to_read > INT_MAX) + bytes_to_read = INT_MAX; + result = recv(sock, buffer + used_buffer_length, (int)bytes_to_read, 0); + + if (result <= 0) { + ws_warning("Broken socket connection while reading reply for <%s>", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + used_buffer_length += result; + } + + if (data_length) + *data_length = used_buffer_length - 8; + + if (memcmp(status, "OKAY", 4)) { + ws_warning("Error while receiving by ADB for <%s>", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + return buffer + 8; +} + + +static char *adb_send_and_read(socket_handle_t sock, const char *adb_service, char *buffer, + int buffer_length, ssize_t *data_length) { + ssize_t used_buffer_length; + ssize_t result; + char status[4]; + size_t adb_service_length; + + adb_service_length = strlen(adb_service); + snprintf(buffer, buffer_length, ADB_HEX4_FORMAT, adb_service_length); + + result = send(sock, buffer, ADB_HEX4_LEN, 0); + if (result < ADB_HEX4_LEN) { + ws_warning("Error while sending <%s> to ADB daemon", adb_service); + return NULL; + } + + result = send(sock, adb_service, (int) adb_service_length, 0); + if (result != (ssize_t) adb_service_length) { + ws_warning("Error while sending <%s> to ADB", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + used_buffer_length = 0; + while (used_buffer_length < 4) { + result = recv(sock, buffer + used_buffer_length, (int)(buffer_length - used_buffer_length), 0); + + if (result <= 0) { + ws_warning("Broken socket connection while fetching reply status for <%s>", adb_service); + + return NULL; + } + + used_buffer_length += result; + } + + memcpy(status, buffer, 4); + + while (result > 0) { + result= recv(sock, buffer + used_buffer_length, (int)(buffer_length - used_buffer_length), 0); + + if (result < 0) { + ws_warning("Broken socket connection while reading reply for <%s>", adb_service); + + return NULL; + } else if (result == 0) { + break; + } + + used_buffer_length += result; + } + + if (data_length) + *data_length = used_buffer_length - 4; + + if (memcmp(status, "OKAY", 4)) { + ws_warning("Error while receiving by ADB for <%s>", adb_service); + if (data_length) + *data_length = 0; + return NULL; + } + + return buffer + 4; +} + + +static int adb_send(socket_handle_t sock, const char *adb_service) { + char buffer[5]; + int used_buffer_length; + ssize_t result; + size_t adb_service_length; + + adb_service_length = strlen(adb_service); + snprintf(buffer, sizeof(buffer), ADB_HEX4_FORMAT, adb_service_length); + + result = send(sock, buffer, ADB_HEX4_LEN, 0); + if (result < ADB_HEX4_LEN) { + ws_warning("Error while sending <%s> to ADB daemon", adb_service); + return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_1; + } + + result = send(sock, adb_service, (int) adb_service_length, 0); + if (result != (ssize_t) adb_service_length) { + ws_warning("Error while sending <%s> to ADB", adb_service); + return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_1; + } + + used_buffer_length = 0; + while (used_buffer_length < 4) { + result = recv(sock, buffer + used_buffer_length, 4 - used_buffer_length, 0); + + if (result <= 0) { + ws_warning("Broken socket connection while fetching reply status for <%s>", adb_service); + + return EXIT_CODE_ERROR_WHILE_RECEIVING_ADB_PACKET_STATUS; + } + + used_buffer_length += (int)result; + } + + if (memcmp(buffer, "OKAY", 4)) { + ws_debug("Error while receiving by ADB for <%s>", adb_service); + + return EXIT_CODE_ERROR_WHILE_RECEIVING_ADB_PACKET_DATA; + } + + return EXIT_CODE_SUCCESS; +} + + +static socket_handle_t +adb_connect_transport(const char *server_ip, unsigned short *server_tcp_port, + const char* serial_number) +{ + static const char *const adb_transport_serial_templace = "host:transport:%s"; + static const char *const adb_transport_any = "host:transport-any"; + char transport_buf[80]; + const char* transport = transport_buf; + socket_handle_t sock; + ssize_t result; + + sock = adb_connect(server_ip, server_tcp_port); + if (sock == INVALID_SOCKET) { + ws_warning("Error while connecting to adb server"); + return sock; + } + + if (!serial_number) { + transport = adb_transport_any; + } else { + result = snprintf(transport_buf, sizeof(transport_buf), adb_transport_serial_templace, serial_number); + if (result <= 0 || result > (int)sizeof(transport_buf)) { + ws_warning("Error while completing adb packet for transport"); + closesocket(sock); + return INVALID_SOCKET; + } + } + + result = adb_send(sock, transport); + if (result) { + ws_warning("Error while setting adb transport for <%s>", transport_buf); + closesocket(sock); + return INVALID_SOCKET; + } + return sock; +} + + +static void new_interface(extcap_parameters * extcap_conf, const char *interface_id, + const char *model_name, const char *serial_number, const char *display_name) +{ + char *interface = ws_strdup_printf("%s-%s", interface_id, serial_number); + char *ifdisplay = ws_strdup_printf("%s %s %s", display_name, model_name, serial_number); + + if (is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP) || + is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER) || + is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET)) { + + extcap_base_register_interface_ext(extcap_conf, interface, ifdisplay, 99, "BluetoothH4", "Bluetooth HCI UART transport layer plus pseudo-header" ); + } else if (is_logcat_interface(interface) || is_logcat_text_interface(interface)) { + extcap_base_register_interface(extcap_conf, interface, ifdisplay, 252, "Upper PDU" ); + } else if (is_specified_interface(interface, INTERFACE_ANDROID_TCPDUMP)) { + extcap_base_register_interface(extcap_conf, interface, ifdisplay, 1, "Ethernet"); + } + g_free(interface); + g_free(ifdisplay); +} + + +static void new_fake_interface_for_list_dlts(extcap_parameters * extcap_conf, + const char *ifname) +{ + if (is_specified_interface(ifname, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP) || + is_specified_interface(ifname, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER) || + is_specified_interface(ifname, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET)) { + extcap_base_register_interface_ext(extcap_conf, ifname, ifname, 99, "BluetoothH4", "Bluetooth HCI UART transport layer plus pseudo-header" ); + } else if (is_logcat_interface(ifname) || is_logcat_text_interface(ifname)) { + extcap_base_register_interface(extcap_conf, ifname, ifname, 252, "Upper PDU" ); + } else if (is_specified_interface(ifname, INTERFACE_ANDROID_TCPDUMP)) { + extcap_base_register_interface(extcap_conf, ifname, ifname, 1, "Ethernet"); + } +} + + +static int add_tcpdump_interfaces(extcap_parameters * extcap_conf, const char *adb_server_ip, unsigned short *adb_server_tcp_port, const char *serial_number) +{ + static const char *const adb_tcpdump_list = "shell:tcpdump -D"; + static const char *const regex_ifaces = "\\d+\\.(?<iface>\\S+)(\\s+?(?:(?:\\(.*\\))*)(\\s*?\\[(?<flags>.*?)\\])?)?"; + static char recv_buffer[PACKET_LENGTH]; + char *response; + ssize_t data_length; + socket_handle_t sock; + GRegex* regex = NULL; + GError *err = NULL; + GMatchInfo *match = NULL; + char* tok; + char iface_name[80]; + bool flags_supported; + + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) { + ws_warning("Failed to connect to adb server"); + return EXIT_CODE_GENERIC; + } + + response = adb_send_and_read(sock, adb_tcpdump_list, recv_buffer, sizeof(recv_buffer), &data_length); + closesocket(sock); + + if (!response) { + ws_warning("Failed to get list of available tcpdump interfaces"); + return EXIT_CODE_GENERIC; + } + response[data_length] = '\0'; + + regex = g_regex_new(regex_ifaces, G_REGEX_RAW, (GRegexMatchFlags)0, &err); + if (!regex) { + ws_warning("Failed to compile regex for tcpdump interface matching"); + return EXIT_CODE_GENERIC; + } + + flags_supported = (strstr(response, "[") != 0) && (strstr(response, "]") != 0); + + tok = strtok(response, "\n"); + while (tok != NULL) { + g_regex_match(regex, tok, (GRegexMatchFlags)0, &match); + if (g_match_info_matches(match)) { + char *iface = g_match_info_fetch_named(match, "iface"); + char *flags = g_match_info_fetch_named(match, "flags"); + + if (!flags_supported || (flags && strstr(flags, "Up"))) { + snprintf(iface_name, sizeof(iface_name), INTERFACE_ANDROID_TCPDUMP_FORMAT, iface); + new_interface(extcap_conf, iface_name, iface, serial_number, "Android tcpdump"); + } + g_free(flags); + g_free(iface); + } + g_match_info_free(match); + tok = strtok(NULL, "\n"); + } + g_regex_unref(regex); + return 0; +} + + +static int register_interfaces(extcap_parameters * extcap_conf, const char *adb_server_ip, unsigned short *adb_server_tcp_port) { + static char packet[PACKET_LENGTH]; + static char helpful_packet[PACKET_LENGTH]; + char check_port_buf[80]; + char *response; + char *device_list; + ssize_t data_length; + size_t device_length; + socket_handle_t sock; + const char *adb_check_port_templace = "shell:cat /proc/%s/net/tcp"; + const char *adb_devices = "host:devices-l"; + const char *adb_api_level = "shell:getprop ro.build.version.sdk"; + const char *adb_hcidump_version = "shell:hcidump --version"; + const char *adb_ps_droid_bluetooth = "shell:ps droid.bluetooth"; + const char *adb_ps_bluetooth_app = "shell:ps com.android.bluetooth"; + const char *adb_ps_with_grep = "shell:ps | grep com.android.bluetooth"; + const char *adb_ps_all_with_grep = "shell:ps -A | grep com.*android.bluetooth"; + char serial_number[SERIAL_NUMBER_LENGTH_MAX]; + char model_name[MODEL_NAME_LENGTH_MAX]; + int result; + char *pos; + char *i_pos; + char *model_pos; + char *device_pos; + char *prev_pos; + int api_level; + int disable_interface; + +/* NOTE: It seems that "adb devices" and "adb shell" closed connection + so cannot send next command after them, there is need to reconnect */ + + sock = adb_connect(adb_server_ip, adb_server_tcp_port); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_INTERFACES_LIST; + + device_list = adb_send_and_receive(sock, adb_devices, packet, sizeof(packet), &device_length); + closesocket(sock); + + if (!device_list) { + ws_warning("Cannot get list of interfaces from devices"); + + return EXIT_CODE_CANNOT_GET_INTERFACES_LIST; + } + + device_list[device_length] = '\0'; + pos = (char *) device_list; + + while (pos < (char *) (device_list + device_length)) { + prev_pos = pos; + pos = strchr(pos, ' '); + i_pos = pos; + result = (int) (pos - prev_pos); + pos = strchr(pos, '\n') + 1; + if (result >= (int) sizeof(serial_number)) { + ws_warning("Serial number too long, ignore device"); + continue; + } + memcpy(serial_number, prev_pos, result); + serial_number[result] = '\0'; + + model_name[0] = '\0'; + model_pos = g_strstr_len(i_pos, pos - i_pos, "model:"); + if (model_pos) { + device_pos = g_strstr_len(i_pos, pos - i_pos, "device:"); + if (device_pos && device_pos - model_pos - 6 - 1 < MODEL_NAME_LENGTH_MAX) { + memcpy(model_name, model_pos + 6, device_pos - model_pos - 6 - 1); + model_name[device_pos - model_pos - 6 - 1] = '\0'; + } + } + + if (model_name[0] == '\0') + strcpy(model_name, "unknown"); + + ws_debug("Processing device: \"%s\" <%s>" , serial_number, model_name); + + /* Function will only add tcpdump interfaces if tcpdump is present on the device */ + result = add_tcpdump_interfaces(extcap_conf, adb_server_ip, adb_server_tcp_port, serial_number ); + if (result) { + ws_warning("Error while adding tcpdump interfaces"); + } + + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) continue; + + response = adb_send_and_read(sock, adb_api_level, helpful_packet, sizeof(helpful_packet), &data_length); + closesocket(sock); + + if (!response) { + ws_warning("Error on socket: <%s>", helpful_packet); + continue; + } + + response[data_length] = '\0'; + api_level = (int) g_ascii_strtoll(response, NULL, 10); + ws_debug("Android API Level for %s is %i", serial_number, api_level); + + if (api_level < 21) { + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_MAIN, model_name, serial_number, "Android Logcat Main"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_SYSTEM, model_name, serial_number, "Android Logcat System"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_RADIO, model_name, serial_number, "Android Logcat Radio"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_EVENTS, model_name, serial_number, "Android Logcat Events"); + + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_MAIN, model_name, serial_number, "Android Logcat Main"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM, model_name, serial_number, "Android Logcat System"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_RADIO, model_name, serial_number, "Android Logcat Radio"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS, model_name, serial_number, "Android Logcat Events"); + } else { + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_MAIN, model_name, serial_number, "Android Logcat Main"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM, model_name, serial_number, "Android Logcat System"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_RADIO, model_name, serial_number, "Android Logcat Radio"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS, model_name, serial_number, "Android Logcat Events"); + new_interface(extcap_conf, INTERFACE_ANDROID_LOGCAT_TEXT_CRASH, model_name, serial_number, "Android Logcat Crash"); + } + + if (api_level >= 5 && api_level < 17) { + disable_interface = 0; + + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) continue; + + response = adb_send_and_read(sock, adb_hcidump_version, helpful_packet, sizeof(helpful_packet), &data_length); + closesocket(sock); + + if (!response || data_length < 1) { + ws_warning("Error while getting hcidump version by <%s> (%p len=%"PRIdMAX")", + adb_hcidump_version, (void*)response, (intmax_t)data_length); + ws_debug("Android hcidump version for %s is unknown", serial_number); + disable_interface = 1; + } else { + response[data_length] = '\0'; + + if (g_ascii_strtoull(response, NULL, 10) == 0) { + ws_debug("Android hcidump version for %s is unknown", serial_number); + disable_interface = 1; + } else { + ws_debug("Android hcidump version for %s is %s", serial_number, response); + } + } + + if (!disable_interface) { + new_interface(extcap_conf, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP, model_name, serial_number, "Android Bluetooth Hcidump"); + } + } + + if (api_level >= 17 && api_level < 21) { + disable_interface = 0; + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) continue; + + response = adb_send_and_read(sock, adb_ps_droid_bluetooth, helpful_packet, sizeof(helpful_packet), &data_length); + closesocket(sock); + if (!response || data_length < 1) { + ws_warning("Error while getting Bluetooth application process id by <%s> " + "(%p len=%"PRIdMAX")", adb_ps_droid_bluetooth, (void*)response, (intmax_t)data_length); + ws_debug( "Android Bluetooth application PID for %s is unknown", serial_number); + disable_interface = 1; + } else { + char *data_str; + char pid[16]; + + memset(pid, 0, sizeof(pid)); + response[data_length] = '\0'; + + data_str = strchr(response, '\n'); + if (data_str && sscanf(data_str, "%*s %15s", pid) == 1) { + ws_debug("Android Bluetooth application PID for %s is %s", serial_number, pid); + + result = snprintf(check_port_buf, sizeof(check_port_buf), adb_check_port_templace, pid); + if (result <= 0 || result > (int)sizeof(check_port_buf)) { + ws_warning("Error while completing adb packet"); + return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_6; + } + + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) continue; + + response = adb_send_and_read(sock, check_port_buf, helpful_packet, sizeof(helpful_packet), &data_length); + closesocket(sock); + + if (!response) { + disable_interface = 1; + } else { + response[data_length] = '\0'; + + data_str = strchr(response, '\n'); + if (data_str && sscanf(data_str, "%*s %15s", pid) == 1 && strlen(pid) > 10 && strcmp(pid + 9, "10EA") == 0) { + ws_debug("Bluedroid External Parser Port for %s is %s", serial_number, pid + 9); + } else { + disable_interface = 1; + ws_debug("Bluedroid External Parser Port for %s is unknown", serial_number); + } + } + } else { + disable_interface = 1; + ws_debug("Android Bluetooth application PID for %s is unknown", serial_number); + } + } + + if (!disable_interface) { + new_interface(extcap_conf, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER, model_name, serial_number, "Android Bluetooth External Parser"); + } + } + + if (api_level >= 21) { + const char* ps_cmd; + disable_interface = 0; + + if (api_level >= 26) { + ps_cmd = adb_ps_all_with_grep; + } else if (api_level >= 24) { + ps_cmd = adb_ps_with_grep; + } else if (api_level >= 23) { + ps_cmd = adb_ps_bluetooth_app; + } else { + ps_cmd = adb_ps_droid_bluetooth; + } + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) continue; + + response = adb_send_and_read(sock, ps_cmd, helpful_packet, sizeof(helpful_packet), &data_length); + closesocket(sock); + + if (!response || data_length < 1) { + ws_warning("Error while getting Bluetooth application process id by <%s> " + "(%p len=%"PRIdMAX")", ps_cmd, (void*)response, (intmax_t)data_length); + ws_debug("Android Bluetooth application PID for %s is unknown", serial_number); + disable_interface = 1; + } else { + char *data_str; + char pid[16]; + + memset(pid, 0, sizeof(pid)); + response[data_length] = '\0'; + + if (api_level >= 24) + data_str = response; + else + data_str = strchr(response, '\n'); + + if (data_str && sscanf(data_str, "%*s %15s", pid) == 1) { + ws_debug("Android Bluetooth application PID for %s is %s", serial_number, pid); + + result = snprintf(check_port_buf, sizeof(check_port_buf), adb_check_port_templace, pid); + if (result <= 0 || result > (int)sizeof(check_port_buf)) { + ws_warning("Error while completing adb packet"); + return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_9; + } + + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) continue; + + response = adb_send_and_read(sock, check_port_buf, helpful_packet, sizeof(helpful_packet), &data_length); + closesocket(sock); + + if (!response) { + disable_interface = 1; + } else { + response[data_length] = '\0'; + data_str = strtok(response, "\n"); + while (data_str != NULL) { + if (sscanf(data_str, "%*s %15s", pid) == 1 && strlen(pid) > 10 && strcmp(pid + 9, "22A8") == 0) { + ws_debug("Btsnoop Net Port for %s is %s", serial_number, pid + 9); + break; + } + data_str = strtok(NULL, "\n"); + } + if (data_str == NULL) { + disable_interface = 1; + ws_debug("Btsnoop Net Port for %s is unknown", serial_number); + } + } + } else { + disable_interface = 1; + ws_debug("Android Bluetooth application PID for %s is unknown", serial_number); + } + } + + if (!disable_interface) { + new_interface(extcap_conf, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET, model_name, serial_number, "Android Bluetooth Btsnoop Net"); + } + } + } + + return EXIT_CODE_SUCCESS; +} + +static int list_config(char *interface) { + int ret = EXIT_CODE_INVALID_INTERFACE; + unsigned inc = 0; + + if (!interface) { + ws_warning("No interface specified."); + return EXIT_CODE_NO_INTERFACE_SPECIFIED; + } + + if (is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER)) { + printf("arg {number=%u}{call=--adb-server-ip}{display=ADB Server IP Address}{type=string}{default=127.0.0.1}\n", inc++); + printf("arg {number=%u}{call=--adb-server-tcp-port}{display=ADB Server TCP Port}{type=integer}{range=0,65535}{default=5037}\n", inc++); + printf("arg {number=%u}{call=--bt-server-tcp-port}{display=Bluetooth Server TCP Port}{type=integer}{range=0,65535}{default=4330}\n", inc++); + printf("arg {number=%u}{call=--bt-forward-socket}{display=Forward Bluetooth Socket}{type=boolean}{default=false}\n", inc++); + printf("arg {number=%u}{call=--bt-local-ip}{display=Bluetooth Local IP Address}{type=string}{default=127.0.0.1}\n", inc++); + printf("arg {number=%u}{call=--bt-local-tcp-port}{display=Bluetooth Local TCP Port}{type=integer}{range=0,65535}{default=4330}{tooltip=Used to do \"adb forward tcp:LOCAL_TCP_PORT tcp:SERVER_TCP_PORT\"}\n", inc++); + ret = EXIT_CODE_SUCCESS; + } else if (is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP) || + is_specified_interface(interface, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET) || + is_specified_interface(interface, INTERFACE_ANDROID_TCPDUMP)) { + printf("arg {number=%u}{call=--adb-server-ip}{display=ADB Server IP Address}{type=string}{default=127.0.0.1}\n", inc++); + printf("arg {number=%u}{call=--adb-server-tcp-port}{display=ADB Server TCP Port}{type=integer}{range=0,65535}{default=5037}\n", inc++); + ret = EXIT_CODE_SUCCESS; + } else if (is_logcat_interface(interface)) { + printf("arg {number=%u}{call=--adb-server-ip}{display=ADB Server IP Address}{type=string}{default=127.0.0.1}\n", inc++); + printf("arg {number=%u}{call=--adb-server-tcp-port}{display=ADB Server TCP Port}{type=integer}{range=0,65535}{default=5037}\n", inc++); + printf("arg {number=%u}{call=--logcat-text}{display=Use text logcat}{type=boolean}{default=false}\n", inc++); + printf("arg {number=%u}{call=--logcat-ignore-log-buffer}{display=Ignore log buffer}{type=boolean}{default=false}\n", inc++); + printf("arg {number=%u}{call=--logcat-custom-options}{display=Custom logcat parameters}{type=string}\n", inc++); + ret = EXIT_CODE_SUCCESS; + } else if (is_logcat_text_interface(interface)) { + printf("arg {number=%u}{call=--adb-server-ip}{display=ADB Server IP Address}{type=string}{default=127.0.0.1}\n", inc++); + printf("arg {number=%u}{call=--adb-server-tcp-port}{display=ADB Server TCP Port}{type=integer}{range=0,65535}{default=5037}\n", inc++); + printf("arg {number=%u}{call=--logcat-ignore-log-buffer}{display=Ignore log buffer}{type=boolean}{default=false}\n", inc++); + printf("arg {number=%u}{call=--logcat-custom-options}{display=Custom logcat parameters}{type=string}\n", inc++); + ret = EXIT_CODE_SUCCESS; + } + + if (ret != EXIT_CODE_SUCCESS) + ws_warning("Invalid interface: <%s>", interface); + else + extcap_config_debug(&inc); + + return ret; +} + +/*----------------------------------------------------------------------------*/ +/* Android Bluetooth Hcidump */ +/*----------------------------------------------------------------------------*/ + +static int capture_android_bluetooth_hcidump(char *interface, char *fifo, + const char *adb_server_ip, unsigned short *adb_server_tcp_port) { + struct extcap_dumper extcap_dumper; + static char data[PACKET_LENGTH]; + static char packet[PACKET_LENGTH]; + ssize_t length; + ssize_t used_buffer_length = 0; + socket_handle_t sock = INVALID_SOCKET; + const char *adb_shell_hcidump = "shell:hcidump -R -t"; + const char *adb_shell_su_hcidump = "shell:su -c hcidump -R -t"; + int result; + char *serial_number; + time_t ts = 0; + unsigned int captured_length; + int64_t hex; + char *hex_data; + char *new_hex_data; + own_pcap_bluetooth_h4_header *h4_header; + int64_t raw_length = 0; + int64_t frame_length; + int ms = 0; + struct tm date; + char direction_character; + + SET_DATA(h4_header, value_own_pcap_bluetooth_h4_header, packet); + + extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR); + + serial_number = get_serial_from_interface(interface); + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_3; + + result = adb_send(sock, adb_shell_hcidump); + if (result) { + ws_warning("Error while starting capture by sending command: %s", adb_shell_hcidump); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + while (endless_loop) { + char *i_position; + + errno = 0; + length = recv(sock, data + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + ws_warning("Broken socket connection."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + used_buffer_length += length; + i_position = (char *) memchr(data, '\n', used_buffer_length); + if (i_position && i_position < data + used_buffer_length) { + char *state_line_position = i_position + 1; + + if (!strncmp(data, "/system/bin/sh: hcidump: not found", 34)) { + ws_warning("Command not found for <%s>", adb_shell_hcidump); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + i_position = (char *) memchr(i_position + 1, '\n', used_buffer_length); + if (i_position) { + i_position += 1; + if (!strncmp(state_line_position, "Can't access device: Permission denied", 38)) { + ws_warning("No permission for command <%s>", adb_shell_hcidump); + used_buffer_length = 0; + closesocket(sock); + sock = INVALID_SOCKET; + break; + } + memmove(data, i_position, used_buffer_length - (i_position - data)); + used_buffer_length = used_buffer_length - (ssize_t)(i_position - data); + break; + } + } + } + + if (sock == INVALID_SOCKET) { + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_4; + + result = adb_send(sock, adb_shell_su_hcidump); + if (result) { + ws_warning("Error while starting capture by sending command: <%s>", adb_shell_su_hcidump); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + used_buffer_length = 0; + while (endless_loop) { + char *i_position; + + errno = 0; + length = recv(sock, data + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + ws_warning("Broken socket connection."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + used_buffer_length += length; + i_position = (char *) memchr(data, '\n', used_buffer_length); + if (i_position && i_position < data + used_buffer_length) { + if (!strncmp(data, "/system/bin/sh: su: not found", 29)) { + ws_warning("Command 'su' not found for <%s>", adb_shell_su_hcidump); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + i_position = (char *) memchr(i_position + 1, '\n', used_buffer_length); + if (i_position) { + i_position += 1; + memmove(data, i_position, used_buffer_length - (i_position - data)); + used_buffer_length = used_buffer_length - (ssize_t)(i_position - data); + break; + } + } + } + } + + while (endless_loop) { + errno = 0; + length = recv(sock, data + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + ws_warning("Broken socket connection."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + while (endless_loop) { + if (used_buffer_length + length >= 1) { + hex_data = data + 29; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + + if ((hex == 0x01 && used_buffer_length + length >= 4) || + (hex == 0x02 && used_buffer_length + length >= 5) || + (hex == 0x04 && used_buffer_length + length >= 3)) { + + if (hex == 0x01) { + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + if (hex < 0 || hex >= 256 || hex_data == new_hex_data) { + ws_warning("data format %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + if (hex < 0 || hex >= 256 || hex_data == new_hex_data) { + ws_warning("data format %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + + raw_length = hex + 4; + } else if (hex == 0x04) { + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + if (hex < 0 || hex >= 256 || hex_data == new_hex_data) { + ws_warning("data format %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + + raw_length = hex + 3; + } else if (hex == 0x02) { + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + if (hex < 0 || hex >= 256 || hex_data == new_hex_data) { + ws_warning("data format %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + if (hex < 0 || hex >= 256 || hex_data == new_hex_data) { + ws_warning("data format %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + raw_length = hex + 5; + + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + raw_length += hex << 8; + } + + } else { + ws_warning("bad raw stream"); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + } else { + used_buffer_length += length; + break; + } + + frame_length = raw_length * 3 + (raw_length / 20) * 4 + ((raw_length % 20) ? 2 : -2) + 29; + + if ((used_buffer_length + length) < frame_length) { + used_buffer_length += length; + break; + } + + if (8 == sscanf(data, "%04d-%02d-%02d %02d:%02d:%02d.%06d %c", + &date.tm_year, &date.tm_mon, &date.tm_mday, &date.tm_hour, + &date.tm_min, &date.tm_sec, &ms, &direction_character)) { + + ws_debug("time %04d-%02d-%02d %02d:%02d:%02d.%06d %c", + date.tm_year, date.tm_mon, date.tm_mday, date.tm_hour, + date.tm_min, date.tm_sec, ms, direction_character); + date.tm_mon -= 1; + date.tm_year -= 1900; + date.tm_isdst = -1; + ts = mktime(&date); + + new_hex_data = data + 29; + } + + captured_length = 0; + + while ((long)(new_hex_data - data + sizeof(own_pcap_bluetooth_h4_header)) < frame_length) { + hex_data = new_hex_data; + hex = g_ascii_strtoll(hex_data, &new_hex_data, 16); + + packet[sizeof(own_pcap_bluetooth_h4_header) + captured_length] = (char) hex; + captured_length += 1; + } + + h4_header->direction = GINT32_TO_BE(direction_character == '>'); + + endless_loop = extcap_dumper_dump(extcap_dumper, fifo, packet, + captured_length + sizeof(own_pcap_bluetooth_h4_header), + captured_length + sizeof(own_pcap_bluetooth_h4_header), + ts, + ms * 1000); + + memmove(data, data + frame_length, (size_t)(used_buffer_length + length - frame_length)); + used_buffer_length = (ssize_t)(used_buffer_length + length - frame_length); + length = 0; + } + } + + closesocket(sock); + return EXIT_CODE_SUCCESS; +} + +/*----------------------------------------------------------------------------*/ +/* Android Bluetooth External Parser */ +/*----------------------------------------------------------------------------*/ + +#define BLUEDROID_H4_PACKET_TYPE 0 +#define BLUEDROID_TIMESTAMP_SIZE 8 +#define BLUEDROID_H4_SIZE 1 + +static const uint64_t BLUEDROID_TIMESTAMP_BASE = UINT64_C(0x00dcddb30f2f8000); + +#define BLUEDROID_H4_PACKET_TYPE_HCI_CMD 0x01 +#define BLUEDROID_H4_PACKET_TYPE_ACL 0x02 +#define BLUEDROID_H4_PACKET_TYPE_SCO 0x03 +#define BLUEDROID_H4_PACKET_TYPE_HCI_EVT 0x04 + +#define BLUEDROID_DIRECTION_SENT 0 +#define BLUEDROID_DIRECTION_RECV 1 + +static int adb_forward(char *serial_number, const char *adb_server_ip, unsigned short *adb_server_tcp_port, + unsigned short local_tcp_port, unsigned short server_tcp_port) { + socket_handle_t sock; + int result; + static char helpful_packet[PACKET_LENGTH]; + static const char *adb_forward_template = "%s%s:forward:tcp:%05u;tcp:%05u"; + + sock = adb_connect(adb_server_ip, adb_server_tcp_port); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_5; + + result = snprintf(helpful_packet, PACKET_LENGTH, adb_forward_template, (serial_number) ? "host-serial:" : "host", (serial_number) ? serial_number: "", local_tcp_port, server_tcp_port); + if (result <= 0 || result > PACKET_LENGTH) { + ws_warning("Error while completing adb packet"); + closesocket(sock); + return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_12; + } + + result = adb_send(sock, helpful_packet); + closesocket(sock); + + return result; +} + +static int capture_android_bluetooth_external_parser(char *interface, + char *fifo, const char *adb_server_ip, unsigned short *adb_server_tcp_port, + unsigned short *bt_server_tcp_port, unsigned int bt_forward_socket, const char *bt_local_ip, + unsigned short *bt_local_tcp_port) { + struct extcap_dumper extcap_dumper; + static char buffer[PACKET_LENGTH]; + uint64_t *timestamp; + char *packet = buffer + BLUEDROID_TIMESTAMP_SIZE - sizeof(own_pcap_bluetooth_h4_header); /* skip timestamp (8 bytes) and reuse its space for header */ + own_pcap_bluetooth_h4_header *h4_header; + uint8_t *payload = packet + sizeof(own_pcap_bluetooth_h4_header); + const char *adb_tcp_bluedroid_external_parser_template = "tcp:%05u"; + socklen_t slen; + ssize_t length; + ssize_t used_buffer_length = 0; + uint64_t ts; + socket_handle_t sock; + struct sockaddr_in server; + int captured_length; + char *serial_number; + static unsigned int id = 1; + struct sockaddr_in client; + + SET_DATA(timestamp, value_u64, buffer); + SET_DATA(h4_header, value_own_pcap_bluetooth_h4_header, packet); + + extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR); + serial_number = get_serial_from_interface(interface); + + if (bt_forward_socket) { + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { + ws_warning("Cannot open system TCP socket: %s", strerror(errno)); + return EXIT_CODE_GENERIC; + } + + ws_debug("Using config: Server TCP Port=%u, Local IP=%s, Local TCP Port=%u", + *bt_server_tcp_port, bt_local_ip, *bt_local_tcp_port); + + if (*bt_local_tcp_port != 0) { + int result; + + result = adb_forward(serial_number, adb_server_ip, adb_server_tcp_port, *bt_local_tcp_port, *bt_server_tcp_port); + ws_debug("DO: adb forward tcp:%u (local) tcp:%u (remote) result=%i", + *bt_local_tcp_port, *bt_server_tcp_port, result); + } + + memset(&server, 0 , sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = GINT16_TO_BE(*bt_local_tcp_port); + ws_inet_pton4(bt_local_ip, (ws_in4_addr *)&(server.sin_addr.s_addr)); + + useSndTimeout(sock); + + if (connect(sock, (struct sockaddr *) &server, sizeof(server)) == SOCKET_ERROR) { + ws_warning("<%s> Please check that adb daemon is running.", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + slen = (socklen_t)sizeof(client); + if (getsockname(sock, (struct sockaddr *) &client, &slen)) { + ws_warning("getsockname: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (slen != sizeof(client)) { + ws_warning("incorrect length"); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + ws_debug("Client port %u", GUINT16_FROM_BE(client.sin_port)); + } else { + int result; + + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_6; + + result = snprintf((char *) buffer, PACKET_LENGTH, adb_tcp_bluedroid_external_parser_template, *bt_server_tcp_port); + if (result <= 0 || result > PACKET_LENGTH) { + ws_warning("Error while completing adb packet"); + closesocket(sock); + return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_14; + } + + result = adb_send(sock, buffer); + if (result) { + ws_warning("Error while forwarding adb port"); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + } + + while (endless_loop) { + errno = 0; + length = recv(sock, buffer + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + if (bt_forward_socket) { + /* NOTE: Workaround... It seems that Bluedroid is slower and we can connect to socket that are not really ready... */ + ws_warning("Broken socket connection. Try reconnect."); + closesocket(sock); + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { + ws_warning("%s", strerror(errno)); + return EXIT_CODE_GENERIC; + } + + server.sin_family = AF_INET; + server.sin_port = GINT16_TO_BE(*bt_local_tcp_port); + ws_inet_pton4(bt_local_ip, (ws_in4_addr *)&(server.sin_addr.s_addr)); + + useSndTimeout(sock); + + if (connect(sock, (struct sockaddr *) &server, sizeof(server)) == SOCKET_ERROR) { + ws_warning("ERROR reconnect: <%s> Please check that adb daemon is running.", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + } else { + ws_warning("Broken socket connection."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + continue; + } + + used_buffer_length += length; + + ws_debug("Received: length=%"PRIdMAX, (intmax_t)length); + + while (((payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_HCI_CMD || payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_SCO) && + used_buffer_length >= BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + 1 && + BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + payload[BLUEDROID_H4_SIZE + 2] + 1 <= used_buffer_length) || + (payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_ACL && + used_buffer_length >= BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + 2 && + BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + payload[BLUEDROID_H4_SIZE + 2] + (payload[BLUEDROID_H4_SIZE + 2 + 1] << 8) + 2 <= used_buffer_length) || + (payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_SCO && + used_buffer_length >= BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + 1 && + BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 2 + payload[BLUEDROID_H4_SIZE + 2] + 1 <= used_buffer_length) || + (payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_HCI_EVT && + used_buffer_length >= BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 1 + 1 && + BLUEDROID_TIMESTAMP_SIZE + BLUEDROID_H4_SIZE + 1 + payload[BLUEDROID_H4_SIZE + 1] + 1 <= used_buffer_length)) { + + ts = GINT64_FROM_BE(*timestamp); + + switch (payload[BLUEDROID_H4_PACKET_TYPE]) { + case BLUEDROID_H4_PACKET_TYPE_HCI_CMD: + h4_header->direction = GINT32_TO_BE(BLUEDROID_DIRECTION_SENT); + + captured_length = (unsigned int)sizeof(own_pcap_bluetooth_h4_header) + payload[3] + 4; + + length = sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_H4_SIZE + 2 + 1 + payload[3]; + + break; + case BLUEDROID_H4_PACKET_TYPE_ACL: + h4_header->direction = (payload[2] & 0x80) ? GINT32_TO_BE(BLUEDROID_DIRECTION_RECV) : GINT32_TO_BE(BLUEDROID_DIRECTION_SENT); + + captured_length = (unsigned int)sizeof(own_pcap_bluetooth_h4_header) + payload[3] + (payload[3 + 1] << 8) + 5; + + length = sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_H4_SIZE + 2 + 2 + payload[3] + (ssize_t)(payload[3 + 1] << 8); + + break; + case BLUEDROID_H4_PACKET_TYPE_SCO: + h4_header->direction = (payload[2] & 0x80) ? GINT32_TO_BE(BLUEDROID_DIRECTION_RECV) : GINT32_TO_BE(BLUEDROID_DIRECTION_SENT); + + captured_length = (unsigned int)sizeof(own_pcap_bluetooth_h4_header) + payload[3] + 4; + + length = sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_H4_SIZE + 2 + 1 + payload[3]; + + break; + case BLUEDROID_H4_PACKET_TYPE_HCI_EVT: + h4_header->direction = GINT32_TO_BE(BLUEDROID_DIRECTION_RECV); + + captured_length = (unsigned int)sizeof(own_pcap_bluetooth_h4_header) + payload[2] + 3; + + length = sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_H4_SIZE + 1 + 1 + payload[2]; + + break; + default: + ws_warning("Invalid stream"); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + ws_debug("\t Packet %u: used_buffer_length=%"PRIdMAX" length=%"PRIdMAX" captured_length=%i type=0x%02x", id, (intmax_t)used_buffer_length, (intmax_t)length, captured_length, payload[BLUEDROID_H4_PACKET_TYPE]); + if (payload[BLUEDROID_H4_PACKET_TYPE] == BLUEDROID_H4_PACKET_TYPE_HCI_EVT) + ws_debug("\t Packet: %02x %02x %02x", (unsigned int) payload[0], (unsigned int) payload[1], (unsigned int)payload[2]); + id +=1; + + ts -= BLUEDROID_TIMESTAMP_BASE; + + endless_loop = extcap_dumper_dump(extcap_dumper, fifo, packet, + captured_length, + captured_length, + (uint32_t)(ts / 1000000), + ((uint32_t)(ts % 1000000)) * 1000); + + used_buffer_length -= length - sizeof(own_pcap_bluetooth_h4_header) + BLUEDROID_TIMESTAMP_SIZE; + if (used_buffer_length < 0) { + ws_warning("Internal Negative used buffer length."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + memmove(buffer, packet + length, used_buffer_length); + } + } + + closesocket(sock); + return EXIT_CODE_SUCCESS; +} + +/*----------------------------------------------------------------------------*/ +/* Android Btsnoop Net */ +/*----------------------------------------------------------------------------*/ + +static int capture_android_bluetooth_btsnoop_net(char *interface, char *fifo, + const char *adb_server_ip, unsigned short *adb_server_tcp_port) { + struct extcap_dumper extcap_dumper; + static char packet[PACKET_LENGTH]; + ssize_t length; + ssize_t used_buffer_length = 0; + socket_handle_t sock; + const char *adb_tcp_btsnoop_net = "tcp:8872"; + int result; + char *serial_number; + uint64_t ts; + static const uint64_t BTSNOOP_TIMESTAMP_BASE = UINT64_C(0x00dcddb30f2f8000); + uint32_t *reported_length; + uint32_t *captured_length; + uint32_t *flags; +/* uint32_t *cumulative_dropped_packets; */ + uint64_t *timestamp; + char *payload = packet + sizeof(own_pcap_bluetooth_h4_header) + 24; + own_pcap_bluetooth_h4_header *h4_header; + + SET_DATA(reported_length, value_u32, packet + sizeof(own_pcap_bluetooth_h4_header) + 0); + SET_DATA(captured_length, value_u32, packet + sizeof(own_pcap_bluetooth_h4_header) + 4); + SET_DATA(flags, value_u32, packet + sizeof(own_pcap_bluetooth_h4_header) + 8); +/* SET_DATA(cumulative_dropped_packets, value_u32, packet + sizeof(own_pcap_bluetooth_h4_header) + 12); */ + SET_DATA(timestamp, value_u64, packet + sizeof(own_pcap_bluetooth_h4_header) + 16); + SET_DATA(h4_header, value_own_pcap_bluetooth_h4_header, payload - sizeof(own_pcap_bluetooth_h4_header)); + + extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_BLUETOOTH_H4_WITH_PHDR); + serial_number = get_serial_from_interface(interface); + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_7; + + result = adb_send(sock, adb_tcp_btsnoop_net); + if (result) { + ws_warning("Error while sending command <%s>", adb_tcp_btsnoop_net); + closesocket(sock); + return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_2; + } + + /* Read "btsnoop" header - 16 bytes */ + while (used_buffer_length < BTSNOOP_HDR_LEN) { + length = recv(sock, packet + used_buffer_length, (int)(BTSNOOP_HDR_LEN - used_buffer_length), 0); + if (length <= 0) { + ws_warning("Broken socket connection."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + used_buffer_length += length; + } + used_buffer_length = 0; + + while (endless_loop) { + errno = 0; + length = recv(sock, packet + used_buffer_length + sizeof(own_pcap_bluetooth_h4_header), + (int)(PACKET_LENGTH - sizeof(own_pcap_bluetooth_h4_header) - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + ws_warning("Broken socket connection."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + used_buffer_length += length; + + while (used_buffer_length >= 24 && + used_buffer_length >= (int) (24 + GINT32_FROM_BE(*captured_length))) { + int32_t direction; + + ts = GINT64_FROM_BE(*timestamp); + ts -= BTSNOOP_TIMESTAMP_BASE; + + direction = GINT32_FROM_BE(*flags) & 0x01; + h4_header->direction = GINT32_TO_BE(direction); + + endless_loop = extcap_dumper_dump(extcap_dumper, fifo, + payload - sizeof(own_pcap_bluetooth_h4_header), + GINT32_FROM_BE(*captured_length) + sizeof(own_pcap_bluetooth_h4_header), + GINT32_FROM_BE(*reported_length) + sizeof(own_pcap_bluetooth_h4_header), + (uint32_t)(ts / 1000000), + ((uint32_t)(ts % 1000000)) * 1000); + + used_buffer_length -= 24 + GINT32_FROM_BE(*captured_length); + if (used_buffer_length < 0) { + ws_warning("Internal Negative used buffer length."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (used_buffer_length > 0) + memmove(packet + sizeof(own_pcap_bluetooth_h4_header), payload + GINT32_FROM_BE(*captured_length), used_buffer_length); + } + } + + closesocket(sock); + return EXIT_CODE_SUCCESS; +} + +/*----------------------------------------------------------------------------*/ +/* Android Logcat Text*/ +/*----------------------------------------------------------------------------*/ + + +static int capture_android_logcat_text(char *interface, char *fifo, + const char *adb_server_ip, unsigned short *adb_server_tcp_port, + int logcat_ignore_log_buffer, const char *logcat_custom_parameter) { + struct extcap_dumper extcap_dumper; + static char packet[PACKET_LENGTH]; + ssize_t length; + size_t used_buffer_length = 0; + socket_handle_t sock; + const char *protocol_name; + size_t exported_pdu_headers_size = 0; + struct exported_pdu_header exported_pdu_header_protocol_normal; + struct exported_pdu_header *exported_pdu_header_protocol; + struct exported_pdu_header exported_pdu_header_end = {0, 0}; + static const char *wireshark_protocol_logcat_text = "logcat_text_threadtime"; + const char *adb_logcat_template = "shell:export ANDROID_LOG_TAGS=\"\" ; exec logcat -v threadtime%s%s %s"; + char *serial_number = NULL; + int result; + char *pos; + const char *logcat_buffer; + const char *logcat_log_buffer; + + extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_WIRESHARK_UPPER_PDU); + + exported_pdu_header_protocol_normal.tag = GUINT16_TO_BE(EXP_PDU_TAG_DISSECTOR_NAME); + exported_pdu_header_protocol_normal.length = GUINT16_TO_BE(strlen(wireshark_protocol_logcat_text) + 2); + + serial_number = get_serial_from_interface(interface); + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_8; + + if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_MAIN) || is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_MAIN)) + logcat_buffer = " -b main"; + else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_SYSTEM) || is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM)) + logcat_buffer = " -b system"; + else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_RADIO) || is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_RADIO)) + logcat_buffer = " -b radio"; + else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_EVENTS) || is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS)) + logcat_buffer = " -b events"; + else if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_TEXT_CRASH)) + logcat_buffer = " -b crash"; + else { + ws_warning("Unknown interface: <%s>", interface); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (logcat_ignore_log_buffer) + logcat_log_buffer = " -T 1"; + else + logcat_log_buffer = ""; + + if (!logcat_custom_parameter) + logcat_custom_parameter = ""; + + result = snprintf((char *) packet, PACKET_LENGTH, adb_logcat_template, logcat_buffer, logcat_log_buffer, logcat_custom_parameter); + if (result <= 0 || result > PACKET_LENGTH) { + ws_warning("Error while completing adb packet"); + closesocket(sock); + return EXIT_CODE_BAD_SIZE_OF_ASSEMBLED_ADB_PACKET_17; + } + + result = adb_send(sock, packet); + if (result) { + ws_warning("Error while sending command <%s>", packet); + closesocket(sock); + return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_3; + } + + protocol_name = wireshark_protocol_logcat_text; + exported_pdu_header_protocol = &exported_pdu_header_protocol_normal; + + memcpy(packet, exported_pdu_header_protocol, sizeof(struct exported_pdu_header)); + exported_pdu_headers_size += sizeof(struct exported_pdu_header); + + memcpy(packet + exported_pdu_headers_size, protocol_name, GUINT16_FROM_BE(exported_pdu_header_protocol->length) - 2); + exported_pdu_headers_size += GUINT16_FROM_BE(exported_pdu_header_protocol->length); + + packet[exported_pdu_headers_size - 1] = 0; + packet[exported_pdu_headers_size - 2] = 0; + + memcpy(packet + exported_pdu_headers_size, &exported_pdu_header_end, sizeof(struct exported_pdu_header)); + exported_pdu_headers_size += sizeof(struct exported_pdu_header) + GUINT16_FROM_BE(exported_pdu_header_end.length); + + used_buffer_length = 0; + while (endless_loop) { + errno = 0; + length = recv(sock, packet + exported_pdu_headers_size + used_buffer_length, (int)(PACKET_LENGTH - exported_pdu_headers_size - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + ws_warning("Broken socket connection. Try reconnect."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + used_buffer_length += length; + + while (used_buffer_length > 0 && (pos = (char *) memchr(packet + exported_pdu_headers_size, '\n', used_buffer_length))) { + int ms; + struct tm* date; + time_t seconds; + time_t secs = 0; + int nsecs = 0; + time_t t; + + length = (ssize_t)(pos - packet) + 1; + + t = time(NULL); + date = localtime(&t); + if (!date) + continue; + if (6 == sscanf(packet + exported_pdu_headers_size, "%d-%d %d:%d:%d.%d", &date->tm_mon, &date->tm_mday, &date->tm_hour, + &date->tm_min, &date->tm_sec, &ms)) { + date->tm_mon -= 1; + date->tm_isdst = -1; + seconds = mktime(date); + secs = (time_t) seconds; + nsecs = (int) (ms * 1e6); + } + + endless_loop = extcap_dumper_dump(extcap_dumper, fifo, packet, + length, + length, + secs, nsecs); + + memmove(packet + exported_pdu_headers_size, packet + length, used_buffer_length + exported_pdu_headers_size - length); + used_buffer_length -= length - exported_pdu_headers_size; + } + } + + closesocket(sock); + return EXIT_CODE_SUCCESS; +} + +/*----------------------------------------------------------------------------*/ +/* Android Logger / Logcat */ +/*----------------------------------------------------------------------------*/ + +static int capture_android_logcat(char *interface, char *fifo, + const char *adb_server_ip, unsigned short *adb_server_tcp_port) { + struct extcap_dumper extcap_dumper; + static char packet[PACKET_LENGTH]; + ssize_t length; + size_t used_buffer_length = 0; + socket_handle_t sock; + const char *protocol_name; + size_t exported_pdu_headers_size = 0; + struct exported_pdu_header exported_pdu_header_protocol_events; + struct exported_pdu_header exported_pdu_header_protocol_normal; + struct exported_pdu_header *exported_pdu_header_protocol; + struct exported_pdu_header exported_pdu_header_end = {0, 0}; + static const char *wireshark_protocol_logcat = "logcat"; + static const char *wireshark_protocol_logcat_events = "logcat_events"; + const char *adb_command; + uint16_t *payload_length; + uint16_t *try_header_size; + uint32_t *timestamp_secs; + uint32_t *timestamp_nsecs; + uint16_t header_size; + int result; + char *serial_number = NULL; + + extcap_dumper = extcap_dumper_open(fifo, EXTCAP_ENCAP_WIRESHARK_UPPER_PDU); + + exported_pdu_header_protocol_events.tag = GUINT16_TO_BE(EXP_PDU_TAG_DISSECTOR_NAME); + exported_pdu_header_protocol_events.length = GUINT16_TO_BE(strlen(wireshark_protocol_logcat_events) + 2); + + exported_pdu_header_protocol_normal.tag = GUINT16_TO_BE(EXP_PDU_TAG_DISSECTOR_NAME); + exported_pdu_header_protocol_normal.length = GUINT16_TO_BE(strlen(wireshark_protocol_logcat) + 2); + + serial_number = get_serial_from_interface(interface); + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_9; + + adb_command = interface_to_logbuf(interface); + if (!adb_command) { + ws_warning("Unknown interface: <%s>", interface); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + result = adb_send(sock, adb_command); + if (result) { + ws_warning("Error while sending command <%s>", adb_command); + closesocket(sock); + return EXIT_CODE_ERROR_WHILE_SENDING_ADB_PACKET_4; + } + + if (is_specified_interface(interface, INTERFACE_ANDROID_LOGCAT_EVENTS)) + { + protocol_name = wireshark_protocol_logcat_events; + exported_pdu_header_protocol = &exported_pdu_header_protocol_events; + } else { + protocol_name = wireshark_protocol_logcat; + exported_pdu_header_protocol = &exported_pdu_header_protocol_normal; + } + + memcpy(packet, exported_pdu_header_protocol, sizeof(struct exported_pdu_header)); + exported_pdu_headers_size += sizeof(struct exported_pdu_header); + + memcpy(packet + exported_pdu_headers_size, protocol_name, GUINT16_FROM_BE(exported_pdu_header_protocol->length) - 2); + exported_pdu_headers_size += GUINT16_FROM_BE(exported_pdu_header_protocol->length); + + packet[exported_pdu_headers_size - 1] = 0; + packet[exported_pdu_headers_size - 2] = 0; + + memcpy(packet + exported_pdu_headers_size, &exported_pdu_header_end, sizeof(struct exported_pdu_header)); + exported_pdu_headers_size += sizeof(struct exported_pdu_header) + GUINT16_FROM_BE(exported_pdu_header_end.length); + + SET_DATA(payload_length, value_u16, packet + exported_pdu_headers_size + 0); + SET_DATA(try_header_size, value_u16, packet + exported_pdu_headers_size + 2); + SET_DATA(timestamp_secs, value_u32, packet + exported_pdu_headers_size + 12); + SET_DATA(timestamp_nsecs, value_u32, packet + exported_pdu_headers_size + 16); + + while (endless_loop) { + errno = 0; + length = recv(sock, packet + exported_pdu_headers_size + used_buffer_length, (int)(PACKET_LENGTH - exported_pdu_headers_size - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + while (endless_loop) { + ws_warning("Broken socket connection. Try reconnect."); + used_buffer_length = 0; + closesocket(sock); + + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + if (sock == INVALID_SOCKET) + return EXIT_CODE_INVALID_SOCKET_10; + + result = adb_send(sock, adb_command); + if (result) { + ws_warning("WARNING: Error while sending command <%s>", adb_command); + continue; + } + + break; + } + } + + used_buffer_length += length + exported_pdu_headers_size; + + if (*try_header_size != 24) + header_size = 20; + else + header_size = *try_header_size; + + length = (*payload_length) + header_size + (ssize_t)exported_pdu_headers_size; + + while (used_buffer_length >= exported_pdu_headers_size + header_size && (size_t)length <= used_buffer_length) { + endless_loop = extcap_dumper_dump(extcap_dumper, fifo, packet, + length, + length, + *timestamp_secs, *timestamp_nsecs); + + memmove(packet + exported_pdu_headers_size, packet + length, used_buffer_length - length); + used_buffer_length -= length; + used_buffer_length += exported_pdu_headers_size; + + + length = (*payload_length) + header_size + (ssize_t)exported_pdu_headers_size; + + if (*try_header_size != 24) + header_size = 20; + else + header_size = *try_header_size; + } + used_buffer_length -= exported_pdu_headers_size; + } + + closesocket(sock); + + return EXIT_CODE_SUCCESS; +} + + +/*----------------------------------------------------------------------------*/ +/* Android Wifi Tcpdump */ +/* The Tcpdump sends data in pcap format. So for using the extcap_dumper we */ +/* need to unpack the pcap and then send the packet data to the dumper. */ +/*----------------------------------------------------------------------------*/ +static int capture_android_tcpdump(char *interface, char *fifo, + char *capture_filter, const char *adb_server_ip, + unsigned short *adb_server_tcp_port) { + static const char *const adb_shell_tcpdump_format = "exec:tcpdump -U -n -s 0 -u -i %s -w - %s 2>/dev/null"; + static const char *const regex_interface = INTERFACE_ANDROID_TCPDUMP "-(?<iface>.*?)-(?<serial>.*)"; + struct extcap_dumper extcap_dumper; + static char data[PACKET_LENGTH]; + ssize_t length; + ssize_t used_buffer_length = 0; + ssize_t frame_length=0; + socket_handle_t sock; + int result; + char *iface = NULL; + char *serial_number = NULL; + bool nanosecond_timestamps; + bool swap_byte_order; + pcap_hdr_t *global_header; + pcaprec_hdr_t p_header; + GRegex *regex = NULL; + GError *err = NULL; + GMatchInfo *match = NULL; + char *tcpdump_cmd = NULL; + char *quoted_filter = NULL; + + regex = g_regex_new(regex_interface, G_REGEX_RAW, (GRegexMatchFlags)0, &err); + if (!regex) { + ws_warning("Failed to compile regex for tcpdump interface"); + return EXIT_CODE_GENERIC; + } + + g_regex_match(regex, interface, (GRegexMatchFlags)0, &match); + if (!g_match_info_matches(match)) { + ws_warning("Failed to determine iface name and serial number"); + g_regex_unref(regex); + return EXIT_CODE_GENERIC; + } + + iface = g_match_info_fetch_named(match, "iface"); + serial_number = g_match_info_fetch_named(match, "serial"); + g_match_info_free(match); + g_regex_unref(regex); + + /* First check for the device if it is connected or not */ + sock = adb_connect_transport(adb_server_ip, adb_server_tcp_port, serial_number); + g_free(serial_number); + if (sock == INVALID_SOCKET) { + g_free(iface); + return EXIT_CODE_INVALID_SOCKET_11; + } + + quoted_filter = g_shell_quote(capture_filter ? capture_filter : ""); + tcpdump_cmd = ws_strdup_printf(adb_shell_tcpdump_format, iface, quoted_filter); + g_free(iface); + g_free(quoted_filter); + result = adb_send(sock, tcpdump_cmd); + g_free(tcpdump_cmd); + if (result) { + ws_warning("Error while setting adb transport"); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + while (used_buffer_length < PCAP_GLOBAL_HEADER_LENGTH) { + errno = 0; + length = recv(sock, data + used_buffer_length, (int)(PCAP_GLOBAL_HEADER_LENGTH - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + ws_warning("Broken socket connection."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + used_buffer_length += length; + } + + global_header = (pcap_hdr_t*) data; + switch (global_header->magic_number) { + case 0xa1b2c3d4: + swap_byte_order = false; + nanosecond_timestamps = false; + break; + case 0xd4c3b2a1: + swap_byte_order = true; + nanosecond_timestamps = false; + break; + case 0xa1b23c4d: + swap_byte_order = false; + nanosecond_timestamps = true; + break; + case 0x4d3cb2a1: + swap_byte_order = true; + nanosecond_timestamps = true; + break; + default: + ws_warning("Received incorrect magic"); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + int encap = (int)(swap_byte_order ? GUINT32_SWAP_LE_BE(global_header->network) : global_header->network); +#ifndef ANDROIDDUMP_USE_LIBPCAP + encap = wtap_pcap_encap_to_wtap_encap(encap); +#endif + extcap_dumper = extcap_dumper_open(fifo, encap); + + used_buffer_length = 0; + while (endless_loop) { + ssize_t offset = 0; + + errno = 0; + length = recv(sock, data + used_buffer_length, (int)(PACKET_LENGTH - used_buffer_length), 0); + if (errno == EAGAIN +#if EWOULDBLOCK != EAGAIN + || errno == EWOULDBLOCK +#endif + ) { + continue; + } + else if (errno != 0) { + ws_warning("ERROR capture: %s", strerror(errno)); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + if (length <= 0) { + ws_warning("Broken socket connection."); + closesocket(sock); + return EXIT_CODE_GENERIC; + } + + used_buffer_length += length; + + while ((used_buffer_length - offset) > PCAP_RECORD_HEADER_LENGTH) { + p_header = *((pcaprec_hdr_t*) (data + offset)); + if (swap_byte_order) { + p_header.ts_sec = GUINT32_SWAP_LE_BE(p_header.ts_sec); + p_header.ts_usec = GUINT32_SWAP_LE_BE(p_header.ts_usec); + p_header.incl_len = GUINT32_SWAP_LE_BE(p_header.incl_len); + p_header.orig_len = GUINT32_SWAP_LE_BE(p_header.orig_len); + } + if (!nanosecond_timestamps) { + p_header.ts_usec = p_header.ts_usec * 1000; + } + + frame_length = p_header.incl_len + PCAP_RECORD_HEADER_LENGTH; + if ((used_buffer_length - offset) < frame_length) { + break; /* wait for complete packet */ + } + + /* It was observed that some times tcpdump reports the length of packet as '0' and that leads to the + * ( Warn Error "Less data was read than was expected" while reading ) + * So to avoid this error we are checking for length of packet before passing it to dumper. + */ + if (p_header.incl_len > 0) { + endless_loop = extcap_dumper_dump(extcap_dumper, fifo, data + offset + PCAP_RECORD_HEADER_LENGTH, + p_header.incl_len, p_header.orig_len, p_header.ts_sec, p_header.ts_usec); + } + + offset += frame_length; + } + + if (offset < used_buffer_length) { + memmove(data, data + offset, used_buffer_length - offset); + } + used_buffer_length -= offset; + } + + closesocket(sock); + return EXIT_CODE_SUCCESS; +} + +int main(int argc, char *argv[]) { + char *err_msg; + static const struct report_message_routines androiddummp_report_routines = { + failure_message, + failure_message, + open_failure_message, + read_failure_message, + write_failure_message, + cfile_open_failure_message, + cfile_dump_open_failure_message, + cfile_read_failure_message, + cfile_write_failure_message, + cfile_close_failure_message + }; + int ret = EXIT_CODE_GENERIC; + int option_idx = 0; + int result; + const char *adb_server_ip = NULL; + unsigned short *adb_server_tcp_port = NULL; + unsigned int logcat_text = 0; + unsigned int logcat_ignore_log_buffer = 0; + const char *logcat_custom_parameter = NULL; + const char *default_adb_server_ip = "127.0.0.1"; + unsigned short default_adb_server_tcp_port = 5037; + unsigned short local_adb_server_tcp_port; + unsigned short local_bt_server_tcp_port; + unsigned short local_bt_local_tcp_port; + unsigned short *bt_server_tcp_port = NULL; + unsigned int bt_forward_socket = 0; + const char *bt_local_ip = NULL; + unsigned short *bt_local_tcp_port = NULL; + unsigned short default_bt_server_tcp_port = 4330; + const char *default_bt_local_ip = "127.0.0.1"; + unsigned short default_bt_local_tcp_port = 4330; + extcap_parameters * extcap_conf = NULL; + char *help_url; + char *help_header = NULL; + + cmdarg_err_init(androiddump_cmdarg_err, androiddump_cmdarg_err); + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("androiddump"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + err_msg = configuration_init(argv[0], NULL); + if (err_msg != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + err_msg); + g_free(err_msg); + } + + init_report_message("androiddump", &androiddummp_report_routines); + + extcap_conf = g_new0(extcap_parameters, 1); + + help_url = data_file_url("androiddump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], ANDROIDDUMP_VERSION_MAJOR, ANDROIDDUMP_VERSION_MINOR, + ANDROIDDUMP_VERSION_RELEASE, help_url); + g_free(help_url); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces [--adb-server-ip=<arg>] [--adb-server-tcp-port=<arg>]\n" + " %s --extcap-interface=INTERFACE --extcap-dlts\n" + " %s --extcap-interface=INTERFACE --extcap-config\n" + " %s --extcap-interface=INTERFACE --fifo=PATH_FILENAME --capture\n" + "\nINTERFACE has the form TYPE-DEVICEID:\n" + "\t""For example: "INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET"-W3D7N15C29005648""\n" + "\n" + "\tTYPE is one of:\n" + "\t"INTERFACE_ANDROID_LOGCAT_MAIN"\n" + "\t"INTERFACE_ANDROID_LOGCAT_SYSTEM"\n" + "\t"INTERFACE_ANDROID_LOGCAT_RADIO"\n" + "\t"INTERFACE_ANDROID_LOGCAT_EVENTS"\n" + "\t"INTERFACE_ANDROID_LOGCAT_TEXT_MAIN"\n" + "\t"INTERFACE_ANDROID_LOGCAT_TEXT_SYSTEM"\n" + "\t"INTERFACE_ANDROID_LOGCAT_TEXT_RADIO"\n" + "\t"INTERFACE_ANDROID_LOGCAT_TEXT_EVENTS"\n" + "\t"INTERFACE_ANDROID_LOGCAT_TEXT_CRASH"\n" + "\t"INTERFACE_ANDROID_BLUETOOTH_HCIDUMP"\n" + "\t"INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER"\n" + "\t"INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET"\n" + "\t"INTERFACE_ANDROID_TCPDUMP"\n" + "\n" + "\t""DEVICEID is the identifier of the device provided by Android SDK (see \"adb devices\")\n" + "\t""For example: W3D7N15C29005648""\n", + + argv[0], argv[0], argv[0], argv[0]); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--adb-server-ip <IP>", "the IP address of the ADB server"); + extcap_help_add_option(extcap_conf, "--adb-server-tcp-port <port>", "the TCP port of the ADB server"); + extcap_help_add_option(extcap_conf, "--logcat-text", "use logcat text format"); + extcap_help_add_option(extcap_conf, "--logcat-ignore-log-buffer", "ignore log buffer"); + extcap_help_add_option(extcap_conf, "--logcat-custom-options <text>", "use custom logcat parameters"); + extcap_help_add_option(extcap_conf, "--bt-server-tcp-port <port>", "bluetooth server TCP port"); + extcap_help_add_option(extcap_conf, "--bt-forward-socket <path>", "bluetooth forward socket"); + extcap_help_add_option(extcap_conf, "--bt-local-ip <IP>", "the bluetooth local IP"); + extcap_help_add_option(extcap_conf, "--bt-local-tcp-port <port>", "the bluetooth local TCP port"); + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + ret = EXIT_CODE_SUCCESS; + goto end; + } + + while ((result = ws_getopt_long(argc, argv, "", longopts, &option_idx)) != -1) { + switch (result) { + + case OPT_VERSION: + extcap_version_print(extcap_conf); + ret = EXIT_CODE_SUCCESS; + goto end; + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_CODE_SUCCESS; + goto end; + case OPT_CONFIG_ADB_SERVER_IP: + adb_server_ip = ws_optarg; + break; + case OPT_CONFIG_ADB_SERVER_TCP_PORT: + adb_server_tcp_port = &local_adb_server_tcp_port; + if (!ws_optarg){ + ws_warning("Impossible exception. Parameter required argument, but there is no it right now."); + goto end; + } + if (!ws_strtou16(ws_optarg, NULL, adb_server_tcp_port)) { + ws_warning("Invalid adb server TCP port: %s", ws_optarg); + goto end; + } + break; + case OPT_CONFIG_LOGCAT_TEXT: + if (ws_optarg && !*ws_optarg) + logcat_text = true; + else + logcat_text = (g_ascii_strncasecmp(ws_optarg, "TRUE", 4) == 0); + break; + case OPT_CONFIG_LOGCAT_IGNORE_LOG_BUFFER: + if (ws_optarg == NULL || (ws_optarg && !*ws_optarg)) + logcat_ignore_log_buffer = true; + else + logcat_ignore_log_buffer = (g_ascii_strncasecmp(ws_optarg, "TRUE", 4) == 0); + break; + case OPT_CONFIG_LOGCAT_CUSTOM_OPTIONS: + if (ws_optarg == NULL || (ws_optarg && *ws_optarg == '\0')) { + logcat_custom_parameter = NULL; + break; + } + + if (g_regex_match_simple("(^|\\s)-[bBcDfgLnpPrv]", ws_optarg, G_REGEX_RAW, (GRegexMatchFlags)0)) { + ws_error("Found prohibited option in logcat-custom-options"); + return EXIT_CODE_GENERIC; + } + + logcat_custom_parameter = ws_optarg; + + break; + case OPT_CONFIG_BT_SERVER_TCP_PORT: + bt_server_tcp_port = &local_bt_server_tcp_port; + if (!ws_optarg){ + ws_warning("Impossible exception. Parameter required argument, but there is no it right now."); + goto end; + } + if (!ws_strtou16(ws_optarg, NULL, bt_server_tcp_port)) { + ws_warning("Invalid bluetooth server TCP port: %s", ws_optarg); + goto end; + } + break; + case OPT_CONFIG_BT_FORWARD_SOCKET: + bt_forward_socket = (g_ascii_strncasecmp(ws_optarg, "TRUE", 4) == 0); + break; + case OPT_CONFIG_BT_LOCAL_IP: + bt_local_ip = ws_optarg; + break; + case OPT_CONFIG_BT_LOCAL_TCP_PORT: + bt_local_tcp_port = &local_bt_local_tcp_port; + if (!ws_optarg){ + ws_warning("Impossible exception. Parameter required argument, but there is no it right now."); + goto end; + } + if (!ws_strtou16(ws_optarg, NULL, bt_local_tcp_port)) { + ws_warning("Invalid bluetooth local tcp port: %s", ws_optarg); + goto end; + } + break; + default: + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) + { + ws_warning("Invalid argument <%s>. Try --help.\n", argv[ws_optind - 1]); + goto end; + } + } + } + + if (!adb_server_ip) + adb_server_ip = default_adb_server_ip; + + if (!adb_server_tcp_port) + adb_server_tcp_port = &default_adb_server_tcp_port; + + if (!bt_server_tcp_port) + bt_server_tcp_port = &default_bt_server_tcp_port; + + if (!bt_local_ip) + bt_local_ip = default_bt_local_ip; + + if (!bt_local_tcp_port) + bt_local_tcp_port = &default_bt_local_tcp_port; + + err_msg = ws_init_sockets(); + if (err_msg != NULL) { + ws_warning("ERROR: %s", err_msg); + g_free(err_msg); + ws_warning("%s", please_report_bug()); + goto end; + } + + extcap_cmdline_debug(argv, argc); + + if (extcap_conf->do_list_interfaces) + register_interfaces(extcap_conf, adb_server_ip, adb_server_tcp_port); + + /* NOTE: + * extcap implementation calls androiddump --extcap-dlts for each interface. + * The only way to know whether an interface exists or not is to go through the + * whole process of listing all interfaces (i.e. calling register_interfaces + * function). Since being a system resource heavy operation and repeated for + * each interface instead register a fake interface to be returned for dlt + * listing only purpose + */ + if (extcap_conf->do_list_dlts) { + new_fake_interface_for_list_dlts(extcap_conf, extcap_conf->interface); + } + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_CODE_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = list_config(extcap_conf->interface); + goto end; + } + + if (extcap_conf->capture) { + if (extcap_conf->interface && is_logcat_interface(extcap_conf->interface)) + if (logcat_text) + ret = capture_android_logcat_text(extcap_conf->interface, + extcap_conf->fifo, adb_server_ip, adb_server_tcp_port, + logcat_ignore_log_buffer, logcat_custom_parameter); + else + ret = capture_android_logcat(extcap_conf->interface, + extcap_conf->fifo, adb_server_ip, adb_server_tcp_port); + else if (extcap_conf->interface && is_logcat_text_interface(extcap_conf->interface)) + ret = capture_android_logcat_text(extcap_conf->interface, + extcap_conf->fifo, adb_server_ip, adb_server_tcp_port, + logcat_ignore_log_buffer, logcat_custom_parameter); + else if (extcap_conf->interface && is_specified_interface(extcap_conf->interface, INTERFACE_ANDROID_BLUETOOTH_HCIDUMP)) + ret = capture_android_bluetooth_hcidump(extcap_conf->interface, extcap_conf->fifo, adb_server_ip, adb_server_tcp_port); + else if (extcap_conf->interface && is_specified_interface(extcap_conf->interface, INTERFACE_ANDROID_BLUETOOTH_EXTERNAL_PARSER)) + ret = capture_android_bluetooth_external_parser(extcap_conf->interface, extcap_conf->fifo, adb_server_ip, adb_server_tcp_port, + bt_server_tcp_port, bt_forward_socket, bt_local_ip, bt_local_tcp_port); + else if (extcap_conf->interface && (is_specified_interface(extcap_conf->interface, INTERFACE_ANDROID_BLUETOOTH_BTSNOOP_NET))) + ret = capture_android_bluetooth_btsnoop_net(extcap_conf->interface, extcap_conf->fifo, adb_server_ip, adb_server_tcp_port); + else if (extcap_conf->interface && (is_specified_interface(extcap_conf->interface,INTERFACE_ANDROID_TCPDUMP))) + ret = capture_android_tcpdump(extcap_conf->interface, extcap_conf->fifo, extcap_conf->capture_filter, adb_server_ip, adb_server_tcp_port); + + goto end; + } + + /* no action was given, assume success */ + ret = EXIT_CODE_SUCCESS; + +end: + /* clean up stuff */ + extcap_base_cleanup(&extcap_conf); +#ifndef ANDROIDDUMP_USE_LIBPCAP + wtap_cleanup(); +#endif + + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/ciscodump.c b/extcap/ciscodump.c new file mode 100644 index 00000000..54751ed0 --- /dev/null +++ b/extcap/ciscodump.c @@ -0,0 +1,2512 @@ +/* ciscodump.c + * ciscodump is extcap tool used to capture data using a ssh on a remote cisco router + * + * Copyright 2015, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN "ciscodump" + +#include <extcap/extcap-base.h> +#include <wsutil/interface.h> +#include <wsutil/strtoi.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/please_report_bug.h> +#include <wsutil/wslog.h> +#include <extcap/ssh-base.h> +#include <writecap/pcapio.h> + +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include <wsutil/time_util.h> +#include <wsutil/ws_strptime.h> + +#include <cli_main.h> + +#define CISCODUMP_VERSION_MAJOR "1" +#define CISCODUMP_VERSION_MINOR "0" +#define CISCODUMP_VERSION_RELEASE "0" + +/* The read timeout in msec */ +#define CISCODUMP_READ_TIMEOUT_MSEC 300 + +#define CISCODUMP_EXTCAP_INTERFACE "ciscodump" +#define SSH_READ_BLOCK_SIZE 1024 +#define SSH_READ_TIMEOUT_MSES 10000 +#define SSH_READ_TIMEOUT_USEC (SSH_READ_TIMEOUT_MSES*1000) + +#define WIRESHARK_CAPTURE "WSC" +#define WIRESHARK_CAPTURE_POINT "WSC_P" +#define WIRESHARK_CAPTURE_BUFFER "WSC_B" +#define WIRESHARK_CAPTURE_ACCESSLIST "WSC_ACL" + +#define PCAP_SNAPLEN 0xffff + +#define PACKET_MAX_SIZE 65535 + +#define MINIMUM_IOS_MAJOR 12 +#define MINIMUM_IOS_MINOR 4 +#define MINIMUM_IOS_XE_MAJOR_16 16 +#define MINIMUM_IOS_XE_MINOR_16 1 +#define MINIMUM_IOS_XE_MAJOR_17 17 +#define MINIMUM_IOS_XE_MINOR_17 1 +#define MINIMUM_ASA_MAJOR 8 +#define MINIMUM_ASA_MINOR 4 + +#define READ_PROMPT_ERROR -1 +#define READ_PROMPT_EOLN 0 +#define READ_PROMPT_PROMPT 1 +#define READ_PROMPT_TOO_LONG 2 + +#define READ_LINE_ERROR -1 +#define READ_LINE_EOLN 0 +#define READ_LINE_TIMEOUT 1 +#define READ_LINE_TOO_LONG 2 + +/* Type of Cisco device */ +typedef enum { + CISCO_UNKNOWN, + CISCO_IOS, + CISCO_IOS_XE_16, + CISCO_IOS_XE_17, + CISCO_ASA +} CISCO_SW_TYPE; + +/* Status of the parser */ +enum { + CISCODUMP_PARSER_STARTING, + CISCODUMP_PARSER_IN_PACKET, + CISCODUMP_PARSER_IN_HEADER, + CISCODUMP_PARSER_END_PACKET, + CISCODUMP_PARSER_UNKNOWN +}; + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_REMOTE_HOST, + OPT_REMOTE_PORT, + OPT_REMOTE_USERNAME, + OPT_REMOTE_PASSWORD, + OPT_REMOTE_INTERFACE, + OPT_REMOTE_FILTER, + OPT_SSHKEY, + OPT_SSHKEY_PASSPHRASE, + OPT_PROXYCOMMAND, + OPT_REMOTE_COUNT +}; + +static char prompt_str[SSH_READ_BLOCK_SIZE + 1]; +static int32_t prompt_len = -1; +CISCO_SW_TYPE global_sw_type = CISCO_UNKNOWN; +static bool send_output_quit = false; /* IOS XE 17: send quit during output */ + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + SSH_BASE_OPTIONS, + { 0, 0, 0, 0} +}; + +static void graceful_shutdown_cb(void) +{ + if (global_sw_type == CISCO_IOS_XE_17) { + send_output_quit = true; + } +} + +/* Replaces needle with rep in line */ +static char* str_replace_char(char *line, char needle, char rep) +{ + for(int i = 0; line[i] != '\0'; i++) { + if (line[i] == needle) { + line[i] = rep; + } + } + + return line; +} + +/* Replaces CR with LN */ +static char* crtoln(char *line) +{ + return str_replace_char(line, '\r', '\n'); +} + +static char* interfaces_list_to_filter(GSList* interfaces, unsigned int remote_port) +{ + GString* filter = g_string_new(NULL); + GSList* cur; + + if (interfaces) { + g_string_append_printf(filter, "deny tcp host %s any eq %u, deny tcp any eq %u host %s", + (char*)interfaces->data, remote_port, remote_port, (char*)interfaces->data); + cur = g_slist_next(interfaces); + while (cur) { + g_string_append_printf(filter, ", deny tcp host %s any eq %u, deny tcp any eq %u host %s", + (char*)cur->data, remote_port, remote_port, (char*)cur->data); + cur = g_slist_next(cur); + } + g_string_append_printf(filter, ", permit ip any any"); + } + + return g_string_free(filter, false); +} + +static char* local_interfaces_to_filter(const unsigned int remote_port) +{ + GSList* interfaces = local_interfaces_to_list(); + char* filter = interfaces_list_to_filter(interfaces, remote_port); + g_slist_free_full(interfaces, g_free); + return filter; +} + +/* Read bytes from the channel with no escape character. + * If bytes == -1, read all data (until timeout). If outbuf != NULL, data are stored there + */ +static int read_output_bytes_any(ssh_channel channel, int bytes, char* outbuf) +{ + char chr; + int total; + int bytes_read; + + total = (bytes > 0 ? bytes : G_MAXINT); + bytes_read = 0; + + while(ssh_channel_read_timeout(channel, &chr, 1, 0, CISCODUMP_READ_TIMEOUT_MSEC) > 0 && bytes_read < total) { + ws_noisy("%c %02x", chr, chr); + if (outbuf) + outbuf[bytes_read] = chr; + bytes_read++; + } + if (outbuf) + outbuf[bytes_read+1] = '\0'; + return EXIT_SUCCESS; +} + +/* Read bytes from the channel. Recognize escape char '^'. + * If bytes == -1, read all data (until timeout). If outbuf != NULL, data are stored there + */ +static int read_output_bytes(ssh_channel channel, int bytes, char* outbuf) +{ + char chr; + int total; + int bytes_read; + + total = (bytes > 0 ? bytes : G_MAXINT); + bytes_read = 0; + + while(ssh_channel_read_timeout(channel, &chr, 1, 0, CISCODUMP_READ_TIMEOUT_MSEC) > 0 && bytes_read < total) { + ws_noisy("%c %02x", chr, chr); + if (chr == '^') + return EXIT_FAILURE; + if (outbuf) + outbuf[bytes_read] = chr; + bytes_read++; + } + return EXIT_SUCCESS; +} + +/* Reads input to buffer and parses EOL + * If line is NULL, just received count of characters in len is calculated + * It returns: + * READ_LINE_ERROR - any ssh error occured + * READ_LINE_EOLN - EOLN found, line/len contains \0 terminated string + * READ_LINE_TIMEOUT - reading ended with timeout, line/len contains \0 terminate prompt + * READ_LINE_TOO_LONG - buffer is full with no EOLN nor PROMPT found, line is filled with NOT \0 terminated data + */ +static int ssh_channel_read_line_timeout(ssh_channel channel, char *line, int *len, int max_len) { + char chr; + int rlen = 0; + + *len = 0; + do { + rlen = ssh_channel_read_timeout(channel, &chr, 1, false, CISCODUMP_READ_TIMEOUT_MSEC); + ws_noisy("%c %02x %d", chr, chr, rlen); + if (rlen == SSH_ERROR) { + ws_warning("Error reading from channel"); + return READ_LINE_ERROR; + } else if (rlen > 0) { + if (chr != '\n') { + /* Ignore \r */ + if (chr != '\r') { + if (line) { + line[*len] = chr; + } + (*len)++; + } + } else { + /* Parse the current line */ + if (line) { + line[*len] = '\0'; + } + return READ_LINE_EOLN; + } + } else { + return READ_LINE_TIMEOUT; + } + } while (*len < max_len); + + return READ_LINE_TOO_LONG; +} + +/* Reads input to buffer and parses EOL or prompt_str PROMPT + * It returns: + * READ_PROMPT_ERROR - any ssh error occured + * READ_PROMPT_EOLN - EOLN found, line/len contains \0 terminated string + * READ_PROMPT_PROMPT - reading ended and it ends with PROMPT, line/len contains \0 terminate prompt + * READ_PROMPT_TOO_LONG - buffer is full with no EOLN nor PROMPT found, line is filled with NOT \0 terminated data + */ +static int ssh_channel_read_prompt(ssh_channel channel, char *line, uint32_t *len, uint32_t max_len) { + char chr; + int rlen = 0; + int64_t start_time = g_get_monotonic_time(); + + do { + rlen = ssh_channel_read_timeout(channel, &chr, 1, false, CISCODUMP_READ_TIMEOUT_MSEC); + ws_noisy("%c %02x %d", chr, chr, rlen); + if (rlen == SSH_ERROR) { + ws_warning("Error reading from channel"); + return READ_PROMPT_ERROR; + } else if (rlen > 0) { + if (chr != '\n') { + line[*len] = chr; + (*len)++; + } else { + /* Parse the current line */ + line[*len] = '\0'; + ws_noisy(" exiting: READ_PROMPT_EOLN (%d/%d)", *len, max_len); + return READ_PROMPT_EOLN; + } + } else { + int64_t cur_time = g_get_monotonic_time(); + + /* ssh timeout, we might be on prompt */ + /* IOS, IOS-XE: check if line has same length as prompt and if it match prompt */ + if ((*len == (uint32_t)prompt_len) && (0 == strncmp(line, prompt_str, prompt_len))) { + line[*len] = '\0'; + ws_noisy(" exiting: READ_PROMPT_PROMPT (%d/%d)", *len, max_len); + return READ_PROMPT_PROMPT; + } + /* ASA: check if line begins with \r and has same length as prompt and if it match prompt */ + if ((line[0] == '\r') && (*len == (uint32_t)prompt_len+1) && (0 == strncmp(line+1, prompt_str, prompt_len))) { + line[*len] = '\0'; + ws_noisy(" exiting: READ_PROMPT_PROMPT (%d/%d)", *len, max_len); + return READ_PROMPT_PROMPT; + } + /* no prompt found, so we continue in waiting for data, but we should check global timeout */ + if ((cur_time-start_time) > SSH_READ_TIMEOUT_USEC) { + line[*len] = '\0'; + ws_noisy(" exiting: READ_PROMPT_ERROR"); + return READ_PROMPT_ERROR; + } + } + } while (!extcap_end_application && (*len < max_len)); + + ws_noisy(" exiting: READ_PROMPT_TOO_LONG (%d/%d/%d)", *len, max_len, extcap_end_application); + line[*len] = '\0'; + return READ_PROMPT_TOO_LONG; +} + +static int ssh_channel_wait_prompt(ssh_channel channel, char *line, uint32_t *len, uint32_t max_len) { + char line2[SSH_READ_BLOCK_SIZE + 1]; + uint32_t len2; + int status; + + memset(line2, 0x0, SSH_READ_BLOCK_SIZE + 1); + line[0] = '\0'; + *len = 0; + do { + len2 = 0; + switch (status = ssh_channel_read_prompt(channel, line2, &len2, SSH_READ_BLOCK_SIZE)) { + case READ_PROMPT_EOLN: + *len = (uint32_t)g_strlcat(line, line2, max_len); + len2 = 0; + break; + case READ_PROMPT_PROMPT: + *len = (uint32_t)g_strlcat(line, line2, max_len); + len2 = 0; + break; + default: + /* We do not have better solution for that cases */ + /* Just terminate the line and return error */ + *len = (uint32_t)g_strlcat(line, line2, max_len); + line[max_len] = '\0'; + ws_noisy("Returning READ_PROMPT_ERROR (%d/%d)", *len, max_len); + return READ_PROMPT_ERROR; + } + } while (status == READ_PROMPT_EOLN); + + ws_noisy("Returning READ_PROMPT_PROMPT (%d/%d)", *len, max_len); + return READ_PROMPT_PROMPT; +} + +/* true if prompt and no error text in response. false otherwise */ +/* Note: It do not catch all CISCO CLI errors, but many of them */ +static bool ssh_channel_wait_prompt_check_error(ssh_channel channel, char *line, uint32_t *len, uint32_t max_len, char *error_re) { + /* Did we received prompt? */ + if (ssh_channel_wait_prompt(channel, line, len, max_len) != READ_PROMPT_PROMPT) { + return false; + } + + /* Is there ERROR: text in output? */ + if (NULL != g_strstr_len(line, -1, "ERROR:")) { + return false; + } + + /* Is there ERROR: text in output? */ + if (NULL != g_strstr_len(line, -1, "% Invalid input detected at")) { + return false; + } + + /* Is there error_re text in output? */ + if (error_re && + g_regex_match_simple(error_re, line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0) + ) { + return false; + } + + return true; +} + +static void ciscodump_cleanup_ios(ssh_channel channel, const char* iface, const char* cfilter) +{ + char* iface_copy = g_strdup(iface); + char* iface_one; + char* str = NULL; + int wscp_cnt = 1; + char* wscp_str = NULL; + + extcap_end_application = false; + if (channel) { + ws_debug("Removing configuration..."); + read_output_bytes(channel, -1, NULL); + + wscp_cnt = 1; + for (str = iface_copy; ; str = NULL) { + iface_one = strtok(str, ","); + if (iface_one == NULL) + break; + + wscp_str = g_strdup_printf("%s_%d", WIRESHARK_CAPTURE_POINT, wscp_cnt); + wscp_cnt++; + + ssh_channel_printf(channel, "monitor capture point stop %s\n", wscp_str); + ssh_channel_printf(channel, "no monitor capture point ip cef %s %s\n", wscp_str, iface_one); + + g_free(wscp_str); + wscp_str = NULL; + } + + ssh_channel_printf(channel, "no monitor capture buffer %s\n", WIRESHARK_CAPTURE_BUFFER); + if (cfilter) { + ssh_channel_printf(channel, "configure terminal\n"); + ssh_channel_printf(channel, "no ip access-list ex %s\n", WIRESHARK_CAPTURE_ACCESSLIST); + } + + read_output_bytes(channel, -1, NULL); + ws_debug("Configuration removed"); + } + + g_free(iface_copy); +} + +static void ciscodump_cleanup_ios_xe_16(ssh_channel channel, const char* cfilter) +{ + if (channel) { + ws_debug("Removing configuration..."); + read_output_bytes(channel, -1, NULL); + + ssh_channel_printf(channel, "monitor capture %s stop\n", WIRESHARK_CAPTURE); + ssh_channel_printf(channel, "no monitor capture %s\n", WIRESHARK_CAPTURE); + if (cfilter) { + ssh_channel_printf(channel, "configure terminal\n"); + ssh_channel_printf(channel, "no ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST); + ssh_channel_printf(channel, "\nend\n"); + } + + read_output_bytes(channel, -1, NULL); + ws_debug("Configuration removed"); + } +} + +static void ciscodump_cleanup_ios_xe_17(ssh_channel channel, const char* cfilter) +{ + if (channel) { + ws_debug("Removing configuration..."); + read_output_bytes(channel, -1, NULL); + + ssh_channel_printf(channel, "monitor capture %s stop\n", WIRESHARK_CAPTURE); + ssh_channel_printf(channel, "no monitor capture %s\n", WIRESHARK_CAPTURE); + if (cfilter) { + ssh_channel_printf(channel, "configure terminal\n"); + ssh_channel_printf(channel, "no ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST); + ssh_channel_printf(channel, "\nend\n"); + } + + read_output_bytes(channel, -1, NULL); + ws_debug("Configuration removed"); + } +} + +static void ciscodump_cleanup_asa(ssh_channel channel, const char* cfilter) +{ + if (channel) { + ws_debug("Removing configuration..."); + read_output_bytes(channel, -1, NULL); + + ssh_channel_printf(channel, "no capture %s\n", WIRESHARK_CAPTURE); + if (cfilter) { + ssh_channel_printf(channel, "configure terminal\n"); + ssh_channel_printf(channel, "clear configure access-list %s\n", WIRESHARK_CAPTURE_ACCESSLIST); + ssh_channel_printf(channel, "\nend\n", WIRESHARK_CAPTURE_ACCESSLIST); + } + + read_output_bytes(channel, -1, NULL); + ws_debug("Configuration removed"); + } +} + +static void ciscodump_cleanup(ssh_channel channel, const char* iface, const char* cfilter, CISCO_SW_TYPE sw_type) +{ + ws_debug("Starting config cleanup"); + switch (sw_type) { + case CISCO_IOS: + ciscodump_cleanup_ios(channel, iface, cfilter); + break; + case CISCO_IOS_XE_16: + ciscodump_cleanup_ios_xe_16(channel, cfilter); + break; + case CISCO_IOS_XE_17: + ciscodump_cleanup_ios_xe_17(channel, cfilter); + break; + case CISCO_ASA: + ciscodump_cleanup_asa(channel, cfilter); + break; + case CISCO_UNKNOWN: + break; + } + ws_debug("Config cleanup finished"); +} + +static void packets_captured_count_ios(char *line, uint32_t *max, bool *running) { + char** part; + + *max = 0; + + ws_debug("Analyzing response: %s", line); + + /* Read count of packets */ + part = g_regex_split_simple( + "Packets :\\s*(\\d+)", + line, G_REGEX_CASELESS, 0); + if (*part && *(part+1)) { + /* RE matched */ + if (strlen(*(part+1)) > 0) { + ws_strtou32(*(part+1), NULL, max); + } + } + g_strfreev(part); + + *running = false; + if (g_regex_match_simple("Status : Active", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) { + *running = true; + } + ws_debug("Count of packets: %d", *max); + ws_debug("Capture is running: %d", *running); +} + +static void packets_captured_count_ios_xe_16(char *line, uint32_t *max, bool *running) { + char** part; + + *max = 0; + + ws_debug("Analyzing response: %s", line); + + part = g_regex_split_simple( + "packets in buf\\s+:\\s+(\\d+)", + line, G_REGEX_CASELESS, 0); + if (*part && *(part+1)) { + /* RE matched */ + if (strlen(*(part+1)) > 0) { + ws_strtou32(*(part+1), NULL, max); + } + } + g_strfreev(part); + + *running = false; + /* Check if capture is running */ + if (g_regex_match_simple("Status : Active", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) { + *running = true; + } + ws_debug("Count of packets: %d", *max); + ws_debug("Capture is running: %d", *running); +} + +static void packets_captured_count_asa(char *line, uint32_t *max, bool *running) { + char** part; + + *max = 0; + + ws_debug("Analyzing response: %s", line); + + /* Read count of packets */ + part = g_regex_split_simple( + "(\\d+) packets captured", + line, G_REGEX_CASELESS, 0); + if (*part && *(part+1)) { + /* RE matched */ + if (strlen(*(part+1)) > 0) { + ws_strtou32(*(part+1), NULL, max); + } + } + g_strfreev(part); + + if (running != NULL) { + *running = false; + /* Check if capture is running */ + if (g_regex_match_simple("\\[Capturing -", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) { + *running = true; + } + ws_debug("Capture is running: %d", *running); + } + ws_debug("Count of packets: %d", *max); +} + +static int parse_line_ios(uint8_t* packet, unsigned* offset, char* line, int status, time_t *pkt_time, uint32_t *pkt_usec) +{ + char** parts; + char** part; + uint32_t value; + size_t size; + + if (strlen(line) <= 1) { + if (status == CISCODUMP_PARSER_IN_PACKET) + return CISCODUMP_PARSER_END_PACKET; + else + return status; + } + +/* +22:45:44.700 UTC Nov 27 2021 : IPv4 LES CEF : Fa4.320 None + +0F1A2C00: B0FAEBC7 A8620050 0zkG(b.P +0F1A2C10: 568494FB 08004588 004EF201 40003F11 V..{..E..Nr.@.?. +0F1A2C20: 5263AC10 1F60AC10 7F31D0DF 00A1003A Rc,..`,..1P_.!.: +0F1A2C30: D1753030 02010104 07707269 76617465 Qu00.....private +0F1A2C40: A0220204 1A00A2E8 02010002 01003014 "...."h......0. +0F1A2C50: 3012060E 2B060104 0182CB21 01040103 0...+.....K!.... +0F1A2C60: 06000500 00 ..... + +22:45:44.700 UTC Nov 27 2021 : IPv4 LES CEF : Fa4.320 None + +0F1A2C00: B0FAEBC7 A8620050 0zkG(b.P +0F1A2C10: 568494FB 08004588 004E7FFB 40003F11 V..{..E..N.{@.?. +0F1A2C20: EB72AC10 1F60AC10 5828E872 00A1003A kr,..`,.X(hr.!.: +0F1A2C30: 2B393030 02010104 07707269 76617465 +900.....private +0F1A2C40: A0220204 07836B18 02010002 01003014 "....k.......0. +0F1A2C50: 3012060E 2B060104 0182CB21 01040103 0...+.....K!.... +0F1A2C60: 06000500 00 ..... +*/ + + /* we got the packet header */ + /* The packet header is a line like: */ + /* 16:09:37.171 ITA Mar 18 2016 : IPv4 LES CEF : Gi0/1 None */ + parts = g_regex_split_simple( + "^(\\d{2}:\\d{2}:\\d{2}).(\\d+) (\\w+) (\\w+ \\d+ \\d+) :", + line, G_REGEX_CASELESS, 0); + if (parts && *(parts+1)) { + /* RE matched */ + char* cp; + struct tm tm; + /* Date without msec, with timezone */ + char* d1 = g_strdup_printf("%s %s %s", *(parts+1), *(parts+3), *(parts+4)); + /* Date without msec, without timezone */ + char* d2 = g_strdup_printf("%s %s", *(parts+1), *(parts+4)); + + memset(&tm, 0x0, sizeof(struct tm)); + + cp = ws_strptime_p(d1, "%H:%M:%S %Z %b %d %Y", &tm); + if (!cp || (*cp != '\0')) { + /* Time zone parse failed */ + cp = ws_strptime_p(d2, "%H:%M:%S %b %d %Y", &tm); + if (!cp || (*cp != '\0')) { + /* Time parse failed, use now */ + time_t t; + struct tm *tm2; + + t = time(0); + tm2 = localtime(&t); + memcpy(&tm, tm2, sizeof(struct tm)); + } + } + ws_strtou32(*(parts+2), NULL, pkt_usec); + *pkt_usec *= 1000; + *pkt_time = mktime(&tm); + + g_strfreev(parts); + return CISCODUMP_PARSER_IN_HEADER; + } + g_strfreev(parts); + + /* we got a line of the packet */ + /* A line looks like */ + /* <address>: <1st group> <2nd group> <3rd group> <4th group> <ascii representation> */ + /* ABCDEF01: 01020304 05060708 090A0B0C 0D0E0F10 ................ */ + /* Note that any of the 4 groups are optional and that a group can be 1 to 4 bytes long */ + parts = g_regex_split_simple( + "^[\\dA-F]{8,8}:\\s+([\\dA-F]{2,8})\\s+([\\dA-F]{2,8}){0,1}\\s+([\\dA-F]{2,8}){0,1}\\s+([\\dA-F]{2,8}){0,1}.*", + line, G_REGEX_CASELESS, 0); + + part = parts; + if (*part && *(part+1)) { + /* There is at least one match. Skip first string */ + part++; + while(*part) { + /* RE matched */ + if (strlen(*part) > 1) { + ws_hexstrtou32(*part, NULL, &value); + value = g_ntohl(value); + size = strlen(*part) / 2; + memcpy(packet + *offset, &value, size); + *offset += (uint32_t)size; + } + part++; + } + } + g_strfreev(parts); + return CISCODUMP_PARSER_IN_PACKET; +} + +static int parse_line_ios_xe_16(uint8_t* packet, unsigned* offset, char* line) +{ + char** parts; + char** part; + uint32_t value; + size_t size; + + if (strlen(line) <= 1) { + return CISCODUMP_PARSER_END_PACKET; + } + + /* +0 + 0000: 00000C07 AC154C5D 3C259068 08004500 ......L]<%.h..E. + 0010: 00547549 40003F01 B582C0A8 4983C0A8 .TuI@.?.....I... + 0020: 46090800 B28E456E 00030000 00000000 F.....En........ + 0030: 00000000 00000000 00000000 00000000 ................ + 0040: 00000000 00000000 00000000 00000000 ................ + 0050: 00000000 00000000 00000000 00000000 ................ + 0060: 0000 .. + +1 + 0000: 4C5D3C25 9068A49B CD904C74 08004500 L]<%.h....Lt..E. + 0010: 00547549 4000FF01 F581C0A8 4609C0A8 .TuI@.......F... + 0020: 49830000 BA8E456E 00030000 00000000 I.....En........ + 0030: 00000000 00000000 00000000 00000000 ................ + 0040: 00000000 00000000 00000000 00000000 ................ + 0050: 00000000 00000000 00000000 00000000 ................ + 0060: 0000 .. +*/ + + /* we got the packet header */ + /*0*/ + if (g_regex_match_simple("^\\d+$", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) { + return CISCODUMP_PARSER_IN_HEADER; + } + + /* we got a line of the packet */ + /* A line looks like */ + /* 0000: 00000C07 AC154C5D 3C259068 08004500 ......L]<%.h..E. */ + /* ... */ + /* 0060: 0000 .. */ + /* Note that any of the 4 groups are optional and that a group can be 1 to 8 bytes long */ + parts = g_regex_split_simple( + "^\\s+[0-9A-F]{4,4}: ([0-9A-F]{2,8}) ([0-9A-F]{2,8}){0,1} ([0-9A-F]{2,8}){0,1} ([0-9A-F]{2,8}){0,1}\\s+.*", + line, G_REGEX_CASELESS, 0); + + part = parts; + if (*part && *(part+1)) { + /* There is at least one match. Skip first string */ + part++; + while(*part) { + if (strlen(*part) > 1) { + ws_hexstrtou32(*part, NULL, &value); + value = g_ntohl(value); + size = strlen(*part) / 2; + memcpy(packet + *offset, &value, size); + *offset += (uint32_t)size; + } + part++; + } + } + g_strfreev(parts); + + return CISCODUMP_PARSER_IN_PACKET; +} + +static int parse_line_ios_xe_17(uint8_t* packet, unsigned* offset, char* line) +{ + char** parts; + char** part; + uint8_t value; + + if (strlen(line) <= 1) { + return CISCODUMP_PARSER_END_PACKET; + } + + /* +0000 6c 5e 3b 88 5e 80 6c 5e 3b 88 5e 80 08 00 45 00 l^;.^.l^;.^...E. +0010 00 28 9b 00 00 00 ff 06 0f 02 c0 a8 c8 01 c0 a8 .(.............. +0020 c8 7a 7e 77 00 31 4c ba ba a5 92 d3 0a eb 50 10 .z~w.1L.......P. +0030 10 20 6a 20 00 00 00 00 00 00 00 00 . j ........ +*/ + + /* we got a line of the packet */ + /* A line looks like */ + /* 0000 6c 5e 3b 88 5e 80 6c 5e 3b 88 5e 80 08 00 45 00 l^;.^.l^;.^...E */ + /* ... */ + /* 0030 10 20 6a 20 00 00 00 00 00 00 00 00 . j ........ */ + /* Note that any of the 4 groups are optional and that a group can be 1 to 8 bytes long */ + parts = g_regex_split_simple( + "^\\s*[0-9A-F]{4,4} ([0-9A-F]{2}) ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1} ([0-9A-F]{2}){0,1}\\s+.*", + line, G_REGEX_CASELESS, 0); + + part = parts; + if (*part && *(part+1)) { + /* There is at least one match. Skip first string */ + part++; + while(*part) { + if (strlen(*part) > 1) { + ws_hexstrtou8(*part, NULL, &value); + memcpy(packet + *offset, &value, 1); + *offset += 1; + } + part++; + } + } + g_strfreev(parts); + + return CISCODUMP_PARSER_IN_PACKET; +} + +static int parse_line_asa(uint8_t* packet, unsigned* offset, char* line, uint32_t *current_max, time_t *pkt_time, uint32_t *pkt_usec) +{ + char** parts; + char** part; + uint16_t value; + size_t size; + uint32_t new_max; + + if (strlen(line) <= 1) { + return CISCODUMP_PARSER_UNKNOWN; + } + + /* +4599 packets captured + + 1: 20:40:01.108469 10.124.255.212 > 10.124.255.5 icmp: echo request +0x0000 a453 0ef3 7fc0 74ad 98e5 0004 0800 4500 .S....t.......E. +0x0010 0054 dc73 4000 4001 4a63 0a7c ffd4 0a7c .T.s@.@.Jc.|...| +0x0020 ff05 0800 275b d0a4 0000 0000 0000 0000 ....'[.......... +0x0030 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0x0040 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0x0050 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0x0060 0000 .. +1 packet shown +*/ + + /* Update count of available packets */ + /* 4599 packets captured */ + packets_captured_count_asa(line, &new_max, NULL); + if (new_max > 0) { + *current_max = new_max; + return CISCODUMP_PARSER_IN_HEADER; + } + + /* we got the packet header */ + /* 1: 20:40:01.108469 10.124.255.212 > 10.124.255.5 icmp: echo request */ + parts = g_regex_split_simple("^\\s*\\d+:\\s+(\\d+):(\\d+):(\\d+)\\.(\\d+)\\s+", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0); + if (parts && *(parts+1)) { + /* RE matched */ + struct tm *tm; + time_t t; + + t = time(0); + tm = localtime(&t); + ws_strtoi32(*(parts+1), NULL, &(tm->tm_hour)); + ws_strtoi32(*(parts+2), NULL, &(tm->tm_min)); + ws_strtoi32(*(parts+3), NULL, &(tm->tm_sec)); + ws_strtou32(*(parts+4), NULL, pkt_usec); + *pkt_time = mktime(tm); + + g_strfreev(parts); + return CISCODUMP_PARSER_IN_HEADER; + } + g_strfreev(parts); + + /* we got the packet tail */ + /* 1 packet shown */ + if (g_regex_match_simple("^\\s*1 packet shown.*$", line, (GRegexCompileFlags) (G_REGEX_CASELESS | G_REGEX_RAW), 0)) { + return CISCODUMP_PARSER_END_PACKET; + } + + /* we got a line of the packet */ + /* A line looks like */ + /* 0x<address>: <1st group> <...> <8th group> <5th group> <ascii representation> */ + /* 0x0000 a453 0ef3 7fc0 74ad 98e5 0004 0800 4500 -.S....t.......E. */ + /* 0x0060 0000 .. */ + /* Note that any of the 8 groups are optional and that a group can be 1 to 8 bytes long */ + parts = g_regex_split_simple( + "^0x[0-9A-F]{4,4}\\s+([0-9A-F]{2,4}) ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1} ([0-9A-F]{2,4}){0,1}\\s+.*", + line, G_REGEX_CASELESS, 0); + + part = parts; + if (*part && *(part+1)) { + /* There is at least one match. Skip first string */ + part++; + while(*part) { + if (strlen(*part) > 1) { + ws_hexstrtou16(*part, NULL, &value); + value = g_ntohs(value); + size = strlen(*part) / 2; + memcpy(packet + *offset, &value, size); + *offset += (uint32_t)size; + } + part++; + } + } + g_strfreev(parts); + + return CISCODUMP_PARSER_IN_PACKET; +} + +/* IOS: Reads response and parses buffer till prompt received */ +static int process_buffer_response_ios(ssh_channel channel, uint8_t* packet, FILE* fp, const uint32_t count, uint32_t *processed_packets) +{ + char line[SSH_READ_BLOCK_SIZE + 1]; + uint32_t read_packets = 1; + int status = CISCODUMP_PARSER_STARTING; + int loop_end = 0; + unsigned packet_size = 0; + time_t pkt_time = 0; + uint32_t pkt_usec = 0; + uint32_t len = 0; + + /* Process response */ + do { + + loop_end = 0; + /* Read input till EOLN or prompt */ + switch (ssh_channel_read_prompt(channel, line, &len, SSH_READ_BLOCK_SIZE)) { + case READ_PROMPT_EOLN: + status = parse_line_ios(packet, &packet_size, line, status, &pkt_time, &pkt_usec); + + if (status == CISCODUMP_PARSER_END_PACKET) { + ws_debug("Read packet %d\n", read_packets); + if (read_packets > *processed_packets) { + int err; + uint64_t bytes_written; + + ws_debug("Exporting packet %d\n", *processed_packets); + /* dump the packet to the pcap file */ + if (!libpcap_write_packet(fp, + pkt_time, pkt_usec, + packet_size, packet_size, packet, &bytes_written, &err)) { + ws_debug("Error in libpcap_write_packet(): %s", g_strerror(err)); + break; + } + fflush(fp); + ws_debug("Dumped packet %u size: %u\n", *processed_packets, packet_size); + (*processed_packets)++; + } + packet_size = 0; + read_packets++; + } + break; + case READ_PROMPT_PROMPT: + ws_debug("Prompt found"); + loop_end = 1; + break; + default: + /* We do not have better solution for that cases */ + if (extcap_end_application) { + /* End by signal causes timeout so we decrease priority */ + ws_debug("Timeout or response was too long\n"); + } else { + ws_warning("Timeout or response was too long\n"); + } + return false; + } + len = 0; + ws_debug("loop end detection %d %d %d %d", extcap_end_application, loop_end, *processed_packets, count); + } while ((!extcap_end_application) && (!loop_end) && (*processed_packets < count)); + + return true; +} + +/* IOS: Queries buffer content and reads it */ +static void ssh_loop_read_ios(ssh_channel channel, FILE* fp, const uint32_t count) +{ + char line[SSH_READ_BLOCK_SIZE + 1]; + uint8_t* packet; + uint32_t processed_packets = 0; + bool running = true; + uint32_t current_max = 0; + uint32_t new_max; + + /* This is big enough to put on the heap */ + packet = (uint8_t*)g_malloc(PACKET_MAX_SIZE); + + do { + uint32_t len = 0; + + /* Query count of available packets in buffer */ + if (ssh_channel_printf(channel, "show monitor capture buffer %s parameters\n", WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE) { + g_free(packet); + return; + } + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + g_free(packet); + return; + } + ws_debug("Read: %s", line); + packets_captured_count_ios(line, &new_max, &running); + ws_debug("Max counts %d %d", current_max, new_max); + if (new_max == current_max) { + /* There is no change in count of available packets, repeat the loop */ + continue; + } else if (new_max < current_max) { + /* Buffer was cleared, stop */ + g_free(packet); + return; + } + current_max = new_max; + ws_debug("New packet count %d\n", current_max); + + /* Dump buffer */ + if (ssh_channel_printf(channel, "show monitor capture buffer %s dump\n", WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE) { + g_free(packet); + return; + } + + /* Process buffer */ + if (!process_buffer_response_ios(channel, packet, fp, count, &processed_packets)) { + g_free(packet); + return; + } + } while (!extcap_end_application && running && (processed_packets < count)); + + g_free(packet); + + /* Discard any subsequent messages */ + read_output_bytes_any(channel, -1, NULL); +} + +/* IOS-XE 16: Reads response and parses buffer till prompt received */ +static int process_buffer_response_ios_xe_16(ssh_channel channel, uint8_t* packet, FILE* fp, const uint32_t count, uint32_t *processed_packets) +{ + char line[SSH_READ_BLOCK_SIZE + 1]; + uint32_t read_packets = 1; + int status = CISCODUMP_PARSER_STARTING; + int loop_end = 0; + unsigned packet_size = 0; + uint32_t len = 0; + + /* Process response */ + do { + loop_end = 0; + /* Read input till EOLN or prompt */ + switch (ssh_channel_read_prompt(channel, line, &len, SSH_READ_BLOCK_SIZE)) { + case READ_PROMPT_EOLN: + status = parse_line_ios_xe_16(packet, &packet_size, line); + + if (status == CISCODUMP_PARSER_END_PACKET) { + ws_debug("Read packet %d\n", read_packets); + if (read_packets > *processed_packets) { + int err; + int64_t cur_time = g_get_real_time(); + uint64_t bytes_written = 0; + + ws_debug("Exporting packet %d\n", *processed_packets); + /* dump the packet to the pcap file */ + if (!libpcap_write_packet(fp, + (uint32_t)(cur_time / G_USEC_PER_SEC), (uint32_t)(cur_time % G_USEC_PER_SEC), + packet_size, packet_size, packet, &bytes_written, &err)) { + ws_debug("Error in libpcap_write_packet(): %s", g_strerror(err)); + break; + } + fflush(fp); + ws_debug("Dumped packet %u size: %u\n", *processed_packets, packet_size); + (*processed_packets)++; + } + packet_size = 0; + read_packets++; + } + break; + case READ_PROMPT_PROMPT: + ws_debug("Prompt found"); + loop_end = 1; + break; + default: + /* We do not have better solution for that cases */ + if (extcap_end_application) { + /* End by signal causes timeout so we decrease priority */ + ws_debug("Timeout or response was too long\n"); + } else { + ws_warning("Timeout or response was too long\n"); + } + return false; + } + len = 0; + ws_debug("loop end detection %d %d %d %d", extcap_end_application, loop_end, *processed_packets, count); + } while ((!extcap_end_application) && (!loop_end) && (*processed_packets < count)); + + return true; +} + +/* IOS-XE 17: Reads response and parses buffer till prompt received */ +static int process_buffer_response_ios_xe_17(ssh_channel channel, uint8_t* packet, FILE* fp, const uint32_t count, uint32_t *processed_packets) +{ + char line[SSH_READ_BLOCK_SIZE + 1]; + uint32_t read_packets = 1; + int status = CISCODUMP_PARSER_STARTING; + int loop_end = 0; + unsigned packet_size = 0; + uint32_t len = 0; + + /* Process response */ + do { + loop_end = 0; + /* Read input till EOLN */ + switch (ssh_channel_read_line_timeout(channel, line, &len, SSH_READ_BLOCK_SIZE)) { + case READ_PROMPT_EOLN: + /* Starting message? Ignore... */ + if (NULL != g_strstr_len(line, -1, "Starting the packet display")) { + break; + } + + /* End of dump message? End loop. */ + if (NULL != g_strstr_len(line, -1, "Started capture point :")) { + loop_end = 1; + break; + } + + status = parse_line_ios_xe_17(packet, &packet_size, line); + ws_debug("Packet offset %d\n", packet_size); + + if ((status == CISCODUMP_PARSER_END_PACKET) && (packet_size > 0)) { + ws_debug("Read packet %d\n", read_packets); + if (read_packets > *processed_packets) { + int err; + int64_t cur_time = g_get_real_time(); + uint64_t bytes_written; + + ws_debug("Exporting packet %d\n", *processed_packets); + /* dump the packet to the pcap file */ + if (!libpcap_write_packet(fp, + (uint32_t)(cur_time / G_USEC_PER_SEC), (uint32_t)(cur_time % G_USEC_PER_SEC), + packet_size, packet_size, packet, &bytes_written, &err)) { + ws_debug("Error in libpcap_write_packet(): %s", g_strerror(err)); + break; + } + fflush(fp); + ws_debug("Dumped packet %u size: %u\n", *processed_packets, packet_size); + (*processed_packets)++; + } + packet_size = 0; + read_packets++; + } + break; + case READ_LINE_TIMEOUT: + /* Timeout is OK during reading of IOS XE 17 buffer */ + break; + default: + /* We do not have better solution for that cases */ + ws_warning("Error or response was too long\n"); + return false; + } + len = 0; + ws_debug("loop end detection %d %d %d %d", extcap_end_application, loop_end, *processed_packets, count); + } while ((!extcap_end_application) && (!loop_end) && (*processed_packets < count)); + + return true; +} + +/* IOS-XE 16: Queries buffer content and reads it */ +static void ssh_loop_read_ios_xe_16(ssh_channel channel, FILE* fp, const uint32_t count) +{ + char line[SSH_READ_BLOCK_SIZE + 1]; + uint8_t* packet; + uint32_t processed_packets = 0; + bool running = true; + uint32_t current_max = 0; + uint32_t new_max; + + /* This is big enough to put on the heap */ + packet = (uint8_t*)g_malloc(PACKET_MAX_SIZE); + + do { + uint32_t len = 0; + + /* Query count of available packets in buffer */ + if (ssh_channel_printf(channel, "show monitor capture %s buffer | inc packets in buf\nshow monitor capture %s | inc Status :\n", WIRESHARK_CAPTURE, WIRESHARK_CAPTURE) == EXIT_FAILURE) { + g_free(packet); + return; + } + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + g_free(packet); + return; + } + ws_debug("Read: %s", line); + packets_captured_count_ios_xe_16(line, &new_max, &running); + ws_debug("Max counts %d %d", current_max, new_max); + if (new_max == current_max) { + /* There is no change in count of available packets, repeat the loop */ + continue; + } else if (new_max < current_max) { + /* Buffer was cleared, stop */ + g_free(packet); + return; + } + current_max = new_max; + ws_debug("New packet count %d\n", current_max); + + /* Dump buffer */ + if (ssh_channel_printf(channel, "show monitor capture %s buffer dump\n", WIRESHARK_CAPTURE) == EXIT_FAILURE) { + g_free(packet); + return; + } + + /* Process buffer */ + if (!process_buffer_response_ios_xe_16(channel, packet, fp, count, &processed_packets)) { + g_free(packet); + return; + } + } while (!extcap_end_application && running && (processed_packets < count)); + + g_free(packet); + + /* Discard any subsequent messages */ + read_output_bytes_any(channel, -1, NULL); +} + +/* IOS-XE 17: Queries buffer content and reads it */ +static void ssh_loop_read_ios_xe_17(ssh_channel channel, FILE* fp, const uint32_t count) +{ + uint8_t* packet; + uint32_t processed_packets = 0; + bool running = true; + + /* This is big enough to put on the heap */ + packet = (uint8_t*)g_malloc(PACKET_MAX_SIZE); + + do { + //uint32_t len = 0; + + /* Process buffer */ + if (!process_buffer_response_ios_xe_17(channel, packet, fp, count, &processed_packets)) { + g_free(packet); + return; + } + if (extcap_end_application && send_output_quit) { + ws_debug("Terminating the output\n"); + ssh_channel_printf(channel, "\n\x1e\n"); + } + } while (!extcap_end_application && running && (processed_packets < count)); + + g_free(packet); + + /* Discard any subsequent messages */ + read_output_bytes_any(channel, -1, NULL); +} + +/* ASA: Reads response and parses buffer till prompt end of packet received */ +static int process_buffer_response_asa(ssh_channel channel, uint8_t* packet, FILE* fp, const uint32_t count, uint32_t *processed_packets, uint32_t *current_max) +{ + char line[SSH_READ_BLOCK_SIZE + 1]; + uint32_t read_packets = 1; + int status = CISCODUMP_PARSER_STARTING; + int loop_end = 0; + unsigned packet_size = 0; + + do { + time_t pkt_time = 0; + uint32_t pkt_usec = 0; + uint32_t len = 0; + + /* Dump buffer */ + if (ssh_channel_printf(channel, "show cap %s packet-number %ld dump\n", WIRESHARK_CAPTURE, (*processed_packets)+1) == EXIT_FAILURE) { + return false; + } + + /* Process response */ + do { + loop_end = 0; + /* Read input till EOLN or prompt */ + switch (ssh_channel_read_prompt(channel, line, &len, SSH_READ_BLOCK_SIZE)) { + case READ_PROMPT_EOLN: + status = parse_line_asa(packet, &packet_size, line, current_max, &pkt_time, &pkt_usec); + + if (status == CISCODUMP_PARSER_END_PACKET) { + ws_debug("Read packet %d\n", read_packets); + int err; + uint64_t bytes_written; + + ws_debug("Exporting packet %d\n", *processed_packets); + /* dump the packet to the pcap file */ + if (!libpcap_write_packet(fp, + pkt_time, pkt_usec, + packet_size, packet_size, packet, &bytes_written, &err)) { + ws_debug("Error in libpcap_write_packet(): %s", g_strerror(err)); + break; + } + fflush(fp); + ws_debug("Dumped packet %u size: %u\n", *processed_packets, packet_size); + (*processed_packets)++; + packet_size = 0; + read_packets++; + loop_end = 1; + } + break; + case READ_PROMPT_PROMPT: + ws_debug("Prompt found"); + loop_end = 1; + break; + default: + /* We do not have better solution for that cases */ + if (extcap_end_application) { + /* End by signal causes timeout so we decrease priority */ + ws_debug("Timeout or response was too long\n"); + } else { + ws_warning("Timeout or response was too long\n"); + } + return false; + } + len = 0; + ws_debug("loop end detection1 %d %d", *processed_packets, count); + } while (!extcap_end_application && !loop_end); + ws_debug("loop end detection2 %d %d %d", extcap_end_application, *processed_packets, count); + } while (!extcap_end_application && (*processed_packets < *current_max) && ((*processed_packets < count))); + + return true; +} + +/* ASA: Queries buffer content and reads it */ +static void ssh_loop_read_asa(ssh_channel channel, FILE* fp, const uint32_t count) +{ + char line[SSH_READ_BLOCK_SIZE + 1]; + uint8_t* packet; + uint32_t processed_packets = 0; + uint32_t current_max = 0; + bool running = true; + uint32_t new_max; + + /* This is big enough to put on the heap */ + packet = (uint8_t*)g_malloc(PACKET_MAX_SIZE); + + do { + uint32_t len = 0; + + /* Query count of available packets in buffer */ + if (ssh_channel_printf(channel, "show cap %s packet-number 0 | inc packets captured\nshow cap | inc %s\n", WIRESHARK_CAPTURE, WIRESHARK_CAPTURE) == EXIT_FAILURE) { + g_free(packet); + return; + } + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + g_free(packet); + return; + } + ws_debug("Read: %s", line); + packets_captured_count_asa(line, &new_max, &running); + ws_debug("Max counts %d %d", current_max, new_max); + if (new_max == current_max) { + /* There is no change in count of available packets, repeat the loop */ + continue; + } else if (new_max < current_max) { + /* Buffer was cleared, stop */ + g_free(packet); + return; + } + current_max = new_max; + ws_debug("New packet count %d\n", current_max); + + /* Process buffer */ + if (!process_buffer_response_asa(channel, packet, fp, count, &processed_packets, ¤t_max)) { + g_free(packet); + return; + } + } while (!extcap_end_application && running && (processed_packets < count)); + + g_free(packet); + + /* Discard any subsequent messages */ + read_output_bytes_any(channel, -1, NULL); +} + + +static void ssh_loop_read(ssh_channel channel, FILE* fp, const uint32_t count _U_, CISCO_SW_TYPE sw_type) +{ + ws_debug("Starting reading loop"); + switch (sw_type) { + case CISCO_IOS: + ssh_loop_read_ios(channel, fp, count); + break; + case CISCO_IOS_XE_16: + ssh_loop_read_ios_xe_16(channel, fp, count); + break; + case CISCO_IOS_XE_17: + ssh_loop_read_ios_xe_17(channel, fp, count); + break; + case CISCO_ASA: + ssh_loop_read_asa(channel, fp, count); + break; + case CISCO_UNKNOWN: + break; + } + ws_debug("Reading loop finished"); +} + +static int detect_host_prompt(ssh_channel channel) +{ + char line[SSH_READ_BLOCK_SIZE + 1]; + int len = 0; + char prompt_2[SSH_READ_BLOCK_SIZE + 1]; + + /* Discard any login message */ + if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE) + return EXIT_FAILURE; + + if (ssh_channel_printf(channel, "\n") == EXIT_FAILURE) + return EXIT_FAILURE; + + /* Check if there is any response to empty line */ + switch (ssh_channel_read_line_timeout(channel, line, &len, SSH_READ_BLOCK_SIZE)) { + case READ_LINE_EOLN: + break; + default: + return EXIT_FAILURE; + } + + if (ssh_channel_printf(channel, "\n") == EXIT_FAILURE) + return EXIT_FAILURE; + + /* Read prompt_str and level char */ + switch (ssh_channel_read_line_timeout(channel, line, &len, SSH_READ_BLOCK_SIZE)) { + case READ_LINE_EOLN: + break; + default: + return EXIT_FAILURE; + } + if (len > 0) { + g_strlcpy(prompt_str, line, SSH_READ_BLOCK_SIZE + 1); + + /* Is there hashtag at the end => enabled mode? */ + if (prompt_str[strlen(prompt_str)-1] != '#') { + /* Is there hashtag and space (ASA) at the end => enabled mode? */ + if ((prompt_str[strlen(prompt_str)-2] != '#') || (prompt_str[strlen(prompt_str)-1] != ' ')) { + return EXIT_FAILURE; + } + } + prompt_len = (int32_t)strlen(prompt_str); + } else { + return EXIT_FAILURE; + } + + if (ssh_channel_printf(channel, "\n") == EXIT_FAILURE) + return EXIT_FAILURE; + + /* Read prompt_str and level char again */ + switch (ssh_channel_read_line_timeout(channel, line, &len, SSH_READ_BLOCK_SIZE)) { + case READ_LINE_EOLN: + break; + default: + return EXIT_FAILURE; + } + if (len > 0) { + g_strlcpy(prompt_2, line, SSH_READ_BLOCK_SIZE + 1); + /* Does second prompt_str match first one? */ + if (0 == g_strcmp0(prompt_str, prompt_2)) { + ws_debug("Detected prompt %s", prompt_str); + return true; + } + } + + return EXIT_FAILURE; +} + +static int check_ios_version(ssh_channel channel, CISCO_SW_TYPE *sw_type) +{ + char* cmdline_version = "show version | include Version\n"; + const char* msg_ios = "Cisco IOS Software"; + const char* msg_ios_xe = "Cisco IOS XE Software"; + const char* msg_asa = "Cisco Adaptive Security Appliance Software"; + const char* msg_version = "Version "; + char version[255]; + int sw_major = 0; + int sw_minor = 0; + char* cur; + + memset(version, 0x0, 255); + + /* Discard any login message */ + if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE) + return false; + + if (ssh_channel_write(channel, cmdline_version, (uint32_t)strlen(cmdline_version)) == SSH_ERROR) + return false; + if (read_output_bytes(channel, 255, version) == EXIT_FAILURE) + return false; + + /* Discard any subsequent text */ + if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE) + return false; + + /* We should check IOS XE first as its version contains IOS string too */ + cur = g_strstr_len(version, strlen(version), msg_ios_xe); + if (cur) { + *sw_type = CISCO_IOS_XE_16; + cur += strlen(msg_ios_xe); + } else { + cur = g_strstr_len(version, strlen(version), msg_ios); + if (cur) { + *sw_type = CISCO_IOS; + cur += strlen(msg_ios); + } else { + cur = g_strstr_len(version, strlen(version), msg_asa); + if (cur) { + *sw_type = CISCO_ASA; + cur += strlen(msg_asa); + } + } + } + + if (*sw_type != CISCO_UNKNOWN && cur) { + cur = g_strstr_len(cur, 255-strlen(cur), msg_version); + if (cur) { + cur += strlen(msg_version); + if (sscanf(cur, "%u.%u", &sw_major, &sw_minor) != 2) + return false; + + switch (*sw_type) { + case CISCO_IOS: + ws_debug("Current IOS version: %u.%u", sw_major, sw_minor); + if ((sw_major > MINIMUM_IOS_MAJOR) || (sw_major == MINIMUM_IOS_MAJOR && sw_minor >= MINIMUM_IOS_MINOR)) { + return true; + } + break; + case CISCO_IOS_XE_16: + ws_debug("Current IOS XE version: %u.%u", sw_major, sw_minor); + if ((sw_major > MINIMUM_IOS_XE_MAJOR_17) || (sw_major == MINIMUM_IOS_XE_MAJOR_17 && sw_minor >= MINIMUM_IOS_XE_MINOR_17)) { + *sw_type = CISCO_IOS_XE_17; + return true; + } + if ((sw_major > MINIMUM_IOS_XE_MAJOR_16) || (sw_major == MINIMUM_IOS_XE_MAJOR_16 && sw_minor >= MINIMUM_IOS_XE_MINOR_16)) { + *sw_type = CISCO_IOS_XE_16; + return true; + } + break; + case CISCO_ASA: + ws_debug("Current ASA version: %u.%u", sw_major, sw_minor); + if ((sw_major > MINIMUM_ASA_MAJOR) || (sw_major == MINIMUM_ASA_MAJOR && sw_minor >= MINIMUM_ASA_MINOR)) { + return true; + } + break; + default: + return false; + } + ws_warning("Recognized software type, but minimal version requirements were not met\n"); + return false; + } else { + ws_warning("Recognized software type %d, but unrecognized version\n", *sw_type); + } + } else { + ws_warning("Unrecognized type of control software."); + } + + return false; +} + +static bool run_capture_ios(ssh_channel channel, const char* iface, const char* cfilter, const uint32_t count) +{ + char* cmdline = NULL; + int ret = 0; + char line[SSH_READ_BLOCK_SIZE + 1]; + uint32_t len; + char* iface_copy = g_strdup(iface); + char* iface_one; + char* str = NULL; + int wscp_cnt = 1; + char* wscp_str = NULL; + + if (ssh_channel_printf(channel, "terminal length 0\n") == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture buffer %s max-size 9500\n", WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (count > 0) { + if (ssh_channel_printf(channel, "monitor capture buffer %s limit packet-count %u\n", WIRESHARK_CAPTURE_BUFFER, count) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + } + + if (cfilter) { + char* multiline_filter; + char* chr; + + if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE) + goto error; + + if (ssh_channel_printf(channel, "ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE) + goto error; + + multiline_filter = g_strdup(cfilter); + chr = multiline_filter; + while((chr = g_strstr_len(chr, strlen(chr), ",")) != NULL) { + chr[0] = '\n'; + ws_debug("Splitting filter into multiline"); + } + ret = ssh_channel_write(channel, multiline_filter, (uint32_t)strlen(multiline_filter)); + g_free(multiline_filter); + if (ret == SSH_ERROR) + goto error; + + if (ssh_channel_printf(channel, "\nend\n") == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture buffer %s filter access-list %s\n", + WIRESHARK_CAPTURE_BUFFER, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + } + + wscp_cnt = 1; + for (str = iface_copy; ; str = NULL) { + iface_one = strtok(str, ","); + if (iface_one == NULL) + break; + + wscp_str = g_strdup_printf("%s_%d", WIRESHARK_CAPTURE_POINT, wscp_cnt); + wscp_cnt++; + + if (0 == g_strcmp0(iface_one, "process-switched")) { + cmdline = g_strdup_printf("monitor capture point ip process-switched %s both", wscp_str); + } else if (0 == g_strcmp0(iface_one, "from-us")) { + cmdline = g_strdup_printf("monitor capture point ip process-switched %s from-us", wscp_str); + } else { + cmdline = g_strdup_printf("monitor capture point ip cef %s %s both", wscp_str, iface_one); + } + + if (ssh_channel_printf(channel, "%s\n", cmdline) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture point associate %s %s \n", wscp_str, + WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + g_free(cmdline); + cmdline = NULL; + } + + wscp_cnt = 1; + for (str = iface_copy; ; str = NULL) { + iface_one = strtok(str, ","); + if (iface_one == NULL) + break; + + wscp_str = g_strdup_printf("%s_%d", WIRESHARK_CAPTURE_POINT, wscp_cnt); + wscp_cnt++; + + if (ssh_channel_printf(channel, "monitor capture point start %s\n", wscp_str) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + g_free(wscp_str); + wscp_str = NULL; + } + + g_free(iface_copy); + return true; +error: + g_free(wscp_str); + g_free(iface_copy); + g_free(cmdline); + ws_warning("Error running ssh remote command"); + + ssh_channel_close(channel); + ssh_channel_free(channel); + return false; +} + +static bool run_capture_ios_xe_16(ssh_channel channel, const char* iface, const char* cfilter, const uint32_t count) +{ + int ret = 0; + char line[SSH_READ_BLOCK_SIZE + 1]; + uint32_t len; + char* iface_copy = g_strdup(iface); + char* iface_one; + char* str = NULL; + + if (ssh_channel_printf(channel, "terminal length 0\n") == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture %s limit packet-len 9500\n", WIRESHARK_CAPTURE) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (count > 0) { + if (ssh_channel_printf(channel, "monitor capture %s limit packets %u\n", WIRESHARK_CAPTURE, count) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + } + + if (cfilter) { + char* multiline_filter; + char* chr; + + if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE) + goto error; + + if (ssh_channel_printf(channel, "ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE) + goto error; + + multiline_filter = g_strdup(cfilter); + chr = multiline_filter; + while((chr = g_strstr_len(chr, strlen(chr), ",")) != NULL) { + chr[0] = '\n'; + ws_debug("Splitting filter into multiline"); + } + ret = ssh_channel_write(channel, multiline_filter, (uint32_t)strlen(multiline_filter)); + g_free(multiline_filter); + if (ret == SSH_ERROR) + goto error; + + if (ssh_channel_printf(channel, "\nend\n") == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture %s access-list %s\n", + WIRESHARK_CAPTURE, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE) + goto error; + } else { + if (ssh_channel_printf(channel, "monitor capture %s match any\n", + WIRESHARK_CAPTURE) == EXIT_FAILURE) + goto error; + } + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + for (str = iface_copy; ; str = NULL) { + iface_one = strtok(str, ","); + if (iface_one == NULL) + break; + + if (0 == g_strcmp0(iface_one, "control-plane")) { + if (ssh_channel_printf(channel, "monitor capture %s control-plane both\n", WIRESHARK_CAPTURE + ) == EXIT_FAILURE) + goto error; + } else { + if (ssh_channel_printf(channel, "monitor capture %s interface %s both\n", WIRESHARK_CAPTURE, + iface_one) == EXIT_FAILURE) + goto error; + } + } + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture %s start\n", WIRESHARK_CAPTURE) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, + "(Capture is not Supported|Unable to activate Capture)") + ) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + g_free(iface_copy); + return true; +error: + g_free(iface_copy); + ws_warning("Error running ssh remote command"); + + ssh_channel_close(channel); + ssh_channel_free(channel); + return false; +} + +static bool run_capture_ios_xe_17(ssh_channel channel, const char* iface, const char* cfilter, const uint32_t count) +{ + int ret = 0; + char line[SSH_READ_BLOCK_SIZE + 1]; + uint32_t len; + char* iface_copy = g_strdup(iface); + char* iface_one; + char* str = NULL; + + if (ssh_channel_printf(channel, "terminal length 0\n") == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture %s limit packet-len 9500\n", WIRESHARK_CAPTURE) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (count > 0) { + if (ssh_channel_printf(channel, "monitor capture %s limit packets %u\n", WIRESHARK_CAPTURE, count) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + } + + if (cfilter) { + char* multiline_filter; + char* chr; + + if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE) + goto error; + + if (ssh_channel_printf(channel, "ip access-list extended %s\n", WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE) + goto error; + + multiline_filter = g_strdup(cfilter); + chr = multiline_filter; + while((chr = g_strstr_len(chr, strlen(chr), ",")) != NULL) { + chr[0] = '\n'; + ws_debug("Splitting filter into multiline"); + } + ret = ssh_channel_write(channel, multiline_filter, (uint32_t)strlen(multiline_filter)); + g_free(multiline_filter); + if (ret == SSH_ERROR) + goto error; + + if (ssh_channel_printf(channel, "\nend\n") == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture %s access-list %s\n", + WIRESHARK_CAPTURE, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE) + goto error; + } else { + if (ssh_channel_printf(channel, "monitor capture %s match any\n", + WIRESHARK_CAPTURE) == EXIT_FAILURE) + goto error; + } + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + for (str = iface_copy; ; str = NULL) { + iface_one = strtok(str, ","); + if (iface_one == NULL) + break; + + if (0 == g_strcmp0(iface_one, "control-plane")) { + if (ssh_channel_printf(channel, "monitor capture %s control-plane both\n", WIRESHARK_CAPTURE + ) == EXIT_FAILURE) + goto error; + } else { + if (ssh_channel_printf(channel, "monitor capture %s interface %s both\n", WIRESHARK_CAPTURE, + iface_one) == EXIT_FAILURE) + goto error; + } + } + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "monitor capture %s start display dump\n", WIRESHARK_CAPTURE) == EXIT_FAILURE) + goto error; + +/* + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, + "(Capture is not Supported|Unable to activate Capture)") + ) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } +*/ + + g_free(iface_copy); + return true; +error: + g_free(iface_copy); + ws_warning("Error running ssh remote command"); + + ssh_channel_close(channel); + ssh_channel_free(channel); + return false; +} + +static bool run_capture_asa(ssh_channel channel, const char* iface, const char* cfilter) +{ + char* cmdline = NULL; + char line[SSH_READ_BLOCK_SIZE + 1]; + uint32_t len; + char *sep; + bool process_filter = true; + char* iface_copy = g_strdup(iface); + char* iface_one; + char* str = NULL; + + if (ssh_channel_printf(channel, "terminal pager 0\n") == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + for (str = iface_copy; ; str = NULL) { + iface_one = strtok(str, ","); + if (iface_one == NULL) + break; + + if (0 == g_strcmp0(iface_one, "asp-drop")) { + /* asp-drop: asp-drop: capture %s type asp-drop all packet-length 9216 !INCLUDE-DECRYPTED + */ + cmdline = g_strdup_printf("capture %s type asp-drop all packet-length 9216", WIRESHARK_CAPTURE); + } else if (NULL != (sep = g_strstr_len(iface_one, -1, "---"))) { + /* Interface type separator found. We support: + * isakmp---ifname: capture %s type isakmp packet-length 32810 interface %s + * // webvpn---ifname: capture %s type webvpn user %s !NO FILTER !INCLUDE-DECRYPTED + * lacp---ifname: capture %s type lacp interface %s packet-length 9216 !NO FILTER !INCLUDE-DECRYPTED + * tls-proxy---ifname: capture %s type tls-proxy packet-length 9216 interface %s + * inline-tag---ifname: capture %s type inline-tag packet-length 9216 interface %s + * raw-data---ifname: capture %s type rawdata packet-length 9216 interface %s + * + * We support /decrypted for some of it: + * isakmp/decrypted---ifname + * tls-proxy/decrypted---ifname + * inline-tag/decrypted---ifname + * raw-data/decrypted---ifname + */ + char* ifname = sep+3; + + if (strstr(iface_one, "isakmp")) { + if (strstr(iface_one, "/decrypted")) { + cmdline = g_strdup_printf("capture %s type isakmp include-decrypted packet-length 32810 interface %s", WIRESHARK_CAPTURE, ifname); + } else { + cmdline = g_strdup_printf("capture %s type isakmp packet-length 32810 interface %s", WIRESHARK_CAPTURE, ifname); + } + /* Completelly different output + } else if (strstr(iface_one, "webvpn")) { + cmdline = g_strdup_printf("capture %s type webvpn user %s", WIRESHARK_CAPTURE, ifname); + process_filter = false; + */ + } else if (strstr(iface_one, "lacp")) { + cmdline = g_strdup_printf("capture %s type lacp interface %s packet-length 9216", WIRESHARK_CAPTURE, ifname); + process_filter = false; + } else if (strstr(iface_one, "tls-proxy")) { + if (strstr(iface_one, "/decrypted")) { + cmdline = g_strdup_printf("capture %s type tls-proxy include-decrypted packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname); + } else { + cmdline = g_strdup_printf("capture %s type tls-proxy packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname); + } + } else if (strstr(iface_one, "inline-tag")) { + if (strstr(iface_one, "/decrypted")) { + cmdline = g_strdup_printf("capture %s type inline-tag include-decrypted packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname); + } else { + cmdline = g_strdup_printf("capture %s type inline-tag packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname); + } + } else if (strstr(iface_one, "raw-data")) { + if (strstr(iface_one, "/decrypted")) { + cmdline = g_strdup_printf("capture %s type raw-data include-decrypted packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname); + } else { + cmdline = g_strdup_printf("capture %s type raw-data packet-length 9216 interface %s", WIRESHARK_CAPTURE, ifname); + } + } else { + ws_warning("Unknown interface type : %s", iface_one); + goto error; + } + } else { + /* Just interface name */ + cmdline = g_strdup_printf("capture %s type raw-data packet-length 9216 interface %s", WIRESHARK_CAPTURE, iface_one); + } + + if (ssh_channel_printf(channel, "%s\n", cmdline) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + g_free(cmdline); + cmdline = NULL; + } + + if (process_filter && cfilter) { + char* multiline_filter; + char* chr; + char* start; + + if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE) + goto error; + + multiline_filter = g_strdup(cfilter); + start = multiline_filter; + while((chr = g_strstr_len(start, strlen(start), ",")) != NULL) { + chr[0] = '\0'; + ws_debug("Splitting filter into multiline"); + if (ssh_channel_printf(channel, "access-list %s %s\n", WIRESHARK_CAPTURE_ACCESSLIST, start) == EXIT_FAILURE) + goto error; + start = chr+1; + } + + if (ssh_channel_printf(channel, "access-list %s %s\n", WIRESHARK_CAPTURE_ACCESSLIST, start) == EXIT_FAILURE) + goto error; + + if (ssh_channel_printf(channel, "\nend\n") == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + + if (ssh_channel_printf(channel, "capture %s access-list %s\n", WIRESHARK_CAPTURE, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE) + goto error; + + if (!ssh_channel_wait_prompt_check_error(channel, line, &len, SSH_READ_BLOCK_SIZE, NULL)) { + ws_warning("Received response: %s", crtoln(line)); + goto error; + } + } + + g_free(iface_copy); + return true; +error: + g_free(iface_copy); + g_free(cmdline); + ws_warning("Error running ssh remote command"); + + ssh_channel_close(channel); + ssh_channel_free(channel); + return false; +} + +static ssh_channel open_channel(ssh_session sshs) +{ + ssh_channel channel; + + channel = ssh_channel_new(sshs); + if (!channel) + return NULL; + + if (ssh_channel_open_session(channel) != SSH_OK) + goto error; + + if (ssh_channel_request_pty(channel) != SSH_OK) + goto error; + + if (ssh_channel_change_pty_size(channel, 80, 24) != SSH_OK) + goto error; + + if (ssh_channel_request_shell(channel) != SSH_OK) + goto error; + + return channel; + +error: + ws_warning("Error running ssh remote command"); + + ssh_channel_close(channel); + ssh_channel_free(channel); + return NULL; +} + +static bool run_capture(ssh_channel channel, const char* iface, const char* cfilter, const uint32_t count, CISCO_SW_TYPE sw_type) +{ + switch (sw_type) { + case CISCO_IOS: + return run_capture_ios(channel, iface, cfilter, count); + case CISCO_IOS_XE_16: + return run_capture_ios_xe_16(channel, iface, cfilter, count); + case CISCO_IOS_XE_17: + return run_capture_ios_xe_17(channel, iface, cfilter, count); + case CISCO_ASA: + return run_capture_asa(channel, iface, cfilter); + case CISCO_UNKNOWN: + ws_warning("Unsupported cisco software. It will not collect any data most probably!"); + return false; + } + + return false; +} + +static int ssh_open_remote_connection(const ssh_params_t* ssh_params, const char* iface, const char* cfilter, + const uint32_t count, const char* fifo) +{ + ssh_session sshs; + ssh_channel channel; + FILE* fp = stdout; + uint64_t bytes_written = 0; + int err; + int ret = EXIT_FAILURE; + char* err_info = NULL; + + if (g_strcmp0(fifo, "-")) { + /* Open or create the output file */ + fp = fopen(fifo, "wb"); + if (!fp) { + ws_warning("Error creating output file: %s", g_strerror(errno)); + return EXIT_FAILURE; + } + } + + if (!libpcap_write_file_header(fp, 1, PCAP_SNAPLEN, false, &bytes_written, &err)) { + ws_warning("Can't write pcap file header"); + goto cleanup; + } + + fflush(fp); + + ws_debug("Create first ssh session"); + sshs = create_ssh_connection(ssh_params, &err_info); + if (!sshs) { + ws_warning("Error creating connection: %s", err_info); + goto cleanup; + } + + channel = open_channel(sshs); + if (!channel) { + ret = EXIT_FAILURE; + goto cleanup; + } + + if (!detect_host_prompt(channel)) + goto cleanup; + + if (!check_ios_version(channel, &global_sw_type)) + goto cleanup; + + /* clean up and exit */ + ciscodump_cleanup(channel, iface, cfilter, global_sw_type); + + if (!run_capture(channel, iface, cfilter, count, global_sw_type)) { + ret = EXIT_FAILURE; + goto cleanup; + } + + /* read from channel and write into fp */ + ssh_loop_read(channel, fp, count, global_sw_type); + + /* Read loop can be terminated by signal or QUIT command in + * mid of long "show" command and its reading can take really + * long time. So we terminate ssh session and then + * create new one to cleanup configuration + */ + ws_debug("Disconnect first ssh session"); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_cleanup(&sshs, &channel); + + ws_debug("Create second ssh session"); + sshs = create_ssh_connection(ssh_params, &err_info); + if (!sshs) { + ws_warning("Error creating connection: %s", err_info); + goto cleanup; + } + + channel = open_channel(sshs); + if (!channel) { + ret = EXIT_FAILURE; + goto cleanup; + } + + /* clean up and exit */ + ciscodump_cleanup(channel, iface, cfilter, global_sw_type); + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_cleanup(&sshs, &channel); + + ret = EXIT_SUCCESS; +cleanup: + if (fp != stdout) + fclose(fp); + + return ret; +} + +static int list_config(char *interface, unsigned int remote_port) +{ + unsigned inc = 0; + char* ipfilter; + + if (!interface) { + ws_warning("No interface specified."); + return EXIT_FAILURE; + } + + if (g_strcmp0(interface, CISCODUMP_EXTCAP_INTERFACE)) { + ws_warning("interface must be %s", CISCODUMP_EXTCAP_INTERFACE); + return EXIT_FAILURE; + } + + ipfilter = local_interfaces_to_filter(remote_port); + + printf("arg {number=%u}{call=--remote-host}{display=Remote SSH server address}" + "{type=string}{tooltip=The remote SSH host. It can be both " + "an IP address or a hostname}{required=true}{group=Server}\n", inc++); + printf("arg {number=%u}{call=--remote-port}{display=Remote SSH server port}" + "{type=unsigned}{default=22}{tooltip=The remote SSH host port (1-65535)}" + "{range=1,65535}{group=Server}\n", inc++); + printf("arg {number=%u}{call=--remote-username}{display=Remote SSH server username}" + "{type=string}{default=%s}{tooltip=The remote SSH username. If not provided, " + "the current user will be used}{group=Authentication}\n", inc++, g_get_user_name()); + printf("arg {number=%u}{call=--remote-password}{display=Remote SSH server password}" + "{type=password}{tooltip=The SSH password, used when other methods (SSH agent " + "or key files) are unavailable.}{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--sshkey}{display=Path to SSH private key}" + "{type=fileselect}{tooltip=The path on the local filesystem of the private ssh key}" + "{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--proxycommand}{display=ProxyCommand}" + "{type=string}{tooltip=The command to use as proxy for the SSH connection}" + "{group=Authentication}\n", inc++); + printf("arg {number=%u}{call--sshkey-passphrase}{display=SSH key passphrase}" + "{type=password}{tooltip=Passphrase to unlock the SSH private key}" + "{group=Authentication\n", inc++); + printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}" + "{type=string}{required=true}{tooltip=The remote network interface used for capture" + "}{group=Capture}\n", inc++); + printf("arg {number=%u}{call=--remote-filter}{display=Remote capture filter}" + "{type=string}{tooltip=The remote capture filter}", inc++); + if (ipfilter) + printf("{default=%s}", ipfilter); + printf("{group=Capture}\n"); + printf("arg {number=%u}{call=--remote-count}{display=Packets to capture}" + "{type=unsigned}{required=true}{tooltip=The number of remote packets to capture.}" + "{group=Capture}\n", inc++); + + extcap_config_debug(&inc); + + g_free(ipfilter); + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) +{ + char* err_msg; + int result; + int option_idx = 0; + ssh_params_t* ssh_params = ssh_params_new(); + char* remote_interface = NULL; + char* remote_filter = NULL; + uint32_t count = 0; + int ret = EXIT_FAILURE; + extcap_parameters * extcap_conf = g_new0(extcap_parameters, 1); + char* help_url; + char* help_header = NULL; + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("ciscodump"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + err_msg = configuration_init(argv[0], NULL); + if (err_msg != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + err_msg); + g_free(err_msg); + } + + help_url = data_file_url("ciscodump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], CISCODUMP_VERSION_MAJOR, CISCODUMP_VERSION_MINOR, + CISCODUMP_VERSION_RELEASE, help_url); + add_libssh_info(extcap_conf); + g_free(help_url); + extcap_base_register_interface(extcap_conf, CISCODUMP_EXTCAP_INTERFACE, "Cisco remote capture", 147, "Remote capture dependent DLT"); + if (!extcap_base_register_graceful_shutdown_cb(extcap_conf, graceful_shutdown_cb)) { + ret = EXIT_FAILURE; + goto end; + } + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --remote-host myhost --remote-port 22222 " + "--remote-username myuser --remote-interface gigabit0/0 " + "--fifo=FILENAME --capture\n", argv[0], argv[0], CISCODUMP_EXTCAP_INTERFACE, argv[0], + CISCODUMP_EXTCAP_INTERFACE, argv[0], CISCODUMP_EXTCAP_INTERFACE); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--remote-host <host>", "the remote SSH host"); + extcap_help_add_option(extcap_conf, "--remote-port <port>", "the remote SSH port (default: 22)"); + extcap_help_add_option(extcap_conf, "--remote-username <username>", "the remote SSH username (default: the current user)"); + extcap_help_add_option(extcap_conf, "--remote-password <password>", "the remote SSH password. " + "If not specified, ssh-agent and ssh-key are used"); + extcap_help_add_option(extcap_conf, "--sshkey <public key path>", "the path of the ssh key"); + extcap_help_add_option(extcap_conf, "--sshkey-passphrase <public key passphrase>", "the passphrase to unlock public ssh"); + extcap_help_add_option(extcap_conf, "--proxycommand <proxy command>", "the command to use as proxy for the ssh connection"); + extcap_help_add_option(extcap_conf, "--remote-interface <iface>", "the remote capture interface"); + extcap_help_add_option(extcap_conf, "--remote-filter <filter>", "a filter for remote capture " + "(default: don't capture data for all interfaces IPs)"); + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + goto end; + } + + while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) { + + switch (result) { + + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_VERSION: + extcap_version_print(extcap_conf); + goto end; + + case OPT_REMOTE_HOST: + g_free(ssh_params->host); + ssh_params->host = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_PORT: + if (!ws_strtou16(ws_optarg, NULL, &ssh_params->port) || ssh_params->port == 0) { + ws_warning("Invalid port: %s", ws_optarg); + goto end; + } + break; + + case OPT_REMOTE_USERNAME: + g_free(ssh_params->username); + ssh_params->username = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_PASSWORD: + g_free(ssh_params->password); + ssh_params->password = g_strdup(ws_optarg); + memset(ws_optarg, 'X', strlen(ws_optarg)); + break; + + case OPT_SSHKEY: + g_free(ssh_params->sshkey_path); + ssh_params->sshkey_path = g_strdup(ws_optarg); + break; + + case OPT_SSHKEY_PASSPHRASE: + g_free(ssh_params->sshkey_passphrase); + ssh_params->sshkey_passphrase = g_strdup(ws_optarg); + memset(ws_optarg, 'X', strlen(ws_optarg)); + break; + + case OPT_PROXYCOMMAND: + g_free(ssh_params->proxycommand); + ssh_params->proxycommand = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_INTERFACE: + g_free(remote_interface); + remote_interface = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_FILTER: + g_free(remote_filter); + remote_filter = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_COUNT: + if (!ws_strtou32(ws_optarg, NULL, &count)) { + ws_warning("Invalid packet count: %s", ws_optarg); + goto end; + } + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (ws_optind != argc) { + ws_warning("Unexpected extra option: %s", argv[ws_optind]); + goto end; + } + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + unsigned int port; + if (!ws_strtou16(ws_optarg, NULL, &ssh_params->port) || ssh_params->port == 0) { + port = 22; + } else { + port = ssh_params->port; + } + ret = list_config(extcap_conf->interface, port); + goto end; + } + + err_msg = ws_init_sockets(); + if (err_msg != NULL) { + ws_warning("ERROR: %s", err_msg); + g_free(err_msg); + ws_warning("%s", please_report_bug()); + goto end; + } + + if (extcap_conf->capture) { + if (!ssh_params->host) { + ws_warning("Missing parameter: --remote-host"); + goto end; + } + + if (!remote_interface) { + ws_warning("ERROR: No interface specified (--remote-interface)"); + goto end; + } + if (count == 0) { + ws_warning("ERROR: count of packets must be specified (--remote-count)"); + goto end; + } + ssh_params->debug = extcap_conf->debug; + ret = ssh_open_remote_connection(ssh_params, remote_interface, + remote_filter, count, extcap_conf->fifo); + } else { + ws_debug("You should not come here... maybe some parameter missing?"); + ret = EXIT_FAILURE; + } + +end: + ssh_params_free(ssh_params); + g_free(remote_interface); + g_free(remote_filter); + extcap_base_cleanup(&extcap_conf); + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/extcap/dpauxmon.c b/extcap/dpauxmon.c new file mode 100644 index 00000000..68280a9f --- /dev/null +++ b/extcap/dpauxmon.c @@ -0,0 +1,589 @@ +/* dpauxmon.c + * dpauxmon is an extcap tool used to monitor DisplayPort AUX channel traffic + * coming in from the kernel via generic netlink + * Copyright 2018, Dirk Eibach, Guntermann & Drunck GmbH <dirk.eibach@gdsys.cc> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN "dpauxmon" + +#include <wireshark.h> + +#include "extcap-base.h" + +#include <wsutil/strtoi.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/wslog.h> +#include <writecap/pcapio.h> + +#include <netlink/netlink.h> +#include <netlink/genl/genl.h> +#include <netlink/genl/ctrl.h> +#include <netlink/genl/mngt.h> + +#include <errno.h> + +#include <linux/genetlink.h> + +#include "dpauxmon_user.h" + +#define PCAP_SNAPLEN 128 + +#define DPAUXMON_EXTCAP_INTERFACE "dpauxmon" +#define DPAUXMON_VERSION_MAJOR "0" +#define DPAUXMON_VERSION_MINOR "1" +#define DPAUXMON_VERSION_RELEASE "0" + +FILE* pcap_fp = NULL; + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_INTERFACE_ID, +}; + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + /* Generic application options */ + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + /* Interfaces options */ + { "interface_id", ws_required_argument, NULL, OPT_INTERFACE_ID}, + { 0, 0, 0, 0 } +}; + +static struct nla_policy dpauxmon_attr_policy[DPAUXMON_ATTR_MAX + 1] = { + [DPAUXMON_ATTR_IFINDEX] = { .type = NLA_U32 }, + [DPAUXMON_ATTR_FROM_SOURCE] = { .type = NLA_FLAG }, + [DPAUXMON_ATTR_TIMESTAMP] = { .type = NLA_MSECS }, +}; + +struct family_handler_args { + const char *group; + int id; +}; + +static int list_config(char *interface) +{ + unsigned inc = 0; + + if (!interface) { + ws_warning("No interface specified."); + return EXIT_FAILURE; + } + + if (g_strcmp0(interface, DPAUXMON_EXTCAP_INTERFACE)) { + ws_warning("interface must be %s", DPAUXMON_EXTCAP_INTERFACE); + return EXIT_FAILURE; + } + + printf("arg {number=%u}{call=--interface_id}{display=Interface index}" + "{type=unsigned}{range=1,65535}{default=%u}{tooltip=The dpauxmon interface index}\n", + inc++, 0); + + extcap_config_debug(&inc); + + return EXIT_SUCCESS; +} + +static int setup_dumpfile(const char* fifo, FILE** fp) +{ + uint64_t bytes_written = 0; + int err; + + if (!g_strcmp0(fifo, "-")) { + *fp = stdout; + return EXIT_SUCCESS; + } + + *fp = fopen(fifo, "wb"); + if (!(*fp)) { + ws_warning("Error creating output file: %s", g_strerror(errno)); + return EXIT_FAILURE; + } + + if (!libpcap_write_file_header(*fp, 275, PCAP_SNAPLEN, false, &bytes_written, &err)) { + ws_warning("Can't write pcap file header"); + return EXIT_FAILURE; + } + + fflush(*fp); + + return EXIT_SUCCESS; +} + +static int dump_packet(FILE* fp, const char* buf, const uint32_t buflen, uint64_t ts_usecs) +{ + uint64_t bytes_written = 0; + int err; + int ret = EXIT_SUCCESS; + + if (!libpcap_write_packet(fp, ts_usecs / 1000000, ts_usecs % 1000000, buflen, buflen, buf, &bytes_written, &err)) { + ws_warning("Can't write packet"); + ret = EXIT_FAILURE; + } + + fflush(fp); + + return ret; +} + +static int error_handler(struct sockaddr_nl *nla _U_, struct nlmsgerr *err, + void *arg) +{ + int *ret = (int*)arg; + *ret = err->error; + return NL_STOP; +} + +static int ack_handler(struct nl_msg *msg _U_, void *arg) +{ + int *ret = (int*)arg; + *ret = 0; + return NL_STOP; +} + +static int family_handler(struct nl_msg *msg, void *arg) +{ + struct family_handler_args *grp = (struct family_handler_args *)arg; + struct nlattr *tb[CTRL_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *mcgrp; + int rem_mcgrp; + + nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (!tb[CTRL_ATTR_MCAST_GROUPS]) + return NL_SKIP; + + nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) { + struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1]; + + nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, + (struct nlattr *)nla_data(mcgrp), nla_len(mcgrp), NULL); + + if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || + !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]) + continue; + + if (strncmp((const char*)nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]), + grp->group, + nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME]))) + continue; + + grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]); + + break; + } + + return NL_SKIP; +} + +static int nl_get_multicast_id(struct nl_sock *sock, int family, + const char *group) +{ + struct nl_msg *msg; + struct nl_cb *cb; + int ret, ctrlid; + struct family_handler_args grp = { + .group = group, + .id = -ENOENT, + }; + + msg = nlmsg_alloc(); + if (!msg) + return -ENOMEM; + + cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) { + ret = -ENOMEM; + goto out_fail_cb; + } + + ctrlid = genl_ctrl_resolve(sock, "nlctrl"); + + genlmsg_put(msg, 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0); + + ret = -ENOBUFS; + NLA_PUT_U16(msg, CTRL_ATTR_FAMILY_ID, family); + + ret = nl_send_auto_complete(sock, msg); + if (ret < 0) + goto nla_put_failure; + + ret = 1; + + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &ret); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &ret); + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, family_handler, &grp); + + while (ret > 0) + nl_recvmsgs(sock, cb); + + if (ret == 0) + ret = grp.id; +nla_put_failure: + nl_cb_put(cb); +out_fail_cb: + nlmsg_free(msg); + return ret; +} + +/* + * netlink callback handlers + */ + +static int nl_receive_timeout(struct nl_sock* sk, struct sockaddr_nl* nla, unsigned char** buf, struct ucred** creds) +{ + struct pollfd fds = {nl_socket_get_fd(sk), POLLIN, 0}; + int poll_res = poll(&fds, 1, 500); + + if (poll_res < 0) { + ws_debug("poll() failed in nl_receive_timeout"); + g_usleep(500000); + return -nl_syserr2nlerr(errno); + } + + return poll_res ? nl_recv(sk, nla, buf, creds) : 0; +} + +static int send_start(struct nl_sock *sock, int family, unsigned int interface_id) +{ + struct nl_msg *msg; + void *hdr; + int err; + int res = 0; + + msg = nlmsg_alloc(); + if (msg == NULL) { + ws_critical("Unable to allocate netlink message"); + return -ENOMEM; + } + + hdr = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, 0, + DPAUXMON_CMD_START, 1); + if (hdr == NULL) { + ws_critical("Unable to write genl header"); + res = -ENOMEM; + goto out_free; + } + + if ((err = nla_put_u32(msg, DPAUXMON_ATTR_IFINDEX, interface_id)) < 0) { + ws_critical("Unable to add attribute: %s", nl_geterror(err)); + res = -EIO; + goto out_free; + } + + if ((err = nl_send_auto_complete(sock, msg)) < 0) + ws_debug("Starting monitor failed, already running? :%s", nl_geterror(err)); + +out_free: + nlmsg_free(msg); + return res; +} + +static void send_stop(struct nl_sock *sock, int family, unsigned int interface_id) +{ + struct nl_msg *msg; + void *hdr; + int err; + + msg = nlmsg_alloc(); + if (msg == NULL) { + ws_critical("Unable to allocate netlink message"); + return; + } + + hdr = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0, 0, + DPAUXMON_CMD_STOP, 1); + if (hdr == NULL) { + ws_critical("Unable to write genl header"); + goto out_free; + } + + if ((err = nla_put_u32(msg, DPAUXMON_ATTR_IFINDEX, interface_id)) < 0) { + ws_critical("Unable to add attribute: %s", nl_geterror(err)); + goto out_free; + } + + if ((err = nl_send_auto_complete(sock, msg)) < 0) { + ws_critical("Unable to send message: %s", nl_geterror(err)); + goto out_free; + } + +out_free: + nlmsg_free(msg); +} + +static int handle_data(struct nl_cache_ops *unused _U_, struct genl_cmd *cmd _U_, + struct genl_info *info, void *arg _U_) +{ + unsigned char *data; + uint32_t data_size; + uint64_t ts = 0; + uint8_t packet[21] = { 0x00 }; + + if (!info->attrs[DPAUXMON_ATTR_DATA]) + return NL_SKIP; + + data = (unsigned char*)nla_data(info->attrs[DPAUXMON_ATTR_DATA]); + data_size = nla_len(info->attrs[DPAUXMON_ATTR_DATA]); + + if (data_size > 19) { + ws_debug("Invalid packet size %u", data_size); + return NL_SKIP; + } + + if (info->attrs[DPAUXMON_ATTR_TIMESTAMP]) + ts = nla_get_msecs(info->attrs[DPAUXMON_ATTR_TIMESTAMP]); + + packet[1] = info->attrs[DPAUXMON_ATTR_FROM_SOURCE] ? 0x01 : 0x00; + + memcpy(&packet[2], data, data_size); + + if (dump_packet(pcap_fp, packet, data_size + 2, ts) == EXIT_FAILURE) + extcap_end_application = true; + + return NL_OK; +} + +static int parse_cb(struct nl_msg *msg, void *arg _U_) +{ + return genl_handle_msg(msg, NULL); +} + +static struct genl_cmd cmds[] = { +#if 0 + { + .c_id = DPAUXMON_CMD_START, + .c_name = "dpauxmon start", + .c_maxattr = DPAUXMON_ATTR_MAX, + .c_attr_policy = dpauxmon_attr_policy, + .c_msg_parser = &handle_start, + }, + { + .c_id = DPAUXMON_CMD_STOP, + .c_name = "dpauxmon stop", + .c_maxattr = DPAUXMON_ATTR_MAX, + .c_attr_policy = dpauxmon_attr_policy, + .c_msg_parser = &handle_stop, + }, +#endif + { + .c_id = DPAUXMON_CMD_DATA, + .c_name = "dpauxmon data", + .c_maxattr = DPAUXMON_ATTR_MAX, + .c_attr_policy = dpauxmon_attr_policy, + .c_msg_parser = &handle_data, + }, +}; + +#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0])) + +static struct genl_ops ops = { + .o_name = "dpauxmon", + .o_cmds = cmds, + .o_ncmds = ARRAY_SIZE(cmds), +}; + +struct nl_sock *sock; + +static void run_listener(const char* fifo, unsigned int interface_id) +{ + int err; + int grp; + struct nl_cb *socket_cb; + + if (setup_dumpfile(fifo, &pcap_fp) == EXIT_FAILURE) { + if (pcap_fp) + goto close_out; + } + + if (!(sock = nl_socket_alloc())) { + ws_critical("Unable to allocate netlink socket"); + goto close_out; + } + + if ((err = nl_connect(sock, NETLINK_GENERIC)) < 0) { + ws_critical("Unable to connect netlink socket: %s", + nl_geterror(err)); + goto free_out; + } + + if ((err = genl_register_family(&ops)) < 0) { + ws_critical("Unable to register Generic Netlink family: %s", + nl_geterror(err)); + goto err_out; + } + + if ((err = genl_ops_resolve(sock, &ops)) < 0) { + ws_critical("Unable to resolve family name: %s", + nl_geterror(err)); + goto err_out; + } + + /* register notification handler callback */ + if ((err = nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, + parse_cb, NULL)) < 0) { + ws_critical("Unable to modify valid message callback %s", + nl_geterror(err)); + goto err_out; + } + + grp = nl_get_multicast_id(sock, ops.o_id, "notify"); + nl_socket_add_membership(sock, grp); + + if (!(socket_cb = nl_socket_get_cb(sock))) { + ws_warning("Can't overwrite recv callback"); + } else { + nl_cb_overwrite_recv(socket_cb, nl_receive_timeout); + nl_cb_put(socket_cb); + } + + err = send_start(sock, ops.o_id, interface_id); + if (err) + goto err_out; + + nl_socket_disable_seq_check(sock); + + ws_debug("DisplayPort AUX monitor running on interface %u", interface_id); + + while(!extcap_end_application) { + if ((err = nl_recvmsgs_default(sock)) < 0) + ws_warning("Unable to receive message: %s", nl_geterror(err)); + } + + send_stop(sock, ops.o_id, interface_id); + +err_out: + nl_close(sock); +free_out: + nl_socket_free(sock); +close_out: + fclose(pcap_fp); +} + +int main(int argc, char *argv[]) +{ + char* configuration_init_error; + int option_idx = 0; + int result; + unsigned int interface_id = 0; + int ret = EXIT_FAILURE; + extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1); + char* help_header = NULL; + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("dpauxmon"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + configuration_init_error = configuration_init(argv[0], NULL); + if (configuration_init_error != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + configuration_init_error); + g_free(configuration_init_error); + } + + extcap_base_set_util_info(extcap_conf, argv[0], DPAUXMON_VERSION_MAJOR, DPAUXMON_VERSION_MINOR, DPAUXMON_VERSION_RELEASE, + NULL); + extcap_base_register_interface(extcap_conf, DPAUXMON_EXTCAP_INTERFACE, "DisplayPort AUX channel monitor capture", 275, "DisplayPort AUX channel monitor"); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --interface_id 0 --fifo myfifo --capture", + argv[0], argv[0], DPAUXMON_EXTCAP_INTERFACE, argv[0], DPAUXMON_EXTCAP_INTERFACE, argv[0], DPAUXMON_EXTCAP_INTERFACE); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--port <port> ", "the dpauxmon interface index"); + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + goto end; + } + + while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) { + switch (result) { + + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_VERSION: + extcap_version_print(extcap_conf); + goto end; + + case OPT_INTERFACE_ID: + if (!ws_strtou32(ws_optarg, NULL, &interface_id)) { + ws_warning("Invalid interface id: %s", ws_optarg); + goto end; + } + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (ws_optind != argc) { + ws_warning("Unexpected extra option: %s", argv[ws_optind]); + goto end; + } + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (!extcap_base_register_graceful_shutdown_cb(extcap_conf, NULL)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = list_config(extcap_conf->interface); + goto end; + } + + if (extcap_conf->capture) + run_listener(extcap_conf->fifo, interface_id); + +end: + /* clean up stuff */ + extcap_base_cleanup(&extcap_conf); + return ret; +} diff --git a/extcap/dpauxmon_user.h b/extcap/dpauxmon_user.h new file mode 100644 index 00000000..645e4b8b --- /dev/null +++ b/extcap/dpauxmon_user.h @@ -0,0 +1,59 @@ +/** @file + * + * Copyright 2018, Dirk Eibach, Guntermann & Drunck GmbH <dirk.eibach@gdsys.cc> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef DPAUXMON_USER_H_ +#define DPAUXMON_USER_H_ + +#include <linux/types.h> + +/* + * Generic Netlink Interface for DisplayPort AUX channel monitoring + */ + +/* + * enum dpauxmon_cmd - supported dpauxmon netlink commands + * + * @__DPAUXMON_CMD_UNSPEC: unspecified command to catch errors + * + * @DPAUXMON_CMD_START: start monitoring on %DPAUXMON_ATTR_IFINDEX + * @DPAUXMON_CMD_STOP: stop monitoring on %DPAUXMON_ATTR_IFINDEX + * @DPAUXMON_CMD_DATA: captured data from %DPAUXMON_ATTR_IFINDEX + */ +enum dpauxmon_cmd { + __DPAUXMON_CMD_UNSPEC, + DPAUXMON_CMD_START, + DPAUXMON_CMD_STOP, + DPAUXMON_CMD_DATA, + + /* keep last */ + __DPAUXMON_CMD_MAX, + DPAUXMON_CMD_MAX = __DPAUXMON_CMD_MAX - 1, +}; + +/* + * enum dpauxmon_attr - dpauxmon netlink attributes + * + * @__DPAUXMON_ATTR_UNSPEC: unspecified attribute to catch errors + * + * @DPAUXMON_ATTR_IFINDEX: index of dpauxmon unit to operate on + * @DPAUXMON_ATTR_DATA: dpauxmon data payload + * @DPAUXMON_ATTR_FROM_SOURCE: data payload is sent from source + * @DPAUXMON_ATTR_TIMESTAMP: data payload is sent from source + */ +enum dpauxmon_attr { + __DPAUXMON_ATTR_UNSPEC, + DPAUXMON_ATTR_IFINDEX, /* NLA_U32 */ + DPAUXMON_ATTR_DATA, /* NLA_BINARY */ + DPAUXMON_ATTR_FROM_SOURCE, /* NLA_FLAG */ + DPAUXMON_ATTR_TIMESTAMP, /* NLA_MSECS */ + + /* keep last */ + __DPAUXMON_ATTR_AFTER_LAST, + DPAUXMON_ATTR_MAX = __DPAUXMON_ATTR_AFTER_LAST - 1 +}; + +#endif diff --git a/extcap/etl.c b/extcap/etl.c new file mode 100644 index 00000000..78f2bf22 --- /dev/null +++ b/extcap/etl.c @@ -0,0 +1,784 @@ +/* etl.c + * + * Copyright 2020, Odysseus Yang + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * Reads an ETL file and writes out a pcap file with LINKTYPE_ETW. + * + * https://docs.microsoft.com/en-us/windows/win32/etw/event-tracing-portal + */ + +#include "config.h" +#define WS_LOG_DOMAIN "etwdump" + +#include "etl.h" +#include "wsutil/ws_getopt.h" +#include "wsutil/strtoi.h" +#include "etw_message.h" + +#include <rpc.h> +#include <winevt.h> + +#define MAX_PACKET_SIZE 0xFFFF +#define G_NSEC_PER_SEC 1000000000 +#define ADD_OFFSET_TO_POINTER(buffer, offset) (((PBYTE)buffer) + offset) +#define ROUND_UP_COUNT(Count,Pow2) \ + ( ((Count)+(Pow2)-1) & (~(((int)(Pow2))-1)) ) + +extern int g_include_undecidable_event; + +//Microsoft-Windows-Wmbclass-Opn +const GUID mbb_provider = { 0xA42FE227, 0xA7BF, 0x4483, {0xA5, 0x02, 0x6B, 0xCD, 0xA4, 0x28, 0xCD, 0x96} }; +// Microsoft-Windows-NDIS-PacketCapture +const GUID ndis_capture_provider = { 0x2ed6006e, 0x4729, 0x4609, 0xb4, 0x23, 0x3e, 0xe7, 0xbc, 0xd6, 0x78, 0xef }; + +EXTERN_C const GUID DECLSPEC_SELECTANY EventTraceGuid = { 0x68fdd900, 0x4a3e, 0x11d1, {0x84, 0xf4, 0x00, 0x00, 0xf8, 0x04, 0x64, 0xe3} }; +EXTERN_C const GUID DECLSPEC_SELECTANY ImageIdGuid = { 0xb3e675d7, 0x2554, 0x4f18, { 0x83, 0xb, 0x27, 0x62, 0x73, 0x25, 0x60, 0xde } }; +EXTERN_C const GUID DECLSPEC_SELECTANY SystemConfigExGuid = { 0x9b79ee91, 0xb5fd, 0x41c0, { 0xa2, 0x43, 0x42, 0x48, 0xe2, 0x66, 0xe9, 0xd0 } }; +EXTERN_C const GUID DECLSPEC_SELECTANY EventMetadataGuid = { 0xbbccf6c1, 0x6cd1, 0x48c4, {0x80, 0xff, 0x83, 0x94, 0x82, 0xe3, 0x76, 0x71 } }; +EXTERN_C const GUID DECLSPEC_SELECTANY ZeroGuid = { 0 }; + +typedef struct _WTAP_ETL_RECORD { + EVENT_HEADER EventHeader; // Event header + ETW_BUFFER_CONTEXT BufferContext; // Buffer context + ULONG UserDataLength; + ULONG MessageLength; + ULONG ProviderLength; +} WTAP_ETL_RECORD; + +enum { + OPT_PROVIDER, + OPT_KEYWORD, + OPT_LEVEL, +}; + +static struct ws_option longopts[] = { + { "p", ws_required_argument, NULL, OPT_PROVIDER}, + { "k", ws_required_argument, NULL, OPT_KEYWORD}, + { "l", ws_required_argument, NULL, OPT_LEVEL}, + { 0, 0, 0, 0 } +}; + +typedef struct _PROVIDER_FILTER { + GUID ProviderId; + ULONG64 Keyword; + UCHAR Level; +} PROVIDER_FILTER; + +char g_err_info[FILENAME_MAX] = { 0 }; +int g_err = ERROR_SUCCESS; +static wtap_dumper* g_pdh = NULL; +extern ULONGLONG g_num_events; +static PROVIDER_FILTER g_provider_filters[32] = { 0 }; +static BOOL g_is_live_session = false; + +static void WINAPI event_callback(PEVENT_RECORD ev); +void etw_dump_write_opn_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp); +void etw_dump_write_ndiscap_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp); +void etw_dump_write_general_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp); +void etw_dump_write_event_head_only(PEVENT_RECORD ev, ULARGE_INTEGER timestamp); +void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length); +wtap_dumper* etw_dump_open(const char* pcapng_filename, int* err, char** err_info); + +DWORD GetPropertyValue(WCHAR* ProviderId, EVT_PUBLISHER_METADATA_PROPERTY_ID PropertyId, PEVT_VARIANT* Value) +{ + BOOL bRet; + DWORD err = ERROR_SUCCESS; + PEVT_VARIANT value = NULL; + DWORD bufSize = 0; + DWORD bufUsedOrReqd = 0; + + EVT_HANDLE pubHandle = EvtOpenPublisherMetadata(NULL, ProviderId, NULL, GetThreadLocale(), 0); + if (pubHandle == NULL) + { + return GetLastError(); + } + + /* + * Get required size for property + */ + bRet = EvtGetPublisherMetadataProperty( + pubHandle, + PropertyId, + 0, + bufSize, + value, + &bufUsedOrReqd); + + if (!bRet && ((err = GetLastError()) != ERROR_INSUFFICIENT_BUFFER)) + { + return err; + } + else if (bRet) /* Didn't expect this to succeed */ + { + return ERROR_INVALID_STATE; + } + + value = (PEVT_VARIANT)g_malloc(bufUsedOrReqd); + if (!value) + { + return ERROR_INSUFFICIENT_BUFFER; + } + bufSize = bufUsedOrReqd; + + /* + * Get the property value + */ + bRet = EvtGetPublisherMetadataProperty( + pubHandle, + PropertyId, + 0, + bufSize, + value, + &bufUsedOrReqd); + if (!bRet) + { + g_free(value); + return GetLastError(); + } + + *Value = value; + return ERROR_SUCCESS; +} + +wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filename, const char* params, int* err, char** err_info) +{ + EVENT_TRACE_LOGFILE log_file = { 0 }; + WCHAR w_etl_filename[FILENAME_MAX] = { 0 }; + wtap_open_return_val returnVal = WTAP_OPEN_MINE; + + SUPER_EVENT_TRACE_PROPERTIES super_trace_properties = { 0 }; + super_trace_properties.prop.Wnode.BufferSize = sizeof(SUPER_EVENT_TRACE_PROPERTIES); + super_trace_properties.prop.Wnode.ClientContext = 2; + super_trace_properties.prop.Wnode.Flags = WNODE_FLAG_TRACED_GUID; + super_trace_properties.prop.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + super_trace_properties.prop.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + TRACEHANDLE traceControllerHandle = (TRACEHANDLE)INVALID_HANDLE_VALUE; + TRACEHANDLE trace_handle = INVALID_PROCESSTRACE_HANDLE; + + SecureZeroMemory(g_provider_filters, sizeof(g_provider_filters)); + SecureZeroMemory(g_err_info, FILENAME_MAX); + g_err = ERROR_SUCCESS; + g_num_events = 0; + g_is_live_session = false; + + if (params) + { + int opt_result = 0; + int option_idx = 0; + int provider_idx = 0; + char** params_array = NULL; + int params_array_num = 0; + WCHAR provider_id[FILENAME_MAX] = { 0 }; + ULONG convert_level = 0; + + params_array = g_strsplit(params, " ", -1); + while (params_array[params_array_num]) + { + params_array_num++; + } + + ws_optind = 0; + while ((opt_result = ws_getopt_long(params_array_num, params_array, ":", longopts, &option_idx)) != -1) { + switch (opt_result) { + case OPT_PROVIDER: + mbstowcs(provider_id, ws_optarg, FILENAME_MAX); + if (UuidFromString(provider_id, &g_provider_filters[provider_idx].ProviderId) == RPC_S_INVALID_STRING_UUID) + { + PEVT_VARIANT value = NULL; + + *err = GetPropertyValue( + provider_id, + EvtPublisherMetadataPublisherGuid, + &value); + + /* + * Copy returned GUID locally + */ + if (*err == ERROR_SUCCESS) + { + if (value->Type == EvtVarTypeGuid && value->GuidVal) + { + g_provider_filters[provider_idx].ProviderId = *(value->GuidVal); + } + else + { + *err = ERROR_INVALID_DATA; + } + } + else + { + *err_info = ws_strdup_printf("Cannot convert provider %s to a GUID, err is 0x%x", ws_optarg, *err); + return WTAP_OPEN_ERROR; + } + + g_free(value); + } + + if (IsEqualGUID(&g_provider_filters[0].ProviderId, &ZeroGuid)) + { + *err = ERROR_INVALID_PARAMETER; + *err_info = ws_strdup_printf("Provider %s is zero, err is 0x%x", ws_optarg, *err); + return WTAP_OPEN_ERROR; + } + provider_idx++; + break; + case OPT_KEYWORD: + if (provider_idx == 0) + { + *err = ERROR_INVALID_PARAMETER; + *err_info = ws_strdup_printf("-k parameter must follow -p, err is 0x%x", *err); + return WTAP_OPEN_ERROR; + } + + g_provider_filters[provider_idx - 1].Keyword = _strtoui64(ws_optarg, NULL, 0); + if (!g_provider_filters[provider_idx - 1].Keyword) + { + *err = ERROR_INVALID_PARAMETER; + *err_info = ws_strdup_printf("Keyword %s cannot be converted, err is 0x%x", ws_optarg, *err); + return WTAP_OPEN_ERROR; + } + break; + case OPT_LEVEL: + if (provider_idx == 0) + { + *err = ERROR_INVALID_PARAMETER; + *err_info = ws_strdup_printf("-l parameter must follow -p, err is 0x%x", *err); + return WTAP_OPEN_ERROR; + } + + convert_level = strtoul(ws_optarg, NULL, 0); + if (convert_level > UCHAR_MAX) + { + *err = ERROR_INVALID_PARAMETER; + *err_info = ws_strdup_printf("Level %s is bigger than 0xff, err is 0x%x", ws_optarg, *err); + return WTAP_OPEN_ERROR; + } + if (!convert_level) + { + *err = ERROR_INVALID_PARAMETER; + *err_info = ws_strdup_printf("Level %s cannot be converted, err is 0x%x", ws_optarg, *err); + return WTAP_OPEN_ERROR; + } + + g_provider_filters[provider_idx - 1].Level = (UCHAR)convert_level; + break; + } + } + g_strfreev(params_array); + } + + /* do/while(false) is used to jump out of loop so no complex nested if/else is needed */ + do + { + /* Read ETW from an etl file */ + if (etl_filename) + { + mbstowcs(w_etl_filename, etl_filename, FILENAME_MAX); + + log_file.LogFileName = w_etl_filename; + log_file.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD; + log_file.EventRecordCallback = event_callback; + log_file.Context = NULL; + } + else + { + /* + * Try the best to stop the leftover session since extcap has no way to cleanup when stop capturing. See issue + * https://gitlab.com/wireshark/wireshark/-/issues/17131 + */ + ControlTrace((TRACEHANDLE)NULL, LOGGER_NAME, &super_trace_properties.prop, EVENT_TRACE_CONTROL_STOP); + + g_is_live_session = true; + + log_file.LoggerName = LOGGER_NAME; + log_file.LogFileName = NULL; + log_file.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD; + log_file.EventRecordCallback = event_callback; + log_file.BufferCallback = NULL; + log_file.Context = NULL; + + *err = StartTrace( + &traceControllerHandle, + log_file.LoggerName, + &super_trace_properties.prop); + if (*err != ERROR_SUCCESS) + { + *err_info = ws_strdup_printf("StartTrace failed with 0x%x", *err); + returnVal = WTAP_OPEN_ERROR; + break; + } + + for (int i = 0; i < ARRAYSIZE(g_provider_filters); i++) + { + if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ZeroGuid)) + { + break; + } + *err = EnableTraceEx( + &g_provider_filters[i].ProviderId, + NULL, + traceControllerHandle, + true, + g_provider_filters[i].Level, + g_provider_filters[i].Keyword, + 0, + 0, + NULL); + if (*err != ERROR_SUCCESS) + { + *err_info = ws_strdup_printf("EnableTraceEx failed with 0x%x", *err); + returnVal = WTAP_OPEN_ERROR; + break; + } + } + } + + trace_handle = OpenTrace(&log_file); + if (trace_handle == INVALID_PROCESSTRACE_HANDLE) { + *err = GetLastError(); + *err_info = ws_strdup_printf("OpenTrace failed with 0x%x", *err); + returnVal = WTAP_OPEN_NOT_MINE; + break; + } + + g_pdh = etw_dump_open(pcapng_filename, err, err_info); + if (g_pdh == NULL) + { + returnVal = WTAP_OPEN_ERROR; + break; + } + + *err = ProcessTrace(&trace_handle, 1, 0, 0); + if (*err != ERROR_SUCCESS) { + returnVal = WTAP_OPEN_ERROR; + *err_info = ws_strdup_printf("ProcessTrace failed with 0x%x", *err); + break; + } + + if (g_err != ERROR_SUCCESS) + { + *err = g_err; + *err_info = g_strdup(g_err_info); + returnVal = WTAP_OPEN_ERROR; + break; + } + + if (!g_num_events) { + *err = ERROR_NO_DATA; + *err_info = ws_strdup_printf("Didn't find any etw event"); + returnVal = WTAP_OPEN_NOT_MINE; + break; + } + } while (false); + + if (trace_handle != INVALID_PROCESSTRACE_HANDLE) + { + CloseTrace(trace_handle); + } + if (g_pdh != NULL) + { + if (*err == ERROR_SUCCESS) + { + if (!wtap_dump_close(g_pdh, NULL, err, err_info)) + { + returnVal = WTAP_OPEN_ERROR; + } + } + else + { + int err_ignore; + char* err_info_ignore = NULL; + if (!wtap_dump_close(g_pdh, NULL, &err_ignore, &err_info_ignore)) + { + returnVal = WTAP_OPEN_ERROR; + g_free(err_info_ignore); + } + } + } + return returnVal; +} + +BOOL is_event_filtered_out(PEVENT_RECORD ev) +{ + if (g_is_live_session) + { + return false; + } + + if (IsEqualGUID(&g_provider_filters[0].ProviderId, &ZeroGuid)) + { + return false; + } + + for (int i = 0; i < ARRAYSIZE(g_provider_filters); i++) + { + if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ev->EventHeader.ProviderId)) + { + return false; + } + if (IsEqualGUID(&g_provider_filters[i].ProviderId, &ZeroGuid)) + { + break; + } + } + + return true; +} + +static void WINAPI event_callback(PEVENT_RECORD ev) +{ + ULARGE_INTEGER timestamp; + g_num_events++; + + if (is_event_filtered_out(ev)) + { + return; + } + + /* + * 100ns since 1/1/1601 -> usec since 1/1/1970. + * The offset of 11644473600 seconds can be calculated with a couple of calls to SystemTimeToFileTime. + */ + timestamp.QuadPart = (ev->EventHeader.TimeStamp.QuadPart / 10) - 11644473600000000ll; + + /* Write OPN events that needs mbim sub dissector */ + if (IsEqualGUID(&ev->EventHeader.ProviderId, &mbb_provider)) + { + etw_dump_write_opn_event(ev, timestamp); + } + else if (IsEqualGUID(&ev->EventHeader.ProviderId, &ndis_capture_provider)) + { + etw_dump_write_ndiscap_event(ev, timestamp); + } + /* Write any event form other providers other than above */ + else + { + etw_dump_write_general_event(ev, timestamp); + } +} + +wtap_dumper* etw_dump_open(const char* pcapng_filename, int* err, char** err_info) +{ + wtap_dump_params params = { 0 }; + GArray* shb_hdrs = NULL; + wtap_block_t shb_hdr; + wtapng_iface_descriptions_t* idb_info; + GArray* idb_datas; + wtap_block_t idb_data; + wtapng_if_descr_mandatory_t* descr_mand; + + wtap_dumper* pdh = NULL; + + shb_hdrs = g_array_new(false, false, sizeof(wtap_block_t)); + shb_hdr = wtap_block_create(WTAP_BLOCK_SECTION); + g_array_append_val(shb_hdrs, shb_hdr); + + /* In the future, may create multiple WTAP_BLOCK_IF_ID_AND_INFO separately for IP packet */ + idb_info = g_new(wtapng_iface_descriptions_t, 1); + idb_datas = g_array_new(false, false, sizeof(wtap_block_t)); + idb_data = wtap_block_create(WTAP_BLOCK_IF_ID_AND_INFO); + descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(idb_data); + descr_mand->tsprecision = WTAP_TSPREC_USEC; + descr_mand->wtap_encap = WTAP_ENCAP_ETW; + /* Timestamp for each pcapng packet is usec units, so time_units_per_second need be set to 10^6 */ + descr_mand->time_units_per_second = G_USEC_PER_SEC; + g_array_append_val(idb_datas, idb_data); + idb_info->interface_data = idb_datas; + + params.encap = WTAP_ENCAP_ETW; + params.snaplen = 0; + params.tsprec = WTAP_TSPREC_USEC; + params.shb_hdrs = shb_hdrs; + params.idb_inf = idb_info; + + pdh = wtap_dump_open(pcapng_filename, wtap_pcapng_file_type_subtype(), WTAP_UNCOMPRESSED, ¶ms, err, err_info); + + if (shb_hdrs) + { + wtap_block_array_free(shb_hdrs); + } + if (params.idb_inf) + { + if (params.idb_inf->interface_data) + { + wtap_block_array_free(params.idb_inf->interface_data); + } + g_free(params.idb_inf); + params.idb_inf = NULL; + } + + return pdh; +} + +ULONG wtap_etl_record_buffer_init(WTAP_ETL_RECORD** out_etl_record, PEVENT_RECORD ev, BOOLEAN include_user_data, WCHAR* message, WCHAR* provider_name) +{ + ULONG total_packet_length = sizeof(WTAP_ETL_RECORD); + WTAP_ETL_RECORD* etl_record = NULL; + ULONG user_data_length = 0; + ULONG user_data_offset = 0; + ULONG message_offset = 0; + ULONG provider_name_offset = 0; + ULONG message_length = 0; + ULONG provider_name_length = 0; + + if (include_user_data) + { + if (ev->UserDataLength < MAX_PACKET_SIZE) + { + user_data_length = ev->UserDataLength; + } + else + { + user_data_length = MAX_PACKET_SIZE; + } + user_data_offset = sizeof(WTAP_ETL_RECORD); + total_packet_length += ROUND_UP_COUNT(user_data_length, sizeof(LONG)); + } + if (message && message[0] != L'\0') + { + message_offset = total_packet_length; + message_length = (ULONG)((wcslen(message) + 1) * sizeof(WCHAR)); + total_packet_length += ROUND_UP_COUNT(message_length, sizeof(LONG)); + } + if (provider_name && provider_name[0] != L'\0') + { + provider_name_offset = total_packet_length; + provider_name_length = (ULONG)((wcslen(provider_name) + 1) * sizeof(WCHAR)); + total_packet_length += ROUND_UP_COUNT(provider_name_length, sizeof(LONG)); + } + + etl_record = g_malloc(total_packet_length); + SecureZeroMemory(etl_record, total_packet_length); + etl_record->EventHeader = ev->EventHeader; + etl_record->BufferContext = ev->BufferContext; + etl_record->UserDataLength = user_data_length; + etl_record->MessageLength = message_length; + etl_record->ProviderLength = provider_name_length; + + if (user_data_offset) + { + memcpy(ADD_OFFSET_TO_POINTER(etl_record, user_data_offset), ev->UserData, user_data_length); + } + if (message_offset) + { + memcpy(ADD_OFFSET_TO_POINTER(etl_record, message_offset), message, message_length); + } + if (provider_name_offset) + { + memcpy(ADD_OFFSET_TO_POINTER(etl_record, provider_name_offset), provider_name, provider_name_length); + } + + *out_etl_record = etl_record; + return total_packet_length; +} + +void wtap_etl_add_interface(int pkt_encap, char* interface_name, unsigned short interface_name_length, char* interface_desc, unsigned short interface_desc_length) +{ + wtap_block_t idb_data; + wtapng_if_descr_mandatory_t* descr_mand; + char* err_info; + int err; + + idb_data = wtap_block_create(WTAP_BLOCK_IF_ID_AND_INFO); + descr_mand = (wtapng_if_descr_mandatory_t*)wtap_block_get_mandatory_data(idb_data); + descr_mand->wtap_encap = pkt_encap; + descr_mand->tsprecision = WTAP_TSPREC_USEC; + /* Timestamp for each pcapng packet is usec units, so time_units_per_second need be set to 10^6 */ + descr_mand->time_units_per_second = G_USEC_PER_SEC; + if (interface_name_length) { + wtap_block_add_string_option(idb_data, OPT_IDB_NAME, interface_name, interface_name_length); + } + if (interface_desc_length) { + wtap_block_add_string_option(idb_data, OPT_IDB_DESCRIPTION, interface_desc, interface_desc_length); + } + if(!wtap_dump_add_idb(g_pdh, idb_data, &err, &err_info)) { + g_err = err; + sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, %s", err_info); + g_free(err_info); + } +} + +void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length) +{ + char* err_info; + int err; + wtap_rec rec = { 0 }; + + wtap_rec_init(&rec); + rec.rec_header.packet_header.caplen = total_packet_length; + rec.rec_header.packet_header.len = original_packet_length; + rec.rec_header.packet_header.pkt_encap = pkt_encap; + rec.rec_header.packet_header.interface_id = interface_id; + rec.presence_flags = WTAP_HAS_INTERFACE_ID; + rec.block = wtap_block_create(WTAP_BLOCK_PACKET); + wtap_block_add_uint32_option(rec.block, OPT_PKT_FLAGS, is_inbound ? PACK_FLAGS_DIRECTION_INBOUND : PACK_FLAGS_DIRECTION_OUTBOUND); + if (comment_length) { + wtap_block_add_string_option(rec.block, OPT_COMMENT, comment, comment_length); + } + /* Convert usec of the timestamp into nstime_t */ + rec.ts.secs = (time_t)(timestamp.QuadPart / G_USEC_PER_SEC); + rec.ts.nsecs = (int)(((timestamp.QuadPart % G_USEC_PER_SEC) * G_NSEC_PER_SEC) / G_USEC_PER_SEC); + + /* and save the packet */ + if (!wtap_dump(g_pdh, &rec, (uint8_t*)etl_record, &err, &err_info)) { + g_err = err; + sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, %s", err_info); + g_free(err_info); + } + + /* Only flush when live session */ + if (g_is_live_session && !wtap_dump_flush(g_pdh, &err)) { + g_err = err; + sprintf_s(g_err_info, sizeof(g_err_info), "wtap_dump failed, 0x%x", err); + } + wtap_rec_cleanup(&rec); +} + +void etw_dump_write_opn_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp) +{ + WTAP_ETL_RECORD* etl_record = NULL; + ULONG total_packet_length = 0; + BOOLEAN is_inbound = false; + /* 0x80000000 mask the function to host message */ + is_inbound = ((*(INT32*)(ev->UserData)) & 0x80000000) ? true : false; + total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, true, NULL, NULL); + wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, is_inbound, timestamp, WTAP_ENCAP_ETW, NULL, 0); + g_free(etl_record); +} + +void etw_dump_write_event_head_only(PEVENT_RECORD ev, ULARGE_INTEGER timestamp) +{ + WTAP_ETL_RECORD* etl_record = NULL; + ULONG total_packet_length = 0; + total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, false, NULL, NULL); + wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, false, timestamp, WTAP_ENCAP_ETW, NULL, 0); + g_free(etl_record); +} + +void etw_dump_write_general_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp) +{ + PTRACE_EVENT_INFO pInfo = NULL; + PBYTE pUserData = NULL; + PBYTE pEndOfUserData = NULL; + DWORD PointerSize = 0; + PROPERTY_KEY_VALUE* prop_arr = NULL; + DWORD dwTopLevelPropertyCount = 0; + DWORD dwSizeofArray = 0; + WCHAR wszMessageBuffer[MAX_LOG_LINE_LENGTH] = { 0 }; + WCHAR formatMessage[MAX_LOG_LINE_LENGTH] = { 0 }; + + WTAP_ETL_RECORD* etl_record = NULL; + ULONG total_packet_length = 0; + BOOLEAN is_message_dumped = false; + + do + { + /* Skip EventTrace events */ + if (ev->EventHeader.Flags & EVENT_HEADER_FLAG_CLASSIC_HEADER && + IsEqualGUID(&ev->EventHeader.ProviderId, &EventTraceGuid)) + { + /* + * The first event in every ETL file contains the data from the file header. + * This is the same data as was returned in the EVENT_TRACE_LOGFILEW by + * OpenTrace. Since we've already seen this information, we'll skip this + * event. + */ + break; + } + + /* Skip events injected by the XPerf tracemerger - they will never be decodable */ + if (IsEqualGUID(&ev->EventHeader.ProviderId, &ImageIdGuid) || + IsEqualGUID(&ev->EventHeader.ProviderId, &SystemConfigExGuid) || + IsEqualGUID(&ev->EventHeader.ProviderId, &EventMetadataGuid)) + { + break; + } + + if (!get_event_information(ev, &pInfo)) + { + break; + } + + /* Skip those events without format message since most of them need special logic to decode like NDIS-PackCapture */ + if (pInfo->EventMessageOffset <= 0) + { + break; + } + + if (EVENT_HEADER_FLAG_32_BIT_HEADER == (ev->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER)) + { + PointerSize = 4; + } + else + { + PointerSize = 8; + } + + pUserData = (PBYTE)ev->UserData; + pEndOfUserData = (PBYTE)ev->UserData + ev->UserDataLength; + + dwTopLevelPropertyCount = pInfo->TopLevelPropertyCount; + if (dwTopLevelPropertyCount > 0) + { + prop_arr = g_malloc(sizeof(PROPERTY_KEY_VALUE) * dwTopLevelPropertyCount); + dwSizeofArray = dwTopLevelPropertyCount * sizeof(PROPERTY_KEY_VALUE); + SecureZeroMemory(prop_arr, dwSizeofArray); + } + + StringCbCopy(formatMessage, MAX_LOG_LINE_LENGTH, (LPWSTR)ADD_OFFSET_TO_POINTER(pInfo, pInfo->EventMessageOffset)); + + for (USHORT i = 0; i < dwTopLevelPropertyCount; i++) + { + pUserData = extract_properties(ev, pInfo, PointerSize, i, pUserData, pEndOfUserData, &prop_arr[i]); + if (NULL == pUserData) + { + break; + } + } + + format_message(formatMessage, prop_arr, dwTopLevelPropertyCount, wszMessageBuffer, sizeof(wszMessageBuffer)); + + total_packet_length = wtap_etl_record_buffer_init(&etl_record, ev, false, wszMessageBuffer, (WCHAR*)ADD_OFFSET_TO_POINTER(pInfo, pInfo->ProviderNameOffset)); + wtap_etl_rec_dump((char*)etl_record, total_packet_length, total_packet_length, 0, false, timestamp, WTAP_ENCAP_ETW, NULL, 0); + g_free(etl_record); + + is_message_dumped = true; + } while (false); + + if (NULL != prop_arr) + { + g_free(prop_arr); + prop_arr = NULL; + } + if (NULL != pInfo) + { + g_free(pInfo); + pInfo = NULL; + } + + if (!is_message_dumped && g_include_undecidable_event) + { + etw_dump_write_event_head_only(ev, timestamp); + } +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/etl.h b/extcap/etl.h new file mode 100644 index 00000000..9480d248 --- /dev/null +++ b/extcap/etl.h @@ -0,0 +1,48 @@ +/** @file + * + * Copyright 2020, Odysseus Yang + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __W_ETL_H__ +#define __W_ETL_H__ + +#include "wiretap/wtap.h" +#include "ws_symbol_export.h" +#include "wiretap/wtap-int.h" + +#include <glib.h> +#include <stdlib.h> +#include <tdh.h> +#include <guiddef.h> + +#define LOGGER_NAME L"wireshark etwdump" + +typedef struct +{ + EVENT_TRACE_PROPERTIES prop; + char padding[64]; +} SUPER_EVENT_TRACE_PROPERTIES; + +wtap_open_return_val etw_dump(const char* etl_filename, const char* pcapng_filename, const char* params, int* err, char** err_info); + +#endif + + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/etw_message.c b/extcap/etw_message.c new file mode 100644 index 00000000..c99eaea8 --- /dev/null +++ b/extcap/etw_message.c @@ -0,0 +1,419 @@ +/* etw_message.h + * + * Copyright 2020, Odysseus Yang + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "config.h" +#define WS_LOG_DOMAIN "etwdump" + +#include "etw_message.h" +#include <wsutil/wslog.h> +ULONGLONG g_num_events = 0; + +VOID format_message(WCHAR* lpszMessage, PROPERTY_KEY_VALUE* propArray, DWORD dwPropertyCount, WCHAR* lpszOutBuffer, DWORD dwOutBufferCount) +{ + DWORD startLoc = 0; + int percent_loc = 0; + + for (int i = 0; lpszMessage[i] != L'\0';) + { + if (lpszMessage[i] != L'%') + { + i++; + continue; + } + if (lpszMessage[i + 1] == '%') + { + i += 2; + continue; + } + + percent_loc = i; + i++; + + if (iswdigit(lpszMessage[i])) + { + DWORD dwDigitalCount = 0; + WCHAR smallBuffer[MAX_SMALL_BUFFER] = { 0 }; + while (iswdigit(lpszMessage[i])) + { + if (dwDigitalCount < (MAX_SMALL_BUFFER - 1)) + { + smallBuffer[dwDigitalCount] = lpszMessage[i]; + } + dwDigitalCount++; + i++; + } + + /* We are not parsing this */ + if (dwDigitalCount >= (MAX_SMALL_BUFFER - 1)) + { + continue; + } + DWORD num = _wtoi(smallBuffer); + /* We are not parsing this */ + if (num == 0 || num > dwPropertyCount || propArray[num - 1].value[0] == L'\0') + { + continue; + } + + if (lpszMessage[i] == L'!' && lpszMessage[i + 1] == L'S' && lpszMessage[i + 2] == L'!') + { + i += 3; + } + + /* We have everything */ + lpszMessage[percent_loc] = L'\0'; + StringCbCat(lpszOutBuffer, dwOutBufferCount, lpszMessage + startLoc); + StringCbCat(lpszOutBuffer, dwOutBufferCount, propArray[num - 1].value); + startLoc = i; + continue; // for + } + } + StringCbCat(lpszOutBuffer, dwOutBufferCount, lpszMessage + startLoc); +} + +/* +* Get the length of the property data. For MOF-based events, the size is inferred from the data type +* of the property. For manifest-based events, the property can specify the size of the property value +* using the length attribute. The length attribue can specify the size directly or specify the name +* of another property in the event data that contains the size. If the property does not include the +* length attribute, the size is inferred from the data type. The length will be zero for variable +* length, null-terminated strings and structures. +*/ +DWORD GetPropertyLength(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT PropertyLength) +{ + DWORD status = ERROR_SUCCESS; + PROPERTY_DATA_DESCRIPTOR DataDescriptor = { 0 }; + DWORD PropertySize = 0; + + /* + * If the property is a binary blob and is defined in a manifest, the property can + * specify the blob's size or it can point to another property that defines the + * blob's size. The PropertyParamLength flag tells you where the blob's size is defined. + */ + if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyParamLength) == PropertyParamLength) + { + DWORD Length = 0; // Expects the length to be defined by a UINT16 or UINT32 + DWORD j = pInfo->EventPropertyInfoArray[i].lengthPropertyIndex; + DataDescriptor.PropertyName = ((ULONGLONG)(pInfo)+(ULONGLONG)pInfo->EventPropertyInfoArray[j].NameOffset); + DataDescriptor.ArrayIndex = ULONG_MAX; + status = TdhGetPropertySize(pEvent, 0, NULL, 1, &DataDescriptor, &PropertySize); + status = TdhGetProperty(pEvent, 0, NULL, 1, &DataDescriptor, PropertySize, (PBYTE)&Length); + *PropertyLength = (USHORT)Length; + } + else + { + if (pInfo->EventPropertyInfoArray[i].length > 0) + { + *PropertyLength = pInfo->EventPropertyInfoArray[i].length; + } + else + { + /* + * If the property is a binary blob and is defined in a MOF class, the extension + * qualifier is used to determine the size of the blob. However, if the extension + * is IPAddrV6, you must set the PropertyLength variable yourself because the + * EVENT_PROPERTY_INFO.length field will be zero. + */ + if (TDH_INTYPE_BINARY == pInfo->EventPropertyInfoArray[i].nonStructType.InType && + TDH_OUTTYPE_IPV6 == pInfo->EventPropertyInfoArray[i].nonStructType.OutType) + { + *PropertyLength = (USHORT)sizeof(IN6_ADDR); + } + else if (TDH_INTYPE_UNICODESTRING == pInfo->EventPropertyInfoArray[i].nonStructType.InType || + TDH_INTYPE_ANSISTRING == pInfo->EventPropertyInfoArray[i].nonStructType.InType || + (pInfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct) + { + *PropertyLength = pInfo->EventPropertyInfoArray[i].length; + } + else + { + ws_debug("Event %d Unexpected length of 0 for intype %d and outtype %d", g_num_events, + pInfo->EventPropertyInfoArray[i].nonStructType.InType, + pInfo->EventPropertyInfoArray[i].nonStructType.OutType); + + status = ERROR_EVT_INVALID_EVENT_DATA; + goto cleanup; + } + } + } +cleanup: + return status; +} + +DWORD GetArraySize(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, USHORT i, PUSHORT ArraySize) +{ + DWORD status = ERROR_SUCCESS; + PROPERTY_DATA_DESCRIPTOR DataDescriptor = { 0 }; + DWORD PropertySize = 0; + + if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyParamCount) == PropertyParamCount) + { + /* Expects the count to be defined by a UINT16 or UINT32 */ + DWORD Count = 0; + DWORD j = pInfo->EventPropertyInfoArray[i].countPropertyIndex; + DataDescriptor.PropertyName = ((ULONGLONG)(pInfo)+(ULONGLONG)(pInfo->EventPropertyInfoArray[j].NameOffset)); + DataDescriptor.ArrayIndex = ULONG_MAX; + status = TdhGetPropertySize(pEvent, 0, NULL, 1, &DataDescriptor, &PropertySize); + status = TdhGetProperty(pEvent, 0, NULL, 1, &DataDescriptor, PropertySize, (PBYTE)&Count); + *ArraySize = (USHORT)Count; + } + else + { + *ArraySize = pInfo->EventPropertyInfoArray[i].count; + } + return status; +} + +DWORD GetMapInfo(PEVENT_RECORD pEvent, LPWSTR pMapName, PEVENT_MAP_INFO* pMapInfo) +{ + DWORD status = ERROR_SUCCESS; + DWORD MapSize = 0; + + /* Retrieve the required buffer size for the map info. */ + status = TdhGetEventMapInformation(pEvent, pMapName, *pMapInfo, &MapSize); + if (ERROR_INSUFFICIENT_BUFFER == status) + { + *pMapInfo = (PEVENT_MAP_INFO)g_malloc(MapSize); + if (*pMapInfo == NULL) + { + status = ERROR_OUTOFMEMORY; + goto cleanup; + } + /* Retrieve the map info. */ + status = TdhGetEventMapInformation(pEvent, pMapName, *pMapInfo, &MapSize); + } + + if (ERROR_NOT_FOUND == status) + { + /* This case is okay. */ + status = ERROR_SUCCESS; + } + +cleanup: + + return status; +} + + +PBYTE extract_properties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, DWORD PointerSize, USHORT i, PBYTE pUserData, PBYTE pEndOfUserData, PROPERTY_KEY_VALUE* pExtract) +{ + TDHSTATUS status = ERROR_SUCCESS; + USHORT PropertyLength = 0; + USHORT UserDataConsumed = 0; + /* Last member of a structure */ + DWORD LastMember = 0; + USHORT ArraySize = 0; + PEVENT_MAP_INFO pMapInfo = NULL; + WCHAR formatted_data[MAX_LOG_LINE_LENGTH]; + DWORD formatted_data_size = sizeof(formatted_data); + LPWSTR oversize_formatted_data = NULL; + + do + { + StringCbCopy(pExtract->key, sizeof(pExtract->key), (PWCHAR)((PBYTE)(pInfo)+pInfo->EventPropertyInfoArray[i].NameOffset)); + /* Get the length of the property. */ + status = GetPropertyLength(pEvent, pInfo, i, &PropertyLength); + if (ERROR_SUCCESS != status) + { + StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: GetPropertyLength failed 0x%x", pExtract->key, status); + break; + } + + /* Get the size of the array if the property is an array. */ + status = GetArraySize(pEvent, pInfo, i, &ArraySize); + if (ERROR_SUCCESS != status) + { + StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: GetArraySize failed 0x%x", pExtract->key, status); + break; + } + + /* Add [] for an array property */ + if (ArraySize > 1) + { + StringCbCat(pExtract->value, sizeof(pExtract->value), L"["); + } + + for (USHORT k = 0; k < ArraySize; k++) + { + /* Add array item separator "," */ + if (k > 0) + { + StringCbCat(pExtract->value, sizeof(pExtract->value), L","); + } + /* If the property is a structure, print the members of the structure. */ + if ((pInfo->EventPropertyInfoArray[i].Flags & PropertyStruct) == PropertyStruct) + { + /* Add {} for an array property */ + StringCbCat(pExtract->value, sizeof(pExtract->value), L"{"); + /* Add struct member separator ";" */ + if (k > 0) + { + StringCbCat(pExtract->value, sizeof(pExtract->value), L";"); + } + LastMember = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex + + pInfo->EventPropertyInfoArray[i].structType.NumOfStructMembers; + + for (USHORT j = pInfo->EventPropertyInfoArray[i].structType.StructStartIndex; j < LastMember; j++) + { + pUserData = extract_properties(pEvent, pInfo, PointerSize, j, pUserData, pEndOfUserData, pExtract); + if (NULL == pUserData) + { + StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: extract_properties of member %d failed 0x%x", pExtract->key, j, status); + break; + } + } + StringCbCat(pExtract->value, sizeof(pExtract->value), L"}"); + } + else + { + /* Get the name/value mapping only at the first time if the property specifies a value map. */ + if (pMapInfo == NULL) + { + status = GetMapInfo(pEvent, + (PWCHAR)((PBYTE)(pInfo)+pInfo->EventPropertyInfoArray[i].nonStructType.MapNameOffset), + &pMapInfo); + + if (ERROR_SUCCESS != status) + { + StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: GetMapInfo failed 0x%x", pExtract->key, status); + break; + } + } + + /* Get the size of the buffer required for the formatted data. */ + + status = TdhFormatProperty( + pInfo, + pMapInfo, + PointerSize, + pInfo->EventPropertyInfoArray[i].nonStructType.InType, + pInfo->EventPropertyInfoArray[i].nonStructType.OutType, + PropertyLength, + (USHORT)(pEndOfUserData - pUserData), + pUserData, + &formatted_data_size, + formatted_data, + &UserDataConsumed); + + if (ERROR_INSUFFICIENT_BUFFER == status) + { + if (oversize_formatted_data) + { + g_free(oversize_formatted_data); + oversize_formatted_data = NULL; + } + + oversize_formatted_data = (LPWSTR)g_malloc(formatted_data_size); + if (oversize_formatted_data == NULL) + { + status = ERROR_OUTOFMEMORY; + StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: Allocate FormattedData memory (size %d) for array item %d failed 0x%x", pExtract->key, formatted_data_size, k, status); + break; + } + + /* Retrieve the formatted data. */ + status = TdhFormatProperty( + pInfo, + pMapInfo, + PointerSize, + pInfo->EventPropertyInfoArray[i].nonStructType.InType, + pInfo->EventPropertyInfoArray[i].nonStructType.OutType, + PropertyLength, + (USHORT)(pEndOfUserData - pUserData), + pUserData, + &formatted_data_size, + oversize_formatted_data, + &UserDataConsumed); + } + + if (ERROR_SUCCESS == status) + { + if (formatted_data_size > sizeof(formatted_data) && oversize_formatted_data != NULL) + { + /* Any oversize FormattedData will be truncated */ + StringCbCat(pExtract->value, sizeof(pExtract->value), oversize_formatted_data); + } + else + { + StringCbCat(pExtract->value, sizeof(pExtract->value), formatted_data); + } + pUserData += UserDataConsumed; + } + else + { + StringCbPrintf(pExtract->value, sizeof(pExtract->value), L"%s: TdhFormatProperty for array item %d failed 0x%x", pExtract->key, k, status); + break; + } + } + } + /* Add [] for an array property */ + if (ArraySize > 1) + { + StringCbCat(pExtract->value, sizeof(pExtract->value), L"]"); + } + } while (false); + + if (oversize_formatted_data) + { + g_free(oversize_formatted_data); + oversize_formatted_data = NULL; + } + if (pMapInfo) + { + g_free(pMapInfo); + pMapInfo = NULL; + } + + return (ERROR_SUCCESS == status) ? pUserData : NULL; +} + + +BOOL get_event_information(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO* pInfo) +{ + BOOL bReturn = false; + DWORD status; + DWORD BufferSize = 0; + + /* Retrieve the required buffer size for the event metadata. */ + status = TdhGetEventInformation(pEvent, 0, NULL, *pInfo, &BufferSize); + if (ERROR_INSUFFICIENT_BUFFER == status) + { + *pInfo = (TRACE_EVENT_INFO*)g_malloc(BufferSize); + if (*pInfo == NULL) + { + ws_debug("Event %d GetEventInformation Failed to allocate memory for event info (size=%lu).", g_num_events, BufferSize); + goto Exit; + } + /* Retrieve the event metadata. */ + status = TdhGetEventInformation(pEvent, 0, NULL, *pInfo, &BufferSize); + } + + if (ERROR_SUCCESS != status) + { + goto Exit; + } + bReturn = true; +Exit: + + return bReturn; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/etw_message.h b/extcap/etw_message.h new file mode 100644 index 00000000..017849a9 --- /dev/null +++ b/extcap/etw_message.h @@ -0,0 +1,59 @@ +/** @file + * + * Copyright 2020, Odysseus Yang + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __W_ETW_MESSAGE_H__ +#define __W_ETW_MESSAGE_H__ + +#include <glib.h> + +#include <windows.h> +#include <SDKDDKVer.h> +#include <strsafe.h> +#include <evntcons.h> +#include <tdh.h> +#include <stdlib.h> + +#define MAX_SMALL_BUFFER 4 +#define MAX_LOG_LINE_LENGTH 1024 +#define MAX_KEY_LENGTH 64 + +typedef struct Property_Key_Value +{ + WCHAR key[MAX_KEY_LENGTH]; + WCHAR value[MAX_LOG_LINE_LENGTH]; +} PROPERTY_KEY_VALUE; + +typedef struct in6_addr { + union { + UCHAR Byte[16]; + USHORT Word[8]; + } u; +} IN6_ADDR, * PIN6_ADDR, FAR* LPIN6_ADDR; + +VOID format_message(WCHAR* lpszMessage, PROPERTY_KEY_VALUE* propArray, DWORD dwPropertyCount, WCHAR* lpszOutBuffer, DWORD dwOutBufferCount); +BOOL get_event_information(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO* pInfo); +PBYTE extract_properties(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, DWORD PointerSize, USHORT i, PBYTE pUserData, PBYTE pEndOfUserData, PROPERTY_KEY_VALUE* pExtract); + +#endif + + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/etw_ndiscap.c b/extcap/etw_ndiscap.c new file mode 100644 index 00000000..d7aab65d --- /dev/null +++ b/extcap/etw_ndiscap.c @@ -0,0 +1,709 @@ +/* etw_ndiscap.c + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* +* Reads IP packets from an Windows event trace logfile or an Windows event trace live session +* and write out a pcap file with LINKTYPE_ETHERNET, LINKTYPE_RAW or LINKTYPE_IEEE802_11. +* The major code of this file is from https://github.com/microsoft/etl2pcapng with some changes by Odysseus Yang. +* The changes mainly include but not limited +* 1. calling pcapng APIs instead of writing the data in the pcapng binary format by its own implementation in etl2pcapng. +* 2. Optimize the process of adding pcapng interfaces so it doesn't need process the same Windows event trace logfile twice, + that not only impacts the performance, but also breaks Wireshark live capture function. +*/ + +#define WIN32_LEAN_AND_MEAN 1 +#include <windows.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <evntrace.h> +#include <evntcons.h> +#include <tdh.h> +#include <strsafe.h> +#include <winsock2.h> +#include <netiodef.h> + +// inet_ipv6.h and netiodef.h define exactly the same stuff, like _IPV6_ROUTING_HEADER and IP6F_OFF_MASK. +// So wiretap/wtap.h cannot be directly included in this file. Defines below three WTAP_ENCAP types with the value in wtap.h for compile +#define WTAP_ENCAP_ETHERNET 1 +#define WTAP_ENCAP_RAW_IP 7 +#define WTAP_ENCAP_IEEE_802_11 20 + +#define MAX_PACKET_SIZE 0xFFFF + +// From the ndiscap manifest +#define KW_MEDIA_WIRELESS_WAN 0x200 +#define KW_MEDIA_NATIVE_802_11 0x10000 +#define KW_PACKET_START 0x40000000 +#define KW_PACKET_END 0x80000000 +#define KW_SEND 0x100000000 +#define KW_RECEIVE 0x200000000 + +#define tidPacketFragment 1001 +#define tidPacketMetadata 1002 +#define tidVMSwitchPacketFragment 1003 + +// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ns-windot11-dot11_extsta_recv_context +#pragma pack(push,8) +typedef struct _NDIS_OBJECT_HEADER { + unsigned char Type; + unsigned char Revision; + unsigned short Size; +} NDIS_OBJECT_HEADER, * PNDIS_OBJECT_HEADER; + +typedef struct DOT11_EXTSTA_RECV_CONTEXT { + NDIS_OBJECT_HEADER Header; + unsigned long uReceiveFlags; + unsigned long uPhyId; + unsigned long uChCenterFrequency; + unsigned short usNumberOfMPDUsReceived; + long lRSSI; + unsigned char ucDataRate; + unsigned long uSizeMediaSpecificInfo; + void *pvMediaSpecificInfo; + unsigned long long ullTimestamp; +} DOT11_EXTSTA_RECV_CONTEXT, * PDOT11_EXTSTA_RECV_CONTEXT; +#pragma pack(pop) + +// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/windot11/ne-windot11-_dot11_phy_type +#define DOT11_PHY_TYPE_NAMES_MAX 10 +static const char* DOT11_PHY_TYPE_NAMES[] = { + "Unknown", // dot11_phy_type_unknown = 0 + "Fhss", // dot11_phy_type_fhss = 1 + "Dsss", // dot11_phy_type_dsss = 2 + "IrBaseband", // dot11_phy_type_irbaseband = 3 + "802.11a", // dot11_phy_type_ofdm = 4 + "802.11b", // dot11_phy_type_hrdsss = 5 + "802.11g", // dot11_phy_type_erp = 6 + "802.11n", // dot11_phy_type_ht = 7 + "802.11ac", // dot11_phy_type_vht = 8 + "802.11ad", // dot11_phy_type_dmg = 9 + "802.11ax" // dot11_phy_type_he = 10 +}; + +unsigned long long NumFramesConverted = 0; +char AuxFragBuf[MAX_PACKET_SIZE] = {0}; +unsigned long AuxFragBufOffset = 0; + +DOT11_EXTSTA_RECV_CONTEXT PacketMetadata; +BOOLEAN AddWlanMetadata = false; + +typedef struct _NDIS_NET_BUFFER_LIST_8021Q_INFO { + union { + struct { + UINT32 UserPriority : 3; // 802.1p priority + UINT32 CanonicalFormatId : 1; // always 0 + UINT32 VlanId : 12; // VLAN Identification + UINT32 Reserved : 16; // set to 0 for ethernet + } TagHeader; + + struct { + UINT32 UserPriority : 3; // 802.1p priority + UINT32 CanonicalFormatId : 1; // always 0 + UINT32 VlanId : 12; // VLAN Identification + UINT32 WMMInfo : 4; + UINT32 Reserved : 12; // set to 0 for Wireless LAN + } WLanTagHeader; + + PVOID Value; + }; +} NDIS_NET_BUFFER_LIST_8021Q_INFO, *PNDIS_NET_BUFFER_LIST_8021Q_INFO; + +// The max OOB data size might increase in the future. If it becomes larger than MaxNetBufferListInfo, +// this tool will print a warning and the value of MaxNetBufferListInfo in the code should be increased. +// From: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/nblinfo/ne-nblinfo-ndis_net_buffer_list_info +#define MaxNetBufferListInfo 200 +#define Ieee8021QNetBufferListInfo 4 +PBYTE OobData[MaxNetBufferListInfo]; + +typedef struct _VMSWITCH_SOURCE_INFO { + unsigned long SourcePortId; + char* SourcePortName; + char* SourceNicName; + char* SourceNicType; +} VMSWITCH_SOURCE_INFO, *PVMSWITCH_SOURCE_INFO; + +typedef struct _VMSWITCH_PACKET_FRAGMENT { + unsigned long SourcePortId; + unsigned long DestinationCount; + short VlanId; +} VMSWITCH_PACKET_FRAGMENT, *PVMSWITCH_PACKET_FRAGMENT; + +BOOLEAN CurrentPacketIsVMSwitchPacketFragment = false; +VMSWITCH_PACKET_FRAGMENT VMSwitchPacketFragment; + +struct INTERFACE { + struct INTERFACE* Next; + unsigned long LowerIfIndex; + unsigned long MiniportIfIndex; + unsigned long PcapNgIfIndex; + int PktEncapType; + short VlanId; + + BOOLEAN IsVMNic; + VMSWITCH_SOURCE_INFO VMNic; +}; + +#define IFACE_HT_SIZE 100 +struct INTERFACE* InterfaceHashTable[IFACE_HT_SIZE] = {0}; +unsigned long NumInterfaces = 0; + +void wtap_etl_rec_dump(char* etl_record, ULONG total_packet_length, ULONG original_packet_length, unsigned int interface_id, BOOLEAN is_inbound, ULARGE_INTEGER timestamp, int pkt_encap, char* comment, unsigned short comment_length); +void wtap_etl_add_interface(int pkt_encap, char* interface_name, unsigned short interface_name_length, char* interface_desc, unsigned short interface_desc_length); + +extern char g_err_info[FILENAME_MAX]; +extern int g_err; + +unsigned long HashInterface(unsigned long LowerIfIndex) +{ + if (CurrentPacketIsVMSwitchPacketFragment) { + return VMSwitchPacketFragment.SourcePortId * (VMSwitchPacketFragment.VlanId + 1); + } else { + return LowerIfIndex; + } +} + +struct INTERFACE* GetInterface(unsigned long LowerIfIndex) +{ + struct INTERFACE* Iface = InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE]; + while (Iface != NULL) { + if (CurrentPacketIsVMSwitchPacketFragment) { + if (Iface->IsVMNic && + Iface->LowerIfIndex == LowerIfIndex && + Iface->VlanId == VMSwitchPacketFragment.VlanId && + Iface->VMNic.SourcePortId == VMSwitchPacketFragment.SourcePortId) { + return Iface; + } + } else { + if (!Iface->IsVMNic && Iface->LowerIfIndex == LowerIfIndex && Iface->VlanId == 0) { + return Iface; + } + } + Iface = Iface->Next; + } + return NULL; +} + +struct INTERFACE* AddInterface(PEVENT_RECORD ev, unsigned long LowerIfIndex, unsigned long MiniportIfIndex, int Type) +{ + struct INTERFACE** Iface = &InterfaceHashTable[HashInterface(LowerIfIndex) % IFACE_HT_SIZE]; + struct INTERFACE* NewIface = malloc(sizeof(struct INTERFACE)); + +#define IF_STRING_MAX_SIZE 128 + char IfName[IF_STRING_MAX_SIZE]; + size_t IfNameLength = 0; + char IfDesc[IF_STRING_MAX_SIZE]; + size_t IfDescLength = 0; + //etw pcagng interface will be 0 always, network pcagng interface will start with 1 + static PcapNgIfIndex = 1; + + if (NewIface == NULL) { + g_err = ERROR_OUTOFMEMORY; + sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface"); + exit(1); + } + + NewIface->LowerIfIndex = LowerIfIndex; + NewIface->MiniportIfIndex = MiniportIfIndex; + NewIface->PktEncapType = Type; + NewIface->VlanId = 0; + NewIface->IsVMNic = false; + + if (CurrentPacketIsVMSwitchPacketFragment) { + + NewIface->IsVMNic = true; + + wchar_t Buffer[8192]; + PROPERTY_DATA_DESCRIPTOR Desc; + int Err; + + // SourceNicName + Desc.PropertyName = (unsigned long long)(L"SourceNicName"); + Desc.ArrayIndex = ULONG_MAX; + ULONG ParamNameSize = 0; + (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize); + NewIface->VMNic.SourceNicName = malloc((ParamNameSize / sizeof(wchar_t)) + 1); + if (NewIface->VMNic.SourceNicName == NULL) { + g_err = ERROR_OUTOFMEMORY; + sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourceNicName"); + exit(1); + } + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer); + if (Err != NO_ERROR) { + Buffer[0] = L'\0'; + } + Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0'; + WideCharToMultiByte(CP_ACP, + 0, + Buffer, + -1, + NewIface->VMNic.SourceNicName, + ParamNameSize / sizeof(wchar_t) + 1, + NULL, + NULL); + NewIface->VMNic.SourceNicName[wcslen(Buffer)] = '\0'; + + // SourcePortName + Desc.PropertyName = (unsigned long long)(L"SourcePortName"); + Desc.ArrayIndex = ULONG_MAX; + (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize); + NewIface->VMNic.SourcePortName = malloc((ParamNameSize / sizeof(wchar_t)) + 1); + if (NewIface->VMNic.SourcePortName == NULL) { + g_err = ERROR_OUTOFMEMORY; + sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourcePortName"); + exit(1); + } + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer); + if (Err != NO_ERROR) { + Buffer[0] = L'\0'; + } + Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0'; + WideCharToMultiByte(CP_ACP, + 0, + Buffer, + -1, + NewIface->VMNic.SourcePortName, + ParamNameSize / sizeof(wchar_t) + 1, + NULL, + NULL); + NewIface->VMNic.SourcePortName[wcslen(Buffer)] = '\0'; + + // SourceNicType + Desc.PropertyName = (unsigned long long)(L"SourceNicType"); + Desc.ArrayIndex = ULONG_MAX; + (void)TdhGetPropertySize(ev, 0, NULL, 1, &Desc, &ParamNameSize); + NewIface->VMNic.SourceNicType = malloc((ParamNameSize / sizeof(wchar_t)) + 1); + if (NewIface->VMNic.SourceNicType == NULL) { + g_err = ERROR_OUTOFMEMORY; + sprintf_s(g_err_info, sizeof(g_err_info), "malloc failed to allocate memory for NewIface->VMNic.SourceNicType"); + exit(1); + } + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(Buffer), (PBYTE)Buffer); + if (Err != NO_ERROR) { + Buffer[0] = L'\0'; + } + Buffer[ParamNameSize / sizeof(wchar_t) + 1] = L'\0'; + WideCharToMultiByte(CP_ACP, + 0, + Buffer, + -1, + NewIface->VMNic.SourceNicType, + ParamNameSize / sizeof(wchar_t) + 1, + NULL, + NULL); + NewIface->VMNic.SourceNicType[wcslen(Buffer)] = '\0'; + + + NewIface->VMNic.SourcePortId = VMSwitchPacketFragment.SourcePortId; + NewIface->VlanId = VMSwitchPacketFragment.VlanId; + } + + NewIface->Next = *Iface; + + *Iface = NewIface; + NumInterfaces++; + + NewIface->PcapNgIfIndex = PcapNgIfIndex; + PcapNgIfIndex++; + memset(IfName, 0, sizeof(IfName)); + memset(IfDesc, 0, sizeof(IfDesc)); + switch (NewIface->PktEncapType) { + case WTAP_ENCAP_ETHERNET: + if (NewIface->IsVMNic) { + printf("IF: medium=%s\tID=%u\tIfIndex=%u\tVlanID=%i", + NewIface->VMNic.SourceNicType, + NewIface->PcapNgIfIndex, + NewIface->VMNic.SourcePortId, + NewIface->VlanId + ); + StringCchPrintfA( + IfName, + IF_STRING_MAX_SIZE, + "%s:%s:%lu:%i", + NewIface->VMNic.SourcePortName, + NewIface->VMNic.SourceNicType, + NewIface->VMNic.SourcePortId, + NewIface->VlanId + ); + } + else { + printf("IF: medium=eth\tID=%u\tIfIndex=%u\tVlanID=%i", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex, NewIface->VlanId); + StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "eth:%lu:%i", NewIface->LowerIfIndex, NewIface->VlanId); + } + break; + case WTAP_ENCAP_IEEE_802_11: + printf("IF: medium=wifi ID=%u\tIfIndex=%u", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex); + StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "wifi:%lu", NewIface->LowerIfIndex); + break; + case WTAP_ENCAP_RAW_IP: + printf("IF: medium=mbb ID=%u\tIfIndex=%u", NewIface->PcapNgIfIndex, NewIface->LowerIfIndex); + StringCchPrintfA(IfName, IF_STRING_MAX_SIZE, "mbb:%lu", NewIface->LowerIfIndex); + break; + } + StringCchLengthA(IfName, IF_STRING_MAX_SIZE, &IfNameLength); + + if (NewIface->LowerIfIndex != NewIface->MiniportIfIndex) { + printf("\t(LWF over IfIndex %u)", NewIface->MiniportIfIndex); + StringCchPrintfA(IfDesc, IF_STRING_MAX_SIZE, "LWF over IfIndex %lu", NewIface->MiniportIfIndex); + StringCchLengthA(IfDesc, IF_STRING_MAX_SIZE, &IfDescLength); + } + + if (NewIface->VlanId != 0) { + StringCchPrintfA(IfDesc + IfDescLength, IF_STRING_MAX_SIZE, " VlanID=%i ", NewIface->VlanId); + StringCchLengthA(IfDesc, IF_STRING_MAX_SIZE, &IfDescLength); + } + + printf("\n"); + + wtap_etl_add_interface(NewIface->PktEncapType, IfName, (unsigned short)IfNameLength, IfDesc, (unsigned short)IfDescLength); + return NewIface; +} + +void ParseVmSwitchPacketFragment(PEVENT_RECORD ev) +{ + // Parse the current VMSwitch packet event for use elsewhere. + // NB: Here we only do per-packet parsing. For any event fields that only need to be + // parsed once and written into an INTERFACE, we do the parsing in AddInterface. + + PROPERTY_DATA_DESCRIPTOR Desc; + int Err; + PNDIS_NET_BUFFER_LIST_8021Q_INFO pNblVlanInfo; + + // Get VLAN from OOB + unsigned long OobLength; + Desc.PropertyName = (unsigned long long)L"OOBDataSize"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(OobLength), (PBYTE)&OobLength); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty OobLength failed, err is 0x%x", Err); + return; + } + + if (OobLength > sizeof(OobData)) { + g_err = ERROR_INVALID_DATA; + sprintf_s(g_err_info, sizeof(g_err_info), "OOB data of %lu bytes too large to fit in hardcoded buffer of size %lu", OobLength, (unsigned long)sizeof(OobData)); + return; + } + + Desc.PropertyName = (unsigned long long)L"OOBData"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, OobLength, (PBYTE)&OobData); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty OobData failed, err is 0x%x", Err); + return; + } + + pNblVlanInfo = (PNDIS_NET_BUFFER_LIST_8021Q_INFO)&OobData[Ieee8021QNetBufferListInfo]; + VMSwitchPacketFragment.VlanId = pNblVlanInfo->TagHeader.VlanId; + + // SourcePortId + Desc.PropertyName = (unsigned long long)L"SourcePortId"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(VMSwitchPacketFragment.SourcePortId), (PBYTE)&VMSwitchPacketFragment.SourcePortId); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty SourcePortId failed, err is 0x%x", Err); + return; + } + + // DestinationCount + Desc.PropertyName = (unsigned long long)L"DestinationCount"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(VMSwitchPacketFragment.DestinationCount), (PBYTE)&VMSwitchPacketFragment.DestinationCount); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty DestinationCount failed, err is 0x%x", Err); + return; + } +} + +void etw_dump_write_ndiscap_event(PEVENT_RECORD ev, ULARGE_INTEGER timestamp) +{ + int Err; + unsigned long LowerIfIndex; + + struct INTERFACE* Iface; + unsigned long FragLength; + PROPERTY_DATA_DESCRIPTOR Desc; + int Type; + unsigned long TotalFragmentLength; + unsigned long InferredOriginalFragmentLength = 0; + PETHERNET_HEADER EthHdr; + PIPV4_HEADER Ipv4Hdr; + PIPV6_HEADER Ipv6Hdr; + + if ((ev->EventHeader.EventDescriptor.Id != tidPacketFragment && + ev->EventHeader.EventDescriptor.Id != tidPacketMetadata && + ev->EventHeader.EventDescriptor.Id != tidVMSwitchPacketFragment)) { + return; + } + + CurrentPacketIsVMSwitchPacketFragment = (ev->EventHeader.EventDescriptor.Id == tidVMSwitchPacketFragment); + if (CurrentPacketIsVMSwitchPacketFragment) { + ParseVmSwitchPacketFragment(ev); + } + + Desc.PropertyName = (unsigned long long)L"LowerIfIndex"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(LowerIfIndex), (PBYTE)&LowerIfIndex); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty LowerIfIndex failed, err is 0x%x", Err); + return; + } + + Iface = GetInterface(LowerIfIndex); + + if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11)) { + Type = WTAP_ENCAP_IEEE_802_11; + } else if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_WIRELESS_WAN)) { + Type = WTAP_ENCAP_RAW_IP; + } else { + Type = WTAP_ENCAP_ETHERNET; + } + + // Record the IfIndex if it's a new one. + if (Iface == NULL) { + unsigned long MiniportIfIndex; + Desc.PropertyName = (unsigned long long)L"MiniportIfIndex"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MiniportIfIndex), (PBYTE)&MiniportIfIndex); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty MiniportIfIndex failed, err is 0x%x", Err); + return; + } + Iface = AddInterface( + ev, + LowerIfIndex, + MiniportIfIndex, + Type + ); + } else if (Iface->PktEncapType != Type) { + printf("WARNING: inconsistent media type in packet events!\n"); + } + + if (Iface == NULL) { + // We generated the list of interfaces directly from the + // packet traces themselves, so there must be a bug. + g_err = ERROR_INVALID_DATA; + sprintf_s(g_err_info, sizeof(g_err_info), "Packet with unrecognized IfIndex"); + exit(1); + } + + // Save off Ndis/Wlan metadata to be added to the next packet + if (ev->EventHeader.EventDescriptor.Id == tidPacketMetadata) { + unsigned long MetadataLength = 0; + Desc.PropertyName = (unsigned long long)L"MetadataSize"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(MetadataLength), (PBYTE)&MetadataLength); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty MetadataSize failed, err is 0x%x", Err); + return; + } + + if (MetadataLength != sizeof(PacketMetadata)) { + g_err = ERROR_INVALID_DATA; + sprintf_s(g_err_info, sizeof(g_err_info), "Unknown Metadata length. Expected %lu, got %lu", (unsigned long)sizeof(DOT11_EXTSTA_RECV_CONTEXT), MetadataLength); + return; + } + + Desc.PropertyName = (unsigned long long)L"Metadata"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, MetadataLength, (PBYTE)&PacketMetadata); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty Metadata failed, err is 0x%x", Err); + return; + } + + AddWlanMetadata = true; + return; + } + + // N.B.: Here we are querying the FragmentSize property to get the + // total size of the packet, and then reading that many bytes from + // the Fragment property. This is unorthodox (normally you are + // supposed to use TdhGetPropertySize to get the size of a property) + // but required due to the way ndiscap puts packet contents in + // multiple adjacent properties (which happen to be contiguous in + // memory). + + Desc.PropertyName = (unsigned long long)L"FragmentSize"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, sizeof(FragLength), (PBYTE)&FragLength); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty FragmentSize failed, err is 0x%x", Err); + return; + } + + if (FragLength > RTL_NUMBER_OF(AuxFragBuf) - AuxFragBufOffset) { + g_err = ERROR_INVALID_DATA; + sprintf_s(g_err_info, sizeof(g_err_info), "Packet too large (size = %u) and skipped", AuxFragBufOffset + FragLength); + return; + } + + Desc.PropertyName = (unsigned long long)L"Fragment"; + Desc.ArrayIndex = ULONG_MAX; + Err = TdhGetProperty(ev, 0, NULL, 1, &Desc, FragLength, (PBYTE)(AuxFragBuf + AuxFragBufOffset)); + if (Err != NO_ERROR) { + g_err = Err; + sprintf_s(g_err_info, sizeof(g_err_info), "TdhGetProperty Fragment failed, err is 0x%x", Err); + return; + } + + // The KW_PACKET_START and KW_PACKET_END keywords are used as follows: + // -A single-event packet has both KW_PACKET_START and KW_PACKET_END. + // -A multi-event packet consists of an event with KW_PACKET_START followed + // by an event with KW_PACKET_END, with zero or more events with neither + // keyword in between. + // + // So, we accumulate fragments in AuxFragBuf until KW_PACKET_END is + // encountered, then call PcapNgWriteEnhancedPacket and start over. There's + // no need for us to even look for KW_PACKET_START. + // + // NB: Starting with Windows 8.1, only single-event packets are traced. + // This logic is here to support packet captures from older systems. + + if (!!(ev->EventHeader.EventDescriptor.Keyword & KW_PACKET_END)) { + + if (ev->EventHeader.EventDescriptor.Keyword & KW_MEDIA_NATIVE_802_11 && + AuxFragBuf[1] & 0x40) { + // Clear Protected bit in the case of 802.11 + // Ndis captures will be decrypted in the etl file + + AuxFragBuf[1] = AuxFragBuf[1] & 0xBF; // _1011_1111_ - Clear "Protected Flag" + } + + // COMMENT_MAX_SIZE must be multiple of 4 + #define COMMENT_MAX_SIZE 256 + char Comment[COMMENT_MAX_SIZE] = { 0 }; + size_t CommentLength = 0; + + if (AddWlanMetadata) { + if (PacketMetadata.uPhyId > DOT11_PHY_TYPE_NAMES_MAX) { + PacketMetadata.uPhyId = 0; // Set to unknown if outside known bounds. + } + + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d ProcessorNumber=%d Packet Metadata: ReceiveFlags:0x%x, PhyType:%s, CenterCh:%u, NumMPDUsReceived:%u, RSSI:%d, DataRate:%u", + ev->EventHeader.ProcessId, + ev->BufferContext.ProcessorNumber, + PacketMetadata.uReceiveFlags, + DOT11_PHY_TYPE_NAMES[PacketMetadata.uPhyId], + PacketMetadata.uChCenterFrequency, + PacketMetadata.usNumberOfMPDUsReceived, + PacketMetadata.lRSSI, + PacketMetadata.ucDataRate); + + AddWlanMetadata = false; + memset(&PacketMetadata, 0, sizeof(DOT11_EXTSTA_RECV_CONTEXT)); + } else if (CurrentPacketIsVMSwitchPacketFragment) { + if (VMSwitchPacketFragment.DestinationCount > 0) { + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d ProcessorNumber=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s DstNicCount=%d", + ev->EventHeader.ProcessId, + ev->BufferContext.ProcessorNumber, + Iface->VlanId, + Iface->VMNic.SourcePortId, + Iface->VMNic.SourceNicType, + Iface->VMNic.SourceNicName, + Iface->VMNic.SourcePortName, + VMSwitchPacketFragment.DestinationCount + ); + } else { + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d ProcessorNumber=%d VlanId=%d SrcPortId=%d SrcNicType=%s SrcNicName=%s SrcPortName=%s", + ev->EventHeader.ProcessId, + ev->BufferContext.ProcessorNumber, + Iface->VlanId, + Iface->VMNic.SourcePortId, + Iface->VMNic.SourceNicType, + Iface->VMNic.SourceNicName, + Iface->VMNic.SourcePortName + ); + } + } else { + Err = StringCchPrintfA(Comment, COMMENT_MAX_SIZE, "PID=%d ProcessorNumber=%d", ev->EventHeader.ProcessId, ev->BufferContext.ProcessorNumber); + } + + if (Err != NO_ERROR) { + printf("Failed converting comment to string with error: %u\n", Err); + } else { + Err = StringCchLengthA(Comment, COMMENT_MAX_SIZE, &CommentLength); + + if (Err != NO_ERROR) { + printf("Failed getting length of comment string with error: %u\n", Err); + CommentLength = 0; + memset(Comment, 0, COMMENT_MAX_SIZE); + } + } + + TotalFragmentLength = AuxFragBufOffset + FragLength; + + // Parse the packet to see if it's truncated. If so, try to recover the original length. + if (Type == WTAP_ENCAP_ETHERNET) { + if (TotalFragmentLength >= sizeof(ETHERNET_HEADER)) { + EthHdr = (PETHERNET_HEADER)AuxFragBuf; + if (ntohs(EthHdr->Type) == ETHERNET_TYPE_IPV4 && + TotalFragmentLength >= sizeof(IPV4_HEADER) + sizeof(ETHERNET_HEADER)) { + Ipv4Hdr = (PIPV4_HEADER)(EthHdr + 1); + InferredOriginalFragmentLength = ntohs(Ipv4Hdr->TotalLength) + sizeof(ETHERNET_HEADER); + } else if (ntohs(EthHdr->Type) == ETHERNET_TYPE_IPV6 && + TotalFragmentLength >= sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER)) { + Ipv6Hdr = (PIPV6_HEADER)(EthHdr + 1); + InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER); + } + } + } else if (Type == WTAP_ENCAP_RAW_IP) { + // Raw frames begins with an IPv4/6 header. + if (TotalFragmentLength >= sizeof(IPV4_HEADER)) { + Ipv4Hdr = (PIPV4_HEADER)AuxFragBuf; + if (Ipv4Hdr->Version == 4) { + InferredOriginalFragmentLength = ntohs(Ipv4Hdr->TotalLength) + sizeof(ETHERNET_HEADER); + } else if (Ipv4Hdr->Version == 6) { + Ipv6Hdr = (PIPV6_HEADER)(AuxFragBuf); + InferredOriginalFragmentLength = ntohs(Ipv6Hdr->PayloadLength) + sizeof(IPV6_HEADER) + sizeof(ETHERNET_HEADER); + } + } + } + + wtap_etl_rec_dump(AuxFragBuf, + TotalFragmentLength, + // For LSO v2 packets, inferred original fragment length is ignored since length field in IP header is not filled. + InferredOriginalFragmentLength <= TotalFragmentLength ? TotalFragmentLength : InferredOriginalFragmentLength, + Iface->PcapNgIfIndex, + !(ev->EventHeader.EventDescriptor.Keyword & KW_SEND), + timestamp, + Type, + Comment, + (unsigned short)CommentLength + ); + + AuxFragBufOffset = 0; + NumFramesConverted++; + } else { + AuxFragBufOffset += FragLength; + } +} + + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/etwdump.c b/extcap/etwdump.c new file mode 100644 index 00000000..ea803a70 --- /dev/null +++ b/extcap/etwdump.c @@ -0,0 +1,312 @@ +/* etwdump.c + * etwdump is an extcap tool used to dump etw to pcapng + * + * Copyright 2020, Odysseus Yang + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN "etwdump" + +#include "extcap-base.h" + +#include <wsutil/strtoi.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/please_report_bug.h> +#include <wsutil/wslog.h> + +#include <cli_main.h> +#include <wsutil/cmdarg_err.h> +#include "etl.h" + +#include <signal.h> + +/* extcap-interface has to be unique, or it may use wrong option output by a different extcapbin */ +#define ETW_EXTCAP_INTERFACE "etwdump" +#define ETWDUMP_VERSION_MAJOR "1" +#define ETWDUMP_VERSION_MINOR "0" +#define ETWDUMP_VERSION_RELEASE "0" + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_INCLUDE_UNDECIDABLE_EVENT, + OPT_ETLFILE, + OPT_PARAMS +}; + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + { "iue", ws_optional_argument, NULL, OPT_INCLUDE_UNDECIDABLE_EVENT}, + { "etlfile", ws_required_argument, NULL, OPT_ETLFILE}, + { "params", ws_required_argument, NULL, OPT_PARAMS}, + { 0, 0, 0, 0 } +}; + +int g_include_undecidable_event = false; + +void SignalHandler(_U_ int signal) +{ + SUPER_EVENT_TRACE_PROPERTIES super_trace_properties = { 0 }; + super_trace_properties.prop.Wnode.BufferSize = sizeof(SUPER_EVENT_TRACE_PROPERTIES); + super_trace_properties.prop.Wnode.ClientContext = 2; + super_trace_properties.prop.Wnode.Flags = WNODE_FLAG_TRACED_GUID; + super_trace_properties.prop.LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + super_trace_properties.prop.LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + /* Close trace when press CONTROL+C when running this console alone */ + ControlTrace((TRACEHANDLE)NULL, LOGGER_NAME, &super_trace_properties.prop, EVENT_TRACE_CONTROL_STOP); +} + +static void help(extcap_parameters* extcap_conf) +{ + extcap_help_print(extcap_conf); +} + +static int list_config(char* interface) +{ + unsigned inc = 0; + + if (!interface) { + ws_warning("No interface specified."); + return EXIT_FAILURE; + } + + if (g_strcmp0(interface, ETW_EXTCAP_INTERFACE)) { + ws_warning("Interface must be %s", ETW_EXTCAP_INTERFACE); + return EXIT_FAILURE; + } + /* + * required=true agu will be displayed before required=false on UI + * + * Empty etlfile and unempty params, read etw events from a live session with the params as the filter + * Unempty etlfile and empty params, read etw events from the etl file without filter + * Unempty etlfile and unemtpy params, read etw events from the etl file with the params as the filter + * Empty eltfile and empty params, invalid + */ + printf("arg {number=%u}{call=--etlfile}{display=etl file}" + "{type=fileselect}{tooltip=Select etl file to display in Wireshark}{required=false}{group=Capture}\n", + inc++); + printf("arg {number=%u}{call=--params}{display=filter parameters}" + "{type=string}{tooltip=Input providers, keyword and level filters for the etl file and live session}{group=Capture}\n", + inc++); + /* + * The undecidable events are those that either don't have sub-dissector or don't have anthing meaningful to display except for the EVENT_HEADER. + */ + printf("arg {number=%u}{call=--iue}{display=Should undecidable events be included}" + "{type=boolflag}{default=false}{tooltip=Choose if the undecidable event is included}{group=Capture}\n", + inc++); + + extcap_config_debug(&inc); + return EXIT_SUCCESS; +} + +int main(int argc, char* argv[]) +{ + char* err_msg; + int option_idx = 0; + int result; + int ret = EXIT_FAILURE; + + char* etlfile = NULL; + char* params = NULL; + + extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1); + char* help_url; + char* help_header = NULL; + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("etwdump"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + err_msg = configuration_init(argv[0], NULL); + if (err_msg != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + err_msg); + g_free(err_msg); + } + + help_url = data_file_url("etwdump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], ETWDUMP_VERSION_MAJOR, ETWDUMP_VERSION_MINOR, + ETWDUMP_VERSION_RELEASE, help_url); + g_free(help_url); + extcap_base_register_interface(extcap_conf, ETW_EXTCAP_INTERFACE, "Event Tracing for Windows (ETW) reader", 290, "DLT_ETW"); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --etlfile c:\\wwansvc.etl \n" + "--fifo=FILENAME --capture\n", argv[0], argv[0], ETW_EXTCAP_INTERFACE, argv[0], ETW_EXTCAP_INTERFACE, + argv[0], ETW_EXTCAP_INTERFACE); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--etlfile <filename>", "A etl filename"); + extcap_help_add_option(extcap_conf, "--iue", "Choose if undecidable event is included"); + + if (argc == 1) { + help(extcap_conf); + goto end; + } + + while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) { + switch (result) { + case OPT_VERSION: + extcap_version_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_HELP: + help(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_ETLFILE: + etlfile = g_strdup(ws_optarg); + break; + + case OPT_PARAMS: + /* Add params as the prefix since getopt_long will ignore the first argument always */ + params = ws_strdup_printf("params %s", ws_optarg); + break; + + case OPT_INCLUDE_UNDECIDABLE_EVENT: + g_include_undecidable_event = true; + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + /* Handle extcap specific options */ + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) + { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = list_config(extcap_conf->interface); + goto end; + } + + if (extcap_conf->capture) { + + if (g_strcmp0(extcap_conf->interface, ETW_EXTCAP_INTERFACE)) { + ws_warning("ERROR: invalid interface"); + goto end; + } + + if (etlfile == NULL && params == NULL) + { + ws_warning("ERROR: Both --etlfile and --params arguments are empty"); + goto end; + } + + wtap_init(false); + + signal(SIGINT, SignalHandler); + + switch(etw_dump(etlfile, extcap_conf->fifo, params, &ret, &err_msg)) + { + case WTAP_OPEN_ERROR: + if (err_msg != NULL) { + ws_warning("etw_dump failed: %s.", + err_msg); + g_free(err_msg); + } + else + { + ws_warning("etw_dump failed"); + } + break; + case WTAP_OPEN_NOT_MINE: + if (etlfile == NULL) + { + if (err_msg != NULL) { + ws_warning("The live session didn't capture any event. Error message: %s.", + err_msg); + g_free(err_msg); + } + else + { + ws_warning("The live session didn't capture any event"); + } + } + else + { + if (err_msg != NULL) { + ws_warning("The file %s is not etl format. Error message: %s.", + etlfile, err_msg); + g_free(err_msg); + } + else + { + ws_warning("The file %s is not etl format", etlfile); + } + } + break; + case WTAP_OPEN_MINE: + ret = EXIT_SUCCESS; + break; + } + } + +end: + /* clean up stuff */ + extcap_base_cleanup(&extcap_conf); + + if (etlfile != NULL) + { + g_free(etlfile); + } + if (params != NULL) + { + g_free(params); + } + + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/extcap-base.c b/extcap/extcap-base.c new file mode 100644 index 00000000..13d42b7a --- /dev/null +++ b/extcap/extcap-base.c @@ -0,0 +1,427 @@ +/* extcap-base.c + * Base function for extcaps + * + * Copyright 2015, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_EXTCAP + +#include "extcap-base.h" + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <wsutil/wslog.h> + +#include <wsutil/ws_assert.h> + +#include "ws_attributes.h" + +enum extcap_options { + EXTCAP_BASE_OPTIONS_ENUM +}; + +typedef struct _extcap_interface +{ + char * interface; + char * description; + + uint16_t dlt; + char * dltname; + char * dltdescription; +} extcap_interface; + +typedef struct _extcap_option { + char * optname; + char * optdesc; +} extcap_option_t; + +static FILE *custom_log = NULL; + +/* used to inform to extcap application that end of application is requested */ +bool extcap_end_application = false; +/* graceful shutdown callback, can be null */ +void (*extcap_graceful_shutdown_cb)(void) = NULL; + +static void extcap_init_log_file(const char *filename); + +/* Called from signals */ +#ifdef _WIN32 +static BOOL WINAPI +extcap_exit_from_loop(DWORD dwCtrlType _U_) +#else +static void extcap_exit_from_loop(int signo _U_) +#endif /* _WIN32 */ +{ + ws_debug("Exiting from main loop by signal"); + extcap_end_application = true; + if (extcap_graceful_shutdown_cb != NULL) { + extcap_graceful_shutdown_cb(); + } +#ifdef _WIN32 + return true; +#endif /* _WIN32 */ +} + +void extcap_base_register_interface(extcap_parameters * extcap, const char * interface, const char * ifdescription, uint16_t dlt, const char * dltdescription ) +{ + extcap_base_register_interface_ext(extcap, interface, ifdescription, dlt, NULL, dltdescription ); +} + +void extcap_base_register_interface_ext(extcap_parameters * extcap, + const char * interface, const char * ifdescription, + uint16_t dlt, const char * dltname, const char * dltdescription ) +{ + extcap_interface * iface; + + if (interface == NULL) + return; + + iface = g_new0(extcap_interface, 1); + + iface->interface = g_strdup(interface); + iface->description = g_strdup(ifdescription); + iface->dlt = dlt; + iface->dltname = g_strdup(dltname); + iface->dltdescription = g_strdup(dltdescription); + + extcap->interfaces = g_list_append(extcap->interfaces, (void *) iface); +} + +bool extcap_base_register_graceful_shutdown_cb(extcap_parameters * extcap _U_, void (*callback)(void)) +{ +#ifndef _WIN32 + struct sigaction sig_handler = { .sa_handler = extcap_exit_from_loop }; +#endif + + extcap_end_application = false; + extcap_graceful_shutdown_cb = callback; +#ifdef _WIN32 + if (!SetConsoleCtrlHandler(extcap_exit_from_loop, true)) { + ws_warning("Can't set console handler"); + return false; + } +#else + /* Catch signals to be able to cleanup config later */ + if (sigaction(SIGINT, &sig_handler, NULL)) { + ws_warning("Can't set SIGINT signal handler"); + return false; + } + if (sigaction(SIGTERM, &sig_handler, NULL)) { + ws_warning("Can't set SIGTERM signal handler"); + return false; + } + if (sigaction(SIGPIPE, &sig_handler, NULL)) { + ws_warning("Can't set SIGPIPE signal handler"); + return false; + } +#endif /* _WIN32 */ + + return true; +} + +void extcap_base_set_util_info(extcap_parameters * extcap, const char * exename, const char * major, + const char * minor, const char * release, const char * helppage) +{ + extcap->exename = g_path_get_basename(exename); + + ws_assert(major); + if (!minor) + ws_assert(!release); + + extcap->version = ws_strdup_printf("%s%s%s%s%s", + major, + minor ? "." : "", + minor ? minor : "", + release ? "." : "", + release ? release : ""); + extcap->helppage = g_strdup(helppage); +} + +void extcap_base_set_compiled_with(extcap_parameters * extcap, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + extcap->compiled_with = ws_strdup_vprintf(fmt, ap); + va_end(ap); +} + +void extcap_base_set_running_with(extcap_parameters * extcap, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + extcap->running_with = ws_strdup_vprintf(fmt, ap); + va_end(ap); +} + +void extcap_log_init(const char *progname) +{ + ws_log_init(progname, NULL); + /* extcaps cannot write debug information to parent on stderr. */ + ws_log_console_writer_set_use_stdout(true); + ws_noisy("Extcap log initialization finished"); +} + +uint8_t extcap_base_parse_options(extcap_parameters * extcap, int result, char * optargument) +{ + uint8_t ret = 1; + enum ws_log_level level; + + switch (result) { + case EXTCAP_OPT_LOG_LEVEL: + level = ws_log_set_level_str(optargument); + if (level == LOG_LEVEL_NONE) { + /* Invalid log level string. */ + ret = 0; + } + else if (level <= LOG_LEVEL_DEBUG) { + extcap->debug = true; + } + break; + case EXTCAP_OPT_LOG_FILE: + extcap_init_log_file(optargument); + break; + case EXTCAP_OPT_LIST_INTERFACES: + extcap->do_list_interfaces = 1; + break; + case EXTCAP_OPT_VERSION: + extcap->ws_version = g_strdup(optargument); + extcap->do_version = 1; + break; + case EXTCAP_OPT_LIST_DLTS: + extcap->do_list_dlts = 1; + break; + case EXTCAP_OPT_INTERFACE: + extcap->interface = g_strdup(optargument); + break; + case EXTCAP_OPT_CONFIG: + extcap->show_config = 1; + break; + case EXTCAP_OPT_CAPTURE: + extcap->capture = 1; + break; + case EXTCAP_OPT_CAPTURE_FILTER: + extcap->capture_filter = g_strdup(optargument); + break; + case EXTCAP_OPT_FIFO: + extcap->fifo = g_strdup(optargument); + break; + default: + ret = 0; + } + + return ret; +} + +static void extcap_iface_print(void * data, void * userdata _U_) +{ + extcap_interface * iface = (extcap_interface *)data; + + printf("interface {value=%s}", iface->interface); + if (iface->description != NULL) + printf ("{display=%s}\n", iface->description); + else + printf ("\n"); +} + +static int extcap_iface_compare(gconstpointer a, gconstpointer b) +{ + const extcap_interface * iface_a = (const extcap_interface *)a; + + return (g_strcmp0(iface_a->interface, (const char *) b)); +} + +static void extcap_print_version(extcap_parameters * extcap) +{ + printf("extcap {version=%s}", extcap->version != NULL ? extcap->version : "unknown"); + if (extcap->helppage != NULL) + printf("{help=%s}", extcap->helppage); + printf("\n"); +} + +static int extcap_iface_listall(extcap_parameters * extcap, uint8_t list_ifs) +{ + if (list_ifs) { + if (g_list_length(extcap->interfaces) > 0) { + extcap_print_version(extcap); + g_list_foreach(extcap->interfaces, extcap_iface_print, extcap); + } + } else if (extcap->do_version) { + extcap_print_version(extcap); + } else { + GList * element = NULL; + extcap_interface * iface = NULL; + if ((element = g_list_find_custom(extcap->interfaces, extcap->interface, extcap_iface_compare)) == NULL) + return 0; + + iface = (extcap_interface *) element->data; + printf("dlt {number=%u}{name=%s}", iface->dlt, iface->dltname != NULL ? iface->dltname : iface->interface); + if (iface->description != NULL) + printf ("{display=%s}\n", iface->dltdescription); + else + printf ("\n"); + } + + return 1; +} + +uint8_t extcap_base_handle_interface(extcap_parameters * extcap) +{ + /* A fifo must be provided for capture */ + if (extcap->capture && (extcap->fifo == NULL || strlen(extcap->fifo) <= 0)) { + extcap->capture = 0; + ws_error("Extcap Error: No FIFO pipe provided"); + return 0; + } + + if (extcap->do_list_interfaces) { + return extcap_iface_listall(extcap, 1); + } else if (extcap->do_version || extcap->do_list_dlts) { + return extcap_iface_listall(extcap, 0); + } + + return 0; +} + +static void extcap_iface_free(void * data) +{ + extcap_interface * iface = (extcap_interface *)data; + g_free(iface->interface); + g_free(iface->description); + g_free(iface->dltname); + g_free(iface->dltdescription); + g_free(iface); +} + +static void extcap_help_option_free(void * option) +{ + extcap_option_t* o = (extcap_option_t*)option; + g_free(o->optname); + g_free(o->optdesc); + g_free(o); +} + +void extcap_base_cleanup(extcap_parameters ** extcap) +{ + g_list_free_full((*extcap)->interfaces, extcap_iface_free); + g_free((*extcap)->exename); + g_free((*extcap)->fifo); + g_free((*extcap)->interface); + g_free((*extcap)->version); + g_free((*extcap)->compiled_with); + g_free((*extcap)->running_with); + g_free((*extcap)->helppage); + g_free((*extcap)->help_header); + g_free((*extcap)->ws_version); + g_list_free_full((*extcap)->help_options, extcap_help_option_free); + g_free(*extcap); + *extcap = NULL; +} + +static void extcap_print_option(void * option, void * user_data _U_) +{ + extcap_option_t* o = (extcap_option_t*)option; + printf("\t%s: %s\n", o->optname, o->optdesc); +} + +void extcap_version_print(extcap_parameters * extcap) +{ + printf("%s version %s\n", extcap->exename, extcap->version); + if (extcap->compiled_with != NULL) + printf("Compiled with %s\n", extcap->compiled_with); + if (extcap->running_with != NULL) + printf("Running with %s\n", extcap->running_with); +} + +void extcap_help_print(extcap_parameters * extcap) +{ + printf("\nWireshark - %s v%s\n\n", extcap->exename, extcap->version); + printf("Usage:\n"); + printf("%s", extcap->help_header); + printf("\n"); + printf("Options:\n"); + g_list_foreach(extcap->help_options, extcap_print_option, NULL); + printf("\n"); +} + +void extcap_help_add_option(extcap_parameters * extcap, const char * help_option_name, const char * help_option_desc) +{ + extcap_option_t* o = g_new0(extcap_option_t, 1); + o->optname = g_strdup(help_option_name); + o->optdesc = g_strdup(help_option_desc); + + extcap->help_options = g_list_append(extcap->help_options, o); +} + +void extcap_help_add_header(extcap_parameters * extcap, char * help_header) +{ + extcap->help_header = g_strdup(help_header); + extcap_help_add_option(extcap, "--extcap-interfaces", "list the extcap Interfaces"); + extcap_help_add_option(extcap, "--extcap-dlts", "list the DLTs"); + extcap_help_add_option(extcap, "--extcap-interface <iface>", "specify the extcap interface"); + extcap_help_add_option(extcap, "--extcap-config", "list the additional configuration for an interface"); + extcap_help_add_option(extcap, "--capture", "run the capture"); + extcap_help_add_option(extcap, "--extcap-capture-filter <filter>", "the capture filter"); + extcap_help_add_option(extcap, "--fifo <file>", "dump data to file or fifo"); + extcap_help_add_option(extcap, "--extcap-version", "print tool version"); + extcap_help_add_option(extcap, "--log-level", "Set the log level"); + extcap_help_add_option(extcap, "--log-file", "Set a log file to log messages in addition to the console"); +} + +static void extcap_init_log_file(const char* filename) +{ + if (!filename || strlen(filename) == 0) + ws_error("Missing log file name"); + custom_log = fopen(filename, "w"); + if (!custom_log) + ws_error("Can't open custom log file: %s (%s)", filename, strerror(errno)); + ws_log_add_custom_file(custom_log); +} + +void extcap_config_debug(unsigned* count) +{ + printf("arg {number=%u}{call=--log-level}{display=Set the log level}" + "{type=selector}{tooltip=Set the log level}{required=false}" + "{group=Debug}\n", *count); + printf("value {arg=%u}{value=message}{display=Message}{default=true}\n", *count); + printf("value {arg=%u}{value=info}{display=Info}\n", *count); + printf("value {arg=%u}{value=debug}{display=Debug}\n", *count); + printf("value {arg=%u}{value=noisy}{display=Noisy}\n", *count); + (*count)++; + printf("arg {number=%u}{call=--log-file}{display=Use a file for logging}" + "{type=fileselect}{tooltip=Set a file where log messages are written}{required=false}" + "{group=Debug}\n", (*count)++); +} + +void extcap_cmdline_debug(char** ar, const unsigned n) +{ + GString* cmdline = g_string_new("cmdline: "); + unsigned i; + for (i = 0; i < n; i++) + g_string_append_printf(cmdline, "%s ", ar[i]); + ws_debug("%s", cmdline->str); + g_string_free(cmdline, true); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/extcap-base.h b/extcap/extcap-base.h new file mode 100644 index 00000000..4bbfb1b4 --- /dev/null +++ b/extcap/extcap-base.h @@ -0,0 +1,129 @@ +/** @file + * + * Base function for extcaps + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef __EXTCAP_BASE_H__ +#define __EXTCAP_BASE_H__ + +#include <glib.h> +#include <glib/gprintf.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> + +#include <wsutil/ws_getopt.h> + +#ifdef _WIN32 +#include <io.h> +#endif + +#include <wsutil/socket.h> + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#define EXTCAP_BASE_OPTIONS_ENUM \ + EXTCAP_OPT_LIST_INTERFACES, \ + EXTCAP_OPT_VERSION, \ + EXTCAP_OPT_LIST_DLTS, \ + EXTCAP_OPT_INTERFACE, \ + EXTCAP_OPT_CONFIG, \ + EXTCAP_OPT_CAPTURE, \ + EXTCAP_OPT_CAPTURE_FILTER, \ + EXTCAP_OPT_FIFO, \ + EXTCAP_OPT_LOG_LEVEL, \ + EXTCAP_OPT_LOG_FILE + + +#define EXTCAP_BASE_OPTIONS \ + { "extcap-interfaces", ws_no_argument, NULL, EXTCAP_OPT_LIST_INTERFACES}, \ + { "extcap-version", ws_optional_argument, NULL, EXTCAP_OPT_VERSION}, \ + { "extcap-dlts", ws_no_argument, NULL, EXTCAP_OPT_LIST_DLTS}, \ + { "extcap-interface", ws_required_argument, NULL, EXTCAP_OPT_INTERFACE}, \ + { "extcap-config", ws_no_argument, NULL, EXTCAP_OPT_CONFIG}, \ + { "capture", ws_no_argument, NULL, EXTCAP_OPT_CAPTURE}, \ + { "extcap-capture-filter", ws_required_argument, NULL, EXTCAP_OPT_CAPTURE_FILTER}, \ + { "fifo", ws_required_argument, NULL, EXTCAP_OPT_FIFO}, \ + { "log-level", ws_required_argument, NULL, EXTCAP_OPT_LOG_LEVEL}, \ + { "log-file", ws_required_argument, NULL, EXTCAP_OPT_LOG_FILE} + +typedef struct _extcap_parameters +{ + char * exename; + char * fifo; + char * interface; + char * capture_filter; + + char * version; + char * compiled_with; + char * running_with; + char * helppage; + uint8_t capture; + uint8_t show_config; + + char * ws_version; + + /* private content */ + GList * interfaces; + uint8_t do_version; + uint8_t do_list_dlts; + uint8_t do_list_interfaces; + + char * help_header; + GList * help_options; + + bool debug; +} extcap_parameters; + +/* used to inform to extcap application that end of application is requested */ +extern bool extcap_end_application; + +void extcap_base_register_interface(extcap_parameters * extcap, const char * interface, const char * ifdescription, uint16_t dlt, const char * dltdescription ); +void extcap_base_register_interface_ext(extcap_parameters * extcap, const char * interface, const char * ifdescription, uint16_t dlt, const char * dltname, const char * dltdescription ); + +/* used to inform extcap framework that graceful shutdown supported by the extcap + */ +bool extcap_base_register_graceful_shutdown_cb(extcap_parameters * extcap, void (*callback)(void)); + +void extcap_base_set_util_info(extcap_parameters * extcap, const char * exename, const char * major, const char * minor, const char * release, const char * helppage); +void extcap_base_set_compiled_with(extcap_parameters * extcap, const char *fmt, ...); +void extcap_base_set_running_with(extcap_parameters * extcap, const char *fmt, ...); +uint8_t extcap_base_parse_options(extcap_parameters * extcap, int result, char * optargument); +uint8_t extcap_base_handle_interface(extcap_parameters * extcap); +void extcap_base_cleanup(extcap_parameters ** extcap); +void extcap_help_add_header(extcap_parameters * extcap, char * help_header); +void extcap_help_add_option(extcap_parameters * extcap, const char * help_option_name, const char * help_optionn_desc); +void extcap_version_print(extcap_parameters * extcap); +void extcap_help_print(extcap_parameters * extcap); +void extcap_cmdline_debug(char** ar, const unsigned n); +void extcap_config_debug(unsigned* count); +void extcap_base_help(void); +void extcap_log_init(const char *progname); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // __EXTCAP_BASE_H__ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/extcap/falcodump.cpp b/extcap/falcodump.cpp new file mode 100644 index 00000000..b8710644 --- /dev/null +++ b/extcap/falcodump.cpp @@ -0,0 +1,1016 @@ +/* falcodump.cpp + * Falcodump is an extcap tool which dumps logs using Falco source plugins. + * https://falco.org/docs/plugins/ + * + * Adapted from sdjournal. + * Copyright 2022, Gerald Combs and Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * To do: + * - Pull plugin source description from list_open_params? + * - Add filtering. + * - Add an option to dump plugin fields. + * - Add options for credentials. + * - Let the user create preconfigured interfaces. + * - Exit more cleanly (see MRs 2063 and 7673). + * - Better config schema default value parsing? Would likely require a schema change. + * - Make sure all types are handled in parse_schema_properties. + * - Handle "required" config schema annotation (Okta). + */ + +#include "config.h" + +#include <sinsp.h> +#include <plugin_manager.h> + +#define WS_LOG_DOMAIN "falcodump" + +#include <extcap/extcap-base.h> + +#include <wsutil/file_util.h> +#include <wsutil/filesystem.h> +#include <wsutil/json_dumper.h> +#include <wsutil/privileges.h> +#include <wsutil/utf8_entities.h> +#include <wsutil/wsjson.h> +#include <wsutil/wslog.h> + +#define FALCODUMP_VERSION_MAJOR "1" +#define FALCODUMP_VERSION_MINOR "0" +#define FALCODUMP_VERSION_RELEASE "0" + +#define FALCODUMP_PLUGIN_PLACEHOLDER "<plugin name>" + +// We load our plugins and fetch their configs before we set our log level. +// #define DEBUG_JSON_PARSING +// #define DEBUG_SINSP + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_PLUGIN_API_VERSION, + OPT_PLUGIN_SOURCE, + OPT_SCHEMA_PROPERTIES_START, +}; + +// "s3DownloadConcurrency": { +// "type": "integer", +// "description": "Controls the number of background goroutines used to download S3 files (Default: 1)" +// }, +struct config_properties { + std::string name; + std::string display; // "title" property || name + std::string option; // Command line option including leading dashes (lowercase display name) + int option_index; // Starts from OPT_SCHEMA_PROPERTIES_START + std::string type; // "boolean", "integer", "string", "enum", "BEGIN_CONFIG_PROPERTIES", or "END_CONFIG_PROPERTIES" + std::string description; + std::string default_value; + std::vector<std::string>enum_values; + std::string current_value; +}; + +struct plugin_configuration { + std::vector<struct config_properties> property_list; + + std::string json_config() { + json_dumper dumper = {}; + dumper.output_string = g_string_new(NULL); + + json_dumper_begin_object(&dumper); + + for (const auto &prop : property_list) { + if (prop.type == "BEGIN_CONFIG_PROPERTIES") { + json_dumper_set_member_name(&dumper, prop.name.c_str()); + json_dumper_begin_object(&dumper); + continue; + } else if (prop.type == "END_CONFIG_PROPERTIES") { + json_dumper_end_object(&dumper); + continue; + } + + if (prop.current_value == prop.default_value) { + continue; + } + + json_dumper_set_member_name(&dumper, prop.name.c_str()); + if (prop.type == "string" || prop.type == "selector") { + json_dumper_value_string(&dumper, prop.current_value.c_str()); + } else { + json_dumper_value_anyf(&dumper, "%s", prop.current_value.c_str()); + } + } + + json_dumper_end_object(&dumper); + json_dumper_finish(&dumper); + std::string config_blob = dumper.output_string->str; + ws_debug("configuration: %s", dumper.output_string->str); + g_string_free(dumper.output_string, TRUE); + return config_blob; + } +}; + +// Read a line without trailing (CR)LF. Returns -1 on failure. Copied from addr_resolv.c. +// XXX Use g_file_get_contents or GMappedFile instead? +static int +fgetline(char *buf, int size, FILE *fp) +{ + if (fgets(buf, size, fp)) { + int len = (int)strcspn(buf, "\r\n"); + buf[len] = '\0'; + return len; + } + return -1; +} + +static const size_t MAX_AWS_LINELEN = 2048; +void print_cloudtrail_aws_profile_config(int arg_num, const char *display, const char *description) { + char buf[MAX_AWS_LINELEN]; + char profile[MAX_AWS_LINELEN]; + FILE *aws_fp; + std::set<std::string>profiles; + + // Look in files as specified in https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html + char *cred_path = g_strdup(g_getenv("AWS_SHARED_CREDENTIALS_FILE")); + if (cred_path == NULL) { + cred_path = g_build_filename(g_get_home_dir(), ".aws", "credentials", (gchar *)NULL); + } + + aws_fp = ws_fopen(cred_path, "r"); + g_free(cred_path); + + if (aws_fp != NULL) { + while (fgetline(buf, sizeof(buf), aws_fp) >= 0) { + if (sscanf(buf, "[%2047[^]]s]", profile) == 1) { + if (strcmp(profile, "default") == 0) { + continue; + } + profiles.insert(profile); + } + } + fclose(aws_fp); + } + + char *conf_path = g_strdup(g_getenv("AWS_CONFIG_FILE")); + if (conf_path == NULL) { + conf_path = g_build_filename(g_get_home_dir(), ".aws", "config", (gchar *)NULL); + } + + aws_fp = ws_fopen(conf_path, "r"); + g_free(conf_path); + + if (aws_fp != NULL) { + while (fgetline(buf, sizeof(buf), aws_fp) >= 0) { + if (sscanf(buf, "[profile %2047[^]]s]", profile) == 1) { + if (strcmp(profile, "default") == 0) { + continue; + } + profiles.insert(profile); + } + } + fclose(aws_fp); + } + + const char *aws_profile_env = g_getenv("AWS_PROFILE"); + for (auto &profile : profiles) { + if (aws_profile_env && profile == aws_profile_env) { + aws_profile_env = nullptr; + } + } + if (aws_profile_env) { + profiles.insert(aws_profile_env); + } + + printf( + "arg {number=%d}" + "{call=--cloudtrail-aws-profile}" + "{display=%s}" + "{type=editselector}" + "{tooltip=%s}" + "{group=Capture}" + "\n", + arg_num, display, description); + printf ("value {arg=%d}{value=}{display=Default}{default=true}\n", arg_num); + for (auto &profile : profiles) { + printf( + "value {arg=%d}" + "{value=%s}" + "{display=%s}" + "\n", + arg_num, profile.c_str(), profile.c_str()); + } +} + +void print_cloudtrail_aws_region_config(int arg_num, const char *display, const char *description) { + // printf ' "%s",\n' $(aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text) | sort + std::set<std::string> regions = { + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-south-2", + "ap-southeast-1", + "ap-southeast-2", + "ap-southeast-3", + "ap-southeast-4", + "ca-central-1", + "eu-central-1", + "eu-central-2", + "eu-north-1", + "eu-south-1", + "eu-south-2", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-central-1", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", + }; + + const char *aws_region_env = g_getenv("AWS_REGION"); + for (auto ®ion : regions) { + if (aws_region_env && region == aws_region_env) { + aws_region_env = nullptr; + } + } + if (aws_region_env) { + regions.insert(aws_region_env); + } + + printf( + "arg {number=%d}" + "{call=--cloudtrail-aws-region}" + "{display=%s}" + "{type=editselector}" + "{tooltip=%s}" + "{group=Capture}" + "\n", + arg_num, display, description); + printf ("value {arg=%d}{value=}{display=From profile}{default=true}\n", arg_num); + + for (auto ®ion : regions) { + printf( + "value {arg=%d}" + "{value=%s}" + "{display=%s}" + "\n", + arg_num, region.c_str(), region.c_str()); + } +} + + +// Load our plugins. This should match the behavior of the Falco Bridge dissector. +static void load_plugins(sinsp &inspector) { + WS_DIR *dir; + WS_DIRENT *file; + char *plugin_paths[] = { + g_build_filename(get_plugins_dir_with_version(), "falco", NULL), + g_build_filename(get_plugins_pers_dir_with_version(), "falco", NULL) + }; + + for (size_t idx = 0; idx < 2; idx++) { + char *plugin_path = plugin_paths[idx]; + if ((dir = ws_dir_open(plugin_path, 0, NULL)) != NULL) { + while ((file = ws_dir_read_name(dir)) != NULL) { + char *libname = g_build_filename(plugin_path, ws_dir_get_name(file), NULL); + try { + auto plugin = inspector.register_plugin(libname); + ws_debug("Registered plugin %s via %s", plugin->name().c_str(), libname); + } catch (sinsp_exception &e) { + ws_warning("%s", e.what()); + } + g_free(libname); + } + ws_dir_close(dir); + } + g_free(plugin_path); + } +} + +// Given a key, try to find its value in a JSON object. +// Returns (value, true) on success, or (err_str, false) on failure. +const std::pair<const std::string,bool> find_json_object_value(const std::string &object_blob, const std::string &key, int value_type) { + std::vector<jsmntok_t> tokens; + int num_tokens = json_parse(object_blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + return std::pair<std::string,bool>("invalid", false); + case JSMN_ERROR_PART: + return std::pair<std::string,bool>("incomplete", false); + default: + break; + } + + tokens.resize(num_tokens); + json_parse(object_blob.c_str(), tokens.data(), num_tokens); + for (int idx = 0; idx < num_tokens - 1; idx++) { + jsmntok_t &k_tok = tokens[idx]; + jsmntok_t &v_tok = tokens[idx+1]; + std::string cur_key = object_blob.substr(k_tok.start, k_tok.end - k_tok.start); + if (cur_key == key && k_tok.type == JSMN_STRING && v_tok.type == value_type) { + std::string value = object_blob.substr(v_tok.start, v_tok.end - v_tok.start); + return std::pair<std::string,bool>(value, true); + } +#ifdef DEBUG_JSON_PARSING + else if (cur_key == key) ws_warning("|%s(%d): %s(%d)|\n", cur_key.c_str(), k_tok.type, object_blob.substr(v_tok.start, v_tok.end - v_tok.start).c_str(), v_tok.type); +#endif + } + return std::pair<const std::string,bool>("", false); +} + +// Given an RFC 6901-style JSON pointer, try to find its value in a JSON object. +// Returns (value, true) on success, or (err_str, false) on failure. +const std::pair<const std::string,bool> find_json_pointer_value(const std::string &object_blob, const std::string &pointer, int value_type) { + std::string blob = object_blob; + std::istringstream ob_stream(pointer); + std::string token; + while (std::getline(ob_stream, token, '/')) { + if (token == "#" || token.empty()) { + continue; + } + std::pair<std::string,bool> jv = find_json_object_value(blob, token, value_type); + if (!jv.second) { +#ifdef DEBUG_JSON_PARSING + ws_warning("JSON pointer %s not found at %s", blob.c_str(), token.c_str()); +#endif + return std::pair<std::string,bool>("", false); + } + blob = jv.first; + } +#ifdef DEBUG_JSON_PARSING + ws_warning("JSON pointer %s = %s ... %s", pointer.c_str(), blob.substr(0, 10).c_str(), blob.substr(blob.size() - 10, 10).c_str()); +#endif + return std::pair<const std::string,bool>(blob, true); +} + +// Convert a JSON array to a string vector. +// Returns (vector, true) on success, or (err_str, false) on failure. +const std::pair<std::vector<std::string>,bool> get_json_array(const std::string &array_blob) { + std::vector<jsmntok_t> tokens; + int num_tokens = json_parse(array_blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"invalid"}, false); + case JSMN_ERROR_PART: + { + return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"incomplete"}, false); + } + default: + break; + } + + tokens.resize(num_tokens); + json_parse(array_blob.c_str(), tokens.data(), num_tokens); + std::vector<std::string> elements; + // First token is the full array. + for (int idx = 1; idx < num_tokens; idx++) { + jsmntok_t &el_tok = tokens[idx]; + elements.push_back(array_blob.substr(el_tok.start, el_tok.end - el_tok.start)); + } +#ifdef DEBUG_JSON_PARSING + ws_warning("%s: %d", array_blob.c_str(), (int)elements.size()); +#endif + return std::pair<std::vector<std::string>,bool>(elements, true); +} + +// Given a JSON blob containing a schema properties object, add each property to the +// given plugin config. +const std::pair<const std::string,bool> get_schema_properties(const std::string props_blob, int &opt_idx, const std::string option_prefix, const std::string plugin_name, std::vector<struct config_properties> &property_list) { + std::vector<jsmntok_t> tokens; + int num_tokens = json_parse(props_blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + return std::pair<std::string,bool>("invalid", false); + case JSMN_ERROR_PART: + return std::pair<std::string,bool>("incomplete", false); + default: + break; + } + + tokens.resize(num_tokens); + json_parse(props_blob.c_str(), tokens.data(), num_tokens); + + if (tokens[0].type != JSMN_OBJECT) { + return std::pair<std::string,bool>("malformed", false); + } + + char *plugin_name_lower = g_ascii_strdown(plugin_name.c_str(), -1); + + // We have an object blob which contains a list of properties and property blobs, e.g. + // { "property_1": { "type": ... }, "prob_blob_1": { "properties": { "prop_blob_2": { "type": ... } } } + // Skip over the outer { ... } and process the contents as pairs. + for (int idx = 1; idx < num_tokens - 2; idx++) { + jsmntok_t &n_tok = tokens[idx]; + jsmntok_t &p_tok = tokens[idx+1]; + + std::string name = props_blob.substr(n_tok.start, n_tok.end - n_tok.start); + std::string display = name; + std::string property_blob = props_blob.substr(p_tok.start, p_tok.end - p_tok.start); + std::vector<std::string> enum_values; + + // XXX Check for errors? + int prop_tokens = json_parse(property_blob.c_str(), NULL, 0); + switch (prop_tokens) { + case JSMN_ERROR_INVAL: + return std::pair<std::string,bool>("invalid property", false); + case JSMN_ERROR_PART: + return std::pair<std::string,bool>("incomplete property", false); + default: + break; + } +#ifdef DEBUG_JSON_PARSING + ws_warning("property %s [%d]\n", name.c_str(), prop_tokens); +#endif + + std::pair<std::string,bool> jv = find_json_object_value(property_blob, "properties", JSMN_OBJECT); + if (jv.second) { + config_properties properties = { + name, + display, + "", + -1, + "BEGIN_CONFIG_PROPERTIES", + "", + "", + enum_values, + "", + }; + property_list.push_back(properties); + get_schema_properties(jv.first, opt_idx, option_prefix + "-" + name, plugin_name, property_list); + properties = { + name, + display, + "", + -1, + "END_CONFIG_PROPERTIES", + "", + "", + enum_values, + "", + }; + property_list.push_back(properties); + idx += prop_tokens; + continue; + } + + jv = find_json_object_value(property_blob, "title", JSMN_STRING); + if (jv.second) { + display = jv.first; + } + // else split+capitalize "name"? + + jv = find_json_object_value(property_blob, "type", JSMN_STRING); + if (!jv.second) { + return std::pair<std::string,bool>("missing type", false); + } + std::string type = jv.first; + jv = find_json_object_value(property_blob, "description", JSMN_STRING); + if (!jv.second) { + return std::pair<std::string,bool>("missing description", false); + } + std::string description = jv.first; + std::string default_value; + jv = find_json_object_value(property_blob, "default", JSMN_STRING); + if (jv.second) { + default_value = jv.first; + } else { + std::string default_pfx = "(Default: "; + size_t pfx_pos = description.rfind(default_pfx); + if (pfx_pos != std::string::npos) { + default_value = description.substr(pfx_pos + default_pfx.size()); + pfx_pos = default_value.rfind(")"); + default_value = default_value.erase(pfx_pos); + } + } + jv = find_json_object_value(property_blob, "enum", JSMN_ARRAY); + if (jv.second) { + const std::pair<std::vector<std::string>,bool> ja = get_json_array(jv.first); + if (ja.second) { + enum_values = ja.first; + type = "selector"; + } + } +#ifdef DEBUG_JSON_PARSING + ws_warning("%s: %s, %s, [%d]\n", name.c_str(), type.c_str(), description.c_str(), (int)enum_values.size()); +#endif + const char *call = g_ascii_strdown(name.c_str(), -1); + config_properties properties = { + name, + display, + std::string() + plugin_name_lower + option_prefix + "-" + call, // Command line option (lowercase plugin + display name) + opt_idx, + type, + description, + default_value, + enum_values, + default_value, + }; + property_list.push_back(properties); + g_free((gpointer)call); + idx += prop_tokens; + opt_idx++; + } + g_free(plugin_name_lower); + return std::pair<std::string,bool>("",true); +} + +// Wherein we try to implement a sufficiently complete JSON Schema parser. +// Given a plugin config schema like the following: +//{ +// "$schema": "http://json-schema.org/draft-04/schema#", +// "$ref": "#/definitions/PluginConfig", +// "definitions": { +// "PluginConfig": { +// "properties": { +// "s3DownloadConcurrency": { +// "type": "integer", +// "title": "S3 download concurrency", +// "description": "Controls the number of background goroutines used to download S3 files (Default: 1)", +// "default": 1 +// }, +// [ ... ] +// "aws": { +// "$schema": "http://json-schema.org/draft-04/schema#", +// "$ref": "#/definitions/PluginConfigAWS" +// } +// }, +// "additionalProperties": true, +// "type": "object" +// }, +// "PluginConfigAWS": { +// "properties": { +// "profile": { +// "type": "string", +// "title": "Shared AWS Config Profile", +// "description": "If non-empty", +// "default": "''" +// }, +// [ ... ] +// }, +// "additionalProperties": true, +// "type": "object" +// } +// } +//} +// find the plugin properties and parse them using parse_schema_properties. +// https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta + +static bool get_plugin_config_schema(const std::shared_ptr<sinsp_plugin> &plugin, plugin_configuration &plugin_config) +{ + ss_plugin_schema_type schema_type = SS_PLUGIN_SCHEMA_JSON; + std::string schema_blob = plugin->get_init_schema(schema_type); + std::string config_name; + std::pair<std::string,bool> jv; + +#ifdef DEBUG_JSON_PARSING + ws_warning("raw schema: %s\n", schema_blob.c_str()); +#endif + + int ref_cnt = 0; + std::string::size_type ref_pos = 0; + while ((ref_pos = schema_blob.find("\"$ref\"", ref_pos)) != std::string::npos) { + ref_cnt++; + ref_pos += 5; + } + + // Dereference all of our $ref pairs. + // This is kind of janky, but makes subsequent parsing more simple. + for (int ref_idx = 0; ref_idx < ref_cnt; ref_idx++) { + jv = find_json_object_value(schema_blob, "$ref", JSMN_STRING); + if (!jv.second) { + break; + } + const std::string ref_pointer = jv.first; + jv = find_json_pointer_value(schema_blob, ref_pointer, JSMN_OBJECT); + if (!jv.second) { + ws_warning("Unable to find $ref %s.", ref_pointer.c_str()); + return false; + } + const std::string ref_body = jv.first.substr(1, jv.first.size() - 2); + + std::vector<jsmntok_t> tokens; + int num_tokens = json_parse(schema_blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + ws_warning("Invalid schema."); + return false; + case JSMN_ERROR_PART: + { + ws_warning("Incomplete schema."); + return false; + } + default: + break; + } + +#ifdef DEBUG_JSON_PARSING + ws_warning("searching for $ref: %s\n", ref_pointer.c_str()); +#endif + tokens.resize(num_tokens); + json_parse(schema_blob.c_str(), tokens.data(), num_tokens); + std::vector<std::string> elements; + // First token is the full array. + for (int idx = 0; idx < num_tokens - 1; idx++) { + if (tokens[idx].type != JSMN_STRING && tokens[idx+1].type != JSMN_STRING) { + continue; + } + auto key_tok = &tokens[idx]; + auto val_tok = &tokens[idx+1]; + std::string key = schema_blob.substr(key_tok->start, key_tok->end - key_tok->start); + std::string value = schema_blob.substr(val_tok->start, val_tok->end - val_tok->start); + if (key != "$ref" || value != ref_pointer) { + continue; + } + try { +#ifdef DEBUG_JSON_PARSING + ws_warning("replacing: %s\n", schema_blob.substr(key_tok->start - 1, val_tok->end - key_tok->start + 2).c_str()); +#endif + schema_blob.replace(key_tok->start - 1, val_tok->end - key_tok->start + 2, ref_body); + } catch (std::out_of_range const&) { + ws_warning("Unknown reference %s.", key.c_str()); + return false; + } + } + } + +#ifdef DEBUG_JSON_PARSING + ws_warning("cooked schema: %s\n", schema_blob.c_str()); +#endif + + // XXX Should each sub-schema ref be in its own category? + jv = find_json_object_value(schema_blob, "properties", JSMN_OBJECT); + if (!jv.second) { + ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), schema_blob.c_str()); + return false; + } + int opt_idx = OPT_SCHEMA_PROPERTIES_START; + jv = get_schema_properties(jv.first, opt_idx, "", plugin->name(), plugin_config.property_list); + if (!jv.second) { + ws_warning("ERROR: Interface \"%s\" has an unsupported or invalid configuration schema: %s", plugin->name().c_str(), jv.first.c_str()); + return false; + } + + return true; +} + +// For each loaded plugin, get its name and properties. +static bool get_source_plugins(sinsp &inspector, std::map<std::string, struct plugin_configuration> &plugin_configs) { + const auto plugin_manager = inspector.get_plugin_manager(); + + // XXX sinsp_plugin_manager::sources() can return different names, e.g. aws_cloudtrail vs cloudtrail. + try { + for (auto &plugin : plugin_manager->plugins()) { + if (plugin->caps() & CAP_SOURCING) { + plugin_configuration plugin_config = {}; + if (!get_plugin_config_schema(plugin, plugin_config)) { + return false; + } + plugin_configs[plugin->name()] = plugin_config; + } + } + } catch (sinsp_exception &e) { + ws_warning("%s", e.what()); + return false; + } + return true; +} + +// Build our command line options based on our source plugins. +static const std::vector<ws_option> get_longopts(const std::map<std::string, struct plugin_configuration> &plugin_configs) { + std::vector<ws_option> longopts; + struct ws_option base_longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + { "plugin-api-version", ws_no_argument, NULL, OPT_PLUGIN_API_VERSION}, + { "plugin-source", ws_required_argument, NULL, OPT_PLUGIN_SOURCE }, + { 0, 0, 0, 0} + }; + int idx; + for (idx = 0; base_longopts[idx].name; idx++) { + longopts.push_back(base_longopts[idx]); + } + for (const auto &it : plugin_configs) { + const struct plugin_configuration plugin_configs = it.second; + for (const auto &prop : plugin_configs.property_list) { + ws_option option = { g_strdup(prop.option.c_str()), ws_required_argument, NULL, prop.option_index }; + longopts.push_back(option); + } + } + longopts.push_back(base_longopts[idx]); + return longopts; +} + +// Show the configuration for a given plugin/interface. +static int show_config(const std::string &interface, const struct plugin_configuration &plugin_config) +{ + unsigned arg_num = 0; +// char* plugin_filter; + + if (interface.empty()) { + ws_warning("ERROR: No interface specified."); + return EXIT_FAILURE; + } + + printf( + "arg {number=%u}" + "{call=--plugin-source}" + "{display=Log data URL}" + "{type=string}" + "{tooltip=The plugin data source. This is usually a URL.}" + "{placeholder=Enter a source URL" UTF8_HORIZONTAL_ELLIPSIS "}" + "{required=true}" + "{group=Capture}\n", + arg_num++); +// if (plugin_filter) +// printf("{default=%s}", plugin_filter); + for (const auto &properties : plugin_config.property_list) { + if (properties.option_index < OPT_SCHEMA_PROPERTIES_START) { + continue; + } + std::string default_value; + if (!properties.default_value.empty()) { + default_value = "{default=" + properties.default_value + "}"; + } + if (properties.option == "cloudtrail-aws-profile") { + print_cloudtrail_aws_profile_config(arg_num, properties.display.c_str(), properties.description.c_str()); + } else if (properties.option == "cloudtrail-aws-region") { + print_cloudtrail_aws_region_config(arg_num, properties.display.c_str(), properties.description.c_str()); + } else { + printf( + "arg {number=%d}" + "{call=--%s}" + "{display=%s}" + "{type=%s}" + "%s" + "{tooltip=%s}" + "{group=Capture}" + "\n", + arg_num, properties.option.c_str(), properties.display.c_str(), properties.type.c_str(), default_value.c_str(), properties.description.c_str()); + if (properties.enum_values.size() > 0) { + for (const auto &enum_val : properties.enum_values) { + printf( + "value {arg=%d}" + "{value=%s}" + "{display=%s}" + "%s" + "\n", + arg_num, enum_val.c_str(), enum_val.c_str(), enum_val == default_value ? "{default=true}" : ""); + } + } + } + arg_num++; + } + extcap_config_debug(&arg_num); + + return EXIT_SUCCESS; +} + +int main(int argc, char **argv) +{ + char* configuration_init_error; + int result; + int option_idx = 0; + int ret = EXIT_FAILURE; + extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1); + std::map<std::string, struct plugin_configuration> plugin_configs; + char* help_url; + char* help_header = NULL; + sinsp inspector; + std::string plugin_source; + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("falcodump"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + configuration_init_error = configuration_init(argv[0], "Logray"); + if (configuration_init_error != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + configuration_init_error); + g_free(configuration_init_error); + } + + load_plugins(inspector); + + if (!get_source_plugins(inspector, plugin_configs)) { + goto end; + } + + for (auto iter = plugin_configs.begin(); iter != plugin_configs.end(); ++iter) { + // We don't have a Falco source plugins DLT, so use USER0 (147). + // Additional info available via plugin->description() and plugin->event_source(). + extcap_base_register_interface(extcap_conf, iter->first.c_str(), "Falco plugin", 147, "USER0"); + } + + help_url = data_file_url("falcodump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], FALCODUMP_VERSION_MAJOR, FALCODUMP_VERSION_MINOR, + FALCODUMP_VERSION_RELEASE, help_url); + g_free(help_url); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --fifo=<filename> --capture --plugin-source=<source url>\n", + argv[0], + argv[0], FALCODUMP_PLUGIN_PLACEHOLDER, + argv[0], FALCODUMP_PLUGIN_PLACEHOLDER, + argv[0], FALCODUMP_PLUGIN_PLACEHOLDER); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--plugin-api-version", "print the Falco plugin API version"); + extcap_help_add_option(extcap_conf, "--plugin-source", "plugin source URL"); + + for (const auto &it : plugin_configs) { + const struct plugin_configuration plugin_configs = it.second; + for (const auto &prop : plugin_configs.property_list) { + if (prop.option_index < OPT_SCHEMA_PROPERTIES_START) { + continue; + } + extcap_help_add_option(extcap_conf, g_strdup_printf("--%s", prop.option.c_str()), g_strdup(prop.description.c_str())); + } + } + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + goto end; + } + + static const std::vector<ws_option> longopts = get_longopts(plugin_configs); + while ((result = ws_getopt_long(argc, argv, ":", longopts.data(), &option_idx)) != -1) { + + switch (result) { + + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_VERSION: + extcap_version_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_PLUGIN_API_VERSION: + fprintf(stdout, "Falco plugin API version %s\n", inspector.get_plugin_api_version()); + ret = EXIT_SUCCESS; + goto end; + + case OPT_PLUGIN_SOURCE: + plugin_source = ws_optarg; + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + if (result >= OPT_SCHEMA_PROPERTIES_START) { + bool found = false; + for (auto &it : plugin_configs) { + struct plugin_configuration *plugin_config = &it.second; + for (auto &prop : plugin_config->property_list) { + if (prop.option_index == result) { + prop.current_value = ws_optarg; + found = true; + break; + } + } + if (found) { + break; + } + } + if (!found) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } else if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (plugin_configs.size() < 1) { + ws_warning("No source plugins found."); + goto end; + } + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = show_config(extcap_conf->interface, plugin_configs.at(extcap_conf->interface)); + goto end; + } + + if (extcap_conf->capture) { + if (plugin_source.empty()) { + ws_warning("Missing or invalid parameter: --plugin-source"); + goto end; + } + + std::shared_ptr<sinsp_plugin> plugin_interface; + const auto plugin_manager = inspector.get_plugin_manager(); + for (auto &plugin : plugin_manager->plugins()) { + if (plugin->name() == extcap_conf->interface) { + plugin_interface = plugin; + } + } + + if (plugin_interface == nullptr) { + ws_warning("Unable to find interface %s", extcap_conf->interface); + goto end; + } + + sinsp_dumper dumper; +#ifdef DEBUG_SINSP + inspector.set_debug_mode(true); + inspector.set_log_stderr(); +#endif + try { + std::string init_err; + plugin_interface->init(plugin_configs[extcap_conf->interface].json_config().c_str(), init_err); + if (!init_err.empty()) { + ws_warning("%s", init_err.c_str()); + goto end; + } + inspector.open_plugin(extcap_conf->interface, plugin_source); + // scap_dump_open handles "-" + dumper.open(&inspector, extcap_conf->fifo, false); + } catch (sinsp_exception &e) { + dumper.close(); + ws_warning("%s", e.what()); + goto end; + } + sinsp_evt *evt; + ws_noisy("Starting capture loop."); + while (!extcap_end_application) { + try { + int32_t res = inspector.next(&evt); + switch (res) { + case SCAP_TIMEOUT: + case SCAP_FILTERED_EVENT: + break; + case SCAP_SUCCESS: + dumper.dump(evt); + dumper.flush(); + break; + default: + ws_noisy("Inspector exited with %d", res); + extcap_end_application = true; + break; + } + } catch (sinsp_exception &e) { + ws_warning("%s", e.what()); + goto end; + } + } + ws_noisy("Closing dumper and inspector."); + dumper.close(); + inspector.close(); + ret = EXIT_SUCCESS; + } else { + ws_debug("You should not come here... maybe some parameter missing?"); + } + +end: + /* clean up stuff */ + extcap_base_cleanup(&extcap_conf); + return ret; +} diff --git a/extcap/randpktdump.c b/extcap/randpktdump.c new file mode 100644 index 00000000..6d518350 --- /dev/null +++ b/extcap/randpktdump.c @@ -0,0 +1,368 @@ +/* randpktdump.c + * randpktdump is an extcap tool used to generate random data for testing/educational purpose + * + * Copyright 2015, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN "randpktdump" + +#include "extcap-base.h" + +#include "randpkt_core/randpkt_core.h" +#include <wsutil/strtoi.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/socket.h> +#include <wsutil/please_report_bug.h> +#include <wsutil/wslog.h> + +#include <cli_main.h> +#include <wsutil/cmdarg_err.h> + +#define RANDPKT_EXTCAP_INTERFACE "randpkt" +#define RANDPKTDUMP_VERSION_MAJOR "0" +#define RANDPKTDUMP_VERSION_MINOR "1" +#define RANDPKTDUMP_VERSION_RELEASE "0" + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_MAXBYTES, + OPT_COUNT, + OPT_DELAY, + OPT_RANDOM_TYPE, + OPT_ALL_RANDOM, + OPT_TYPE +}; + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + { "maxbytes", ws_required_argument, NULL, OPT_MAXBYTES}, + { "count", ws_required_argument, NULL, OPT_COUNT}, + { "delay", ws_required_argument, NULL, OPT_DELAY}, + { "random-type", ws_no_argument, NULL, OPT_RANDOM_TYPE}, + { "all-random", ws_no_argument, NULL, OPT_ALL_RANDOM}, + { "type", ws_required_argument, NULL, OPT_TYPE}, + { 0, 0, 0, 0 } +}; + + +static void help(extcap_parameters* extcap_conf) +{ + unsigned i = 0; + char** abbrev_list; + char** longname_list; + + extcap_help_print(extcap_conf); + + printf("\nPacket types:\n"); + randpkt_example_list(&abbrev_list, &longname_list); + while (abbrev_list[i] && longname_list[i]) { + printf("\t%-16s%s\n", abbrev_list[i], longname_list[i]); + i++; + } + printf("\n"); + g_strfreev(abbrev_list); + g_strfreev(longname_list); +} + +static int list_config(char *interface) +{ + unsigned inc = 0; + unsigned i = 0; + char** abbrev_list; + char** longname_list; + + if (!interface) { + ws_warning("No interface specified."); + return EXIT_FAILURE; + } + + if (g_strcmp0(interface, RANDPKT_EXTCAP_INTERFACE)) { + ws_warning("Interface must be %s", RANDPKT_EXTCAP_INTERFACE); + return EXIT_FAILURE; + } + + printf("arg {number=%u}{call=--maxbytes}{display=Max bytes in a packet}" + "{type=unsigned}{range=1,5000}{default=5000}{tooltip=The max number of bytes in a packet}\n", + inc++); + printf("arg {number=%u}{call=--count}{display=Number of packets}" + "{type=long}{default=1000}{tooltip=Number of packets to generate}\n", + inc++); + printf("arg {number=%u}{call=--delay}{display=Packet delay (ms)}" + "{type=long}{default=0}{tooltip=Milliseconds to wait after writing each packet}\n", + inc++); + printf("arg {number=%u}{call=--random-type}{display=Random type}" + "{type=boolflag}{default=false}{tooltip=The packets type is randomly chosen}\n", + inc++); + printf("arg {number=%u}{call=--all-random}{display=All random packets}" + "{type=boolflag}{default=false}{tooltip=Packet type for each packet is randomly chosen}\n", + inc++); + + /* Now the types */ + printf("arg {number=%u}{call=--type}{display=Type of packet}" + "{type=selector}{tooltip=Type of packet to generate}\n", + inc); + randpkt_example_list(&abbrev_list, &longname_list); + while (abbrev_list[i] && longname_list[i]) { + printf("value {arg=%u}{value=%s}{display=%s}\n", inc, abbrev_list[i], longname_list[i]); + i++; + } + g_strfreev(abbrev_list); + g_strfreev(longname_list); + inc++; + + extcap_config_debug(&inc); + + return EXIT_SUCCESS; +} + +static void randpktdump_cmdarg_err(const char *msg_format, va_list ap) +{ + ws_logv(LOG_DOMAIN_CAPCHILD, LOG_LEVEL_WARNING, msg_format, ap); +} + +int main(int argc, char *argv[]) +{ + char* err_msg; + int option_idx = 0; + int result; + uint16_t maxbytes = 5000; + uint64_t count = 1000; + uint64_t packet_delay_ms = 0; + int random_type = false; + int all_random = false; + char* type = NULL; + int produce_type = -1; + int file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_UNKNOWN; + randpkt_example *example; + wtap_dumper* savedump; + int ret = EXIT_FAILURE; + + extcap_parameters * extcap_conf = g_new0(extcap_parameters, 1); + char* help_url; + char* help_header = NULL; + + cmdarg_err_init(randpktdump_cmdarg_err, randpktdump_cmdarg_err); + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("randpktdump"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + err_msg = configuration_init(argv[0], NULL); + if (err_msg != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + err_msg); + g_free(err_msg); + } + + help_url = data_file_url("randpktdump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], RANDPKTDUMP_VERSION_MAJOR, RANDPKTDUMP_VERSION_MINOR, + RANDPKTDUMP_VERSION_RELEASE, help_url); + g_free(help_url); + extcap_base_register_interface(extcap_conf, RANDPKT_EXTCAP_INTERFACE, "Random packet generator", 147, "Generator dependent DLT"); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --type dns --count 10 " + "--fifo=FILENAME --capture\n", argv[0], argv[0], RANDPKT_EXTCAP_INTERFACE, argv[0], RANDPKT_EXTCAP_INTERFACE, + argv[0], RANDPKT_EXTCAP_INTERFACE); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--maxbytes <bytes>", "max bytes per pack"); + extcap_help_add_option(extcap_conf, "--count <num>", "number of packets to generate"); + extcap_help_add_option(extcap_conf, "--delay <ms>", "milliseconds to wait after writing each packet"); + extcap_help_add_option(extcap_conf, "--random-type", "one random type is chosen for all packets"); + extcap_help_add_option(extcap_conf, "--all-random", "a random type is chosen for each packet"); + extcap_help_add_option(extcap_conf, "--type <type>", "the packet type"); + + if (argc == 1) { + help(extcap_conf); + goto end; + } + + while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) { + switch (result) { + case OPT_VERSION: + extcap_version_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_HELP: + help(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_MAXBYTES: + if (!ws_strtou16(ws_optarg, NULL, &maxbytes)) { + ws_warning("Invalid parameter maxbytes: %s (max value is %u)", + ws_optarg, UINT16_MAX); + goto end; + } + break; + + case OPT_COUNT: + if (!ws_strtou64(ws_optarg, NULL, &count)) { + ws_warning("Invalid packet count: %s", ws_optarg); + goto end; + } + break; + + case OPT_DELAY: + if (!ws_strtou64(ws_optarg, NULL, &packet_delay_ms)) { + ws_warning("Invalid packet delay: %s", ws_optarg); + goto end; + } + break; + + case OPT_RANDOM_TYPE: + random_type = true; + break; + + case OPT_ALL_RANDOM: + all_random = true; + break; + + case OPT_TYPE: + g_free(type); + type = g_strdup(ws_optarg); + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + /* Handle extcap specific options */ + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) + { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = list_config(extcap_conf->interface); + goto end; + } + + /* Some sanity checks */ + if ((random_type) && (all_random)) { + ws_warning("You can specify only one between: --random-type, --all-random"); + goto end; + } + + /* Wireshark sets the type, even when random options are selected. We don't want it */ + if (random_type || all_random) { + g_free(type); + type = NULL; + } + + err_msg = ws_init_sockets(); + if (err_msg != NULL) { + ws_warning("ERROR: %s", err_msg); + g_free(err_msg); + ws_warning("%s", please_report_bug()); + goto end; + } + + if (extcap_conf->capture) { + + if (g_strcmp0(extcap_conf->interface, RANDPKT_EXTCAP_INTERFACE)) { + ws_warning("ERROR: invalid interface"); + goto end; + } + + wtap_init(false); + + if (file_type_subtype == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) { + file_type_subtype = wtap_pcapng_file_type_subtype(); + } + + if (!all_random) { + produce_type = randpkt_parse_type(type); + + example = randpkt_find_example(produce_type); + if (!example) + goto end; + + ws_debug("Generating packets: %s", example->abbrev); + + randpkt_example_init(example, extcap_conf->fifo, maxbytes, file_type_subtype); + randpkt_loop(example, count, packet_delay_ms); + randpkt_example_close(example); + } else { + produce_type = randpkt_parse_type(NULL); + example = randpkt_find_example(produce_type); + if (!example) + goto end; + randpkt_example_init(example, extcap_conf->fifo, maxbytes, file_type_subtype); + + while (count-- > 0) { + randpkt_loop(example, 1, packet_delay_ms); + produce_type = randpkt_parse_type(NULL); + + savedump = example->dump; + + example = randpkt_find_example(produce_type); + if (!example) + goto end; + example->dump = savedump; + } + randpkt_example_close(example); + } + ret = EXIT_SUCCESS; + } + +end: + /* clean up stuff */ + g_free(type); + extcap_base_cleanup(&extcap_conf); + + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/extcap/sdjournal.c b/extcap/sdjournal.c new file mode 100644 index 00000000..879fea87 --- /dev/null +++ b/extcap/sdjournal.c @@ -0,0 +1,472 @@ +/* sdjournal.c + * sdjournal is an extcap tool used to dump systemd journal entries. + * + * Adapted from sshdump. + * Copyright 2018, Gerald Combs and Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* + * To do: + * - Add an option for sd_journal_open flags, e.g. SD_JOURNAL_LOCAL_ONLY. + * - Add journalctl options - --boot, --machine, --directory, etc. + */ + +#include "config.h" +#define WS_LOG_DOMAIN "sdjournal" + +#include <extcap/extcap-base.h> +#include <wsutil/interface.h> +#include <wsutil/file_util.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/wslog.h> +#include <writecap/pcapio.h> +#include <wiretap/wtap.h> + +#include <systemd/sd-journal.h> +#include <systemd/sd-id128.h> + +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#define SDJOURNAL_VERSION_MAJOR "1" +#define SDJOURNAL_VERSION_MINOR "0" +#define SDJOURNAL_VERSION_RELEASE "0" + +#define SDJOURNAL_EXTCAP_INTERFACE "sdjournal" +#define BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT 0x00000009 + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_START_FROM +}; + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + { "start-from", ws_required_argument, NULL, OPT_START_FROM}, + { 0, 0, 0, 0} +}; + +#define FLD_BOOT_ID "_BOOT_ID=" +#define FLD_BOOT_ID_LEN (8 + 1 + 33 + 1) + +// The Journal Export Format specification doesn't place limits on entry +// lengths or lines per entry. We do. +#define ENTRY_BUF_LENGTH WTAP_MAX_PACKET_SIZE_STANDARD +#define MAX_EXPORT_ENTRY_LENGTH (ENTRY_BUF_LENGTH - 4 - 4 - 4) // Block type - total length - total length + +static int sdj_dump_entries(sd_journal *jnl, FILE* fp) +{ + int ret = EXIT_SUCCESS; + uint8_t *entry_buff = g_new(uint8_t, ENTRY_BUF_LENGTH); + int jr = 0; + + /* + * Read journal entries and write them as packets. Output must + * match `journalctl --output=export`. + */ + while (jr == 0) { + char *cursor; + uint64_t pkt_rt_ts, mono_ts; + sd_id128_t boot_id; + char boot_id_str[FLD_BOOT_ID_LEN] = FLD_BOOT_ID; + uint32_t block_type = BLOCK_TYPE_SYSTEMD_JOURNAL_EXPORT; + uint32_t data_end = 8; // Block type + total length + const void *fld_data; + size_t fld_len; + uint64_t bytes_written = 0; + int err; + + memcpy(entry_buff, &block_type, 4); + + jr = sd_journal_next(jnl); + ws_debug("sd_journal_next: %d", jr); + if (jr < 0) { + ws_warning("Error fetching journal entry: %s", g_strerror(jr)); + goto end; + } else if (jr == 0) { + sd_journal_wait(jnl, (uint64_t) -1); + continue; + } + + jr = sd_journal_get_cursor(jnl, &cursor); + if (jr < 0) { + ws_warning("Error fetching cursor: %s", g_strerror(jr)); + goto end; + } + data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__CURSOR=%s\n", cursor); + free(cursor); + + jr = sd_journal_get_realtime_usec(jnl, &pkt_rt_ts); + if (jr < 0) { + ws_warning("Error fetching realtime timestamp: %s", g_strerror(jr)); + goto end; + } + data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__REALTIME_TIMESTAMP=%" PRIu64 "\n", pkt_rt_ts); + + jr = sd_journal_get_monotonic_usec(jnl, &mono_ts, &boot_id); + if (jr < 0) { + ws_warning("Error fetching monotonic timestamp: %s", g_strerror(jr)); + goto end; + } + sd_id128_to_string(boot_id, boot_id_str + strlen(FLD_BOOT_ID)); + data_end += snprintf(entry_buff+data_end, MAX_EXPORT_ENTRY_LENGTH-data_end, "__MONOTONIC_TIMESTAMP=%" PRIu64 "\n%s\n", mono_ts, boot_id_str); + ws_debug("Entry header is %u bytes", data_end); + + SD_JOURNAL_FOREACH_DATA(jnl, fld_data, fld_len) { + uint8_t *eq_ptr = (uint8_t *) memchr(fld_data, '=', fld_len); + if (!eq_ptr) { + ws_warning("Invalid field."); + goto end; + } + if (g_utf8_validate((const char *) fld_data, (ssize_t) fld_len, NULL)) { + // Allow for two trailing newlines, one here and one + // at the end of the buffer. + if (fld_len > MAX_EXPORT_ENTRY_LENGTH-data_end-2) { + ws_debug("Breaking on UTF-8 field: %u + %zd", data_end, fld_len); + break; + } + memcpy(entry_buff+data_end, fld_data, fld_len); + data_end += (uint32_t) fld_len; + entry_buff[data_end] = '\n'; + data_end++; + } else { + // \n + 64-bit size + \n + trailing \n = 11 + if (fld_len > MAX_EXPORT_ENTRY_LENGTH-data_end-11) { + ws_debug("Breaking on binary field: %u + %zd", data_end, fld_len); + break; + } + ptrdiff_t name_len = eq_ptr - (const uint8_t *) fld_data; + uint64_t le_data_len; + le_data_len = htole64(fld_len - name_len - 1); + memcpy(entry_buff+data_end, fld_data, name_len); + data_end+= name_len; + entry_buff[data_end] = '\n'; + data_end++; + memcpy(entry_buff+data_end, &le_data_len, 8); + data_end += 8; + memcpy(entry_buff+data_end, (const uint8_t *) fld_data + name_len + 1, fld_len - name_len); + data_end += fld_len - name_len; + } + } + + if (data_end % 4) { + size_t pad_len = 4 - (data_end % 4); + memset(entry_buff+data_end, '\0', pad_len); + data_end += pad_len; + } + + uint32_t total_len = data_end + 4; + memcpy (entry_buff+4, &total_len, 4); + memcpy (entry_buff+data_end, &total_len, 4); + + ws_debug("Attempting to write %u bytes", total_len); + if (!pcapng_write_block(fp, entry_buff, total_len, &bytes_written, &err)) { + ws_warning("Can't write event: %s", strerror(err)); + ret = EXIT_FAILURE; + break; + } + + fflush(fp); + } + +end: + g_free(entry_buff); + return ret; +} + +static int sdj_start_export(const int start_from_entries, const bool start_from_end, const char* fifo) +{ + FILE* fp = stdout; + uint64_t bytes_written = 0; + int err; + sd_journal *jnl = NULL; + sd_id128_t boot_id; + char boot_id_str[FLD_BOOT_ID_LEN] = FLD_BOOT_ID; + int ret = EXIT_FAILURE; + char* err_info = NULL; + char *appname; + bool success; + int jr = 0; + + if (g_strcmp0(fifo, "-")) { + /* Open or create the output file */ + fp = fopen(fifo, "wb"); + if (fp == NULL) { + ws_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno)); + return EXIT_FAILURE; + } + } + + + appname = ws_strdup_printf(SDJOURNAL_EXTCAP_INTERFACE " (Wireshark) %s.%s.%s", + SDJOURNAL_VERSION_MAJOR, SDJOURNAL_VERSION_MINOR, SDJOURNAL_VERSION_RELEASE); + success = pcapng_write_section_header_block(fp, + NULL, /* Comment */ + NULL, /* HW */ + NULL, /* OS */ + appname, + -1, /* section_length */ + &bytes_written, + &err); + g_free(appname); + + if (!success) { + ws_warning("Can't write pcapng file header"); + goto cleanup; + } + + jr = sd_journal_open(&jnl, 0); + if (jr < 0) { + ws_warning("Error opening journal: %s", g_strerror(jr)); + goto cleanup; + } + + jr = sd_id128_get_boot(&boot_id); + if (jr < 0) { + ws_warning("Error fetching system boot ID: %s", g_strerror(jr)); + goto cleanup; + } + + sd_id128_to_string(boot_id, boot_id_str + strlen(FLD_BOOT_ID)); + jr = sd_journal_add_match(jnl, boot_id_str, strlen(boot_id_str)); + if (jr < 0) { + ws_warning("Error adding match: %s", g_strerror(jr)); + goto cleanup; + } + + // According to the documentation, fields *might be* truncated to 64K. + // Let's assume that 2048 is a good balance between fetching entire fields + // and being able to fit as many fields as possible into a packet. + sd_journal_set_data_threshold(jnl, 2048); + + if (start_from_end) { + ws_debug("Attempting to seek %d entries from the end", start_from_entries); + jr = sd_journal_seek_tail(jnl); + if (jr < 0) { + ws_warning("Error starting at end: %s", g_strerror(jr)); + goto cleanup; + } + jr = sd_journal_previous_skip(jnl, (uint64_t) start_from_entries + 1); + if (jr < 0) { + ws_warning("Error skipping backward: %s", g_strerror(jr)); + goto cleanup; + } + } else { + ws_debug("Attempting to seek %d entries from the beginning", start_from_entries); + jr = sd_journal_seek_head(jnl); + if (jr < 0) { + ws_warning("Error starting at beginning: %s", g_strerror(jr)); + goto cleanup; + } + if (start_from_entries > 0) { + jr = sd_journal_next_skip(jnl, (uint64_t) start_from_entries); + if (jr < 0) { + ws_warning("Error skipping forward: %s", g_strerror(jr)); + goto cleanup; + } + } + } + + /* read from channel and write into fp */ + if (sdj_dump_entries(jnl, fp) != 0) { + ws_warning("Error dumping entries"); + goto cleanup; + } + + ret = EXIT_SUCCESS; + +cleanup: + if (jnl) { + sd_journal_close(jnl); + } + + if (err_info) { + ws_warning("%s", err_info); + } + + g_free(err_info); + + /* clean up and exit */ + if (g_strcmp0(fifo, "-")) { + fclose(fp); + } + return ret; +} + +static int list_config(char *interface) +{ + unsigned inc = 0; + + if (!interface) { + ws_warning("ERROR: No interface specified."); + return EXIT_FAILURE; + } + + if (g_strcmp0(interface, SDJOURNAL_EXTCAP_INTERFACE)) { + ws_warning("ERROR: interface must be %s", SDJOURNAL_EXTCAP_INTERFACE); + return EXIT_FAILURE; + } + + printf("arg {number=%u}{call=--start-from}{display=Starting position}" + "{type=string}{tooltip=The journal starting position. Values " + "with a leading \"+\" start from the beginning, similar to the " + "\"tail\" command}{required=false}{group=Journal}\n", inc++); + + extcap_config_debug(&inc); + + return EXIT_SUCCESS; +} + +int main(int argc, char **argv) +{ + char* configuration_init_error; + int result; + int option_idx = 0; + int start_from_entries = 10; + bool start_from_end = true; + int ret = EXIT_FAILURE; + extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1); + char* help_url; + char* help_header = NULL; + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("sdjournal"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + configuration_init_error = configuration_init(argv[0], NULL); + if (configuration_init_error != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + configuration_init_error); + g_free(configuration_init_error); + } + + help_url = data_file_url("sdjournal.html"); + extcap_base_set_util_info(extcap_conf, argv[0], SDJOURNAL_VERSION_MAJOR, SDJOURNAL_VERSION_MINOR, + SDJOURNAL_VERSION_RELEASE, help_url); + g_free(help_url); + // We don't have an SDJOURNAL DLT, so use USER0 (147). + extcap_base_register_interface(extcap_conf, SDJOURNAL_EXTCAP_INTERFACE, "systemd Journal Export", 147, "USER0"); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --start-from=+0 --fifo=FILENAME --capture\n", + argv[0], + argv[0], SDJOURNAL_EXTCAP_INTERFACE, + argv[0], SDJOURNAL_EXTCAP_INTERFACE, + argv[0], SDJOURNAL_EXTCAP_INTERFACE); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--start-from <entry count>", "starting position"); + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + goto end; + } + + while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) { + + switch (result) { + + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_VERSION: + extcap_version_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_START_FROM: + start_from_entries = (int) strtol(ws_optarg, NULL, 10); + if (errno == EINVAL) { + ws_warning("Invalid entry count: %s", ws_optarg); + goto end; + } + if (strlen(ws_optarg) > 0 && ws_optarg[0] == '+') { + start_from_end = false; + } + if (start_from_entries < 0) { + start_from_end = true; + start_from_entries *= -1; + } + ws_debug("start %d from %s", start_from_entries, start_from_end ? "end" : "beginning"); + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = list_config(extcap_conf->interface); + goto end; + } + + if (extcap_conf->capture) { + ret = sdj_start_export(start_from_entries, start_from_end, extcap_conf->fifo); + } else { + ws_debug("You should not come here... maybe some parameter missing?"); + ret = EXIT_FAILURE; + } + +end: + /* clean up stuff */ + extcap_base_cleanup(&extcap_conf); + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/extcap/ssh-base.c b/extcap/ssh-base.c new file mode 100644 index 00000000..124b825d --- /dev/null +++ b/extcap/ssh-base.c @@ -0,0 +1,226 @@ +/* ssh-base.c + * ssh-base has base utility functions to connect to hosts via ssh + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN LOG_DOMAIN_EXTCAP + +#include "ssh-base.h" + +#include <extcap/extcap-base.h> +#include <string.h> +#include <libssh/callbacks.h> +#include <ws_attributes.h> +#include <wsutil/wslog.h> + +static void extcap_log(int priority _U_, const char *function, const char *buffer, void *userdata _U_) +{ + ws_debug("[%s] %s", function, buffer); +} + +void add_libssh_info(extcap_parameters * extcap_conf) +{ + extcap_base_set_compiled_with(extcap_conf, "libssh version %s", SSH_STRINGIFY(LIBSSH_VERSION)); + extcap_base_set_running_with(extcap_conf, "libssh version %s", ssh_version(0)); +} + +ssh_session create_ssh_connection(const ssh_params_t* ssh_params, char** err_info) +{ + ssh_session sshs; + char* username = NULL; + unsigned port; + + /* Open session and set options */ + sshs = ssh_new(); + if (sshs == NULL) { + *err_info = g_strdup("Can't create ssh session"); + return NULL; + } + + if (!ssh_params->host) { + *err_info = g_strdup("Hostname needed"); + goto failure; + } + + if (ssh_options_set(sshs, SSH_OPTIONS_HOST, ssh_params->host)) { + *err_info = ws_strdup_printf("Can't set the host: %s", ssh_params->host); + goto failure; + } + + /* Load the configurations already present in the system configuration file. */ + /* They will be overwritten by the user-provided configurations. */ + if (ssh_options_parse_config(sshs, NULL) != 0) { + *err_info = g_strdup("Unable to load the configuration file"); + goto failure; + } + + if (ssh_params->debug) { + int debug_level = SSH_LOG_INFO; + ssh_options_set(sshs, SSH_OPTIONS_LOG_VERBOSITY, &debug_level); + ssh_set_log_callback(extcap_log); + } + + if (ssh_params->port != 0) { + port = ssh_params->port; + if (ssh_options_set(sshs, SSH_OPTIONS_PORT, &port)) { + *err_info = ws_strdup_printf("Can't set the port: %u", port); + goto failure; + } + } + + if (ssh_params->proxycommand) { + if (ssh_options_set(sshs, SSH_OPTIONS_PROXYCOMMAND, ssh_params->proxycommand)) { + *err_info = ws_strdup_printf("Can't set the ProxyCommand: %s", ssh_params->proxycommand); + goto failure; + } + } + + if (ssh_params->username) { + if (ssh_options_set(sshs, SSH_OPTIONS_USER, ssh_params->username)) { + *err_info = ws_strdup_printf("Can't set the username: %s", ssh_params->username); + goto failure; + } + } + + ssh_options_get(sshs, SSH_OPTIONS_USER, &username); + ssh_options_get_port(sshs, &port); + + ws_log(LOG_DOMAIN_CAPCHILD, LOG_LEVEL_INFO, "Opening ssh connection to %s@%s:%u", username, + ssh_params->host, port); + + ssh_string_free_char(username); + + /* Connect to server */ + if (ssh_connect(sshs) != SSH_OK) { + *err_info = ws_strdup_printf("Connection error: %s", ssh_get_error(sshs)); + goto failure; + } + + /* If a public key path has been provided, try to authenticate using it */ + if (ssh_params->sshkey_path) { + ssh_key pkey = ssh_key_new(); + int ret; + + ws_info("Connecting using public key in %s...", ssh_params->sshkey_path); + ret = ssh_pki_import_privkey_file(ssh_params->sshkey_path, ssh_params->sshkey_passphrase, NULL, NULL, &pkey); + + if (ret == SSH_OK) { + if (ssh_userauth_publickey(sshs, NULL, pkey) == SSH_AUTH_SUCCESS) { + ws_info("done"); + ssh_key_free(pkey); + return sshs; + } + } + ssh_key_free(pkey); + ws_info("failed (%s)", ssh_get_error(sshs)); + } + + /* Workaround: it may happen that libssh closes socket in meantime and any next ssh_ call fails so we should detect it in advance */ + if (ssh_get_fd(sshs) != (socket_t)-1) { + /* If a password has been provided and all previous attempts failed, try to use it */ + if (ssh_params->password) { + ws_info("Connecting using password..."); + if (ssh_userauth_password(sshs, ssh_params->username, ssh_params->password) == SSH_AUTH_SUCCESS) { + ws_info("done"); + return sshs; + } + ws_info("failed"); + } + } else { + ws_info("ssh connection closed before password authentication"); + } + + /* Workaround: it may happen that libssh closes socket in meantime and any next ssh_ call fails so we should detect it in advance */ + if (ssh_get_fd(sshs) != (socket_t)-1) { + /* Try to authenticate using standard public key */ + ws_info("Connecting using standard public key..."); + if (ssh_userauth_publickey_auto(sshs, NULL, NULL) == SSH_AUTH_SUCCESS) { + ws_info("done"); + return sshs; + } + ws_info("failed"); + } else { + ws_info("ssh connection closed before public key authentication"); + } + + *err_info = ws_strdup_printf("Can't find a valid authentication. Disconnecting."); + + /* All authentication failed. Disconnect and return */ + ssh_disconnect(sshs); + +failure: + ssh_free(sshs); + return NULL; +} + +int ssh_channel_printf(ssh_channel channel, const char* fmt, ...) +{ + char* buf; + va_list arg; + int ret = EXIT_SUCCESS; + + va_start(arg, fmt); + buf = ws_strdup_vprintf(fmt, arg); + ws_debug("%s", buf); + if (ssh_channel_write(channel, buf, (uint32_t)strlen(buf)) == SSH_ERROR) + ret = EXIT_FAILURE; + va_end(arg); + g_free(buf); + + return ret; +} + +void ssh_cleanup(ssh_session* sshs, ssh_channel* channel) +{ + if (*channel) { + ssh_channel_send_eof(*channel); + ssh_channel_close(*channel); + ssh_channel_free(*channel); + *channel = NULL; + } + + if (*sshs) { + ssh_disconnect(*sshs); + ssh_free(*sshs); + *sshs = NULL; + } +} + +ssh_params_t* ssh_params_new(void) +{ + return g_new0(ssh_params_t, 1); +} + +void ssh_params_free(ssh_params_t* ssh_params) +{ + if (!ssh_params) + return; + g_free(ssh_params->host); + g_free(ssh_params->username); + g_free(ssh_params->password); + g_free(ssh_params->sshkey_path); + g_free(ssh_params->sshkey_passphrase); + g_free(ssh_params->proxycommand); + g_free(ssh_params); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/extcap/ssh-base.h b/extcap/ssh-base.h new file mode 100644 index 00000000..8283bffc --- /dev/null +++ b/extcap/ssh-base.h @@ -0,0 +1,85 @@ +/** @file + * + * ssh-base has base utility functions to connect to hosts via ssh + * + * Copyright 2016, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __SSHBASE_H__ +#define __SSHBASE_H__ + +#include <libssh/libssh.h> + +#include <glib.h> + +#include <extcap/extcap-base.h> + +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif + +#define SSH_BASE_OPTIONS \ + { "remote-host", ws_required_argument, NULL, OPT_REMOTE_HOST}, \ + { "remote-port", ws_required_argument, NULL, OPT_REMOTE_PORT}, \ + { "remote-username", ws_required_argument, NULL, OPT_REMOTE_USERNAME}, \ + { "remote-password", ws_required_argument, NULL, OPT_REMOTE_PASSWORD}, \ + { "remote-interface", ws_required_argument, NULL, OPT_REMOTE_INTERFACE}, \ + { "remote-filter", ws_required_argument, NULL, OPT_REMOTE_FILTER}, \ + { "remote-count", ws_required_argument, NULL, OPT_REMOTE_COUNT}, \ + { "sshkey", ws_required_argument, NULL, OPT_SSHKEY}, \ + { "sshkey-passphrase", ws_required_argument, NULL, OPT_SSHKEY_PASSPHRASE}, \ + { "proxycommand", ws_required_argument, NULL, OPT_PROXYCOMMAND} + +typedef struct _ssh_params { + char* host; + uint16_t port; + char* username; + char* password; + char* sshkey_path; + char* sshkey_passphrase; + char* proxycommand; + bool debug; +} ssh_params_t; + +/* Add libssh version information to an extcap_parameters structure */ +void add_libssh_info(extcap_parameters * extcap_conf); + +/* Create a ssh connection using all the possible authentication menthods */ +ssh_session create_ssh_connection(const ssh_params_t* ssh_params, char** err_info); + +/* Write a formatted message in the channel */ +int ssh_channel_printf(ssh_channel channel, const char* fmt, ...); + +/* Clean the current ssh session and channel. */ +void ssh_cleanup(ssh_session* sshs, ssh_channel* channel); + +/* Init the ssh_params_t structure */ +ssh_params_t* ssh_params_new(void); + +/* Clean the ssh params */ +void ssh_params_free(ssh_params_t* ssh_params); + +#endif + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/extcap/sshdump.c b/extcap/sshdump.c new file mode 100644 index 00000000..12864731 --- /dev/null +++ b/extcap/sshdump.c @@ -0,0 +1,694 @@ +/* sshdump.c + * sshdump is extcap tool used to capture data using a remote ssh host + * + * Copyright 2015, Dario Lombardo + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN "sshdump" + +#include <extcap/extcap-base.h> +#include <extcap/ssh-base.h> +#include <wsutil/interface.h> +#include <wsutil/file_util.h> +#include <wsutil/strtoi.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/please_report_bug.h> +#include <wsutil/wslog.h> + +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include <cli_main.h> + +static char* sshdump_extcap_interface; +#ifdef _WIN32 +#define DEFAULT_SSHDUMP_EXTCAP_INTERFACE "sshdump.exe" +#else +#define DEFAULT_SSHDUMP_EXTCAP_INTERFACE "sshdump" +#endif + +#define SSHDUMP_VERSION_MAJOR "1" +#define SSHDUMP_VERSION_MINOR "2" +#define SSHDUMP_VERSION_RELEASE "0" + +#define SSH_READ_BLOCK_SIZE 256 + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_REMOTE_HOST, + OPT_REMOTE_PORT, + OPT_REMOTE_USERNAME, + OPT_REMOTE_PASSWORD, + OPT_REMOTE_INTERFACE, + OPT_REMOTE_CAPTURE_COMMAND_SELECT, + OPT_REMOTE_CAPTURE_COMMAND, + OPT_REMOTE_FILTER, + OPT_SSHKEY, + OPT_SSHKEY_PASSPHRASE, + OPT_PROXYCOMMAND, + OPT_REMOTE_COUNT, + OPT_REMOTE_SUDO, // Deprecated + OPT_REMOTE_PRIV, + OPT_REMOTE_PRIV_USER, + OPT_REMOTE_NOPROM +}; + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + SSH_BASE_OPTIONS, + { "remote-capture-command-select", ws_required_argument, NULL, OPT_REMOTE_CAPTURE_COMMAND_SELECT}, + { "remote-capture-command", ws_required_argument, NULL, OPT_REMOTE_CAPTURE_COMMAND}, + { "remote-sudo", ws_no_argument, NULL, OPT_REMOTE_SUDO }, // Deprecated + { "remote-priv", ws_required_argument, NULL, OPT_REMOTE_PRIV }, + { "remote-priv-user", ws_required_argument, NULL, OPT_REMOTE_PRIV_USER }, + { "remote-noprom", ws_no_argument, NULL, OPT_REMOTE_NOPROM }, + { 0, 0, 0, 0} +}; + +static char* interfaces_list_to_filter(GSList* if_list, unsigned int remote_port); + +static int ssh_loop_read(ssh_channel channel, FILE* fp) +{ + int nbytes; + int ret = EXIT_SUCCESS; + char buffer[SSH_READ_BLOCK_SIZE]; + + /* read from stdin until data are available */ + while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) { + nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 0); + if (nbytes < 0) { + ws_warning("Error reading from channel"); + goto end; + } + if (nbytes == 0) { + break; + } + if (fwrite(buffer, 1, nbytes, fp) != (unsigned)nbytes) { + ws_warning("Error writing to fifo"); + ret = EXIT_FAILURE; + goto end; + } + fflush(fp); + } + + /* read loop finished... maybe something wrong happened. Read from stderr */ + while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) { + nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 1); + if (nbytes < 0) { + ws_warning("Error reading from channel"); + goto end; + } + if (fwrite(buffer, 1, nbytes, stderr) != (unsigned)nbytes) { + ws_warning("Error writing to stderr"); + break; + } + } + +end: + if (ssh_channel_send_eof(channel) != SSH_OK) { + ws_warning("Error sending EOF in ssh channel"); + ret = EXIT_FAILURE; + } + return ret; +} + +static char* local_interfaces_to_filter(const uint16_t remote_port) +{ + GSList* interfaces = local_interfaces_to_list(); + char* filter = interfaces_list_to_filter(interfaces, remote_port); + g_slist_free_full(interfaces, g_free); + return filter; +} + +static ssh_channel run_ssh_command(ssh_session sshs, const char* capture_command_select, + const char* capture_command, const char* privilege, bool noprom, + const char* iface, const char* cfilter, const uint32_t count) +{ + char* cmdline = NULL; + ssh_channel channel; + char** ifaces_array = NULL; + int ifaces_array_num = 0; + GString *ifaces_string; + char *ifaces = NULL; + char* quoted_iface = NULL; + char* quoted_filter = NULL; + char* count_str = NULL; + unsigned int remote_port = 22; + + channel = ssh_channel_new(sshs); + if (!channel) { + ws_warning("Can't create channel"); + return NULL; + } + + if (ssh_channel_open_session(channel) != SSH_OK) { + ws_warning("Can't open session"); + ssh_channel_free(channel); + return NULL; + } + + ssh_options_get_port(sshs, &remote_port); + + if (capture_command_select == NULL || !g_strcmp0(capture_command_select, "other")) { + if (capture_command && *capture_command) { + cmdline = g_strdup(capture_command); + ws_debug("Remote capture command has disabled other options"); + } else { + capture_command_select = "tcpdump"; + } + } + + /* escape parameters to go save with the shell */ + if (!g_strcmp0(capture_command_select, "tcpdump")) { + quoted_iface = iface ? g_shell_quote(iface) : NULL; + quoted_filter = g_shell_quote(cfilter ? cfilter : ""); + if (count > 0) + count_str = ws_strdup_printf("-c %u", count); + + cmdline = ws_strdup_printf("%s tcpdump -U %s%s %s -w - %s %s", + privilege, + quoted_iface ? "-i " : "", + quoted_iface ? quoted_iface : "", + noprom ? "-p" : "", + count_str ? count_str : "", + quoted_filter); + } else if (!g_strcmp0(capture_command_select, "dumpcap")) { + if (iface) { + ifaces_array = g_strsplit(iface, " ", -1); + ifaces_string = g_string_new(NULL); + while (ifaces_array[ifaces_array_num]) + { + quoted_iface = g_shell_quote(ifaces_array[ifaces_array_num]); + g_string_append_printf(ifaces_string, "-i %s ", quoted_iface); + ifaces_array_num++; + } + ifaces = g_string_free(ifaces_string, false); + } + quoted_filter = g_shell_quote(cfilter ? cfilter : ""); + if (count > 0) + count_str = ws_strdup_printf("-c %u", count); + + cmdline = ws_strdup_printf("%s dumpcap %s %s -w - %s -f %s", + privilege, + noprom ? "-p" : "", + ifaces ? ifaces : "", + count_str ? count_str : "", + quoted_filter); + + g_free(ifaces); + g_strfreev(ifaces_array); + } + + ws_debug("Running: %s", cmdline); + if (ssh_channel_request_exec(channel, cmdline) != SSH_OK) { + ws_warning("Can't request exec"); + ssh_channel_close(channel); + ssh_channel_free(channel); + channel = NULL; + } + + g_free(quoted_iface); + g_free(quoted_filter); + g_free(cmdline); + g_free(count_str); + + return channel; +} + +static int ssh_open_remote_connection(const ssh_params_t* params, const char* iface, const char* cfilter, + const char* capture_command_select, const char* capture_command, const char* privilege, + bool noprom, const uint32_t count, const char* fifo) +{ + ssh_session sshs = NULL; + ssh_channel channel = NULL; + FILE* fp = stdout; + int ret = EXIT_FAILURE; + char* err_info = NULL; + + if (g_strcmp0(fifo, "-")) { + /* Open or create the output file */ + fp = fopen(fifo, "wb"); + if (fp == NULL) { + ws_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno)); + return EXIT_FAILURE; + } + } + + sshs = create_ssh_connection(params, &err_info); + + if (!sshs) { + ws_warning("Error creating connection."); + goto cleanup; + } + + channel = run_ssh_command(sshs, capture_command_select, capture_command, privilege, noprom, iface, cfilter, count); + + if (!channel) { + ws_warning("Can't run ssh command."); + goto cleanup; + } + + /* read from channel and write into fp */ + if (ssh_loop_read(channel, fp) != EXIT_SUCCESS) { + ws_warning("Error in read loop."); + ret = EXIT_FAILURE; + goto cleanup; + } + + ret = EXIT_SUCCESS; +cleanup: + if (err_info) + ws_warning("%s", err_info); + g_free(err_info); + + /* clean up and exit */ + ssh_cleanup(&sshs, &channel); + + if (g_strcmp0(fifo, "-")) + fclose(fp); + return ret; +} + +static char* interfaces_list_to_filter(GSList* interfaces, unsigned int remote_port) +{ + GString* filter = g_string_new(NULL); + GSList* cur; + + // If no port is given, assume the default one. This might not be + // correct if the port is looked up from the ssh config file, but it is + // better than nothing. + if (remote_port == 0) { + remote_port = 22; + } + + if (!interfaces) { + g_string_append_printf(filter, "not port %u", remote_port); + } else { + g_string_append_printf(filter, "not ((host %s", (char*)interfaces->data); + cur = g_slist_next(interfaces); + while (cur) { + g_string_append_printf(filter, " or host %s", (char*)cur->data); + cur = g_slist_next(cur); + } + g_string_append_printf(filter, ") and port %u)", remote_port); + } + return g_string_free(filter, false); +} + +static int list_config(char *interface, unsigned int remote_port) +{ + unsigned inc = 0; + char* ipfilter; + + if (!interface) { + ws_warning("ERROR: No interface specified."); + return EXIT_FAILURE; + } + + if (g_strcmp0(interface, sshdump_extcap_interface)) { + ws_warning("ERROR: interface must be %s", sshdump_extcap_interface); + return EXIT_FAILURE; + } + + ipfilter = local_interfaces_to_filter(remote_port); + + printf("arg {number=%u}{call=--remote-host}{display=Remote SSH server address}" + "{type=string}{tooltip=The remote SSH host. It can be both " + "an IP address or a hostname}{required=true}{group=Server}\n", inc++); + printf("arg {number=%u}{call=--remote-port}{display=Remote SSH server port}" + "{type=unsigned}{default=22}{tooltip=The remote SSH host port (1-65535)}" + "{range=1,65535}{group=Server}\n", inc++); + printf("arg {number=%u}{call=--remote-username}{display=Remote SSH server username}" + "{type=string}{tooltip=The remote SSH username. If not provided, " + "the current user will be used}{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--remote-password}{display=Remote SSH server password}" + "{type=password}{tooltip=The SSH password, used when other methods (SSH agent " + "or key files) are unavailable.}{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--sshkey}{display=Path to SSH private key}" + "{type=fileselect}{tooltip=The path on the local filesystem of the private SSH key (OpenSSH format)}" + "{mustexist=true}{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--sshkey-passphrase}{display=SSH key passphrase}" + "{type=password}{tooltip=Passphrase to unlock the SSH private key}{group=Authentication}\n", + inc++); + printf("arg {number=%u}{call=--proxycommand}{display=ProxyCommand}" + "{type=string}{tooltip=The command to use as proxy for the SSH connection}" + "{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}" + "{type=string}{tooltip=The remote network interface used for capture" + "}{group=Capture}\n", inc++); + printf("arg {number=%u}{call=--remote-capture-command-select}{display=Remote capture command selection}" + "{type=radio}{tooltip=The remote capture command to build a command line for}{group=Capture}\n", inc); + printf("value {arg=%u}{value=dumpcap}{display=dumpcap}\n", inc); + printf("value {arg=%u}{value=tcpdump}{display=tcpdump}{default=true}\n", inc); + printf("value {arg=%u}{value=other}{display=Other:}\n", inc++); + printf("arg {number=%u}{call=--remote-capture-command}{display=Remote capture command}" + "{type=string}{tooltip=The remote command used to capture}{group=Capture}\n", inc++); + // Deprecated + //printf("arg {number=%u}{call=--remote-sudo}{display=Use sudo on the remote machine}" + // "{type=boolflag}{tooltip=Prepend the capture command with sudo on the remote machine}" + // "{group=Capture}\n", inc++); + printf("arg {number=%u}{call=--remote-priv}{display=Gain capture privilege on the remote machine}" + "{type=radio}{tooltip=Optionally prepend the capture command with sudo or doas on the remote machine}" + "{group=Capture}\n", inc); + printf("value {arg=%u}{value=none}{display=none}{default=true}\n", inc); + printf("value {arg=%u}{value=sudo}{display=sudo}\n", inc); + printf("value {arg=%u}{value=doas -n}{display=doas}\n", inc++); + printf("arg {number=%u}{call=--remote-priv-user}{display=Privileged user name for sudo or doas}" + "{type=string}{tooltip=User name of privileged user to execute the capture command on the remote machine}" + "{group=Capture}\n", inc++); + printf("arg {number=%u}{call=--remote-noprom}{display=No promiscuous mode}" + "{type=boolflag}{tooltip=Don't use promiscuous mode on the remote machine}{group=Capture}" + "\n", inc++); + printf("arg {number=%u}{call=--remote-filter}{display=Remote capture filter}{type=string}" + "{tooltip=The remote capture filter}", inc++); + if (ipfilter) + printf("{default=%s}", ipfilter); + printf("{group=Capture}\n"); + printf("arg {number=%u}{call=--remote-count}{display=Packets to capture}" + "{type=unsigned}{default=0}{tooltip=The number of remote packets to capture. (Default: inf)}" + "{group=Capture}\n", inc++); + + extcap_config_debug(&inc); + + g_free(ipfilter); + + return EXIT_SUCCESS; +} + +static char* concat_filters(const char* extcap_filter, const char* remote_filter) +{ + if (!extcap_filter && remote_filter) + return g_strdup(remote_filter); + + if (!remote_filter && extcap_filter) + return g_strdup(extcap_filter); + + if (!remote_filter && !extcap_filter) + return NULL; + + return ws_strdup_printf("(%s) and (%s)", extcap_filter, remote_filter); +} + +int main(int argc, char *argv[]) +{ + char* err_msg; + int result; + int option_idx = 0; + ssh_params_t* ssh_params = ssh_params_new(); + char* remote_interface = NULL; + char* remote_capture_command_select = NULL; + char* remote_capture_command = NULL; + char* remote_filter = NULL; + uint32_t count = 0; + int ret = EXIT_FAILURE; + extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1); + char* help_url; + char* help_header = NULL; + char* priv = NULL; + char* priv_user = NULL; + bool noprom = false; + char* interface_description = g_strdup("SSH remote capture"); + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("sshdump"); + + sshdump_extcap_interface = g_path_get_basename(argv[0]); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + err_msg = configuration_init(argv[0], NULL); + if (err_msg != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + err_msg); + g_free(err_msg); + } + + help_url = data_file_url("sshdump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], SSHDUMP_VERSION_MAJOR, SSHDUMP_VERSION_MINOR, + SSHDUMP_VERSION_RELEASE, help_url); + g_free(help_url); + add_libssh_info(extcap_conf); + if (g_strcmp0(sshdump_extcap_interface, DEFAULT_SSHDUMP_EXTCAP_INTERFACE)) { + char* temp = interface_description; + interface_description = ws_strdup_printf("%s, custom version", interface_description); + g_free(temp); + } + extcap_base_register_interface(extcap_conf, sshdump_extcap_interface, interface_description, 147, "Remote capture dependent DLT"); + g_free(interface_description); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --remote-host myhost --remote-port 22222 " + "--remote-username myuser --remote-interface eth2 --remote-capture-command 'tcpdump -U -i eth0 -w -' " + "--fifo=FILENAME --capture\n", argv[0], argv[0], sshdump_extcap_interface, argv[0], + sshdump_extcap_interface, argv[0], sshdump_extcap_interface); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--remote-host <host>", "the remote SSH host"); + extcap_help_add_option(extcap_conf, "--remote-port <port>", "the remote SSH port"); + extcap_help_add_option(extcap_conf, "--remote-username <username>", "the remote SSH username"); + extcap_help_add_option(extcap_conf, "--remote-password <password>", "the remote SSH password. If not specified, ssh-agent and ssh-key are used"); + extcap_help_add_option(extcap_conf, "--sshkey <private key path>", "the path of the SSH key (OpenSSH format)"); + extcap_help_add_option(extcap_conf, "--sshkey-passphrase <private key passphrase>", "the passphrase to unlock private SSH key"); + extcap_help_add_option(extcap_conf, "--proxycommand <proxy command>", "the command to use as proxy for the SSH connection"); + extcap_help_add_option(extcap_conf, "--remote-interface <iface>", "the remote capture interface"); + extcap_help_add_option(extcap_conf, "--remote-capture-command-select <selection>", "dumpcap, tcpdump or other remote capture command"); + extcap_help_add_option(extcap_conf, "--remote-capture-command <capture command>", "the remote capture command"); + //extcap_help_add_option(extcap_conf, "--remote-sudo", "use sudo on the remote machine to capture"); // Deprecated + extcap_help_add_option(extcap_conf, "--remote-priv <selection>", "none, sudo or doas"); + extcap_help_add_option(extcap_conf, "--remote-priv-user <username>", "privileged user name"); + extcap_help_add_option(extcap_conf, "--remote-noprom", "don't use promiscuous mode on the remote machine"); + extcap_help_add_option(extcap_conf, "--remote-filter <filter>", "a filter for remote capture (default: don't listen on local interfaces IPs)"); + extcap_help_add_option(extcap_conf, "--remote-count <count>", "the number of packets to capture"); + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + goto end; + } + + while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) { + + switch (result) { + + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_VERSION: + extcap_version_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_REMOTE_HOST: + g_free(ssh_params->host); + ssh_params->host = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_PORT: + if (!ws_strtou16(ws_optarg, NULL, &ssh_params->port) || ssh_params->port == 0) { + ws_warning("Invalid port: %s", ws_optarg); + goto end; + } + break; + + case OPT_REMOTE_USERNAME: + g_free(ssh_params->username); + ssh_params->username = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_PASSWORD: + g_free(ssh_params->password); + ssh_params->password = g_strdup(ws_optarg); + memset(ws_optarg, 'X', strlen(ws_optarg)); + break; + + case OPT_SSHKEY: + g_free(ssh_params->sshkey_path); + ssh_params->sshkey_path = g_strdup(ws_optarg); + break; + + case OPT_SSHKEY_PASSPHRASE: + g_free(ssh_params->sshkey_passphrase); + ssh_params->sshkey_passphrase = g_strdup(ws_optarg); + memset(ws_optarg, 'X', strlen(ws_optarg)); + break; + + case OPT_PROXYCOMMAND: + g_free(ssh_params->proxycommand); + ssh_params->proxycommand = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_INTERFACE: + g_free(remote_interface); + remote_interface = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_CAPTURE_COMMAND_SELECT: + g_free(remote_capture_command_select); + remote_capture_command_select = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_CAPTURE_COMMAND: + g_free(remote_capture_command); + remote_capture_command = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_SUDO: + // Deprecated + g_free(priv); + priv = g_strdup("sudo"); + break; + + case OPT_REMOTE_PRIV: + g_free(priv); + priv = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_PRIV_USER: + g_free(priv_user); + priv_user = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_FILTER: + g_free(remote_filter); + remote_filter = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_COUNT: + if (!ws_strtou32(ws_optarg, NULL, &count)) { + ws_warning("Invalid value for count: %s", ws_optarg); + goto end; + } + break; + + case OPT_REMOTE_NOPROM: + noprom = true; + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = list_config(extcap_conf->interface, ssh_params->port); + goto end; + } + + err_msg = ws_init_sockets(); + if (err_msg != NULL) { + ws_warning("ERROR: %s", err_msg); + g_free(err_msg); + ws_warning("%s", please_report_bug()); + goto end; + } + + if (extcap_conf->capture) { + char* filter; + char* privilege; + + if (!ssh_params->host) { + ws_warning("Missing parameter: --remote-host"); + goto end; + } + + if ((priv) && g_strcmp0(priv, "none") && strlen(g_strstrip(priv))) { + if ((priv_user) && strlen(g_strstrip(priv_user))) + /* Both sudo and doas use the same command line option */ + privilege = g_strconcat(priv, " -u ", priv_user, NULL); + else + privilege = g_strdup(priv); + } else { + privilege = g_strdup(""); + } + + // This may result in the use of a different port number than was given in + // the default filter string, as presented in the config dialog. The default + // given is always using the default SSH port since there's no remote SSH port + // given on the command line to get the extcap arguments. + // However the remote SSH port used here is the one given on the command line + // when the capture us started, which is the indended one. + // And this is only happening when no remote filter is specified on the command + // line to start the capture. + if (remote_filter == NULL) + remote_filter = local_interfaces_to_filter(ssh_params->port); + filter = concat_filters(extcap_conf->capture_filter, remote_filter); + ssh_params->debug = extcap_conf->debug; + ret = ssh_open_remote_connection(ssh_params, remote_interface, + filter, remote_capture_command_select, remote_capture_command, + privilege, noprom, count, extcap_conf->fifo); + g_free(filter); + g_free(privilege); + } else { + ws_debug("You should not come here... maybe some parameter missing?"); + ret = EXIT_FAILURE; + } + +end: + /* clean up stuff */ + ssh_params_free(ssh_params); + g_free(remote_capture_command_select); + g_free(remote_capture_command); + g_free(remote_interface); + g_free(remote_filter); + g_free(priv); + g_free(priv_user); + extcap_base_cleanup(&extcap_conf); + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/extcap/udpdump.c b/extcap/udpdump.c new file mode 100644 index 00000000..7f4602ac --- /dev/null +++ b/extcap/udpdump.c @@ -0,0 +1,490 @@ +/* udpdump.c + * udpdump is an extcap tool used to get packets exported from a source (like a network device or a GSMTAP producer) that + * are dumped to a pcap file + * + * Copyright 2016, Dario Lombardo <lomato@gmail.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN "udpdump" + +#include <extcap/extcap-base.h> + +#include <glib.h> +#include <glib/gprintf.h> +#include <stdlib.h> + +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#endif + +#ifdef HAVE_NETINET_IN_H + #include <netinet/in.h> +#endif + +#include <string.h> +#include <errno.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <writecap/pcapio.h> +#include <wiretap/wtap.h> +#include <wsutil/strtoi.h> +#include <wsutil/inet_addr.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/socket.h> +#include <wsutil/please_report_bug.h> +#include <wsutil/wslog.h> +#include <wsutil/pint.h> +#include <wsutil/exported_pdu_tlvs.h> + +#include <cli_main.h> + +#define PCAP_SNAPLEN 0xffff + +#define UDPDUMP_DEFAULT_PORT 5555 + +#define UDPDUMP_EXTCAP_INTERFACE "udpdump" +#define UDPDUMP_VERSION_MAJOR "0" +#define UDPDUMP_VERSION_MINOR "1" +#define UDPDUMP_VERSION_RELEASE "0" + +#define PKT_BUF_SIZE 65535 + +#define UDPDUMP_EXPORT_HEADER_LEN 40 + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_PORT, + OPT_PAYLOAD +}; + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + /* Generic application options */ + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + /* Interfaces options */ + { "port", ws_required_argument, NULL, OPT_PORT}, + { "payload", ws_required_argument, NULL, OPT_PAYLOAD}, + { 0, 0, 0, 0 } +}; + +static int list_config(char *interface) +{ + unsigned inc = 0; + + if (!interface) { + ws_warning("No interface specified."); + return EXIT_FAILURE; + } + + printf("arg {number=%u}{call=--port}{display=Listen port}" + "{type=unsigned}{range=1,65535}{default=%u}{tooltip=The port the receiver listens on}\n", + inc++, UDPDUMP_DEFAULT_PORT); + printf("arg {number=%u}{call=--payload}{display=Payload type}" + "{type=string}{default=data}{tooltip=The type used to describe the payload in the exported pdu format}\n", + inc++); + + extcap_config_debug(&inc); + + return EXIT_SUCCESS; +} + +static int setup_listener(const uint16_t port, socket_handle_t* sock) +{ + int optval; + struct sockaddr_in serveraddr; +#ifndef _WIN32 + struct timeval timeout = { 1, 0 }; +#endif + + *sock = socket(AF_INET, SOCK_DGRAM, 0); + + if (*sock == INVALID_SOCKET) { + ws_warning("Error opening socket: %s", strerror(errno)); + return EXIT_FAILURE; + } + + optval = 1; + if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, (socklen_t)sizeof(int)) < 0) { + ws_warning("Can't set socket option SO_REUSEADDR: %s", strerror(errno)); + goto cleanup_setup_listener; + } + +#ifndef _WIN32 + if (setsockopt (*sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, (socklen_t)sizeof(timeout)) < 0) { + ws_warning("Can't set socket option SO_RCVTIMEO: %s", strerror(errno)); + goto cleanup_setup_listener; + } +#endif + + memset(&serveraddr, 0x0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); + serveraddr.sin_port = htons(port); + + if (bind(*sock, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr)) < 0) { + ws_warning("Error on binding: %s", strerror(errno)); + goto cleanup_setup_listener; + } + + return EXIT_SUCCESS; + +cleanup_setup_listener: + closesocket(*sock); + return EXIT_FAILURE; + +} + +static int setup_dumpfile(const char* fifo, FILE** fp) +{ + uint64_t bytes_written = 0; + int err; + + if (!g_strcmp0(fifo, "-")) { + *fp = stdout; + return EXIT_SUCCESS; + } + + *fp = fopen(fifo, "wb"); + if (!(*fp)) { + ws_warning("Error creating output file: %s", g_strerror(errno)); + return EXIT_FAILURE; + } + + if (!libpcap_write_file_header(*fp, 252, PCAP_SNAPLEN, false, &bytes_written, &err)) { + ws_warning("Can't write pcap file header: %s", g_strerror(err)); + return EXIT_FAILURE; + } + + fflush(*fp); + + return EXIT_SUCCESS; +} + +static void add_proto_name(uint8_t* mbuf, unsigned* offset, const char* proto_name) +{ + size_t proto_str_len = strlen(proto_name); + uint16_t proto_name_len = (uint16_t)((proto_str_len + 3) & 0xfffffffc); + + phton16(mbuf + *offset, EXP_PDU_TAG_DISSECTOR_NAME); + *offset += 2; + phton16(mbuf + *offset, proto_name_len); + *offset += 2; + + memcpy(mbuf + *offset, proto_name, proto_str_len); + *offset += proto_name_len; +} + +static void add_ip_source_address(uint8_t* mbuf, unsigned* offset, uint32_t source_address) +{ + phton16(mbuf + *offset, EXP_PDU_TAG_IPV4_SRC); + *offset += 2; + phton16(mbuf + *offset, 4); + *offset += 2; + memcpy(mbuf + *offset, &source_address, 4); + *offset += 4; +} + +static void add_ip_dest_address(uint8_t* mbuf, unsigned* offset, uint32_t dest_address) +{ + phton16(mbuf + *offset, EXP_PDU_TAG_IPV4_DST); + *offset += 2; + phton16(mbuf + *offset, 4); + *offset += 2; + memcpy(mbuf + *offset, &dest_address, 4); + *offset += 4; +} + +static void add_udp_source_port(uint8_t* mbuf, unsigned* offset, uint16_t src_port) +{ + uint32_t port = htonl(src_port); + + phton16(mbuf + *offset, EXP_PDU_TAG_SRC_PORT); + *offset += 2; + phton16(mbuf + *offset, 4); + *offset += 2; + memcpy(mbuf + *offset, &port, 4); + *offset += 4; +} + +static void add_udp_dst_port(uint8_t* mbuf, unsigned* offset, uint16_t dst_port) +{ + uint32_t port = htonl(dst_port); + + phton16(mbuf + *offset, EXP_PDU_TAG_DST_PORT); + *offset += 2; + phton16(mbuf + *offset, 4); + *offset += 2; + memcpy(mbuf + *offset, &port, 4); + *offset += 4; +} + +static void add_end_options(uint8_t* mbuf, unsigned* offset) +{ + memset(mbuf + *offset, 0x0, 4); + *offset += 4; +} + +static int dump_packet(const char* proto_name, const uint16_t listenport, const char* buf, + const ssize_t buflen, const struct sockaddr_in clientaddr, FILE* fp) +{ + uint8_t* mbuf; + unsigned offset = 0; + int64_t curtime = g_get_real_time(); + uint64_t bytes_written = 0; + int err; + int ret = EXIT_SUCCESS; + + /* The space we need is the standard header + variable lengths */ + mbuf = (uint8_t*)g_malloc0(UDPDUMP_EXPORT_HEADER_LEN + ((strlen(proto_name) + 3) & 0xfffffffc) + buflen); + + add_proto_name(mbuf, &offset, proto_name); + add_ip_source_address(mbuf, &offset, clientaddr.sin_addr.s_addr); + add_ip_dest_address(mbuf, &offset, WS_IN4_LOOPBACK); + add_udp_source_port(mbuf, &offset, clientaddr.sin_port); + add_udp_dst_port(mbuf, &offset, listenport); + add_end_options(mbuf, &offset); + + memcpy(mbuf + offset, buf, buflen); + offset += (unsigned)buflen; + + if (!libpcap_write_packet(fp, + (uint32_t)(curtime / G_USEC_PER_SEC), (uint32_t)(curtime % G_USEC_PER_SEC), + offset, offset, mbuf, &bytes_written, &err)) { + ws_warning("Can't write packet: %s", g_strerror(err)); + ret = EXIT_FAILURE; + } + + fflush(fp); + + g_free(mbuf); + return ret; +} + +static void run_listener(const char* fifo, const uint16_t port, const char* proto_name) +{ + struct sockaddr_in clientaddr; + socklen_t clientlen = sizeof(clientaddr); + socket_handle_t sock; + char* buf; + ssize_t buflen; + FILE* fp = NULL; + + if (setup_dumpfile(fifo, &fp) == EXIT_FAILURE) { + if (fp) + fclose(fp); + return; + } + + if (setup_listener(port, &sock) == EXIT_FAILURE) + return; + + ws_debug("Listener running on port %u", port); + + buf = (char*)g_malloc(PKT_BUF_SIZE); + while(!extcap_end_application) { + memset(buf, 0x0, PKT_BUF_SIZE); + + buflen = recvfrom(sock, buf, PKT_BUF_SIZE, 0, (struct sockaddr *)&clientaddr, &clientlen); + if (buflen < 0) { + switch(errno) { + case EAGAIN: + case EINTR: + break; + default: +#ifdef _WIN32 + { + wchar_t *errmsg = NULL; + int err = WSAGetLastError(); + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&errmsg, 0, NULL); + ws_warning("Error in recvfrom: %S (err=%d)", errmsg, err); + LocalFree(errmsg); + } +#else + ws_warning("Error in recvfrom: %s (errno=%d)", strerror(errno), errno); +#endif + extcap_end_application = true; + break; + } + } else { + if (dump_packet(proto_name, port, buf, buflen, clientaddr, fp) == EXIT_FAILURE) + extcap_end_application = true; + } + } + + fclose(fp); + closesocket(sock); + g_free(buf); +} + +int main(int argc, char *argv[]) +{ + char* err_msg; + int option_idx = 0; + int result; + uint16_t port = 0; + int ret = EXIT_FAILURE; + extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1); + char* help_url; + char* help_header = NULL; + char* payload = NULL; + char* port_msg = NULL; + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("udpdump"); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + err_msg = configuration_init(argv[0], NULL); + if (err_msg != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + err_msg); + g_free(err_msg); + } + + help_url = data_file_url("udpdump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], UDPDUMP_VERSION_MAJOR, UDPDUMP_VERSION_MINOR, UDPDUMP_VERSION_RELEASE, + help_url); + g_free(help_url); + extcap_base_register_interface(extcap_conf, UDPDUMP_EXTCAP_INTERFACE, "UDP Listener remote capture", 252, "Exported PDUs"); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --port 5555 --fifo myfifo --capture", + argv[0], argv[0], UDPDUMP_EXTCAP_INTERFACE, argv[0], UDPDUMP_EXTCAP_INTERFACE, argv[0], UDPDUMP_EXTCAP_INTERFACE); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + port_msg = ws_strdup_printf("the port to listens on. Default: %u", UDPDUMP_DEFAULT_PORT); + extcap_help_add_option(extcap_conf, "--port <port>", port_msg); + g_free(port_msg); + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + goto end; + } + + while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) { + switch (result) { + + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_VERSION: + extcap_version_print(extcap_conf); + goto end; + + case OPT_PORT: + if (!ws_strtou16(ws_optarg, NULL, &port)) { + ws_warning("Invalid port: %s", ws_optarg); + goto end; + } + break; + + case OPT_PAYLOAD: + g_free(payload); + payload = g_strdup(ws_optarg); + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (ws_optind != argc) { + ws_warning("Unexpected extra option: %s", argv[ws_optind]); + goto end; + } + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (!extcap_base_register_graceful_shutdown_cb(extcap_conf, NULL)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = list_config(extcap_conf->interface); + goto end; + } + + if (!payload) + payload = g_strdup("data"); + + err_msg = ws_init_sockets(); + if (err_msg != NULL) { + ws_warning("Error: %s", err_msg); + g_free(err_msg); + ws_warning("%s", please_report_bug()); + goto end; + } + + if (port == 0) + port = UDPDUMP_DEFAULT_PORT; + + if (extcap_conf->capture) + run_listener(extcap_conf->fifo, port, payload); + +end: + /* clean up stuff */ + extcap_base_cleanup(&extcap_conf); + g_free(payload); + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ diff --git a/extcap/wifidump.c b/extcap/wifidump.c new file mode 100644 index 00000000..489118eb --- /dev/null +++ b/extcap/wifidump.c @@ -0,0 +1,747 @@ +/* wifidump.c + * wifidump is an extcap tool used to capture Wi-Fi frames using a remote ssh host + * + * Adapted from sshdump. + * + * Copyright 2022, Adrian Granados <adrian@intuitibits.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "config.h" +#define WS_LOG_DOMAIN "wifidump" + +#include <extcap/extcap-base.h> +#include <extcap/ssh-base.h> +#include <wsutil/interface.h> +#include <wsutil/file_util.h> +#include <wsutil/strtoi.h> +#include <wsutil/filesystem.h> +#include <wsutil/privileges.h> +#include <wsutil/please_report_bug.h> +#include <wsutil/wslog.h> + +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include <cli_main.h> + +static char* wifidump_extcap_interface; +#ifdef _WIN32 +#define DEFAULT_WIFIDUMP_EXTCAP_INTERFACE "wifidump.exe" +#else +#define DEFAULT_WIFIDUMP_EXTCAP_INTERFACE "wifidump" +#endif + +#define WIFIDUMP_VERSION_MAJOR "1" +#define WIFIDUMP_VERSION_MINOR "0" +#define WIFIDUMP_VERSION_RELEASE "0" + +#define SSH_READ_BLOCK_SIZE 256 + +enum { + EXTCAP_BASE_OPTIONS_ENUM, + OPT_HELP, + OPT_VERSION, + OPT_REMOTE_HOST, + OPT_REMOTE_PORT, + OPT_REMOTE_USERNAME, + OPT_REMOTE_PASSWORD, + OPT_REMOTE_INTERFACE, + OPT_REMOTE_CHANNEL_FREQUENCY, + OPT_REMOTE_CHANNEL_WIDTH, + OPT_REMOTE_FILTER, + OPT_SSHKEY, + OPT_SSHKEY_PASSPHRASE, + OPT_PROXYCOMMAND, + OPT_REMOTE_COUNT +}; + +static struct ws_option longopts[] = { + EXTCAP_BASE_OPTIONS, + { "help", ws_no_argument, NULL, OPT_HELP}, + { "version", ws_no_argument, NULL, OPT_VERSION}, + SSH_BASE_OPTIONS, + { "remote-channel-frequency", ws_required_argument, NULL, OPT_REMOTE_CHANNEL_FREQUENCY}, + { "remote-channel-width", ws_required_argument, NULL, OPT_REMOTE_CHANNEL_WIDTH}, + { 0, 0, 0, 0} +}; + +static const char * remote_capture_functions = +"\n" +"function iface_down {\n" +" local iface=$1\n" +" sudo ip link set $iface down > /dev/null 2>&1\n" +"}\n" +"\n" +"function iface_up {\n" +" local iface=$1\n" +" sudo ip link set $iface up > /dev/null 2>&1\n" +"}\n" +"\n" +"function iface_monitor {\n" +" local iface=$1\n" +" sudo iw dev $iface set monitor control otherbss > /dev/null 2>&1 ||\n" +" sudo iw dev $iface set type monitor control otherbss > /dev/null 2>&1\n" +"}\n" +"\n" +"function iface_scan {\n" +" local iface=$1\n" +" iface_down $iface &&\n" +" sudo iw dev $iface set type managed > /dev/null 2>&1 &&\n" +" iface_up $iface &&\n" +" sudo iw dev $iface scan > /dev/null 2>&1\n" +"}\n" +"\n" +"function iface_config {\n" +" local iface=$1\n" +" local freq=$2\n" +" local ch_width=$3\n" +" local center_freq=$4\n" +" if [ $freq -eq $center_freq ]; then\n" +" sudo iw dev $1 set freq $freq $ch_width 2>&1\n" +" else\n" +" sudo iw dev $1 set freq $freq $ch_width $center_freq 2>&1\n" +" fi\n" +"}\n" +"\n" +"function iface_start {\n" +" local iface=$1\n" +" local count=$2\n" +" local filter=\"${@:3}\"\n" +" if [ $count -gt 0 ]; then\n" +" sudo tcpdump -i $iface -U -w - -c $count $filter\n" +" else\n" +" sudo tcpdump -i $iface -U -w - $filter\n" +" fi\n" +"}\n" +"\n" +"function capture_generic {\n" +" local iface=$1\n" +" local freq=$2\n" +" local ch_width=$3\n" +" local center_freq=$4\n" +" local count=$5\n" +" local filter=\"${@:6}\"\n" +" if ! { iwconfig $iface | grep Monitor > /dev/null 2>&1; }; then\n" +" iface_down $iface &&\n" +" iface_monitor $iface &&\n" +" iface_up $iface\n" +" else\n" +" iface_monitor $iface\n" +" fi\n" +" iface_config $iface $freq $ch_width $center_freq &&\n" +" iface_start $iface $count $filter\n" +"}\n" +"\n" +"function capture_iwlwifi {\n" +" local iface=$1\n" +" local freq=$2\n" +" local ch_width=$3\n" +" local center_freq=$4\n" +" local count=$5\n" +" local filter=\"${@:6}\"\n" +" INDEX=`sudo iw dev $iface info | grep wiphy | grep -Eo '[0-9]+'`\n" +" sudo iw phy phy${INDEX} channels | grep $freq | grep -i disabled > /dev/null 2>&1 &&\n" +" iface_scan $iface\n" +" MON=${iface}mon\n" +" sudo iw $iface interface add $MON type monitor flags none > /dev/null 2>&1\n" +" iface_up $MON &&\n" +" iface_down $iface &&\n" +" iface_config $MON $freq $ch_width $center_freq &&\n" +" iface_start $MON $count $filter\n" +"}\n" +"\n" +"function capture {\n" +" local iface=$1\n" +" local freq=$2\n" +" local ch_width=$3\n" +" local center_freq=$4\n" +" local count=$5\n" +" local filter=\"${@:6}\"\n" +" if [ \"$iface\" == \"auto\" ]; then\n" +" iface=`sudo iw dev | grep -i interface | awk '{ print $2 }' | sort | head -n 1`\n" +" fi\n" +" local driver=`/usr/sbin/ethtool -i $iface | grep driver | awk '{ print $2 }'`\n" +" if [ $driver = \"iwlwifi\" ]; then\n" +" capture_iwlwifi $iface $freq $ch_width $center_freq $count $filter\n" +" else\n" +" capture_generic $iface $freq $ch_width $center_freq $count $filter\n" +" fi\n" +"}\n" +"\n"; + +static unsigned int wifi_freqs_2dot4_5ghz[] = { + 2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452, 2457, 2462, + 2467, 2472, 2484, + 5180, 5200, 5220, 5240, 5260, 5280, 5300, 5320, 5500, 5520, 5540, 5560, 5580, + 5600, 5620, 5640, 5660, 5680, 5700, 5720, 5745, 5765, 5785, 5805, 5825, + 0 +}; + +static unsigned int freq_to_channel(unsigned int freq_mhz) { + if (freq_mhz == 2484) + return 14; + else if (freq_mhz >= 2412 && freq_mhz <= 2484) + return ((freq_mhz - 2412) / 5) + 1; + else if (freq_mhz >= 5160 && freq_mhz <= 5885) + return ((freq_mhz - 5180) / 5) + 36; + else if (freq_mhz >= 5955 && freq_mhz <= 7115) + return ((freq_mhz - 5955) / 5) + 1; + else + return 0; +} + +static const char *freq_to_band(unsigned int freq_mhz) +{ + if (freq_mhz >= 2412 && freq_mhz <= 2484) + return "2.4 GHz"; + else if (freq_mhz >= 5160 && freq_mhz <= 5885) + return "5 GHz"; + else if (freq_mhz >= 5955 && freq_mhz <= 7115) + return "6 GHz"; + else + return NULL; +} + +static unsigned int center_freq(unsigned int freq_mhz, unsigned int ch_width_mhz) { + + unsigned int start_freq; + + if (ch_width_mhz == 20) { + return freq_mhz; + } + else if (ch_width_mhz == 40) { + if (freq_mhz >= 5180 && freq_mhz <= 5720) { + for (start_freq = 5180; start_freq <= 5700; start_freq += ch_width_mhz) { + if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 20)) + return ((start_freq * 2) + 20) / 2; + } + } + else if (freq_mhz >= 5745 && freq_mhz <= 5765) + return 5755; + else if (freq_mhz >= 5785 && freq_mhz <= 5805) + return 5795; + else if (freq_mhz >= 5955 && freq_mhz <= 7095) { + for (start_freq = 5955; start_freq <= 7075; start_freq += ch_width_mhz) { + if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 20)) + return ((start_freq * 2) + 20) / 2; + } + } + } + else if (ch_width_mhz == 80) { + if (freq_mhz >= 5180 && freq_mhz <= 5660) { + for (start_freq = 5180; start_freq <= 5660; start_freq += ch_width_mhz) { + if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 60)) + return ((start_freq * 2) + 60) / 2; + } + } + else if (freq_mhz >= 5745 && freq_mhz <= 5805) + return 5775; + else if (freq_mhz >= 5955 && freq_mhz <= 7055) { + for (start_freq = 5955; start_freq <= 6995; start_freq += ch_width_mhz) { + if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 60)) + return ((start_freq * 2) + 60) / 2; + } + } + } + else if (ch_width_mhz == 160) { + if (freq_mhz >= 5180 && freq_mhz <= 5640) { + for (start_freq = 5180; start_freq <= 5500; start_freq += ch_width_mhz) { + if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 140)) + return ((start_freq * 2) + 140) / 2; + } + } + else if (freq_mhz >= 5955 && freq_mhz <= 7055) { + for (start_freq = 5955; start_freq <= 6915; start_freq += ch_width_mhz) { + if (freq_mhz >= start_freq && freq_mhz <= (start_freq + 140)) + return ((start_freq * 2) + 140) / 2; + } + } + } + + return -1; +} + +static int ssh_loop_read(ssh_channel channel, FILE* fp) +{ + int nbytes; + int ret = EXIT_SUCCESS; + char buffer[SSH_READ_BLOCK_SIZE]; + + /* read from stdin until data are available */ + while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) { + nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 0); + if (nbytes < 0) { + ws_warning("Error reading from channel"); + goto end; + } + if (nbytes == 0) { + break; + } + if (fwrite(buffer, 1, nbytes, fp) != (unsigned)nbytes) { + ws_warning("Error writing to fifo"); + ret = EXIT_FAILURE; + goto end; + } + fflush(fp); + } + + /* read loop finished... maybe something wrong happened. Read from stderr */ + while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) { + nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 1); + if (nbytes < 0) { + ws_warning("Error reading from channel"); + goto end; + } + if (fwrite(buffer, 1, nbytes, stderr) != (unsigned)nbytes) { + ws_warning("Error writing to stderr"); + break; + } + } + +end: + if (ssh_channel_send_eof(channel) != SSH_OK) { + ws_warning("Error sending EOF in ssh channel"); + ret = EXIT_FAILURE; + } + return ret; +} + +static ssh_channel run_ssh_command(ssh_session sshs, const char* capture_functions, + const char* iface, const uint16_t channel_frequency, const uint16_t channel_width, + const uint16_t center_frequency, const char* cfilter, const uint32_t count) +{ + char* cmdline; + ssh_channel channel; + char* quoted_iface = NULL; + char* quoted_filter = NULL; + char* count_str = NULL; + unsigned int remote_port = 22; + + channel = ssh_channel_new(sshs); + if (!channel) { + ws_warning("Can't create channel"); + return NULL; + } + + if (ssh_channel_open_session(channel) != SSH_OK) { + ws_warning("Can't open session"); + ssh_channel_free(channel); + return NULL; + } + + ssh_options_get_port(sshs, &remote_port); + + quoted_iface = iface ? g_shell_quote(iface) : NULL; + quoted_filter = g_shell_quote(cfilter ? cfilter : ""); + cmdline = ws_strdup_printf("%s capture %s %u %u %u %u %s", + capture_functions, + quoted_iface ? quoted_iface : "auto", + channel_frequency, + channel_width, + center_frequency, + count, + quoted_filter); + + ws_debug("Running: %s", cmdline); + if (ssh_channel_request_exec(channel, cmdline) != SSH_OK) { + ws_warning("Can't request exec"); + ssh_channel_close(channel); + ssh_channel_free(channel); + channel = NULL; + } + + g_free(quoted_iface); + g_free(quoted_filter); + g_free(cmdline); + g_free(count_str); + + return channel; +} + +static int ssh_open_remote_connection(const ssh_params_t* params, const char* capture_functions, + const char* iface, const uint16_t channel_frequency, const uint16_t channel_width, + const uint16_t center_frequency, const char* cfilter, const uint32_t count, const char* fifo) +{ + ssh_session sshs = NULL; + ssh_channel channel = NULL; + FILE* fp = stdout; + int ret = EXIT_FAILURE; + char* err_info = NULL; + + if (g_strcmp0(fifo, "-")) { + /* Open or create the output file */ + fp = fopen(fifo, "wb"); + if (fp == NULL) { + ws_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno)); + return EXIT_FAILURE; + } + } + + sshs = create_ssh_connection(params, &err_info); + + if (!sshs) { + ws_warning("Error creating connection."); + goto cleanup; + } + + channel = run_ssh_command(sshs, capture_functions, iface, channel_frequency, + channel_width, center_frequency, cfilter, count); + + if (!channel) { + ws_warning("Can't run ssh command."); + goto cleanup; + } + + /* read from channel and write into fp */ + if (ssh_loop_read(channel, fp) != EXIT_SUCCESS) { + ws_warning("Error in read loop."); + ret = EXIT_FAILURE; + goto cleanup; + } + + ret = EXIT_SUCCESS; +cleanup: + if (err_info) + ws_warning("%s", err_info); + g_free(err_info); + + /* clean up and exit */ + ssh_cleanup(&sshs, &channel); + + if (g_strcmp0(fifo, "-")) + fclose(fp); + return ret; +} + +static int list_config(char *interface) +{ + unsigned inc = 0; + int i, psc; + + if (!interface) { + ws_warning("ERROR: No interface specified."); + return EXIT_FAILURE; + } + + if (g_strcmp0(interface, wifidump_extcap_interface)) { + ws_warning("ERROR: interface must be %s", wifidump_extcap_interface); + return EXIT_FAILURE; + } + + // Server tab + printf("arg {number=%u}{call=--remote-host}{display=Remote SSH server address}" + "{type=string}{tooltip=The remote SSH host. It can be both " + "an IP address or a hostname}{required=true}{group=Server}\n", inc++); + printf("arg {number=%u}{call=--remote-port}{display=Remote SSH server port}" + "{type=unsigned}{tooltip=The remote SSH host port (1-65535)}" + "{range=1,65535}{group=Server}\n", inc++); + + // Authentication tab + printf("arg {number=%u}{call=--remote-username}{display=Remote SSH server username}" + "{type=string}{tooltip=The remote SSH username. If not provided, " + "the current user will be used}{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--remote-password}{display=Remote SSH server password}" + "{type=password}{tooltip=The SSH password, used when other methods (SSH agent " + "or key files) are unavailable.}{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--sshkey}{display=Path to SSH private key}" + "{type=fileselect}{tooltip=The path on the local filesystem of the private ssh key}" + "{mustexist=true}{group=Authentication}\n", inc++); + printf("arg {number=%u}{call=--sshkey-passphrase}{display=SSH key passphrase}" + "{type=password}{tooltip=Passphrase to unlock the SSH private key}{group=Authentication}\n", + inc++); + + // Capture tab + printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}" + "{type=string}{tooltip=The remote network interface used to capture" + "}{default=auto}{group=Capture}\n", inc++); + printf("arg {number=%u}{call=--remote-channel-frequency}{display=Remote channel}" + "{type=selector}{tooltip=The remote channel used to capture}{group=Capture}\n", inc); + + unsigned int freq = 0; + for (i = 0; (freq = wifi_freqs_2dot4_5ghz[i]); i++) { + printf("value {arg=%u}{value=%u}{display=%s, Channel %u}\n", inc, freq, freq_to_band(freq), freq_to_channel(freq)); + } + + for (freq = 5955, psc = 3; freq <= 7115; freq += 20, psc++) { + printf("value {arg=%u}{value=%u}{display=%s, Channel %u%s}\n", inc, freq, + freq_to_band(freq), freq_to_channel(freq), (psc % 4 == 0) ? " (PSC)" : ""); + } + inc++; + + printf("arg {number=%u}{call=--remote-channel-width}{display=Remote channel width}" + "{type=selector}{tooltip=The remote channel width used to capture}" + "{group=Capture}\n", inc); + printf("value {arg=%u}{value=20}{display=20 MHz}\n", inc); + printf("value {arg=%u}{value=40}{display=40 MHz}\n", inc); + printf("value {arg=%u}{value=80}{display=80 MHz}\n", inc); + printf("value {arg=%u}{value=160}{display=160 MHz}\n", inc); + inc++; + + printf("arg {number=%u}{call=--remote-filter}{display=Remote capture filter}{type=string}" + "{tooltip=The remote capture filter}{group=Capture}\n", inc++); + printf("arg {number=%u}{call=--remote-count}{display=Frames to capture}" + "{type=unsigned}{tooltip=The number of remote frames to capture.}" + "{group=Capture}\n", inc++); + + extcap_config_debug(&inc); + + return EXIT_SUCCESS; +} + +static char* concat_filters(const char* extcap_filter, const char* remote_filter) +{ + if (!extcap_filter && remote_filter) + return g_strdup(remote_filter); + + if (!remote_filter && extcap_filter) + return g_strdup(extcap_filter); + + if (!remote_filter && !extcap_filter) + return NULL; + + return ws_strdup_printf("(%s) and (%s)", extcap_filter, remote_filter); +} + +int main(int argc, char *argv[]) +{ + char* err_msg; + int result; + int option_idx = 0; + ssh_params_t* ssh_params = ssh_params_new(); + char* remote_interface = NULL; + uint16_t remote_channel_frequency = 0; + uint16_t remote_channel_width = 0; + uint16_t remote_center_frequency = 0; + char* remote_filter = NULL; + uint32_t count = 0; + int ret = EXIT_FAILURE; + extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1); + char* help_url; + char* help_header = NULL; + char* interface_description = g_strdup("Wi-Fi remote capture"); + + /* Initialize log handler early so we can have proper logging during startup. */ + extcap_log_init("wifidump"); + + wifidump_extcap_interface = g_path_get_basename(argv[0]); + + /* + * Get credential information for later use. + */ + init_process_policies(); + + /* + * Attempt to get the pathname of the directory containing the + * executable file. + */ + err_msg = configuration_init(argv[0], NULL); + if (err_msg != NULL) { + ws_warning("Can't get pathname of directory containing the extcap program: %s.", + err_msg); + g_free(err_msg); + } + + help_url = data_file_url("wifidump.html"); + extcap_base_set_util_info(extcap_conf, argv[0], WIFIDUMP_VERSION_MAJOR, WIFIDUMP_VERSION_MINOR, + WIFIDUMP_VERSION_RELEASE, help_url); + g_free(help_url); + add_libssh_info(extcap_conf); + if (g_strcmp0(wifidump_extcap_interface, DEFAULT_WIFIDUMP_EXTCAP_INTERFACE)) { + char* temp = interface_description; + interface_description = ws_strdup_printf("%s, custom version", interface_description); + g_free(temp); + } + extcap_base_register_interface(extcap_conf, wifidump_extcap_interface, interface_description, 147, "Remote capture dependent DLT"); + g_free(interface_description); + + help_header = ws_strdup_printf( + " %s --extcap-interfaces\n" + " %s --extcap-interface=%s --extcap-dlts\n" + " %s --extcap-interface=%s --extcap-config\n" + " %s --extcap-interface=%s --remote-host myhost --remote-port 22222 " + "--remote-username myuser --remote-interface wlan0 --remote-channel-frequency 5180 " + "--remote-channel-width 40 --fifo=FILENAME --capture\n", argv[0], argv[0], wifidump_extcap_interface, argv[0], + wifidump_extcap_interface, argv[0], wifidump_extcap_interface); + extcap_help_add_header(extcap_conf, help_header); + g_free(help_header); + extcap_help_add_option(extcap_conf, "--help", "print this help"); + extcap_help_add_option(extcap_conf, "--version", "print the version"); + extcap_help_add_option(extcap_conf, "--remote-host <host>", "the remote SSH host"); + extcap_help_add_option(extcap_conf, "--remote-port <port>", "the remote SSH port"); + extcap_help_add_option(extcap_conf, "--remote-username <username>", "the remote SSH username"); + extcap_help_add_option(extcap_conf, "--remote-password <password>", "the remote SSH password. If not specified, ssh-agent and ssh-key are used"); + extcap_help_add_option(extcap_conf, "--sshkey <public key path>", "the path of the ssh key"); + extcap_help_add_option(extcap_conf, "--sshkey-passphrase <public key passphrase>", "the passphrase to unlock public ssh"); + extcap_help_add_option(extcap_conf, "--remote-interface <iface>", "the remote capture interface"); + extcap_help_add_option(extcap_conf, "--remote-channel-frequency <channel_frequency>", "the remote channel frequency in MHz"); + extcap_help_add_option(extcap_conf, "--remote-channel-width <channel_width>", "the remote channel width in MHz"); + extcap_help_add_option(extcap_conf, "--remote-filter <filter>", "a filter for remote capture"); + extcap_help_add_option(extcap_conf, "--remote-count <count>", "the number of frames to capture"); + + ws_opterr = 0; + ws_optind = 0; + + if (argc == 1) { + extcap_help_print(extcap_conf); + goto end; + } + + while ((result = ws_getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) { + + switch (result) { + + case OPT_HELP: + extcap_help_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_VERSION: + extcap_version_print(extcap_conf); + ret = EXIT_SUCCESS; + goto end; + + case OPT_REMOTE_HOST: + g_free(ssh_params->host); + ssh_params->host = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_PORT: + if (!ws_strtou16(ws_optarg, NULL, &ssh_params->port) || ssh_params->port == 0) { + ws_warning("Invalid port: %s", ws_optarg); + goto end; + } + break; + + case OPT_REMOTE_USERNAME: + g_free(ssh_params->username); + ssh_params->username = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_PASSWORD: + g_free(ssh_params->password); + ssh_params->password = g_strdup(ws_optarg); + memset(ws_optarg, 'X', strlen(ws_optarg)); + break; + + case OPT_SSHKEY: + g_free(ssh_params->sshkey_path); + ssh_params->sshkey_path = g_strdup(ws_optarg); + break; + + case OPT_SSHKEY_PASSPHRASE: + g_free(ssh_params->sshkey_passphrase); + ssh_params->sshkey_passphrase = g_strdup(ws_optarg); + memset(ws_optarg, 'X', strlen(ws_optarg)); + break; + + case OPT_REMOTE_INTERFACE: + g_free(remote_interface); + remote_interface = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_CHANNEL_FREQUENCY: + if (!ws_strtou16(ws_optarg, NULL, &remote_channel_frequency)) { + ws_warning("Invalid channel frequency: %s", ws_optarg); + goto end; + } + break; + + case OPT_REMOTE_CHANNEL_WIDTH: + if (!ws_strtou16(ws_optarg, NULL, &remote_channel_width)) { + ws_warning("Invalid channel width: %s", ws_optarg); + goto end; + } + break; + + case OPT_REMOTE_FILTER: + g_free(remote_filter); + remote_filter = g_strdup(ws_optarg); + break; + + case OPT_REMOTE_COUNT: + if (!ws_strtou32(ws_optarg, NULL, &count)) { + ws_warning("Invalid value for count: %s", ws_optarg); + goto end; + } + break; + + case ':': + /* missing option argument */ + ws_warning("Option '%s' requires an argument", argv[ws_optind - 1]); + break; + + default: + if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, ws_optarg)) { + ws_warning("Invalid option: %s", argv[ws_optind - 1]); + goto end; + } + } + } + + extcap_cmdline_debug(argv, argc); + + if (extcap_base_handle_interface(extcap_conf)) { + ret = EXIT_SUCCESS; + goto end; + } + + if (extcap_conf->show_config) { + ret = list_config(extcap_conf->interface); + goto end; + } + + err_msg = ws_init_sockets(); + if (err_msg != NULL) { + ws_warning("ERROR: %s", err_msg); + g_free(err_msg); + ws_warning("%s", please_report_bug()); + goto end; + } + + if (extcap_conf->capture) { + char* filter; + + if (!ssh_params->host) { + ws_warning("Missing parameter: --remote-host"); + goto end; + } + remote_center_frequency = center_freq(remote_channel_frequency, remote_channel_width); + filter = concat_filters(extcap_conf->capture_filter, remote_filter); + ssh_params->debug = extcap_conf->debug; + ret = ssh_open_remote_connection(ssh_params, remote_capture_functions, + remote_interface, remote_channel_frequency, remote_channel_width, remote_center_frequency, + filter, count, extcap_conf->fifo); + g_free(filter); + } else { + ws_debug("You should not come here... maybe some parameter missing?"); + ret = EXIT_FAILURE; + } + +end: + /* clean up stuff */ + ssh_params_free(ssh_params); + g_free(remote_interface); + g_free(remote_filter); + extcap_base_cleanup(&extcap_conf); + return ret; +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 8 + * tab-width: 8 + * indent-tabs-mode: t + * End: + * + * vi: set shiftwidth=8 tabstop=8 noexpandtab: + * :indentSize=8:tabSize=8:noTabs=false: + */ |