470 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			470 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 [ -z "$major" ]; then
 | |
|             # ne devrait pas se produire... mais au cas où
 | |
|             persist=
 | |
|         elif [ "$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
 | |
|     echo_setv ssh "$SSH"
 | |
|     echo_setv exec "$exec"
 | |
|     set_array_cmd args @ "$@"
 | |
|     for host in "${hosts[@]}"; do
 | |
|         array_copy sshopts SSHOPTS
 | |
|         __update_sshopts "$host" "$@"
 | |
|         echo_setv host "$host"
 | |
|         set_array_cmd options sshopts
 | |
|     done
 | |
| }
 | |
| 
 | |
| function do_ssh() {
 | |
|     local -a sshopts
 | |
|     local onehost r
 | |
|     if [ ${#hosts[*]} -eq 0 -a $# -eq 0 ]; then
 | |
|         ${exec:+exec} "$SSH" "${SSHOPTS[@]}"
 | |
|         return
 | |
|     elif [ "${#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
 | |
| elif [ -n "$1" ]; then
 | |
|     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
 |