Browse Source

Merge branch 'develop' into feature/mess_around_with_transport_layer

Strobel, Stefan | Friedrich Lütze GmbH 1 year ago
parent
commit
d84635fbbe

+ 21 - 0
.vscode/launch.json

@@ -66,6 +66,27 @@
             ],
             "preLaunchTask": "make integrationtest",
             "miDebuggerPath": "/usr/bin/gdb"
+        },
+        {
+            "name": "debug integrationtest_auth",
+            "type": "cppdbg",
+            "request": "launch",
+            "program": "${workspaceFolder}/output/test/integrationtest_auth/integrationtest_auth",
+            "args": [],
+            "stopAtEntry": false,
+            "cwd": "${workspaceFolder}",
+            "environment": [],
+            "externalConsole": false,
+            "MIMode": "gdb",
+            "setupCommands": [
+                {
+                    "description": "Enable pretty-printing for gdb",
+                    "text": "-enable-pretty-printing",
+                    "ignoreFailures": true
+                }
+            ],
+            "preLaunchTask": "make integrationtest_auth",
+            "miDebuggerPath": "/usr/bin/gdb"
         }
     ]
 }

+ 0 - 13
.vscode/settings.json

@@ -1,13 +0,0 @@
-{
-    "files.associations": {
-        "random": "cpp",
-        "*.tcc": "cpp",
-        "string": "cpp",
-        "vector": "cpp",
-        "fstream": "cpp",
-        "limits": "cpp",
-        "sstream": "cpp",
-        "utility": "cpp",
-        "algorithm": "cpp"
-    }
-}

+ 8 - 0
.vscode/tasks.json

@@ -38,6 +38,14 @@
                 "$gcc"
             ]
         },
+        {
+            "label": "make integrationtest_auth",
+            "type": "shell",
+            "command": "make integrationtest_auth",
+            "problemMatcher": [
+                "$gcc"
+            ]
+        },
         {
             "label": "make test",
             "type": "shell",

+ 9 - 7
README.md

@@ -35,6 +35,7 @@ The `shellmatta` piled up some features over time:
 2. auto complete
 3. heredoc like interface to pass multiline data
 4. option parser (getopt like)
+5. simple authentication mechanism
 
 ## Documentation
 
@@ -140,13 +141,14 @@ int main(void)
 
 There are some defines you can use to change the behaviour of the shellmatta:
 
-| Define                     | Description                                    |
-| -------------------------- | ---------------------------------------------- |
-| SHELLMATTA_STRIP_PRINTF    | removes stdio dependencies to reduce footprint |
-| SHELLMATTA_HELP_COMMAND    | string to overwrite the help command name      |
-| SHELLMATTA_HELP_ALIAS      | string to overwrite the help command alias     |
-| SHELLMATTA_HELP_HELP_TEXT  | string to overwrite the help command help      |
-| SHELLMATTA_HELP_USAGE_TEXT | string to overwrite the help command usage     |
+| Define                                 | Description                                    |
+| -------------------------------------- | ---------------------------------------------- |
+| SHELLMATTA_STRIP_PRINTF                | removes stdio dependencies to reduce footprint |
+| SHELLMATTA_HELP_COMMAND                | string to overwrite the help command name      |
+| SHELLMATTA_HELP_ALIAS                  | string to overwrite the help command alias     |
+| SHELLMATTA_HELP_HELP_TEXT              | string to overwrite the help command help      |
+| SHELLMATTA_HELP_USAGE_TEXT             | string to overwrite the help command usage     |
+| SHELLMATTA_AUTHENTICATION              | if defined this enables the authentication     |
 
 ## Example
 

+ 121 - 5
api/shellmatta.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -25,6 +25,21 @@
 
 /* global defines */
 
+
+/*
+ * Define the printf format specifier for all GCC versions > 3.3
+ * This will let the compiler know that shelmatta_printf() is a function taking printf-like format specifiers.
+ */
+#ifndef SHELLMATTA_ATTR_FORMAT
+#   if ((__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)))
+#       define SHELLMATTA_ATTR_FORMAT(fmt, args) __attribute__((__format__(__printf__, fmt, args)))
+#   else
+#       define SHELLMATTA_ATTR_FORMAT(fmt, args)
+#   endif
+#else
+#   define SHELLMATTA_ATTR_FORMAT(fmt, args)
+#endif
+
 /**
  * @brief definition of a shellmatta handle
  */
@@ -101,6 +116,66 @@ typedef shellmatta_retCode_t (*shellmatta_cmdFct_t)(const shellmatta_handle_t
  */
 typedef shellmatta_retCode_t (*shellmatta_write_t)(const char* data, uint32_t length);
 
+#ifdef SHELLMATTA_AUTHENTICATION
+/**
+ * @brief user role matrix
+ */
+typedef struct
+{
+    uint32_t    userId;     /**< id of the user (!= 0)                              */
+    bool        superuser;  /**< allow the user to access all commands              */
+    const char  *username;  /**< name of the user role                              */
+    const char  *password;  /**< password of the user role or NULL (custom auth)    */
+} shellmatta_auth_user_t;
+
+/**
+ * @brief typedefinition of one line of the authentication table
+ */
+typedef struct
+{
+    const char*    cmd;           /**< command to grand access to               */
+    const uint32_t *userIds;      /**< list of user ids with access to the cmd  */
+    const uint32_t userIdslength; /**< length of the user list                  */
+} shellmatta_auth_perm_t;
+
+/**
+ * @brief login states
+ */
+typedef enum
+{
+    SHELLMATTA_AUTH_IDLE,       /**< authentication system waits    */
+    SHELLMATTA_AUTH_USERNAME,   /**< input of username              */
+    SHELLMATTA_AUTH_PASSWORD    /**< input of password              */
+} shellmatta_auth_state_t;
+
+/**
+ * @brief log actions - passed to the log function
+ */
+typedef enum
+{
+    SHELLMATTA_AUTH_EVENT_NONE,         /**< no event (init value)      */
+    SHELLMATTA_AUTH_EVENT_LOGIN,        /**< successful login event     */
+    SHELLMATTA_AUTH_EVENT_LOGIN_FAILED, /**< failed login event         */
+    SHELLMATTA_AUTH_EVENT_LOGOUT,       /**< succesful logout event     */
+} shellmatta_auth_log_event_t;
+
+/**
+ * @brief custom shellmatta authentication method
+ * @param[in]   userId      user id to log in (name of the user role)
+ * @param[in]   password    password for the login
+ * @return      userId if password was correct - otherwise 0
+ */
+typedef shellmatta_retCode_t (*shellmatta_auth_check_t)(const uint32_t userId, const char* password);
+
+/**
+ * @brief shellmatta authentication log method - will be called whenever a login attempt is done
+ * @param[in]   userId  userId to be logged in (0 on unknown user)
+ * @param[in]   event   event type to be logged (e.g. successful login)
+ */
+typedef void (*shellmatta_auth_log_t)(const uint32_t userId, shellmatta_auth_log_event_t event);
+
+#endif
+
 /**
  * @brief structure of one shellmatta command
  */
@@ -110,7 +185,10 @@ typedef struct shellmatta_cmd
     char                    *cmdAlias;  /**< command alias                          */
     char                    *helpText;  /**< help text to print in "help" command   */
     char                    *usageText; /**< usage text - printed on "help cmd"     */
-    shellmatta_cmdFct_t     cmdFct;     /**< pointer to the cmd callback function   */
+    shellmatta_cmdFct_t     cmdFct;     /**< pointer to the cmd callack function    */
+#ifdef SHELLMATTA_AUTHENTICATION
+    shellmatta_auth_perm_t  *authLink;  /**< internally used - pointer to perm list */
+#endif
     struct shellmatta_cmd   *next;      /**< pointer to next command or NULL        */
 } shellmatta_cmd_t;
 
@@ -224,6 +302,20 @@ typedef struct
     bool                            cmdListIsConst;     /**< true if the #cmdList was passed during
                                                              initialization                         */
     shellmatta_opt_t                optionParser;       /**< option parser sructure                 */
+#ifdef SHELLMATTA_AUTHENTICATION
+    shellmatta_auth_state_t         loginState;         /**< state variable of the login cmd        */
+    shellmatta_cmd_t                loginCmd;           /**< login command structure                */
+    shellmatta_cmd_t                logoutCmd;          /**< logout command structure               */
+    uint32_t                        userId;             /**< user ID of the current session         */
+    uint32_t                        tmpUserId;          /**< remporary user ID during input         */
+    shellmatta_auth_user_t          *userPointer;       /**< pointer to the user in the user list   */
+    shellmatta_auth_user_t          *userList;          /**< user list                              */
+    uint32_t                        userListLength;     /**< length of the user list                */
+    shellmatta_auth_perm_t          *permList;          /**< permission list                        */
+    uint32_t                        permListLength;     /**< length of the permission list          */
+    shellmatta_auth_check_t         checkFct;           /**< custom credential check function       */
+    shellmatta_auth_log_t           logFct;             /**< auth log function                      */
+#endif
 #ifdef SHELLMATTA_TRANSPORT_ENABLE
     uint32_t                        transportBusyMark;  /**< transport processing position during 
                                                              busy cmd execution                     */
@@ -258,8 +350,8 @@ shellmatta_retCode_t shellmatta_resetShell( shellmatta_handle_t handle,
 shellmatta_retCode_t shellmatta_addCmd(     shellmatta_handle_t handle,
                                             shellmatta_cmd_t    *cmd);
 
-shellmatta_retCode_t shellmatta_removeCmd(  shellmatta_handle_t handle,
-                                            shellmatta_cmd_t    *cmd);
+shellmatta_retCode_t shellmatta_removeCmd(  shellmatta_handle_t     handle,
+                                            const shellmatta_cmd_t  *cmd);
 
 shellmatta_retCode_t shellmatta_configure(  shellmatta_handle_t handle,
                                             shellmatta_mode_t   mode,
@@ -306,7 +398,31 @@ shellmatta_retCode_t shellmatta_transport_flush(shellmatta_handle_t     handle);
 #ifndef SHELLMATTA_STRIP_PRINTF
 shellmatta_retCode_t shellmatta_printf(     shellmatta_handle_t handle,
                                             const char          *fmt,
-                                            ...);
+                                            ...)
+                                            SHELLMATTA_ATTR_FORMAT(2, 3);
+#endif
+
+#ifdef SHELLMATTA_AUTHENTICATION
+
+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 shellmatta_auth_login(                 shellmatta_handle_t     handle,
+                                                            uint32_t                userId);
+shellmatta_retCode_t shellmatta_auth_logout(                shellmatta_handle_t     handle);
+uint32_t             shellmatta_auth_getLoggedInUserId(     shellmatta_handle_t     handle);
+shellmatta_retCode_t shellmatta_auth_getLoggedInUserName(   shellmatta_handle_t     handle,
+                                                            char                    *data,
+                                                            uint32_t                *length);
+shellmatta_retCode_t shellmatta_auth_chpasswd(              shellmatta_handle_t     handle,
+                                                            const char              *username,
+                                                            const char              *password);
+
 #endif
 
 #endif

+ 72 - 0
cfg/cppcheck/cppcheck_suppressions.xml

@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<suppressions>
+    <suppress>
+        <id>missingIncludeSystem</id>
+    </suppress>
+    <suppress>
+        <id>variableScope</id>
+        <!--Intentionally
+        not limiting the variable scope as suggested - declaring variables at
+            the top of each function.-->
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_doInit</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_resetShell</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_addCmd</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_removeCmd</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_configure</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_processData</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta.c</fileName>
+        <symbolName>shellmatta_printf</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_opt.c</fileName>
+        <symbolName>shellmatta_opt</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_auth.c</fileName>
+        <symbolName>shellmatta_auth_init</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_auth.c</fileName>
+        <symbolName>shellmatta_auth_getLoggedInUserId</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_auth.c</fileName>
+        <symbolName>shellmatta_auth_getLoggedInUserName</symbolName>
+    </suppress>
+    <suppress>
+        <id>unusedFunction</id>
+        <fileName>src/shellmatta_auth.c</fileName>
+        <symbolName>shellmatta_auth_chpasswd</symbolName>
+    </suppress>
+</suppressions>

+ 9 - 0
doc/shellmatta.dox

@@ -24,6 +24,10 @@
             App -> Shellmatta: shellmatta_addCmd(command)
         end
 
+        alt Authentication enabled
+            App -> Shellmatta: shellmatta_auth_init()
+        end
+
         loop until finished
             IO -> Shellmatta: shellmatta_processData(data)
             Shellmatta -> App: call command function
@@ -33,6 +37,11 @@
     @enduml
 
 
+    @section shellmatta_options Shellmatta options
+
+    The Shellmatta comes with some features which can be opted in:
+
+    @subpage shellmatta_auth
     @subpage shellmatta_transport_layer
 
 */

+ 84 - 0
doc/shellmatta_auth.dox

@@ -0,0 +1,84 @@
+/**
+
+    @page shellmatta_auth Shellmatta Authentication
+
+    The shellmatta comes with a simple authentication mechanism.
+    It can be used to hide certain (or all) commands from users without
+    permission.
+
+    The permissions can be set per command.
+
+    To enable the shellmatta auth module you have to include the file
+    shellmatta_auth.c into your build and set the define
+    ``SHELLMATTA_AUTHENTICATION``.
+
+    Unfortunately the structure of each command has to be altered to include
+    the additional information required by the auth module.
+    Please add another NULL to the initializers of every command of type
+    #shellmatta_cmd_t.
+
+
+        shellmatta_cmd_t exampleCmd = { "example",
+                                        "e",
+                                        "example command",
+                                        "example [options]\n"
+                                        "\t-v, --version - print the version of the command",
+                                        exampleCmdFct,
+                                        NULL,
+                                        NULL};
+
+    After initializing the shellmatta instance you have to setup users with
+    username and password.
+    By enabling the user to be superuser you grant this user access to all
+    commands without the need of setting a permission list.
+
+        shellmatta_auth_user_t userList[] = {
+            {1, true, "root", "rootpw"},
+            {2, false, "shimatta", "12345678"},
+            {3, false, "not_shimatta", "87654321"}
+        };
+
+    Every command can get a permission matrix - the perm lists can be reused for
+    multiple users with the same permissions.
+    When no entry is found for a command in the permission list the command
+    defaults to be public.
+
+    It is also possible to use the userID 0 to hide a command when logged in.
+
+        uint32_t exampleCmdPerms[] = {2};
+        shellmatta_auth_perm_t permList[] = {
+            {"exampleCmd", exampleCmdPerms, sizeof(exampleCmdPerms)/sizeof(exampleCmdPerms[0])}
+        };
+
+    Now call the #shellmatta_auth_init method and pass the user and permissions
+    lists.
+    It is possible to register optional callbacks for a custom password check
+    and a log function which is called on every authentication event.
+
+        shellmatta_auth_init(handle, userList, 3, permList, 1, false, NULL, NULL);
+
+
+    @section shellmatta_auth_custom_login Custom login
+
+    By default the shellmatta uses plain text passwords.
+    This of course is not state of the art and usually highly insecure.
+
+    As most of the fancy password hashing methods are platform dependant none of
+    those is included to keep up the compatibility with as many platforms as
+    possible (sacrificing security).
+
+    To overcome this limitation you can register your own function to check the
+    credentials.
+
+    Just implement a function of type #shellmatta_auth_check_t and pass it to
+    the #shellmatta_auth_init method during initialization.
+
+        shellmatta_retCode_t custom_auth_check(const uint32_t userId, const char* password) {
+            /‌/ Check if the passed userID matches the passed password.
+            if (password_matches()) {
+                return SHELLMATTA_OK;
+            }
+            return SHELLMATTA_ERROR;
+        }
+
+*/

+ 24 - 9
example/main.c

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -31,7 +31,7 @@ static shellmatta_retCode_t doSomething(shellmatta_handle_t handle, const char *
     shellmatta_printf(handle, "%s - length: %u", arguments, length);
     return SHELLMATTA_OK;
 }
-shellmatta_cmd_t doSomethingCmd = {"doSomething", "do", "Function does something", "use me, please", doSomething, NULL};
+shellmatta_cmd_t doSomethingCmd = {"doSomething", "do", "Function does something", "use me, please", doSomething, NULL, NULL};
 
 static shellmatta_retCode_t doSome(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
@@ -45,7 +45,7 @@ static shellmatta_retCode_t doSome(shellmatta_handle_t handle, const char *argum
 
     return SHELLMATTA_OK;
 }
-shellmatta_cmd_t doSomeCmd = {"adoSome2", "adof2", "Function does something", "use me, please", doSome, NULL};
+shellmatta_cmd_t doSomeCmd = {"adoSome2", "adof2", "Function does something", "use me, please", doSome, NULL, NULL};
 
 static shellmatta_retCode_t removeCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
@@ -58,7 +58,7 @@ static shellmatta_retCode_t removeCmdFct(shellmatta_handle_t handle, const char
 
     return SHELLMATTA_OK;
 }
-shellmatta_cmd_t removeCommand = {"remove", "r", "Function removes a command", "", removeCmdFct, NULL};
+shellmatta_cmd_t removeCommand = {"remove", "r", "Function removes a command", "", removeCmdFct, NULL, NULL};
 
 
 static shellmatta_retCode_t quit(shellmatta_handle_t handle, const char *arguments, uint32_t length)
@@ -71,7 +71,7 @@ static shellmatta_retCode_t quit(shellmatta_handle_t handle, const char *argumen
 
     return SHELLMATTA_OK;
 }
-shellmatta_cmd_t quitCommand = {"quit", "q", "Function quits the shell", "", quit, NULL};
+shellmatta_cmd_t quitCommand = {"quit", "q", "Function quits the shell", "", quit, NULL, NULL};
 
 static shellmatta_retCode_t empty(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
@@ -83,7 +83,7 @@ static shellmatta_retCode_t empty(shellmatta_handle_t handle, const char *argume
 
     return SHELLMATTA_OK;
 }
-shellmatta_cmd_t emptyCommand = {"empty", NULL, NULL, NULL, empty, NULL};
+shellmatta_cmd_t emptyCommand = {"empty", NULL, NULL, NULL, empty, NULL, NULL};
 
 static shellmatta_retCode_t reset(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
@@ -127,7 +127,7 @@ static shellmatta_retCode_t reset(shellmatta_handle_t handle, const char *argume
 
     return SHELLMATTA_OK;
 }
-shellmatta_cmd_t resetCommand = {"reset", NULL, "resets the shellmatta instance", "reset [--prompt true/false]", reset, NULL};
+shellmatta_cmd_t resetCommand = {"reset", NULL, "resets the shellmatta instance", "reset [--prompt true/false]", reset, NULL, NULL};
 
 static shellmatta_retCode_t continuous(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
@@ -151,7 +151,7 @@ static shellmatta_retCode_t continuous(shellmatta_handle_t handle, const char *a
     }
     return ret;
 }
-shellmatta_cmd_t continuousCommand = {"continuous", "cont", "prints continously all input bytes", "continuous", continuous, NULL};
+shellmatta_cmd_t continuousCommand = {"continuous", "cont", "prints continously all input bytes", "continuous", continuous, NULL, NULL};
 
 static shellmatta_retCode_t busy(shellmatta_handle_t handle, const char *arguments, uint32_t length)
 {
@@ -173,7 +173,7 @@ static shellmatta_retCode_t busy(shellmatta_handle_t handle, const char *argumen
 
     return ret;
 }
-shellmatta_cmd_t busyCommand = {"busy", NULL, NULL, NULL, busy, NULL};
+shellmatta_cmd_t busyCommand = {"busy", NULL, NULL, NULL, busy, NULL, NULL};
 
 
 shellmatta_retCode_t writeFct(const char* data, uint32_t length)
@@ -221,6 +221,21 @@ int main(int argc, char **argv)
     shellmatta_addCmd(handle, &continuousCommand);
     shellmatta_addCmd(handle, &busyCommand);
 
+
+    shellmatta_auth_user_t userList[] = {
+        {1, false, "shimatta", "12345678"},
+        {2, false, "not_shimatta", "87654321"}
+    };
+
+    uint32_t doSomeCmdPerms[] = {1, 2};
+    uint32_t removeCommandPerms[] = {1};
+    shellmatta_auth_perm_t permList[] = {
+        {"adoSome2", doSomeCmdPerms, sizeof(doSomeCmdPerms)/sizeof(doSomeCmdPerms[0])},
+        {"remove", removeCommandPerms, sizeof(removeCommandPerms)/sizeof(removeCommandPerms[0])}
+    };
+
+    shellmatta_auth_init(handle, userList, 2, permList, 2, false, NULL, NULL);
+
     while(exitRequest == false)
     {
         char c;

+ 119 - 65
makefile

@@ -1,17 +1,26 @@
-# 
-# Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
-# 
+#
+# Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+#
 # 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/.
-# 
+#
 
-OBJ_DIR := output/
-INTEGRATIONTEST_CPP_OBJ_DIR := $(OBJ_DIR)test/integrationtest/
-INTEGRATIONTEST_C_OBJ_DIR := $(INTEGRATIONTEST_CPP_OBJ_DIR)
-INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT := $(OBJ_DIR)test/integrationtest_transport/
-INTEGRATIONTEST_C_OBJ_DIR_TRANSPORT := $(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT)
-UNITTEST_OBJ_DIR := $(OBJ_DIR)test/unittest/
+OBJ_DIR                                 := output/
+OBJ_DIR_EXAMPLE                         := $(OBJ_DIR)example_obj/
+INTEGRATIONTEST_CPP_OBJ_DIR             := $(OBJ_DIR)test/integrationtest/
+INTEGRATIONTEST_AUTH_CPP_OBJ_DIR        := $(OBJ_DIR)test/integrationtest_auth/
+INTEGRATIONTEST_C_OBJ_DIR               := $(INTEGRATIONTEST_CPP_OBJ_DIR)
+INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT   := $(OBJ_DIR)test/integrationtest_transport/
+INTEGRATIONTEST_C_OBJ_DIR_TRANSPORT     := $(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT)
+INTEGRATIONTEST_AUTH_C_OBJ_DIR          := $(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR)
+UNITTEST_OBJ_DIR                        := $(OBJ_DIR)test/unittest/
+
+# helper define to run tests from a list
+define \n
+
+
+endef
 
 CC  := gcc
 CPP := g++
@@ -28,6 +37,23 @@ SOURCES :=  src/shellmatta.c                \
 SOURCES_TRANPORT_LAYER  :=  $(SOURCES)                      \
                             src/shellmatta_transport.c      \
                             src/shellmatta_crc.c
+AUTH_SOURCES     := $(SOURCES) src/shellmatta_auth.c
+EXAMPLE_SOURCES  := example/main.c
+EXAMPLE_SOURCES  += $(filter-out $(EXAMPLE_SOURCES),$(AUTH_SOURCES))
+EXAMPLE_SOURCES  += $(filter-out $(EXAMPLE_SOURCES),$(SOURCES_TRANPORT_LAYER))
+EXAMPLE_COBJ     := $(patsubst %.c,$(OBJ_DIR_EXAMPLE)%.o,$(EXAMPLE_SOURCES))
+
+CPPCHECK_SOURCES := $(AUTH_SOURCES)                         \
+                    src/shellmatta_transport.c              \
+                    src/shellmatta_crc.c
+CPPCHECK_OPTIONS := --enable=all                                            \
+                    --check-level=exhaustive                                \
+                    --template=gcc                                          \
+                    --cppcheck-build-dir=output/cppcheck                    \
+                    --suppress-xml=cfg/cppcheck/cppcheck_suppressions.xml   \
+                    -DSHELLMATTA_AUTHENTICATION                             \
+                    -Iapi                                                   \
+                    $(CPPCHECK_SOURCES)
 
 INCLUDES    := api .
 
@@ -63,6 +89,8 @@ INTEGRATIONTEST_SOURCES :=  test/integrationtest/test_main.cpp
 
 INTEGRATIONTEST_TRANSPORT_SOURCES  :=  $(INTEGRATIONTEST_SOURCES)                           \
                                        test/integrationtest/test_integration_transport.cpp
+INTEGRATIONTEST_AUTH_SOURCES := test/integrationtest_auth/test_main.cpp                 \
+                                test/integrationtest_auth/test_integration_auth.cpp
 
 UNITTEST_CPPOBJ  := $(patsubst %.cpp,$(UNITTEST_OBJ_DIR)%.o,$(UNITTEST_SOURCES))
 
@@ -71,32 +99,46 @@ INTEGRATIONTEST_COBJ    :=  $(patsubst %.c,$(INTEGRATIONTEST_CPP_OBJ_DIR)%.o,$(S
 
 INTEGRATIONTEST_TRANSPORT_CPPOBJ    :=  $(patsubst %.cpp,$(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT)%.o,$(INTEGRATIONTEST_TRANSPORT_SOURCES))
 INTEGRATIONTEST_TRANSPORT_COBJ     	:=  $(patsubst %.c,$(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT)%.o,$(SOURCES_TRANPORT_LAYER))
+INTEGRATIONTEST_AUTH_CPPOBJ :=  $(patsubst %.cpp,$(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR)%.o,$(INTEGRATIONTEST_AUTH_SOURCES))
+INTEGRATIONTEST_AUTH_COBJ   :=  $(patsubst %.c,$(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR)%.o,$(AUTH_SOURCES))
+
 
 CFLAGS                  := $(INCLUDES:%=-I%) -g -Wall -Werror -Wextra -pedantic
 CFLAGS                  += -DSHELLMATTA_HELP_ALIAS=\(char*\)\"?\"
+CFLAGS_EXAMPLE          := $(CFLAGS) -DSHELLMATTA_AUTHENTICATION -DSHELLMATTA_TRANSPORT_ENABLE
 TESTFLAGS               := $(CFLAGS) -fprofile-arcs -ftest-coverage
+TESTFLAGS_AUTH          := $(CFLAGS) -DSHELLMATTA_AUTHENTICATION -fprofile-arcs -ftest-coverage
 TESTLFLAGS              := -fprofile-arcs -Wl,--allow-multiple-definition
 TESTFLAGS_TRANSPORT     := $(TESTFLAGS) -DSHELLMATTA_TRANSPORT_ENABLE
 TESTLFLAGS_TRANSPORT    := $(TESTLFLAGS)
-EXAMPLE_CFLAGS          := $(CFLAGS) -DSHELLMATTA_TRANSPORT_ENABLE
 DEPEND                  = -MT $@ -MF "$(@:%.o=%.d)" -MG -MM
 
-EXAMPLE_SOURCES  := example/main.c $(SOURCES_TRANPORT_LAYER)
-EXAMPLE_COBJ     := $(patsubst %.c,$(OBJ_DIR)%.o,$(EXAMPLE_SOURCES))
 
+COBJ := $(patsubst %.c,$(OBJ_DIR)%.o,$(SOURCES))
 
 EXAMPLE_TARGET                      := $(OBJ_DIR)example/example
 UNITTEST_TARGET                     := $(OBJ_DIR)test/unittest/unittest
 INTEGRATIONTEST_TARGET              := $(OBJ_DIR)test/integrationtest/integrationtest
-INTEGRATIONTEST_TRANSPORT_TARGET    := $(OBJ_DIR)test/integrationtest_transport/integrationtest_transport
-
-OBJ     := $(EXAMPLE_COBJ)                      \
-           $(UNITTEST_CPPOBJ)                   \
-           $(INTEGRATIONTEST_CPPOBJ)            \
-           $(INTEGRATIONTEST_COBJ)              \
-           $(INTEGRATIONTEST_TRANSPORT_CPPOBJ)  \
-           $(INTEGRATIONTEST_TRANSPORT_COBJ)
-DEPS    := $(OBJ:%.o=%.d)
+INTEGRATIONTEST_TARGET_AUTH         := $(OBJ_DIR)test/integrationtest_auth/integrationtest_auth
+INTEGRATIONTEST_TARGET_TRANSPORT    := $(OBJ_DIR)test/integrationtest_transport/integrationtest_transport
+
+TEST_RUN_TARGETS := unittest integrationtest integrationtest_auth integrationtest_transport
+# ensure the tests are running one after another when general test target is called
+ifneq ($(filter test,$(MAKECMDGOALS)),)
+TEST_RUN_TARGETS_HELPER := $(TEST_RUN_TARGETS)
+endif
+TEST_TARGETS := $(UNITTEST_TARGET) $(INTEGRATIONTEST_TARGET) $(INTEGRATIONTEST_TARGET_AUTH) $(INTEGRATIONTEST_TARGET_TRANSPORT)
+
+OBJ     :=  $(COBJ)                             \
+            $(EXAMPLE_COBJ)                     \
+            $(UNITTEST_CPPOBJ)                  \
+            $(INTEGRATIONTEST_CPPOBJ)           \
+            $(INTEGRATIONTEST_COBJ)             \
+            $(INTEGRATIONTEST_AUTH_CPPOBJ)      \
+            $(INTEGRATIONTEST_AUTH_COBJ)        \
+            $(INTEGRATIONTEST_TRANSPORT_CPPOBJ) \
+            $(INTEGRATIONTEST_TRANSPORT_COBJ)
+DEPS    :=  $(OBJ:%.o=%.d)
 
 export
 
@@ -110,46 +152,34 @@ help:
 	@echo example   - build example
 	@echo -----------------------------------------------
 
-test: unittest integrationtest integrationtest_transport
-
-cppcheck:
-	- @mkdir -p output/cppcheck/html
-	cppcheck --enable=all --template=gcc --cppcheck-build-dir=output/cppcheck $(SOURCES)
-	cppcheck --enable=all --template=gcc --cppcheck-build-dir=output/cppcheck $(SOURCES) --xml 2>output/cppcheck/cppcheck.xml
-	cppcheck-htmlreport --file=output/cppcheck/cppcheck.xml --title="Shellmatta" --report-dir=output/cppcheck/html
-
-unittest: $(UNITTEST_TARGET)
-	- @mkdir -p output/test/unittest/report
-	@echo running test:
+test: $(TEST_RUN_TARGETS)
+	- @mkdir -p output/test/report
+# run the test alltogether to get coverage from all combined tests
 #	remove coverage from former run
 	@-find . -name "*.gcda" -type f -delete
-	-$(UNITTEST_TARGET)
+	$(foreach TEST_RUN_TARGET,$(TEST_RUN_TARGETS),./$(OBJ_DIR)test/$(TEST_RUN_TARGET)/$(TEST_RUN_TARGET)${\n})
 
 #	remove report from former run
-	-rm -rf $(OBJ_DIR)test/unittest/report/*
-	gcovr --html-details --output $(OBJ_DIR)test/unittest/report/report.html output/test/unittest -f src -f api -d
+	-rm -rf $(OBJ_DIR)test/report/*
+	gcovr --html-details --output $(OBJ_DIR)test/report/report.html -f src -f api -d
 
-integrationtest: $(INTEGRATIONTEST_TARGET)
-	- @mkdir -p output/test/integrationtest/report
-	@echo running test:
-#	remove coverage from former run
-#	@-find . -name "*.gcda" -type f -delete
-	-$(INTEGRATIONTEST_TARGET)
+cppcheck:
+	- @mkdir -p output/cppcheck/html
+	cppcheck $(CPPCHECK_OPTIONS)
+	cppcheck $(CPPCHECK_OPTIONS) --xml 2>output/cppcheck/cppcheck.xml
+	cppcheck-htmlreport --file=output/cppcheck/cppcheck.xml --title="Shellmatta" --report-dir=output/cppcheck/html
 
-	#	remove report from former run
-#	-rm -rf $(OBJ_DIR)test/unittest/report/*
-	gcovr --html-details --output $(OBJ_DIR)test/integrationtest/report/report.html output/test/integrationtest -f src -f api -d
 
-integrationtest_transport: $(INTEGRATIONTEST_TRANSPORT_TARGET)
-	- @mkdir -p output/test/integrationtest_transport/report
-	@echo running test:
+$(TEST_RUN_TARGETS): $(TEST_TARGETS) $(filter-out $@,$(TEST_RUN_TARGETS_HELPER))
+	- @mkdir -p output/test/$@/report
+	@echo running test $@:
 #	remove coverage from former run
-#	@-find . -name "*.gcda" -type f -delete
-	-$(INTEGRATIONTEST_TRANSPORT_TARGET)
+	@-find . -name "*.gcda" -type f -delete
+	-$(OBJ_DIR)test/$@/$@
 
-	#	remove report from former run
-#	-rm -rf $(OBJ_DIR)test/unittest/report/*
-	gcovr --html-details --output $(OBJ_DIR)test/integrationtest_transport/report/report.html output/test/integrationtest_transport -f src -f api -d
+#	remove report from former run
+	-rm -rf $(OBJ_DIR)test/$@/report/*
+	gcovr --html-details --output $(OBJ_DIR)test/$@/report/report.html output/test/$@ -f src -f api -d
 
 example: $(EXAMPLE_TARGET)
 	@echo building example
@@ -158,7 +188,7 @@ doc:
 	- @mkdir -p output/doc/html
 	- @mkdir -p output/doc/latex
 	doxygen cfg/doxygen/doxyfile
-	
+
 clean:
 	- rm -rf $(OBJ_DIR)
 
@@ -169,48 +199,72 @@ $(EXAMPLE_TARGET): $(EXAMPLE_COBJ)
 $(UNITTEST_TARGET): $(UNITTEST_CPPOBJ)
 	- @mkdir -p $(@D)
 	$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
-	
+
 $(INTEGRATIONTEST_TARGET): $(INTEGRATIONTEST_CPPOBJ) $(INTEGRATIONTEST_COBJ)
 	- @mkdir -p $(@D)
 	$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
 	
-$(INTEGRATIONTEST_TRANSPORT_TARGET): $(INTEGRATIONTEST_TRANSPORT_CPPOBJ) $(INTEGRATIONTEST_TRANSPORT_COBJ)
+$(INTEGRATIONTEST_TARGET_TRANSPORT): $(INTEGRATIONTEST_TRANSPORT_CPPOBJ) $(INTEGRATIONTEST_TRANSPORT_COBJ)
 	- @mkdir -p $(@D)
 	$(CPP) $(TESTLFLAGS_TRANSPORT) $(LIB_PATH) -o $@ $^ $(LIBS)
 
+$(INTEGRATIONTEST_TARGET_AUTH): $(INTEGRATIONTEST_AUTH_CPPOBJ) $(INTEGRATIONTEST_AUTH_COBJ)
+	- @mkdir -p $(@D)
+	echo askdjhaskjdhskj
+	$(CPP) $(TESTLFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
+
+$(TARGET): $(OBJ)
+	- @mkdir -p $(@D)
+	$(CC) $(LFLAGS) $(LIB_PATH) -o $@ $^ $(LIBS)
+
+$(COBJ):
+	- @mkdir -p $(@D)
+	@$(CC) -c $(CFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
+	$(CC) -c $(CFLAGS) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
+
 $(EXAMPLE_COBJ):
 	- @mkdir -p $(@D)
-	@$(CC) -c $(EXAMPLE_CFLAGS) $(DEPEND) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
-	$(CC) -c $(EXAMPLE_CFLAGS) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
+	@$(CC) -c $(CFLAGS_EXAMPLE) $(DEPEND) -o $(@:%.o=%.d) $(subst $(OBJ_DIR_EXAMPLE), ,$(@:%.o=%.c))
+	$(CC) -c $(CFLAGS_EXAMPLE) -o $@  $(subst $(OBJ_DIR_EXAMPLE), ,$(@:%.o=%.c))
 
 $(UNITTEST_CPPOBJ):
 	- @mkdir -p $(@D)
-	@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $@  $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
-	$(CPP) -c $(TESTFLAGS) -o $@  $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
+	@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
+	$(CPP) -c $(TESTFLAGS) -o $@ $(subst $(UNITTEST_OBJ_DIR), ,$(@:%.o=%.cpp))
 
 $(INTEGRATIONTEST_CPPOBJ):
 	- @mkdir -p $(@D)
-	@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $@  $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
+	@$(CPP) -c $(TESTFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
 	$(CPP) -c $(TESTFLAGS) -o $@  $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
 
 $(INTEGRATIONTEST_COBJ):
 	- @mkdir -p $(@D)
-	@$(CC) -c $(TESTFLAGS) $(DEPEND) -o $@  $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
+	@$(CC) -c $(TESTFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
 	$(CC) -c $(TESTFLAGS) -o $@  $(subst $(INTEGRATIONTEST_C_OBJ_DIR), ,$(@:%.o=%.c))
 
 $(INTEGRATIONTEST_TRANSPORT_CPPOBJ):
 	- @mkdir -p $(@D)
-	@$(CPP) -c $(TESTFLAGS_TRANSPORT) $(DEPEND) -o $@  $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.cpp))
+	@$(CPP) -c $(TESTFLAGS_TRANSPORT) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.cpp))
 	$(CPP) -c $(TESTFLAGS_TRANSPORT) -o $@  $(subst $(INTEGRATIONTEST_CPP_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.cpp))
 
 $(INTEGRATIONTEST_TRANSPORT_COBJ):
 	- @mkdir -p $(@D)
-	@$(CC) -c $(TESTFLAGS_TRANSPORT) $(DEPEND) -o $@  $(subst $(INTEGRATIONTEST_C_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.c))
+	@$(CC) -c $(TESTFLAGS_TRANSPORT) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_C_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.c))
 	$(CC) -c $(TESTFLAGS_TRANSPORT) -o $@  $(subst $(INTEGRATIONTEST_C_OBJ_DIR_TRANSPORT), ,$(@:%.o=%.c))
 
+$(INTEGRATIONTEST_AUTH_CPPOBJ):
+	- @mkdir -p $(@D)
+	@$(CPP) -c $(TESTFLAGS_AUTH) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
+	$(CPP) -c $(TESTFLAGS_AUTH) -o $@  $(subst $(INTEGRATIONTEST_AUTH_CPP_OBJ_DIR), ,$(@:%.o=%.cpp))
+
+$(INTEGRATIONTEST_AUTH_COBJ):
+	- @mkdir -p $(@D)
+	$(CC) -c $(TESTFLAGS_AUTH) $(DEPEND) -o $(@:%.o=%.d) $(subst $(INTEGRATIONTEST_AUTH_C_OBJ_DIR), ,$(@:%.o=%.c))
+	$(CC) -c $(TESTFLAGS_AUTH) -o $@  $(subst $(INTEGRATIONTEST_AUTH_C_OBJ_DIR), ,$(@:%.o=%.c))
+
 %.o: %.cpp
 	- @mkdir -p $(@D)
-	@$(CPP) -c $(CFLAGS) $(DEPEND) -o $@  $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
+	@$(CPP) -c $(CFLAGS) $(DEPEND) -o $(@:%.o=%.d) $(subst $(OBJ_DIR), ,$(@:%.o=%.c))
 	$(CPP) -c $(CFLAGS) -o $@  $<
 
 -include $(DEPS)

+ 70 - 33
src/shellmatta.c

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -26,6 +26,9 @@
 #ifdef SHELLMATTA_TRANSPORT_ENABLE
 #include "shellmatta_transport.h"
 #endif
+#ifdef SHELLMATTA_AUTHENTICATION
+#include "shellmatta_auth.h"
+#endif
 #include <stddef.h>
 #include <stdint.h>
 #include <string.h>
@@ -147,13 +150,13 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
             if(0u == inst->hereLength)
             {
                 /**
-                    *  @dot
-                    *  digraph heredocParser {
-                    *      start -> wait [ label="<< in first line - store delimiter" ];
-                    *      wait -> wait [ label="delimiter not detected" ];
-                    *      wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ];
-                    *  }
-                    *  @enddot */
+                     *  @dot
+                     *  digraph heredocParser {
+                     *      start -> wait [ label="<< in first line - store delimiter" ];
+                     *      wait -> wait [ label="delimiter not detected" ];
+                     *      wait -> end [ label="delimiter found - remove all heredoc stuff and send the input to the command" ];
+                     *  }
+                     *  @enddot */
 
                 /** -# check for heredoc - add string delimiter to stop strstr from searching too far */
                 inst->buffer[inst->inputCount] = '\0';
@@ -279,14 +282,25 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
                     {
                         utils_writeEcho(inst, "\r\n", 2u);
                         shellmatta_opt_init(inst, cmdLen + 1u);
+
+#ifdef SHELLMATTA_AUTHENTICATION
+                        cmdRet = SHELLMATTA_OK;
+                        if (SHELLMATTA_OK == shellmatta_auth_is_cmd_permitted(inst, cmd))
+                        {
+                            cmdExecuted = 1u;
+                            cmdRet = cmd->cmdFct(handle, inst->buffer, inst->inputCount);
+                        }
+
+#else
                         cmdExecuted = 1u;
                         cmdRet = cmd->cmdFct(handle, inst->buffer, inst->inputCount);
+#endif
 
                         switch(cmdRet)
                         {
                             case SHELLMATTA_CONTINUE:
                                 /** -# initialize stdin buffer and continuous cmd */
-                                inst->stdinIdx      = inst->inputCount + 1u;
+                                inst->stdinIdx      = inst->bufferSize - 2u;
                                 inst->stdinLength   = 0u;
                                 inst->continuousCmd = cmd;
                                 ret                 = cmdRet;
@@ -311,9 +325,16 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
 
                 if ((0u == cmdExecuted) && (inst->inputCount > 0))
                 {
-                    SHELLMATTA_WRITE("\r\nCommand: ", 11u);
-                    SHELLMATTA_WRITE(inst->buffer, inst->inputCount);
-                    SHELLMATTA_WRITE(" not found", 10u);
+                    if (inst->echoEnabled)
+                    {
+                        inst->write("\r\nCommand: ", 11u);
+                        inst->write(inst->buffer, inst->inputCount);
+                        inst->write(" not found", 10u);
+                    }
+                    else
+                    {
+                        inst->write("\r\nCommand not found!", 20u);
+                    }
                 }
 
                 /** -# terminate this session if no continuous mode is requested */
@@ -325,7 +346,7 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
             }
         }
         /** -# ignore newline as first character (to be compatible to
-            * terminals sending newline after return) */
+         * terminals sending newline after return */
         else if((0u == inst->inputCount) && ('\n' == data[inst->byteCounter]))
         {
             /* do nothing */
@@ -337,7 +358,7 @@ static shellmatta_retCode_t shellmatta_processDataInt(shellmatta_handle_t     ha
             autocomplete_run(inst);
         }
         /** -# check for cancel -
-            *      terminate current input and print prompt again */
+         *      terminate current input and print prompt again */
         else if('\x03' == data[inst->byteCounter])
         {
             inst->dirty = false;
@@ -421,44 +442,39 @@ shellmatta_retCode_t shellmatta_doInit(
         &&  (NULL != writeFct)
         &&  ((NULL != historyBuffer) || (0u == historyBufferSize)))
     {
+        /** -# clear the shellmatta instance */
+        memset((void *)inst, 0, sizeof(shellmatta_instance_t));
+
         /** -# copy all provided buffers into the shellmatta instance */
         inst->buffer                = buffer;
         inst->bufferSize            = bufferSize;
-        inst->inputCount            = 0u;
-        inst->byteCounter           = 0u;
-        inst->lastNewlineIdx        = 0u;
-        inst->cursor                = 0u;
-        inst->stdinIdx              = 0u;
-        inst->stdinLength           = 0u;
         inst->historyBuffer         = historyBuffer;
         inst->historyBufferSize     = historyBufferSize;
-        inst->historyStart          = 0u;
-        inst->historyEnd            = 0u;
-        inst->historyRead           = 0u;
         inst->historyReadUp         = true;
         inst->write                 = writeFct;
         inst->prompt                = prompt;
         inst->echoEnabled           = true;
         inst->dirty                 = false;
-        inst->tabCounter            = 0u;
-        inst->escapeCounter         = 0u;
-        inst->hereStartIdx          = 0u;
-        inst->hereDelimiterIdx      = 0u;
-        inst->hereLength            = 0u;
         inst->delimiter             = '\r';
         inst->mode                  = SHELLMATTA_MODE_INSERT;
         inst->cmdList               = &(inst->helpCmd);
-        inst->continuousCmd         = NULL;
-        inst->busyCmd               = NULL;
-        inst->cmdListIsConst        = false;
         shellmatta_opt_init(inst, 0u);
 
         /** -# copy the help command structure to this instance */
         memcpy(&(inst->helpCmd), &helpCmd, sizeof(shellmatta_cmd_t));
+#ifdef SHELLMATTA_AUTHENTICATION
+        /** -# copy the auth commands to the instance */
+        memcpy(&(inst->loginCmd), &shellmatta_auth_loginCmd, sizeof(shellmatta_cmd_t));
+        memcpy(&(inst->logoutCmd), &shellmatta_auth_logoutCmd, sizeof(shellmatta_cmd_t));
+#endif
 
         if(NULL != cmdList)
         {
+#ifndef SHELLMATTA_AUTHENTICATION
             inst->helpCmd.next = (shellmatta_cmd_t *) cmdList;
+#else
+            inst->logoutCmd.next = (shellmatta_cmd_t *) cmdList;
+#endif
             inst->cmdListIsConst = true;
         }
 
@@ -488,7 +504,7 @@ shellmatta_retCode_t shellmatta_doInit(
  * It resets all internal states - the buffers are left as they are - they will be overwritten.
  * The history buffer is deleted as well.
  */
-shellmatta_retCode_t shellmatta_resetShell( shellmatta_handle_t handle, bool printPrompt)
+shellmatta_retCode_t shellmatta_resetShell(shellmatta_handle_t handle, bool printPrompt)
 {
     shellmatta_instance_t *inst = (shellmatta_instance_t *)handle;
     shellmatta_retCode_t ret = SHELLMATTA_OK;
@@ -517,6 +533,11 @@ shellmatta_retCode_t shellmatta_resetShell( shellmatta_handle_t handle, bool pri
         inst->hereLength            = 0u;
         shellmatta_opt_init(inst, 0u);
 
+#ifdef SHELLMATTA_AUTHENTICATION
+        inst->userId = 0u;
+        inst->userPointer = NULL;
+#endif
+
         if(true == printPrompt)
         {
             /** -# print a prompt if requested */
@@ -608,6 +629,22 @@ shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cm
                 tempCmd = tempCmd->next;
             }
         }
+
+#ifdef SHELLMATTA_AUTHENTICATION
+        /** -# append permissions to added command if any */
+        if (inst->permList)
+        {
+            /** -# Search for command in perm list */
+            for (uint32_t i = 0u; i < inst->permListLength; i++)
+            {
+                if (0 == strcmp(cmd->cmd, inst->permList[i].cmd))
+                {
+                    cmd->authLink = &inst->permList[i];
+                    break;
+                }
+            }
+        }
+#endif
     }
     else
     {
@@ -624,7 +661,7 @@ shellmatta_retCode_t shellmatta_addCmd(shellmatta_handle_t handle, shellmatta_cm
  * @return      errorcode   #SHELLMATTA_OK
  *                          #SHELLMATTA_USE_FAULT (param err)
  */
-shellmatta_retCode_t shellmatta_removeCmd(shellmatta_handle_t handle, shellmatta_cmd_t *cmd)
+shellmatta_retCode_t shellmatta_removeCmd(shellmatta_handle_t handle, const shellmatta_cmd_t *cmd)
 {
     shellmatta_instance_t   *inst       = (shellmatta_instance_t*)handle;
     shellmatta_cmd_t       *prevCmd;

+ 634 - 0
src/shellmatta_auth.c

@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2022 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * 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 <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_auth
+ * @{
+ */
+
+#include "shellmatta.h"
+#include "shellmatta_auth.h"
+#include "shellmatta_history.h"
+#include "shellmatta_utils.h"
+#include "shellmatta_escape.h"
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+/**
+ * @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;
+    bool echoEnabledBackup = inst->echoEnabled;
+
+    /** -# disable echo when hidden */
+    inst->echoEnabled &= !hide;
+
+    /** -# 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);
+    }
+
+    inst->echoEnabled = echoEnabledBackup;
+
+    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, const 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) == SHELLMATTA_OK ? userId : 0u;
+    }
+    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, SHELLMATTA_AUTH_EVENT_LOGIN_FAILED);
+    }
+
+    /** -# 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_write(handle, "Unknown option\r\n", 16);
+                        return SHELLMATTA_USE_FAULT;
+                        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);
+            }
+            /** -# user passed username and start interactive password input */
+            else if(NULL != username)
+            {
+                inst->inputCount = 0u;
+                inst->cursor = 0u;
+
+                inst->tmpUserId = getUserIdFromName(handle, username);
+                shellmatta_write(handle, "\r\nenter password:\r\n", 19);
+                inst->loginState = SHELLMATTA_AUTH_PASSWORD;
+                ret = SHELLMATTA_CONTINUE;
+            }
+            else if (NULL != password)
+            {
+                shellmatta_write(handle, "Missing username\r\n", 18);
+                ret = SHELLMATTA_USE_FAULT;
+            }
+            else
+            {
+                /** -# no credentials are passed with the command - start the interactive input */
+                inst->inputCount = 0u;
+                inst->cursor = 0u;
+                shellmatta_write(handle, "enter username:\r\n", 17);
+                inst->loginState = SHELLMATTA_AUTH_USERNAME;
+                ret = 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"
+                                                 , "login interactively or use -u <username> -p <password>"
+                                                 , 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_instance_t *inst = (shellmatta_instance_t*)handle;
+
+    /** -# log the logout event */
+    if (inst->logFct)
+    {
+        inst->logFct(inst->userId, SHELLMATTA_AUTH_EVENT_LOGOUT);
+    }
+
+    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_DUPLICATE (login/logout commands already existed)
+ *                          #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;
+        SHELLMATTA_RET(ret, shellmatta_addCmd(handle, &inst->logoutCmd));
+        if(!customLogin)
+        {
+            SHELLMATTA_RET(ret, shellmatta_addCmd(handle, &inst->loginCmd));
+        }
+        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       logs a passed user id into the shellmatta instance
+ * @param[in]   handle      shellmatta instance handle
+ * @param[in]   userId      userId to login
+ * @return      errorcode   #SHELLMATTA_OK
+ *                          #SHELLMATTA_ERR (user does not exist)
+ *                          #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;
+        inst->userPointer = NULL;
+        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;
+                }
+            }
+        }
+
+        /** -# check if the user did exist */
+        if (NULL == inst->userPointer)
+        {
+            ret = SHELLMATTA_ERROR;
+        }
+
+        /** -# call log function */
+        if (inst->logFct)
+        {
+            inst->logFct(userId,
+                         NULL != inst->userPointer ?                \
+                                 SHELLMATTA_AUTH_EVENT_LOGIN :      \
+                                 SHELLMATTA_AUTH_EVENT_LOGIN_FAILED);
+        }
+    }
+    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;
+    const 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           changes the password of the passed username
+ * @param[in]       handle      shellmatta instance handle
+ * @param[in]       username    username to change the password of
+ * @param[in]       password    password to be set for the user
+ * @return          errorcode   #SHELLMATTA_OK
+ *                              #SHELLMATTA_ERROR (user not found)
+ *                              #SHELLMATTA_USE_FAULT (param err)
+ */
+shellmatta_retCode_t shellmatta_auth_chpasswd(shellmatta_handle_t handle, const char *username, const char *password)
+{
+    shellmatta_retCode_t    ret     = SHELLMATTA_ERROR;
+    shellmatta_instance_t   *inst   = (shellmatta_instance_t*)handle;
+    uint32_t                i;
+
+    /** -# check parameters for plausibility  */
+    if(     (NULL               != inst)
+        &&  (SHELLMATTA_MAGIC   == inst->magic)
+        &&  (username           != NULL)
+        &&  (password           != NULL))
+    {
+        for (i = 0u; i < inst->userListLength; i++)
+        {
+            /** -# search for user */
+            if (0 == strcmp(inst->userList[i].username, username))
+            {
+                inst->userList[i].password = password;
+                return SHELLMATTA_OK;
+            }
+        }
+    }
+    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;
+
+    /**! -# commands without an authentication set are public */
+    if (NULL == cmd->authLink)
+    {
+        ret = SHELLMATTA_OK;
+    }
+    /**! -# allow superuser to access all commands */
+    else if ((NULL != inst->userPointer) && (true == inst->userPointer->superuser))
+    {
+        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;
+}
+
+/**
+ * @}
+ */

+ 38 - 0
src/shellmatta_auth.h

@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * 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 <stefan.strobel@shimatta.net>
+ */
+
+/**
+ * @addtogroup shellmatta_auth
+ * @{
+ */
+#ifndef _SHELLMATTA_AUTH_H_
+#define _SHELLMATTA_AUTH_H_
+
+#include "shellmatta.h"
+#include <stdint.h>
+
+/** @brief login command to enable a user to login */
+extern const shellmatta_cmd_t shellmatta_auth_loginCmd;
+
+/** @brief logout command */
+extern const shellmatta_cmd_t shellmatta_auth_logoutCmd;
+
+
+shellmatta_retCode_t shellmatta_auth_is_cmd_permitted(const shellmatta_instance_t   *inst,
+                                                      shellmatta_cmd_t              *cmd);
+
+#endif
+
+/** @} */
+

+ 25 - 1
src/shellmatta_autocomplete.c

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -20,6 +20,9 @@
 #include "shellmatta.h"
 #include "shellmatta_autocomplete.h"
 #include "shellmatta_utils.h"
+#ifdef SHELLMATTA_AUTHENTICATION
+#include "shellmatta_auth.h"
+#endif
 #include <stdint.h>
 #include <string.h>
 
@@ -50,6 +53,13 @@ void autocomplete_run(shellmatta_instance_t *inst)
         /** -# loop through all registered commands */
         while (NULL != cmd)
         {
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (SHELLMATTA_OK != shellmatta_auth_is_cmd_permitted(inst, cmd))
+            {
+                cmd = cmd->next;
+                continue;
+            }
+#endif
             /** -# check if command matches the input */
             if(    (strlen(cmd->cmd) >= inst->cursor)
                 && (0 == strncmp(cmd->cmd, inst->buffer, inst->cursor)))
@@ -85,6 +95,13 @@ void autocomplete_run(shellmatta_instance_t *inst)
         if(printedLen > 0u)
         {
             utils_writeEcho(inst, "\r\n", 2u);
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (NULL != inst->userPointer)
+            {
+                utils_writeEcho(inst, inst->userPointer->username, strlen(inst->userPointer->username));
+                utils_writeEcho(inst, "@", 1);
+            }
+#endif
             utils_writeEcho(inst, inst->prompt, strlen(inst->prompt));
             utils_writeEcho(inst, inst->buffer, inst->inputCount);
             tempCursor = inst->cursor;
@@ -98,6 +115,13 @@ void autocomplete_run(shellmatta_instance_t *inst)
         /** -# loop through all registered commands */
         while (NULL != cmd)
         {
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (SHELLMATTA_OK != shellmatta_auth_is_cmd_permitted(inst, cmd))
+            {
+                cmd = cmd->next;
+                continue;
+            }
+#endif
             /** -# check if command matches the input */
             if(    (strlen(cmd->cmd) >= inst->cursor)
                 && (0 == strncmp(cmd->cmd, inst->buffer, inst->cursor)))

+ 1 - 1
src/shellmatta_autocomplete.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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

+ 1 - 1
src/shellmatta_escape.c

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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

+ 1 - 1
src/shellmatta_escape.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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

+ 16 - 3
src/shellmatta_history.c

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -16,7 +16,7 @@
  * @addtogroup shellmatta_history
  * @{
  */
-
+#include <string.h>
 #include "shellmatta_history.h"
 #include "shellmatta.h"
 #include "shellmatta_utils.h"
@@ -89,7 +89,7 @@ static bool getHistoryByte(shellmatta_instance_t *inst, char *byte)
  * @param[in]   inst    pointer to a shellmatta instance
  * @return      true:   current command is identical to the last one in the history buffer
  */
-static bool compareLastCommand(shellmatta_instance_t *inst)
+static bool compareLastCommand(const shellmatta_instance_t *inst)
 {
     bool        ret = false;
     uint32_t    i;
@@ -286,6 +286,19 @@ void history_reset(shellmatta_instance_t *inst)
     inst->historyReadUp = true;
 }
 
+/**
+ * @brief       clears the history buffer
+ * @param[in]   inst    pointer to a shellmatta instance
+ */
+void history_clear(shellmatta_instance_t *inst)
+{
+    inst->historyStart      = 0u;
+    inst->historyEnd        = 0u;
+    inst->historyRead       = 0u;
+    inst->historyReadUp     = true;
+    memset(inst->historyBuffer, 0, inst->historyBufferSize);
+}
+
 /**
  * @}
  */

+ 2 - 1
src/shellmatta_history.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -26,6 +26,7 @@ bool history_navigate(shellmatta_instance_t *inst, int32_t cnt);
 void history_storeCmd(shellmatta_instance_t *inst);
 void history_restoreCmd(shellmatta_instance_t *inst);
 void history_reset(shellmatta_instance_t *inst);
+void history_clear(shellmatta_instance_t *inst);
 
 #endif
 

+ 3 - 3
src/shellmatta_opt.c

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -129,7 +129,7 @@ static shellmatta_retCode_t parseShortOpt(  shellmatta_instance_t       *inst,
                                             shellmatta_opt_argtype_t    *argtype)
 {
     shellmatta_retCode_t ret = SHELLMATTA_ERROR;
-    char *buffer = &inst->buffer[inst->optionParser.offset];
+    const char *buffer = &inst->buffer[inst->optionParser.offset];
     uint32_t i;
 
     /** -# check for correct syntax */
@@ -191,7 +191,7 @@ static shellmatta_retCode_t parseLongOpt(   shellmatta_instance_t       *inst,
                                             shellmatta_opt_argtype_t    *argtype)
 {
     shellmatta_retCode_t ret = SHELLMATTA_ERROR;
-    char *buffer = &inst->buffer[inst->optionParser.offset];
+    const char *buffer = &inst->buffer[inst->optionParser.offset];
     uint32_t i;
 
     /** -# check for correct syntax for short options */

+ 1 - 1
src/shellmatta_opt.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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

+ 33 - 3
src/shellmatta_utils.c

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -19,6 +19,9 @@
 
 #include "shellmatta_utils.h"
 #include "shellmatta.h"
+#ifdef SHELLMATTA_AUTHENTICATION
+#include "shellmatta_auth.h"
+#endif
 #include <string.h>
 
 /**
@@ -164,7 +167,7 @@ void utils_forwardCursor(shellmatta_instance_t *inst, uint32_t length)
  * @param[in]   length  length of the data to be inserted
  */
 void utils_insertChars( shellmatta_instance_t   *inst,
-                        char                    *data,
+                        const char              *data,
                         uint32_t                 length)
 {
     uint32_t tmpLength = length;
@@ -189,6 +192,7 @@ void utils_insertChars( shellmatta_instance_t   *inst,
 
             /** -# store and print the new chars */
             memcpy(&(inst->buffer[inst->cursor]), data, tmpLength);
+
             utils_writeEcho(inst, data, tmpLength);
 
             /** -# print the other chars and restore the cursor to this position */
@@ -385,6 +389,13 @@ static shellmatta_retCode_t helpCmdFct(const shellmatta_handle_t handle, const c
         cmd = inst->cmdList;
         while(NULL != cmd)
         {
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (SHELLMATTA_OK != shellmatta_auth_is_cmd_permitted(inst, cmd))
+            {
+                cmd = cmd->next;
+                continue;
+            }
+#endif
             maxCmdLen           = SHELLMATTA_MAX(maxCmdLen,         strlen(cmd->cmd));
             if(NULL != cmd->cmdAlias)
             {
@@ -397,6 +408,13 @@ static shellmatta_retCode_t helpCmdFct(const shellmatta_handle_t handle, const c
         cmd = inst->cmdList;
         while(NULL != cmd)
         {
+#ifdef SHELLMATTA_AUTHENTICATION
+            if (SHELLMATTA_OK != shellmatta_auth_is_cmd_permitted(inst, cmd))
+            {
+                cmd = cmd->next;
+                continue;
+            }
+#endif
             /** -# determine the length of each field to add padding */
             cmdLen       = strlen(cmd->cmd);
             cmdAliasLen  = (NULL != cmd->cmdAlias) ? strlen(cmd->cmdAlias) : 0u;
@@ -436,7 +454,11 @@ const shellmatta_cmd_t helpCmd = {SHELLMATTA_HELP_COMMAND
                                 , SHELLMATTA_HELP_HELP_TEXT
                                 , SHELLMATTA_HELP_USAGE_TEXT
                                 , helpCmdFct
-                                , NULL};
+                                , NULL
+#ifdef SHELLMATTA_AUTHENTICATION
+                                , NULL
+#endif
+                                };
 
 /**
  * @brief       terminates an input and prints the prompt again
@@ -453,6 +475,14 @@ void utils_terminateInput(shellmatta_instance_t *inst)
     inst->continuousCmd     = NULL;
     inst->busyCmd           = NULL;
     SHELLMATTA_WRITE("\r\n", 2u);
+#ifdef SHELLMATTA_AUTHENTICATION
+    inst->loginState        = SHELLMATTA_AUTH_IDLE;
+    if (NULL != inst->userPointer)
+    {
+        SHELLMATTA_WRITE(inst->userPointer->username, strlen(inst->userPointer->username));
+        SHELLMATTA_WRITE("@", 1);
+    }
+#endif
     SHELLMATTA_WRITE(inst->prompt, strlen(inst->prompt));
 }
 

+ 2 - 2
src/shellmatta_utils.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -116,7 +116,7 @@ void utils_eraseLine(shellmatta_instance_t *inst);
 void utils_rewindCursor(shellmatta_instance_t *inst, uint32_t length);
 void utils_forwardCursor(shellmatta_instance_t *inst, uint32_t length);
 void utils_insertChars( shellmatta_instance_t   *inst,
-                        char                    *data,
+                        const char              *data,
                         uint32_t                 length);
 void utils_removeChars( shellmatta_instance_t   *inst,
                         uint32_t                 length,

File diff suppressed because it is too large
+ 2282 - 1171
test/framework/catch.hpp


+ 31 - 38
test/integrationtest/test_integration.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -11,12 +11,12 @@
  * @brief   integration test implementation for some general functions
  * @author  Stefan Strobel <stefan.strobel@shimatta.net>
  */
-
 #include "test/framework/catch.hpp"
 extern "C" {
 #include "shellmatta.h"
 }
 #include <string.h>
+using Catch::Matchers::Equals;
 
 static uint32_t write_callCnt = 0u;
 static char write_data[1024];
@@ -65,7 +65,6 @@ TEST_CASE( "shellmatta empty function" ) {
     shellmatta_handle_t handle;
     char buffer[1024];
     char historyBuffer[1024];
-    char *dummyData =   (char*)"\r\nshellmatta->";
 
     shellmatta_doInit(  &inst,
                         &handle,
@@ -84,8 +83,7 @@ TEST_CASE( "shellmatta empty function" ) {
     shellmatta_processData(handle, (char*)"\r", 1);
 
     CHECK( write_length == 14u);
-    REQUIRE( strcmp(dummyData, write_data) == 0);
-
+    REQUIRE_THAT( write_data, Equals("\r\nshellmatta->") );
 }
 
 TEST_CASE( "shellmatta heredoc test" ) {
@@ -94,9 +92,6 @@ TEST_CASE( "shellmatta heredoc test" ) {
     shellmatta_handle_t handle;
     char buffer[1024];
     char historyBuffer[1024];
-    char *dummyData =   (char*)"do this ";
-    char *dummyStdin =  (char*)"asdf\r\n"
-                        "1234";
 
     shellmatta_doInit(  &inst,
                         &handle,
@@ -119,9 +114,9 @@ TEST_CASE( "shellmatta heredoc test" ) {
                                 , 33);
 
     CHECK( doSomethingStdinLength == 10u);
-    CHECK( strcmp(dummyStdin, doSomethingStdin) == 0);
+    CHECK_THAT( doSomethingStdin, Equals("asdf\r\n1234") );
     CHECK( doSomethingLength == 8u);
-    REQUIRE( strcmp(dummyData, doSomethingArguments) == 0);
+    REQUIRE_THAT( doSomethingArguments, Equals("do this ") );
 }
 
 TEST_CASE( "shellmatta heredoc test empty" ) {
@@ -130,7 +125,6 @@ TEST_CASE( "shellmatta heredoc test empty" ) {
     shellmatta_handle_t handle;
     char buffer[1024];
     char historyBuffer[1024];
-    char *dummyData =   (char*)"do this ";
 
     shellmatta_doInit(  &inst,
                         &handle,
@@ -153,7 +147,7 @@ TEST_CASE( "shellmatta heredoc test empty" ) {
     CHECK( doSomethingStdinLength == 0u);
     CHECK( NULL == doSomethingStdin );
     CHECK( doSomethingLength == 8u);
-    REQUIRE( strcmp(dummyData, doSomethingArguments) == 0);
+    REQUIRE_THAT( doSomethingArguments, Equals("do this ") );
 }
 
 TEST_CASE( "shellmatta remove function" ) {
@@ -185,8 +179,7 @@ TEST_CASE( "shellmatta remove function" ) {
     shellmatta_processData(handle, (char*)"?\r", 2);
 
     CHECK( write_length == strlen(dummyData));
-    CHECK( strcmp(dummyData, write_data) == 0);
-
+    CHECK_THAT( write_data, Equals(dummyData) );
 
     write_callCnt = 0u;
     memset(write_data, 0, sizeof(write_data));
@@ -199,8 +192,8 @@ TEST_CASE( "shellmatta remove function" ) {
 
     shellmatta_processData(handle, (char*)"? 564 321 56 465 46\r", 20);
 
-    CHECK( write_length == strlen(dummyData));
-    CHECK( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    CHECK_THAT(write_data, Catch::Matchers::Equals(dummyData));
 
     write_callCnt = 0u;
     memset(write_data, 0, sizeof(write_data));
@@ -213,8 +206,8 @@ TEST_CASE( "shellmatta remove function" ) {
                     "help  ?  help [command] - print help or usage information\r\n"
                     "\r\nshellmatta->";
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta reset no prompt" ) {
@@ -247,8 +240,8 @@ TEST_CASE( "shellmatta reset no prompt" ) {
     shellmatta_resetShell(handle, false);
     shellmatta_processData(handle, (char*)"?\r", 2);
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta reset with prompt" ) {
@@ -282,8 +275,8 @@ TEST_CASE( "shellmatta reset with prompt" ) {
     shellmatta_resetShell(handle, true);
     shellmatta_processData(handle, (char*)"?\r", 2);
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta reset no prompt history buffer" ) {
@@ -322,8 +315,8 @@ TEST_CASE( "shellmatta reset no prompt history buffer" ) {
     shellmatta_processData(handle, (char*)"\033[A", 3u);
     shellmatta_processData(handle, (char*)"?\r", 2);
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta reset no prompt heredoc" ) {
@@ -362,8 +355,8 @@ TEST_CASE( "shellmatta reset no prompt heredoc" ) {
     /* now the new command should be processed */
     shellmatta_processData(handle, (char*)"?\r", 2);
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta configure disable echo" ) {
@@ -400,8 +393,8 @@ TEST_CASE( "shellmatta configure disable echo" ) {
     /* check with echo enabled */
     shellmatta_processData(handle, (char*)"help this is some dummy Text\r\n", 30u);
 
-    CHECK( write_length == strlen(dummyData));
-    CHECK( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    CHECK_THAT(write_data, Catch::Matchers::Equals(dummyData));
 
     write_callCnt = 0u;
     memset(write_data, 0, sizeof(write_data));
@@ -414,8 +407,8 @@ TEST_CASE( "shellmatta configure disable echo" ) {
     /* check with echo disabled */
     shellmatta_processData(handle, (char*)"help this is some dummy Text\r\n", 30u);
 
-    CHECK( write_length == strlen(dummyData2));
-    REQUIRE( strcmp(dummyData2, write_data) == 0);
+    CHECK(write_length == strlen(dummyData2));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData2));
 }
 
 TEST_CASE( "shellmatta configure mode" ) {
@@ -425,9 +418,9 @@ TEST_CASE( "shellmatta configure mode" ) {
     shellmatta_retCode_t ret;
     char buffer[1024];
     char historyBuffer[1024];
-    char *dummyData =   (char*)"\r\nCommand: meow this is some dum123456my Text not found\r\nshellmatta->";
+    char *dummyData =   (char*)"\r\nCommand not found!\r\nshellmatta->";
 
-    char *dummyData2 =   (char*)"\r\nCommand: meow this is some dum123456t not found\r\nshellmatta->";
+    char *dummyData2 =   (char*)"\r\nCommand not found!\r\nshellmatta->";
 
     shellmatta_doInit(  &inst,
                         &handle,
@@ -448,8 +441,8 @@ TEST_CASE( "shellmatta configure mode" ) {
     /* check with insert mode */
     shellmatta_processData(handle, (char*)"meow this is some dummy Text\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D123456\r\n", 57u);
 
-    CHECK( write_length == strlen(dummyData));
-    CHECK( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    CHECK_THAT(write_data, Catch::Matchers::Equals(dummyData));
 
     write_callCnt = 0u;
     memset(write_data, 0, sizeof(write_data));
@@ -462,8 +455,8 @@ TEST_CASE( "shellmatta configure mode" ) {
     /* check with echo disabled */
     shellmatta_processData(handle, (char*)"meow this is some dummy Text\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D\x1b[D123456\r\n", 57u);
 
-    CHECK( write_length == strlen(dummyData2));
-    REQUIRE( strcmp(dummyData2, write_data) == 0);
+    CHECK(write_length == strlen(dummyData2));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData2));
 }
 
 TEST_CASE( "shellmatta configure delimiter" ) {
@@ -508,6 +501,6 @@ TEST_CASE( "shellmatta configure delimiter" ) {
     /* check with echo disabled */
     shellmatta_processData(handle, (char*)"doSomething argument\n", 21u);
 
-    CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    CHECK(write_length == strlen(dummyData));
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }

+ 3 - 3
test/integrationtest/test_integration_busy.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -133,7 +133,7 @@ TEST_CASE( "shellmatta busy 1" ) {
     CHECK( 10u  == busyCallCnt);
     CHECK( 1u   == notBusyCallCnt );
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta busy suspend with continuous mode" ) {
@@ -179,5 +179,5 @@ TEST_CASE( "shellmatta busy suspend with continuous mode" ) {
     CHECK( 6u  == busyCallCnt);
     CHECK( 0u   == notBusyCallCnt );
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }

+ 5 - 5
test/integrationtest/test_integration_continue.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -74,7 +74,7 @@ static shellmatta_retCode_t continueCmdFct(shellmatta_handle_t handle, const cha
 
     /* -# the arguments shall stay the same on every call - data is transferred per stdin */
     CHECK(length == 28u);
-    CHECK(strcmp(arguments, "continue some arguments meow") == 0);
+    CHECK_THAT(arguments, Catch::Matchers::Equals("continue some arguments meow"));
 
     contCallCnt ++;
 
@@ -148,7 +148,7 @@ TEST_CASE( "shellmatta continue 1" ) {
     CHECK( 0u  == busyCallCnt);
     CHECK( 10u  == contCallCnt);
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta continue cancel" ) {
@@ -207,7 +207,7 @@ TEST_CASE( "shellmatta continue cancel" ) {
     CHECK( 0u  == busyCallCnt);
     CHECK( 5u  == contCallCnt);
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta continue suspend with busy mode" ) {
@@ -280,5 +280,5 @@ TEST_CASE( "shellmatta continue suspend with busy mode" ) {
     CHECK( 10u  == contCallCnt);
     CHECK( 2u  == busyCallCnt);
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }

+ 9 - 9
test/integrationtest/test_integration_help.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -97,7 +97,7 @@ SCENARIO("Test the help function")
                                                         "shellmatta->";
                 CHECK(writeFct_fake.call_count == 23);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
         }
 
@@ -122,7 +122,7 @@ SCENARIO("Test the help function")
                                                         "shellmatta->";
                 CHECK(writeFct_fake.call_count == 20);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
         }
     }
@@ -172,7 +172,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 22);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
         }
 
@@ -197,7 +197,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 19);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
         }
 
@@ -219,7 +219,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 15);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
         }
 
@@ -242,7 +242,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 15);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
         }
 
@@ -264,7 +264,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "\r\nshellmatta->";
                 CHECK(writeFct_fake.call_count == 45);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
         }
 
@@ -289,7 +289,7 @@ SCENARIO("Test if the help command prints the usage correctly")
                                                         "shellmatta->";
                 CHECK(writeFct_fake.call_count == 28);
                 CHECK(strlen(response) == fakeWriteLength);
-                CHECK(0 == strcmp(response, fakeWriteData));
+                CHECK_THAT(response, Catch::Matchers::Equals(fakeWriteData));
             }
         }
     }

+ 1 - 1
test/integrationtest/test_integration_history.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2021 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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

+ 3 - 3
test/integrationtest/test_integration_opt.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -175,7 +175,7 @@ TEST_CASE( "shellmatta option parser 1" ) {
     CHECK( lenE == 4u );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta option parser 2 - ignore \"--\"" ) {
@@ -213,6 +213,6 @@ TEST_CASE( "shellmatta option parser 2 - ignore \"--\"" ) {
     CHECK( lenE == 4u );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 

+ 3 - 3
test/integrationtest/test_integration_optLong.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 - 2021 Stefan Strobel <stefan.strobel@shimatta.net>
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
  *
  * 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
@@ -185,7 +185,7 @@ TEST_CASE( "shellmatta long option parser 1" ) {
     CHECK( lenE == 4u );
     CHECK( (cntB == 0u && cntC == 0u && cntD == 0u && cntF == 0u && cntDef == 0u) );
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }
 
 TEST_CASE( "shellmatta long option parser 2" ) {
@@ -225,5 +225,5 @@ TEST_CASE( "shellmatta long option parser 2" ) {
     CHECK( cntDef == 2u );
     CHECK( lenDef == 2u );
     CHECK( write_length == strlen(dummyData));
-    REQUIRE( strcmp(dummyData, write_data) == 0);
+    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
 }

+ 847 - 0
test/integrationtest_auth/test_integration_auth.cpp

@@ -0,0 +1,847 @@
+/*
+ * Copyright (c) 2019 - 2024 Stefan Strobel <stefan.strobel@shimatta.net>
+ *
+ * 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    test_integration_auth.cpp
+ * @brief   integration test implementation for the authentication functions
+ * @author  Stefan Strobel <stefan.strobel@shimatta.net>
+ */
+
+#include "test/framework/catch.hpp"
+extern "C" {
+#include "shellmatta.h"
+}
+#include <string.h>
+
+static uint32_t write_callCnt = 0u;
+static char write_data[1024];
+static uint32_t write_length;
+static uint32_t privateCallCount;
+static uint32_t logUserId;
+static shellmatta_auth_log_event_t logEvent;
+
+#define TEST_SHELLMATTA_SETUP   shellmatta_retCode_t ret;                   \
+                                shellmatta_instance_t inst;                 \
+                                shellmatta_handle_t handle;                 \
+                                char buffer[1024] = {0};                    \
+                                char historyBuffer[1024] = {0};             \
+                                                                            \
+                                shellmatta_doInit(  &inst,                  \
+                                                    &handle,                \
+                                                    buffer,                 \
+                                                    sizeof(buffer),         \
+                                                    historyBuffer,          \
+                                                    sizeof(historyBuffer),  \
+                                                    "shellmatta->",         \
+                                                    NULL,                   \
+                                                    writeFct);              \
+                                                                            \
+                                write_callCnt = 0u;                         \
+                                memset(write_data, 0, sizeof(write_data));  \
+                                write_length = 0u;                          \
+                                privateCallCount = 0u;                      \
+                                logUserId = 255u;                           \
+                                logEvent = SHELLMATTA_AUTH_EVENT_NONE;      \
+                                                                            \
+                                shellmatta_addCmd(handle, &publicCmd);      \
+                                shellmatta_addCmd(handle, &privateCmd);
+
+#define TEST_SHELLMATTA_AUTH_SETUP  shellmatta_auth_user_t userList[] = {                                                   \
+                                        {1, false, "shimatta", "12345678"},                                                 \
+                                        {2, false, "not_shimatta", "87654321"},                                             \
+                                        {3, true, "root", "rootpw"}                                                         \
+                                    };                                                                                      \
+                                                                                                                            \
+                                    uint32_t privateCmdPerms[] = {1};                                                       \
+                                    shellmatta_auth_perm_t permList[] = {                                                   \
+                                        {"private", privateCmdPerms, sizeof(privateCmdPerms)/sizeof(privateCmdPerms[0])},   \
+                                        {"additional", privateCmdPerms, sizeof(privateCmdPerms)/sizeof(privateCmdPerms[0])} \
+                                    };                                                                                      \
+                                                                                                                            \
+                                    shellmatta_auth_init(handle, userList, 3, permList, 2, false, NULL, NULL);
+
+static shellmatta_retCode_t writeFct(const char* data, uint32_t length)
+{
+    write_callCnt ++;
+    while((length > 0) && (write_length < sizeof(write_data)))
+    {
+        write_data[write_length] = *data;
+        data ++;
+        length --;
+        write_length ++;
+    }
+
+    return SHELLMATTA_OK;
+}
+
+static shellmatta_retCode_t publicCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    (void)      handle;
+    (void)      arguments;
+    (void)      length;
+    shellmatta_retCode_t ret = SHELLMATTA_OK;
+
+    return ret;
+}
+shellmatta_cmd_t publicCmd = {(char*)"public", (char*)"p", NULL, NULL, publicCmdFct, NULL, NULL};
+
+
+static shellmatta_retCode_t privateCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    (void)      handle;
+    (void)      arguments;
+    (void)      length;
+
+    privateCallCount ++;
+
+    return SHELLMATTA_OK;
+}
+shellmatta_cmd_t privateCmd = {(char*)"private", (char*)"r", NULL, NULL, privateCmdFct, NULL, NULL};
+
+
+static shellmatta_retCode_t additionalCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length)
+{
+    (void)      handle;
+    (void)      arguments;
+    (void)      length;
+
+    return SHELLMATTA_OK;
+}
+shellmatta_cmd_t additionalCmd = {(char*)"additional", (char*)"a", NULL, NULL, additionalCmdFct, NULL, NULL};
+
+
+SCENARIO("Check help auth uninitialized") {
+    GIVEN("An initialized shellmatta instance without initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        WHEN("The help command is called") {
+
+            ret = shellmatta_processData(handle, (char*)"help\r", 5);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The help command prints all commands - without login/logout.") {
+
+                char *dummyData =   (char*) "help\r\n"
+                                            "help     ?  help [command] - print help or usage information\r\n"
+                                            "private  r\r\n"
+                                            "public   p\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+    }
+}
+
+SCENARIO("Check auth unauthorized") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The help command is called") {
+
+            ret = shellmatta_processData(handle, (char*)"help\r", 5);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The help command prints only public commands.") {
+
+                char *dummyData =   (char*) "help\r\n"
+                                            "help    ?   help [command] - print help or usage information\r\n"
+                                            "login   li  Login command\r\n"
+                                            "logout  lo  Logout command\r\n"
+                                            "public  p\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+
+        WHEN("A private function is called") {
+
+            ret = shellmatta_processData(handle, (char*)"private\r", 8);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The command is not executed.") {
+
+                char *dummyData =   (char*) "private\r\n"
+                                            "\r\n"
+                                            "Command: private not found\r\n"
+                                            "shellmatta->";
+
+                CHECK(privateCallCount == 0u);
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+
+        WHEN("Autocomplete is triggered") {
+
+            ret = shellmatta_processData(handle, (char*)"\t\t", 2);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("Only public commands are shown.") {
+
+                char *dummyData =   (char*) "\r\n"
+                                            "help    ?    login    li    logout    lo    public    p    \r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+    }
+}
+
+SCENARIO("Check authorized") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The user shimatta is logged in") {
+
+            ret = shellmatta_auth_login(handle, 1);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The help command is called") {
+
+                ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The help command prints all commands.") {
+
+                    char *dummyData =   (char*) "help\r\n"
+                                                "help     ?   help [command] - print help or usage information\r\n"
+                                                "login    li  Login command\r\n"
+                                                "logout   lo  Logout command\r\n"
+                                                "private  r\r\n"
+                                                "public   p\r\n"
+                                                "\r\n"
+                                                "shimatta@shellmatta->";
+
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                    AND_THEN("The shimatta user is logged in") {
+                        char usernameBuffer[16] = {0};
+                        uint32_t usernameBufferLength = sizeof(usernameBuffer);
+
+                        CHECK(1 == shellmatta_auth_getLoggedInUserId(handle));
+                        ret = shellmatta_auth_getLoggedInUserName(handle, usernameBuffer, &usernameBufferLength);
+                        CHECK(ret == SHELLMATTA_OK);
+                        CHECK(usernameBufferLength == strlen("shimatta"));
+                        REQUIRE_THAT(usernameBuffer, Catch::Matchers::Equals("shimatta"));
+                    }
+                }
+
+                AND_WHEN("The user is logged out using the logout command") {
+                    write_length = 0;
+                    memset(write_data, 0, sizeof(write_data));
+                    ret = shellmatta_processData(handle, (char*)"logout\r", 7);
+                    CHECK(ret == SHELLMATTA_OK);
+
+                    THEN("The Shellmatta prints the logout message") {
+                        char *dummyData =   (char*) "logout\r\n"
+                                                    "good bye\r\n\r\n"
+                                                    "shellmatta->";
+                        CHECK(write_length == strlen(dummyData));
+                        REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                    }
+
+                    AND_WHEN("The help command is called") {
+
+                        write_length = 0;
+                        memset(write_data, 0, sizeof(write_data));
+                        ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        THEN("The help command prints only public commands.") {
+
+                            char *dummyData =   (char*) "help\r\n"
+                                                        "help    ?   help [command] - print help or usage information\r\n"
+                                                        "login   li  Login command\r\n"
+                                                        "logout  lo  Logout command\r\n"
+                                                        "public  p\r\n"
+                                                        "\r\n"
+                                                        "shellmatta->";
+
+                            CHECK(write_length == strlen(dummyData));
+                            REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                        }
+                    }
+                }
+            }
+            AND_WHEN("A private function is called") {
+
+                ret = shellmatta_processData(handle, (char*)"private\r", 8);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The command executed.") {
+
+                    char *dummyData =   (char*) "private\r\n"
+                                                "\r\n"
+                                                "shimatta@shellmatta->";
+
+                    CHECK(privateCallCount == 1u);
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                }
+            }
+        }
+        WHEN("The user not_shellmatta is logged in") {
+
+            ret = shellmatta_auth_login(handle, 2);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The help command is called") {
+
+                ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The help command prints not all commands.") {
+
+                    char *dummyData =   (char*) "help\r\n"
+                                                "help    ?   help [command] - print help or usage information\r\n"
+                                                "login   li  Login command\r\n"
+                                                "logout  lo  Logout command\r\n"
+                                                "public  p\r\n"
+                                                "\r\n"
+                                                "not_shimatta@shellmatta->";
+
+                    CHECK(ret == SHELLMATTA_OK);
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                    AND_THEN("The not_shimatta user is logged in") {
+                        char usernameBuffer[16] = {0};
+                        uint32_t usernameBufferLength = sizeof(usernameBuffer);
+
+                        CHECK(2 == shellmatta_auth_getLoggedInUserId(handle));
+                        ret = shellmatta_auth_getLoggedInUserName(handle, usernameBuffer, &usernameBufferLength);
+                        CHECK(ret == SHELLMATTA_OK);
+                        CHECK(usernameBufferLength == strlen("not_shimatta"));
+                        REQUIRE_THAT(usernameBuffer, Catch::Matchers::Equals("not_shimatta"));
+                    }
+                }
+            }
+            AND_WHEN("A private function is called") {
+
+                ret = shellmatta_processData(handle, (char*)"private\r", 8);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The command is not executed.") {
+
+                    char *dummyData =   (char*) "private\r\n"
+                                                "\r\n"
+                                                "Command: private not found\r\n"
+                                                "not_shimatta@shellmatta->";
+
+                    CHECK(privateCallCount == 0u);
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                }
+            }
+        }
+        WHEN("The user root is logged in") {
+            ret = shellmatta_auth_login(handle, 3);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The help command is called") {
+
+                ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The help command prints all commands.") {
+
+                    char *dummyData =   (char*) "help\r\n"
+                                                "help     ?   help [command] - print help or usage information\r\n"
+                                                "login    li  Login command\r\n"
+                                                "logout   lo  Logout command\r\n"
+                                                "private  r\r\n"
+                                                "public   p\r\n"
+                                                "\r\n"
+                                                "root@shellmatta->";
+
+                    CHECK(ret == SHELLMATTA_OK);
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Check login command with privileged user") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The user shimatta logs in interactively using the correct credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r\n"
+                                                        "shimatta\r\n"
+                                                        "12345678\r", 26);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in interactively using wrong credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r\n"
+                                                        "shimatta\r\n"
+                                                        "11111111\r", 26);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("A warning message is printed") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "username or password is wrong\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in interactively manipulating the input") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r"
+                                                        "shimg\batta\r"
+                                                        "1h" "\x7f" "2346\033[D5\033[C78\b8\r", 36);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimg\033[1D\033[K\033[s\033[uatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in passing the credentials none interactively") {
+
+            ret = shellmatta_processData(handle, (char*)"login -u shimatta -p 12345678\r", 30);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed") {
+                char *dummyData =   (char*) "login -u shimatta -p 12345678\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in passing the credentials half interactively") {
+
+            ret = shellmatta_processData(handle, (char*)"login -u shimatta\r12345678\r", 27);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login -u shimatta\r\n\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta tries to login non interactively without username") {
+
+            ret = shellmatta_processData(handle, (char*)"login -p 12345678\r", 18);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("A warning message is printed") {
+                char *dummyData =   (char*) "login -p 12345678\r\n"
+                                            "Missing username\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+
+        WHEN("The user shimatta tries to login using the wrong options") {
+
+            ret = shellmatta_processData(handle, (char*)"login -o meow\r", 14);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("A warning message is printed") {
+                char *dummyData =   (char*) "login -o meow\r\n"
+                                            "Unknown option\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Check login command with unprivileged user") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The user not_shimatta logs in interactively using the correct credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r"
+                                                        "not_shimatta\r"
+                                                        "87654321\r", 28);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "not_shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "not_shimatta@shellmatta->";
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The not_shimatta user is logged in") {
+                    REQUIRE(2 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Check adding commands after the authentication is initialized") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("A command is added") {
+
+            ret = shellmatta_addCmd(handle, &additionalCmd);
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The help command is called") {
+
+                ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The additional command is not shown as it requires login") {
+                    char *dummyData =   (char*) "help\r\n"
+                                                "help    ?   help [command] - print help or usage information\r\n"
+                                                "login   li  Login command\r\n"
+                                                "logout  lo  Logout command\r\n"
+                                                "public  p\r\n"
+                                                "\r\n"
+                                                "shellmatta->";
+                    CHECK(write_length == strlen(dummyData));
+                    REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                    AND_WHEN("The root user is logged in") {
+                        write_length = 0;
+                        memset(write_data, 0, sizeof(write_data));
+
+                        ret = shellmatta_auth_login(handle, 3);
+                        CHECK(ret == SHELLMATTA_OK);
+
+                        AND_WHEN("The help command is called") {
+
+                            ret = shellmatta_processData(handle, (char*)"help\r", 5);
+                            CHECK(ret == SHELLMATTA_OK);
+
+                            THEN("all commands are shown including the additional command") {
+                                char *dummyData =   (char*) "help\r\n"
+                                                            "additional  a\r\n"
+                                                            "help        ?   help [command] - print help or usage information\r\n"
+                                                            "login       li  Login command\r\n"
+                                                            "logout      lo  Logout command\r\n"
+                                                            "private     r\r\n"
+                                                            "public      p\r\n"
+                                                            "\r\n"
+                                                            "root@shellmatta->";
+                                CHECK(write_length == strlen(dummyData));
+                                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+shellmatta_retCode_t customLogin(const uint32_t userId, const char *password)
+{
+    if ((userId == 1) && (0 == strcmp(password, "123456789")))
+    {
+        return SHELLMATTA_OK;
+    }
+    else if ((userId == 2) && (0 == strcmp(password, "876543210")))
+    {
+        return SHELLMATTA_OK;
+    }
+
+    return SHELLMATTA_ERROR;
+}
+
+void logFct(const uint32_t userId, shellmatta_auth_log_event_t event)
+{
+    logUserId = userId;
+    logEvent = event;
+}
+
+SCENARIO("Check custom login") {
+    GIVEN("An initialized shellmatta instance with initialized auth with custom login and log") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        shellmatta_auth_user_t userList[] = {
+            {1, false, "shimatta", NULL},
+            {2, false, "not_shimatta", NULL}
+        };
+
+        uint32_t privateCmdPerms[] = {1};
+        shellmatta_auth_perm_t permList[] = {
+            {"private", privateCmdPerms, sizeof(privateCmdPerms)/sizeof(privateCmdPerms[0])}
+        };
+
+        shellmatta_auth_init(handle, userList, 2, permList, 1, false, customLogin, logFct);
+
+        WHEN("The user shimatta logs in interactively using the correct credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r"
+                                                        "shimatta\r"
+                                                        "123456789\r", 25);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("The login message is printed - password is hidden") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "login successful\r\n"
+                                            "\r\n"
+                                            "shimatta@shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+
+                    AND_WHEN("The shimatta user logs out") {
+                        ret = shellmatta_processData(handle, (char*)"logout\r", 7);
+                        CHECK(ret == SHELLMATTA_OK);
+                        THEN("The logout event is logged") {
+                            CHECK(1 == logUserId);
+                            REQUIRE(SHELLMATTA_AUTH_EVENT_LOGOUT == logEvent);
+                        }
+                    }
+
+                }
+
+                AND_THEN("The login event is logged") {
+                    CHECK(1 == logUserId);
+                    REQUIRE(SHELLMATTA_AUTH_EVENT_LOGIN == logEvent);
+                }
+            }
+        }
+
+        WHEN("The user shimatta logs in interactively using the wrong credentials") {
+
+            ret = shellmatta_processData(handle, (char*)"login\r"
+                                                        "shimatta\r"
+                                                        "12345678\r", 24);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("Login error message is printed") {
+                char *dummyData =   (char*) "login\r\n"
+                                            "enter username:\r\n"
+                                            "shimatta\r\n"
+                                            "enter password:\r\n"
+                                            "\r\n"
+                                            "username or password is wrong\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+
+                AND_THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+
+                AND_THEN("The failed login event is logged") {
+                    CHECK(1 == logUserId);
+                    REQUIRE(SHELLMATTA_AUTH_EVENT_LOGIN_FAILED == logEvent);
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Check custom login with custom login function") {
+    GIVEN("An initialized shellmatta instance with initialized auth with custom login and log") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        shellmatta_auth_user_t userList[] = {
+            {1, false, "shimatta", NULL},
+            {2, false, "not_shimatta", NULL}
+        };
+
+        uint32_t privateCmdPerms[] = {1};
+        shellmatta_auth_perm_t permList[] = {
+            {"private", privateCmdPerms, sizeof(privateCmdPerms)/sizeof(privateCmdPerms[0])}
+        };
+
+        shellmatta_auth_init(handle, userList, 2, permList, 1, true, customLogin, logFct);
+
+        WHEN("The help command is called") {
+
+            ret = shellmatta_processData(handle, (char*)"help\r", 5);
+            CHECK(ret == SHELLMATTA_OK);
+
+            THEN("There is no login command") {
+                char *dummyData =   (char*) "help\r\n"
+                                            "help    ?   help [command] - print help or usage information\r\n"
+                                            "logout  lo  Logout command\r\n"
+                                            "public  p\r\n"
+                                            "\r\n"
+                                            "shellmatta->";
+
+                CHECK(write_length == strlen(dummyData));
+                REQUIRE_THAT(write_data, Catch::Matchers::Equals(dummyData));
+            }
+        }
+    }
+}
+
+SCENARIO("Check if passwords can be changed") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("The password of the shellmatta user is changed") {
+
+            ret = shellmatta_auth_chpasswd(handle, "shimatta", "new_password");
+            CHECK(ret == SHELLMATTA_OK);
+
+            AND_WHEN("The user shimatta logs in passing the new credentials") {
+
+                ret = shellmatta_processData(handle, (char*)"login -u shimatta -p new_password\r", 34);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The shimatta user is logged in") {
+                    REQUIRE(1 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+
+            }
+
+            AND_WHEN("The user shimatta logs in passing the old credentials") {
+
+                ret = shellmatta_processData(handle, (char*)"login -u shimatta -p 12345678\r", 30);
+                CHECK(ret == SHELLMATTA_OK);
+
+                THEN("The shimatta user is not logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+    }
+}
+
+SCENARIO("Use functions with wrong parameters") {
+    GIVEN("An initialized shellmatta instance with initialized auth") {
+
+        TEST_SHELLMATTA_SETUP;
+
+        TEST_SHELLMATTA_AUTH_SETUP;
+
+        WHEN("Trying to log in user 0") {
+
+            ret = shellmatta_auth_login(handle, 0);
+
+            THEN("Shellmatta returns SHELLMATTA_ERROR") {
+                CHECK(ret == SHELLMATTA_ERROR);
+                AND_THEN("No user is logged in") {
+                    REQUIRE(0 == shellmatta_auth_getLoggedInUserId(handle));
+                }
+            }
+        }
+    }
+}

+ 11 - 0
test/integrationtest_auth/test_main.cpp

@@ -0,0 +1,11 @@
+// 010-TestCase.cpp
+
+// Let Catch provide main():
+#define CATCH_CONFIG_MAIN
+
+#include "test/framework/catch.hpp"
+extern "C" {
+#include "test/framework/fff.h"
+DEFINE_FFF_GLOBALS
+}
+

+ 15 - 15
test/unittest/shellmatta_utils/test_utils_shellItoa.cpp

@@ -2,51 +2,51 @@
 #include "src/shellmatta_utils.c"
 #include <string.h>
 
-TEST_CASE( "shellmatta_utils.c - itoa - 123456 base 10" ) {
+TEST_CASE( "shellmatta_utils.c - itoa - 123456 base 10") {
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(123456, buffer, 10) == 6 );
-    REQUIRE( strcmp(buffer, "123456") == 0);
+    CHECK(utils_shellItoa(123456, buffer, 10) == 6);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("123456"));
 }
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x0ABBCCDD base 16") {
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x0ABBCCDD, buffer, 16) == 7 );
-    REQUIRE( strcmp(buffer, "ABBCCDD") == 0);
+    CHECK(utils_shellItoa(0x0ABBCCDD, buffer, 16) == 7);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("ABBCCDD"));
 }
 
 TEST_CASE( "shellmatta_utils.c - itoa - -574236 base 10") {
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(-574236, buffer, 10) == 7 );
-    REQUIRE( strcmp(buffer, "-574236") == 0);
+    CHECK(utils_shellItoa(-574236, buffer, 10) == 7);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("-574236"));
 }
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x80000000 base 2") {
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x80000000, buffer, 2) == 33 );
-    REQUIRE( strcmp(buffer, "-10000000000000000000000000000000") == 0);
+    CHECK(utils_shellItoa(0x80000000, buffer, 2) == 33);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("-10000000000000000000000000000000"));
 }
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 2") {
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x7FFFFFFF, buffer, 2) == 31 );
-    REQUIRE( strcmp(buffer, "1111111111111111111111111111111") == 0);
+    CHECK(utils_shellItoa(0x7FFFFFFF, buffer, 2) == 31);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("1111111111111111111111111111111"));
 }
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 1 - wrong base") {
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x7FFFFFFF, buffer, 1) == 0 );
-    REQUIRE( strcmp(buffer, "\0") == 0);
+    CHECK(utils_shellItoa(0x7FFFFFFF, buffer, 1) == 0);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("\0"));
 }
 
 TEST_CASE( "shellmatta_utils.c - itoa - 0x7FFFFFFF base 17 - wrong base") {
     char buffer[64];
     memset(buffer, 0, sizeof(buffer));
-    CHECK( utils_shellItoa(0x7FFFFFFF, buffer, 17) == 0 );
-    REQUIRE( strcmp(buffer, "\0") == 0);
+    CHECK(utils_shellItoa(0x7FFFFFFF, buffer, 17) == 0);
+    REQUIRE_THAT(buffer, Catch::Matchers::Equals("\0"));
 }

+ 2 - 2
test/unittest/shellmatta_utils/test_utils_writeEcho.cpp

@@ -17,7 +17,7 @@ static shellmatta_retCode_t writeFct(const char* data, uint32_t length)
 TEST_CASE( "shellmatta_writeEcho echo enabled" ) {
 
     shellmatta_instance_t inst;
-    char buffer[20];
+    char buffer[20] = {0};
     char dummyData[29] = "asd";
 
     inst.buffer = buffer;
@@ -42,7 +42,7 @@ TEST_CASE( "shellmatta_writeEcho echo enabled" ) {
 TEST_CASE( "shellmatta_writeEcho echo disabled" ) {
 
     shellmatta_instance_t inst;
-    char buffer[20];
+    char buffer[20] = {0};
     char dummyData[29] = "asd";
 
     inst.buffer = buffer;