#!/usr/bin/env bash # OpsLogger - Complete logging solution for red team operations # Version 2.5.3 - Merged config and prompt fixes # Configuration variables CONFIG_FILE="${HOME}/.ops-logger.conf" DEFAULT_TARGET="target-$(hostname | tr '.' '-')" DEFAULT_LOG_DIR="${HOME}/OperationLogs" PROMPT_NEW_SHELLS=true RECORD_INTERVAL=0.5 DEBUG=false # File markers LOG_MARKER="/tmp/ops-logger-active" RECORDING_MARKER="/tmp/ops-logger-recording" CONFIG_IN_PROGRESS="/tmp/ops-logger-configuring" VERBOSE_CMD_MARKER="/tmp/ops-logger-cmd" # Helper functions log_debug() { if [[ "$DEBUG" == "true" ]]; then local debug_dir="${LOG_DIR:-${DEFAULT_LOG_DIR}}" mkdir -p "$debug_dir" 2>/dev/null echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: $*" >> "${debug_dir}/ops-logger-debug.log" fi } ensure_dir() { mkdir -p "$1" 2>/dev/null || return 1 return 0 } load_config() { if [[ -f "$CONFIG_FILE" ]]; then source "$CONFIG_FILE" else TARGET_NAME="$DEFAULT_TARGET" LOG_DIR="$DEFAULT_LOG_DIR" fi TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}" LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}" } save_config() { ensure_dir "$(dirname "$CONFIG_FILE")" cat > "$CONFIG_FILE" << EOF # Ops Logger Configuration TARGET_NAME="$TARGET_NAME" LOG_DIR="$LOG_DIR" PROMPT_NEW_SHELLS=$PROMPT_NEW_SHELLS RECORD_INTERVAL=$RECORD_INTERVAL DEBUG=$DEBUG EOF } # Get public IP get_public_ip() { local ip ip=$(curl -s --connect-timeout 3 ifconfig.me) || \ ip=$(curl -s --connect-timeout 3 ipinfo.io/ip) || \ ip=$(curl -s --connect-timeout 3 icanhazip.com) || \ ip="unknown" echo "$ip" } # Detection functions is_tmux() { [[ -n "$TMUX" ]]; } # Get normalized pane ID (session-window-pane format) get_pane_id() { if is_tmux; then # If TMUX_PANE is set (from hook), use that specific pane if [[ -n "$TMUX_PANE" ]]; then local session=$(tmux display -t "$TMUX_PANE" -p '#{session_name}') local window=$(tmux display -t "$TMUX_PANE" -p '#{window_index}') local pane=$(tmux display -t "$TMUX_PANE" -p '#{pane_index}') echo "${session}-${window}-${pane}" else local session=$(tmux display -p '#{session_name}') local window=$(tmux display -p '#{window_index}') local pane=$(tmux display -p '#{pane_index}') echo "${session}-${window}-${pane}" fi else echo "$$" fi } # Get tmux pane reference for tmux commands (% format for tmux commands) get_tmux_pane_ref() { if is_tmux; then # If TMUX_PANE is set (from hook), use that specific pane if [[ -n "$TMUX_PANE" ]]; then echo "$TMUX_PANE" else tmux display -p '#{pane_id}' fi else echo "" fi } # Get the actual tmux prefix key instead of hardcoding C-b get_tmux_prefix() { # First check if we're using oh-my-tmux which often uses C-a if tmux show-options -g | grep -q "TMUX_CONF"; then # Check if prefix has been overridden in .local config local prefix=$(tmux show-options -g prefix 2>/dev/null | awk '{print $2}') if [[ -n "$prefix" ]]; then case "$prefix" in "C-q") echo "C-q" ;; "C-a") echo "C-a" ;; "C-b") echo "C-b" ;; *) echo "$prefix" ;; esac else echo "C-a" # oh-my-tmux default fi else # Try to get the actual prefix from tmux config local prefix=$(tmux show-options -g prefix 2>/dev/null | awk '{print $2}') if [[ -n "$prefix" ]]; then echo "$prefix" else # Default to C-b if we can't determine echo "C-b" fi fi } is_logging_active() { [[ -f "${LOG_MARKER}-$1" ]]; } is_recording_active() { [[ -f "${RECORDING_MARKER}-$1" ]]; } # ================================================================ # DEPENDENCY CHECKING # ================================================================ # Check if TPM is installed check_tpm_installed() { [[ -d "$HOME/.tmux/plugins/tpm" ]] } # Check if tmux-logging plugin is installed check_tmux_logging_installed() { [[ -d "$HOME/.tmux/plugins/tmux-logging" ]] } # Print installation instructions for TMux Plugin Manager (TPM) print_tpm_install_instructions() { echo "=========================================================================" echo "Tmux Plugin Manager (TPM) is not installed. To install:" echo "=========================================================================" echo "1. Run these commands:" echo " mkdir -p ~/.tmux/plugins" echo " git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm" echo "" echo "2. Add these lines to your ~/.tmux.conf:" echo " # List of plugins" echo " set -g @plugin 'tmux-plugins/tpm'" echo " set -g @plugin 'tmux-plugins/tmux-sensible'" echo " set -g @plugin 'tmux-plugins/tmux-logging'" echo "" echo " # Initialize TMUX plugin manager (keep this line at the bottom)" echo " run '~/.tmux/plugins/tpm/tpm'" echo "" echo "3. Reload your tmux configuration:" echo " tmux source-file ~/.tmux.conf" echo "" echo "4. Install plugins by pressing:" echo " prefix + I (capital I)" echo "=========================================================================" } # Print installation instructions for tmux-logging plugin print_tmux_logging_install_instructions() { echo "=========================================================================" echo "tmux-logging plugin is not installed. To install:" echo "=========================================================================" echo "1. Ensure TPM is installed (see previous instructions if needed)" echo "" echo "2. Add this line to your ~/.tmux.conf (before the tpm init line):" echo " set -g @plugin 'tmux-plugins/tmux-logging'" echo "" echo "3. Reload your tmux configuration:" echo " tmux source-file ~/.tmux.conf" echo "" echo "4. Install the plugin by pressing:" echo " prefix + I (capital I)" echo "=========================================================================" } # Check for required dependencies check_dependencies() { local id="$1" local missing=false if ! is_tmux; then echo "WARNING: Not running in tmux. Only basic CSV logging will be available." echo " For full functionality, start tmux first." return 0 fi # Check TPM and tmux-logging plugin if ! check_tpm_installed; then missing=true print_tpm_install_instructions elif ! check_tmux_logging_installed; then missing=true print_tmux_logging_install_instructions fi if $missing; then # Don't use display-message, just echo echo "Plugin dependencies missing, see instructions above" return 1 fi return 0 } # ================================================================ # CSV LOGGING (our custom implementation) # ================================================================ # Setup CSV logging (ORIGINAL FORMAT MAINTAINED) setup_csv_log() { local target="$1" local log_dir="$2" local csv_file="${log_dir}/${target}_commands.csv" ensure_dir "$log_dir" if [[ ! -f "$csv_file" ]]; then echo '"StartTime","EndTime","SourceIP","User","Path","Command"' > "$csv_file" fi echo "$csv_file" } # Create command hook with better timing and error handling create_command_hook() { local hook_script="$1" local csv_log="$2" local pane_id="$3" local verbose_log="$4" cat > "$hook_script" << 'EOF' #!/usr/bin/env bash # Command hook - FIXED version with proper function timing # IMMEDIATELY set all variables and disable errexit set +e # Set variables first before anything else CSV_LOG="__CSV_LOG__" VERBOSE_LOG="__VERBOSE_LOG__" PANE_ID="__PANE_ID__" PUBLIC_IP=$(timeout 5 curl -s ifconfig.me 2>/dev/null || echo "unknown") RTL_INTERNAL_LOGGING=true RTL_CMD_START_TIME="" VERBOSE_CMD_MARKER="__VERBOSE_CMD_MARKER__" # Function to check if command should be logged should_log_command() { local cmd="$1" case "$cmd" in *"RTL_"*|*"log_command"*|*"CSV_LOG"*|*"should_log_command"*) return 1 ;; "history "*|*"PROMPT_COMMAND"*|*"source /tmp/ops"*) return 1 ;; "") return 1 ;; *) return 0 ;; esac } # Function to write verbose command header write_verbose_header() { local cmd="$1" local start_time="$2" local user=$(whoami) local path=$(pwd) # Create a marker file with command info for the verbose processor echo "$cmd|$start_time|$user|$path|$PUBLIC_IP|$PANE_ID" > "${VERBOSE_CMD_MARKER}-${PANE_ID}" } # Function to log command to CSV RTL_log_command() { local cmd="$1" local start_time="$2" local end_time="$3" local user=$(whoami) local path=$(pwd) should_log_command "$cmd" || return 0 local old_flag="$RTL_INTERNAL_LOGGING" RTL_INTERNAL_LOGGING=false local escaped_cmd=${cmd//\"/\"\"} printf '"%s","%s","%s","%s","%s","%s"\n' \ "$start_time" "$end_time" "$PUBLIC_IP" "$user" "$path" "$escaped_cmd" >> "$CSV_LOG" 2>/dev/null RTL_INTERNAL_LOGGING="$old_flag" } # BASH-specific setup if [[ -n "$BASH_VERSION" ]]; then # Save original PROMPT_COMMAND [[ -z "$RTL_ORIG_PROMPT_COMMAND" ]] && RTL_ORIG_PROMPT_COMMAND="$PROMPT_COMMAND" RTL_LAST_COMMAND="" RTL_LAST_HISTNUM="" # FIXED: Define preexec function BEFORE setting trap RTL_preexec() { local cmd="$BASH_COMMAND" if should_log_command "$cmd" 2>/dev/null; then RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')" write_verbose_header "$cmd" "$RTL_CMD_START_TIME" fi } # FIXED: Define PROMPT_COMMAND function BEFORE using it RTL_PROMPT_COMMAND() { if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then return 0 fi local current_histnum=$(history 1 2>/dev/null | awk '{print $1}') local current_cmd=$(history 1 2>/dev/null | sed 's/^[ ]*[0-9]*[ ]*//') if [[ "$current_cmd" != "$RTL_LAST_COMMAND" && "$current_histnum" != "$RTL_LAST_HISTNUM" ]]; then if should_log_command "$current_cmd" 2>/dev/null; then local end_time="$(date '+%Y-%m-%d %H:%M:%S')" local start_time="${RTL_CMD_START_TIME:-$end_time}" RTL_log_command "$current_cmd" "$start_time" "$end_time" RTL_LAST_COMMAND="$current_cmd" RTL_LAST_HISTNUM="$current_histnum" fi fi RTL_CMD_START_TIME="" } # NOW set the trap (function is already defined) trap 'RTL_preexec 2>/dev/null || true' DEBUG 2>/dev/null # Set PROMPT_COMMAND (function is already defined) if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then PROMPT_COMMAND="RTL_PROMPT_COMMAND; $RTL_ORIG_PROMPT_COMMAND" else PROMPT_COMMAND="RTL_PROMPT_COMMAND" fi # ZSH-specific setup elif [[ -n "$ZSH_VERSION" ]]; then # Define functions first RTL_zsh_preexec() { local cmd="$1" if should_log_command "$cmd" 2>/dev/null; then RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')" write_verbose_header "$cmd" "$RTL_CMD_START_TIME" fi } RTL_zsh_precmd() { if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then return 0 fi local cmd=$(fc -ln -1 2>/dev/null) if should_log_command "$cmd" 2>/dev/null; then local end_time="$(date '+%Y-%m-%d %H:%M:%S')" local start_time="${RTL_CMD_START_TIME:-$end_time}" RTL_log_command "$cmd" "$start_time" "$end_time" fi RTL_CMD_START_TIME="" } # NOW set the hooks (functions are already defined) if autoload -Uz add-zsh-hook 2>/dev/null; then add-zsh-hook preexec RTL_zsh_preexec 2>/dev/null || true add-zsh-hook precmd RTL_zsh_precmd 2>/dev/null || true fi fi # Mark as active echo "$$" > "__LOG_MARKER__-${PANE_ID}" echo "Ops Logger command hook installed for pane $PANE_ID" >&2 EOF # Replace placeholders with actual values sed -i "s|__CSV_LOG__|$csv_log|g" "$hook_script" sed -i "s|__VERBOSE_LOG__|$verbose_log|g" "$hook_script" sed -i "s|__PANE_ID__|$pane_id|g" "$hook_script" sed -i "s|__LOG_MARKER__|$LOG_MARKER|g" "$hook_script" sed -i "s|__VERBOSE_CMD_MARKER__|$VERBOSE_CMD_MARKER|g" "$hook_script" chmod +x "$hook_script" } # ================================================================ # UNIFIED VERBOSE LOGGING WITH COMMAND HEADERS # ================================================================ # Create single master verbose log file for all panes create_master_verbose_log() { local target="$1" local log_dir="$2" # Remove date from filename for continuous logging local verbose_dir="${log_dir}/verbose" ensure_dir "$verbose_dir" # Single log file for entire target local log_file="${verbose_dir}/${target}_master.log" # Create the file with header if it doesn't exist if [[ ! -f "$log_file" ]]; then cat > "$log_file" << EOF ================================================================================ RED TEAM TERMINAL LOGGER Master Verbose Log ================================================================================ Target: $target Date: $(date +%Y-%m-%d) Host: $(hostname) Public IP: $(get_public_ip) Started: $(date '+%Y-%m-%d %H:%M:%S') ================================================================================ EOF fi echo "$log_file" } # Create debug filter that captures EVERYTHING (no filtering) create_debug_filter_script() { local filter_script="/tmp/ops-debug-filter-$$.sh" cat > "$filter_script" << 'EODEBUG' #!/usr/bin/env bash # DEBUG: Raw pipe-pane output capture LOGFILE="$1" PANE_ID="$2" DEBUG_RAW_FILE="${LOGFILE}.debug-raw" mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null echo "[$(date)] DEBUG FILTER STARTED for pane $PANE_ID" >> "$DEBUG_RAW_FILE" echo "=================================================" >> "$DEBUG_RAW_FILE" line_count=0 while IFS= read -r line; do line_count=$((line_count + 1)) # Write EVERYTHING to debug file with line numbers printf "[%04d][$(date +%H:%M:%S)] RAW: %s\n" "$line_count" "$line" >> "$DEBUG_RAW_FILE" # Also write to main log (no filtering at all) echo "$line" >> "$LOGFILE" done echo "[$(date)] DEBUG FILTER ENDED - Total lines: $line_count" >> "$DEBUG_RAW_FILE" EODEBUG chmod +x "$filter_script" echo "$filter_script" } # Add debug start function start_debug_verbose_logging() { local id="$1" local tmux_pane_ref="$2" local target="$3" local log_dir="$4" local debug_log="${log_dir}/debug-${target}-${id}-$(date +%H%M%S).log" local filter_script=$(create_debug_filter_script) echo "Starting DEBUG verbose logging to: $debug_log" # Use simple pipe-pane with debug filter tmux pipe-pane -t "$tmux_pane_ref" "bash '$filter_script' '$debug_log' '$id'" sleep 1 # Check if logging started if tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$tmux_pane_ref.*1"; then echo "DEBUG logging started successfully" echo "Raw output file: ${debug_log}.debug-raw" echo "Filtered output file: $debug_log" echo "$filter_script" > "/tmp/ops-debug-filter-script-${id}" return 0 else echo "ERROR: DEBUG logging failed to start" rm -f "$filter_script" return 1 fi } # Create the MINIMAL FIX verbose filter script # Create the FIXED verbose filter script create_verbose_filter_script() { local filter_script="/tmp/ops-verbose-filter-$$.sh" cat > "$filter_script" << 'EOFILTER' #!/usr/bin/env bash # FIXED: Proper command boundary detection LOGFILE="$1" PANE_ID="$2" TARGET="$3" VERBOSE_CMD_MARKER="$4" mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null # Command tracking CURRENT_COMMAND="" IN_COMMAND=false OUTPUT_LINE_COUNT=0 MAX_LINES_PER_COMMAND=22 LAST_MARKER_CHECK="" # Function to properly close a command close_current_command() { if [[ "$IN_COMMAND" == "true" ]]; then echo "==============================================================================" >> "$LOGFILE" echo "" >> "$LOGFILE" IN_COMMAND=false OUTPUT_LINE_COUNT=0 CURRENT_COMMAND="" fi } # Function to check for new command markers check_command_marker() { local marker_file="${VERBOSE_CMD_MARKER}-${PANE_ID}" if [[ -f "$marker_file" ]]; then # Read the marker content local marker_content=$(cat "$marker_file" 2>/dev/null) # Only process if this is a new marker (different from last check) if [[ "$marker_content" != "$LAST_MARKER_CHECK" ]]; then LAST_MARKER_CHECK="$marker_content" # FIXED: Always close previous command first close_current_command # Read command info IFS='|' read -r cmd start_time user path public_ip pane_marker <<< "$marker_content" # Start new command if it's for this pane if [[ "$pane_marker" == "$PANE_ID" && -n "$cmd" ]]; then CURRENT_COMMAND="$cmd" IN_COMMAND=true OUTPUT_LINE_COUNT=0 # Write command header cat >> "$LOGFILE" << EOCMD ============================================================================== COMMAND EXECUTION - $start_time ============================================================================== Command: $cmd User: $user Path: $path Start: $start_time Pane: $PANE_ID Public IP: $public_ip ------------------------------------------------------------------------------ OUTPUT: EOCMD # Remove the marker file after processing rm -f "$marker_file" 2>/dev/null fi fi fi } # Simple ANSI stripping strip_ansi() { local line="$1" # Remove bracketed paste mode line="${line//[?2004h/}" line="${line//[?2004l/}" # Remove ANSI escape sequences line=$(echo "$line" | sed -E 's/\x1b\[[0-9;]*[mGKHF]//g; s/\x1b\[[?]?[0-9]*[hlc]//g') echo "$line" } # Check if line looks like a shell prompt is_prompt_line() { local line="$1" # More specific prompt detection patterns if [[ "$line" =~ .*@.*:.*[\$#][[:space:]]*$ ]] || \ [[ "$line" =~ ^[[:space:]]*[\$#][[:space:]]*$ ]] || \ [[ "$line" =~ .*[\$#][[:space:]]+[a-zA-Z] ]]; then return 0 fi return 1 } # Main processing loop while IFS= read -r line; do # ALWAYS check for new commands first, before any processing check_command_marker # Clean the line clean_line=$(strip_ansi "$line") # Skip completely empty lines [[ -z "$clean_line" ]] && continue # If we're in a command, log the output if [[ "$IN_COMMAND" == "true" ]]; then OUTPUT_LINE_COUNT=$((OUTPUT_LINE_COUNT + 1)) # Check if this looks like a prompt (indicating command end) if is_prompt_line "$clean_line" && [[ $OUTPUT_LINE_COUNT -gt 1 ]]; then # Don't log the prompt line, just close the command close_current_command continue fi # Log the output line with timestamp if [[ $OUTPUT_LINE_COUNT -le $MAX_LINES_PER_COMMAND ]]; then echo "$(date '+%H:%M:%S') $clean_line" >> "$LOGFILE" elif [[ $OUTPUT_LINE_COUNT -eq $(($MAX_LINES_PER_COMMAND + 1)) ]]; then echo "... [OUTPUT TRUNCATED - showing first $MAX_LINES_PER_COMMAND lines only] ..." >> "$LOGFILE" fi fi done # End any active command on exit close_current_command EOFILTER chmod +x "$filter_script" echo "$filter_script" } # Enhanced command hook that integrates with verbose logging create_enhanced_command_hook() { local hook_script="$1" local csv_log="$2" local pane_id="$3" local verbose_log="$4" cat > "$hook_script" << 'EOF' #!/usr/bin/env bash # Enhanced command hook with verbose integration set +e # Configuration CSV_LOG="__CSV_LOG__" VERBOSE_LOG="__VERBOSE_LOG__" PANE_ID="__PANE_ID__" PUBLIC_IP=$(timeout 5 curl -s ifconfig.me 2>/dev/null || echo "unknown") RTL_INTERNAL_LOGGING=true RTL_CMD_START_TIME="" VERBOSE_CMD_MARKER="__VERBOSE_CMD_MARKER__" # Function to check if command should be logged should_log_command() { local cmd="$1" case "$cmd" in *"RTL_"*|*"log_command"*|*"CSV_LOG"*|*"should_log_command"*) return 1 ;; "history "*|*"PROMPT_COMMAND"*|*"source /tmp/ops"*) return 1 ;; "") return 1 ;; *) return 0 ;; esac } # Enhanced function to write verbose command header write_verbose_header() { local cmd="$1" local start_time="$2" local user=$(whoami) local path=$(pwd) # Create marker for verbose filter echo "$cmd|$start_time|$user|$path|$PUBLIC_IP|$PANE_ID" > "${VERBOSE_CMD_MARKER}-${PANE_ID}" } # Function to log command to CSV (unchanged) RTL_log_command() { local cmd="$1" local start_time="$2" local end_time="$3" local user=$(whoami) local path=$(pwd) should_log_command "$cmd" || return 0 local old_flag="$RTL_INTERNAL_LOGGING" RTL_INTERNAL_LOGGING=false local escaped_cmd=${cmd//\"/\"\"} printf '"%s","%s","%s","%s","%s","%s"\n' \ "$start_time" "$end_time" "$PUBLIC_IP" "$user" "$path" "$escaped_cmd" >> "$CSV_LOG" 2>/dev/null RTL_INTERNAL_LOGGING="$old_flag" } # BASH-specific setup if [[ -n "$BASH_VERSION" ]]; then [[ -z "$RTL_ORIG_PROMPT_COMMAND" ]] && RTL_ORIG_PROMPT_COMMAND="$PROMPT_COMMAND" RTL_LAST_COMMAND="" RTL_LAST_HISTNUM="" RTL_preexec() { local cmd="$BASH_COMMAND" if should_log_command "$cmd" 2>/dev/null; then RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')" write_verbose_header "$cmd" "$RTL_CMD_START_TIME" fi } RTL_PROMPT_COMMAND() { if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then return 0 fi local current_histnum=$(history 1 2>/dev/null | awk '{print $1}') local current_cmd=$(history 1 2>/dev/null | sed 's/^[ ]*[0-9]*[ ]*//') if [[ "$current_cmd" != "$RTL_LAST_COMMAND" && "$current_histnum" != "$RTL_LAST_HISTNUM" ]]; then if should_log_command "$current_cmd" 2>/dev/null; then local end_time="$(date '+%Y-%m-%d %H:%M:%S')" local start_time="${RTL_CMD_START_TIME:-$end_time}" RTL_log_command "$current_cmd" "$start_time" "$end_time" RTL_LAST_COMMAND="$current_cmd" RTL_LAST_HISTNUM="$current_histnum" fi fi RTL_CMD_START_TIME="" } trap 'RTL_preexec 2>/dev/null || true' DEBUG 2>/dev/null if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then PROMPT_COMMAND="RTL_PROMPT_COMMAND; $RTL_ORIG_PROMPT_COMMAND" else PROMPT_COMMAND="RTL_PROMPT_COMMAND" fi # ZSH-specific setup elif [[ -n "$ZSH_VERSION" ]]; then RTL_zsh_preexec() { local cmd="$1" if should_log_command "$cmd" 2>/dev/null; then RTL_CMD_START_TIME="$(date '+%Y-%m-%d %H:%M:%S')" write_verbose_header "$cmd" "$RTL_CMD_START_TIME" fi } RTL_zsh_precmd() { if [[ "$RTL_INTERNAL_LOGGING" == "false" ]]; then return 0 fi local cmd=$(fc -ln -1 2>/dev/null) if should_log_command "$cmd" 2>/dev/null; then local end_time="$(date '+%Y-%m-%d %H:%M:%S')" local start_time="${RTL_CMD_START_TIME:-$end_time}" RTL_log_command "$cmd" "$start_time" "$end_time" fi RTL_CMD_START_TIME="" } if autoload -Uz add-zsh-hook 2>/dev/null; then add-zsh-hook preexec RTL_zsh_preexec 2>/dev/null || true add-zsh-hook precmd RTL_zsh_precmd 2>/dev/null || true fi fi # Mark as active echo "$$" > "__LOG_MARKER__-${PANE_ID}" echo "Enhanced Ops Logger with verbose integration installed for pane $PANE_ID" >&2 EOF # Replace placeholders sed -i "s|__CSV_LOG__|$csv_log|g" "$hook_script" sed -i "s|__VERBOSE_LOG__|$verbose_log|g" "$hook_script" sed -i "s|__PANE_ID__|$pane_id|g" "$hook_script" sed -i "s|__LOG_MARKER__|$LOG_MARKER|g" "$hook_script" sed -i "s|__VERBOSE_CMD_MARKER__|$VERBOSE_CMD_MARKER|g" "$hook_script" chmod +x "$hook_script" } # Start unified verbose logging # Start unified verbose logging (add delay for stability) start_unified_verbose_logging() { local id="$1" local tmux_pane_ref="$2" local target="$3" local log_dir="$4" # Create master log file local master_log=$(create_master_verbose_log "$target" "$log_dir") # Create filter script local filter_script=$(create_verbose_filter_script) log_debug "Starting unified verbose logging to: $master_log" # Use pipe-pane with our filter script tmux pipe-pane -t "$tmux_pane_ref" "bash '$filter_script' '$master_log' '$id' '$target' '$VERBOSE_CMD_MARKER'" # Give pipe-pane time to establish sleep 0.5 # Check if logging started successfully if tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$tmux_pane_ref.*1"; then log_debug "Unified verbose logging started successfully" # Store filter script path for cleanup echo "$filter_script" > "/tmp/ops-filter-script-${id}" return 0 else log_debug "ERROR: Unified verbose logging failed to start" rm -f "$filter_script" return 1 fi } # Stop unified verbose logging stop_unified_verbose_logging() { local tmux_pane_ref="$1" local id="$2" # Stop pipe-pane tmux pipe-pane -t "$tmux_pane_ref" 2>/dev/null || true # Clean up filter script if [[ -f "/tmp/ops-filter-script-${id}" ]]; then local filter_script=$(cat "/tmp/ops-filter-script-${id}") rm -f "$filter_script" 2>/dev/null rm -f "/tmp/ops-filter-script-${id}" fi # Clean up markers rm -f "${VERBOSE_CMD_MARKER}-${id}" 2>/dev/null rm -f "/tmp/ops-cmd-active-${id}" 2>/dev/null log_debug "Stopped unified verbose logging for pane: $tmux_pane_ref" } # ================================================================ # OHMYTMUX-COMPATIBLE WINDOW NAME MANAGEMENT # ================================================================ # Improved window name handling that works with ohmytmux get_current_window_name() { local tmux_pane_ref="$1" local name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}') # Remove our indicators as well as any ohmytmux status indicators name="${name#🔴 }" name="${name#🎥 }" name="${name#● }" name="${name#⚠ }" name="${name#▶ }" echo "$name" } set_window_logging_indicator() { local tmux_pane_ref="$1" local current_name=$(get_current_window_name "$tmux_pane_ref") # Preserve ohmytmux automatic formats but add our indicator if [[ "$current_name" == *Z ]]; then # Zoomed window format in ohmytmux tmux rename-window -t "$tmux_pane_ref" "🔴 ${current_name%Z}Z" else tmux rename-window -t "$tmux_pane_ref" "🔴 $current_name" fi } set_window_recording_indicator() { local tmux_pane_ref="$1" local current_name=$(get_current_window_name "$tmux_pane_ref") # Remove logging indicator if present and add recording current_name="${current_name#🔴 }" # Preserve ohmytmux automatic formats if [[ "$current_name" == *Z ]]; then # Zoomed window format in ohmytmux tmux rename-window -t "$tmux_pane_ref" "🎥 ${current_name%Z}Z" else tmux rename-window -t "$tmux_pane_ref" "🎥 $current_name" fi } clear_window_indicators() { local tmux_pane_ref="$1" local current_name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}') local clean_name=$(get_current_window_name "$tmux_pane_ref") # Preserve any ohmytmux indicators that might be present if [[ "$current_name" == *Z ]]; then # Zoomed window format in ohmytmux tmux rename-window -t "$tmux_pane_ref" "${clean_name}Z" else tmux rename-window -t "$tmux_pane_ref" "$clean_name" fi } # MAIN LOGGING FUNCTIONS # ================================================================ # Enhanced logging installation with unified verbose logging install_logging() { local id="$1" local csv_log="$2" local target="$3" local log_dir="$4" log_debug "Installing enhanced logging for pane $id" if is_tmux; then local tmux_pane_ref=$(get_tmux_pane_ref) # Get master verbose log path local master_log=$(create_master_verbose_log "$target" "$log_dir") # Start unified verbose logging if start_unified_verbose_logging "$id" "$tmux_pane_ref" "$target" "$log_dir"; then log_debug "Unified verbose logging started successfully" else log_debug "Verbose logging failed, continuing with CSV only" fi # Create enhanced command hook local hook_script="/tmp/ops-hook-${id}.sh" create_enhanced_command_hook "$hook_script" "$csv_log" "$id" "$master_log" # Install the hook tmux send-keys -t "$tmux_pane_ref" "source '$hook_script' 2>/dev/null && echo 'Enhanced logging hooks installed successfully' || echo 'Logging may have warnings but is active'" ENTER sleep 2 # Set window indicator set_window_logging_indicator "$tmux_pane_ref" # Mark as active touch "${LOG_MARKER}-${id}" touch "${LOG_MARKER}-${id}.success" else # Direct shell logging (CSV only) local hook_script="/tmp/ops-hook-${id}.sh" create_enhanced_command_hook "$hook_script" "$csv_log" "$id" "" source "$hook_script" echo "Direct shell logging started (CSV only)" touch "${LOG_MARKER}-${id}" touch "${LOG_MARKER}-${id}.success" fi log_debug "Enhanced logging installation completed" } # Enhanced removal function remove_logging() { local id="$1" log_debug "Removing enhanced logging for pane $id" if is_tmux; then local tmux_pane_ref=$(get_tmux_pane_ref) # Stop unified verbose logging stop_unified_verbose_logging "$tmux_pane_ref" "$id" # Clean up command hooks (same as before) local cleanup_script="/tmp/ops-cleanup-${id}.sh" cat > "$cleanup_script" << 'EOF' #!/usr/bin/env bash set +e if [[ -n "$BASH_VERSION" ]]; then trap - DEBUG 2>/dev/null if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then PROMPT_COMMAND="$RTL_ORIG_PROMPT_COMMAND" unset RTL_ORIG_PROMPT_COMMAND else unset PROMPT_COMMAND fi unset -f RTL_PROMPT_COMMAND RTL_log_command should_log_command RTL_preexec write_verbose_header 2>/dev/null elif [[ -n "$ZSH_VERSION" ]]; then add-zsh-hook -d preexec RTL_zsh_preexec 2>/dev/null add-zsh-hook -d precmd RTL_zsh_precmd 2>/dev/null unset -f RTL_zsh_preexec RTL_zsh_precmd RTL_log_command should_log_command write_verbose_header 2>/dev/null fi unset RTL_CMD_START_TIME RTL_LAST_COMMAND RTL_LAST_HISTNUM RTL_INTERNAL_LOGGING 2>/dev/null unset CSV_LOG VERBOSE_LOG PANE_ID PUBLIC_IP VERBOSE_CMD_MARKER 2>/dev/null echo "Enhanced Ops Logger hooks removed" EOF chmod +x "$cleanup_script" tmux send-keys -t "$tmux_pane_ref" "source '$cleanup_script' 2>/dev/null; rm -f '$cleanup_script'" ENTER # Clear window indicators clear_window_indicators "$tmux_pane_ref" # Clean up temp files rm -f "/tmp/ops-hook-${id}.sh" else # Direct shell cleanup (same as before) if [[ -n "$BASH_VERSION" ]]; then trap - DEBUG 2>/dev/null if [[ -n "$RTL_ORIG_PROMPT_COMMAND" ]]; then PROMPT_COMMAND="$RTL_ORIG_PROMPT_COMMAND" unset RTL_ORIG_PROMPT_COMMAND else unset PROMPT_COMMAND fi unset -f RTL_PROMPT_COMMAND RTL_log_command should_log_command RTL_preexec write_verbose_header 2>/dev/null elif [[ -n "$ZSH_VERSION" ]]; then add-zsh-hook -d preexec RTL_zsh_preexec 2>/dev/null add-zsh-hook -d precmd RTL_zsh_precmd 2>/dev/null unset -f RTL_zsh_preexec RTL_zsh_precmd RTL_log_command should_log_command write_verbose_header 2>/dev/null fi echo "Direct shell logging stopped" fi # Remove markers rm -f "${LOG_MARKER}-${id}" "${LOG_MARKER}-${id}.success" rm -f "/tmp/ops-hook-${id}.sh" log_debug "Enhanced logging removal completed" } # ================================================================ # RECORDING FUNCTIONS # ================================================================ start_recording() { local id="$1" local target="$2" local log_dir="$3" local recordings_dir="${log_dir}/recordings" local timestamp=$(date +%Y%m%d_%H%M%S) ensure_dir "$recordings_dir" # Check for asciinema if ! command -v asciinema >/dev/null 2>&1; then local error_msg="ERROR: asciinema not installed" echo "$error_msg" >&2 echo "To install asciinema:" echo " Ubuntu/Debian: sudo apt install asciinema" echo " Fedora/RHEL: sudo dnf install asciinema" echo " macOS: brew install asciinema" echo " Pip: pip3 install asciinema" return 1 fi local cast_file="${recordings_dir}/${target}_${id}_${timestamp}.cast" echo "asciinema:$cast_file" > "${RECORDING_MARKER}-${id}" if is_tmux; then local tmux_pane_ref=$(get_tmux_pane_ref) # Set recording indicator (ohmytmux compatible) set_window_recording_indicator "$tmux_pane_ref" # Start recording cleanly tmux send-keys -t "$tmux_pane_ref" "asciinema rec '$cast_file'" ENTER else echo "Starting asciinema recording. Use 'exit' or Ctrl+D to stop." asciinema rec "$cast_file" fi } stop_recording() { local id="$1" [[ ! -f "${RECORDING_MARKER}-${id}" ]] && { echo "No active recording" return 1 } read -r rec_type rec_path < <(cat "${RECORDING_MARKER}-${id}" | tr ':' ' ') if [[ "$rec_type" == "asciinema" ]]; then if is_tmux; then local tmux_pane_ref=$(get_tmux_pane_ref) # Try sending Ctrl+D tmux send-keys -t "$tmux_pane_ref" C-d sleep 1 # If that didn't work, try killing the asciinema process if ps aux | grep -v grep | grep -q "asciinema rec"; then log_debug "Ctrl+D didn't stop recording, trying to kill asciinema process" tmux send-keys -t "$tmux_pane_ref" "pkill -f 'asciinema rec'" ENTER sleep 1 if ps aux | grep -v grep | grep -q "asciinema rec"; then pkill -f "asciinema rec" || true fi fi # Clear recording indicator but preserve logging indicator local current_name=$(tmux display -t "$tmux_pane_ref" -p '#{window_name}') if [[ "$current_name" == "🎥"* ]]; then if is_logging_active "$id"; then local clean_name="${current_name#🎥 }" tmux rename-window -t "$tmux_pane_ref" "🔴 $clean_name" else clear_window_indicators "$tmux_pane_ref" fi fi else echo "Recording stopped: $rec_path" fi fi rm -f "${RECORDING_MARKER}-${id}" } # ================================================================ # CONFIGURATION FUNCTIONS # ================================================================ create_config() { local is_first_run="${1:-false}" # If this is a first run from ensure_config in tmux, use the local version's approach if [[ "$is_first_run" == "true" ]] && is_tmux; then # Mark configuration as in progress touch "$CONFIG_IN_PROGRESS" # Create a temporary script that will handle the config creation local config_script="/tmp/ops-config-$$.sh" cat > "$config_script" << 'EOSCRIPT' #!/usr/bin/env bash CONFIG_FILE="${HOME}/.ops-logger.conf" DEFAULT_TARGET="target-$(hostname | tr '.' '-')" DEFAULT_LOG_DIR="${HOME}/OperationLogs" echo "Red Team Terminal Logger - First Time Setup" echo "===========================================" echo "" read -p "Enter target name [$DEFAULT_TARGET]: " TARGET_NAME TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}" read -p "Enter log directory [$DEFAULT_LOG_DIR]: " LOG_DIR LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}" read -p "Prompt for logging in new shells? [Y/n]: " PROMPT_NEW_SHELLS [[ "${PROMPT_NEW_SHELLS,,}" == "n" ]] && PROMPT_NEW_SHELLS=false || PROMPT_NEW_SHELLS=true read -p "Enable debug logging? [y/N]: " DEBUG [[ "${DEBUG,,}" == "y" ]] && DEBUG=true || DEBUG=false # Save config mkdir -p "$(dirname "$CONFIG_FILE")" cat > "$CONFIG_FILE" << EOF # Ops Logger Configuration TARGET_NAME="$TARGET_NAME" LOG_DIR="$LOG_DIR" PROMPT_NEW_SHELLS=$PROMPT_NEW_SHELLS RECORD_INTERVAL=0.5 DEBUG=$DEBUG EOF # Create directories mkdir -p "$LOG_DIR" "$LOG_DIR/verbose" "$LOG_DIR/recordings" # Remove the in-progress marker rm -f "/tmp/ops-logger-configuring" echo "" echo "Configuration saved!" echo " Target name: $TARGET_NAME" echo " Log directory: $LOG_DIR" echo " Prompt new shells: $PROMPT_NEW_SHELLS" echo " Debug mode: $DEBUG" echo "" echo "Now run: prefix+L to start logging (or ops-logger --start)" echo "Press ENTER to close this window..." read EOSCRIPT chmod +x "$config_script" # Open new window for config bash "$config_script"; rm -f "$config_script" return 0 fi # For manual --config or non-tmux environments (GitHub version approach) echo "Red Team Terminal Logger - First Time Setup" echo "===========================================" echo "" CONFIG_FILE="${HOME}/.ops-logger.conf" DEFAULT_TARGET="target-$(hostname | tr '.' '-')" DEFAULT_LOG_DIR="${HOME}/OperationLogs" # Detect whether we're in a tty if [[ ! -t 0 ]]; then echo "Error: Cannot prompt user in non-interactive shell." return 1 fi read -p "Enter target name [$DEFAULT_TARGET]: " TARGET_NAME TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}" read -p "Enter log directory [$DEFAULT_LOG_DIR]: " LOG_DIR LOG_DIR="${LOG_DIR:-$DEFAULT_LOG_DIR}" read -p "Prompt for logging in new shells? [Y/n]: " PROMPT_NEW_SHELLS [[ "${PROMPT_NEW_SHELLS,,}" == "n" ]] && PROMPT_NEW_SHELLS=false || PROMPT_NEW_SHELLS=true read -p "Enable debug logging? [y/N]: " DEBUG [[ "${DEBUG,,}" == "y" ]] && DEBUG=true || DEBUG=false # Save config mkdir -p "$(dirname "$CONFIG_FILE")" cat > "$CONFIG_FILE" </dev/null || echo "/dev/tty") exec 1>$tty 2>$tty fi # Auto-setup config with prompts on first logging start ensure_config || return 1 load_config local id=$(get_pane_id) is_logging_active "$id" && { echo "Already logging" return 0 } local csv_log=$(setup_csv_log "$TARGET_NAME" "$LOG_DIR") install_logging "$id" "$csv_log" "$TARGET_NAME" "$LOG_DIR" } stop_logging() { # Ensure we output to terminal if run from tmux keybinding if is_tmux && [[ -t 1 ]]; then local tty=$(tty 2>/dev/null || echo "/dev/tty") exec 1>$tty 2>$tty fi local id=$(get_pane_id) is_logging_active "$id" || { echo "Not logging" return 0 } remove_logging "$id" } toggle_logging() { # Ensure we output to terminal if run from tmux keybinding if is_tmux && [[ -t 1 ]]; then local tty=$(tty 2>/dev/null || echo "/dev/tty") exec 1>$tty 2>$tty fi local id=$(get_pane_id) is_logging_active "$id" && stop_logging || start_logging } toggle_recording() { # Ensure we output to terminal if run from tmux keybinding if is_tmux && [[ -t 1 ]]; then local tty=$(tty 2>/dev/null || echo "/dev/tty") exec 1>$tty 2>$tty fi local id=$(get_pane_id) # Load existing config or use defaults [[ -f "$CONFIG_FILE" ]] && load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; } is_recording_active "$id" && stop_recording "$id" || start_recording "$id" "$TARGET_NAME" "$LOG_DIR" } prompt_for_logging() { local id id=$(get_pane_id) # ✅ Debug logging to see what's happening echo "[$(date)] prompt_for_logging called for pane: $id" >> /tmp/ops-logger-prompt-debug.log echo "[$(date)] TMUX_PANE: ${TMUX_PANE:-'not set'}" >> /tmp/ops-logger-prompt-debug.log # ✅ Avoid recursion with a pane-specific lock - BUT with timeout local lock_file="/tmp/ops-logger-prompted-${id}" if [[ -f "$lock_file" ]]; then # Check if lock file is stale (older than 30 seconds) if [[ $(find "$lock_file" -mmin +0.5 2>/dev/null) ]]; then echo "[$(date)] Removing stale lock file" >> /tmp/ops-logger-prompt-debug.log rm -f "$lock_file" else echo "[$(date)] Recent lock file exists, returning" >> /tmp/ops-logger-prompt-debug.log return 0 fi fi touch "$lock_file" # ✅ Cleanup function to ensure lock file is always removed cleanup_lock() { rm -f "$lock_file" echo "[$(date)] Lock file cleaned up" >> /tmp/ops-logger-prompt-debug.log } trap cleanup_lock EXIT is_logging_active "$id" && { echo "[$(date)] Already logging, returning" >> /tmp/ops-logger-prompt-debug.log cleanup_lock return 0 } load_config echo "[$(date)] Config loaded - PROMPT_NEW_SHELLS: $PROMPT_NEW_SHELLS" >> /tmp/ops-logger-prompt-debug.log [[ "$PROMPT_NEW_SHELLS" != "true" ]] && { echo "[$(date)] Prompting disabled, returning" >> /tmp/ops-logger-prompt-debug.log cleanup_lock return 0 } # ✅ FIXED: Use tmux display-popup for proper interaction in tmux if is_tmux; then echo "[$(date)] In tmux, creating popup" >> /tmp/ops-logger-prompt-debug.log # Create a temporary script for the popup local popup_script="/tmp/ops-prompt-${id}.sh" local script_path=$(readlink -f "$0") cat > "$popup_script" << 'EOPOPUP' #!/bin/bash echo "Start logging this shell?" echo "" echo " [Y]es - Start logging" echo " [N]o - Skip logging" echo "" read -n 1 -p "Choice [Y/n]: " response echo "" if [[ -z "$response" || "${response,,}" == "y" ]]; then echo "Starting logging..." __SCRIPT_PATH__ --start echo "Logging started!" sleep 2 else echo "Logging skipped." sleep 1 fi EOPOPUP # Replace placeholder with actual script path sed -i "s|__SCRIPT_PATH__|$script_path|g" "$popup_script" chmod +x "$popup_script" echo "[$(date)] Popup script created, calling display-popup" >> /tmp/ops-logger-prompt-debug.log # Use tmux display-popup for proper interaction tmux display-popup -E -w 50 -h 10 -T "Ops Logger" "bash '$popup_script'; rm -f '$popup_script'" echo "[$(date)] display-popup completed" >> /tmp/ops-logger-prompt-debug.log else echo "[$(date)] Not in tmux, using direct prompt" >> /tmp/ops-logger-prompt-debug.log # Direct shell - standard approach echo "Start logging this shell?" echo "" echo " [Y]es - Start logging" echo " [N]o - Skip logging" echo "" if read -t 10 -n 1 -p "Choice [Y/n]: " response; then echo "" if [[ -z "$response" || "${response,,}" == "y" ]]; then start_logging echo "Logging started!" else echo "Logging skipped." fi else echo "" echo "Timeout - logging skipped." fi fi # Clean up will be handled by trap echo "[$(date)] prompt_for_logging completed" >> /tmp/ops-logger-prompt-debug.log } # ================================================================ # TMUX INTEGRATION # ================================================================ install_tmux_keys() { load_config if is_tmux; then local script_path script_path=$(readlink -f "$0") # Bind keys tmux bind-key L run-shell "bash -c '[ -f ~/.ops-logger.conf ] && tmux run-shell \"$script_path --toggle\" || tmux display-popup -E -w 80% -h 60% -T \"Ops Logger Config\" \"bash $script_path --toggle\"'" tmux bind-key R run-shell "'$script_path' --toggle-recording" # ✅ FIXED: Create a wrapper script that exports the pane context if [[ "$PROMPT_NEW_SHELLS" == "true" ]]; then local hook_wrapper="/tmp/ops-logger-pane-wrapper.sh" cat > "$hook_wrapper" << EOWRAPPER #!/bin/bash # Wrapper that sets pane context and calls the prompt # Sleep to let pane initialize sleep 1 # Check config if [ -f ~/.ops-logger.conf ]; then source ~/.ops-logger.conf if [ "\$PROMPT_NEW_SHELLS" = "true" ]; then # Get pane info in tmux context PANE_ID=\$(tmux display-message -p "#{session_name}-#{window_index}-#{pane_index}") # Check if already logging if [ ! -f "/tmp/ops-logger-active-\$PANE_ID" ]; then # Export the tmux pane reference for the script to use export TMUX_PANE=\$(tmux display-message -p "#{pane_id}") # Call the script with the pane context $script_path --prompt fi fi fi EOWRAPPER chmod +x "$hook_wrapper" tmux set-hook -g after-new-window "run-shell '$hook_wrapper'" tmux set-hook -g after-split-window "run-shell '$hook_wrapper'" echo "Hooks installed for new windows/panes" fi echo "Keys installed: $(get_tmux_prefix)+L (logging), $(get_tmux_prefix)+R (recording)" log_debug "Tmux key bindings installed" else echo "Not in tmux, no keys installed" fi } uninstall_tmux_keys() { if is_tmux; then tmux unbind-key L 2>/dev/null tmux unbind-key R 2>/dev/null tmux set-hook -gu after-new-window 2>/dev/null tmux set-hook -gu after-split-window 2>/dev/null echo "Tmux keys removed" log_debug "Tmux key bindings removed" # Remove our hook scripts rm -f /tmp/ops-logger-hook-wrapper.sh 2>/dev/null rm -f /tmp/ops-logger-tmux-hook.sh 2>/dev/null rm -f /tmp/ops-logger-pane-wrapper.sh 2>/dev/null fi # Also remove config file as user expected load_config local log_dir_for_cleanup="$LOG_DIR" # Remove config [[ -f "$CONFIG_FILE" ]] && rm -f "$CONFIG_FILE" # Remove debug log [[ -f "${log_dir_for_cleanup}/ops-logger-debug.log" ]] && rm -f "${log_dir_for_cleanup}/ops-logger-debug.log" echo "Configuration removed" } uninstall_all() { local id=$(get_pane_id) log_debug "Starting complete uninstall" # Stop active sessions is_logging_active "$id" && stop_logging is_recording_active "$id" && stop_recording "$id" # Remove tmux integration and config is_tmux && uninstall_tmux_keys # Clean up temp files rm -f /tmp/ops-* 2>/dev/null echo "OpsLogger uninstalled completely" } show_status() { # Ensure we output to terminal if run from tmux keybinding if is_tmux && [[ -t 1 ]]; then local tty=$(tty 2>/dev/null || echo "/dev/tty") exec 1>$tty 2>$tty fi load_config local id=$(get_pane_id) echo "Red Team Terminal Logger Status:" echo "===============================" echo "Target: $TARGET_NAME" echo "Log directory: $LOG_DIR" echo "Debug mode: $DEBUG" echo "Logging: $(is_logging_active "$id" && echo "ACTIVE" || echo "INACTIVE")" echo "Recording: $(is_recording_active "$id" && echo "ACTIVE" || echo "INACTIVE")" echo "Public IP: $(get_public_ip)" echo "Asciinema: $(command -v asciinema >/dev/null 2>&1 && echo "INSTALLED" || echo "NOT INSTALLED")" if is_tmux; then echo "Environment: tmux ($(tmux display -p '#{session_name}'))" echo "Tmux prefix: $(get_tmux_prefix)" echo "ohmytmux: $(tmux show-options -g | grep -q "TMUX_CONF" && echo "DETECTED" || echo "NOT DETECTED")" echo "Normalized Pane ID: $id" # Check plugin installation status echo "TPM: $(check_tpm_installed && echo "INSTALLED" || echo "NOT INSTALLED")" echo "tmux-logging plugin: $(check_tmux_logging_installed && echo "INSTALLED" || echo "NOT INSTALLED")" # Check if there's active pipe-pane logging echo "Active logging pipe: $(tmux list-panes -F "#{pane_id} #{pane_pipe}" | grep -q "$(get_tmux_pane_ref).*1" && echo "YES" || echo "NO")" else echo "Environment: direct shell" fi # Show current log files local csv_log="${LOG_DIR}/${TARGET_NAME}_commands_$(date +%Y-%m-%d).csv" [[ -f "$csv_log" ]] && { echo "Current CSV log: $csv_log" echo "Commands logged: $(($(wc -l < "$csv_log") - 1))" } # Show verbose logs with headers local verbose_dir="${LOG_DIR}/verbose" if [[ -d "$verbose_dir" ]]; then local verbose_files=$(find "$verbose_dir" -name "*${TARGET_NAME}*.log" -type f 2>/dev/null | wc -l) echo "Verbose log files: $verbose_files in $verbose_dir" if [[ "$verbose_files" -gt 0 ]]; then local latest_verbose=$(find "$verbose_dir" -name "*${TARGET_NAME}*.log" -type f -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -d' ' -f2-) [[ -n "$latest_verbose" ]] && echo "Latest verbose log: $latest_verbose ($(du -h "$latest_verbose" | cut -f1))" fi fi } show_help() { echo "Red Team Terminal Logger - Professional Solution v2.5.2" echo "========================================================" echo "USAGE: $0 [OPTIONS]" echo "" echo "LOGGING CONTROLS:" echo " --start Start command/verbose logging" echo " --stop Stop logging" echo " --toggle Toggle logging on/off" echo "" echo "RECORDING CONTROLS:" echo " --start-recording Start terminal recording (requires asciinema)" echo " --stop-recording Stop recording" echo " --toggle-recording Toggle recording on/off" echo "" echo "SETUP & CONFIGURATION:" echo " --prompt Show logging prompt (for new shells)" echo " --install Install tmux keybindings" echo " --uninstall Remove tmux keybindings and config" echo " --uninstall-all Complete removal (same as --uninstall)" echo " --config Configure settings" echo " --save-config Save config (internal use)" echo "" echo "INFO:" echo " --status Show current status" echo " --help Show this help" echo "" echo "DEBUG:" echo " --debug-on Enable debug logging" echo " --debug-off Disable debug logging" echo "" echo "TMUX INTEGRATION:" echo " Keys: prefix+L (toggle logging), prefix+R (recording)" echo " Compatible with ohmytmux themes and window naming" echo " Auto-prompts for new windows/panes (configurable)" echo "" echo "VERSION 2.5.2 FIXES:" echo " - FIXED: Verbose logs now include formatted command headers" echo " - FIXED: Configuration uses interactive tmux windows" echo " - FIXED: Command metadata properly captured and formatted" echo " - IMPROVED: Better separation of capture and formatting" } # Main command handler with output handling main() { # Always ensure direct terminal output for tmux operations if is_tmux && [[ -t 1 ]]; then local tty=$(tty 2>/dev/null || echo "/dev/tty") exec 1>$tty 2>$tty fi case "$1" in --start) start_logging ;; --stop) stop_logging ;; --toggle) toggle_logging ;; --start-recording) [[ -f "$CONFIG_FILE" ]] && load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; }; start_recording "$(get_pane_id)" "$TARGET_NAME" "$LOG_DIR" ;; --stop-recording) stop_recording "$(get_pane_id)" ;; --toggle-recording) toggle_recording ;; --prompt) prompt_for_logging ;; --install) install_tmux_keys ;; --uninstall) uninstall_tmux_keys ;; --uninstall-all) uninstall_all ;; --config) create_config ;; --save-config) save_config_from_tmux ;; --status) show_status ;; --debug-pipe) load_config || { TARGET_NAME="$DEFAULT_TARGET"; LOG_DIR="$DEFAULT_LOG_DIR"; } local id=$(get_pane_id) local tmux_pane_ref=$(get_tmux_pane_ref) start_debug_verbose_logging "$id" "$tmux_pane_ref" "$TARGET_NAME" "$LOG_DIR" ;; --debug-on) [[ -f "$CONFIG_FILE" ]] && { load_config; DEBUG=true; save_config; } || echo "Run --config first to set up configuration"; echo "Debug logging enabled" ;; --debug-off) [[ -f "$CONFIG_FILE" ]] && { load_config; DEBUG=false; save_config; } || echo "Run --config first to set up configuration"; echo "Debug logging disabled" ;; --help|'') show_help ;; *) echo "Unknown option: $1. Use --help" ;; esac } # ShellOpsLog compatibility layer if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then export REDTEAM_LOGGER_PATH="${BASH_SOURCE[0]}" start_operation_log() { local auto_start=0 local log_dir="$HOME/OperationLogs" while [ "$#" -gt 0 ]; do case "$1" in -AutoStart|-autostart) auto_start=1; shift ;; *) log_dir="$1"; shift ;; esac done [[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE" LOG_DIR="$log_dir" TARGET_NAME="${TARGET_NAME:-$DEFAULT_TARGET}" save_config if [[ "$auto_start" -eq 1 ]]; then "$REDTEAM_LOGGER_PATH" --start else "$REDTEAM_LOGGER_PATH" --prompt fi } stop_operation_log() { "$REDTEAM_LOGGER_PATH" --stop } log_debug "ShellOpsLog compatibility layer loaded" else # Script is being executed directly main "$@" fi