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