1# Copyright (C) 2011 Jeff Forcier <jeff@bitprophet.org> 2# 3# This file is part of ssh. 4# 5# 'ssh' is free software; you can redistribute it and/or modify it under the 6# terms of the GNU Lesser General Public License as published by the Free 7# Software Foundation; either version 2.1 of the License, or (at your option) 8# any later version. 9# 10# 'ssh' is distrubuted in the hope that it will be useful, but WITHOUT ANY 11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 13# details. 14# 15# You should have received a copy of the GNU Lesser General Public License 16# along with 'ssh'; if not, write to the Free Software Foundation, Inc., 17# 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA. 18 19""" 20L{ServerInterface} is an interface to override for server support. 21""" 22 23import threading 24from ssh.common import * 25from ssh import util 26 27 28class InteractiveQuery (object): 29 """ 30 A query (set of prompts) for a user during interactive authentication. 31 """ 32 33 def __init__(self, name='', instructions='', *prompts): 34 """ 35 Create a new interactive query to send to the client. The name and 36 instructions are optional, but are generally displayed to the end 37 user. A list of prompts may be included, or they may be added via 38 the L{add_prompt} method. 39 40 @param name: name of this query 41 @type name: str 42 @param instructions: user instructions (usually short) about this query 43 @type instructions: str 44 @param prompts: one or more authentication prompts 45 @type prompts: str 46 """ 47 self.name = name 48 self.instructions = instructions 49 self.prompts = [] 50 for x in prompts: 51 if (type(x) is str) or (type(x) is unicode): 52 self.add_prompt(x) 53 else: 54 self.add_prompt(x[0], x[1]) 55 56 def add_prompt(self, prompt, echo=True): 57 """ 58 Add a prompt to this query. The prompt should be a (reasonably short) 59 string. Multiple prompts can be added to the same query. 60 61 @param prompt: the user prompt 62 @type prompt: str 63 @param echo: C{True} (default) if the user's response should be echoed; 64 C{False} if not (for a password or similar) 65 @type echo: bool 66 """ 67 self.prompts.append((prompt, echo)) 68 69 70class ServerInterface (object): 71 """ 72 This class defines an interface for controlling the behavior of ssh 73 in server mode. 74 75 Methods on this class are called from ssh's primary thread, so you 76 shouldn't do too much work in them. (Certainly nothing that blocks or 77 sleeps.) 78 """ 79 80 def check_channel_request(self, kind, chanid): 81 """ 82 Determine if a channel request of a given type will be granted, and 83 return C{OPEN_SUCCEEDED} or an error code. This method is 84 called in server mode when the client requests a channel, after 85 authentication is complete. 86 87 If you allow channel requests (and an ssh server that didn't would be 88 useless), you should also override some of the channel request methods 89 below, which are used to determine which services will be allowed on 90 a given channel: 91 - L{check_channel_pty_request} 92 - L{check_channel_shell_request} 93 - L{check_channel_subsystem_request} 94 - L{check_channel_window_change_request} 95 - L{check_channel_x11_request} 96 - L{check_channel_forward_agent_request} 97 98 The C{chanid} parameter is a small number that uniquely identifies the 99 channel within a L{Transport}. A L{Channel} object is not created 100 unless this method returns C{OPEN_SUCCEEDED} -- once a 101 L{Channel} object is created, you can call L{Channel.get_id} to 102 retrieve the channel ID. 103 104 The return value should either be C{OPEN_SUCCEEDED} (or 105 C{0}) to allow the channel request, or one of the following error 106 codes to reject it: 107 - C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED} 108 - C{OPEN_FAILED_CONNECT_FAILED} 109 - C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE} 110 - C{OPEN_FAILED_RESOURCE_SHORTAGE} 111 112 The default implementation always returns 113 C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}. 114 115 @param kind: the kind of channel the client would like to open 116 (usually C{"session"}). 117 @type kind: str 118 @param chanid: ID of the channel 119 @type chanid: int 120 @return: a success or failure code (listed above) 121 @rtype: int 122 """ 123 return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED 124 125 def get_allowed_auths(self, username): 126 """ 127 Return a list of authentication methods supported by the server. 128 This list is sent to clients attempting to authenticate, to inform them 129 of authentication methods that might be successful. 130 131 The "list" is actually a string of comma-separated names of types of 132 authentication. Possible values are C{"password"}, C{"publickey"}, 133 and C{"none"}. 134 135 The default implementation always returns C{"password"}. 136 137 @param username: the username requesting authentication. 138 @type username: str 139 @return: a comma-separated list of authentication types 140 @rtype: str 141 """ 142 return 'password' 143 144 def check_auth_none(self, username): 145 """ 146 Determine if a client may open channels with no (further) 147 authentication. 148 149 Return L{AUTH_FAILED} if the client must authenticate, or 150 L{AUTH_SUCCESSFUL} if it's okay for the client to not 151 authenticate. 152 153 The default implementation always returns L{AUTH_FAILED}. 154 155 @param username: the username of the client. 156 @type username: str 157 @return: L{AUTH_FAILED} if the authentication fails; 158 L{AUTH_SUCCESSFUL} if it succeeds. 159 @rtype: int 160 """ 161 return AUTH_FAILED 162 163 def check_auth_password(self, username, password): 164 """ 165 Determine if a given username and password supplied by the client is 166 acceptable for use in authentication. 167 168 Return L{AUTH_FAILED} if the password is not accepted, 169 L{AUTH_SUCCESSFUL} if the password is accepted and completes 170 the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your 171 authentication is stateful, and this key is accepted for 172 authentication, but more authentication is required. (In this latter 173 case, L{get_allowed_auths} will be called to report to the client what 174 options it has for continuing the authentication.) 175 176 The default implementation always returns L{AUTH_FAILED}. 177 178 @param username: the username of the authenticating client. 179 @type username: str 180 @param password: the password given by the client. 181 @type password: str 182 @return: L{AUTH_FAILED} if the authentication fails; 183 L{AUTH_SUCCESSFUL} if it succeeds; 184 L{AUTH_PARTIALLY_SUCCESSFUL} if the password auth is 185 successful, but authentication must continue. 186 @rtype: int 187 """ 188 return AUTH_FAILED 189 190 def check_auth_publickey(self, username, key): 191 """ 192 Determine if a given key supplied by the client is acceptable for use 193 in authentication. You should override this method in server mode to 194 check the username and key and decide if you would accept a signature 195 made using this key. 196 197 Return L{AUTH_FAILED} if the key is not accepted, 198 L{AUTH_SUCCESSFUL} if the key is accepted and completes the 199 authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your 200 authentication is stateful, and this password is accepted for 201 authentication, but more authentication is required. (In this latter 202 case, L{get_allowed_auths} will be called to report to the client what 203 options it has for continuing the authentication.) 204 205 Note that you don't have to actually verify any key signtature here. 206 If you're willing to accept the key, ssh will do the work of 207 verifying the client's signature. 208 209 The default implementation always returns L{AUTH_FAILED}. 210 211 @param username: the username of the authenticating client 212 @type username: str 213 @param key: the key object provided by the client 214 @type key: L{PKey <pkey.PKey>} 215 @return: L{AUTH_FAILED} if the client can't authenticate 216 with this key; L{AUTH_SUCCESSFUL} if it can; 217 L{AUTH_PARTIALLY_SUCCESSFUL} if it can authenticate with 218 this key but must continue with authentication 219 @rtype: int 220 """ 221 return AUTH_FAILED 222 223 def check_auth_interactive(self, username, submethods): 224 """ 225 Begin an interactive authentication challenge, if supported. You 226 should override this method in server mode if you want to support the 227 C{"keyboard-interactive"} auth type, which requires you to send a 228 series of questions for the client to answer. 229 230 Return L{AUTH_FAILED} if this auth method isn't supported. Otherwise, 231 you should return an L{InteractiveQuery} object containing the prompts 232 and instructions for the user. The response will be sent via a call 233 to L{check_auth_interactive_response}. 234 235 The default implementation always returns L{AUTH_FAILED}. 236 237 @param username: the username of the authenticating client 238 @type username: str 239 @param submethods: a comma-separated list of methods preferred by the 240 client (usually empty) 241 @type submethods: str 242 @return: L{AUTH_FAILED} if this auth method isn't supported; otherwise 243 an object containing queries for the user 244 @rtype: int or L{InteractiveQuery} 245 """ 246 return AUTH_FAILED 247 248 def check_auth_interactive_response(self, responses): 249 """ 250 Continue or finish an interactive authentication challenge, if 251 supported. You should override this method in server mode if you want 252 to support the C{"keyboard-interactive"} auth type. 253 254 Return L{AUTH_FAILED} if the responses are not accepted, 255 L{AUTH_SUCCESSFUL} if the responses are accepted and complete 256 the authentication, or L{AUTH_PARTIALLY_SUCCESSFUL} if your 257 authentication is stateful, and this set of responses is accepted for 258 authentication, but more authentication is required. (In this latter 259 case, L{get_allowed_auths} will be called to report to the client what 260 options it has for continuing the authentication.) 261 262 If you wish to continue interactive authentication with more questions, 263 you may return an L{InteractiveQuery} object, which should cause the 264 client to respond with more answers, calling this method again. This 265 cycle can continue indefinitely. 266 267 The default implementation always returns L{AUTH_FAILED}. 268 269 @param responses: list of responses from the client 270 @type responses: list(str) 271 @return: L{AUTH_FAILED} if the authentication fails; 272 L{AUTH_SUCCESSFUL} if it succeeds; 273 L{AUTH_PARTIALLY_SUCCESSFUL} if the interactive auth is 274 successful, but authentication must continue; otherwise an object 275 containing queries for the user 276 @rtype: int or L{InteractiveQuery} 277 """ 278 return AUTH_FAILED 279 280 def check_port_forward_request(self, address, port): 281 """ 282 Handle a request for port forwarding. The client is asking that 283 connections to the given address and port be forwarded back across 284 this ssh connection. An address of C{"0.0.0.0"} indicates a global 285 address (any address associated with this server) and a port of C{0} 286 indicates that no specific port is requested (usually the OS will pick 287 a port). 288 289 The default implementation always returns C{False}, rejecting the 290 port forwarding request. If the request is accepted, you should return 291 the port opened for listening. 292 293 @param address: the requested address 294 @type address: str 295 @param port: the requested port 296 @type port: int 297 @return: the port number that was opened for listening, or C{False} to 298 reject 299 @rtype: int 300 """ 301 return False 302 303 def cancel_port_forward_request(self, address, port): 304 """ 305 The client would like to cancel a previous port-forwarding request. 306 If the given address and port is being forwarded across this ssh 307 connection, the port should be closed. 308 309 @param address: the forwarded address 310 @type address: str 311 @param port: the forwarded port 312 @type port: int 313 """ 314 pass 315 316 def check_global_request(self, kind, msg): 317 """ 318 Handle a global request of the given C{kind}. This method is called 319 in server mode and client mode, whenever the remote host makes a global 320 request. If there are any arguments to the request, they will be in 321 C{msg}. 322 323 There aren't any useful global requests defined, aside from port 324 forwarding, so usually this type of request is an extension to the 325 protocol. 326 327 If the request was successful and you would like to return contextual 328 data to the remote host, return a tuple. Items in the tuple will be 329 sent back with the successful result. (Note that the items in the 330 tuple can only be strings, ints, longs, or bools.) 331 332 The default implementation always returns C{False}, indicating that it 333 does not support any global requests. 334 335 @note: Port forwarding requests are handled separately, in 336 L{check_port_forward_request}. 337 338 @param kind: the kind of global request being made. 339 @type kind: str 340 @param msg: any extra arguments to the request. 341 @type msg: L{Message} 342 @return: C{True} or a tuple of data if the request was granted; 343 C{False} otherwise. 344 @rtype: bool 345 """ 346 return False 347 348 349 ### Channel requests 350 351 352 def check_channel_pty_request(self, channel, term, width, height, pixelwidth, pixelheight, 353 modes): 354 """ 355 Determine if a pseudo-terminal of the given dimensions (usually 356 requested for shell access) can be provided on the given channel. 357 358 The default implementation always returns C{False}. 359 360 @param channel: the L{Channel} the pty request arrived on. 361 @type channel: L{Channel} 362 @param term: type of terminal requested (for example, C{"vt100"}). 363 @type term: str 364 @param width: width of screen in characters. 365 @type width: int 366 @param height: height of screen in characters. 367 @type height: int 368 @param pixelwidth: width of screen in pixels, if known (may be C{0} if 369 unknown). 370 @type pixelwidth: int 371 @param pixelheight: height of screen in pixels, if known (may be C{0} 372 if unknown). 373 @type pixelheight: int 374 @return: C{True} if the psuedo-terminal has been allocated; C{False} 375 otherwise. 376 @rtype: bool 377 """ 378 return False 379 380 def check_channel_shell_request(self, channel): 381 """ 382 Determine if a shell will be provided to the client on the given 383 channel. If this method returns C{True}, the channel should be 384 connected to the stdin/stdout of a shell (or something that acts like 385 a shell). 386 387 The default implementation always returns C{False}. 388 389 @param channel: the L{Channel} the request arrived on. 390 @type channel: L{Channel} 391 @return: C{True} if this channel is now hooked up to a shell; C{False} 392 if a shell can't or won't be provided. 393 @rtype: bool 394 """ 395 return False 396 397 def check_channel_exec_request(self, channel, command): 398 """ 399 Determine if a shell command will be executed for the client. If this 400 method returns C{True}, the channel should be connected to the stdin, 401 stdout, and stderr of the shell command. 402 403 The default implementation always returns C{False}. 404 405 @param channel: the L{Channel} the request arrived on. 406 @type channel: L{Channel} 407 @param command: the command to execute. 408 @type command: str 409 @return: C{True} if this channel is now hooked up to the stdin, 410 stdout, and stderr of the executing command; C{False} if the 411 command will not be executed. 412 @rtype: bool 413 414 @since: 1.1 415 """ 416 return False 417 418 def check_channel_subsystem_request(self, channel, name): 419 """ 420 Determine if a requested subsystem will be provided to the client on 421 the given channel. If this method returns C{True}, all future I/O 422 through this channel will be assumed to be connected to the requested 423 subsystem. An example of a subsystem is C{sftp}. 424 425 The default implementation checks for a subsystem handler assigned via 426 L{Transport.set_subsystem_handler}. 427 If one has been set, the handler is invoked and this method returns 428 C{True}. Otherwise it returns C{False}. 429 430 @note: Because the default implementation uses the L{Transport} to 431 identify valid subsystems, you probably won't need to override this 432 method. 433 434 @param channel: the L{Channel} the pty request arrived on. 435 @type channel: L{Channel} 436 @param name: name of the requested subsystem. 437 @type name: str 438 @return: C{True} if this channel is now hooked up to the requested 439 subsystem; C{False} if that subsystem can't or won't be provided. 440 @rtype: bool 441 """ 442 handler_class, larg, kwarg = channel.get_transport()._get_subsystem_handler(name) 443 if handler_class is None: 444 return False 445 handler = handler_class(channel, name, self, *larg, **kwarg) 446 handler.start() 447 return True 448 449 def check_channel_window_change_request(self, channel, width, height, pixelwidth, pixelheight): 450 """ 451 Determine if the pseudo-terminal on the given channel can be resized. 452 This only makes sense if a pty was previously allocated on it. 453 454 The default implementation always returns C{False}. 455 456 @param channel: the L{Channel} the pty request arrived on. 457 @type channel: L{Channel} 458 @param width: width of screen in characters. 459 @type width: int 460 @param height: height of screen in characters. 461 @type height: int 462 @param pixelwidth: width of screen in pixels, if known (may be C{0} if 463 unknown). 464 @type pixelwidth: int 465 @param pixelheight: height of screen in pixels, if known (may be C{0} 466 if unknown). 467 @type pixelheight: int 468 @return: C{True} if the terminal was resized; C{False} if not. 469 @rtype: bool 470 """ 471 return False 472 473 def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number): 474 """ 475 Determine if the client will be provided with an X11 session. If this 476 method returns C{True}, X11 applications should be routed through new 477 SSH channels, using L{Transport.open_x11_channel}. 478 479 The default implementation always returns C{False}. 480 481 @param channel: the L{Channel} the X11 request arrived on 482 @type channel: L{Channel} 483 @param single_connection: C{True} if only a single X11 channel should 484 be opened 485 @type single_connection: bool 486 @param auth_protocol: the protocol used for X11 authentication 487 @type auth_protocol: str 488 @param auth_cookie: the cookie used to authenticate to X11 489 @type auth_cookie: str 490 @param screen_number: the number of the X11 screen to connect to 491 @type screen_number: int 492 @return: C{True} if the X11 session was opened; C{False} if not 493 @rtype: bool 494 """ 495 return False 496 497 def check_channel_forward_agent_request(self, channel): 498 """ 499 Determine if the client will be provided with an forward agent session. 500 If this method returns C{True}, the server will allow SSH Agent 501 forwarding. 502 503 The default implementation always returns C{False}. 504 505 @param channel: the L{Channel} the request arrived on 506 @type channel: L{Channel} 507 @return: C{True} if the AgentForward was loaded; C{False} if not 508 @rtype: bool 509 """ 510 return False 511 512 def check_channel_direct_tcpip_request(self, chanid, origin, destination): 513 """ 514 Determine if a local port forwarding channel will be granted, and 515 return C{OPEN_SUCCEEDED} or an error code. This method is 516 called in server mode when the client requests a channel, after 517 authentication is complete. 518 519 The C{chanid} parameter is a small number that uniquely identifies the 520 channel within a L{Transport}. A L{Channel} object is not created 521 unless this method returns C{OPEN_SUCCEEDED} -- once a 522 L{Channel} object is created, you can call L{Channel.get_id} to 523 retrieve the channel ID. 524 525 The origin and destination parameters are (ip_address, port) tuples 526 that correspond to both ends of the TCP connection in the forwarding 527 tunnel. 528 529 The return value should either be C{OPEN_SUCCEEDED} (or 530 C{0}) to allow the channel request, or one of the following error 531 codes to reject it: 532 - C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED} 533 - C{OPEN_FAILED_CONNECT_FAILED} 534 - C{OPEN_FAILED_UNKNOWN_CHANNEL_TYPE} 535 - C{OPEN_FAILED_RESOURCE_SHORTAGE} 536 537 The default implementation always returns 538 C{OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED}. 539 540 @param chanid: ID of the channel 541 @type chanid: int 542 @param origin: 2-tuple containing the IP address and port of the 543 originator (client side) 544 @type origin: tuple 545 @param destination: 2-tuple containing the IP address and port of the 546 destination (server side) 547 @type destination: tuple 548 @return: a success or failure code (listed above) 549 @rtype: int 550 """ 551 return OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED 552 553 554class SubsystemHandler (threading.Thread): 555 """ 556 Handler for a subsytem in server mode. If you create a subclass of this 557 class and pass it to 558 L{Transport.set_subsystem_handler}, 559 an object of this 560 class will be created for each request for this subsystem. Each new object 561 will be executed within its own new thread by calling L{start_subsystem}. 562 When that method completes, the channel is closed. 563 564 For example, if you made a subclass C{MP3Handler} and registered it as the 565 handler for subsystem C{"mp3"}, then whenever a client has successfully 566 authenticated and requests subsytem C{"mp3"}, an object of class 567 C{MP3Handler} will be created, and L{start_subsystem} will be called on 568 it from a new thread. 569 """ 570 def __init__(self, channel, name, server): 571 """ 572 Create a new handler for a channel. This is used by L{ServerInterface} 573 to start up a new handler when a channel requests this subsystem. You 574 don't need to override this method, but if you do, be sure to pass the 575 C{channel} and C{name} parameters through to the original C{__init__} 576 method here. 577 578 @param channel: the channel associated with this subsystem request. 579 @type channel: L{Channel} 580 @param name: name of the requested subsystem. 581 @type name: str 582 @param server: the server object for the session that started this 583 subsystem 584 @type server: L{ServerInterface} 585 """ 586 threading.Thread.__init__(self, target=self._run) 587 self.__channel = channel 588 self.__transport = channel.get_transport() 589 self.__name = name 590 self.__server = server 591 592 def get_server(self): 593 """ 594 Return the L{ServerInterface} object associated with this channel and 595 subsystem. 596 597 @rtype: L{ServerInterface} 598 """ 599 return self.__server 600 601 def _run(self): 602 try: 603 self.__transport._log(DEBUG, 'Starting handler for subsystem %s' % self.__name) 604 self.start_subsystem(self.__name, self.__transport, self.__channel) 605 except Exception, e: 606 self.__transport._log(ERROR, 'Exception in subsystem handler for "%s": %s' % 607 (self.__name, str(e))) 608 self.__transport._log(ERROR, util.tb_strings()) 609 try: 610 self.finish_subsystem() 611 except: 612 pass 613 614 def start_subsystem(self, name, transport, channel): 615 """ 616 Process an ssh subsystem in server mode. This method is called on a 617 new object (and in a new thread) for each subsystem request. It is 618 assumed that all subsystem logic will take place here, and when the 619 subsystem is finished, this method will return. After this method 620 returns, the channel is closed. 621 622 The combination of C{transport} and C{channel} are unique; this handler 623 corresponds to exactly one L{Channel} on one L{Transport}. 624 625 @note: It is the responsibility of this method to exit if the 626 underlying L{Transport} is closed. This can be done by checking 627 L{Transport.is_active} or noticing an EOF 628 on the L{Channel}. If this method loops forever without checking 629 for this case, your python interpreter may refuse to exit because 630 this thread will still be running. 631 632 @param name: name of the requested subsystem. 633 @type name: str 634 @param transport: the server-mode L{Transport}. 635 @type transport: L{Transport} 636 @param channel: the channel associated with this subsystem request. 637 @type channel: L{Channel} 638 """ 639 pass 640 641 def finish_subsystem(self): 642 """ 643 Perform any cleanup at the end of a subsystem. The default 644 implementation just closes the channel. 645 646 @since: 1.1 647 """ 648 self.__channel.close() 649