Skip to content

Commit

Permalink
#1646: add GUI dialog for SSH password and SSH key passphrase input
Browse files Browse the repository at this point in the history
git-svn-id: https://xpra.org/svn/Xpra/trunk@19943 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jul 21, 2018
1 parent 99e7eb7 commit b70106c
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 22 deletions.
6 changes: 3 additions & 3 deletions src/xpra/client/gtk_base/confirm_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import sys
import signal

from xpra.platform.gui import init as gui_init
gui_init()

from xpra.gtk_common.gobject_compat import import_gtk, import_pango, import_glib

Expand Down Expand Up @@ -72,7 +70,6 @@ def al(label, font="sans 14", xalign=0):
for label, code in buttons:
b = self.btn(label, "", code)
hbox.pack_start(b)
#btn("Close", "", self.close, "quit.png")

add_close_accel(self.window, self.quit)
vbox.show_all()
Expand Down Expand Up @@ -137,6 +134,9 @@ def show_confirm_dialog(argv):
from xpra.os_util import SIGNAMES
from xpra.platform.gui import ready as gui_ready
from xpra.gtk_common.quit import gtk_main_quit_on_fatal_exceptions_enable
from xpra.platform.gui import init as gui_init

gui_init()
gtk_main_quit_on_fatal_exceptions_enable()

log("show_confirm_dialog(%s)", argv)
Expand Down
177 changes: 177 additions & 0 deletions src/xpra/client/gtk_base/pass_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env python
# This file is part of Xpra.
# Copyright (C) 2018 Antoine Martin <[email protected]>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.


import os.path
import sys
import signal

from xpra.gtk_common.gobject_compat import import_gtk, import_pango, import_glib

gtk = import_gtk()
glib = import_glib()
pango = import_pango()


from xpra.os_util import get_util_logger
from xpra.gtk_common.gtk_util import gtk_main, add_close_accel, pixbuf_new_from_file, window_defaults, \
WIN_POS_CENTER, WINDOW_TOPLEVEL, is_gtk3
from xpra.platform.paths import get_icon_dir
log = get_util_logger()


class PasswordInputDialogWindow(object):

def __init__(self, title="Title", prompt="", info=[], icon="", buttons=[]):
if is_gtk3():
self.window = gtk.Window(type=WINDOW_TOPLEVEL)
else:
self.window = gtk.Window(WINDOW_TOPLEVEL)
window_defaults(self.window)
self.window.set_position(WIN_POS_CENTER)
self.window.connect("destroy", self.quit)
self.window.set_default_size(400, 150)
self.window.set_title(title)
self.window.set_modal(True)

if icon:
icon_pixbuf = self.get_icon(icon)
if icon_pixbuf:
self.window.set_icon(icon_pixbuf)

vbox = gtk.VBox(False, 0)
vbox.set_spacing(10)

def al(label, font="sans 14", xalign=0):
l = gtk.Label(label)
l.modify_font(pango.FontDescription(font))
al = gtk.Alignment(xalign=xalign, yalign=0.5, xscale=0.0, yscale=0)
al.add(l)
vbox.add(al)

#window title is visible so this would be redundant:
#al(title, "sans 18", 0.5)
al(prompt, "sans 14")
self.password_input = gtk.Entry()
self.password_input.set_max_length(255)
self.password_input.set_width_chars(32)
self.password_input.connect('activate', self.activate)
self.password_input.set_visibility(False)
vbox.add(self.password_input)

# Buttons:
self.exit_code = 0
hbox = gtk.HBox(False, 0)
al = gtk.Alignment(xalign=1, yalign=0.5, xscale=0, yscale=0)
al.add(hbox)
vbox.pack_start(al)
for label, code, isdefault in [("Confirm", 0, True), ("Cancel", 1, False)]:
b = self.btn(label, code, isdefault)
hbox.pack_start(b)

add_close_accel(self.window, self.quit)
vbox.show_all()
self.window.add(vbox)

def btn(self, label, code, isdefault=False):
btn = gtk.Button(label)
settings = btn.get_settings()
settings.set_property('gtk-button-images', True)
def btn_clicked(*_args):
log("%s button clicked, returning %s", label, code)
self.exit_code = code
self.quit()
btn.set_size_request(100, 48)
btn.connect("clicked", btn_clicked)
btn.set_can_focus(True)
btn.set_can_default(isdefault)
if isdefault:
self.window.set_default(btn)
self.window.set_focus(btn)
return btn


def show(self):
log("show()")
self.window.show_all()
glib.idle_add(self.window.present)

def destroy(self, *args):
log("destroy%s", args)
if self.window:
self.window.destroy()
self.window = None

def run(self):
log("run()")
gtk_main()
log("run() gtk_main done")
return self.exit_code

def quit(self, *args):
log("quit%s", args)
self.destroy()
gtk.main_quit()


def activate(self, *args):
log("activate%s", args)
sys.stdout.write(self.password_input.get_text())
sys.stdout.flush()
self.quit()

def get_icon(self, icon_name):
icon_filename = os.path.join(get_icon_dir(), icon_name)
if os.path.exists(icon_filename):
return pixbuf_new_from_file(icon_filename)
return None


def show_pass_dialog(argv):
from xpra.os_util import SIGNAMES
from xpra.platform.gui import ready as gui_ready
from xpra.gtk_common.quit import gtk_main_quit_on_fatal_exceptions_enable
from xpra.platform.gui import init as gui_init

gui_init()
gtk_main_quit_on_fatal_exceptions_enable()

log("show_pass_dialog(%s)", argv)
def arg(n):
if len(argv)<=n:
return ""
return argv[n].replace("\\n\\r", "\\n").replace("\\n", "\n")
title = arg(0) or "Enter Password"
prompt = arg(1)
icon = arg(2)
app = PasswordInputDialogWindow(title, prompt, icon)
def app_signal(signum, _frame):
print("")
log.info("got signal %s", SIGNAMES.get(signum, signum))
app.quit()
signal.signal(signal.SIGINT, app_signal)
signal.signal(signal.SIGTERM, app_signal)
gui_ready()
app.show()
return app.run()


def main():
from xpra.platform import program_context
with program_context("Password-Input-Dialog", "Password Input Dialog"):
if "-v" in sys.argv:
from xpra.log import enable_debug_for
enable_debug_for("util")

try:
return show_pass_dialog(sys.argv[1:])
except KeyboardInterrupt:
return 1


if __name__ == "__main__":
v = main()
sys.exit(v)
60 changes: 41 additions & 19 deletions src/xpra/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def configure_logging(options, mode):
#the logging system every time, and just undo things here..
from xpra.log import setloghandler, enable_color, enable_format, LOG_FORMAT, NOPREFIX_FORMAT
setloghandler(logging.StreamHandler(to))
if mode in ("start", "start-desktop", "upgrade", "attach", "shadow", "proxy", "_sound_record", "_sound_play", "stop", "print", "showconfig", "request-start", "request-start-desktop", "request-shadow", "_dialog"):
if mode in ("start", "start-desktop", "upgrade", "attach", "shadow", "proxy", "_sound_record", "_sound_play", "stop", "print", "showconfig", "request-start", "request-start-desktop", "request-shadow", "_dialog", "_pass"):
if "help" in options.speaker_codec or "help" in options.microphone_codec:
info = show_sound_codec_help(mode!="attach", options.speaker_codec, options.microphone_codec)
raise InitInfo("\n".join(info))
Expand Down Expand Up @@ -400,8 +400,10 @@ def attach_client():
error_cb("no sound support!")
from xpra.sound.wrapper import run_sound
return run_sound(mode, error_cb, options, args)
elif mode in ("_dialog"):
elif mode=="_dialog":
return run_dialog(args)
elif mode=="_pass":
return run_pass(args)
elif mode=="opengl":
return run_glcheck(options)
elif mode == "initenv":
Expand Down Expand Up @@ -1019,28 +1021,39 @@ def keymd5(k):
f = f[2:]
return s

def dialog_confirm(title, prompt, qinfo="", icon="", buttons=[("OK", 1)]):
cmd = get_xpra_command()+["_dialog", nonl(title), nonl(prompt), nonl("\\n".join(qinfo)), icon]
for label, code in buttons:
cmd.append(nonl(label))
cmd.append(str(code))
def get_ssh_logger():
from xpra.log import Logger
return Logger("ssh")

def exec_dialog_subprocess(cmd):
import subprocess
env = os.environ.copy()
log = get_util_logger()
log = get_ssh_logger()
try:
log("dialog_confirm command: %s", cmd)
proc = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, env=env)
log("exec_dialog_subprocess(%s)", cmd)
proc = subprocess.Popen(cmd, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
stdout, stderr = proc.communicate()
log("exec_dialog_subprocess(%s)", cmd)
if stderr:
log.warn("Warning: dialog process error output:")
for x in stderr.splitlines():
log.warn(" %s", x)
return proc.returncode, stdout
except Exception as e:
log("dialog_confirm(..)", exc_info=True)
log("exec_dialog_subprocess(..)", exc_info=True)
log.error("Error: failed to execute the dialog subcommand")
log.error(" %s", e)
return -1, ""
return -1, None

def dialog_pass(title="Password Input", prompt="enter password", icon=""):
cmd = get_xpra_command()+["_pass", nonl(title), nonl(prompt), icon]
return exec_dialog_subprocess(cmd)

def dialog_confirm(title, prompt, qinfo="", icon="", buttons=[("OK", 1)]):
cmd = get_xpra_command()+["_dialog", nonl(title), nonl(prompt), nonl("\\n".join(qinfo)), icon]
for label, code in buttons:
cmd.append(nonl(label))
cmd.append(str(code))
return exec_dialog_subprocess(cmd)

def confirm_key(info=[]):
SKIP_UI = envbool("XPRA_SKIP_UI", False)
Expand All @@ -1052,12 +1065,12 @@ def confirm_key(info=[]):
icon = get_icon_filename("authentication", "png")
prompt = "Are you sure you want to continue connecting?"
code, out = dialog_confirm("Confirm Key", prompt, info, icon, buttons=[("yes", 200), ("NO", 201)])
log = get_util_logger()
log = get_ssh_logger()
log.debug("dialog output: '%s', return code=%s", nonl(out), code)
r = code==200
log.info("host key %sconfirmed", ["not ", ""][r])
return r
prompt = "Are you sure you want to continue connecting (yes/NO)?"
prompt = "Are you sure you want to continue connecting (yes/NO)? "
sys.stderr.write(os.linesep.join(info)+os.linesep+prompt)
v = sys.stdin.readline().rstrip(os.linesep)
return v and v.lower() in ("y", "yes")
Expand All @@ -1066,18 +1079,23 @@ def input_pass(prompt):
SKIP_UI = envbool("XPRA_SKIP_UI", False)
if SKIP_UI:
return None
from xpra.platform.paths import get_icon_filename
from xpra.os_util import use_tty
if not use_tty():
#TODO!
return None
icon = get_icon_filename("authentication", "png")
code, out = dialog_pass("Password Input", prompt, icon)
log = get_ssh_logger()
log.debug("pass dialog output return code=%s", code)
if code!=0:
return None
return out
from getpass import getpass
return getpass(prompt)

def paramiko_connect_to(display_desc, opts, debug_cb, ssh_connect_failed):
#TODO: parse full_ssh for attributes like timeout, key_filename
import time
from xpra.log import Logger
log = Logger("ssh")
log = get_ssh_logger()
display_name = display_desc["display_name"]
dtype = display_desc["type"]
host = display_desc["host"]
Expand Down Expand Up @@ -1637,6 +1655,10 @@ def run_dialog(extra_args):
from xpra.client.gtk_base.confirm_dialog import show_confirm_dialog
return show_confirm_dialog(extra_args)

def run_pass(extra_args):
from xpra.client.gtk_base.pass_dialog import show_pass_dialog
return show_pass_dialog(extra_args)


def get_sockpath(display_desc, error_cb):
#if the path was specified, use that:
Expand Down

0 comments on commit b70106c

Please sign in to comment.