summaryrefslogtreecommitdiff
path: root/third_party
diff options
context:
space:
mode:
authorAndreas Schneider <asn@samba.org>2017-11-24 13:34:25 +0100
committerAndreas Schneider <asn@cryptomilk.org>2017-11-25 10:14:13 +0100
commit8635465d77496e882ca73477690bbd20d877a197 (patch)
tree6af1d731f589e20de0efd489a0b5b565a6f022f8 /third_party
parent6fbc8b810e094b946b92fb720953400fe28041ab (diff)
downloadsamba-8635465d77496e882ca73477690bbd20d877a197.tar.gz
build: Move pam_wrapper to third_party
Signed-off-by: Andreas Schneider <asn@samba.org> Reviewed-by: Alexander Bokovoy <ab@samba.org>
Diffstat (limited to 'third_party')
-rw-r--r--third_party/pam_wrapper/libpamtest.c323
-rw-r--r--third_party/pam_wrapper/libpamtest.h278
-rw-r--r--third_party/pam_wrapper/pam_wrapper.c1694
-rw-r--r--third_party/pam_wrapper/pwrap_compat.h30
-rw-r--r--third_party/pam_wrapper/python/pypamtest.c1112
-rw-r--r--third_party/pam_wrapper/wscript117
-rw-r--r--third_party/wscript5
7 files changed, 3559 insertions, 0 deletions
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 <asn@samba.org>
+ * Copyright (c) 2015 Jakub Hrozek <jakub.hrozek@posteo.se>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#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 <asn@samba.org>
+ * Copyright (c) 2015 Jakub Hrozek <jakub.hrozek@posteo.se>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBPAMTEST_H_
+#define __LIBPAMTEST_H_
+
+#include <stdint.h>
+#include <security/pam_appl.h>
+
+/**
+ * @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 <asn@samba.org>
+ * Copyright (c) 2015 Jakub Hrozek <jakub.hrozek@posteo.se>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <libgen.h>
+#include <signal.h>
+#include <limits.h>
+
+#include <ftw.h>
+
+#ifdef HAVE_SECURITY_PAM_APPL_H
+#include <security/pam_appl.h>
+#endif
+#ifdef HAVE_SECURITY_PAM_MODULES_H
+#include <security/pam_modules.h>
+#endif
+#ifdef HAVE_SECURITY_PAM_EXT_H
+#include <security/pam_ext.h>
+#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 <asn@samba.org>
+ * Copyright (c) 2015 Jakub Hrozek <jakub.hrozek@posteo.se>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_OPENPAM
+#include <security/openpam.h>
+#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 <asn@samba.org>
+ * Copyright (c) 2015 Jakub Hrozek <jakub.hrozek@posteo.se>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <Python.h>
+#include <structmember.h>
+
+#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')