Ticket #2041: ssh-2hop-v4.patch
File ssh-2hop-v4.patch, 35.4 KB (added by , 3 years ago) |
---|
-
man/xpra.1
88 88 Connect using websocket protocol. 89 89 .SS wss://[USERNAME[:PASSWORD]@]HOST:PORT/[DISPLAY] 90 90 Connect using secure websocket protocol. (websocket with SSL) 91 .SS ssh://[USERNAME[:PASSWORD]@]HOST[:SSH_PORT]/DISPLAY 91 .SS ssh://[USERNAME[:PASSWORD]@]HOST[:SSH_PORT]/DISPLAY[?proxy=ssh://[USERNAME[:PASSWORD]@]HOST[:SSH_PORT]] 92 92 Further options can be specified using the \fB\-\-ssh\fP command line option. 93 93 .P 94 94 For backwards compatibility, SSH mode also supports the syntax: … … 101 101 The password need only be specified when the server authentication module 102 102 requires it. (ie: often when authenticating against MS Windows servers, 103 103 or with \fBmultifile\fP and \fBsqlite\fP authentication modules) 104 .P 105 When the "?proxy=" option is set XPRA will establish an SSH connection to the 106 specified "proxy" host. From that host XPRA will set up an SSH connection to 107 the XPRA server. 104 108 .\" -------------------------------------------------------------------- 105 109 .SH EXAMPLES 106 110 .TP \w'xpra\ 'u -
xpra/client/gtk_base/client_launcher.py
40 40 from xpra.client.gtk_base.gtk_tray_menu_base import make_min_auto_menu, make_encodingsmenu, \ 41 41 MIN_QUALITY_OPTIONS, QUALITY_OPTIONS, MIN_SPEED_OPTIONS, SPEED_OPTIONS 42 42 from xpra.gtk_common.about import about 43 from xpra.scripts.main import connect_to, make_client, configure_network, is_local, add_ssh_args, parse_ssh_string 43 from xpra.scripts.main import ( 44 connect_to, make_client, configure_network, is_local, 45 add_ssh_args, parse_ssh_string, add_ssh_proxy_args, 46 ) 44 47 from xpra.platform.paths import get_icon_dir 45 48 from xpra.platform import get_username 46 49 from xpra.log import Logger, enable_debug_for … … 47 50 log = Logger("launcher") 48 51 49 52 #what we save in the config file: 50 SAVED_FIELDS = ["username", "password", "host", "port", "mode", "ssh_port", 51 "encoding", "quality", "min-quality", "speed", "min-speed"] 53 SAVED_FIELDS = [ 54 "username", "password", "host", "port", "mode", "ssh_port", 55 "encoding", "quality", "min-quality", "speed", "min-speed", 56 "proxy_port", "proxy_username", "proxy_key", "proxy_password", 57 ] 52 58 53 59 #options not normally found in xpra config file 54 60 #but which can be present in a launcher config: … … 60 66 "mode" : str, 61 67 "autoconnect" : bool, 62 68 "ssh_port" : int, 69 "proxy_host" : str, 70 "proxy_port" : int, 71 "proxy_username" : str, 72 "proxy_password" : str, 73 "proxy_key" : str, 63 74 } 64 75 LAUNCHER_DEFAULTS = { 65 76 "host" : "", … … 66 77 "port" : -1, 67 78 "username" : get_username(), 68 79 "password" : "", 69 "mode" : " tcp", #tcp,ssh,..80 "mode" : "ssh -> ssh", #tcp,ssh,.. 70 81 "autoconnect" : False, 71 82 "ssh_port" : 22, 83 "proxy_host" : "", 84 "proxy_port" : 22, 85 "proxy_username" : get_username(), 86 "proxy_password" : "", 87 "proxy_key" : "", 72 88 } 73 89 74 90 … … 113 129 def __init__(self): 114 130 # Default connection options 115 131 self.config = make_defaults_struct(extras_defaults=LAUNCHER_DEFAULTS, extras_types=LAUNCHER_OPTION_TYPES, extras_validation=self.get_launcher_validation()) 132 ssh_cmd = parse_ssh_string(self.config.ssh)[0].strip().lower() 133 self.is_putty = ssh_cmd.endswith("plink") or ssh_cmd.endswith("plink.exe") 134 self.is_paramiko = ssh_cmd=="paramiko" 116 135 #TODO: the fixup does not belong here? 117 136 from xpra.scripts.main import fixup_options 118 137 fixup_options(self.config) … … 127 146 128 147 def get_connection_modes(self): 129 148 modes = ["ssh"] 149 modes.append("ssh -> ssh") 130 150 try: 131 151 import ssl 132 152 assert ssl … … 221 241 vbox.pack_start(hbox) 222 242 223 243 # Mode: 224 hbox = gtk.HBox(False, 20) 225 hbox.set_spacing(20) 226 hbox.pack_start(gtk.Label("Mode: ")) 244 hbox = gtk.HBox(False, 5) 227 245 self.mode_combo = gtk.combo_box_new_text() 228 246 for x in self.get_connection_modes(): 229 247 self.mode_combo.append_text(x.upper()) 230 248 self.mode_combo.connect("changed", self.mode_changed) 231 hbox.pack_start(self.mode_combo) 232 vbox.pack_start(hbox) 249 hbox.pack_start(gtk.Label("Mode: "), False, False) 250 hbox.pack_start(self.mode_combo, False, False) 251 align_hbox = gtk.Alignment(xalign = .5) 252 align_hbox.add(hbox) 253 vbox.pack_start(align_hbox) 233 254 234 # Username@Host:Port 235 hbox = gtk.HBox(False, 0) 236 hbox.set_spacing(5) 255 # Username@Host:Port (ssh -> ssh, proxy) 256 vbox_proxy = gtk.VBox(False, 15) 257 hbox = gtk.HBox(False, 5) 258 self.proxy_vbox = vbox_proxy 259 self.proxy_username_entry = gtk.Entry() 260 self.proxy_username_entry.set_max_length(128) 261 self.proxy_username_entry.set_width_chars(16) 262 self.proxy_username_entry.connect("changed", self.validate) 263 self.proxy_username_entry.connect("activate", self.connect_clicked) 264 self.proxy_username_entry.set_tooltip_text("username") 265 self.proxy_host_entry = gtk.Entry() 266 self.proxy_host_entry.set_max_length(128) 267 self.proxy_host_entry.set_width_chars(24) 268 self.proxy_host_entry.connect("changed", self.validate) 269 self.proxy_host_entry.connect("activate", self.connect_clicked) 270 self.proxy_host_entry.set_tooltip_text("hostname") 271 self.proxy_port_entry = gtk.Entry() 272 self.proxy_port_entry.set_max_length(5) 273 self.proxy_port_entry.set_width_chars(5) 274 self.proxy_port_entry.connect("changed", self.validate) 275 self.proxy_port_entry.connect("activate", self.connect_clicked) 276 self.proxy_port_entry.set_tooltip_text("SSH port") 277 hbox.pack_start(gtk.Label("Proxy: "), False, False) 278 hbox.pack_start(self.proxy_username_entry, True, True) 279 hbox.pack_start(gtk.Label("@"), False, False) 280 hbox.pack_start(self.proxy_host_entry, True, True) 281 hbox.pack_start(self.proxy_port_entry, False, False) 282 vbox_proxy.pack_start(hbox) 283 284 # Password 285 hbox = gtk.HBox(False, 5) 286 self.proxy_password_entry = gtk.Entry() 287 self.proxy_password_entry.set_max_length(128) 288 self.proxy_password_entry.set_width_chars(30) 289 self.proxy_password_entry.set_text("") 290 self.proxy_password_entry.set_visibility(False) 291 self.proxy_password_entry.connect("changed", self.password_ok) 292 self.proxy_password_entry.connect("changed", self.validate) 293 self.proxy_password_entry.connect("activate", self.connect_clicked) 294 self.proxy_password_label = gtk.Label("Proxy Password") 295 hbox.pack_start(self.proxy_password_label, False, False) 296 hbox.pack_start(self.proxy_password_entry, True, True) 297 vbox_proxy.pack_start(hbox) 298 299 # Private key 300 hbox = gtk.HBox(False, 5) 301 self.pkey_hbox = hbox 302 self.proxy_key_label = gtk.Label("Proxy private key path (PPK):") 303 self.proxy_key_entry = gtk.Entry() 304 self.proxy_key_browse = gtk.Button("Browse") 305 self.proxy_key_browse.connect("clicked", self.proxy_key_browse_clicked) 306 hbox.pack_start(self.proxy_key_label, False, False) 307 hbox.pack_start(self.proxy_key_entry, True, True) 308 hbox.pack_start(self.proxy_key_browse, False, False) 309 vbox_proxy.pack_start(hbox) 310 311 # Check boxes 312 hbox = gtk.HBox(False, 5) 313 self.password_scb = gtk.CheckButton("Server password same as proxy") 314 self.password_scb.set_mode(True) 315 self.password_scb.set_active(True) 316 self.password_scb.connect("toggled", self.validate) 317 align_password_scb = gtk.Alignment(xalign = 1.0) 318 align_password_scb.add(self.password_scb) 319 self.username_scb = gtk.CheckButton("Server username same as proxy") 320 self.username_scb.set_mode(True) 321 self.username_scb.set_active(True) 322 self.username_scb.connect("toggled", self.validate) 323 align_username_scb = gtk.Alignment(xalign = 0.0) 324 align_username_scb.add(self.username_scb) 325 hbox.pack_start(align_username_scb, True, True) 326 hbox.pack_start(align_password_scb, True, True) 327 vbox_proxy.pack_start(hbox) 328 329 # coniditonal stuff that goes away for "normal" ssh 330 vbox.pack_start(vbox_proxy) 331 332 # Username@Host:Port (main) 333 hbox = gtk.HBox(False, 5) 237 334 self.username_entry = gtk.Entry() 238 335 self.username_entry.set_max_length(128) 239 336 self.username_entry.set_width_chars(16) … … 240 337 self.username_entry.connect("changed", self.validate) 241 338 self.username_entry.connect("activate", self.connect_clicked) 242 339 self.username_entry.set_tooltip_text("username") 243 self.username_label = gtk.Label("@")244 340 self.host_entry = gtk.Entry() 245 341 self.host_entry.set_max_length(128) 246 342 self.host_entry.set_width_chars(24) … … 259 355 self.port_entry.connect("changed", self.validate) 260 356 self.port_entry.connect("activate", self.connect_clicked) 261 357 self.port_entry.set_tooltip_text("port/display") 262 263 hbox.pack_start(self.username_entry )264 hbox.pack_start( self.username_label)265 hbox.pack_start(self.host_entry )266 hbox.pack_start(self.ssh_port_entry )267 hbox.pack_start(gtk.Label(":") )268 hbox.pack_start(self.port_entry )358 hbox.pack_start(gtk.Label("Server:"), False, False) 359 hbox.pack_start(self.username_entry, True, True) 360 hbox.pack_start(gtk.Label("@"), False, False) 361 hbox.pack_start(self.host_entry, True, True) 362 hbox.pack_start(self.ssh_port_entry, False, False) 363 hbox.pack_start(gtk.Label(":"), False, False) 364 hbox.pack_start(self.port_entry, False, False) 269 365 vbox.pack_start(hbox) 270 366 271 367 # Password 272 hbox = gtk.HBox(False, 0) 273 hbox.set_spacing(20) 368 hbox = gtk.HBox(False, 5) 274 369 self.password_entry = gtk.Entry() 275 370 self.password_entry.set_max_length(128) 276 371 self.password_entry.set_width_chars(30) … … 278 373 self.password_entry.set_visibility(False) 279 374 self.password_entry.connect("changed", self.password_ok) 280 375 self.password_entry.connect("changed", self.validate) 281 self.password_label = gtk.Label(" Password:")282 hbox.pack_start(self.password_label )283 hbox.pack_start(self.password_entry )376 self.password_label = gtk.Label("Server Password:") 377 hbox.pack_start(self.password_label, False, False) 378 hbox.pack_start(self.password_entry, True, True) 284 379 vbox.pack_start(hbox) 285 380 286 381 #strict host key check for SSL and SSH 287 hbox = gtk.HBox(False, 0) 288 hbox.set_spacing(20) 382 hbox = gtk.HBox(False, 5) 289 383 self.nostrict_host_check = gtk.CheckButton("Disable Strict Host Key Check") 290 384 self.nostrict_host_check.set_active(False) 291 385 al = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0) … … 412 506 413 507 def validate(self, *args): 414 508 ssh = self.mode_combo.get_active_text()=="SSH" 509 sshtossh = self.mode_combo.get_active_text()=="SSH -> SSH" 415 510 errs = [] 416 511 host = self.host_entry.get_text() 417 512 errs.append((self.host_entry, not bool(host), "specify the host")) 418 if ssh :513 if ssh or sshtossh: 419 514 #validate ssh port: 420 515 ssh_port = self.ssh_port_entry.get_text() 421 516 try: … … 423 518 except: 424 519 ssh_port = -1 425 520 errs.append((self.ssh_port_entry, ssh_port<0 or ssh_port>=2**16, "invalid SSH port number")) 521 if sshtossh: 522 #validate ssh port: 523 proxy_port = self.proxy_port_entry.get_text() 524 try: 525 proxy_port = int(proxy_port) 526 except: 527 proxy_port = -1 528 errs.append((self.proxy_port_entry, proxy_port<0 or proxy_port>=2**16, "invalid SSH port number")) 426 529 port = self.port_entry.get_text() 427 if ssh and not port: 530 if sshtossh: 531 if self.password_scb.get_active(): 532 self.password_entry.set_sensitive(False) 533 self.password_entry.set_text(self.proxy_password_entry.get_text()) 534 else: 535 self.password_entry.set_sensitive(True) 536 if self.username_scb.get_active(): 537 self.username_entry.set_sensitive(False) 538 self.username_entry.set_text(self.proxy_username_entry.get_text()) 539 else: 540 self.username_entry.set_sensitive(True) 541 errs.append((self.proxy_host_entry, not bool(self.proxy_host_entry.get_text()), "specify the proxy host")) 542 # check username *after* the checkbox action 543 if ssh or sshtossh: 544 errs.append((self.username_entry, not bool(self.username_entry.get_text()), "specify username")) 545 if sshtossh: 546 errs.append((self.proxy_username_entry, not bool(self.proxy_username_entry.get_text()), "specify proxy username")) 547 if ssh or sshtossh and not port: 428 548 port = 0 #port optional with ssh 429 549 else: 430 550 try: … … 460 580 def mode_changed(self, *_args): 461 581 mode = self.mode_combo.get_active_text().lower() 462 582 ssh = mode=="ssh" 463 if ssh: 583 sshtossh = mode=="ssh -> ssh" 584 if ssh or sshtossh: 464 585 self.port_entry.set_tooltip_text("Display number (optional)") 465 586 self.port_entry.set_text("") 466 self.ssh_port_entry.set_text("22")467 587 self.ssh_port_entry.show() 468 588 self.password_entry.set_tooltip_text("SSH Password") 469 589 self.username_entry.set_tooltip_text("SSH Username") 590 if ssh: 591 self.proxy_vbox.hide() 592 self.password_scb.hide() 593 self.password_entry.set_sensitive(True) 594 self.username_entry.set_sensitive(True) 595 if sshtossh: 596 self.proxy_vbox.show() 597 self.password_scb.show() 470 598 else: 599 self.password_entry.set_sensitive(True) 600 self.username_entry.set_sensitive(True) 601 self.proxy_vbox.hide() 471 602 self.ssh_port_entry.hide() 472 603 self.ssh_port_entry.set_text("") 473 604 port_str = self.port_entry.get_text() … … 479 610 if self.config.port>0: 480 611 self.port_entry.set_text("%s" % self.config.port) 481 612 can_use_password = True 482 if ssh: 483 ssh_cmd = parse_ssh_string(self.config.ssh)[0].strip().lower() 484 is_putty = ssh_cmd.endswith("plink") or ssh_cmd.endswith("plink.exe") 485 is_paramiko = ssh_cmd=="paramiko" 486 if is_putty or is_paramiko: 613 if ssh or sshtossh: 614 if not self.is_putty: 615 self.proxy_key_entry.set_text("OpenSSH/Paramiko use ~/.ssh") 616 self.proxy_key_entry.set_editable(False) 617 self.proxy_key_entry.set_sensitive(False) 618 self.proxy_key_browse.hide() 619 if self.is_paramiko or self.is_putty: 487 620 can_use_password = True 488 621 else: 489 622 #we can also use password if sshpass is installed: … … 493 626 if can_use_password: 494 627 self.password_label.show() 495 628 self.password_entry.show() 629 if sshtossh: 630 self.proxy_password_label.show() 631 self.proxy_password_entry.show() 496 632 else: 497 633 self.password_label.hide() 498 634 self.password_entry.hide() 635 if sshtossh: 636 self.proxy_password_label.hide() 637 self.proxy_password_entry.hide() 499 638 self.validate() 500 639 if mode=="ssl" or (mode=="ssh" and not WIN32): 501 640 self.nostrict_host_check.show() … … 531 670 def reset_errors(self): 532 671 self.set_sensitive(True) 533 672 self.set_info_text("") 534 for widget in (self.info, self.password_entry, self.username_entry, self.host_entry, self.port_entry): 673 for widget in ( 674 self.info, self.password_entry, self.username_entry, self.host_entry, 675 self.port_entry, self.proxy_password_entry, self.proxy_username_entry, 676 self.proxy_host_entry, self.proxy_port_entry, self.ssh_port_entry, 677 ): 535 678 self.set_widget_fg_color(self.info, False) 536 679 self.set_widget_bg_color(widget, False) 537 680 … … 546 689 def set_sensitive(self, s): 547 690 glib.idle_add(self.window.set_sensitive, s) 548 691 692 def choose_pkey_file(self, title, action, action_button, callback): 693 file_filter = gtk.FileFilter() 694 file_filter.set_name("All Files") 695 file_filter.add_pattern("*") 696 choose_file(self.window, title, action, action_button, callback, file_filter) 697 698 def proxy_key_browse_clicked(self, *args): 699 log("proxy_key_browse_clicked%s", args) 700 def do_choose(filename): 701 #make sure the file extension is .ppk 702 if os.path.splitext(filename)[-1]!=".ppk": 703 filename += ".ppk" 704 self.proxy_key_entry.set_text(filename) 705 self.choose_pkey_file("Choose SSH private key", FILE_CHOOSER_ACTION_OPEN, gtk.STOCK_OPEN, do_choose) 706 549 707 def connect_clicked(self, *args): 550 708 log("connect_clicked%s", args) 551 709 self.update_options_from_gui() … … 591 749 #cooked vars used by connect_to 592 750 params = {"type" : self.config.mode} 593 751 username = self.config.username 594 if self.config.mode=="ssh" :752 if self.config.mode=="ssh" or self.config.mode=="ssh -> ssh": 595 753 if self.config.socket_dir: 596 754 params["socket_dir"] = self.config.socket_dir 597 755 params["remote_xpra"] = self.config.remote_xpra … … 602 760 else: 603 761 params["display"] = "auto" 604 762 params["display_as_args"] = [] 605 full_ssh = parse_ssh_string(self.config.ssh) 606 ssh_cmd = full_ssh[0].lower() 607 is_putty = ssh_cmd.endswith("plink") or ssh_cmd.endswith("plink.exe") 608 is_paramiko = ssh_cmd=="paramiko" 609 params["is_putty"] = is_putty 610 params["is_paramiko"] = is_paramiko 763 params["ssh"] = self.config.ssh 764 params["is_putty"] = self.is_putty 765 params["is_paramiko"] = self.is_paramiko 611 766 password = self.config.password 612 767 host = self.config.host 613 768 upos = host.find("@") … … 622 777 username = username[:ppos] 623 778 if self.config.ssh_port and self.config.ssh_port!=22: 624 779 params["ssh-port"] = self.config.ssh_port 625 full_ssh += add_ssh_args(username, password, host, self.config.ssh_port, is_putty, is_paramiko) 780 ssh_cmd = parse_ssh_string(self.config.ssh) 781 self.is_putty = ssh_cmd.endswith("plink") or ssh_cmd.endswith("plink.exe") 782 self.is_paramiko = ssh_cmd=="paramiko" 783 full_ssh = ssh_cmd[:] 784 full_ssh += add_ssh_args(username, password, host, self.config.ssh_port, self.is_putty, self.is_paramiko) 626 785 if username: 627 786 params["username"] = username 628 787 if self.nostrict_host_check.get_active(): 629 788 full_ssh += ["-o", "StrictHostKeyChecking=no"] 789 if params["type"] == "ssh -> ssh": 790 params["type"] = "ssh" 791 params["proxy_host"] = self.config.proxy_host 792 params["proxy_port"] = self.config.proxy_port 793 params["proxy_username"] = self.config.proxy_username 794 params["proxy_password"] = self.config.proxy_password 795 full_ssh += add_ssh_proxy_args(self.config.proxy_username, self.config.proxy_password, 796 self.config.proxy_host, self.config.proxy_port, 797 self.config.proxy_key, ssh_cmd, 798 self.is_putty, self.is_paramiko) 630 799 params["host"] = host 631 800 params["local"] = is_local(self.config.host) 632 801 params["full_ssh"] = full_ssh … … 788 957 self.config.ssh_port = pint(self.ssh_port_entry.get_text()) 789 958 self.config.port = pint(self.port_entry.get_text()) 790 959 self.config.username = self.username_entry.get_text() 960 self.config.password = self.password_entry.get_text() 961 962 self.config.proxy_host = self.proxy_host_entry.get_text() 963 self.config.proxy_port = pint(self.proxy_port_entry.get_text()) 964 self.config.proxy_username = self.proxy_username_entry.get_text() 965 self.config.proxy_password = self.proxy_password_entry.get_text() 966 if self.is_putty: 967 self.config.proxy_key = self.proxy_key_entry.get_text() 968 791 969 self.config.encoding = self.get_selected_encoding() or self.config.encoding 792 970 mode_enc = self.mode_combo.get_active_text().lower() 793 971 if mode_enc.startswith("tcp"): … … 794 972 self.config.mode = "tcp" 795 973 if mode_enc.find("aes")>0: 796 974 self.config.encryption = "AES" 797 elif mode_enc in ("ssl", "ssh", "ws", "wss" ):975 elif mode_enc in ("ssl", "ssh", "ws", "wss", "ssh -> ssh"): 798 976 self.config.mode = mode_enc 799 977 self.config.encryption = "" 800 self.config.password = self.password_entry.get_text()801 978 log("update_options_from_gui() %s", (self.config.username, self.config.password, self.config.mode, self.config.encryption, self.config.host, self.config.port, self.config.ssh_port, self.config.encoding)) 802 979 803 980 def update_gui_from_config(self): … … 830 1007 pass 831 1008 return str(default_port) 832 1009 dport = DEFAULT_PORT 833 if mode=="ssh" :1010 if mode=="ssh" or mode=="ssh -> ssh": 834 1011 #not required, so don't specify one 835 1012 dport = "" 836 1013 self.port_entry.set_text(get_port(self.config.port, dport)) 837 1014 self.ssh_port_entry.set_text(get_port(self.config.ssh_port)) 1015 #proxy bits: 1016 self.proxy_host_entry.set_text(self.config.proxy_host) 1017 self.proxy_port_entry.set_text(get_port(self.config.proxy_port)) 1018 self.proxy_username_entry.set_text(self.config.proxy_username) 1019 self.proxy_password_entry.set_text(self.config.proxy_password) 1020 if self.is_putty: 1021 self.proxy_key_entry.set_text(self.config.proxy_key) 838 1022 839 1023 def close_window(self, *_args): 840 1024 w = self.window -
xpra/net/ssh.py
15 15 16 16 from xpra.scripts.main import InitException, InitExit, shellquote 17 17 from xpra.platform.paths import get_xpra_command, get_ssh_known_hosts_files 18 from xpra.platform import get_username 18 19 from xpra.net.bytestreams import SocketConnection, SOCKET_TIMEOUT, ConnectionClosedException 19 20 from xpra.exit_codes import EXIT_SSH_KEY_FAILURE, EXIT_SSH_FAILURE 20 from xpra.os_util import bytestostr, osexpand, monotonic_time, setsid, nomodule_context, umask_context, is_WSL,WIN32, OSX, POSIX21 from xpra.os_util import bytestostr, osexpand, monotonic_time, setsid, nomodule_context, umask_context, WIN32, OSX, POSIX 21 22 from xpra.util import envint, envbool, nonl, engs 22 23 23 24 INITENV_COMMAND = os.environ.get("XPRA_INITENV_COMMAND", "xpra initenv") … … 202 203 port = display_desc.get("ssh-port", 22) 203 204 ipv6 = display_desc.get("ipv6", False) 204 205 #ssh and command attributes: 205 username = display_desc.get("username") 206 if not username: 207 import getpass 208 username = getpass.getuser() 206 username = display_desc.get("username") or get_username() 207 if "proxy_host" in display_desc: 208 display_desc.setdefault("proxy_username", get_username()) 209 209 password = display_desc.get("password") 210 210 target = ssh_target_string(display_desc) 211 211 remote_xpra = display_desc["remote_xpra"] … … 252 252 from paramiko.ssh_exception import ProxyCommandFailure 253 253 bytestreams.CLOSED_EXCEPTIONS = tuple(list(bytestreams.CLOSED_EXCEPTIONS)+[ProxyCommandFailure]) 254 254 return conn 255 from xpra.scripts.main import socket_connect 256 from paramiko.transport import Transport 257 from paramiko import SSHException 258 if "proxy_host" in display_desc: 259 proxy_host = display_desc["proxy_host"] 260 proxy_port = display_desc.get("proxy_port", 22) 261 proxy_username = display_desc.get("proxy_username", username) 262 proxy_password = display_desc.get("proxy_password", password) 263 proxy_ipv6 = display_desc.get("proxy_ipv6", False) 264 sock = socket_connect(dtype, proxy_host, proxy_port, proxy_ipv6) 265 middle_transport = Transport(sock) 266 middle_transport.use_compression(False) 267 try: 268 middle_transport.start_client() 269 except SSHException as e: 270 log("start_client()", exc_info=True) 271 raise InitException("SSH negotiation failed: %s" % e) 272 chan_to_middle = do_ssh_paramiko_connect_to(middle_transport, proxy_host, proxy_username, proxy_password, dest_host=host, dest_port=port) 273 transport = Transport(chan_to_middle) 274 transport.use_compression(False) 275 try: 276 transport.start_client() 277 except SSHException as e: 278 log("start_client()", exc_info=True) 279 raise InitException("SSH negotiation failed: %s" % e) 280 chan = do_ssh_paramiko_connect_to(transport, host, username, password, proxy_command, remote_xpra, socket_dir, display_as_args) 281 peername = (host, port) 282 conn = SSHProxyCommandConnection(chan, peername, target, socket_info) 283 conn.timeout = SOCKET_TIMEOUT 284 conn.start_stderr_reader() 285 return conn 255 286 256 287 #plain TCP connection to the server, 257 288 #we open it then give the socket to paramiko: 258 from xpra.scripts.main import socket_connect259 289 sock = socket_connect(dtype, host, port, ipv6) 260 290 sockname = sock.getsockname() 261 291 peername = sock.getpeername() 262 292 log("paramiko socket_connect: sockname=%s, peername=%s", sockname, peername) 263 from paramiko.transport import Transport264 from paramiko import SSHException265 293 transport = Transport(sock) 266 294 transport.use_compression(False) 267 295 try: … … 284 312 def __init__(self): 285 313 nomodule_context.__init__(self, "gssapi") 286 314 287 315 # (1) If the arguments after "proxy_command" are "None", then we're opening a port-forward 316 # (2) If "parachan" is set, that means we're using a port-forward 288 317 def do_ssh_paramiko_connect_to(transport, host, username, password, 289 xpra_proxy_command, remote_xpra, socket_dir, display_as_args): 318 xpra_proxy_command=None, remote_xpra=None, socket_dir=None, display_as_args=None, 319 dest_host=None, dest_port=None): 290 320 from paramiko import SSHException, RSAKey, PasswordRequiredException 291 321 from paramiko.agent import Agent 292 322 from paramiko.hostkeys import HostKeys … … 455 485 log("auth_password(..)", exc_info=True) 456 486 log.info("SSH password authentication failed: %s", e) 457 487 488 def auth_interactive(): 489 log("trying interactive authentication") 490 class iauthhandler: 491 def __init__(self): 492 self.authcount = 0 493 def handlestuff(self, title, instructions, prompt_list): 494 p = [] 495 for pent in prompt_list: 496 if self.authcount==0 and password: 497 p.append(password) 498 else: 499 p.append(input_pass(pent[0])) 500 self.authcount += 1 501 return p 502 try: 503 myiauthhandler = iauthhandler() 504 transport.auth_interactive(username, myiauthhandler.handlestuff, "") 505 except SSHException as e: 506 log("auth_interactive(..)", exc_info=True) 507 log.info("SSH password authentication failed: %s", e) 508 458 509 banner = transport.get_banner() 459 510 if banner: 460 511 log.info("SSH server banner:") … … 462 513 log.info(" %s", x) 463 514 464 515 log("starting authentication") 516 # per the RFC we probably should do none first always and read off the supported 517 # methods, however, the current code seems to work fine with OpenSSH 465 518 if not transport.is_authenticated() and NONE_AUTH: 466 519 auth_none() 467 520 521 # Some people do two-factor using KEY_AUTH to kick things off, so this happens first 468 522 if not transport.is_authenticated() and KEY_AUTH: 469 523 auth_publickey() 470 524 525 if not transport.is_authenticated() and PASSWORD_AUTH: 526 auth_interactive() 527 471 528 if not transport.is_authenticated() and PASSWORD_AUTH and password: 472 529 auth_password() 473 530 … … 485 542 486 543 if not transport.is_authenticated(): 487 544 transport.close() 488 raise InitException("SSH Authentication failed")545 raise InitException("SSH Authentication on %s failed" % host) 489 546 547 if remote_xpra is None: 548 log("Opening proxy channel") 549 return transport.open_channel("direct-tcpip", (dest_host, dest_port), ('localhost', 0)) 550 490 551 assert len(remote_xpra)>0 491 552 log("will try to run xpra from: %s", remote_xpra) 492 553 for xpra_cmd in remote_xpra: -
xpra/scripts/main.py
511 511 args += ["-T", host] 512 512 return args 513 513 514 def add_ssh_proxy_args(username, password, host, ssh_port, pkey, ssh, is_putty=False, is_paramiko=False): 515 args = [] 516 proxyline = ssh 517 if is_putty: 518 proxyline += ["-nc", "%host:%port"] 519 if pkey: 520 # tortoise plink works with either slash, backslash needs too much escaping 521 # because of the weird way it's passed through as a ProxyCommand 522 proxyline += [ "-i", "\"" + pkey.replace("\\", "/") + "\""] 523 elif not is_paramiko: 524 proxyline += ["-W", "%h:%p"] 525 # the double quotes are in case the password has something like "&" 526 proxyline += add_ssh_args(username, password, host, ssh_port, is_putty, is_paramiko) 527 if is_putty: 528 args += ["-proxycmd", " ".join(proxyline)] 529 elif not is_paramiko: 530 from xpra.platform.paths import get_sshpass_command 531 if password: 532 sshpass_command = get_sshpass_command() 533 if sshpass_command: 534 proxyline.insert(0, sshpass_command) 535 # is -e forces proxy password to match destination password 536 proxyline.insert(1, "-e") 537 args += ["-o", "ProxyCommand " + " ".join(proxyline)] 538 return args 539 540 541 def parse_proxy_attributes(display_name): 542 import re 543 # Notes: 544 # (1) this regex permits a "?" in the password or username (because not just splitting at "?"). 545 # It doesn't look for the next "?" until after the "@", where a "?" really indicates 546 # another field. 547 # (2) all characters including "@"s go to "userpass" until the *last* "@" after which it all goes 548 # to "hostport" 549 reout = re.search("\\?proxy=(?P<p>((?P<userpass>.+)@)?(?P<hostport>[^?]+))", display_name) 550 if not reout: 551 return display_name, {} 552 try: 553 desc_tmp = dict() 554 # This one should *always* return a host, and should end with an optional numeric port 555 hostport = reout.group("hostport") 556 hostport_match = re.match("(?P<host>[^:]+)($|:(?P<port>\d+)$)", hostport) 557 if not hostport_match: 558 raise RuntimeError("bad format for 'hostport': '%s'" % hostport) 559 host = hostport_match.group("host") 560 if not host: 561 raise RuntimeError("bad format: missing host in '%s'" % hostport) 562 desc_tmp["proxy_host"] = host 563 if hostport_match.group("port"): 564 desc_tmp["proxy_port"] = hostport_match.group("ssh_port") 565 userpass = reout.group("userpass") 566 if userpass: 567 # The username ends at the first colon. This decision was not unique: I could have 568 # allowed one colon in username if there were two in the string. 569 userpass_match = re.match("(?P<username>[^:]+)(:(?P<password>.+))?", userpass) 570 if not userpass_match: 571 raise RuntimeError("bad format for 'userpass': '%s'" % userpass) 572 # If there is a "userpass" part, then it *must* have a username 573 username = userpass_match.group("username") 574 if not username: 575 raise RuntimeError("bad format: missing username in '%s'" % userpass) 576 desc_tmp["proxy_username"] = username 577 password = userpass_match.group("password") 578 if password: 579 desc_tmp["proxy_password"] = password 580 except RuntimeError: 581 from xpra.log import Logger 582 sshlog = Logger("ssh") 583 sshlog.error("bad proxy argument: " + reout.group(0)) 584 return display_name, {} 585 else: 586 # rip out the part we've processed 587 display_name = display_name[:reout.start()] + display_name[reout.end():] 588 return display_name, desc_tmp 589 514 590 def parse_display_name(error_cb, opts, display_name, session_name_lookup=False): 515 591 desc = {"display_name" : display_name} 592 display_name, proxy_attrs = parse_proxy_attributes(display_name) 593 desc.update(proxy_attrs) 594 516 595 #split the display name on ":" or "/" 517 596 scpos = display_name.find(":") 518 597 slpos = display_name.find("/") … … 676 755 host = parts[0] 677 756 #ie: ssh=["/usr/bin/ssh", "-v"] 678 757 ssh = parse_ssh_string(opts.ssh) 679 full_ssh = ssh 758 full_ssh = ssh[:] 680 759 681 760 #maybe restrict to win32 only? 682 761 ssh_cmd = ssh[0].lower() … … 698 777 if ssh_port and ssh_port!=22: 699 778 desc["ssh-port"] = ssh_port 700 779 full_ssh += add_ssh_args(username, password, host, ssh_port, is_putty, is_paramiko) 780 if "proxy_host" in desc: 781 proxy_username = desc.get("proxy_username", "") 782 proxy_password = desc.get("proxy_password", "") 783 proxy_host = desc["proxy_host"] 784 proxy_port = desc.get("proxy_port", 22) 785 proxy_key = desc.get("proxy_key", "") 786 full_ssh += add_ssh_proxy_args(proxy_username, proxy_password, proxy_host, proxy_port, 787 proxy_key, ssh, is_putty, is_paramiko) 701 788 desc.update({ 702 789 "host" : host, 703 790 "full_ssh" : full_ssh