#!/usr/pkg/bin/bash

# shellcheck disable=SC2034
GITSECRET_VERSION='0.5.0'
#!/usr/pkg/bin/bash

# Folders:
_SECRETS_DIR=${SECRETS_DIR:-".gitsecret"}
# if SECRETS_DIR env var is set, use that instead of .gitsecret
# for full path to secrets dir, use _get_secrets_dir()
# from _git_secret_tools.sh
_SECRETS_DIR_KEYS="${_SECRETS_DIR}/keys"
_SECRETS_DIR_PATHS="${_SECRETS_DIR}/paths"

# Files:
_SECRETS_DIR_KEYS_TRUSTDB="${_SECRETS_DIR_KEYS}/trustdb.gpg"

_SECRETS_DIR_PATHS_MAPPING="${_SECRETS_DIR_PATHS}/mapping.cfg"


# shellcheck disable=SC2153
if [[ -n "$SECRETS_VERBOSE" ]] && [[ "$SECRETS_VERBOSE" -ne 0 ]]; then
  # shellcheck disable=SC2034
  _SECRETS_VERBOSE='1'
  # _SECRETS_VERBOSE is empty or '1'. 
  # Empty means 'off', any other value means 'on'.
fi

: "${SECRETS_EXTENSION:=".secret"}"

# Commands:
: "${SECRETS_GPG_COMMAND:="/usr/pkg/bin/gpg2"}"
: "${SECRETS_CHECKSUM_COMMAND:="_os_based __sha256"}"
: "${SECRETS_OCTAL_PERMS_COMMAND:="_os_based __get_octal_perms"}"
: "${SECRETS_EPOCH_TO_DATE:="_os_based __epoch_to_date"}"

# Temp Dir:
: "${TMPDIR:=/tmp}"

# AWK scripts:
# shellcheck disable=SC2016
AWK_FSDB_HAS_RECORD='
BEGIN { FS=":"; OFS=":"; cnt=0; }
{
  if ( key == $1 )
  {
    cnt++
  }
}
END { if ( cnt > 0 ) print "0"; else print "1"; }
'

# shellcheck disable=SC2016
AWK_FSDB_RM_RECORD='
BEGIN { FS=":"; OFS=":"; }
{
  if ( key != $1 )
  {
    print $1,$2;
  }
}
'

# shellcheck disable=SC2016
AWK_FSDB_CLEAR_HASHES='
BEGIN { FS=":"; OFS=":"; }
{
  print $1,"";
}
'

# shellcheck disable=SC2016
AWK_GPG_VER_CHECK='
/^gpg/{
  version=$3
  n=split(version,array,".")
  if( n >= 2) {
    if(array[1] >= 2)
    {
      if(array[2] >= 1)
      {
        print 1
      }
      else
      {
        print 0
      }
    }
    else
    {
      print 0
    }
  }
  else if(array[1] >= 2)
  {
    print 1
  }
  else
  {
    print 0
  }
}
'

# This is 1 for gpg version 2.1 or greater, otherwise 0
GPG_VER_MIN_21="$($SECRETS_GPG_COMMAND --version | gawk "$AWK_GPG_VER_CHECK")"


# Bash:

# echos 0 if function exists, otherwise non-zero
function _function_exists {
  local function_name="$1" # required

  declare -f -F "$function_name" > /dev/null 2>&1
  echo $?
}


# OS based:

function _os_based {
  # Pass function name as first parameter.
  # It will be invoked as os-based function with the postfix.

  case "$(uname -s)" in

    Darwin)
      "$1_osx" "${@:2}"
    ;;

    Linux)
      "$1_linux" "${@:2}"
    ;;

    MINGW*)
      "$1_linux" "${@:2}"
    ;;

    MSYS*)
      "$1_linux" "${@:2}"
    ;;

    CYGWIN*)
      "$1_linux" "${@:2}"
    ;;

    FreeBSD)
      "$1_freebsd" "${@:2}"
    ;;

    # TODO: add MS Windows support.
    # MINGW32*|MSYS*)
    #   $1_ms ${@:2}
    # ;;

    *)
      _abort 'unsupported OS.'
    ;;
  esac
}


# File System:

function _clean_windows_path {
  # This function transforms windows paths to *nix paths
  # such as  c:\this\that.file -> /c/this/that/file
  # shellcheck disable=SC2001
  echo "$1" | sed 's#^\([a-zA-Z]\):/#/\1/#'
}


function _set_config {
  # This function creates a line in the config, or alters it.

  local key="$1" # required
  local value="$2" # required
  local filename="$3" # required

  # The exit status is 0 (true) if the name was found, 1 (false) if not:
  local contains
  contains=$(grep -Fq "$key" "$filename"; echo "$?")

  # Append or alter?
  if [[ "$contains" -eq 0 ]]; then
    _os_based __replace_in_file "$@"
  elif [[ "$contains" -eq 1 ]]; then
    echo "${key} = ${value}" >> "$filename"
  fi
}


# this sets the global variable 'temporary_filename'
# currently this function is only used by 'hide'
function _temporary_file {
  # This function creates temporary file
  # which will be removed on system exit.
  temporary_filename=$(_os_based __temp_file)  # is not `local` on purpose.

  trap 'if [[ -f "$temporary_filename" ]]; then if [[ -n "$_SECRETS_VERBOSE" ]] || [[ "$SECRETS_TEST_VERBOSE" == 1 ]]; then echo "git-secret: cleaning up: $temporary_filename"; fi; rm -f "$temporary_filename"; fi;' EXIT
}


# Helper function


function _gawk_inplace {
  local parms="$*"
  local dest_file
  dest_file="$(echo "$parms" | gawk -v RS="'" -v FS="'" 'END{ gsub(/^\s+/,""); print $1 }')"

  _temporary_file

  bash -c "gawk ${parms}" > "$temporary_filename"
  mv "$temporary_filename" "$dest_file"
}


# File System Database (fsdb):


function _get_record_filename {
  # Returns 1st field from passed record
  local record="$1"
  local filename
  filename=$(echo "$record" | awk -F: '{print $1}')

  echo "$filename"
}


function _get_record_hash {
  # Returns 2nd field from passed record
  local record="$1"
  local hash
  hash=$(echo "$record" | awk -F: '{print $2}')

  echo "$hash"
}


function _fsdb_has_record {
  # First parameter is the key
  # Second is the fsdb
  local key="$1"  # required
  local fsdb="$2" # required

  # 0 on contains, 1 for error.
  gawk -v key="$key" "$AWK_FSDB_HAS_RECORD" "$fsdb"
}


function _fsdb_rm_record {
  # First parameter is the key (filename)
  # Second is the path to fsdb
  local key="$1"  # required
  local fsdb="$2" # required

  _gawk_inplace -v key="'$key'" "'$AWK_FSDB_RM_RECORD'" "$fsdb"
}


function _fsdb_clear_hashes {
  # First parameter is the path to fsdb
  local fsdb="$1" # required

  _gawk_inplace "'$AWK_FSDB_CLEAR_HASHES'" "$fsdb"
}


# Manuals:


function _show_manual_for {
  local function_name="$1" # required

  man "git-secret-${function_name}"
  exit 0
}


# Invalid options

function _invalid_option_for {
  local function_name="$1" # required

  man "git-secret-${function_name}"
  exit 1
}


# VCS:


function _check_ignore {
  local filename="$1" # required

  local result
  result="$(git check-ignore -q "$filename"; echo $?)"
  # returns 1 when not ignored, and 0 when ignored
  echo "$result"
}


function _git_normalize_filename {
  local filename="$1" # required

  local result
  result=$(git ls-files --full-name -o "$filename")
  echo "$result"
}


function _maybe_create_gitignore {
  # This function creates '.gitignore' if it was missing.

  local full_path
  full_path=$(_prepend_root_path '.gitignore')

  if [[ ! -f "$full_path" ]]; then
    touch "$full_path"
  fi
}


function _add_ignored_file {
  # This function adds a line with the filename into the '.gitignore' file.
  # It also creates '.gitignore' if it's not there

  local filename="$1" # required

  _maybe_create_gitignore

  local full_path
  full_path=$(_prepend_root_path '.gitignore')

  printf '%q\n' "$filename" >> "$full_path"
}


function _is_inside_git_tree {
  # Checks if we are working inside the `git` tree.
  local result
  result=$(git rev-parse --is-inside-work-tree > /dev/null 2>&1; echo $?)

  echo "$result"
}


function _is_tracked_in_git {
  local filename="$1" # required
  local result
  result="$(git ls-files --error-unmatch "$filename" >/dev/null 2>&1; echo $?)"

  if [[ "$result" -eq 0 ]]; then
    echo "1"
  else
    echo "0"
  fi
}


# This can give unexpected .git dir when used in a _subdirectory_
# of another git repo; See #431 and #433.
function _get_git_root_path {
  # We need this function to get the location of the `.git` folder,
  # since `.gitsecret` (or value set by SECRETS_DIR env var)
  # must be in the same dir.

  local result
  result=$(_clean_windows_path "$(git rev-parse --show-toplevel)")
  echo "$result"
}


# Relative paths:

function _prepend_root_path {
  # This function adds root path to any other path.

  local path="$1" # required

  local root_path
  root_path=$(_get_git_root_path)

  echo "$root_path/$path"
}


# if passed a name like 'filename.txt', returns a full path in the repo
# For #710: if we are in a subdir, fixup the path with the subdir
function _prepend_relative_root_path {
  local path="$1" # required

  local full_path
  full_path=$(_prepend_root_path "$path")

  local subdir
  subdir=$(git rev-parse --show-prefix)   # get the subdir of repo, like "subdir/"
  if [ -n "$subdir" ]; then
    full_path="$(dirname "$full_path")/${subdir}/$(basename "$full_path")"
  fi

  echo "$full_path"
}

function _get_secrets_dir {
  _prepend_root_path "${_SECRETS_DIR}"
}


function _get_secrets_dir_keys {
  _prepend_root_path "${_SECRETS_DIR_KEYS}"
}


function _get_secrets_dir_path {
  _prepend_root_path "${_SECRETS_DIR_PATHS}"
}


function _get_secrets_dir_keys_trustdb {
  _prepend_root_path "${_SECRETS_DIR_KEYS_TRUSTDB}"
}


function _get_secrets_dir_paths_mapping {
  _prepend_root_path "${_SECRETS_DIR_PATHS_MAPPING}"
}


# Logic:

function _message {
  local message="$1" # required
  echo "git-secret: $message"
}


function _abort {
  local message="$1" # required
  local exit_code=${2:-"1"}     # defaults to 1

  >&2 echo "git-secret: abort: $message"
  exit "$exit_code"
}


# _warn() sends warnings to stdout so user sees them
function _warn {
  local message="$1" # required

  >&2 echo "git-secret: warning: $message"
}


# _warn_or_abort "$error_message" "$exit_code" "$error_ok"
function _warn_or_abort {
  local message="$1"            # required
  local exit_code=${2:-"1"}     # defaults to 1
  local error_ok=${3:-0}        # can be 0 or 1

  if [[ "$error_ok" -eq "0" ]]; then
    if [[ "$exit_code" -eq "0" ]]; then
      # if caller sends an exit_code of 0, we change it to 1 before aborting.
      exit_code=1
    fi
    _abort "$message" "$exit_code"
  else
    _warn "$message" "$exit_code"
  fi
}


function _find_and_remove_secrets_formatted {
  local filenames
  _list_all_added_files # sets array variable 'filenames'

  for filename in "${filenames[@]}"; do
    local path # absolute path
    encrypted_filename=$(_get_encrypted_filename "$filename")
    if [[ -f "$encrypted_filename" ]]; then
      rm "$encrypted_filename"
      if [[ -n "$_SECRETS_VERBOSE" ]]; then
        echo "git-secret: deleted: $encrypted_filename"
      fi
    fi
  done
}



# this sets the global array variable 'filenames'
function _list_all_added_files {
  local path_mappings
  path_mappings=$(_get_secrets_dir_paths_mapping)

  if [[ ! -s "$path_mappings" ]]; then
    _abort "path_mappings file is missing or empty: $path_mappings"
  fi

  local filename
  filenames=()      # not local
  while read -r line; do
    filename=$(_get_record_filename "$line")
    filenames+=("$filename")
  done < "$path_mappings"

  declare -a filenames     # so caller can get list from filenames array
}


function _secrets_dir_exists {
  # This function checks if "$_SECRETS_DIR" exists and.

  local full_path
  full_path=$(_get_secrets_dir)

  if [[ ! -d "$full_path" ]]; then
    local name
    name=$(basename "$full_path")
    _abort "directory '$name' does not exist. Use 'git secret init' to initialize git-secret"
  fi
}


function _secrets_dir_is_not_ignored {
  # This function checks that "$_SECRETS_DIR" is not ignored.

  local git_secret_dir
  git_secret_dir=$(_get_secrets_dir)

  local ignores
  ignores=$(_check_ignore "$git_secret_dir")

  if [[ ! $ignores -eq 1 ]]; then
    _abort "entry already in .gitignore: $git_secret_dir"
  fi
}


function _exe_is_busybox {
  local exe
  exe="$1"

  # we assume stat is from busybox if it's a symlink
  local is_busybox=0
  local stat_path
  stat_path=$(command -v "$exe")
  if [ -L "$stat_path" ]; then
    is_busybox=1
  fi
  echo "$is_busybox"
}


# this is used by just about every command
function _user_required {
  # This function does a bunch of validations:
  # 1. It calls `_secrets_dir_exists` to verify that "$_SECRETS_DIR" exists.
  # 2. It ensures that "$_SECRETS_DIR_KEYS_TRUSTDB" exists.
  # 3. It ensures that there are added public keys.

  _secrets_dir_exists

  local trustdb
  trustdb=$(_get_secrets_dir_keys_trustdb)

  local error_message="no public keys for users found. run 'git secret tell email@address'."
  if [[ ! -f "$trustdb" ]]; then
    _abort "$error_message"
  fi

  local secrets_dir_keys
  secrets_dir_keys=$(_get_secrets_dir_keys)

  # see https://github.com/bats-core/bats-core#file-descriptor-3-read-this-if-bats-hangs for info about 3>&-
  local keys_exist
  keys_exist=$($SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" --no-permission-warning -n --list-keys 3>&-)
  local exit_code=$?
  if [[ -z "$keys_exist" ]]; then
    _abort "$error_message"
  fi
  if [[ "$exit_code" -ne 0 ]]; then
    # this might catch corner case where gpg --list-keys shows
    # 'gpg: skipped packet of type 12 in keybox' warnings but succeeds?
    # See #136
    echo "$keys_exist"	# show whatever _did_ come out of gpg
    _abort "problem listing public keys with gpg: exit code $exit_code"
  fi
}


# note: this has the same 'username matching' issue described in
# https://github.com/sobolevn/git-secret/issues/268
# where it will match emails that have other emails as substrings.
# we need to use fingerprints for a unique key id with gpg.
function _get_user_key_expiry {
  # This function returns the user's key's expiry, as an epoch.
  # It will return the empty string
  # if there is no expiry date for the user's key
  local username="$1"
  local line

  local secrets_dir_keys
  secrets_dir_keys=$(_get_secrets_dir_keys)

  # 3>&- closes fd 3 for bats, see https://github.com/bats-core/bats-core#file-descriptor-3-read-this-if-bats-hangs
  line=$($SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" --no-permission-warning --list-public-keys --with-colon --fixed-list-mode "$username" | grep ^pub: 3>&-)

  local expiry_epoch
  expiry_epoch=$(echo "$line" | cut -d: -f7)
  echo "$expiry_epoch"
}


function _assert_keyring_contains_emails {
  local homedir="$1"
  local keyring_name="$2"
  local emails="$3"

  # 1 here means 'expect $emails in keyring':
  _assert_keyring_emails "$homedir" "$keyring_name" "$emails" 1
}


function _assert_keyring_doesnt_contain_emails {
  local homedir="$1"
  local keyring_name="$2"
  local emails="$3"

  # 0 here means 'don't expect $emails in keyring':
  _assert_keyring_emails "$homedir" "$keyring_name" "$emails" 0
}

function _assert_keyring_contains_emails_at_least_once {
  local homedir=$1
  local keyring_name=$2
  local emails=$3
  _assert_keyring_emails "$homedir" "$keyring_name" "$emails" 1 1 # expect the email at least once in the keyring
}



function _assert_keyring_emails {
  local homedir="$1"
  local keyring_name="$2"
  local emails="$3"
  # set this to:
  # 0 to not expect the email in the keyring;
  # 1 to expect the email in the keyring
  local expected="$4"
  local allow_duplicates=$5 # set this to 0 to not allow duplicate emails in the keyring when processing assertion (optional)

  local gpg_uids
  gpg_uids=$(_get_users_in_gpg_keyring "$homedir")
  for email in "${emails[@]}"; do
    if [[ $email != *"@"* ]]; then
      _abort "does not appear to be an email: $email"
    fi
    local emails_found=0
    for uid in $gpg_uids; do
      if [[ "$uid" == "$email" ]]; then
        emails_found=$((emails_found+1))
      fi
    done
    if [[ $expected -eq 1 ]]; then
        if [[ $emails_found -eq 0 ]]; then
          _abort "no key found in gpg $keyring_name for: $email"
        elif [[ $emails_found -gt 1 ]]; then
          if [[ $allow_duplicates -ne 1 ]]; then
            _abort "$emails_found keys found in gpg $keyring_name for: $email"
          fi
        fi
    else
        if [[ $emails_found -gt 0 ]]; then
          _abort "$emails_found keys found in gpg $keyring_name for: $email"
        fi
    fi

  done
}


function _get_encrypted_filename {
  local filename
  filename="$(dirname "$1")/$(basename "$1" "$SECRETS_EXTENSION")"
  echo "${filename}${SECRETS_EXTENSION}" | sed -e 's#^\./##'
}


# this is used throughout this file, and in 'whoknows'
function _get_users_in_gpg_keyring {
  # show the users in the gpg keyring.
  # `whoknows` command uses it internally.
  # parses the `gpg` public keys
  local homedir=$1
  local result
  local args=()
  if [[ -n "$homedir" ]]; then
    args+=( "--homedir" "$homedir" )
  fi

  ## We use --fixed-list-mode so older versions of gpg emit 'uid:' lines.
  ## Gawk splits on colon as --with-colon, matches field 1 as 'uid',
  result=$($SECRETS_GPG_COMMAND "${args[@]}" --no-permission-warning --list-public-keys --with-colon --fixed-list-mode | \
      gawk -F: '$1=="uid"' )

  local emails
  emails=$(_extract_emails_from_gpg_output "$result")

  # For #508 / #552: warn user if gpg indicates keys are one of:
  # i=invalid, d=disabled, r=revoked, e=expired, n=not valid
  # See https://github.com/gpg/gnupg/blob/master/doc/DETAILS#field-2---validity # for more on gpg 'validity codes'.
  local invalid_lines
  invalid_lines=$(echo "$result" | gawk -F: '$2=="i" || $2=="d" || $2=="r" || $2=="e" || $2=="n"')

  local emails_with_invalid_keys
  emails_with_invalid_keys=$(_extract_emails_from_gpg_output "$invalid_lines")

  if [[ -n "$emails_with_invalid_keys" ]]; then
     _warn "at least one key for email(s) is revoked, expired, or otherwise invalid: $emails_with_invalid_keys"
  fi

  echo "$emails"
}


function _extract_emails_from_gpg_output {
  local result=$1

  # gensub() outputs email from <> within field 10, "User-ID".  If there's no <>, then field is just an email address
  #  (and maybe a comment) and we pass it through.
  # Sed at the end removes any 'comment' that appears in parentheses, for #530
  # 3>&- closes fd 3 for bats, see https://github.com/bats-core/bats-core#file-descriptor-3-read-this-if-bats-hangs
  local emails
  emails=$(echo "$result" | gawk -F: '{print gensub(/.*<(.*)>.*/, "\\1", "g", $10); }' | sed 's/([^)]*)//g' 3>&-)
  echo "$emails"
}


function _get_users_in_gitsecret_keyring {
  # show the users in the gitsecret keyring.
  local secrets_dir_keys
  secrets_dir_keys=$(_get_secrets_dir_keys)

  local result
  result=$(_get_users_in_gpg_keyring "$secrets_dir_keys")

  echo "$result"
}


function _get_recipients {
  # This function is required to create an encrypted file for different users.
  # These users are called 'recipients' in the `gpg` terms.
  # It basically just parses the `gpg` public keys

  local result
  # put -r before each user:
  result=$(_get_users_in_gitsecret_keyring | sed 's/^/-r/')
  echo "$result"
}


function _decrypt {
  # required:
  local filename="$1"

  # optional:
  local write_to_file=${2:-1} # can be 0 or 1
  local force=${3:-0} # can be 0 or 1
  local homedir=${4:-""}
  local passphrase=${5:-""}
  local error_ok=${6:-0} # can be 0 or 1

  local encrypted_filename
  encrypted_filename=$(_get_encrypted_filename "$filename")

  if [ ! -f "$encrypted_filename" ]; then
    _warn_or_abort "cannot find file to decrypt: $encrypted_filename" "1" "$error_ok"
  else
    local args=( "--use-agent" "--decrypt" )
  
    if [[ "$write_to_file" -eq 1 ]]; then
      args+=( "-o" "$filename" )
    fi
  
    if [[ "$force" -eq 1 ]]; then
      args+=( "--yes" )
    fi
  
    if [[ -n "$homedir" ]]; then
      args+=( "--homedir" "$homedir" )
    fi
  
    if [[ "$GPG_VER_MIN_21" -eq 1 ]]; then
      if [[ -n "$SECRETS_PINENTRY" ]]; then
        args+=( "--pinentry-mode" "$SECRETS_PINENTRY" )
      else
        args+=( "--pinentry-mode" "loopback" )
      fi
    fi
  
    if [[ -z "$_SECRETS_VERBOSE" ]]; then
      # we no longer use --no-permission-warning here, for #811
      args+=( "--quiet" )
    fi
  
    set +e   # disable 'set -e' so we can capture exit_code
  
    #echo "# gpg passphrase: $passphrase" >&3
    local exit_code
    if [[ -n "$passphrase" ]]; then
      exec 5<<<"$passphrase"  # use 5, because descriptors 3 and 4 are used by bats
      $SECRETS_GPG_COMMAND "${args[@]}" --batch --yes --no-tty --passphrase-fd 5 "$encrypted_filename"
      exit_code=$?
      exec 5>&-   # close file descriptor 5
    else
      $SECRETS_GPG_COMMAND "${args[@]}" "$encrypted_filename"
      exit_code=$?
    fi
  
    set -e  # re-enable set -e
  
    # note that according to https://github.com/sobolevn/git-secret/issues/238 ,
    # it's possible for gpg to return a 0 exit code but not have decrypted the file
    #echo "# gpg exit code: $exit_code, error_ok: $error_ok" >&3
    if [[ "$exit_code" -ne "0" ]]; then
      local msg="problem decrypting file with gpg: exit code $exit_code: $filename"
      _warn_or_abort "$msg" "$exit_code" "$error_ok"
    fi
  fi

  # at this point the file should be written to disk or output to stdout
}
#!/usr/pkg/bin/bash

# support for freebsd. Mostly the same as MacOS.


# shellcheck disable=SC1117
function __replace_in_file_freebsd {
  sed -i.bak "s/^\($1[[:space:]]*=[[:space:]]*\).*\$/\1$2/" "$3"
}


function __temp_file_freebsd {
  local filename
  # man mktemp on FreeBSD:
  # ...
  # If	the -t prefix option is	given, mktemp will generate a template string
  #   based on the prefix and the TMPDIR	environment variable if	set.  The
  #   default location if TMPDIR	is not set is /tmp. "

  filename=$(mktemp -t _git_secret )
  echo "$filename";
}


function __sha256_freebsd {
  # this is in a different location than MacOS
  /usr/local/bin/shasum -a256 "$1"
}

function __get_octal_perms_freebsd {
  local filename
  filename=$1
  local perms
  perms=$(stat -f "%04OLp" "$filename")
  # perms is a string like '0644'.
  # In the "%04OLp':
  #   the '04' means 4 digits, 0 padded.  So we get 0644, not 644.
  #   the 'O' means Octal.
  #   the 'Lp' means 'low subfield of file type and permissions (st_mode).'
  #     (without 'L' you get 6 digits like '100644'.)
  echo "$perms"
}

function __epoch_to_date_freebsd {
  local epoch=$1;
  if [ -z "$epoch" ]; then
    echo ''
  else
    local cmd="date -I -r $epoch"
    #echo "# running: $cmd" >&3
    local datetime
    datetime=$($cmd)
    echo "$datetime"
  fi
}
#!/usr/pkg/bin/bash


# shellcheck disable=SC1117
function __replace_in_file_linux {
  sed -i.bak "s/^\($1\s*=\s*\).*\$/\1$2/" "$3"
}


function __temp_file_linux {
  local filename
  # man mktemp on CentOS 7:
  # mktemp [OPTION]... [TEMPLATE]
  # ...
  #  -p DIR, --tmpdir[=DIR]
  #        interpret TEMPLATE relative to DIR; if DIR is not specified,
  #        use $TMPDIR if set, else /tmp.  With this option, TEMPLATE
  #        must not be an absolute name; unlike  with -t, TEMPLATE may
  #        contain slashes, but mktemp creates only the final component
  # ...
  #  -t     interpret TEMPLATE as a single file name component,
  #         relative to a directory: $TMPDIR, if set; else the directory
  #         specified via -p; else /tmp [deprecated]

  filename=$(mktemp -p "${TMPDIR}" _git_secret.XXXXXX )
  # makes a filename like /$TMPDIR/_git_secret.ONIHo
  echo "$filename"
}

function __sha256_linux {
  sha256sum "$1"
}

function __get_octal_perms_linux {
  local filename
  filename=$1

  local stat_is_busybox
  stat_is_busybox=$(_exe_is_busybox "stat")
  local perms   # a string like '644'
  if [ "$stat_is_busybox" -eq 1 ]; then
    # special case for busybox, which doesn't understand --format
    perms=$(stat -c '%a' "$filename")
  else
    perms=$(stat --format '%a' "$filename")
  fi
  echo "$perms"
}

function __epoch_to_date_linux {
  local epoch=$1;
  if [ -z "$epoch" ]; then
    echo ''
  else
    local cmd="date +%F -d @$epoch"
    local datetime
    datetime=$($cmd)
    echo "$datetime"
  fi
}
#!/usr/pkg/bin/bash


# shellcheck disable=SC1117
function __replace_in_file_osx {
  sed -i.bak "s/^\($1[[:space:]]*=[[:space:]]*\).*\$/\1$2/" "$3"
}


function __temp_file_osx {
  local filename
  # man mktemp on OSX:
  # ...
  # "If the -t prefix option is given, mktemp will generate a template string
  #   based on the prefix and the _CS_DARWIN_USER_TEMP_DIR configuration vari-
  #   able if available.  Fallback locations if _CS_DARWIN_USER_TEMP_DIR is not
  #   available are TMPDIR and /tmp."

  # we use /usr/bin/mktemp in case there's another mktemp available. See #485
  filename=$(/usr/bin/mktemp -t _git_secret )
  # On OSX this can make a filename like
  # '/var/folders/nz/vv4_91234569k3tkvyszvwg90009gn/T/_git_secret.HhvUPlUI'
  echo "$filename";
}


function __sha256_osx {
  /usr/bin/shasum -a256 "$1"
}

function __get_octal_perms_osx {
  local filename
  filename=$1
  local perms
  perms=$(stat -f "%04OLp" "$filename")
  # see _git_secret_tools_freebsd.sh for more about stat's format string
  echo "$perms"
}

function __epoch_to_date_osx {
  local epoch=$1;
  if [ -z "$epoch" ]; then
    echo ''
  else
    #date -r 234234234 +"%Y-%m-%d"
    local datetime
    datetime=$(date -r "$epoch" +'%Y-%m-%d')
    echo "$datetime"
  fi
}

#!/usr/pkg/bin/bash


function add {
  OPTIND=1

  while getopts "ihv" opt; do
    case "$opt" in
      i) ;;    # this doesn't change anything

      h) _show_manual_for "add";;

      v) _SECRETS_VERBOSE=1;;

      *) _invalid_option_for "add";;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  _user_required

  # Checking if all files are correct (ignored and inside the repo):

  local not_ignored=()
  local items=( "$@" )

  # Checking if all files in options are ignored:
  for item in "${items[@]}"; do
    local path # absolute path
    local normalized_path # relative to the .git dir
    normalized_path=$(_git_normalize_filename "$item")
    path=$(_prepend_root_path "$normalized_path")

    # check that the file is not tracked
    local in_git
    in_git=$(_is_tracked_in_git "$item")
    if [[ "$in_git" -ne 0  ]]; then
       _abort "file tracked in git, consider using 'git rm --cached $item'"
    fi

    # Checking that file is valid:
    if [[ ! -f "$path" ]]; then
      _abort "file not found: $item"
    fi

    # Checking that it is ignored:
    local ignored
    ignored=$(_check_ignore "$path")

    if [[ "$ignored" -ne 0 ]]; then
      # Collect unignored files:
      not_ignored+=("$normalized_path")
    fi
  done

  # Are there any unignored files?

  if [[ ! "${#not_ignored[@]}" -eq 0 ]]; then
    # Add these files to `.gitignore` automatically:
    # see https://github.com/sobolevn/git-secret/issues/18 for more.

    for item in "${not_ignored[@]}"; do
      _message "file not in .gitignore, adding: $item"
      _add_ignored_file "$item"
    done
  fi

  # Adding files to path mappings:

  local fsdb
  fsdb=$(_get_secrets_dir_paths_mapping)
  local count
  count=0

  for item in "${items[@]}"; do
    local path
    local key
    path=$(_git_normalize_filename "$item")
    key="$path"

    # Adding files into system, skipping duplicates.
    local already_in
    already_in=$(_fsdb_has_record "$key" "$fsdb")
    if [[ "$already_in" -eq 1 ]]; then
      echo "$key" >> "$fsdb"
       if [[ -n "$_SECRETS_VERBOSE" ]]; then
        _message "adding file: $key"
      fi

      ((count=count+1))
    fi
  done

  _message "$count item(s) added."
}
#!/usr/pkg/bin/bash


function cat {
  local homedir=''
  local passphrase=''

  OPTIND=1

  while getopts 'hd:p:' opt; do
    case "$opt" in
      h) _show_manual_for 'cat';;

      p) passphrase=$OPTARG;;

      d) homedir=$(_clean_windows_path "$OPTARG");;

      *) _invalid_option_for 'cat';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  _user_required

  # Command logic:

  for line in "$@"
  do
    local filename
    local path

    filename=$(_get_record_filename "$line")
    path=$(_prepend_relative_root_path "$filename")  # this uses the _relative version because of #710

    # The parameters are: filename, write-to-file, force, homedir, passphrase
    _decrypt "$path" "0" "0" "$homedir" "$passphrase"
  done
}
#!/usr/pkg/bin/bash

function changes {
  local passphrase=""

  OPTIND=1

  while getopts 'hd:p:' opt; do
    case "$opt" in
      h) _show_manual_for 'changes';;

      p) passphrase=$OPTARG;;

      d) homedir=$(_clean_windows_path "$OPTARG");;

      *) _invalid_option_for 'changes';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  _user_required

  filenames=("$@")  # list of positional parameters. global.
  if [[ ${#filenames[@]} -eq 0 ]]; then
    # Checking if no filenames are passed, show diff for all files.
    _list_all_added_files    # this sets the array variable 'filenames'
  fi

  IFS='
  '

  for filename in "${filenames[@]}"; do
    local path # absolute path
    local normalized_path # relative to the .git dir
    local encrypted_filename
    normalized_path=$(_git_normalize_filename "$filename")
    encrypted_filename=$(_get_encrypted_filename "$filename")

    if [[ ! -f "$encrypted_filename" ]]; then
        _abort "cannot find encrypted version of file: $filename"
    fi
    if [[ -n "$normalized_path" ]]; then
      path=$(_prepend_root_path "$normalized_path")
    else
      # Path was already normalized
      path=$(_prepend_root_path "$filename")
    fi

    if [[ ! -f "$path" ]]; then
        _abort "file not found. Consider using 'git secret reveal': $filename"
    fi

    # Now we have all the data required to do the last encryption and compare results:
    # now do a two-step to protect trailing newlines from the $() construct.
    local decrypted_x
    local decrypted
    decrypted_x=$(_decrypt "$path" "0" "0" "$homedir" "$passphrase"; echo x$?)
    decrypted="${decrypted_x%x*}"
    # we ignore the exit code because _decrypt will abort_ if appropriate.


    _message "changes in ${path}:"
    # diff the result:
    # we have the '|| true' because `diff` returns error code if files differ.
    diff -u <(echo -n "$decrypted") "$path" || true
  done
}
#!/usr/pkg/bin/bash


function clean {
  OPTIND=1

  # shellcheck disable=SC2034
  while getopts 'vh' opt; do
    case "$opt" in
      v) _SECRETS_VERBOSE=1;;

      h) _show_manual_for 'clean';;

      *) _invalid_option_for 'clean';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  if [ $# -ne 0 ]; then
    _abort "clean does not understand params: $*"
  fi

  _user_required

  _find_and_remove_secrets_formatted
}
#!/usr/pkg/bin/bash

# shellcheck disable=SC2016
AWK_FSDB_UPDATE_HASH='
BEGIN { FS=":"; OFS=":"; }
{
  if ( key == $1 )
  {
    print key,hash;
  }
  else
  {
    print $1,$2;
  }
}
'


function _optional_delete {
  local delete="$1"

  if [[ $delete -eq 1 ]]; then
    local path_mappings
    path_mappings=$(_get_secrets_dir_paths_mapping)

    # We use custom formatting here:
    if [[ -n "$_SECRETS_VERBOSE" ]]; then
      _message 'removing unencrypted files'
    fi

    while read -r line; do  # each line is a record like: filename: or filename:hash
      local filename
      filename=$(_get_record_filename "$line")
      if [[ -e "$filename" ]]; then 
        rm "$filename"
        if [[ -n "$_SECRETS_VERBOSE" ]]; then
          _message "deleted: $filename"
        fi
      fi
    done < "$path_mappings"
  fi
}

function _get_checksum_local {
  local checksum="$SECRETS_CHECKSUM_COMMAND"
  echo "$checksum"
}

function _get_file_hash {
  local input_path="$1" # Required
  local checksum_local
  local file_hash

  checksum_local="$(_get_checksum_local)"
  file_hash=$($checksum_local "$input_path" | gawk '{print $1}')

  echo "$file_hash"
}

function _fsdb_update_hash {
  local key="$1"
  local hash="$2"
  local fsdb          # path_mappings

  fsdb=$(_get_secrets_dir_paths_mapping)

  _gawk_inplace -v key="'$key'" -v hash="$hash" "'$AWK_FSDB_UPDATE_HASH'" "$fsdb"
}


