312 lines
8.4 KiB
C
312 lines
8.4 KiB
C
|
/**
|
||
|
* @file pam_netconf.c
|
||
|
* @author Roman Janota <xjanot04@fit.vutbr.cz>
|
||
|
* @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;
|
||
|
}
|