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