From 507dd283282fab01e8d02a19b4144e298c075746 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Fri, 22 Apr 2016 13:53:10 +0200 Subject: [PATCH 12/12] TESTS: Add a unit test for pam_sss Adds a pam_wrapper-powered test for pam_sss. Mostly tests that given the expected input (an action and optionally authtokens on the stack) the module produces the expected output (PAM status code and optionally auth tokens on the stack). --- Makefile.am | 40 +- configure.ac | 6 + contrib/sssd.spec.in | 4 + src/external/cwrap.m4 | 32 + src/sss_client/sss_cli.h | 2 + src/tests/cwrap/Makefile.am | 54 +- src/tests/cwrap/mock_pam_responder.c | 525 ++++++++ src/tests/cwrap/pam_services/test_pam_sss.in | 4 + .../cwrap/pam_services/test_pam_sss_ignore.in | 1 + src/tests/cwrap/pam_services/test_pam_sss_stack.in | 6 + src/tests/cwrap/pam_sss-tests.sh | 5 + src/tests/cwrap/pam_wrapper_test_setup.sh | 13 + src/tests/cwrap/test_pam_sss_common.c | 128 ++ src/tests/cwrap/test_pam_sss_common.h | 63 + src/tests/cwrap/test_wrapper_pam_sss.c | 1346 ++++++++++++++++++++ 15 files changed, 2226 insertions(+), 3 deletions(-) create mode 100644 src/tests/cwrap/mock_pam_responder.c create mode 100644 src/tests/cwrap/pam_services/test_pam_sss.in create mode 100644 src/tests/cwrap/pam_services/test_pam_sss_ignore.in create mode 100644 src/tests/cwrap/pam_services/test_pam_sss_stack.in create mode 100755 src/tests/cwrap/pam_sss-tests.sh create mode 100755 src/tests/cwrap/pam_wrapper_test_setup.sh create mode 100644 src/tests/cwrap/test_pam_sss_common.c create mode 100644 src/tests/cwrap/test_pam_sss_common.h create mode 100644 src/tests/cwrap/test_wrapper_pam_sss.c diff --git a/Makefile.am b/Makefile.am index 85ddf5ccd56442550e2eba548b679fbd53909dc7..687d2c574adf87cd48d2fd43cccf2b4628023422 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2011,7 +2011,7 @@ responder_socket_access_tests_LDADD = \ $(SSSD_INTERNAL_LTLIBS) \ $(SYSTEMD_DAEMON_LIBS) \ libsss_test_common.la -endif +endif # HAVE_CHECK stress_tests_SOURCES = \ src/tests/stress-tests.c @@ -3195,6 +3195,42 @@ pam_sss_la_LDFLAGS = \ -avoid-version \ -Wl,--version-script,$(srcdir)/src/sss_client/sss_pam.exports +if HAVE_PAM_WRAPPER +if HAVE_LIBPAMTEST +check_LTLIBRARIES += \ + pam_test_sss.la \ + $(NULL) + +cwrap_test_modules = pam_test_sss.la +endif # HAVE_LIBPAMTEST +endif # HAVE_PAM_WRAPPER + +pam_test_sss_la_SOURCES = \ + $(pam_sss_la_SOURCES) \ + src/tests/cwrap/mock_pam_responder.c \ + src/responder/common/responder_packet.c \ + src/responder/pam/pamsrv_extract.c \ + src/responder/pam/pamsrv_reply.c \ + src/responder/pam/pamsrv_p11.c \ + src/providers/dp_pam_data_util.c \ + $(NULL) +pam_test_sss_la_CFLAGS = \ + $(AM_CFLAGS) \ + -DPAM_PREAUTH_INDICATOR=TEST_DIR\"/pam_preauth_available\" \ + $(NULL) +pam_test_sss_la_LIBADD = \ + $(pam_sss_la_LIBADD) \ + libsss_debug.la \ + libsss_util.la \ + $(NULL) +pam_test_sss_la_LDFLAGS = \ + $(pam_sss_la_LDFLAGS) \ + -rpath /nowhere \ + -Wl,-wrap,sss_pam_make_request \ + -Wl,-wrap,sss_packet_get_body \ + -Wl,-wrap,sss_parse_name_for_domains \ + $(NULL) + if BUILD_SUDO libsss_sudo_la_SOURCES = \ @@ -4195,7 +4231,7 @@ endif CLEANFILES += *.X */*.X */*/*.X -tests: all $(check_PROGRAMS) +tests: all $(check_PROGRAMS) $(cwrap_test_modules) (cd src/tests/cwrap && $(MAKE) $(AM_MAKEFLAGS) $@) || exit 1; diff --git a/configure.ac b/configure.ac index 8760a8561979664a02bc3c2b2b2994a73825f2c3..6ba7ffa66dae14373ff45197c4b2baacb496198f 100644 --- a/configure.ac +++ b/configure.ac @@ -434,6 +434,9 @@ AM_CONDITIONAL([HAVE_CHECK], [test x$have_check != x]) AM_CHECK_CMOCKA AM_CHECK_UID_WRAPPER AM_CHECK_NSS_WRAPPER +AM_CHECK_PAM_WRAPPER +AM_CHECK_LIBPAMTEST +AM_CHECK_PWRAP_MODULES # Check if the user wants SSSD to be compiled with systemtap probes AM_CHECK_SYSTEMTAP @@ -459,6 +462,9 @@ AC_CONFIG_FILES([Makefile contrib/sssd.spec src/examples/rwtab src/doxy.config po/Makefile.in src/man/Makefile src/tests/cwrap/Makefile src/tests/intg/Makefile src/lib/ipa_hbac/ipa_hbac.pc src/lib/ipa_hbac/ipa_hbac.doxy + src/tests/cwrap/pam_services/test_pam_sss + src/tests/cwrap/pam_services/test_pam_sss_ignore + src/tests/cwrap/pam_services/test_pam_sss_stack src/lib/idmap/sss_idmap.pc src/lib/idmap/sss_idmap.doxy src/sss_client/idmap/sss_nss_idmap.pc src/sss_client/idmap/sss_nss_idmap.doxy diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index 40e4454fab32a9967c4398ac7ea0cc78a5601a90..ea4acada1839c9d9189a0edb99c19c5b850cded0 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -194,6 +194,10 @@ BuildRequires: libcmocka-devel >= 1.0.0 BuildRequires: uid_wrapper BuildRequires: nss_wrapper %endif +%if (0%{?fedora} >= 23) +BuildRequires: pam_wrapper +BuildRequires: libpamtest-devel +%endif BuildRequires: libnl3-devel %if (0%{?use_systemd} == 1) BuildRequires: systemd-devel diff --git a/src/external/cwrap.m4 b/src/external/cwrap.m4 index b8489cc765f34f3bc6ad4e4b7e69626f6ea8060e..8aa2050c79270575e59ee617affc5cc0a7790f63 100644 --- a/src/external/cwrap.m4 +++ b/src/external/cwrap.m4 @@ -28,3 +28,35 @@ AC_DEFUN([AM_CHECK_NSS_WRAPPER], [ AM_CHECK_WRAPPER(nss_wrapper, HAVE_NSS_WRAPPER) ]) + +AC_DEFUN([AM_CHECK_PAM_WRAPPER], +[ + AM_CHECK_WRAPPER(pam_wrapper, HAVE_PAM_WRAPPER) +]) + +AC_DEFUN([AM_CHECK_LIBPAMTEST], +[ + have_libpamtest="no" + PKG_CHECK_EXISTS(libpamtest, + dnl PKG_CHECK_EXISTS ACTION-IF-FOUND + [PKG_CHECK_MODULES([PAMTEST], [libpamtest], [have_libpamtest="yes"])], + dnl PKG_CHECK_EXISTS ACTION-IF-NOT-FOUND + [AC_MSG_WARN([No libpamtest library found, some tests will not be built])] + ) + AM_CONDITIONAL([HAVE_LIBPAMTEST], [test x$have_libpamtest = xyes]) +]) + +AC_DEFUN([AM_CHECK_PWRAP_MODULES], +[ + PKG_CHECK_VAR([PAM_WRAPPER_MODULESDIR], [pam_wrapper], [modules]) + AC_MSG_CHECKING([pam_wrapper helper module path]) + if test x"$PAM_WRAPPER_MODULESDIR" = "x"; then + AC_MSG_RESULT([no]) + AC_SUBST([PAM_WRAPPER_MODULESDIR]) + AC_MSG_WARN([No pam_wrapper helper modules found, some tests will not be built]) + else + AC_MSG_RESULT([yes]) + fi + + AM_CONDITIONAL([HAVE_PWRAP_MODULES], [test x$PAM_WRAPPER_MODULESDIR != x]) +]) diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h index b6610bc6d694fae385f462e1be53c78af0a3d8cb..f1309036b31269a8bf0ace2be5693a44a11cb03a 100644 --- a/src/sss_client/sss_cli.h +++ b/src/sss_client/sss_cli.h @@ -329,7 +329,9 @@ enum sss_authtok_type { #define SSS_START_OF_PAM_REQUEST 0x4d415049 #define SSS_END_OF_PAM_REQUEST 0x4950414d +#ifndef PAM_PREAUTH_INDICATOR /* Unit tests define their own path */ #define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available" +#endif enum pam_item_type { SSS_PAM_ITEM_EMPTY = 0x0000, diff --git a/src/tests/cwrap/Makefile.am b/src/tests/cwrap/Makefile.am index 4104eebc2d0730974e283fb4b14119b8bb8018e1..eea415b4090e63432da58ee10ead2631ddd58655 100644 --- a/src/tests/cwrap/Makefile.am +++ b/src/tests/cwrap/Makefile.am @@ -15,7 +15,6 @@ AM_CPPFLAGS = \ TESTS_ENVIRONMENT = \ CWRAP_TEST_SRCDIR=$(abs_srcdir) \ ABS_TOP_BUILDDIR=$(abs_top_builddir) \ - . $(srcdir)/cwrap_test_setup.sh; \ $(AUX_TESTS_ENVIRONMENT) \ $(NULL) @@ -25,6 +24,12 @@ dist_noinst_SCRIPTS = \ server-tests.sh \ usertools-tests.sh \ responder_common-tests.sh \ + pam_wrapper_test_setup.sh \ + pam_sss-tests.sh \ + $(NULL) + +dist_noinst_HEADERS = \ + test_pam_sss_common.h \ $(NULL) SSSD_LIBS = \ @@ -73,6 +78,15 @@ check_PROGRAMS += \ $(NULL) endif # HAVE_UID_WRAPPER endif # HAVE_NSS_WRAPPER + +if HAVE_PAM_WRAPPER +if HAVE_LIBPAMTEST +check_PROGRAMS += \ + pam_sss_wrapper-tests \ + $(NULL) +endif # HAVE_LIBPAMTEST +endif # HAVE_PAM_WRAPPER + endif # HAVE_CMOCKA TESTS = \ @@ -82,6 +96,17 @@ TESTS = \ responder_common-tests.sh \ $(NULL) +if HAVE_PAM_WRAPPER +if HAVE_LIBPAMTEST +if HAVE_PWRAP_MODULES +TESTS += \ + pam_sss-tests.sh \ + $(NULL) +endif # HAVE_PWRAP_MODULES +endif # HAVE_LIBPAMTEST +endif # HAVE_PAM_WRAPPER + + become_user_tests_SOURCES = \ test_become_user.c \ $(NULL) @@ -177,4 +202,31 @@ negcache_tests_LDADD = \ $(abs_top_builddir)/libsss_test_common.la \ $(NULL) +PAM_WRAPPER_TEST_FLGS = \ + $(AM_CFLAGS) \ + -DPAM_PREAUTH_INDICATOR=TEST_DIR\"/pam_preauth_available\" \ + $(NULL) + +PAM_WRAPPER_TEST_LBS = \ + $(CMOCKA_LIBS) \ + $(TALLOC_LIBS) \ + $(POPT_LIBS) \ + $(PAM_LIBS) \ + $(PAMTEST_LIBS) \ + $(abs_top_builddir)/libsss_util.la \ + $(abs_top_builddir)/libsss_debug.la \ + $(abs_top_builddir)/libsss_test_common.la \ + $(NULL) + +pam_sss_wrapper_tests_SOURCES = \ + test_wrapper_pam_sss.c \ + test_pam_sss_common.c \ + $(NULL) +pam_sss_wrapper_tests_CFLAGS = \ + $(PAM_WRAPPER_TEST_FLGS) \ + $(NULL) +pam_sss_wrapper_tests_LDADD = \ + $(PAM_WRAPPER_TEST_LBS) \ + $(NULL) + tests: $(check_PROGRAMS) diff --git a/src/tests/cwrap/mock_pam_responder.c b/src/tests/cwrap/mock_pam_responder.c new file mode 100644 index 0000000000000000000000000000000000000000..349e2a0a3444e5ceabd1bc5963a12f541308a315 --- /dev/null +++ b/src/tests/cwrap/mock_pam_responder.c @@ -0,0 +1,525 @@ +/* + Copyright (C) 2015 Red Hat + + SSSD tests: PAM tests + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include "sss_cli.h" + +#include "responder/pam/pamsrv.h" +#include "responder/common/responder_packet.h" +#include "responder/common/sss_packet.h" +#include "providers/data_provider.h" + +/* Make linker happy */ +int __wrap_sss_parse_name_for_domains(TALLOC_CTX *memctx, + struct sss_domain_info *domains, + const char *default_domain, + const char *orig, + char **domain, char **name) +{ + char *atsign; + + atsign = strrchr(orig, '@'); + if (atsign == NULL) { + *domain = NULL; + *name = talloc_strdup(memctx, orig); + if (*name == NULL) { + return ENOMEM; + } + return EOK; + } + + *name = talloc_strndup(memctx, orig, atsign - orig); + *domain = talloc_strdup(memctx, atsign+1); + if (*name == NULL || *domain == NULL) { + return ENOMEM; + } + + return EOK; +} + +void __wrap_sss_packet_get_body(struct sss_packet *packet, uint8_t **body, size_t *blen) +{ + *body = packet->buffer; + *blen = packet->memsize; +} + +static struct cli_ctx * +mock_pam_cctx(TALLOC_CTX *mem_ctx, + enum sss_cli_command cmd, + int cli_protocol_version, + struct sss_cli_req_data *rd) +{ + struct cli_ctx *cctx = NULL; + struct pam_ctx *pctx; + struct cli_protocol *prctx; + int ret; + + cctx = talloc_zero(mem_ctx, struct cli_ctx); + if (!cctx) goto fail; + + cctx->rctx = talloc_zero(cctx, struct resp_ctx); + if (cctx->rctx == NULL) goto fail; + + pctx = talloc_zero(cctx->rctx, struct pam_ctx); + if (pctx == NULL) goto fail; + pctx->rctx = cctx->rctx; + + prctx = talloc_zero(cctx, struct cli_protocol); + if (prctx == NULL) goto fail; + + prctx->creq = talloc_zero(prctx, struct cli_request); + if (prctx->creq == NULL) goto fail; + + prctx->cli_protocol_version = talloc_zero(prctx, + struct cli_protocol_version); + if (prctx->cli_protocol_version == NULL) goto fail; + + prctx->cli_protocol_version->version = cli_protocol_version; + + prctx->creq = talloc_zero(cctx, struct cli_request); + if (prctx->creq == NULL) goto fail; + + ret = sss_packet_new(prctx->creq, 0, cmd, &prctx->creq->in); + if (ret != EOK) goto fail; + + prctx->creq->in->buffer = discard_const(rd->data); + prctx->creq->in->memsize = rd->len; + + cctx->protocol_ctx = prctx; + return cctx; + +fail: + talloc_free(cctx); + return NULL; +} + +static struct pam_data * +mock_pam_data(TALLOC_CTX *mem_ctx, enum sss_cli_command cmd) +{ + struct pam_data *pd = NULL; + + pd = talloc_zero(mem_ctx, struct pam_data); + if (pd == NULL) goto fail; + + pd->cmd = cmd; + pd->authtok = sss_authtok_new(pd); + pd->newauthtok = sss_authtok_new(pd); + if (pd->authtok == NULL || pd->newauthtok == NULL) goto fail; + + return pd; + +fail: + talloc_free(pd); + return NULL; +} + +static bool authtok_matches(struct sss_auth_token *authtok, + const char *exp_pass) +{ + int ret; + const char *password; + size_t pwlen; + + ret = sss_authtok_get_password(authtok, &password, &pwlen); + if (ret != EOK) { + return false; + } + + if (strncmp(password, exp_pass, pwlen) == 0) { + return true; + } + + return false; +} + +static int test_auth(struct pam_data *pd, const char *exp_pass) +{ + pd->pam_status = PAM_AUTH_ERR; + + if (authtok_matches(pd->authtok, exp_pass) == true) { + pd->pam_status = PAM_SUCCESS; + } + + return EOK; +} + +static int test_2fa_auth(struct pam_data *pd, + const char *ltp, const char *otp) +{ + errno_t ret; + const char *fa1; + size_t fa1_len; + const char *fa2; + size_t fa2_len; + + pd->pam_status = PAM_AUTH_ERR; + + ret = sss_authtok_get_2fa(pd->authtok, &fa1, &fa1_len, &fa2, &fa2_len); + if (ret != EOK) { + return ret; + } + + if (strncmp(ltp, fa1, fa1_len) == 0 && strncmp(otp, fa2, fa2_len) == 0) { + pd->pam_status = PAM_SUCCESS; + } + + return EOK; +} + +static int test_sc_auth(struct pam_data *pd, const char *exp_pin) +{ + errno_t ret; + const char *pin; + size_t pin_len; + + pd->pam_status = PAM_AUTH_ERR; + + ret = sss_authtok_get_sc_pin(pd->authtok, &pin, &pin_len); + if (ret != EOK) { + return ret; + } + + if (strncmp(pin, exp_pin, pin_len) == 0) { + pd->pam_status = PAM_SUCCESS; + } + + return EOK; +} + +static int test_chauthtok(struct pam_data *pd, + const char *old_pass, + const char *new_pass) +{ + pd->pam_status = PAM_AUTH_ERR; + + if (authtok_matches(pd->authtok, old_pass) == true + && authtok_matches(pd->newauthtok, new_pass) == true) { + pd->pam_status = PAM_SUCCESS; + } + + return EOK; +} + +static int mock_pam_preauth(struct pam_data *pd) +{ + errno_t ret = PAM_SYSTEM_ERR; + char *internal_name; + + if (strcmp(pd->user, "otpuser") == 0) { + ret = pam_resp_otp_info(pd, "test_vendor", + "test_id", "enter PIN for test"); + } else if (strcmp(pd->user, "scuser") == 0) { + internal_name = sss_create_internal_fqname(pd, pd->user, "testdomain"); + if (internal_name == NULL) { + return ENOMEM; + } + + ret = add_pam_cert_response(pd, internal_name, "sc_name"); + talloc_free(internal_name); + pd->pam_status = PAM_SUCCESS; + } + + return ret; +} + +static void mock_pam_add_domain_name(struct pam_data *pd, + const char *domname) +{ + errno_t ret; + + ret = pam_add_response(pd, SSS_PAM_DOMAIN_NAME, strlen(domname)+1, + (uint8_t *) discard_const(domname)); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n"); + } +} + +static int mock_pam_prelim(struct pam_data *pd) +{ + errno_t ret = PAM_SYSTEM_ERR; + + if (strcmp(pd->user, "testuser") == 0) { + ret = test_auth(pd, "secret"); + } else if (strcmp(pd->user, "offlinechpass") == 0) { + ret = test_auth(pd, "secret"); + } else if (strcmp(pd->user, "srvchpass") == 0) { + ret = test_auth(pd, "secret"); + } else if (strcmp(pd->user, "otpuser") == 0) { + ret = test_auth(pd, "secret"); + } + + return ret; +} + +static int mock_pam_auth(struct pam_data *pd) +{ + errno_t ret = PAM_SYSTEM_ERR; + + if (strcmp(pd->user, "testuser") == 0) { + ret = test_auth(pd, "secret"); + if (ret == EOK) { + const char *info_message = "This is a syslog message"; + ret = pam_add_response(pd, SSS_PAM_SYSTEM_INFO, + strlen(info_message)+1, + (const uint8_t *) info_message); + } + } else if (strcmp(pd->user, "offlinechpass") == 0) { + ret = test_auth(pd, "secret"); + } else if (strcmp(pd->user, "textinfo") == 0) { + ret = test_auth(pd, "secret"); + if (ret == EOK) { + const char *info_message = "This is a textinfo message"; + ret = pam_add_response(pd, SSS_PAM_TEXT_MSG, + strlen(info_message)+1, + (const uint8_t *) info_message); + } + } else if (strcmp(pd->user, "srvchpass") == 0) { + ret = test_auth(pd, "secret"); + } else if (strcmp(pd->user, "otpuser") == 0) { + ret = test_2fa_auth(pd, "secret", "1234"); + } else if (strcmp(pd->user, "otpsshuser") == 0) { + ret = test_auth(pd, "secret1234"); + if (ret == EOK) { + ret = pam_resp_otp_used(pd); + } + } else if (strcmp(pd->user, "otpsingle") == 0) { + ret = test_2fa_auth(pd, "secret", "1234"); + if (ret == EOK) { + ret = pam_resp_otp_used(pd); + } + } else if (strcmp(pd->user, "scuser") == 0) { + ret = test_sc_auth(pd, "4321"); + } else if (strcmp(pd->user, "domtest") == 0) { + pd->pam_status = PAM_AUTH_ERR; + if (pd->requested_domains[0] != NULL + && strcmp(pd->requested_domains[0], "mydomain") == 0 + && pd->requested_domains[1] == NULL) { + pd->pam_status = PAM_SUCCESS; + } + + ret = EOK; + } else if (strcmp(pd->user, "retrytest") == 0) { + ret = test_auth(pd, "retried_secret"); + } else if (strcmp(pd->user, "offlineuser") == 0) { + ret = test_auth(pd, "secret"); + if (pd->pam_status == PAM_SUCCESS) { + pamsrv_resp_offline_auth(pd, 123); + } else if (pd->pam_status == PAM_AUTH_ERR) { + pamsrv_resp_offline_delayed_auth(pd, 456); + } + } else if (strcmp(pd->user, "gracelogin") == 0) { + ret = test_auth(pd, "secret"); + if (ret == PAM_SUCCESS) { + pam_resp_grace_login(pd, 1); + } + } else if (strcmp(pd->user, "expirelogin_sec") == 0) { + ret = test_auth(pd, "secret"); + if (ret == PAM_SUCCESS) { + pam_resp_expired_login(pd, 1); + } + } else if (strcmp(pd->user, "expirelogin_min") == 0) { + ret = test_auth(pd, "secret"); + if (ret == PAM_SUCCESS) { + pam_resp_expired_login(pd, 61); + } + } else if (strcmp(pd->user, "expirelogin_hour") == 0) { + ret = test_auth(pd, "secret"); + if (ret == PAM_SUCCESS) { + pam_resp_expired_login(pd, 3601); + } + } else if (strcmp(pd->user, "expirelogin_day") == 0) { + ret = test_auth(pd, "secret"); + if (ret == PAM_SUCCESS) { + pam_resp_expired_login(pd, 24*3601); + } + } else if (strcmp(pd->user, "sshuser") == 0) { + ret = test_auth(pd, "secret"); + if (ret == PAM_SUCCESS) { + pd->pam_status = PAM_ACCT_EXPIRED; + pamsrv_exp_warn(pd, PAM_VERBOSITY_INFO, "SSH user is expired"); + } + } else if (strcmp(pd->user, "reqduser") == 0) { + pd->pam_status = PAM_NEW_AUTHTOK_REQD; + ret = PAM_SUCCESS; + } else if (strcmp(pd->user, "root_chpass") == 0) { + mock_pam_add_domain_name(pd, "chpass_domain"); + pd->pam_status = PAM_PERM_DENIED; + ret = PAM_SUCCESS; + } else if (strcmp(pd->user, "unknown_user") == 0) { + pd->pam_status = PAM_USER_UNKNOWN; + ret = PAM_SUCCESS; + } else if (strcmp(pd->user, "unavail_user") == 0) { + pd->pam_status = PAM_AUTHINFO_UNAVAIL; + ret = PAM_SUCCESS; + } else if (strcmp(pd->user, "emptypass") == 0) { + if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_EMPTY) { + pd->pam_status = PAM_SUCCESS; + ret = PAM_SUCCESS; + } + } + + return ret; +} + +static int mock_pam_chauthtok(struct pam_data *pd) +{ + errno_t ret = PAM_SYSTEM_ERR; + + if (strcmp(pd->user, "testuser") == 0) { + ret = test_chauthtok(pd, "secret", "new_secret"); + } else if (strcmp(pd->user, "offlinechpass") == 0) { + pamsrv_resp_offline_chpass(pd); + pd->pam_status = PAM_AUTH_ERR; + ret = EOK; + } else if (strcmp(pd->user, "srvchpass") == 0) { + pam_resp_srv_msg(pd, "Test server message"); + pd->pam_status = PAM_AUTH_ERR; + ret = EOK; + } else if (strcmp(pd->user, "otpuser") == 0) { + pam_resp_otp_chpass(pd); + pd->pam_status = PAM_SUCCESS; + ret = EOK; + } + + return ret; +} + +static int mock_pam_acct(struct pam_data *pd) +{ + if (strcmp(pd->user, "allowed_user") == 0) { + pd->pam_status = PAM_SUCCESS; + } else if (strcmp(pd->user, "denied_user") == 0) { + pd->pam_status = PAM_PERM_DENIED; + } else if (strcmp(pd->user, "reqduser") == 0) { + pd->pam_status = PAM_SUCCESS; + } + + return EOK; +} + +static int mock_pam_set_cred(struct pam_data *pd) +{ + const char *cred_msg = "CREDS=set"; + + pd->pam_status = PAM_SUCCESS; + return pam_add_response(pd, SSS_ALL_ENV_ITEM, + strlen(cred_msg)+1, + (const uint8_t *) cred_msg); +} + +static int mock_pam_open_session(struct pam_data *pd) +{ + const char *session_msg = "SESSION=open"; + + pd->pam_status = PAM_SUCCESS; + return pam_add_response(pd, SSS_ALL_ENV_ITEM, + strlen(session_msg)+1, + (const uint8_t *) session_msg); +} + +static int mock_pam_close_session(struct pam_data *pd) +{ + pd->pam_status = PAM_SUCCESS; + return EOK; +} + +/* Receives a packed response and returns a mock reply */ +int __wrap_sss_pam_make_request(enum sss_cli_command cmd, + struct sss_cli_req_data *rd, + uint8_t **repbuf, size_t *replen, + int *errnop) +{ + errno_t ret; + TALLOC_CTX *test_ctx; + struct cli_ctx *cctx; + struct pam_data *pd; + struct cli_protocol *prctx; + + test_ctx = talloc_new(NULL); + if (test_ctx == NULL) { + return ENOMEM; + } + + /* The PAM responder functions expect both cctx and pd to be talloc + * contexts + */ + cctx = mock_pam_cctx(test_ctx, cmd, 3, rd); + pd = mock_pam_data(test_ctx, cmd); + if (cctx == NULL || pd == NULL) { + ret = ENOMEM; + goto done; + } + + prctx = talloc_get_type(cctx->protocol_ctx, struct cli_protocol); + + ret = pam_forwarder_parse_data(cctx, pd); + if (ret != EOK) { + goto done; + } + + pd->pam_status = PAM_SYSTEM_ERR; + + switch (cmd) { + case SSS_PAM_PREAUTH: + ret = mock_pam_preauth(pd); + break; + case SSS_PAM_CHAUTHTOK_PRELIM: + ret = mock_pam_prelim(pd); + break; + case SSS_PAM_AUTHENTICATE: + ret = mock_pam_auth(pd); + break; + case SSS_PAM_ACCT_MGMT: + ret = mock_pam_acct(pd); + break; + case SSS_PAM_CHAUTHTOK: + ret = mock_pam_chauthtok(pd); + break; + case SSS_PAM_SETCRED: + ret = mock_pam_set_cred(pd); + break; + case SSS_PAM_OPEN_SESSION: + ret = mock_pam_open_session(pd); + break; + case SSS_PAM_CLOSE_SESSION: + ret = mock_pam_close_session(pd); + break; + default: + break; + } + + if (ret != EOK) { + goto done; + } + + ret = pamsrv_reply_packet(prctx->creq, pd, cmd, &prctx->creq->out); + if (ret != EOK) { + goto done; + } + + *repbuf = malloc(prctx->creq->out->memsize); + memcpy(*repbuf, prctx->creq->out->buffer, prctx->creq->out->memsize); + *replen = prctx->creq->out->memsize; + + ret = EOK; +done: + return ret; +} diff --git a/src/tests/cwrap/pam_services/test_pam_sss.in b/src/tests/cwrap/pam_services/test_pam_sss.in new file mode 100644 index 0000000000000000000000000000000000000000..8cff06112ca65c51a28ca8307c94d1c1c5a74b93 --- /dev/null +++ b/src/tests/cwrap/pam_services/test_pam_sss.in @@ -0,0 +1,4 @@ +auth required @abs_top_builddir@/.libs/pam_test_sss.so +account required @abs_top_builddir@/.libs/pam_test_sss.so +password required @abs_top_builddir@/.libs/pam_test_sss.so +session required @abs_top_builddir@/.libs/pam_test_sss.so diff --git a/src/tests/cwrap/pam_services/test_pam_sss_ignore.in b/src/tests/cwrap/pam_services/test_pam_sss_ignore.in new file mode 100644 index 0000000000000000000000000000000000000000..27fc2794cb5578379d019e85b8bff6ab4e650a09 --- /dev/null +++ b/src/tests/cwrap/pam_services/test_pam_sss_ignore.in @@ -0,0 +1 @@ +auth [ignore=ok default=bad] @abs_top_builddir@/.libs/pam_test_sss.so diff --git a/src/tests/cwrap/pam_services/test_pam_sss_stack.in b/src/tests/cwrap/pam_services/test_pam_sss_stack.in new file mode 100644 index 0000000000000000000000000000000000000000..793cff235ea34ea7e68395aae5d5ec5b204d139b --- /dev/null +++ b/src/tests/cwrap/pam_services/test_pam_sss_stack.in @@ -0,0 +1,6 @@ +auth required @PAM_WRAPPER_MODULESDIR@/pam_set_items.so +auth required @abs_top_builddir@/.libs/pam_test_sss.so +auth required @PAM_WRAPPER_MODULESDIR@/pam_get_items.so +password required @PAM_WRAPPER_MODULESDIR@/pam_set_items.so +password required @abs_top_builddir@/.libs/pam_test_sss.so +password required @PAM_WRAPPER_MODULESDIR@/pam_get_items.so diff --git a/src/tests/cwrap/pam_sss-tests.sh b/src/tests/cwrap/pam_sss-tests.sh new file mode 100755 index 0000000000000000000000000000000000000000..764429f58fd2762845679f54b6db1594c158d12f --- /dev/null +++ b/src/tests/cwrap/pam_sss-tests.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +. $CWRAP_TEST_SRCDIR/pam_wrapper_test_setup.sh + +exec ./pam_sss_wrapper-tests "$@" diff --git a/src/tests/cwrap/pam_wrapper_test_setup.sh b/src/tests/cwrap/pam_wrapper_test_setup.sh new file mode 100755 index 0000000000000000000000000000000000000000..586b8bfffe5f22a548f2d780b241f1ca0063122b --- /dev/null +++ b/src/tests/cwrap/pam_wrapper_test_setup.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +pam_wrapper_libs=$(pkg-config --libs pam_wrapper) + +export LD_PRELOAD="$LD_PRELOAD $pam_wrapper_libs" +if [ -z $pam_wrapper_libs ]; then + echo "Cannot locate cwrap libraries" + exit 2 +fi + +export PAM_WRAPPER=1 +export PAM_WRAPPER_SERVICE_DIR=$(pwd)/pam_services +export PAM_WRAPPER_DEBUGLEVEL=3 diff --git a/src/tests/cwrap/test_pam_sss_common.c b/src/tests/cwrap/test_pam_sss_common.c new file mode 100644 index 0000000000000000000000000000000000000000..4ca68fba4843545f25fbd1cdbdc233ee7c118c9f --- /dev/null +++ b/src/tests/cwrap/test_pam_sss_common.c @@ -0,0 +1,128 @@ +/* + Copyright (C) 2015 Red Hat + + SSSD tests: PAM tests + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "util/util.h" +#include "tests/cmocka/common_mock.h" + +/* Correct testuser's authtok */ +const char *authtoks[] = { + "secret", + NULL, +}; + +/* Correct testuser's old and two new authtoks */ +const char *chauthtoks[] = { + "secret", + "new_secret", + "new_secret", + NULL, +}; + +const char *wrong_authtoks[] = { + "wrong_secret", + NULL, +}; + +/* First authtok is wrong, second one is correct */ +const char *retry_authtoks[] = { + "wrong_secret", + "retried_secret", + NULL, +}; + +const char *otp_authtoks[] = { + "secret", + "1234", + NULL, +}; + +const char *otp_ssh_authtoks[] = { + "secret1234", + "secret1234", + NULL, +}; + +const char *no_authtoks[] = { + NULL, +}; + +void assert_pam_test(enum pamtest_err perr, + const enum pamtest_err perr_exp, + struct pam_testcase *tests) +{ + const struct pam_testcase *tc; + + if (perr != perr_exp) { + tc = pamtest_failed_case(tests); + if (tc == NULL) { + /* Probably pam_start/pam_end failed..*/ + fail_msg("PAM test with pamtest err %d\n", perr); + } + + /* FIXME - would be nice to print index..*/ + fail_msg("PAM test expected %d returned %d\n", + tc->expected_rv, tc->op_rv); + } +} + +enum pamtest_err pamtest_loc(const char *service, + const char *user, + const char *test_locale, + struct pamtest_conv_data *conv_data, + struct pam_testcase *test_cases, + size_t num_test_cases) +{ + char *old_locale; + enum pamtest_err perr; + + old_locale = setlocale(LC_ALL, NULL); + setlocale(LC_ALL, test_locale); + perr = _pamtest(service, user, conv_data, test_cases, num_test_cases); + setlocale(LC_ALL, old_locale); + + return perr; +} + +void test_chpass_conv_loc(const char *service, + const char *user, + const char *test_locale, + int exp_opcode, + char *chpass_info_msg) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + char *info_arr[] = { + chpass_info_msg, + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_CHAUTHTOK, exp_opcode), + }; + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = no_authtoks; + conv_data.out_info = info_arr; + + perr = pamtest_loc(service, user, test_locale, + &conv_data, tests, N_ELEMENTS(tests)); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} diff --git a/src/tests/cwrap/test_pam_sss_common.h b/src/tests/cwrap/test_pam_sss_common.h new file mode 100644 index 0000000000000000000000000000000000000000..e625030c16b6f2779cbd8d8ad71fda3fe16c6e77 --- /dev/null +++ b/src/tests/cwrap/test_pam_sss_common.h @@ -0,0 +1,63 @@ +/* + Copyright (C) 2015 Red Hat + + SSSD tests: PAM tests + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TEST_PAM_SSS_COMMON_H +#define TEST_PAM_SSS_COMMON_H + +#include + +/* Correct testuser's authtok */ +extern const char *authtoks[]; + +/* Correct testuser's old and two new authtoks */ +extern const char *chauthtoks[]; + +extern const char *wrong_authtoks[]; + +/* First authtok is wrong, second one is correct */ +extern const char *retry_authtoks[]; + +extern const char *otp_authtoks[]; + +extern const char *otp_ssh_authtoks[]; + +extern const char *no_authtoks[]; + +void assert_pam_test(enum pamtest_err perr, + const enum pamtest_err perr_exp, + struct pam_testcase *tests); + +enum pamtest_err pamtest_loc(const char *service, + const char *user, + const char *test_locale, + struct pamtest_conv_data *conv_data, + struct pam_testcase *test_cases, + size_t num_test_cases); + +#define pamtest_c_loc(service, user, conv_data, test_cases, num_test_cases) \ + pamtest_loc(service, user, "C", conv_data, test_cases, num_test_cases) + +void test_chpass_conv_loc(const char *service, + const char *user, + const char *test_locale, + int exp_opcode, + char *chpass_info_msg); + + +#endif /* TEST_PAM_SSS_COMMON_H */ diff --git a/src/tests/cwrap/test_wrapper_pam_sss.c b/src/tests/cwrap/test_wrapper_pam_sss.c new file mode 100644 index 0000000000000000000000000000000000000000..f9c80047e6828325ddead0f7551c060c03619b9d --- /dev/null +++ b/src/tests/cwrap/test_wrapper_pam_sss.c @@ -0,0 +1,1346 @@ +/* + Copyright (C) 2015 Red Hat + + SSSD tests: PAM tests + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "util/util.h" +#include "tests/cmocka/common_mock.h" +#include "tests/cwrap/test_pam_sss_common.h" + +static char *service_arg(TALLOC_CTX *mem_ctx, + const char *src_file, + const char *dst_file, + const char *arg) +{ + TALLOC_CTX *tmp_ctx; + const char *dir; + char *dst; + char *src; + errno_t ret; + struct stat sb; + char *svc; + int src_fd = -1; + FILE *dst_f = NULL; + ssize_t nb; + char *line; + size_t nlines = 0; + size_t i; + + dir = getenv("PAM_WRAPPER_RUNTIME_DIR"); + if (dir == NULL) { + return NULL; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + src = talloc_asprintf(tmp_ctx, "%s/%s", dir, src_file); + dst = talloc_asprintf(tmp_ctx, "%s/%s", dir, dst_file); + if (dst == NULL || src == NULL) { + goto fail; + } + + ret = stat(src, &sb); + if (ret == -1) { + goto fail; + } + + /* This is OK, the file is small..*/ + svc = talloc_size(tmp_ctx, sb.st_size + 1); + if (svc == NULL) { + goto fail; + } + + src_fd = open(src, O_RDONLY); + if (src_fd == -1) { + goto fail; + } + + dst_f = fopen(dst, "w"); + if (dst_f == NULL) { + goto fail; + } + + nb = sss_atomic_read_s(src_fd, svc, sb.st_size); + if (nb < sb.st_size) { + goto fail; + } + svc[sb.st_size] = '\0'; + + line = strchr(svc, '\n'); + while (line != NULL) { + *line = '\0'; + line++; + nlines++; + + line = strchr(line, '\n'); + } + + line = svc; + for (i = 0; i < nlines; i++) { + if (strstr(line, "pam_test_sss") != NULL && arg != NULL) { + nb = fprintf(dst_f, "%s %s\n", line, arg); + } else { + nb = fprintf(dst_f, "%s\n", line); + } + if (nb < 0) { + goto fail; + } + line += strlen(line) + 1; + } + + ret = EOK; + fflush(dst_f); + fclose(dst_f); + talloc_steal(mem_ctx, dst); + return dst; +fail: + if (dst_f) { + fclose(dst_f); + } + talloc_free(tmp_ctx); + return NULL; +} + +static char *copy_service(TALLOC_CTX *mem_ctx, + const char *src_file, + const char *dst_file) +{ + return service_arg(mem_ctx, src_file, dst_file, NULL); +} + +struct test_svc { + const char *svc_file; +}; + +static const char *find_string_in_list(char **list, const char *key) +{ + char key_eq[strlen(key)+1+1]; /* trailing NULL and '=' */ + + if (list == NULL || key == NULL) { + return NULL; + } + + snprintf(key_eq, sizeof(key_eq), "%s=", key); + for (size_t i = 0; list[i] != NULL; i++) { + if (strncmp(list[i], key_eq, sizeof(key_eq)-1) == 0) { + return list[i] + sizeof(key_eq)-1; + } + } + + return NULL; +} + +static void assert_in_env(struct pam_testcase *test, + const char *key, + const char *val) +{ + const char *v; + + v = find_string_in_list(test->case_out.envlist, key); + assert_non_null(v); + assert_string_equal(v, val); +} + +static void assert_not_in_env(struct pam_testcase *test, + const char *key) +{ + const char *v; + + v = find_string_in_list(test->case_out.envlist, key); + assert_null(v); +} + +static void test_auth_conv(const char *svc, + const char *username, + int auth_opcode, + char *auth_info_msg) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + char *info_arr[] = { + auth_info_msg, + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, auth_opcode), + }; + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = authtoks; + conv_data.out_info = info_arr; + + perr = pamtest_c_loc(svc, username, &conv_data, tests, N_ELEMENTS(tests)); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_chpass_conv(const char *svc, + const char *username, + int exp_opcode, + char *chpass_info_msg) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + char *info_arr[] = { + chpass_info_msg, + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_CHAUTHTOK, exp_opcode), + }; + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = chauthtoks; + conv_data.out_info = info_arr; + + perr = pamtest_c_loc(svc, username, &conv_data, tests, N_ELEMENTS(tests)); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static int setup_svc(void **state) +{ + struct test_svc *svc; + + svc = talloc_zero(NULL, struct test_svc); + if (svc == NULL) { + return 1; + } + + *state = svc; + return 0; +} + +static int teardown_svc(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + + if (svc != NULL && svc->svc_file != NULL) { + unlink(svc->svc_file); + } + return 0; +} + +static void test_pam_authenticate(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + pam_test(PAMTEST_SETCRED, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + pam_test(PAMTEST_OPEN_SESSION, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + pam_test(PAMTEST_CLOSE_SESSION, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = authtoks; + + perr = run_pamtest("test_pam_sss", "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + + assert_not_in_env(&tests[0], "CREDS"); + assert_not_in_env(&tests[0], "SESSION"); + + assert_in_env(&tests[2], "CREDS", "set"); + assert_not_in_env(&tests[2], "SESSION"); + + assert_in_env(&tests[4], "CREDS", "set"); + assert_in_env(&tests[4], "SESSION", "open"); +} + +static void test_pam_authenticate_offline(void **state) +{ + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_auth_conv("test_pam_sss", "offlineuser", PAM_SUCCESS, auth_info_msg); + assert_string_equal(auth_info_msg, + "Authenticated with cached credentials, " \ + "your cached password will expire at: " \ + "Thu Jan 1 01:02:03 1970."); +} + +static void test_pam_authenticate_textinfo(void **state) +{ + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_auth_conv("test_pam_sss", "textinfo", PAM_SUCCESS, auth_info_msg); + assert_string_equal(auth_info_msg, "This is a textinfo message"); +} + +static void test_pam_authenticate_offline_err(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + char *info_arr[] = { + auth_info_msg, + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = wrong_authtoks; + conv_data.out_info = info_arr; + + perr = pamtest_c_loc("test_pam_sss", "offlineuser", + &conv_data, tests, N_ELEMENTS(tests)); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + + assert_string_equal(auth_info_msg, + "Authentication is denied until: " \ + "Thu Jan 1 01:07:36 1970."); +} + +static void test_pam_auth_new_authtok_reqd(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + char *info_arr[] = { + auth_info_msg, + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + pam_test(PAMTEST_ACCOUNT, PAM_NEW_AUTHTOK_REQD), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = authtoks; + conv_data.out_info = info_arr; + + perr = pamtest_c_loc("test_pam_sss", "reqduser", + &conv_data, tests, N_ELEMENTS(tests)); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + + assert_string_equal(auth_info_msg, + "Password expired. Change your password now."); +} + +static void test_pam_chpass_offline_msg(void **state) +{ + char chpass_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_chpass_conv("test_pam_sss", "offlinechpass", PAM_AUTH_ERR, chpass_info_msg); + assert_string_equal(chpass_info_msg, + "System is offline, password change not possible"); +} + +static void test_pam_chpass_srv_msg(void **state) +{ + char chpass_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_chpass_conv("test_pam_sss", "srvchpass", PAM_AUTH_ERR, chpass_info_msg); + assert_string_equal(chpass_info_msg, + "Password change failed. Server message: Test server message"); +} + +static void test_pam_auth_grace_msg(void **state) +{ + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_auth_conv("test_pam_sss", "gracelogin", PAM_SUCCESS, auth_info_msg); + assert_string_equal(auth_info_msg, + "Your password has expired. " \ + "You have 1 grace login(s) remaining."); +} + +static void test_pam_auth_expire_sec_msg(void **state) +{ + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_auth_conv("test_pam_sss", "expirelogin_sec", PAM_SUCCESS, auth_info_msg); + assert_string_equal(auth_info_msg, + "Your password will expire in 1 second(s)."); +} + +static void test_pam_auth_expire_min_msg(void **state) +{ + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_auth_conv("test_pam_sss", "expirelogin_min", PAM_SUCCESS, auth_info_msg); + assert_string_equal(auth_info_msg, + "Your password will expire in 1 minute(s)."); +} + +static void test_pam_auth_expire_hour_msg(void **state) +{ + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_auth_conv("test_pam_sss", "expirelogin_hour", PAM_SUCCESS, auth_info_msg); + assert_string_equal(auth_info_msg, + "Your password will expire in 1 hour(s)."); +} + +static void test_pam_auth_expire_day_msg(void **state) +{ + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + + (void) state; /* unused */ + + test_auth_conv("test_pam_sss", "expirelogin_day", PAM_SUCCESS, auth_info_msg); + assert_string_equal(auth_info_msg, + "Your password will expire in 1 day(s)."); +} + +static void test_pam_authenticate_err(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = wrong_authtoks; + + perr = run_pamtest("test_pam_sss", "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_null_password(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = no_authtoks; + + perr = run_pamtest("test_pam_sss", "emptypass", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_acct(void **state) +{ + enum pamtest_err perr; + struct pam_testcase tests[] = { + pam_test(PAMTEST_ACCOUNT, PAM_SUCCESS), + }; + + (void) state; /* unused */ + + perr = run_pamtest("test_pam_sss", "allowed_user", NULL, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_acct_err(void **state) +{ + enum pamtest_err perr; + struct pam_testcase tests[] = { + pam_test(PAMTEST_ACCOUNT, PAM_PERM_DENIED), + }; + + (void) state; /* unused */ + + perr = run_pamtest("test_pam_sss", "denied_user", NULL, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_chauthtok(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_CHAUTHTOK, PAM_SUCCESS), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = chauthtoks; + + perr = run_pamtest("test_pam_sss", "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_chauthtok_prelim_fail(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_CHAUTHTOK, PAM_AUTH_ERR), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = wrong_authtoks; + + perr = run_pamtest("test_pam_sss", "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_chauthtok_diff_authtoks(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + const char *testuser_authtoks[] = { + "secret", + "new_secret", + "different_secret", + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_CHAUTHTOK, PAM_CRED_ERR), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = testuser_authtoks; + + perr = run_pamtest("test_pam_sss", "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_root(void **state) +{ + enum pamtest_err perr; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_USER_UNKNOWN), + }; + + (void) state; /* unused */ + + perr = run_pamtest("test_pam_sss_ignore", "root", NULL, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_root_ignore(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_IGNORE), + }; + const char *svcname = "test_pam_sss_ignore_arg"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_ignore", + svcname, "ignore_unknown_user"); + assert_non_null(svc->svc_file); + + perr = run_pamtest(svcname, "root", NULL, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_unknown(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase no_opt_tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_USER_UNKNOWN), + }; + struct pam_testcase opt_tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_IGNORE), + }; + const char *svcname = "test_pam_sss_ignore_unknown_user"; + const char *username = "unknown_user"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_ignore", + svcname, "ignore_unknown_user"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = authtoks; + + /* No option should return user_unknown */ + perr = run_pamtest("test_pam_sss_ignore", username, &conv_data, no_opt_tests); + assert_pam_test(perr, PAMTEST_ERR_OK, no_opt_tests); + + /* With option should return ignore */ + perr = run_pamtest(svcname, username, &conv_data, opt_tests); + assert_pam_test(perr, PAMTEST_ERR_OK, opt_tests); +} + +static void test_pam_authenticate_unavail(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase no_opt_tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTHINFO_UNAVAIL), + }; + struct pam_testcase opt_tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_IGNORE), + }; + const char *svcname = "test_pam_sss_ignore_unavail_user"; + const char *username = "unavail_user"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_ignore", + svcname, "ignore_authinfo_unavail"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = authtoks; + + /* No option should return user_unavail */ + perr = run_pamtest("test_pam_sss_ignore", username, &conv_data, no_opt_tests); + assert_pam_test(perr, PAMTEST_ERR_OK, no_opt_tests); + + /* With option should return ignore */ + perr = run_pamtest(svcname, username, &conv_data, opt_tests); + assert_pam_test(perr, PAMTEST_ERR_OK, opt_tests); +} + +static void test_pam_authenticate_domains(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_domains"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "domains=mydomain"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = authtoks; + + perr = run_pamtest(svcname, "domtest", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_domains_err(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SYSTEM_ERR), + }; + const char *svcname = "test_pam_sss_domains_err"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "domains="); + assert_non_null(svc->svc_file); + + perr = run_pamtest(svcname, "domtest", NULL, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_retry(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_retry"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "retry=1"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = retry_authtoks; + + perr = run_pamtest(svcname, "retrytest", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_retry_neg(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), + }; + const char *svcname = "test_pam_sss_retry"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "retry=-1"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = retry_authtoks; + + perr = run_pamtest(svcname, "retrytest", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_retry_noarg(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), + }; + const char *svcname = "test_pam_sss_retry"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "retry"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = retry_authtoks; + + perr = run_pamtest(svcname, "retrytest", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_retry_eparse(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), + }; + const char *svcname = "test_pam_sss_retry"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "retry=xxx"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = retry_authtoks; + + perr = run_pamtest(svcname, "retrytest", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_unknown_opt(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), + }; + const char *svcname = "test_pam_sss_nosuchopt"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "nosuchopt"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = retry_authtoks; + + perr = run_pamtest(svcname, "retrytest", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} +static void test_pam_authenticate_ssh_expire(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + const char *svcname = "sshd"; + + /* This test only works with sshd service */ + svc->svc_file = copy_service(svc, "test_pam_sss", svcname); + assert_non_null(svc->svc_file); + + test_auth_conv(svcname, "sshuser", PAM_ACCT_EXPIRED, auth_info_msg); + assert_string_equal(auth_info_msg, + "Permission denied. Server message: " \ + "SSH user is expired"); +} + +static void test_pam_authenticate_stack_forward_pass(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_forward"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_stack", + svcname, "forward_pass"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = authtoks; + + /* No authtok passed on w/o forward_pass */ + perr = run_pamtest("test_pam_sss_stack", "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + assert_not_in_env(&tests[1], "PAM_AUTHTOK"); + + /* Authtok passed on with forward_pass */ + perr = run_pamtest(svcname, "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + assert_in_env(&tests[1], "PAM_AUTHTOK", "secret"); +} + +static void test_pam_authenticate_stack_use_first_pass(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase neg_tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_use_first_pass"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_stack", + svcname, "use_first_pass"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + + /* No authtok passed onto the stack, must error... */ + perr = run_pamtest(svcname, "testuser", &conv_data, neg_tests); + assert_pam_test(perr, PAMTEST_ERR_OK, neg_tests); + + /* Authtok passed onto the stack, should be used.. */ + setenv("PAM_AUTHTOK", "secret", 1); + perr = run_pamtest(svcname, "testuser", &conv_data, tests); + unsetenv("PAM_AUTHTOK"); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_chauthtok_stack_forward_pass(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_CHAUTHTOK, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_forward"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_stack", + svcname, "forward_pass"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = chauthtoks; + + /* No authtok passed on w/o forward_pass */ + perr = run_pamtest("test_pam_sss_stack", "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + assert_not_in_env(&tests[1], "PAM_AUTHTOK"); + + /* Authtok passed on with forward_pass */ + perr = run_pamtest(svcname, "testuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + assert_in_env(&tests[1], "PAM_AUTHTOK", "new_secret"); +} + +static void test_pam_chauthtok_stack_use_authtok(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase neg_tests[] = { + pam_test(PAMTEST_CHAUTHTOK, PAM_AUTH_ERR), + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_CHAUTHTOK, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_use_authtok"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_stack", + svcname, "use_authtok"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = authtoks; + + /* No authtok passed onto the stack, must error... */ + perr = run_pamtest(svcname, "testuser", &conv_data, neg_tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + + /* Authtok passed onto the stack, should be used.. */ + setenv("PAM_OLDAUTHTOK", "secret", 1); + setenv("PAM_AUTHTOK", "new_secret", 1); + perr = run_pamtest(svcname, "testuser", &conv_data, tests); + unsetenv("PAM_AUTHTOK"); + unsetenv("PAM_OLDAUTHTOK"); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static int setup_preauth(void **state) +{ + struct test_svc *svc; + int rv; + int fd; + const char *file = PAM_PREAUTH_INDICATOR; + + rv = setup_svc((void **) &svc); + if (rv != 0) { + return rv; + } + + errno = 0; + fd = open(file, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW, + 0644); + if (fd < 0 && errno != EEXIST) { + return 1; + } + + *state = svc; + return 0; +} + +static int teardown_preauth(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + int rv; + + rv = teardown_svc((void **) &svc); + if (rv != 0) { + return rv; + } + + unlink(PAM_PREAUTH_INDICATOR); + return 0; +} + +static void test_pam_authenticate_otp_auth(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_otp_auth"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "use_2fa"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = otp_authtoks; + + perr = run_pamtest(svcname, "otpuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + assert_not_in_env(&tests[1], "PAM_AUTHTOK"); +} + +static void test_pam_authenticate_otp_ssh_auth(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + const char *svcname = "sshd"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_stack", + svcname, "use_2fa"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = otp_ssh_authtoks; + + perr = run_pamtest(svcname, "otpsshuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + /* Even though internally SSS_AUTHTOK_TYPE_PASSWORD is used + * as SSHD combines the passwords, the response must include + * the OTP flag so that the password is not forwarded in the + * stack + */ + assert_not_in_env(&tests[1], "PAM_AUTHTOK"); +} + +static void test_pam_authenticate_otp_auth_forward_pass(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_otp_auth"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_stack", + svcname, "use_2fa forward_pass"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = otp_authtoks; + + perr = run_pamtest(svcname, "otpuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + /* Only first factor must be forwarded */ + assert_in_env(&tests[1], "PAM_AUTHTOK", "secret"); +} + +static void test_pam_authenticate_otp_auth_forward_pass_single(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + char *info_arr[] = { + auth_info_msg, + NULL, + }; + const char *svcname = "test_pam_sss_otp_auth"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss_stack", + svcname, "use_2fa forward_pass"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = otp_authtoks; + conv_data.out_info = info_arr; + + perr = run_pamtest(svcname, "otpsingle", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + /* Only first factor must not be forwarded if backend sends SSS_OTP*/ + assert_not_in_env(&tests[1], "PAM_AUTHTOK"); +} + +static void test_pam_authenticate_otp_missing_factor(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_CRED_INSUFFICIENT), + }; + const char *otp_one_factor[] = { + "", + "1234", + NULL, + }; + + const char *svcname = "test_pam_sss_otp_auth"; + + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "use_2fa"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = otp_one_factor; + + perr = run_pamtest(svcname, "otpuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_otp_chpass_msg(void **state) +{ + struct test_svc *svc = talloc_get_type(*state, struct test_svc); + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + const char *otp_chpass_authtoks[] = { + "secret", + "new_secret", + "new_secret", + NULL, + }; + char auth_info_msg[PAM_MAX_MSG_SIZE] = { '\0' }; + char *info_arr[] = { + auth_info_msg, + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_CHAUTHTOK, PAM_SUCCESS), + pam_test(PAMTEST_GETENVLIST, PAM_SUCCESS), + }; + const char *svcname = "test_pam_sss_otp_auth"; + + /* Copy file from the previous test and just add an argument. The retval + * will be different this time + */ + svc->svc_file = service_arg(svc, "test_pam_sss", + svcname, "use_2fa"); + assert_non_null(svc->svc_file); + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = otp_chpass_authtoks; + conv_data.out_info = info_arr; + + perr = run_pamtest(svcname, "otpuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); + assert_string_equal(auth_info_msg, + "After changing the OTP password, you need to log out " + "and back in order to acquire a ticket"); +} + +static void test_pam_authenticate_sc(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + const char *sc_authtoks[] = { + "4321", + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_SUCCESS), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = sc_authtoks; + + perr = run_pamtest("test_pam_sss", "scuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +static void test_pam_authenticate_sc_err(void **state) +{ + enum pamtest_err perr; + struct pamtest_conv_data conv_data; + const char *sc_authtoks[] = { + "666", + NULL, + }; + struct pam_testcase tests[] = { + pam_test(PAMTEST_AUTHENTICATE, PAM_AUTH_ERR), + }; + + (void) state; /* unused */ + + ZERO_STRUCT(conv_data); + conv_data.in_echo_off = sc_authtoks; + + perr = run_pamtest("test_pam_sss", "scuser", &conv_data, tests); + assert_pam_test(perr, PAMTEST_ERR_OK, tests); +} + +int main(int argc, const char *argv[]) +{ + poptContext pc; + int opt; + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + POPT_TABLEEND + }; + + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_pam_authenticate), + cmocka_unit_test(test_pam_authenticate_err), + cmocka_unit_test(test_pam_authenticate_null_password), + cmocka_unit_test(test_pam_authenticate_offline), + cmocka_unit_test(test_pam_authenticate_offline_err), + cmocka_unit_test(test_pam_authenticate_textinfo), + cmocka_unit_test(test_pam_auth_new_authtok_reqd), + cmocka_unit_test(test_pam_auth_expire_sec_msg), + cmocka_unit_test(test_pam_auth_expire_min_msg), + cmocka_unit_test(test_pam_auth_expire_hour_msg), + cmocka_unit_test(test_pam_auth_expire_day_msg), + cmocka_unit_test(test_pam_auth_grace_msg), + cmocka_unit_test(test_pam_chpass_offline_msg), + cmocka_unit_test(test_pam_chpass_srv_msg), + cmocka_unit_test(test_pam_acct), + cmocka_unit_test(test_pam_acct_err), + cmocka_unit_test(test_pam_chauthtok), + cmocka_unit_test(test_pam_chauthtok_prelim_fail), + cmocka_unit_test(test_pam_chauthtok_diff_authtoks), + cmocka_unit_test(test_pam_authenticate_root), + cmocka_unit_test_setup_teardown(test_pam_authenticate_root_ignore, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_unknown, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_unavail, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_domains, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_domains_err, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_retry, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_retry_noarg, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_retry_neg, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_retry_eparse, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_unknown_opt, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_ssh_expire, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_stack_forward_pass, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_stack_use_first_pass, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_chauthtok_stack_forward_pass, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_chauthtok_stack_use_authtok, + setup_svc, + teardown_svc), + cmocka_unit_test_setup_teardown(test_pam_authenticate_otp_auth, + setup_preauth, + teardown_preauth), + cmocka_unit_test_setup_teardown(test_pam_authenticate_otp_ssh_auth, + setup_preauth, + teardown_preauth), + cmocka_unit_test_setup_teardown(test_pam_authenticate_otp_auth_forward_pass, + setup_preauth, + teardown_preauth), + cmocka_unit_test_setup_teardown(test_pam_authenticate_otp_auth_forward_pass_single, + setup_preauth, + teardown_preauth), + cmocka_unit_test_setup_teardown(test_pam_authenticate_otp_missing_factor, + setup_preauth, + teardown_preauth), + cmocka_unit_test_setup_teardown(test_pam_authenticate_otp_chpass_msg, + setup_preauth, + teardown_preauth), + cmocka_unit_test_setup_teardown(test_pam_authenticate_sc, + setup_preauth, + teardown_preauth), + cmocka_unit_test_setup_teardown(test_pam_authenticate_sc_err, + setup_preauth, + teardown_preauth), + }; + + /* Set debug level to invalid value so we can deside if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + return 1; + } + } + poptFreeContext(pc); + + DEBUG_CLI_INIT(debug_level); + tests_set_cwd(); + + setenv("PAM_WRAPPER", "1", 1); + return cmocka_run_group_tests(tests, NULL, NULL); +} -- 2.7.4