#!/bin/bash

#----------------------------------------------------------------------
# copyright (C) 2022 Koozali SME Server
#               
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#               
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#               
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
# 
#----------------------------------------------------------------------
#set -x
event=$1
fqdn=$2
todo=$3
domainlist=""

case $todo in
  disabled|enabled)
        #
        ;;
  *)
	todo="all"
	;;
esac

get_dns() {
    #list from pihole install script; only ipv4 ones
    #removed as too slow or dysfunc 84.200.69.80 
    local dns=$(/sbin/e-smith/db configuration getprop letsencrypt dnscheck||echo "https://cloudflare-dns.com/dns-query,8.8.8.8,8.8.4.4,1.1.1.1,1.0.0.1,208.67.222.222,208.67.220.220,4.2.2.1,4.2.2.2,8.20.247.20,84.200.70.40,9.9.9.10,149.112.112.10")
    # from https://stackoverflow.com/questions/10586153/how-to-split-a-string-into-an-array-in-bash
    IFS=', ' read -r -a DNS <<< "$dns"
    RANDOM=$$$(date +%s)
    x=1
    #TODO: if only one element skip the random selection
    while [ $x -le 5 ]; do
	lmydns=${DNS[ $RANDOM % ${#DNS[@]} ]}
	if [[ $lmydns == http* ]] ; then
	  curl -s $lmydns >/dev/null
	  if [[ "$?" == "0" ]]; then
	    break
	  fi
	else
	  nc -z  -w2 $lmydns 53
	  if [[ "$?" == "0" ]]; then
	    break
	  fi
	fi
	x=$(( $x + 1 ))
	#in case of failure defaulting on a dns over https after 5
	lmydns="https://cloudflare-dns.com/dns-query"
    done
    echo $lmydns
    return 0
}

get_domain_ip() {
    local max_attempts=4
    local attempt=1
    local empty_count=0
    local ip=""
    local ldns=$mydns

    while [ $attempt -le $max_attempts ]; do
	ip=$(/usr/bin/q @"$ldns" A "$DOMAIN" --timeout=15s -f json 2>/dev/null)
	local q_status=$?
	# if q return -1 there is a technical issue, value is not to be used.
        if [ $q_status -eq 0 ]; then
             THISDOMIP=$(echo $ip | jq -r 'first(.[].replies[]?.answer[]?.a | select(. != null)) // "null"' 2>/dev/null)
	    #THISDOMIP=$(echo $ip | jq -r 'first(.[].replies[].answer[].a | select(. != null)) // null')
	    #THISDOMIP=$(echo "$ip" | jq -r 'first(.[].replies[].answer[].a | select(. != null)) // "null"')
	    #return 0
	    if [ "$THISDOMIP" != "null" ]; then
                # Found IP : instant success
                return 0
            else
                # no technical issue but IP empty or null; new dns
                ((empty_count++))
		ldns=$(get_dns)
                if [ $empty_count -ge 3 ]; then
                    # 3 try, still empty
                    THISDOMIP=""
                    return 1
                fi
            fi
        fi
        ((attempt++))
	# if this is the second iteration with no ip, try another dns server
	if [ $attempt -gt 2 ]; then
            ldns=$(get_dns)
        fi
        sleep 1 #wait 1 second between tries 
    done
    # unable to check the domain due to multiple technical failure
    # better not removing it and risk to fail the cert renewal
    THISDOMIP="SKIP"
    return 1
}


if [[ ! -z "$fqdn"  ]]
  then
  # if fqdn not empty just use this one
  domainlist="$fqdn"
elif [[ ! -z "$event" ]] && [[ "$event" == *"domain"* ]]
  then
  # else if event *domains* => all domains
  domainlist=$(perl -Mesmith::DomainsDB -e 'my $domains = esmith::DomainsDB->open; my @DOM = $domains->get_all_by_prop(type=>"domain"); print( join(" " , map { $_->key } @DOM)) ')
elif [[ ! -z "$event" ]] && [[ "$event" == *"host"* ]]
  then
  # else if event *hosts* => all hosts
  domainlist=$(perl -Mesmith::HostsDB -e 'my $domains = esmith::HostsDB->open; my @DOM = $domains->get_all_by_prop(type=>"host"); print( join(" " , map { $_->key } @DOM)) ')
else
  # else all domain and hosts
  domains=$(perl -Mesmith::DomainsDB -e 'my $domains = esmith::DomainsDB->open; my @DOM = $domains->get_all_by_prop(type=>"domain"); print( join(" " , map { $_->key } @DOM)) ')
  hosts=$(perl -Mesmith::HostsDB -e 'my $domains = esmith::HostsDB->open; my @DOM = $domains->get_all_by_prop(type=>"host"); print( join(" " , map { $_->key } @DOM)) ')
  domainlist="$domains $hosts"
fi
domainlist=$(echo $domainlist |sort|uniq)

mydns=$(get_dns)
echo "External DNS Server  : $mydns"

MYFORCEDIP=$(/sbin/e-smith/db configuration getprop letsencrypt ExternalIP)
# check if gateway or server only
MYMODE=$(/sbin/e-smith/db configuration get SystemMode)
# check our external ip if gateway, internal else
LOCALIP=$(/sbin/e-smith/db configuration get InternalIP)
MYIP=$LOCALIP
# check the ip suggested by external world that point to us.
MYEXTIP=$(/usr/sbin/e-smith/getmyip)

# Regex pour IPv4 et IPv6
IPV4_REGEX="^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
IPV6_REGEX="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"

if [[ $MYEXTIP =~ $IPV4_REGEX ]]; then
    #echo "$MYEXTIP is a valid IPv4."
    true
elif [[ $MYEXTIP =~ $IPV6_REGEX ]]; then
    #echo "$MYEXTIP is a valid IPv6."
    true
else
    echo "Error : '\$MYEXTIP' not a valid IP."
    exit 0
fi

if [ -z "$MYFORCEDIP" ]; then
  # we do not expect that a server-only has an ip routable on internet as firewall is not designed for that
  # but in case, we handle the situation as for ExternalIP in servergateway mode, please write ad hoc rules for masq if you do so...
  if [[ "$MYMODE" == "servergateway"  ]] ; then
    MYIP=$(/sbin/e-smith/db configuration get ExternalIP);
  fi
  if [[ "$MYIP" != "$MYEXTIP"  ]] ; then
    echo "External Interface IP: $MYIP"
    echo "Detected Wan IP      : $MYEXTIP"
    echo "You seem to be behind a firewall, using the external IP obtained with our test $MYEXTIP"
    MYIP=$MYEXTIP
  fi
else
  MYIP=$MYFORCEDIP
fi

echo "============================================================================================="
OUTPUT="Domain\tStatus\tMYIP\tA\tLE_status\tLE_previous"
# TODO all : check disabled and enabled ; active : check enabled and undef only

for DOMAIN in  $domainlist 
   do
   # is it a host, a domain or should we ignore it
   TYPE=$(/sbin/e-smith/db domains gettype $DOMAIN || /sbin/e-smith/db hosts gettype $DOMAIN )
   if [[ "$TYPE" == "domain" ]] ; then 
	TYPE="domains"
   elif [[ "$TYPE" == "host" ]] ; then
        TYPE="hosts"
   else
	echo "$DOMAIN is not in domains and not in hosts ($TYPE)"
	continue
   fi
   # do we have a priority ?
   currentstate=$(/sbin/e-smith/db $TYPE getprop $DOMAIN letsencryptSSLcert || echo "disabled")
   if [ "$currentstate" != "$todo" -a "$todo" != "all" ] ; then
	#echo "$DOMAIN skipping, only checking $todo $TYPE"
	continue
   fi
   # https://stackoverflow.com/questions/15268987/bash-based-regex-domain-name-validation 
   if ( !  echo $DOMAIN| grep -P -q '(?=^.{4,253}$)(^(?:[a-zA-Z0-9](?:(?:[a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$)' -Z ) ; then
	if [[ "$currentstate" == "disabled" ]]; then continue; fi
        echo "$DOMAIN is not a RFC compliant domain, disabling"
	/sbin/e-smith/db $TYPE setprop $DOMAIN letsencryptSSLcert disabled
        /sbin/e-smith/db $TYPE delprop $DOMAIN letsencryptMYIP
        continue
   else
       if [[ "$TYPE" == "hosts" ]] ; then
            if ( !  echo $DOMAIN| grep -P -q '^[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' -Z ) ; then
                  echo "$TYPE $DOMAIN missing a subdomain part, disabling"
		  /sbin/e-smith/db $TYPE setprop $DOMAIN letsencryptSSLcert disabled
                 /sbin/e-smith/db $TYPE delprop $DOMAIN letsencryptMYIP
	    fi
       fi
   fi
    
   THISDOMIP=""
   get_domain_ip
   previous=$(/sbin/e-smith/db $TYPE getprop $DOMAIN letsencryptSSLcert||echo 'undefined');
   
   # technical failure
    if [[ "$THISDOMIP" == "SKIP"  ]]
        then
	OUTPUT="$OUTPUT\n$DOMAIN\tSKIP\t$MYIP\tSKIP\t$previous\t$previous"
        continue
   fi	
   # if it does not resolve, next
   if [[ "$THISDOMIP" == ""  ]]
        then
        OUTPUT="$OUTPUT\n$DOMAIN\tNOK\t$MYIP\tnoip\tdisabled\t$previous"
        /sbin/e-smith/db $TYPE setprop $DOMAIN letsencryptSSLcert disabled
        /sbin/e-smith/db $TYPE delprop $DOMAIN letsencryptMYIP
        continue;
   fi
   if [[ "$MYIP" == "$THISDOMIP" ]]
        then
        OUTPUT="$OUTPUT\n$DOMAIN\tOK\t$MYIP\t$THISDOMIP\tenabled\t$previous"
        /sbin/e-smith/db $TYPE setprop $DOMAIN letsencryptSSLcert enabled letsencryptMYIP $THISDOMIP
   else
        OUTPUT="$OUTPUT\n$DOMAIN\tNOK\t$MYIP\t$THISDOMIP\tdisabled\t$previous"
        /sbin/e-smith/db $TYPE setprop $DOMAIN letsencryptSSLcert disabled letsencryptMYIP $THISDOMIP
   fi
   sleep 1
done
printf "%b" $OUTPUT |column -t -s $'\t' 

