#!/bin/bash
#
# script: rc.library.source
#
# Library used by nfsd, ntpd, rpc, samba, nginx, sshd, avahidaemon, show_interfaces
#
# Bergware - created for Unraid OS, December 2023
# Bergware - updated May 2025

WIREGUARD="/etc/wireguard"
NETWORK_INI="/var/local/emhttp/network.ini"
NETWORK_EXTRA="/boot/config/network-extra.cfg"

var(){
  if [[ $# -eq 3 ]]; then
    [[ -r "$3" ]] && sed -n "/^\[$1\]\$/,/^\[/p" "$3" | grep -Pom1 "^$2=\"\K[^\"]+"
  elif [[ $# -eq 2 ]]; then
    [[ -r "$2" ]] && grep -Pom1 "^$1=\"\K[^\"]+" "$2"
  fi
}

ipv(){
  local t=${1//[^:]}
  [[ ${#t} -le 1 ]] && echo 4 || echo 6
}

this(){
  local MAP ADDR
  case $CALLER in
  'avahi')
    grep -Pom1 "^$1=\K.*" $CONF ;;
  'smb')
    grep -Pom1 "^$1 = \K.*" $CONF ;;
  'ntp'|'ssh')
    grep -Po "^$1 \K\S+" $CONF | tr '\n' ' ' | sed 's/ $//' ;;
  'nfs')
    grep -Pom1 "^RPC_NFSD_OPTS=\"$OPTIONS \K[^\"]+" $NFS ;;
  'rpc')
    grep -Pom1 "^RPCBIND_OPTS=\"\K[^\"]+" $RPC ;;
  'nginx')
    grep -Po "^NGINX_BIND=\"\K[^\"]+" ${INI%.*} 2>/dev/null ;;
  esac
}

scan(){
  grep -Pom1 "^$1=\"?\K[^\"]+" $2
}

good(){
  local TAG BAD
  for TAG in ${BIND[@]}; do
    [[ -z $1 || $1 == $TAG || ${1:0:4} == fe80 ]] && BAD=1 && break
  done
  echo $BAD
}

show(){
  case $# in
    1) ip -br addr show scope global primary -deprecated to $1 2>/dev/null | awk '{gsub("@.+","",$1);print $1;exit}' ;;
    2) ip -br addr show scope global primary -deprecated $1 $2 2>/dev/null | awk '{$1=$2="";print;exit}' | sed -r 's/ metric [0-9]+//g' ;;
  esac
}

unmask(){
  if [[ $CALLER != smb ]]; then
    # remove netmask
    echo ${1/\/*}
  elif [[ $(ipv $1) == 4 ]]; then
    # replace netmask
    echo ${1/\/32/\/24}
  elif [[ -z $DENY6 ]]; then
    # IPv6 only when netbios is disabled
    echo ${1/\/128/\/64}
  fi
}

remove(){
  local i ADDR
  for ADDR in $@; do
    ADDR=$(unmask $ADDR)
    [[ -z $ADDR ]] && continue
    for i in ${!BIND[@]}; do
      [[ $ADDR == ${BIND[$i]} ]] && unset 'BIND[i]'
    done
  done
}

isname(){
  [[ -z ${1//[^.:]} || ${1//[^.:]} == . ]] && return 0 || return 1
}

add_name(){
  local NET
  for NET in $include_interfaces; do
    if $(isname $NET); then
      # NET is an interface name, validate
      [[ -n $(show dev $NET) && -z $(good $NET) ]] && BIND+=($NET)
    else
      # NET is an IP address, convert to name
      NET=$(show $NET)
      [[ -n $NET && -z $(good $NET) ]] && BIND+=($NET)
    fi
  done
  for NET in $exclude_interfaces; do
    if $(isname $NET); then
      # NET is an interface name
      remove "$NET"
    else
      # NET is an IP address, convert to name
      remove "$(show $NET)"
    fi
  done
}

add_addr(){
  local NET MAP ADDR
  for NET in $include_interfaces; do
    if $(isname $NET); then
      # NET is an interface name, get IP addresses
      MAP="$(show dev $NET)"
    else
      # NET is an IP address, validate
      MAP="$(show to $NET)"
    fi
    for ADDR in $MAP; do
      ADDR=$(unmask $ADDR)
      [[ -z $(good $ADDR) ]] && BIND+=($ADDR) || continue
      [[ $(ipv $ADDR) == 4 ]] && IPV4=yes || IPV6=yes
    done
  done
  for NET in $exclude_interfaces; do
    if $(isname $NET); then
      # NET is an interface name, get IP addresses
      remove "$(show dev $NET)"
    else
      # NET is an IP address
      remove "$(show to $NET)"
    fi
  done
}

check(){
  # quick check
  [[ -n $BIND ]] && return 0;
  # preset return values
  BIND=(); IPV4=no; IPV6=no; FAMILY=any;
  # read active interfaces (including wireguard tunnels)
  while IFS=$'\n' read -r NET; do
    NET=($NET)
    if [[ ${NET:0:2} == wg ]]; then
      # skip wireguard tunnel if NTP or VPN type
      [[ $CALLER == ntp || $(grep -Pom1 '^TYPE:1="\K[^"]+' $WIREGUARD/$NET.cfg) == 8 ]] && continue
    fi
    for ADDR in ${NET[@]/$NET}; do
      ADDR=$(unmask $ADDR)
      if [[ "ntp avahi show" =~ "$CALLER" ]]; then
        [[ -z $(good $NET) ]] && BIND+=($NET)
      else
        [[ -z $(good $ADDR) ]] && BIND+=($ADDR) || continue
      fi
      [[ $(ipv $ADDR) == 4 ]] && IPV4=yes || IPV6=yes
    done
  done <<< $(ip -br addr show scope global primary -deprecated | awk '$1~"^(br|bond|eth|wlan|wg)[0-9]+(.[0-9]+)?" && $3!="" {gsub("@.+","",$1);$2="";print}' | sed -r 's/ metric [0-9]+//g' | sort)
  # add loopback interface
  if [[ "smb nfs" =~ "$CALLER" ]]; then
    [[ $IPV4 == yes ]] && BIND+=(127.0.0.1)
    [[ $IPV6 == yes ]] && BIND+=(::1)
  fi
  # add user defined interfaces
  if [[ -f $NETWORK_EXTRA && $CALLER != ntp ]]; then
    . <(fromdos <$NETWORK_EXTRA)
    [[ "avahi show" =~ "$CALLER" ]] && add_name || add_addr
  fi
  if [[ $CALLER == ssh ]]; then
    # BIND stays array
    BIND=(${BIND[@]})
    [[ $IPV4 == yes && $IPV6 == no ]] && FAMILY=inet
    [[ $IPV6 == yes && $IPV4 == no ]] && FAMILY=inet6
  else
    # convert array to string
    BIND=${BIND[@]}
    [[ $CALLER == avahi ]] && BIND=${BIND// /,}
  fi
  return 0
}
