Skip to content

Commit

Permalink
#1646: add GUI dialog for confirming keys via hidden xpra subcommand
Browse files Browse the repository at this point in the history
git-svn-id: https://xpra.org/svn/Xpra/trunk@19941 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jul 21, 2018
1 parent 15bf2df commit c78385e
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 31 deletions.
190 changes: 190 additions & 0 deletions src/xpra/client/gtk_base/confirm_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#!/usr/bin/env python
# This file is part of Xpra.
# Copyright (C) 2018 Antoine Martin <antoine@devloop.org.uk>
# 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.platform.gui import init as gui_init
gui_init()

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, scaled_image, pixbuf_new_from_file, window_defaults, color_parse, \
WIN_POS_CENTER, WINDOW_POPUP, STATE_NORMAL, is_gtk3
from xpra.platform.paths import get_icon_dir
log = get_util_logger()


class ConfirmDialogWindow(object):

def __init__(self, title="Title", prompt="", info=[], icon="", buttons=[]):
if is_gtk3():
self.window = gtk.Window(type=WINDOW_POPUP)
else:
self.window = gtk.Window(WINDOW_POPUP)
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))
if label.startswith("WARNING"):
red = color_parse("red")
l.modify_fg(STATE_NORMAL, red)
al = gtk.Alignment(xalign=xalign, yalign=0.5, xscale=0.0, yscale=0)
al.add(l)
vbox.add(al)

al(title, "sans 18", 0.5)
al(info, "sans 14")
al(prompt, "sans 14")

# Buttons:
self.exit_code = 0
if buttons:
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 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()
self.window.add(vbox)

def btn(self, label, tooltip, code, icon_name=None):
btn = gtk.Button(label)
settings = btn.get_settings()
settings.set_property('gtk-button-images', True)
if tooltip:
btn.set_tooltip_text(tooltip)
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)
isdefault = label[:1].upper()!=label[:1]
btn.set_can_default(isdefault)
if isdefault:
self.window.set_default(btn)
self.window.set_focus(btn)
if icon_name:
icon = self.get_icon(icon_name)
if icon:
btn.set_image(scaled_image(icon, 24))
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 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_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
gtk_main_quit_on_fatal_exceptions_enable()

log("show_confirm_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 "Confirm Key"
prompt = arg(1)
info = arg(2)
icon = arg(3)
buttons = []
n = 4
while len(argv)>(n+1):
label = arg(n)
try:
code = int(arg(n+1))
except ValueError as e:
log.error("Error: confirm dialog cannot parse code '%s': %s", arg(n+1), e)
return 1
buttons.append((label, code))
n += 2
app = ConfirmDialogWindow(title, prompt, info, icon, buttons)
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("Confirm-Dialog", "Confirm Dialog"):
#logging init:
if "-v" in sys.argv:
from xpra.log import enable_debug_for
enable_debug_for("util")

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


if __name__ == "__main__":
v = main()
sys.exit(v)
105 changes: 74 additions & 31 deletions src/xpra/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import traceback

from xpra.platform.dotxpra import DotXpra
from xpra.util import csv, envbool, envint, engs, DEFAULT_PORT
from xpra.util import csv, envbool, envint, engs, nonl, DEFAULT_PORT
from xpra.exit_codes import EXIT_SSL_FAILURE, EXIT_SSH_FAILURE, EXIT_SSH_KEY_FAILURE, EXIT_STR
from xpra.os_util import get_util_logger, getuid, getgid, monotonic_time, setsid, bytestostr, osexpand, WIN32, OSX, POSIX
from xpra.scripts.parsing import info, warn, error, \
Expand All @@ -28,9 +28,9 @@
from xpra.scripts.config import OPTION_TYPES, CLIENT_OPTIONS, NON_COMMAND_LINE_OPTIONS, CLIENT_ONLY_OPTIONS, START_COMMAND_OPTIONS, BIND_OPTIONS, PROXY_START_OVERRIDABLE_OPTIONS, OPTIONS_ADDED_SINCE_V1, \
InitException, InitInfo, InitExit, \
fixup_options, dict_to_validated_config, \
make_defaults_struct, parse_bool, has_sound_support, name_to_field,\
TRUE_OPTIONS
make_defaults_struct, parse_bool, has_sound_support, name_to_field
from xpra.net.common import ConnectionClosedException
from xpra.platform.paths import get_xpra_command
assert info and warn and error, "used by modules importing those from here"

NO_ROOT_WARNING = envbool("XPRA_NO_ROOT_WARNING", False)
Expand Down 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"):
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 "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,6 +400,8 @@ 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"):
return run_dialog(args)
elif mode=="opengl":
return run_glcheck(options)
elif mode == "initenv":
Expand Down Expand Up @@ -1017,15 +1019,47 @@ def keymd5(k):
f = f[2:]
return s

