LCOV - code coverage report
Current view: top level - third_party/pam_wrapper/python - pypamtest.c (source / functions) Hit Total Coverage
Test: coverage report for abartlet/fix-coverage dd10fb34 Lines: 236 415 56.9 %
Date: 2021-09-23 10:06:22 Functions: 21 29 72.4 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (c) 2015 Andreas Schneider <asn@samba.org>
       3             :  * Copyright (c) 2015 Jakub Hrozek <jakub.hrozek@posteo.se>
       4             :  *
       5             :  * This program is free software: you can redistribute it and/or modify
       6             :  * it under the terms of the GNU General Public License as published by
       7             :  * the Free Software Foundation, either version 3 of the License, or
       8             :  * (at your option) any later version.
       9             :  *
      10             :  * This program is distributed in the hope that it will be useful,
      11             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :  * GNU General Public License for more details.
      14             :  *
      15             :  * You should have received a copy of the GNU General Public License
      16             :  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
      17             :  */
      18             : 
      19             : #include "config.h"
      20             : 
      21             : #include <Python.h>
      22             : #include <structmember.h>
      23             : 
      24             : #include "libpamtest.h"
      25             : 
      26             : #define PYTHON_MODULE_NAME  "pypamtest"
      27             : 
      28             : #ifndef discard_const_p
      29             : #if defined(__intptr_t_defined) || defined(HAVE_UINTPTR_T)
      30             : # define discard_const_p(type, ptr) ((type *)((uintptr_t)(ptr)))
      31             : #else
      32             : # define discard_const_p(type, ptr) ((type *)(ptr))
      33             : #endif
      34             : #endif
      35             : 
      36             : #define    __unused    __attribute__((__unused__))
      37             : 
      38             : #if PY_MAJOR_VERSION >= 3
      39             : #define IS_PYTHON3 1
      40             : #define RETURN_ON_ERROR return NULL
      41             : #else
      42             : #define IS_PYTHON3 0
      43             : #define RETURN_ON_ERROR return
      44             : #endif /* PY_MAJOR_VERSION */
      45             : 
      46             : /* We only return up to 16 messages from the PAM conversation */
      47             : #define PAM_CONV_MSG_MAX        16
      48             : 
      49             : #if IS_PYTHON3
      50             : PyMODINIT_FUNC PyInit_pypamtest(void);
      51             : #else
      52             : PyMODINIT_FUNC initpypamtest(void);
      53             : #endif
      54             : 
      55             : typedef struct {
      56             :         PyObject_HEAD
      57             : 
      58             :         enum pamtest_ops pam_operation;
      59             :         int expected_rv;
      60             :         int flags;
      61             : } TestCaseObject;
      62             : 
      63             : /**********************************************************
      64             :  *** module-specific exceptions
      65             :  **********************************************************/
      66             : static PyObject *PyExc_PamTestError;
      67             : 
      68             : /**********************************************************
      69             :  *** helper functions
      70             :  **********************************************************/
      71             : 
      72             : #define REPR_FMT "{ pam_operation [%d] " \
      73             :                               "expected_rv [%d] " \
      74             :                               "flags [%d] }"
      75             : 
      76         272 : static char *py_strdup(const char *string)
      77             : {
      78             :         char *copy;
      79             : 
      80         272 :         copy = PyMem_New(char, strlen(string) + 1);
      81         272 :         if (copy ==  NULL) {
      82           0 :                 PyErr_NoMemory();
      83           0 :                 return NULL;
      84             :         }
      85             : 
      86         272 :         return strcpy(copy, string);
      87             : }
      88             : 
      89         272 : static PyObject *get_utf8_string(PyObject *obj,
      90             :                                  const char *attrname)
      91             : {
      92         272 :         const char *a = attrname ? attrname : "attribute";
      93         272 :         PyObject *obj_utf8 = NULL;
      94             : 
      95         272 :         if (PyBytes_Check(obj)) {
      96           0 :                 obj_utf8 = obj;
      97           0 :                 Py_INCREF(obj_utf8); /* Make sure we can DECREF later */
      98         272 :         } else if (PyUnicode_Check(obj)) {
      99         272 :                 if ((obj_utf8 = PyUnicode_AsUTF8String(obj)) == NULL) {
     100           0 :                         return NULL;
     101             :                 }
     102             :         } else {
     103           0 :                 PyErr_Format(PyExc_TypeError, "%s must be a string", a);
     104           0 :                 return NULL;
     105             :         }
     106             : 
     107         272 :         return obj_utf8;
     108             : }
     109             : 
     110         496 : static void free_cstring_list(const char **list)
     111             : {
     112             :         int i;
     113             : 
     114         496 :         if (list == NULL) {
     115         248 :                 return;
     116             :         }
     117             : 
     118         520 :         for (i=0; list[i]; i++) {
     119         272 :                 PyMem_Free(discard_const_p(char, list[i]));
     120             :         }
     121         248 :         PyMem_Free(list);
     122             : }
     123             : 
     124         496 : static void free_string_list(char **list)
     125             : {
     126             :         int i;
     127             : 
     128         496 :         if (list == NULL) {
     129           0 :                 return;
     130             :         }
     131             : 
     132        8432 :         for (i=0; list[i]; i++) {
     133        7936 :                 PyMem_Free(list[i]);
     134             :         }
     135         496 :         PyMem_Free(list);
     136             : }
     137             : 
     138         496 : static char **new_conv_list(const size_t list_size)
     139             : {
     140             :         char **list;
     141             :         size_t i;
     142             : 
     143         496 :         if (list_size == 0) {
     144           0 :                 return NULL;
     145             :         }
     146             : 
     147         496 :         if (list_size + 1 < list_size) {
     148           0 :                 return NULL;
     149             :         }
     150             : 
     151         496 :         list = PyMem_New(char *, list_size + 1);
     152         496 :         if (list == NULL) {
     153           0 :                 return NULL;
     154             :         }
     155         496 :         list[list_size] = NULL;
     156             : 
     157        8432 :         for (i = 0; i < list_size; i++) {
     158        7936 :                 list[i] = PyMem_New(char, PAM_MAX_MSG_SIZE);
     159        7936 :                 if (list[i] == NULL) {
     160           0 :                         PyMem_Free(list);
     161           0 :                         return NULL;
     162             :                 }
     163        7936 :                 memset(list[i], 0, PAM_MAX_MSG_SIZE);
     164             :         }
     165             : 
     166         496 :         return list;
     167             : }
     168             : 
     169         248 : static int sequence_as_string_list(PyObject *seq,
     170             :                                    const char *paramname,
     171             :                                    const char **str_list[],
     172             :                                    size_t *num_str_list)
     173             : {
     174         248 :         const char *p = paramname ? paramname : "attribute values";
     175             :         const char **result;
     176             :         PyObject *utf_item;
     177             :         int i;
     178             :         Py_ssize_t len;
     179             :         PyObject *item;
     180             : 
     181         248 :         if (!PySequence_Check(seq)) {
     182           0 :                 PyErr_Format(PyExc_TypeError,
     183             :                              "The object must be a sequence\n");
     184           0 :                 return -1;
     185             :         }
     186             : 
     187         248 :         len = PySequence_Size(seq);
     188         248 :         if (len == -1) {
     189           0 :                 return -1;
     190             :         }
     191             : 
     192         248 :         result = PyMem_New(const char *, (len + 1));
     193         248 :         if (result == NULL) {
     194           0 :                 PyErr_NoMemory();
     195           0 :                 return -1;
     196             :         }
     197             : 
     198         520 :         for (i = 0; i < len; i++) {
     199         272 :                 item = PySequence_GetItem(seq, i);
     200         272 :                 if (item == NULL) {
     201           0 :                         break;
     202             :                 }
     203             : 
     204         272 :                 utf_item = get_utf8_string(item, p);
     205         272 :                 if (utf_item == NULL) {
     206           0 :                         Py_DECREF(item);
     207           0 :                         return -1;
     208             :                 }
     209             : 
     210         272 :                 result[i] = py_strdup(PyBytes_AsString(utf_item));
     211         272 :                 Py_DECREF(utf_item);
     212         272 :                 if (result[i] == NULL) {
     213           0 :                         Py_DECREF(item);
     214           0 :                         return -1;
     215             :                 }
     216         272 :                 Py_DECREF(item);
     217             :         }
     218             : 
     219         248 :         result[i] = NULL;
     220             : 
     221         248 :         *str_list = result;
     222         248 :         *num_str_list = (size_t)len;
     223             : 
     224         248 :         return 0;
     225             : }
     226             : 
     227         496 : static PyObject *string_list_as_tuple(char **str_list)
     228             : {
     229             :         int rc;
     230             :         size_t len, i;
     231             :         PyObject *tup;
     232             :         PyObject *py_str;
     233             : 
     234         564 :         for (len=0; str_list[len] != NULL; len++) {
     235         564 :                 if (str_list[len][0] == '\0') {
     236             :                         /* unused string, stop counting */
     237         496 :                         break;
     238             :                 }
     239             :         }
     240             : 
     241         496 :         tup = PyTuple_New(len);
     242         496 :         if (tup == NULL) {
     243           0 :                 PyErr_NoMemory();
     244           0 :                 return NULL;
     245             :         }
     246             : 
     247         564 :         for (i = 0; i < len; i++) {
     248          68 :                 py_str = PyUnicode_FromString(str_list[i]);
     249          68 :                 if (py_str == NULL) {
     250           0 :                         Py_DECREF(tup);
     251           0 :                         PyErr_NoMemory();
     252           0 :                         return NULL;
     253             :                 }
     254             : 
     255             :                 /* PyTuple_SetItem() steals the reference to
     256             :                  * py_str, so it's enough to decref the tuple
     257             :                  * pointer afterwards */
     258          68 :                 rc = PyTuple_SetItem(tup, i, py_str);
     259          68 :                 if (rc != 0) {
     260             :                         /* cleanup */
     261           0 :                         Py_DECREF(py_str);
     262           0 :                         Py_DECREF(tup);
     263           0 :                         PyErr_NoMemory();
     264           0 :                         return NULL;
     265             :                 }
     266             :         }
     267             : 
     268         496 :         return tup;
     269             : }
     270             : 
     271             : static void
     272           0 : set_pypamtest_exception(PyObject *exc,
     273             :                         enum pamtest_err perr,
     274             :                         struct pam_testcase *tests,
     275             :                         size_t num_tests)
     276             : {
     277           0 :         PyObject *obj = NULL;
     278             :         /* REPR_FMT contains just %d expansions, so this is safe */
     279           0 :         char test_repr[256] = { '\0' };
     280             :         union {
     281             :                 char *str;
     282             :                 PyObject *obj;
     283             :         } pypam_str_object;
     284             :         const char *strerr;
     285           0 :         const struct pam_testcase *failed = NULL;
     286             : 
     287           0 :         if (exc == NULL) {
     288           0 :                 PyErr_BadArgument();
     289           0 :                 return;
     290             :         }
     291             : 
     292           0 :         strerr = pamtest_strerror(perr);
     293             : 
     294           0 :         if (perr == PAMTEST_ERR_CASE) {
     295           0 :                 failed = _pamtest_failed_case(tests, num_tests);
     296           0 :                 if (failed) {
     297           0 :                         snprintf(test_repr, sizeof(test_repr), REPR_FMT,
     298           0 :                                  failed->pam_operation,
     299           0 :                                  failed->expected_rv,
     300           0 :                                  failed->flags);
     301             :                 }
     302             :         }
     303             : 
     304           0 :         if (test_repr[0] != '\0' && failed != NULL) {
     305           0 :                 PyErr_Format(exc,
     306             :                              "Error [%d]: Test case %s returned [%d]",
     307           0 :                              perr, test_repr, failed->op_rv);
     308             :         } else {
     309           0 :                 obj = Py_BuildValue(discard_const_p(char, "(i,s)"),
     310             :                                         perr,
     311             :                                         strerr ? strerr : "Unknown error");
     312           0 :                 PyErr_SetObject(exc, obj);
     313             :         }
     314             : 
     315           0 :         pypam_str_object.str = test_repr;
     316           0 :         Py_XDECREF(pypam_str_object.obj);
     317           0 :         Py_XDECREF(obj);
     318             : }
     319             : 
     320             : /* Returned when doc(test_case) is invoked */
     321             : PyDoc_STRVAR(TestCaseObject__doc__,
     322             : "pamtest test case\n\n"
     323             : "Represents one operation in PAM transaction. An example is authentication, "
     324             : "opening a session or password change. Each operation has an expected error "
     325             : "code. The run_pamtest() function accepts a list of these test case objects\n"
     326             : "Params:\n\n"
     327             : "pam_operation: - the PAM operation to run. Use constants from pypamtest "
     328             : "such as pypamtest.PAMTEST_AUTHENTICATE. This argument is required.\n"
     329             : "expected_rv: - The PAM return value we expect the operation to return. "
     330             : "Defaults to 0 (PAM_SUCCESS)\n"
     331             : "flags: - Additional flags to pass to the PAM operation. Defaults to 0.\n"
     332             : );
     333             : 
     334             : static PyObject *
     335         248 : TestCase_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     336             : {
     337             :         TestCaseObject *self;
     338             : 
     339             :         (void) args; /* unused */
     340             :         (void) kwds; /* unused */
     341             : 
     342         248 :         self = (TestCaseObject *)type->tp_alloc(type, 0);
     343         248 :         if (self == NULL) {
     344           0 :                 PyErr_NoMemory();
     345           0 :                 return NULL;
     346             :         }
     347             : 
     348         248 :         return (PyObject *) self;
     349             : }
     350             : 
     351             : /* The traverse and clear methods must be defined even though they do nothing
     352             :  * otherwise Garbage Collector is not happy
     353             :  */
     354           0 : static int TestCase_clear(TestCaseObject *self)
     355             : {
     356             :         (void) self; /* unused */
     357             : 
     358           0 :         return 0;
     359             : }
     360             : 
     361         248 : static void TestCase_dealloc(TestCaseObject *self)
     362             : {
     363         248 :         Py_TYPE(self)->tp_free((PyObject *)self);
     364         248 : }
     365             : 
     366           0 : static int TestCase_traverse(TestCaseObject *self,
     367             :                              visitproc visit,
     368             :                              void *arg)
     369             : {
     370             :         (void) self; /* unused */
     371             :         (void) visit; /* unused */
     372             :         (void) arg; /* unused */
     373             : 
     374           0 :         return 0;
     375             : }
     376             : 
     377         248 : static int TestCase_init(TestCaseObject *self,
     378             :                          PyObject *args,
     379             :                          PyObject *kwargs)
     380             : {
     381         248 :         const char * const kwlist[] = { "pam_operation",
     382             :                                         "expected_rv",
     383             :                                         "flags",
     384             :                                         NULL };
     385         248 :         int pam_operation = -1;
     386         248 :         int expected_rv = PAM_SUCCESS;
     387         248 :         int flags = 0;
     388             :         int ok;
     389             : 
     390         248 :         ok = PyArg_ParseTupleAndKeywords(args,
     391             :                                          kwargs,
     392             :                                          "i|ii",
     393             :                                          discard_const_p(char *, kwlist),
     394             :                                          &pam_operation,
     395             :                                          &expected_rv,
     396             :                                          &flags);
     397         248 :         if (!ok) {
     398           0 :                 return -1;
     399             :         }
     400             : 
     401         248 :         switch (pam_operation) {
     402         248 :         case PAMTEST_AUTHENTICATE:
     403             :         case PAMTEST_SETCRED:
     404             :         case PAMTEST_ACCOUNT:
     405             :         case PAMTEST_OPEN_SESSION:
     406             :         case PAMTEST_CLOSE_SESSION:
     407             :         case PAMTEST_CHAUTHTOK:
     408             :         case PAMTEST_GETENVLIST:
     409             :         case PAMTEST_KEEPHANDLE:
     410         248 :                 break;
     411           0 :         default:
     412           0 :                 PyErr_Format(PyExc_ValueError,
     413             :                              "Unsupported PAM operation %d",
     414             :                              pam_operation);
     415           0 :                 return -1;
     416             :         }
     417             : 
     418         248 :         self->flags = flags;
     419         248 :         self->expected_rv = expected_rv;
     420         248 :         self->pam_operation = pam_operation;
     421             : 
     422         248 :         return 0;
     423             : }
     424             : 
     425             : /*
     426             :  * This function returns string representation of the object, but one that
     427             :  * can be parsed by a machine.
     428             :  *
     429             :  * str() is also string represtentation, but just human-readable.
     430             :  */
     431           0 : static PyObject *TestCase_repr(TestCaseObject *self)
     432             : {
     433           0 :         return PyUnicode_FromFormat(REPR_FMT,
     434           0 :                                     self->pam_operation,
     435             :                                     self->expected_rv,
     436             :                                     self->flags);
     437             : }
     438             : 
     439             : static PyMemberDef pypamtest_test_case_members[] = {
     440             :         {
     441             :                 discard_const_p(char, "pam_operation"),
     442             :                 T_INT,
     443             :                 offsetof(TestCaseObject, pam_operation),
     444             :                 READONLY,
     445             :                 discard_const_p(char, "The PAM operation to run"),
     446             :         },
     447             : 
     448             :         {
     449             :                 discard_const_p(char, "expected_rv"),
     450             :                 T_INT,
     451             :                 offsetof(TestCaseObject, expected_rv),
     452             :                 READONLY,
     453             :                 discard_const_p(char, "The expected PAM return code"),
     454             :         },
     455             : 
     456             :         {
     457             :                 discard_const_p(char, "flags"),
     458             :                 T_INT,
     459             :                 offsetof(TestCaseObject, flags),
     460             :                 READONLY,
     461             :                 discard_const_p(char, "Additional flags for the PAM operation"),
     462             :         },
     463             : 
     464             :         { NULL, 0, 0, 0, NULL } /* Sentinel */
     465             : };
     466             : 
     467             : static PyTypeObject pypamtest_test_case = {
     468             :         PyVarObject_HEAD_INIT(NULL, 0)
     469             :         .tp_name = "pypamtest.TestCase",
     470             :         .tp_basicsize = sizeof(TestCaseObject),
     471             :         .tp_new = TestCase_new,
     472             :         .tp_dealloc = (destructor) TestCase_dealloc,
     473             :         .tp_traverse = (traverseproc) TestCase_traverse,
     474             :         .tp_clear = (inquiry) TestCase_clear,
     475             :         .tp_init = (initproc) TestCase_init,
     476             :         .tp_repr = (reprfunc) TestCase_repr,
     477             :         .tp_members = pypamtest_test_case_members,
     478             :         .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
     479             :         .tp_doc   = TestCaseObject__doc__
     480             : };
     481             : 
     482             : PyDoc_STRVAR(TestResultObject__doc__,
     483             : "pamtest test result\n\n"
     484             : "The test result object is returned from run_pamtest on success. It contains"
     485             : "two lists of strings (up to 16 strings each) which contain the info and error"
     486             : "messages the PAM conversation printed\n\n"
     487             : "Attributes:\n"
     488             : "errors: PAM_ERROR_MSG-level messages printed during the PAM conversation\n"
     489             : "info: PAM_TEXT_INFO-level messages printed during the PAM conversation\n"
     490             : );
     491             : 
     492             : typedef struct {
     493             :         PyObject_HEAD
     494             : 
     495             :         PyObject *info_msg_list;
     496             :         PyObject *error_msg_list;
     497             : } TestResultObject;
     498             : 
     499             : static PyObject *
     500         248 : TestResult_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
     501             : {
     502             :         TestResultObject *self;
     503             : 
     504             :         (void) args; /* unused */
     505             :         (void) kwds; /* unused */
     506             : 
     507         248 :         self = (TestResultObject *)type->tp_alloc(type, 0);
     508         248 :         if (self == NULL) {
     509           0 :                 PyErr_NoMemory();
     510           0 :                 return NULL;
     511             :         }
     512             : 
     513         248 :         return (PyObject *) self;
     514             : }
     515             : 
     516           0 : static int TestResult_clear(TestResultObject *self)
     517             : {
     518             :         (void) self; /* unused */
     519             : 
     520           0 :         return 0;
     521             : }
     522             : 
     523         248 : static void TestResult_dealloc(TestResultObject *self)
     524             : {
     525         248 :         Py_TYPE(self)->tp_free((PyObject *)self);
     526         248 : }
     527             : 
     528           0 : static int TestResult_traverse(TestResultObject *self,
     529             :                                visitproc visit,
     530             :                                void *arg)
     531             : {
     532             :         (void) self;    /* unused */
     533             :         (void) visit;   /* unused */
     534             :         (void) arg;     /* unused */
     535             : 
     536           0 :         return 0;
     537             : }
     538             : 
     539         248 : static int TestResult_init(TestResultObject *self,
     540             :                            PyObject *args,
     541             :                            PyObject *kwargs)
     542             : {
     543         248 :         const char * const kwlist[] = { "info_msg_list",
     544             :                                         "error_msg_list",
     545             :                                         NULL };
     546             :         int ok;
     547         248 :         PyObject *py_info_list = NULL;
     548         248 :         PyObject *py_err_list = NULL;
     549             : 
     550         248 :         ok = PyArg_ParseTupleAndKeywords(args,
     551             :                                          kwargs,
     552             :                                          "|OO",
     553             :                                          discard_const_p(char *, kwlist),
     554             :                                          &py_info_list,
     555             :                                          &py_err_list);
     556         248 :         if (!ok) {
     557           0 :                 return -1;
     558             :         }
     559             : 
     560         248 :         if (py_info_list) {
     561         248 :                 ok = PySequence_Check(py_info_list);
     562         248 :                 if (!ok) {
     563           0 :                         PyErr_Format(PyExc_TypeError,
     564             :                                 "List of info messages must be a sequence\n");
     565           0 :                         return -1;
     566             :                 }
     567             : 
     568         248 :                 self->info_msg_list = py_info_list;
     569         248 :                 Py_XINCREF(py_info_list);
     570             :         } else {
     571           0 :                 self->info_msg_list = PyList_New(0);
     572           0 :                 if (self->info_msg_list == NULL) {
     573           0 :                         PyErr_NoMemory();
     574           0 :                         return -1;
     575             :                 }
     576             :         }
     577             : 
     578         248 :         if (py_err_list) {
     579         248 :                 ok = PySequence_Check(py_err_list);
     580         248 :                 if (!ok) {
     581           0 :                         PyErr_Format(PyExc_TypeError,
     582             :                                 "List of error messages must be a sequence\n");
     583           0 :                         return -1;
     584             :                 }
     585             : 
     586         248 :                 self->error_msg_list = py_err_list;
     587         248 :                 Py_XINCREF(py_err_list);
     588             :         } else {
     589           0 :                 self->error_msg_list = PyList_New(0);
     590           0 :                 if (self->error_msg_list == NULL) {
     591           0 :                         PyErr_NoMemory();
     592           0 :                         return -1;
     593             :                 }
     594             :         }
     595             : 
     596         248 :         return 0;
     597             : }
     598             : 
     599           0 : static PyObject *test_result_list_concat(PyObject *list,
     600             :                                          const char delim_pre,
     601             :                                          const char delim_post)
     602             : {
     603             :         PyObject *res;
     604             :         PyObject *item;
     605             :         Py_ssize_t size;
     606             :         Py_ssize_t i;
     607             : 
     608           0 :         res = PyUnicode_FromString("");
     609           0 :         if (res == NULL) {
     610           0 :                 return NULL;
     611             :         }
     612             : 
     613           0 :         size = PySequence_Size(list);
     614             : 
     615           0 :         for (i=0; i < size; i++) {
     616           0 :                 item = PySequence_GetItem(list, i);
     617           0 :                 if (item == NULL) {
     618           0 :                         PyMem_Free(res);
     619           0 :                         return NULL;
     620             :                 }
     621             : 
     622             : #if IS_PYTHON3
     623           0 :                 res = PyUnicode_FromFormat("%U%c%U%c",
     624             :                                            res, delim_pre, item, delim_post);
     625             : #else
     626             :                 res = PyUnicode_FromFormat("%U%c%s%c",
     627             :                                            res,
     628             :                                            delim_pre,
     629             :                                            PyString_AsString(item),
     630             :                                            delim_post);
     631             : #endif
     632           0 :                 Py_XDECREF(item);
     633             :         }
     634             : 
     635           0 :         return res;
     636             : }
     637             : 
     638           0 : static PyObject *TestResult_repr(TestResultObject *self)
     639             : {
     640           0 :         PyObject *u_info = NULL;
     641           0 :         PyObject *u_error = NULL;
     642           0 :         PyObject *res = NULL;
     643             : 
     644           0 :         u_info = test_result_list_concat(self->info_msg_list, '{', '}');
     645           0 :         u_error = test_result_list_concat(self->info_msg_list, '{', '}');
     646           0 :         if (u_info == NULL || u_error == NULL) {
     647           0 :                 Py_XDECREF(u_error);
     648           0 :                 Py_XDECREF(u_info);
     649           0 :                 return NULL;
     650             :         }
     651             : 
     652           0 :         res = PyUnicode_FromFormat("{ errors: { %U } infos: { %U } }",
     653             :                                    u_info, u_error);
     654           0 :         Py_DECREF(u_error);
     655           0 :         Py_DECREF(u_info);
     656           0 :         return res;
     657             : }
     658             : 
     659             : static PyMemberDef pypamtest_test_result_members[] = {
     660             :         {
     661             :                 discard_const_p(char, "errors"),
     662             :                 T_OBJECT_EX,
     663             :                 offsetof(TestResultObject, error_msg_list),
     664             :                 READONLY,
     665             :                 discard_const_p(char,
     666             :                                 "List of error messages from PAM conversation"),
     667             :         },
     668             : 
     669             :         {
     670             :                 discard_const_p(char, "info"),
     671             :                 T_OBJECT_EX,
     672             :                 offsetof(TestResultObject, info_msg_list),
     673             :                 READONLY,
     674             :                 discard_const_p(char,
     675             :                                 "List of info messages from PAM conversation"),
     676             :         },
     677             : 
     678             :         { NULL, 0, 0, 0, NULL } /* Sentinel */
     679             : };
     680             : 
     681             : static PyTypeObject pypamtest_test_result = {
     682             :         PyVarObject_HEAD_INIT(NULL, 0)
     683             :         .tp_name = "pypamtest.TestResult",
     684             :         .tp_basicsize = sizeof(TestResultObject),
     685             :         .tp_new = TestResult_new,
     686             :         .tp_dealloc = (destructor) TestResult_dealloc,
     687             :         .tp_traverse = (traverseproc) TestResult_traverse,
     688             :         .tp_clear = (inquiry) TestResult_clear,
     689             :         .tp_init = (initproc) TestResult_init,
     690             :         .tp_repr = (reprfunc) TestResult_repr,
     691             :         .tp_members = pypamtest_test_result_members,
     692             :         .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
     693             :         .tp_doc   = TestResultObject__doc__
     694             : };
     695             : 
     696             : /**********************************************************
     697             :  *** Methods of the module
     698             :  **********************************************************/
     699             : 
     700         248 : static TestResultObject *construct_test_conv_result(char **msg_info, char **msg_err)
     701             : {
     702         248 :         PyObject *py_msg_info = NULL;
     703         248 :         PyObject *py_msg_err = NULL;
     704         248 :         TestResultObject *result = NULL;
     705         248 :         PyObject *result_args = NULL;
     706             :         int rc;
     707             : 
     708         248 :         py_msg_info = string_list_as_tuple(msg_info);
     709         248 :         py_msg_err = string_list_as_tuple(msg_err);
     710         248 :         if (py_msg_info == NULL || py_msg_err == NULL) {
     711             :                 /* The exception is raised in string_list_as_tuple() */
     712           0 :                 Py_XDECREF(py_msg_err);
     713           0 :                 Py_XDECREF(py_msg_info);
     714           0 :                 return NULL;
     715             :         }
     716             : 
     717         248 :         result = (TestResultObject *) TestResult_new(&pypamtest_test_result,
     718             :                                                      NULL,
     719             :                                                      NULL);
     720         248 :         if (result == NULL) {
     721             :                 /* The exception is raised in TestResult_new */
     722           0 :                 Py_XDECREF(py_msg_err);
     723           0 :                 Py_XDECREF(py_msg_info);
     724           0 :                 return NULL;
     725             :         }
     726             : 
     727         248 :         result_args = PyTuple_New(2);
     728         248 :         if (result_args == NULL) {
     729             :                 /* The exception is raised in TestResult_new */
     730           0 :                 Py_XDECREF(result);
     731           0 :                 Py_XDECREF(py_msg_err);
     732           0 :                 Py_XDECREF(py_msg_info);
     733           0 :                 return NULL;
     734             :         }
     735             : 
     736             :         /* Brand new tuples with fixed size don't need error checking */
     737         248 :         PyTuple_SET_ITEM(result_args, 0, py_msg_info);
     738         248 :         PyTuple_SET_ITEM(result_args, 1, py_msg_err);
     739             : 
     740         248 :         rc = TestResult_init(result, result_args, NULL);
     741         248 :         Py_XDECREF(result_args);
     742         248 :         if (rc != 0) {
     743           0 :                 Py_XDECREF(result);
     744           0 :                 return NULL;
     745             :         }
     746             : 
     747         248 :         return result;
     748             : }
     749             : 
     750         744 : static int py_testcase_get(PyObject *py_test,
     751             :                            const char *member_name,
     752             :                            long *_value)
     753             : {
     754         744 :         PyObject* item = NULL;
     755             : 
     756             :         /*
     757             :          * PyPyObject_GetAttrString() increases the refcount on the
     758             :          * returned value.
     759             :          */
     760         744 :         item = PyObject_GetAttrString(py_test, member_name);
     761         744 :         if (item == NULL) {
     762           0 :                 return EINVAL;
     763             :         }
     764             : 
     765         744 :         *_value = PyLong_AsLong(item);
     766         744 :         Py_DECREF(item);
     767             : 
     768         744 :         return 0;
     769             : }
     770             : 
     771         248 : static int py_testcase_to_cstruct(PyObject *py_test, struct pam_testcase *test)
     772             : {
     773             :         int rc;
     774             :         long value;
     775             : 
     776         248 :         rc = py_testcase_get(py_test, "pam_operation", &value);
     777         248 :         if (rc != 0) {
     778           0 :                 return rc;
     779             :         }
     780         248 :         test->pam_operation = value;
     781             : 
     782         248 :         rc = py_testcase_get(py_test, "expected_rv", &value);
     783         248 :         if (rc != 0) {
     784           0 :                 return rc;
     785             :         }
     786         248 :         test->expected_rv = value;
     787             : 
     788         248 :         rc = py_testcase_get(py_test, "flags", &value);
     789         248 :         if (rc != 0) {
     790           0 :                 return rc;
     791             :         }
     792         248 :         test->flags = value;
     793             : 
     794         248 :         return 0;
     795             : }
     796             : 
     797         248 : static void free_conv_data(struct pamtest_conv_data *conv_data)
     798             : {
     799         248 :         if (conv_data == NULL) {
     800           0 :                 return;
     801             :         }
     802             : 
     803         248 :         free_string_list(conv_data->out_err);
     804         248 :         free_string_list(conv_data->out_info);
     805         248 :         free_cstring_list(conv_data->in_echo_on);
     806         248 :         free_cstring_list(conv_data->in_echo_off);
     807             : }
     808             : 
     809             : /* conv_data must be a pointer to allocated conv_data structure.
     810             :  *
     811             :  * Use free_conv_data() to free the contents.
     812             :  */
     813         248 : static int fill_conv_data(PyObject *py_echo_off,
     814             :                           PyObject *py_echo_on,
     815             :                           struct pamtest_conv_data *conv_data)
     816             : {
     817         248 :         size_t conv_count = 0;
     818         248 :         size_t count = 0;
     819             :         int rc;
     820             : 
     821         248 :         conv_data->in_echo_on = NULL;
     822         248 :         conv_data->in_echo_off = NULL;
     823         248 :         conv_data->out_err = NULL;
     824         248 :         conv_data->out_info = NULL;
     825             : 
     826         248 :         if (py_echo_off != NULL) {
     827         248 :                 rc = sequence_as_string_list(py_echo_off,
     828             :                                              "echo_off",
     829             :                                              &conv_data->in_echo_off,
     830             :                                              &count);
     831         248 :                 if (rc != 0) {
     832           0 :                         free_conv_data(conv_data);
     833           0 :                         return ENOMEM;
     834             :                 }
     835         248 :                 conv_count += count;
     836             :         }
     837             : 
     838         248 :         if (py_echo_on != NULL) {
     839           0 :                 rc = sequence_as_string_list(py_echo_on,
     840             :                                              "echo_on",
     841             :                                              &conv_data->in_echo_on,
     842             :                                              &count);
     843           0 :                 if (rc != 0) {
     844           0 :                         free_conv_data(conv_data);
     845           0 :                         return ENOMEM;
     846             :                 }
     847           0 :                 conv_count += count;
     848             :         }
     849             : 
     850         248 :         if (conv_count > PAM_CONV_MSG_MAX) {
     851           0 :                 free_conv_data(conv_data);
     852           0 :                 return ENOMEM;
     853             :         }
     854             : 
     855         248 :         conv_data->out_info = new_conv_list(PAM_CONV_MSG_MAX);
     856         248 :         conv_data->out_err = new_conv_list(PAM_CONV_MSG_MAX);
     857         248 :         if (conv_data->out_info == NULL || conv_data->out_err == NULL) {
     858           0 :                 free_conv_data(conv_data);
     859           0 :                 return ENOMEM;
     860             :         }
     861             : 
     862         248 :         return 0;
     863             : }
     864             : 
     865             : /* test_list is allocated using PyMem_New and must be freed accordingly.
     866             :  * Returns errno that should be handled into exception in the caller
     867             :  */
     868         248 : static int py_tc_list_to_cstruct_list(PyObject *py_test_list,
     869             :                                       Py_ssize_t num_tests,
     870             :                                       struct pam_testcase **_test_list)
     871             : {
     872             :         Py_ssize_t i;
     873             :         PyObject *py_test;
     874             :         int rc;
     875             :         struct pam_testcase *test_list;
     876             : 
     877         248 :         test_list = PyMem_New(struct pam_testcase,
     878             :                             num_tests * sizeof(struct pam_testcase));
     879         248 :         if (test_list == NULL) {
     880           0 :                 return ENOMEM;
     881             :         }
     882             : 
     883         496 :         for (i = 0; i < num_tests; i++) {
     884             :                 /*
     885             :                  * PySequence_GetItem() increases the refcount on the
     886             :                  * returned value
     887             :                  */
     888         248 :                 py_test = PySequence_GetItem(py_test_list, i);
     889         248 :                 if (py_test == NULL) {
     890           0 :                         PyMem_Free(test_list);
     891           0 :                         return EIO;
     892             :                 }
     893             : 
     894         248 :                 rc = py_testcase_to_cstruct(py_test, &test_list[i]);
     895         248 :                 Py_DECREF(py_test);
     896         248 :                 if (rc != 0) {
     897           0 :                         PyMem_Free(test_list);
     898           0 :                         return EIO;
     899             :                 }
     900             :         }
     901             : 
     902         248 :         *_test_list = test_list;
     903         248 :         return 0;
     904             : }
     905             : 
     906             : PyDoc_STRVAR(RunPamTest__doc__,
     907             : "Run PAM tests\n\n"
     908             : "This function runs PAM test cases and reports result\n"
     909             : "Parameters:\n"
     910             : "service: The PAM service to use in the conversation (string)\n"
     911             : "username: The user to run PAM conversation as\n"
     912             : "test_list: Sequence of pypamtest.TestCase objects\n"
     913             : "echo_off_list: Sequence of strings that will be used by PAM "
     914             : "conversation for PAM_PROMPT_ECHO_OFF input. These are typically "
     915             : "passwords.\n"
     916             : "echo_on_list: Sequence of strings that will be used by PAM "
     917             : "conversation for PAM_PROMPT_ECHO_ON input.\n"
     918             : );
     919             : 
     920         248 : static PyObject *pypamtest_run_pamtest(PyObject *module, PyObject *args)
     921             : {
     922             :         int ok;
     923             :         int rc;
     924         248 :         char *username = NULL;
     925         248 :         char *service = NULL;
     926             :         PyObject *py_test_list;
     927         248 :         PyObject *py_echo_off = NULL;
     928         248 :         PyObject *py_echo_on = NULL;
     929             :         Py_ssize_t num_tests;
     930             :         struct pam_testcase *test_list;
     931             :         enum pamtest_err perr;
     932             :         struct pamtest_conv_data conv_data;
     933         248 :         TestResultObject *result = NULL;
     934             : 
     935             :         (void) module;  /* unused */
     936             : 
     937         248 :         ok = PyArg_ParseTuple(args,
     938             :                               discard_const_p(char, "ssO|OO"),
     939             :                               &username,
     940             :                               &service,
     941             :                               &py_test_list,
     942             :                               &py_echo_off,
     943             :                               &py_echo_on);
     944         248 :         if (!ok) {
     945           0 :                 return NULL;
     946             :         }
     947             : 
     948         248 :         ok = PySequence_Check(py_test_list);
     949         248 :         if (!ok) {
     950           0 :                 PyErr_Format(PyExc_TypeError, "tests must be a sequence");
     951           0 :                 return NULL;
     952             :         }
     953             : 
     954         248 :         num_tests = PySequence_Size(py_test_list);
     955         248 :         if (num_tests == -1) {
     956           0 :                 PyErr_Format(PyExc_IOError, "Cannot get sequence length");
     957           0 :                 return NULL;
     958             :         }
     959             : 
     960         248 :         rc = py_tc_list_to_cstruct_list(py_test_list, num_tests, &test_list);
     961         248 :         if (rc != 0) {
     962           0 :                 if (rc == ENOMEM) {
     963           0 :                         PyErr_NoMemory();
     964           0 :                         return NULL;
     965             :                 } else {
     966           0 :                         PyErr_Format(PyExc_IOError,
     967             :                                      "Cannot convert test to C structure");
     968           0 :                         return NULL;
     969             :                 }
     970             :         }
     971             : 
     972         248 :         rc = fill_conv_data(py_echo_off, py_echo_on, &conv_data);
     973         248 :         if (rc != 0) {
     974           0 :                 PyMem_Free(test_list);
     975           0 :                 PyErr_NoMemory();
     976           0 :                 return NULL;
     977             :         }
     978             : 
     979         248 :         perr = _pamtest(service, username, &conv_data, test_list, num_tests);
     980         248 :         if (perr != PAMTEST_ERR_OK) {
     981           0 :                 free_conv_data(&conv_data);
     982           0 :                 set_pypamtest_exception(PyExc_PamTestError,
     983             :                                         perr,
     984             :                                         test_list,
     985             :                                         num_tests);
     986           0 :                 PyMem_Free(test_list);
     987           0 :                 return NULL;
     988             :         }
     989         248 :         PyMem_Free(test_list);
     990             : 
     991         248 :         result = construct_test_conv_result(conv_data.out_info,
     992             :                                             conv_data.out_err);
     993         248 :         free_conv_data(&conv_data);
     994         248 :         if (result == NULL) {
     995           0 :                 PyMem_Free(test_list);
     996           0 :                 return NULL;
     997             :         }
     998             : 
     999         248 :         return (PyObject *)result;
    1000             : }
    1001             : 
    1002             : static PyMethodDef pypamtest_module_methods[] = {
    1003             :         {
    1004             :                 discard_const_p(char, "run_pamtest"),
    1005             :                 (PyCFunction) pypamtest_run_pamtest,
    1006             :                 METH_VARARGS,
    1007             :                 RunPamTest__doc__,
    1008             :         },
    1009             : 
    1010             :         { NULL, NULL, 0, NULL }  /* Sentinel */
    1011             : };
    1012             : 
    1013             : /*
    1014             :  * This is the module structure describing the module and
    1015             :  * to define methods
    1016             :  */
    1017             : #if IS_PYTHON3
    1018             : static struct PyModuleDef pypamtestdef = {
    1019             :         .m_base = PyModuleDef_HEAD_INIT,
    1020             :         .m_name = PYTHON_MODULE_NAME,
    1021             :         .m_size = -1,
    1022             :         .m_methods = pypamtest_module_methods,
    1023             : };
    1024             : #endif
    1025             : 
    1026             : /**********************************************************
    1027             :  *** Initialize the module
    1028             :  **********************************************************/
    1029             : 
    1030             : #if PY_VERSION_HEX >= 0x02070000 /* >= 2.7.0 */
    1031             : PyDoc_STRVAR(PamTestError__doc__,
    1032             : "pypamtest specific exception\n\n"
    1033             : "This exception is raised if the _pamtest() function fails. If _pamtest() "
    1034             : "returns PAMTEST_ERR_CASE (a test case returns unexpected error code), then "
    1035             : "the exception also details which test case failed."
    1036             : );
    1037             : #endif
    1038             : 
    1039             : #if IS_PYTHON3
    1040          96 : PyMODINIT_FUNC PyInit_pypamtest(void)
    1041             : #else
    1042             : PyMODINIT_FUNC initpypamtest(void)
    1043             : #endif
    1044             : {
    1045             :         PyObject *m;
    1046             :         union {
    1047             :                 PyTypeObject *type_obj;
    1048             :                 PyObject *obj;
    1049             :         } pypam_object;
    1050             :         int ret;
    1051             : 
    1052             : #if IS_PYTHON3
    1053          96 :         m = PyModule_Create(&pypamtestdef);
    1054          96 :         if (m == NULL) {
    1055           0 :                 RETURN_ON_ERROR;
    1056             :         }
    1057             : #else
    1058             :         m = Py_InitModule(discard_const_p(char, PYTHON_MODULE_NAME),
    1059             :                           pypamtest_module_methods);
    1060             : #endif
    1061             : 
    1062             : #if PY_VERSION_HEX >= 0x02070000 /* >= 2.7.0 */
    1063          96 :         PyExc_PamTestError = PyErr_NewExceptionWithDoc(discard_const_p(char, "pypamtest.PamTestError"),
    1064             :                                                        PamTestError__doc__,
    1065             :                                                        PyExc_EnvironmentError,
    1066             :                                                        NULL);
    1067             : #else /* < 2.7.0 */
    1068             :         PyExc_PamTestError = PyErr_NewException(discard_const_p(char, "pypamtest.PamTestError"),
    1069             :                                                        PyExc_EnvironmentError,
    1070             :                                                        NULL);
    1071             : #endif
    1072             : 
    1073          96 :         if (PyExc_PamTestError == NULL) {
    1074           0 :                 RETURN_ON_ERROR;
    1075             :         }
    1076             : 
    1077          96 :         Py_INCREF(PyExc_PamTestError);
    1078          96 :         ret = PyModule_AddObject(m, discard_const_p(char, "PamTestError"),
    1079             :                                  PyExc_PamTestError);
    1080          96 :         if (ret == -1) {
    1081           0 :                 RETURN_ON_ERROR;
    1082             :         }
    1083             : 
    1084          96 :         ret = PyModule_AddIntMacro(m, PAMTEST_AUTHENTICATE);
    1085          96 :         if (ret == -1) {
    1086           0 :                 RETURN_ON_ERROR;
    1087             :         }
    1088          96 :         ret = PyModule_AddIntMacro(m, PAMTEST_SETCRED);
    1089          96 :         if (ret == -1) {
    1090           0 :                 RETURN_ON_ERROR;
    1091             :         }
    1092          96 :         ret = PyModule_AddIntMacro(m, PAMTEST_ACCOUNT);
    1093          96 :         if (ret == -1) {
    1094           0 :                 RETURN_ON_ERROR;
    1095             :         }
    1096          96 :         ret = PyModule_AddIntMacro(m, PAMTEST_OPEN_SESSION);
    1097          96 :         if (ret == -1) {
    1098           0 :                 RETURN_ON_ERROR;
    1099             :         }
    1100          96 :         ret = PyModule_AddIntMacro(m, PAMTEST_CLOSE_SESSION);
    1101          96 :         if (ret == -1) {
    1102           0 :                 RETURN_ON_ERROR;
    1103             :         }
    1104          96 :         ret = PyModule_AddIntMacro(m, PAMTEST_CHAUTHTOK);
    1105          96 :         if (ret == -1) {
    1106           0 :                 RETURN_ON_ERROR;
    1107             :         }
    1108             : 
    1109          96 :         ret = PyModule_AddIntMacro(m, PAMTEST_GETENVLIST);
    1110          96 :         if (ret == -1) {
    1111           0 :                 RETURN_ON_ERROR;
    1112             :         }
    1113          96 :         ret = PyModule_AddIntMacro(m, PAMTEST_KEEPHANDLE);
    1114          96 :         if (ret == -1) {
    1115           0 :                 RETURN_ON_ERROR;
    1116             :         }
    1117             : 
    1118          96 :         pypam_object.type_obj = &pypamtest_test_case;
    1119          96 :         if (PyType_Ready(pypam_object.type_obj) < 0) {
    1120           0 :                 RETURN_ON_ERROR;
    1121             :         }
    1122          96 :         Py_INCREF(pypam_object.obj);
    1123          96 :         PyModule_AddObject(m, "TestCase", pypam_object.obj);
    1124             : 
    1125          96 :         pypam_object.type_obj = &pypamtest_test_result;
    1126          96 :         if (PyType_Ready(pypam_object.type_obj) < 0) {
    1127           0 :                 RETURN_ON_ERROR;
    1128             :         }
    1129          96 :         Py_INCREF(pypam_object.obj);
    1130          96 :         PyModule_AddObject(m, "TestResult", pypam_object.obj);
    1131             : 
    1132             : #if IS_PYTHON3
    1133          96 :         return m;
    1134             : #endif
    1135             : }

Generated by: LCOV version 1.13