codekingpro/portable-devtools
114k
1# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>2#3# This file is part of paramiko.4#5# Paramiko is free software; you can redistribute it and/or modify it under the6# terms of the GNU Lesser General Public License as published by the Free7# Software Foundation; either version 2.1 of the License, or (at your option)8# any later version.9#10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR12# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more13# details.14#15# You should have received a copy of the GNU Lesser General Public License16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.18 19"""20`.ServerInterface` is an interface to override for server support.21"""22 23import threading24from paramiko import util25from paramiko.common import (26 DEBUG,27 ERROR,28 OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED,29 AUTH_FAILED,30 AUTH_SUCCESSFUL,31)32 33 34class ServerInterface:35 """36 This class defines an interface for controlling the behavior of Paramiko37 in server mode.38 39 Methods on this class are called from Paramiko's primary thread, so you40 shouldn't do too much work in them. (Certainly nothing that blocks or41 sleeps.)42 """43 44 def check_channel_request(self, kind, chanid):45 """46 Determine if a channel request of a given type will be granted, and47 return ``OPEN_SUCCEEDED`` or an error code. This method is48 called in server mode when the client requests a channel, after49 authentication is complete.50 51 If you allow channel requests (and an ssh server that didn't would be52 useless), you should also override some of the channel request methods53 below, which are used to determine which services will be allowed on54 a given channel:55 56 - `check_channel_pty_request`57 - `check_channel_shell_request`58 - `check_channel_subsystem_request`59 - `check_channel_window_change_request`60 - `check_channel_x11_request`61 - `check_channel_forward_agent_request`62 63 The ``chanid`` parameter is a small number that uniquely identifies the64 channel within a `.Transport`. A `.Channel` object is not created65 unless this method returns ``OPEN_SUCCEEDED`` -- once a66 `.Channel` object is created, you can call `.Channel.get_id` to67 retrieve the channel ID.68 69 The return value should either be ``OPEN_SUCCEEDED`` (or70 ``0``) to allow the channel request, or one of the following error71 codes to reject it:72 73 - ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``74 - ``OPEN_FAILED_CONNECT_FAILED``75 - ``OPEN_FAILED_UNKNOWN_CHANNEL_TYPE``76 - ``OPEN_FAILED_RESOURCE_SHORTAGE``77 78 The default implementation always returns79 ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``.80 81 :param str kind:82 the kind of channel the client would like to open (usually83 ``"session"``).84 :param int chanid: ID of the channel85 :return: an `int` success or failure code (listed above)86 """87 return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED88 89 def get_allowed_auths(self, username):90 """91 Return a list of authentication methods supported by the server.92 This list is sent to clients attempting to authenticate, to inform them93 of authentication methods that might be successful.94 95 The "list" is actually a string of comma-separated names of types of96 authentication. Possible values are ``"password"``, ``"publickey"``,97 and ``"none"``.98 99 The default implementation always returns ``"password"``.100 101 :param str username: the username requesting authentication.102 :return: a comma-separated `str` of authentication types103 """104 return "password"105 106 def check_auth_none(self, username):107 """108 Determine if a client may open channels with no (further)109 authentication.110 111 Return ``AUTH_FAILED`` if the client must authenticate, or112 ``AUTH_SUCCESSFUL`` if it's okay for the client to not113 authenticate.114 115 The default implementation always returns ``AUTH_FAILED``.116 117 :param str username: the username of the client.118 :return:119 ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if120 it succeeds.121 :rtype: int122 """123 return AUTH_FAILED124 125 def check_auth_password(self, username, password):126 """127 Determine if a given username and password supplied by the client is128 acceptable for use in authentication.129 130 Return ``AUTH_FAILED`` if the password is not accepted,131 ``AUTH_SUCCESSFUL`` if the password is accepted and completes132 the authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your133 authentication is stateful, and this key is accepted for134 authentication, but more authentication is required. (In this latter135 case, `get_allowed_auths` will be called to report to the client what136 options it has for continuing the authentication.)137 138 The default implementation always returns ``AUTH_FAILED``.139 140 :param str username: the username of the authenticating client.141 :param str password: the password given by the client.142 :return:143 ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if144 it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the password auth is145 successful, but authentication must continue.146 :rtype: int147 """148 return AUTH_FAILED149 150 def check_auth_publickey(self, username, key):151 """152 Determine if a given key supplied by the client is acceptable for use153 in authentication. You should override this method in server mode to154 check the username and key and decide if you would accept a signature155 made using this key.156 157 Return ``AUTH_FAILED`` if the key is not accepted,158 ``AUTH_SUCCESSFUL`` if the key is accepted and completes the159 authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your160 authentication is stateful, and this password is accepted for161 authentication, but more authentication is required. (In this latter162 case, `get_allowed_auths` will be called to report to the client what163 options it has for continuing the authentication.)164 165 Note that you don't have to actually verify any key signtature here.166 If you're willing to accept the key, Paramiko will do the work of167 verifying the client's signature.168 169 The default implementation always returns ``AUTH_FAILED``.170 171 :param str username: the username of the authenticating client172 :param .PKey key: the key object provided by the client173 :return:174 ``AUTH_FAILED`` if the client can't authenticate with this key;175 ``AUTH_SUCCESSFUL`` if it can; ``AUTH_PARTIALLY_SUCCESSFUL`` if it176 can authenticate with this key but must continue with177 authentication178 :rtype: int179 """180 return AUTH_FAILED181 182 def check_auth_interactive(self, username, submethods):183 """184 Begin an interactive authentication challenge, if supported. You185 should override this method in server mode if you want to support the186 ``"keyboard-interactive"`` auth type, which requires you to send a187 series of questions for the client to answer.188 189 Return ``AUTH_FAILED`` if this auth method isn't supported. Otherwise,190 you should return an `.InteractiveQuery` object containing the prompts191 and instructions for the user. The response will be sent via a call192 to `check_auth_interactive_response`.193 194 The default implementation always returns ``AUTH_FAILED``.195 196 :param str username: the username of the authenticating client197 :param str submethods:198 a comma-separated list of methods preferred by the client (usually199 empty)200 :return:201 ``AUTH_FAILED`` if this auth method isn't supported; otherwise an202 object containing queries for the user203 :rtype: int or `.InteractiveQuery`204 """205 return AUTH_FAILED206 207 def check_auth_interactive_response(self, responses):208 """209 Continue or finish an interactive authentication challenge, if210 supported. You should override this method in server mode if you want211 to support the ``"keyboard-interactive"`` auth type.212 213 Return ``AUTH_FAILED`` if the responses are not accepted,214 ``AUTH_SUCCESSFUL`` if the responses are accepted and complete215 the authentication, or ``AUTH_PARTIALLY_SUCCESSFUL`` if your216 authentication is stateful, and this set of responses is accepted for217 authentication, but more authentication is required. (In this latter218 case, `get_allowed_auths` will be called to report to the client what219 options it has for continuing the authentication.)220 221 If you wish to continue interactive authentication with more questions,222 you may return an `.InteractiveQuery` object, which should cause the223 client to respond with more answers, calling this method again. This224 cycle can continue indefinitely.225 226 The default implementation always returns ``AUTH_FAILED``.227 228 :param responses: list of `str` responses from the client229 :return:230 ``AUTH_FAILED`` if the authentication fails; ``AUTH_SUCCESSFUL`` if231 it succeeds; ``AUTH_PARTIALLY_SUCCESSFUL`` if the interactive auth232 is successful, but authentication must continue; otherwise an233 object containing queries for the user234 :rtype: int or `.InteractiveQuery`235 """236 return AUTH_FAILED237 238 def check_auth_gssapi_with_mic(239 self, username, gss_authenticated=AUTH_FAILED, cc_file=None240 ):241 """242 Authenticate the given user to the server if he is a valid krb5243 principal.244 245 :param str username: The username of the authenticating client246 :param int gss_authenticated: The result of the krb5 authentication247 :param str cc_filename: The krb5 client credentials cache filename248 :return: ``AUTH_FAILED`` if the user is not authenticated otherwise249 ``AUTH_SUCCESSFUL``250 :rtype: int251 :note: Kerberos credential delegation is not supported.252 :see: `.ssh_gss`253 :note: : We are just checking in L{AuthHandler} that the given user is254 a valid krb5 principal!255 We don't check if the krb5 principal is allowed to log in on256 the server, because there is no way to do that in python. So257 if you develop your own SSH server with paramiko for a certain258 platform like Linux, you should call C{krb5_kuserok()} in259 your local kerberos library to make sure that the260 krb5_principal has an account on the server and is allowed to261 log in as a user.262 :see: http://www.unix.com/man-page/all/3/krb5_kuserok/263 """264 if gss_authenticated == AUTH_SUCCESSFUL:265 return AUTH_SUCCESSFUL266 return AUTH_FAILED267 268 def check_auth_gssapi_keyex(269 self, username, gss_authenticated=AUTH_FAILED, cc_file=None270 ):271 """272 Authenticate the given user to the server if he is a valid krb5273 principal and GSS-API Key Exchange was performed.274 If GSS-API Key Exchange was not performed, this authentication method275 won't be available.276 277 :param str username: The username of the authenticating client278 :param int gss_authenticated: The result of the krb5 authentication279 :param str cc_filename: The krb5 client credentials cache filename280 :return: ``AUTH_FAILED`` if the user is not authenticated otherwise281 ``AUTH_SUCCESSFUL``282 :rtype: int283 :note: Kerberos credential delegation is not supported.284 :see: `.ssh_gss` `.kex_gss`285 :note: : We are just checking in L{AuthHandler} that the given user is286 a valid krb5 principal!287 We don't check if the krb5 principal is allowed to log in on288 the server, because there is no way to do that in python. So289 if you develop your own SSH server with paramiko for a certain290 platform like Linux, you should call C{krb5_kuserok()} in291 your local kerberos library to make sure that the292 krb5_principal has an account on the server and is allowed293 to log in as a user.294 :see: http://www.unix.com/man-page/all/3/krb5_kuserok/295 """296 if gss_authenticated == AUTH_SUCCESSFUL:297 return AUTH_SUCCESSFUL298 return AUTH_FAILED299 300 def enable_auth_gssapi(self):301 """302 Overwrite this function in your SSH server to enable GSSAPI303 authentication.304 The default implementation always returns false.305 306 :returns bool: Whether GSSAPI authentication is enabled.307 :see: `.ssh_gss`308 """309 UseGSSAPI = False310 return UseGSSAPI311 312 def check_port_forward_request(self, address, port):313 """314 Handle a request for port forwarding. The client is asking that315 connections to the given address and port be forwarded back across316 this ssh connection. An address of ``"0.0.0.0"`` indicates a global317 address (any address associated with this server) and a port of ``0``318 indicates that no specific port is requested (usually the OS will pick319 a port).320 321 The default implementation always returns ``False``, rejecting the322 port forwarding request. If the request is accepted, you should return323 the port opened for listening.324 325 :param str address: the requested address326 :param int port: the requested port327 :return:328 the port number (`int`) that was opened for listening, or ``False``329 to reject330 """331 return False332 333 def cancel_port_forward_request(self, address, port):334 """335 The client would like to cancel a previous port-forwarding request.336 If the given address and port is being forwarded across this ssh337 connection, the port should be closed.338 339 :param str address: the forwarded address340 :param int port: the forwarded port341 """342 pass343 344 def check_global_request(self, kind, msg):345 """346 Handle a global request of the given ``kind``. This method is called347 in server mode and client mode, whenever the remote host makes a global348 request. If there are any arguments to the request, they will be in349 ``msg``.350 351 There aren't any useful global requests defined, aside from port352 forwarding, so usually this type of request is an extension to the353 protocol.354 355 If the request was successful and you would like to return contextual356 data to the remote host, return a tuple. Items in the tuple will be357 sent back with the successful result. (Note that the items in the358 tuple can only be strings, ints, or bools.)359 360 The default implementation always returns ``False``, indicating that it361 does not support any global requests.362 363 .. note:: Port forwarding requests are handled separately, in364 `check_port_forward_request`.365 366 :param str kind: the kind of global request being made.367 :param .Message msg: any extra arguments to the request.368 :return:369 ``True`` or a `tuple` of data if the request was granted; ``False``370 otherwise.371 """372 return False373 374 # ...Channel requests...375 376 def check_channel_pty_request(377 self, channel, term, width, height, pixelwidth, pixelheight, modes378 ):379 """380 Determine if a pseudo-terminal of the given dimensions (usually381 requested for shell access) can be provided on the given channel.382 383 The default implementation always returns ``False``.384 385 :param .Channel channel: the `.Channel` the pty request arrived on.386 :param str term: type of terminal requested (for example, ``"vt100"``).387 :param int width: width of screen in characters.388 :param int height: height of screen in characters.389 :param int pixelwidth:390 width of screen in pixels, if known (may be ``0`` if unknown).391 :param int pixelheight:392 height of screen in pixels, if known (may be ``0`` if unknown).393 :return:394 ``True`` if the pseudo-terminal has been allocated; ``False``395 otherwise.396 """397 return False398 399 def check_channel_shell_request(self, channel):400 """401 Determine if a shell will be provided to the client on the given402 channel. If this method returns ``True``, the channel should be403 connected to the stdin/stdout of a shell (or something that acts like404 a shell).405 406 The default implementation always returns ``False``.407 408 :param .Channel channel: the `.Channel` the request arrived on.409 :return:410 ``True`` if this channel is now hooked up to a shell; ``False`` if411 a shell can't or won't be provided.412 """413 return False414 415 def check_channel_exec_request(self, channel, command):416 """417 Determine if a shell command will be executed for the client. If this418 method returns ``True``, the channel should be connected to the stdin,419 stdout, and stderr of the shell command.420 421 The default implementation always returns ``False``.422 423 :param .Channel channel: the `.Channel` the request arrived on.424 :param str command: the command to execute.425 :return:426 ``True`` if this channel is now hooked up to the stdin, stdout, and427 stderr of the executing command; ``False`` if the command will not428 be executed.429 430 .. versionadded:: 1.1431 """432 return False433 434 def check_channel_subsystem_request(self, channel, name):435 """436 Determine if a requested subsystem will be provided to the client on437 the given channel. If this method returns ``True``, all future I/O438 through this channel will be assumed to be connected to the requested439 subsystem. An example of a subsystem is ``sftp``.440 441 The default implementation checks for a subsystem handler assigned via442 `.Transport.set_subsystem_handler`.443 If one has been set, the handler is invoked and this method returns444 ``True``. Otherwise it returns ``False``.445 446 .. note:: Because the default implementation uses the `.Transport` to447 identify valid subsystems, you probably won't need to override this448 method.449 450 :param .Channel channel: the `.Channel` the pty request arrived on.451 :param str name: name of the requested subsystem.452 :return:453 ``True`` if this channel is now hooked up to the requested454 subsystem; ``False`` if that subsystem can't or won't be provided.455 """456 transport = channel.get_transport()457 handler_class, args, kwargs = transport._get_subsystem_handler(name)458 if handler_class is None:459 return False460 handler = handler_class(channel, name, self, *args, **kwargs)461 handler.start()462 return True463 464 def check_channel_window_change_request(465 self, channel, width, height, pixelwidth, pixelheight466 ):467 """468 Determine if the pseudo-terminal on the given channel can be resized.469 This only makes sense if a pty was previously allocated on it.470 471 The default implementation always returns ``False``.472 473 :param .Channel channel: the `.Channel` the pty request arrived on.474 :param int width: width of screen in characters.475 :param int height: height of screen in characters.476 :param int pixelwidth:477 width of screen in pixels, if known (may be ``0`` if unknown).478 :param int pixelheight:479 height of screen in pixels, if known (may be ``0`` if unknown).480 :return: ``True`` if the terminal was resized; ``False`` if not.481 """482 return False483 484 def check_channel_x11_request(485 self,486 channel,487 single_connection,488 auth_protocol,489 auth_cookie,490 screen_number,491 ):492 """493 Determine if the client will be provided with an X11 session. If this494 method returns ``True``, X11 applications should be routed through new495 SSH channels, using `.Transport.open_x11_channel`.496 497 The default implementation always returns ``False``.498 499 :param .Channel channel: the `.Channel` the X11 request arrived on500 :param bool single_connection:501 ``True`` if only a single X11 channel should be opened, else502 ``False``.503 :param str auth_protocol: the protocol used for X11 authentication504 :param str auth_cookie: the cookie used to authenticate to X11505 :param int screen_number: the number of the X11 screen to connect to506 :return: ``True`` if the X11 session was opened; ``False`` if not507 """508 return False509 510 def check_channel_forward_agent_request(self, channel):511 """512 Determine if the client will be provided with an forward agent session.513 If this method returns ``True``, the server will allow SSH Agent514 forwarding.515 516 The default implementation always returns ``False``.517 518 :param .Channel channel: the `.Channel` the request arrived on519 :return: ``True`` if the AgentForward was loaded; ``False`` if not520 521 If ``True`` is returned, the server should create an522 :class:`AgentServerProxy` to access the agent.523 """524 return False525 526 def check_channel_direct_tcpip_request(self, chanid, origin, destination):527 """528 Determine if a local port forwarding channel will be granted, and529 return ``OPEN_SUCCEEDED`` or an error code. This method is530 called in server mode when the client requests a channel, after531 authentication is complete.532 533 The ``chanid`` parameter is a small number that uniquely identifies the534 channel within a `.Transport`. A `.Channel` object is not created535 unless this method returns ``OPEN_SUCCEEDED`` -- once a536 `.Channel` object is created, you can call `.Channel.get_id` to537 retrieve the channel ID.538 539 The origin and destination parameters are (ip_address, port) tuples540 that correspond to both ends of the TCP connection in the forwarding541 tunnel.542 543 The return value should either be ``OPEN_SUCCEEDED`` (or544 ``0``) to allow the channel request, or one of the following error545 codes to reject it:546 547 - ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``548 - ``OPEN_FAILED_CONNECT_FAILED``549 - ``OPEN_FAILED_UNKNOWN_CHANNEL_TYPE``550 - ``OPEN_FAILED_RESOURCE_SHORTAGE``551 552 The default implementation always returns553 ``OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED``.554 555 :param int chanid: ID of the channel556 :param tuple origin:557 2-tuple containing the IP address and port of the originator558 (client side)559 :param tuple destination:560 2-tuple containing the IP address and port of the destination561 (server side)562 :return: an `int` success or failure code (listed above)563 """564 return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED565 566 def check_channel_env_request(self, channel, name, value):567 """568 Check whether a given environment variable can be specified for the569 given channel. This method should return ``True`` if the server570 is willing to set the specified environment variable. Note that571 some environment variables (e.g., PATH) can be exceedingly572 dangerous, so blindly allowing the client to set the environment573 is almost certainly not a good idea.574 575 The default implementation always returns ``False``.576 577 :param channel: the `.Channel` the env request arrived on578 :param str name: name579 :param str value: Channel value580 :returns: A boolean581 """582 return False583 584 def get_banner(self):585 """586 A pre-login banner to display to the user. The message may span587 multiple lines separated by crlf pairs. The language should be in588 rfc3066 style, for example: en-US589 590 The default implementation always returns ``(None, None)``.591 592 :returns: A tuple containing the banner and language code.593 594 .. versionadded:: 2.3595 """596 return (None, None)597 598 599class InteractiveQuery:600 """601 A query (set of prompts) for a user during interactive authentication.602 """603 604 def __init__(self, name="", instructions="", *prompts):605 """606 Create a new interactive query to send to the client. The name and607 instructions are optional, but are generally displayed to the end608 user. A list of prompts may be included, or they may be added via609 the `add_prompt` method.610 611 :param str name: name of this query612 :param str instructions:613 user instructions (usually short) about this query614 :param str prompts: one or more authentication prompts615 """616 self.name = name617 self.instructions = instructions618 self.prompts = []619 for x in prompts:620 if isinstance(x, str):621 self.add_prompt(x)622 else:623 self.add_prompt(x[0], x[1])624 625 def add_prompt(self, prompt, echo=True):626 """627 Add a prompt to this query. The prompt should be a (reasonably short)628 string. Multiple prompts can be added to the same query.629 630 :param str prompt: the user prompt631 :param bool echo:632 ``True`` (default) if the user's response should be echoed;633 ``False`` if not (for a password or similar)634 """635 self.prompts.append((prompt, echo))636 637 638class SubsystemHandler(threading.Thread):639 """640 Handler for a subsystem in server mode. If you create a subclass of this641 class and pass it to `.Transport.set_subsystem_handler`, an object of this642 class will be created for each request for this subsystem. Each new object643 will be executed within its own new thread by calling `start_subsystem`.644 When that method completes, the channel is closed.645 646 For example, if you made a subclass ``MP3Handler`` and registered it as the647 handler for subsystem ``"mp3"``, then whenever a client has successfully648 authenticated and requests subsystem ``"mp3"``, an object of class649 ``MP3Handler`` will be created, and `start_subsystem` will be called on650 it from a new thread.651 """652 653 def __init__(self, channel, name, server):654 """655 Create a new handler for a channel. This is used by `.ServerInterface`656 to start up a new handler when a channel requests this subsystem. You657 don't need to override this method, but if you do, be sure to pass the658 ``channel`` and ``name`` parameters through to the original659 ``__init__`` method here.660 661 :param .Channel channel: the channel associated with this662 subsystem request.663 :param str name: name of the requested subsystem.664 :param .ServerInterface server:665 the server object for the session that started this subsystem666 """667 threading.Thread.__init__(self, target=self._run)668 self.__channel = channel669 self.__transport = channel.get_transport()670 self.__name = name671 self.__server = server672 673 def get_server(self):674 """675 Return the `.ServerInterface` object associated with this channel and676 subsystem.677 """678 return self.__server679 680 def _run(self):681 try:682 self.__transport._log(683 DEBUG, "Starting handler for subsystem {}".format(self.__name)684 )685 self.start_subsystem(self.__name, self.__transport, self.__channel)686 except Exception as e:687 self.__transport._log(688 ERROR,689 'Exception in subsystem handler for "{}": {}'.format(690 self.__name, e691 ),692 )693 self.__transport._log(ERROR, util.tb_strings())694 try:695 self.finish_subsystem()696 except:697 pass698 699 def start_subsystem(self, name, transport, channel):700 """701 Process an ssh subsystem in server mode. This method is called on a702 new object (and in a new thread) for each subsystem request. It is703 assumed that all subsystem logic will take place here, and when the704 subsystem is finished, this method will return. After this method705 returns, the channel is closed.706 707 The combination of ``transport`` and ``channel`` are unique; this708 handler corresponds to exactly one `.Channel` on one `.Transport`.709 710 .. note::711 It is the responsibility of this method to exit if the underlying712 `.Transport` is closed. This can be done by checking713 `.Transport.is_active` or noticing an EOF on the `.Channel`. If714 this method loops forever without checking for this case, your715 Python interpreter may refuse to exit because this thread will716 still be running.717 718 :param str name: name of the requested subsystem.719 :param .Transport transport: the server-mode `.Transport`.720 :param .Channel channel: the channel associated with this subsystem721 request.722 """723 pass724 725 def finish_subsystem(self):726 """727 Perform any cleanup at the end of a subsystem. The default728 implementation just closes the channel.729 730 .. versionadded:: 1.1731 """732 self.__channel.close()733 