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 |