From: Alessandro Carminati <alessandro.carmin...@gmail.com>

This commit adds functionality to the ash shell that sends each executed command
to a remote logging server over TCP, enabling remote auditing and session 
tracking.

The design is inspired by the tacacs2 approach used in network devices. This is
particularly useful in embedded Linux environments replacing traditional 
routers,
where audit trails are essential.

Unlike bash, ash does not support PROMPT_COMMAND. This implementation fills that
gap using internal hooks in the shell.

The feature is controlled via three environment variables:

  - SESSIONID_ : unique identifier for the shell session
  - LOG_RHOST  : remote log server hostname or IP address
  - LOG_RPORT  : remote log server TCP port

When these variables are set, each command entered is sent to the specified
logging server, prepended with the session ID.

This enhancement is lightweight and optional, and does not impact users who
do not configure the environment variables

Signed-off-by: Alessandro Carminati <acarm...@redhat.com>
---
 include/libbb.h       |   7 +++
 libbb/Config.src      |  10 ++++
 libbb/Kbuild.src      |   1 +
 libbb/lineedit.c      |   3 ++
 libbb/loggers_utils.c | 117 ++++++++++++++++++++++++++++++++++++++++++
 shell/ash.c           |   3 ++
 6 files changed, 141 insertions(+)
 create mode 100644 libbb/loggers_utils.c

diff --git a/include/libbb.h b/include/libbb.h
index 558f3c627..c3ade877a 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1192,6 +1192,9 @@ void die_if_bad_username(const char* name) FAST_FUNC;
 #else
 #define die_if_bad_username(name) ((void)(name))
 #endif
+# if ENABLE_FEATURE_SEND_COMMAND_REMOTE
+void loggers_utils_set_var_lookup(void *func);
+# endif
 /*
  * Returns (-1) terminated malloced result of getgroups().
  * Reallocs group_array (useful for repeated calls).
@@ -2147,6 +2150,10 @@ enum {
        PSSCAN_RUIDGID  = (1 << 21) * ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS,
        PSSCAN_TASKS    = (1 << 22) * ENABLE_FEATURE_SHOW_THREADS,
 };
+# if ENABLE_FEATURE_SEND_COMMAND_REMOTE
+int rlog_this(const char *history_itm);
+# endif
+
 //procps_status_t* alloc_procps_scan(void) FAST_FUNC;
 void free_procps_scan(procps_status_t* sp) FAST_FUNC;
 procps_status_t* procps_scan(procps_status_t* sp, int flags) FAST_FUNC;
diff --git a/libbb/Config.src b/libbb/Config.src
index b980f19a9..a6f58824d 100644
--- a/libbb/Config.src
+++ b/libbb/Config.src
@@ -202,6 +202,16 @@ config FEATURE_EDITING_SAVE_ON_EXIT
        help
        Save history on shell exit, not after every command.
 
+config  FEATURE_SEND_COMMAND_REMOTE
+       bool "Send last command to remote logger for audit"
+       default n
+       depends on FEATURE_EDITING_SAVEHISTORY
+       help
+       Send last command to remote logger for audit.
+       It is mandatory that LOG_RHOST and LOG_RPORT environment variables
+       are defined to specify the remote ip and port where send logs.
+       It alse needs the environment SESSIONID_ to be defined as sessionid.
+
 config FEATURE_REVERSE_SEARCH
        bool "Reverse history search"
        default y
diff --git a/libbb/Kbuild.src b/libbb/Kbuild.src
index cb8d2c2ec..096a9f3e8 100644
--- a/libbb/Kbuild.src
+++ b/libbb/Kbuild.src
@@ -208,3 +208,4 @@ lib-$(CONFIG_FEATURE_CUT_REGEX) += xregcomp.o
 
 # Add the experimental logging functionality, only used by zcip
 lib-$(CONFIG_ZCIP) += logenv.o
+lib-$(CONFIG_FEATURE_SEND_COMMAND_REMOTE) += loggers_utils.o
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index 151208c1c..6eeca52ae 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -1685,6 +1685,9 @@ static void remember_in_history(char *str)
        /* i <= state->max_history-1 */
        state->history[i++] = xstrdup(str);
        /* i <= state->max_history */
+# if ENABLE_FEATURE_SEND_COMMAND_REMOTE
+       rlog_this(state->history[i-1]);
+# endif
        state->cur_history = i;
        state->cnt_history = i;
 # if ENABLE_FEATURE_EDITING_SAVEHISTORY && !ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
diff --git a/libbb/loggers_utils.c b/libbb/loggers_utils.c
new file mode 100644
index 000000000..9a7744f12
--- /dev/null
+++ b/libbb/loggers_utils.c
@@ -0,0 +1,117 @@
+/*
+ * Add shell audit logging to remote server.
+ *
+ * This feature allows the shell to send audit logs to a recipient server for
+ * centralized logging and auditing purposes.
+ *
+ * Copyright (c) 2025 Alessandro Carminati <acarm...@redhat.com>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#define SESSION_ID_ENV "SESSIONID_"
+#define SESSION_LEN 9
+#define SESSION_RHOST "LOG_RHOST"
+#define SESSION_RPORT "LOG_RPORT"
+
+typedef const char* (*loggers_utils_var_lookup_t)(const char *name);
+
+void get_timestamp(char *, size_t);
+int send_log(const char *, const char *, const char *);
+int rlog_this(const char *);
+void loggers_utils_set_var_lookup(void *func);
+
+static loggers_utils_var_lookup_t loggers_utils_lookup_var;
+
+void loggers_utils_set_var_lookup(void *func) {
+       loggers_utils_lookup_var = (loggers_utils_var_lookup_t) func;
+}
+
+void get_timestamp(char *buf, size_t len) {
+       time_t now = time(NULL);
+       struct tm *tm_info = localtime(&now);
+       strftime(buf, len, "%Y%m%d.%H%M%S", tm_info);
+}
+
+int send_log(const char *line, const char *host, const char *port_str) {
+       int sockfd;
+       struct addrinfo hints, *res, *p;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = SOCK_STREAM;
+
+       if (getaddrinfo(host, port_str, &hints, &res) != 0) {
+               fprintf(stderr, "send_log: cant' resolve host in %s\n",
+                       SESSION_RHOST);
+               return -1;
+       }
+
+       for (p = res; p != NULL; p = p->ai_next) {
+               sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
+               if (sockfd < 0) continue;
+               if (connect(sockfd, p->ai_addr, p->ai_addrlen) == 0) break;
+               close(sockfd);
+       }
+
+       if (p == NULL) {
+               fprintf(stderr, "send_log: Unable to connect to %s:%s\n",
+                       host, port_str);
+               freeaddrinfo(res);
+               return -1;
+       }
+
+       ssize_t len = strlen(line);
+       if (send(sockfd, line, len, 0) != len) {
+               fprintf(stderr, "send_log: Unable to send data\n");
+               close(sockfd);
+               freeaddrinfo(res);
+               return -1;
+       }
+
+       close(sockfd);
+       freeaddrinfo(res);
+       return 0;
+}
+
+int rlog_this(const char *history_itm) {
+       char timestamp[32], hostname[64];
+       char *sess_id, *r_ip, *r_port;
+       char logline[1500];
+
+       if (!loggers_utils_lookup_var) return -1;
+
+       sess_id = loggers_utils_lookup_var(SESSION_ID_ENV);
+       if (!sess_id || (strlen(sess_id) > 9)) return 1;
+
+       r_ip = loggers_utils_lookup_var(SESSION_RHOST);
+       if (!r_ip) return -1;
+
+       r_port = loggers_utils_lookup_var(SESSION_RPORT);
+       if (!r_port) return -1;
+
+       if (!atoi(r_port)) return -1;
+
+       get_timestamp(timestamp, sizeof(timestamp));
+       gethostname(hostname, sizeof(hostname));
+
+       snprintf(logline, sizeof(logline), "%s - %s - %s > %s\n",
+                        timestamp, sess_id, hostname, history_itm);
+
+       if (send_log(logline, r_ip, r_port)!=0){
+               fprintf(stderr, "rlog_this: can't send log to remote.\n");
+               return -2;
+       };
+
+       return 0;
+}
diff --git a/shell/ash.c b/shell/ash.c
index 9173b8608..0d8cbc140 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -9787,6 +9787,9 @@ setinteractive(int on)
                        did_banner = 1;
                }
 #endif
+# if ENABLE_FEATURE_SEND_COMMAND_REMOTE
+               loggers_utils_set_var_lookup(&lookupvar);
+# endif
 #if ENABLE_FEATURE_EDITING
                if (!line_input_state) {
                        line_input_state = new_line_input_t(FOR_SHELL | 
WITH_PATH_LOOKUP);
-- 
2.34.1

_______________________________________________
busybox mailing list
busybox@busybox.net
https://lists.busybox.net/mailman/listinfo/busybox

Reply via email to