464 lines
14 KiB
Bash
Executable File
464 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
|
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
|
|
urequire DEFAULTS
|
|
|
|
function display_help() {
|
|
uecho "$scriptname: se connecter par ssh à un ou plusieurs hôtes
|
|
|
|
USAGE
|
|
$scriptname [options] hosts
|
|
$scriptname [options] @hostsfile
|
|
$scriptname -r hosts
|
|
$scriptname --parse hosts
|
|
|
|
OPTIONS
|
|
hosts
|
|
@hostsfile
|
|
Spécifier un ou plusieurs hôtes distants sur lequels faire la connexion.
|
|
Pour spécifier plusieurs hôtes, il faut les séparer par un espace ou le
|
|
caractère ':', e.g. 'host1 host2' ou 'host1:host2'. Si la spécification
|
|
contient les caractères { et }, l'expansion est effectuée, e.g
|
|
'root@{host1,host2}.univ.run'
|
|
La forme @hostsfile permet de lire la liste des hôtes depuis le fichier
|
|
hostsfile, à raison d'un hôte par ligne.
|
|
|
|
Toutes les options de ssh sont reconnues. Les options longues suivantes sont
|
|
reconnues comme alias de certaines options courtes de ssh:
|
|
--quiet
|
|
alias de -q, activer le mode non verbeux
|
|
--tty
|
|
alias de -t, forcer l'allocation d'un TTY
|
|
--login USER
|
|
alias de -l, spécifier le user avec lequel se connecter
|
|
--port PORT
|
|
alias de -p, spécifier le port sur lequel se connecter
|
|
|
|
Les options suivantes sont exclusives à ce script:
|
|
-d, --domain DOMAIN
|
|
Spécifier un domaine par défaut pour les hôtes qui sont spécifiés sans
|
|
domaine.
|
|
-z, --ssh SSH
|
|
Spécifier l'exécutable à utiliser pour lancer ssh.
|
|
-r, --remove
|
|
Lancer 'ssh-keygen -R' pour chacun des hôtes spécifiés avant de s'y
|
|
connecter. Par exemple:
|
|
$scriptname -r host.tld
|
|
est équivalent à:
|
|
ssh-keygen -R host.tld
|
|
ssh-keygen -R host
|
|
ssh-keygen -R 10.10.1.5
|
|
ssh host.tld
|
|
si l'adresse ip de host.tld est 10.10.1.5
|
|
Quand cette option est spécifié, l'option -j est reconnue et permet de
|
|
NE PAS se reconnecter à l'hôte juste après avoir nettoyé les clés. Avec
|
|
l'option -j, TOUS les arguments sont des noms d'hôte puisqu'aucune
|
|
connexion n'est effectuée.
|
|
--exec
|
|
--no-exec
|
|
Avec --exec, si un seul hôte est spécifié, lancer le processus ssh avec
|
|
exec, pour éviter d'encombrer la mémoire. C'est l'option par défaut.
|
|
Avec --no-exec, ne jamais utiliser exec pour lancer ssh.
|
|
--parse
|
|
Afficher la définition des variables ssh, options, hosts et args qui
|
|
permettent d'effectuer la connexion à partir d'un autre script. Exemple:
|
|
"'eval "$(ussh --parse args...)"
|
|
for host in "${hosts[@]}"; do
|
|
${exec:+exec} "$ssh" "${options[@]}" "$host" "${args[@]}"
|
|
done'"
|
|
--cc
|
|
Assumer que nutools est installé sur l'hôte distant, et y lancer uwatch
|
|
avec l'option --cc, pour permettre de garder la connexion active dans le
|
|
cadre d'une redirection de port.
|
|
|
|
Si la variable UTOOLS_USSH_RSYNC_SUPPORT contient une valeur non vide, l'analyse
|
|
des arguments s'arrête à la première valeur qui n'est pas une option, afin de
|
|
permettre l'utilisation de ce script avec l'option -e de rsync."
|
|
}
|
|
|
|
__PARSED_HOSTS=()
|
|
__PARSED_FILES=()
|
|
function parse_hostsfile() {
|
|
# Lire chacun des fichiers $* et initialiser __PARSED_HOSTS avec la liste
|
|
# des hôtes mentionnés dans les fichiers.
|
|
local inputfile basedir inputs input
|
|
for inputfile in "$@"; do
|
|
inputfile="$(abspath "$inputfile")"
|
|
array_contains __PARSED_FILES "$inputfile" && {
|
|
ewarn "$(ppath "$inputfile"): inclusion récursive"
|
|
continue
|
|
}
|
|
array_add __PARSED_FILES "$inputfile"
|
|
basedir="$(dirname "$inputfile")"
|
|
|
|
array_from_lines inputs "$(<"$inputfile" filter_conf)" || {
|
|
ewarn "$inputfile: fichier ingnoré"
|
|
continue
|
|
}
|
|
for input in "${inputs[@]}"; do
|
|
if [ "${input#@}" != "$input" ]; then
|
|
# fichier inclus
|
|
parse_hostsfile "$(abspath "${input#@}" "$basedir")"
|
|
else
|
|
array_addu __PARSED_HOSTS "$input"
|
|
fi
|
|
done
|
|
done
|
|
}
|
|
function __expand_braces() {
|
|
if [[ "$1" == *{* ]] && [[ "$1" == *}* ]]; then
|
|
eval "echo $1"
|
|
else
|
|
echo "$1"
|
|
fi
|
|
}
|
|
function __dot_is_localhost() {
|
|
[ "$1" == "." ] && echo "localhost" || echo "$1"
|
|
}
|
|
function __fix_domain() {
|
|
local user host
|
|
splituserhost "$1" user host
|
|
if [ "$host" == localhost ]; then
|
|
: # ne pas corriger localhost
|
|
elif [ "${host%.}" != "$host" ]; then
|
|
# si le nom se termine par ., le prendre tel quel
|
|
host="${host%.}"
|
|
elif ! [[ "$host" == *.* ]]; then
|
|
# sinon rajouter le domaine par défaut le cas échéant
|
|
host="$host${DOMAIN:+.$DOMAIN}"
|
|
fi
|
|
echo "${user:+$user@}$host"
|
|
}
|
|
function fix_hosts() {
|
|
# Si hosts contient des éléments multiple, comme a:b, séparer ces
|
|
# éléments. i.e (a b:c "d e") --> (a b c d e)
|
|
# Supporter la syntaxe @hostsfile qui permet de charger la liste des hôtes
|
|
# depuis un fichier.
|
|
# Remplacer aussi les '.' par 'localhost'
|
|
array_map hosts __expand_braces
|
|
array_fix_paths hosts ":"
|
|
array_fix_paths hosts " "
|
|
|
|
local -a _hosts _tmphosts host
|
|
for host in "${hosts[@]}"; do
|
|
host="${host%/}"
|
|
if [ "${host#@}" != "$host" ]; then
|
|
__PARSED_HOSTS=()
|
|
parse_hostsfile "${host#@}"
|
|
array_fix_paths __PARSED_HOSTS
|
|
array_extendu _hosts __PARSED_HOSTS
|
|
else
|
|
array_addu _hosts "$host"
|
|
fi
|
|
done
|
|
array_map _hosts __dot_is_localhost
|
|
array_map _hosts __fix_domain
|
|
array_copy hosts _hosts
|
|
}
|
|
|
|
function remove_key() {
|
|
estep "$1"
|
|
ssh-keygen -R "$1" >&/dev/null
|
|
}
|
|
function remove_keys() {
|
|
urequire ipcalc
|
|
|
|
local -a __hosts; array_copy __hosts hosts
|
|
local -a allhosts hosts ips; local host hostname user ip
|
|
array_copy allhosts __hosts
|
|
|
|
etitle "Suppression des entrées dans ~/.ssh/known_hosts"
|
|
for host in "${allhosts[@]}"; do
|
|
splituserhost "$host" user host
|
|
if ipcalc_checkip "$host" >/dev/null; then
|
|
ip="$host"
|
|
remove_key "$ip"
|
|
|
|
resolv_hosts hosts "$ip"
|
|
for host in "${hosts[@]}"; do
|
|
remove_key "$host"
|
|
hostname="${host%%.*}"
|
|
[ "$hostname" != "$host" ] && remove_key "$hostname"
|
|
done
|
|
else
|
|
remove_key "$host"
|
|
hostname="${host%%.*}"
|
|
[ "$hostname" != "$host" ] && remove_key "$hostname"
|
|
|
|
resolv_ips ips "$host"
|
|
for ip in "${ips[@]}"; do
|
|
remove_key "$ip"
|
|
done
|
|
fi
|
|
done
|
|
eend
|
|
}
|
|
|
|
function __parse_ssh_config_user() {
|
|
local config="$1"
|
|
if [ -f "$config" ]; then
|
|
awkrun host="$host" '
|
|
BEGIN {
|
|
in_host = 0
|
|
found_user = 0
|
|
}
|
|
!found_user && tolower($1) == "host" {
|
|
in_host = 0
|
|
for (i = 2; i <= NF; i++) {
|
|
if ($i == host) {
|
|
in_host = 1
|
|
break
|
|
}
|
|
}
|
|
}
|
|
in_host && tolower($1) == "user" {
|
|
print "SSH_USER=\"" $2 "\""
|
|
found_user = 1
|
|
}
|
|
END {
|
|
if (found_user) print "true"
|
|
else print "false"
|
|
}
|
|
' -- "$config"
|
|
else
|
|
echo "false"
|
|
fi
|
|
}
|
|
function __update_sshopts_user() {
|
|
# Déterminer le compte utilisateur de connexion et le placer dans la
|
|
# variables SSH_USER. Analyser pour cela les arguments qui sont fournis dans
|
|
# les paramètres 2..@. Ajouter l'option -l $DEFAULT_USER au tableau sshopts
|
|
# si aucune définition pour l'hôte $1 n'existe dans l'un des fichiers de
|
|
# configuration de ssh et si l'utilisateur n'a pas déjà spécifié un compte
|
|
# utilisateur.
|
|
SSH_USER=
|
|
# L'utilisateur est peut-être fourni avec l'hôte
|
|
if [ -n "$user" ]; then
|
|
SSH_USER="$user"
|
|
return
|
|
fi
|
|
# Analyser les configuration de ssh
|
|
eval "$(__parse_ssh_config_user ~/.ssh/config)" && return
|
|
eval "$(__parse_ssh_config_user /etc/ssh/ssh_config)" && return
|
|
# Analyser les arguments
|
|
local i=1
|
|
while [ $i -le $# ]; do
|
|
if [ "${!i}" == -l ]; then
|
|
i=$(($i + 1))
|
|
SSH_USER="${!i}"
|
|
fi
|
|
i=$(($i + 1))
|
|
done
|
|
[ -n "$SSH_USER" ] && return
|
|
# Rajouter l'option -l au début. De cette façon, on laisse la possibilité à
|
|
# l'utilisateur de surcharger cette option, dans le cas où nous n'avons pas
|
|
# pu analyser les arguments correctement
|
|
SSH_USER="$DEFAULT_USER"
|
|
sshopts=(${DEFAULT_USER:+-l "$DEFAULT_USER"} "${sshopts[@]}")
|
|
}
|
|
function __parse_ssh_config_port() {
|
|
local config="$1"
|
|
if [ -f "$config" ]; then
|
|
awkrun host="$host" '
|
|
BEGIN {
|
|
in_host = 0
|
|
found_port = 0
|
|
}
|
|
!found_port && tolower($1) == "host" {
|
|
in_host = 0
|
|
for (i = 2; i <= NF; i++) {
|
|
if ($i == host) {
|
|
in_host = 1
|
|
break
|
|
}
|
|
}
|
|
}
|
|
in_host && tolower($1) == "port" {
|
|
print "SSH_PORT=\"" $2 "\""
|
|
found_port = 1
|
|
}
|
|
END {
|
|
if (found_port) print "true"
|
|
else print "false"
|
|
}
|
|
' -- "$config"
|
|
else
|
|
echo "false"
|
|
fi
|
|
}
|
|
function __update_sshopts_port() {
|
|
# Déterminer le port de connexion et le placer dans la variable SSH_PORT.
|
|
# Analyser pour cela les arguments qui sont fournis dans les paramètres 2..@
|
|
SSH_PORT=22
|
|
# Analyser les configuration de ssh
|
|
eval "$(__parse_ssh_config_port ~/.ssh/config)" && return
|
|
eval "$(__parse_ssh_config_port /etc/ssh/ssh_config)" && return
|
|
# Analyser les arguments
|
|
local i=1
|
|
while [ $i -le $# ]; do
|
|
if [ "${!i}" == -p ]; then
|
|
i=$(($i + 1))
|
|
SSH_PORT="${!i}"
|
|
fi
|
|
i=$(($i + 1))
|
|
done
|
|
}
|
|
function __update_sshopts_cm() {
|
|
# Rajouter le cas échéant les options pour le multiplexage des connexions au
|
|
# tableau sshopts, pour l'hôte $1
|
|
# SSH_USER doit être défini
|
|
local found cmhost cmkey tmphost shared control persist
|
|
local -a hosts
|
|
for cmhost in "${CMHOSTS[@]}"; do
|
|
[ -n "$cmhost" ] || continue
|
|
cmkey="$cmhost"; cmkey="${cmkey//./_}"; cmkey="${cmkey//-/_}"
|
|
array_copy hosts "${cmkey}_HOSTS"
|
|
if [ -z "${hosts[0]}" ]; then
|
|
if [[ "$cmhost" == *.* ]]; then
|
|
hosts=("$cmhost" "${cmhost%%.*}")
|
|
else
|
|
hosts=("$cmhost${DEFAULT_DOMAIN:+.$DEFAULT_DOMAIN}" "$cmhost")
|
|
fi
|
|
fi
|
|
if array_contains hosts "$host"; then
|
|
found=1
|
|
break
|
|
fi
|
|
done
|
|
[ -n "$found" ] || return
|
|
shared="${cmkey}_SHARED"; shared="${!shared}"
|
|
[ -n "$shared" ] || shared=1 # connexion multiplexée par défaut
|
|
is_yes "$shared" || return
|
|
control="${cmkey}_CONTROL"; control="${!control}"
|
|
[ -n "$control" ] || control="$HOME/.ssh/@user@@host:@port"
|
|
control="${control//@user/$SSH_USER}"
|
|
control="${control//@port/$SSH_PORT}"
|
|
control="${control//@host/${hosts[0]}}"
|
|
persist="${cmkey}_PERSIST"; persist="${!persist}"
|
|
[ -n "$persist" ] || persist=auto
|
|
|
|
sshopts=("${sshopts[@]}" -o ControlPath="$control" -o ControlMaster=auto)
|
|
if [ "$persist" == auto ]; then
|
|
local version major minor
|
|
version="$(ssh -V 2>&1)"
|
|
version="${version#OpenSSH_}"; major="${version%%.*}"
|
|
minor="${version#$major.}"; minor="${minor:0:1}"
|
|
if [ $major -ge 6 ]; then
|
|
persist="$DEFAULT_PERSIST"
|
|
elif [ $major -eq 5 ]; then
|
|
if [ "$minor" -ge 6 ]; then
|
|
persist="$DEFAULT_PERSIST"
|
|
else
|
|
persist=
|
|
fi
|
|
elif [ $major -le 4 ]; then
|
|
persist=
|
|
fi
|
|
fi
|
|
[ -n "$persist" ] && sshopts=("${sshopts[@]}" -o ControlPersist="$persist")
|
|
}
|
|
function __update_sshopts() {
|
|
local user host
|
|
splituserhost "$1" user host; shift
|
|
__update_sshopts_user "$@"
|
|
__update_sshopts_port "$@"
|
|
__update_sshopts_cm
|
|
}
|
|
|
|
function show_vars() {
|
|
local -a sshopts
|
|
[ "${#hosts[*]}" -gt 1 ] && exec=
|
|
set_array_cmd hosts
|
|
set_var_cmd ssh "$SSH"
|
|
set_var_cmd exec "$exec"
|
|
set_array_cmd args @ "$@"
|
|
for host in "${hosts[@]}"; do
|
|
array_copy sshopts SSHOPTS
|
|
__update_sshopts "$host" "$@"
|
|
set_var_cmd host "$host"
|
|
set_array_cmd options sshopts
|
|
done
|
|
}
|
|
|
|
function do_ssh() {
|
|
local -a sshopts
|
|
local onehost r
|
|
if [ "${#hosts[*]}" -gt 1 ]; then
|
|
onehost=
|
|
exec=
|
|
else
|
|
onehost=1
|
|
fi
|
|
for host in "${hosts[@]}"; do
|
|
[ -z "$onehost" ] && etitle "$host"
|
|
|
|
array_copy sshopts SSHOPTS
|
|
__update_sshopts "$host" "$@"
|
|
${exec:+exec} "$SSH" "${sshopts[@]}" "$host" "$@" || r=$?
|
|
|
|
[ -z "$onehost" ] && eend
|
|
done
|
|
return "${r:-0}"
|
|
}
|
|
|
|
function __update_SSHOPTS() { SSHOPTS=("${SSHOPTS[@]}" "$@"); }
|
|
|
|
# charger la configuration
|
|
set_defaults nutools
|
|
set_defaults ussh
|
|
[ -n "$DEFAULT_USER" ] || DEFAULT_USER="$USSH_USER"
|
|
[ -n "$DEFAULT_DOMAIN" ] || DEFAULT_DOMAIN="$USSH_DOMAIN"
|
|
[ -n "$DEFAULT_PERSIST" ] || DEFAULT_PERSIST=5m
|
|
|
|
DOMAIN="$DEFAULT_DOMAIN"
|
|
SSH=
|
|
remove=
|
|
remove_only=
|
|
exec=1
|
|
parse=
|
|
uwatch=
|
|
parse_opts ${UTOOLS_USSH_RSYNC_SUPPORT:++} "${PRETTYOPTS[@]}" \
|
|
--help '$exit_with display_help' \
|
|
-1,-2,-4,-6,-A,-a,-C,-f,-g,-K,-k,-M,-N,-n,-q,-s,-T,-t,-V,-v,-X,-x,-Y,-y '$__update_SSHOPTS "$option_"' \
|
|
-b:,-c:,-D:,-e:,-F:,-I:,-i:,-L:,-l:,-m:,-O:,-o:,-p:,-R:,-S:,-W:,-w: '$__update_SSHOPTS "$option_" "$value_"' \
|
|
--quiet '$__update_SSHOPTS -q' \
|
|
--tty '$__update_SSHOPTS -t' \
|
|
--login: '$__update_SSHOPTS -l "$value_"' \
|
|
--port: '$__update_SSHOPTS -p "$value_"' \
|
|
-d:,--domain: DOMAIN= \
|
|
-z:,--ssh: SSH= \
|
|
-r,--remove '$remove=1; parse=' \
|
|
-j remove_only=1 \
|
|
--exec exec=1 \
|
|
--no-exec exec= \
|
|
--parse '$parse=1; remove=' \
|
|
--cc uwatch=1 \
|
|
@ args -- "$@" && set -- "${args[@]}" || die "$args"
|
|
|
|
if [ -n "$remove" ]; then
|
|
[ -n "$*" ] || die "Vous devez spécifier les hôtes à supprimer de ~/.ssh/known_hosts"
|
|
if [ -n "$remove_only" ]; then
|
|
hosts=("$@")
|
|
fix_hosts
|
|
remove_keys
|
|
exit 0
|
|
fi
|
|
|
|
hosts=("$1"); shift
|
|
fix_hosts
|
|
remove_keys
|
|
else
|
|
hosts=("$1"); shift
|
|
fix_hosts
|
|
fi
|
|
|
|
[ -n "$SSH" ] || SSH=ssh
|
|
if [ -n "$parse" ]; then
|
|
show_vars "$@"
|
|
elif [ -n "$uwatch" ]; then
|
|
__update_SSHOPTS -t
|
|
do_ssh "/usr/local/nutools/uwatch --cc"
|
|
else
|
|
do_ssh "$@"
|
|
fi
|