xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #2041: p.txt

File p.txt, 27.2 KB (added by Nathan Hallquist, 3 years ago)
Line 
1Index: xpra/client/gtk_base/client_launcher.py
2===================================================================
3--- xpra/client/gtk_base/client_launcher.py     (revision 21058)
4+++ xpra/client/gtk_base/client_launcher.py     (working copy)
5@@ -48,7 +48,9 @@
6 
7 #what we save in the config file:
8 SAVED_FIELDS = ["username", "password", "host", "port", "mode", "ssh_port",
9-                "encoding", "quality", "min-quality", "speed", "min-speed"]
10+                "encoding", "quality", "min-quality", "speed", "min-speed",
11+                "sshproxy_ssh_port", "proxy_username", "sshproxy_pkey_path",
12+                "sshproxy_password" ]
13 
14 #options not normally found in xpra config file
15 #but which can be present in a launcher config:
16@@ -56,19 +58,29 @@
17                         "host"              : str,
18                         "port"              : int,
19                         "username"          : str,
20+                        "proxy_username"    : str,
21+                        "sshproxy_host"     : str,
22+                        "sshproxy_password" : str,
23+                        "sshproxy_pkey_path": str,
24                         "password"          : str,
25                         "mode"              : str,
26                         "autoconnect"       : bool,
27                         "ssh_port"          : int,
28+                        "sshproxy_ssh_port" : int,
29                         }
30 LAUNCHER_DEFAULTS = {
31                         "host"              : "",
32                         "port"              : -1,
33                         "username"          : get_username(),
34+                        "proxy_username"    : get_username(),
35+                        "sshproxy_host"     : "",
36+                        "sshproxy_pkey_path": "",
37                         "password"          : "",
38-                        "mode"              : "tcp",    #tcp,ssh,..
39+                        "sshproxy_password" : "",
40+                        "mode"              : "ssh -> ssh",    #tcp,ssh,..
41                         "autoconnect"       : False,
42                         "ssh_port"          : 22,
43+                        "sshproxy_ssh_port" : 22,
44                     }
45 
46 
47@@ -128,6 +140,19 @@
48     def get_connection_modes(self):
49         modes = ["ssh"]
50         try:
51+            # the next line will always fail on first call to get_connection_modes()
52+            # because self.config doesn't exist yet
53+            ssh_cmd = parse_ssh_string(self.config.ssh)[0].strip().lower()
54+            is_putty = ssh_cmd.endswith("plink") or ssh_cmd.endswith("plink.exe")
55+            is_paramiko = ssh_cmd=="paramiko"
56+            # this mode not implemented for openssh (it is implemented for plink and paramiko)
57+            if is_putty or is_paramiko:
58+                modes.append("ssh -> ssh")
59+            else:
60+                log("ssh is not putty or paramiko, disabling 'ssh->ssh' mode")
61+        except:
62+            pass
63+        try:
64             import ssl
65             assert ssl
66             modes.append("ssl")
67@@ -221,19 +246,102 @@
68         vbox.pack_start(hbox)
69 
70         # Mode:
71-        hbox = gtk.HBox(False, 20)
72-        hbox.set_spacing(20)
73-        hbox.pack_start(gtk.Label("Mode: "))
74+        hbox = gtk.HBox(False, 5)
75         self.mode_combo = gtk.combo_box_new_text()
76         for x in self.get_connection_modes():
77             self.mode_combo.append_text(x.upper())
78         self.mode_combo.connect("changed", self.mode_changed)
79-        hbox.pack_start(self.mode_combo)
80+        self.padding_label = gtk.Label("")
81+        self.padding_label2 = gtk.Label("")
82+        hbox.pack_start(self.padding_label, True, True)
83+        hbox.pack_start(gtk.Label("Mode: "), False, False)
84+        hbox.pack_start(self.mode_combo, False, False)
85+        hbox.pack_start(self.padding_label2, True, True)
86         vbox.pack_start(hbox)
87 
88-        # Username@Host:Port
89-        hbox = gtk.HBox(False, 0)
90-        hbox.set_spacing(5)
91+        # Username@Host:Port (ssh -> ssh, proxy)
92+        vbox_proxy = gtk.VBox(False, 15)
93+        hbox = gtk.HBox(False, 5)
94+        self.sshproxy_vbox = vbox_proxy
95+        self.sshproxy_label = gtk.Label("proxy: ")
96+        self.sshproxy_username_entry = gtk.Entry()
97+        self.sshproxy_username_entry.set_max_length(128)
98+        self.sshproxy_username_entry.set_width_chars(16)
99+        self.sshproxy_username_entry.connect("changed", self.validate)
100+        self.sshproxy_username_entry.connect("activate", self.connect_clicked)
101+        self.sshproxy_username_entry.set_tooltip_text("username")
102+        self.sshproxy_username_label = gtk.Label("@")
103+        self.sshproxy_host_entry = gtk.Entry()
104+        self.sshproxy_host_entry.set_max_length(128)
105+        self.sshproxy_host_entry.set_width_chars(24)
106+        self.sshproxy_host_entry.connect("changed", self.validate)
107+        self.sshproxy_host_entry.connect("activate", self.connect_clicked)
108+        self.sshproxy_host_entry.set_tooltip_text("hostname")
109+        self.sshproxy_ssh_port_entry = gtk.Entry()
110+        self.sshproxy_ssh_port_entry.set_max_length(5)
111+        self.sshproxy_ssh_port_entry.set_width_chars(5)
112+        self.sshproxy_ssh_port_entry.connect("changed", self.validate)
113+        self.sshproxy_ssh_port_entry.connect("activate", self.connect_clicked)
114+        self.sshproxy_ssh_port_entry.set_tooltip_text("SSH port")
115+        hbox.pack_start(self.sshproxy_label, False, False)
116+        hbox.pack_start(self.sshproxy_username_entry, True, True)
117+        hbox.pack_start(self.sshproxy_username_label, False, False)
118+        hbox.pack_start(self.sshproxy_host_entry, True, True)
119+        hbox.pack_start(self.sshproxy_ssh_port_entry, False, False)
120+        vbox_proxy.pack_start(hbox)
121+
122+        #
123+        # Password
124+        #
125+        hbox = gtk.HBox(False, 5)
126+        self.sshproxy_password_entry = gtk.Entry()
127+        self.sshproxy_password_entry.set_max_length(128)
128+        self.sshproxy_password_entry.set_width_chars(30)
129+        self.sshproxy_password_entry.set_text("")
130+        self.sshproxy_password_entry.set_visibility(False)
131+        self.sshproxy_password_entry.connect("changed", self.password_ok)
132+        self.sshproxy_password_entry.connect("changed", self.validate)
133+        self.sshproxy_password_entry.connect("activate", self.connect_clicked)
134+        self.sshproxy_password_label = gtk.Label("Proxy password:")
135+        hbox.pack_start(self.sshproxy_password_label, False, False)
136+        hbox.pack_start(self.sshproxy_password_entry, True, True)
137+        vbox_proxy.pack_start(hbox)
138+
139+        hbox = gtk.HBox(False,  5)
140+        self.pkey_hbox = hbox
141+        self.sshproxy_pkey_path_l = gtk.Label("Proxy private key path (PPK):")
142+        self.sshproxy_pkey_path_entry = gtk.Entry()
143+        self.sshproxy_pkey_path_browse = gtk.Button("Browse")
144+        self.sshproxy_pkey_path_browse.connect("clicked", self.sshproxy_pkey_path_browse_clicked)
145+        hbox.pack_start(self.sshproxy_pkey_path_l, False, False)
146+        hbox.pack_start(self.sshproxy_pkey_path_entry, True, True)
147+        hbox.pack_start(self.sshproxy_pkey_path_browse, False, False)
148+        vbox_proxy.pack_start(hbox)
149+
150+        #
151+        # Same as checkbox
152+        #
153+        hbox = gtk.HBox(False, 5)
154+        self.password_scb = gtk.CheckButton("Server password same as proxy")
155+        self.password_scb.set_mode(True)
156+        self.password_scb.set_active(True)
157+        self.password_scb.connect("toggled", self.validate)
158+        self.username_scb = gtk.CheckButton("Server username same as proxy")
159+        self.username_scb.set_mode(True)
160+        self.username_scb.set_active(True)
161+        self.username_scb.connect("toggled", self.validate)
162+        self.spacer_scb = gtk.Label("")
163+        hbox.pack_start(self.username_scb, False, False)
164+        hbox.pack_start(self.spacer_scb, True, True)
165+        hbox.pack_start(self.password_scb, False, False)
166+        vbox_proxy.pack_start(hbox)
167+
168+        vbox.pack_start(vbox_proxy)
169+
170+
171+        # Username@Host:Port (main)
172+        hbox = gtk.HBox(False, 5)
173+        self.server_label = gtk.Label("Server:")
174         self.username_entry = gtk.Entry()
175         self.username_entry.set_max_length(128)
176         self.username_entry.set_width_chars(16)
177@@ -260,17 +368,19 @@
178         self.port_entry.connect("activate", self.connect_clicked)
179         self.port_entry.set_tooltip_text("port/display")
180 
181-        hbox.pack_start(self.username_entry)
182-        hbox.pack_start(self.username_label)
183-        hbox.pack_start(self.host_entry)
184-        hbox.pack_start(self.ssh_port_entry)
185-        hbox.pack_start(gtk.Label(":"))
186-        hbox.pack_start(self.port_entry)
187+        hbox.pack_start(self.server_label, False, False)
188+        hbox.pack_start(self.username_entry, True, True)
189+        hbox.pack_start(self.username_label, False, False)
190+        hbox.pack_start(self.host_entry, True, True)
191+        hbox.pack_start(self.ssh_port_entry, False, False)
192+        hbox.pack_start(gtk.Label(":"), False, False)
193+        hbox.pack_start(self.port_entry, False, False)
194         vbox.pack_start(hbox)
195 
196+        #
197         # Password
198-        hbox = gtk.HBox(False, 0)
199-        hbox.set_spacing(20)
200+        #
201+        hbox = gtk.HBox(False, 5)
202         self.password_entry = gtk.Entry()
203         self.password_entry.set_max_length(128)
204         self.password_entry.set_width_chars(30)
205@@ -278,14 +388,13 @@
206         self.password_entry.set_visibility(False)
207         self.password_entry.connect("changed", self.password_ok)
208         self.password_entry.connect("changed", self.validate)
209-        self.password_label = gtk.Label("Password: ")
210-        hbox.pack_start(self.password_label)
211-        hbox.pack_start(self.password_entry)
212+        self.password_label = gtk.Label("Server Password:")
213+        hbox.pack_start(self.password_label, False, False)
214+        hbox.pack_start(self.password_entry, True, True)
215         vbox.pack_start(hbox)
216 
217-        #strict host key check for SSL and SSH
218-        hbox = gtk.HBox(False, 0)
219-        hbox.set_spacing(20)
220+        # strict host key check for SSL and SSH
221+        hbox = gtk.HBox(False, 5)
222         self.nostrict_host_check = gtk.CheckButton("Disable Strict Host Key Check")
223         self.nostrict_host_check.set_active(False)
224         al = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0)
225@@ -412,10 +521,11 @@
226 
227     def validate(self, *args):
228         ssh = self.mode_combo.get_active_text()=="SSH"
229+        sshtossh = self.mode_combo.get_active_text()=="SSH -> SSH"
230         errs = []
231         host = self.host_entry.get_text()
232         errs.append((self.host_entry, not bool(host), "specify the host"))
233-        if ssh:
234+        if ssh or sshtossh:
235             #validate ssh port:
236             ssh_port = self.ssh_port_entry.get_text()
237             try:
238@@ -424,8 +534,21 @@
239                 ssh_port = -1
240             errs.append((self.ssh_port_entry, ssh_port<0 or ssh_port>=2**16, "invalid SSH port number"))
241         port = self.port_entry.get_text()
242-        if ssh and not port:
243-            port = 0        #port optional with ssh
244+        if sshtossh:
245+            if self.password_scb.get_active():
246+                self.password_entry.set_sensitive(False)
247+                self.password_entry.set_text(self.sshproxy_password_entry.get_text())
248+            else:
249+                self.password_entry.set_sensitive(True)
250+            if self.username_scb.get_active():
251+                self.username_entry.set_sensitive(False)
252+                self.username_entry.set_text(self.sshproxy_username_entry.get_text())
253+            else:
254+                self.username_entry.set_sensitive(True)
255+            sshproxy_host = self.sshproxy_host_entry.get_text()
256+            errs.append((self.sshproxy_host_entry, not bool(sshproxy_host), "specify the proxy host"))
257+        if ssh or sshtossh and not port:
258+            port = 0        # port optional with ssh
259         else:
260             try:
261                 port = int(port)
262@@ -460,14 +583,25 @@
263     def mode_changed(self, *_args):
264         mode = self.mode_combo.get_active_text().lower()
265         ssh = mode=="ssh"
266-        if ssh:
267+        sshtossh = mode=="ssh -> ssh"
268+        if ssh or sshtossh:
269             self.port_entry.set_tooltip_text("Display number (optional)")
270             self.port_entry.set_text("")
271-            self.ssh_port_entry.set_text("22")
272             self.ssh_port_entry.show()
273             self.password_entry.set_tooltip_text("SSH Password")
274             self.username_entry.set_tooltip_text("SSH Username")
275+            if ssh:
276+                self.sshproxy_vbox.hide()
277+                self.password_scb.hide()
278+                self.password_entry.set_sensitive(True)
279+                self.username_entry.set_sensitive(True)
280+            if sshtossh:
281+                self.sshproxy_vbox.show()
282+                self.password_scb.show()
283         else:
284+            self.password_entry.set_sensitive(True)
285+            self.username_entry.set_sensitive(True)
286+            self.sshproxy_vbox.hide()
287             self.ssh_port_entry.hide()
288             self.ssh_port_entry.set_text("")
289             port_str = self.port_entry.get_text()
290@@ -479,10 +613,15 @@
291             if self.config.port>0:
292                 self.port_entry.set_text("%s" % self.config.port)
293         can_use_password = True
294-        if ssh:
295+        if ssh or sshtossh:
296             ssh_cmd = parse_ssh_string(self.config.ssh)[0].strip().lower()
297             is_putty = ssh_cmd.endswith("plink") or ssh_cmd.endswith("plink.exe")
298             is_paramiko = ssh_cmd=="paramiko"
299+            if is_paramiko:
300+                self.sshproxy_pkey_path_entry.set_text("paramiko automatically searches ~/.ssh/")
301+                self.sshproxy_pkey_path_entry.set_editable(False)
302+                self.sshproxy_pkey_path_entry.set_sensitive(False)
303+                self.sshproxy_pkey_path_browse.hide()
304             if is_putty or is_paramiko:
305                 can_use_password = True
306             else:
307@@ -490,12 +629,19 @@
308                 from xpra.platform.paths import get_sshpass_command
309                 sshpass = get_sshpass_command()
310                 can_use_password = bool(sshpass)
311+                self.connect_btn.set_sensitive(len(err_text)==0)
312         if can_use_password:
313             self.password_label.show()
314             self.password_entry.show()
315+            if sshtossh:
316+                self.sshproxy_password_label.show()
317+                self.sshproxy_password_entry.show()
318         else:
319             self.password_label.hide()
320             self.password_entry.hide()
321+            if sshtossh:
322+                self.sshproxy_password_label.hide()
323+                self.sshproxy_password_entry.hide()
324         self.validate()
325         if mode=="ssl" or (mode=="ssh" and not WIN32):
326             self.nostrict_host_check.show()
327@@ -546,6 +692,21 @@
328     def set_sensitive(self, s):
329         glib.idle_add(self.window.set_sensitive, s)
330 
331+    def choose_pkey_file(self, title, action, action_button, callback):
332+        file_filter = gtk.FileFilter()
333+        file_filter.set_name("All Files")
334+        file_filter.add_pattern("*")
335+        choose_file(self.window, title, action, action_button, callback, file_filter)
336+
337+    def sshproxy_pkey_path_browse_clicked(self, *args):
338+        log("sshproxy_pkey_path_browse_clicked%s", args)
339+        def do_choose(filename):
340+            #make sure the file extension is .ppk
341+            if os.path.splitext(filename)[-1]!=".ppk":
342+                filename += ".ppk"
343+            self.sshproxy_pkey_path_entry.set_text(filename)
344+        self.choose_pkey_file("Choose SSH private key", FILE_CHOOSER_ACTION_SAVE, gtk.STOCK_SAVE, do_choose)
345+
346     def connect_clicked(self, *args):
347         log("connect_clicked%s", args)
348         self.update_options_from_gui()
349@@ -591,7 +752,7 @@
350         #cooked vars used by connect_to
351         params = {"type"    : self.config.mode}
352         username = self.config.username
353-        if self.config.mode=="ssh":
354+        if self.config.mode=="ssh" or self.config.mode=="ssh -> ssh":
355             if self.config.socket_dir:
356                 params["socket_dir"] = self.config.socket_dir
357             params["remote_xpra"] = self.config.remote_xpra
358@@ -627,6 +788,29 @@
359                 params["username"] = username
360             if self.nostrict_host_check.get_active():
361                 full_ssh += ["-o", "StrictHostKeyChecking=no"]
362+            if params["type"] == "ssh -> ssh":
363+                log("2hop")
364+                params["type"] = "ssh"
365+                if is_putty:
366+                    proxyline = ssh_cmd
367+                    proxyline += " -nc %host:%port"
368+                    proxyline += " -P " + str(self.config.sshproxy_ssh_port)
369+                    proxyline += " -l " + self.config.sshproxy_username
370+                    ptext = self.config.sshproxy_password
371+                    if bool(ptext):
372+                        proxyline += " -pw " + ptext
373+                    ptext = self.config.sshproxy_pkey_path
374+                    ptext = ptext.replace("\\", "/")
375+                    if bool(ptext):
376+                        proxyline += " -i " + '"' + ptext + '"'
377+                    proxyline += " " + self.config.sshproxy_host
378+                    full_ssh += [ "-proxycmd", proxyline ]
379+                if is_paramiko:
380+                    log("setting phost/pport")
381+                    params["phost"] = self.config.sshproxy_host
382+                    params["pport"] = self.config.sshproxy_ssh_port
383+                    params["pusername"] = self.config.sshproxy_username
384+                    params["ppassword"] = self.config.sshproxy_password
385             params["host"] = host
386             params["local"] = is_local(self.config.host)
387             params["full_ssh"] = full_ssh
388@@ -784,8 +968,16 @@
389                 return int(v)
390             except ValueError:
391                 return 0
392+        self.config.sshproxy_ssh_port = pint(self.sshproxy_ssh_port_entry.get_text())
393+        self.config.sshproxy_host = self.sshproxy_host_entry.get_text()
394+        self.config.sshproxy_username = self.sshproxy_username_entry.get_text()
395+        ssh_cmd = parse_ssh_string(self.config.ssh)[0].strip().lower()
396+        if ssh_cmd != "paramiko":
397+            self.config.sshproxy_pkey_path = self.sshproxy_pkey_path_entry.get_text()
398         self.config.host = self.host_entry.get_text()
399         self.config.ssh_port = pint(self.ssh_port_entry.get_text())
400+        self.config.sshproxy_ssh_port = pint(self.sshproxy_ssh_port_entry.get_text())
401+        self.config.sshproxy_password = self.sshproxy_password_entry.get_text()
402         self.config.port = pint(self.port_entry.get_text())
403         self.config.username = self.username_entry.get_text()
404         self.config.encoding = self.get_selected_encoding() or self.config.encoding
405@@ -794,7 +986,7 @@
406             self.config.mode = "tcp"
407             if mode_enc.find("aes")>0:
408                 self.config.encryption = "AES"
409-        elif mode_enc in ("ssl", "ssh", "ws", "wss"):
410+        elif mode_enc in ("ssl", "ssh", "ws", "wss", "ssh -> ssh"):
411             self.config.mode = mode_enc
412             self.config.encryption = ""
413         self.config.password = self.password_entry.get_text()
414@@ -803,9 +995,14 @@
415     def update_gui_from_config(self):
416         #mode:
417         mode = (self.config.mode or "").lower()
418+        got_one = False
419         for i,e in enumerate(self.get_connection_modes()):
420             if e.lower()==mode:
421+                got_one = True
422                 self.mode_combo.set_active(i)
423+        if not got_one:
424+            log("Bad default for mode_combo, applying fallback default")
425+            self.mode_combo.set_active(0)
426         if self.config.encoding and self.encoding_combo:
427             index = self.encoding_combo.get_menu().encoding_to_index.get(self.config.encoding, -1)
428             log("setting encoding combo to %s / %s", self.config.encoding, index)
429@@ -815,8 +1012,14 @@
430             #then select it in the combo:
431             if index>=0:
432                 self.encoding_combo.set_history(index)
433+        self.sshproxy_username_entry.set_text(self.config.proxy_username)
434+        self.sshproxy_host_entry.set_text(self.config.sshproxy_host)
435+        ssh_cmd = parse_ssh_string(self.config.ssh)[0].strip().lower()
436+        if(ssh_cmd != "paramiko"):
437+            self.sshproxy_pkey_path_entry.set_text(self.config.sshproxy_pkey_path)
438         self.username_entry.set_text(self.config.username)
439         self.password_entry.set_text(self.config.password)
440+        self.sshproxy_password_entry.set_text(self.config.sshproxy_password)
441         self.host_entry.set_text(self.config.host)
442         def get_port(v, default_port=""):
443             try:
444@@ -827,11 +1030,12 @@
445                 pass
446             return str(default_port)
447         dport = DEFAULT_PORT
448-        if mode=="ssh":
449+        if mode=="ssh" or mode=="ssh -> ssh":
450             #not required, so don't specify one
451             dport = ""
452         self.port_entry.set_text(get_port(self.config.port, dport))
453         self.ssh_port_entry.set_text(get_port(self.config.ssh_port))
454+        self.sshproxy_ssh_port_entry.set_text(get_port(self.config.sshproxy_ssh_port))
455 
456     def close_window(self, *_args):
457         w = self.window
458@@ -986,7 +1190,6 @@
459             #file says we should connect,
460             #do that only (not showing UI unless something goes wrong):
461             glib.idle_add(app.do_connect)
462-        if not has_file:
463             app.reset_errors()
464         if not app.config.autoconnect or app.config.debug:
465             if OSX and not has_file:
466Index: xpra/net/ssh.py
467===================================================================
468--- xpra/net/ssh.py     (revision 21058)
469+++ xpra/net/ssh.py     (working copy)
470@@ -249,16 +251,43 @@
471                     from paramiko.ssh_exception import ProxyCommandFailure
472                     bytestreams.CLOSED_EXCEPTIONS = tuple(list(bytestreams.CLOSED_EXCEPTIONS)+[ProxyCommandFailure])
473                     return conn
474+        from xpra.scripts.main import socket_connect
475+        from paramiko.transport import Transport
476+        from paramiko import SSHException
477+        if "phost" in display_desc:
478+            phost = display_desc["phost"]
479+            pport = display_desc.get("pport", 22)
480+            pusername = display_desc.get("pusername", username)
481+            ppassword = display_desc.get("ppassword", password)
482+            sock = socket_connect(dtype, phost, pport, ipv6)
483+            middle_transport = Transport(sock)
484+            middle_transport.use_compression(False)
485+            try:
486+                middle_transport.start_client()
487+            except SSHException as e:
488+                log("start_client()", exc_info=True)
489+                raise InitException("SSH negotiation failed: %s" % e)
490+            chan_to_middle = do_ssh_paramiko_connect_to(middle_transport, phost, pusername, ppassword, dest_host=host, dest_port=port)
491+            transport = Transport(chan_to_middle)
492+            transport.use_compression(False)
493+            try:
494+                transport.start_client()
495+            except SSHException as e:
496+                log("start_client()", exc_info=True)
497+                raise InitException("SSH negotiation failed: %s" % e)
498+            chan = do_ssh_paramiko_connect_to(transport, host, username, password, proxy_command, remote_xpra, socket_dir, display_as_args)
499+            peername = (host, port)
500+            conn = SSHProxyCommandConnection(chan, peername, target, socket_info)
501+            conn.timeout = SOCKET_TIMEOUT
502+            conn.start_stderr_reader()
503+            return conn
504 
505         #plain TCP connection to the server,
506         #we open it then give the socket to paramiko:
507-        from xpra.scripts.main import socket_connect
508         sock = socket_connect(dtype, host, port, ipv6)
509         sockname = sock.getsockname()
510         peername = sock.getpeername()
511         log("paramiko socket_connect: sockname=%s, peername=%s", sockname, peername)
512-        from paramiko.transport import Transport
513-        from paramiko import SSHException
514         transport = Transport(sock)
515         transport.use_compression(False)
516         try:
517@@ -281,9 +310,11 @@
518     def __init__(self):
519         nomodule_context.__init__(self, "gssapi")
520 
521-
522+# (1) If the arguments after "proxy_command" are "None", then we're opening a port-forward
523+# (2) If "parachan" is set, that means we're using a port-forward
524 def do_ssh_paramiko_connect_to(transport, host, username, password,
525-                               xpra_proxy_command, remote_xpra, socket_dir, display_as_args):
526+                               xpra_proxy_command=None, remote_xpra=None, socket_dir=None, display_as_args=None,
527+                               dest_host=None, dest_port=None):
528     from paramiko import SSHException, RSAKey, PasswordRequiredException
529     from paramiko.agent import Agent
530     from paramiko.hostkeys import HostKeys
531@@ -452,6 +483,27 @@
532             log("auth_password(..)", exc_info=True)
533             log.info("SSH password authentication failed: %s", e)
534 
535+    def auth_interactive():
536+        log("trying interactive authentication")
537+        class iauthhandler:
538+            def __init__(self):
539+                self.authcount = 0
540+            def handlestuff(self, title, instructions, prompt_list):
541+                p = [ ]
542+                for pent in prompt_list:
543+                    if self.authcount == 0:
544+                        p.append(password)
545+                    else:
546+                        p.append(input_pass(pent[0]))
547+                    self.authcount += 1
548+                return p
549+        try:
550+            myiauthhandler = iauthhandler()
551+            transport.auth_interactive(username, myiauthhandler.handlestuff, '')
552+        except SSHException as e:
553+            log("auth_password(..)", exc_info=True)
554+            log.info("SSH password authentication failed: %s", e)
555+
556     banner = transport.get_banner()
557     if banner:
558         log.info("SSH server banner:")
559@@ -459,18 +511,24 @@
560             log.info(" %s", x)
561 
562     log("starting authentication")
563+    # per the RFC we probably should do none first always and read off the supported
564+    # methods, however, the current code seems to work fine with OpenSSH
565     if not transport.is_authenticated() and NONE_AUTH:
566         auth_none()
567 
568+    # Some people do two-factor using KEY_AUTH to kick things off, so this happens first
569+    if not transport.is_authenticated() and KEY_AUTH:
570+        auth_publickey()
571+
572     if not transport.is_authenticated() and PASSWORD_AUTH and password:
573+        auth_interactive()
574+
575+    if not transport.is_authenticated() and PASSWORD_AUTH and password:
576         auth_password()
577 
578     if not transport.is_authenticated() and AGENT_AUTH:
579         auth_agent()
580 
581-    if not transport.is_authenticated() and KEY_AUTH:
582-        auth_publickey()
583-
584     if not transport.is_authenticated() and PASSWORD_AUTH and not password:
585         for _ in range(1+PASSWORD_RETRY):
586             password = input_pass("please enter the SSH password for %s@%s" % (username, host))
587@@ -482,8 +540,13 @@
588 
589     if not transport.is_authenticated():
590         transport.close()
591-        raise InitException("SSH Authentication failed")
592+        # added the "host" so the user can figure out errors in ssh -> ssh mode
593+        raise InitException("SSH Authentication on " + host + " failed")
594 
595+    if(remote_xpra == None):
596+        log("Opening proxy channel")
597+        return transport.open_channel("direct-tcpip", (dest_host, dest_port), ('localhost', 0))
598+
599     assert len(remote_xpra)>0
600     log("will try to run xpra from: %s", remote_xpra)
601     for xpra_cmd in remote_xpra: