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 : }
|