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
14 #define WS_LOG_DOMAIN "sshdump"
16 #include <extcap/extcap-base.h>
17 #include <extcap/ssh-base.h>
18 #include <wsutil/interface.h>
19 #include <wsutil/file_util.h>
20 #include <wsutil/strtoi.h>
21 #include <wsutil/filesystem.h>
22 #include <wsutil/privileges.h>
23 #include <wsutil/please_report_bug.h>
24 #include <wsutil/wslog.h>
32 static gchar* sshdump_extcap_interface;
34 #define DEFAULT_SSHDUMP_EXTCAP_INTERFACE "sshdump.exe"
36 #define DEFAULT_SSHDUMP_EXTCAP_INTERFACE "sshdump"
39 #define SSHDUMP_VERSION_MAJOR "1"
40 #define SSHDUMP_VERSION_MINOR "0"
41 #define SSHDUMP_VERSION_RELEASE "0"
43 #define SSH_READ_BLOCK_SIZE 256
46 EXTCAP_BASE_OPTIONS_ENUM,
54 OPT_REMOTE_CAPTURE_COMMAND,
57 OPT_SSHKEY_PASSPHRASE,
64 static struct option longopts[] = {
66 { "help", no_argument, NULL, OPT_HELP},
67 { "version", no_argument, NULL, OPT_VERSION},
69 { "remote-capture-command", required_argument, NULL, OPT_REMOTE_CAPTURE_COMMAND},
70 { "remote-sudo", no_argument, NULL, OPT_REMOTE_SUDO },
71 { "remote-noprom", no_argument, NULL, OPT_REMOTE_NOPROM },
75 static char* interfaces_list_to_filter(GSList* if_list, unsigned int remote_port);
77 static int ssh_loop_read(ssh_channel channel, FILE* fp)
80 int ret = EXIT_SUCCESS;
81 char buffer[SSH_READ_BLOCK_SIZE];
83 /* read from stdin until data are available */
84 while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
85 nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 0);
87 ws_warning("Error reading from channel");
93 if (fwrite(buffer, 1, nbytes, fp) != (guint)nbytes) {
94 ws_warning("Error writing to fifo");
101 /* read loop finished... maybe something wrong happened. Read from stderr */
102 while (ssh_channel_is_open(channel) && !ssh_channel_is_eof(channel)) {
103 nbytes = ssh_channel_read(channel, buffer, SSH_READ_BLOCK_SIZE, 1);
105 ws_warning("Error reading from channel");
108 if (fwrite(buffer, 1, nbytes, stderr) != (guint)nbytes) {
109 ws_warning("Error writing to stderr");
115 if (ssh_channel_send_eof(channel) != SSH_OK) {
116 ws_warning("Error sending EOF in ssh channel");
122 static char* local_interfaces_to_filter(const guint16 remote_port)
124 GSList* interfaces = local_interfaces_to_list();
125 char* filter = interfaces_list_to_filter(interfaces, remote_port);
126 g_slist_free_full(interfaces, g_free);
130 static ssh_channel run_ssh_command(ssh_session sshs, const char* capture_command, const gboolean use_sudo, gboolean noprom,
131 const char* iface, const char* cfilter, const guint32 count)
135 char* quoted_iface = NULL;
136 char* quoted_filter = NULL;
137 char* count_str = NULL;
138 unsigned int remote_port = 22;
140 channel = ssh_channel_new(sshs);
142 ws_warning("Can't create channel");
146 if (ssh_channel_open_session(channel) != SSH_OK) {
147 ws_warning("Can't open session");
148 ssh_channel_free(channel);
152 ssh_options_get_port(sshs, &remote_port);
154 /* escape parameters to go save with the shell */
155 if (capture_command && *capture_command) {
156 cmdline = g_strdup(capture_command);
157 ws_debug("Remote capture command has disabled other options");
159 quoted_iface = iface ? g_shell_quote(iface) : NULL;
160 quoted_filter = g_shell_quote(cfilter ? cfilter : "");
162 count_str = g_strdup_printf("-c %u", count);
164 cmdline = g_strdup_printf("%s tcpdump -U %s%s %s -w - %s %s",
165 use_sudo ? "sudo" : "",
166 quoted_iface ? "-i " : "",
167 quoted_iface ? quoted_iface : "",
169 count_str ? count_str : "",
173 ws_debug("Running: %s", cmdline);
174 if (ssh_channel_request_exec(channel, cmdline) != SSH_OK) {
175 ws_warning("Can't request exec");
176 ssh_channel_close(channel);
177 ssh_channel_free(channel);
181 g_free(quoted_iface);
182 g_free(quoted_filter);
189 static int ssh_open_remote_connection(const ssh_params_t* params, const char* iface, const char* cfilter,
190 const char* capture_command, const gboolean use_sudo, gboolean noprom, const guint32 count, const char* fifo)
192 ssh_session sshs = NULL;
193 ssh_channel channel = NULL;
195 int ret = EXIT_FAILURE;
196 char* err_info = NULL;
198 if (g_strcmp0(fifo, "-")) {
199 /* Open or create the output file */
200 fp = fopen(fifo, "wb");
202 ws_warning("Error creating output file: %s (%s)", fifo, g_strerror(errno));
207 sshs = create_ssh_connection(params, &err_info);
210 ws_warning("Error creating connection.");
214 channel = run_ssh_command(sshs, capture_command, use_sudo, noprom, iface, cfilter, count);
217 ws_warning("Can't run ssh command.");
221 /* read from channel and write into fp */
222 if (ssh_loop_read(channel, fp) != EXIT_SUCCESS) {
223 ws_warning("Error in read loop.");
231 ws_warning("%s", err_info);
234 /* clean up and exit */
235 ssh_cleanup(&sshs, &channel);
237 if (g_strcmp0(fifo, "-"))
242 static char* interfaces_list_to_filter(GSList* interfaces, unsigned int remote_port)
244 GString* filter = g_string_new(NULL);
247 // If no port is given, assume the default one. This might not be
248 // correct if the port is looked up from the ssh config file, but it is
249 // better than nothing.
250 if (remote_port == 0) {
255 g_string_append_printf(filter, "not port %u", remote_port);
257 g_string_append_printf(filter, "not ((host %s", (char*)interfaces->data);
258 cur = g_slist_next(interfaces);
260 g_string_append_printf(filter, " or host %s", (char*)cur->data);
261 cur = g_slist_next(cur);
263 g_string_append_printf(filter, ") and port %u)", remote_port);
265 return g_string_free(filter, FALSE);
268 static int list_config(char *interface, unsigned int remote_port)
274 ws_warning("ERROR: No interface specified.");
278 if (g_strcmp0(interface, sshdump_extcap_interface)) {
279 ws_warning("ERROR: interface must be %s", sshdump_extcap_interface);
283 ipfilter = local_interfaces_to_filter(remote_port);
285 printf("arg {number=%u}{call=--remote-host}{display=Remote SSH server address}"
286 "{type=string}{tooltip=The remote SSH host. It can be both "
287 "an IP address or a hostname}{required=true}{group=Server}\n", inc++);
288 printf("arg {number=%u}{call=--remote-port}{display=Remote SSH server port}"
289 "{type=unsigned}{tooltip=The remote SSH host port (1-65535)}"
290 "{range=1,65535}{group=Server}\n", inc++);
291 printf("arg {number=%u}{call=--remote-username}{display=Remote SSH server username}"
292 "{type=string}{tooltip=The remote SSH username. If not provided, "
293 "the current user will be used}{group=Authentication}\n", inc++);
294 printf("arg {number=%u}{call=--remote-password}{display=Remote SSH server password}"
295 "{type=password}{tooltip=The SSH password, used when other methods (SSH agent "
296 "or key files) are unavailable.}{group=Authentication}\n", inc++);
297 printf("arg {number=%u}{call=--sshkey}{display=Path to SSH private key}"
298 "{type=fileselect}{tooltip=The path on the local filesystem of the private ssh key}"
299 "{mustexist=true}{group=Authentication}\n", inc++);
300 printf("arg {number=%u}{call=--sshkey-passphrase}{display=SSH key passphrase}"
301 "{type=password}{tooltip=Passphrase to unlock the SSH private key}{group=Authentication}\n",
303 printf("arg {number=%u}{call=--proxycommand}{display=ProxyCommand}"
304 "{type=string}{tooltip=The command to use as proxy for the SSH connection}"
305 "{group=Authentication}\n", inc++);
306 printf("arg {number=%u}{call=--remote-interface}{display=Remote interface}"
307 "{type=string}{tooltip=The remote network interface used for capture"
308 "}{group=Capture}\n", inc++);
309 printf("arg {number=%u}{call=--remote-capture-command}{display=Remote capture command}"
310 "{type=string}{tooltip=The remote command used to capture}{group=Capture}\n", inc++);
311 printf("arg {number=%u}{call=--remote-sudo}{display=Use sudo on the remote machine}"
312 "{type=boolean}{tooltip=Prepend the capture command with sudo on the remote machine}"
313 "{group=Capture}\n", inc++);
314 printf("arg {number=%u}{call=--remote-noprom}{display=No promiscuous mode}"
315 "{type=boolflag}{tooltip=Don't use promiscuous mode on the remote machine}{group=Capture}"
317 printf("arg {number=%u}{call=--remote-filter}{display=Remote capture filter}{type=string}"
318 "{tooltip=The remote capture filter}", inc++);
320 printf("{default=%s}", ipfilter);
321 printf("{group=Capture}\n");
322 printf("arg {number=%u}{call=--remote-count}{display=Packets to capture}"
323 "{type=unsigned}{default=0}{tooltip=The number of remote packets to capture. (Default: inf)}"
324 "{group=Capture}\n", inc++);
326 extcap_config_debug(&inc);
333 static char* concat_filters(const char* extcap_filter, const char* remote_filter)
335 if (!extcap_filter && remote_filter)
336 return g_strdup(remote_filter);
338 if (!remote_filter && extcap_filter)
339 return g_strdup(extcap_filter);
341 if (!remote_filter && !extcap_filter)
344 return g_strdup_printf("(%s) and (%s)", extcap_filter, remote_filter);
347 int main(int argc, char *argv[])
352 ssh_params_t* ssh_params = ssh_params_new();
353 char* remote_interface = NULL;
354 char* remote_capture_command = NULL;
355 char* remote_filter = NULL;
357 int ret = EXIT_FAILURE;
358 extcap_parameters* extcap_conf = g_new0(extcap_parameters, 1);
360 char* help_header = NULL;
361 gboolean use_sudo = FALSE;
362 gboolean noprom = FALSE;
363 gchar* interface_description = g_strdup("SSH remote capture");
365 sshdump_extcap_interface = g_path_get_basename(argv[0]);
368 * Get credential information for later use.
370 init_process_policies();
373 * Attempt to get the pathname of the directory containing the
376 err_msg = init_progfile_dir(argv[0]);
377 if (err_msg != NULL) {
378 ws_warning("Can't get pathname of directory containing the captype program: %s.",
383 help_url = data_file_url("sshdump.html");
384 extcap_base_set_util_info(extcap_conf, argv[0], SSHDUMP_VERSION_MAJOR, SSHDUMP_VERSION_MINOR,
385 SSHDUMP_VERSION_RELEASE, help_url);
387 add_libssh_info(extcap_conf);
388 if (g_strcmp0(sshdump_extcap_interface, DEFAULT_SSHDUMP_EXTCAP_INTERFACE)) {
389 gchar* temp = interface_description;
390 interface_description = g_strdup_printf("%s, custom version", interface_description);
393 extcap_base_register_interface(extcap_conf, sshdump_extcap_interface, interface_description, 147, "Remote capture dependent DLT");
394 g_free(interface_description);
396 help_header = g_strdup_printf(
397 " %s --extcap-interfaces\n"
398 " %s --extcap-interface=%s --extcap-dlts\n"
399 " %s --extcap-interface=%s --extcap-config\n"
400 " %s --extcap-interface=%s --remote-host myhost --remote-port 22222 "
401 "--remote-username myuser --remote-interface eth2 --remote-capture-command 'tcpdump -U -i eth0 -w -' "
402 "--fifo=FILENAME --capture\n", argv[0], argv[0], sshdump_extcap_interface, argv[0],
403 sshdump_extcap_interface, argv[0], sshdump_extcap_interface);
404 extcap_help_add_header(extcap_conf, help_header);
406 extcap_help_add_option(extcap_conf, "--help", "print this help");
407 extcap_help_add_option(extcap_conf, "--version", "print the version");
408 extcap_help_add_option(extcap_conf, "--remote-host <host>", "the remote SSH host");
409 extcap_help_add_option(extcap_conf, "--remote-port <port>", "the remote SSH port");
410 extcap_help_add_option(extcap_conf, "--remote-username <username>", "the remote SSH username");
411 extcap_help_add_option(extcap_conf, "--remote-password <password>", "the remote SSH password. If not specified, ssh-agent and ssh-key are used");
412 extcap_help_add_option(extcap_conf, "--sshkey <public key path>", "the path of the ssh key");
413 extcap_help_add_option(extcap_conf, "--sshkey-passphrase <public key passphrase>", "the passphrase to unlock public ssh");
414 extcap_help_add_option(extcap_conf, "--proxycommand <proxy command>", "the command to use as proxy the the ssh connection");
415 extcap_help_add_option(extcap_conf, "--remote-interface <iface>", "the remote capture interface");
416 extcap_help_add_option(extcap_conf, "--remote-capture-command <capture command>", "the remote capture command");
417 extcap_help_add_option(extcap_conf, "--remote-sudo", "use sudo on the remote machine to capture");
418 extcap_help_add_option(extcap_conf, "--remote-noprom", "don't use promiscuous mode on the remote machine");
419 extcap_help_add_option(extcap_conf, "--remote-filter <filter>", "a filter for remote capture (default: don't "
420 "listen on local interfaces IPs)");
421 extcap_help_add_option(extcap_conf, "--remote-count <count>", "the number of packets to capture");
427 extcap_help_print(extcap_conf);
431 while ((result = getopt_long(argc, argv, ":", longopts, &option_idx)) != -1) {
436 extcap_help_print(extcap_conf);
441 extcap_version_print(extcap_conf);
445 case OPT_REMOTE_HOST:
446 g_free(ssh_params->host);
447 ssh_params->host = g_strdup(optarg);
450 case OPT_REMOTE_PORT:
451 if (!ws_strtou16(optarg, NULL, &ssh_params->port) || ssh_params->port == 0) {
452 ws_warning("Invalid port: %s", optarg);
457 case OPT_REMOTE_USERNAME:
458 g_free(ssh_params->username);
459 ssh_params->username = g_strdup(optarg);
462 case OPT_REMOTE_PASSWORD:
463 g_free(ssh_params->password);
464 ssh_params->password = g_strdup(optarg);
465 memset(optarg, 'X', strlen(optarg));
469 g_free(ssh_params->sshkey_path);
470 ssh_params->sshkey_path = g_strdup(optarg);
473 case OPT_SSHKEY_PASSPHRASE:
474 g_free(ssh_params->sshkey_passphrase);
475 ssh_params->sshkey_passphrase = g_strdup(optarg);
476 memset(optarg, 'X', strlen(optarg));
479 case OPT_PROXYCOMMAND:
480 g_free(ssh_params->proxycommand);
481 ssh_params->proxycommand = g_strdup(optarg);
484 case OPT_REMOTE_INTERFACE:
485 g_free(remote_interface);
486 remote_interface = g_strdup(optarg);
489 case OPT_REMOTE_CAPTURE_COMMAND:
490 g_free(remote_capture_command);
491 remote_capture_command = g_strdup(optarg);
494 case OPT_REMOTE_SUDO:
498 case OPT_REMOTE_FILTER:
499 g_free(remote_filter);
500 remote_filter = g_strdup(optarg);
503 case OPT_REMOTE_COUNT:
504 if (!ws_strtou32(optarg, NULL, &count)) {
505 ws_warning("Invalid value for count: %s", optarg);
510 case OPT_REMOTE_NOPROM:
515 /* missing option argument */
516 ws_warning("Option '%s' requires an argument", argv[optind - 1]);
520 if (!extcap_base_parse_options(extcap_conf, result - EXTCAP_OPT_LIST_INTERFACES, optarg)) {
521 ws_warning("Invalid option: %s", argv[optind - 1]);
527 extcap_cmdline_debug(argv, argc);
529 if (extcap_base_handle_interface(extcap_conf)) {
534 if (extcap_conf->show_config) {
535 ret = list_config(extcap_conf->interface, ssh_params->port);
539 err_msg = ws_init_sockets();
540 if (err_msg != NULL) {
541 ws_warning("ERROR: %s", err_msg);
543 ws_warning("%s", please_report_bug());
547 if (extcap_conf->capture) {
550 if (!ssh_params->host) {
551 ws_warning("Missing parameter: --remote-host");
554 filter = concat_filters(extcap_conf->capture_filter, remote_filter);
555 ssh_params->debug = extcap_conf->debug;
556 ret = ssh_open_remote_connection(ssh_params, remote_interface,
557 filter, remote_capture_command, use_sudo, noprom, count, extcap_conf->fifo);
560 ws_debug("You should not come here... maybe some parameter missing?");
566 ssh_params_free(ssh_params);
567 g_free(remote_capture_command);
568 g_free(remote_interface);
569 g_free(remote_filter);
570 extcap_base_cleanup(&extcap_conf);
575 * Editor modelines - https://www.wireshark.org/tools/modelines.html
580 * indent-tabs-mode: t
583 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
584 * :indentSize=8:tabSize=8:noTabs=false: