#!/usr/bin/python
# coding: utf-8
#
# Copyright 2004-2016 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.

from __future__ import print_function

import re
import argparse
import subprocess

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="database URL (default: {})".format(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"]

	for option in ("url", "user", "password", "authentication-file",
				   "simple-bind-dn", "kerberos", "krb5-ccache"):
		value = getattr(parsed, option.replace("-", "_"))
		if value: arguments.append("--{}={}".format(option, value))

	for option in ("no-pass", "machine-pass", "sign", "encrypt"):
		value = getattr(parsed, option.replace("-", "_"))
		if value: arguments.append("--{}".format(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",
										  "samaccountname=" + user, "dn"])
	except (OSError, subprocess.CalledProcessError) as error:
		exit("Error calling univention-s4search: " + str(error))

	match = re.search("^dn: (.*)$", output, re.MULTILINE)
	if match and match.group(1):
		return match.group(1)
	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))
	if cmd.returncode != 0:
		exit("Error unlocking user " + dn)

def main():
	arguments = parse_arguments()

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

	unlock_user(modify, dn)

if __name__ == "__main__":
	main()
