extcap: add ciscodump.

Ciscodump is a new extcap that allows packet capture
on Cisco routers (IOS 12.4 and later) through SSH.

Change-Id: Ic9c5be01d3bd0112116f7fc9fa10e26c1552b007
Reviewed-on: https://code.wireshark.org/review/13886
Reviewed-by: Roland Knall <rknall@gmail.com>
This commit is contained in:
Dario Lombardo 2016-03-04 10:53:56 +01:00 committed by Roland Knall
parent 2e98866171
commit a6921c79ab
12 changed files with 1119 additions and 24 deletions

View File

@ -1218,7 +1218,7 @@ endforeach()
include(FeatureSummary)
#SET_FEATURE_INFO(NAME DESCRIPTION [URL [COMMENT] ])
SET_FEATURE_INFO(SBC "SBC Codec for Bluetooth A2DP stream playing" "www: http://git.kernel.org/cgit/bluetooth/sbc.git" )
SET_FEATURE_INFO(LIBSSH "libssh is library for ssh connections and it is needed to build sshdump" "www: https://www.libssh.org/get-it/" )
SET_FEATURE_INFO(LIBSSH "libssh is library for ssh connections and it is needed to build sshdump/ciscodump" "www: https://www.libssh.org/get-it/" )
FEATURE_SUMMARY(WHAT ALL)
@ -2374,6 +2374,32 @@ elseif (BUILD_sshdump)
#message( WARNING "Cannot find libssh, cannot build sshdump" )
endif()
if(ENABLE_EXTCAP AND BUILD_ciscodump AND LIBSSH_FOUND)
set(ciscodump_LIBS
wsutil
${GLIB2_LIBRARIES}
${CMAKE_DL_LIBS}
${LIBSSH_LIBRARIES}
)
if (WIN32)
set(ciscodump_LIBS wsutil ${ciscodump_LIBS})
endif()
set(ciscodump_FILES
extcap/ciscodump.c
extcap/extcap-base.c
extcap/ssh-base.c
pcapio.c
)
add_executable(ciscodump WIN32 ${ciscodump_FILES})
set_extcap_executable_properties(ciscodump)
target_link_libraries(ciscodump ${ciscodump_LIBS})
target_include_directories(ciscodump PUBLIC ${LIBSSH_INCLUDE_DIR})
install(TARGETS ciscodump RUNTIME DESTINATION ${EXTCAP_DIR})
elseif (BUILD_ciscodump)
#message( WARNING "Cannot find libssh, cannot build ciscodump" )
endif()
if(ENABLE_EXTCAP AND BUILD_randpktdump)
set(randpktdump_LIBS
randpkt_core
@ -2464,6 +2490,7 @@ set(CLEAN_FILES
${dumpcap_FILES}
${androiddump_FILES}
${sshdump_FILES}
${ciscodump_FILES}
)
if (WERROR_COMMON_FLAGS)

View File

@ -16,6 +16,7 @@ option(BUILD_randpkt "Build randpkt" ON)
option(BUILD_dftest "Build dftest" ON)
option(BUILD_androiddump "Build androiddump" ON)
option(BUILD_sshdump "Build sshdump" ON)
option(BUILD_ciscodump "Build ciscodump" ON)
option(BUILD_randpktdump "Build randpktdump" ON)
option(AUTOGEN_dcerpc "Autogenerate DCE RPC dissectors" OFF)
option(AUTOGEN_pidl "Autogenerate pidl dissectors" OFF)

View File

@ -416,7 +416,7 @@ GNUTLS_PKG=3.2.15-2.7
GPGERROR_DLL=libgpg-error-0.dll
GCC_DLL=libgcc_s_sjlj-1.dll
# Optional: libssh library is required for sshdump support
# Optional: libssh library is required for sshdump and ciscodump support
#
# If you don't have libssh, comment this line out so that LIBSSH_DIR
# isn't defined.

View File

@ -3016,13 +3016,16 @@ dnl sshdump check
AC_MSG_CHECKING(whether to build sshdump)
AC_ARG_ENABLE(sshdump,
AC_HELP_STRING( [--enable-sshdump],
[build sshdump @<:@default=yes@:>@]),
sshdump=$enableval,enable_sshdump=yes)
AC_HELP_STRING( [--enable-sshdump],
[build sshdump @<:@default=yes@:>@]),
[],[enable_sshdump=yes])
if test "x$have_extcap" != xyes; then
AC_MSG_RESULT(no, extcap disabled)
enable_sshdump=no
elif test "x$have_good_libssh" != xyes; then
AC_MSG_RESULT(no, libssh not available)
enable_sshdump=no
elif test "x$enable_sshdump" = "xyes" ; then
AC_MSG_RESULT(yes)
else
@ -3030,15 +3033,8 @@ else
fi
if test "x$enable_sshdump" = "xyes" ; then
if test "x$have_good_libssh" = "xyes" ; then
sshdump_bin="sshdump\$(EXEEXT)"
sshdump_man="sshdump.1"
else
echo "Can't find libssh. Disabling sshdump."
enable_sshdump=no
sshdump_bin=""
sshdump_man=""
fi
sshdump_bin="sshdump\$(EXEEXT)"
sshdump_man="sshdump.1"
else
sshdump_bin=""
sshdump_man=""
@ -3046,13 +3042,43 @@ fi
AC_SUBST(sshdump_bin)
AC_SUBST(sshdump_man)
dnl ciscodump check
AC_MSG_CHECKING(whether to build ciscodump)
AC_ARG_ENABLE(ciscodump,
AC_HELP_STRING( [--enable-ciscodump],
[build ciscodump @<:@default=yes@:>@]),
[],[enable_ciscodump=yes])
if test "x$have_extcap" != xyes; then
AC_MSG_RESULT(no, extcap disabled)
enable_ciscodump=no
elif test "x$have_good_libssh" != xyes; then
AC_MSG_RESULT(no, libssh not available)
enable_ciscodump=no
elif test "x$enable_ciscodump" = "xyes" ; then
AC_MSG_RESULT(yes)
else
AC_MSG_RESULT(no)
fi
if test "x$enable_ciscodump" = "xyes" ; then
ciscodump_bin="ciscodump\$(EXEEXT)"
ciscodump_man="ciscodump.1"
else
ciscodump_bin=""
ciscodump_man=""
fi
AC_SUBST(ciscodump_bin)
AC_SUBST(ciscodump_man)
dnl randpktdump check
AC_MSG_CHECKING(whether to build randpktdump)
AC_ARG_ENABLE(randpktdump,
AC_HELP_STRING( [--enable-randpktdump],
[build androiddump @<:@default=yes@:>@]),
randpktdump=$enableval,enable_randpktdump=yes)
AC_HELP_STRING( [--enable-randpktdump],
[build androiddump @<:@default=yes@:>@]),
randpktdump=$enableval,enable_randpktdump=yes)
if test "x$have_extcap" != xyes; then
AC_MSG_RESULT(no, extcap disabled)
@ -3443,6 +3469,7 @@ echo " Build dftest : $enable_dftest"
echo " Build rawshark : $enable_rawshark"
echo " Build androiddump : $enable_androiddump"
echo " Build sshdump : $enable_sshdump"
echo " Build ciscodump : $enable_ciscodump"
echo " Build randpktdump : $enable_randpktdump"
echo " Build echld : $have_echld"
echo ""

View File

@ -84,6 +84,7 @@ pod2manhtml(${CMAKE_CURRENT_SOURCE_DIR}/randpktdump 1)
pod2manhtml(${CMAKE_CURRENT_SOURCE_DIR}/rawshark 1)
pod2manhtml(${CMAKE_CURRENT_SOURCE_DIR}/reordercap 1)
pod2manhtml(${CMAKE_CURRENT_SOURCE_DIR}/sshdump 1)
pod2manhtml(${CMAKE_CURRENT_SOURCE_DIR}/ciscodump 1)
pod2manhtml(${CMAKE_CURRENT_SOURCE_DIR}/text2pcap 1)
pod2manhtml(${CMAKE_CURRENT_SOURCE_DIR}/tshark 1)
pod2manhtml(${CMAKE_CURRENT_BINARY_DIR}/wireshark 1)
@ -107,6 +108,7 @@ set(MAN1_INSTALL_FILES
${CMAKE_CURRENT_BINARY_DIR}/rawshark.1
${CMAKE_CURRENT_BINARY_DIR}/reordercap.1
${CMAKE_CURRENT_BINARY_DIR}/sshdump.1
${CMAKE_CURRENT_BINARY_DIR}/ciscodump.1
${CMAKE_CURRENT_BINARY_DIR}/text2pcap.1
${CMAKE_CURRENT_BINARY_DIR}/tshark.1
${CMAKE_CURRENT_BINARY_DIR}/wireshark.1
@ -134,6 +136,7 @@ set(HTML_INSTALL_FILES
${CMAKE_CURRENT_BINARY_DIR}/rawshark.html
${CMAKE_CURRENT_BINARY_DIR}/reordercap.html
${CMAKE_CURRENT_BINARY_DIR}/sshdump.html
${CMAKE_CURRENT_BINARY_DIR}/ciscodump.html
${CMAKE_CURRENT_BINARY_DIR}/text2pcap.html
${CMAKE_CURRENT_BINARY_DIR}/tshark.html
${CMAKE_CURRENT_BINARY_DIR}/wireshark.html

231
doc/ciscodump.pod Normal file
View File

@ -0,0 +1,231 @@
=head1 NAME
ciscodump - Provide interfaces to capture from a remote Cisco router through SSH.
=head1 SYNOPSIS
B<ciscodump>
S<[ B<--help> ]>
S<[ B<--version> ]>
S<[ B<--extcap-interfaces> ]>
S<[ B<--extcap-dlts> ]>
S<[ B<--extcap-interface>=E<lt>interfaceE<gt> ]>
S<[ B<--extcap-config> ]>
S<[ B<--extcap-capture-filter>=E<lt>capture filterE<gt> ]>
S<[ B<--capture> ]>
S<[ B<--fifo>=E<lt>path to file or pipeE<gt> ]>
S<[ B<--remote-host>=E<lt>IP addressE<gt> ]>
S<[ B<--remote-port>=E<lt>TCP portE<gt> ]>
S<[ B<--remote-username>=E<lt>usernameE<gt> ]>
S<[ B<--remote-password>=E<lt>passwordE<gt> ]>
S<[ B<--remote-filter>=E<lt>filter<gt> ]>
S<[ B<--sshkey>=E<lt>public key path<gt> ]>
S<[ B<--remote-interface>=E<lt>interfaceE<gt> ]>
B<ciscodump>
S<B<--extcap-interfaces>>
B<ciscodump>
S<B<--extcap-interface>=E<lt>interfaceE<gt>>
S<B<--extcap-dlts>>
B<ciscodump>
S<B<--extcap-interface>=E<lt>interfaceE<gt>>
S<B<--extcap-config>>
B<ciscodump>
S<B<--extcap-interface>=E<lt>interfaceE<gt>>
S<B<--fifo>=E<lt>path to file or pipeE<gt>>
S<B<--capture>>
S<B<--remote-host=remoterouter>>
S<B<--remote-port=22>>
S<B<--remote-username=user>>
S<B<--remote-interface>=E<lt>the router interfaceE<gt>>
=head1 DESCRIPTION
B<ciscodump> is an extcap tool that relys on Cisco EPC to allow a user to run a remote capture
on a Cisco router in a SSH connection. The minimum IOS version supporting this feature is 12.4(20)T. More details can be
found here:
http://www.cisco.com/c/en/us/products/collateral/ios-nx-os-software/ios-embedded-packet-capture/datasheet_c78-502727.html
Supported interfaces:
=over 4
=item 1. cisco
=back
=head1 OPTIONS
=over 4
=item --help
Print program arguments.
=item --version
Print program version.
=item --extcap-interfaces
List available interfaces.
=item --extcap-interface=E<lt>interfaceE<gt>
Use specified interfaces.
=item --extcap-dlts
List DLTs of specified interface.
=item --extcap-config
List configuration options of specified interface.
=item --capture
Start capturing from specified interface and save it in place specified by --fifo.
=item --fifo=E<lt>path to file or pipeE<gt>
Save captured packet to file or send it through pipe.
=item --remote-host=E<lt>remote hostE<gt>
The address of the remote host for capture.
=item --remote-port=E<lt>remote portE<gt>
The SSH port of the remote host.
=item --remote-username=E<lt>usernameE<gt>
The username for ssh authentication.
=item --remote-password=E<lt>passwordE<gt>
The password to use (if not ssh-agent and pubkey are used). WARNING: the
passwords are stored in plaintext and visible to all users on this system. It is
recommended to use keyfiles with a SSH agent.
=item --remote-filter=E<lt>filterE<gt>
The remote filter on the router. This is a capture filter that follows the Cisco IOS standards (http://www.cisco.com/c/en/us/support/docs/ip/access-lists/26448-ACLsamples.html). Multiple filters can be specified using a comma between them. BEWARE: when using a filter, the default behavior is to drop all the packets except the ones that fall into the filter.
Examples:
permit ip host MYHOST any, permit ip any host MYHOST (capture the traffic for MYHOST)
deny ip host MYHOST any, deny ip any host MYHOST, permit ip any any (capture all the traffic except MYHOST)
=item --sshkey=E<lt>SSH private key pathE<gt>
The path to a private key for authentication.
=item --remote-interface=E<lt>remote interfaceE<gt>
The remote network interface to capture from.
=item --extcap-capture-filter=E<lt>capture filterE<gt>
Unused (compatibility only).
=back
=head1 EXAMPLES
To see program arguments:
ciscodump --help
To see program version:
ciscodump --version
To see interfaces:
ciscodump --extcap-interfaces
Only one interface (cisco) is supported.
Output:
interface {value=cisco}{display=SSH remote capture}
To see interface DLTs:
ciscodump --extcap-interface=cisco --extcap-dlts
Output:
dlt {number=147}{name=cisco}{display=Remote capture dependant DLT}
To see interface configuration options:
ciscodump --extcap-interface=cisco --extcap-config
Output:
ciscodump --extcap-interface=cisco --extcap-config
arg {number=0}{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}
arg {number=1}{call=--remote-port}{display=Remote SSH server port}{type=unsigned}
{default=22}{tooltip=The remote SSH host port (1-65535)}{range=1,65535}
arg {number=2}{call=--remote-username}{display=Remote SSH server username}{type=string}
{default=<current user>}{tooltip=The remote SSH username. If not provided, the current
user will be used}
arg {number=3}{call=--remote-password}{display=Remote SSH server password}{type=string}
{tooltip=The SSH password, used when other methods (SSH agent or key files) are unavailable.}
arg {number=4}{call=--sshkey}{display=Path to SSH private key}{type=fileselect}
{tooltip=The path on the local filesystem of the private ssh key}
arg {number=5}{call--sshkey-passphrase}{display=SSH key passphrase}
{type=string}{tooltip=Passphrase to unlock the SSH private key}
arg {number=6}{call=--remote-interface}{display=Remote interface}{type=string}
{required=true}{tooltip=The remote network interface used for capture}
arg {number=7}{call=--remote-filter}{display=Remote capture filter}{type=string}
{default=(null)}{tooltip=The remote capture filter}
arg {number=8}{call=--remote-count}{display=Packets to capture}{type=unsigned}{required=true}
{tooltip=The number of remote packets to capture.}
To capture:
ciscodump --extcap-interface cisco --fifo=/tmp/cisco.pcap --capture --remote-host 192.168.1.10
--remote-username user --remote-interface gigabit0/0
--remote-filter "permit ip host 192.168.1.1 any, permit ip any host 192.168.1.1"
NOTE: Packet count is mandatory, hence the capture will start after this number.
=head1 KNOWN ISSUES
The configuration of the capture on the routers is a multi-step process. If the SSH connection is interrupted during
it, the configuration can be in an inconsistent state. That can happen also if the capture is stopped and ciscodump
can't clean the configuration up. In this case it is necessary to log into the router and manually clean the
configuration, removing both the capture point (WIRESHARK_CAPTURE_POINT), the capture buffer (WIRESHARK_CAPTURE_BUFFER)
and the capture filter (WIRESHARK_CAPTURE_FILTER).
Another known issues is related to the number of captured packets (--remote-count). Due to the nature of the capture
buffer, ciscodump waits for the capture to complete and then issues the command to show it. It means that if the user
specifies a number of packets above the currently captured, the show command is never shown. Not only is the count of
the maximum number of captured packets, but it is also the _exact_ number of expected packets.
=head1 SEE ALSO
wireshark(1), tshark(1), dumpcap(1), extcap(4), sshdump(1)
=head1 NOTES
B<ciscodump> is part of the B<Wireshark> distribution. The latest version
of B<Wireshark> can be found at L<https://www.wireshark.org>.
HTML versions of the Wireshark project man pages are available at:
L<https://www.wireshark.org/docs/man-pages>.
=head1 AUTHORS
Original Author
-------- ------
Dario Lombardo <lomato[AT]gmail.com>

View File

@ -34,9 +34,10 @@ EXTRA_DIST = \
extcap_PROGRAMS = \
@androiddump_bin@ \
@randpktdump_bin@ \
@sshdump_bin@
@sshdump_bin@ \
@ciscodump_bin@
EXTRA_PROGRAMS = androiddump randpktdump sshdump
EXTRA_PROGRAMS = androiddump randpktdump sshdump ciscodump
if ENABLE_STATIC
androiddump_LDFLAGS = -Wl,-static -all-static
@ -78,3 +79,17 @@ sshdump_LDADD = \
@GLIB_LIBS@ \
@LIBSSH_LIBS@ \
@SOCKET_LIBS@
if ENABLE_STATIC
ciscodump_LDFLAGS = -Wl,-static -all-static
else
ciscodump_LDFLAGS = -export-dynamic
endif
# Libraries and plugin flags with which to link ciscodump.
ciscodump_LDADD = \
../wiretap/libwiretap.la \
../wsutil/libwsutil.la \
@GLIB_LIBS@ \
@LIBSSH_LIBS@ \
@SOCKET_LIBS@

View File

@ -37,6 +37,12 @@ sshdump_SOURCES = \
extcap-base.c \
ssh-base.c
# ciscodump specifics
ciscodump_SOURCES = \
ciscodump.c \
extcap-base.c \
ssh-base.c
noinst_HEADERS = \
extcap-base.h \
extcap-base.h \
ssh-base.h

View File

@ -61,10 +61,21 @@ sshdump_LIBS = $(sshdump_WSLIBS) \
$(LIBSSH_LIBS) \
$(GLIB_LIBS)
ciscodump_OBJECTS = $(ciscodump_SOURCES:.c=.obj)
ciscodump_WSLIBS = \
..\wiretap\wiretap-$(WTAP_VERSION).lib \
..\wsutil\libwsutil.lib
ciscodump_LIBS = $(ciscodump_WSLIBS) \
wsock32.lib user32.lib \
$(LIBSSH_LIBS) \
$(GLIB_LIBS)
EXECUTABLES=androiddump.exe randpktdump.exe
!IFDEF LIBSSH_DIR
EXECUTABLES += sshdump.exe
EXECUTABLES += sshdump.exe ciscodump.exe
!ENDIF
all: $(EXECUTABLES)
@ -96,11 +107,19 @@ sshdump.exe : $(LIBS_CHECK) ..\config.h sshdump.obj extcap-base.obj ssh-base.obj
!IFDEF MANIFEST_INFO_REQUIRED
mt.exe -nologo -manifest "sshdump.exe.manifest" -outputresource:sshdump.exe;1
!ENDIF
ciscodump.exe : $(LIBS_CHECK) ..\config.h ciscodump.obj extcap-base.obj ssh-base.obj $(ciscodump_WSLIBS)
@echo Linking $@
$(LINK) @<<
/OUT:ciscodump.exe $(conflags) $(conlibsdll) $(LDFLAGS) /SUBSYSTEM:WINDOWS ciscodump.obj extcap-base.obj ssh-base.obj $(ciscodump_LIBS)
<<
!IFDEF MANIFEST_INFO_REQUIRED
mt.exe -nologo -manifest "ciscodump.exe.manifest" -outputresource:ciscodump.exe;1
!ENDIF
!ENDIF
clean:
rm -f $(androiddump_OBJECTS) $(randpktdump_OBJECTS) $(sshdump_OBJECTS) \
$(EXECUTABLES) *.nativecodeanalysis.xml *.pdb *.sbr \
${ciscodump_OBJECTS} $(EXECUTABLES) *.nativecodeanalysis.xml *.pdb *.sbr \
doxygen.cfg *.exe.manifest
# "distclean" removes all files not part of the distribution.
@ -138,8 +157,8 @@ checkapi: checkapi-base checkapi-todo
checkapi-base:
$(PERL) ../tools/checkAPIs.pl -g deprecated-gtk -build \
$(androiddump_SOURCES) $(randpktdump_SOURCES) $(sshdump_SOURCES)
$(androiddump_SOURCES) $(randpktdump_SOURCES) $(sshdump_SOURCES) ${ciscodump_SOURCES}
checkapi-todo:
$(PERL) ../tools/checkAPIs.pl -M -g deprecated-gtk-todo -build \
$(androiddump_SOURCES) $(randpktdump_SOURCES) $(sshdump_SOURCES)
$(androiddump_SOURCES) $(randpktdump_SOURCES) $(sshdump_SOURCES) ${ciscodump_SOURCES}

735
extcap/ciscodump.c Normal file
View File

@ -0,0 +1,735 @@
/* 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
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include <extcap/extcap-base.h>
#include <wsutil/interface.h>
#include <extcap/ssh-base.h>
#include <pcapio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#ifndef STDERR_FILENO
#define STDERR_FILENO 2
#endif
#ifndef STDOUT_FILENO
#define STDOUT_FILENO 1
#endif
#define CISCODUMP_VERSION_MAJOR "1"
#define CISCODUMP_VERSION_MINOR "0"
#define CISCODUMP_VERSION_RELEASE "0"
/* The read timeout in msec */
#define CISCODUMP_READ_TIMEOUT 3000
#define CISCODUMP_EXTCAP_INTERFACE "cisco"
#define SSH_READ_BLOCK_SIZE 1024
#define SSH_READ_TIMEOUT 10000
#define WIRESHARK_CAPTURE_POINT "WIRESHARK_CAPTURE_POINT"
#define WIRESHARK_CAPTURE_BUFFER "WIRESHARK_CAPTURE_BUFFER"
#define WIRESHARK_CAPTURE_ACCESSLIST "WIRESHARK_CAPTURE_ACCESSLIST"
#define PCAP_SNAPLEN 0xffff
#define PACKET_MAX_SIZE 65535
#define MINIMUM_IOS_MAJOR 12
#define MINIMUM_IOS_MINOR 4
/* Status of the parser */
enum {
CISCODUMP_PARSER_STARTING,
CISCODUMP_PARSER_IN_PACKET,
CISCODUMP_PARSER_IN_HEADER,
CISCODUMP_PARSER_END_PACKET,
CISCODUMP_PARSER_ERROR
};
#define verbose_print(...) { if (verbose) printf(__VA_ARGS__); }
static gboolean verbose = TRUE;
enum {
EXTCAP_BASE_OPTIONS_ENUM,
OPT_HELP,
OPT_VERSION,
OPT_VERBOSE,
OPT_REMOTE_HOST,
OPT_REMOTE_PORT,
OPT_REMOTE_USERNAME,
OPT_REMOTE_PASSWORD,
OPT_REMOTE_INTERFACE,
OPT_REMOTE_FILTER,
OPT_SSHKEY,
OPT_SSHKEY_PASSPHRASE,
OPT_REMOTE_COUNT
};
static struct option longopts[] = {
EXTCAP_BASE_OPTIONS,
{ "help", no_argument, NULL, OPT_HELP},
{ "version", no_argument, NULL, OPT_VERSION},
{ "verbose", optional_argument, NULL, OPT_VERBOSE},
SSH_BASE_OPTIONS,
{ 0, 0, 0, 0}
};
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->next != NULL) {
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 = cur->next;
}
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. 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, 2000) > 0 && bytes_read < total) {
verbose_print("%c", chr);
if (chr == '^')
return EXIT_FAILURE;
if (outbuf)
outbuf[bytes_read] = chr;
bytes_read++;
}
return EXIT_SUCCESS;
}
static void ciscodump_cleanup(ssh_session sshs, ssh_channel channel, const char* iface, const char* cfilter)
{
if (channel) {
if (read_output_bytes(channel, -1, NULL) == EXIT_SUCCESS) {
ssh_channel_printf(channel, "monitor capture point stop %s\n", WIRESHARK_CAPTURE_POINT);
ssh_channel_printf(channel, "no monitor capture point ip cef %s %s\n", WIRESHARK_CAPTURE_POINT, iface);
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);
}
}
ssh_cleanup(&sshs, &channel);
}
static int wait_until_data(ssh_channel channel, const long unsigned count)
{
long unsigned got = 0;
char output[SSH_READ_BLOCK_SIZE];
char* output_ptr;
guint rounds = 100;
while (got < count && rounds--) {
if (ssh_channel_printf(channel, "show monitor capture buffer %s parameters\n", WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE) {
errmsg_print("Can't write to channel");
return EXIT_FAILURE;
}
if (read_output_bytes(channel, SSH_READ_BLOCK_SIZE, output) == EXIT_FAILURE)
return EXIT_FAILURE;
output_ptr = g_strstr_len(output, strlen(output), "Packets");
if (!output_ptr) {
errmsg_print("Error in sscanf()");
return EXIT_FAILURE;
} else {
sscanf(output_ptr, "Packets : %lu", &got);
}
}
verbose_print("All packets got: dumping\n");
return EXIT_SUCCESS;
}
static int parse_line(char* packet _U_, unsigned* offset, char* line, int status)
{
char** parts;
char** part;
int value;
guint64 size;
if (strlen(line) <= 1) {
if (status == CISCODUMP_PARSER_IN_PACKET)
return CISCODUMP_PARSER_END_PACKET;
else
return status;
}
/* 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 */
if (g_regex_match_simple("^\\d{2}:\\d{2}:\\d{2}.\\d+ .*", line, G_REGEX_CASELESS, G_REGEX_MATCH_ANCHORED)) {
return CISCODUMP_PARSER_IN_HEADER;
}
/* 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-Z]{8,8}:\\s+([\\dA-Z]{2,8})\\s+([\\dA-Z]{2,8}){0,1}\\s+([\\dA-Z]{2,8}){0,1}\\s+([\\dA-Z]{2,8}){0,1}.*",
line, G_REGEX_CASELESS, G_REGEX_MATCH_ANCHORED);
part = parts;
while(*part) {
if (strlen(*part) > 1) {
value = htonl(strtoul(*part, NULL, 16));
size = strlen(*part) / 2;
memcpy(packet + *offset, &value, size);
*offset += size;
}
part++;
}
return CISCODUMP_PARSER_IN_PACKET;
}
static void ssh_loop_read(ssh_channel channel, FILE* fp, const long unsigned count)
{
char line[SSH_READ_BLOCK_SIZE];
char chr;
unsigned offset = 0;
unsigned packet_size = 0;
char packet[PACKET_MAX_SIZE];
time_t curtime = time(NULL);
int err;
guint64 bytes_written;
long unsigned packets = 0;
int status = CISCODUMP_PARSER_STARTING;
do {
if (ssh_channel_read_timeout(channel, &chr, 1, FALSE, SSH_READ_TIMEOUT) == SSH_ERROR) {
errmsg_print("Error reading from channel");
return;
}
if (chr != '\n') {
line[offset] = chr;
offset++;
} else {
/* Parse the current line */
line[offset] = '\0';
status = parse_line(packet, &packet_size, line, status);
if (status == CISCODUMP_PARSER_END_PACKET) {
/* dump the packet to the pcap file */
libpcap_write_packet(fp, curtime, (guint32)(curtime / 1000), packet_size, packet_size, packet, &bytes_written, &err);
verbose_print("Dumped packet %lu size: %u\n", packets, packet_size);
packet_size = 0;
status = CISCODUMP_PARSER_STARTING;
packets++;
}
offset = 0;
}
} while(packets < count);
}
static int check_ios_version(ssh_channel channel)
{
gchar* cmdline = "show version | include Cisco IOS\n";
gchar version[255];
guint major = 0;
guint minor = 0;
gchar* cur;
memset(version, 0x0, 255);
if (ssh_channel_write(channel, cmdline, (guint32)strlen(cmdline)) == SSH_ERROR)
return FALSE;
if (read_output_bytes(channel, (int)strlen(cmdline), NULL) == EXIT_FAILURE)
return FALSE;
if (read_output_bytes(channel, 255, version) == EXIT_FAILURE)
return FALSE;
cur = g_strstr_len(version, strlen(version), "Version");
if (cur) {
cur += strlen("Version ");
sscanf(cur, "%u.%u", &major, &minor);
if ((major > MINIMUM_IOS_MAJOR) || (major == MINIMUM_IOS_MAJOR && minor >= MINIMUM_IOS_MINOR)) {
verbose_print("Current IOS Version: %u.%u\n", major, minor);
if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE)
return FALSE;
return TRUE;
}
}
errmsg_print("Invalid IOS version. Minimum version: 12.4, current: %u.%u", major, minor);
return FALSE;
}
static ssh_channel run_capture(ssh_session sshs, const char* iface, const char* cfilter, const unsigned long int count)
{
char* cmdline = NULL;
ssh_channel channel;
int ret = 0;
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;
if (!check_ios_version(channel))
goto error;
if (ssh_channel_printf(channel, "terminal length 0\n") == EXIT_FAILURE)
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_printf(channel, "monitor capture buffer %s limit packet-count %lu\n", WIRESHARK_CAPTURE_BUFFER, count) == EXIT_FAILURE)
goto error;
if (cfilter) {
gchar* multiline_filter;
gchar* chr;
if (ssh_channel_printf(channel, "configure terminal\n") == EXIT_FAILURE)
goto error;
if (ssh_channel_printf(channel, "ip access-list ex %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';
verbose_print("Splitting filter into multiline\n");
}
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_printf(channel, "monitor capture buffer %s filter access-list %s\n",
WIRESHARK_CAPTURE_BUFFER, WIRESHARK_CAPTURE_ACCESSLIST) == EXIT_FAILURE)
goto error;
}
if (ssh_channel_printf(channel, "monitor capture point ip cef %s %s both\n", WIRESHARK_CAPTURE_POINT,
iface) == EXIT_FAILURE)
goto error;
if (ssh_channel_printf(channel, "monitor capture point associate %s %s \n", WIRESHARK_CAPTURE_POINT,
WIRESHARK_CAPTURE_BUFFER) == EXIT_FAILURE)
goto error;
if (ssh_channel_printf(channel, "monitor capture point start %s\n", WIRESHARK_CAPTURE_POINT) == EXIT_FAILURE)
goto error;
if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE)
goto error;
if (wait_until_data(channel, count) == EXIT_FAILURE)
goto error;
if (read_output_bytes(channel, -1, NULL) == EXIT_FAILURE)
goto error;
cmdline = g_strdup_printf("show monitor capture buffer %s dump\n", WIRESHARK_CAPTURE_BUFFER);
if (ssh_channel_printf(channel, cmdline) == EXIT_FAILURE)
goto error;
if (read_output_bytes(channel, (int)strlen(cmdline), NULL) == EXIT_FAILURE)
goto error;
g_free(cmdline);
return channel;
error:
g_free(cmdline);
errmsg_print("Error running ssh remote command");
read_output_bytes(channel, -1, NULL);
ssh_channel_close(channel);
ssh_channel_free(channel);
return NULL;
}
static int ssh_open_remote_connection(const char* hostname, const unsigned int port, const char* username, const char* password,
const char* sshkey, const char* sshkey_passphrase, const char* iface, const char* cfilter,
const unsigned long int count, const char* fifo)
{
ssh_session sshs;
ssh_channel channel;
FILE* fp = stdout;
guint64 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, "w");
if (!fp) {
errmsg_print("Error creating output file: %s\n", g_strerror(errno));
return EXIT_FAILURE;
}
}
sshs = create_ssh_connection(hostname, port, username, password, sshkey, sshkey_passphrase, &err_info);
if (!sshs) {
errmsg_print("Error creating connection: %s", err_info);
goto cleanup;
}
if (!libpcap_write_file_header(fp, 1, PCAP_SNAPLEN, FALSE, &bytes_written, &err)) {
errmsg_print("Can't write pcap file header");
goto cleanup;
}
channel = run_capture(sshs, iface, cfilter, count);
if (!channel) {
ret = EXIT_FAILURE;
goto cleanup;
}
verbose_print("\n");
/* read from channel and write into fp */
ssh_loop_read(channel, fp, count);
/* clean up and exit */
ciscodump_cleanup(sshs, channel, iface, cfilter);
ret = EXIT_SUCCESS;
cleanup:
if (fp != stdout)
fclose(fp);
verbose_print("\n\n");
return ret;
}
static void help(const char* binname)
{
printf("Help\n");
printf(" Usage:\n");
printf(" %s --extcap-interfaces\n", binname);
printf(" %s --extcap-interface=INTERFACE --extcap-dlts\n", binname);
printf(" %s --extcap-interface=INTERFACE --extcap-config\n", binname);
printf(" %s --extcap-interface=INTERFACE --remote-host myhost --remote-port 22222 "
"--remote-username myuser --remote-interface gigabit0/0 "
"--fifo=FILENAME --capture\n", binname);
printf("\n\n");
printf(" --help: print this help\n");
printf(" --version: print the version\n");
printf(" --verbose: print more messages\n");
printf(" --extcap-interfaces: list the interfaces\n");
printf(" --extcap-interface <iface>: specify the interface\n");
printf(" --extcap-dlts: list the DTLs for an interface\n");
printf(" --extcap-config: list the additional configuration for an interface\n");
printf(" --extcap-capture-filter <filter>: the capture filter\n");
printf(" --capture: run the capture\n");
printf(" --fifo <file>: dump data to file or fifo\n");
printf(" --remote-host <host>: the remote SSH host\n");
printf(" --remote-port <port>: the remote SSH port (default: 22)\n");
printf(" --remote-username <username>: the remote SSH username (default: the current user)\n");
printf(" --remote-password <password>: the remote SSH password. If not specified, ssh-agent and ssh-key are used\n");
printf(" --sshkey <public key path>: the path of the ssh key\n");
printf(" --sshkey-passphrase <public key passphrase>: the passphrase to unlock public ssh\n");
printf(" --remote-interface <iface>: the remote capture interface\n");
printf(" --remote-filter <filter>: a filter for remote capture (default: don't capture data for local interfaces IPs)\n");
}
static int list_config(char *interface, unsigned int remote_port)
{
unsigned inc = 0;
char* ipfilter;
if (!interface) {
g_fprintf(stderr, "ERROR: No interface specified.\n");
return EXIT_FAILURE;
}
if (g_strcmp0(interface, CISCODUMP_EXTCAP_INTERFACE)) {
errmsg_print("ERROR: interface must be %s\n", 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}\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}\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}\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.}\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}\n",
inc++);
printf("arg {number=%u}{call--sshkey-passphrase}{display=SSH key passphrase}"
"{type=password}{tooltip=Passphrase to unlock the SSH private key}\n",
inc++);
printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}"
"{type=string}{required=true}{tooltip=The remote network interface used for 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("\n");
printf("arg {number=%u}{call=--remote-count}{display=Packets to capture}"
"{type=unsigned}{required=true}{tooltip=The number of remote packets to capture.}\n",
inc++);
g_free(ipfilter);
return EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
int result;
int option_idx = 0;
int i;
char* remote_host = NULL;
unsigned int remote_port = 22;
char* remote_username = NULL;
char* remote_password = NULL;
char* remote_interface = NULL;
char* sshkey = NULL;
char* sshkey_passphrase = NULL;
char* remote_filter = NULL;
unsigned long int count = 0;
int ret = EXIT_SUCCESS;
extcap_parameters * extcap_conf = g_new0(extcap_parameters, 1);
#ifdef _WIN32
WSADATA wsaData;
attach_parent_console();
#endif /* _WIN32 */
extcap_base_set_util_info(extcap_conf, CISCODUMP_VERSION_MAJOR, CISCODUMP_VERSION_MINOR, CISCODUMP_VERSION_RELEASE, NULL);
extcap_base_register_interface(extcap_conf, CISCODUMP_EXTCAP_INTERFACE, "Cisco remote capture", 147, "Remote capture dependent DLT");
opterr = 0;
optind = 0;
if (argc == 1) {
help(argv[0]);
return EXIT_FAILURE;
}
for (i = 0; i < argc; i++) {
verbose_print("%s ", argv[i]);
}
verbose_print("\n");
while ((result = getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
switch (result) {
case OPT_HELP:
help(argv[0]);
return EXIT_SUCCESS;
case OPT_VERBOSE:
verbose = TRUE;
break;
case OPT_VERSION:
printf("%s.%s.%s\n", CISCODUMP_VERSION_MAJOR, CISCODUMP_VERSION_MINOR, CISCODUMP_VERSION_RELEASE);
return EXIT_SUCCESS;
case OPT_REMOTE_HOST:
g_free(remote_host);
remote_host = g_strdup(optarg);
break;
case OPT_REMOTE_PORT:
remote_port = (unsigned int)strtoul(optarg, NULL, 10);
if (remote_port > 65535 || remote_port == 0) {
printf("Invalid port: %s\n", optarg);
return EXIT_FAILURE;
}
break;
case OPT_REMOTE_USERNAME:
g_free(remote_username);
remote_username = g_strdup(optarg);
break;
case OPT_REMOTE_PASSWORD:
g_free(remote_password);
remote_password = g_strdup(optarg);
memset(optarg, 'X', strlen(optarg));
break;
case OPT_SSHKEY:
g_free(sshkey);
sshkey = g_strdup(optarg);
break;
case OPT_SSHKEY_PASSPHRASE:
g_free(sshkey_passphrase);
sshkey_passphrase = g_strdup(optarg);
memset(optarg, 'X', strlen(optarg));
break;
case OPT_REMOTE_INTERFACE:
g_free(remote_interface);
remote_interface = g_strdup(optarg);
break;
case OPT_REMOTE_FILTER:
g_free(remote_filter);
remote_filter = g_strdup(optarg);
break;
case OPT_REMOTE_COUNT:
count = strtoul(optarg, NULL, 10);
break;
case ':':
/* missing option argument */
errmsg_print("Option '%s' requires an argument", argv[optind - 1]);
break;
default:
if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, optarg)) {
errmsg_print("Invalid option: %s", argv[optind - 1]);
return EXIT_FAILURE;
}
}
}
if (optind != argc) {
errmsg_print("Unexpected extra option: %s", argv[optind]);
return EXIT_FAILURE;
}
if (extcap_base_handle_interface(extcap_conf))
return EXIT_SUCCESS;
if (extcap_conf->show_config)
return list_config(extcap_conf->interface, remote_port);
#ifdef _WIN32
result = WSAStartup(MAKEWORD(1,1), &wsaData);
if (result != 0) {
if (verbose)
errmsg_print("ERROR: WSAStartup failed with error: %d", result);
return EXIT_FAILURE;
}
#endif /* _WIN32 */
if (extcap_conf->capture) {
if (!remote_host) {
errmsg_print("Missing parameter: --remote-host");
return EXIT_FAILURE;
}
if (!remote_interface) {
errmsg_print("ERROR: No interface specified (--remote-interface)");
return EXIT_FAILURE;
}
if (count == 0) {
errmsg_print("ERROR: count of packets must be specified (--remote-count)");
return EXIT_FAILURE;
}
ret = ssh_open_remote_connection(remote_host, remote_port, remote_username,
remote_password, sshkey, sshkey_passphrase, remote_interface,
remote_filter, count, extcap_conf->fifo);
} else {
verbose_print("You should not come here... maybe some parameter missing?\n");
ret = EXIT_FAILURE;
}
extcap_base_cleanup(&extcap_conf);
return ret;
}
#ifdef _WIN32
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
return main(__argc, __argv);
}
#endif
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: t
* End:
*
* vi: set shiftwidth=4 tabstop=4 noexpandtab:
* :indentSize=4:tabSize=4:noTabs=false:
*/

View File

@ -26,6 +26,7 @@
#include <extcap/extcap-base.h>
#include <log.h>
#include <string.h>
#define verbose_print(...) { if (verbose) printf(__VA_ARGS__); }
@ -132,6 +133,22 @@ failure:
return NULL;
}
int ssh_channel_printf(ssh_channel channel, const char* fmt, ...)
{
gchar* buf;
va_list arg;
int ret = EXIT_SUCCESS;
va_start(arg, fmt);
buf = g_strdup_vprintf(fmt, arg);
if (ssh_channel_write(channel, buf, (guint32)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) {

View File

@ -27,10 +27,24 @@
#include <libssh/libssh.h>
#define SSH_BASE_OPTIONS \
{ "remote-host", required_argument, NULL, OPT_REMOTE_HOST}, \
{ "remote-port", required_argument, NULL, OPT_REMOTE_PORT}, \
{ "remote-username", required_argument, NULL, OPT_REMOTE_USERNAME}, \
{ "remote-password", required_argument, NULL, OPT_REMOTE_PASSWORD}, \
{ "remote-interface", required_argument, NULL, OPT_REMOTE_INTERFACE}, \
{ "remote-filter", required_argument, NULL, OPT_REMOTE_FILTER}, \
{ "remote-count", required_argument, NULL, OPT_REMOTE_COUNT}, \
{ "sshkey", required_argument, NULL, OPT_SSHKEY}, \
{ "sshkey-passphrase", required_argument, NULL, OPT_SSHKEY_PASSPHRASE}
/* Create a ssh connection using all the possible authentication menthods */
ssh_session create_ssh_connection(const char* hostname, const unsigned int port, const char* username,
const char* password, const char* sshkey_path, const char* sshkey_passphrase, 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);