pamtest: Add libpamtest
authorJakub Hrozek <jakub.hrozek@posteo.se>
Fri, 9 Oct 2015 13:39:11 +0000 (15:39 +0200)
committerAndreas Schneider <asn@samba.org>
Thu, 10 Dec 2015 12:31:19 +0000 (13:31 +0100)
CMakeLists.txt
include/CMakeLists.txt [new file with mode: 0644]
include/libpamtest.h [new file with mode: 0644]
libpamtest.pc.cmake [new file with mode: 0644]
pam_wrapper.pc.cmake
src/CMakeLists.txt
src/libpamtest.c [new file with mode: 0644]

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