Skip to content

Commit

Permalink
tests UPDATE add back PAM test
Browse files Browse the repository at this point in the history
  • Loading branch information
roman authored and michalvasko committed Dec 14, 2023
1 parent d46b5a6 commit 7d13668
Show file tree
Hide file tree
Showing 4 changed files with 570 additions and 12 deletions.
8 changes: 8 additions & 0 deletions CMakeModules/FindLibPAM.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ else()

if(LIBPAM_INCLUDE_DIR AND LIBPAM_LIBRARY)
set(LIBPAM_FOUND TRUE)

# check if the function pam_start_confdir is in pam_appl.h header (added in PAM 1.4)
file(STRINGS ${LIBPAM_INCLUDE_DIR}/security/pam_appl.h PAM_CONFDIR REGEX "pam_start_confdir")
if ("${PAM_CONFDIR}" STREQUAL "")
set(LIBPAM_HAVE_CONFDIR FALSE)
else()
set(LIBPAM_HAVE_CONFDIR TRUE)
endif()
else()
set(LIBPAM_FOUND FALSE)
endif()
Expand Down
41 changes: 29 additions & 12 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,33 @@ if(ENABLE_SSH_TLS)
list(APPEND tests test_auth test_two_channels test_ks_ts test_ec
test_ed25519 test_replace test_endpt_share_clients test_tls test_crl test_ch
test_runtime_changes test_client_ssh test_client_tls)

if (LIBPAM_HAVE_CONFDIR)
list(APPEND tests test_pam)
endif()
endif()

foreach(src IN LISTS libsrc)
list(APPEND test_srcs "../${src}")
endforeach()
add_library(testobj OBJECT ${test_srcs} ${compatsrc})

# add -Wl,--wrap flags
set(test test_client_ssh)
set(${test}_mock_funcs connect ssh_connect ssh_userauth_none ssh_userauth_kbdint ssh_is_connected
# set the mocked functions for the tests
set(mock_tests test_client_ssh test_client_tls test_pam)
set(test_client_ssh_mock_funcs connect ssh_connect ssh_userauth_none ssh_userauth_kbdint ssh_is_connected
ssh_channel_open_session ssh_channel_request_subsystem ssh_channel_is_close ssh_channel_write
ssh_channel_poll_timeout ssh_userauth_password nc_handshake_io nc_ctx_check_and_fill
ssh_userauth_try_publickey ssh_userauth_publickey nc_sock_listen_inet nc_sock_accept_binds nc_accept_callhome_ssh_sock)
set(${test}_wrap_link_flags "-Wl")
foreach(mock_func IN LISTS ${test}_mock_funcs)
set(${test}_wrap_link_flags "${${test}_wrap_link_flags},--wrap=${mock_func}")
endforeach()
set(test_client_tls_mock_funcs connect SSL_connect nc_send_hello_io nc_handshake_io nc_ctx_check_and_fill)
set(test_pam_mock_funcs pam_start)

set(test test_client_tls)
set(${test}_mock_funcs connect SSL_connect nc_send_hello_io nc_handshake_io nc_ctx_check_and_fill)
set(${test}_wrap_link_flags "-Wl")
foreach(mock_func IN LISTS ${test}_mock_funcs)
set(${test}_wrap_link_flags "${${test}_wrap_link_flags},--wrap=${mock_func}")
# add -Wl,--wrap flags to tests that require it
set(moc_funcs test_client_ssh_mock_funcs test_client_tls_mock_funcs test_pam_mock_funcs)
foreach(mock_test IN LISTS mock_tests)
set(${mock_test}_wrap_link_flags "-Wl")
foreach(mock_func IN LISTS ${mock_test}_mock_funcs)
set(${mock_test}_wrap_link_flags "${${mock_test}_wrap_link_flags},--wrap=${mock_func}")
endforeach()
endforeach()

foreach(test_name IN LISTS tests)
Expand All @@ -58,3 +62,16 @@ endif()

include_directories(${CMAKE_SOURCE_DIR}/src ${PROJECT_BINARY_DIR})
configure_file("${PROJECT_SOURCE_DIR}/tests/config.h.in" "${PROJECT_BINARY_DIR}/tests/config.h" ESCAPE_QUOTES @ONLY)

# compile PAM test module
add_library(pam_netconf SHARED ${CMAKE_SOURCE_DIR}/tests/pam/pam_netconf.c)
set_target_properties(pam_netconf PROPERTIES PREFIX "")
target_link_libraries(pam_netconf ${LIBPAM_LIBRARIES})

# generate PAM configuration file
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/netconf.conf
"#%PAM-1.4\n"
"auth required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
"account required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
"password required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
)
311 changes: 311 additions & 0 deletions tests/pam/pam_netconf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
/**
* @file pam_netconf.c
* @author Roman Janota <[email protected]>
* @brief libnetconf2 Linux PAM test module
*
* @copyright
* Copyright (c) 2022 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/

#include <security/pam_modules.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "config.h"

#define N_MESSAGES 2
#define N_REQUESTS 2

/**
* @brief Exchange module's messages for user's replies.
*
* @param[in] pam_h PAM handle.
* @param[in] n_messages Number of messages.
* @param[in] msg Module's messages for the user.
* @param[out] resp User's responses.
* @return PAM_SUCCESS on success;
* @return PAM error otherwise.
*/
static int
nc_pam_mod_call_clb(pam_handle_t *pam_h, int n_messages, const struct pam_message **msg, struct pam_response **resp)
{
struct pam_conv *conv;
int r;

/* the callback can be accessed through the handle */
r = pam_get_item(pam_h, PAM_CONV, (void *) &conv);
if (r != PAM_SUCCESS) {
return r;
}
return conv->conv(n_messages, msg, resp, conv->appdata_ptr);
}

/**
* @brief Validate the user's responses.
*
* @param[in] username Username.
* @param[in] reversed_username User's response to the first challenge.
* @param[in] eq_ans User's response to the second challenge.
* @return PAM_SUCCESS on success;
* @return PAM_AUTH_ERR whenever the user's replies are incorrect.
*/
static int
nc_pam_mod_auth(const char *username, char *reversed_username, char *eq_ans)
{
int i, j, r;
size_t len;
char *buffer;

len = strlen(reversed_username);
buffer = calloc(len + 1, sizeof *buffer);
if (!buffer) {
fprintf(stderr, "Memory allocation error.\n");
return PAM_BUF_ERR;
}

/* reverse the user's response */
for (i = len - 1, j = 0; i >= 0; i--) {
buffer[j++] = reversed_username[i];
}
buffer[j] = '\0';

if (!strcmp(username, buffer) && !strcmp(eq_ans, "2")) {
/* it's a match */
r = PAM_SUCCESS;
} else {
r = PAM_AUTH_ERR;
}

free(buffer);
return r;
}

/**
* @brief Free the user's responses.
*
* @param[in] resp Responses.
* @param[in] n Number of responses to be freed.
*/
static void
nc_pam_mod_resp_free(struct pam_response *resp, int n)
{
int i;

if (!resp) {
return;
}

for (i = 0; i < n; i++) {
free((resp + i)->resp);
}
free(resp);
}

/**
* @brief Test module's implementation of "auth" service.
*
* Prepare prompts for the client and decide based on his
* answers whether to allow or disallow access.
*
* @param[in] pam_h PAM handle.
* @param[in] flags Flags.
* @param[in] argc Count of module options defined in the PAM configuration file.
* @param[in] argv Module options.
* @return PAM_SUCCESS on success;
* @return PAM error otherwise.
*/
API int
pam_sm_authenticate(pam_handle_t *pam_h, int flags, int argc, const char **argv)
{
int r;
const char *username;
char *reversed_username = NULL, *eq_ans = NULL;
struct pam_message echo_msg, no_echo_msg, unexpected_type_msg, info_msg, err_msg;
const struct pam_message *msg[N_MESSAGES];
struct pam_response *resp = NULL;

(void) flags;
(void) argc;
(void) argv;

/* get the username and if it's not known then the user will be prompted to enter it */
r = pam_get_user(pam_h, &username, NULL);
if (r != PAM_SUCCESS) {
fprintf(stderr, "Unable to get username.\n");
r = PAM_AUTHINFO_UNAVAIL;
goto cleanup;
}

/* prepare the messages */
echo_msg.msg_style = PAM_PROMPT_ECHO_ON;
echo_msg.msg = "Enter your username backwards: ";
no_echo_msg.msg_style = PAM_PROMPT_ECHO_OFF;
no_echo_msg.msg = "Enter the result to 1+1: ";
unexpected_type_msg.msg_style = PAM_AUTH_ERR;
unexpected_type_msg.msg = "Arbitrary test message";
info_msg.msg_style = PAM_TEXT_INFO;
info_msg.msg = "Test info message";
err_msg.msg_style = PAM_ERROR_MSG;
err_msg.msg = "Test error message";

/* tests */
printf("[TEST #1] Too many PAM messages. Output:\n");
r = nc_pam_mod_call_clb(pam_h, 500, msg, &resp);
if (r == PAM_SUCCESS) {
fprintf(stderr, "[TEST #1] Failed.\n");
r = PAM_AUTH_ERR;
goto cleanup;
}
printf("[TEST #1] Passed.\n\n");

printf("[TEST #2] Negative number of PAM messages. Output:\n");
r = nc_pam_mod_call_clb(pam_h, -1, msg, &resp);
if (r == PAM_SUCCESS) {
fprintf(stderr, "[TEST #2] Failed.\n");
r = PAM_AUTH_ERR;
goto cleanup;
}
printf("[TEST #2] Passed.\n\n");

printf("[TEST #3] 0 PAM messages. Output:\n");
r = nc_pam_mod_call_clb(pam_h, 0, msg, &resp);
if (r == PAM_SUCCESS) {
fprintf(stderr, "[TEST #3] Failed.\n");
r = PAM_AUTH_ERR;
goto cleanup;
}
printf("[TEST #3] Passed.\n\n");

printf("[TEST #4] Unexpected message type. Output:\n");
msg[0] = &unexpected_type_msg;
r = nc_pam_mod_call_clb(pam_h, N_MESSAGES, msg, &resp);
if (r == PAM_SUCCESS) {
fprintf(stderr, "[TEST #4] Failed.\n");
r = PAM_AUTH_ERR;
goto cleanup;
}
printf("[TEST #4] Passed.\n\n");

printf("[TEST #5] Info and error messages. Output:\n");
msg[0] = &info_msg;
msg[1] = &err_msg;
r = nc_pam_mod_call_clb(pam_h, N_MESSAGES, msg, &resp);
if (r == PAM_SUCCESS) {
fprintf(stderr, "[TEST #5] Failed.\n");
r = PAM_AUTH_ERR;
goto cleanup;
}
printf("[TEST #5] Passed.\n\n");

printf("[TEST #6] Authentication attempt with an expired token. Output:\n");
/* store the correct messages */
msg[0] = &echo_msg;
msg[1] = &no_echo_msg;

/* get responses */
r = nc_pam_mod_call_clb(pam_h, N_MESSAGES, msg, &resp);
if (r != PAM_SUCCESS) {
fprintf(stderr, "[TEST #6] Failed.\n");
goto cleanup;
}

reversed_username = resp[0].resp;
eq_ans = resp[1].resp;

/* validate the responses */
r = nc_pam_mod_auth(username, reversed_username, eq_ans);

/* not authenticated */
if (r != PAM_SUCCESS) {
fprintf(stderr, "[TEST #6] Failed.\n");
r = PAM_AUTH_ERR;
}

cleanup:
/* free the responses */
nc_pam_mod_resp_free(resp, N_REQUESTS);
return r;
}

/**
* @brief Test module's silly implementation of "account" service.
*
* @param[in] pam_h PAM handle.
* @param[in] flags Flags.
* @param[in] argc The count of module options defined in the PAM configuration file.
* @param[in] argv Module options.
* @return PAM_NEW_AUTHTOK_REQD on success;
* @return PAM error otherwise.
*/
API int
pam_sm_acct_mgmt(pam_handle_t *pam_h, int flags, int argc, const char *argv[])
{
int r;
const void *username;

(void) flags;
(void) argc;
(void) argv;

/* get and check the username */
r = pam_get_item(pam_h, PAM_USER, &username);
if (r != PAM_SUCCESS) {
return r;
}
if (!strcmp((const char *)username, "test")) {
return PAM_NEW_AUTHTOK_REQD;
}
return PAM_SYSTEM_ERR;
}

/**
* @brief Test module's silly implementation of "password" service.
*
* @param[in] pam_h PAM handle.
* @param[in] flags Flags.
* @param[in] argc The count of module options defined in the PAM configuration file.
* @param[in] argv Module options.
* @return PAM_SUCCESS on success;
* @return PAM error otherwise.
*/
API int
pam_sm_chauthtok(pam_handle_t *pam_h, int flags, int argc, const char *argv[])
{
int r;
const void *username;

(void) argc;
(void) argv;

/* the function is called twice, each time with a different flag,
* in the first call just check the username if it matches */
if (flags & PAM_PRELIM_CHECK) {
r = pam_get_item(pam_h, PAM_USER, &username);
if (r != PAM_SUCCESS) {
return r;
}
if (!strcmp((const char *)username, "test")) {
return PAM_SUCCESS;
} else {
return PAM_SYSTEM_ERR;
}

/* change the authentication token in the second call */
} else if (flags & PAM_UPDATE_AUTHTOK) {
r = pam_set_item(pam_h, PAM_AUTHTOK, "test");
if (r == PAM_SUCCESS) {
printf("[TEST #6] Passed.\n\n");
} else {
fprintf(stderr, "[TEST #6] Failed.\n");
}
return r;
}
return PAM_SYSTEM_ERR;
}
Loading

0 comments on commit 7d13668

Please sign in to comment.