#!/usr/bin/env -S python3 -W default
#
# This is the test script for libpython-pam.  There aren't many stones
# left unturned.
#
# Best run from the Makefile using the target 'test'.  To run manually:
#   sudo ln -s $PWD/test-pam_python.pam /etc/pam.d
#   python test.py
#   sudo rm /etc/pam.d/test-pam_python.pam
#
import warnings; warnings.simplefilter('default')
import os
import sys

if sys.hexversion < 0x03000000:
  py23_base_exception = Exception
  py23_standard_exception = StandardError
  def py23_function_name(func):
    return func.func_name
  TEST_PAM_MODULE = "test-pam_python.pam"
else:
  py23_base_exception = BaseException
  py23_standard_exception = Exception
  def py23_function_name(func):
    return func.__name__
  TEST_PAM_MODULE = "test-pam_python3.pam"

TEST_PAM_USER	= "root"

#
# A Fairly straight forward test harness.
#
def pam_sm_end(pamh):
  return test(pam_sm_end, pamh, None, None)
def pam_sm_authenticate(pamh, flags, argv):
  return test(pam_sm_authenticate, pamh, flags, argv)
def pam_sm_setcred(pamh, flags, argv):
  return test(pam_sm_setcred, pamh, flags, argv)
def pam_sm_acct_mgmt(pamh, flags, argv):
  return test(pam_sm_acct_mgmt, pamh, flags, argv)
def pam_sm_open_session(pamh, flags, argv):
  return test(pam_sm_open_session, pamh, flags, argv)
def pam_sm_close_session(pamh, flags, argv):
  return test(pam_sm_close_session, pamh, flags, argv)
def pam_sm_chauthtok(pamh, flags, argv):
  return test(pam_sm_chauthtok, pamh, flags, argv)

def test(who, pamh, flags, argv):
  if 'test' not in sys.modules:         # only true if not called via "main"
    return test_cve_2019_16729(pamh)    # happens only if run by ctest
  if not os.getcwd() in sys.path:
    # Needed in python 3.9
    sys.path.insert(0, os.getcwd())
  import test
  test_function = globals()[test.test_function.__name__]
  return test_function(test.test_results, who, pamh, flags, argv)

def test_cve_2019_16729(pamh):
  try:
    import test_cve_2019_16729
    sys.stderr.write('Failed CVE-2019-16729 test!\n')
    sys.stderr.flush()
    return pamh.PAM_SYSTEM_ERR
  except ImportError:
    pass
  return pamh.PAM_SUCCESS


def run_test(test_function):
  import test
  test_name = test_function.__name__[len('run_'):]
  sys.stdout.write("Testing " + test_name + " ")
  sys.stdout.flush()
  test.test_results = []
  test.test_function = globals()["test_" + test_name]
  test_function(test.test_results)
  sys.stdout.write("OK\n")

def pam_conv(auth, query_list, userData=None):
  return query_list

#
# Verify the results match.
#
def assert_results(expected_results, results):
  for i in range(min(len(expected_results), len(results))):
    assert expected_results[i] == results[i], (i, expected_results[i], results[i])
  if len(expected_results) < len(results):
    assert len(expected_results) == len(results), (i, results[len(expected_results)])
  else:
    assert len(expected_results) == len(results), (i, expected_results[len(results)])

#
# Test all the calls happen.
#
def test_basic_calls(results, who, pamh, flags, argv):
  results.append((py23_function_name(who), flags, argv))
  return pamh.PAM_SUCCESS

def run_basic_calls(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  pam.acct_mgmt()
  pam.chauthtok()
  pam.open_session()
  pam.close_session()
  del pam
  me = os.path.normpath(os.path.join(os.getcwd(), __file__))
  expected_results = [
      (py23_function_name(pam_sm_authenticate), 0, [me]),
      (py23_function_name(pam_sm_acct_mgmt), 0, [me, 'arg1', 'arg2']),
      (py23_function_name(pam_sm_chauthtok), 16384, [me]),
      (py23_function_name(pam_sm_chauthtok), 8192, [me]),
      (py23_function_name(pam_sm_open_session), 0, [me]),
      (py23_function_name(pam_sm_close_session), 0, [me]),
      (py23_function_name(pam_sm_end), None, None)]
  assert_results(expected_results, results)

#
# Test all the constants are defined.
#
PAM_CONSTANTS = {
    #
    # Constants defined in _pam_types.h.  The item constants are omitted.
    #
    "PAM_SUCCESS":			0,
    "PAM_OPEN_ERR":			1,
    "PAM_SYMBOL_ERR":			2,
    "PAM_SERVICE_ERR":			3,
    "PAM_SYSTEM_ERR":			4,
    "PAM_BUF_ERR":			5,
    "PAM_PERM_DENIED":			6,
    "PAM_AUTH_ERR":			7,
    "PAM_CRED_INSUFFICIENT":		8,
    "PAM_AUTHINFO_UNAVAIL":		9,
    "PAM_USER_UNKNOWN":			10,
    "PAM_MAXTRIES":			11,
    "PAM_NEW_AUTHTOK_REQD":		12,
    "PAM_ACCT_EXPIRED":			13,
    "PAM_SESSION_ERR":			14,
    "PAM_CRED_UNAVAIL":			15,
    "PAM_CRED_EXPIRED":			16,
    "PAM_CRED_ERR":			17,
    "PAM_NO_MODULE_DATA":		18,
    "PAM_CONV_ERR":			19,
    "PAM_AUTHTOK_ERR":			20,
    "PAM_AUTHTOK_RECOVER_ERR":		21,
    "PAM_AUTHTOK_RECOVERY_ERR":		21,
    "PAM_AUTHTOK_LOCK_BUSY":		22,
    "PAM_AUTHTOK_DISABLE_AGING":	23,
    "PAM_TRY_AGAIN":			24,
    "PAM_IGNORE":			25,
    "PAM_ABORT":			26,
    "PAM_AUTHTOK_EXPIRED":		27,
    "PAM_MODULE_UNKNOWN":		28,
    "PAM_BAD_ITEM":			29,
    "PAM_CONV_AGAIN":			30,
    "PAM_INCOMPLETE":			31,
    "PAM_SERVICE":			1,
    "PAM_USER":				2,
    "PAM_TTY":				3,
    "PAM_RHOST":			4,
    "PAM_CONV":				5,
    "PAM_AUTHTOK":			6,
    "PAM_OLDAUTHTOK":			7,
    "PAM_RUSER":			8,
    "PAM_USER_PROMPT":			9,
    "PAM_FAIL_DELAY":			10,
    "PAM_XDISPLAY":			11,
    "PAM_XAUTHDATA":			12,
    "PAM_AUTHTOK_TYPE":			13,
    "PAM_SILENT":			0x8000,
    "PAM_DISALLOW_NULL_AUTHTOK":	0x0001,
    "PAM_ESTABLISH_CRED":		0x0002,
    "PAM_DELETE_CRED":			0x0004,
    "PAM_REINITIALIZE_CRED":		0x0008,
    "PAM_REFRESH_CRED":			0x0010,
    "PAM_CHANGE_EXPIRED_AUTHTOK":	0x0020,
    "PAM_DATA_SILENT":			0x40000000,
    "PAM_PROMPT_ECHO_OFF":		1,
    "PAM_PROMPT_ECHO_ON":		2,
    "PAM_ERROR_MSG":			3,
    "PAM_TEXT_INFO":			4,
    "PAM_RADIO_TYPE":			5,
    "PAM_BINARY_PROMPT":		7,
    "PAM_MAX_NUM_MSG":			32,
    "PAM_MAX_MSG_SIZE":			512,
    "PAM_MAX_RESP_SIZE":		512,
    "_PAM_RETURN_VALUES":		32,
    #
    # Constants defined in pam_modules.h.  The item constants are omitted.
    #
    "PAM_PRELIM_CHECK":			0x4000,
    "PAM_UPDATE_AUTHTOK":		0x2000,
    "PAM_DATA_REPLACE":			0x20000000,
  }
def test_constants(results, who, pamh, flags, argv):
  results.append(py23_function_name(who))
  if who != pam_sm_authenticate:
    return pamh.PAM_SUCCESS
  pam_constants = dict([
      (var, getattr(pamh, var))
      for var in dir(pamh)
      if var.startswith("PAM_") or var.startswith("_PAM_")])
  results.append(pam_constants)
  try:
    pamh.PAM_SUCCESS = 1
    results.append("Opps, pamh.PAM_SUCCESS = 1 worked!")
  except py23_standard_exception as e:
    results.append("except: %s" % e)
  return pamh.PAM_SUCCESS

def run_constants(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  pam.close_session()
  del pam
  assert results[0] == py23_function_name(pam_sm_authenticate), (results[0], py23_function_name(pam_sm_authenticate))
  assert results[2] == "except: attribute 'PAM_SUCCESS' of 'PamHandle_type' objects is not writable", results[2]
  assert results[3] == py23_function_name(pam_sm_close_session), (results[3], py23_function_name(pam_sm_close_session))
  assert results[4] == py23_function_name(pam_sm_end), (results[4], py23_function_name(pam_sm_end))
  consts = results[1]
  for var in PAM_CONSTANTS.keys():
    assert var in consts, var
    assert consts[var] == PAM_CONSTANTS[var], (var, consts[var], PAM_CONSTANTS[var])
  for var in consts.keys():
    assert var in PAM_CONSTANTS, var
    assert PAM_CONSTANTS[var] == consts[var], (var, PAM_CONSTANTS[var], consts[var])
  assert len(results) == 5, len(results)

#
# Test the environment calls.
#
def test_environment(results, who, pamh, flags, argv):
  results.append(py23_function_name(who))
  if who != pam_sm_acct_mgmt:
    return pamh.PAM_SUCCESS
  def test_exception(func):
    try:
      func()
      return str(None)
    except Exception as e:
      return e.__class__.__name__ + ": " + str(e)
  #
  # A few things to test here.  First that PamEnv_as_mapping works.
  #
  results.append(len(pamh.env))
  results.append(pamh.env["x1"])
  pamh.env["yy"] = "y"
  results.append(pamh.env["yy"])
  pamh.env["yy"] = "z"
  results.append(pamh.env["yy"])
  def t(): pamh.env["yy"] = 1
  results.append(test_exception(t))
  del pamh.env["yy"]
  results.append(test_exception(lambda: pamh.env["yy"]))
  results.append(test_exception(lambda: pamh.env[1]))
  results.append(test_exception(lambda: pamh.env['a=']))
  results.append(test_exception(lambda: pamh.env['']))
  #
  # Now the dict functions.
  #
  pamh.env["xx"] = "x"
  results.append("not in" in pamh.env)
  results.append("xx" in pamh.env)
  results.append("not in" in pamh.env)
  results.append("xx" in pamh.env)
  results.append(test_exception(lambda: pamh.env.__getitem__("not in")))
  results.append(pamh.env.get("not in"))
  results.append(pamh.env.get("not in", "default"))
  results.append(pamh.env.get("xx"))
  results.append(pamh.env.get("xx", "default"))
  del pamh.env["x1"]
  results.append(list(pamh.env.items()))
  results.append(list(pamh.env.keys()))
  results.append(list(pamh.env.values()))
  return pamh.PAM_SUCCESS

def run_environment(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  pam.putenv("x1=1")
  pam.putenv("x2=2")
  pam.putenv("x3=3")
  pam.acct_mgmt()
  pam.close_session()
  del pam
  expected_results = [
      py23_function_name(pam_sm_authenticate), py23_function_name(pam_sm_acct_mgmt),
      3, '1', 'y', 'z',
      'TypeError: PAM environment value must be a string',
      "KeyError: 'yy'",
      'TypeError: PAM environment key must be a string',
      "ValueError: PAM environment key can't contain '='",
      "ValueError: PAM environment key mustn't be 0 length",
      False, True, False, True,
      "KeyError: 'not in'",
      None, 'default', 'x', 'x',
      [('x2', '2'), ('x3', '3'), ('xx', 'x')],
      ['x2', 'x3', 'xx'],
      ['2', '3', 'x'],
      py23_function_name(pam_sm_close_session), py23_function_name(pam_sm_end)]
  assert_results(expected_results, results)

#
# Test strerror().
#
def test_strerror(results, who, pamh, flags, argv):
  results.append(py23_function_name(who))
  if who != pam_sm_authenticate:
    return pamh.PAM_SUCCESS
  results.extend([(e, pamh.strerror(e).lower()) for e in (0, 1, 30, 31)])
  return pamh.PAM_SUCCESS

def run_strerror(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  del pam
  expected_results = [
      py23_function_name(pam_sm_authenticate),
      ( 0, 'success'),
      ( 1, 'failed to load module'),
      (30, 'conversation is waiting for event'),
      (31, 'application needs to call libpam again'),
      py23_function_name(pam_sm_end)]
  assert_results(expected_results, results)

#
# Test items.
#
def test_items(results, who, pamh, flags, argv):
  results.append(py23_function_name(who))
  if not who in (pam_sm_open_session, pam_sm_close_session):
    return pamh.PAM_SUCCESS
  items = {
	"authtok":	"authtok-module",
	"authtok_type":	"authtok_type-module",
	"oldauthtok":	"oldauthtok-module",
	"rhost":	"rhost-module",
	"ruser":	"ruser-module",
	"tty":		"tty-module",
	"user_prompt":	"user_prompt-module",
	"user":		"user-module",
	"xdisplay":	"xdisplay-module",
      }
  for key in sorted(items.keys()):
    results.append((key, getattr(pamh, key)))
    value = items[key]
    if value != None:
      setattr(pamh, key, value)
  try:
    setattr(pamh, "tty", 1)
    results.append("%r = %r" % (key, value))
  except py23_standard_exception as e:
    results.append("except: %s" % e)
  results.append(pamh.get_user("a prompt"))
  return pamh.PAM_SUCCESS

def run_items(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  items = {
      2:	"user",
      3:	"tty",
      4:	"rhost",
      8:	"ruser",
      9:	"user_prompt",
      11:	"xdisplay",
      13:	"authtok_type"}
  for item in sorted(items.keys()):
    pam.set_item(item, items[item])
  pam.open_session()
  pam.close_session()
  del pam
  expected_results = [
      py23_function_name(pam_sm_authenticate), py23_function_name(pam_sm_open_session),
      ('authtok',	None),
      ('authtok_type',	'authtok_type'),
      ('oldauthtok',	None),
      ('rhost',		'rhost'),
      ('ruser',		'ruser'),
      ('tty',		'tty'),
      ('user',		'user'),
      ('user_prompt',	'user_prompt'),
      ('xdisplay',	'xdisplay'),
      'except: PAM item PAM_TTY must be set to a string',
      'user-module',
      py23_function_name(pam_sm_close_session),
      ('authtok',	'authtok-module'),
      ('authtok_type',	'authtok_type-module'),
      ('oldauthtok',	'oldauthtok-module'),
      ('rhost',		'rhost-module'),
      ('ruser',		'ruser-module'),
      ('tty',		'tty-module'),
      ('user',		'user-module'),
      ('user_prompt',	'user_prompt-module'),
      ('xdisplay',	'xdisplay-module'),
      'except: PAM item PAM_TTY must be set to a string',
      'user-module',
      py23_function_name(pam_sm_end)]
  assert_results(expected_results, results)

#
# Test the xauthdata item.
#
def test_xauthdata(results, who, pamh, flags, argv):
  results.append(py23_function_name(who))
  if not who in (pam_sm_open_session, pam_sm_close_session):
    return pamh.PAM_SUCCESS
  xauthdata0 = pamh.XAuthData("name-module", "data-module")
  pamh.xauthdata = xauthdata0
  xauthdata1 = pamh.xauthdata
  results.append('name=%r, data=%r' % (xauthdata1.name, xauthdata1.data))
  try:
    xauthdata2 = pamh.XAuthData(None, "x")
    results.append('pamh.XAuthData(%r, %r)' % (xauthdata2.name, xauthdata2.data))
  except TypeError as e:
    results.append('except: %s' % e)
  try:
    xauthdata2 = pamh.XAuthData("x", 1)
    results.append('pamh.XAuthData(%r, %r)' % (xauthdata2.name, xauthdata2.data))
  except TypeError as e:
    results.append('except: %s' % e)
  class XA: pass
  XA.name = "name-XA"
  XA.data = "data-XA"
  pamh.xauthdata = XA
  xauthdata2 = pamh.xauthdata
  results.append('name=%r, data=%r' % (xauthdata2.name, xauthdata2.data))
  xa = XA()
  xa.name = "name-xa"
  xa.data = "data-xa"
  pamh.xauthdata = xa
  xauthdata4 = pamh.xauthdata
  results.append('name=%r, data=%r' % (xauthdata4.name, xauthdata4.data))
  return pamh.PAM_SUCCESS

def run_xauthdata(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  #
  # The PAM module doesn't support XAUTHDATA, so check what we can from the
  # module only.
  #
  pam.open_session()
  pam.close_session()
  del pam
  if sys.hexversion < 0x03000000:
      string_type="string"
  else:
      string_type="str"
  expected_results = [
      py23_function_name(pam_sm_authenticate), py23_function_name(pam_sm_open_session),
      ("name='name-module', data='data-module'"),
      'except: XAuthData() argument 1 must be '+string_type+', not None',
      'except: XAuthData() argument 2 must be '+string_type+', not int',
      ("name='name-XA', data='data-XA'"),
      ("name='name-xa', data='data-xa'"),
      py23_function_name(pam_sm_close_session),
      ("name='name-module', data='data-module'"),
      'except: XAuthData() argument 1 must be '+string_type+', not None',
      'except: XAuthData() argument 2 must be '+string_type+', not int',
      ("name='name-XA', data='data-XA'"),
      ("name='name-xa', data='data-xa'"),
      py23_function_name(pam_sm_end)]
  assert_results(expected_results, results)

#
# Test having no pam_sm_end.
#
def test_no_sm_end(results, who, pamh, flags, argv):
  results.append(py23_function_name(who))
  global pam_sm_end
  del pam_sm_end
  return pamh.PAM_SUCCESS

def run_no_sm_end(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  del pam
  expected_results = [py23_function_name(pam_sm_authenticate)]
  assert_results(expected_results, results)

#
# Test the conversation mechanism.
#
def test_conv(results, who, pamh, flags, argv):
  results.append(py23_function_name(who))
  if who == pam_sm_end:
    return
  #
  # We must get rid of all references to pamh.Response objects.  This instance
  # of the test.py module is running inside of libpam_python.  That shared
  # library will be unloaded soon.  Should a pamh.Response instance be
  # dealloc'ed after it is unloaded the now non-existant dealloc function will
  # be called, and a SIGSEGV will result.  Normally instances would not leak,
  # but with the trickery we are performing with fake import's here they will
  # leak via the results variable unless we take special action.
  #
  def conv(convs):
    responses = pamh.conversation(convs)
    if type(responses) != type(()):
      return (responses.resp, responses.resp_retcode)
    return [(r.resp, r.resp_retcode) for r in responses]
  if who == pam_sm_authenticate:
    convs = [
	pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, "Prompt_echo_off"),
	pamh.Message(pamh.PAM_PROMPT_ECHO_ON, "Prompt_echo_on"),
	pamh.Message(pamh.PAM_ERROR_MSG, "Error_msg"),
	pamh.Message(pamh.PAM_TEXT_INFO, "Text_info")]
  if who == pam_sm_acct_mgmt:
    convs = pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, "single")
  results.append(conv(convs))
  return pamh.PAM_SUCCESS

def run_conv(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  pam.acct_mgmt()
  del pam
  expected_results = [
      py23_function_name(pam_sm_authenticate),
      [('Prompt_echo_off', 1), ('Prompt_echo_on', 2), ('Error_msg', 3), ('Text_info', 4)],
      py23_function_name(pam_sm_acct_mgmt),
      ('single', 1),
      py23_function_name(pam_sm_end)]
  assert_results(expected_results, results)

#
# Test pam error returns.
#
def test_pamerr(results, who, pamh, flags, argv):
  return results[-1]

def run_pamerr(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  for err in range(0, PAM._PAM_RETURN_VALUES):
    results.append(err)
    try:
      pam.authenticate(0)
    except PAM.error as e:
      results[-1] = -e.args[1]
  del pam
  expected_results = [-r for r in range(PAM._PAM_RETURN_VALUES)]
  expected_results[25] = -6
  assert_results(expected_results, results)

#
# Test fail_delay.
#
def test_fail_delay(results, who, pamh, flags, argv):
  pamh.fail_delay(10)
  return pamh.PAM_SUCCESS

def run_fail_delay(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  del pam

#
# Test raising an exception.
#
def test_exceptions(results, who, pamh, flags, argv):
  if who != pam_sm_end:
    return pamh.PAM_SUCCESS
  #
  # Here we have use of a backdoor put into pam_python.c specifically
  # for testing raising exceptions.  Oddly, normally PAM should never
  # return anything other than PAM_SUCCESS to anything pam_python.c
  # calls.
  #
  debug_magic = 0x4567abcd
  results.append(pamh._PAM_RETURN_VALUES)
  for err in range(pamh._PAM_RETURN_VALUES):
    try:
      pamh.strerror(debug_magic + err)
      results.append(err)
    except pamh.exception as e:
      results.append((-e.pam_result,))
  return pamh.PAM_SUCCESS

def run_exceptions(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  del pam
  expected_results = [results[0], 0]
  expected_results += [(-r,) for r in range(1, results[0])]
  assert_results(expected_results, results)

#
# Test absent entry point.
#
def test_absent(results, who, pamh, flags, argv):
  results.append(py23_function_name(who))
  if who != pam_sm_authenticate:
    return pamh.PAM_SUCCESS
  global pam_sm_acct_mgmt; del pam_sm_acct_mgmt
  global pam_sm_setcred; del pam_sm_setcred
  global pam_sm_open_session; del pam_sm_open_session
  global pam_sm_close_session; del pam_sm_close_session
  global pam_sm_chauthtok; del pam_sm_chauthtok
  return pamh.PAM_SUCCESS

def run_absent(results):
  pam = PAM.pam()
  pam.start(TEST_PAM_MODULE, TEST_PAM_USER, pam_conv)
  pam.authenticate(0)
  funcs = (
      pam.acct_mgmt,
      pam.setcred,
      pam.open_session,
      pam.close_session,
      pam.chauthtok
    )
  for func in funcs:
    try:
      func(0)
      exception = None
    except py23_base_exception as e:
      exception = e
    results.append((exception.__class__.__name__, str(exception)))
  del pam
  expected_results = [
      'pam_sm_authenticate',
      ('error', "('Symbol not found', 2)"),
      ('error', "('Symbol not found', 2)"),
      ('error', "('Symbol not found', 2)"),
      ('error', "('Symbol not found', 2)"),
      ('error', "('Symbol not found', 2)"),
    ]
  assert_results(expected_results, results)

#
# Run ./ctest.c.
#
def run_ctest():
  import tempfile
  with tempfile.TemporaryDirectory() as tempdir:
    #
    # Run in a new Python interpreter, so it sees the PYTHONPATH environment
    # variable we set.
    #
    tempdir = tempfile.mkdtemp()
    with open(os.path.join(tempdir, 'test_cve_2019_16729.py'), "w") as handle:
      handle.write("pass\n")
    old_environ = os.environ
    if 'PYTHONPATH' not in os.environ:
      os.environ['PYTHONPATH'] = ''
    else:
      os.environ['PYTHONPATH'] += ':'
    os.environ['PAM_PYTHON_TEST_CVE_2019_16729'] = tempdir
    os.environ['PYTHONPATH'] += tempdir
    ctest_prog = os.path.join(os.path.dirname(__file__), "ctest")
    python_prog = os.path.basename(sys.executable)
    results = [os.system(' '.join((ctest_prog, python_prog,)))]
    expected_results = [0]
    assert_results(expected_results, results)

#
# Entry point.
#
def main(argv):
  run_test(run_basic_calls)
  run_test(run_constants)
  run_test(run_environment)
  run_test(run_strerror)
  run_test(run_items)
  run_test(run_xauthdata)
  run_test(run_no_sm_end)
  run_test(run_conv)
  run_test(run_pamerr)
  run_test(run_fail_delay)
  run_test(run_exceptions)
  run_test(run_absent)
  run_ctest()

#
# If run from Python run the test suite.  Otherwse we are being used
# as a real PAM module presumable from ctest, so just make every call
# return success.
#
if __name__ == "__main__":
  import PAM
  main(sys.argv)
