- Get LDAP attribute values in CSV format - usage: getAttribs [-h] (-F FILE | -v VALUES) [-k KEY] -a ATTRIBUTES [-o OUTPUT] [-d DELIMITER]
206 lines
5.0 KiB
Python
206 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import csv
|
|
import sys
|
|
|
|
import ldap
|
|
from ldap.filter import escape_filter_chars
|
|
|
|
# ============================================================================
|
|
# Configuration
|
|
# ============================================================================
|
|
|
|
LDAP_URI = "ldaps://ldap.example.com"
|
|
BIND_DN = "uid=admin,ou=People,dc=example,dc=com"
|
|
BIND_PW = "password"
|
|
BASE_DN = "dc=example,dc=com"
|
|
|
|
DEFAULT_KEY_ATTRIBUTE = "customStaffNo"
|
|
DEFAULT_DELIMITER = "|"
|
|
DEFAULT_OUTPUT_FILE = "/tmp/ldap_output.csv"
|
|
|
|
INFO = "[i]"
|
|
START = "[*]"
|
|
SUCCESS = "[+]"
|
|
WARN = "[!]"
|
|
ERROR = "[x]"
|
|
|
|
# ============================================================================
|
|
|
|
|
|
def read_input_file(filename):
|
|
values = []
|
|
|
|
with open(filename, "r") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line:
|
|
values.append(line)
|
|
|
|
return values
|
|
|
|
|
|
def ldap_connect():
|
|
conn = ldap.initialize(LDAP_URI)
|
|
conn.protocol_version = ldap.VERSION3
|
|
conn.simple_bind_s(BIND_DN, BIND_PW)
|
|
return conn
|
|
|
|
|
|
def decode(value):
|
|
if isinstance(value, bytes):
|
|
return value.decode("utf-8", errors="replace")
|
|
return str(value)
|
|
|
|
|
|
def update_progress(current, total, matched, not_found):
|
|
"""Update the progress display in-place."""
|
|
|
|
sys.stdout.write("\033[3A")
|
|
|
|
sys.stdout.write(f"\r{INFO} Progress : {current:<6} / {total}\n")
|
|
sys.stdout.write(f"\r{INFO} Entries matched : {matched:<6}\n")
|
|
sys.stdout.write(f"\r{INFO} Not found : {not_found:<6}\n")
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
|
|
group.add_argument(
|
|
"-F",
|
|
"--file",
|
|
help="Input file containing one value per line",
|
|
)
|
|
|
|
group.add_argument(
|
|
"-v",
|
|
"--values",
|
|
help="Comma-separated list of search values",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-k",
|
|
"--key",
|
|
default=DEFAULT_KEY_ATTRIBUTE,
|
|
help=f"LDAP attribute used for searching (default: {DEFAULT_KEY_ATTRIBUTE})",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-a",
|
|
"--attributes",
|
|
required=True,
|
|
help="Comma-separated LDAP attributes to retrieve",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-o",
|
|
"--output",
|
|
default=DEFAULT_OUTPUT_FILE,
|
|
help=f"Output CSV file (default: {DEFAULT_OUTPUT_FILE})",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-d",
|
|
"--delimiter",
|
|
default=DEFAULT_DELIMITER,
|
|
help=f"Output CSV delimiter (default: {DEFAULT_DELIMITER})",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
attributes = [x.strip() for x in args.attributes.split(",") if x.strip()]
|
|
|
|
if args.file:
|
|
search_values = read_input_file(args.file)
|
|
else:
|
|
search_values = [
|
|
x.strip()
|
|
for x in args.values.split(",")
|
|
if x.strip()
|
|
]
|
|
|
|
total = len(search_values)
|
|
|
|
matched = 0
|
|
not_found = []
|
|
incomplete = []
|
|
|
|
print(f"{START} Fetching LDAP attributes...")
|
|
print(f"{INFO} Search key : {args.key}")
|
|
print(f"{INFO} Requested attributes: {', '.join(attributes)}")
|
|
print(f"{INFO} Input values : {total}")
|
|
|
|
print(f"{INFO} Progress : 0 / {total}")
|
|
print(f"{INFO} Entries matched : 0")
|
|
print(f"{INFO} Not found : 0")
|
|
|
|
conn = ldap_connect()
|
|
|
|
with open(args.output, "w", newline="") as csvfile:
|
|
writer = csv.writer(csvfile, delimiter=args.delimiter)
|
|
|
|
writer.writerow(attributes)
|
|
|
|
for current, value in enumerate(search_values, start=1):
|
|
flt = f"({args.key}={escape_filter_chars(value)})"
|
|
|
|
result = conn.search_s(
|
|
BASE_DN,
|
|
ldap.SCOPE_SUBTREE,
|
|
flt,
|
|
attributes,
|
|
)
|
|
|
|
if not result:
|
|
not_found.append(value)
|
|
update_progress(current, total, matched, len(not_found))
|
|
continue
|
|
|
|
matched += 1
|
|
|
|
_, entry = result[0]
|
|
|
|
row = []
|
|
missing_attribute = False
|
|
|
|
for attr in attributes:
|
|
vals = entry.get(attr)
|
|
|
|
if vals:
|
|
row.append(";".join(decode(v) for v in vals))
|
|
else:
|
|
row.append("")
|
|
missing_attribute = True
|
|
|
|
if missing_attribute:
|
|
incomplete.append(value)
|
|
|
|
writer.writerow(row)
|
|
|
|
update_progress(current, total, matched, len(not_found))
|
|
|
|
conn.unbind_s()
|
|
|
|
print()
|
|
|
|
if not_found:
|
|
print(f"{WARN} Missing entries")
|
|
print(f" {args.key}: {', '.join(not_found)}")
|
|
print()
|
|
|
|
if incomplete:
|
|
print(f"{WARN} Incomplete entries")
|
|
print(" Missing one or more requested attributes:")
|
|
print(f" {args.key}: {', '.join(incomplete)}")
|
|
print()
|
|
|
|
print(f"{SUCCESS} Output written to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |