def proxy_got_contents(self, request_id, selection, target, dtype, dformat, data): def no_contents(): self.send("clipboard-contents-none", request_id, selection) dtype = bytestostr(dtype) if is_debug_enabled("clipboard"): log("proxy_got_contents(%s, %s, %s, %s, %s, %s:%s) data=0x%s..", request_id, selection, target, dtype, dformat, type(data), len(data or ""), hexstr((data or "")[:200])) if dtype is None or data is None or (dformat==0 and not data): no_contents() return truncated = 0 if self.max_clipboard_send_size > 0: log("perform clipboard limit checking - datasize - %d, %d", len(data), self.max_clipboard_send_size) max_send_datalen = self.max_clipboard_send_size * 8 // get_format_size(dformat) if len(data) > max_send_datalen: truncated = len(data) - max_send_datalen data = data[:max_send_datalen] munged = self._munge_raw_selection_to_wire(target, dtype, dformat, data) if is_debug_enabled("clipboard"): log("clipboard raw -> wire: %r -> %r", (dtype, dformat, ellipsizer(data)), ellipsizer(munged)) wire_encoding, wire_data = munged if wire_encoding is None: no_contents() return wire_data = self._may_compress(dtype, dformat, wire_data) if wire_data is not None: packet = ["clipboard-contents", request_id, selection, dtype, dformat, wire_encoding, wire_data] if self.clipboard_contents_slice_fix: #sending the extra argument requires the fix packet.append(truncated) self.send(*packet)
def parse_server_capabilities(self, c: typedict) -> bool: if self.remote_logging.lower( ) in ("send", "both", "yes", "true", "on") and ( #'remote-logging.receive' was only added in v4.1 so check both: c.boolget("remote-logging") or c.boolget("remote-logging.receive")): #check for debug: from xpra.log import is_debug_enabled conflict = tuple(v for v in ("network", "crypto", "udp", "websocket") if is_debug_enabled(v)) if conflict: log.warn("Warning: cannot enable remote logging") log.warn(" because debug logging is enabled for: %s", csv(conflict)) return True log.info("enabled remote logging") if not self.log_both: log.info(" see server log file for further output") self.local_logging = set_global_logging_handler( self.remote_logging_handler) elif self.remote_logging.lower() == "receive": self.request_server_log = c.boolget("remote-logging.send") if not self.request_server_log: log.warn("Warning: cannot receive log output from the server") log.warn( " the feature is not enabled or not supported by the server" ) else: self.after_handshake(self.start_receiving_logging) #pylint: disable=no-member return True
def test_remotelogging(self): from xpra.log import Logger, is_debug_enabled for x in ("network", "crypto", "udp"): if is_debug_enabled(x): #remote logging will be disabled, #so we have to skip this test return opts = AdHocStruct() opts.remote_logging = "yes" self._test_mixin_class(RemoteLogging, opts, { "remote-logging" : True, }) assert len(self.packets)==0 log = Logger("util") message = b"hello" log.info(message) assert len(self.packets)==1 packet = self.packets[0] assert packet[0]=="logging", "expected logging packet but got '%s'" % (packet[0],) assert packet[1]==20, "expected INFO level (20) but got %s" % (packet[1],) assert packet[2].data==message, "expected message '%s' but got '%s'" % (message, packet[2].data) #after cleanup, log messages should not be intercepted: self.packets = [] self.mixin.cleanup() log.info("foo") assert len(self.packets)==0
def handle(self, packet) -> bool: digest = bytestostr(packet[3]) if not digest.startswith("u2f:"): log("%s is not a u2f challenge", digest) return False try: from pyu2f import model #@UnresolvedImport from pyu2f.u2f import GetLocalU2FInterface #@UnresolvedImport except ImportError as e: log.warn("Warning: cannot use u2f authentication handler") log.warn(" %s", e) return False if not is_debug_enabled("auth"): logging.getLogger("pyu2f.hardware").setLevel(logging.INFO) logging.getLogger("pyu2f.hidtransport").setLevel(logging.INFO) dev = GetLocalU2FInterface() APP_ID = os.environ.get("XPRA_U2F_APP_ID", "Xpra") key_handle = self.get_key_handle() if not key_handle: return False key = model.RegisteredKey(key_handle) #use server salt as challenge directly challenge = packet[1] log.info("activate your U2F device for authentication") response = dev.Authenticate(APP_ID, challenge, [key]) sig = response.signature_data client_data = response.client_data log("process_challenge_u2f client data=%s, signature=%s", client_data, binascii.hexlify(sig)) self.client.do_send_challenge_reply(bytes(sig), client_data.origin) return True
def check(self, password): log("check(%s)", obsc(password)) def emsg(e): try: log.warn(" LDAP Error: %s", e.message["desc"]) if "info" in e.message: log.warn(" %s", e.message["info"]) except: #python3: no way to get to the message dict? log.warn(" %s", e) try: import ldap except ImportError as e: log("check(..)", exc_info=True) log.warn("Warning: cannot use ldap authentication:") log.warn(" %s", e) return False try: assert self.username and password if self.tls: protocol = "ldaps" else: protocol = "ldap" server = "%s://%s:%i" % (protocol, self.host, self.port) conn = ldap.initialize(server, trace_level=LDAP_TRACE_LEVEL or is_debug_enabled("auth")) conn.protocol_version = LDAP_PROTOCOL_VERSION conn.set_option(ldap.OPT_REFERRALS, LDAP_REFERRALS) if self.cacert: conn.set_option(ldap.OPT_X_TLS_CACERTFILE, self.cacert) log("ldap.open(%s)=%s", server, conn) try: domain = socket.getfqdn().split(".", 1)[1] except: domain = "localdomain" user = self.username_format.replace("%username", self.username).replace("%domain", domain) log("user=%s", user) try: #password should be the result of a digest function, #ie: xor will return bytes.. p = bytestostr(password) password = p.encode(self.encoding) log("ldap encoded password as %s", self.encoding) except: pass v = conn.simple_bind_s(user, password) log("simple_bind_s(%s, %s)=%s", user, obsc(password), v) return True except ldap.INVALID_CREDENTIALS: log("check(..)", exc_info=True) return False except ldap.SERVER_DOWN as e: log("check(..)", exc_info=True) log.warn("Warning: LDAP %sserver at %s:%i is unreachable", ["", "TLS "][self.tls], self.host, self.port) emsg(e) except ldap.LDAPError as e: log("check(..)", exc_info=True) log.warn("Error: ldap authentication failed:") emsg(e) return False
def check(self, password): log("check(%s)", obsc(password)) try: from ldap3 import Server, Connection, Tls, ALL, SIMPLE, SASL, NTLM #@UnresolvedImport except ImportError as e: log("check(..)", exc_info=True) log.warn("Warning: cannot use ldap3 authentication:") log.warn(" %s", e) return False try: MECHANISM = { "SIMPLE": SIMPLE, "SASL": SASL, "NTLM": NTLM, } authentication = MECHANISM[self.authentication] tls = None if self.tls: tls = Tls(validate=self.tls_validate, version=self.tls_version, ca_certs_file=self.cacert) log("TLS=%s", tls) server = Server(self.host, port=self.port, tls=tls, use_ssl=self.tls, get_info=ALL) log("ldap3 Server(%s)=%s", (self.host, self.port, self.tls), server) conn = Connection(server, user=self.username, password=password, authentication=authentication, receive_timeout=10) log("ldap3 Connection(%s, %s, %s)=%s", server, self.username, self.authentication, conn) if self.tls: conn.start_tls() r = conn.bind() log("ldap3 %s.bind()=%s", conn, r) if not r: return False if is_debug_enabled("auth"): log("ldap3 server info:") for l in server.info.splitlines(): log(" %s", l) log("ldap3 who_am_i()=%s", conn.extend.standard.who_am_i()) return True except Exception as e: log("ldap3 check(..)", exc_info=True) log.error("Error: ldap3 authentication failed:") log.error(" %s", e) return False
def process_challenge_u2f(self, packet): digest = packet[3] if not digest.startswith(b"u2f:"): authlog("%s is not a u2f challenge", digest) return False import binascii import logging if not is_debug_enabled("auth"): logging.getLogger("pyu2f.hardware").setLevel(logging.INFO) logging.getLogger("pyu2f.hidtransport").setLevel(logging.INFO) from pyu2f import model from pyu2f.u2f import GetLocalU2FInterface dev = GetLocalU2FInterface() APP_ID = os.environ.get("XPRA_U2F_APP_ID", "Xpra") key_handle_str = os.environ.get("XPRA_U2F_KEY_HANDLE") authlog("process_challenge_u2f XPRA_U2F_KEY_HANDLE=%s", key_handle_str) if not key_handle_str: #try to load the key handle from the user conf dir(s): from xpra.platform.paths import get_user_conf_dirs info = self._protocol.get_info(False) key_handle_filenames = [] for hostinfo in ("-%s" % info.get("host", ""), ""): key_handle_filenames += [ os.path.join(d, "u2f-keyhandle%s.hex" % hostinfo) for d in get_user_conf_dirs() ] for filename in key_handle_filenames: p = osexpand(filename) key_handle_str = load_binary_file(p) authlog("key_handle_str(%s)=%s", p, key_handle_str) if key_handle_str: key_handle_str = key_handle_str.rstrip(b" \n\r") break if not key_handle_str: authlog.warn("Warning: no U2F key handle found") return False authlog("process_challenge_u2f key_handle=%s", key_handle_str) key_handle = binascii.unhexlify(key_handle_str) key = model.RegisteredKey(key_handle) #use server salt as challenge directly challenge = packet[1] authlog.info("activate your U2F device for authentication") response = dev.Authenticate(APP_ID, challenge, [key]) sig = response.signature_data client_data = response.client_data authlog("process_challenge_u2f client data=%s, signature=%s", client_data, binascii.hexlify(sig)) self.do_send_challenge_reply(bytes(sig), client_data.origin) return True
def parse_server_capabilities(self): c = self.server_capabilities if self.client_supports_remote_logging and c.boolget("remote-logging"): #check for debug: from xpra.log import is_debug_enabled for x in ("network", "crypto", "udp"): if is_debug_enabled(x): log.warn("Warning: cannot enable remote logging") log.warn(" because '%s' debug logging is enabled", x) return True log.info("enabled remote logging") if not self.log_both: log.info(" see server log file for further output") self.local_logging = set_global_logging_handler(self.remote_logging_handler) return True
def parse_server_capabilities(self, c: typedict) -> bool: if self.client_supports_remote_logging and c.boolget("remote-logging"): #check for debug: from xpra.log import is_debug_enabled conflict = tuple(v for v in ("network", "crypto", "udp", "websocket") if is_debug_enabled(v)) if conflict: log.warn("Warning: cannot enable remote logging") log.warn(" because debug logging is enabled for: %s", csv(conflict)) return True log.info("enabled remote logging") if not self.log_both: log.info(" see server log file for further output") self.local_logging = set_global_logging_handler( self.remote_logging_handler) return True
def test_remotelogging(self): from xpra.log import is_debug_enabled for x in ("network", "crypto", "udp"): if is_debug_enabled(x): #remote logging will be disabled, #so we have to skip this test return x = RemoteLogging() opts = AdHocStruct() opts.remote_logging = "yes" x.init(opts) assert x.get_caps() is not None x.server_capabilities = typedict({ "remote-logging": True, }) x.parse_server_capabilities() packets = [] def send(*args): packets.append(args) x.send = send from xpra.log import Logger log = Logger("util") message = b"hello" log.info(message) assert len(packets) == 1 packet = packets[0] assert packet[ 0] == "logging", "expected logging packet but got '%s'" % ( packet[0], ) assert packet[1] == 20, "expected INFO level (20) but got %s" % ( packet[1], ) assert packet[ 2].data == message, "expected message '%s' but got '%s'" % ( message, packet[2].data) #after cleanup, log messages should not be intercepted: x.cleanup() log.info("foo") assert len(packets) == 1
def ssh_exec_connect_to(display_desc, opts=None, debug_cb=None, ssh_fail_cb=ssh_connect_failed): if not ssh_fail_cb: ssh_fail_cb = ssh_connect_failed sshpass_command = None try: cmd = list(display_desc["full_ssh"]) kwargs = {} env = display_desc.get("env") kwargs["stderr"] = sys.stderr if WIN32: from subprocess import CREATE_NEW_PROCESS_GROUP, CREATE_NEW_CONSOLE, STARTUPINFO, STARTF_USESHOWWINDOW startupinfo = STARTUPINFO() startupinfo.dwFlags |= STARTF_USESHOWWINDOW startupinfo.wShowWindow = 0 #aka win32.con.SW_HIDE flags = CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE kwargs.update({ "startupinfo" : startupinfo, "creationflags" : flags, "stderr" : PIPE, }) elif not display_desc.get("exit_ssh", False) and not OSX: kwargs["start_new_session"] = True remote_xpra = display_desc["remote_xpra"] assert remote_xpra socket_dir = display_desc.get("socket_dir") proxy_command = display_desc["proxy_command"] #ie: "_proxy_start" display_as_args = display_desc["display_as_args"] #ie: "--start=xterm :10" remote_cmd = "" for x in remote_xpra: if not remote_cmd: check = "if" else: check = "elif" if x=="xpra": #no absolute path, so use "which" to check that the command exists: pc = ['%s which "%s" > /dev/null 2>&1; then' % (check, x)] else: pc = ['%s [ -x %s ]; then' % (check, x)] pc += [x] + proxy_command + [shellquote(x) for x in display_as_args] if socket_dir: pc.append("--socket-dir=%s" % socket_dir) remote_cmd += " ".join(pc)+";" remote_cmd += "else echo \"no run-xpra command found\"; exit 1; fi" if INITENV_COMMAND: remote_cmd = INITENV_COMMAND + ";" + remote_cmd #how many times we need to escape the remote command string #depends on how many times the ssh command is parsed nssh = sum(int(x=="ssh") for x in cmd) if nssh>=2 and MAGIC_QUOTES: for _ in range(nssh): remote_cmd = shlex.quote(remote_cmd) else: remote_cmd = "'%s'" % remote_cmd cmd.append("sh -c %s" % remote_cmd) if debug_cb: debug_cb("starting %s tunnel" % str(cmd[0])) #non-string arguments can make Popen choke, #instead of lazily converting everything to a string, we validate the command: for x in cmd: if not isinstance(x, str): raise InitException("argument is not a string: %s (%s), found in command: %s" % (x, type(x), cmd)) password = display_desc.get("password") if password and not display_desc.get("is_putty", False): from xpra.platform.paths import get_sshpass_command sshpass_command = get_sshpass_command() if sshpass_command: #sshpass -e ssh ... cmd.insert(0, sshpass_command) cmd.insert(1, "-e") if env is None: env = os.environ.copy() env["SSHPASS"] = password #the password will be used by ssh via sshpass, #don't try to authenticate again over the ssh-proxy connection, #which would trigger warnings if the server does not require #authentication over unix-domain-sockets: opts.password = None del display_desc["password"] if env: kwargs["env"] = env if is_debug_enabled("ssh"): log.info("executing ssh command: %s" % (" ".join("\"%s\"" % x for x in cmd))) child = Popen(cmd, stdin=PIPE, stdout=PIPE, **kwargs) except OSError as e: raise InitExit(EXIT_SSH_FAILURE, "Error running ssh command '%s': %s" % (" ".join("\"%s\"" % x for x in cmd), e)) def abort_test(action): """ if ssh dies, we don't need to try to read/write from its sockets """ e = child.poll() if e is not None: had_connected = conn.input_bytecount>0 or conn.output_bytecount>0 if had_connected: error_message = "cannot %s using SSH" % action else: error_message = "SSH connection failure" sshpass_error = None if sshpass_command: sshpass_error = { 1 : "Invalid command line argument", 2 : "Conflicting arguments given", 3 : "General runtime error", 4 : "Unrecognized response from ssh (parse error)", 5 : "Invalid/incorrect password", 6 : "Host public key is unknown. sshpass exits without confirming the new key.", }.get(e) if sshpass_error: error_message += ": %s" % sshpass_error if debug_cb: debug_cb(error_message) if ssh_fail_cb: ssh_fail_cb(error_message) if "ssh_abort" not in display_desc: display_desc["ssh_abort"] = True if not had_connected: log.error("Error: SSH connection to the xpra server failed") if sshpass_error: log.error(" %s", sshpass_error) else: log.error(" check your username, hostname, display number, firewall, etc") display_name = display_desc["display_name"] log.error(" for server: %s", display_name) else: log.error("The SSH process has terminated with exit code %s", e) cmd_info = " ".join(display_desc["full_ssh"]) log.error(" the command line used was:") log.error(" %s", cmd_info) raise ConnectionClosedException(error_message) from None def stop_tunnel(): if POSIX: #on posix, the tunnel may be shared with other processes #so don't kill it... which may leave it behind after use. #but at least make sure we close all the pipes: for name,fd in { "stdin" : child.stdin, "stdout" : child.stdout, "stderr" : child.stderr, }.items(): try: if fd: fd.close() except Exception as e: print("error closing ssh tunnel %s: %s" % (name, e)) if not display_desc.get("exit_ssh", False): #leave it running return try: if child.poll() is None: child.terminate() except Exception as e: print("error trying to stop ssh tunnel process: %s" % e) host = display_desc["host"] port = display_desc.get("ssh-port", 22) username = display_desc.get("username") display = display_desc.get("display") info = { "host" : host, "port" : port, } from xpra.net.bytestreams import TwoFileConnection conn = TwoFileConnection(child.stdin, child.stdout, abort_test, target=(host, port), socktype="ssh", close_cb=stop_tunnel, info=info) conn.endpoint = host_target_string("ssh", username, host, port, display) conn.timeout = 0 #taken care of by abort_test conn.process = (child, "ssh", cmd) if kwargs.get("stderr")==PIPE: def stderr_reader(): errs = [] while child.poll() is None: try: v = child.stderr.readline() except OSError: log("stderr_reader()", exc_info=True) break if not v: log("SSH EOF on stderr of %s", cmd) break s = bytestostr(v.rstrip(b"\n\r")) if s: errs.append(s) if errs: log.warn("remote SSH stderr:") for e in errs: log.warn(" %s", e) start_thread(stderr_reader, "ssh-stderr-reader", daemon=True) return conn