Statistics
| Branch: | Tag: | Revision:

root / ncclient / transport / ssh.py @ c35cebbf

History | View | Annotate | Download (10.3 kB)

1 d095a59e Shikhar Bhushan
# Copyright 2009 Shikhar Bhushan
2 d095a59e Shikhar Bhushan
#
3 d095a59e Shikhar Bhushan
# Licensed under the Apache License, Version 2.0 (the "License");
4 d095a59e Shikhar Bhushan
# you may not use this file except in compliance with the License.
5 d095a59e Shikhar Bhushan
# You may obtain a copy of the License at
6 d095a59e Shikhar Bhushan
#
7 d095a59e Shikhar Bhushan
#    http://www.apache.org/licenses/LICENSE-2.0
8 d095a59e Shikhar Bhushan
#
9 d095a59e Shikhar Bhushan
# Unless required by applicable law or agreed to in writing, software
10 d095a59e Shikhar Bhushan
# distributed under the License is distributed on an "AS IS" BASIS,
11 d095a59e Shikhar Bhushan
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 d095a59e Shikhar Bhushan
# See the License for the specific language governing permissions and
13 d095a59e Shikhar Bhushan
# limitations under the License.
14 d095a59e Shikhar Bhushan
15 d095a59e Shikhar Bhushan
import os
16 d095a59e Shikhar Bhushan
import socket
17 d095a59e Shikhar Bhushan
from binascii import hexlify
18 d095a59e Shikhar Bhushan
from cStringIO import StringIO
19 d095a59e Shikhar Bhushan
from select import select
20 d095a59e Shikhar Bhushan
21 d095a59e Shikhar Bhushan
import paramiko
22 d095a59e Shikhar Bhushan
23 d095a59e Shikhar Bhushan
from . import logger
24 c35cebbf Shikhar Bhushan
from errors import AuthenticationError, SessionCloseError, SSHError, SSHUnknownHostError
25 d095a59e Shikhar Bhushan
from session import Session
26 d095a59e Shikhar Bhushan
27 d095a59e Shikhar Bhushan
BUF_SIZE = 4096
28 d095a59e Shikhar Bhushan
MSG_DELIM = ']]>]]>'
29 d095a59e Shikhar Bhushan
TICK = 0.1
30 d095a59e Shikhar Bhushan
31 d095a59e Shikhar Bhushan
class SSHSession(Session):
32 e91a5349 Shikhar Bhushan
    
33 d095a59e Shikhar Bhushan
    def __init__(self):
34 d095a59e Shikhar Bhushan
        Session.__init__(self)
35 d095a59e Shikhar Bhushan
        self._host_keys = paramiko.HostKeys()
36 4ba5e843 Shikhar Bhushan
        self._system_host_keys = paramiko.HostKeys()
37 d095a59e Shikhar Bhushan
        self._transport = None
38 d095a59e Shikhar Bhushan
        self._connected = False
39 d095a59e Shikhar Bhushan
        self._channel = None
40 d095a59e Shikhar Bhushan
        self._buffer = StringIO() # for incoming data
41 d095a59e Shikhar Bhushan
        # parsing-related, see _parse()
42 d095a59e Shikhar Bhushan
        self._parsing_state = 0 
43 d095a59e Shikhar Bhushan
        self._parsing_pos = 0
44 d095a59e Shikhar Bhushan
    
45 d095a59e Shikhar Bhushan
    def _parse(self):
46 d095a59e Shikhar Bhushan
        '''Messages ae delimited by MSG_DELIM. The buffer could have grown by a
47 d095a59e Shikhar Bhushan
        maximum of BUF_SIZE bytes everytime this method is called. Retains state
48 d095a59e Shikhar Bhushan
        across method calls and if a byte has been read it will not be considered
49 d095a59e Shikhar Bhushan
        again.
50 d095a59e Shikhar Bhushan
        '''
51 d095a59e Shikhar Bhushan
        delim = MSG_DELIM
52 d095a59e Shikhar Bhushan
        n = len(delim) - 1
53 d095a59e Shikhar Bhushan
        expect = self._parsing_state
54 d095a59e Shikhar Bhushan
        buf = self._buffer
55 d095a59e Shikhar Bhushan
        buf.seek(self._parsing_pos)
56 d095a59e Shikhar Bhushan
        while True:
57 d095a59e Shikhar Bhushan
            x = buf.read(1)
58 d095a59e Shikhar Bhushan
            if not x: # done reading
59 d095a59e Shikhar Bhushan
                break
60 d095a59e Shikhar Bhushan
            elif x == delim[expect]: # what we expected
61 d095a59e Shikhar Bhushan
                expect += 1 # expect the next delim char
62 d095a59e Shikhar Bhushan
            else:
63 d095a59e Shikhar Bhushan
                continue
64 d095a59e Shikhar Bhushan
            # loop till last delim char expected, break if other char encountered
65 d095a59e Shikhar Bhushan
            for i in range(expect, n):
66 d095a59e Shikhar Bhushan
                x = buf.read(1)
67 d095a59e Shikhar Bhushan
                if not x: # done reading
68 d095a59e Shikhar Bhushan
                    break
69 d095a59e Shikhar Bhushan
                if x == delim[expect]: # what we expected
70 d095a59e Shikhar Bhushan
                    expect += 1 # expect the next delim char
71 d095a59e Shikhar Bhushan
                else:
72 d095a59e Shikhar Bhushan
                    expect = 0 # reset
73 d095a59e Shikhar Bhushan
                    break
74 d095a59e Shikhar Bhushan
            else: # if we didn't break out of the loop, full delim was parsed
75 d095a59e Shikhar Bhushan
                msg_till = buf.tell() - n
76 d095a59e Shikhar Bhushan
                buf.seek(0)
77 e91a5349 Shikhar Bhushan
                self._dispatch_received(buf.read(msg_till).strip())
78 d095a59e Shikhar Bhushan
                buf.seek(n+1, os.SEEK_CUR)
79 d095a59e Shikhar Bhushan
                rest = buf.read()
80 d095a59e Shikhar Bhushan
                buf = StringIO()
81 d095a59e Shikhar Bhushan
                buf.write(rest)
82 d095a59e Shikhar Bhushan
                buf.seek(0)
83 4ba5e843 Shikhar Bhushan
                expect = 0
84 d095a59e Shikhar Bhushan
        self._buffer = buf
85 d095a59e Shikhar Bhushan
        self._parsing_state = expect
86 d095a59e Shikhar Bhushan
        self._parsing_pos = self._buffer.tell()
87 d095a59e Shikhar Bhushan
    
88 d095a59e Shikhar Bhushan
    def load_system_host_keys(self, filename=None):
89 d095a59e Shikhar Bhushan
        if filename is None:
90 d095a59e Shikhar Bhushan
            filename = os.path.expanduser('~/.ssh/known_hosts')
91 d095a59e Shikhar Bhushan
            try:
92 d095a59e Shikhar Bhushan
                self._system_host_keys.load(filename)
93 d095a59e Shikhar Bhushan
            except IOError:
94 4ba5e843 Shikhar Bhushan
                # for windows
95 4ba5e843 Shikhar Bhushan
                filename = os.path.expanduser('~/ssh/known_hosts')
96 4ba5e843 Shikhar Bhushan
                try:
97 4ba5e843 Shikhar Bhushan
                    self._system_host_keys.load(filename)
98 4ba5e843 Shikhar Bhushan
                except IOError:
99 4ba5e843 Shikhar Bhushan
                    pass
100 d095a59e Shikhar Bhushan
            return
101 d095a59e Shikhar Bhushan
        self._system_host_keys.load(filename)
102 d095a59e Shikhar Bhushan
    
103 d095a59e Shikhar Bhushan
    def load_host_keys(self, filename):
104 d095a59e Shikhar Bhushan
        self._host_keys.load(filename)
105 d095a59e Shikhar Bhushan
106 d095a59e Shikhar Bhushan
    def add_host_key(self, key):
107 d095a59e Shikhar Bhushan
        self._host_keys.add(key)
108 d095a59e Shikhar Bhushan
    
109 d095a59e Shikhar Bhushan
    def save_host_keys(self, filename):
110 d095a59e Shikhar Bhushan
        f = open(filename, 'w')
111 d095a59e Shikhar Bhushan
        for hostname, keys in self._host_keys.iteritems():
112 d095a59e Shikhar Bhushan
            for keytype, key in keys.iteritems():
113 d095a59e Shikhar Bhushan
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
114 d095a59e Shikhar Bhushan
        f.close()    
115 d095a59e Shikhar Bhushan
    
116 d095a59e Shikhar Bhushan
    def close(self):
117 d095a59e Shikhar Bhushan
        if self._transport.is_active():
118 d095a59e Shikhar Bhushan
            self._transport.close()
119 d095a59e Shikhar Bhushan
        self._connected = False
120 d095a59e Shikhar Bhushan
    
121 d095a59e Shikhar Bhushan
    def connect(self, hostname, port=830, timeout=None,
122 d095a59e Shikhar Bhushan
                unknown_host_cb=None, username=None, password=None,
123 d095a59e Shikhar Bhushan
                key_filename=None, allow_agent=True, look_for_keys=True):
124 d095a59e Shikhar Bhushan
        
125 d095a59e Shikhar Bhushan
        assert(username is not None)
126 d095a59e Shikhar Bhushan
        
127 d095a59e Shikhar Bhushan
        for (family, socktype, proto, canonname, sockaddr) in \
128 d095a59e Shikhar Bhushan
        socket.getaddrinfo(hostname, port):
129 d095a59e Shikhar Bhushan
            if socktype==socket.SOCK_STREAM:
130 d095a59e Shikhar Bhushan
                af = family
131 d095a59e Shikhar Bhushan
                addr = sockaddr
132 d095a59e Shikhar Bhushan
                break
133 d095a59e Shikhar Bhushan
        else:
134 d095a59e Shikhar Bhushan
            raise SSHError('No suitable address family for %s' % hostname)
135 d095a59e Shikhar Bhushan
        sock = socket.socket(af, socket.SOCK_STREAM)
136 d095a59e Shikhar Bhushan
        sock.settimeout(timeout)
137 d095a59e Shikhar Bhushan
        sock.connect(addr)
138 d095a59e Shikhar Bhushan
        t = self._transport = paramiko.Transport(sock)
139 d095a59e Shikhar Bhushan
        t.set_log_channel(logger.name)
140 d095a59e Shikhar Bhushan
        
141 d095a59e Shikhar Bhushan
        try:
142 d095a59e Shikhar Bhushan
            t.start_client()
143 d095a59e Shikhar Bhushan
        except paramiko.SSHException:
144 d095a59e Shikhar Bhushan
            raise SSHError('Negotiation failed')
145 d095a59e Shikhar Bhushan
        
146 d095a59e Shikhar Bhushan
        # host key verification
147 d095a59e Shikhar Bhushan
        server_key = t.get_remote_server_key()
148 d095a59e Shikhar Bhushan
        known_host = self._host_keys.check(hostname, server_key) or \
149 d095a59e Shikhar Bhushan
                        self._system_host_keys.check(hostname, server_key)
150 d095a59e Shikhar Bhushan
        
151 d095a59e Shikhar Bhushan
        if unknown_host_cb is None:
152 d095a59e Shikhar Bhushan
            unknown_host_cb = lambda *args: False
153 d095a59e Shikhar Bhushan
        if not known_host and not unknown_host_cb(hostname, server_key):
154 d095a59e Shikhar Bhushan
                raise SSHUnknownHostError(hostname, server_key)
155 d095a59e Shikhar Bhushan
        
156 d095a59e Shikhar Bhushan
        if key_filename is None:
157 d095a59e Shikhar Bhushan
            key_filenames = []
158 d095a59e Shikhar Bhushan
        elif isinstance(key_filename, basestring):
159 d095a59e Shikhar Bhushan
            key_filenames = [ key_filename ]
160 d095a59e Shikhar Bhushan
        else:
161 d095a59e Shikhar Bhushan
            key_filenames = key_filename
162 d095a59e Shikhar Bhushan
        
163 d095a59e Shikhar Bhushan
        self._auth(username, password, key_filenames, allow_agent, look_for_keys)
164 d095a59e Shikhar Bhushan
        
165 d095a59e Shikhar Bhushan
        self._connected = True # there was no error authenticating
166 d095a59e Shikhar Bhushan
        
167 d095a59e Shikhar Bhushan
        c = self._channel = self._transport.open_session()
168 d095a59e Shikhar Bhushan
        c.invoke_subsystem('netconf')
169 d095a59e Shikhar Bhushan
        c.set_name('netconf')
170 d095a59e Shikhar Bhushan
        
171 d095a59e Shikhar Bhushan
        self._post_connect()
172 d095a59e Shikhar Bhushan
    
173 d095a59e Shikhar Bhushan
    # on the lines of paramiko.SSHClient._auth()
174 d095a59e Shikhar Bhushan
    def _auth(self, username, password, key_filenames, allow_agent,
175 d095a59e Shikhar Bhushan
              look_for_keys):
176 d095a59e Shikhar Bhushan
        saved_exception = None
177 d095a59e Shikhar Bhushan
        
178 d095a59e Shikhar Bhushan
        for key_filename in key_filenames:
179 d095a59e Shikhar Bhushan
            for cls in (paramiko.RSAKey, paramiko.DSSKey):
180 d095a59e Shikhar Bhushan
                try:
181 d095a59e Shikhar Bhushan
                    key = cls.from_private_key_file(key_filename, password)
182 d095a59e Shikhar Bhushan
                    logger.debug('Trying key %s from %s' %
183 d095a59e Shikhar Bhushan
                              (hexlify(key.get_fingerprint()), key_filename))
184 d095a59e Shikhar Bhushan
                    self._transport.auth_publickey(username, key)
185 d095a59e Shikhar Bhushan
                    return
186 d095a59e Shikhar Bhushan
                except Exception as e:
187 d095a59e Shikhar Bhushan
                    saved_exception = e
188 d095a59e Shikhar Bhushan
                    logger.debug(e)
189 d095a59e Shikhar Bhushan
        
190 d095a59e Shikhar Bhushan
        if allow_agent:
191 d095a59e Shikhar Bhushan
            for key in paramiko.Agent().get_keys():
192 d095a59e Shikhar Bhushan
                try:
193 d095a59e Shikhar Bhushan
                    logger.debug('Trying SSH agent key %s' %
194 d095a59e Shikhar Bhushan
                                 hexlify(key.get_fingerprint()))
195 d095a59e Shikhar Bhushan
                    self._transport.auth_publickey(username, key)
196 d095a59e Shikhar Bhushan
                    return
197 d095a59e Shikhar Bhushan
                except Exception as e:
198 d095a59e Shikhar Bhushan
                    saved_exception = e
199 d095a59e Shikhar Bhushan
                    logger.debug(e)
200 d095a59e Shikhar Bhushan
        
201 d095a59e Shikhar Bhushan
        keyfiles = []
202 d095a59e Shikhar Bhushan
        if look_for_keys:
203 d095a59e Shikhar Bhushan
            rsa_key = os.path.expanduser('~/.ssh/id_rsa')
204 d095a59e Shikhar Bhushan
            dsa_key = os.path.expanduser('~/.ssh/id_dsa')
205 d095a59e Shikhar Bhushan
            if os.path.isfile(rsa_key):
206 d095a59e Shikhar Bhushan
                keyfiles.append((paramiko.RSAKey, rsa_key))
207 d095a59e Shikhar Bhushan
            if os.path.isfile(dsa_key):
208 d095a59e Shikhar Bhushan
                keyfiles.append((paramiko.DSSKey, dsa_key))
209 d095a59e Shikhar Bhushan
            # look in ~/ssh/ for windows users:
210 d095a59e Shikhar Bhushan
            rsa_key = os.path.expanduser('~/ssh/id_rsa')
211 d095a59e Shikhar Bhushan
            dsa_key = os.path.expanduser('~/ssh/id_dsa')
212 d095a59e Shikhar Bhushan
            if os.path.isfile(rsa_key):
213 d095a59e Shikhar Bhushan
                keyfiles.append((paramiko.RSAKey, rsa_key))
214 d095a59e Shikhar Bhushan
            if os.path.isfile(dsa_key):
215 d095a59e Shikhar Bhushan
                keyfiles.append((paramiko.DSSKey, dsa_key))
216 d095a59e Shikhar Bhushan
        
217 d095a59e Shikhar Bhushan
        for cls, filename in keyfiles:
218 d095a59e Shikhar Bhushan
            try:
219 d095a59e Shikhar Bhushan
                key = cls.from_private_key_file(filename, password)
220 d095a59e Shikhar Bhushan
                logger.debug('Trying discovered key %s in %s' %
221 d095a59e Shikhar Bhushan
                          (hexlify(key.get_fingerprint()), filename))
222 d095a59e Shikhar Bhushan
                self._transport.auth_publickey(username, key)
223 d095a59e Shikhar Bhushan
                return
224 d095a59e Shikhar Bhushan
            except Exception as e:
225 d095a59e Shikhar Bhushan
                saved_exception = e
226 d095a59e Shikhar Bhushan
                logger.debug(e)
227 d095a59e Shikhar Bhushan
        
228 d095a59e Shikhar Bhushan
        if password is not None:
229 d095a59e Shikhar Bhushan
            try:
230 d095a59e Shikhar Bhushan
                self._transport.auth_password(username, password)
231 d095a59e Shikhar Bhushan
                return
232 d095a59e Shikhar Bhushan
            except Exception as e:
233 d095a59e Shikhar Bhushan
                saved_exception = e
234 d095a59e Shikhar Bhushan
                logger.debug(e)
235 d095a59e Shikhar Bhushan
        
236 d095a59e Shikhar Bhushan
        if saved_exception is not None:
237 4ba5e843 Shikhar Bhushan
            raise SSHAuthenticationError(repr(saved_exception))
238 d095a59e Shikhar Bhushan
        
239 4ba5e843 Shikhar Bhushan
        raise SSHAuthenticationError('No authentication methods available')
240 d095a59e Shikhar Bhushan
    
241 d095a59e Shikhar Bhushan
    def run(self):
242 d095a59e Shikhar Bhushan
        chan = self._channel
243 d095a59e Shikhar Bhushan
        chan.setblocking(0)
244 d095a59e Shikhar Bhushan
        q = self._q
245 d095a59e Shikhar Bhushan
        try:
246 d095a59e Shikhar Bhushan
            while True:
247 d095a59e Shikhar Bhushan
                # select on a paramiko ssh channel object does not ever return
248 d095a59e Shikhar Bhushan
                # it in the writable list, so it channel's don't exactly emulate 
249 d095a59e Shikhar Bhushan
                # the socket api
250 d095a59e Shikhar Bhushan
                r, w, e = select([chan], [], [], TICK)
251 d095a59e Shikhar Bhushan
                # will wakeup evey TICK seconds to check if something
252 d095a59e Shikhar Bhushan
                # to send, more if something to read (due to select returning
253 d095a59e Shikhar Bhushan
                # chan in readable list)
254 d095a59e Shikhar Bhushan
                if r:
255 d095a59e Shikhar Bhushan
                    data = chan.recv(BUF_SIZE)
256 d095a59e Shikhar Bhushan
                    if data:
257 d095a59e Shikhar Bhushan
                        self._buffer.write(data)
258 d095a59e Shikhar Bhushan
                        self._parse()
259 d095a59e Shikhar Bhushan
                    else:
260 94265508 Shikhar Bhushan
                        raise SessionCloseError(self._buffer.getvalue())
261 d095a59e Shikhar Bhushan
                if not q.empty() and chan.send_ready():
262 d095a59e Shikhar Bhushan
                    data = q.get() + MSG_DELIM
263 d095a59e Shikhar Bhushan
                    while data:
264 d095a59e Shikhar Bhushan
                        n = chan.send(data)
265 d095a59e Shikhar Bhushan
                        if n <= 0:
266 94265508 Shikhar Bhushan
                            raise SessionCloseError(self._buffer.getvalue(), data)
267 d095a59e Shikhar Bhushan
                        data = data[n:]
268 d095a59e Shikhar Bhushan
        except Exception as e:
269 d095a59e Shikhar Bhushan
            self.close()
270 d095a59e Shikhar Bhushan
            logger.debug('*** broke out of main loop ***')
271 e91a5349 Shikhar Bhushan
            self._dispatch_error(e)
272 d095a59e Shikhar Bhushan
    
273 d095a59e Shikhar Bhushan
    @property
274 d095a59e Shikhar Bhushan
    def transport(self):
275 e91a5349 Shikhar Bhushan
        '''Get underlying paramiko transport object; this is provided so methods
276 e91a5349 Shikhar Bhushan
        like set_keepalive can be called on it. See paramiko.Transport
277 e91a5349 Shikhar Bhushan
        documentation for details.
278 d095a59e Shikhar Bhushan
        '''
279 d095a59e Shikhar Bhushan
        return self._transport