2 * sshdump is extcap tool used to capture data using a remote ssh host
4 * Copyright 2015, Dario Lombardo
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
15 #include <extcap/extcap-base.h>
16 #include <extcap/ssh-base.h>
17 #include <wsutil/interface.h>
18 #include <wsutil/file_util.h>
19 #include <wsutil/strtoi.h>
20 #include <wsutil/filesystem.h>
26 #define SSHDUMP_VERSION_MAJOR "1"
27 #define SSHDUMP_VERSION_MINOR "0"
28 #define SSHDUMP_VERSION_RELEASE "0"
30 #define SSH_EXTCAP_INTERFACE "sshdump"
31 #define SSH_READ_BLOCK_SIZE 256
34 EXTCAP_BASE_OPTIONS_ENUM,
42 OPT_REMOTE_CAPTURE_COMMAND,
45 OPT_SSHKEY_PASSPHRASE,
51 static struct option longopts[] = {
53 { "help", no_argument, NULL, OPT_HELP},
54 { "version", no_argument, NULL, OPT_VERSION},
56 { "remote-capture-command", required_argument, NULL, OPT_REMOTE_CAPTURE_COMMAND},
57 { "remote-sudo", required_argument, NULL, OPT_REMOTE_SUDO },
58 { "remote-noprom", no_argument, NULL, OPT_REMOTE_NOPROM },
62 static char* interfaces_list_to_filter(GSList* if_list, const unsigned int remote_port);
64 static int ssh_loop_read(ssh_channel channel, FILE* fp)
67 int ret = EXIT_SUCCESS;
68 char buffer[SSH_READ_BLOCK_SIZE];
70 /* read from stdin until data are available */
71 while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
72 nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 0);
74 g_warning("Error reading from channel");
80 if (fwrite(buffer, 1, nbytes, fp) != (guint)nbytes) {
81 g_warning("Error writing to fifo");
88 /* read loop finished... maybe something wrong happened. Read from stderr */
89 while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
90 nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 1);
92 g_warning("Error reading from channel");
95 if (fwrite(buffer, 1, nbytes, stderr) != (guint)nbytes) {
96 g_warning("Error writing to stderr");
102 if (ssh_channel_send_eof(channel) != SSH_OK) {
103 g_warning("Error sending EOF in ssh channel");
109 static char* local_interfaces_to_filter(const guint16 remote_port)
111 GSList* interfaces = local_interfaces_to_list();
112 char* filter = interfaces_list_to_filter(interfaces, remote_port);
113 g_slist_free_full(interfaces, g_free);
117 static ssh_channel run_ssh_command(ssh_session sshs, const char* capture_command, const gboolean use_sudo, gboolean noprom,
118 const char* iface, const char* cfilter, const guint32 count)
122 char* quoted_iface = NULL;
123 char* quoted_filter = NULL;
124 char* count_str = NULL;
125 unsigned int remote_port = 22;
130 channel = ssh_channel_new(sshs);
132 g_warning("Can't create channel");
136 if (ssh_channel_open_session(channel) != SSH_OK) {
137 g_warning("Can't open session");
138 ssh_channel_free(channel);
142 ssh_options_get_port(sshs, &remote_port);
144 /* escape parameters to go save with the shell */
145 if (capture_command && *capture_command) {
146 cmdline = g_strdup(capture_command);
147 g_debug("Remote capture command has disabled other options");
149 quoted_iface = g_shell_quote(iface);
150 quoted_filter = g_shell_quote(cfilter ? cfilter : "");
152 count_str = g_strdup_printf("-c %u", count);
154 cmdline = g_strdup_printf("%s tcpdump -U -i %s %s -w - %s %s",
155 use_sudo ? "sudo" : "",
158 count_str ? count_str : "",
162 g_debug("Running: %s", cmdline);
163 if (ssh_channel_request_exec(channel, cmdline) != SSH_OK) {
164 g_warning("Can't request exec");
165 ssh_channel_close(channel);
166 ssh_channel_free(channel);
170 g_free(quoted_iface);
171 g_free(quoted_filter);
178 static int ssh_open_remote_connection(const char* hostname, const unsigned int port, const char* username, const char* password,
179 const char* sshkey, const char* sshkey_passphrase, const char* iface, const char* cfilter, const char* capture_command,
180 const gboolean use_sudo, gboolean noprom, const guint32 count, const char* fifo)
182 ssh_session sshs = NULL;
183 ssh_channel channel = NULL;
185 int ret = EXIT_FAILURE;
186 char* err_info = NULL;
188 if (g_strcmp0(fifo, "-")) {
189 /* Open or create the output file */
190 fp = fopen(fifo, "wb");
192 g_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno));
197 sshs = create_ssh_connection(hostname, port, username, password, sshkey, sshkey_passphrase, &err_info);
200 g_warning("Error creating connection: %s", err_info);
204 channel = run_ssh_command(sshs, capture_command, use_sudo, noprom, iface, cfilter, count);
207 g_warning("Can't run ssh command");
211 /* read from channel and write into fp */
212 if (ssh_loop_read(channel, fp) != EXIT_SUCCESS) {
213 g_warning("Error in read loop");
221 g_warning("%s", err_info);
224 /* clean up and exit */
225 ssh_cleanup(&sshs, &channel);
227 if (g_strcmp0(fifo, "-"))
232 static char* interfaces_list_to_filter(GSList* interfaces, const unsigned int remote_port)
234 GString* filter = g_string_new(NULL);
238 g_string_append_printf(filter, "not port %u", remote_port);
240 g_string_append_printf(filter, "not ((host %s", (char*)interfaces->data);
241 cur = g_slist_next(interfaces);
243 g_string_append_printf(filter, " or host %s", (char*)cur->data);
244 cur = g_slist_next(cur);
246 g_string_append_printf(filter, ") and port %u)", remote_port);
248 return g_string_free(filter, FALSE);
251 static int list_config(char *interface, unsigned int remote_port)
257 g_warning("ERROR: No interface specified.");
261 if (g_strcmp0(interface, SSH_EXTCAP_INTERFACE)) {
262 g_warning("ERROR: interface must be %s", SSH_EXTCAP_INTERFACE);
266 ipfilter = local_interfaces_to_filter(remote_port);
268 printf("arg {number=%u}{call=--remote-host}{display=Remote SSH server address}"
269 "{type=string}{tooltip=The remote SSH host. It can be both "
270 "an IP address or a hostname}{required=true}{group=Server}\n", inc++);
271 printf("arg {number=%u}{call=--remote-port}{display=Remote SSH server port}"
272 "{type=unsigned}{default=22}{tooltip=The remote SSH host port (1-65535)}"
273 "{range=1,65535}{group=Server}\n", inc++);
274 printf("arg {number=%u}{call=--remote-username}{display=Remote SSH server username}"
275 "{type=string}{default=%s}{tooltip=The remote SSH username. If not provided, "
276 "the current user will be used}{group=Authentication}\n", inc++, g_get_user_name());
277 printf("arg {number=%u}{call=--remote-password}{display=Remote SSH server password}"
278 "{type=password}{tooltip=The SSH password, used when other methods (SSH agent "
279 "or key files) are unavailable.}{group=Authentication}\n", inc++);
280 printf("arg {number=%u}{call=--sshkey}{display=Path to SSH private key}"
281 "{type=fileselect}{tooltip=The path on the local filesystem of the private ssh key}"
282 "{group=Authentication}\n",inc++);
283 printf("arg {number=%u}{call=--sshkey-passphrase}{display=SSH key passphrase}"
284 "{type=password}{tooltip=Passphrase to unlock the SSH private key}{group=Authentication}\n",
286 printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}"
287 "{type=string}{default=eth0}{tooltip=The remote network interface used for capture"
288 "}{group=Capture}\n", inc++);
289 printf("arg {number=%u}{call=--remote-capture-command}{display=Remote capture command}"
290 "{type=string}{tooltip=The remote command used to capture}{group=Capture}\n", inc++);
291 printf("arg {number=%u}{call=--remote-sudo}{display=Use sudo on the remote machine}"
292 "{type=boolean}{tooltip=Prepend the capture command with sudo on the remote machine}"
293 "{group=Capture}\n", inc++);
294 printf("arg {number=%u}{call=--remote-noprom}{display=No promiscuous mode}"
295 "{type=boolflag}{tooltip=Don't use promiscuous mode on the remote machine}{group=Capture}"
297 printf("arg {number=%u}{call=--remote-filter}{display=Remote capture filter}{type=string}"
298 "{tooltip=The remote capture filter}", inc++);
300 printf("{default=%s}", ipfilter);
301 printf("{group=Capture}\n");
302 printf("arg {number=%u}{call=--remote-count}{display=Packets to capture}"
303 "{type=unsigned}{default=0}{tooltip=The number of remote packets to capture. (Default: inf)}"
304 "{group=Capture}\n", inc++);
306 extcap_config_debug(&inc);
313 static char* concat_filters(const char* extcap_filter, const char* remote_filter)
315 if (!extcap_filter && remote_filter)
316 return g_strdup(remote_filter);
318 if (!remote_filter && extcap_filter)
319 return g_strdup(extcap_filter);
321 if (!remote_filter && !extcap_filter)
324 return g_strdup_printf("(%s) and (%s)", extcap_filter, remote_filter);
327 int main(int argc, char **argv)
331 char* remote_host = NULL;
332 guint16 remote_port = 22;
333 char* remote_username = NULL;
334 char* remote_password = NULL;
335 char* remote_interface = NULL;
336 char* remote_capture_command = NULL;
338 char* sshkey_passphrase = NULL;
339 char* remote_filter = NULL;
341 int ret = EXIT_FAILURE;
342 extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
344 char* help_header = NULL;
345 gboolean use_sudo = FALSE;
346 gboolean noprom = FALSE;
351 attach_parent_console();
354 help_url = data_file_url("sshdump.html");
355 extcap_base_set_util_info(extcap_conf, argv[0], SSHDUMP_VERSION_MAJOR, SSHDUMP_VERSION_MINOR,
356 SSHDUMP_VERSION_RELEASE, help_url);
358 extcap_base_register_interface(extcap_conf, SSH_EXTCAP_INTERFACE, "SSH remote capture", 147, "Remote capture dependent DLT");
360 help_header = g_strdup_printf(
361 " %s --extcap-interfaces\n"
362 " %s --extcap-interface=%s --extcap-dlts\n"
363 " %s --extcap-interface=%s --extcap-config\n"
364 " %s --extcap-interface=%s --remote-host myhost --remote-port 22222 "
365 "--remote-username myuser --remote-interface eth2 --remote-capture-command 'tcpdump -U -i eth0 -w -' "
366 "--fifo=FILENAME --capture\n", argv[0], argv[0], SSH_EXTCAP_INTERFACE, argv[0],
367 SSH_EXTCAP_INTERFACE, argv[0], SSH_EXTCAP_INTERFACE);
368 extcap_help_add_header(extcap_conf, help_header);
370 extcap_help_add_option(extcap_conf, "--help", "print this help");
371 extcap_help_add_option(extcap_conf, "--version", "print the version");
372 extcap_help_add_option(extcap_conf, "--remote-host <host>", "the remote SSH host");
373 extcap_help_add_option(extcap_conf, "--remote-port <port>", "the remote SSH port (default: 22)");
374 extcap_help_add_option(extcap_conf, "--remote-username <username>", "the remote SSH username (default: the current user)");
375 extcap_help_add_option(extcap_conf, "--remote-password <password>", "the remote SSH password. If not specified, ssh-agent and ssh-key are used");
376 extcap_help_add_option(extcap_conf, "--sshkey <public key path>", "the path of the ssh key");
377 extcap_help_add_option(extcap_conf, "--sshkey-passphrase <public key passphrase>", "the passphrase to unlock public ssh");
378 extcap_help_add_option(extcap_conf, "--remote-interface <iface>", "the remote capture interface (default: eth0)");
379 extcap_help_add_option(extcap_conf, "--remote-capture-command <capture command>", "the remote capture command");
380 extcap_help_add_option(extcap_conf, "--remote-sudo yes", "use sudo on the remote machine to capture");
381 extcap_help_add_option(extcap_conf, "--remote-noprom", "don't use promiscuous mode on the remote machine");
382 extcap_help_add_option(extcap_conf, "--remote-filter <filter>", "a filter for remote capture (default: don't "
383 "listen on local interfaces IPs)");
384 extcap_help_add_option(extcap_conf, "--remote-count <count>", "the number of packets to capture");
390 extcap_help_print(extcap_conf);
394 while ((result = getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
399 extcap_help_print(extcap_conf);
404 printf("%s\n", extcap_conf->version);
408 case OPT_REMOTE_HOST:
410 remote_host = g_strdup(optarg);
413 case OPT_REMOTE_PORT:
414 if (!ws_strtou16(optarg, NULL, &remote_port) || remote_port == 0) {
415 g_warning("Invalid port: %s", optarg);
420 case OPT_REMOTE_USERNAME:
421 g_free(remote_username);
422 remote_username = g_strdup(optarg);
425 case OPT_REMOTE_PASSWORD:
426 g_free(remote_password);
427 remote_password = g_strdup(optarg);
428 memset(optarg, 'X', strlen(optarg));
433 sshkey = g_strdup(optarg);
436 case OPT_SSHKEY_PASSPHRASE:
437 g_free(sshkey_passphrase);
438 sshkey_passphrase = g_strdup(optarg);
439 memset(optarg, 'X', strlen(optarg));
442 case OPT_REMOTE_INTERFACE:
443 g_free(remote_interface);
444 remote_interface = g_strdup(optarg);
447 case OPT_REMOTE_CAPTURE_COMMAND:
448 g_free(remote_capture_command);
449 remote_capture_command = g_strdup(optarg);
452 case OPT_REMOTE_SUDO:
456 case OPT_REMOTE_FILTER:
457 g_free(remote_filter);
458 remote_filter = g_strdup(optarg);
461 case OPT_REMOTE_COUNT:
462 if (!ws_strtou32(optarg, NULL, &count)) {
463 g_warning("Invalid value for count: %s", optarg);
468 case OPT_REMOTE_NOPROM:
473 /* missing option argument */
474 g_warning("Option '%s' requires an argument", argv[optind - 1]);
478 if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, optarg)) {
479 g_warning("Invalid option: %s", argv[optind - 1]);
485 extcap_cmdline_debug(argv, argc);
487 if (extcap_base_handle_interface(extcap_conf)) {
492 if (extcap_conf->show_config) {
493 ret = list_config(extcap_conf->interface, remote_port);
498 result = WSAStartup(MAKEWORD(1,1), &wsaData);
500 g_warning("ERROR: WSAStartup failed with error: %d", result);
505 if (extcap_conf->capture) {
509 g_warning("Missing parameter: --remote-host");
512 filter = concat_filters(extcap_conf->capture_filter, remote_filter);
513 ret = ssh_open_remote_connection(remote_host, remote_port, remote_username,
514 remote_password, sshkey, sshkey_passphrase, remote_interface,
515 filter, remote_capture_command, use_sudo, noprom, count, extcap_conf->fifo);
518 g_debug("You should not come here... maybe some parameter missing?");
525 g_free(remote_username);
526 g_free(remote_password);
527 g_free(remote_interface);
528 g_free(remote_capture_command);
530 g_free(sshkey_passphrase);
531 g_free(remote_filter);
532 extcap_base_cleanup(&extcap_conf);
538 WinMain (struct HINSTANCE__ *hInstance,
539 struct HINSTANCE__ *hPrevInstance,
543 return main(__argc, __argv);
548 * Editor modelines - https://www.wireshark.org/tools/modelines.html
553 * indent-tabs-mode: t
556 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
557 * :indentSize=8:tabSize=8:noTabs=false: