From 5e83d10cfcdd5267f437943e93bc2e64f7f53764 Mon Sep 17 00:00:00 2001
From: Thoralf Czichy <thoralf.czichy@nokia.com>
Date: Mon, 24 Oct 2016 15:43:17 +0300
Subject: [PATCH] syslog multiline support

This patch expands on the previous SYSLOG_HISTORY implementation. It hooks into the bash history support to grab the correct command line to be logged. The actual logging only happens in the DEBUG trap (trap.c). This now supports logging of multi-line commands into a single syslog entry and only when the multi-line command is completed, e.g., CTRL-C in the middle of enterring a multi-line command will not lead to a syslog entry. Also this does now log the command again if it is exactly invoked as before. Syslog logging can be disabled by the user, for example, by disabling history logging via "set +o history".
---
 bashhist.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++----------------
 bashline.c | 11 +++++++++-
 trap.c     | 40 ++++++++++++++++++++++++++++++++++
 3 files changed, 105 insertions(+), 19 deletions(-)

diff --git a/bashhist.c b/bashhist.c
index 9979f99..736c246 100644
--- a/bashhist.c
+++ b/bashhist.c
@@ -38,10 +38,6 @@
 
 #include "bashintl.h"
 
-#if defined (SYSLOG_HISTORY)
-#  include <syslog.h>
-#endif
-
 #include "shell.h"
 #include "flags.h"
 #include "input.h"
@@ -742,11 +738,28 @@ check_add_history (line, force)
 	bash_add_history (line);
       return 1;
     }
+
+#if defined (SYSLOG_HISTORY)
+  /* log also in this case in order to get log again if same command is ran
+     again and again. We also come here only if bash_add_history() above
+     was not called. That's good as in all other cases bash_add_history()
+     adds the log line */
+  bash_syslog_history(line);
+#endif
+
   return 0;
 }
 
 #if defined (SYSLOG_HISTORY)
+
+/* always keep same value as in trap.c */
 #define SYSLOG_MAXLEN 600
+#define SYSLOG_MAXLEN_EXCL_TRUNCATED_TAG SYSLOG_MAXLEN-strlen(" (TRUNCATED)")
+
+/* Contains latest command (being edited) if global_last_full_line_valid */
+/* is non-zero */
+char global_last_full_line[SYSLOG_MAXLEN];
+unsigned int global_last_full_line_valid=0;
 
 extern char *shell_name;
 
@@ -758,24 +771,40 @@ void
 bash_syslog_history (line)
      const char *line;
 {
-  char trunc[SYSLOG_MAXLEN];
-  static int first = 1;
 
-  if (first)
+  size_t line_length=strnlen(line, SYSLOG_MAXLEN);
+  size_t old_line_length=0;
+  size_t line_length_to_copy=0;
+
+  if (0==global_last_full_line_valid)
     {
-      openlog (shell_name, OPENLOG_OPTS, SYSLOG_FACILITY);
-      first = 0;
+      /* create first part of possible multi-line entry */
+      strncpy(global_last_full_line, line,
+        (SYSLOG_MAXLEN==line_length)?line_length:line_length+1);
+      if (SYSLOG_MAXLEN==line_length) /* truncate last character */
+        global_last_full_line[SYSLOG_MAXLEN-1]='\0';
+      global_last_full_line_valid=1;
     }
-
-  if (strlen(line) < SYSLOG_MAXLEN)
-    syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d UID=%d %s", getpid(), current_user.uid, line);
   else
     {
-      strncpy (trunc, line, SYSLOG_MAXLEN);
-      trunc[SYSLOG_MAXLEN - 1] = '\0';
-      syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d UID=%d %s", getpid(), current_user.uid, trunc);
+      /* this is a multi-line entry. Append */
+      old_line_length=strnlen(global_last_full_line, SYSLOG_MAXLEN);
+      line_length_to_copy=line_length;
+      if (old_line_length+line_length>SYSLOG_MAXLEN-1)
+        line_length_to_copy=SYSLOG_MAXLEN-old_line_length-1;
+      strncpy(global_last_full_line+old_line_length, line, line_length_to_copy);
+      global_last_full_line[line_length_to_copy+old_line_length]='\0';
     }
+
+  if (SYSLOG_MAXLEN-1<old_line_length+line_length)
+    {
+      strcpy(global_last_full_line+SYSLOG_MAXLEN_EXCL_TRUNCATED_TAG-1,
+        " (TRUNCATED)"); /*strcpy guarantees final /0 byte */
+      global_last_full_line_valid=1;
+    }
+
 }
