From 983b813269c3d9cf49e3aebebfaed79cbdd19323 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Fri, 9 Oct 2015 15:39:11 +0200 Subject: pamtest: Add libpamtest --- CMakeLists.txt | 11 +++ include/CMakeLists.txt | 14 ++++ include/libpamtest.h | 93 +++++++++++++++++++++ libpamtest.pc.cmake | 4 + pam_wrapper.pc.cmake | 4 +- src/CMakeLists.txt | 24 ++++++ src/libpamtest.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 include/CMakeLists.txt create mode 100644 include/libpamtest.h create mode 100644 libpamtest.pc.cmake create mode 100644 src/libpamtest.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b2635cd..4623409 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) # check subdirectories add_subdirectory(src) +add_subdirectory(include) if (UNIT_TESTING) find_package(CMocka REQUIRED) @@ -69,6 +70,16 @@ install( pkgconfig ) +configure_file(libpamtest.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libpamtest.pc @ONLY) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/libpamtest.pc + DESTINATION + ${LIB_INSTALL_DIR}/pkgconfig + COMPONENT + pkgconfig +) + # cmake config files configure_file(pam_wrapper-config-version.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/pam_wrapper-config-version.cmake @ONLY) configure_file(pam_wrapper-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/pam_wrapper-config.cmake @ONLY) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..5eaaba6 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,14 @@ +project(pam_wrapper-headers C) + +set(libpamtest_HDRS + libpamtest.h +) + +install( + FILES + ${libpamtest_HDRS} + DESTINATION + ${INCLUDE_INSTALL_DIR} + COMPONENT + headers +) diff --git a/include/libpamtest.h b/include/libpamtest.h new file mode 100644 index 0000000..b6de813 --- /dev/null +++ b/include/libpamtest.h @@ -0,0 +1,93 @@ +/* + * 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 . + */ + +#ifndef __LIBPAMTEST_H_ +#define __LIBPAMTEST_H_ + +#include +#include + +/* operations */ +enum pamtest_ops { + /* These operations correspond to libpam ops */ + PAMTEST_AUTHENTICATE, + PAMTEST_SETCRED, + PAMTEST_ACCOUNT, + PAMTEST_OPEN_SESSION, + PAMTEST_CLOSE_SESSION, + PAMTEST_CHAUTHTOK, + + /* These operation affect test output */ + PAMTEST_GETENVLIST, /* Call pam_getenvlist. */ + PAMTEST_KEEPHANDLE, /* Don't call pam_end() but return handle */ + + /* The two below can't be set by API user, but are useful if pam_start() + * or pam_end() fails and the API user wants to find out what happened + * with pamtest_failed_case() + */ + PAMTEST_START, + PAMTEST_END, + + /* Boundary.. */ + PAMTEST_SENTINEL, +}; + +struct pamtest_case { + enum pamtest_ops pam_operation; /* The pam operation to run */ + int expected_rv; /* What we expect the op to return */ + int flags; /* Extra flags to pass to the op */ + + int op_rv; /* What the op really returns */ + + union { + char **envlist; /* output of PAMTEST_ENVLIST */ + pam_handle_t *ph; /* output of PAMTEST_KEEPHANDLE */ + } case_out; /* depends on pam_operation, mostly unused */ +}; + +enum pamtest_err { + PAMTEST_ERR_OK, /* Testcases returns correspond with input */ + PAMTEST_ERR_START, /* pam_start() failed */ + PAMTEST_ERR_CASE, /* A testcase failed. Use pamtest_failed_case */ + PAMTEST_ERR_OP, /* Could not run a test case */ + PAMTEST_ERR_END, /* pam_end failed */ + PAMTEST_ERR_KEEPHANDLE, /* Handled internally */ + PAMTEST_ERR_INTERNAL, /* Internal error - bad input or similar */ +}; + +typedef int (*pam_conv_fn)(int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr); + +enum pamtest_err pamtest_ex(const char *service, + const char *user, + pam_conv_fn conv_fn, + void *conv_userdata, + struct pamtest_case *test_cases); + +void pamtest_free_env(char **envlist); + +const struct pamtest_case *pamtest_failed_case(struct pamtest_case *test_cases); + +enum pamtest_err pamtest(const char *service, + const char *user, + void *conv_userdata, + struct pamtest_case *test_cases); + +#endif /* __LIBPAMTEST_H_ */ diff --git a/libpamtest.pc.cmake b/libpamtest.pc.cmake new file mode 100644 index 0000000..20758c7 --- /dev/null +++ b/libpamtest.pc.cmake @@ -0,0 +1,4 @@ +Name: libpamtest +Description: A helper library for PAM testing +Version: @APPLICATION_VERSION@ +Libs: @LIB_INSTALL_DIR@/libpamtest.so diff --git a/pam_wrapper.pc.cmake b/pam_wrapper.pc.cmake index 6377dfe..3c1c848 100644 --- a/pam_wrapper.pc.cmake +++ b/pam_wrapper.pc.cmake @@ -1,4 +1,4 @@ Name: @APPLICATION_NAME@ -Description: The uid_wrapper library +Description: The pam_wrapper library Version: @APPLICATION_VERSION@ -Libs: @LIB_INSTALL_DIR@/@UID_WRAPPER_LIB@ +Libs: @LIB_INSTALL_DIR@/@PAM_WRAPPER_LIB@ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e99a033..b09a78f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,6 +21,30 @@ install( ARCHIVE DESTINATION ${LIB_INSTALL_DIR} ) +set(pamtest_SOURCES + libpamtest.c +) + +set(pamtest_HEADERS + ${CMAKE_SOURCE_DIR}/include/libpamtest.h +) +include_directories(${CMAKE_SOURCE_DIR}/include) + +add_library(pamtest SHARED + ${pamtest_SOURCES} + ${pamtest_HEADERS} +) +target_link_libraries(pamtest + ${PAM_LIBRARIES}) + +install( + TARGETS + pamtest + RUNTIME DESTINATION ${BIN_INSTALL_DIR} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} +) + add_library(pam_matrix MODULE modules/pam_matrix.c) set_property(TARGET pam_matrix PROPERTY PREFIX "") diff --git a/src/libpamtest.c b/src/libpamtest.c new file mode 100644 index 0000000..1990a18 --- /dev/null +++ b/src/libpamtest.c @@ -0,0 +1,216 @@ +/* + * 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 +#include +#include + +#include "libpamtest.h" + +static enum pamtest_err run_test_case(pam_handle_t *ph, + struct pamtest_case *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_ex(const char *service, + const char *user, + pam_conv_fn conv_fn, + void *conv_userdata, + struct pamtest_case *test_cases) +{ + int rv; + pam_handle_t *ph; + struct pam_conv conv; + size_t tcindex; + struct pamtest_case *tc; + bool call_pam_end = true; + + conv.conv = conv_fn; + conv.appdata_ptr = conv_userdata; + + if (test_cases == NULL) { + return PAMTEST_ERR_INTERNAL; + } + + rv = pam_start(service, user, &conv, &ph); + if (rv != PAM_SUCCESS) { + return PAMTEST_ERR_START; + } + + for (tcindex = 0; + test_cases[tcindex].pam_operation != PAMTEST_SENTINEL; + 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) { + rv = pam_end(ph, tc->op_rv); + if (rv != PAM_SUCCESS) { + return PAMTEST_ERR_END; + } + } + + if (test_cases[tcindex].pam_operation != PAMTEST_SENTINEL) { + return PAMTEST_ERR_CASE; + } + + return PAMTEST_ERR_OK; +} + +void pamtest_free_env(char **envlist) +{ + if (envlist == NULL) { + return; + } + + for (size_t i = 0; envlist[i] != NULL; i++) { + free(envlist[i]); + } + free(envlist); +} + +const struct pamtest_case *pamtest_failed_case(struct pamtest_case *test_cases) +{ + size_t tcindex; + + for (tcindex = 0; + test_cases[tcindex].pam_operation != PAMTEST_SENTINEL; + tcindex++) { + const struct pamtest_case *tc = &test_cases[tcindex]; + + if (tc->expected_rv != tc->op_rv) { + return tc; + } + } + + /* Nothing failed */ + return NULL; +} + +struct pamtest_conv_data { + const char **conv_input; + size_t conv_index; +}; + +static int pamtest_simple_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 pamtest_conv_data *cdata = \ + (struct pamtest_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->conv_input[cdata->conv_index]; + if (password == NULL) { + return PAM_CONV_ERR; + } + + pwlen = strlen(password) + 1; + + cdata->conv_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; +} + +enum pamtest_err pamtest(const char *service, + const char *user, + void *conv_userdata, + struct pamtest_case *test_cases) +{ + struct pamtest_conv_data cdata; + + cdata.conv_input = conv_userdata; + cdata.conv_index = 0; + + return pamtest_ex(service, user, + pamtest_simple_conv, &cdata, + test_cases); +} -- cgit v1.2.3