/* * Copyright (c) 2022 Stefan Strobel * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * @file shellmatta_auth.c * @brief simple authentication method * @author Stefan Strobel */ /** * @addtogroup shellmatta_auth * @{ */ #include "shellmatta.h" #include "shellmatta_auth.h" #include "shellmatta_history.h" #include "shellmatta_utils.h" #include "shellmatta_escape.h" #include #include #include /** * @brief wraps the input to the login command and mimics the editability of the standard shell input * @param[in, out] inst shellmatta instance to work with * @param[in] data one byte of data the user put in * @param[in] hide true: do not print anything tho the output (e.g. password input) * @return #SHELLMATTA_BUSY => reading in data * #SHELLMATTA_OK => delimiter (e.g. enter) detected */ shellmatta_retCode_t inputWrapper(shellmatta_instance_t *inst, char data, bool hide) { shellmatta_retCode_t ret = SHELLMATTA_BUSY; /** -# handle escape sequences */ if(inst->escapeCounter != 0u) { escape_handleSequence(inst, data); } else if (inst->delimiter == data) { ret = SHELLMATTA_OK; } /** -# ignore newline as first character (to be compatible to * terminals sending newline after return */ else if((0u == inst->inputCount) && ('\n' == data)) { /* do nothing */ } /** -# check for backspace */ else if( ('\b' == data) || ('\x7f' == data)) { utils_removeChars(inst, 1u, true); } /** -# check for start of escape sequence */ else if('\x1b' == data) { inst->escapeCounter = 1u; } else { utils_insertChars(inst, &data, 1u, hide); } return ret; } /** * @brief searches the user list for a passed username and returns the userId * @param[in] inst shellmatta instance to work with * @param[in] username username to search for - pointer to string * @return userId or 0 if user was not found */ static uint32_t getUserIdFromName(shellmatta_instance_t *inst, char *username) { uint32_t userId = 0u; uint32_t i; for (i = 0u; i < inst->userListLength; i++) { /** -# search for user */ if (0 == strcmp(inst->userList[i].username, username)) { userId = inst->userList[i].userId; break; } } return userId; } /** * @brief checks the passed password and logs the user into the instance on success * @param[in, out] handle shellmatta handle to work with * @param[in] userId userId to check the password for * @param[in] password password to check for - pointer to string */ void checkPassword(shellmatta_handle_t handle, uint32_t userId, char *password) { shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; uint32_t approvedUserId = 0u; uint32_t i; if (NULL != inst->checkFct) { approvedUserId = inst->checkFct(userId, password); } else { for (i = 0u; i < inst->userListLength; i++) { /** -# search for user */ if (inst->userList[i].userId == userId) { /** -# check password */ if (0 == strcmp(inst->userList[i].password, password)) { approvedUserId = inst->userList[i].userId; } break; } } } /** -# call log function - only for unsuccessful logins - successful logins are handled by #shellmatta_auth_login */ if ((inst->logFct) && (0u == approvedUserId)) { inst->logFct(userId, false); } /** -# print login result */ if (0 == approvedUserId) { shellmatta_write(handle, "username or password is wrong\r\n", 31); } else { shellmatta_write(handle, "login successful\r\n", 18); } (void)shellmatta_auth_login(handle, approvedUserId); } /** * @brief handles the login based on username and password * @param[in] handle handle shellmatta instance handle * @param[in] arguments arguments containing a command name or alias * @param[in] length length of the arguments * @return #SHELLMATTA_OK * #SHELLMATTA_CONTINUE (waiting for input) */ static shellmatta_retCode_t loginCmdFct(const shellmatta_handle_t handle, const char *arguments, uint32_t length) { shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; shellmatta_retCode_t ret = SHELLMATTA_OK; char option; char *argument; uint32_t argLen; char *username = NULL; char *password = NULL; static const shellmatta_opt_long_t options[] = { {"username" , 'u', SHELLMATTA_OPT_ARG_REQUIRED}, {"password" , 'p', SHELLMATTA_OPT_ARG_REQUIRED}, {NULL , '\0', SHELLMATTA_OPT_ARG_NONE} }; /** -# handle the login state machine */ switch(inst->loginState) { /** -# init the login procedure - use directly passed credentials if any */ case SHELLMATTA_AUTH_IDLE: ret = shellmatta_opt_long(handle, options, &option, &argument, &argLen); while(SHELLMATTA_OK == ret) { switch(option) { case 'u': if(NULL != argument) { username = argument; } break; case 'p': if(NULL != argument) { password = argument; } break; default: shellmatta_printf(handle, "Unknown option: %c\r\n", option); break; } ret = shellmatta_opt_long(handle, options, &option, &argument, &argLen); } if ((NULL != username) && (NULL != password)) { inst->tmpUserId = getUserIdFromName(inst, username); checkPassword(handle, inst->tmpUserId, password); } else { /** -# no credentials are passed with the command - start the input */ inst->inputCount = 0u; inst->cursor = 0u; /** -# store pointer to username in buffer */ shellmatta_write(handle, "enter username:\r\n", 17); inst->loginState = SHELLMATTA_AUTH_USERNAME; return SHELLMATTA_CONTINUE; } break; case SHELLMATTA_AUTH_USERNAME: /** -# read in password characters to the instance buffer */ (void)shellmatta_read(handle, &argument, &argLen); if ((1 == argLen) && (SHELLMATTA_OK == inputWrapper(inst, argument[0], false))) { /** -# store user id */ inst->buffer[inst->inputCount] = '\0'; inst->tmpUserId = getUserIdFromName(handle, inst->buffer); /** -# reinitialize input */ inst->inputCount = 0u; inst->cursor = 0u; shellmatta_write(handle, "\r\nenter password:\r\n", 19); inst->loginState = SHELLMATTA_AUTH_PASSWORD; } ret = SHELLMATTA_CONTINUE; break; case SHELLMATTA_AUTH_PASSWORD: /** -# read in password characters to the instance buffer - hiding output */ (void)shellmatta_read(handle, &argument, &argLen); if ((1 == argLen) && (SHELLMATTA_OK == inputWrapper(inst, argument[0], true))) { /** -# check input username and password */ inst->buffer[inst->inputCount] = '\0'; shellmatta_write(handle, "\r\n", 2); checkPassword(handle, inst->tmpUserId, inst->buffer); inst->loginState = SHELLMATTA_AUTH_IDLE; } else { ret = SHELLMATTA_CONTINUE; } break; default: break; } (void)arguments; (void)length; return ret; } const shellmatta_cmd_t shellmatta_auth_loginCmd = {"login" , "li" , "Login command" , "interactive or use -u -p " , loginCmdFct , NULL , NULL}; /** * @brief handles the logout of the current session * @param[in] handle handle shellmatta instance handle * @param[in] arguments arguments containing a command name or alias * @param[in] length length of the arguments * @return #SHELLMATTA_OK */ static shellmatta_retCode_t logoutCmdFct(const shellmatta_handle_t handle, const char *arguments, uint32_t length) { shellmatta_auth_logout(handle); shellmatta_write(handle, "good bye\r\n", 10); (void)arguments; (void)length; return SHELLMATTA_OK; } const shellmatta_cmd_t shellmatta_auth_logoutCmd = {"logout" , "lo" , "Logout command" , "logs out the currently logged in user" , logoutCmdFct , NULL , NULL}; /** * @brief initializes the shellmatta authentication system * @param[in] handle shellmatta instance handle * @param[in] userList list to specify all users pointer to a list of type shellmatta_user_t * @param[in] userListLength length of the user list * @param[in] permList list to specify the command permissions pointer to a list of type shellmatta_perm_t * @param[in] permListLength length of the perm list * @param[in] customLogin true: no internal login command is provided * @param[in] checkFct pointer to custom credential check function of type shellmatta_check_t * @param[in] logFct pointer to login log function of type shellmatta_auth_log_t * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_USE_FAULT (param err) */ shellmatta_retCode_t shellmatta_auth_init(shellmatta_handle_t handle, shellmatta_auth_user_t *userList, uint32_t userListLength, shellmatta_auth_perm_t *permList, uint32_t permListLength, bool customLogin, shellmatta_auth_check_t checkFct, shellmatta_auth_log_t logFct) { shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; uint32_t i; shellmatta_cmd_t *cmd; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic) && (NULL != userList) && (0 != userListLength) && (NULL != permList) && (0 != permListLength)) { inst->loginState = SHELLMATTA_AUTH_IDLE; inst->userId = 0u; inst->tmpUserId = 0u; inst->userPointer = NULL; inst->userList = userList; inst->userListLength = userListLength; inst->permList = permList; inst->permListLength = permListLength; if(customLogin) { inst->helpCmd.next = &inst->logoutCmd; } inst->checkFct = checkFct; inst->logFct = logFct; /** -# assign links to the perm list to each command */ cmd = inst->cmdList; /** -# search for a matching command */ while (NULL != cmd) { /** -# Search for command in perm list */ for (i = 0u; i < permListLength; i++) { if (0 == strcmp(cmd->cmd, permList[i].cmd)) { cmd->authLink = &permList[i]; break; } } cmd = cmd->next; } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief manually logs the passed user id in * @param[in] handle shellmatta instance handle * @param[in] userId userId to login * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_USE_FAULT (param err) */ shellmatta_retCode_t shellmatta_auth_login(shellmatta_handle_t handle, uint32_t userId) { shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; uint32_t i; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { /** -# log passed user id into the instance */ inst->userId = userId; if (0 != userId) { /** -# set user pointer to print the name in the prompt */ for (i = 0u; i < inst->userListLength; i++) { if (inst->userList[i].userId == userId) { inst->userPointer = &inst->userList[i]; break; } } } else { inst->userPointer = NULL; } /** -# call log function */ if (inst->logFct) { inst->logFct(userId, userId != 0u ? true : false); } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief logs the currently logged in user out of the instance * @param[in] handle shellmatta instance handle * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_USE_FAULT (param err) */ shellmatta_retCode_t shellmatta_auth_logout(shellmatta_handle_t handle) { shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { inst->userId = 0u; inst->userPointer = NULL; /** -# clear the history buffer */ (void)history_clear(handle); } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief returns the currently logged in userId * @param[in] handle shellmatta instance handle * @return userId or 0 if no user is logged in */ uint32_t shellmatta_auth_getLoggedInUserId(shellmatta_handle_t handle) { uint32_t userId = 0u; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic)) { userId = inst->userId; } return userId; } /** * @brief copies the username into the passed buffer * @param[in] handle shellmatta instance handle * @param[out] data pointer to buffer to write the username into * @param[in, out] length in - length of the passed buffer, out - actual stringlength of the username * @return errorcode #SHELLMATTA_OK * #SHELLMATTA_ERROR (passed buffer is too small) * #SHELLMATTA_USE_FAULT (param err) */ shellmatta_retCode_t shellmatta_auth_getLoggedInUserName(shellmatta_handle_t handle, char *data, uint32_t *length) { shellmatta_retCode_t ret = SHELLMATTA_OK; shellmatta_instance_t *inst = (shellmatta_instance_t*)handle; size_t userNameLength; /** -# check parameters for plausibility */ if( (NULL != inst) && (SHELLMATTA_MAGIC == inst->magic) && (data != NULL) && (length != NULL)) { if (NULL != inst->userPointer) { userNameLength = strlen(inst->userPointer->username); if (userNameLength < *length) { (void)strcpy(data, inst->userPointer->username); *length = userNameLength; } else { ret = SHELLMATTA_ERROR; } } } else { ret = SHELLMATTA_USE_FAULT; } return ret; } /** * @brief checks if a command is accessible by the logged in user * @param[in] inst shellmatta instance to work with * @param[in] cmd command to check permissions for * @return errorcode #SHELLMATTA_OK => permitted * #SHELLMATTA_ERROR => not permitted */ shellmatta_retCode_t shellmatta_auth_is_cmd_permitted(const shellmatta_instance_t *inst, shellmatta_cmd_t *cmd) { shellmatta_retCode_t ret = SHELLMATTA_ERROR; shellmatta_auth_perm_t *permList; uint32_t i; if (NULL == cmd->authLink) { ret = SHELLMATTA_OK; } else { permList = cmd->authLink; for (i = 0u; i < permList->userIdslength; i++) { if (inst->userId == permList->userIds[i]) { ret = SHELLMATTA_OK; break; } } } return ret; } /** * @} */