#!/bin/bash
# vim:tabstop=3:expandtab:shiftwidth=3

######################### Automatic option processing #########################
#                                                                             #
#  Variables in lowecase, starting with _ are processed as options.           #
#  Only the first character can be Upper-case ;-)                             #
#  Checking [ "$__switch" = "yes" ] will be a switch; "yes" is ON "" is OFF   #
#  Checking [ ${#__values} -gt 0 ]  will be an array with values              #
#                                                                             #
###############################################################################

# Store original $* arguments
aRGs="$*"

# Collect VAL opts
vALo="$(grep -v '^#' $BASH_SOURCE | tr '$' '\n' | grep '^{#_[_a-zA-Z][_a-z]\?'|\
        sed 's|^{#\([_a-zA-Z]\+\)[^_a-z].*|\1|' | sort -u)"
nOTy="$(sed -e 's/$/$/' -e 's/\s/$|^/g' -e 's/^/^/' <<< "$(echo $vALo)")"
yESo="$(grep -v '^#' $BASH_SOURCE | tr '$' '\n' | grep '^_[_a-zA-Z][_a-z]\?' |\
        sed 's|^\([_a-zA-Z]\+\)[^_a-z].*|\1|' | grep -E -v "$nOTy" | sort -u)"

# Get the -[-] options into _[_] vars
while [ "${1:0:1}" = '-' ]
do
   opT="$(sed 's|=.*||' <<< "$1")"
   oPT="$(sed 's|-|_|g' <<< "$opT")"
   if grep -q "^${oPT}\$" <<< "$yESo"
   then
      eval "$oPT=yes" 2> /dev/null
   elif grep -q "^${oPT}\$" <<< "$vALo"
   then
      iSvAL="$(grep "^${opT}=" <<< "$1" | sed "s|^${opT}=||")"
      if [ ${#iSvAL} -eq 0 ] && [ $# -gt 0 ]
      then
         shift
         eval "$oPT[\${#${oPT}[@]}]='$1'" 2> /dev/null
      else
         eval "$oPT[\${#${oPT}[@]}]='$iSvAL'" 2> /dev/null
      fi
   fi
   unset opT oPT iSvAL
   shift
done # processing the options, __have_fun

# Simple help example with showing the options
if [ "$__help" = "yes" ] || [ "$_h" = "yes" ] || [ $# -lt 1 ]
then
   echo
   echo "  Usage: $(basename $BASH_SOURCE) [OPTIONS] URL [DIR]"
   echo
   echo "  Clone https git repo using a client CERT (and client key) file"
   echo
   echo "  Options:"
   echo
   (echo "$yESo" | sort ; echo "$vALo" | sort | sed 's|$|[=]VALUE|') | \
   grep -v '^___' | sed -e 's|_|-|g' -e 's|^|   |' # hide --- opts + indent
   echo
   exit 0
fi

# Main

Cols=$(tput cols 2> /dev/null | sed 's|^$|80|')

CutLine()
{
   if [ "$_v" = "yes" ]
   then
      cat
   else
      cut -c -$Cols | sed -E 's|^(.{'"$(($Cols-1))"'}).|\1>|'
   fi
}

# Store certs in
DotCertDir='.https,cert'

# Collect issues before we exit
unset Exit

if ! grep -q '^https://' <<< "$1"
then
   echo "! [FAIL] URL missing 'https://'"
   Exit=1
fi

# Check for second argument
DIR="$2"

if [ ${#__remote_add} -gt 0 ]
then
   if [ ! -f .git/config ] || ! git branch --show-current &> /dev/null
   then
      echo "! [FAIL] $(basename $(pwd)) is not a git dir"
      exit 1
   elif git remote 2> /dev/null | grep -q "^${__remote_add}\$" 
   then
      echo "! [FAIL] Remote '${__remote_add}' already in use"
      exit 1
   elif [ ${#DIR} -gt 0 ]
   then
      echo "! [CHECK] Second argument ($DIR) not used in --remote-add"
      exit 1
   fi
fi

# Fill in DIR if not provided as second argument
if [ ${#Exit} -eq 0 ] && [ ${#DIR} -eq 0 ]
then
   DIR="$(basename "$(sed 's|https://[^/]\+/\?||' <<<"$1")")"
   if [ ${#DIR} -gt 0 ]
   then
      echo "# Using $DIR as target dir"
   else
      echo "! [FAIL] Unable to determine target dir from url ($1)"
      Exit=1
   fi
fi

if [ ${#Exit} -eq 0 ]
then
   if grep -q '/' <<< "$DIR"
   then
      echo "! [FAIL] DIR cannot be (in) a directory tree"
      Exit=1
   elif [ ${#DIR} -gt 0 ] && [ -d "$DIR" ]
   then
      if [ $(find "$DIR" 2> /dev/null | wc -l) -eq 1 ]
      then
         echo "# [OK] DIR $DIR already exists, but is empty, reusing"
      elif [ ${#__remote_add} -gt 0 ]
      then
         if [ ! -f "$DIR/.git/config" ]
         then
            echo "! [FAIL] $DIR is not a git directory, unable to add cert"
            exit 1
         fi
      else
         echo "! [FAIL] DIR $DIR already exists and contains data"
         Exit=1
      fi
   else
      Fence='YWt0LXNlYS90cmVjLXQtbmFsLWsuY2dpPw=='
      Seed="$(openssl x509 -subject -noout -in "$DotCertDir/$(sed \
                      -e 's|^https://||' -e 's|/.*||' <<< "$1"),$DIR,cert" \
                      2> /dev/null | \
              grep '^subject=' | \
              sed -e 's|.*\sOU=||' -e 's|,.*emailAddress=||')"
      if [ ${#Seed} -eq 0 ]
      then
         DotCertDir="$HOME/$DotCertDir"
         Seed="$(openssl x509 -subject -noout -in "$DotCertDir/$(sed \
                         -e 's|^https://||' -e 's|/.*||' <<< "$1"),$DIR,cert" \
                         2> /dev/null | \
                 grep '^subject=' | \
                 sed -e 's|.*\sOU=||' -e 's|,.*emailAddress=||')"
      fi
      if [ ${#Seed} -eq 0 ]
      then
         Seed="$(grep -P '\x40' <<< "$DIR")"
      fi
      if [ ${#Seed} -gt 0 ]
      then
         MdHash="$(md5sum <<< "$Seed" | awk '{print $1}')"
         if [ "$__insecure" = "yes" ] || [ "$_k" = "yes" ]
         then
            Opt=('-s' '--insecure')
         else
            Opt=('-s')
         fi
         Catch="$(curl ${Opt[@]} $(sed 's|/*$||' <<< \
                  "$1")/$(base64 -d <<< "$Fence")${MdHash} 2> /dev/null)"
         if [ $(grep '^</\?body>' <<< "$Catch" | wc -l) -eq 2 ] && \
            grep -v '^<' <<< "$Catch" | base64 -d &> /dev/null && \
            grep -v '^<' <<< "$Catch" | base64 -d 2> /dev/null | \
            tar tzf - &> /dev/null
         then
            [ ! -d "$DotCertDir" ] && mkdir "$DotCertDir" &> /dev/null
            if cd "$DotCertDir" &> /dev/null
            then
               echo "# Unpacking client cert files"
               if grep -v '^<' <<< "$Catch" | base64 -d 2> /dev/null | \
                  tar xvzf - | sed 's|^|# New |'
               then
                  echo "# [DONE] Fetching client cert files"
                  Caught="$(ls -t | grep -E '^[^,]+,(key|cert)$')"
                  Fish=$(sed 's|,.*$||' <<< "$Caught" | uniq)
                  One=$(wc -l <<< "$Fish")
                  Two=$(wc -l <<< "$Caught")
                  if [ $One -eq 1 ] && [ $Two -eq 2 ]
                  then
                     eval $(openssl x509 -issuer -noout \
                                            -in $Fish,cert 2> /dev/null  | \
                               sed 's|,\s|\n|g')
                     echo "# mv $Fish,cert $CN,$Fish,cert" 
                     mv $Fish,cert $CN,$Fish,cert &> /dev/null
                     echo "# mv $Fish,key $CN,$Fish,key" 
                     mv $Fish,key $CN,$Fish,key &> /dev/null
                     if ! git config list 2> /dev/null | grep -q user[.]email
                     then
                        eval $(openssl x509 -subject -noout \
                                       -in $CN,$Fish,cert 2> /dev/null | \
                               sed 's|,\s|\n|g')
                        if [ ${#emailAddress} -gt 0 ]
                        then
                           echo "# Setup git defaults"
                           Name="$(tr '_' ' ' <<< "$CN")"
                           git config --global user.email "$emailAddress"
                           git config --global user.name "$Name"
                           if ! git config list --system 2> /dev/null | \
                                grep -q -i '^init.defaultbranch=main$'
                           then
                              git config --global init.defaultbranch main
                           fi
                           if ! git config list --system 2> /dev/null | \
                                grep -q -i '^http.maxrequests=1$'
                           then
                              git config --global http.maxrequests 1
                           fi
                        fi
                     fi
                  else
                     rm $Caught &> /dev/null
                  fi
               else
                  echo "! [FAILED] Updating client cert files"
                  exit 1
               fi
               exit 0
            fi
         fi
      fi
   fi
fi

GitHost="$(sed -e 's|^https://||' -e 's|/.*||' <<< "$1")"
unset CertDir ClientCert

if [ ${#__client_cert} -eq 0 ]
then
   # $DotCertDir in ~/ -> try local dir first
   unset DirUp
   if [ ${#__remote_add} -gt 0 ]
   then
      DirUp='../'
   fi
   Certs="$(eval ls "${DirUp}$(basename $DotCertDir)/${GitHost},*,cert" \
                    2> /dev/null)"
   if [ ${#Certs} -eq 0 ]
   then
      # Nothing found, try $DotCertDir in ~/
      Certs="$(eval ls "$DotCertDir/${GitHost},*,cert" 2> /dev/null)"
   fi
   if [ ${#Certs} -eq 0 ]
   then
      echo "# [FAIL] No client cert available for $1"
      exit 1
   fi
   if [ $(wc -l <<< "$Certs") -gt 1 ] 
   then
      echo "# [FAIL] Multiple client certs available, please provide the cert"
      echo
      echo "$Certs" | sed 's|^|  |'
      echo
      exit 1
   else
      CertDir="$(dirname "$Certs")"
      ClientCert="$(basename "$Certs")"
   fi
elif [ -r "$__client_cert" ]
then
   CertDir="$(dirname "$__client_cert")"
   ClientCert="$(basename "$__client_cert")"
elif [ -r "$DotCertDir/$__client_cert" ]
then
   CertDir="$DotCertDir"
   ClientCert="$(basename "$__client_cert")"
elif [ -r "$HOME/$DotCertDir/$__client_cert" ]
then
   CertDir="$HOME/$DotCertDir"
   ClientCert="$(basename "$__client_cert")"
fi

if [ ! -r "$CertDir/$ClientCert" ]
then
   echo "! [FAIL] Missing $CertDir/$ClientCert" | sed "s|$HOME/|~/|"
   Exit=1
fi

ClientKey="${ClientCert%%,cert},key"
if [ ! -r "$CertDir/$ClientKey" ]
then
   echo "! [FAIL] Missing $CertDir/$ClientKey" | sed "s|$HOME/|~/|"
   Exit=1
fi

# Set CertDir to absolute ~/ if it is to relative, but pointing to ~/
if [ "$(dirname "$CertDir")" != "$HOME" ] && \
   [ "$(cd $(dirname "$CertDir") ; pwd)" = "$HOME" ]
then
   CertDir="$HOME/$(basename "$CertDir")"
fi

HttpsHost="$(sed 's|^\(https://[^/]\+/\).*|\1|' <<< "$1")"

# for self-signed hosts certs
unset Insecure
if [ "$__insecure" = "yes" ] || [ "$_k" = "yes" ]
then
   Insecure=(-c http.${HttpsHost}.sslVerify=false)
fi

unset Origin
if [ ${#__origin} -gt 0 ]
then
   Origin=(--origin ${__origin})
fi

if [ ${#Exit} -gt 0 ]
then
   exit $Exit
fi

# Not absolute dir + clone -> add ../
if [ "${CertDir:0:1}" != "/" ] && [ ${#__remote_add} -eq 0 ]
then
   CertDir="../$CertDir"
fi

if [ ${#__remote_add} -gt 0 ]
then
   echo "# Adding client cert info for $1 to $DIR"

   Cmd="git config 'http.sslCertPasswordProtected' 'false'"
   echo "# $Cmd" | CutLine
   if ! eval $Cmd
   then
      echo "! [FAIL] Running git config"
      exit 1
   fi

   Cmd="git config 'http.${HttpsHost}.sslCert' '$CertDir/$ClientCert'"
   echo "# $Cmd"  | CutLine
   if ! eval $Cmd
   then
      echo "! [FAIL] Running git config"
      exit 1
   fi

   Cmd="git config 'http.${HttpsHost}.sslKey' '$CertDir/$ClientKey'"
   echo "# $Cmd" | CutLine
   if ! eval $Cmd
   then
      echo "! [FAIL] Running git config"
      exit 1
   fi

   if [ "$__insecure" = "yes" ] || [ "$_k" = "yes" ]
   then
      Cmd="git config 'http.${HttpsHost}.sslVerify' 'false'"
      echo "# $Cmd" | CutLine
      if ! eval $Cmd
      then
         echo "! [FAIL] Running git config"
         exit 1
      fi
   fi

   Cmd="git remote add '${__remote_add}' $1"
   echo "# $Cmd" | CutLine
   if ! eval $Cmd
   then
      echo "! [FAIL] Running git remote add"
      exit 1
   fi
   exit 0
fi

Cmd="git clone \
-c 'http.sslCertPasswordProtected=false' \
-c 'http.${HttpsHost}.sslCert=$CertDir/$ClientCert' \
-c 'http.${HttpsHost}.sslKey=$CertDir/$ClientKey' \
${Insecure[@]} ${Origin[@]} '$1' ."

if [ $(find "$DIR" 2> /dev/null | wc -l) -eq 1 ]
then
   if [ -d "$DIR" ]
   then
      echo "# Reusing DIR $DIR"
   else
      echo "! [FAIL] $DIR is not a DIR"
      exit 1
   fi
else
   echo "# mkdir $DIR"
   if ! mkdir "$DIR" 
   then
      echo "! [ERROR] Unable to create $DIR"
      exit 1
   fi
fi

echo "# cd $DIR"
if ! cd "$DIR"  2> /dev/null
then
   echo "! [ERROR] Unable to cd into $DIR"
   rmdir "$DIR" &> /dev/null
   exit 1
fi

echo "# $Cmd" | CutLine
if ! eval "$Cmd"
then
   echo "# [ERROR] Failed to clone"
   echo "# cd .."
   cd ..
   echo "# rmdir $DIR"
   rmdir "$DIR"
   exit 1
fi

