#!/usr/share/ucs-test/runner python
## desc: |
##  Check if kpasswd spuriously reports success
##  This script tests if kpasswd spuriously reports success when trying to change the password to a too short one.
##  It performs the following steps for the test:
##  * Create user with "long password"
##  * Log in with this user using ssh
##  * Try to change password with kpasswd to "too short password" and parse its output
##  * Test "long password" and "too short password" by trying to log in using ssh
## tags: [basic]
## bugs: [10013]
## roles:
##  - domaincontroller_master
##  - domaincontroller_backup
## versions:
##  2.2-0: fixed
## packages:
##  - python-pexpect
##  - univention-heimdal-kdc
## exposure: dangerous

import pexpect
import tempfile
import sys
import atexit
import univention.config_registry
import random
import subprocess

ucr = univention.config_registry.ConfigRegistry()
ucr.load()

def get_unique_username():
	"""returns a random username that is not used."""
	while True:
		randomname = "T%x" % (random.getrandbits(44),)
		# this equivalent to
		# univention-directory-manager users/user list | sed -rne "s_^\s+username:\s+(.*)$_\1_p"
		udm = subprocess.Popen(["univention-directory-manager", "users/user", "list"], stdout=subprocess.PIPE)
		sed = subprocess.Popen(["sed", "-rne", "s_^\s+username:\s+(.*)$_\1_p"], stdin=udm.stdout, stdout=subprocess.PIPE)
		stdout, stderr = sed.communicate()
		for username in stdout:
			if randomname == username.strip(): # collision
				break # continue while
		else: # "for" did not "break"
			return randomname # randomname is unique

def create_user(username, password):
	udm = subprocess.Popen(["univention-directory-manager",
				"users/user", "create",
				"--set", "password=%s" % password,
				"--set", "username=%s" % username,
				"--set", "firstname=%s" % username,
				"--set", "lastname=%s" % username,
				"--set", "primaryGroup=cn=Domain Admins,cn=groups,%(ldap/base)s" % ucr,
				"--position", "cn=users,%(ldap/base)s" % ucr,])
	return udm.wait() == 0

def remove_user(username):
	udm = subprocess.Popen(["univention-directory-manager",
				"users/user", "remove",
				"--dn", "uid=%s,cn=users,%s" % (username, ucr["ldap/base"],)])
	return udm.wait() == 0

def create_ssh_session(username, password):
	known_hosts_file = tempfile.NamedTemporaryFile()
	shell = pexpect.spawn('ssh', ['-o', 'UserKnownHostsFile="%s"' % known_hosts_file.name, '%s@localhost' % username,], timeout=10,) # logfile=sys.stdout)
	status = shell.expect([pexpect.TIMEOUT, '[Pp]assword: ', 'Are you sure you want to continue connecting',])
	del known_hosts_file
	if status == 2: # accept public key
		shell.sendline('yes')
		status = shell.expect([pexpect.TIMEOUT, '[Pp]assword: ',])
	if status == 0: # timeout
		raise Exception('ssh behaved unexpectedly! Output:\n\t%r' % (shell.before,))
	assert status == 1, "password prompt"
	shell.sendline(password)
	status = shell.expect([pexpect.TIMEOUT, 'Permission denied', ':~\$'])
	if status == 0: # timeout
		raise Exception('No shell prompt found! Output:\n\t%r' % (shell.before,))
	if status == 1: # permission denied
		raise Exception('ssh error: Permission denied.')
	assert status == 2, 'shell prompt'
	return shell

if __name__ == "__main__":
	username = get_unique_username()
	print 'Using username "%s"' % username
	password = '%010x' % (random.getrandbits(40),)
	if not create_user(username, password):
		print 'Could not create user "%s" with password "%s"' % (username, password,)
		sys.exit(120) # Transient error
	atexit.register(remove_user, username)

	try:
		shell = create_ssh_session(username, password)
	except Exception, e:
		print e # print error
		sys.exit(120)

	newpassword = '%02x' % (random.getrandbits(8),) # a short password to trigger the bug
	shell.sendline('kpasswd')
	status = shell.expect([pexpect.TIMEOUT, '[Pp]assword:',])
	if status == 0: # timeout
		print 'kpasswd behaved unexpectedly! Output:\n\t%r' % (shell.before,)
		sys.exit(120)
	shell.sendline(password)
	status = shell.expect([pexpect.TIMEOUT, 'New password:',])
	shell.sendline(newpassword)
	status = shell.expect([pexpect.TIMEOUT, 'New password:',])
	shell.sendline(newpassword)
	status = shell.expect(['(?i)[Ss]uccess', '(?i)[Ee]rror', pexpect.TIMEOUT,])
	kpasswd_reported_success = status == 0

	try:
		create_ssh_session(username, password)
		accepted_old_pwd = True
	except Exception, e:
		accepted_old_pwd = False
	try:
		create_ssh_session(username, newpassword)
		accepted_new_pwd = True
	except Exception, e:
		accepted_new_pwd = False
	if accepted_old_pwd == accepted_new_pwd:
		print 'ERROR: Both passwords were',
		if accepted_old_pwd and accepted_new_pwd:
			print 'accepted'
		else:
			print 'rejected'
		sys.exit(120) # Transient error
	password_changed = accepted_new_pwd

	if kpasswd_reported_success:
		print 'TEST FAILED: "kpasswd" reported acceptance of too short password',
	else:
		print 'TEST SUCCEEDED: "kpasswd" reported refusal of too short password',
	if kpasswd_reported_success == password_changed:
		print 'and',
	else:
		print 'but',
	if password_changed:
		print 'the password was changed'
	else:
		print 'the password was not changed'
	if password_changed:
		print '\tThe short password "%s" was accepted - this should not happen.' % (newpassword,)
		sys.exit(120) # Transient error

	if kpasswd_reported_success:
		sys.exit(1)
	else:
		sys.exit(0)

# vim: set ft=python :
