#!/usr/bin/python3
# SPDX-FileCopyrightText: 2016-2026 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only


import argparse
import re
import subprocess
import sys

from ldap.filter import filter_format

import univention.config_registry as configRegistry


def get_ldap_server_name():
    ucr = configRegistry.ConfigRegistry()
    ucr.load()
    return "ldaps://{}".format(ucr.get("ldap/server/name"))


def parse_arguments():
    description = ("Unlock a Samba account by setting lockoutTime to 0. This requires authentication as in ldbmodify.")
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument("--is-dn", action="store_true", help="ACCOUNT is DN (no additional lookup needed)")
    parser.add_argument("account", help="account to unlock (or DN)")

    general = parser.add_argument_group("General options (as in ldbmodify)")
    ldap_server_name = get_ldap_server_name()
    general.add_argument("-H", "--url", nargs="?", default=ldap_server_name, help=f"database URL (default: {ldap_server_name})")

    auth = parser.add_argument_group("Authentication options (as in ldbmodify)")
    auth.add_argument("-U", "--user", help="Set the network username")
    auth.add_argument("-N", "--no-pass", action="store_true", help="Don't ask for a password")
    auth.add_argument("--password", metavar="STRING", help="Password")
    auth.add_argument("-A", "--authentication-file", metavar="FILE", help="Get the credentials from a file")
    auth.add_argument("-P", "--machine-pass", action="store_true", help="Use stored machine account password")
    auth.add_argument("--simple-bind-dn", metavar="STRING", help="DN to use for a simple bind")
    auth.add_argument("-k", "--kerberos", metavar="STRING", choices=["yes", "no"], help="Use Kerbos")
    auth.add_argument("--krb5-ccache", metavar="STRING", help="Credentials cache location for Kerberos")
    auth.add_argument("-S", "--sign", action="store_true", help="Sign connection to prevent modification in transit")
    auth.add_argument("-e", "--encrypt", action="store_true", help="Encrypt connection for privacy")

    return parser.parse_args()


def build_modify_command(parsed):
    arguments = ["ldbmodify"]
    options = ("url", "user", "password", "authentication-file", "simple-bind-dn", "kerberos", "krb5-ccache")

    for option in options:
        value = getattr(parsed, option.replace("-", "_"))
        if value:
            arguments.append(f"--{option}={value}")

    for option in ("no-pass", "machine-pass", "sign", "encrypt"):
        value = getattr(parsed, option.replace("-", "_"))
        if value:
            arguments.append(f"--{option}")

    # Hack to convince lbdmodify to read the ldif from stdin
    arguments.append("/dev/stdin")
    return arguments


def lookup_dn(user):
    try:
        output = subprocess.check_output(["univention-s4search", filter_format("samaccountname=", [user]), "dn"]).decode('UTF-8', 'replace')
    except (OSError, subprocess.CalledProcessError) as error:
        sys.exit("Error calling univention-s4search: " + str(error))

    match = re.search("^dn: (.*)$", output, re.MULTILINE)
    if match and match.group(1):
        return match.group(1)
    sys.exit("Unable to find DN for user " + user)


def unlock_user(arguments, dn):
    cmd = subprocess.Popen(arguments, stdin=subprocess.PIPE)
    modify = "dn: {}\nchangetype: modify\nreplace: lockoutTime\nlockoutTime: 0\n"
    cmd.communicate(modify.format(dn).encode('UTF-8'))
    if cmd.returncode != 0:
        sys.exit("Error unlocking user " + dn)


def main():
    arguments = parse_arguments()

    modify = build_modify_command(arguments)
    dn = arguments.account if arguments.is_dn else lookup_dn(arguments.account)

    unlock_user(modify, dn)


if __name__ == "__main__":
    main()
