pam_wrapper-1.1.4/README.install000644 001750 000144 00000003556 12632316172 016353 0ustar00asnusers000000 000000 Obtaining the sources ===================== Source tarballs for pam_wrapper can be downloaded from https://ftp.samba.org/pub/cwrap/ The source code repository for socket wrapper is located under git://git.samba.org/pam_wrapper.git To create a local copy, run $ git clone git://git.samba.org/pam_wrapper.git $ cd pam_wrapper Building from sources ===================== pam_wrapper uses cmake (www.cmake.org) as its build system. In an unpacked sources base directory, create a directory to contain the build results, e.g. $ mkdir obj $ cd obj Note that "obj" is just an example. The directory can be named arbitrarily. Next, run cmake to configure the build, e.g. $ cmake -DCMAKE_INSTALL_PREFIX= .. or on a 64 bit red hat system: $ cmake -DCMAKE_INSTALL_PREFIX= -DLIB_SUFFIX=64 .. The "" should be replaced by the intended installation target prefix directory, typically /usr or /usr/local. Note that the target directory does not have to be a direct or indirect subdirectory of the source base directory: It can be an arbitrary directory in the system. In the general case, ".." has to be replaced by a relative or absolute path of the source base directory in the "cmake" command line. One can control the build type with "-DCMAKE_BUILD_TYPE=" where can be one of Debug, Release, RelWithDebInfo, and some more (see cmake.org). The default is "RelWithDebInfo". After configuring with cmake, run the build with $ make Unit testing ============ In order to support running the test suite after building, the cmocka unit test framework needs to be installed (cmocka.org), and you need to specify -DUNIT_TESTING=ON in the cmake run. After running "make", $ make test runs the test suite. Installing ========== pam_wrapper is installed into the prefix directory after running "cmake" and "make" with $ make install pam_wrapper-1.1.4/src/000755 001750 000144 00000000000 14136460614 014606 5ustar00asnusers000000 000000 pam_wrapper-1.1.4/src/CMakeLists.txt000644 001750 000144 00000005021 13635146323 017345 0ustar00asnusers000000 000000 project(libpam_wrapper C) ########################################################### ### pam_wrapper ########################################################### add_library(pam_wrapper SHARED pam_wrapper.c) target_include_directories(pam_wrapper PRIVATE ${pam_wrapper-headers_SOURCE_DIR} ${pam_wrapper_BINARY_DIR}) set(PAM_WRAPPER_LIBRARIES ${PAMWRAP_REQUIRED_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) if (HAVE_OPENPAM) list(APPEND PAM_WRAPPER_LIBRARIES pam) endif (HAVE_OPENPAM) target_link_libraries(pam_wrapper ${PAM_WRAPPER_LIBRARIES} ${DLFCN_LIBRARY}) target_compile_options(pam_wrapper PRIVATE ${DEFAULT_C_COMPILE_FLAGS} -D_GNU_SOURCE=1) set_target_properties( pam_wrapper PROPERTIES VERSION ${LIBRARY_VERSION} SOVERSION ${LIBRARY_SOVERSION} ) install(TARGETS pam_wrapper RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) ########################################################### ### libpamtest ########################################################### set(pamtest_SOURCES libpamtest.c ) set(PAM_LIBRARIES pam) if (HAVE_PAM_MISC) list(APPEND PAM_LIBRARIES pam_misc) endif (HAVE_PAM_MISC) add_library(pamtest SHARED ${pamtest_SOURCES}) target_include_directories(pamtest PRIVATE ${pam_wrapper_BINARY_DIR} PUBLIC $ $) add_library(pamtest::pamtest ALIAS pamtest) target_link_libraries(pamtest ${PAM_LIBRARIES}) target_compile_options(pamtest PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) set_target_properties(pamtest PROPERTIES VERSION ${LIBRARY_VERSION} SOVERSION ${LIBRARY_SOVERSION}) install(TARGETS pamtest EXPORT pamtest-config RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) install(EXPORT pamtest-config NAMESPACE pamtest:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/pamtest) add_subdirectory(modules) add_subdirectory(python) # This needs to be at the end set(PAM_WRAPPER_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}pam_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}" PARENT_SCOPE) pam_wrapper-1.1.4/src/python/000755 001750 000144 00000000000 14136460614 016127 5ustar00asnusers000000 000000 pam_wrapper-1.1.4/src/python/CMakeLists.txt000644 001750 000144 00000000460 14136460515 020667 0ustar00asnusers000000 000000 project(pypamtest C) add_subdirectory(python2) add_subdirectory(python3) if (WITH_WNO_CAST_FUNCTION_TYPE) set_source_files_properties(pypamtest.c DIRECTORY python2 python3 PROPERTIES COMPILE_OPTIONS "-Wno-cast-function-type") endif() pam_wrapper-1.1.4/src/python/pypamtest.c000644 001750 000144 00000070415 14065302605 020324 0ustar00asnusers000000 000000 /* * Copyright (c) 2015 Andreas Schneider * Copyright (c) 2015 Jakub Hrozek * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "libpamtest.h" #define PYTHON_MODULE_NAME "pypamtest" #ifndef discard_const_p #if defined(__intptr_t_defined) || defined(HAVE_UINTPTR_T) # define discard_const_p(type, ptr) ((type *)((uintptr_t)(ptr))) #else # define discard_const_p(type, ptr) ((type *)(ptr)) #endif #endif #define __unused __attribute__((__unused__)) #if PY_MAJOR_VERSION >= 3 #define IS_PYTHON3 1 #define RETURN_ON_ERROR return NULL #else #define IS_PYTHON3 0 #define RETURN_ON_ERROR return #endif /* PY_MAJOR_VERSION */ /* We only return up to 16 messages from the PAM conversation */ #define PAM_CONV_MSG_MAX 16 #if IS_PYTHON3 PyMODINIT_FUNC PyInit_pypamtest(void); #else PyMODINIT_FUNC initpypamtest(void); #endif typedef struct { PyObject_HEAD enum pamtest_ops pam_operation; int expected_rv; int flags; PyObject *pam_handle; PyObject *pam_env; } TestCaseObject; #define PyTestCase_AsTestCaseObject(py_obj) \ (TestCaseObject *)(py_obj) /********************************************************** *** module-specific exceptions **********************************************************/ static PyObject *PyExc_PamTestError; /********************************************************** *** helper functions **********************************************************/ #define REPR_FMT "{ pam_operation [%d] " \ "expected_rv [%d] " \ "flags [%d] }" static char *py_strdup(const char *string) { char *copy; copy = PyMem_New(char, strlen(string) + 1); if (copy == NULL) { PyErr_NoMemory(); return NULL; } return strcpy(copy, string); } static PyObject *get_utf8_string(PyObject *obj, const char *attrname) { const char *a = attrname ? attrname : "attribute"; PyObject *obj_utf8 = NULL; if (PyBytes_Check(obj)) { obj_utf8 = obj; Py_INCREF(obj_utf8); /* Make sure we can DECREF later */ } else if (PyUnicode_Check(obj)) { if ((obj_utf8 = PyUnicode_AsUTF8String(obj)) == NULL) { return NULL; } } else { PyErr_Format(PyExc_TypeError, "%s must be a string", a); return NULL; } return obj_utf8; } static void free_cstring_list(const char **list) { int i; if (list == NULL) { return; } for (i=0; list[i]; i++) { PyMem_Free(discard_const_p(char, list[i])); } PyMem_Free(list); } static void free_string_list(char **list) { int i; if (list == NULL) { return; } for (i=0; list[i]; i++) { PyMem_Free(list[i]); } PyMem_Free(list); } static char **new_conv_list(const size_t list_size) { char **list; size_t i; if (list_size == 0) { return NULL; } if (list_size + 1 < list_size) { return NULL; } list = PyMem_New(char *, list_size + 1); if (list == NULL) { return NULL; } list[list_size] = NULL; for (i = 0; i < list_size; i++) { list[i] = PyMem_New(char, PAM_MAX_MSG_SIZE); if (list[i] == NULL) { PyMem_Free(list); return NULL; } memset(list[i], 0, PAM_MAX_MSG_SIZE); } return list; } static int sequence_as_string_list(PyObject *seq, const char *paramname, const char **str_list[], size_t *num_str_list) { const char *p = paramname ? paramname : "attribute values"; const char **result; PyObject *utf_item; int i; Py_ssize_t len; PyObject *item; if (!PySequence_Check(seq)) { PyErr_Format(PyExc_TypeError, "The object must be a sequence\n"); return -1; } len = PySequence_Size(seq); if (len == -1) { return -1; } result = PyMem_New(const char *, (len + 1)); if (result == NULL) { PyErr_NoMemory(); return -1; } for (i = 0; i < len; i++) { item = PySequence_GetItem(seq, i); if (item == NULL) { break; } utf_item = get_utf8_string(item, p); if (utf_item == NULL) { Py_DECREF(item); return -1; } result[i] = py_strdup(PyBytes_AsString(utf_item)); Py_DECREF(utf_item); if (result[i] == NULL) { Py_DECREF(item); return -1; } Py_DECREF(item); } result[i] = NULL; *str_list = result; *num_str_list = (size_t)len; return 0; } static PyObject *string_list_as_tuple(char **str_list) { int rc; size_t len, i; PyObject *tup; PyObject *py_str; for (len=0; str_list[len] != NULL; len++) { if (str_list[len][0] == '\0') { /* unused string, stop counting */ break; } } tup = PyTuple_New(len); if (tup == NULL) { PyErr_NoMemory(); return NULL; } for (i = 0; i < len; i++) { py_str = PyUnicode_FromString(str_list[i]); if (py_str == NULL) { Py_DECREF(tup); PyErr_NoMemory(); return NULL; } /* PyTuple_SetItem() steals the reference to * py_str, so it's enough to decref the tuple * pointer afterwards */ rc = PyTuple_SetItem(tup, i, py_str); if (rc != 0) { /* cleanup */ Py_DECREF(py_str); Py_DECREF(tup); PyErr_NoMemory(); return NULL; } } return tup; } static void set_pypamtest_exception(PyObject *exc, enum pamtest_err perr, struct pam_testcase *tests, size_t num_tests) { PyObject *obj = NULL; /* REPR_FMT contains just %d expansions, so this is safe */ char test_repr[256] = { '\0' }; union { char *str; PyObject *obj; } pypam_str_object; const char *strerr; const struct pam_testcase *failed = NULL; if (exc == NULL) { PyErr_BadArgument(); return; } strerr = pamtest_strerror(perr); if (perr == PAMTEST_ERR_CASE) { failed = _pamtest_failed_case(tests, num_tests); if (failed) { snprintf(test_repr, sizeof(test_repr), REPR_FMT, failed->pam_operation, failed->expected_rv, failed->flags); } } if (test_repr[0] != '\0' && failed != NULL) { PyErr_Format(exc, "Error [%d]: Test case %s returned [%d]", perr, test_repr, failed->op_rv); } else { obj = Py_BuildValue(discard_const_p(char, "(i,s)"), perr, strerr ? strerr : "Unknown error"); PyErr_SetObject(exc, obj); } pypam_str_object.str = test_repr; Py_XDECREF(pypam_str_object.obj); Py_XDECREF(obj); } /* Returned when doc(test_case) is invoked */ PyDoc_STRVAR(TestCaseObject__doc__, "pamtest test case\n\n" "Represents one operation in PAM transaction. An example is authentication, " "opening a session or password change. Each operation has an expected error " "code. The run_pamtest() function accepts a list of these test case objects\n" "Params:\n\n" "pam_operation: - the PAM operation to run. Use constants from pypamtest " "such as pypamtest.PAMTEST_AUTHENTICATE. This argument is required.\n" "expected_rv: - The PAM return value we expect the operation to return. " "Defaults to 0 (PAM_SUCCESS)\n" "flags: - Additional flags to pass to the PAM operation. Defaults to 0.\n" ); static PyObject * TestCase_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { TestCaseObject *self; (void) args; /* unused */ (void) kwds; /* unused */ self = (TestCaseObject *)type->tp_alloc(type, 0); if (self == NULL) { PyErr_NoMemory(); return NULL; } return (PyObject *) self; } /* The traverse and clear methods must be defined even though they do nothing * otherwise Garbage Collector is not happy */ static int TestCase_clear(TestCaseObject *self) { (void) self; /* unused */ return 0; } static void TestCase_dealloc(TestCaseObject *self) { Py_TYPE(self)->tp_free((PyObject *)self); } static int TestCase_traverse(TestCaseObject *self, visitproc visit, void *arg) { (void) self; /* unused */ (void) visit; /* unused */ (void) arg; /* unused */ return 0; } static int TestCase_init(TestCaseObject *self, PyObject *args, PyObject *kwargs) { const char * const kwlist[] = { "pam_operation", "expected_rv", "flags", NULL }; int pam_operation = -1; int expected_rv = PAM_SUCCESS; int flags = 0; int ok; ok = PyArg_ParseTupleAndKeywords(args, kwargs, "i|ii", discard_const_p(char *, kwlist), &pam_operation, &expected_rv, &flags); if (!ok) { return -1; } switch (pam_operation) { case PAMTEST_AUTHENTICATE: case PAMTEST_SETCRED: case PAMTEST_ACCOUNT: case PAMTEST_OPEN_SESSION: case PAMTEST_CLOSE_SESSION: case PAMTEST_CHAUTHTOK: case PAMTEST_GETENVLIST: case PAMTEST_KEEPHANDLE: break; default: PyErr_Format(PyExc_ValueError, "Unsupported PAM operation %d", pam_operation); return -1; } self->flags = flags; self->expected_rv = expected_rv; self->pam_operation = pam_operation; return 0; } /* * This function returns string representation of the object, but one that * can be parsed by a machine. * * str() is also string represtentation, but just human-readable. */ static PyObject *TestCase_repr(TestCaseObject *self) { return PyUnicode_FromFormat(REPR_FMT, self->pam_operation, self->expected_rv, self->flags); } static PyMemberDef pypamtest_test_case_members[] = { { discard_const_p(char, "pam_operation"), T_INT, offsetof(TestCaseObject, pam_operation), READONLY, discard_const_p(char, "The PAM operation to run"), }, { discard_const_p(char, "expected_rv"), T_INT, offsetof(TestCaseObject, expected_rv), READONLY, discard_const_p(char, "The expected PAM return code"), }, { discard_const_p(char, "flags"), T_INT, offsetof(TestCaseObject, flags), READONLY, discard_const_p(char, "Additional flags for the PAM operation"), }, { discard_const_p(char, "pam_handle"), T_OBJECT_EX, offsetof(TestCaseObject, pam_handle), READONLY, discard_const_p(char, "Pam handle"), }, { discard_const_p(char, "pam_env"), T_OBJECT_EX, offsetof(TestCaseObject, pam_env), READONLY, discard_const_p(char, "Pam env"), }, { NULL, 0, 0, 0, NULL } /* Sentinel */ }; static PyTypeObject pypamtest_test_case = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "pypamtest.TestCase", .tp_basicsize = sizeof(TestCaseObject), .tp_new = TestCase_new, .tp_dealloc = (destructor) TestCase_dealloc, .tp_traverse = (traverseproc) TestCase_traverse, .tp_clear = (inquiry) TestCase_clear, .tp_init = (initproc) TestCase_init, .tp_repr = (reprfunc) TestCase_repr, .tp_members = pypamtest_test_case_members, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, .tp_doc = TestCaseObject__doc__ }; PyDoc_STRVAR(TestResultObject__doc__, "pamtest test result\n\n" "The test result object is returned from run_pamtest on success. It contains" "two lists of strings (up to 16 strings each) which contain the info and error" "messages the PAM conversation printed\n\n" "Attributes:\n" "errors: PAM_ERROR_MSG-level messages printed during the PAM conversation\n" "info: PAM_TEXT_INFO-level messages printed during the PAM conversation\n" ); typedef struct { PyObject_HEAD PyObject *info_msg_list; PyObject *error_msg_list; } TestResultObject; static PyObject * TestResult_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { TestResultObject *self; (void) args; /* unused */ (void) kwds; /* unused */ self = (TestResultObject *)type->tp_alloc(type, 0); if (self == NULL) { PyErr_NoMemory(); return NULL; } return (PyObject *) self; } static int TestResult_clear(TestResultObject *self) { (void) self; /* unused */ return 0; } static void TestResult_dealloc(TestResultObject *self) { Py_TYPE(self)->tp_free((PyObject *)self); } static int TestResult_traverse(TestResultObject *self, visitproc visit, void *arg) { (void) self; /* unused */ (void) visit; /* unused */ (void) arg; /* unused */ return 0; } static int TestResult_init(TestResultObject *self, PyObject *args, PyObject *kwargs) { const char * const kwlist[] = { "info_msg_list", "error_msg_list", NULL }; int ok; PyObject *py_info_list = NULL; PyObject *py_err_list = NULL; ok = PyArg_ParseTupleAndKeywords(args, kwargs, "|OO", discard_const_p(char *, kwlist), &py_info_list, &py_err_list); if (!ok) { return -1; } if (py_info_list) { ok = PySequence_Check(py_info_list); if (!ok) { PyErr_Format(PyExc_TypeError, "List of info messages must be a sequence\n"); return -1; } self->info_msg_list = py_info_list; Py_XINCREF(py_info_list); } else { self->info_msg_list = PyList_New(0); if (self->info_msg_list == NULL) { PyErr_NoMemory(); return -1; } } if (py_err_list) { ok = PySequence_Check(py_err_list); if (!ok) { PyErr_Format(PyExc_TypeError, "List of error messages must be a sequence\n"); return -1; } self->error_msg_list = py_err_list; Py_XINCREF(py_err_list); } else { self->error_msg_list = PyList_New(0); if (self->error_msg_list == NULL) { PyErr_NoMemory(); return -1; } } return 0; } static PyObject *test_result_list_concat(PyObject *list, const char delim_pre, const char delim_post) { PyObject *res; PyObject *item; Py_ssize_t size; Py_ssize_t i; res = PyUnicode_FromString(""); if (res == NULL) { return NULL; } size = PySequence_Size(list); for (i=0; i < size; i++) { item = PySequence_GetItem(list, i); if (item == NULL) { PyMem_Free(res); return NULL; } #if IS_PYTHON3 res = PyUnicode_FromFormat("%U%c%U%c", res, delim_pre, item, delim_post); #else res = PyUnicode_FromFormat("%U%c%s%c", res, delim_pre, PyString_AsString(item), delim_post); #endif Py_XDECREF(item); } return res; } static PyObject *TestResult_repr(TestResultObject *self) { PyObject *u_info = NULL; PyObject *u_error = NULL; PyObject *res = NULL; u_info = test_result_list_concat(self->info_msg_list, '{', '}'); u_error = test_result_list_concat(self->info_msg_list, '{', '}'); if (u_info == NULL || u_error == NULL) { Py_XDECREF(u_error); Py_XDECREF(u_info); return NULL; } res = PyUnicode_FromFormat("{ errors: { %U } infos: { %U } }", u_info, u_error); Py_DECREF(u_error); Py_DECREF(u_info); return res; } static PyMemberDef pypamtest_test_result_members[] = { { discard_const_p(char, "errors"), T_OBJECT_EX, offsetof(TestResultObject, error_msg_list), READONLY, discard_const_p(char, "List of error messages from PAM conversation"), }, { discard_const_p(char, "info"), T_OBJECT_EX, offsetof(TestResultObject, info_msg_list), READONLY, discard_const_p(char, "List of info messages from PAM conversation"), }, { NULL, 0, 0, 0, NULL } /* Sentinel */ }; static PyTypeObject pypamtest_test_result = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "pypamtest.TestResult", .tp_basicsize = sizeof(TestResultObject), .tp_new = TestResult_new, .tp_dealloc = (destructor) TestResult_dealloc, .tp_traverse = (traverseproc) TestResult_traverse, .tp_clear = (inquiry) TestResult_clear, .tp_init = (initproc) TestResult_init, .tp_repr = (reprfunc) TestResult_repr, .tp_members = pypamtest_test_result_members, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, .tp_doc = TestResultObject__doc__ }; /********************************************************** *** Methods of the module **********************************************************/ static TestResultObject *construct_test_conv_result(char **msg_info, char **msg_err) { PyObject *py_msg_info = NULL; PyObject *py_msg_err = NULL; TestResultObject *result = NULL; PyObject *result_args = NULL; int rc; py_msg_info = string_list_as_tuple(msg_info); py_msg_err = string_list_as_tuple(msg_err); if (py_msg_info == NULL || py_msg_err == NULL) { /* The exception is raised in string_list_as_tuple() */ Py_XDECREF(py_msg_err); Py_XDECREF(py_msg_info); return NULL; } result = (TestResultObject *) TestResult_new(&pypamtest_test_result, NULL, NULL); if (result == NULL) { /* The exception is raised in TestResult_new */ Py_XDECREF(py_msg_err); Py_XDECREF(py_msg_info); return NULL; } result_args = PyTuple_New(2); if (result_args == NULL) { /* The exception is raised in TestResult_new */ Py_XDECREF(result); Py_XDECREF(py_msg_err); Py_XDECREF(py_msg_info); return NULL; } /* Brand new tuples with fixed size don't need error checking */ PyTuple_SET_ITEM(result_args, 0, py_msg_info); PyTuple_SET_ITEM(result_args, 1, py_msg_err); rc = TestResult_init(result, result_args, NULL); Py_XDECREF(result_args); if (rc != 0) { Py_XDECREF(result); return NULL; } return result; } static int py_testcase_get(PyObject *py_test, const char *member_name, long *_value) { PyObject* item = NULL; /* * PyPyObject_GetAttrString() increases the refcount on the * returned value. */ item = PyObject_GetAttrString(py_test, member_name); if (item == NULL) { return EINVAL; } *_value = PyLong_AsLong(item); Py_DECREF(item); return 0; } static int py_testcase_to_cstruct(PyObject *py_test, struct pam_testcase *test) { int rc; long value; memset(test, 0, sizeof(struct pam_testcase)); rc = py_testcase_get(py_test, "pam_operation", &value); if (rc != 0) { return rc; } test->pam_operation = value; rc = py_testcase_get(py_test, "expected_rv", &value); if (rc != 0) { return rc; } test->expected_rv = value; rc = py_testcase_get(py_test, "flags", &value); if (rc != 0) { return rc; } test->flags = value; return 0; } static void free_conv_data(struct pamtest_conv_data *conv_data) { if (conv_data == NULL) { return; } free_string_list(conv_data->out_err); free_string_list(conv_data->out_info); free_cstring_list(conv_data->in_echo_on); free_cstring_list(conv_data->in_echo_off); } /* conv_data must be a pointer to allocated conv_data structure. * * Use free_conv_data() to free the contents. */ static int fill_conv_data(PyObject *py_echo_off, PyObject *py_echo_on, struct pamtest_conv_data *conv_data) { size_t conv_count = 0; size_t count = 0; int rc; conv_data->in_echo_on = NULL; conv_data->in_echo_off = NULL; conv_data->out_err = NULL; conv_data->out_info = NULL; if (py_echo_off != NULL) { rc = sequence_as_string_list(py_echo_off, "echo_off", &conv_data->in_echo_off, &count); if (rc != 0) { free_conv_data(conv_data); return ENOMEM; } conv_count += count; } if (py_echo_on != NULL) { rc = sequence_as_string_list(py_echo_on, "echo_on", &conv_data->in_echo_on, &count); if (rc != 0) { free_conv_data(conv_data); return ENOMEM; } conv_count += count; } if (conv_count > PAM_CONV_MSG_MAX) { free_conv_data(conv_data); return ENOMEM; } conv_data->out_info = new_conv_list(PAM_CONV_MSG_MAX); conv_data->out_err = new_conv_list(PAM_CONV_MSG_MAX); if (conv_data->out_info == NULL || conv_data->out_err == NULL) { free_conv_data(conv_data); return ENOMEM; } return 0; } /* test_list is allocated using PyMem_New and must be freed accordingly. * Returns errno that should be handled into exception in the caller */ static int py_tc_list_to_cstruct_list(PyObject *py_test_list, Py_ssize_t num_tests, struct pam_testcase **_test_list) { Py_ssize_t i; PyObject *py_test; int rc; struct pam_testcase *test_list; test_list = PyMem_New(struct pam_testcase, num_tests * sizeof(struct pam_testcase)); if (test_list == NULL) { return ENOMEM; } for (i = 0; i < num_tests; i++) { /* * PySequence_GetItem() increases the refcount on the * returned value */ py_test = PySequence_GetItem(py_test_list, i); if (py_test == NULL) { PyMem_Free(test_list); return EIO; } rc = py_testcase_to_cstruct(py_test, &test_list[i]); Py_DECREF(py_test); if (rc != 0) { PyMem_Free(test_list); return EIO; } } *_test_list = test_list; return 0; } static int cstruct_to_py_testcase(PyObject *pytest, struct pam_testcase *ctest) { TestCaseObject *t = PyTestCase_AsTestCaseObject(pytest); size_t i; int rc; switch (t->pam_operation) { case PAMTEST_GETENVLIST: if (ctest->case_out.envlist == NULL) { break; } t->pam_env = PyDict_New(); if (t->pam_env == NULL) { return ENOMEM; } for (i = 0; ctest->case_out.envlist[i] != NULL; i++) { char *key = NULL; char *val = NULL; key = strdup(ctest->case_out.envlist[i]); if (key == NULL) { return ENOMEM; } val = strrchr(key, '='); if (val == NULL) { PyErr_Format(PyExc_IOError, "Failed to parse PAM environment " "variable"); free(key); return EINVAL; } *val = '\0'; rc = PyDict_SetItem(t->pam_env, PyUnicode_FromString(key), PyUnicode_FromString(val + 1)); free(key); if (rc == -1) { return rc; } } break; case PAMTEST_KEEPHANDLE: t->pam_handle = PyCapsule_New(ctest->case_out.ph, NULL, NULL); if (t->pam_handle == NULL) { return ENOMEM; } break; default: break; } return 0; } static int cstruct_list_to_py_tc_list(PyObject *py_test_list, Py_ssize_t num_tests, struct pam_testcase *test_list) { Py_ssize_t i; PyObject *py_test = NULL; int rc; for (i = 0; i < num_tests; i++) { py_test = PySequence_GetItem(py_test_list, i); if (py_test == NULL) { return EIO; } rc = cstruct_to_py_testcase(py_test, &test_list[i]); Py_DECREF(py_test); if (rc != 0) { return EIO; } } return 0; } PyDoc_STRVAR(RunPamTest__doc__, "Run PAM tests\n\n" "This function runs PAM test cases and reports result\n" "Parameters:\n" "service: The PAM service to use in the conversation (string)\n" "username: The user to run PAM conversation as\n" "test_list: Sequence of pypamtest.TestCase objects\n" "echo_off_list: Sequence of strings that will be used by PAM " "conversation for PAM_PROMPT_ECHO_OFF input. These are typically " "passwords.\n" "echo_on_list: Sequence of strings that will be used by PAM " "conversation for PAM_PROMPT_ECHO_ON input.\n" ); static PyObject *pypamtest_run_pamtest(PyObject *module, PyObject *args, PyObject *kwargs) { int ok; int rc; char *username = NULL; char *service = NULL; PyObject *py_test_list; PyObject *py_echo_off = NULL; PyObject *py_echo_on = NULL; PyObject *py_pam_handle = NULL; Py_ssize_t num_tests; struct pam_testcase *test_list; enum pamtest_err perr; struct pamtest_conv_data conv_data; pam_handle_t *pam_handle = NULL; TestResultObject *result = NULL; const char * const kwnames[] = { "username", "service", "tests", "echo_off", "echo_on", "handle", NULL }; (void) module; /* unused */ ok = PyArg_ParseTupleAndKeywords(args, kwargs, discard_const_p(char, "ssO|OOO"), discard_const_p(char *, kwnames), &username, &service, &py_test_list, &py_echo_off, &py_echo_on, &py_pam_handle); if (!ok) { return NULL; } ok = PySequence_Check(py_test_list); if (!ok) { PyErr_Format(PyExc_TypeError, "tests must be a sequence"); return NULL; } num_tests = PySequence_Size(py_test_list); if (num_tests == -1) { PyErr_Format(PyExc_IOError, "Cannot get sequence length"); return NULL; } rc = py_tc_list_to_cstruct_list(py_test_list, num_tests, &test_list); if (rc != 0) { if (rc == ENOMEM) { PyErr_NoMemory(); return NULL; } else { PyErr_Format(PyExc_IOError, "Cannot convert test to C structure"); return NULL; } } rc = fill_conv_data(py_echo_off, py_echo_on, &conv_data); if (rc != 0) { PyMem_Free(test_list); PyErr_NoMemory(); return NULL; } if (py_pam_handle != NULL) { pam_handle = (pam_handle_t *)PyCapsule_GetPointer(py_pam_handle, NULL); if (pam_handle == NULL) { PyMem_Free(test_list); PyErr_Format(PyExc_IOError, "Failed to get the pam handle pointer"); return NULL; } } perr = _pamtest(service, username, &conv_data, test_list, num_tests, pam_handle); if (perr != PAMTEST_ERR_OK) { free_conv_data(&conv_data); set_pypamtest_exception(PyExc_PamTestError, perr, test_list, num_tests); PyMem_Free(test_list); return NULL; } rc = cstruct_list_to_py_tc_list(py_test_list, num_tests, test_list); if (rc != 0) { if (rc == ENOMEM) { PyErr_NoMemory(); return NULL; } else { PyErr_Format(PyExc_IOError, "Cannot convert C structure to python"); return NULL; } } PyMem_Free(test_list); result = construct_test_conv_result(conv_data.out_info, conv_data.out_err); free_conv_data(&conv_data); if (result == NULL) { PyMem_Free(test_list); return NULL; } return (PyObject *)result; } static PyMethodDef pypamtest_module_methods[] = { { discard_const_p(char, "run_pamtest"), (PyCFunction) pypamtest_run_pamtest, METH_VARARGS | METH_KEYWORDS, RunPamTest__doc__, }, { NULL, NULL, 0, NULL } /* Sentinel */ }; /* * This is the module structure describing the module and * to define methods */ #if IS_PYTHON3 static struct PyModuleDef pypamtestdef = { .m_base = PyModuleDef_HEAD_INIT, .m_name = PYTHON_MODULE_NAME, .m_size = -1, .m_methods = pypamtest_module_methods, }; #endif /********************************************************** *** Initialize the module **********************************************************/ #if PY_VERSION_HEX >= 0x02070000 /* >= 2.7.0 */ PyDoc_STRVAR(PamTestError__doc__, "pypamtest specific exception\n\n" "This exception is raised if the _pamtest() function fails. If _pamtest() " "returns PAMTEST_ERR_CASE (a test case returns unexpected error code), then " "the exception also details which test case failed." ); #endif #if IS_PYTHON3 PyMODINIT_FUNC PyInit_pypamtest(void) #else PyMODINIT_FUNC initpypamtest(void) #endif { PyObject *m; union { PyTypeObject *type_obj; PyObject *obj; } pypam_object; int ret; #if IS_PYTHON3 m = PyModule_Create(&pypamtestdef); if (m == NULL) { RETURN_ON_ERROR; } #else m = Py_InitModule(discard_const_p(char, PYTHON_MODULE_NAME), pypamtest_module_methods); #endif #if PY_VERSION_HEX >= 0x02070000 /* >= 2.7.0 */ PyExc_PamTestError = PyErr_NewExceptionWithDoc(discard_const_p(char, "pypamtest.PamTestError"), PamTestError__doc__, PyExc_EnvironmentError, NULL); #else /* < 2.7.0 */ PyExc_PamTestError = PyErr_NewException(discard_const_p(char, "pypamtest.PamTestError"), PyExc_EnvironmentError, NULL); #endif if (PyExc_PamTestError == NULL) { RETURN_ON_ERROR; } Py_INCREF(PyExc_PamTestError); ret = PyModule_AddObject(m, discard_const_p(char, "PamTestError"), PyExc_PamTestError); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntMacro(m, PAMTEST_AUTHENTICATE); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntMacro(m, PAMTEST_SETCRED); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntMacro(m, PAMTEST_ACCOUNT); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntMacro(m, PAMTEST_OPEN_SESSION); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntMacro(m, PAMTEST_CLOSE_SESSION); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntMacro(m, PAMTEST_CHAUTHTOK); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntMacro(m, PAMTEST_GETENVLIST); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntMacro(m, PAMTEST_KEEPHANDLE); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntConstant(m, "PAMTEST_FLAG_DELETE_CRED", PAM_DELETE_CRED); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntConstant(m, "PAMTEST_FLAG_ESTABLISH_CRED", PAM_ESTABLISH_CRED); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntConstant(m, "PAMTEST_FLAG_REINITIALIZE_CRED", PAM_REINITIALIZE_CRED); if (ret == -1) { RETURN_ON_ERROR; } ret = PyModule_AddIntConstant(m, "PAMTEST_FLAG_REFRESH_CRED", PAM_REFRESH_CRED); if (ret == -1) { RETURN_ON_ERROR; } pypam_object.type_obj = &pypamtest_test_case; if (PyType_Ready(pypam_object.type_obj) < 0) { RETURN_ON_ERROR; } Py_INCREF(pypam_object.obj); PyModule_AddObject(m, "TestCase", pypam_object.obj); pypam_object.type_obj = &pypamtest_test_result; if (PyType_Ready(pypam_object.type_obj) < 0) { RETURN_ON_ERROR; } Py_INCREF(pypam_object.obj); PyModule_AddObject(m, "TestResult", pypam_object.obj); #if IS_PYTHON3 return m; #endif } pam_wrapper-1.1.4/src/python/python2/000755 001750 000144 00000000000 14136460614 017532 5ustar00asnusers000000 000000 pam_wrapper-1.1.4/src/python/python2/CMakeLists.txt000644 001750 000144 00000003026 13635146323 022274 0ustar00asnusers000000 000000 project(python2-pamtest C) if (NOT PYTHON2_LIBRARY) unset(PYTHON_EXECUTABLE CACHE) unset(PYTHON_INCLUDE_DIR CACHE) unset(PYTHON_LIBRARY CACHE) unset(PYTHON_SITELIB CACHE) unset(PYTHONLIBS_FOUND CACHE) unset(PYTHONLIBS_VERSION_STRING CACHE) find_package(PythonLibs 2) find_package(PythonInterp 2) find_package(PythonSiteLibs 2) if (PYTHONLIBS_FOUND) set(PYTHON2_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "PYTHON2_EXECUTABLE") set(PYTHON2_INCLUDE_DIR ${PYTHON_INCLUDE_DIR}) set(PYTHON2_LIBRARY ${PYTHON_LIBRARY}) set(PYTHON2_SITELIB ${PYTHON_SITELIB}) endif() unset(PYTHON_EXECUTABLE CACHE) unset(PYTHON_INCLUDE_DIR CACHE) unset(PYTHON_LIBRARY CACHE) unset(PYTHON_SITELIB CACHE) unset(PYTHONLIBS_FOUND CACHE) unset(PYTHONLIBS_VERSION_STRING CACHE) endif() if (PYTHON2_INCLUDE_DIR AND PYTHON2_LIBRARY AND PYTHON2_SITELIB) include_directories(${CMAKE_BINARY_DIR}) include_directories(${pam_wrapper-headers_DIR}) include_directories(${PYTHON2_INCLUDE_DIR}) python_add_module(python2-pamtest ${pypamtest_SOURCE_DIR}/pypamtest.c) target_link_libraries(python2-pamtest pamtest::pamtest ${PYTHON2_LIBRARY}) target_compile_options(python2-pamtest PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) set_target_properties(python2-pamtest PROPERTIES OUTPUT_NAME "pypamtest") install(TARGETS python2-pamtest DESTINATION ${CMAKE_INSTALL_PREFIX}/${PYTHON2_SITELIB}) endif() pam_wrapper-1.1.4/src/python/python3/000755 001750 000144 00000000000 14136460614 017533 5ustar00asnusers000000 000000 pam_wrapper-1.1.4/src/python/python3/CMakeLists.txt000644 001750 000144 00000003026 13635146323 022275 0ustar00asnusers000000 000000 project(python3-pamtest C) if (NOT PYTHON3_LIBRARY) unset(PYTHON_EXECUTABLE CACHE) unset(PYTHON_INCLUDE_DIR CACHE) unset(PYTHON_LIBRARY CACHE) unset(PYTHON_SITELIB CACHE) unset(PYTHONLIBS_FOUND CACHE) unset(PYTHONLIBS_VERSION_STRING CACHE) find_package(PythonLibs 3) find_package(PythonInterp 3) find_package(PythonSiteLibs 3) if (PYTHONLIBS_FOUND) set(PYTHON3_LIBRARY ${PYTHON_LIBRARY}) set(PYTHON3_INCLUDE_DIR ${PYTHON_INCLUDE_DIR}) set(PYTHON3_SITELIB ${PYTHON_SITELIB}) set(PYTHON3_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "PYTHON3_EXECUTABLE") endif() unset(PYTHON_EXECUTABLE CACHE) unset(PYTHON_INCLUDE_DIR CACHE) unset(PYTHON_LIBRARY CACHE) unset(PYTHON_SITELIB CACHE) unset(PYTHONLIBS_FOUND CACHE) unset(PYTHONLIBS_VERSION_STRING CACHE) endif() if (PYTHON3_INCLUDE_DIR AND PYTHON3_LIBRARY AND PYTHON3_SITELIB) include_directories(${CMAKE_BINARY_DIR}) include_directories(${pam_wrapper-headers_DIR}) include_directories(${PYTHON3_INCLUDE_DIR}) python_add_module(python3-pamtest ${pypamtest_SOURCE_DIR}/pypamtest.c) target_link_libraries(python3-pamtest pamtest::pamtest ${PYTHON3_LIBRARY}) target_compile_options(python3-pamtest PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) set_target_properties(python3-pamtest PROPERTIES OUTPUT_NAME "pypamtest") install(TARGETS python3-pamtest DESTINATION ${CMAKE_INSTALL_PREFIX}/${PYTHON3_SITELIB}) endif() pam_wrapper-1.1.4/src/libpamtest.c000644 001750 000144 00000016330 14136455653 017130 0ustar00asnusers000000 000000 /* * Copyright (c) 2015 Andreas Schneider * Copyright (c) 2015 Jakub Hrozek * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "libpamtest.h" #define MIN(a,b) ((a) < (b) ? (a) : (b)) static enum pamtest_err run_test_case(pam_handle_t *ph, struct pam_testcase *tc) { switch (tc->pam_operation) { case PAMTEST_AUTHENTICATE: tc->op_rv = pam_authenticate(ph, tc->flags); return PAMTEST_ERR_OK; case PAMTEST_SETCRED: tc->op_rv = pam_setcred(ph, tc->flags); return PAMTEST_ERR_OK; case PAMTEST_ACCOUNT: tc->op_rv = pam_acct_mgmt(ph, tc->flags); return PAMTEST_ERR_OK; case PAMTEST_OPEN_SESSION: tc->op_rv = pam_open_session(ph, tc->flags); return PAMTEST_ERR_OK; case PAMTEST_CLOSE_SESSION: tc->op_rv = pam_close_session(ph, tc->flags); return PAMTEST_ERR_OK; case PAMTEST_CHAUTHTOK: tc->op_rv = pam_chauthtok(ph, tc->flags); return PAMTEST_ERR_OK; case PAMTEST_GETENVLIST: tc->case_out.envlist = pam_getenvlist(ph); return PAMTEST_ERR_OK; case PAMTEST_KEEPHANDLE: tc->case_out.ph = ph; return PAMTEST_ERR_KEEPHANDLE; default: return PAMTEST_ERR_OP; } return PAMTEST_ERR_OP; } enum pamtest_err _pamtest_conv(const char *service, const char *user, pam_conv_fn conv_fn, void *conv_userdata, struct pam_testcase test_cases[], size_t num_test_cases, pam_handle_t *pam_handle) { int rv; pam_handle_t *ph; struct pam_conv conv; size_t tcindex; struct pam_testcase *tc = NULL; bool call_pam_end = true; conv.conv = conv_fn; conv.appdata_ptr = conv_userdata; if (test_cases == NULL) { return PAMTEST_ERR_INTERNAL; } if (pam_handle == NULL) { rv = pam_start(service, user, &conv, &ph); if (rv != PAM_SUCCESS) { return PAMTEST_ERR_START; } } else { ph = pam_handle; } for (tcindex = 0; tcindex < num_test_cases; tcindex++) { tc = &test_cases[tcindex]; rv = run_test_case(ph, tc); if (rv == PAMTEST_ERR_KEEPHANDLE) { call_pam_end = false; continue; } else if (rv != PAMTEST_ERR_OK) { return PAMTEST_ERR_INTERNAL; } if (tc->op_rv != tc->expected_rv) { break; } } if (call_pam_end == true && tc != NULL) { rv = pam_end(ph, tc->op_rv); if (rv != PAM_SUCCESS) { return PAMTEST_ERR_END; } } if (tcindex < num_test_cases) { return PAMTEST_ERR_CASE; } return PAMTEST_ERR_OK; } void pamtest_free_env(char **envlist) { size_t i; if (envlist == NULL) { return; } for (i = 0; envlist[i] != NULL; i++) { free(envlist[i]); } free(envlist); } const struct pam_testcase * _pamtest_failed_case(struct pam_testcase *test_cases, size_t num_test_cases) { size_t tcindex; for (tcindex = 0; tcindex < num_test_cases; tcindex++) { const struct pam_testcase *tc = &test_cases[tcindex]; if (tc->expected_rv != tc->op_rv) { return tc; } } /* Nothing failed */ return NULL; } const char *pamtest_strerror(enum pamtest_err perr) { switch (perr) { case PAMTEST_ERR_OK: return "Success"; case PAMTEST_ERR_START: return "pam_start failed()"; case PAMTEST_ERR_CASE: return "Unexpected testcase result"; case PAMTEST_ERR_OP: return "Could not run a test case"; case PAMTEST_ERR_END: return "pam_end failed()"; case PAMTEST_ERR_KEEPHANDLE: /* Fallthrough */ case PAMTEST_ERR_INTERNAL: return "Internal libpamtest error"; } return "Unknown"; } struct pamtest_conv_ctx { struct pamtest_conv_data *data; size_t echo_off_idx; size_t echo_on_idx; size_t err_idx; size_t info_idx; }; static int add_to_reply(struct pam_response *reply, const char *str) { size_t len; len = strlen(str) + 1; reply->resp = calloc(len, sizeof(char)); if (reply->resp == NULL) { return PAM_BUF_ERR; } memcpy(reply->resp, str, len); return PAM_SUCCESS; } static void free_reply(struct pam_response *reply, int num_msg) { int i; if (reply == NULL) { return; } for (i = 0; i < num_msg; i++) { free(reply[i].resp); } free(reply); } static int pamtest_simple_conv(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) { int i = 0; int ret; struct pam_response *reply = NULL; const char *prompt; struct pamtest_conv_ctx *cctx = (struct pamtest_conv_ctx *)appdata_ptr; if (cctx == NULL) { return PAM_CONV_ERR; } if (response) { reply = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response)); if (reply == NULL) { return PAM_CONV_ERR; } } for (i=0; i < num_msg; i++) { switch (msgm[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: prompt = (const char *) \ cctx->data->in_echo_off[cctx->echo_off_idx]; if (reply != NULL) { if (prompt != NULL) { ret = add_to_reply(&reply[i], prompt); if (ret != PAM_SUCCESS) { free_reply(reply, num_msg); return ret; } } } cctx->echo_off_idx++; break; case PAM_PROMPT_ECHO_ON: prompt = (const char *) \ cctx->data->in_echo_on[cctx->echo_on_idx]; if (prompt == NULL) { free_reply(reply, num_msg); return PAM_CONV_ERR; } if (reply != NULL) { if (prompt != NULL) { ret = add_to_reply(&reply[i], prompt); if (ret != PAM_SUCCESS) { free_reply(reply, num_msg); return ret; } } } cctx->echo_on_idx++; break; case PAM_ERROR_MSG: if (reply != NULL) { ret = add_to_reply(&reply[i], msgm[i]->msg); if (ret != PAM_SUCCESS) { free_reply(reply, num_msg); return ret; } } if (cctx->data->out_err != NULL) { memcpy(cctx->data->out_err[cctx->err_idx], msgm[i]->msg, MIN(strlen(msgm[i]->msg), PAM_MAX_MSG_SIZE)); cctx->err_idx++; } break; case PAM_TEXT_INFO: if (reply != NULL) { ret = add_to_reply(&reply[i], msgm[i]->msg); if (ret != PAM_SUCCESS) { free_reply(reply, num_msg); return ret; } } if (cctx->data->out_info != NULL) { memcpy(cctx->data->out_info[cctx->info_idx], msgm[i]->msg, MIN(strlen(msgm[i]->msg), PAM_MAX_MSG_SIZE)); cctx->info_idx++; } break; default: continue; } } if (response != NULL) { *response = reply; } else { free(reply); } return PAM_SUCCESS; } enum pamtest_err _pamtest(const char *service, const char *user, struct pamtest_conv_data *conv_data, struct pam_testcase test_cases[], size_t num_test_cases, pam_handle_t *pam_handle) { struct pamtest_conv_ctx cctx = { .data = conv_data, }; return _pamtest_conv(service, user, pamtest_simple_conv, &cctx, test_cases, num_test_cases, pam_handle); } pam_wrapper-1.1.4/src/modules/000755 001750 000144 00000000000 14136460614 016256 5ustar00asnusers000000 000000 pam_wrapper-1.1.4/src/modules/CMakeLists.txt000644 001750 000144 00000001432 13635146323 021017 0ustar00asnusers000000 000000 project(pam_wrapper-modules C) set(PAM_MODULES pam_matrix pam_get_items pam_set_items pam_chatty) set(PAM_LIBRARIES pam) if (HAVE_PAM_MISC) list(APPEND PAM_LIBRARIES pam_misc) endif (HAVE_PAM_MISC) foreach(_PAM_MODULE ${PAM_MODULES}) add_library(${_PAM_MODULE} MODULE ${_PAM_MODULE}.c) target_include_directories(${_PAM_MODULE} PRIVATE ${pam_wrapper-headers_SOURCE_DIR} ${pam_wrapper_BINARY_DIR}) set_property(TARGET ${_PAM_MODULE} PROPERTY PREFIX "") target_link_libraries(${_PAM_MODULE} ${PAM_LIBRARIES}) install( TARGETS ${_PAM_MODULE} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/pam_wrapper ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/pam_wrapper) endforeach() pam_wrapper-1.1.4/src/modules/pam_set_items.c000644 001750 000144 00000012354 13352475320 021257 0ustar00asnusers000000 000000 /* * Copyright (c) 2015 Andreas Schneider * Copyright (c) 2015 Jakub Hrozek * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #ifdef HAVE_SECURITY_PAM_APPL_H #include #endif #ifdef HAVE_SECURITY_PAM_MODULES_H #include #endif #include "config.h" /* GCC have printf type attribute check. */ #ifdef HAVE_FUNCTION_ATTRIBUTE_FORMAT #define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) #else #define PRINTF_ATTRIBUTE(a,b) #endif /* HAVE_FUNCTION_ATTRIBUTE_FORMAT */ /***************** * LOGGING *****************/ enum pwrap_dbglvl_e { PWRAP_LOG_ERROR = 0, PWRAP_LOG_WARN, PWRAP_LOG_DEBUG, PWRAP_LOG_TRACE }; static void pwrap_log(enum pwrap_dbglvl_e dbglvl, const char *function, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); # define PWRAP_LOG(dbglvl, ...) pwrap_log((dbglvl), __func__, __VA_ARGS__) static void pwrap_vlog(enum pwrap_dbglvl_e dbglvl, const char *function, const char *format, va_list args) PRINTF_ATTRIBUTE(3, 0); static void pwrap_vlog(enum pwrap_dbglvl_e dbglvl, const char *function, const char *format, va_list args) { char buffer[1024]; const char *d; unsigned int lvl = 0; const char *prefix = "PWRAP"; d = getenv("PAM_WRAPPER_DEBUGLEVEL"); if (d != NULL) { lvl = atoi(d); } if (lvl < dbglvl) { return; } vsnprintf(buffer, sizeof(buffer), format, args); switch (dbglvl) { case PWRAP_LOG_ERROR: prefix = "PWRAP_ERROR"; break; case PWRAP_LOG_WARN: prefix = "PWRAP_WARN"; break; case PWRAP_LOG_DEBUG: prefix = "PWRAP_DEBUG"; break; case PWRAP_LOG_TRACE: prefix = "PWRAP_TRACE"; break; } fprintf(stderr, "%s(%d) - PAM_SET_ITEM %s: %s\n", prefix, (int)getpid(), function, buffer); } static void pwrap_log(enum pwrap_dbglvl_e dbglvl, const char *function, const char *format, ...) { va_list va; va_start(va, format); pwrap_vlog(dbglvl, function, format, va); va_end(va); } #define ITEM_FILE_KEY "item_file=" static const char *envs[] = { #ifndef HAVE_OPENPAM "PAM_SERVICE", #endif "PAM_USER", "PAM_USER_PROMPT", "PAM_TTY", "PAM_RUSER", "PAM_RHOST", "PAM_AUTHTOK", "PAM_OLDAUTHTOK", #ifdef PAM_XDISPLAY "PAM_XDISPLAY", #endif #ifdef PAM_AUTHTOK_TYPE "PAM_AUTHTOK_TYPE", #endif NULL }; static const int items[] = { #ifndef HAVE_OPENPAM PAM_SERVICE, #endif PAM_USER, PAM_USER_PROMPT, PAM_TTY, PAM_RUSER, PAM_RHOST, PAM_AUTHTOK, PAM_OLDAUTHTOK, #ifdef PAM_XDISPLAY PAM_XDISPLAY, #endif #ifdef PAM_AUTHTOK_TYPE PAM_AUTHTOK_TYPE, #endif }; static void pam_setitem_env(pam_handle_t *pamh) { int i; int rv; const char *v; for (i = 0; envs[i] != NULL; i++) { v = getenv(envs[i]); if (v == NULL) { continue; } PWRAP_LOG(PWRAP_LOG_TRACE, "%s=%s", envs[i], v); rv = pam_set_item(pamh, items[i], v); if (rv != PAM_SUCCESS) { continue; } } } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ PWRAP_LOG(PWRAP_LOG_TRACE, "AUTHENTICATE"); pam_setitem_env(pamh); return PAM_SUCCESS; } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ PWRAP_LOG(PWRAP_LOG_TRACE, "SETCRED"); pam_setitem_env(pamh); return PAM_SUCCESS; } PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ PWRAP_LOG(PWRAP_LOG_TRACE, "ACCT_MGMT"); pam_setitem_env(pamh); return PAM_SUCCESS; } PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ PWRAP_LOG(PWRAP_LOG_TRACE, "OPEN_SESSION"); pam_setitem_env(pamh); return PAM_SUCCESS; } PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ PWRAP_LOG(PWRAP_LOG_TRACE, "CLOSE_SESSION"); pam_setitem_env(pamh); return PAM_SUCCESS; } PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ PWRAP_LOG(PWRAP_LOG_TRACE, "CHAUTHTOK"); pam_setitem_env(pamh); return PAM_SUCCESS; } pam_wrapper-1.1.4/src/modules/pam_matrix.c000644 001750 000144 00000041232 13635146323 020566 0ustar00asnusers000000 000000 /* * Copyright (c) 2015 Andreas Schneider * Copyright (c) 2015 Jakub Hrozek * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef discard_const #define discard_const(ptr) ((void *)((uintptr_t)(ptr))) #endif #ifndef discard_const_p #define discard_const_p(type, ptr) ((type *)discard_const(ptr)) #endif #ifdef HAVE_SECURITY_PAM_APPL_H #include #endif #ifdef HAVE_SECURITY_PAM_MODULES_H #include #endif #ifdef HAVE_SECURITY_PAM_EXT_H #include #endif #include "pwrap_compat.h" #define HOME_VAR "HOMEDIR" #define HOME_VAR_SZ sizeof(HOME_VAR)-1 #define CRED_VAR "CRED" #define CRED_VAR_SZ sizeof(CRED_VAR)-1 #define PAM_EXAMPLE_AUTH_DATA "pam_matrix:auth_data" #define PASSDB_KEY "passdb=" #define VERBOSE_KEY "verbose" #define ECHO_KEY "echo" #define PAM_MATRIX_FLG_VERBOSE (1 << 0) #define PAM_MATRIX_FLG_ECHO (1 << 1) #define MAX_AUTHTOK_SIZE 1024 /* Walks over the key until a colon (:) is find */ #define NEXT_KEY(buf, key) do { \ (key) = (buf) ? strpbrk((buf), ":") : NULL; \ if ((key) != NULL) { \ (key)[0] = '\0'; \ (key)++; \ } \ while ((key) != NULL \ && (isblank((int)(key)[0]))) { \ (key)++; \ } \ } while(0); #define wipe_authtok(tok) do { \ if (tok != NULL) { \ char *__tk = tok; \ while(*__tk != '\0') { \ *__tk = '\0'; \ } \ } \ } while(0); struct pam_lib_items { const char *username; const char *service; char *password; }; struct pam_matrix_mod_items { char *password; char *service; }; struct pam_matrix_ctx { const char *passdb; int flags; struct pam_lib_items pli; struct pam_matrix_mod_items pmi; }; /* Search the passdb for user entry and fill his info into pmi */ static int pam_matrix_mod_items_get(const char *db, const char *username, struct pam_matrix_mod_items *pmi) { int rv; FILE *fp = NULL; char buf[BUFSIZ]; char *file_user = NULL; char *file_password = NULL; char *file_svc = NULL; fp = fopen(db, "r"); if (fp == NULL) { rv = errno; goto fail; } while (fgets(buf, sizeof(buf), fp) != NULL) { char *q; /* Find the user, his password and allowed service */ file_user = buf; file_password = NULL; /* Skip comments */ if (file_user[0] == '#') { continue; } NEXT_KEY(file_user, file_password); NEXT_KEY(file_password, file_svc); q = file_svc; while(q[0] != '\n' && q[0] != '\0') { q++; } q[0] = '\0'; if (file_password == NULL) { continue; } if (strcmp(file_user, username) == 0) { pmi->password = strdup(file_password); if (pmi->password == NULL) { rv = errno; goto fail; } pmi->service = strdup(file_svc); if (pmi->service == NULL) { rv = errno; goto fail; } break; } } fclose(fp); return 0; fail: free(pmi->password); free(pmi->service); if (fp) { fclose(fp); } return rv; } /* Replace authtok of user in the database with the one from pli */ static int pam_matrix_lib_items_put(const char *db, struct pam_lib_items *pli) { int rv; mode_t old_mask; FILE *fp = NULL; FILE *fp_tmp = NULL; char buf[BUFSIZ]; char template[PATH_MAX] = { '\0' }; char *file_user = NULL; char *file_password = NULL; char *file_svc = NULL; rv = snprintf(template, sizeof(template), "%s.XXXXXX", db); if (rv <= 0) { rv = PAM_BUF_ERR; goto done; } /* We don't support concurrent runs.. */ old_mask = umask(S_IRWXO | S_IRWXG); rv = mkstemp(template); umask(old_mask); if (rv <= 0) { rv = PAM_BUF_ERR; goto done; } fp = fopen(db, "r"); fp_tmp = fopen(template, "w"); if (fp == NULL || fp_tmp == NULL) { rv = errno; goto done; } while (fgets(buf, sizeof(buf), fp) != NULL) { char *q; file_user = buf; file_password = NULL; /* Skip comments */ if (file_user[0] == '#') { continue; } /* Find the user, his password and allowed service */ NEXT_KEY(file_user, file_password); NEXT_KEY(file_password, file_svc); q = file_svc; while(q[0] != '\n' && q[0] != '\0') { q++; } q[0] = '\0'; if (file_password == NULL) { continue; } if (strcmp(file_user, pli->username) == 0) { if (pli->password) { file_password = pli->password; } } rv = fprintf(fp_tmp, "%s:%s:%s\n", file_user, file_password, file_svc); if (rv < 0) { rv = PAM_CRED_ERR; goto done; } } rv = PAM_SUCCESS; done: if (fp != NULL) { fclose(fp); } if (fp_tmp != NULL) { fflush(fp_tmp); fclose(fp_tmp); } if (rv == PAM_SUCCESS) { rv = rename(template, db); if (rv == -1) { rv = PAM_SYSTEM_ERR; } } if (template[0] != '\0') { unlink(template); }; return rv; } static void pam_matrix_mod_items_free(struct pam_matrix_mod_items *pmi) { if (pmi == NULL) { return; } free(pmi->password); free(pmi->service); } static int pam_matrix_conv(pam_handle_t *pamh, const int msg_style, const char *msg, char **answer) { int ret; const struct pam_conv *conv; const struct pam_message *mesg[1]; struct pam_response *resp = NULL; struct pam_response **r = NULL; struct pam_message *pam_msg; ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); if (ret != PAM_SUCCESS) { return ret; } pam_msg = malloc(sizeof(struct pam_message)); if (pam_msg == NULL) { return PAM_BUF_ERR; } pam_msg->msg_style = msg_style; pam_msg->msg = discard_const_p(char, msg); if (msg_style == PAM_PROMPT_ECHO_ON || msg_style == PAM_PROMPT_ECHO_OFF) { r = &resp; } mesg[0] = (const struct pam_message *) pam_msg; ret = conv->conv(1, mesg, r, conv->appdata_ptr); free(pam_msg); if (ret != PAM_SUCCESS) { free(resp); return ret; } if (msg_style == PAM_PROMPT_ECHO_OFF || msg_style == PAM_PROMPT_ECHO_ON) { if (resp == NULL) { /* Response expected, but none find! */ return PAM_SYSTEM_ERR; } if (resp[0].resp == NULL) { /* Empty password */ *answer = NULL; free(resp); return PAM_SUCCESS; } *answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE); wipe_authtok(resp[0].resp); free(resp[0].resp); free(resp); if (*answer == NULL) { return PAM_BUF_ERR; } } return PAM_SUCCESS; } /* Read user password. If both prompts are provided, then ask twice and * assert that both passwords match. * * The authtok would be returned in _out_tok if needed and set in * authtok_item as well */ static int pam_matrix_read_password(pam_handle_t *pamh, int flags, int authtok_item, const char *prompt1, const char *prompt2, const void **_out_tok) { int rv = PAM_AUTHTOK_RECOVERY_ERR; char *authtok1 = NULL; char *authtok2 = NULL; const void *item; int read_flg = PAM_PROMPT_ECHO_OFF; if (flags & PAM_MATRIX_FLG_ECHO) { read_flg = PAM_PROMPT_ECHO_ON; } rv = pam_matrix_conv(pamh, read_flg, prompt1, &authtok1); if (authtok1 == NULL) { goto done; } if (rv == PAM_SUCCESS && prompt2 != NULL) { rv = pam_matrix_conv(pamh, read_flg, prompt2, &authtok2); if (rv != PAM_SUCCESS) { goto done; } if (authtok2 == NULL) { rv = PAM_AUTHTOK_RECOVERY_ERR; goto done; } if (strcmp(authtok1, authtok2) != 0) { pam_matrix_conv(pamh, PAM_ERROR_MSG, "Passwords do not match", NULL); rv = PAM_AUTHTOK_RECOVERY_ERR; goto done; } wipe_authtok(authtok2); free(authtok2); authtok2 = NULL; } if (rv != PAM_SUCCESS) { goto done; } rv = pam_set_item(pamh, authtok_item, authtok1); wipe_authtok(authtok1); free(authtok1); authtok1 = NULL; if (rv != PAM_SUCCESS) { goto done; } rv = pam_get_item(pamh, authtok_item, &item); if (_out_tok) { *_out_tok = item; } item = NULL; if (rv != PAM_SUCCESS) { goto done; } rv = PAM_SUCCESS; done: wipe_authtok(authtok1); wipe_authtok(authtok2); return rv; } /* Retrieve user info -- username and service that were provided by * pam_start */ static int pam_lib_items_get(pam_handle_t *pamh, struct pam_lib_items *pli) { int rv; rv = pam_get_item(pamh, PAM_USER, (const void **) &(pli->username)); if (rv != PAM_SUCCESS) { return rv; } if (pli->username == NULL) { return PAM_BAD_ITEM; } rv = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pli->service)); if (rv != PAM_SUCCESS) { return rv; } return PAM_SUCCESS; } /* Evaluate command line arguments and store info about them in the * pam_matrix context */ static void eval_args(struct pam_matrix_ctx *pe_ctx, int argc, const char *argv[]) { pe_ctx->flags = 0; for (; argc-- > 0; ++argv) { if (strncmp(*argv, PASSDB_KEY, strlen(PASSDB_KEY)) == 0) { if (*(*argv+strlen(PASSDB_KEY)) == '\0') { pe_ctx->passdb = NULL; } else { pe_ctx->passdb = *argv+strlen(PASSDB_KEY); } } else if (strncmp(*argv, VERBOSE_KEY, strlen(VERBOSE_KEY)) == 0) { pe_ctx->flags |= PAM_MATRIX_FLG_VERBOSE; } else if (strncmp(*argv, ECHO_KEY, strlen(ECHO_KEY)) == 0) { pe_ctx->flags |= PAM_MATRIX_FLG_ECHO; } } } /* Retrieve info about the user who is logging in and find his * record in the database */ static int pam_matrix_get(pam_handle_t *pamh, int argc, const char *argv[], struct pam_matrix_ctx *pe_ctx) { int rv; eval_args(pe_ctx, argc, argv); /* If no db is provided as argument, fall back to environment variable */ if (pe_ctx->passdb == NULL) { pe_ctx->passdb = getenv("PAM_MATRIX_PASSWD"); if (pe_ctx->passdb == NULL) { return PAM_AUTHINFO_UNAVAIL; } } rv = pam_lib_items_get(pamh, &pe_ctx->pli); if (rv != PAM_SUCCESS) { return rv; } rv = pam_matrix_mod_items_get(pe_ctx->passdb, pe_ctx->pli.username, &pe_ctx->pmi); if (rv != PAM_SUCCESS) { return PAM_AUTHINFO_UNAVAIL; } return PAM_SUCCESS; } static void pam_matrix_free(struct pam_matrix_ctx *pe_ctx) { pam_matrix_mod_items_free(&pe_ctx->pmi); } static int _pam_matrix_auth(struct pam_matrix_ctx *pctx) { int rv = PAM_AUTH_ERR; if (pctx->pli.password == NULL) { /* NULL passwords are not allowed */ return PAM_CRED_ERR; } if (pctx->pli.password != NULL && pctx->pmi.password != NULL && strcmp(pctx->pli.password, pctx->pmi.password) == 0) { rv = PAM_SUCCESS; } return rv; } static int pam_matrix_auth(pam_handle_t *pamh, struct pam_matrix_ctx *pctx) { int rv = PAM_AUTH_ERR; rv = _pam_matrix_auth(pctx); wipe_authtok(pctx->pli.password); wipe_authtok(pctx->pmi.password); if (pctx->flags & PAM_MATRIX_FLG_VERBOSE) { if (rv == PAM_SUCCESS) { pam_matrix_conv(pamh, PAM_TEXT_INFO, "Authentication succeeded", NULL); } else { pam_matrix_conv(pamh, PAM_ERROR_MSG, "Authentication failed", NULL); } } return rv; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { struct pam_matrix_ctx pctx; int rv; (void) flags; /* unused */ memset(&pctx, 0, sizeof(struct pam_matrix_ctx)); /* Search the user info in database */ rv = pam_matrix_get(pamh, argc, argv, &pctx); if (rv != PAM_SUCCESS) { goto done; } rv = pam_matrix_read_password(pamh, pctx.flags, PAM_AUTHTOK, "Password: ", NULL, (const void **) &pctx.pli.password); if (rv != PAM_SUCCESS) { rv = PAM_AUTHINFO_UNAVAIL; goto done; } /* Auth and get rid of the authtok */ rv = pam_matrix_auth(pamh, &pctx); done: pam_matrix_free(&pctx); return rv; } /* Really silly setcred function that just sets a pam environment variable */ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { struct pam_matrix_ctx pctx; int rv; char cred[PATH_MAX + CRED_VAR_SZ]; (void) flags; /* unused */ memset(&pctx, 0, sizeof(struct pam_matrix_ctx)); rv = pam_matrix_get(pamh, argc, argv, &pctx); if (rv != PAM_SUCCESS) { goto done; } rv = snprintf(cred, sizeof(cred), "%s=/tmp/%s", CRED_VAR, pctx.pli.username); if (rv <= 0) { rv = PAM_BUF_ERR; goto done; } rv = pam_putenv(pamh, cred); if (rv != PAM_SUCCESS) { goto done; } rv = PAM_SUCCESS; done: pam_matrix_free(&pctx); return rv; } PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { struct pam_matrix_ctx pctx; int rv; (void) flags; /* unused */ memset(&pctx, 0, sizeof(struct pam_matrix_ctx)); /* Search the user info in database */ rv = pam_matrix_get(pamh, argc, argv, &pctx); if (rv != PAM_SUCCESS) { goto done; } /* Check if the allowed service matches the PAM service */ if (pctx.pli.service != NULL && pctx.pmi.service != NULL && strcmp(pctx.pli.service, pctx.pmi.service) == 0) { rv = PAM_SUCCESS; goto done; } rv = PAM_PERM_DENIED; done: pam_matrix_free(&pctx); return rv; } /* Really silly session function that just sets a pam environment variable */ PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { struct pam_matrix_ctx pctx; int rv; char home[PATH_MAX + HOME_VAR_SZ]; (void) flags; /* unused */ memset(&pctx, 0, sizeof(struct pam_matrix_ctx)); rv = pam_matrix_get(pamh, argc, argv, &pctx); if (rv != PAM_SUCCESS) { goto done; } rv = snprintf(home, sizeof(home), "%s=/home/%s", HOME_VAR, pctx.pli.username); if (rv <= 0) { rv = PAM_BUF_ERR; goto done; } rv = pam_putenv(pamh, home); if (rv != PAM_SUCCESS) { goto done; } rv = PAM_SUCCESS; done: pam_matrix_free(&pctx); return rv; } /* Just unsets whatever session set */ PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { struct pam_matrix_ctx pctx; int rv; (void) flags; /* unused */ memset(&pctx, 0, sizeof(struct pam_matrix_ctx)); rv = pam_matrix_get(pamh, argc, argv, &pctx); if (rv != PAM_SUCCESS) { goto done; } #if HAVE_OPENPAM /* OpenPAM does not support unsetting variable, set it to * and empty string instead */ rv = pam_putenv(pamh, HOME_VAR"="); #else rv = pam_putenv(pamh, HOME_VAR); #endif if (rv != PAM_SUCCESS) { goto done; } rv = PAM_SUCCESS; done: pam_matrix_free(&pctx); return rv; } static void pam_matrix_stamp_destructor(pam_handle_t *pamh, void *data, int error_status) { (void) pamh; /* unused */ (void) error_status; /* unused */ free(data); } PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { struct pam_matrix_ctx pctx; const char *old_pass; int rv; time_t *auth_stamp = NULL; const time_t *auth_stamp_out = NULL; (void) flags; /* unused */ memset(&pctx, 0, sizeof(struct pam_matrix_ctx)); rv = pam_matrix_get(pamh, argc, argv, &pctx); if (rv != PAM_SUCCESS) { goto done; } if (flags & PAM_PRELIM_CHECK) { rv = pam_matrix_read_password( pamh, pctx.flags, PAM_OLDAUTHTOK, "Old password: ", NULL, (const void **) &pctx.pli.password); if (rv != PAM_SUCCESS) { rv = PAM_AUTHINFO_UNAVAIL; goto done; } auth_stamp = malloc(sizeof(time_t)); if (auth_stamp == NULL) { rv = PAM_BUF_ERR; goto done; } *auth_stamp = time(NULL); /* Not really useful, just test that between the two phases, * data can be passed */ rv = pam_set_data(pamh, PAM_EXAMPLE_AUTH_DATA, auth_stamp, pam_matrix_stamp_destructor); if (rv != PAM_SUCCESS) { goto done; } rv = pam_matrix_auth(pamh, &pctx); } else if (flags & PAM_UPDATE_AUTHTOK) { rv = pam_get_item(pamh, PAM_OLDAUTHTOK, (const void **) &old_pass); if (rv != PAM_SUCCESS || old_pass == NULL) { rv = PAM_AUTHINFO_UNAVAIL; goto done; } rv = pam_get_data(pamh, PAM_EXAMPLE_AUTH_DATA, (const void **) &auth_stamp_out); if (rv != PAM_SUCCESS) { goto done; } rv = pam_matrix_read_password(pamh, pctx.flags, PAM_AUTHTOK, "New Password :", "Verify New Password :", (const void **) &pctx.pli.password); if (rv != PAM_SUCCESS) { rv = PAM_AUTHINFO_UNAVAIL; goto done; } /* Write the new password to the db */ rv = pam_matrix_lib_items_put(pctx.passdb, &pctx.pli); } else { rv = PAM_SYSTEM_ERR; } done: pam_matrix_free(&pctx); return rv; } pam_wrapper-1.1.4/src/modules/pam_get_items.c000644 001750 000144 00000010122 12632316172 021231 0ustar00asnusers000000 000000 /* * Copyright (c) 2015 Andreas Schneider * Copyright (c) 2015 Jakub Hrozek * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #ifdef HAVE_SECURITY_PAM_APPL_H #include #endif #ifdef HAVE_SECURITY_PAM_MODULES_H #include #endif #include "pwrap_compat.h" static const char *str_opt(const int opt) { switch (opt) { case PAM_SERVICE: return "PAM_SERVICE"; case PAM_USER: return "PAM_USER"; case PAM_USER_PROMPT: return "PAM_USER_PROMPT"; case PAM_TTY: return "PAM_TTY"; case PAM_RUSER: return "PAM_RUSER"; case PAM_RHOST: return "PAM_RHOST"; case PAM_AUTHTOK: return "PAM_AUTHTOK"; case PAM_OLDAUTHTOK: return "PAM_OLDAUTHTOK"; #ifdef PAM_XDISPLAY case PAM_XDISPLAY: return "PAM_XDISPLAY"; #endif #ifdef PAM_AUTHTOK_TYPE case PAM_AUTHTOK_TYPE: return "PAM_AUTHTOK_TYPE"; #endif } return NULL; /* Unsupported */ } static int putenv_item(pam_handle_t *pamh, int item_type) { const char *opt_name; const char *value = NULL; char *env_name; size_t env_len; int rv; rv = pam_get_item(pamh, item_type, (const void **) &value); if (rv != PAM_SUCCESS) { return rv; } if (value == NULL) { return PAM_SUCCESS; } opt_name = str_opt(item_type); if (opt_name == NULL) { /* Probably some non-printable value */ return PAM_BAD_ITEM; } env_len = strlen(value) + strlen(opt_name) + 2; env_name = malloc(env_len); if (env_name == NULL) { return PAM_BUF_ERR; } rv = snprintf(env_name, env_len, "%s=%s", opt_name, value); if (rv < 0) { free(env_name); return PAM_BUF_ERR; } rv = pam_putenv(pamh, env_name); free(env_name); return rv; } /* Get all pam_items and put them into environment */ static int pam_putitem(pam_handle_t *pamh) { putenv_item(pamh, PAM_SERVICE); putenv_item(pamh, PAM_USER); putenv_item(pamh, PAM_USER_PROMPT); putenv_item(pamh, PAM_TTY); putenv_item(pamh, PAM_RUSER); putenv_item(pamh, PAM_RHOST); putenv_item(pamh, PAM_AUTHTOK); putenv_item(pamh, PAM_OLDAUTHTOK); #ifdef PAM_XDISPLAY putenv_item(pamh, PAM_XDISPLAY); #endif #ifdef PAM_AUTHTOK_TYPE putenv_item(pamh, PAM_AUTHTOK_TYPE); #endif return PAM_SUCCESS; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ return pam_putitem(pamh); } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ return pam_putitem(pamh); } PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ return pam_putitem(pamh); } PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ return pam_putitem(pamh); } PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ return pam_putitem(pamh); } PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { (void) flags; /* unused */ (void) argc; /* unused */ (void) argv; /* unused */ return pam_putitem(pamh); } pam_wrapper-1.1.4/src/modules/pam_chatty.c000644 001750 000144 00000007635 13635146323 020567 0ustar00asnusers000000 000000 /* * Copyright (c) 2015 Andreas Schneider * Copyright (c) 2015 Jakub Hrozek * Copyright (c) 2020 Bastien Nocera * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef discard_const #define discard_const(ptr) ((void *)((uintptr_t)(ptr))) #endif #ifndef discard_const_p #define discard_const_p(type, ptr) ((type *)discard_const(ptr)) #endif #ifdef HAVE_SECURITY_PAM_APPL_H #include #endif #ifdef HAVE_SECURITY_PAM_MODULES_H #include #endif #ifdef HAVE_SECURITY_PAM_EXT_H #include #endif #include "pwrap_compat.h" #define ERROR_KEY "error" #define INFO_KEY "info" #define NUM_LINES_KEY "num_lines=" #define DEFAULT_NUM_LINES 3 /* We only return up to 16 messages from the PAM conversation. * Value from src/python/pypamtest.c */ #define PAM_CONV_MSG_MAX 16 #define PAM_CHATTY_FLG_ERROR (1 << 0) #define PAM_CHATTY_FLG_INFO (1 << 1) static int pam_chatty_conv(pam_handle_t *pamh, const int msg_style, const char *msg) { int ret; const struct pam_conv *conv = NULL; const struct pam_message *mesg[1]; struct pam_response *r = NULL; struct pam_message *pam_msg = NULL; ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv); if (ret != PAM_SUCCESS) { return ret; } pam_msg = calloc(sizeof(struct pam_message), 1); if (pam_msg == NULL) { return PAM_BUF_ERR; } pam_msg->msg_style = msg_style; pam_msg->msg = discard_const_p(char, msg); mesg[0] = (const struct pam_message *) pam_msg; ret = conv->conv(1, mesg, &r, conv->appdata_ptr); free(pam_msg); return ret; } /* Evaluate command line arguments and store info about them in the * pam_matrix context */ static uint32_t parse_args(int argc, const char *argv[], unsigned int *num_lines) { uint32_t flags = 0; *num_lines = DEFAULT_NUM_LINES; for (; argc-- > 0; ++argv) { if (strncmp(*argv, NUM_LINES_KEY, strlen(NUM_LINES_KEY)) == 0) { if (*(*argv+strlen(NUM_LINES_KEY)) != '\0') { *num_lines = atoi(*argv+strlen(NUM_LINES_KEY)); if (*num_lines <= DEFAULT_NUM_LINES) *num_lines = DEFAULT_NUM_LINES; if (*num_lines > PAM_CONV_MSG_MAX) *num_lines = PAM_CONV_MSG_MAX; } } else if (strncmp(*argv, ERROR_KEY, strlen(ERROR_KEY)) == 0) { flags |= PAM_CHATTY_FLG_ERROR; } else if (strncmp(*argv, INFO_KEY, strlen(INFO_KEY)) == 0) { flags |= PAM_CHATTY_FLG_INFO; } } return flags; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { uint32_t optflags; unsigned int num_lines; (void) flags; /* unused */ optflags = parse_args (argc, argv, &num_lines); if (optflags & PAM_CHATTY_FLG_INFO) { unsigned int i; for (i = 0; i < num_lines; i++) { pam_chatty_conv(pamh, PAM_TEXT_INFO, "Authentication succeeded"); } } if (optflags & PAM_CHATTY_FLG_ERROR) { unsigned int i; for (i = 0; i < num_lines; i++) { pam_chatty_conv(pamh, PAM_ERROR_MSG, "Authentication generated an error"); } } return PAM_SUCCESS; } pam_wrapper-1.1.4/src/pam_wrapper.c000644 001750 000144 00000130173 14065307740 017275 0ustar00asnusers000000 000000 /* * Copyright (c) 2015 Andreas Schneider * Copyright (c) 2015 Jakub Hrozek * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SECURITY_PAM_APPL_H #include #endif #ifdef HAVE_SECURITY_PAM_MODULES_H #include #endif #ifdef HAVE_SECURITY_PAM_EXT_H #include #endif #include "pwrap_compat.h" #ifdef HAVE_GCC_THREAD_LOCAL_STORAGE # define PWRAP_THREAD __thread #else # define PWRAP_THREAD #endif #ifdef HAVE_CONSTRUCTOR_ATTRIBUTE #define CONSTRUCTOR_ATTRIBUTE __attribute__ ((constructor)) #else #define CONSTRUCTOR_ATTRIBUTE #endif /* HAVE_CONSTRUCTOR_ATTRIBUTE */ #ifdef HAVE_DESTRUCTOR_ATTRIBUTE #define DESTRUCTOR_ATTRIBUTE __attribute__ ((destructor)) #else #define DESTRUCTOR_ATTRIBUTE #endif /* HAVE_DESTRUCTOR_ATTRIBUTE */ #ifdef HAVE_ADDRESS_SANITIZER_ATTRIBUTE #define DO_NOT_SANITIZE_ADDRESS_ATTRIBUTE __attribute__((no_sanitize_address)) #else /* DO_NOT_SANITIZE_ADDRESS_ATTRIBUTE */ #define DO_NOT_SANITIZE_ADDRESS_ATTRIBUTE #endif /* DO_NOT_SANITIZE_ADDRESS_ATTRIBUTE */ /* GCC have printf type attribute check. */ #ifdef HAVE_FUNCTION_ATTRIBUTE_FORMAT #define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) #else #define PRINTF_ATTRIBUTE(a,b) #endif /* HAVE_FUNCTION_ATTRIBUTE_FORMAT */ #ifndef SAFE_FREE #define SAFE_FREE(x) do { if ((x) != NULL) {free(x); (x)=NULL;} } while(0) #endif #ifndef discard_const #define discard_const(ptr) ((void *)((uintptr_t)(ptr))) #endif #ifndef discard_const_p #define discard_const_p(type, ptr) ((type *)discard_const(ptr)) #endif /***************** * LOGGING *****************/ #ifndef HAVE_GETPROGNAME static const char *getprogname(void) { #if defined(HAVE_PROGRAM_INVOCATION_SHORT_NAME) return program_invocation_short_name; #elif defined(HAVE_GETEXECNAME) return getexecname(); #else return NULL; #endif /* HAVE_PROGRAM_INVOCATION_SHORT_NAME */ } #endif /* HAVE_GETPROGNAME */ enum pwrap_dbglvl_e { PWRAP_LOG_ERROR = 0, PWRAP_LOG_WARN, PWRAP_LOG_DEBUG, PWRAP_LOG_TRACE }; static void pwrap_log(enum pwrap_dbglvl_e dbglvl, const char *function, const char *format, ...) PRINTF_ATTRIBUTE(3, 4); # define PWRAP_LOG(dbglvl, ...) pwrap_log((dbglvl), __func__, __VA_ARGS__) static void pwrap_vlog(enum pwrap_dbglvl_e dbglvl, const char *function, const char *format, va_list args) PRINTF_ATTRIBUTE(3, 0); static void pwrap_vlog(enum pwrap_dbglvl_e dbglvl, const char *function, const char *format, va_list args) { char buffer[1024]; const char *d; unsigned int lvl = 0; const char *prefix = "PWRAP"; const char *progname = getprogname(); d = getenv("PAM_WRAPPER_DEBUGLEVEL"); if (d != NULL) { lvl = atoi(d); } if (lvl < dbglvl) { return; } vsnprintf(buffer, sizeof(buffer), format, args); switch (dbglvl) { case PWRAP_LOG_ERROR: prefix = "PWRAP_ERROR"; break; case PWRAP_LOG_WARN: prefix = "PWRAP_WARN"; break; case PWRAP_LOG_DEBUG: prefix = "PWRAP_DEBUG"; break; case PWRAP_LOG_TRACE: prefix = "PWRAP_TRACE"; break; } if (progname == NULL) { progname = ""; } fprintf(stderr, "%s[%s (%u)] - %s: %s\n", prefix, progname, (unsigned int)getpid(), function, buffer); } static void pwrap_log(enum pwrap_dbglvl_e dbglvl, const char *function, const char *format, ...) { va_list va; va_start(va, format); pwrap_vlog(dbglvl, function, format, va); va_end(va); } /***************** * LIBC *****************/ #define LIBPAM_NAME "libpam.so.0" typedef int (*__libpam_pam_start)(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh); typedef int (*__libpam_pam_start_confdir)(const char *service_name, const char *user, const struct pam_conv *pam_conversation, const char *confdir, pam_handle_t **pamh); typedef int (*__libpam_pam_end)(pam_handle_t *pamh, int pam_status); typedef int (*__libpam_pam_authenticate)(pam_handle_t *pamh, int flags); typedef int (*__libpam_pam_chauthtok)(pam_handle_t *pamh, int flags); typedef int (*__libpam_pam_acct_mgmt)(pam_handle_t *pamh, int flags); typedef int (*__libpam_pam_putenv)(pam_handle_t *pamh, const char *name_value); typedef const char * (*__libpam_pam_getenv)(pam_handle_t *pamh, const char *name); typedef char ** (*__libpam_pam_getenvlist)(pam_handle_t *pamh); typedef int (*__libpam_pam_open_session)(pam_handle_t *pamh, int flags); typedef int (*__libpam_pam_close_session)(pam_handle_t *pamh, int flags); typedef int (*__libpam_pam_setcred)(pam_handle_t *pamh, int flags); typedef int (*__libpam_pam_get_item)(const pam_handle_t *pamh, int item_type, const void **item); typedef int (*__libpam_pam_set_item)(pam_handle_t *pamh, int item_type, const void *item); typedef int (*__libpam_pam_get_data)(const pam_handle_t *pamh, const char *module_data_name, const void **data); typedef int (*__libpam_pam_set_data)(pam_handle_t *pamh, const char *module_data_name, void *data, void (*cleanup)(pam_handle_t *pamh, void *data, int error_status)); typedef int (*__libpam_pam_vprompt)(pam_handle_t *pamh, int style, char **response, const char *fmt, va_list args); typedef const char * (*__libpam_pam_strerror)(pam_handle_t *pamh, int errnum); #ifdef HAVE_PAM_VSYSLOG typedef void (*__libpam_pam_vsyslog)(const pam_handle_t *pamh, int priority, const char *fmt, va_list args); #endif #define PWRAP_SYMBOL_ENTRY(i) \ union { \ __libpam_##i f; \ void *obj; \ } _libpam_##i struct pwrap_libpam_symbols { PWRAP_SYMBOL_ENTRY(pam_start); PWRAP_SYMBOL_ENTRY(pam_start_confdir); PWRAP_SYMBOL_ENTRY(pam_end); PWRAP_SYMBOL_ENTRY(pam_authenticate); PWRAP_SYMBOL_ENTRY(pam_chauthtok); PWRAP_SYMBOL_ENTRY(pam_acct_mgmt); PWRAP_SYMBOL_ENTRY(pam_putenv); PWRAP_SYMBOL_ENTRY(pam_getenv); PWRAP_SYMBOL_ENTRY(pam_getenvlist); PWRAP_SYMBOL_ENTRY(pam_open_session); PWRAP_SYMBOL_ENTRY(pam_close_session); PWRAP_SYMBOL_ENTRY(pam_setcred); PWRAP_SYMBOL_ENTRY(pam_get_item); PWRAP_SYMBOL_ENTRY(pam_set_item); PWRAP_SYMBOL_ENTRY(pam_get_data); PWRAP_SYMBOL_ENTRY(pam_set_data); PWRAP_SYMBOL_ENTRY(pam_vprompt); PWRAP_SYMBOL_ENTRY(pam_strerror); #ifdef HAVE_PAM_VSYSLOG PWRAP_SYMBOL_ENTRY(pam_vsyslog); #endif }; struct pwrap { struct { void *handle; struct pwrap_libpam_symbols symbols; } libpam; bool enabled; bool initialised; char *config_dir; char *libpam_so; }; static struct pwrap pwrap; /********************************************************* * PWRAP PROTOTYPES *********************************************************/ bool pam_wrapper_enabled(void); #if ! defined(HAVE_CONSTRUCTOR_ATTRIBUTE) && defined(HAVE_PRAGMA_INIT) /* xlC and other oldschool compilers support (only) this */ #pragma init (pwrap_constructor) #endif void pwrap_constructor(void) CONSTRUCTOR_ATTRIBUTE; #if ! defined(HAVE_DESTRUCTOR_ATTRIBUTE) && defined(HAVE_PRAGMA_FINI) #pragma fini (pwrap_destructor) #endif void pwrap_destructor(void) DESTRUCTOR_ATTRIBUTE; /********************************************************* * PWRAP LIBC LOADER FUNCTIONS *********************************************************/ enum pwrap_lib { PWRAP_LIBPAM, }; static void *pwrap_load_lib_handle(enum pwrap_lib lib) { int flags = RTLD_LAZY; void *handle = NULL; #ifdef RTLD_DEEPBIND const char *env_preload = getenv("LD_PRELOAD"); const char *env_deepbind = getenv("UID_WRAPPER_DISABLE_DEEPBIND"); bool enable_deepbind = true; /* Don't do a deepbind if we run with libasan */ if (env_preload != NULL && strlen(env_preload) < 1024) { const char *p = strstr(env_preload, "libasan.so"); if (p != NULL) { enable_deepbind = false; } } if (env_deepbind != NULL && strlen(env_deepbind) >= 1) { enable_deepbind = false; } if (enable_deepbind) { flags |= RTLD_DEEPBIND; } #endif switch (lib) { case PWRAP_LIBPAM: handle = pwrap.libpam.handle; if (handle == NULL) { handle = dlopen(pwrap.libpam_so, flags); if (handle != NULL) { PWRAP_LOG(PWRAP_LOG_DEBUG, "Opened %s\n", pwrap.libpam_so); pwrap.libpam.handle = handle; break; } } break; } if (handle == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to dlopen library: %s\n", dlerror()); exit(-1); } return handle; } static void *_pwrap_bind_symbol(enum pwrap_lib lib, const char *fn_name) { void *handle; void *func; handle = pwrap_load_lib_handle(lib); func = dlsym(handle, fn_name); if (func == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to find %s: %s\n", fn_name, dlerror()); exit(-1); } return func; } #define pwrap_bind_symbol_libpam(sym_name) \ if (pwrap.libpam.symbols._libpam_##sym_name.obj == NULL) { \ pwrap.libpam.symbols._libpam_##sym_name.obj = \ _pwrap_bind_symbol(PWRAP_LIBPAM, #sym_name); \ } \ /* * IMPORTANT * * Functions especially from libpam need to be loaded individually, you can't * load all at once or gdb will segfault at startup. The same applies to * valgrind and has probably something todo with with the linker. * So we need load each function at the point it is called the first time. */ #ifdef HAVE_PAM_START_CONFDIR static int libpam_pam_start_confdir(const char *service_name, const char *user, const struct pam_conv *pam_conversation, const char *confdir, pam_handle_t **pamh) { pwrap_bind_symbol_libpam(pam_start_confdir); return pwrap.libpam.symbols._libpam_pam_start_confdir.f(service_name, user, pam_conversation, confdir, pamh); } #else static int libpam_pam_start(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh) { pwrap_bind_symbol_libpam(pam_start); return pwrap.libpam.symbols._libpam_pam_start.f(service_name, user, pam_conversation, pamh); } #endif static int libpam_pam_end(pam_handle_t *pamh, int pam_status) { pwrap_bind_symbol_libpam(pam_end); return pwrap.libpam.symbols._libpam_pam_end.f(pamh, pam_status); } static int libpam_pam_authenticate(pam_handle_t *pamh, int flags) { pwrap_bind_symbol_libpam(pam_authenticate); return pwrap.libpam.symbols._libpam_pam_authenticate.f(pamh, flags); } static int libpam_pam_chauthtok(pam_handle_t *pamh, int flags) { pwrap_bind_symbol_libpam(pam_chauthtok); return pwrap.libpam.symbols._libpam_pam_chauthtok.f(pamh, flags); } static int libpam_pam_acct_mgmt(pam_handle_t *pamh, int flags) { pwrap_bind_symbol_libpam(pam_acct_mgmt); return pwrap.libpam.symbols._libpam_pam_acct_mgmt.f(pamh, flags); } static int libpam_pam_putenv(pam_handle_t *pamh, const char *name_value) { pwrap_bind_symbol_libpam(pam_putenv); return pwrap.libpam.symbols._libpam_pam_putenv.f(pamh, name_value); } static const char *libpam_pam_getenv(pam_handle_t *pamh, const char *name) { pwrap_bind_symbol_libpam(pam_getenv); return pwrap.libpam.symbols._libpam_pam_getenv.f(pamh, name); } static char **libpam_pam_getenvlist(pam_handle_t *pamh) { pwrap_bind_symbol_libpam(pam_getenvlist); return pwrap.libpam.symbols._libpam_pam_getenvlist.f(pamh); } static int libpam_pam_open_session(pam_handle_t *pamh, int flags) { pwrap_bind_symbol_libpam(pam_open_session); return pwrap.libpam.symbols._libpam_pam_open_session.f(pamh, flags); } static int libpam_pam_close_session(pam_handle_t *pamh, int flags) { pwrap_bind_symbol_libpam(pam_close_session); return pwrap.libpam.symbols._libpam_pam_close_session.f(pamh, flags); } static int libpam_pam_setcred(pam_handle_t *pamh, int flags) { pwrap_bind_symbol_libpam(pam_setcred); return pwrap.libpam.symbols._libpam_pam_setcred.f(pamh, flags); } static int libpam_pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) { pwrap_bind_symbol_libpam(pam_get_item); return pwrap.libpam.symbols._libpam_pam_get_item.f(pamh, item_type, item); } static int libpam_pam_set_item(pam_handle_t *pamh, int item_type, const void *item) { pwrap_bind_symbol_libpam(pam_set_item); return pwrap.libpam.symbols._libpam_pam_set_item.f(pamh, item_type, item); } static int libpam_pam_get_data(const pam_handle_t *pamh, const char *module_data_name, const void **data) { pwrap_bind_symbol_libpam(pam_get_data); return pwrap.libpam.symbols._libpam_pam_get_data.f(pamh, module_data_name, data); } static int libpam_pam_set_data(pam_handle_t *pamh, const char *module_data_name, void *data, void (*cleanup)(pam_handle_t *pamh, void *data, int error_status)) { pwrap_bind_symbol_libpam(pam_set_data); return pwrap.libpam.symbols._libpam_pam_set_data.f(pamh, module_data_name, data, cleanup); } static int libpam_pam_vprompt(pam_handle_t *pamh, int style, char **response, const char *fmt, va_list args) { pwrap_bind_symbol_libpam(pam_vprompt); return pwrap.libpam.symbols._libpam_pam_vprompt.f(pamh, style, response, fmt, args); } #ifdef HAVE_PAM_STRERROR_CONST static const char *libpam_pam_strerror(const pam_handle_t *pamh, int errnum) #else static const char *libpam_pam_strerror(pam_handle_t *pamh, int errnum) #endif { pwrap_bind_symbol_libpam(pam_strerror); return pwrap.libpam.symbols._libpam_pam_strerror.f(discard_const_p(pam_handle_t, pamh), errnum); } #ifdef HAVE_PAM_VSYSLOG static void libpam_pam_vsyslog(const pam_handle_t *pamh, int priority, const char *fmt, va_list args) { pwrap_bind_symbol_libpam(pam_vsyslog); pwrap.libpam.symbols._libpam_pam_vsyslog.f(pamh, priority, fmt, args); } #endif /* HAVE_PAM_VSYSLOG */ /********************************************************* * PWRAP INIT *********************************************************/ #define BUFFER_SIZE 32768 /* copy file from src to dst, overwrites dst */ static int p_copy(const char *src, const char *dst, mode_t mode) { int srcfd = -1; int dstfd = -1; int rc = -1; ssize_t bread, bwritten; struct stat sb; char buf[BUFFER_SIZE]; int cmp; cmp = strcmp(src, dst); if (cmp == 0) { return -1; } srcfd = open(src, O_RDONLY, 0); if (srcfd < 0) { return -1; } if (mode == 0) { rc = fstat(srcfd, &sb); if (rc != 0) { rc = -1; goto out; } mode = sb.st_mode; } dstfd = open(dst, O_CREAT|O_WRONLY|O_TRUNC, mode); if (dstfd < 0) { rc = -1; goto out; } for (;;) { bread = read(srcfd, buf, BUFFER_SIZE); if (bread == 0) { /* done */ break; } else if (bread < 0) { errno = EIO; rc = -1; goto out; } bwritten = write(dstfd, buf, bread); if (bwritten < 0) { errno = EIO; rc = -1; goto out; } if (bread != bwritten) { errno = EFAULT; rc = -1; goto out; } } rc = 0; out: if (srcfd != -1) { close(srcfd); } if (dstfd != -1) { close(dstfd); } if (rc < 0) { unlink(dst); } return rc; } /* Do not pass any flag if not defined */ #ifndef FTW_ACTIONRETVAL #define FTW_ACTIONRETVAL 0 #endif /* Action return values */ #ifndef FTW_STOP #define FTW_STOP -1 #endif #ifndef FTW_CONTINUE #define FTW_CONTINUE 0 #endif #ifndef FTW_SKIP_SUBTREE #define FTW_SKIP_SUBTREE 0 #endif static int copy_ftw(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { int rc; char buf[BUFFER_SIZE]; switch (typeflag) { case FTW_D: case FTW_DNR: /* We want to copy the directories from this directory */ if (ftwbuf->level == 0) { return FTW_CONTINUE; } return FTW_SKIP_SUBTREE; case FTW_F: break; default: return FTW_CONTINUE; } rc = snprintf(buf, BUFFER_SIZE, "%s/%s", pwrap.config_dir, fpath + ftwbuf->base); if (rc >= BUFFER_SIZE) { return FTW_STOP; } PWRAP_LOG(PWRAP_LOG_TRACE, "Copying %s", fpath); rc = p_copy(fpath, buf, sb->st_mode); if (rc != 0) { return FTW_STOP; } return FTW_CONTINUE; } static int copy_confdir(const char *src) { int rc; PWRAP_LOG(PWRAP_LOG_DEBUG, "Copy config files from %s to %s", src, pwrap.config_dir); rc = nftw(src, copy_ftw, 1, FTW_ACTIONRETVAL); if (rc != 0) { return -1; } return 0; } static int p_rmdirs(const char *path); static void pwrap_clean_stale_dirs(const char *dir) { size_t len = strlen(dir); char pidfile[len + 5]; ssize_t rc; char buf[8] = {0}; long int tmp; pid_t pid; int fd; snprintf(pidfile, sizeof(pidfile), "%s/pid", dir); /* read the pidfile */ fd = open(pidfile, O_RDONLY); if (fd < 0) { if (errno == ENOENT) { PWRAP_LOG(PWRAP_LOG_TRACE, "pidfile %s missing, nothing to do\n", pidfile); } else { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to open pidfile %s - error: %s", pidfile, strerror(errno)); } return; } rc = read(fd, buf, sizeof(buf)); close(fd); if (rc < 0) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to read pidfile %s - error: %s", pidfile, strerror(errno)); return; } buf[sizeof(buf) - 1] = '\0'; tmp = strtol(buf, NULL, 10); if (tmp == 0 || errno == ERANGE) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to parse pid, buf=%s", buf); return; } pid = (pid_t)tmp; /* Check if we are out of pid_t range on this system */ if ((long)pid != tmp) { PWRAP_LOG(PWRAP_LOG_ERROR, "pid out of range: %ld", tmp); return; } rc = kill(pid, 0); if (rc == -1) { PWRAP_LOG(PWRAP_LOG_TRACE, "Remove stale pam_wrapper dir: %s", dir); p_rmdirs(dir); } return; } #ifdef HAVE_PAM_START_CONFDIR static void pwrap_init(void) { char tmp_config_dir[] = "/tmp/pam.X"; size_t len = strlen(tmp_config_dir); const char *env; struct stat sb; int rc; unsigned i; ssize_t ret; FILE *pidfile; char pidfile_path[1024] = { 0 }; char letter; if (!pam_wrapper_enabled()) { return; } if (pwrap.initialised) { return; } /* * The name is selected to match/replace /etc/pam.d * We start from a random alphanum trying letters until * an available directory is found. */ letter = 48 + (getpid() % 70); for (i = 0; i < 127; i++) { if (isalpha(letter) || isdigit(letter)) { tmp_config_dir[len - 1] = letter; rc = lstat(tmp_config_dir, &sb); if (rc == 0) { PWRAP_LOG(PWRAP_LOG_TRACE, "Check if pam_wrapper dir %s is a " "stale directory", tmp_config_dir); pwrap_clean_stale_dirs(tmp_config_dir); } else if (rc < 0) { if (errno != ENOENT) { continue; } break; /* found */ } } letter++; letter %= 127; } if (i == 127) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to find a possible path to create " "pam_wrapper config dir: %s", tmp_config_dir); exit(1); } PWRAP_LOG(PWRAP_LOG_DEBUG, "Initialize pam_wrapper"); pwrap.config_dir = strdup(tmp_config_dir); if (pwrap.config_dir == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "No memory"); exit(1); } PWRAP_LOG(PWRAP_LOG_TRACE, "pam_wrapper config dir: %s", tmp_config_dir); rc = mkdir(pwrap.config_dir, 0755); if (rc != 0) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to create pam_wrapper config dir: %s - %s", tmp_config_dir, strerror(errno)); } /* Create file with the PID of the the process */ ret = snprintf(pidfile_path, sizeof(pidfile_path), "%s/pid", pwrap.config_dir); if (ret < 0) { p_rmdirs(pwrap.config_dir); exit(1); } pidfile = fopen(pidfile_path, "w"); if (pidfile == NULL) { p_rmdirs(pwrap.config_dir); exit(1); } rc = fprintf(pidfile, "%d", getpid()); fclose(pidfile); if (rc <= 0) { p_rmdirs(pwrap.config_dir); exit(1); } pwrap.libpam_so = strdup(PAM_LIBRARY); if (pwrap.libpam_so == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "No memory"); p_rmdirs(pwrap.config_dir); exit(1); } PWRAP_LOG(PWRAP_LOG_TRACE, "Using libpam path: %s", pwrap.libpam_so); pwrap.initialised = true; env = getenv("PAM_WRAPPER_SERVICE_DIR"); if (env == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "No config file"); p_rmdirs(pwrap.config_dir); exit(1); } rc = copy_confdir(env); if (rc != 0) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to copy config files"); p_rmdirs(pwrap.config_dir); exit(1); } setenv("PAM_WRAPPER_RUNTIME_DIR", pwrap.config_dir, 1); PWRAP_LOG(PWRAP_LOG_DEBUG, "Successfully initialized pam_wrapper"); } #else /* HAVE_PAM_START_CONFDIR */ static int pso_copy(const char *src, const char *dst, const char *pdir, mode_t mode) { #define PSO_COPY_READ_SIZE 9 int srcfd = -1; int dstfd = -1; int rc = -1; ssize_t bread, bwritten; struct stat sb; char buf[PSO_COPY_READ_SIZE + 1]; int cmp; size_t to_read; bool found_slash; cmp = strcmp(src, dst); if (cmp == 0) { return -1; } srcfd = open(src, O_RDONLY, 0); if (srcfd < 0) { return -1; } if (mode == 0) { rc = fstat(srcfd, &sb); if (rc != 0) { rc = -1; goto out; } mode = sb.st_mode; } dstfd = open(dst, O_CREAT|O_WRONLY|O_TRUNC, mode); if (dstfd < 0) { rc = -1; goto out; } found_slash = false; to_read = 1; for (;;) { bread = read(srcfd, buf, to_read); if (bread == 0) { /* done */ break; } else if (bread < 0) { errno = EIO; rc = -1; goto out; } to_read = 1; if (!found_slash && buf[0] == '/') { found_slash = true; to_read = PSO_COPY_READ_SIZE; } if (found_slash && bread == PSO_COPY_READ_SIZE) { cmp = memcmp(buf, "etc/pam.d", PSO_COPY_READ_SIZE); if (cmp == 0) { memcpy(buf, pdir + 1, PSO_COPY_READ_SIZE); } found_slash = false; } bwritten = write(dstfd, buf, bread); if (bwritten < 0) { errno = EIO; rc = -1; goto out; } if (bread != bwritten) { errno = EFAULT; rc = -1; goto out; } } rc = 0; out: if (srcfd != -1) { close(srcfd); } if (dstfd != -1) { close(dstfd); } if (rc < 0) { unlink(dst); } return rc; #undef PSO_COPY_READ_SIZE } static void pwrap_init(void) { char tmp_config_dir[] = "/tmp/pam.X"; size_t len = strlen(tmp_config_dir); const char *env; struct stat sb; int rc; unsigned i; char pam_library[128] = { 0 }; char libpam_path[1024] = { 0 }; ssize_t ret; FILE *pidfile; char pidfile_path[1024] = { 0 }; char letter; if (!pam_wrapper_enabled()) { return; } if (pwrap.initialised) { return; } /* * The name is selected to match/replace /etc/pam.d * We start from a random alphanum trying letters until * an available directory is found. */ letter = 48 + (getpid() % 70); for (i = 0; i < 127; i++) { if (isalpha(letter) || isdigit(letter)) { tmp_config_dir[len - 1] = letter; rc = lstat(tmp_config_dir, &sb); if (rc == 0) { PWRAP_LOG(PWRAP_LOG_TRACE, "Check if pam_wrapper dir %s is a " "stale directory", tmp_config_dir); pwrap_clean_stale_dirs(tmp_config_dir); } else if (rc < 0) { if (errno != ENOENT) { continue; } break; /* found */ } } letter++; letter %= 127; } if (i == 127) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to find a possible path to create " "pam_wrapper config dir: %s", tmp_config_dir); exit(1); } PWRAP_LOG(PWRAP_LOG_DEBUG, "Initialize pam_wrapper"); pwrap.config_dir = strdup(tmp_config_dir); if (pwrap.config_dir == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "No memory"); exit(1); } PWRAP_LOG(PWRAP_LOG_TRACE, "pam_wrapper config dir: %s", tmp_config_dir); rc = mkdir(pwrap.config_dir, 0755); if (rc != 0) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to create pam_wrapper config dir: %s - %s", tmp_config_dir, strerror(errno)); } /* Create file with the PID of the the process */ ret = snprintf(pidfile_path, sizeof(pidfile_path), "%s/pid", pwrap.config_dir); if (ret < 0) { p_rmdirs(pwrap.config_dir); exit(1); } pidfile = fopen(pidfile_path, "w"); if (pidfile == NULL) { p_rmdirs(pwrap.config_dir); exit(1); } rc = fprintf(pidfile, "%d", getpid()); fclose(pidfile); if (rc <= 0) { p_rmdirs(pwrap.config_dir); exit(1); } /* create lib subdirectory */ snprintf(libpam_path, sizeof(libpam_path), "%s/lib", pwrap.config_dir); rc = mkdir(libpam_path, 0755); if (rc != 0) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to create path for libpam: %s - %s", tmp_config_dir, strerror(errno)); p_rmdirs(pwrap.config_dir); exit(1); } snprintf(libpam_path, sizeof(libpam_path), "%s/lib/%s", pwrap.config_dir, LIBPAM_NAME); pwrap.libpam_so = strdup(libpam_path); if (pwrap.libpam_so == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "No memory"); p_rmdirs(pwrap.config_dir); exit(1); } /* copy libpam.so.0 */ snprintf(libpam_path, sizeof(libpam_path), "%s", PAM_LIBRARY); PWRAP_LOG(PWRAP_LOG_TRACE, "PAM path: %s", libpam_path); ret = readlink(libpam_path, pam_library, sizeof(pam_library) - 1); PWRAP_LOG(PWRAP_LOG_TRACE, "PAM library: %s", pam_library); if (ret <= 0) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to read %s link", LIBPAM_NAME); p_rmdirs(pwrap.config_dir); exit(1); } if (pam_library[0] == '/') { snprintf(libpam_path, sizeof(libpam_path), "%s", pam_library); } else { char libpam_path_cp[1024] = {0}; char *dname = NULL; snprintf(libpam_path_cp, sizeof(libpam_path_cp), "%s", libpam_path); dname = dirname(libpam_path_cp); if (dname == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "No directory component in %s", libpam_path); p_rmdirs(pwrap.config_dir); exit(1); } snprintf(libpam_path, sizeof(libpam_path), "%s/%s", dname, pam_library); } PWRAP_LOG(PWRAP_LOG_TRACE, "Reconstructed PAM path: %s", libpam_path); PWRAP_LOG(PWRAP_LOG_DEBUG, "Copy %s to %s", libpam_path, pwrap.libpam_so); rc = pso_copy(libpam_path, pwrap.libpam_so, pwrap.config_dir, 0644); if (rc != 0) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to copy %s - error: %s", LIBPAM_NAME, strerror(errno)); p_rmdirs(pwrap.config_dir); exit(1); } PWRAP_LOG(PWRAP_LOG_TRACE, "Using libpam path: %s", pwrap.libpam_so); pwrap.initialised = true; env = getenv("PAM_WRAPPER_SERVICE_DIR"); if (env == NULL) { PWRAP_LOG(PWRAP_LOG_ERROR, "No config file"); p_rmdirs(pwrap.config_dir); exit(1); } rc = copy_confdir(env); if (rc != 0) { PWRAP_LOG(PWRAP_LOG_ERROR, "Failed to copy config files"); p_rmdirs(pwrap.config_dir); exit(1); } setenv("PAM_WRAPPER_RUNTIME_DIR", pwrap.config_dir, 1); PWRAP_LOG(PWRAP_LOG_DEBUG, "Successfully initialized pam_wrapper"); } #endif /* HAVE_PAM_START_CONFDIR */ bool pam_wrapper_enabled(void) { const char *env; pwrap.enabled = false; env = getenv("PAM_WRAPPER"); if (env != NULL && env[0] == '1') { pwrap.enabled = true; } if (pwrap.enabled) { pwrap.enabled = false; env = getenv("PAM_WRAPPER_SERVICE_DIR"); if (env != NULL && env[0] != '\0') { pwrap.enabled = true; } } return pwrap.enabled; } #ifdef HAVE_OPENPAM static int pwrap_openpam_start(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh) { int rv; char fullpath[1024]; rv = openpam_set_feature(OPENPAM_RESTRICT_SERVICE_NAME, 0); if (rv != PAM_SUCCESS) { PWRAP_LOG(PWRAP_LOG_ERROR, "Cannot disable OPENPAM_RESTRICT_SERVICE_NAME"); return rv; } rv = openpam_set_feature(OPENPAM_RESTRICT_MODULE_NAME, 0); if (rv != PAM_SUCCESS) { PWRAP_LOG(PWRAP_LOG_ERROR, "Cannot disable OPENPAM_RESTRICT_MODULE_NAME"); return rv; } rv = openpam_set_feature(OPENPAM_VERIFY_MODULE_FILE, 0); if (rv != PAM_SUCCESS) { PWRAP_LOG(PWRAP_LOG_ERROR, "Cannot disable OPENPAM_VERIFY_MODULE_FILE"); return rv; } rv = openpam_set_feature(OPENPAM_VERIFY_POLICY_FILE, 0); if (rv != PAM_SUCCESS) { PWRAP_LOG(PWRAP_LOG_ERROR, "Cannot disable OPENPAM_VERIFY_POLICY_FILE"); return rv; } snprintf(fullpath, sizeof(fullpath), "%s/%s", pwrap.config_dir, service_name); return libpam_pam_start(fullpath, user, pam_conversation, pamh); } #endif static int pwrap_pam_start(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh) { int rc; pwrap_init(); PWRAP_LOG(PWRAP_LOG_TRACE, "pam_start service=%s, user=%s", service_name, user); #if defined(HAVE_OPENPAM) rc = pwrap_openpam_start(service_name, user, pam_conversation, pamh); #elif defined (HAVE_PAM_START_CONFDIR) rc = libpam_pam_start_confdir(service_name, user, pam_conversation, pwrap.config_dir, pamh); #else rc = libpam_pam_start(service_name, user, pam_conversation, pamh); #endif PWRAP_LOG(PWRAP_LOG_TRACE, "pam_start rc=%d", rc); return rc; } int pam_start(const char *service_name, const char *user, const struct pam_conv *pam_conversation, pam_handle_t **pamh) { return pwrap_pam_start(service_name, user, pam_conversation, pamh); } static int pwrap_pam_end(pam_handle_t *pamh, int pam_status) { PWRAP_LOG(PWRAP_LOG_TRACE, "pam_end status=%d", pam_status); return libpam_pam_end(pamh, pam_status); } int pam_end(pam_handle_t *pamh, int pam_status) { return pwrap_pam_end(pamh, pam_status); } static int pwrap_pam_authenticate(pam_handle_t *pamh, int flags) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_pam_authenticate flags=%d", flags); return libpam_pam_authenticate(pamh, flags); } int pam_authenticate(pam_handle_t *pamh, int flags) { return pwrap_pam_authenticate(pamh, flags); } static int pwrap_pam_chauthtok(pam_handle_t *pamh, int flags) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_pam_chauthtok flags=%d", flags); return libpam_pam_chauthtok(pamh, flags); } int pam_chauthtok(pam_handle_t *pamh, int flags) { return pwrap_pam_chauthtok(pamh, flags); } static int pwrap_pam_acct_mgmt(pam_handle_t *pamh, int flags) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_pam_acct_mgmt flags=%d", flags); return libpam_pam_acct_mgmt(pamh, flags); } int pam_acct_mgmt(pam_handle_t *pamh, int flags) { return pwrap_pam_acct_mgmt(pamh, flags); } static int pwrap_pam_putenv(pam_handle_t *pamh, const char *name_value) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_putenv name_value=%s", name_value); return libpam_pam_putenv(pamh, name_value); } int pam_putenv(pam_handle_t *pamh, const char *name_value) { return pwrap_pam_putenv(pamh, name_value); } static const char *pwrap_pam_getenv(pam_handle_t *pamh, const char *name) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_getenv name=%s", name); return libpam_pam_getenv(pamh, name); } const char *pam_getenv(pam_handle_t *pamh, const char *name) { return pwrap_pam_getenv(pamh, name); } static char **pwrap_pam_getenvlist(pam_handle_t *pamh) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_getenvlist called"); return libpam_pam_getenvlist(pamh); } char **pam_getenvlist(pam_handle_t *pamh) { return pwrap_pam_getenvlist(pamh); } static int pwrap_pam_open_session(pam_handle_t *pamh, int flags) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_pam_open_session flags=%d", flags); return libpam_pam_open_session(pamh, flags); } int pam_open_session(pam_handle_t *pamh, int flags) { return pwrap_pam_open_session(pamh, flags); } static int pwrap_pam_close_session(pam_handle_t *pamh, int flags) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_pam_close_session flags=%d", flags); return libpam_pam_close_session(pamh, flags); } int pam_close_session(pam_handle_t *pamh, int flags) { return pwrap_pam_close_session(pamh, flags); } static int pwrap_pam_setcred(pam_handle_t *pamh, int flags) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_pam_setcred flags=%d", flags); return libpam_pam_setcred(pamh, flags); } int pam_setcred(pam_handle_t *pamh, int flags) { return pwrap_pam_setcred(pamh, flags); } static const char *pwrap_get_service(const char *libpam_service) { #ifdef HAVE_OPENPAM const char *service_name; PWRAP_LOG(PWRAP_LOG_TRACE, "internal PAM_SERVICE=%s", libpam_service); service_name = strrchr(libpam_service, '/'); if (service_name != NULL && service_name[0] == '/') { service_name++; } PWRAP_LOG(PWRAP_LOG_TRACE, "PAM_SERVICE=%s", service_name); return service_name; #else return libpam_service; #endif } static int pwrap_pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) { int rc; const char *svc; PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item called"); rc = libpam_pam_get_item(pamh, item_type, item); if (rc == PAM_SUCCESS) { switch(item_type) { case PAM_USER: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_USER=%s", (const char *)*item); break; case PAM_SERVICE: svc = pwrap_get_service((const char *) *item); PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_SERVICE=%s", svc); *item = svc; break; case PAM_USER_PROMPT: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_USER_PROMPT=%s", (const char *)*item); break; case PAM_TTY: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_TTY=%s", (const char *)*item); break; case PAM_RUSER: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_RUSER=%s", (const char *)*item); break; case PAM_RHOST: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_RHOST=%s", (const char *)*item); break; case PAM_AUTHTOK: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_AUTHTOK=%s", (const char *)*item); break; case PAM_OLDAUTHTOK: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_OLDAUTHTOK=%s", (const char *)*item); break; case PAM_CONV: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item PAM_CONV=%p", (const void *)*item); break; default: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item item_type=%d item=%p", item_type, (const void *)*item); break; } } else { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_item failed rc=%d", rc); } return rc; } int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item) { return pwrap_pam_get_item(pamh, item_type, item); } static int pwrap_pam_set_item(pam_handle_t *pamh, int item_type, const void *item) { int rc; PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item called"); rc = libpam_pam_set_item(pamh, item_type, item); if (rc == PAM_SUCCESS) { switch(item_type) { case PAM_USER: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_USER=%s", (const char *)item); break; case PAM_SERVICE: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_SERVICE=%s", (const char *)item); break; case PAM_USER_PROMPT: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_USER_PROMPT=%s", (const char *)item); break; case PAM_TTY: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_TTY=%s", (const char *)item); break; case PAM_RUSER: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_RUSER=%s", (const char *)item); break; case PAM_RHOST: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_RHOST=%s", (const char *)item); break; case PAM_AUTHTOK: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_AUTHTOK=%s", (const char *)item); break; case PAM_OLDAUTHTOK: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_OLDAUTHTOK=%s", (const char *)item); break; case PAM_CONV: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item PAM_CONV=%p", item); break; default: PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item item_type=%d item=%p", item_type, item); break; } } else { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_item failed rc=%d", rc); } return rc; } int pam_set_item(pam_handle_t *pamh, int item_type, const void *item) { return pwrap_pam_set_item(pamh, item_type, item); } static int pwrap_pam_get_data(const pam_handle_t *pamh, const char *module_data_name, const void **data) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_get_data module_data_name=%s", module_data_name); return libpam_pam_get_data(pamh, module_data_name, data); } int pam_get_data(const pam_handle_t *pamh, const char *module_data_name, const void **data) { return pwrap_pam_get_data(pamh, module_data_name, data); } static int pwrap_pam_set_data(pam_handle_t *pamh, const char *module_data_name, void *data, void (*cleanup)(pam_handle_t *pamh, void *data, int error_status)) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_set_data module_data_name=%s data=%p", module_data_name, data); return libpam_pam_set_data(pamh, module_data_name, data, cleanup); } int pam_set_data(pam_handle_t *pamh, const char *module_data_name, void *data, void (*cleanup)(pam_handle_t *pamh, void *data, int error_status)) { return pwrap_pam_set_data(pamh, module_data_name, data, cleanup); } #ifdef HAVE_PAM_VPROMPT_CONST static int pwrap_pam_vprompt(const pam_handle_t *pamh, #else static int pwrap_pam_vprompt(pam_handle_t *pamh, #endif int style, char **response, const char *fmt, va_list args) { PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_pam_vprompt style=%d", style); return libpam_pam_vprompt(discard_const_p(pam_handle_t, pamh), style, response, fmt, args); } #ifdef HAVE_PAM_VPROMPT_CONST int pam_vprompt(const pam_handle_t *pamh, int style, char **response, const char *fmt, va_list args) #else int pam_vprompt(pam_handle_t *pamh, int style, char **response, const char *fmt, va_list args) #endif { return pwrap_pam_vprompt(discard_const_p(pam_handle_t, pamh), style, response, fmt, args); } #ifdef HAVE_PAM_PROMPT_CONST int pam_prompt(const pam_handle_t *pamh, int style, char **response, const char *fmt, ...) #else int pam_prompt(pam_handle_t *pamh, int style, char **response, const char *fmt, ...) #endif { va_list args; int rv; va_start(args, fmt); rv = pwrap_pam_vprompt(discard_const_p(pam_handle_t, pamh), style, response, fmt, args); va_end(args); return rv; } #ifdef HAVE_PAM_STRERROR_CONST static const char *pwrap_pam_strerror(const pam_handle_t *pamh, int errnum) #else static const char *pwrap_pam_strerror(pam_handle_t *pamh, int errnum) #endif { const char *str; pwrap_init(); PWRAP_LOG(PWRAP_LOG_TRACE, "pam_strerror errnum=%d", errnum); str = libpam_pam_strerror(discard_const_p(pam_handle_t, pamh), errnum); PWRAP_LOG(PWRAP_LOG_TRACE, "pam_strerror error=%s", str); return str; } #ifdef HAVE_PAM_STRERROR_CONST const char *pam_strerror(const pam_handle_t *pamh, int errnum) #else const char *pam_strerror(pam_handle_t *pamh, int errnum) #endif { return pwrap_pam_strerror(discard_const_p(pam_handle_t, pamh), errnum); } #if defined(HAVE_PAM_VSYSLOG) || defined(HAVE_PAM_SYSLOG) static void pwrap_pam_vsyslog(const pam_handle_t *pamh, int priority, const char *fmt, va_list args) PRINTF_ATTRIBUTE(3, 0); static void pwrap_pam_vsyslog(const pam_handle_t *pamh, int priority, const char *fmt, va_list args) { const char *d; char syslog_str[32] = {0}; enum pwrap_dbglvl_e dbglvl = PWRAP_LOG_TRACE; PWRAP_LOG(PWRAP_LOG_TRACE, "pwrap_pam_vsyslog called"); #ifdef HAVE_PAM_VSYSLOG d = getenv("PAM_WRAPPER_USE_SYSLOG"); if (d != NULL && d[0] == '1') { libpam_pam_vsyslog(pamh, priority, fmt, args); return; } #endif /* HAVE_PAM_VSYSLOG */ switch(priority) { case 0: /* LOG_EMERG */ case 1: /* LOG_ALERT */ case 2: /* LOG_CRIT */ case 3: /* LOG_ERR */ dbglvl = PWRAP_LOG_ERROR; break; case 4: /* LOG_WARN */ dbglvl = PWRAP_LOG_WARN; break; case 5: /* LOG_NOTICE */ case 6: /* LOG_INFO */ case 7: /* LOG_DEBUG */ dbglvl = PWRAP_LOG_DEBUG; break; default: dbglvl = PWRAP_LOG_TRACE; break; } snprintf(syslog_str, sizeof(syslog_str), "SYSLOG(%d)", priority); pwrap_vlog(dbglvl, syslog_str, fmt, args); } #endif /* defined(HAVE_PAM_VSYSLOG) || defined(HAVE_PAM_SYSLOG) */ #ifdef HAVE_PAM_VSYSLOG void pam_vsyslog(const pam_handle_t *pamh, int priority, const char *fmt, va_list args) { pwrap_pam_vsyslog(pamh, priority, fmt, args); } #endif #ifdef HAVE_PAM_SYSLOG void pam_syslog(const pam_handle_t *pamh, int priority, const char *fmt, ...) { va_list args; va_start(args, fmt); pwrap_pam_vsyslog(pamh, priority, fmt, args); va_end(args); } #endif /* This might be called by pam_end() running with sshd */ int audit_open(void); int audit_open(void) { /* * Tell the application that the kernel doesn't * have audit compiled in. */ errno = EPROTONOSUPPORT; return -1; } /* Disable BSD auditing */ int cannot_audit(int x); int cannot_audit(int x) { (void) x; return 1; } /**************************** * CONSTRUCTOR ***************************/ /* * Handler executed before fork(2) processing starts. */ static void pwrap_thread_prepare(void) { } /* * Handler that is executed in the parent process after fork(2) processing * completes. */ static void pwrap_thread_parent(void) { } /* * Handler that is executed in the child process after fork(2) processing * completes. */ static void pwrap_thread_child(void) { pwrap.initialised = false; } void pwrap_constructor(void) { /* * If we hold a lock and the application forks, then the child * is not able to unlock the mutex and we are in a deadlock. * This should prevent such deadlocks. */ pthread_atfork(&pwrap_thread_prepare, &pwrap_thread_parent, &pwrap_thread_child); /* * Here is safe place to call pwrap_init() and initialize data * for main process. */ pwrap_init(); } /**************************** * DESTRUCTOR ***************************/ static int p_rmdirs_at(const char *path, int parent_fd) { DIR *d = NULL; struct dirent *dp = NULL; struct stat sb; char fd_str[64] = { 0 }; int path_fd; int rc; switch(parent_fd) { case AT_FDCWD: snprintf(fd_str, sizeof(fd_str), "CWD"); break; default: snprintf(fd_str, sizeof(fd_str), "fd=%d", parent_fd); break; } /* If path is absolute, parent_fd is ignored. */ PWRAP_LOG(PWRAP_LOG_TRACE, "p_rmdirs_at removing %s at %s\n", path, fd_str); path_fd = openat(parent_fd, path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); if (path_fd == -1) { return -1; } d = fdopendir(path_fd); if (d == NULL) { close(path_fd); return -1; } while ((dp = readdir(d)) != NULL) { /* skip '.' and '..' */ if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) { continue; } rc = fstatat(path_fd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW); if (rc != 0) { continue; } if (S_ISDIR(sb.st_mode)) { rc = p_rmdirs_at(dp->d_name, path_fd); } else { rc = unlinkat(path_fd, dp->d_name, 0); } if (rc != 0) { continue; } } closedir(d); rc = unlinkat(parent_fd, path, AT_REMOVEDIR); if (rc != 0) { rc = errno; PWRAP_LOG(PWRAP_LOG_TRACE, "cannot unlink %s error %d\n", path, rc); return -1; } return 0; } static int p_rmdirs(const char *path) { /* * If path is absolute, p_rmdirs_at ignores parent_fd. * If it's relative, start from cwd. */ return p_rmdirs_at(path, AT_FDCWD); } /* * This function is called when the library is unloaded and makes sure that * resources are freed. */ void pwrap_destructor(void) { const char *env; PWRAP_LOG(PWRAP_LOG_TRACE, "entering pwrap_destructor"); if (pwrap.libpam.handle != NULL) { dlclose(pwrap.libpam.handle); } if (pwrap.libpam_so != NULL) { free(pwrap.libpam_so); pwrap.libpam_so = NULL; } if (!pwrap.initialised) { return; } pwrap.initialised = false; PWRAP_LOG(PWRAP_LOG_TRACE, "destructor called for pam_wrapper dir %s", pwrap.config_dir); env = getenv("PAM_WRAPPER_KEEP_DIR"); if (env == NULL || env[0] != '1') { p_rmdirs(pwrap.config_dir); } if (pwrap.config_dir != NULL) { free(pwrap.config_dir); pwrap.config_dir = NULL; } } pam_wrapper-1.1.4/libpamtest-build-tree-settings.cmake.in000644 001750 000144 00000000064 12632316172 023460 0ustar00asnusers000000 000000 set(LIBSSH_INLUDE_DIR @PROJECT_SOURCE_DIR@/include) pam_wrapper-1.1.4/CTestConfig.cmake000644 001750 000144 00000000430 13341212715 017160 0ustar00asnusers000000 000000 set(UPDATE_TYPE "true") set(CTEST_PROJECT_NAME "pam_wrapper") set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") set(CTEST_DROP_METHOD "https") set(CTEST_DROP_SITE "test.cmocka.org") set(CTEST_DROP_LOCATION "/submit.php?project=${CTEST_PROJECT_NAME}") set(CTEST_DROP_SITE_CDASH TRUE) pam_wrapper-1.1.4/CHANGELOG000644 001750 000144 00000003763 14136460515 015242 0ustar00asnusers000000 000000 ChangeLog ========== version 1.1.4 (released 2020-10-28) * NOTE: pam_wrapper stopped working with the latest OpenPAM on FreeBSD 12. Help is needed to add back support. * Added support to retrieve the PAM environment from a python's PAMTEST_GETENVLIST * Added a new keyword parameter to reuse the PAM handle in libpamtest * Fixed pid range * Fixed constructor/destructor on AIX version 1.1.3 (released 2020-03-26) * Fixed paths in pkgconfig and cmake config files version 1.1.2 (released 2020-03-24) * Fix manpage installation version 1.1.1 (released 2020-03-24) * Fixed pam_wrapper on Ubuntu * Improved PAM detection for openSUSE Tumbleweed version 1.1.0 (released 2020-03-20) * Added support for pam_start_confdir() * Added pam_chatty module * Added gitlab CI support * Fixed crash when a PAM module outputs too much data version 1.0.7 (released 2018-09-26) * Added support for running with AddressSanitizer * Added logging for pam_set_items module * Fixed building python modules * Fixed pam conversation in libpamtest version 1.0.6 (released 2018-03-27) * Improved file copy * Fixed build warnings version 1.0.5 (released 2018-02-22) * Added support to build python2 and python3 module at the same time * Improved pam test directory creating * Fixed python 2.6 compatibilty * Fixed some build issues on FreeBSD version 1.0.4 (released 2017-05-15) * Fix build on OpenBSD * Fix a resource leak version 1.0.3 (released 2017-04-06) * Fixed some build issues with strict compiler flags * Logging to syslog is disabled by default version 1.0.2 (released 2016-05-24) * Fixed pam_wrapper on some BSDs * Fixed simple conversation in libpamtest version 1.0.1 (released 2016-01-18) * Fixed issue with audit_open() and sshd * Fixed several issues found by Coverity * Fixed python linking issues version 1.0.0 (released 2016-01-14) * Initial release - pam_wrapper - libpamtest - pypamtest - pam_matrix - pam_get_items - pam_set_items pam_wrapper-1.1.4/CompilerChecks.cmake000644 001750 000144 00000012531 14136460515 017716 0ustar00asnusers000000 000000 include(AddCCompilerFlag) include(CheckCCompilerFlagSSP) if (UNIX) # # Check for -Werror turned on if possible # # This will prevent that compiler flags are detected incorrectly. # check_c_compiler_flag("-Werror" REQUIRED_FLAGS_WERROR) if (REQUIRED_FLAGS_WERROR) set(CMAKE_REQUIRED_FLAGS "-Werror") if (PICKY_DEVELOPER) list(APPEND SUPPORTED_COMPILER_FLAGS "-Werror") endif() endif() add_c_compiler_flag("-std=gnu99" SUPPORTED_COMPILER_FLAGS) #add_c_compiler_flag("-Wpedantic" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wall" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wshadow" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wmissing-prototypes" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wcast-align" SUPPORTED_COMPILER_FLAGS) #add_c_compiler_flag("-Wcast-qual" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=address" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wstrict-prototypes" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=strict-prototypes" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wwrite-strings" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=write-strings" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror-implicit-function-declaration" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wpointer-arith" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=pointer-arith" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wdeclaration-after-statement" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=declaration-after-statement" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wreturn-type" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=return-type" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wuninitialized" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=uninitialized" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wimplicit-fallthrough" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=strict-overflow" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wstrict-overflow=2" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wno-format-zero-length" SUPPORTED_COMPILER_FLAGS) check_c_compiler_flag("-Wformat" REQUIRED_FLAGS_WFORMAT) if (REQUIRED_FLAGS_WFORMAT) list(APPEND SUPPORTED_COMPILER_FLAGS "-Wformat") set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Wformat") endif() add_c_compiler_flag("-Wformat-security" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Werror=format-security" SUPPORTED_COMPILER_FLAGS) # Allow zero for a variadic macro argument string(TOLOWER "${CMAKE_C_COMPILER_ID}" _C_COMPILER_ID) if ("${_C_COMPILER_ID}" STREQUAL "clang") add_c_compiler_flag("-Wno-gnu-zero-variadic-macro-arguments" SUPPORTED_COMPILER_FLAGS) endif() add_c_compiler_flag("-fno-common" SUPPORTED_COMPILER_FLAGS) if (CMAKE_BUILD_TYPE) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if (CMAKE_BUILD_TYPE_LOWER MATCHES (release|relwithdebinfo|minsizerel)) add_c_compiler_flag("-Wp,-D_FORTIFY_SOURCE=2" SUPPORTED_COMPILER_FLAGS) endif() endif() if (NOT SOLARIS) check_c_compiler_flag_ssp("-fstack-protector-strong" WITH_STACK_PROTECTOR_STRONG) if (WITH_STACK_PROTECTOR_STRONG) list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-protector-strong") # This is needed as Solaris has a seperate libssp if (SOLARIS) list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector-strong") endif() else (WITH_STACK_PROTECTOR_STRONG) check_c_compiler_flag_ssp("-fstack-protector" WITH_STACK_PROTECTOR) if (WITH_STACK_PROTECTOR) list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-protector") # This is needed as Solaris has a seperate libssp if (SOLARIS) list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector") endif() endif() endif (WITH_STACK_PROTECTOR_STRONG) check_c_compiler_flag_ssp("-fstack-clash-protection" WITH_STACK_CLASH_PROTECTION) if (WITH_STACK_CLASH_PROTECTION) list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-clash-protection") endif() endif() if (PICKY_DEVELOPER) add_c_compiler_flag("-Wno-error=deprecated-declarations" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("-Wno-error=tautological-compare" SUPPORTED_COMPILER_FLAGS) endif() # Needed by src/python/CMakeLists.txt check_c_compiler_flag("-Wno-cast-function-type" WITH_WNO_CAST_FUNCTION_TYPE) # Unset CMAKE_REQUIRED_FLAGS unset(CMAKE_REQUIRED_FLAGS) endif() if (MSVC) add_c_compiler_flag("/D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("/D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("/D _CRT_NONSTDC_NO_WARNINGS=1" SUPPORTED_COMPILER_FLAGS) add_c_compiler_flag("/D _CRT_SECURE_NO_WARNINGS=1" SUPPORTED_COMPILER_FLAGS) endif() if (SUPPORTED_COMPILER_FLAGS) set(DEFAULT_C_COMPILE_FLAGS ${SUPPORTED_COMPILER_FLAGS} CACHE INTERNAL "Default C Compiler Flags" FORCE) endif() if (SUPPORTED_LINKER_FLAGS) set(DEFAULT_LINK_FLAGS ${SUPPORTED_LINKER_FLAGS} CACHE INTERNAL "Default C Linker Flags" FORCE) endif() pam_wrapper-1.1.4/tests/000755 001750 000144 00000000000 14136460614 015161 5ustar00asnusers000000 000000 pam_wrapper-1.1.4/tests/services/000755 001750 000144 00000000000 14136460614 017004 5ustar00asnusers000000 000000 pam_wrapper-1.1.4/tests/services/matrix_opt.in000644 001750 000144 00000000107 12632316172 021516 0ustar00asnusers000000 000000 auth required @PAM_MATRIX_PATH@ passdb=@PASSDB_RO_PATH@ verbose echo pam_wrapper-1.1.4/tests/services/matrix.in000644 001750 000144 00000000215 12632316172 020634 0ustar00asnusers000000 000000 auth required @PAM_MATRIX_PATH@ account required @PAM_MATRIX_PATH@ password required @PAM_MATRIX_PATH@ session required @PAM_MATRIX_PATH@ pam_wrapper-1.1.4/tests/services/chatty.in000644 001750 000144 00000000071 13635146323 020627 0ustar00asnusers000000 000000 auth required @PAM_CHATTY_PATH@ num_lines=16 info error pam_wrapper-1.1.4/tests/services/matrix_py.in000644 001750 000144 00000000365 12632316172 021352 0ustar00asnusers000000 000000 auth required @PAM_MATRIX_PATH@ verbose passdb=@PASSDB_PY_PATH@ account required @PAM_MATRIX_PATH@ passdb=@PASSDB_PY_PATH@ session required @PAM_MATRIX_PATH@ passdb=@PASSDB_PY_PATH@ password required @PAM_MATRIX_PATH@ passdb=@PASSDB_PY_PATH@ pam_wrapper-1.1.4/tests/services/pwrap_get_set.in000644 001750 000144 00000000232 12632316172 022172 0ustar00asnusers000000 000000 session required @CMAKE_CURRENT_BINARY_DIR@/../src/modules/pam_set_items.so session required @CMAKE_CURRENT_BINARY_DIR@/../src/modules/pam_get_items.so pam_wrapper-1.1.4/tests/passdb_py000644 001750 000144 00000000140 12632316172 017061 0ustar00asnusers000000 000000 # The format is username:password:allowed_pam_svc neo:secret:matrix_py trinity:secret:matrix_py pam_wrapper-1.1.4/tests/CMakeLists.txt000644 001750 000144 00000007245 13635146323 017732 0ustar00asnusers000000 000000 project(tests C) set(PAM_MATRIX_PATH "${CMAKE_BINARY_DIR}/src/modules/pam_matrix.so") configure_file(services/matrix.in ${CMAKE_CURRENT_BINARY_DIR}/services/matrix @ONLY) # Some tests use a passdb as argument for pam_matrix set(PASSDB_RO_PATH ${CMAKE_CURRENT_BINARY_DIR}/passdb_ro) configure_file(passdb_ro ${PASSDB_RO_PATH} @ONLY) configure_file(services/matrix_opt.in ${CMAKE_CURRENT_BINARY_DIR}/services/matrix_opt @ONLY) set(PASSDB_PY_PATH ${CMAKE_CURRENT_BINARY_DIR}/passdb_py) configure_file(passdb_py ${PASSDB_PY_PATH} @ONLY) configure_file(services/matrix_py.in ${CMAKE_CURRENT_BINARY_DIR}/services/matrix_py @ONLY) configure_file(services/pwrap_get_set.in ${CMAKE_CURRENT_BINARY_DIR}/services/pwrap_get_set @ONLY) set(PAM_CHATTY_PATH "${CMAKE_BINARY_DIR}/src/modules/pam_chatty.so") configure_file(services/chatty.in ${CMAKE_CURRENT_BINARY_DIR}/services/chatty @ONLY) function(ADD_CMOCKA_TEST_ENVIRONMENT _TEST_NAME) if (CMAKE_BUILD_TYPE) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if (CMAKE_BUILD_TYPE_LOWER STREQUAL "addresssanitizer") find_library(ASAN_LIBRARY NAMES asan) if (NOT ASAN_LIBRARY) foreach(version RANGE 10 1) if (NOT ASAN_LIBRARY) find_library(ASAN_LIBRARY libasan.so.${version}) endif() endforeach() endif() endif() endif() if (ASAN_LIBRARY) list(APPEND PRELOAD_LIBRARIES ${ASAN_LIBRARY}) endif() list(APPEND PRELOAD_LIBRARIES ${PAM_WRAPPER_LOCATION}) if (OSX) set(TORTURE_ENVIRONMENT "DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${RESOLV_WRAPPER_LOCATION}:${SOCKET_WRAPPER_LIBRARY}") else () string(REPLACE ";" ":" _TMP_ENV "${PRELOAD_LIBRARIES}") set(TORTURE_ENVIRONMENT "LD_PRELOAD=${_TMP_ENV}") endif() list(APPEND TORTURE_ENVIRONMENT PAM_WRAPPER=1) list(APPEND TORTURE_ENVIRONMENT PAM_WRAPPER_SERVICE_DIR=${CMAKE_CURRENT_BINARY_DIR}/services) foreach(_arg ${ARGN}) list(APPEND TORTURE_ENVIRONMENT ${_arg}) endforeach() set_property(TEST ${_TEST_NAME} PROPERTY ENVIRONMENT "${TORTURE_ENVIRONMENT}") endfunction() set(PAM_LIBRARIES pam) if (HAVE_PAM_MISC) list(APPEND PAM_LIBRARIES pam_misc) endif (HAVE_PAM_MISC) set(TEST_LIBRARIES pamtest ${CMOCKA_LIBRARY} ${PAM_LIBRARIES} ) add_cmocka_test(test_pam_wrapper SOURCES test_pam_wrapper.c COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} LINK_LIBRARIES ${TEST_LIBRARIES}) target_include_directories(test_pam_wrapper PRIVATE ${CMOCKA_INCLUDE_DIR} ${pam_wrapper_BINARY_DIR}) add_cmocka_test_environment(test_pam_wrapper) # Do not run python tests with AddressSanitizer set(RUN_PYTHON_TESTS ON) if (CMAKE_BUILD_TYPE) string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if (CMAKE_BUILD_TYPE_LOWER STREQUAL "addresssanitizer") set(RUN_PYTHON_TESTS OFF) endif() endif() if (RUN_PYTHON_TESTS) if (PYTHON2_EXECUTABLE) add_test(NAME py2pamtest_test COMMAND ${PYTHON2_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/pypamtest_test.py) add_cmocka_test_environment(py2pamtest_test) endif() if (PYTHON3_EXECUTABLE) add_test(NAME py3pamtest_test COMMAND ${PYTHON3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/pypamtest_test.py) add_cmocka_test_environment(py3pamtest_test) endif() endif() pam_wrapper-1.1.4/tests/pypamtest_test.py000755 001750 000144 00000015266 14033256532 020633 0ustar00asnusers000000 000000 #!/usr/bin/env python import unittest import os import sys import os.path import platform class PyPamTestCase(unittest.TestCase): def assertPamTestResultEqual(self, test_result, err_list, info_list): self.assertTrue(test_result != None) self.assertTrue(hasattr(test_result, 'info')) self.assertTrue(hasattr(test_result, 'errors')) self.assertSequenceEqual(test_result.info, err_list) self.assertSequenceEqual(test_result.errors, info_list) class PyPamTestImport(unittest.TestCase): def setUp(self): " Make sure we load the in-tree module " if sys.hexversion >= 0x3000000: self.modpath = os.path.join(os.getcwd(), "../src/python/python3") else: self.modpath = os.path.join(os.getcwd(), "../src/python/python2") self.system_path = sys.path[:] sys.path = [ self.modpath ] def tearDown(self): " Restore the system path " sys.path = self.system_path def testImport(self): " Import the module " try: import pypamtest except ImportError as e: print("Could not load the pypamtest module from %s. Please check if it is compiled" % self.modpath) raise e class PyPamTestTestCase(unittest.TestCase): def test_constants(self): " Tests the enum was added correctly " self.assertTrue(hasattr(pypamtest, 'PAMTEST_AUTHENTICATE')) self.assertTrue(hasattr(pypamtest, 'PAMTEST_SETCRED')) self.assertTrue(hasattr(pypamtest, 'PAMTEST_ACCOUNT')) self.assertTrue(hasattr(pypamtest, 'PAMTEST_OPEN_SESSION')) self.assertTrue(hasattr(pypamtest, 'PAMTEST_CLOSE_SESSION')) self.assertTrue(hasattr(pypamtest, 'PAMTEST_CHAUTHTOK')) self.assertTrue(hasattr(pypamtest, 'PAMTEST_GETENVLIST')) self.assertTrue(hasattr(pypamtest, 'PAMTEST_KEEPHANDLE')) def test_members(self): tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE) self.assertEqual(tc.pam_operation, pypamtest.PAMTEST_AUTHENTICATE) self.assertEqual(tc.expected_rv, 0) # PAM_SUCCESS self.assertEqual(tc.flags, 0) tc = pypamtest.TestCase(pypamtest.PAMTEST_CHAUTHTOK, 1, 2) self.assertEqual(tc.pam_operation, pypamtest.PAMTEST_CHAUTHTOK) self.assertEqual(tc.expected_rv, 1) self.assertEqual(tc.flags, 2) # Testcase members should be immutable after constructing the test # case # with self.assertRaises(AttributeError): # tc.pam_operation = pypamtest.PAMTEST_AUTHENTICATE # # with self.assertRaises(AttributeError): # tc.expected_rv = 2 # # with self.assertRaises(AttributeError): # tc.flags = 3 def test_bad_op(self): self.assertRaises(ValueError, pypamtest.TestCase, 666) # These are not silly tests. They test setup of the object and proper # GC function class PyPamTestTestResult(PyPamTestCase): def setUp(self): self.list_info = [ "info", "list" ] self.list_error = [ "error", "list" ] def test_default(self): res = pypamtest.TestResult() self.assertPamTestResultEqual(res, [], []) def test_set_both(self): res = pypamtest.TestResult(self.list_info, self.list_error) self.assertPamTestResultEqual(res, self.list_info, self.list_error) def test_repr_default(self): res = pypamtest.TestResult() self.assertEqual(repr(res), "{ errors: { } infos: { } }") def test_repr_both(self): res = pypamtest.TestResult(self.list_info, self.list_error) self.assertEqual(repr(res), "{ errors: { {info}{list} } infos: { {info}{list} } }") class PyPamTestRunTest(unittest.TestCase): def __init__(self, *args, **kwargs): super(PyPamTestRunTest, self).__init__(*args, **kwargs) self.PAM_AUTH_ERR = 7 if ("BSD" in platform.system()): self.PAM_AUTH_ERR = 9 def test_run(self): neo_password = "secret" tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE) res = pypamtest.run_pamtest("neo", "matrix_py", [tc], [ neo_password ]) # No messages from this test -> both info and err should be empty tuples self.assertTrue(res != None) self.assertTrue(hasattr(res, 'info')) self.assertTrue(hasattr(res, 'errors')) # Running with verbose mode so there would be an info message self.assertSequenceEqual(res.info, (u'Authentication succeeded',)) self.assertSequenceEqual(res.errors, ()) def test_run_failed_auth(self): neo_password = "not-the-secret" tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE, expected_rv=self.PAM_AUTH_ERR) res = pypamtest.run_pamtest("neo", "matrix_py", [tc], [ neo_password ]) def test_run_chatty_auth(self): neo_password = "secret" tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE) res = pypamtest.run_pamtest("neo", "chatty", [tc], [ neo_password ]) def test_repr(self): tc = pypamtest.TestCase(pypamtest.PAMTEST_CHAUTHTOK, 1, 2) r = repr(tc) self.assertEqual(r, "{ pam_operation [5] expected_rv [1] flags [2] }") def test_exception(self): neo_password = "wrong_secret" tc = pypamtest.TestCase(pypamtest.PAMTEST_AUTHENTICATE) self.assertRaisesRegexp(pypamtest.PamTestError, "Error \[2\]: Test case { pam_operation \[0\] " "expected_rv \[0\] flags \[0\] } " "returned \[\d\]", pypamtest.run_pamtest, "neo", "matrix_py", [tc], [ neo_password ]) if __name__ == "__main__": error = 0 suite = unittest.TestLoader().loadTestsFromTestCase(PyPamTestImport) res = unittest.TextTestRunner().run(suite) if not res.wasSuccessful(): error |= 0x1 # need to bail out here because module could not be imported sys.exit(error) sys.path.insert(0, os.path.join(os.getcwd())) import pypamtest suite = unittest.TestLoader().loadTestsFromTestCase(PyPamTestTestCase) res = unittest.TextTestRunner().run(suite) if not res.wasSuccessful(): error |= 0x2 suite = unittest.TestLoader().loadTestsFromTestCase(PyPamTestTestResult) res = unittest.TextTestRunner().run(suite) if not res.wasSuccessful(): error |= 0x3 suite = unittest.TestLoader().loadTestsFromTestCase(PyPamTestRunTest) res = unittest.TextTestRunner().run(suite) if not res.wasSuccessful(): error |= 0x4 sys.exit(error) pam_wrapper-1.1.4/tests/passdb_ro000644 001750 000144 00000000125 12632316172 017054 0ustar00asnusers000000 000000 # The format is username:password:allowed_pam_svc trinity_ro:secret_ro:pwrap_pam_opt pam_wrapper-1.1.4/tests/test_pam_wrapper.c000644 001750 000144 00000061604 14136460515 020710 0ustar00asnusers000000 000000 /* * Copyright (c) 2015 Andreas Schneider * Copyright (c) 2015 Jakub Hrozek * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SECURITY_PAM_APPL_H #include #endif #ifdef HAVE_SECURITY_PAM_MODULES_H #include #endif #ifdef HAVE_SECURITY_PAM_EXT_H #include #endif #include "pwrap_compat.h" #include "libpamtest.h" /* GCC have printf type attribute check. */ #ifdef HAVE_FUNCTION_ATTRIBUTE_FORMAT #define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) #else #define PRINTF_ATTRIBUTE(a,b) #endif /* HAVE_FUNCTION_ATTRIBUTE_FORMAT */ #ifndef ZERO_STRUCT #define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) #endif struct pwrap_test_ctx { struct pam_conv conv; pam_handle_t *ph; }; struct pwrap_conv_data { const char **authtoks; size_t authtok_index; }; static int pwrap_conv(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) { int i; struct pam_response *reply; const char *password; size_t pwlen; struct pwrap_conv_data *cdata = (struct pwrap_conv_data *) appdata_ptr; if (cdata == NULL) { return PAM_CONV_ERR; } reply = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response)); if (reply == NULL) { return PAM_CONV_ERR; } for (i=0; i < num_msg; i++) { switch (msgm[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: password = (const char *) cdata->authtoks[cdata->authtok_index]; if (password == NULL) { free(reply); return PAM_CONV_ERR; } pwlen = strlen(password) + 1; cdata->authtok_index++; reply[i].resp = calloc(pwlen, sizeof(char)); if (reply[i].resp == NULL) { free(reply); return PAM_CONV_ERR; } memcpy(reply[i].resp, password, pwlen); break; default: continue; } } *response = reply; return PAM_SUCCESS; } static int setup_passdb(void **state) { int rv; const char *db; FILE *fp = NULL; char passdb_path[PATH_MAX]; (void) state; /* unused */ db = getcwd(passdb_path, PATH_MAX); assert_non_null(db); assert_true(strlen(passdb_path) + sizeof("/passdb") < PATH_MAX); db = strncat(passdb_path, "/passdb", strlen("/passdb") + 1); rv = setenv("PAM_MATRIX_PASSWD", passdb_path, 1); assert_int_equal(rv, 0); fp = fopen(db, "w"); assert_non_null(fp); fprintf(fp, "trinity:secret:matrix\n"); fprintf(fp, "neo:secret:pwrap_wrong_svc"); fflush(fp); fclose(fp); return 0; } static int teardown_passdb(void **state) { const char *db; (void) state; /* unused */ db = getenv("PAM_MATRIX_PASSWD"); assert_non_null(db); unlink(db); /* Don't pollute environment for other tests */ unsetenv("PAM_MATRIX_PASSWD"); return 0; } static int setup_ctx_only(void **state) { struct pwrap_test_ctx *test_ctx; setup_passdb(NULL); test_ctx = malloc(sizeof(struct pwrap_test_ctx)); assert_non_null(test_ctx); test_ctx->conv.conv = pwrap_conv; *state = test_ctx; return 0; } static int setup_noconv(void **state) { struct pwrap_test_ctx *test_ctx; int rv; setup_ctx_only(state); test_ctx = *state; /* We'll get an error if the test module talks to us */ test_ctx->conv.appdata_ptr = NULL; rv = pam_start("matrix", "trinity", &test_ctx->conv, &test_ctx->ph); assert_int_equal(rv, PAM_SUCCESS); *state = test_ctx; return 0; } static int teardown_simple(void **state) { struct pwrap_test_ctx *test_ctx; test_ctx = (struct pwrap_test_ctx *) *state; free(test_ctx); return 0; } static void test_env(void **state) { const char *v; struct stat sb; int ret; (void) state; /* unused */ v = getenv("PAM_WRAPPER_RUNTIME_DIR"); assert_non_null(v); ret = stat(v, &sb); assert_int_not_equal(ret, -1); assert_true(S_ISDIR(sb.st_mode)); } static void test_pam_start(void **state) { int rv; pam_handle_t *ph; struct pwrap_test_ctx *test_ctx; test_ctx = (struct pwrap_test_ctx *) *state; test_ctx->conv.appdata_ptr = (void *) "testpassword"; rv = pam_start("matrix", "trinity", &test_ctx->conv, &ph); assert_int_equal(rv, PAM_SUCCESS); rv = pam_end(ph, PAM_SUCCESS); assert_int_equal(rv, PAM_SUCCESS); } static int teardown(void **state) { struct pwrap_test_ctx *test_ctx; int rv; teardown_passdb(NULL); test_ctx = (struct pwrap_test_ctx *) *state; rv = pam_end(test_ctx->ph, PAM_SUCCESS); assert_int_equal(rv, PAM_SUCCESS); return teardown_simple(state); } static void test_pam_authenticate(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; const char *trinity_authtoks[] = { "secret", NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_off = trinity_authtoks; perr = run_pamtest("matrix", "trinity", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); } static void test_pam_authenticate_null_password(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; const char *empty_authtoks[] = { NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_AUTHENTICATE, PAM_CRED_ERR), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_off = empty_authtoks; perr = run_pamtest("matrix", "trinity", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); } static void test_pam_authenticate_err(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; const char *trinity_authtoks[] = { "wrong_password", NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_off = trinity_authtoks; perr = run_pamtest("matrix", "trinity", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); } static void test_pam_acct(void **state) { enum pamtest_err perr; struct pam_testcase tests[] = { pam_test(PAMTEST_ACCOUNT, PAM_SUCCESS), }; (void) state; /* unused */ perr = run_pamtest("matrix", "trinity", NULL, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); } static void test_pam_acct_err(void **state) { enum pamtest_err perr; struct pam_testcase tests[] = { pam_test(PAMTEST_ACCOUNT, PAM_PERM_DENIED), }; (void) state; /* unused */ perr = run_pamtest("matrix", "neo", NULL, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); } static inline void free_vlist(char **vlist) { size_t i; for (i = 0; vlist[i] != NULL; i++) { free(vlist[i]); } free(vlist); } static void test_pam_env_functions(void **state) { int rv; const char *v; char **vlist; struct pwrap_test_ctx *test_ctx; test_ctx = (struct pwrap_test_ctx *) *state; rv = pam_putenv(test_ctx->ph, "KEY=value"); assert_int_equal(rv, PAM_SUCCESS); rv = pam_putenv(test_ctx->ph, "KEY2=value2"); assert_int_equal(rv, PAM_SUCCESS); v = pam_getenv(test_ctx->ph, "KEY"); assert_non_null(v); #ifdef HAVE_OPENPAM /* * Off-by-one error in pam_getenv() * https://www.openpam.org/wiki/Errata/2019-02-22 */ if (*v == '=') { v++; } #endif assert_string_equal(v, "value"); v = pam_getenv(test_ctx->ph, "KEY2"); assert_non_null(v); #ifdef HAVE_OPENPAM /* * Off-by-one error in pam_getenv() * https://www.openpam.org/wiki/Errata/2019-02-22 */ if (*v == '=') { v++; } #endif assert_string_equal(v, "value2"); vlist = pam_getenvlist(test_ctx->ph); assert_non_null(vlist); assert_non_null(vlist[0]); assert_string_equal(vlist[0], "KEY=value"); assert_non_null(vlist[1]); assert_string_equal(vlist[1], "KEY2=value2"); assert_null(vlist[2]); free_vlist(vlist); rv = pam_putenv(test_ctx->ph, "KEY2="); assert_int_equal(rv, PAM_SUCCESS); vlist = pam_getenvlist(test_ctx->ph); assert_non_null(vlist); assert_non_null(vlist[0]); assert_string_equal(vlist[0], "KEY=value"); assert_non_null(vlist[1]); assert_string_equal(vlist[1], "KEY2="); assert_null(vlist[2]); free_vlist(vlist); #ifndef HAVE_OPENPAM /* OpenPAM does not support this feature */ rv = pam_putenv(test_ctx->ph, "KEY2"); assert_int_equal(rv, PAM_SUCCESS); vlist = pam_getenvlist(test_ctx->ph); assert_non_null(vlist); assert_non_null(vlist[0]); assert_string_equal(vlist[0], "KEY=value"); assert_null(vlist[1]); free_vlist(vlist); #endif } static const char *string_in_list(char **list, const char *key) { if (list == NULL || key == NULL) { return NULL; } if (strlen(key) > 0) { char key_eq[strlen(key) + 1 + 1]; /* trailing = and '\0' */ snprintf(key_eq, sizeof(key_eq), "%s=", key); for (size_t i = 0; list[i] != NULL; i++) { if (strncmp(list[i], key_eq, sizeof(key_eq)-1) == 0) { return list[i] + sizeof(key_eq)-1; } } } return NULL; } static void test_pam_session(void **state) { enum pamtest_err perr; const char *v; struct pam_testcase tests[] = { pam_test(PAMTEST_OPEN_SESSION, PAM_SUCCESS), pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), pam_test(PAMTEST_CLOSE_SESSION, PAM_SUCCESS), pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), }; (void) state; /* unused */ perr = run_pamtest("matrix", "trinity", NULL, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); v = string_in_list(tests[1].case_out.envlist, "HOMEDIR"); assert_non_null(v); assert_string_equal(v, "/home/trinity"); pamtest_free_env(tests[1].case_out.envlist); /* environment is cleared after session close */ assert_non_null(tests[3].case_out.envlist); #ifdef HAVE_OPENPAM v = string_in_list(tests[3].case_out.envlist, "HOMEDIR"); assert_non_null(v); assert_string_equal(v, ""); #else assert_null(tests[3].case_out.envlist[0]); #endif pamtest_free_env(tests[3].case_out.envlist); } static void test_pam_chauthtok(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; const char *trinity_new_authtoks[] = { "secret", /* old password */ "new_secret", /* new password */ "new_secret", /* verify new password */ "new_secret", /* login with the new password */ NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_CHAUTHTOK, PAM_SUCCESS), pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_off = trinity_new_authtoks; perr = run_pamtest("matrix", "trinity", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); } static void test_pam_chauthtok_prelim_failed(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; const char *trinity_new_authtoks[] = { "wrong_secret", /* old password */ "new_secret", /* new password */ "new_secret", /* verify new password */ NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_CHAUTHTOK, PAM_AUTH_ERR), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_off = trinity_new_authtoks; perr = run_pamtest("matrix", "trinity", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); } static void test_pam_chauthtok_diff_passwords(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; const char *trinity_new_authtoks[] = { "wrong_secret", /* old password */ "new_secret", /* new password */ "different_new_secret", /* verify new password */ NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_CHAUTHTOK, PAM_AUTH_ERR), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_off = trinity_new_authtoks; perr = run_pamtest("matrix", "trinity", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); } static void test_pam_setcred(void **state) { enum pamtest_err perr; const char *v; struct pam_testcase tests[] = { pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), pam_test(PAMTEST_SETCRED, PAM_SUCCESS), pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), }; (void) state; /* unused */ perr = run_pamtest("matrix", "trinity", NULL, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); /* environment is clean before setcred */ assert_non_null(tests[0].case_out.envlist); assert_null(tests[0].case_out.envlist[0]); pamtest_free_env(tests[0].case_out.envlist); /* and has an item after setcred */ v = string_in_list(tests[2].case_out.envlist, "CRED"); assert_non_null(v); assert_string_equal(v, "/tmp/trinity"); pamtest_free_env(tests[2].case_out.envlist); } static void test_pam_item_functions(void **state) { struct pwrap_test_ctx *test_ctx; const char *item; int rv; test_ctx = (struct pwrap_test_ctx *) *state; rv = pam_get_item(test_ctx->ph, PAM_USER, (const void **) &item); assert_int_equal(rv, PAM_SUCCESS); assert_string_equal(item, "trinity"); rv = pam_set_item(test_ctx->ph, PAM_USER_PROMPT, "test_login"); assert_int_equal(rv, PAM_SUCCESS); assert_string_equal(item, "trinity"); rv = pam_get_item(test_ctx->ph, PAM_USER_PROMPT, (const void **) &item); assert_int_equal(rv, PAM_SUCCESS); assert_string_equal(item, "test_login"); rv = pam_set_item(test_ctx->ph, PAM_AUTHTOK, "mysecret"); #ifdef HAVE_OPENPAM /* OpenPAM allows PAM_AUTHTOK getset */ assert_int_equal(rv, PAM_SUCCESS); #else assert_int_equal(rv, PAM_BAD_ITEM); #endif rv = pam_get_item(test_ctx->ph, PAM_AUTHTOK, (const void **) &item); #ifdef HAVE_OPENPAM /* OpenPAM allows PAM_AUTHTOK getset */ assert_int_equal(rv, PAM_SUCCESS); assert_string_equal(item, "mysecret"); #else assert_int_equal(rv, PAM_BAD_ITEM); #endif } static int add_to_reply(struct pam_response *res, const char *s1, const char *s2) { size_t res_len; int rv; res_len = strlen(s1) + strlen(s2) + 1; res->resp = calloc(res_len, sizeof(char)); if (res->resp == NULL) { return ENOMEM; } rv = snprintf(res->resp, res_len, "%s%s", s1, s2); if (rv < 0) { return EIO; } return 0; } static int pwrap_echo_conv(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) { int i; struct pam_response *reply; int *resp_array = appdata_ptr; reply = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response)); if (reply == NULL) { return PAM_CONV_ERR; } for (i=0; i < num_msg; i++) { switch (msgm[i]->msg_style) { case PAM_PROMPT_ECHO_OFF: add_to_reply(&reply[i], "echo off: ", msgm[i]->msg); break; case PAM_PROMPT_ECHO_ON: add_to_reply(&reply[i], "echo on: ", msgm[i]->msg); break; case PAM_TEXT_INFO: resp_array[0] = 1; break; case PAM_ERROR_MSG: resp_array[1] = 1; break; default: break; } } *response = reply; return PAM_SUCCESS; } static int vprompt_test_fn(pam_handle_t *pamh, int style, char **response, const char *fmt, ...) PRINTF_ATTRIBUTE(4, 5); static int vprompt_test_fn(pam_handle_t *pamh, int style, char **response, const char *fmt, ...) { va_list args; int rv; va_start(args, fmt); rv = pam_vprompt(pamh, style, response, fmt, args); va_end(args); return rv; } static void test_pam_prompt(void **state) { struct pwrap_test_ctx *test_ctx; int rv; char *response; int resp_array[2]; test_ctx = (struct pwrap_test_ctx *) *state; memset(resp_array, 0, sizeof(resp_array)); test_ctx->conv.conv = pwrap_echo_conv; test_ctx->conv.appdata_ptr = resp_array; rv = pam_start("matrix", "trinity", &test_ctx->conv, &test_ctx->ph); assert_int_equal(rv, PAM_SUCCESS); rv = pam_prompt(test_ctx->ph, PAM_PROMPT_ECHO_OFF, &response, "no echo"); assert_int_equal(rv, PAM_SUCCESS); assert_string_equal(response, "echo off: no echo"); free(response); rv = vprompt_test_fn(test_ctx->ph, PAM_PROMPT_ECHO_OFF, &response, "no echo"); assert_int_equal(rv, PAM_SUCCESS); assert_string_equal(response, "echo off: no echo"); free(response); rv = pam_prompt(test_ctx->ph, PAM_PROMPT_ECHO_ON, &response, "echo"); assert_int_equal(rv, PAM_SUCCESS); assert_string_equal(response, "echo on: echo"); free(response); rv = vprompt_test_fn(test_ctx->ph, PAM_PROMPT_ECHO_ON, &response, "echo"); assert_int_equal(rv, PAM_SUCCESS); assert_string_equal(response, "echo on: echo"); free(response); assert_int_equal(resp_array[0], 0); pam_info(test_ctx->ph, "info"); assert_int_equal(resp_array[0], 1); assert_int_equal(resp_array[1], 0); pam_error(test_ctx->ph, "error"); assert_int_equal(resp_array[1], 1); } static void test_pam_strerror(void **state) { struct pwrap_test_ctx *test_ctx; const char *s = NULL; test_ctx = (struct pwrap_test_ctx *) *state; s = pam_strerror(test_ctx->ph, PAM_AUTH_ERR); assert_non_null(s); } static void test_pam_authenticate_db_opt(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; char *info_arr[] = { auth_info_msg, NULL, }; const char *trinity_authtoks[] = { "secret_ro", NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_on = trinity_authtoks; conv_data.out_info = info_arr; perr = run_pamtest("matrix_opt", "trinity_ro", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); assert_string_equal(auth_info_msg, "Authentication succeeded"); } static void test_pam_authenticate_db_opt_err(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; char auth_err_msg[PAM_MAX_MSG_SIZE] = { '\0' }; char *err_arr[] = { auth_err_msg, NULL, }; const char *trinity_authtoks[] = { "wrong_secret", NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_on = trinity_authtoks; conv_data.out_err = err_arr; perr = run_pamtest("matrix_opt", "trinity_ro", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); assert_string_equal(auth_err_msg, "Authentication failed"); } #ifdef HAVE_PAM_VSYSLOG static void vsyslog_test_fn(const pam_handle_t *pamh, int priority, const char *fmt, ...) PRINTF_ATTRIBUTE(3, 4); static void vsyslog_test_fn(const pam_handle_t *pamh, int priority, const char *fmt, ...) { va_list args; va_start(args, fmt); pam_vsyslog(pamh, priority, fmt, args); va_end(args); } static void test_pam_vsyslog(void **state) { struct pwrap_test_ctx *test_ctx; test_ctx = (struct pwrap_test_ctx *) *state; pam_syslog(test_ctx->ph, LOG_INFO, "This is pam_wrapper test\n"); vsyslog_test_fn(test_ctx->ph, LOG_INFO, "This is pam_wrapper test\n"); } #endif /* HAVE_PAM_VSYSLOG */ static void test_libpamtest_strerror(void **state) { const char *s; (void) state; /* unused */ s = pamtest_strerror(PAMTEST_ERR_OK); assert_non_null(s); s = pamtest_strerror(PAMTEST_ERR_START); assert_non_null(s); s = pamtest_strerror(PAMTEST_ERR_CASE); assert_non_null(s); s = pamtest_strerror(PAMTEST_ERR_OP); assert_non_null(s); s = pamtest_strerror(PAMTEST_ERR_END); assert_non_null(s); s = pamtest_strerror(PAMTEST_ERR_KEEPHANDLE); assert_non_null(s); s = pamtest_strerror(PAMTEST_ERR_INTERNAL); assert_non_null(s); } #define test_setenv(env) setenv(env, "test_"env, 1) #define test_getenv(envlist, key) do { \ const char *__v; \ __v = string_in_list(envlist, key); \ assert_non_null(__v); \ assert_string_equal(__v, "test_"key); \ } while(0); static void test_get_set(void **state) { #ifndef HAVE_OPENPAM const char *svc; #endif enum pamtest_err perr; struct pam_testcase tests[] = { pam_test(PAMTEST_OPEN_SESSION, PAM_SUCCESS), pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), }; (void) state; /* unused */ #ifndef HAVE_OPENPAM test_setenv("PAM_SERVICE"); #endif test_setenv("PAM_USER"); test_setenv("PAM_USER_PROMPT"); test_setenv("PAM_TTY"); test_setenv("PAM_RUSER"); test_setenv("PAM_RHOST"); test_setenv("PAM_AUTHTOK"); test_setenv("PAM_OLDAUTHTOK"); #ifdef PAM_XDISPLAY test_setenv("PAM_XDISPLAY"); #endif #ifdef PAM_AUTHTOK_TYPE test_setenv("PAM_AUTHTOK_TYPE"); #endif perr = run_pamtest("pwrap_get_set", "trinity", NULL, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); /* PAM_SERVICE is a special case, Linux's libpam lowercases it. * OpenPAM only allows PAM_SERVICE to be set by pam_start() */ #ifndef HAVE_OPENPAM svc = string_in_list(tests[1].case_out.envlist, "PAM_SERVICE"); assert_non_null(svc); assert_string_equal(svc, "test_pam_service"); #endif test_getenv(tests[1].case_out.envlist, "PAM_USER"); test_getenv(tests[1].case_out.envlist, "PAM_USER_PROMPT"); test_getenv(tests[1].case_out.envlist, "PAM_TTY"); test_getenv(tests[1].case_out.envlist, "PAM_RUSER"); test_getenv(tests[1].case_out.envlist, "PAM_RHOST"); test_getenv(tests[1].case_out.envlist, "PAM_AUTHTOK"); test_getenv(tests[1].case_out.envlist, "PAM_OLDAUTHTOK"); #ifdef PAM_XDISPLAY test_getenv(tests[1].case_out.envlist, "PAM_XDISPLAY"); #endif #ifdef PAM_AUTHTOK_TYPE test_getenv(tests[1].case_out.envlist, "PAM_AUTHTOK_TYPE"); #endif pamtest_free_env(tests[1].case_out.envlist); } static void test_libpamtest_keepopen(void **state) { int rv; enum pamtest_err perr; struct pamtest_conv_data conv_data; const char *trinity_authtoks[] = { "secret", NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), pam_test(PAMTEST_KEEPHANDLE, PAM_SUCCESS), }; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_off = trinity_authtoks; perr = run_pamtest("matrix", "trinity", &conv_data, tests, NULL); assert_int_equal(perr, PAMTEST_ERR_OK); assert_non_null(tests[1].case_out.ph); rv = pam_end(tests[1].case_out.ph, PAM_SUCCESS); assert_int_equal(rv, PAM_SUCCESS); } static void test_libpamtest_get_failed_test(void **state) { enum pamtest_err perr; struct pamtest_conv_data conv_data; const char *trinity_authtoks[] = { "secret", NULL, }; struct pam_testcase tests[] = { pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), }; const struct pam_testcase *failed_tc; (void) state; /* unused */ ZERO_STRUCT(conv_data); conv_data.in_echo_off = trinity_authtoks; perr = run_pamtest("matrix", "trinity", &conv_data, tests, NULL); assert_int_not_equal(perr, PAMTEST_ERR_OK); failed_tc = pamtest_failed_case(tests); assert_ptr_equal(failed_tc, &tests[0]); } int main(int argc, char *argv[]) { int rc; const struct CMUnitTest init_tests[] = { cmocka_unit_test(test_env), cmocka_unit_test_setup_teardown(test_pam_start, setup_ctx_only, teardown_simple), cmocka_unit_test_setup_teardown(test_pam_authenticate, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_authenticate_null_password, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_authenticate_err, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_acct, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_acct_err, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_env_functions, setup_noconv, teardown), cmocka_unit_test_setup_teardown(test_pam_session, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_chauthtok, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_chauthtok_prelim_failed, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_chauthtok_diff_passwords, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_setcred, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_pam_item_functions, setup_noconv, teardown), cmocka_unit_test_setup_teardown(test_pam_prompt, setup_ctx_only, teardown), cmocka_unit_test_setup_teardown(test_pam_strerror, setup_noconv, teardown), cmocka_unit_test_setup_teardown(test_pam_authenticate_db_opt, setup_ctx_only, teardown_simple), cmocka_unit_test_setup_teardown(test_pam_authenticate_db_opt_err, setup_ctx_only, teardown_simple), #ifdef HAVE_PAM_VSYSLOG cmocka_unit_test_setup_teardown(test_pam_vsyslog, setup_noconv, teardown), #endif cmocka_unit_test_setup_teardown(test_libpamtest_keepopen, setup_passdb, teardown_passdb), cmocka_unit_test_setup_teardown(test_libpamtest_get_failed_test, setup_passdb, teardown_passdb), cmocka_unit_test(test_libpamtest_strerror), cmocka_unit_test(test_get_set), }; if (argc == 2) { cmocka_set_test_filter(argv[1]); } rc = cmocka_run_group_tests(init_tests, NULL, NULL); return rc; } pam_wrapper-1.1.4/doc/000755 001750 000144 00000000000 14136460614 014564 5ustar00asnusers000000 000000 pam_wrapper-1.1.4/doc/pam_set_items.8000644 001750 000144 00000004572 12632316172 017514 0ustar00asnusers000000 000000 '\" t .\" Title: pam_set_items .\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] .\" Generator: DocBook XSL Stylesheets v1.78.1 .\" Date: 2015-11-04 .\" Manual: \ \& .\" Source: \ \& .\" Language: English .\" .TH "PAM_SET_ITEMS" "8" "2015\-11\-04" "\ \&" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .\" http://bugs.debian.org/507673 .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation .nh .\" disable justification (adjust text to left margin only) .ad l .\" ----------------------------------------------------------------- .\" * MAIN CONTENT STARTS HERE * .\" ----------------------------------------------------------------- .SH "NAME" pam_set_items \- A PAM test module to set module\-specific PAM items .SH "SYNOPSIS" .sp pam_set_items\&.so .SH "DESCRIPTION" .sp PAM modules store data in PAM \fIitems\fR\&. These items are only accessible from module context, not application context as they might include private data (PAM_AUTHTOK normally contains the password)\&. But when testing PAM modules, it\(cqs often nice to make sure a PAM module under test can retrieve data from the stack\&. The pam_set_items module makes this possible by reading environment variables and setting them as PAM items\&. .SH "OPTIONS" .sp None .SH "MODULE TYPES PROVIDED" .sp All module types (\fBaccount\fR, \fBauth\fR, \fBpassword\fR and \fBsession\fR) are provided\&. .SH "EXAMPLE" .sp Consider an example that tests that pam_unix is able to read a provided password and doesn\(cqt query on its own\&. The test service file would contain: .sp .if n \{\ .RS 4 .\} .nf auth required pam_set_items\&.so auth required pam_unix\&.so .fi .if n \{\ .RE .\} .sp Then the test would put the item to the test environment with: .sp .if n \{\ .RS 4 .\} .nf setenv("PAM_AUTHTOK", "secret"); .fi .if n \{\ .RE .\} .sp Then run the PAM conversation\&. pam_wrapper-1.1.4/doc/Doxyfile.in000644 001750 000144 00000311046 12632316172 016702 0ustar00asnusers000000 000000 # Doxyfile 1.8.8 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = @APPLICATION_NAME@ # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = @APPLICATION_VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@ # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = @CMAKE_SOURCE_DIR@ # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a # new page for each member. If set to NO, the documentation of a member will be # part of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 2 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = YES # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. When set to YES local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = YES # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = @CMAKE_INTERNAL_DOC@ # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = YES # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the # todo list. This list is created by putting \todo commands in the # documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the # test list. This list is created by putting \test commands in the # documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES the list # will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. # Note: If this tag is empty the current directory is searched. INPUT = @CMAKE_SOURCE_DIR@/include \ @CMAKE_SOURCE_DIR@/src \ @CMAKE_SOURCE_DIR@/doc # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank the # following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, # *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = *.cpp \ *.cc \ *.c \ *.h \ *.hh \ *.hpp \ *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = */.git/* \ */.svn/* \ */cmake/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = *.c \ *.h \ INSTALL \ DEPENDENCIES \ CHANGELOG \ LICENSE \ LGPL # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = YES # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES, then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 2 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefor more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra stylesheet files is of importance (e.g. the last # stylesheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler ( hhc.exe). If non-empty # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated ( # YES) or that it should be included in the master .chm file ( NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated ( # YES) or a normal table of contents ( NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /