backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / services / protocols / bgp / operator / ssh.py
1 # Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
2 # Copyright (C) 2013 YAMAMOTO Takashi <yamamoto at valinux co jp>
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #    http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 """
18  CLI application for SSH management.
19 """
20
21 from copy import copy
22 import logging
23 import os.path
24 import sys
25
26 import paramiko
27
28 from ryu import version
29 from ryu.lib import hub
30 from ryu.services.protocols.bgp.base import Activity
31 from ryu.services.protocols.bgp.operator.command import Command
32 from ryu.services.protocols.bgp.operator.command import CommandsResponse
33 from ryu.services.protocols.bgp.operator.command import STATUS_OK
34 from ryu.services.protocols.bgp.operator.commands.root import RootCmd
35 from ryu.services.protocols.bgp.operator.internal_api import InternalApi
36
37 SSH_PORT = "ssh_port"
38 SSH_HOST = "ssh_host"
39 SSH_HOST_KEY = "ssh_host_key"
40 SSH_USERNAME = "ssh_username"
41 SSH_PASSWORD = "ssh_password"
42
43 DEFAULT_SSH_PORT = 4990
44 DEFAULT_SSH_HOST = "localhost"
45 DEFAULT_SSH_HOST_KEY = None
46 DEFAULT_SSH_USERNAME = "ryu"
47 DEFAULT_SSH_PASSWORD = "ryu"
48
49 CONF = {
50     SSH_PORT: DEFAULT_SSH_PORT,
51     SSH_HOST: DEFAULT_SSH_HOST,
52     SSH_HOST_KEY: DEFAULT_SSH_HOST_KEY,
53     SSH_USERNAME: DEFAULT_SSH_USERNAME,
54     SSH_PASSWORD: DEFAULT_SSH_PASSWORD,
55 }
56
57 LOG = logging.getLogger('bgpspeaker.cli')
58
59
60 def find_ssh_server_key():
61     if CONF[SSH_HOST_KEY]:
62         return paramiko.RSAKey.from_private_key_file(CONF[SSH_HOST_KEY])
63     elif os.path.exists("/etc/ssh_host_rsa_key"):
64         # OSX
65         return paramiko.RSAKey.from_private_key_file(
66             "/etc/ssh_host_rsa_key")
67     elif os.path.exists("/etc/ssh/ssh_host_rsa_key"):
68         # Linux
69         return paramiko.RSAKey.from_private_key_file(
70             "/etc/ssh/ssh_host_rsa_key")
71     else:
72         return paramiko.RSAKey.generate(1024)
73
74
75 class SshServer(paramiko.ServerInterface):
76     TERM = "ansi"
77     PROMPT = "bgpd> "
78     WELCOME = "\n\rHello, this is Ryu BGP speaker (version %s).\n\r" % version
79
80     class HelpCmd(Command):
81         help_msg = 'show this help'
82         command = 'help'
83
84         def action(self, params):
85             return self.parent_cmd.question_mark()[0]
86
87     class QuitCmd(Command):
88         help_msg = 'exit this session'
89         command = 'quit'
90
91         def action(self, params):
92             self.api.sshserver.end_session()
93             return CommandsResponse(STATUS_OK, True)
94
95     def __init__(self, sock, addr):
96         super(SshServer, self).__init__()
97         self.sock = sock
98         self.addr = addr
99         self.is_connected = True
100
101         # For pylint
102         self.buf = None
103         self.chan = None
104         self.curpos = None
105         self.histindex = None
106         self.history = None
107         self.prompted = None
108         self.promptlen = None
109
110         # tweak InternalApi and RootCmd for non-bgp related commands
111         self.api = InternalApi(log_handler=logging.StreamHandler(sys.stderr))
112         setattr(self.api, 'sshserver', self)
113         self.root = RootCmd(self.api)
114         self.root.subcommands['help'] = self.HelpCmd
115         self.root.subcommands['quit'] = self.QuitCmd
116
117         self.transport = paramiko.Transport(self.sock)
118         self.transport.load_server_moduli()
119         host_key = find_ssh_server_key()
120         self.transport.add_server_key(host_key)
121         self.transport.start_server(server=self)
122
123     def check_auth_none(self, username):
124         return paramiko.AUTH_SUCCESSFUL
125
126     def check_auth_password(self, username, password):
127         if (username == CONF[SSH_USERNAME]
128                 and password == CONF[SSH_PASSWORD]):
129             return paramiko.AUTH_SUCCESSFUL
130         return paramiko.AUTH_FAILED
131
132     def check_channel_request(self, kind, chanid):
133         if kind == 'session':
134             return paramiko.OPEN_SUCCEEDED
135         return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
136
137     def check_channel_shell_request(self, channel):
138         hub.spawn(self._handle_shell_request)
139         return True
140
141     def check_channel_pty_request(self, channel, term, width, height,
142                                   pixelwidth, pixelheight, modes):
143         self.TERM = term
144         return True
145
146     def check_channel_window_change_request(self, channel, width, height,
147                                             pixelwidth, pixelheight):
148         return True
149
150     @staticmethod
151     def _is_echoable(c):
152         return not (c < chr(0x20) or c == chr(0x7F))
153
154     @staticmethod
155     def _is_enter(c):
156         return c == chr(0x0d)
157
158     @staticmethod
159     def _is_eof(c):
160         return c == chr(0x03)
161
162     @staticmethod
163     def _is_esc(c):
164         return c == chr(0x1b)
165
166     @staticmethod
167     def _is_hist(c):
168         return c == chr(0x10) or c == chr(0x0e)
169
170     @staticmethod
171     def _is_del(c):
172         return (c == chr(0x04) or c == chr(0x08) or c == chr(0x15)
173                 or c == chr(0x17) or c == chr(0x0c) or c == chr(0x7f))
174
175     @staticmethod
176     def _is_curmov(c):
177         return c == chr(0x01) or c == chr(0x02) or c == chr(0x05) \
178             or c == chr(0x06)
179
180     @staticmethod
181     def _is_cmpl(c):
182         return c == chr(0x09)
183
184     def _handle_csi_seq(self):
185         c = self.chan.recv(1)
186         c = c.decode()  # For Python3 compatibility
187         if c == 'A':
188             self._lookup_hist_up()
189         elif c == 'B':
190             self._lookup_hist_down()
191         elif c == 'C':
192             self._movcursor(self.curpos + 1)
193         elif c == 'D':
194             self._movcursor(self.curpos - 1)
195         else:
196             LOG.error("unknown CSI sequence. do nothing: %c", c)
197
198     def _handle_esc_seq(self):
199         c = self.chan.recv(1)
200         c = c.decode()  # For Python3 compatibility
201         if c == '[':
202             self._handle_csi_seq()
203         else:
204             LOG.error("non CSI sequence. do nothing")
205
206     def _send_csi_seq(self, cmd):
207         self.chan.send('\x1b[' + cmd)
208
209     def _movcursor(self, curpos):
210         if self.prompted and curpos < len(self.PROMPT):
211             self.curpos = len(self.PROMPT)
212         elif self.prompted and curpos > (len(self.PROMPT) + len(self.buf)):
213             self.curpos = len(self.PROMPT) + len(self.buf)
214         else:
215             self._send_csi_seq('%dG' % (curpos + 1))
216             self.curpos = curpos
217
218     def _clearscreen(self, prompt=None):
219         if not prompt and self.prompted:
220             prompt = self.PROMPT
221         # clear screen
222         self._send_csi_seq('2J')
223         # move cursor to the top
224         self._send_csi_seq('d')
225         # redraw prompt and buf
226         self._refreshline(prompt=prompt)
227
228     def _clearline(self, prompt=None):
229         if not prompt and self.prompted:
230             prompt = self.PROMPT
231         self.prompted = False
232         self._movcursor(0)
233         self._send_csi_seq('2K')
234         if prompt:
235             self.prompted = True
236             self.chan.send(prompt)
237             self._movcursor(len(prompt))
238         self.buf = []
239
240     def _refreshline(self, prompt=None):
241         if not prompt and self.prompted:
242             prompt = self.PROMPT
243         buf = copy(self.buf)
244         curpos = copy(self.curpos)
245         self._clearline(prompt=prompt)
246         self.chan.send(''.join(buf))
247         self.buf = buf
248         self.curpos = curpos
249         self._movcursor(curpos)
250
251     def _refreshnewline(self, prompt=None):
252         if not prompt and self.prompted:
253             prompt = self.PROMPT
254         buf = copy(self.buf)
255         curpos = copy(self.curpos)
256         self._startnewline(prompt)
257         self.chan.send(''.join(buf))
258         self.buf = buf
259         self.curpos = curpos
260         self._movcursor(curpos)
261
262     def _startnewline(self, prompt=None, buf=None):
263         buf = buf or []
264         if not prompt and self.prompted:
265             prompt = self.PROMPT
266         if isinstance(buf, str):
267             buf = list(buf)
268         if self.chan:
269             self.buf = buf
270             if prompt:
271                 self.chan.send('\n\r' + prompt + ''.join(buf))
272                 self.curpos = len(prompt) + len(buf)
273                 self.prompted = True
274             else:
275                 self.chan.send('\n\r' + ''.join(buf))
276                 self.curpos = len(buf)
277                 self.prompted = False
278
279     def _lookup_hist_up(self):
280         if len(self.history) == 0:
281             return
282         self.buf = self.history[self.histindex]
283         self.curpos = self.promptlen + len(self.buf)
284         self._refreshline()
285         if self.histindex + 1 < len(self.history):
286             self.histindex += 1
287
288     def _lookup_hist_down(self):
289         if self.histindex > 0:
290             self.histindex -= 1
291             self.buf = self.history[self.histindex]
292             self.curpos = self.promptlen + len(self.buf)
293             self._refreshline()
294         else:
295             self._clearline()
296
297     def _do_cmpl(self, buf, is_exec=False):
298         cmpleter = self.root
299         is_spaced = buf[-1] == ' ' if len(buf) > 0 else False
300         cmds = [tkn.strip() for tkn in ''.join(buf).split()]
301         ret = []
302
303         for i, cmd in enumerate(cmds):
304             subcmds = cmpleter.subcommands
305             matches = [x for x in subcmds.keys() if x.startswith(cmd)]
306
307             if len(matches) == 1:
308                 cmpled_cmd = matches[0]
309                 cmpleter = subcmds[cmpled_cmd](self.api)
310
311                 if is_exec:
312                     ret.append(cmpled_cmd)
313                     continue
314
315                 if (i + 1) == len(cmds):
316                     if is_spaced:
317                         result, cmd = cmpleter('?')
318                         result = result.value.replace('\n', '\n\r').rstrip()
319                         self.prompted = False
320                         buf = copy(buf)
321                         self._startnewline(buf=result)
322                         self.prompted = True
323                         self._startnewline(buf=buf)
324                     else:
325                         self.buf = buf[:(-1 * len(cmd))] + \
326                             list(cmpled_cmd + ' ')
327                         self.curpos += len(cmpled_cmd) - len(cmd) + 1
328                         self._refreshline()
329             else:
330                 self.prompted = False
331                 buf = copy(self.buf)
332                 if len(matches) == 0:
333                     if cmpleter.param_help_msg:
334                         self.prompted = True
335                         ret.append(cmd)
336                         continue
337                     else:
338                         self._startnewline(buf='Error: Not implemented')
339                 else:
340                     if (i + 1) < len(cmds):
341                         self._startnewline(buf='Error: Ambiguous command')
342                     else:
343                         self._startnewline(buf=', '.join(matches))
344                 ret = []
345                 self.prompted = True
346                 if not is_exec:
347                     self._startnewline(buf=buf)
348                 break
349
350         return ret
351
352     def _execute_cmd(self, cmds):
353         result, _ = self.root(cmds)
354         LOG.debug("result: %s", result)
355         if cmds[0] == 'quit':
356             self.is_connected = False
357             return result.status
358         self.prompted = False
359         self._startnewline()
360         output = result.value.replace('\n', '\n\r').rstrip()
361         self.chan.send(output)
362         self.prompted = True
363         self._startnewline()
364         return result.status
365
366     def end_session(self):
367         self._startnewline(prompt=False, buf='bye.\n\r')
368         self.chan.close()
369
370     def _handle_shell_request(self):
371         LOG.info("session start")
372         chan = self.transport.accept(20)
373         if not chan:
374             LOG.info("transport.accept timed out")
375             return
376
377         self.chan = chan
378         self.buf = []
379         self.curpos = 0
380         self.history = []
381         self.histindex = 0
382         self.prompted = True
383         self.chan.send(self.WELCOME)
384         self._startnewline()
385
386         while self.is_connected:
387             c = self.chan.recv(1)
388             c = c.decode()  # For Python3 compatibility
389
390             if len(c) == 0:
391                 break
392
393             LOG.debug("ord:%d, hex:0x%x", ord(c), ord(c))
394             self.promptlen = len(self.PROMPT) if self.prompted else 0
395             if c == '?':
396                 cmpleter = self.root
397                 cmds = [tkn.strip() for tkn in ''.join(self.buf).split()]
398
399                 for i, cmd in enumerate(cmds):
400                     subcmds = cmpleter.subcommands
401                     matches = [x for x in subcmds.keys() if x.startswith(cmd)]
402                     if len(matches) == 1:
403                         cmpled_cmd = matches[0]
404                         cmpleter = subcmds[cmpled_cmd](self.api)
405
406                 result, cmd = cmpleter('?')
407                 result = result.value.replace('\n', '\n\r').rstrip()
408                 self.prompted = False
409                 buf = copy(self.buf)
410                 self._startnewline(buf=result)
411                 self.prompted = True
412                 self._startnewline(buf=buf)
413             elif self._is_echoable(c):
414                 self.buf.insert(self.curpos - self.promptlen, c)
415                 self.curpos += 1
416                 self._refreshline()
417             elif self._is_esc(c):
418                 self._handle_esc_seq()
419             elif self._is_eof(c):
420                 self.end_session()
421             elif self._is_curmov(c):
422                 # <C-a>
423                 if c == chr(0x01):
424                     self._movcursor(self.promptlen)
425                 # <C-b>
426                 elif c == chr(0x02):
427                     self._movcursor(self.curpos - 1)
428                 # <C-e>
429                 elif c == chr(0x05):
430                     self._movcursor(self.promptlen + len(self.buf))
431                 # <C-f>
432                 elif c == chr(0x06):
433                     self._movcursor(self.curpos + 1)
434                 else:
435                     LOG.error("unknown cursor move cmd.")
436                     continue
437             elif self._is_hist(c):
438                 # <C-p>
439                 if c == chr(0x10):
440                     self._lookup_hist_up()
441                 # <C-n>
442                 elif c == chr(0x0e):
443                     self._lookup_hist_down()
444             elif self._is_del(c):
445                 # <C-d>
446                 if c == chr(0x04):
447                     if self.curpos < (self.promptlen + len(self.buf)):
448                         self.buf.pop(self.curpos - self.promptlen)
449                         self._refreshline()
450                 # <C-h> or delete
451                 elif c == chr(0x08) or c == chr(0x7f):
452                     if self.curpos > self.promptlen:
453                         self.buf.pop(self.curpos - self.promptlen - 1)
454                         self.curpos -= 1
455                         self._refreshline()
456                 # <C-u>
457                 elif c == chr(0x15):
458                     self._clearline()
459                 # <C-w>
460                 elif c == chr(0x17):
461                     pos = self.curpos - self.promptlen
462                     i = pos
463                     flag = False
464                     for c in reversed(self.buf[:pos]):
465                         if flag and c == ' ':
466                             break
467                         if c != ' ':
468                             flag = True
469                         i -= 1
470                     del self.buf[i:pos]
471                     self.curpos = self.promptlen + i
472                     self._refreshline()
473                 # <C-l>
474                 elif c == chr(0x0c):
475                     self._clearscreen()
476             elif self._is_cmpl(c):
477                 self._do_cmpl(self.buf)
478             elif self._is_enter(c):
479                 if len(''.join(self.buf).strip()) != 0:
480                     # cmd line interpretation
481                     cmds = self._do_cmpl(self.buf, is_exec=True)
482                     if cmds:
483                         self.history.insert(0, self.buf)
484                         self.histindex = 0
485                         self._execute_cmd(cmds)
486                     else:
487                         LOG.debug("no command is interpreted. "
488                                   "just start a new line.")
489                         self._startnewline()
490                 else:
491                     LOG.debug("blank buf is detected. "
492                               "just start a new line.")
493                     self._startnewline()
494
495             LOG.debug("curpos: %d, buf: %s, prompted: %s", self.curpos,
496                       self.buf, self.prompted)
497
498         LOG.info("session end")
499
500
501 def ssh_server_factory(sock, addr):
502     SshServer(sock, addr)
503
504
505 class Cli(Activity):
506     def __init__(self):
507         super(Cli, self).__init__()
508
509     def _run(self, *args, **kwargs):
510         for k, v in kwargs.items():
511             if k in CONF:
512                 CONF[k] = v
513
514         listen_info = (CONF[SSH_HOST], CONF[SSH_PORT])
515         LOG.info("starting ssh server at %s:%d" % listen_info)
516         server = hub.StreamServer(listen_info, ssh_server_factory)
517         server.serve_forever()
518
519
520 SSH_CLI_CONTROLLER = Cli()