PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /opt/sharedrads/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //opt/sharedrads/check_apache |
#!/opt/imh-python/bin/python3 # vim: set ts=4 sw=4 expandtab syntax=python: """RADS tool to parse and display the output of the Apache server status page""" import sys import re import os import argparse import requests from requests import ConnectionError as ConnError, HTTPError from netaddr import IPAddress, IPNetwork, AddrFormatError def parse_page(itext): """parse HTML from status page and return a list of connections""" # strip out all HTML tags; use <td> as vertical-bar delimiter, # <tr> as newlines itext = ( re.sub(r'<td[^>]*>', '|', itext).replace('\n', '').replace('<tr>', '\n') ) itext = re.sub(r'<\/?[^>]+>', '', itext).replace('&', '&') outbuf = [] for tline in itext.splitlines(): if tline[0] != '|': continue tline = tline[1:] # srv, pid, acc, m, cpu, ss, req, (dur), conn, child, slot, client, # (proto), vhost, method uri proto # proto and dur sections are not present on some machines, so we get # 13 - 15 columns line = tline.strip().split('|') if len(line) < 13: continue if len(line) == 14: del line[11] elif len(line) > 14: del line[7] del line[11] htmatch = re.match(r'^([^ ]+) ?(.*) (HTTP.*)$', line[12], re.I) if htmatch: hmeth, huri, htype = htmatch.groups() else: hmeth, huri, htype = ('', '', '') outbuf.append( { 'srv': line[0], 'pid': line[1], 'acc': line[2], 'm': line[3], 'cpu': line[4], 'ss': line[5], 'req': line[6], 'conn': line[7], 'child': line[8], 'slot': line[9], 'client': line[10], 'vhost': line[11], 'uri': huri, 'htype': htype, 'method': hmeth, } ) return outbuf def get_status_page(url="http://localhost/whm-server-status-imh"): """retrieve server status page from @url""" try: resp = requests.get(url, timeout=10.0) resp.raise_for_status() except ConnError as e: print(f"!! Failed to retrieve status page ({url}): {e}") sys.exit(100) except HTTPError as e: print("!! Received error from server: %s" % (e)) sys.exit(101) return resp.text def parse_status(intext, full=False): """parse and format status from HTML""" # pick scoreboard header if full is True: gtext = intext else: gtext = re.search(r'<body>(.+)<(/p|p /)>', intext, re.S).group(1) # strip out HTML tags otext = re.sub(r'<td[^>]*>', ' ', gtext) otext = re.sub(r'<\/?[^>]+>', '', otext) return otext def enum_domains(indata, ipmask=None): """produce a dict of connections per domain""" # create mask if ipmask is not None: try: netmask = IPNetwork(ipmask) except AddrFormatError as e: print("ERROR: Failed to parse netmask: " + str(e)) netmask = IPNetwork('0/0') else: netmask = IPNetwork('0/0') odata = {} for tline in indata: # parse vhost & port if tline['vhost'].find(':') > 0: vhost, vport = tline['vhost'].split(':', 1) vhost = vhost.lower() else: vhost = tline['vhost'].lower() vport = '80' # parse client IP try: tclient = IPAddress(tline['client']) except AddrFormatError: continue # match against netmask if tclient not in netmask: continue # build entry if vhost in odata: odata[vhost]['count'] += 1 if tclient in odata[vhost]['ips']: odata[vhost]['ips'][tclient] += 1 else: odata[vhost]['ips'][tclient] = 1 else: odata[vhost] = { 'count': 1, 'domain': vhost, 'port': vport, 'ips': {tclient: 1}, } return odata def enum_ips(indata, ipmask=None): """produce a dict of connections per IP""" # create mask if ipmask is not None: try: netmask = IPNetwork(ipmask) except AddrFormatError as e: print("ERROR: Failed to parse netmask: " + str(e)) netmask = IPNetwork('0/0') else: netmask = IPNetwork('0/0') odata = {} for tline in indata: # parse vhost & port if tline['vhost'].find(':') > 0: vhost, vport = tline['vhost'].split(':', 1) vhost = vhost.lower() else: vhost = tline['vhost'].lower() vport = '80' # parse client IP try: tclient = IPAddress(tline['client']) except AddrFormatError: continue # match against netmask if tclient not in netmask: continue # build entry if tclient in odata: odata[tclient]['count'] += 1 odata[tclient]['ip'] = tclient if vhost in odata[tclient]['domains']: odata[tclient]['domains'][vhost] += 1 else: odata[tclient]['domains'][vhost] = 1 else: odata[tclient] = { 'count': 1, 'client': tclient, 'port': vport, 'domains': {vhost: 1}, } return odata def format_output(indata, sortby='count', nototal=False): """perform final processing, then output data""" # sort sortlist = sorted(indata, key=lambda x: indata[x][sortby]) # display tcount = 0 for tkey in sortlist: tval = indata[tkey] if tkey == 'key': continue tcount += tval['count'] print("{:6} {}".format(tval['count'], tkey)) if nototal is False: print(f"{tcount:6} TOTAL") def parse_args(): """parse CLI args; .mode will contain the operation mode, .ip is an optional IP or CIDR range""" aparser = argparse.ArgumentParser( description="Parse the output of the Apache status page and " "process the results" ) aparser.set_defaults(mode=None, ip=None, nototal=False) aparser.add_argument( '--status', action='store_const', dest='mode', const='status', help="Show Apache scoreboard", ) aparser.add_argument( '--statusfull', action='store_const', dest='mode', const='statusfull', help="Show full Apache status output", ) aparser.add_argument( '--countdomain', action='store_const', dest='mode', const='countdomain', help="Count number of connections per domain/VHost", ) aparser.add_argument( '--sortdomain', action='store_const', dest='mode', const='sortdomain', help="Sort domains/VHosts by number of connections", ) aparser.add_argument( '--countip', action='store_const', dest='mode', const='countip', help="Count number of connections by IP", ) aparser.add_argument( '--sortip', action='store_const', dest='mode', const='sortip', help="Sort IPs by number of connections", ) aparser.add_argument( '--ipmask', dest='ip', metavar="IP/CIDR", help="Filter by a specific IP address or CIDR range", ) aparser.add_argument( '--nototal', dest='nototal', action='store_true', default=False, help="Suppress total output", ) args = aparser.parse_args(sys.argv[1:]) if args.mode is None: aparser.print_help() sys.exit(250) else: return args def _main(): """entry point""" args = parse_args() if not 'sharedrads' in os.path.realpath(__file__): url = 'http://localhost/whm-server-status' else: url = 'http://localhost/whm-server-status-imh' spage = get_status_page(url) sdata = parse_page(spage) sortby = None if args.mode.startswith('status'): if args.mode == 'statusfull': sbtext = parse_status(spage, full=True) else: sbtext = parse_status(spage) print(sbtext) elif args.mode == 'countdomain': fdata = enum_domains(sdata, ipmask=args.ip) sortby = 'count' elif args.mode == 'sortdomain': fdata = enum_domains(sdata, ipmask=args.ip) sortby = 'domain' elif args.mode == 'countip': fdata = enum_ips(sdata, ipmask=args.ip) sortby = 'count' elif args.mode == 'sortip': fdata = enum_ips(sdata, ipmask=args.ip) sortby = 'client' else: print("!! No valid action selected.") sys.exit(251) # display sorted output if sortby is not None: format_output(fdata, sortby, nototal=args.nototal) if __name__ == '__main__': _main()