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