# The plugin configuration file
###############################
PLUGIN_CONF_FILE="adaptive-ban.conf"

# Preinit return value for success
PLUGIN_RET_VAL=0

TEMPFILE="/var/tmp/aif_adaptive_ban.temp"

# Check sanity of environment
adaptive_ban_helper_sanity_check()
{
  # Check whether chains exists
  if ! check_for_chain ADAPTIVE_BAN_CHAIN; then
    echo "** ERROR: ADAPTIVE_BAN_CHAIN does not exist! **" >&2
    return 1
  fi

  if ! check_for_chain ADAPTIVE_BAN_DROP_CHAIN; then
    echo "** ERROR: ADAPTIVE_BAN_DROP_CHAIN does not exist! **" >&2
    return 1
  fi

  if [ ! -f "$ADAPTIVE_BAN_FILE" ]; then
    echo "** ERROR: Input log file $ADAPTIVE_BAN_FILE does not exist! **" >&2
    return 1
  fi

  return 0
}


adaptive_ban_helper_do_work()
{
  local filetime=0

  if [ "$filetime" != "$(date -r "$ADAPTIVE_BAN_FILE" "+%s")" ]; then
    filter "$ADAPTIVE_BAN_FILE" "$ADAPTIVE_BAN_COUNT" "$ADAPTIVE_BAN_TYPES"

    filetime="$(date -r "$ADAPTIVE_BAN_FILE" "+%s")"
  fi

  return 0
}


filter()
{
  local file="$1" count="$2" type types PREFIX HOST IFS

  shift 2
  types="$@"

  # regex to pull out offending IPv4/IPv6 address
  #
  HOST="([0-9a-fA-F:.]{7,})"

  unset IFS
  for type in $types; do

    # regex match the start of the syslog string
    #
    PREFIX=".*${type}\[[0-9]*]:[[:space:]]*"

    case "$type" in
      sshd) filter_sshd "$file" "$PREFIX" "$HOST"
         ;;
      asterisk) filter_asterisk "$file" "$PREFIX" "$HOST"
         ;;
      lighttpd) filter_lighttpd "$file" "$PREFIX" "$HOST"
         ;;
      mini_httpd) filter_mini_httpd "$file" "$PREFIX" "$HOST"
         ;;
      pptpd) filter_pptpd "$file" "$PREFIX" "$HOST"
         ;;
      *) echo "Unsupported type \"$type\"" >&2
         continue
         ;;
    esac
    if [ $? -ne 0 ]; then
      echo "Filter error for type \"$type\"" >&2
    else
      count_attempts_then_ban "$count" "$type"
    fi
    rm -f "$TEMPFILE"
  done
}


filter_sshd()
{
  local file="$1" PREFIX="$2" HOST="$3"

  sed -n -r -e "s/^${PREFIX}Failed (password|publickey) for .* from ${HOST}( port [0-9]*)?( ssh[0-9]*)?$/\2/p" \
            -e "s/^${PREFIX}[iI](llegal|nvalid) user .* from ${HOST}[[:space:]]*$/\2/p" \
               "$file" >"$TEMPFILE"
}

filter_asterisk()
{
  local file="$1" PREFIX="$2" HOST="$3"

  sed -n -r -e "s/^${PREFIX}NOTICE.* .*: Registration from '.*' failed for '${HOST}' - Wrong password$/\1/p" \
            -e "s/^${PREFIX}NOTICE.* .*: Registration from '.*' failed for '${HOST}' - No matching peer found$/\1/p" \
            -e "s/^${PREFIX}NOTICE.* .*: Registration from '.*' failed for '${HOST}' - Username\/auth name mismatch$/\1/p" \
            -e "s/^${PREFIX}NOTICE.* .*: Registration from '.*' failed for '${HOST}' - Device does not match ACL$/\1/p" \
            -e "s/^${PREFIX}NOTICE.* '${HOST}' - Dialplan Noted Suspicious IP Address$/\1/p" \
            -e "s/^${PREFIX}NOTICE.* ${HOST} failed to authenticate as '.*'$/\1/p" \
            -e "s/^${PREFIX}NOTICE.* .*: No registration for peer '.*' \(from ${HOST}\)$/\1/p" \
            -e "s/^${PREFIX}NOTICE.* .*: Host ${HOST} failed MD5 authentication for '.*' \(.*\)$/\1/p" \
               "$file" >"$TEMPFILE"
}

filter_lighttpd()
{
  local file="$1" PREFIX="$2" HOST="$3"

  sed -n -r -e "s/^${PREFIX}.* password doesn't match for .* IP: ${HOST}[[:space:]]*$/\1/p" \
            -e "s/^${PREFIX}.* get_password failed, IP: ${HOST}[[:space:]]*$/\1/p" \
               "$file" >"$TEMPFILE"
}

filter_mini_httpd()
{
  local file="$1" PREFIX="$2" HOST="$3"

  sed -n -r -e "s/^${PREFIX}${HOST} authentication failure - access denied$/\1/p" \
               "$file" >"$TEMPFILE"
}

filter_pptpd()
{
  local file="$1" PREFIX="$2" HOST="$3" PPP_PREFIX=".*pppd\[[0-9]*]:[[:space:]]*"

  sed -n -r -e "/^${PPP_PREFIX}.* failed CHAP authentication$/ {N;N;N;N;N;N;N;N;N;N;N;N;N;N;N;\
               s/^.*\n${PREFIX}CTRL: Client ${HOST} control connection finished\n.*$/\1/p}" \
               "$file" >"$TEMPFILE"
}

count_attempts_then_ban()
{
  local count="$1" type="$2" line host IFS

  # Remove possible IPv4 port numbers, IPv4:PORT -> IPv4
  sed -i -r -e 's/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):[0-9]+$/\1/' "$TEMPFILE"

  IFS=$EOL
  for line in $(sort "$TEMPFILE" | uniq -c); do
    if [ "$(echo "$line" | awk '{ print $1; }')" -ge "$count" ]; then
      host="$(echo "$line" | awk '{ print $2; }')"
      ban_host "$host" "$type"
    fi
  done
}

ban_host()
{
  local host="$1" type="$2"

  get_numeric_ip_version "$host"
  case $? in
  4)
    if ! ip4tables -n -L ADAPTIVE_BAN_CHAIN | grep -q " ${host//./\.}[/ ]"; then
      ip4tables -A ADAPTIVE_BAN_CHAIN -s $host -j ADAPTIVE_BAN_DROP_CHAIN
      if [ $? -eq 0 ]; then
        echo "Banned IPv4 host: $host  Filter type: $type" >&2
      fi
    fi
    ;;
  6)
    if [ "$IPV6_SUPPORT" = "1" ]; then
      if ! ip6tables -n -L ADAPTIVE_BAN_CHAIN | grep -q " ${host}[/ ]"; then
        ip6tables -A ADAPTIVE_BAN_CHAIN -s $host -j ADAPTIVE_BAN_DROP_CHAIN
        if [ $? -eq 0 ]; then
          echo "Banned IPv6 host: $host  Filter type: $type" >&2
        fi
      fi
    fi
    ;;
  esac
}

check_for_chain()
{
  local err

  ip4tables -n -L "$1" >/dev/null 2>&1
  err=$?

  if [ "$IPV6_SUPPORT" = "1" -a $err -eq 0 ]; then
    ip6tables -n -L "$1" >/dev/null 2>&1
    err=$?
  fi

  return $err
}

############
# Mainline #
############

# Check where to find the config file
CONF_FILE=""
if [ -n "$PLUGIN_CONF_PATH" ]; then
  CONF_FILE="$PLUGIN_CONF_PATH/$PLUGIN_CONF_FILE"
fi

# Check if the config file exists
if [ ! -e "$CONF_FILE" ]; then
  echo "** ERROR: Config file \"$CONF_FILE\" not found! **" >&2
  PLUGIN_RET_VAL=1
else
  # Source the plugin config file
  . "$CONF_FILE"

  # Only proceed if environment ok
  if ! adaptive_ban_helper_sanity_check; then
    PLUGIN_RET_VAL=1
  else
    # Parse rules
    if ! adaptive_ban_helper_do_work; then
      PLUGIN_RET_VAL=1
    fi
  fi
fi
