1# -*- coding: utf-8 -*- 2# Copyright © 2008 Carl Chenet <chaica@ohmytux.com> 3# This program is free software: you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation, either version 3 of the License, or 6# any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16#Ouvre un terminal sur une machine distante 17"""Ouvre un terminal sur une machine distante""" 18 19import sys 20import stat 21from os import linesep, chmod, sep 22from os.path import expanduser, abspath, join 23 24from optionstunnel import OptionsTunnel 25 26SSHOPTS = '-o NoHostAuthenticationForLocalhost=yes -o StrictHostKeyChecking=no' 27 28class Terminal: 29 """Ouvre un shell sur une machine distante""" 30 31 def __init__(self, opts): 32 self._ordres = [] 33 self._est_premiereligne = True 34 self._a_commande = False 35 self._a_tunnel = False 36 self._port = '' 37 self._pdest = '' 38 self._a_optstunnel = False 39 self._dernierssh = 0 40 self._destination = '' 41 self._machinefinale = '' 42 self._extfichier = '.sh' 43 self._jetons = {} 44 self._indice = 0 45 delai = opts.lesoptions()[0].delai 46 self._entetel1 = '#!/usr/bin/expect -f%s' % linesep 47 self._entetel2 = 'set timeout %s%s%s' % (str(delai), linesep, linesep) 48 self._script = [self._entetel1, self._entetel2] 49 if opts.lesoptions()[0].repsortie is not None: 50 self._destination = opts.lesoptions()[0].repsortie + sep 51 else: 52 self._destination = '' 53 self.extrait_ordres(opts.lesoptions()[0].nomfichier) 54 55 def extrait_ordres(self, nomfichier): 56 """Extrait les ordres du fichier d'ordres""" 57 # on récupère les ordres (fichiers ou entrée standard) 58 try: 59 if nomfichier is not None: 60 self._ordres = open(expanduser(nomfichier), 'r').readlines() 61 else: 62 self._ordres = sys.stdin.readlines() 63 except IOError, message: 64 print message 65 sys.exit(1) 66 except KeyboardInterrupt: 67 print _("Belier has been stopped manually by the user") 68 sys.exit(1) 69 # deux passes pour étudier les ordres 70 for boucle in xrange(2): 71 for num in xrange(len(self._ordres)): 72 # 1ère passe : on écarte les erreurs banales 73 if boucle == 0 and self._ordres[num] != linesep: 74 if '\0' in self._ordres[num]: 75 print _("The file format is invalid \ 76It may be a binary file ?") 77 sys.exit(1) 78 self._ordres[num] = self.remplace_guillemets_motdepasse( 79 self._ordres[num]) 80 if len(self._ordres[num].split(' ')) > 5: 81 print _("Incorrect argument number \ 82on the order file line") 83 sys.exit(1) 84 identifiant = self._ordres[num].split(' ')[0] 85 if len(identifiant) <= 2 and identifiant != linesep: 86 print _("A hostname must contain at \ 87least two characters (rfc952)") 88 sys.exit(1) 89 ipoudns = identifiant.split('@')[-1] 90 if len(ipoudns) > 255: 91 print _('Your domain name size \ 92exceeds 255 characters') 93 sys.exit(1) 94 for hostname in ipoudns.split('.'): 95 if len(hostname) > 64: 96 print _("Your hostname size \ 97exceeds 64 characters") 98 sys.exit(1) 99 if self._ordres[num].split()[-1] == '-c'+ linesep or \ 100 self._ordres[num].split()[-1] == '-c': 101 break 102 # 2ème passe : on génère les scripts 103 if boucle == 1: 104 self.interprete_ordres(num) 105 self.fin_liste_ordres(num) 106 107 def remplace_guillemets_motdepasse(self, ligne): 108 """On remplace les mots de passe par un jeton""" 109 est_temoin = True 110 est_vraiefin = False 111 motdepasse = '"' 112 # on transforme les mots de passe entre guillemets en jeton 113 # on évite ainsi les caractères d'espacement gênants 114 while est_temoin: 115 debut = ligne.find(' "') 116 if debut != -1: 117 intermediaire = ligne[debut+2:] 118 while not est_vraiefin: 119 if '"' in intermediaire: 120 prochain = intermediaire.find('"') 121 motdepasse = motdepasse + intermediaire[:prochain+1] 122 if motdepasse[-2] == '\\': 123 intermediaire = intermediaire[prochain+1:] 124 else: 125 est_vraiefin = True 126 else: 127 est_vraiefin = True 128 chaine = motdepasse 129 motdepasse = '"' 130 est_vraiefin = False 131 nomjeton = 'jeton%s' % str(self._indice) 132 self._jetons[nomjeton] = chaine.strip('"') 133 ligne = ligne.replace(chaine, nomjeton, 1) 134 self._indice = self._indice + 1 135 else: 136 est_temoin = False 137 return ligne 138 139 def fin_liste_ordres(self, num): 140 """On traite la fin d'une liste d'ordres""" 141 if (self._ordres[num] == linesep and 142 self._script[-1] != self._entetel2) or ( 143 num + 1 == len(self._ordres) and 144 self._script[-1] != self._entetel2): 145 # remplace les numéros de port du tunnel 146 if self._a_tunnel: 147 if self._pdest: 148 if self._port: 149 self._script[self._dernierssh] = self._script[ 150 self._dernierssh].replace( 151 '-L9999:127.0.0.1:9999','-L%s:127.0.0.1:%s' % ( 152 self._pdest, self._port),1) 153 else: 154 self._script[self._dernierssh] = self._script[ 155 self._dernierssh].replace( 156 '-L9999:127.0.0.1:9999','-L%s:127.0.0.1:22' % ( 157 self._pdest),1) 158 else: 159 if self._port: 160 self._script[self._dernierssh] = self._script[ 161 self._dernierssh].replace( 162 '127.0.0.1:9999','127.0.0.1:%s'% self._port, 1) 163 else: 164 self._script[self._dernierssh] = self._script[ 165 self._dernierssh].replace( 166 '127.0.0.1:9999','127.0.0.1:22', 1) 167 # dernière ligne du script en fonction mode commande 168 if not self._a_commande: 169 self._script.append('interact +++ return%s' % linesep) 170 else: 171 self._script.append('expect eof%s' % linesep) 172 resultat = abspath(join(self._destination, 173 ''.join([self._machinefinale, self._extfichier]))) 174 # écriture du script 175 try: 176 open(resultat, 'w').writelines(self._script) 177 chmod(resultat, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR ) 178 except IOError, message: 179 print message 180 sys.exit(1) 181 # compteur à zéro pour le prochain bloc d'ordres 182 self._script = [self._entetel1, self._entetel2] 183 self._est_premiereligne = True 184 self._a_commande = False 185 186 def interprete_ordres(self, num): 187 """On interprete un ordre""" 188 psource = '' 189 pdestination = '' 190 ligne = self._ordres[num] 191 if ligne != linesep: 192 if self._a_commande: 193 # on traite le cas spécial du mode commande 194 self.traite_commande(ligne) 195 elif self._a_optstunnel: 196 # on prepare les options du tunnel 197 optstunnel = OptionsTunnel(ligne) 198 psource, pdestination = \ 199 optstunnel.retourne_options() 200 nligne = '-L%s:127.0.0.1:%s' % (psource, 201 pdestination) 202 self._pdest = pdestination 203 self._script[self._dernierssh] = self._script[ 204 self._dernierssh].replace( 205 '-L9999:127.0.0.1:9999', nligne) 206 self._a_optstunnel = False 207 else: 208 if ligne.split()[-1] == '-t': 209 self._a_tunnel = True 210 if ligne.split()[-1] == '-ot': 211 self._a_tunnel = True 212 self._a_optstunnel = True 213 machine = ligne.split()[0] 214 self._machinefinale = ligne.split()[0].split('@')[-1] 215 self._port = '' 216 if ':' in self._machinefinale: 217 self._machinefinale, self._port = \ 218self._machinefinale.split(':') 219 machine = ligne.split()[0].split(':')[0] 220 if self._a_tunnel: 221 tunnelopts = '-L9999:127.0.0.1:9999' 222 else: 223 tunnelopts = '' 224 225 if self._est_premiereligne: 226 # première connexion 227 if not self._port: 228 self._script.append('spawn ssh %s %s %s%s' % ( 229 SSHOPTS, tunnelopts, machine, linesep)) 230 else: 231 self._script.append('spawn ssh -p %s %s %s %s%s' % 232 (self._port, SSHOPTS, tunnelopts, machine, linesep)) 233 self._est_premiereligne = False 234 else: 235 if not self._port: 236 self._script.append('send -- "ssh %s %s %s\\r"%s' % 237 (SSHOPTS, tunnelopts, machine, linesep)) 238 else: 239 self._script.append( 240 'send -- "ssh -p %s %s %s %s\\r"%s' % 241 (self._port, SSHOPTS, tunnelopts, machine, 242 linesep)) 243 if self._a_tunnel: 244 self._dernierssh = len(self._script) - 1 245 if ligne.split()[-1] == '-c': 246 self.avec_commande(ligne) 247 self._a_commande = True 248 elif ligne.split()[-1] == '-t' or ligne.split()[-1] == '-ot': 249 self.avec_commande(ligne) 250 else: 251 self.sans_commande(ligne) 252 253 def avec_commande(self, ligne): 254 """La ligne du fichier d'ordre contient le symbole commande""" 255 if len(ligne.split()) == 2: 256 self.prepare_prompt() 257 if len(ligne.split()) == 3: 258 self.envoie_motdepasse_ssh(ligne.split()[1]) 259 if len(ligne.split()) == 4 : 260 self.prepare_prompt() 261 self.change_utilisateur(ligne.split()[1], ligne.split()[2]) 262 if len(ligne.split()) == 5 : 263 self.envoie_motdepasse_ssh(ligne.split()[1]) 264 self.change_utilisateur(ligne.split()[2], ligne.split()[3]) 265 266 def sans_commande(self, ligne): 267 """La ligne du fichier d'ordre ne contient pas le symbole commande""" 268 if len(ligne.split()) == 1: 269 self.prepare_prompt() 270 if len(ligne.split()) == 2: 271 self.envoie_motdepasse_ssh(ligne.split()[1]) 272 if len(ligne.split()) == 3 : 273 self.prepare_prompt() 274 self.change_utilisateur(ligne.split()[1], ligne.split()[2]) 275 if len(ligne.split()) == 4 : 276 self.envoie_motdepasse_ssh(ligne.split()[1]) 277 self.change_utilisateur(ligne.split()[2], ligne.split()[3]) 278 279 def traite_commande(self, ligne): 280 """Traite la commande contenue dans la ligne courante""" 281 self._script.append('send -- "%s\\r"%s' 282 % (ligne.rstrip(linesep), linesep)) 283 self.prepare_prompt() 284 285 def change_utilisateur(self, utilisateur, motdepasse): 286 """Génère le script pour changer d'utilisateur""" 287 if motdepasse in self._jetons: 288 motdepasse = self._jetons[motdepasse] 289 self._script.append('send -- "su - %s\\r"%s' % (utilisateur, linesep)) 290 self._script.append('expect ":"%s' % linesep) 291 self._script.append('send -- "%s\\r"%s' % (motdepasse, linesep)) 292 self.prepare_prompt() 293 294 def prepare_prompt(self): 295 """Prépare le prompt en fonction de l'identité de l'utilisateur""" 296 self._script.append('expect -re "(%s|#|\\\\$) $"%s' % ('%', linesep)) 297 298 def envoie_motdepasse_ssh(self, motdepasse): 299 """Envoie le mot de passe lors d'une connexion ssh""" 300 if motdepasse in self._jetons: 301 motdepasse = self._jetons[motdepasse] 302 self._script.append('expect -re {@[^\\n]*:}%s' % linesep) 303 self._script.append('send -- "%s\\r"%s' % (motdepasse, linesep)) 304 self.prepare_prompt() 305