/** * @file pam_netconf.c * @author Roman Janota * @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 #include #include #include #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; }