lib: Add pam_wrapper 1.0.3
authorAndreas Schneider <asn@samba.org>
Wed, 29 Mar 2017 13:55:53 +0000 (15:55 +0200)
committerStefan Metzmacher <metze@samba.org>
Fri, 7 Apr 2017 08:32:13 +0000 (10:32 +0200)
Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
lib/pam_wrapper/libpamtest.c [new file with mode: 0644]
lib/pam_wrapper/libpamtest.h [new file with mode: 0644]
lib/pam_wrapper/pam_wrapper.c [new file with mode: 0644]
lib/pam_wrapper/pwrap_compat.h [new file with mode: 0644]
lib/pam_wrapper/python/pypamtest.c [new file with mode: 0644]
lib/pam_wrapper/wscript [new file with mode: 0644]
script/compare_cc_results.py
wscript
wscript_build

diff --git a/lib/pam_wrapper/libpamtest.c b/lib/pam_wrapper/libpamtest.c
new file mode 100644 (file)
index 0000000..c0ab41d
--- /dev/null
@@ -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/lib/pam_wrapper/libpamtest.h b/lib/pam_wrapper/libpamtest.h
new file mode 100644 (file)
index 0000000..0307a26
--- /dev/null
@@ -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/lib/pam_wrapper/pam_wrapper.c b/lib/pam_wrapper/pam_wrapper.c
new file mode 100644 (file)
index 0000000..d1ae075
--- /dev/null
@@ -0,0 +1,1686 @@
+/*
+ * 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);
+}
+
+static void libpam_pam_vsyslog(const pam_handle_t *pamh,
+                              int priority,
+                              const char *fmt,
+                              va_list args)
+{
+       pwrap_bind_symbol_libpam(pam_vsyslog);
+
+       pwrap.libpam.symbols._libpam_pam_vsyslog.f(pamh,
+                                                  priority,
+                                                  fmt,
+                                                  args);
+}
+
+/*********************************************************
+ * 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) {
+                       return -1;
+               }
+               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);
+}
+
+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);
+}
+
+#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
new file mode 100644 (file)
index 0000000..a30df15
--- /dev/null
@@ -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/lib/pam_wrapper/python/pypamtest.c b/lib/pam_wrapper/python/pypamtest.c
new file mode 100644 (file)
index 0000000..585f27d
--- /dev/null
@@ -0,0 +1,1103 @@
+/*
+ * 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_INTPTR_T)
+# define discard_const_p(type, ptr) ((type *)((intptr_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
+ **********************************************************/
+
+static const char *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 is fixed and 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
+ **********************************************************/
+
+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."
+);
+
+#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
+
+       PyExc_PamTestError = PyErr_NewExceptionWithDoc(discard_const_p(char, "pypamtest.PamTestError"),
+                                                      PamTestError__doc__,
+                                                      PyExc_EnvironmentError,
+                                                      NULL);
+       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
new file mode 100644 (file)
index 0000000..87181c3
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+
+import os
+
+VERSION="1.0.3"
+
+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')
+
+        # 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)
index fd5817b1a913e710554443812430998c95faf34f..1ab92490426a1e6c6336134104794ba755b7f0a6 100755 (executable)
@@ -12,6 +12,7 @@ exceptions = [
     'CROSS_COMPILE', 'CROSS_ANSWERS', 'CROSS_EXECUTE',
     'LIBSOCKET_WRAPPER_SO_PATH',
     'LIBNSS_WRAPPER_SO_PATH',
+    'LIBPAM_WRAPPER_SO_PATH',
     'LIBUID_WRAPPER_SO_PATH',
     'LIBRESOLV_WRAPPER_SO_PATH',
 ]
diff --git a/wscript b/wscript
index ba6269722cc18f0c8ddb0f6128513fa4b51d19f5..3e4a0cdb6d4fccd9884cd91a09daf597c2654764 100644 (file)
--- a/wscript
+++ b/wscript
@@ -173,6 +173,7 @@ def configure(conf):
     conf.RECURSE('selftest')
     if conf.CONFIG_GET('ENABLE_SELFTEST'):
         conf.RECURSE('lib/nss_wrapper')
+        conf.RECURSE('lib/pam_wrapper')
         conf.RECURSE('lib/resolv_wrapper')
         conf.RECURSE('lib/socket_wrapper')
         conf.RECURSE('lib/uid_wrapper')
index 954eed11061e722ba11f025b2b0268c8ef096697..59e0bdfa4ccaa213d86a6baed6f463bd27cf9436 100644 (file)
@@ -69,6 +69,8 @@ bld.RECURSE('source4/lib/cmdline')
 bld.RECURSE('source4/lib/http')
 if bld.CONFIG_GET('NSS_WRAPPER'):
     bld.RECURSE('lib/nss_wrapper')
+if bld.CONFIG_GET('PAM_WRAPPER'):
+    bld.RECURSE('lib/pam_wrapper')
 if bld.CONFIG_GET('SOCKET_WRAPPER'):
     bld.RECURSE('lib/socket_wrapper')
 if bld.CONFIG_GET('RESOLV_WRAPPER'):