~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/conch/client/default.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.conch.test.test_knownhosts,twisted.conch.test.test_default -*-
 
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Various classes and functions for implementing user-interaction in the
 
7
command-line conch client.
 
8
 
 
9
You probably shouldn't use anything in this module directly, since it assumes
 
10
you are sitting at an interactive terminal.  For example, to programmatically
 
11
interact with a known_hosts database, use L{twisted.conch.client.knownhosts}.
 
12
"""
 
13
 
 
14
from twisted.python import log
 
15
from twisted.python.filepath import FilePath
 
16
 
 
17
from twisted.conch.error import ConchError
 
18
from twisted.conch.ssh import common, keys, userauth
 
19
from twisted.internet import defer, protocol, reactor
 
20
 
 
21
from twisted.conch.client.knownhosts import KnownHostsFile, ConsoleUI
 
22
 
 
23
from twisted.conch.client import agent
 
24
 
 
25
import os, sys, base64, getpass
 
26
 
 
27
# This name is bound so that the unit tests can use 'patch' to override it.
 
28
_open = open
 
29
 
 
30
def verifyHostKey(transport, host, pubKey, fingerprint):
 
31
    """
 
32
    Verify a host's key.
 
33
 
 
34
    This function is a gross vestige of some bad factoring in the client
 
35
    internals.  The actual implementation, and a better signature of this logic
 
36
    is in L{KnownHostsFile.verifyHostKey}.  This function is not deprecated yet
 
37
    because the callers have not yet been rehabilitated, but they should
 
38
    eventually be changed to call that method instead.
 
39
 
 
40
    However, this function does perform two functions not implemented by
 
41
    L{KnownHostsFile.verifyHostKey}.  It determines the path to the user's
 
42
    known_hosts file based on the options (which should really be the options
 
43
    object's job), and it provides an opener to L{ConsoleUI} which opens
 
44
    '/dev/tty' so that the user will be prompted on the tty of the process even
 
45
    if the input and output of the process has been redirected.  This latter
 
46
    part is, somewhat obviously, not portable, but I don't know of a portable
 
47
    equivalent that could be used.
 
48
 
 
49
    @param host: Due to a bug in L{SSHClientTransport.verifyHostKey}, this is
 
50
    always the dotted-quad IP address of the host being connected to.
 
51
    @type host: L{str}
 
52
 
 
53
    @param transport: the client transport which is attempting to connect to
 
54
    the given host.
 
55
    @type transport: L{SSHClientTransport}
 
56
 
 
57
    @param fingerprint: the fingerprint of the given public key, in
 
58
    xx:xx:xx:... format.  This is ignored in favor of getting the fingerprint
 
59
    from the key itself.
 
60
    @type fingerprint: L{str}
 
61
 
 
62
    @param pubKey: The public key of the server being connected to.
 
63
    @type pubKey: L{str}
 
64
 
 
65
    @return: a L{Deferred} which fires with C{1} if the key was successfully
 
66
    verified, or fails if the key could not be successfully verified.  Failure
 
67
    types may include L{HostKeyChanged}, L{UserRejectedKey}, L{IOError} or
 
68
    L{KeyboardInterrupt}.
 
69
    """
 
70
    actualHost = transport.factory.options['host']
 
71
    actualKey = keys.Key.fromString(pubKey)
 
72
    kh = KnownHostsFile.fromPath(FilePath(
 
73
            transport.factory.options['known-hosts']
 
74
            or os.path.expanduser("~/.ssh/known_hosts")
 
75
            ))
 
76
    ui = ConsoleUI(lambda : _open("/dev/tty", "r+b"))
 
77
    return kh.verifyHostKey(ui, actualHost, host, actualKey)
 
78
 
 
79
 
 
80
 
 
81
def isInKnownHosts(host, pubKey, options):
 
82
    """checks to see if host is in the known_hosts file for the user.
 
83
    returns 0 if it isn't, 1 if it is and is the same, 2 if it's changed.
 
84
    """
 
85
    keyType = common.getNS(pubKey)[0]
 
86
    retVal = 0
 
87
 
 
88
    if not options['known-hosts'] and not os.path.exists(os.path.expanduser('~/.ssh/')):
 
89
        print 'Creating ~/.ssh directory...'
 
90
        os.mkdir(os.path.expanduser('~/.ssh'))
 
91
    kh_file = options['known-hosts'] or '~/.ssh/known_hosts'
 
92
    try:
 
93
        known_hosts = open(os.path.expanduser(kh_file))
 
94
    except IOError:
 
95
        return 0
 
96
    for line in known_hosts.xreadlines():
 
97
        split = line.split()
 
98
        if len(split) < 3:
 
99
            continue
 
100
        hosts, hostKeyType, encodedKey = split[:3]
 
101
        if host not in hosts.split(','): # incorrect host
 
102
            continue
 
103
        if hostKeyType != keyType: # incorrect type of key
 
104
            continue
 
105
        try:
 
106
            decodedKey = base64.decodestring(encodedKey)
 
107
        except:
 
108
            continue
 
109
        if decodedKey == pubKey:
 
110
            return 1
 
111
        else:
 
112
            retVal = 2
 
113
    return retVal
 
114
 
 
115
 
 
116
 
 
117
class SSHUserAuthClient(userauth.SSHUserAuthClient):
 
118
 
 
119
    def __init__(self, user, options, *args):
 
120
        userauth.SSHUserAuthClient.__init__(self, user, *args)
 
121
        self.keyAgent = None
 
122
        self.options = options
 
123
        self.usedFiles = []
 
124
        if not options.identitys:
 
125
            options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
 
126
 
 
127
    def serviceStarted(self):
 
128
        if 'SSH_AUTH_SOCK' in os.environ and not self.options['noagent']:
 
129
            log.msg('using agent')
 
130
            cc = protocol.ClientCreator(reactor, agent.SSHAgentClient)
 
131
            d = cc.connectUNIX(os.environ['SSH_AUTH_SOCK'])
 
132
            d.addCallback(self._setAgent)
 
133
            d.addErrback(self._ebSetAgent)
 
134
        else:
 
135
            userauth.SSHUserAuthClient.serviceStarted(self)
 
136
 
 
137
    def serviceStopped(self):
 
138
        if self.keyAgent:
 
139
            self.keyAgent.transport.loseConnection()
 
140
            self.keyAgent = None
 
141
 
 
142
    def _setAgent(self, a):
 
143
        self.keyAgent = a
 
144
        d = self.keyAgent.getPublicKeys()
 
145
        d.addBoth(self._ebSetAgent)
 
146
        return d
 
147
 
 
148
    def _ebSetAgent(self, f):
 
149
        userauth.SSHUserAuthClient.serviceStarted(self)
 
150
 
 
151
    def _getPassword(self, prompt):
 
152
        try:
 
153
            oldout, oldin = sys.stdout, sys.stdin
 
154
            sys.stdin = sys.stdout = open('/dev/tty','r+')
 
155
            p=getpass.getpass(prompt)
 
156
            sys.stdout,sys.stdin=oldout,oldin
 
157
            return p
 
158
        except (KeyboardInterrupt, IOError):
 
159
            print
 
160
            raise ConchError('PEBKAC')
 
161
 
 
162
    def getPassword(self, prompt = None):
 
163
        if not prompt:
 
164
            prompt = "%s@%s's password: " % (self.user, self.transport.transport.getPeer().host)
 
165
        try:
 
166
            p = self._getPassword(prompt)
 
167
            return defer.succeed(p)
 
168
        except ConchError:
 
169
            return defer.fail()
 
170
 
 
171
 
 
172
    def getPublicKey(self):
 
173
        """
 
174
        Get a public key from the key agent if possible, otherwise look in
 
175
        the next configured identity file for one.
 
176
        """
 
177
        if self.keyAgent:
 
178
            key = self.keyAgent.getPublicKey()
 
179
            if key is not None:
 
180
                return key
 
181
        files = [x for x in self.options.identitys if x not in self.usedFiles]
 
182
        log.msg(str(self.options.identitys))
 
183
        log.msg(str(files))
 
184
        if not files:
 
185
            return None
 
186
        file = files[0]
 
187
        log.msg(file)
 
188
        self.usedFiles.append(file)
 
189
        file = os.path.expanduser(file)
 
190
        file += '.pub'
 
191
        if not os.path.exists(file):
 
192
            return self.getPublicKey() # try again
 
193
        try:
 
194
            return keys.Key.fromFile(file)
 
195
        except keys.BadKeyError:
 
196
            return self.getPublicKey() # try again
 
197
 
 
198
 
 
199
    def signData(self, publicKey, signData):
 
200
        """
 
201
        Extend the base signing behavior by using an SSH agent to sign the
 
202
        data, if one is available.
 
203
 
 
204
        @type publicKey: L{Key}
 
205
        @type signData: C{str}
 
206
        """
 
207
        if not self.usedFiles: # agent key
 
208
            return self.keyAgent.signData(publicKey.blob(), signData)
 
209
        else:
 
210
            return userauth.SSHUserAuthClient.signData(self, publicKey, signData)
 
211
 
 
212
 
 
213
    def getPrivateKey(self):
 
214
        """
 
215
        Try to load the private key from the last used file identified by
 
216
        C{getPublicKey}, potentially asking for the passphrase if the key is
 
217
        encrypted.
 
218
        """
 
219
        file = os.path.expanduser(self.usedFiles[-1])
 
220
        if not os.path.exists(file):
 
221
            return None
 
222
        try:
 
223
            return defer.succeed(keys.Key.fromFile(file))
 
224
        except keys.EncryptedKeyError:
 
225
            for i in range(3):
 
226
                prompt = "Enter passphrase for key '%s': " % \
 
227
                    self.usedFiles[-1]
 
228
                try:
 
229
                    p = self._getPassword(prompt)
 
230
                    return defer.succeed(keys.Key.fromFile(file, passphrase=p))
 
231
                except (keys.BadKeyError, ConchError):
 
232
                    pass
 
233
                return defer.fail(ConchError('bad password'))
 
234
            raise
 
235
        except KeyboardInterrupt:
 
236
            print
 
237
            reactor.stop()
 
238
 
 
239
 
 
240
    def getGenericAnswers(self, name, instruction, prompts):
 
241
        responses = []
 
242
        try:
 
243
            oldout, oldin = sys.stdout, sys.stdin
 
244
            sys.stdin = sys.stdout = open('/dev/tty','r+')
 
245
            if name:
 
246
                print name
 
247
            if instruction:
 
248
                print instruction
 
249
            for prompt, echo in prompts:
 
250
                if echo:
 
251
                    responses.append(raw_input(prompt))
 
252
                else:
 
253
                    responses.append(getpass.getpass(prompt))
 
254
        finally:
 
255
            sys.stdout,sys.stdin=oldout,oldin
 
256
        return defer.succeed(responses)