def confirm_key():
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))
import subprocess
env = os.environ.copy()
log = get_util_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)
stdout, stderr = proc.communicate()
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.error("Error: failed to execute the dialog subcommand")
log.error(" %s", e)
return -1, ""

def confirm_key(info=[]):
SKIP_UI = envbool("XPRA_SKIP_UI", False)
if SKIP_UI:
return False
from xpra.platform.paths import get_icon_filename
from xpra.os_util import use_tty
if not use_tty():
#TODO!
return False
v = sys.stdin.readline().rstrip("\n\r")
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.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)?"
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")

def input_pass(prompt):
Expand Down Expand Up @@ -1093,39 +1127,42 @@ def keyname():
else:
if known_host_key:
log.warn("Warning: SSH server key mismatch")
sys.stderr.write(
"""@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @\n
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!\n
Someone could be eavesdropping on you right now (man-in-the-middle attack)!\n
It is also possible that a host key has just been changed.\n
The fingerprint for the %s key sent by the remote host is\n
%s\n
""" % (keyname(), keymd5(host_key)))
qinfo = [
"WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!",
"IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!",
"Someone could be eavesdropping on you right now (man-in-the-middle attack)!",
"It is also possible that a host key has just been changed.",
"The fingerprint for the %s key sent by the remote host is" % keyname(),
keymd5(host_key),
]
if envbool("XPRA_SSH_VERIFY_STRICT", False):
log.warn("Please contact your system administrator.")
log.warn("Add correct host key in %s to get rid of this message.")
log.warn("Offending %s key in %s", keyname(), host_keys_filename)
log.warn("ECDSA host key for %s has changed and you have requested strict checking.", keyname())
log.warn("Host key verification failed.")
#TODO: show alert with no option to accept key
qinfo += [
"Please contact your system administrator.",
"Add correct host key in %s to get rid of this message.",
"Offending %s key in %s" % (keyname(), host_keys_filename),
"ECDSA host key for %s has changed and you have requested strict checking." % keyname(),
]
sys.stderr.write(os.linesep.join(qinfo))
transport.close()
raise InitExit(EXIT_SSH_KEY_FAILURE, "SSH Host key has changed")
sys.stderr.write("Are you sure you want to continue connecting (yes/no)?")
if not confirm_key():
if not confirm_key(qinfo):
transport.close()
raise InitExit(EXIT_SSH_KEY_FAILURE, "SSH Host key has changed")

else:
assert (not keys) or (host_key.get_name() not in keys)
if not keys:
log.warn("Warning: unknown host")
log.warn("Warning: unknown SSH host")
else:
log.warn("Warning: unknown %s host key", keyname())
sys.stderr.write("The authenticity of host '%s' can't be established.\n" % (host,))
sys.stderr.write("%s key fingerprint is %s\n" % (keyname(), keymd5(host_key)))
sys.stderr.write("Are you sure you want to continue connecting (yes/no)?")
if not confirm_key():
log.warn("Warning: unknown %s SSH host key", keyname())
qinfo = [
"The authenticity of host '%s' can't be established." % (host,),
"%s key fingerprint is" % keyname(),
keymd5(host_key),
]
if not confirm_key(qinfo):
transport.close()
raise InitExit(EXIT_SSH_KEY_FAILURE, "Unknown SSH host '%s'" % host)

Expand Down Expand Up @@ -1596,6 +1633,11 @@ def do_wrap_socket(tcp_socket):
return do_wrap_socket


def run_dialog(extra_args):
from xpra.client.gtk_base.confirm_dialog import show_confirm_dialog
return show_confirm_dialog(extra_args)


def get_sockpath(display_desc, error_cb):
#if the path was specified, use that:
sockpath = display_desc.get("socket_path")
Expand Down Expand Up @@ -1780,10 +1822,11 @@ def impcheck(*modules):
for mod in modules:
try:
__import__("xpra.%s" % mod, {}, {}, [])
except ImportError as e:
except ImportError:
if mod not in impwarned:
impwarned.append(mod)
log = get_util_logger()
log("impcheck%s", modules, exc_info=True)
log.warn("Warning: missing %s module", mod)
return False
return True
Expand Down

0 comments on commit c78385e

Please sign in to comment.