+
 #endif
      	
 /* Add a line to the history list.
@@ -838,6 +867,13 @@ bash_add_history (line)
 	  sprintf (new_line, "%s%s%s", current->line, chars_to_add, line);
 	  offset = where_history ();
 	  old = replace_history_entry (offset, new_line, current->data);
+#if defined (SYSLOG_HISTORY)
+          /* global_last_full_line_valid=0 - this causes bash_syslog_history()
+             to replace and not append */
+          global_last_full_line_valid=0;
+          bash_syslog_history (new_line);
+#endif
+
 	  free (new_line);
 
 	  if (old)
@@ -848,11 +884,12 @@ bash_add_history (line)
     }
 
   if (add_it)
-    really_add_history (line);
-
+    {
+      really_add_history (line);
 #if defined (SYSLOG_HISTORY)
-  bash_syslog_history (line);
+      bash_syslog_history (line);
 #endif
+    }
 
   using_history ();
 }
diff --git a/bashline.c b/bashline.c
index f4fe9f1..852465f 100644
--- a/bashline.c
+++ b/bashline.c
@@ -84,6 +84,10 @@
 #  define VI_EDITING_MODE	 0
 #endif
 
+#if defined (SYSLOG_HISTORY)
+  extern unsigned int global_last_full_line_valid;
+#endif
+
 #define RL_BOOLEAN_VARIABLE_VALUE(s)	((s)[0] == 'o' && (s)[1] == 'n' && (s)[2] == '\0')
 
 #if defined (BRACE_COMPLETION)
@@ -4331,7 +4335,12 @@ bash_event_hook ()
      calling run_interrupt_trap along the way.  The check for sigalrm_seen is
      to clean up the read builtin's state. */
   if (terminating_signal || interrupt_state || sigalrm_seen)
-    rl_cleanup_after_signal ();
+    {
+#if defined (SYSLOG_HISTORY)
+      global_last_full_line_valid=0;
+#endif
+      rl_cleanup_after_signal ();
+    }
   bashline_reset_event_hook ();
   check_signals_and_traps ();	/* XXX */
   return 0;
diff --git a/trap.c b/trap.c
index eb8ecf3..fce904f 100644
--- a/trap.c
+++ b/trap.c
@@ -51,6 +51,10 @@
 #  include "bashline.h"
 #endif
 
+#if defined (SYSLOG_HISTORY)
+#  include <syslog.h>
+#endif
+
 #ifndef errno
 extern int errno;
 #endif
@@ -128,6 +132,20 @@ int wait_signal_received;
 
 int trapped_signal_received;
 
+#if defined (SYSLOG_HISTORY)
+/* always keep same value as in bashhist.c*/
+#define SYSLOG_MAXLEN 600
+
+extern char global_last_full_line[SYSLOG_MAXLEN];
+extern unsigned int global_last_full_line_valid;
+extern char *shell_name;
+
+#ifndef OPENLOG_OPTS
+#define OPENLOG_OPTS 0
+#endif
+
+#endif
+
 #define GETORIGSIG(sig) \
   do { \
     original_signals[sig] = (SigHandler *)set_signal_handler (sig, SIG_DFL); \
@@ -1064,6 +1082,28 @@ run_debug_trap ()
   pid_t save_pgrp;
   int save_pipe[2];
 
+#if defined (SYSLOG_HISTORY)
+
+  if (global_last_full_line_valid)
+    {
+       static int first = 1;
+
+       if (first)
+         {
+           openlog (shell_name, OPENLOG_OPTS, SYSLOG_FACILITY);
+           first = 0;
+         }
+
+       /* no need to call get_current_user_info() as we only use uid */
+       syslog(SYSLOG_FACILITY|SYSLOG_LEVEL,
+         "HISTORY: PID=%d UID=%d %s",
+         getpid(), current_user.uid, global_last_full_line);
+       global_last_full_line_valid=0;
+       memset(global_last_full_line, '\0', SYSLOG_MAXLEN);
+    }
+
+#endif
+
   /* XXX - question:  should the DEBUG trap inherit the RETURN trap? */
   trap_exit_value = 0;
   if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && ((sigmodes[DEBUG_TRAP] & SIG_IGNORED) == 0) && ((sigmodes[DEBUG_TRAP] & SIG_INPROGRESS) == 0))
-- 
2.7.4

