Skip to content

Instantly share code, notes, and snippets.

@Varbin
Created February 14, 2021 14:32
Show Gist options
  • Save Varbin/9a75ea266f8730799e2355923f62652e to your computer and use it in GitHub Desktop.
Save Varbin/9a75ea266f8730799e2355923f62652e to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
#############################################################################
# #
# This script was initially developed by Infoxchange for internal use #
# and has kindly been made available to the Open Source community for #
# redistribution and further development under the terms of the #
# GNU General Public License v2: http://www.gnu.org/licenses/gpl.html #
# Copyright 2015 Infoxchange #
# #
#############################################################################
# #
# This script is supplied 'as-is', in the hope that it will be useful, but #
# neither Infoxchange nor the authors make any warranties or guarantees #
# as to its correct operation, including its intended function. #
# #
# Or in other words: #
# Test it yourself, and make sure it works for YOU. #
# #
#############################################################################
# Author: George Hansper e-mail: [email protected] #
#############################################################################
import sys, getopt, re, subprocess
import dateutil.parser, dateutil.tz
import datetime
# for debugging:
import pprint
version = 'v1.1 $Id$'
#nagios return codes
UNKNOWN = 3
OK = 0
WARNING = 1
CRITICAL = 2
verbose=0
debug=0
exit_code = 0
now = datetime.datetime.now(dateutil.tz.tzlocal())
message=''
perf_message=''
result_full=''
test_file = None
expect_host=None
expect_ip=None
def get_realm():
realm = 'no_realm'
bind_path = 'no_bind_path'
ldap_server_name = 'none'
ldap_server_ip = 'none'
re_split = re.compile(r":\s+")
if test_file:
output = subprocess.check_output(['cat', test_file], text=1)
else:
output = subprocess.check_output(['net', 'ads','info'], text=1)
for line in output.splitlines():
print_debug(line)
fields = re_split.split(line)
if fields[0].lower() == 'realm':
realm = fields[1].lower()
elif fields[0].lower() == 'bind path':
bind_path = fields[1]
elif fields[0].lower() == 'ldap server name':
ldap_server_name = fields[1]
elif fields[0].lower() == 'ldap server':
ldap_server_ip = fields[1]
print_debug("realm=%s bind_path=%s" % (realm,bind_path) )
return(realm,bind_path,ldap_server_name,ldap_server_ip)
def parse_date(date_str):
if date_str == 'NTTIME(0)':
return (dateutil.parser.parse('1 jan 1970 0:00 UTC'),'forever')
date_obj = dateutil.parser.parse(date_str)
date_delta = now - date_obj
if date_delta.days >=2:
how_long="%d days" % date_delta.days
elif date_delta.total_seconds() > 6000: # 100 minutes
how_long="%d hrs" % int(date_delta.total_seconds() / 3600)
else:
how_long="%d mins" % int(date_delta.total_seconds() / 60)
return (date_obj,how_long)
def print_debug(msg):
global debug
if debug:
sys.stderr.write(msg+"\n")
def print_v(msg):
global verbose
if verbose:
sys.stderr.write(msg+"\n")
def usage():
print('Usage: ' + sys.argv[0] + " [-v] [-V]")
print("""
-v, --verbose ... verbose messages, print detailed summary
-H, --host ... expect this host as the LDAP server reported by 'net ads info'
-I, --ip ... expect this ip address as the LDAP server reported by 'net ads info'
""")
def command_args(argv):
global verbose, debug, expect_host, expect_ip, test_file
try:
opts, args = getopt.getopt(argv, 'vdhVH:I:T:', ['verbose', 'debug', 'version', 'help', 'host=', 'ip='])
except getopt.GetoptError:
usage()
sys.exit(3)
for opt, arg in opts:
if opt in ('-h', '--help'):
usage()
sys.exit(WARNING)
elif opt in ('-V', '--version'):
print(version)
sys.exit(1)
elif opt in ('-v', '--verbose'):
verbose = 1
elif opt in ('-d', '--debug'):
debug = 1
elif opt in ('-H','--host'):
expect_host = arg
elif opt in ('-I','--iphost'):
expect_ip = arg
# Read data from this file (or fifo) instead of normal commands
elif opt in ('-T','--test'):
test_file = arg
# Parse command line args
command_args(sys.argv[1:])
(realm,bind_path,ldap_server_name,ldap_server_ip) = get_realm()
if realm == 'no_realm':
print("CRITICAL: No realm in 'net ads info' output|ok=0")
sys.exit(CRITICAL)
else:
message = "Realm: %s" % realm
exit_code=0
if expect_host is not None and not ( \
ldap_server_name.startswith(expect_host+'.') or \
ldap_server_name == expect_host ):
message += ', LDAP server is: %s, expected: %s (!!)' % ( ldap_server_name, expect_host )
exit_code=2
if expect_ip is not None and expect_ip != ldap_server_ip:
message += ', LDAP server is: %s, expected: %s (!!)' % ( ldap_server_ip, expect_ip )
exit_code=2
if test_file:
output = subprocess.check_output(['cat', test_file], text=1)
else:
output = subprocess.check_output(['samba-tool', 'drs','showrepl'], text=1)
re_object = re.compile(r"^([^\s]*)"+bind_path,re.I)
re_object_bad = re.compile(r"^([^\s]*)..=",re.I)
re_start_section=re.compile(r"^===*\s*(.*)")
re_last_success=re.compile(r"Last success @\s*(.*)")
re_peer_name = re.compile(r"([^ \\]*)\\([-A-Z0-9]+) +via|Server DNS name :\s*([-A-Za-z0-9]+)|NTDS DN: CN=NTDS Settings[^, =]*,CN=([-A-Za-z0-9]+)")
re_success=re.compile(r"Last attempt @\s*(.*) (was successful|failed)(, (.*))?")
re_failure=re.compile(r"Last attempt @\s*(.*) consecutive failure")
section='none'
peer_name = 'unknown'
ad_object = 'unknown'
isok =3
ad_peers=dict()
ad_objects=dict()
ad_objects_bad=dict()
nt_domains=dict()
peer_fail=dict()
peer_ok=dict()
peer_oldest_fail=dict()
peer_oldest_ok=dict()
object_fail=dict()
object_ok=dict()
for line in output.splitlines():
match = re_start_section.match(line)
if match:
if match.group(1).startswith("IN"):
section='in'
elif match.group(1).startswith("OUT"):
section='out'
elif match.group(1).startswith("KCC"):
section='kcc'
else:
section='none'
peer_name = 'unknown'
ad_object = 'unknown'
isok = 3
last_when_str = 'NTTIME(0)'
last_when_t = parse_date(last_when_str)
if section not in peer_ok:
peer_ok[section] = {}
if section not in peer_fail:
peer_fail[section] = {}
if section not in peer_oldest_fail:
peer_oldest_fail[section] = {}
if section not in peer_oldest_ok:
peer_oldest_ok[section] = {}
if section not in object_fail:
object_fail[section] = {}
if section not in object_ok:
object_ok[section] = {}
print_debug("found section %s in %s" % (section, line))
continue
match = re_object.search(line)
if match:
peer_name = 'unknown'
ad_object = match.group(1)
print_debug("found object '%s' in %s" % (ad_object, line) )
if ad_object == '':
ad_object='DC=...'
if not ad_object in ad_objects:
ad_objects[ad_object]=1
if not ad_object in object_ok[section]:
object_ok[section][ad_object] = 0
if not ad_object in object_fail[section]:
object_fail[section][ad_object] = 0
continue
elif re_object_bad.search(line):
peer_name = 'unknown'
print_v("object not in my domain: %s"%line)
ad_objects_bad[line.strip()]=1
match = re_peer_name.search(line)
if match:
if match.group(2):
nt_domain = match.group(1).strip()
peer_name = match.group(2).lower()
nt_domains[nt_domain] = 1
elif match.group(3):
peer_name = match.group(3).lower()
elif match.group(4):
peer_name = match.group(4).lower() + ' (stale NTDS)'
else:
peer_name = 'unknown'
if not peer_name in ad_peers:
ad_peers[peer_name]=1
if not peer_name in peer_ok[section]:
peer_ok[section][peer_name] = 0
if not peer_name in peer_fail[section]:
peer_fail[section][peer_name] = 0
print_debug("found peer %s in %s" % (peer_name, line))
continue
last_result = re_success.search(line)
if last_result:
when_str = last_result.group(1)
when_t = parse_date(when_str)
if last_result.group(2) == 'was successful':
isok=0
else:
isok=1
# Peer last OK
# If peer has failures, we want to know the OLDEST recent success
# if peer is OK, we want to know the OLDEST recent success
# BUT we want to distingush the 2 above cases
if re_last_success.search(line):
result = re_last_success.search(line)
last_when_str = result.group(1)
last_when_t = parse_date(last_when_str)
if isok == 0:
if peer_name in peer_ok[section]:
peer_ok[section][peer_name] += 1
else:
peer_ok[section][peer_name] = 1
if ad_object in object_ok[section]:
object_ok[section][ad_object] += 1
else:
object_ok[section][ad_object] = 1
if not peer_name in peer_oldest_ok[section] \
or peer_oldest_ok[section][peer_name][0] > last_when_t[0]:
peer_oldest_ok[section][peer_name] = last_when_t
elif isok == 1:
if peer_name in peer_fail[section]:
peer_fail[section][peer_name] += 1
else:
peer_fail[section][peer_name] = 1
if ad_object in object_fail[section]:
object_fail[section][ad_object] += 1
else:
object_fail[section][ad_object] = 1
if not peer_name in peer_oldest_fail[section] \
or peer_oldest_fail[section][peer_name][0] > last_when_t[0]:
peer_oldest_fail[section][peer_name] = last_when_t
# Analyse the results
# First, the critical list:
failing_peers=list()
ok_peers=list()
# Inbound replication - alert on this
for peer_name in sorted(ad_peers.keys()):
if peer_name in peer_oldest_fail['in'] and peer_fail['in'][peer_name] > 0:
failing_peers.append(peer_name+' since '+peer_oldest_fail['in'][peer_name][1])
elif peer_name in peer_oldest_ok['in']:
ok_peers.append(peer_name+' as of '+peer_oldest_ok['in'][peer_name][1])
if len(failing_peers) > 0:
exit_code |= 2
message += ' Failing: ' + ", ".join(failing_peers) + '(!!)'
if len(ok_peers) > 0:
message += ', Still OK: ' + ", ".join(ok_peers)
else:
exit_code |= 0
message += ' OK: ' + ", ".join(ok_peers)
perf_message = 'ok=%d fail=%d' % ( len(ok_peers), len(failing_peers) )
# Any object outside of our domain (Domain Component / bind_path ) (highly unexpected)
# net ads info disagrees with samba-tool drs showrepl
if len(ad_objects_bad) != 0:
message += ", %s bad objects (!!)" % len(ad_objects_bad.keys())
exit_code != 2
if exit_code == 0:
state = 'OK'
elif exit_code == 1:
state = 'WARNING'
elif exit_code == 2 or exit_code == 3:
state = 'CRITICAL'
exit_code = 2
else:
state = 'UNKNOWN'
exit_code = 3
print('%s: %s|%s' % ( state, message, perf_message ))
# Long output message
result_full += "NTDomain: %s\n" % " ".join(nt_domains)
result_full += 'Bind Path: ' + bind_path
result_full += "\n"
for section in sorted(peer_ok.keys()):
result_full += "\n%s:\n" % section
for peer_name in sorted(ad_peers.keys()):
if peer_name in peer_oldest_fail[section]:
result_full+= " %-12s failing since %s\n" % ( peer_name,peer_oldest_fail[section][peer_name][1] )
if peer_name in peer_oldest_ok[section]:
result_full+= " %-12s ok as at %s\n" % ( peer_name,peer_oldest_ok[section][peer_name][1] )
if peer_name in peer_ok[section] and peer_ok[section][peer_name]==0 and peer_name in peer_fail[section] and peer_fail[section][peer_name]==0:
result_full+=" %-12s\n" % ( peer_name )
if object_ok[section] or object_fail[section]:
result_full += " Objects:\n"
for ad_object in ad_objects.keys():
if ad_object in object_ok[section] or ad_object in object_fail[section]:
result_full += ' %-28s' % ad_object
if ad_object in object_ok[section]:
result_full += ' %d OK' % object_ok[section][ad_object]
if ad_object in object_fail[section]:
result_full += ' %d failiing' % object_fail[section][ad_object]
result_full += "\n"
if len(ad_objects_bad) != 0:
result_full += "Bad Objects:\n " + "\n ".join(ad_objects_bad.keys())
if verbose:
print(result_full)
sys.exit(exit_code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment