Skip to content

Commit 7d13668

Browse files
romanmichalvasko
authored andcommitted
tests UPDATE add back PAM test
1 parent d46b5a6 commit 7d13668

File tree

4 files changed

+570
-12
lines changed

4 files changed

+570
-12
lines changed

CMakeModules/FindLibPAM.cmake

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ else()
6262

6363
if(LIBPAM_INCLUDE_DIR AND LIBPAM_LIBRARY)
6464
set(LIBPAM_FOUND TRUE)
65+
66+
# check if the function pam_start_confdir is in pam_appl.h header (added in PAM 1.4)
67+
file(STRINGS ${LIBPAM_INCLUDE_DIR}/security/pam_appl.h PAM_CONFDIR REGEX "pam_start_confdir")
68+
if ("${PAM_CONFDIR}" STREQUAL "")
69+
set(LIBPAM_HAVE_CONFDIR FALSE)
70+
else()
71+
set(LIBPAM_HAVE_CONFDIR TRUE)
72+
endif()
6573
else()
6674
set(LIBPAM_FOUND FALSE)
6775
endif()

tests/CMakeLists.txt

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,33 @@ if(ENABLE_SSH_TLS)
1616
list(APPEND tests test_auth test_two_channels test_ks_ts test_ec
1717
test_ed25519 test_replace test_endpt_share_clients test_tls test_crl test_ch
1818
test_runtime_changes test_client_ssh test_client_tls)
19+
20+
if (LIBPAM_HAVE_CONFDIR)
21+
list(APPEND tests test_pam)
22+
endif()
1923
endif()
2024

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

26-
# add -Wl,--wrap flags
27-
set(test test_client_ssh)
28-
set(${test}_mock_funcs connect ssh_connect ssh_userauth_none ssh_userauth_kbdint ssh_is_connected
30+
# set the mocked functions for the tests
31+
set(mock_tests test_client_ssh test_client_tls test_pam)
32+
set(test_client_ssh_mock_funcs connect ssh_connect ssh_userauth_none ssh_userauth_kbdint ssh_is_connected
2933
ssh_channel_open_session ssh_channel_request_subsystem ssh_channel_is_close ssh_channel_write
3034
ssh_channel_poll_timeout ssh_userauth_password nc_handshake_io nc_ctx_check_and_fill
3135
ssh_userauth_try_publickey ssh_userauth_publickey nc_sock_listen_inet nc_sock_accept_binds nc_accept_callhome_ssh_sock)
32-
set(${test}_wrap_link_flags "-Wl")
33-
foreach(mock_func IN LISTS ${test}_mock_funcs)
34-
set(${test}_wrap_link_flags "${${test}_wrap_link_flags},--wrap=${mock_func}")
35-
endforeach()
36+
set(test_client_tls_mock_funcs connect SSL_connect nc_send_hello_io nc_handshake_io nc_ctx_check_and_fill)
37+
set(test_pam_mock_funcs pam_start)
3638

37-
set(test test_client_tls)
38-
set(${test}_mock_funcs connect SSL_connect nc_send_hello_io nc_handshake_io nc_ctx_check_and_fill)
39-
set(${test}_wrap_link_flags "-Wl")
40-
foreach(mock_func IN LISTS ${test}_mock_funcs)
41-
set(${test}_wrap_link_flags "${${test}_wrap_link_flags},--wrap=${mock_func}")
39+
# add -Wl,--wrap flags to tests that require it
40+
set(moc_funcs test_client_ssh_mock_funcs test_client_tls_mock_funcs test_pam_mock_funcs)
41+
foreach(mock_test IN LISTS mock_tests)
42+
set(${mock_test}_wrap_link_flags "-Wl")
43+
foreach(mock_func IN LISTS ${mock_test}_mock_funcs)
44+
set(${mock_test}_wrap_link_flags "${${mock_test}_wrap_link_flags},--wrap=${mock_func}")
45+
endforeach()
4246
endforeach()
4347

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

5963
include_directories(${CMAKE_SOURCE_DIR}/src ${PROJECT_BINARY_DIR})
6064
configure_file("${PROJECT_SOURCE_DIR}/tests/config.h.in" "${PROJECT_BINARY_DIR}/tests/config.h" ESCAPE_QUOTES @ONLY)
65+
66+
# compile PAM test module
67+
add_library(pam_netconf SHARED ${CMAKE_SOURCE_DIR}/tests/pam/pam_netconf.c)
68+
set_target_properties(pam_netconf PROPERTIES PREFIX "")
69+
target_link_libraries(pam_netconf ${LIBPAM_LIBRARIES})
70+
71+
# generate PAM configuration file
72+
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/netconf.conf
73+
"#%PAM-1.4\n"
74+
"auth required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
75+
"account required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
76+
"password required ${CMAKE_CURRENT_BINARY_DIR}/pam_netconf.so\n"
77+
)

tests/pam/pam_netconf.c

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
/**
2+
* @file pam_netconf.c
3+
* @author Roman Janota <[email protected]>
4+
* @brief libnetconf2 Linux PAM test module
5+
*
6+
* @copyright
7+
* Copyright (c) 2022 CESNET, z.s.p.o.
8+
*
9+
* This source code is licensed under BSD 3-Clause License (the "License").
10+
* You may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* https://opensource.org/licenses/BSD-3-Clause
14+
*/
15+
16+
#include <security/pam_modules.h>
17+
#include <stdio.h>
18+
#include <stdlib.h>
19+
#include <string.h>
20+
21+
#include "config.h"
22+
23+
#define N_MESSAGES 2
24+
#define N_REQUESTS 2
25+
26+
/**
27+
* @brief Exchange module's messages for user's replies.
28+
*
29+
* @param[in] pam_h PAM handle.
30+
* @param[in] n_messages Number of messages.
31+
* @param[in] msg Module's messages for the user.
32+
* @param[out] resp User's responses.
33+
* @return PAM_SUCCESS on success;
34+
* @return PAM error otherwise.
35+
*/
36+
static int
37+
nc_pam_mod_call_clb(pam_handle_t *pam_h, int n_messages, const struct pam_message **msg, struct pam_response **resp)
38+
{
39+
struct pam_conv *conv;
40+
int r;
41+
42+
/* the callback can be accessed through the handle */
43+
r = pam_get_item(pam_h, PAM_CONV, (void *) &conv);
44+
if (r != PAM_SUCCESS) {
45+
return r;
46+
}
47+
return conv->conv(n_messages, msg, resp, conv->appdata_ptr);
48+
}
49+
50+
/**
51+
* @brief Validate the user's responses.
52+
*
53+
* @param[in] username Username.
54+
* @param[in] reversed_username User's response to the first challenge.
55+
* @param[in] eq_ans User's response to the second challenge.
56+
* @return PAM_SUCCESS on success;
57+
* @return PAM_AUTH_ERR whenever the user's replies are incorrect.
58+
*/
59+
static int
60+
nc_pam_mod_auth(const char *username, char *reversed_username, char *eq_ans)
61+
{
62+
int i, j, r;
63+
size_t len;
64+
char *buffer;
65+
66+
len = strlen(reversed_username);
67+
buffer = calloc(len + 1, sizeof *buffer);
68+
if (!buffer) {
69+
fprintf(stderr, "Memory allocation error.\n");
70+
return PAM_BUF_ERR;
71+
}
72+
73+
/* reverse the user's response */
74+
for (i = len - 1, j = 0; i >= 0; i--) {
75+
buffer[j++] = reversed_username[i];
76+
}
77+
buffer[j] = '\0';
78+
79+
if (!strcmp(username, buffer) && !strcmp(eq_ans, "2")) {
80+
/* it's a match */
81+
r = PAM_SUCCESS;
82+
} else {
83+
r = PAM_AUTH_ERR;
84+
}
85+
86+
free(buffer);
87+
return r;
88+
}
89+
90+
/**
91+
* @brief Free the user's responses.
92+
*
93+
* @param[in] resp Responses.
94+
* @param[in] n Number of responses to be freed.
95+
*/
96+
static void
97+
nc_pam_mod_resp_free(struct pam_response *resp, int n)
98+
{
99+
int i;
100+
101+
if (!resp) {
102+
return;
103+
}
104+
105+
for (i = 0; i < n; i++) {
106+
free((resp + i)->resp);
107+
}
108+
free(resp);
109+
}
110+
111+
/**
112+
* @brief Test module's implementation of "auth" service.
113+
*
114+
* Prepare prompts for the client and decide based on his
115+
* answers whether to allow or disallow access.
116+
*
117+
* @param[in] pam_h PAM handle.
118+
* @param[in] flags Flags.
119+
* @param[in] argc Count of module options defined in the PAM configuration file.
120+
* @param[in] argv Module options.
121+
* @return PAM_SUCCESS on success;
122+
* @return PAM error otherwise.
123+
*/
124+
API int
125+
pam_sm_authenticate(pam_handle_t *pam_h, int flags, int argc, const char **argv)
126+
{
127+
int r;
128+
const char *username;
129+
char *reversed_username = NULL, *eq_ans = NULL;
130+
struct pam_message echo_msg, no_echo_msg, unexpected_type_msg, info_msg, err_msg;
131+
const struct pam_message *msg[N_MESSAGES];
132+
struct pam_response *resp = NULL;
133+
134+
(void) flags;
135+
(void) argc;
136+
(void) argv;
137+
138+
/* get the username and if it's not known then the user will be prompted to enter it */
139+
r = pam_get_user(pam_h, &username, NULL);
140+
if (r != PAM_SUCCESS) {
141+
fprintf(stderr, "Unable to get username.\n");
142+
r = PAM_AUTHINFO_UNAVAIL;
143+
goto cleanup;
144+
}
145+
146+
/* prepare the messages */
147+
echo_msg.msg_style = PAM_PROMPT_ECHO_ON;
148+
echo_msg.msg = "Enter your username backwards: ";
149+
no_echo_msg.msg_style = PAM_PROMPT_ECHO_OFF;
150+
no_echo_msg.msg = "Enter the result to 1+1: ";
151+
unexpected_type_msg.msg_style = PAM_AUTH_ERR;
152+
unexpected_type_msg.msg = "Arbitrary test message";
153+
info_msg.msg_style = PAM_TEXT_INFO;
154+
info_msg.msg = "Test info message";
155+
err_msg.msg_style = PAM_ERROR_MSG;
156+
err_msg.msg = "Test error message";
157+
158+
/* tests */
159+
printf("[TEST #1] Too many PAM messages. Output:\n");
160+
r = nc_pam_mod_call_clb(pam_h, 500, msg, &resp);
161+
if (r == PAM_SUCCESS) {
162+
fprintf(stderr, "[TEST #1] Failed.\n");
163+
r = PAM_AUTH_ERR;
164+
goto cleanup;
165+
}
166+
printf("[TEST #1] Passed.\n\n");
167+
168+
printf("[TEST #2] Negative number of PAM messages. Output:\n");
169+
r = nc_pam_mod_call_clb(pam_h, -1, msg, &resp);
170+
if (r == PAM_SUCCESS) {
171+
fprintf(stderr, "[TEST #2] Failed.\n");
172+
r = PAM_AUTH_ERR;
173+
goto cleanup;
174+
}
175+
printf("[TEST #2] Passed.\n\n");
176+
177+
printf("[TEST #3] 0 PAM messages. Output:\n");
178+
r = nc_pam_mod_call_clb(pam_h, 0, msg, &resp);
179+
if (r == PAM_SUCCESS) {
180+
fprintf(stderr, "[TEST #3] Failed.\n");
181+
r = PAM_AUTH_ERR;
182+
goto cleanup;
183+
}
184+
printf("[TEST #3] Passed.\n\n");
185+
186+
printf("[TEST #4] Unexpected message type. Output:\n");
187+
msg[0] = &unexpected_type_msg;
188+
r = nc_pam_mod_call_clb(pam_h, N_MESSAGES, msg, &resp);
189+
if (r == PAM_SUCCESS) {
190+
fprintf(stderr, "[TEST #4] Failed.\n");
191+
r = PAM_AUTH_ERR;
192+
goto cleanup;
193+
}
194+
printf("[TEST #4] Passed.\n\n");
195+
196+
printf("[TEST #5] Info and error messages. Output:\n");
197+
msg[0] = &info_msg;
198+
msg[1] = &err_msg;
199+
r = nc_pam_mod_call_clb(pam_h, N_MESSAGES, msg, &resp);
200+
if (r == PAM_SUCCESS) {
201+
fprintf(stderr, "[TEST #5] Failed.\n");
202+
r = PAM_AUTH_ERR;
203+
goto cleanup;
204+
}
205+
printf("[TEST #5] Passed.\n\n");
206+
207+
printf("[TEST #6] Authentication attempt with an expired token. Output:\n");
208+
/* store the correct messages */
209+
msg[0] = &echo_msg;
210+
msg[1] = &no_echo_msg;
211+
212+
/* get responses */
213+
r = nc_pam_mod_call_clb(pam_h, N_MESSAGES, msg, &resp);
214+
if (r != PAM_SUCCESS) {
215+
fprintf(stderr, "[TEST #6] Failed.\n");
216+
goto cleanup;
217+
}
218+
219+
reversed_username = resp[0].resp;
220+
eq_ans = resp[1].resp;
221+
222+
/* validate the responses */
223+
r = nc_pam_mod_auth(username, reversed_username, eq_ans);
224+
225+
/* not authenticated */
226+
if (r != PAM_SUCCESS) {
227+
fprintf(stderr, "[TEST #6] Failed.\n");
228+
r = PAM_AUTH_ERR;
229+
}
230+
231+
cleanup:
232+
/* free the responses */
233+
nc_pam_mod_resp_free(resp, N_REQUESTS);
234+
return r;
235+
}
236+
237+
/**
238+
* @brief Test module's silly implementation of "account" service.
239+
*
240+
* @param[in] pam_h PAM handle.
241+
* @param[in] flags Flags.
242+
* @param[in] argc The count of module options defined in the PAM configuration file.
243+
* @param[in] argv Module options.
244+
* @return PAM_NEW_AUTHTOK_REQD on success;
245+
* @return PAM error otherwise.
246+
*/
247+
API int
248+
pam_sm_acct_mgmt(pam_handle_t *pam_h, int flags, int argc, const char *argv[])
249+
{
250+
int r;
251+
const void *username;
252+
253+
(void) flags;
254+
(void) argc;
255+
(void) argv;
256+
257+
/* get and check the username */
258+
r = pam_get_item(pam_h, PAM_USER, &username);
259+
if (r != PAM_SUCCESS) {
260+
return r;
261+
}
262+
if (!strcmp((const char *)username, "test")) {
263+
return PAM_NEW_AUTHTOK_REQD;
264+
}
265+
return PAM_SYSTEM_ERR;
266+
}
267+
268+
/**
269+
* @brief Test module's silly implementation of "password" service.
270+
*
271+
* @param[in] pam_h PAM handle.
272+
* @param[in] flags Flags.
273+
* @param[in] argc The count of module options defined in the PAM configuration file.
274+
* @param[in] argv Module options.
275+
* @return PAM_SUCCESS on success;
276+
* @return PAM error otherwise.
277+
*/
278+
API int
279+
pam_sm_chauthtok(pam_handle_t *pam_h, int flags, int argc, const char *argv[])
280+
{
281+
int r;
282+
const void *username;
283+
284+
(void) argc;
285+
(void) argv;
286+
287+
/* the function is called twice, each time with a different flag,
288+
* in the first call just check the username if it matches */
289+
if (flags & PAM_PRELIM_CHECK) {
290+
r = pam_get_item(pam_h, PAM_USER, &username);
291+
if (r != PAM_SUCCESS) {
292+
return r;
293+
}
294+
if (!strcmp((const char *)username, "test")) {
295+
return PAM_SUCCESS;
296+
} else {
297+
return PAM_SYSTEM_ERR;
298+
}
299+
300+
/* change the authentication token in the second call */
301+
} else if (flags & PAM_UPDATE_AUTHTOK) {
302+
r = pam_set_item(pam_h, PAM_AUTHTOK, "test");
303+
if (r == PAM_SUCCESS) {
304+
printf("[TEST #6] Passed.\n\n");
305+
} else {
306+
fprintf(stderr, "[TEST #6] Failed.\n");
307+
}
308+
return r;
309+
}
310+
return PAM_SYSTEM_ERR;
311+
}

0 commit comments

Comments
 (0)