nutools/ussh

315 lines
9.4 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")/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'"
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 __have_ssh_config() {
local host="$1" config="$2"
[ -f "$config" ] || return 1
awkrun host="$host" '
BEGIN {
in_host = 0
ec = 1
}
tolower($1) == "host" {
in_host = 0
for (i = 2; i <= NF; i++) {
if ($i == host) {
in_host = 1
break
}
}
}
in_host && tolower($1) == "user" {
ec = 0
exit 0
}
END {
exit ec
}
' -- "$config"
}
function __update_sshopts_l() {
# Ajouter l'option -l USSH_USER au tableau sshopts si aucune définition pour
# l'hôte $1 n'existe dans l'un des fichiers de configuration de ssh
if [ -n "$1" ]; then
__have_ssh_config "$1" ~/.ssh/config && return
__have_ssh_config "$1" /etc/ssh/ssh_config && return
fi
# Rajouter l'option au début pour que l'utilisateur puisse la surcharger
sshopts=(${USSH_USER:+-l "$USSH_USER"} "${sshopts[@]}")
}
function show_vars() {
[ "${#hosts[*]}" -gt 1 ] && exec=
local -a sshopts
array_copy sshopts SSHOPTS
__update_sshopts_l "${hosts[0]}"
set_var_cmd ssh "$SSH"
set_var_cmd exec "$exec"
set_array_cmd options sshopts
set_array_cmd hosts
set_array_cmd args @ "$@"
}
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_l "$host"
${exec:+exec} "$SSH" "${sshopts[@]}" "$host" "$@" || r=$?
[ -z "$onehost" ] && eend
done
return "${r:-0}"
}
function __update_SSHOPTS() { SSHOPTS=("${SSHOPTS[@]}" "$@"); }
# charger USSH_DOMAIN et USSH_USER
set_defaults nutools
DOMAIN="$USSH_DOMAIN"
SSH=
remove=
remove_only=
exec=1
parse=
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=' \
@ 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 "$@"
else
do_ssh "$@"
fi