def import_ns(self, namespace): """ """ python3share._send(Request_ns(namespace), self._to_server) rv = python3share._recv(self._from_server) if isinstance(rv, Ns_rv) and rv.status: return True else: raise python3share.PythonShareError(rv.errormsg)
def exec_in(self, namespace, code, expr=None, async_=False, lock=True): """Execute code in a namespace. Parameters: namespace (string) namespace in which the code and the expression (if given) will be executed. code (string) Python code to be executed in the namespace. expr (string, optional) expression to be evaluated in the namespace after executing the code. async_ (boolean, optional) If true, execute code and expr asynchronously. If so, handle to the return value (Async_rv) will be returned. The default is False. lock (boolean, optional) lock the namespace from others until this execution has finished. The default is True. Returns return value from expr or None. Raise RemoteExecError or RemoteEvalError if code or expr caused an exception in remote end, respectively. """ if namespace == None: namespace = self.namespace() python3share._check_hook( "before:client.exec_in", { "code": code, "expr": expr, "namespace": namespace, "async_": async_, "lock": lock }) try: python3share._send( Exec(namespace, code, expr, async_=async_, lock=lock, recv_caps=python3share.messages.RECV_CAP_COMPRESSION), self._to_server) response = python3share._recv_with_info(self._from_server) return self.make_local(response) except EOFError: raise python3share.PythonShareError( 'No connection to namespace "%s"' % (namespace, ))
def _remote_execute_and_forward(ns, exec_msg, to_client, peername=None): """returns (forward_status, info) forward_status values: True: everything successfully forwarded, info contains pair (forwarded byte count, full length). False: not everything forwarded, info contains pair (forwarded byte count, full length). to_client file/socket is not functional. None: no forwarding, info contains Exec_rv that should be sent normally. Raises EOFError if connection to remote namespace is not functional. The peername parameter is used for logging only. """ client_supports_rv_info = exec_msg.recv_cap_data_info() exec_msg.set_recv_cap_data_info(True) rns = _g_remote_namespaces[ns] from_remote = rns.from_remote # Must keep simultaneously two locks: # - send lock on to_client # - recv lock on from_remote python3share._acquire_recv_lock(from_remote) try: python3share._send(exec_msg, rns.to_remote) response = python3share._recv(from_remote, acquire_recv_lock=False) if not isinstance(response, messages.Data_info): # Got direct response without forward mode return (None, response) python3share._acquire_send_lock(to_client) if client_supports_rv_info: # send data_info to client python3share._send(response, to_client, acquire_send_lock=False) try: if opt_debug and peername: daemon_log("%s:%s <= Exec_rv([forwarding %s B])" % (peername + (response.data_length, ))) forwarded_bytes = python3share._forward(from_remote, to_client, response.data_length, acquire_recv_lock=False, acquire_send_lock=False) if forwarded_bytes == response.data_length: return (True, (forwarded_bytes, response.data_length)) else: return (False, (forwarded_bytes, response.data_length)) finally: python3share._release_send_lock(to_client) finally: exec_msg.set_recv_cap_data_info(client_supports_rv_info) python3share._release_recv_lock(from_remote)
def unlock_ns(self, namespace): """Unlock namespace on the remote peer Parameters: namespace (string) Namespace to be unlocked, can be local or remote to the server. Returns True on success or raises an exception. """ python3share._send(Server_ctl("unlock", namespace), self._to_server) rv = python3share._recv(self._from_server) if isinstance(rv, Server_ctl_rv) and rv.status == 0: return True else: raise python3share.PythonShareError(rv.message)
def drop_ns(self, namespace): """Delete namespace from the remote peer Parameters: namespace (string) Namespace to be dropped, can be local or remote to the server. Returns True on success or raises an exception. """ python3share._send(Drop_ns(namespace), self._to_server) rv = python3share._recv(self._from_server) if isinstance(rv, Ns_rv) and rv.status: return True else: raise python3share.PythonShareError(rv.errormsg)
def export_ns(self, namespace): """Export namespace to remote peer Parameters: namespace (string) Namespace to be exported, can be local or remote to current host. Returns True on success or raises an exception. If succeeded, this connection becomes a server for requests from remote peer. (The remote peer accesses registered namespace through this connection object.) """ python3share._send(Register_ns(namespace), self._to_server) rv = python3share._recv(self._from_server) if isinstance(rv, Ns_rv) and rv.status: return True else: raise python3share.PythonShareError(rv.errormsg)
def _serve_connection(conn, conn_opts): global _g_async_rv_counter global _g_server_shutdown if isinstance(conn, client.Connection): to_client = conn._to_server from_client = conn._from_server else: # conn is a connected socket to_client = conn.makefile("wb") from_client = conn.makefile("rb") try: peername = conn.getpeername() except socket.error: peername = ("unknown", "?") if opt_debug: daemon_log("connected %s:%s" % peername) conn_id = "%s-%s" % (timestamp(), id(conn)) auth_ok = False passwords = [k for k in conn_opts.keys() if k.startswith("password.")] kill_server_on_close = conn_opts.get("kill-server-on-close", False) if passwords: # password authentication is required for this connection try: received_password = python3share._recv(from_client) except Exception as e: daemon_log('error receiving password: %r' % (e, )) received_password = None for password_type in passwords: algorithm = password_type.split(".")[1] if type(received_password) == str: if (algorithm == "plaintext" and received_password == conn_opts[password_type]): auth_ok = True elif (hasattr(hashlib, algorithm) and getattr( hashlib, algorithm)(received_password).hexdigest() == conn_opts[password_type]): auth_ok = True try: if auth_ok: python3share._send(messages.Auth_rv(True), to_client) if opt_debug: daemon_log("%s:%s authentication ok" % peername) elif not received_password is None: python3share._send(messages.Auth_rv(False), to_client) if opt_debug: daemon_log("%s:%s authentication failed" % peername) except socket.error: daemon_log("authentication failed due to socket error") auth_ok = False else: auth_ok = True # no password required whitelist_local = conn_opts.get("whitelist_local", None) while auth_ok: try: obj = python3share._recv(from_client) if opt_debug: daemon_log("%s:%s => %s" % (peername + (obj, ))) except (EOFError, python3share.socket.error): break if isinstance(obj, messages.Register_ns): try: _init_remote_namespace(obj.ns, conn, to_client, from_client) python3share._send(messages.Ns_rv(True), to_client) # from this point on, this connection is reserved for # sending remote namespace traffic. The connection will be # used by other threads, this thread stops here. return except Exception as e: python3share._send( messages.Ns_rv(False, exception2string(sys.exc_info())), to_client) elif isinstance(obj, messages.Drop_ns): try: if obj.ns in _g_local_namespaces: _drop_local_namespace(obj.ns) elif obj.ns in _g_remote_namespaces: _drop_remote_namespace(obj.ns) else: raise ValueError('Unknown namespace "%s"' % (obj.ns, )) python3share._send(messages.Ns_rv(True), to_client) except Exception as e: if opt_debug: daemon_log("namespace drop error: %s" % (e, )) python3share._send( messages.Ns_rv(False, exception2string(sys.exc_info())), to_client) elif isinstance(obj, messages.Request_ns): ns = obj.ns if (ns in _g_remote_namespaces or ns in _g_local_namespaces): _register_exported_namespace(ns, conn) python3share._send(messages.Ns_rv(True), to_client) # from this point on, this connection is reserved for # receiving executions on requested namespace. This # thread starts serving the connection. elif isinstance(obj, messages.Exec): ns = obj.namespace if ns in _g_remote_namespaces: # execute in remote namespace try: _fwd_status, _fwd_info = _remote_execute_and_forward( ns, obj, to_client, peername) if _fwd_status == True: # successfully forwarded if opt_debug: daemon_log("%s:%s forwarded %s B" % (peername + (_fwd_info[0], ))) exec_rv = None # return value fully forwarded elif _fwd_status == False: # connection to client is broken if opt_debug: daemon_log("%s:%s error after forwarding %s/%s B" % (peername + _fwd_info)) break elif _fwd_status is None: # nothing forwarded, send return value by normal means exec_rv = _fwd_info except EOFError: daemon_log('connection lost to "%s"' % (ns, )) _drop_remote_namespace(ns) break else: # execute in local namespace if whitelist_local == None or ns in whitelist_local: _init_local_namespace(ns) if getattr(obj, "async"): # asynchronous execution, return handle (Async_rv) _g_async_rv_counter += 1 rvid = datetime.datetime.now().strftime("%s.%f") + str( _g_async_rv_counter) exec_rv = messages.Async_rv(ns, rvid) _g_async_rvs[ns][rvid] = python3share.InProgress() _thread.start_new_thread(_local_async_execute, (exec_rv, obj)) else: # synchronous execution, return true return value exec_rv = _local_execute(obj, conn_id) if not exec_rv is None: if opt_debug: daemon_log("%s:%s <= %s" % (peername + (exec_rv, ))) try: try: if obj.recv_cap_data_info(): info = python3share._send_opt( exec_rv, to_client, obj.recv_caps) if info: sent_info = " %s B, format:%s" % ( info.data_length, info.data_format) else: sent_info = "" else: python3share._send(exec_rv, to_client) sent_info = "" if opt_debug: daemon_log("%s:%s sent%s" % (peername + (sent_info, ))) except (EOFError, socket.error): break except (TypeError, ValueError, pickle.PicklingError): # pickling rv fails exec_rv.expr_rv = messages.Unpicklable(exec_rv.expr_rv) try: python3share._send(exec_rv, to_client) except (EOFError, socket.error): break elif isinstance(obj, messages.Server_ctl): if obj.command == "die": ns = obj.args[0] if ns in _g_remote_namespaces: try: rv = _remote_execute(ns, obj) if opt_debug: daemon_log("%s:%s <= %s" % (peername + (rv, ))) python3share._send(rv, to_client) except (EOFError, socket.error): # connection lost daemon_log('connection lost to "%s"' % (ns, )) _drop_remote_namespace(ns) break else: _g_server_shutdown = True server_ctl_rv = messages.Server_ctl_rv(0, "shutting down") python3share._send(server_ctl_rv, to_client) if _g_wake_server_function: _g_wake_server_function() break elif obj.command == "unlock": try: ns = obj.args[0] if ns in _g_remote_namespaces: try: rv = _remote_execute(ns, obj) except (EOFError, socket.error): # connection lost daemon_log('connection lost to "%s"' % (ns, )) _drop_remote_namespace(ns) break elif ns in _g_local_namespace_locks: try: _g_local_namespace_locks[ns].release() server_ctl_rv = messages.Server_ctl_rv( 0, "%s unlocked" % (repr(ns), )) except _thread.error as e: server_ctl_rv = messages.Server_ctl_rv( 1, "%s already unlocked" % (repr(ns), )) elif ns in _g_local_namespaces: server_ctl_rv = messages.Server_ctl_rv( 2, "namespace %s is not locked" % (repr(ns), )) else: server_ctl_rv = messages.Server_ctl_rv( -1, "unknown namespace %s" % (repr(ns), )) if opt_debug: daemon_log("%s:%s <= %s" % (peername + (server_ctl_rv, ))) python3share._send(server_ctl_rv, to_client) except Exception as e: if opt_debug: daemon_log("Exception in handling %s: %s" % (obj, e)) else: daemon_log("unknown message type: %s in %s" % (type(obj), obj)) python3share._send(messages.Auth_rv(False), to_client) auth_ok = False if opt_debug: daemon_log("disconnected %s:%s" % peername) _connection_lost(conn_id, to_client, from_client, conn) if kill_server_on_close: _g_server_shutdown = True if _g_wake_server_function: _g_wake_server_function()
def _remote_execute(ns, exec_msg): rns = _g_remote_namespaces[ns] python3share._send(exec_msg, rns.to_remote) # _recv raises EOFError() if disconnected, # let it raise through. return python3share._recv(rns.from_remote)
def __init__(self, host_or_from_server, port_or_to_server, password=None, namespace="default"): """Connect to a pythonshare server The server is listening to connections at host:port, or it can be communicated via file-like objects from_server and to_server. Parameters: host_or_from_server (string or file-like object) string: host file: file for receiving messages from the server port_or_to_server (int or file-like object) int: port number file: file for sending messages to the server password (string, optional) server password. The default is None, that is, do not send password to the server when connecting. namespace (string, optional) the default namespace that is used on eval_() and exec_(). The default is "default". """ self.set_namespace(namespace) if isinstance(host_or_from_server, str) and isinstance( port_or_to_server, int): host = host_or_from_server port = port_or_to_server python3share._check_hook("before:client.socket.connect", { "host": host, "port": port }) self._s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self._s.connect((host, port)) self._from_server = self._s.makefile("rb") self._to_server = self._s.makefile("wb") elif isinstance(host_or_from_server, file) and isinstance( port_or_to_server, file): self._s = None self._to_server = port_or_to_server self._from_server = host_or_from_server else: raise ValueError( "invalid host:port (str:int) or to_server:from_server (file:file)" ) if password: # authenticate to server python3share._send(password, self._to_server) auth_rv = python3share._recv(self._from_server) try: auth_ok = auth_rv.success except AttributeError: auth_ok = False if not auth_ok: raise python3share.AuthenticationError("Permission denied")
def kill_server(self, namespace=None): """Send server shutdown message""" if namespace == None: namespace = self.namespace() python3share._send(Server_ctl("die", namespace), self._to_server) return True