function hide {
  local clean=0
  local preserve=0
  local delete=0
  local update_only_modified=0
  local force_continue=0

  OPTIND=1

  while getopts 'cFPdmvh' opt; do
    case "$opt" in
      c) clean=1;;

      F) force_continue=1;;

      P) preserve=1;;

      d) delete=1;;

      m) update_only_modified=1;;

      v) _SECRETS_VERBOSE=1;;

      h) _show_manual_for 'hide';;

      *) _invalid_option_for 'hide';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  if [ $# -ne 0 ]; then
    _abort "hide does not understand params: $*"
  fi

  # We need user to continue:
  _user_required

  # If -c option was provided, clean the hidden files
  # before creating new ones.
  if [[ $clean -eq 1 ]]; then
    _find_and_remove_secrets_formatted
  fi

  # Encrypting files:

  local path_mappings
  path_mappings=$(_get_secrets_dir_paths_mapping)
  local num_mappings
  num_mappings=$(gawk 'END{print NR}' "$path_mappings")

  # make sure all the unencrypted files needed are present
  local to_hide=()
  while read -r record; do
    to_hide+=("$record")  # add record to array
  done < "$path_mappings"

  local recipients
  recipients=$(_get_recipients)

  local secrets_dir_keys
  secrets_dir_keys=$(_get_secrets_dir_keys)

  local counter=0
  for record in "${to_hide[@]}"; do
    local filename
    local fsdb_file_hash
    local encrypted_filename
    filename=$(_get_record_filename "$record")
    fsdb_file_hash=$(_get_record_hash "$record")
    encrypted_filename=$(_get_encrypted_filename "$filename")

    local input_path
    local output_path
    input_path=$(_prepend_root_path "$filename")
    output_path=$(_prepend_root_path "$encrypted_filename")

    # Checking that file is valid:
    if [[ ! -f "$input_path" ]]; then
      # this catches the case where some decrypted files don't exist
      _warn_or_abort "file not found: $input_path" "1" "$force_continue"
    else
      file_hash=$(_get_file_hash "$input_path")

      # encrypt file only if required
      if [[ "$update_only_modified" -eq 0 ]] ||
         [[ "$fsdb_file_hash" != "$file_hash" ]]; then

        local args=( --homedir "$secrets_dir_keys" --use-agent --yes '--trust-model=always' --encrypt )

        # SECRETS_GPG_ARMOR is expected to be empty or '1'.
        # Empty means 'off', any other value means 'on'.
        # See: https://github.com/sobolevn/git-secret/pull/661
        # shellcheck disable=SC2153
        if [[ -n "$SECRETS_GPG_ARMOR" ]] &&
           [[ "$SECRETS_GPG_ARMOR" -ne 0 ]]; then
          args+=( '--armor' )
        fi

        # we no longer use --no-permission-warning here in non-verbose mode, for #811

        # we depend on $recipients being split on whitespace
        # shellcheck disable=SC2206
        args+=( $recipients -o "$output_path" "$input_path" )

        set +e  # disable 'set -e' so we can capture exit_code

     	  # For info about `3>&-` see:
        # https://github.com/bats-core/bats-core#file-descriptor-3-read-this-if-bats-hangs
        local gpg_output
        gpg_output=$($SECRETS_GPG_COMMAND "${args[@]}" 3>&-)  # we leave stderr alone
        local exit_code=$?

        set -e  # re-enable set -e

        local error=0
        if [[ "$exit_code" -ne 0 ]] || [[ ! -f "$output_path" ]]; then
          error=1
        fi

        if [[ "$error" -ne 0 ]] || [[ -n "$_SECRETS_VERBOSE" ]]; then
          if [[ -n "$gpg_output" ]]; then
            echo "$gpg_output"
          fi
        fi

        if [[ ! -f "$output_path" ]]; then
          # if gpg can't encrypt a file we asked it to, that's an error unless in force_continue mode.
          _warn_or_abort "problem encrypting file with gpg: exit code $exit_code: $filename" "$exit_code" "$force_continue"
        else
          counter=$((counter+1))
          if [[ "$preserve" == 1 ]]; then
            local perms
            perms=$($SECRETS_OCTAL_PERMS_COMMAND "$input_path")
            chmod "$perms" "$output_path"
          fi
        fi

        # Update file hash for future use of -m
        local key="$filename"
        local hash="$file_hash"
        _fsdb_update_hash "$key" "$hash"
      fi
    fi
  done

  # If -d option was provided, it would delete the source files
  # after we have already hidden them.
  _optional_delete "$delete"

  _message "done. $counter of $num_mappings files are hidden."
}
#!/usr/pkg/bin/bash

# shellcheck disable=SC2016
AWK_ADD_TO_GITIGNORE='
BEGIN {
  cnt=0
}

function check_print_line(line){
  if (line == pattern) {
    cnt++
  }
  print line
}

# main function
{
  check_print_line($0)      # check and print first line
  while (getline == 1) {    # check and print all other
    check_print_line($0)
  }
}

END {
  if ( cnt == 0) {         # if file did not contain pattern add
    print pattern
  }
}
'

function gitignore_add_pattern {
  local pattern
  local gitignore_file_path

  pattern="$1"
  gitignore_file_path=$(_prepend_root_path '.gitignore')

  _maybe_create_gitignore
  _gawk_inplace -v pattern="$pattern" "'$AWK_ADD_TO_GITIGNORE'" "$gitignore_file_path"
}

function init {
  OPTIND=1

  while getopts 'h' opt; do
    case "$opt" in
      h) _show_manual_for 'init';;

      *) _invalid_option_for 'init';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  if [ $# -ne 0 ]; then
    _abort "init does not understand params: $*"
  fi

  # Check if '.gitsecret/' already exists:
  local git_secret_dir
  git_secret_dir=$(_get_secrets_dir)

  if [[ -d "$git_secret_dir" ]]; then
    _abort 'already initialized.'
  fi

  # Check if it is ignored:
  _secrets_dir_is_not_ignored

  # Create internal files:

  mkdir "$git_secret_dir" "$(_get_secrets_dir_keys)" "$(_get_secrets_dir_path)"
  chmod 700 "$(_get_secrets_dir_keys)"  # for #811, set to rwx------
  touch "$(_get_secrets_dir_paths_mapping)"

  _message "init created: '$git_secret_dir/'"

  local random_seed_file
  random_seed_file="${_SECRETS_DIR}/keys/random_seed"
  gitignore_add_pattern "$random_seed_file"
  gitignore_add_pattern "!*$SECRETS_EXTENSION"

  # TODO: git attributes to view diffs
}
#!/usr/pkg/bin/bash


function list {
  OPTIND=1

  while getopts 'h' opt; do
    case "$opt" in
      h) _show_manual_for 'list';;

      *) _invalid_option_for 'list';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  if [ $# -ne 0 ]; then
    _abort "list does not understand params: $*"
  fi

  _user_required

  # Command logic:
  filenames=()
  _list_all_added_files  # exports 'filenames' array
  local filename
  for filename in "${filenames[@]}"; do
    echo "$filename"    # do not prepend 'git-secret: '
  done
}
#!/usr/pkg/bin/bash


function remove {
  local clean=0

  OPTIND=1

  while getopts 'ch' opt; do
    case "$opt" in
      c) clean=1;;

      h) _show_manual_for 'remove';;

      *) _invalid_option_for 'remove';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  # Validate if user exists:
  _user_required

  # Command logic:

  local path_mappings
  path_mappings=$(_get_secrets_dir_paths_mapping)

  for item in "$@"; do
    local path # absolute path
    local normalized_path # relative to .git folder
    normalized_path=$(_git_normalize_filename "$item")
    path=$(_prepend_root_path "$normalized_path")

    # Checking if file exists:
    if [[ ! -f "$path" ]]; then
      _abort "file not found: $item"
    fi

    # Deleting it from path mappings:
    # Remove record from fsdb with matching key
    local key
    key="$normalized_path"
    fsdb="$path_mappings"
    _fsdb_rm_record "$key" "$fsdb"

    rm -f "${path_mappings}.bak"  # not all systems create '.bak'

    # Optional clean:
    if [[ "$clean" -eq 1 ]]; then
      local encrypted_filename
      encrypted_filename=$(_get_encrypted_filename "$path")

      rm "$encrypted_filename" # fail on error
      if [[ -n "$_SECRETS_VERBOSE" ]]; then
          _message "deleted: $encrypted_filename"
      fi
    fi
  done

  echo 'git-secret: removed from index.'
  echo "git-secret: ensure that files: [$*] are now not ignored."
}
#!/usr/pkg/bin/bash


function removeperson {
  OPTIND=1

  while getopts 'h' opt; do
    case "$opt" in
      h) _show_manual_for 'removeperson';;

      *) _invalid_option_for 'removeperson';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  _user_required

  # Command logic:

  local emails=( "$@" )

  if [[ ${#emails[@]} -eq 0 ]]; then
    _abort "at least one email is required for removeperson."
  fi
  # Getting the local git-secret `gpg` key directory:
  local secrets_dir_keys
  secrets_dir_keys=$(_get_secrets_dir_keys)

  _assert_keyring_contains_emails_at_least_once "$secrets_dir_keys" "git-secret keyring" "${emails[@]}"

  local args=( --homedir "$secrets_dir_keys" --batch --yes )
  # we no longer use --no-permission-warning here in non-verbose mode, for #811

  for email in "${emails[@]}"; do
    # see https://github.com/bats-core/bats-core#file-descriptor-3-read-this-if-bats-hangs for info about 3>&-
    $SECRETS_GPG_COMMAND "${args[@]}" --delete-key "$email" 3>&-
    local exit_code=$?
    if [[ "$exit_code" -ne 0 ]]; then
      _abort "problem deleting key for '$email' with gpg: exit code $exit_code"
    fi
  done

  _message 'removed keys.'
  _message "now [$*] do not have an access to the repository."
  _message 'make sure to hide the existing secrets again.'
}

function killperson {
  echo "Warning: 'killperson' has been renamed to 'removeperson'. This alias will be removed in the future versions, please switch to call 'removeperson' going forward."

  removeperson "$@"
}
#!/usr/pkg/bin/bash


function reveal {
  local homedir=''
  local passphrase=''
  local force=0             # this means 'clobber without warning'
  local force_continue=0    # this means 'continue if we have decryption errors'
  local preserve=0

  OPTIND=1

  while getopts 'hfFPd:p:v' opt; do
    # line below is for _SECRETS_VERBOSE
    # shellcheck disable=SC2034
    case "$opt" in
      h) _show_manual_for 'reveal';;

      f) force=1;;

      F) force_continue=1;;

      P) preserve=1;;

      p) passphrase=$OPTARG;;

      d) homedir=$(_clean_windows_path "$OPTARG");;

      v) _SECRETS_VERBOSE=1;;

      *) _invalid_option_for 'reveal';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = '--' ] && shift

  _user_required

  # Command logic:

  local path_mappings
  path_mappings=$(_get_secrets_dir_paths_mapping)

  local counter=0
  local to_show=( "$@" )

  if [ ${#to_show[@]} -eq 0 ]; then
    while read -r record; do
      to_show+=("$record")  # add record to array
    done < "$path_mappings"
  fi

  for line in "${to_show[@]}"; do
    local filename
    local path
    filename=$(_get_record_filename "$line")
    path=$(_prepend_relative_root_path "$filename")  # this uses the _relative version because of #710

    if [[ "$filename" == *"$SECRETS_EXTENSION" ]]; then
      _abort "cannot decrypt to secret version of file: $filename"
    fi

    # The parameters are: filename, write-to-file, force, homedir, passphrase, error_ok
    _decrypt "$path" "1" "$force" "$homedir" "$passphrase" "$force_continue"

    if [[ ! -f "$path" ]]; then
      _warn_or_abort "cannot find decrypted version of file: $filename" "2" "$force_continue"
    else
      counter=$((counter+1))
      local secret_file
      secret_file=$(_get_encrypted_filename "$path")
      if [[ "$preserve" == 1 ]] && [[ -f "$secret_file" ]]; then
        local perms
        perms=$($SECRETS_OCTAL_PERMS_COMMAND "$secret_file")
        chmod "$perms" "$path"
      fi
    fi

  done

  _message "done. $counter of ${#to_show[@]} files are revealed."
}
#!/usr/pkg/bin/bash

# shellcheck disable=SC2016
AWK_GPG_KEY_CNT='
BEGIN { cnt=0; OFS=":"; FS=":"; }
flag=0; $1 == "pub" { cnt++ }
END { print cnt }
'

function get_gpg_key_count {
  local secrets_dir_keys
  secrets_dir_keys=$(_get_secrets_dir_keys)
  # 3>&- closes fd 3 for bats, see https://github.com/bats-core/bats-core#file-descriptor-3-read-this-if-bats-hangs
  $SECRETS_GPG_COMMAND --homedir "$secrets_dir_keys" --no-permission-warning --list-public-keys --with-colon | gawk "$AWK_GPG_KEY_CNT" 3>&-
  local exit_code=$?
  if [[ "$exit_code" -ne 0 ]]; then
    _abort "problem counting keys with gpg: exit code $exit_code"
  fi
}

function tell {
  local emails
  local self_email=0
  local homedir

  # A POSIX variable
  # Reset in case getopts has been used previously in the shell.
  OPTIND=1

  while getopts "vhmd:" opt; do
    case "$opt" in
      v) _SECRETS_VERBOSE=1;;

      h) _show_manual_for "tell";;

      m) self_email=1;;

      d) homedir=$(_clean_windows_path "$OPTARG");;

      *) _invalid_option_for 'tell';;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  # Validates that application is initialized:
  _secrets_dir_exists

  # Command logic:
  emails=( "$@" )
  local git_email

  if [[ "$self_email" -eq 1 ]]; then
    git_email=$(git config user.email)

    if [[ -z "$git_email" ]]; then
      _abort "'git config user.email' is not set."
    fi

    emails+=("$git_email")
  fi

  if [[ "${#emails[@]}" -eq 0 ]]; then
    # If after possible addition of git_email, emails are still empty,
    # we should raise an exception.
    _abort "you must use -m or provide at least one email address."
  fi

  local secrets_dir_keys
  secrets_dir_keys=$(_get_secrets_dir_keys)

  _assert_keyring_contains_emails "$homedir" "user keyring" "${emails[@]}"
  _assert_keyring_doesnt_contain_emails "$secrets_dir_keys" "git-secret keyring" "${emails[@]}"

  local start_key_cnt
  start_key_cnt=$(get_gpg_key_count)
  for email in "${emails[@]}"; do
    _temporary_file  # note that `_temporary_file` will export `temporary_filename` var.
    # shellcheck disable=SC2154
    local keyfile="$temporary_filename"

    # 3>&- closes fd 3 for bats, see https://github.com/bats-core/bats-core#file-descriptor-3-read-this-if-bats-hangs
    local exit_code
    if [[ -z "$homedir" ]]; then
      $SECRETS_GPG_COMMAND --export -a "$email" > "$keyfile" 3>&-
      exit_code=$?
    else
      # This means that homedir is set as an extra argument via `-d`:
      # we no longer use --no-permission-warning here, for #811
      $SECRETS_GPG_COMMAND --homedir="$homedir" \
        --export -a "$email" > "$keyfile" 3>&-
      exit_code=$?
    fi
    if [[ "$exit_code" -ne 0 ]]; then
      _abort "problem exporting public key for '$email' with gpg: exit code $exit_code"
    fi

    if [[ ! -s "$keyfile" ]]; then
      _abort "no keyfile found for '$email'. Check your key name: 'gpg --list-keys'."
    fi

    # Importing public key to the local keyring:
    local args=( --homedir "$secrets_dir_keys" --import "$keyfile" )
    if [[ -z "$_SECRETS_VERBOSE" ]]; then
      $SECRETS_GPG_COMMAND "${args[@]}" > /dev/null 2>&1 3>&-
    else
      $SECRETS_GPG_COMMAND "${args[@]}" 3>&-
    fi
    exit_code=$?

    rm -f "$keyfile" || _abort "error removing temporary keyfile: $keyfile"

    if [[ "$exit_code" -ne 0 ]]; then
      _abort "problem importing public key for '$email' with gpg: exit code $exit_code"
    fi
  done

  _message "done. ${emails[*]} added as user(s) who know the secret."

  # force re-encrypting of files if required
  local fsdb
  local end_key_cnt
  fsdb=$(_get_secrets_dir_paths_mapping)
  end_key_cnt=$(get_gpg_key_count)
  [[ $start_key_cnt -ne $end_key_cnt ]] && _fsdb_clear_hashes "$fsdb"
}
#!/usr/pkg/bin/bash


function usage {
  OPTIND=1

  while getopts "h?" opt; do
    case "$opt" in
      h) _show_manual_for "usage";;

      *) _invalid_option_for "usage";;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  echo "usage: git secret [--version] [command] [command-options]"
  echo ""
  echo "options:"
  echo " --version                 - prints the version number"
  echo ""
  echo "commands:"
  echo "see 'git secret [command] -h' for more info about commands and their options"
  echo " add [file.txt]            - adds file to be hidden to the list"
  echo " cat [file.txt]            - decrypts and prints contents of the file"
  echo " changes [file.txt.secret] - indicates if the file changed since last commit"
  echo " clean                     - deletes all encrypted files"
  echo " hide                      - encrypts (or re-encrypts) the files to be hidden"
  echo " init                      - initializes the  git-secret repository"
  echo " removeperson [emails]     - deletes a person's public key from the keyring"
  echo " list                      - prints all the added files"
  echo " remove [files]            - removes files from the list of hidden files"
  echo " reveal                    - decrypts all hidden files"
  echo " tell [email]              - imports a person's public key into the keyring"
  echo " usage                     - prints this message"
  echo " whoknows                  - prints list of authorized email addresses"
}
#!/usr/pkg/bin/bash


function whoknows {
  OPTIND=1

  local long_display=0
  while getopts "hl?" opt; do
    case "$opt" in
      h) _show_manual_for "whoknows";;

      l) long_display=1;;   # like ls -l

      *) _invalid_option_for "whoknows";;
    esac
  done

  shift $((OPTIND-1))
  [ "$1" = "--" ] && shift

  if [ $# -ne 0 ]; then
    _abort "whoknows does not understand params: $*"
  fi

  # Validating, that we have a user:
  _user_required

  local users

  # Getting the users from gpg:
  users=$(_get_users_in_gitsecret_keyring)
  for user in $users; do
      echo -n "$user"

      if [[ "$long_display" -eq 1 ]]; then
        local expiration
        expiration=$(_get_user_key_expiry "$user")
        if [[ -n "$expiration"  ]]; then
          local expiration_date
          expiration_date=$($SECRETS_EPOCH_TO_DATE "$expiration")
          echo -n " (expires: $expiration_date)"
        else
          echo -n " (expires: never)"
        fi
      fi

      echo
  done
}
#!/usr/pkg/bin/bash

set -e

function _check_setup {
  # Checking git and secret-plugin setup:
  local is_tree
  is_tree=$(_is_inside_git_tree)
  if [[ "$is_tree" -ne 0 ]]; then
    _abort "not in dir with git repo. Use 'git init' or 'git clone', then in repo use 'git secret init'"
  fi

  # Checking if the '.gitsecret' dir (or as set by SECRETS_DIR) is not ignored:
  _secrets_dir_is_not_ignored

  # Checking gpg setup:
  local keys_dir
  keys_dir=$(_get_secrets_dir_keys)

  local secring="$keys_dir/secring.gpg"
  if [[ -f $secring ]] && [[ -s $secring ]]; then
    # secring.gpg exists and is not empty,
    # someone has imported a private key.
    _abort 'it seems that someone has imported a secret key.'
  fi
}

function _incorrect_usage {
  local message="$1"
  local exitcode="$2"
  shift 2
  echo "git-secret: abort: ${message}"
  usage "$@"
  exit "${exitcode}"
}

function _show_version {
  echo "$GITSECRET_VERSION"
  exit 0
}


function _init_script {
  if [[ $# == 0 ]]; then
    _incorrect_usage 'no input parameters provided.' 126 "$@"
  fi

  # Parse plugin-level options:
  local dry_run=0

  while [[ $# -gt 0 ]]; do
    local opt="$1"

    case "$opt" in
      # Options for quick-exit strategy:
      --dry-run)
        dry_run=1
        shift;;

      --version) _show_version;;

      *) break;;  # do nothing
    esac
  done

  if [[ "$dry_run" == 0 ]]; then
    # Checking for proper set-up:
    _check_setup

    # Routing the input command:
    local function_exists
    function_exists=$(_function_exists "$1")

    if [[ "$function_exists" == 0 ]] && [[ ! $1 == _* ]]; then
      $1 "${@:2}"
    else  # TODO: elif [[ $(_plugin_exists $1) == 0 ]]; then
      _incorrect_usage "command $1 not found." 126 "$@"
    fi
  fi
}


_init_script "$@"
