From 8635465d77496e882ca73477690bbd20d877a197 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 24 Nov 2017 13:34:25 +0100 Subject: build: Move pam_wrapper to third_party Signed-off-by: Andreas Schneider Reviewed-by: Alexander Bokovoy --- buildtools/wafsamba/samba_third_party.py | 5 + lib/pam_wrapper/libpamtest.c | 323 ---- lib/pam_wrapper/libpamtest.h | 278 ---- lib/pam_wrapper/pam_wrapper.c | 1694 -------------------- lib/pam_wrapper/pwrap_compat.h | 30 - lib/pam_wrapper/python/pypamtest.c | 1112 ------------- lib/pam_wrapper/wscript | 116 -- python/samba/tests/test_pam_winbind.sh | 2 +- .../tests/test_pam_winbind_warn_pwd_expire.sh | 2 +- third_party/pam_wrapper/libpamtest.c | 323 ++++ third_party/pam_wrapper/libpamtest.h | 278 ++++ third_party/pam_wrapper/pam_wrapper.c | 1694 ++++++++++++++++++++ third_party/pam_wrapper/pwrap_compat.h | 30 + third_party/pam_wrapper/python/pypamtest.c | 1112 +++++++++++++ third_party/pam_wrapper/wscript | 117 ++ third_party/wscript | 5 + wscript | 7 +- wscript_build | 2 - 18 files changed, 3571 insertions(+), 3559 deletions(-) delete mode 100644 lib/pam_wrapper/libpamtest.c delete mode 100644 lib/pam_wrapper/libpamtest.h delete mode 100644 lib/pam_wrapper/pam_wrapper.c delete mode 100644 lib/pam_wrapper/pwrap_compat.h delete mode 100644 lib/pam_wrapper/python/pypamtest.c delete mode 100644 lib/pam_wrapper/wscript create mode 100644 third_party/pam_wrapper/libpamtest.c create mode 100644 third_party/pam_wrapper/libpamtest.h create mode 100644 third_party/pam_wrapper/pam_wrapper.c create mode 100644 third_party/pam_wrapper/pwrap_compat.h create mode 100644 third_party/pam_wrapper/python/pypamtest.c create mode 100644 third_party/pam_wrapper/wscript diff --git a/buildtools/wafsamba/samba_third_party.py b/buildtools/wafsamba/samba_third_party.py index 3253f948b8b..9c894e4aed4 100644 --- a/buildtools/wafsamba/samba_third_party.py +++ b/buildtools/wafsamba/samba_third_party.py @@ -59,3 +59,8 @@ Build.BuildContext.CHECK_RESOLV_WRAPPER = CHECK_RESOLV_WRAPPER def CHECK_UID_WRAPPER(conf): return conf.CHECK_BUNDLED_SYSTEM_PKG('uid_wrapper', minversion='1.2.4') Build.BuildContext.CHECK_UID_WRAPPER = CHECK_UID_WRAPPER + +@conf +def CHECK_PAM_WRAPPER(conf): + return conf.CHECK_BUNDLED_SYSTEM_PKG('pam_wrapper', minversion='1.0.4') +Build.BuildContext.CHECK_PAM_WRAPPER = CHECK_PAM_WRAPPER diff --git a/lib/pam_wrapper/libpamtest.c b/lib/pam_wrapper/libpamtest.c deleted file mode 100644 index c0ab41dac8e..00000000000 --- a/lib/pam_wrapper/libpamtest.c +++ /dev/null @@ -1,323 +0,0 @@ -/* - * 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" - -#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) -{ - 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; - } - - rv = pam_start(service, user, &conv, &ph); - if (rv != PAM_SUCCESS) { - return PAMTEST_ERR_START; - } - - 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, ri = 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[ri], prompt); - if (ret != PAM_SUCCESS) { - free_reply(reply, num_msg); - return ret; - } - } else { - reply[ri].resp = NULL; - } - ri++; - } - - 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[ri], prompt); - if (ret != PAM_SUCCESS) { - free_reply(reply, num_msg); - return ret; - } - } - ri++; - } - - cctx->echo_on_idx++; - break; - case PAM_ERROR_MSG: - 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 (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 && ri > 0) { - *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) -{ - struct pamtest_conv_ctx cctx = { - .data = conv_data, - }; - - return _pamtest_conv(service, user, - pamtest_simple_conv, - &cctx, - test_cases, - num_test_cases); -} diff --git a/lib/pam_wrapper/libpamtest.h b/lib/pam_wrapper/libpamtest.h deleted file mode 100644 index 0307a2663af..00000000000 --- a/lib/pam_wrapper/libpamtest.h +++ /dev/null @@ -1,278 +0,0 @@ -/* - * 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 - -/** - * @defgroup pamtest The pamtest API - * - * @{ - */ - -/** - * @brief The enum which describes the operations performed by pamtest(). - */ -enum pamtest_ops { - /** run pam_authenticate to authenticate the account */ - PAMTEST_AUTHENTICATE, - /** run pam_setcred() to establish/delete user credentials */ - PAMTEST_SETCRED, - /** run pam_acct_mgmt() to validate the PAM account */ - PAMTEST_ACCOUNT, - /** run pam_open_session() to start a PAM session */ - PAMTEST_OPEN_SESSION, - /** run pam_close_session() to end a PAM session */ - PAMTEST_CLOSE_SESSION, - /** run pam_chauthtok() to update the authentication token */ - PAMTEST_CHAUTHTOK, - - /** - * If this option is set the test will call pam_getenvlist() and copy - * the environment into case_out.envlist. - */ - PAMTEST_GETENVLIST = 20, - /** - * This will prevent calling pam_end() and will just return the - * PAM handle in case_out.ph. - */ - PAMTEST_KEEPHANDLE, -}; - - -/** - * @brief The PAM testcase struction. Use the pam_test and pam_test_flags - * macros to fill them. - * - * @see run_pamtest() - */ -struct pam_testcase { - 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 */ -}; - -/** Initializes a pam_tescase structure. */ -#define pam_test(op, expected) { op, expected, 0, 0, { .envlist = NULL } } -/** Initializes a CMUnitTest structure with additional PAM flags. */ -#define pam_test_flags(op, expected, flags) { op, expected, flags, 0, { .envlist = NULL } } - -/** - * @brief The return code of the pamtest function - */ -enum pamtest_err { - /** Testcases returns correspond with input */ - PAMTEST_ERR_OK, - /** pam_start() failed */ - PAMTEST_ERR_START, - /** A testcase failed. Use pamtest_failed_case */ - PAMTEST_ERR_CASE, - /** Could not run a test case */ - PAMTEST_ERR_OP, - /** pam_end failed */ - PAMTEST_ERR_END, - /** Handled internally */ - PAMTEST_ERR_KEEPHANDLE, - /** Internal error - bad input or similar */ - PAMTEST_ERR_INTERNAL, -}; - -/** - * @brief PAM conversation function, defined in pam_conv(3) - * - * This is just a typedef to use in our declarations. See man pam_conv(3) - * for more details. - */ -typedef int (*pam_conv_fn)(int num_msg, - const struct pam_message **msg, - struct pam_response **resp, - void *appdata_ptr); - -/** - * @brief This structure should be used when using run_pamtest, - * which uses an internal conversation function. - */ -struct pamtest_conv_data { - /** When the conversation function receives PAM_PROMPT_ECHO_OFF, - * it reads the auth token from the in_echo_off array and keeps - * an index internally. - */ - const char **in_echo_off; - /** When the conversation function receives PAM_PROMPT_ECHO_ON, - * it reads the input from the in_echo_off array and keeps - * an index internally. - */ - const char **in_echo_on; - - /** Captures messages through PAM_TEXT_INFO. The test caller is - * responsible for allocating enough space in the array. - */ - char **out_err; - /** Captures messages through PAM_ERROR_MSG. The test caller is - * responsible for allocating enough space in the array. - */ - char **out_info; -}; - -#ifdef DOXYGEN -/** - * @brief Run libpamtest test cases - * - * This is using the default libpamtest conversation function. - * - * @param[in] service The PAM service to use in the conversation - * - * @param[in] user The user to run conversation as - * - * @param[in] conv_fn Test-specific conversation function - * - * @param[in] conv_userdata Test-specific conversation data - * - * @param[in] test_cases List of libpamtest test cases. Must end with - * PAMTEST_CASE_SENTINEL - * - * @code - * int main(void) { - * int rc; - * const struct pam_testcase tests[] = { - * pam_test(PAM_AUTHENTICATE, PAM_SUCCESS), - * }; - * - * rc = run_pamtest(tests, NULL, NULL); - * - * return rc; - * } - * @endcode - * - * @return PAMTEST_ERR_OK on success, else the error code matching the failure. - */ -enum pamtest_err run_pamtest_conv(const char *service, - const char *user, - pam_conv_fn conv_fn, - void *conv_userdata, - struct pam_testcase test_cases[]); -#else -#define run_pamtest_conv(service, user, conv_fn, conv_data, test_cases) \ - _pamtest_conv(service, user, conv_fn, conv_data, test_cases, sizeof(test_cases)/sizeof(test_cases[0]) -#endif - -#ifdef DOXYGEN -/** - * @brief Run libpamtest test cases - * - * This is using the default libpamtest conversation function. - * - * @param[in] service The PAM service to use in the conversation - * - * @param[in] user The user to run conversation as - * - * @param[in] conv_data Test-specific conversation data - * - * @param[in] test_cases List of libpamtest test cases. Must end with - * PAMTEST_CASE_SENTINEL - * - * @code - * int main(void) { - * int rc; - * const struct pam_testcase tests[] = { - * pam_test(PAM_AUTHENTICATE, PAM_SUCCESS), - * }; - * - * rc = run_pamtest(tests, NULL, NULL); - * - * return rc; - * } - * @endcode - * - * @return PAMTEST_ERR_OK on success, else the error code matching the failure. - */ -enum pamtest_err run_pamtest(const char *service, - const char *user, - struct pamtest_conv_data *conv_data, - struct pam_testcase test_cases[]); -#else -#define run_pamtest(service, user, conv_data, test_cases) \ - _pamtest(service, user, conv_data, test_cases, sizeof(test_cases)/sizeof(test_cases[0])) -#endif - -#ifdef DOXYGEN -/** - * @brief Helper you can call if run_pamtest() fails. - * - * If PAMTEST_ERR_CASE is returned by run_pamtest() you should call this - * function get a pointer to the failed test case. - * - * @param[in] test_cases The array of tests. - * - * @return a pointer to the array of test_cases[] that corresponds to the - * first test case where the expected error code doesn't match the real error - * code. - */ -const struct pam_testcase *pamtest_failed_case(struct pam_testcase *test_cases); -#else -#define pamtest_failed_case(test_cases) \ - _pamtest_failed_case(test_cases, sizeof(test_cases) / sizeof(test_cases[0])) -#endif - -/** - * @brief return a string representation of libpamtest error code. - * - * @param[in] perr libpamtest error code - * - * @return String representation of the perr argument. Never returns NULL. - */ -const char *pamtest_strerror(enum pamtest_err perr); - -/** - * @brief This frees the string array returned by the PAMTEST_GETENVLIST test. - * - * @param[in] envlist The array to free. - */ -void pamtest_free_env(char **envlist); - - -/* Internal function protypes */ -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); - -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); - -const struct pam_testcase *_pamtest_failed_case(struct pam_testcase test_cases[], - size_t num_test_cases); - -/** @} */ - -#endif /* __LIBPAMTEST_H_ */ diff --git a/lib/pam_wrapper/pam_wrapper.c b/lib/pam_wrapper/pam_wrapper.c deleted file mode 100644 index 03584beb95e..00000000000 --- a/lib/pam_wrapper/pam_wrapper.c +++ /dev/null @@ -1,1694 +0,0 @@ -/* - * 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 - -#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 - *****************/ - -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) - %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); -} - -/***************** - * 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_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_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); -void pwrap_constructor(void) CONSTRUCTOR_ATTRIBUTE; -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 - 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. - */ -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); -} - -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); -} - -#if defined(HAVE_PAM_VSYSLOG) || defined(HAVE_PAM_SYSLOG) -static void libpam_pam_vsyslog(const pam_handle_t *pamh, - int priority, - const char *fmt, - va_list args) -{ -#ifdef HAVE_PAM_VSYSLOG - pwrap_bind_symbol_libpam(pam_vsyslog); - - pwrap.libpam.symbols._libpam_pam_vsyslog.f(pamh, - priority, - fmt, - args); -#endif -} -#endif - -/********************************************************* - * PWRAP INIT - *********************************************************/ - -#define BUFFER_SIZE 32768 - -/* copy file from src to dst, overwrites dst */ -static int p_copy(const char *src, const char *dst, const char *pdir, 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 (;;) { - char *p; - bread = read(srcfd, buf, BUFFER_SIZE); - if (bread == 0) { - /* done */ - break; - } else if (bread < 0) { - errno = EIO; - rc = -1; - goto out; - } - - /* EXTRA UGLY HACK */ - if (pdir != NULL) { - p = buf; - - while (p < buf + BUFFER_SIZE) { - if (*p == '/') { - cmp = memcmp(p, "/etc/pam.d", 10); - if (cmp == 0) { - memcpy(p, pdir, 10); - } - } - p++; - } - } - - 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, NULL, 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 || tmp > 0xFFFF || errno == ERANGE) { - PWRAP_LOG(PWRAP_LOG_ERROR, - "Failed to parse pid, buf=%s", - buf); - return; - } - - pid = (pid_t)(tmp & 0xFFFF); - - rc = kill(pid, 0); - if (rc == -1) { - PWRAP_LOG(PWRAP_LOG_TRACE, - "Remove stale pam_wrapper dir: %s", - dir); - p_rmdirs(dir); - } - - return; -} - -static void pwrap_init(void) -{ - char tmp_config_dir[] = "/tmp/pam.X"; - size_t len = strlen(tmp_config_dir); - const char *env; - uint32_t i; - int rc; - char pam_library[128] = { 0 }; - char libpam_path[1024] = { 0 }; - ssize_t ret; - FILE *pidfile; - char pidfile_path[1024] = { 0 }; - - if (!pam_wrapper_enabled()) { - return; - } - - if (pwrap.initialised) { - return; - } - - PWRAP_LOG(PWRAP_LOG_DEBUG, "Initialize pam_wrapper"); - - for (i = 0; i < 36; i++) { - struct stat sb; - char c; - - if (i < 10) { - c = (char)(i + 48); - } else { - c = (char)(i + 87); - } - - tmp_config_dir[len - 1] = c; - 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); - continue; - } else if (errno == ENOENT) { - break; - } - } - - if (i == 36) { - PWRAP_LOG(PWRAP_LOG_ERROR, - "Failed to find a possible path to create " - "pam_wrapper config dir: %s", - tmp_config_dir); - exit(1); - } - - 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 pam_wrapper config dir: %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[sizeof(libpam_path)]; - char *dname; - - strncpy(libpam_path_cp, libpam_path, sizeof(libpam_path_cp)); - libpam_path_cp[sizeof(libpam_path_cp) - 1] = '\0'; - - 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 = p_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.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"); -} - -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; -} - -/**************************** - * CONSTRUCTOR - ***************************/ -void pwrap_constructor(void) -{ - /* - * Here is safe place to call pwrap_init() and initialize data - * for main process. - */ - pwrap_init(); -} - - -#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) -{ - PWRAP_LOG(PWRAP_LOG_TRACE, - "pam_start service=%s, user=%s", - service_name, - user); - -#ifdef HAVE_OPENPAM - return pwrap_openpam_start(service_name, - user, - pam_conversation, - pamh); -#else - return libpam_pam_start(service_name, - user, - pam_conversation, - pamh); -#endif -} - - -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"); - - d = getenv("PAM_WRAPPER_USE_SYSLOG"); - if (d != NULL && d[0] == '1') { - libpam_pam_vsyslog(pamh, priority, fmt, args); - return; - } - - 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; -} - -/**************************** - * DESTRUCTOR - ***************************/ - -static int p_rmdirs_at(const char *path, int parent_fd) -{ - DIR *d; - struct dirent *dp; - struct stat sb; - int path_fd; - int rc; - - /* If path is absolute, parent_fd is ignored. */ - PWRAP_LOG(PWRAP_LOG_TRACE, - "p_rmdirs_at removing %s at %d\n", path, parent_fd); - - 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_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; - } -} diff --git a/lib/pam_wrapper/pwrap_compat.h b/lib/pam_wrapper/pwrap_compat.h deleted file mode 100644 index a30df15c61f..00000000000 --- a/lib/pam_wrapper/pwrap_compat.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 . - */ - -#ifdef HAVE_OPENPAM -#include -#endif - -/* OpenPAM doesn't define PAM_BAD_ITEM */ -#ifndef PAM_BAD_ITEM -#define PAM_BAD_ITEM PAM_SYSTEM_ERR -#endif /* PAM_BAD_ITEM */ - -#ifndef ENODATA -#define ENODATA EPIPE -#endif diff --git a/lib/pam_wrapper/python/pypamtest.c b/lib/pam_wrapper/python/pypamtest.c deleted file mode 100644 index e25900f001b..00000000000 --- a/lib/pam_wrapper/python/pypamtest.c +++ /dev/null @@ -1,1112 +0,0 @@ -/* - * 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 "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; -} TestCaseObject; - -/********************************************************** - *** 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 const char **sequence_as_string_list(PyObject *seq, - const char *paramname) -{ - const char *p = paramname ? paramname : "attribute values"; - const char **ret; - 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 NULL; - } - - len = PySequence_Size(seq); - if (len == -1) { - return NULL; - } - - ret = PyMem_New(const char *, (len + 1)); - if (!ret) { - PyErr_NoMemory(); - return NULL; - } - - 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 NULL; - } - - ret[i] = py_strdup(PyBytes_AsString(utf_item)); - Py_DECREF(utf_item); - if (!ret[i]) { - Py_DECREF(item); - return NULL; - } - Py_DECREF(item); - } - - ret[i] = NULL; - return ret; -} - -static PyObject *string_list_as_tuple(char **str_list) -{ - int rc; - size_t len, i; - PyObject *tup; - PyObject *py_str; - - for (len=0; len < PAM_CONV_MSG_MAX; 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 retured [%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"), - }, - - { 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; - - 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) -{ - 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) { - conv_data->in_echo_off = sequence_as_string_list(py_echo_off, - "echo_off"); - if (conv_data->in_echo_off == NULL) { - free_conv_data(conv_data); - return ENOMEM; - } - } - - if (py_echo_on != NULL) { - conv_data->in_echo_on = sequence_as_string_list(py_echo_on, - "echo_on"); - if (conv_data->in_echo_on == NULL) { - 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; -} - -PyDoc_STRVAR(RunPamTest__doc__, -"Run PAM tests\n\n" -"This function runs PAM test cases and reports result\n" -"Paramaters:\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) -{ - int ok; - int rc; - char *username = NULL; - char *service = NULL; - PyObject *py_test_list; - PyObject *py_echo_off = NULL; - PyObject *py_echo_on = NULL; - Py_ssize_t num_tests; - struct pam_testcase *test_list; - enum pamtest_err perr; - struct pamtest_conv_data conv_data; - TestResultObject *result = NULL; - - (void) module; /* unused */ - - ok = PyArg_ParseTuple(args, - discard_const_p(char, "ssO|OO"), - &username, - &service, - &py_test_list, - &py_echo_off, - &py_echo_on); - 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; - } - - perr = _pamtest(service, username, &conv_data, test_list, num_tests); - 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; - } - 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, - 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; - } - - 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 -} diff --git a/lib/pam_wrapper/wscript b/lib/pam_wrapper/wscript deleted file mode 100644 index cfd8b4f9baf..00000000000 --- a/lib/pam_wrapper/wscript +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python - -import os - -VERSION="1.0.4" - -def find_library(library_names, lookup_paths): - for directory in lookup_paths: - for filename in library_names: - libpam_path = os.path.join(directory, filename) - if os.path.exists(libpam_path): - return libpam_path - return '' - -def configure(conf): - if conf.CHECK_BUNDLED_SYSTEM('pam_wrapper', minversion=VERSION, set_target=False): - conf.DEFINE('USING_SYSTEM_PAM_WRAPPER', 1) - libpam_wrapper_so_path = 'libpam_wrapper.so' - else: - # check HAVE_GCC_THREAD_LOCAL_STORAGE - conf.CHECK_CODE(''' - __thread int tls; - - int main(void) { - return 0; - } - ''', - 'HAVE_GCC_THREAD_LOCAL_STORAGE', - addmain=False, - msg='Checking for thread local storage') - - # check HAVE_DESTRUCTOR_ATTRIBUTE - conf.CHECK_CODE(''' - void test_destructor_attribute(void) __attribute__ ((destructor)); - - void test_destructor_attribute(void) - { - return; - } - - int main(void) { - return 0; - } - ''', - 'HAVE_DESTRUCTOR_ATTRIBUTE', - addmain=False, - msg='Checking for library destructor support') - - # check HAVE_FUNCTION_ATTRIBUTE_FORMAT - conf.CHECK_CODE(''' - void log_fn(const char *format, ...) __attribute__ ((format (printf, 1, 2))); - - int main(void) { - return 0; - } - ''', - 'HAVE_FUNCTION_ATTRIBUTE_FORMAT', - addmain=False, - msg='Checking for printf format validation support') - - conf.CHECK_HEADERS('security/pam_appl.h') - conf.CHECK_HEADERS('security/pam_modules.h') - conf.CHECK_HEADERS('security/pam_ext.h') - - conf.CHECK_FUNCS_IN('pam_vsyslog', - 'pam', - checklibc=False, - headers='security/pam_ext.h') - - conf.CHECK_FUNCS_IN('pam_syslog', - 'pam', - checklibc=False, - headers='security/pam_ext.h') - - conf.CHECK_C_PROTOTYPE('pam_vprompt', - 'int pam_vprompt(const pam_handle_t *_pamh, int _style, char **_resp, const char *_fmt, va_list _ap)', - define='HAVE_PAM_VPROMPT_CONST', headers='stdio.h sys/types.h security/pam_appl.h security/pam_modules.h') - - conf.CHECK_C_PROTOTYPE('pam_prompt', - 'int pam_prompt(const pam_handle_t *_pamh, int _style, char **_resp, const char *_fmt, ...)', - define='HAVE_PAM_PROMPT_CONST', headers='stdio.h sys/types.h security/pam_appl.h security/pam_modules.h') - - conf.CHECK_C_PROTOTYPE( - 'pam_strerror', - 'const char *pam_strerror(const pam_handle_t *pamh, int errnum)', - define='HAVE_PAM_STRERROR_CONST', - headers='''stdio.h sys/types.h security/pam_appl.h - security/pam_modules.h''') - - # Find the absolute path to libpam.so.0 - libpam_path = find_library(['libpam.so.0', 'libpam.so'], conf.env.STANDARD_LIBPATH) - conf.DEFINE('PAM_LIBRARY', ('"%s"' % libpam_path )) - - # Create full path to pam_wrapper - blddir = os.path.realpath(conf.blddir) - libpam_wrapper_so_path = blddir + '/default/lib/pam_wrapper/libpam-wrapper.so' - - conf.DEFINE('LIBPAM_WRAPPER_SO_PATH', libpam_wrapper_so_path) - conf.DEFINE('PAM_WRAPPER', 1) - -def build(bld): - if not bld.CONFIG_SET("USING_SYSTEM_PAM_WRAPPER"): - # We need to do it this way or the library wont work. - # Using private_library=True will add symbol version which - # breaks preloading! - bld.SAMBA_LIBRARY('pam_wrapper', - source='pam_wrapper.c', - deps='dl', - install=False, - realname='libpam-wrapper.so') - - # Can be used to write pam tests in python - bld.SAMBA_PYTHON('pypamtest', - source='python/pypamtest.c libpamtest.c', - deps='dl pam', - install=False) diff --git a/python/samba/tests/test_pam_winbind.sh b/python/samba/tests/test_pam_winbind.sh index c535759885c..0406b108b31 100755 --- a/python/samba/tests/test_pam_winbind.sh +++ b/python/samba/tests/test_pam_winbind.sh @@ -12,7 +12,7 @@ PASSWORD="$3" export PASSWORD shift 3 -PAM_WRAPPER_PATH="$BINDIR/default/lib/pam_wrapper" +PAM_WRAPPER_PATH="$BINDIR/default/third_party/pam_wrapper" pam_winbind="$BINDIR/shared/pam_winbind.so" service_dir="$SELFTEST_TMPDIR/pam_services" diff --git a/python/samba/tests/test_pam_winbind_warn_pwd_expire.sh b/python/samba/tests/test_pam_winbind_warn_pwd_expire.sh index 305633d5a31..16dede44227 100755 --- a/python/samba/tests/test_pam_winbind_warn_pwd_expire.sh +++ b/python/samba/tests/test_pam_winbind_warn_pwd_expire.sh @@ -12,7 +12,7 @@ PASSWORD="$3" export PASSWORD shift 3 -PAM_WRAPPER_PATH="$BINDIR/default/lib/pam_wrapper" +PAM_WRAPPER_PATH="$BINDIR/default/third_party/pam_wrapper" pam_winbind="$BINDIR/shared/pam_winbind.so" service_dir="$SELFTEST_TMPDIR/pam_services" diff --git a/third_party/pam_wrapper/libpamtest.c b/third_party/pam_wrapper/libpamtest.c new file mode 100644 index 00000000000..c0ab41dac8e --- /dev/null +++ b/third_party/pam_wrapper/libpamtest.c @@ -0,0 +1,323 @@ +/* + * 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" + +#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) +{ + 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; + } + + rv = pam_start(service, user, &conv, &ph); + if (rv != PAM_SUCCESS) { + return PAMTEST_ERR_START; + } + + 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, ri = 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[ri], prompt); + if (ret != PAM_SUCCESS) { + free_reply(reply, num_msg); + return ret; + } + } else { + reply[ri].resp = NULL; + } + ri++; + } + + 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[ri], prompt); + if (ret != PAM_SUCCESS) { + free_reply(reply, num_msg); + return ret; + } + } + ri++; + } + + cctx->echo_on_idx++; + break; + case PAM_ERROR_MSG: + 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 (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 && ri > 0) { + *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) +{ + struct pamtest_conv_ctx cctx = { + .data = conv_data, + }; + + return _pamtest_conv(service, user, + pamtest_simple_conv, + &cctx, + test_cases, + num_test_cases); +} diff --git a/third_party/pam_wrapper/libpamtest.h b/third_party/pam_wrapper/libpamtest.h new file mode 100644 index 00000000000..0307a2663af --- /dev/null +++ b/third_party/pam_wrapper/libpamtest.h @@ -0,0 +1,278 @@ +/* + * 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 + +/** + * @defgroup pamtest The pamtest API + * + * @{ + */ + +/** + * @brief The enum which describes the operations performed by pamtest(). + */ +enum pamtest_ops { + /** run pam_authenticate to authenticate the account */ + PAMTEST_AUTHENTICATE, + /** run pam_setcred() to establish/delete user credentials */ + PAMTEST_SETCRED, + /** run pam_acct_mgmt() to validate the PAM account */ + PAMTEST_ACCOUNT, + /** run pam_open_session() to start a PAM session */ + PAMTEST_OPEN_SESSION, + /** run pam_close_session() to end a PAM session */ + PAMTEST_CLOSE_SESSION, + /** run pam_chauthtok() to update the authentication token */ + PAMTEST_CHAUTHTOK, + + /** + * If this option is set the test will call pam_getenvlist() and copy + * the environment into case_out.envlist. + */ + PAMTEST_GETENVLIST = 20, + /** + * This will prevent calling pam_end() and will just return the + * PAM handle in case_out.ph. + */ + PAMTEST_KEEPHANDLE, +}; + + +/** + * @brief The PAM testcase struction. Use the pam_test and pam_test_flags + * macros to fill them. + * + * @see run_pamtest() + */ +struct pam_testcase { + 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 */ +}; + +/** Initializes a pam_tescase structure. */ +#define pam_test(op, expected) { op, expected, 0, 0, { .envlist = NULL } } +/** Initializes a CMUnitTest structure with additional PAM flags. */ +#define pam_test_flags(op, expected, flags) { op, expected, flags, 0, { .envlist = NULL } } + +/** + * @brief The return code of the pamtest function + */ +enum pamtest_err { + /** Testcases returns correspond with input */ + PAMTEST_ERR_OK, + /** pam_start() failed */ + PAMTEST_ERR_START, + /** A testcase failed. Use pamtest_failed_case */ + PAMTEST_ERR_CASE, + /** Could not run a test case */ + PAMTEST_ERR_OP, + /** pam_end failed */ + PAMTEST_ERR_END, + /** Handled internally */ + PAMTEST_ERR_KEEPHANDLE, + /** Internal error - bad input or similar */ + PAMTEST_ERR_INTERNAL, +}; + +/** + * @brief PAM conversation function, defined in pam_conv(3) + * + * This is just a typedef to use in our declarations. See man pam_conv(3) + * for more details. + */ +typedef int (*pam_conv_fn)(int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr); + +/** + * @brief This structure should be used when using run_pamtest, + * which uses an internal conversation function. + */ +struct pamtest_conv_data { + /** When the conversation function receives PAM_PROMPT_ECHO_OFF, + * it reads the auth token from the in_echo_off array and keeps + * an index internally. + */ + const char **in_echo_off; + /** When the conversation function receives PAM_PROMPT_ECHO_ON, + * it reads the input from the in_echo_off array and keeps + * an index internally. + */ + const char **in_echo_on; + + /** Captures messages through PAM_TEXT_INFO. The test caller is + * responsible for allocating enough space in the array. + */ + char **out_err; + /** Captures messages through PAM_ERROR_MSG. The test caller is + * responsible for allocating enough space in the array. + */ + char **out_info; +}; + +#ifdef DOXYGEN +/** + * @brief Run libpamtest test cases + * + * This is using the default libpamtest conversation function. + * + * @param[in] service The PAM service to use in the conversation + * + * @param[in] user The user to run conversation as + * + * @param[in] conv_fn Test-specific conversation function + * + * @param[in] conv_userdata Test-specific conversation data + * + * @param[in] test_cases List of libpamtest test cases. Must end with + * PAMTEST_CASE_SENTINEL + * + * @code + * int main(void) { + * int rc; + * const struct pam_testcase tests[] = { + * pam_test(PAM_AUTHENTICATE, PAM_SUCCESS), + * }; + * + * rc = run_pamtest(tests, NULL, NULL); + * + * return rc; + * } + * @endcode + * + * @return PAMTEST_ERR_OK on success, else the error code matching the failure. + */ +enum pamtest_err run_pamtest_conv(const char *service, + const char *user, + pam_conv_fn conv_fn, + void *conv_userdata, + struct pam_testcase test_cases[]); +#else +#define run_pamtest_conv(service, user, conv_fn, conv_data, test_cases) \ + _pamtest_conv(service, user, conv_fn, conv_data, test_cases, sizeof(test_cases)/sizeof(test_cases[0]) +#endif + +#ifdef DOXYGEN +/** + * @brief Run libpamtest test cases + * + * This is using the default libpamtest conversation function. + * + * @param[in] service The PAM service to use in the conversation + * + * @param[in] user The user to run conversation as + * + * @param[in] conv_data Test-specific conversation data + * + * @param[in] test_cases List of libpamtest test cases. Must end with + * PAMTEST_CASE_SENTINEL + * + * @code + * int main(void) { + * int rc; + * const struct pam_testcase tests[] = { + * pam_test(PAM_AUTHENTICATE, PAM_SUCCESS), + * }; + * + * rc = run_pamtest(tests, NULL, NULL); + * + * return rc; + * } + * @endcode + * + * @return PAMTEST_ERR_OK on success, else the error code matching the failure. + */ +enum pamtest_err run_pamtest(const char *service, + const char *user, + struct pamtest_conv_data *conv_data, + struct pam_testcase test_cases[]); +#else +#define run_pamtest(service, user, conv_data, test_cases) \ + _pamtest(service, user, conv_data, test_cases, sizeof(test_cases)/sizeof(test_cases[0])) +#endif + +#ifdef DOXYGEN +/** + * @brief Helper you can call if run_pamtest() fails. + * + * If PAMTEST_ERR_CASE is returned by run_pamtest() you should call this + * function get a pointer to the failed test case. + * + * @param[in] test_cases The array of tests. + * + * @return a pointer to the array of test_cases[] that corresponds to the + * first test case where the expected error code doesn't match the real error + * code. + */ +const struct pam_testcase *pamtest_failed_case(struct pam_testcase *test_cases); +#else +#define pamtest_failed_case(test_cases) \ + _pamtest_failed_case(test_cases, sizeof(test_cases) / sizeof(test_cases[0])) +#endif + +/** + * @brief return a string representation of libpamtest error code. + * + * @param[in] perr libpamtest error code + * + * @return String representation of the perr argument. Never returns NULL. + */ +const char *pamtest_strerror(enum pamtest_err perr); + +/** + * @brief This frees the string array returned by the PAMTEST_GETENVLIST test. + * + * @param[in] envlist The array to free. + */ +void pamtest_free_env(char **envlist); + + +/* Internal function protypes */ +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); + +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); + +const struct pam_testcase *_pamtest_failed_case(struct pam_testcase test_cases[], + size_t num_test_cases); + +/** @} */ + +#endif /* __LIBPAMTEST_H_ */ diff --git a/third_party/pam_wrapper/pam_wrapper.c b/third_party/pam_wrapper/pam_wrapper.c new file mode 100644 index 00000000000..2db9bcca889 --- /dev/null +++ b/third_party/pam_wrapper/pam_wrapper.c @@ -0,0 +1,1694 @@ +/* + * 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 + +#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 + *****************/ + +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) - %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); +} + +/***************** + * 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_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_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); +void pwrap_constructor(void) CONSTRUCTOR_ATTRIBUTE; +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 + 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. + */ +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); +} + +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); +} + +#if defined(HAVE_PAM_VSYSLOG) || defined(HAVE_PAM_SYSLOG) +static void libpam_pam_vsyslog(const pam_handle_t *pamh, + int priority, + const char *fmt, + va_list args) +{ +#ifdef HAVE_PAM_VSYSLOG + pwrap_bind_symbol_libpam(pam_vsyslog); + + pwrap.libpam.symbols._libpam_pam_vsyslog.f(pamh, + priority, + fmt, + args); +#endif +} +#endif + +/********************************************************* + * PWRAP INIT + *********************************************************/ + +#define BUFFER_SIZE 32768 + +/* copy file from src to dst, overwrites dst */ +static int p_copy(const char *src, const char *dst, const char *pdir, 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 (;;) { + char *p; + bread = read(srcfd, buf, BUFFER_SIZE); + if (bread == 0) { + /* done */ + break; + } else if (bread < 0) { + errno = EIO; + rc = -1; + goto out; + } + + /* EXTRA UGLY HACK */ + if (pdir != NULL) { + p = buf; + + while (p < buf + BUFFER_SIZE) { + if (*p == '/') { + cmp = memcmp(p, "/etc/pam.d", 10); + if (cmp == 0) { + memcpy(p, pdir, 10); + } + } + p++; + } + } + + 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, NULL, 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 || tmp > 0xFFFF || errno == ERANGE) { + PWRAP_LOG(PWRAP_LOG_ERROR, + "Failed to parse pid, buf=%s", + buf); + return; + } + + pid = (pid_t)(tmp & 0xFFFF); + + rc = kill(pid, 0); + if (rc == -1) { + PWRAP_LOG(PWRAP_LOG_TRACE, + "Remove stale pam_wrapper dir: %s", + dir); + p_rmdirs(dir); + } + + return; +} + +static void pwrap_init(void) +{ + char tmp_config_dir[] = "/tmp/pam.X"; + size_t len = strlen(tmp_config_dir); + const char *env; + uint32_t i; + int rc; + char pam_library[128] = { 0 }; + char libpam_path[1024] = { 0 }; + ssize_t ret; + FILE *pidfile; + char pidfile_path[1024] = { 0 }; + + if (!pam_wrapper_enabled()) { + return; + } + + if (pwrap.initialised) { + return; + } + + PWRAP_LOG(PWRAP_LOG_DEBUG, "Initialize pam_wrapper"); + + for (i = 0; i < 36; i++) { + struct stat sb; + char c; + + if (i < 10) { + c = (char)(i + 48); + } else { + c = (char)(i + 87); + } + + tmp_config_dir[len - 1] = c; + 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); + continue; + } else if (errno == ENOENT) { + break; + } + } + + if (i == 36) { + PWRAP_LOG(PWRAP_LOG_ERROR, + "Failed to find a possible path to create " + "pam_wrapper config dir: %s", + tmp_config_dir); + exit(1); + } + + 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 pam_wrapper config dir: %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[sizeof(libpam_path)]; + char *dname; + + strncpy(libpam_path_cp, libpam_path, sizeof(libpam_path_cp)); + libpam_path_cp[sizeof(libpam_path_cp) - 1] = '\0'; + + 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 = p_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.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"); +} + +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; +} + +/**************************** + * CONSTRUCTOR + ***************************/ +void pwrap_constructor(void) +{ + /* + * Here is safe place to call pwrap_init() and initialize data + * for main process. + */ + pwrap_init(); +} + + +#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) +{ + PWRAP_LOG(PWRAP_LOG_TRACE, + "pam_start service=%s, user=%s", + service_name, + user); + +#ifdef HAVE_OPENPAM + return pwrap_openpam_start(service_name, + user, + pam_conversation, + pamh); +#else + return libpam_pam_start(service_name, + user, + pam_conversation, + pamh); +#endif +} + + +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"); + + d = getenv("PAM_WRAPPER_USE_SYSLOG"); + if (d != NULL && d[0] == '1') { + libpam_pam_vsyslog(pamh, priority, fmt, args); + return; + } + + 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; +} + +/**************************** + * DESTRUCTOR + ***************************/ + +static int p_rmdirs_at(const char *path, int parent_fd) +{ + DIR *d; + struct dirent *dp; + struct stat sb; + int path_fd; + int rc; + + /* If path is absolute, parent_fd is ignored. */ + PWRAP_LOG(PWRAP_LOG_TRACE, + "p_rmdirs_at removing %s at %d\n", path, parent_fd); + + 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_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; + } +} diff --git a/third_party/pam_wrapper/pwrap_compat.h b/third_party/pam_wrapper/pwrap_compat.h new file mode 100644 index 00000000000..a30df15c61f --- /dev/null +++ b/third_party/pam_wrapper/pwrap_compat.h @@ -0,0 +1,30 @@ +/* + * 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 . + */ + +#ifdef HAVE_OPENPAM +#include +#endif + +/* OpenPAM doesn't define PAM_BAD_ITEM */ +#ifndef PAM_BAD_ITEM +#define PAM_BAD_ITEM PAM_SYSTEM_ERR +#endif /* PAM_BAD_ITEM */ + +#ifndef ENODATA +#define ENODATA EPIPE +#endif diff --git a/third_party/pam_wrapper/python/pypamtest.c b/third_party/pam_wrapper/python/pypamtest.c new file mode 100644 index 00000000000..e25900f001b --- /dev/null +++ b/third_party/pam_wrapper/python/pypamtest.c @@ -0,0 +1,1112 @@ +/* + * 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 "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; +} TestCaseObject; + +/********************************************************** + *** 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 const char **sequence_as_string_list(PyObject *seq, + const char *paramname) +{ + const char *p = paramname ? paramname : "attribute values"; + const char **ret; + 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 NULL; + } + + len = PySequence_Size(seq); + if (len == -1) { + return NULL; + } + + ret = PyMem_New(const char *, (len + 1)); + if (!ret) { + PyErr_NoMemory(); + return NULL; + } + + 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 NULL; + } + + ret[i] = py_strdup(PyBytes_AsString(utf_item)); + Py_DECREF(utf_item); + if (!ret[i]) { + Py_DECREF(item); + return NULL; + } + Py_DECREF(item); + } + + ret[i] = NULL; + return ret; +} + +static PyObject *string_list_as_tuple(char **str_list) +{ + int rc; + size_t len, i; + PyObject *tup; + PyObject *py_str; + + for (len=0; len < PAM_CONV_MSG_MAX; 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 retured [%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"), + }, + + { 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; + + 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) +{ + 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) { + conv_data->in_echo_off = sequence_as_string_list(py_echo_off, + "echo_off"); + if (conv_data->in_echo_off == NULL) { + free_conv_data(conv_data); + return ENOMEM; + } + } + + if (py_echo_on != NULL) { + conv_data->in_echo_on = sequence_as_string_list(py_echo_on, + "echo_on"); + if (conv_data->in_echo_on == NULL) { + 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; +} + +PyDoc_STRVAR(RunPamTest__doc__, +"Run PAM tests\n\n" +"This function runs PAM test cases and reports result\n" +"Paramaters:\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) +{ + int ok; + int rc; + char *username = NULL; + char *service = NULL; + PyObject *py_test_list; + PyObject *py_echo_off = NULL; + PyObject *py_echo_on = NULL; + Py_ssize_t num_tests; + struct pam_testcase *test_list; + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + TestResultObject *result = NULL; + + (void) module; /* unused */ + + ok = PyArg_ParseTuple(args, + discard_const_p(char, "ssO|OO"), + &username, + &service, + &py_test_list, + &py_echo_off, + &py_echo_on); + 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; + } + + perr = _pamtest(service, username, &conv_data, test_list, num_tests); + 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; + } + 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, + 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; + } + + 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 +} diff --git a/third_party/pam_wrapper/wscript b/third_party/pam_wrapper/wscript new file mode 100644 index 00000000000..75b4eec2314 --- /dev/null +++ b/third_party/pam_wrapper/wscript @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +import os + +VERSION="1.0.4" + +def find_library(library_names, lookup_paths): + for directory in lookup_paths: + for filename in library_names: + libpam_path = os.path.join(directory, filename) + if os.path.exists(libpam_path): + return libpam_path + return '' + +def configure(conf): + if conf.CHECK_PAM_WRAPPER(): + conf.DEFINE('USING_SYSTEM_PAM_WRAPPER', 1) + libpam_wrapper_so_path = 'libpam_wrapper.so' + else: + # check HAVE_GCC_THREAD_LOCAL_STORAGE + conf.CHECK_CODE(''' + __thread int tls; + + int main(void) { + return 0; + } + ''', + 'HAVE_GCC_THREAD_LOCAL_STORAGE', + addmain=False, + msg='Checking for thread local storage') + + # check HAVE_DESTRUCTOR_ATTRIBUTE + conf.CHECK_CODE(''' + void test_destructor_attribute(void) __attribute__ ((destructor)); + + void test_destructor_attribute(void) + { + return; + } + + int main(void) { + return 0; + } + ''', + 'HAVE_DESTRUCTOR_ATTRIBUTE', + addmain=False, + msg='Checking for library destructor support') + + # check HAVE_FUNCTION_ATTRIBUTE_FORMAT + conf.CHECK_CODE(''' + void log_fn(const char *format, ...) __attribute__ ((format (printf, 1, 2))); + + int main(void) { + return 0; + } + ''', + 'HAVE_FUNCTION_ATTRIBUTE_FORMAT', + addmain=False, + msg='Checking for printf format validation support') + + conf.CHECK_HEADERS('security/pam_appl.h') + conf.CHECK_HEADERS('security/pam_modules.h') + conf.CHECK_HEADERS('security/pam_ext.h') + + conf.CHECK_FUNCS_IN('pam_vsyslog', + 'pam', + checklibc=False, + headers='security/pam_ext.h') + + conf.CHECK_FUNCS_IN('pam_syslog', + 'pam', + checklibc=False, + headers='security/pam_ext.h') + + conf.CHECK_C_PROTOTYPE('pam_vprompt', + 'int pam_vprompt(const pam_handle_t *_pamh, int _style, char **_resp, const char *_fmt, va_list _ap)', + define='HAVE_PAM_VPROMPT_CONST', headers='stdio.h sys/types.h security/pam_appl.h security/pam_modules.h') + + conf.CHECK_C_PROTOTYPE('pam_prompt', + 'int pam_prompt(const pam_handle_t *_pamh, int _style, char **_resp, const char *_fmt, ...)', + define='HAVE_PAM_PROMPT_CONST', headers='stdio.h sys/types.h security/pam_appl.h security/pam_modules.h') + + conf.CHECK_C_PROTOTYPE( + 'pam_strerror', + 'const char *pam_strerror(const pam_handle_t *pamh, int errnum)', + define='HAVE_PAM_STRERROR_CONST', + headers='''stdio.h sys/types.h security/pam_appl.h + security/pam_modules.h''') + + # Find the absolute path to libpam.so.0 + libpam_path = find_library(['libpam.so.0', 'libpam.so'], conf.env.STANDARD_LIBPATH) + conf.DEFINE('PAM_LIBRARY', ('"%s"' % libpam_path )) + + # Create full path to pam_wrapper + blddir = os.path.realpath(conf.blddir) + libpam_wrapper_so_path = blddir + '/default/third_party/pam_wrapper/libpam-wrapper.so' + + conf.DEFINE('LIBPAM_WRAPPER_SO_PATH', libpam_wrapper_so_path) + conf.DEFINE('PAM_WRAPPER', 1) + +def build(bld): + if not bld.CONFIG_SET("USING_SYSTEM_PAM_WRAPPER"): + # We need to do it this way or the library wont work. + # Using private_library=True will add symbol version which + # breaks preloading! + bld.SAMBA_LIBRARY('pam_wrapper', + source='pam_wrapper.c', + deps='dl', + install=False, + realname='libpam-wrapper.so') + + # Can be used to write pam tests in python + for env in bld.gen_python_environments(): + bld.SAMBA_PYTHON('pypamtest', + source='python/pypamtest.c libpamtest.c', + deps='dl pam', + install=False) diff --git a/third_party/wscript b/third_party/wscript index 72f1daabcde..9a86dfe44e3 100644 --- a/third_party/wscript +++ b/third_party/wscript @@ -1,6 +1,7 @@ #!/usr/bin/env python import samba_git +import Options import Utils import os import sys @@ -56,6 +57,8 @@ def configure(conf): conf.RECURSE('nss_wrapper') conf.RECURSE('resolv_wrapper') conf.RECURSE('uid_wrapper') + if Options.options.with_pam: + conf.RECURSE('pam_wrapper') def build(bld): @@ -88,3 +91,5 @@ def build(bld): bld.RECURSE('resolv_wrapper') if bld.CONFIG_GET('UID_WRAPPER'): bld.RECURSE('uid_wrapper') + if bld.CONFIG_GET('PAM_WRAPPER'): + bld.RECURSE('pam_wrapper') diff --git a/wscript b/wscript index 7dbaa90e773..b1671022be1 100644 --- a/wscript +++ b/wscript @@ -176,6 +176,11 @@ def configure(conf): else: conf.define('USING_SYSTEM_UID_WRAPPER', 1) + if not conf.CHECK_PAM_WRAPPER(): + raise Utils.WafError('pam_wrapper package has not been found.\nIf third_party is installed, check that it is in the proper place.') + else: + conf.define('USING_SYSTEM_PAM_WRAPPER', 1) + conf.RECURSE('lib/ldb') if not (Options.options.without_ad_dc): @@ -204,8 +209,6 @@ def configure(conf): conf.RECURSE('lib/crypto') conf.RECURSE('pidl') if conf.CONFIG_GET('ENABLE_SELFTEST'): - if Options.options.with_pam: - conf.RECURSE('lib/pam_wrapper') if Options.options.with_ntvfs_fileserver != False: if not (Options.options.without_ad_dc): conf.DEFINE('WITH_NTVFS_FILESERVER', 1) diff --git a/wscript_build b/wscript_build index b7ebaae630e..253f95f8bc0 100644 --- a/wscript_build +++ b/wscript_build @@ -67,8 +67,6 @@ bld.RECURSE('source4/lib/messaging') bld.RECURSE('source4/lib/events') bld.RECURSE('source4/lib/cmdline') bld.RECURSE('source4/lib/http') -if bld.CONFIG_GET('PAM_WRAPPER') and Options.options.with_pam: - bld.RECURSE('lib/pam_wrapper') if bld.CHECK_FOR_THIRD_PARTY(): bld.RECURSE('third_party') bld.RECURSE('source4/lib/stream') -- cgit v1.2.1