/* 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 * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "config.h" #define WS_LOG_DOMAIN "ciscodump" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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_SSH_SHA1, 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 */ /*
: <1st group> <2nd group> <3rd group> <4th group> */ /* 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
: <1st group> <...> <8th group> <5th group> */ /* 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=--ssh-sha1}{display=Support SHA-1 keys (deprecated)}" "{type=boolflag}{tooltip=Support keys and key exchange algorithms using SHA-1 (deprecated)}{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 ", "the remote SSH host"); extcap_help_add_option(extcap_conf, "--remote-port ", "the remote SSH port (default: 22)"); extcap_help_add_option(extcap_conf, "--remote-username ", "the remote SSH username (default: the current user)"); extcap_help_add_option(extcap_conf, "--remote-password ", "the remote SSH password. " "If not specified, ssh-agent and ssh-key are used"); extcap_help_add_option(extcap_conf, "--sshkey ", "the path of the ssh key"); extcap_help_add_option(extcap_conf, "--sshkey-passphrase ", "the passphrase to unlock public ssh"); extcap_help_add_option(extcap_conf, "--proxycommand ", "the command to use as proxy for the ssh connection"); extcap_help_add_option(extcap_conf, "--ssh-sha1", "support keys and key exchange using SHA-1 (deprecated)"); extcap_help_add_option(extcap_conf, "--remote-interface ", "the remote capture interface"); extcap_help_add_option(extcap_conf, "--remote-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_SSH_SHA1: ssh_params->ssh_sha1 = true; 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: */