From b915eb97556ccc5fc60e8d23f7298ffec2d8e3b7 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 19 May 2020 15:15:08 -0700 Subject: [PATCH] initial osx parser --- jc/parsers/netstat.py | 98 +++++++++++---------- jc/parsers/netstat_osx.py | 174 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+), 45 deletions(-) create mode 100644 jc/parsers/netstat_osx.py diff --git a/jc/parsers/netstat.py b/jc/parsers/netstat.py index db5c3194..d5155650 100644 --- a/jc/parsers/netstat.py +++ b/jc/parsers/netstat.py @@ -6,7 +6,7 @@ Usage: Compatibility: - 'linux' + 'linux', 'darwin' Examples: @@ -310,16 +310,17 @@ Examples: """ import string import jc.utils +import jc.parsers.netstat_osx class info(): - version = '1.4' + version = '1.5' description = 'netstat command parser' author = 'Kelly Brazil' author_email = 'kellyjonbrazil@gmail.com' # compatible options: linux, darwin, cygwin, win32, aix, freebsd - compatible = ['linux'] + compatible = ['linux', 'darwin'] magic_commands = ['netstat'] @@ -532,59 +533,66 @@ def parse(data, raw=False, quiet=False): cleandata = data.splitlines() cleandata = list(filter(None, cleandata)) - raw_output = [] - network = False - socket = False - bluetooth = False - headers = '' - network_list = [] - socket_list = [] - for line in cleandata: + # check for OSX vs Linux + # is this from OSX? + if cleandata[0] == 'Active Internet connections' or cleandata[0] == 'Active Internet connections (including servers)': + raw_output = jc.parsers.netstat_osx.parse(cleandata) - if line.startswith('Active Internet'): - network_list = [] - network = True - socket = False - bluetooth = False - continue + # use linux parser + else: + network = False + socket = False + bluetooth = False + headers = '' + network_list = [] + socket_list = [] - if line.startswith('Active UNIX'): - socket_list = [] - network = False - socket = True - bluetooth = False - continue + for line in cleandata: - if line.startswith('Active Bluetooth'): - network = False - socket = False - bluetooth = True - continue + if line.startswith('Active Internet'): + network_list = [] + network = True + socket = False + bluetooth = False + continue - if line.startswith('Proto'): - header_text = normalize_headers(line) - headers = header_text.split() - continue + if line.startswith('Active UNIX'): + socket_list = [] + network = False + socket = True + bluetooth = False + continue - if network: - network_list.append(parse_network(headers, line)) - continue + if line.startswith('Active Bluetooth'): + network = False + socket = False + bluetooth = True + continue - if socket: - socket_list.append(parse_socket(header_text, headers, line)) - continue + if line.startswith('Proto'): + header_text = normalize_headers(line) + headers = header_text.split() + continue - if bluetooth: - # maybe implement later if requested - continue + if network: + network_list.append(parse_network(headers, line)) + continue - for item in [network_list, socket_list]: - for entry in item: - raw_output.append(entry) + if socket: + socket_list.append(parse_socket(header_text, headers, line)) + continue - raw_output = parse_post(raw_output) + if bluetooth: + # maybe implement later if requested + continue + + for item in [network_list, socket_list]: + for entry in item: + raw_output.append(entry) + + raw_output = parse_post(raw_output) if raw: return raw_output diff --git a/jc/parsers/netstat_osx.py b/jc/parsers/netstat_osx.py new file mode 100644 index 00000000..cb361830 --- /dev/null +++ b/jc/parsers/netstat_osx.py @@ -0,0 +1,174 @@ +"""jc - JSON CLI output utility OSX netstat Parser""" +import string +import jc.utils + + +def normalize_headers(header): + header = header.lower() + header = header.replace('local address', 'local_address') + header = header.replace('foreign address', 'foreign_address') + header = header.replace('(state)', 'state') + header = header.replace('-', '_') + + return header + + +def parse_network(headers, entry): + # Count words in header + # if len of line is one less than len of header, then insert None in field 5 + entry = entry.split(maxsplit=len(headers) - 1) + + if len(entry) == len(headers) - 1: + entry.insert(5, None) + + output_line = dict(zip(headers, entry)) + output_line['kind'] = 'network' + + return output_line + + +def parse_socket(headers, entry): + # Count words in header + # if len of line is one less than len of header, then insert None in field 5 + entry = entry.split(maxsplit=len(headers) - 1) + + if len(entry) == len(headers) - 1: + entry.insert(5, None) + + output_line = dict(zip(headers, entry)) + output_line['kind'] = 'socket' + + return output_line + + +def parse_post(raw_data): + # clean up trailing whitespace on each item in each entry + # flags --- = null + # program_name - = null + # split pid and program name and ip addresses and ports + # create network and transport protocol fields + + for entry in raw_data: + for item in entry: + try: + entry[item] = entry[item].rstrip() + except (AttributeError): + # skips trying to rstrip Null entries + pass + + if 'flags' in entry: + if entry['flags'] == '---': + entry['flags'] = None + + if 'program_name' in entry: + entry['program_name'] = entry['program_name'].strip() + if entry['program_name'] == '-': + entry['program_name'] = None + + if entry['program_name']: + pid = entry['program_name'].split('/', maxsplit=1)[0] + name = entry['program_name'].split('/', maxsplit=1)[1] + entry['pid'] = pid + entry['program_name'] = name + + if 'local_address' in entry: + if entry['local_address']: + ladd = entry['local_address'].rsplit(':', maxsplit=1)[0] + lport = entry['local_address'].rsplit(':', maxsplit=1)[1] + entry['local_address'] = ladd + entry['local_port'] = lport + + if 'foreign_address' in entry: + if entry['foreign_address']: + fadd = entry['foreign_address'].rsplit(':', maxsplit=1)[0] + fport = entry['foreign_address'].rsplit(':', maxsplit=1)[1] + entry['foreign_address'] = fadd + entry['foreign_port'] = fport + + if 'proto' in entry and 'kind' in entry: + if entry['kind'] == 'network': + if 'tcp' in entry['proto']: + entry['transport_protocol'] = 'tcp' + elif 'udp' in entry['proto']: + entry['transport_protocol'] = 'udp' + else: + entry['transport_protocol'] = None + + if '6' in entry['proto']: + entry['network_protocol'] = 'ipv6' + else: + entry['network_protocol'] = 'ipv4' + + return raw_data + + +def parse(cleandata): + """ + Main text parsing function + + Parameters: + + cleandata: (string) text data to parse + + Returns: + + List of dictionaries. Raw or processed structured data. + """ + raw_output = [] + network = False + socket = False + bluetooth = False + headers = '' + network_list = [] + socket_list = [] + + for line in cleandata: + + if line.startswith('Active Internet'): + network_list = [] + network = True + socket = False + bluetooth = False + continue + + if line.startswith('Active LOCAL (UNIX) domain sockets'): + socket_list = [] + network = False + socket = True + bluetooth = False + continue + + if line.startswith('Active Bluetooth'): + network = False + socket = False + bluetooth = True + continue + + if line.startswith('Socket ') or line.startswith('Proto '): + header_text = normalize_headers(line) + headers = header_text.split() + continue + + if line.startswith('Address '): + header_text = normalize_headers(line) + headers = header_text.split() + continue + + if network: + network_list.append(parse_network(headers, line)) + continue + + if socket: + socket_list.append(parse_socket(headers, line)) + continue + + if bluetooth: + # maybe implement later if requested + continue + + for item in [network_list, socket_list]: + for entry in item: + raw_output.append(entry) + + return raw_output +