#!/bin/bash

#OSV-19536: Generate the list of 'static identifications' to be used for obfuscation
#This task is part of OSV-19226: OSV Obfuscation
#
#Authors:
#   Antonios Chalas, for Atos (c), 2021

# Environment initialization (global variables, dirs, etc...)
function init( )
{
   PASSWD_FILE=/etc/passwd
   HOSTS_FILE=/etc/hosts
   NODE_CFG=/etc/hiq8000/node.cfg
   USERS_REFERENCE_FILE=/log/security/save/default_users.lst
   FCFGREAD_TOOL=$(which fcfgread 2>/dev/null)

   TMPFILE=$(mktemp -p /tmp)

   [ -z "${OUTPUT_FILE}" ] && OUTPUT_FILE="OSV-pii-$(date +"%s").log"
   [ -z "${OUTPUT_DIR}" ] && OUTPUT_DIR="$(pwd)"
}

# Verbose output useful for debugging purposes
function print_debug( )
{
   [ "${DEBUG}" = "enabled" ] && echo "$@"
}

# Print informative messages to stdout
function print_info( )
{
   echo "$@"
}

# Print error messages to stderr
function print_error( )
{
   echo "$@" >&2
}

# Read node.cfg parameters
function get_node_cfg( )
{
   local key="$1"

   [ -z "${key}" ] && print_error "No key to check on ${NODE_CFG}" && return 1

   [ -f  ${NODE_CFG} ] || { print_error "${NODE_CFG} not found!"; return 1; }

   if [ -x "${FCFGREAD_TOOL}" ];
   then
      ${FCFGREAD_TOOL} "${NODE_CFG}" "${key}" 2>/dev/null || \
      { print_error "Unable to read param <${key}> from ${NODE_CFG}"; return 1; }
   else
      local value=""
      if value=$(grep -w "^${key}" "${NODE_CFG}");
      then
         echo "${value}" | awk -F': ' '{print $2}'
      else
         print_error "Unable to read param <${key}> from ${NODE_CFG} with fallback method" && return 1
      fi
   fi
}

# Extract customer-created users.
# During installations / upgrades and before any import,
# a list is created with the default users (OSV + OS/system).
# A hard-coded list is provided as well in case the above
# file-list is missing for some reason.
# The list will be used as a reference and any user out of
# this list will be considered as a customer-created user.
function extract_custom_users( )
{

   print_info "Extracting custom users..."

   DEFAULT_USERS="bin                \
                  daemon             \
                  messagebus         \
                  named              \
                  nscd               \
                  ntp                \
                  openslp            \
                  pesign             \
                  polkitd            \
                  postfix            \
                  root               \
                  rpc                \
                  scard              \
                  sshd               \
                  statd              \
                  systemd-timesync   \
                  systemd-bus-proxy  \
                  openbranchuser     \
                  ftpsecure          \
                  nuance             \
                  webad              \
                  hipatham           \
                  hipathcol          \
                  wwwrun             \
                  rtp                \
                  solid              \
                  srx                \
                  cdr                \
                  sysad              \
                  superad            \
                  secad              \
                  dbad               \
                  at                 \
                  nobody             \
                  man                \
                  sym"

    [ -f ${PASSWD_FILE} ] || { print_error "${PASSWD_FILE} not found!"; return 1; }

    [ -s ${USERS_REFERENCE_FILE} ] && DEFAULT_USERS=$(cat ${USERS_REFERENCE_FILE})

    local whitelist=$(echo "${DEFAULT_USERS}" | xargs | sed 's/ /|/g')

    local all_users=$(awk -F':' '{print $1}' ${PASSWD_FILE})

    local custom_users=$(echo "${all_users}" | grep -Ev "${whitelist}") 

    if [ ! -z "${custom_users}" ]
    then
       echo "${custom_users}" >>${TMPFILE}
       print_debug -e "Found users:\n${custom_users}"
    else
       print_debug "Not found any custom users."
    fi
}

# Extract customer-defined hosts.
# Any customer-defined host is expected to get added under a specific borderline.
# Any host defined under that line will be considered as customer-defined.
# Node names are considered PIIs.
function extract_custom_hosts( )
{
   print_info "Extracting custom hosts..."

   [ -f ${HOSTS_FILE} ] || { print_error "${HOSTS_FILE} not found!"; return 1; }

   borderline="Please add new hosts under this line"

   local custom_hosts=$(sed -n "/${borderline}/,$ p" ${HOSTS_FILE} | \
                        egrep -o '^[^#][^\#]+' | egrep -o '[[:blank:]]+.*' | xargs | sed 's/ /\n/g')

   if [ ! -z "${custom_hosts}" ]
   then
      echo "${custom_hosts}" >>${TMPFILE}
      print_debug -e "Found hosts:\n${custom_hosts}"
   fi

   local node1_name=$(get_node_cfg node_1_name) || return 1
   printf "%s\n" "${node1_name}" >>${TMPFILE} && print_debug "${node1_name}"

   if [ "$(get_node_cfg test_bed)" = "cluster" ]
   then
      local node2_name=$(get_node_cfg node_2_name) || return 1
      printf "%s\n" "${node2_name}" >>${TMPFILE} && print_debug "${node2_name}"
   fi
}

# Helper function than ignores any comments (starting with #)
# and finds any FQDN-like matches.
function find_fqdns_in_file( )
{
   filename=$1
   [ -f ${filename} ] || return 1
   egrep -v "^[[:blank:]]*#" "${filename}" | \
   egrep -o '[a-zA-Z0-9]+[a-zA-Z0-9\-\.]*\.[a-zA-Z]{2,}'
   return $?
}

# Extract FQDNs.
# There is a list of files that may contain FQDNs.
function extract_fqdns( )
{
   print_info "Extracting FQDNs..."

   LIST_TO_CHECK_FQDNS="/etc/resolv.conf                     \
                        /etc/hiq8000/node.cfg                \
                        /etc/named.d/named.openscape.include \
                        /var/lib/named/master/openscape/*"

   local fqdns

   for file in ${LIST_TO_CHECK_FQDNS};
   do
      fqdns=$(find_fqdns_in_file ${file})

      if [ ! -z "${fqdns}" ]
      then
         echo "${fqdns}" >>${TMPFILE}
         print_debug -e "In ${file}:\n${fqdns}"
      fi
   done
}

# Show help message
function show_help( )
{
   echo -e "Usage: ${program_name} [OPTION]...\n"
   echo "Description:"
   echo -e "   This program generates a list of <static identifications> that will be used to obfuscate PIIs.\n"
   echo "OPTIONS:"
   echo "-h, --help"
   echo -e "   Print this help menu and exit.\n"
   echo "-d, --dir"
   echo -e "   Specify the output directory of the list. (default: <current directory>)\n"
   echo "-f, --filename"
   echo -e "   Specify the filename of the list. (default: OSV-pii-<timestamp>.txt)\n"
   echo "-t, --tar"
   echo -e "   Add the generated list to a tar archive\n"
   echo "--debug"
   echo -e "   Print debug messages that describe all actions in detail"
}

# Parse input args
function parse_input_options( )
{
   local program_name=$(basename "$0")

   if [ $# -gt 0 ]
   then
      local option
      while [[ $# -gt 0 ]]
      do
         option=$1
         case ${option} in
            -h|--help)
               show_help "${program_name}"
               exit 0
               ;;
            -d|--dir)
               if [ -f ${2} ];
               then
                  OUTPUT_DIR="$(dirname ${2})"
               else
                  OUTPUT_DIR="${2}"
               fi
               mkdir -p ${OUTPUT_DIR}
               shift
               ;;
            -f|--filename)
               OUTPUT_FILE="$(basename ${2})"
               shift
               ;;
            -t|--tar)
               TAR_FILE="${2}"
               shift
               ;;
            --debug)
               DEBUG='enabled'
               ;;
            *)
               print_info "Unknown option ${option}"
               exit 1
         esac
         shift
      done
   fi
}

# Exclude any known false-positive SIs.
# The implemented FQDN extraction functionality is file/parameter-agnostic.
# The same regex is applied to all files appearing on the list.
# As a matter of fact, false positives with FQDN-like strings are possible.
# This "false_positives" list will filter-out those strings.
function exclude_known_identifiers( )
{
   print_info "Excluding known identifiers..."
   local sanefile=$(mktemp -p /tmp)

   #This can be a list with known false-positive patterns
   local false_positives="V[0-9]+.*ALL"
   egrep -v "${false_positives}" ${TMPFILE} >${sanefile}

   local excluded_identifiers=$(diff --changed-group-format='%<%>' \
                                     --unchanged-group-format=''   \
                                     ${TMPFILE} ${sanefile})

   [ ! -z "${excluded_identifiers}" ] && print_debug -e "Excluded identifiers:\n${excluded_identifiers}"

   cp -p ${sanefile} ${TMPFILE} && rm -f ${sanefile}
}

# Generate the static list and add it to the desired location.
# The location can be a specific directory or a tar archive.
function create_output( )
{
   if sort -u ${TMPFILE} -o "${OUTPUT_DIR}/${OUTPUT_FILE}";
   then
      print_info "${OUTPUT_DIR}/${OUTPUT_FILE} generated successfully!"
   else
      print_error "Failed to generate the list" && return 1
   fi

   if [ ! -z "${TAR_FILE}" ]
   then
      print_info "Adding ${OUTPUT_FILE} to ${TAR_FILE} ..."
      if ! tar rf ${TAR_FILE} -C "${OUTPUT_DIR}" "${OUTPUT_FILE}";
      then
         print_error "Failed to add ${OUTPUT_DIR}/${OUTPUT_FILE} to ${TAR_FILE}"
         return 1
      fi
      rm -f "${OUTPUT_DIR}/${OUTPUT_FILE}"
   fi
}

# Cleanup function
function cleanup( )
{
   [ -z ${TMPFILE} ] || rm -f ${TMPFILE}
}

function setup_trap( )
{
   trap cleanup EXIT HUP QUIT TERM
}

### Main ###
setup_trap
parse_input_options "$@"
init
extract_custom_users
extract_custom_hosts
extract_fqdns
exclude_known_identifiers
create_output

