Mini Shell
Direktori : /opt/sharedrads/ |
|
Current File : //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()
Zerion Mini Shell 1.0