From 4ac86e84e55e793ba468c8cb6111510c9831377b Mon Sep 17 00:00:00 2001 From: Platon Pronko Date: Wed, 10 May 2023 18:01:01 +0800 Subject: [PATCH] fix(ssh): resolve relative ssh_config Include correctly As per ssh_config man, "Files without absolute paths are assumed to be in ~/.ssh if included in a user configuration file or /etc/ssh if included from the system configuration file." So relative include base stays the same throughout recursion, even if the system-wide config is included from user's config. This commit also optimizes traversing ssh config includes. Previously each config file was read with separate `sed` command to extract `Include` directives. This can get quite slow if there are a lot of included files. Instead of reading each individual file, we can put all the files at a current recursion level into a single sed invocation and extract all the directives in one go. --- bash_completion | 61 +++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/bash_completion b/bash_completion index 31529920c7f..d56097046c7 100644 --- a/bash_completion +++ b/bash_completion @@ -2111,33 +2111,44 @@ _included_ssh_config_files() local configfile i files f configfile=$1 + # From man ssh_config: + # "Files without absolute paths are assumed to be in ~/.ssh if included + # in a user configuration file or /etc/ssh if included from the system + # configuration file." + # This behavior is not affected by the the including file location - + # if the system configuration file is included from the user's config, + # relative includes are still resolved in the user's ssh config directory. + local relative_include_base + if [[ $configfile == /etc/ssh* ]]; then + relative_include_base="/etc/ssh" + else + relative_include_base="$HOME/.ssh" + fi + + local depth=1 local -a included - _comp_split included "$(command sed -ne 's/^[[:blank:]]*[Ii][Nn][Cc][Ll][Uu][Dd][Ee][[:blank:]]\(.*\)$/\1/p' "${configfile}")" || return - - for i in "${included[@]}"; do - # Check the origin of $configfile to complete relative included paths on included - # files according to ssh_config(5): - # "[...] Files without absolute paths are assumed to be in ~/.ssh if included in a user - # configuration file or /etc/ssh if included from the system configuration file.[...]" - if ! [[ $i =~ ^\~.*|^\/.* ]]; then - if [[ $configfile =~ ^\/etc\/ssh.* ]]; then - i="/etc/ssh/$i" - else - i="$HOME/.ssh/$i" + local -a include_files + included=("$configfile") + + while ((${#included[@]} > 0 && depth++ < 16)); do + _comp_split include_files "$(command sed -ne 's/^[[:blank:]]*[Ii][Nn][Cc][Ll][Uu][Dd][Ee][[:blank:]]\(.*\)$/\1/p' "${included[@]}")" || return + included=() + for i in "${include_files[@]}"; do + if [[ $i != [~/]* ]]; then + i="${relative_include_base}/${i}" fi - fi - __expand_tilde_by_ref i - # In case the expanded variable contains multiple paths - _comp_expand_glob files '$i' - if ((${#files[@]})); then - for f in "${files[@]}"; do - if [[ -r $f && ! -d $f ]]; then - config+=("$f") - # The Included file is processed to look for Included files in itself - _included_ssh_config_files "$f" - fi - done - fi + __expand_tilde_by_ref i + # In case the expanded variable contains multiple paths + _comp_expand_glob files '$i' + if ((${#files[@]})); then + for f in "${files[@]}"; do + if [[ -r $f && ! -d $f ]]; then + config+=("$f") + included+=("$f") + fi + done + fi + done done } # _included_ssh_config_files()