1# ssh(1) completion -*- shell-script -*- 2 3_ssh_queries() 4{ 5 COMPREPLY+=($(compgen -W \ 6 "cipher cipher-auth help mac kex key key-cert key-plain key-sig 7 protocol-version compression sig 8 ciphers macs kexalgorithms pubkeyacceptedkeytypes 9 hostkeyalgorithms hostbasedkeytypes hostbasedacceptedkeytypes" \ 10 -- "${cur,,}")) 11} 12 13_ssh_query() 14{ 15 ${1:-ssh} -Q $2 2>/dev/null 16} 17 18_ssh_ciphers() 19{ 20 local ciphers='$(_ssh_query "$1" cipher)' 21 [[ $ciphers ]] || ciphers="3des-cbc aes128-cbc aes192-cbc aes256-cbc 22 aes128-ctr aes192-ctr aes256-ctr arcfour128 arcfour256 arcfour 23 blowfish-cbc cast128-cbc" 24 COMPREPLY+=($(compgen -W "$ciphers" -- "$cur")) 25} 26 27_ssh_macs() 28{ 29 local macs='$(_ssh_query "$1" mac)' 30 [[ $macs ]] || macs="hmac-md5 hmac-sha1 umac-64@openssh.com hmac-ripemd160 31 hmac-sha1-96 hmac-md5-96" 32 COMPREPLY+=($(compgen -W "$macs" -- "$cur")) 33} 34 35_ssh_options() 36{ 37 local opts=( 38 AddKeysToAgent AddressFamily BatchMode BindAddress CanonicalDomains 39 CanonicalizeFallbackLocal CanonicalizeHostname CanonicalizeMaxDots 40 CanonicalizePermittedCNAMEs CASignatureAlgorithms CertificateFile 41 ChallengeResponseAuthentication CheckHostIP Ciphers ClearAllForwardings 42 Compression ConnectionAttempts ConnectTimeout ControlMaster ControlPath 43 ControlPersist DynamicForward EnableSSHKeysign EscapeChar 44 ExitOnForwardFailure FingerprintHash ForwardAgent ForwardX11 45 ForwardX11Timeout ForwardX11Trusted GatewayPorts GlobalKnownHostsFile 46 GSSAPIAuthentication GSSAPIClientIdentity GSSAPIDelegateCredentials 47 GSSAPIKeyExchange GSSAPIRenewalForcesRekey GSSAPIServerIdentity 48 GSSAPITrustDns HashKnownHosts Host HostbasedAuthentication 49 HostbasedKeyTypes HostKeyAlgorithms HostKeyAlias HostName 50 IdentitiesOnly IdentityAgent IdentityFile IgnoreUnknown Include IPQoS 51 KbdInteractiveAuthentication KbdInteractiveDevices KexAlgorithms 52 LocalCommand LocalForward LogLevel MACs 53 NoHostAuthenticationForLocalhost NumberOfPasswordPrompts 54 PasswordAuthentication PermitLocalCommand PKCS11Provider Port 55 PreferredAuthentications ProxyCommand ProxyJump ProxyUseFdpass 56 PubkeyAcceptedKeyTypes PubkeyAuthentication RekeyLimit RemoteCommand 57 RemoteForward RequestTTY RevokedHostKeys SendEnv ServerAliveCountMax 58 ServerAliveInterval SmartcardDevice StreamLocalBindMask 59 StreamLocalBindUnlink StrictHostKeyChecking SyslogFacility TCPKeepAlive 60 Tunnel TunnelDevice UpdateHostKeys UsePrivilegedPort User 61 UserKnownHostsFile VerifyHostKeyDNS VisualHostKey XAuthLocation) 62 local protocols=$(_ssh_query "$1" protocol-version) 63 if [[ -z $protocols || $protocols == *1* ]]; then 64 opts+=(Cipher CompressionLevel Protocol RhostsRSAAuthentication 65 RSAAuthentication) 66 fi 67 68 compopt -o nospace 69 local IFS=$' \t\n' reset=$(shopt -p nocasematch) 70 shopt -s nocasematch 71 local option 72 COMPREPLY=($(for option in "${opts[@]}"; do 73 [[ $option == "$cur"* ]] && printf '%s=\n' "$option" 74 done)) 75 $reset 76} 77 78# Complete a ssh suboption (like ForwardAgent=y<tab>) 79# Two parameters: the string to complete including the equal sign, and 80# the ssh executable to invoke (optional). 81# Not all suboptions are completed. 82# Doesn't handle comma-separated lists. 83_ssh_suboption() 84{ 85 # Split into subopt and subval 86 local prev=${1%%=*} cur=${1#*=} 87 88 case ${prev,,} in 89 batchmode | canonicaldomains | canonicalizefallbacklocal | \ 90 challengeresponseauthentication | checkhostip | \ 91 clearallforwardings | controlpersist | compression | enablesshkeysign | \ 92 exitonforwardfailure | forwardagent | forwardx11 | forwardx11trusted | \ 93 gatewayports | gssapiauthentication | gssapikeyexchange | \ 94 gssapidelegatecredentials | gssapirenewalforcesrekey | gssapitrustdns | \ 95 hashknownhosts | hostbasedauthentication | identitiesonly | \ 96 kbdinteractiveauthentication | kbdinteractivedevices | \ 97 nohostauthenticationforlocalhost | passwordauthentication | permitlocalcommand | \ 98 proxyusefdpass | pubkeyauthentication | rhostsrsaauthentication | \ 99 rsaauthentication | streamlocalbindunlink | \ 100 tcpkeepalive | useprivilegedport | visualhostkey) 101 COMPREPLY=($(compgen -W 'yes no' -- "$cur")) 102 ;; 103 addkeystoagent) 104 COMPREPLY=($(compgen -W 'yes ask confirm no' -- "$cur")) 105 ;; 106 addressfamily) 107 COMPREPLY=($(compgen -W 'any inet inet6' -- "$cur")) 108 ;; 109 bindaddress) 110 _ip_addresses 111 ;; 112 canonicalizehostname) 113 COMPREPLY=($(compgen -W 'yes no always' -- "$cur")) 114 ;; 115 identityfile) 116 _ssh_identityfile 117 ;; 118 *file | identityagent | include | controlpath | revokedhostkeys | xauthlocation) 119 _filedir 120 ;; 121 casignaturealgorithms) 122 COMPREPLY=($(compgen -W '$(_ssh_query "$2" sig)' -- "$cur")) 123 ;; 124 cipher) 125 COMPREPLY=($(compgen -W 'blowfish des 3des' -- "$cur")) 126 ;; 127 ciphers) 128 _ssh_ciphers "$2" 129 ;; 130 controlmaster) 131 COMPREPLY=($(compgen -W 'yes ask auto autoask no' -- "$cur")) 132 ;; 133 compressionlevel) 134 COMPREPLY=($(compgen -W '{1..9}' -- "$cur")) 135 ;; 136 fingerprinthash) 137 COMPREPLY=($(compgen -W 'md5 sha256' -- "$cur")) 138 ;; 139 ipqos) 140 COMPREPLY=($(compgen -W 'af1{1..4} af2{2..3} af3{1..3} af4{1..3} 141 cs{0..7} ef lowdelay throughput reliability' -- "$cur")) 142 ;; 143 hostbasedkeytypes | hostkeyalgorithms) 144 COMPREPLY=($(compgen -W '$(_ssh_query "$2" key)' -- "$cur")) 145 ;; 146 kexalgorithms) 147 COMPREPLY=($(compgen -W '$(_ssh_query "$2" kex)' -- "$cur")) 148 ;; 149 loglevel) 150 COMPREPLY=($(compgen -W 'QUIET FATAL ERROR INFO VERBOSE DEBUG{,1,2,3}' -- "$cur")) 151 ;; 152 macs) 153 _ssh_macs "$2" 154 ;; 155 pkcs11provider) 156 _filedir so 157 ;; 158 preferredauthentications) 159 COMPREPLY=($(compgen -W 'gssapi-with-mic host-based publickey 160 keyboard-interactive password' -- "$cur")) 161 ;; 162 protocol) 163 local protocols=($(_ssh_query "$2" protocol-version)) 164 [[ $protocols ]] || protocols=(1 2) 165 if ((${#protocols[@]} > 1)); then 166 COMPREPLY=($(compgen -W '${protocols[@]}' -- "$cur")) 167 fi 168 ;; 169 proxyjump) 170 _known_hosts_real -a ${configfile:+-F "$configfile"} -- "$cur" 171 ;; 172 proxycommand | remotecommand | localcommand) 173 COMPREPLY=($(compgen -c -- "$cur")) 174 ;; 175 pubkeyacceptedkeytypes) 176 COMPREPLY=($(compgen -W '$(_ssh_query "$2" key)' -- "$cur")) 177 ;; 178 requesttty) 179 COMPREPLY=($(compgen -W 'no yes force auto' -- "$cur")) 180 ;; 181 stricthostkeychecking) 182 COMPREPLY=($(compgen -W 'accept-new ask no off' -- "$cur")) 183 ;; 184 syslogfacility) 185 COMPREPLY=($(compgen -W 'DAEMON USER AUTH LOCAL{0..7}' -- "$cur")) 186 ;; 187 tunnel) 188 COMPREPLY=($(compgen -W 'yes no point-to-point ethernet' \ 189 -- "$cur")) 190 ;; 191 updatehostkeys | verifyhostkeydns) 192 COMPREPLY=($(compgen -W 'yes no ask' -- "$cur")) 193 ;; 194 esac 195 return 0 196} 197 198# Try to complete -o SubOptions= 199# 200# Returns 0 if the completion was handled or non-zero otherwise. 201_ssh_suboption_check() 202{ 203 # Get prev and cur words without splitting on = 204 local cureq=$(_get_cword :=) preveq=$(_get_pword :=) 205 if [[ $cureq == *=* && $preveq == -*o ]]; then 206 _ssh_suboption $cureq "$1" 207 return $? 208 fi 209 return 1 210} 211 212# Search COMP_WORDS for '-F configfile' or '-Fconfigfile' argument 213_ssh_configfile() 214{ 215 set -- "${words[@]}" 216 while (($# > 0)); do 217 if [[ $1 == -F* ]]; then 218 if ((${#1} > 2)); then 219 configfile="$(dequote "${1:2}")" 220 else 221 shift 222 [[ ${1-} ]] && configfile="$(dequote "$1")" 223 fi 224 break 225 fi 226 shift 227 done 228} 229 230# With $1 set, look for public key files, else private 231# shellcheck disable=SC2120 232_ssh_identityfile() 233{ 234 [[ -z $cur && -d ~/.ssh ]] && cur=~/.ssh/id 235 _filedir 236 if ((${#COMPREPLY[@]} > 0)); then 237 COMPREPLY=($(compgen -W '${COMPREPLY[@]}' \ 238 -X "${1:+!}*.pub" -- "$cur")) 239 fi 240} 241 242_ssh() 243{ 244 local cur prev words cword 245 _init_completion -n : || return 246 247 local configfile 248 _ssh_configfile 249 250 _ssh_suboption_check "$1" && return 251 252 local ipvx 253 254 case $prev in 255 -*b) 256 _ip_addresses 257 return 258 ;; 259 -*c) 260 _ssh_ciphers "$1" 261 return 262 ;; 263 -*[DeLpRW]) 264 return 265 ;; 266 -*[EFS]) 267 _filedir 268 return 269 ;; 270 -*i) 271 _ssh_identityfile 272 return 273 ;; 274 -*I) 275 _filedir so 276 return 277 ;; 278 -*J) 279 _known_hosts_real -a ${configfile:+-F "$configfile"} -- "$cur" 280 return 281 ;; 282 -*l) 283 COMPREPLY=($(compgen -u -- "$cur")) 284 return 285 ;; 286 -*m) 287 _ssh_macs "$1" 288 return 289 ;; 290 -*O) 291 COMPREPLY=($(compgen -W 'check forward cancel exit stop' -- "$cur")) 292 return 293 ;; 294 -*o) 295 _ssh_options "$1" 296 return 297 ;; 298 -*Q) 299 _ssh_queries "$1" 300 return 301 ;; 302 -*w) 303 _available_interfaces 304 return 305 ;; 306 -*4*) 307 ipvx=-4 308 ;; 309 -*6*) 310 ipvx=-6 311 ;; 312 esac 313 314 if [[ $cur == -F* ]]; then 315 cur=${cur#-F} 316 _filedir 317 # Prefix completions with '-F' 318 COMPREPLY=("${COMPREPLY[@]/#/-F}") 319 cur=-F$cur # Restore cur 320 elif [[ $cur == -* ]]; then 321 COMPREPLY=($(compgen -W '$(_parse_usage "$1")' -- "$cur")) 322 else 323 _known_hosts_real ${ipvx-} -a ${configfile:+-F "$configfile"} -- "$cur" 324 325 local args 326 _count_args 327 if ((args > 1)); then 328 compopt -o filenames 329 COMPREPLY+=($(compgen -c -- "$cur")) 330 fi 331 fi 332} && 333 shopt -u hostcomplete && complete -F _ssh ssh slogin autossh sidedoor 334 335# sftp(1) completion 336# 337_sftp() 338{ 339 local cur prev words cword 340 _init_completion || return 341 342 local configfile 343 _ssh_configfile 344 345 _ssh_suboption_check && return 346 347 local ipvx 348 349 case $prev in 350 -*[BDlPRs]) 351 return 352 ;; 353 -*[bF]) 354 _filedir 355 return 356 ;; 357 -*i) 358 _ssh_identityfile 359 return 360 ;; 361 -*c) 362 _ssh_ciphers 363 return 364 ;; 365 -*J) 366 _known_hosts_real -a ${configfile:+-F "$configfile"} -- "$cur" 367 return 368 ;; 369 -*o) 370 _ssh_options 371 return 372 ;; 373 -*S) 374 compopt -o filenames 375 COMPREPLY=($(compgen -c -- "$cur")) 376 return 377 ;; 378 -*4*) 379 ipvx=-4 380 ;; 381 -*6*) 382 ipvx=-6 383 ;; 384 esac 385 386 if [[ $cur == -F* ]]; then 387 cur=${cur#-F} 388 _filedir 389 # Prefix completions with '-F' 390 COMPREPLY=("${COMPREPLY[@]/#/-F}") 391 cur=-F$cur # Restore cur 392 elif [[ $cur == -* ]]; then 393 COMPREPLY=($(compgen -W '$(_parse_usage "$1")' -- "$cur")) 394 else 395 _known_hosts_real ${ipvx-} -a ${configfile:+-F "$configfile"} -- "$cur" 396 fi 397} && 398 shopt -u hostcomplete && complete -F _sftp sftp 399 400# things we want to backslash escape in scp paths 401# shellcheck disable=SC2089 402_scp_path_esc='[][(){}<>"'"'"',:;^&!$=?`\\|[:space:]]' 403 404# Complete remote files with ssh. If the first arg is -d, complete on dirs 405# only. Returns paths escaped with three backslashes. 406# shellcheck disable=SC2120 407_scp_remote_files() 408{ 409 local IFS=$'\n' 410 411 # remove backslash escape from the first colon 412 cur=${cur/\\:/:} 413 414 local userhost=${cur%%?(\\):*} 415 local path=${cur#*:} 416 417 # unescape (3 backslashes to 1 for chars we escaped) 418 # shellcheck disable=SC2090 419 path=$(command sed -e 's/\\\\\\\('$_scp_path_esc'\)/\\\1/g' <<<"$path") 420 421 # default to home dir of specified user on remote host 422 if [[ -z $path ]]; then 423 path=$(ssh -o 'Batchmode yes' $userhost pwd 2>/dev/null) 424 fi 425 426 local files 427 if [[ $1 == -d ]]; then 428 # escape problematic characters; remove non-dirs 429 # shellcheck disable=SC2090 430 files=$(ssh -o 'Batchmode yes' $userhost \ 431 command ls -aF1dL "$path*" 2>/dev/null | 432 command sed -e 's/'$_scp_path_esc'/\\\\\\&/g' -e '/[^\/]$/d') 433 else 434 # escape problematic characters; remove executables, aliases, pipes 435 # and sockets; add space at end of file names 436 # shellcheck disable=SC2090 437 files=$(ssh -o 'Batchmode yes' $userhost \ 438 command ls -aF1dL "$path*" 2>/dev/null | 439 command sed -e 's/'$_scp_path_esc'/\\\\\\&/g' -e 's/[*@|=]$//g' \ 440 -e 's/[^\/]$/& /g') 441 fi 442 COMPREPLY+=($files) 443} 444 445# This approach is used instead of _filedir to get a space appended 446# after local file/dir completions, and -o nospace retained for others. 447# If first arg is -d, complete on directory names only. The next arg is 448# an optional prefix to add to returned completions. 449_scp_local_files() 450{ 451 local IFS=$'\n' 452 453 local dirsonly=false 454 if [[ ${1-} == -d ]]; then 455 dirsonly=true 456 shift 457 fi 458 459 if $dirsonly; then 460 COMPREPLY+=($(command ls -aF1dL $cur* 2>/dev/null | 461 command sed -e "s/$_scp_path_esc/\\\\&/g" -e '/[^\/]$/d' -e "s/^/${1-}/")) 462 else 463 COMPREPLY+=($(command ls -aF1dL $cur* 2>/dev/null | 464 command sed -e "s/$_scp_path_esc/\\\\&/g" -e 's/[*@|=]$//g' \ 465 -e 's/[^\/]$/& /g' -e "s/^/${1-}/")) 466 fi 467} 468 469# scp(1) completion 470# 471_scp() 472{ 473 local cur prev words cword 474 _init_completion -n : || return 475 476 local configfile 477 _ssh_configfile 478 479 _ssh_suboption_check && { 480 COMPREPLY=("${COMPREPLY[@]/%/ }") 481 return 482 } 483 484 local ipvx 485 486 case $prev in 487 -*c) 488 _ssh_ciphers 489 COMPREPLY=("${COMPREPLY[@]/%/ }") 490 return 491 ;; 492 -*F) 493 _filedir 494 compopt +o nospace 495 return 496 ;; 497 -*i) 498 _ssh_identityfile 499 compopt +o nospace 500 return 501 ;; 502 -*J) 503 _known_hosts_real -a ${configfile:+-F "$configfile"} -- "$cur" 504 return 505 ;; 506 -*[lP]) 507 return 508 ;; 509 -*o) 510 _ssh_options 511 return 512 ;; 513 -*S) 514 compopt +o nospace -o filenames 515 COMPREPLY=($(compgen -c -- "$cur")) 516 return 517 ;; 518 -*4*) 519 ipvx=-4 520 ;; 521 -*6*) 522 ipvx=-6 523 ;; 524 esac 525 526 _expand || return 527 528 case $cur in 529 !(*:*)/* | [.~]*) ;; # looks like a path 530 *:*) 531 _scp_remote_files 532 return 533 ;; 534 esac 535 536 local prefix 537 538 if [[ $cur == -F* ]]; then 539 cur=${cur#-F} 540 prefix=-F 541 else 542 case $cur in 543 -*) 544 COMPREPLY=($(compgen -W '$(_parse_usage "${words[0]}")' \ 545 -- "$cur")) 546 COMPREPLY=("${COMPREPLY[@]/%/ }") 547 return 548 ;; 549 */* | [.~]*) 550 # not a known host, pass through 551 ;; 552 *) 553 _known_hosts_real ${ipvx-} -c -a \ 554 ${configfile:+-F "$configfile"} -- "$cur" 555 ;; 556 esac 557 fi 558 559 _scp_local_files "${prefix-}" 560} && 561 complete -F _scp -o nospace scp 562 563# ex: filetype=sh 564