Description: Fix CVE-2019-16729, add unit test so it doesn't get unfixed again.
Author: Russell Stuart <ras@debian.org>

--- a/src/test.py
+++ b/src/test.py
@@ -6,7 +6,7 @@
 # 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 
+#   sudo rm /etc/pam.d/test-pam_python.pam
 #
 import warnings; warnings.simplefilter('default')
 import os
@@ -46,23 +46,34 @@
   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())
-  if 'test' not in sys.modules:# only true if not called via "main"
-    return pamh.PAM_SUCCESS		# normally happens only if run by ctest
+    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 run_test(caller):
+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 = caller.__name__[4:]
+  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]
-  caller(test.test_results)
+  test_function(test.test_results)
   sys.stdout.write("OK\n")
 
 def pam_conv(auth, query_list, userData=None):
@@ -189,7 +200,7 @@
   if who != pam_sm_authenticate:
     return pamh.PAM_SUCCESS
   pam_constants = dict([
-      (var, getattr(pamh,var))
+      (var, getattr(pamh, var))
       for var in dir(pamh)
       if var.startswith("PAM_") or var.startswith("_PAM_")])
   results.append(pam_constants)
@@ -631,6 +642,32 @@
   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):
@@ -646,6 +683,7 @@
   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
--- a/src/ctest.c
+++ b/src/ctest.c
@@ -141,7 +141,8 @@
   memset(&walk_info_after, 0, sizeof(walk_info_after));
   walk_info_after.pam_python_so = filename;
   walk_dlls(&walk_info_after);
-  printf("Testing dll load/unload ");
+  printf("Testing dll load/unload: ");
+  fflush(stdout);
   if (!walk_info_before.libpam_python_seen)
   {
     fprintf(stderr, "It looks like pam_%s.so wasn't loaded!\n", pyver);
@@ -159,7 +160,7 @@
   }
   else if (walk_info_after.python_seen)
   {
-    fprintf(stderr, "libpythonX.Y.so wasn't uloaded.\n");
+    fprintf(stderr, "libpythonX.Y.so wasn't unloaded.\n");
     exit_status = 1;
   }
   else
