def execute_cmd(options): channel = default_channel() timeout = options.command_timeout with char_buffered(sys.stdin): # Combine stdout and stderr to get around oddball mixing issues channel.set_combine_stderr(False) # Assume pty use, and allow overriding of this either via kwarg or env # var. (invoke_shell always wants a pty no matter what.) using_pty = True # Request pty with size params (default to 80x24, obtain real # parameters if on POSIX platform) channel.get_pty(width=80, height=24) channel.invoke_shell() while not channel.recv_ready(): time.sleep(0.01) workers = ( ThreadHandler('out', output_loop, channel, "recv", capture=None, stream=sys.stdout, timeout=timeout, cmd=options.command), ThreadHandler('err', output_loop, channel, "recv_stderr", capture=None, stream=sys.stderr, timeout=timeout), ThreadHandler('in', input_loop, channel, using_pty) ) while True: if channel.exit_status_ready(): break else: # Check for thread exceptions here so we can raise ASAP # (without chance of getting blocked by, or hidden by an # exception within, recv_exit_status()) for worker in workers: worker.raise_if_needed() try: time.sleep(ssh.io_sleep) except KeyboardInterrupt: channel.send('\x03') # Obtain exit code of remote program now that we're done. status = channel.recv_exit_status() # Wait for threads to exit so we aren't left with stale threads for worker in workers: worker.thread.join() worker.raise_if_needed() # Close channel channel.close() return status
def interact(self): """ Invoke shell interactive mode """ with char_buffered(sys.stdin): # Init stdout, stderr capturing. Must use lists instead of strings as # strings are immutable and we're using these as pass-by-reference stdout_buf, stderr_buf = [], [] workers = (ThreadHandler('out', output_loop, self.channel, "recv", capture=stdout_buf, stream=self.stdout, timeout=self.timeout), ThreadHandler('err', output_loop, self.channel, "recv_stderr", capture=stderr_buf, stream=self.stderr, timeout=self.timeout), ThreadHandler('in', input_loop, self.channel, True)) while True: if self.channel.exit_status_ready(): break else: # Check for thread exceptions here so we can raise ASAP # (without chance of getting blocked by, or hidden by an # exception within, recv_exit_status()) for worker in workers: worker.raise_if_needed() try: time.sleep(ssh.io_sleep) except KeyboardInterrupt, e: raise e # Obtain exit code of remote program now that we're done. status = self.channel.recv_exit_status() # Wait for threads to exit so we aren't left with stale threads for worker in workers: worker.thread.join() worker.raise_if_needed() # Close channel self.channel.close() return stdout_buf, stderr_buf, status
def remote(local_port, remote_port=0, local_host="localhost", remote_bind_address="127.0.0.1"): """ Create a tunnel forwarding a locally-visible port to the remote target. """ sockets = [] channels = [] threads = [] def accept(channel, (src_addr, src_port), (dest_addr, dest_port)): channels.append(channel) sock = socket.socket() sockets.append(sock) try: sock.connect((local_host, local_port)) except Exception as e: try: channel.close() except Exception as e2: close_error = ' (While trying to close channel: {0})'.format( e2) else: close_error = '' raise NonRecoverableError( '[{0}] rtunnel: cannot connect to {1}:{2} ({3}){4}'.format( fabric_api.env.host_string, local_host, local_port, e, close_error)) th = ThreadHandler('fwd', _forwarder, channel, sock) threads.append(th)
def remote(remote_port, local_port=None, local_host="localhost", remote_bind_address="127.0.0.1"): """ Create a tunnel forwarding a locally-visible port to the remote target. """ if local_port is None: local_port = remote_port sockets = [] channels = [] threads = [] def accept(channel, (src_addr, src_port), (dest_addr, dest_port)): channels.append(channel) sock = socket.socket() sockets.append(sock) try: sock.connect((local_host, local_port)) except Exception as e: raise NonRecoverableError( '[{0}] rtunnel: cannot connect to {1}:{2} ({3})'.format( fabric_api.env.host_string, local_host, local_port, e.message)) channel.close() return th = ThreadHandler('fwd', _forwarder, channel, sock) threads.append(th)
def accept(listen_sock, transport, remote_addr): accept_sock, local_peer = listen_sock.accept() channel = self.get_channel(transport, remote_addr, local_peer) handler = ThreadHandler('fwd', _forwarder, channel, accept_sock) self.sockets.append(accept_sock) self.channels.append(channel) self.threads.append(handler)
def connect(self): def listener_thread_main(thead_sock, callback, *a, **kw): try: while True: selsockets = select.select([thead_sock], [], [], 1) if thead_sock in selsockets[0]: callback(thead_sock, *a, **kw) except socket.error as e: #Sockets return bad file descriptor if closed. #Maybe there is a cleaner way of doing this? if e.errno != socket.EBADF: raise except select.error as e: if e[0] != socket.EBADF: raise listening_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listening_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) listening_socket.bind((self.bind_host, self.bind_port)) listening_socket.listen(1) def accept(listen_sock, transport, remote_addr): accept_sock, local_peer = listen_sock.accept() channel = self.get_channel(transport, remote_addr, local_peer) handler = ThreadHandler('fwd', _forwarder, channel, accept_sock) self.sockets.append(accept_sock) self.channels.append(channel) self.threads.append(handler) self.sockets = [] self.channels = [] self.threads = [] self.listening_socket = listening_socket self.listening_thread = ThreadHandler('local_bind', listener_thread_main, listening_socket, accept, connections[env.host_string].get_transport(), (self.remote_host, self.remote_port))
def _execute_local(command, shell=True, combine_stderr=None): ''' Local implementation of fabric.operations._execute using subprocess. ''' if combine_stderr is None: combine_stderr = env.combine_stderr stderr = subprocess.STDOUT if combine_stderr else subprocess.PIPE process = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, stderr=stderr) # Create handlers to buffer and store output with fabric's output_loop() capture_out, capture_err = [], [] channel = MockChannel(process.stdout, process.stderr) workers = ( ThreadHandler('out', output_loop, channel, "recv", capture_out), ThreadHandler('err', output_loop, channel, "recv_stderr", capture_err), ) # Wait for process to finish, raising on any errors while process.poll() is None: for worker in workers: e = worker.exception if e: raise e[0], e[1], e[2] time.sleep(ssh.io_sleep) # Join threads to make sure all output was read for worker in workers: worker.thread.join() out = ''.join(capture_out).rstrip('\n') err = ''.join(capture_err).rstrip('\n') return out, err, process.returncode
def remote(local_port, remote_port=0, local_host="localhost", remote_bind_address="127.0.0.1"): """ Create a tunnel forwarding a locally-visible port to the remote target. """ sockets = [] channels = [] threads = [] def accept(channel, (src_addr, src_port), (dest_addr, dest_port)): # This seemingly innocent statement seems to be doing nothing # but the truth is far from it! # calling fileno() on a paramiko channel the first time, creates # the required plumbing to make the channel valid for select. # While this would generally happen implicitly inside the _forwarder # function when select is called, it may already be too late and may # cause the select loop to hang. # Specifically, when new data arrives to the channel, a flag is set # on an "event" object which is what makes the select call work. # problem is this will only happen if the event object is not None # and it will be not-None only after channel.fileno() has been called # for the first time. If we wait until _forwarder calls select for the # first time it may be after initial data has reached the channel. # calling it explicitly here in the paramiko transport main event loop # guarantees this will not happen. channel.fileno() channels.append(channel) sock = socket.socket() sockets.append(sock) try: sock.connect((local_host, local_port)) except Exception as e: try: channel.close() except Exception as e2: close_error = ' (While trying to close channel: {0})'.format( e2) else: close_error = '' raise NonRecoverableError( '[{0}] rtunnel: cannot connect to {1}:{2} ({3}){4}'.format( fabric_api.env.host_string, local_host, local_port, e, close_error)) th = ThreadHandler('fwd', _forwarder, channel, sock) threads.append(th)
def accept(channel, src_address, dest_address): (src_addr, src_port) = src_address (dest_addr, dest_port) = dest_address channels.append(channel) sock = socket.socket() sockets.append(sock) try: sock.connect((local_host, local_port)) except Exception as e: print("[%s] rtunnel: cannot connect to %s:%d (from local)" % (env.host_string, local_host, local_port)) chan.close() return print("[%s] rtunnel: opened reverse tunnel: %r -> %r -> %r"\ % (env.host_string, channel.origin_addr, channel.getpeername(), (local_host, local_port))) th = ThreadHandler('fwd', _forwarder, channel, sock) threads.append(th)
def inner(*args, **kwargs): # Start server _server = serve_responses(responses, files, passwords, home, pubkeys, port) _server.all_done = threading.Event() worker = ThreadHandler('server', _server.serve_forever) # Execute function try: return func(*args, **kwargs) finally: # Clean up client side connections with hide('status'): disconnect_all() # Stop server _server.all_done.set() _server.shutdown() # Why this is not called in shutdown() is beyond me. _server.server_close() worker.thread.join() # Handle subthread exceptions e = worker.exception if e: raise e[0], e[1], e[2]
def test_patch(): from fabric.thread_handling import ThreadHandler from fabric.api import env, output env.host_string = 'myhost' output['test'] = 'ok' # test dict proxy assert env['host_string'] == 'myhost' assert output['test'] == 'ok' assert len(output.expand_aliases(['everything'])) > 0 # test attribute dict proxy assert env.host_string == 'myhost' # test state transfer def test_state_transfer(x, y): assert x == 1 assert y == 2 assert env.host_string == 'myhost' # test fresh state def test_default(x, y): assert x == 1 assert y == 2 assert env.host_string == None th = ThreadHandler('footest', test_state_transfer, [1], {'y': 2}) th.thread.join() t = threading.Thread(None, test_default, args=[1], kwargs={'y': 2}) t.start() t.join() print 'OK'
def remote_tunnel(remote_port, local_port=None, local_host="localhost", remote_bind_address="127.0.0.1"): """ Create a tunnel forwarding a locally-visible port to the remote target. For example, you can let the remote host access a database that is installed on the client host:: # Map localhost:6379 on the server to localhost:6379 on the client, # so that the remote 'redis-cli' program ends up speaking to the local # redis-server. with remote_tunnel(6379): run("redis-cli -i") The database might be installed on a client only reachable from the client host (as opposed to *on* the client itself):: # Map localhost:6379 on the server to redis.internal:6379 on the client with remote_tunnel(6379, local_host="redis.internal") run("redis-cli -i") ``remote_tunnel`` accepts up to four arguments: * ``remote_port`` (mandatory) is the remote port to listen to. * ``local_port`` (optional) is the local port to connect to; the default is the same port as the remote one. * ``local_host`` (optional) is the locally-reachable computer (DNS name or IP address) to connect to; the default is ``localhost`` (that is, the same computer Fabric is running on). * ``remote_bind_address`` (optional) is the remote IP address to bind to for listening, on the current target. It should be an IP address assigned to an interface on the target (or a DNS name that resolves to such IP). You can use "0.0.0.0" to bind to all interfaces. .. note:: By default, most SSH servers only allow remote tunnels to listen to the localhost interface (127.0.0.1). In these cases, `remote_bind_address` is ignored by the server, and the tunnel will listen only to 127.0.0.1. .. versionadded: 1.6 """ if local_port is None: local_port = remote_port sockets = [] channels = [] threads = [] def accept(channel, (src_addr, src_port), (dest_addr, dest_port)): channels.append(channel) sock = socket.socket() sockets.append(sock) try: sock.connect((local_host, local_port)) except Exception: print "[%s] rtunnel: cannot connect to %s:%d (from local)" % ( env.host_string, local_host, local_port) channel.close() return print "[%s] rtunnel: opened reverse tunnel: %r -> %r -> %r"\ % (env.host_string, channel.origin_addr, channel.getpeername(), (local_host, local_port)) th = ThreadHandler('fwd', _forwarder, channel, sock) threads.append(th)
channels.append(channel) sock = socket.socket() sockets.append(sock) try: sock.connect((local_host, local_port)) except Exception, e: print "[%s] rtunnel: cannot connect to %s:%d (from local)" % (env.host_string, local_host, local_port) chan.close() return print "[%s] rtunnel: opened reverse tunnel: %r -> %r -> %r"\ % (env.host_string, channel.origin_addr, channel.getpeername(), (local_host, local_port)) th = ThreadHandler('fwd', _forwarder, channel, sock) threads.append(th) transport = connections[env.host_string].get_transport() transport.request_port_forward(remote_bind_address, remote_port, handler=accept) try: yield finally: for sock, chan, th in zip(sockets, channels, threads): sock.close() chan.close() th.thread.join() th.raise_if_needed() transport.cancel_port_forward(remote_bind_address, remote_port)
def _execute(channel, command, pty=True, combine_stderr=None, invoke_shell=False, stdout=None, stderr=None, timeout=None): """ Execute ``command`` over ``channel``. ``pty`` controls whether a pseudo-terminal is created. ``combine_stderr`` controls whether we call ``channel.set_combine_stderr``. By default, the global setting for this behavior (:ref:`env.combine_stderr <combine-stderr>`) is consulted, but you may specify ``True`` or ``False`` here to override it. ``invoke_shell`` controls whether we use ``exec_command`` or ``invoke_shell`` (plus a handful of other things, such as always forcing a pty.) Returns a three-tuple of (``stdout``, ``stderr``, ``status``), where ``stdout``/``stderr`` are captured output strings and ``status`` is the program's return code, if applicable. """ # stdout/stderr redirection stdout = stdout or sys.stdout stderr = stderr or sys.stderr # Timeout setting control timeout = env.command_timeout if (timeout is None) else timeout # What to do with CTRl-C? remote_interrupt = env.remote_interrupt with char_buffered(sys.stdin): # Combine stdout and stderr to get around oddball mixing issues if combine_stderr is None: combine_stderr = env.combine_stderr channel.set_combine_stderr(combine_stderr) # Assume pty use, and allow overriding of this either via kwarg or env # var. (invoke_shell always wants a pty no matter what.) using_pty = True if not invoke_shell and (not pty or not env.always_use_pty): using_pty = False # Request pty with size params (default to 80x24, obtain real # parameters if on POSIX platform) if using_pty: rows, cols = _pty_size() channel.get_pty(width=cols, height=rows) # Use SSH agent forwarding from 'ssh' if enabled by user config_agent = ssh_config().get('forwardagent', 'no').lower() == 'yes' forward = None if env.forward_agent or config_agent: forward = ssh.agent.AgentRequestHandler(channel) # Kick off remote command if invoke_shell: channel.invoke_shell() if command: channel.sendall(command + "\n") else: channel.exec_command(command=command) # Init stdout, stderr capturing. Must use lists instead of strings as # strings are immutable and we're using these as pass-by-reference stdout_buf, stderr_buf = [], [] if invoke_shell: stdout_buf = stderr_buf = None workers = ( ThreadHandler('out', output_loop, channel, "recv", capture=stdout_buf, stream=stdout, timeout=timeout), ThreadHandler('err', output_loop, channel, "recv_stderr", capture=stderr_buf, stream=stderr, timeout=timeout), ThreadHandler('in', input_loop, channel, using_pty) ) if remote_interrupt is None: remote_interrupt = invoke_shell if remote_interrupt and not using_pty: remote_interrupt = False while True: if channel.exit_status_ready(): break else: # Check for thread exceptions here so we can raise ASAP # (without chance of getting blocked by, or hidden by an # exception within, recv_exit_status()) for worker in workers: worker.raise_if_needed() try: time.sleep(ssh.io_sleep) except KeyboardInterrupt: if not remote_interrupt: raise channel.send('\x03') # Obtain exit code of remote program now that we're done. status = channel.recv_exit_status() # Wait for threads to exit so we aren't left with stale threads for worker in workers: worker.thread.join() worker.raise_if_needed() # Close channel channel.close() # Close any agent forward proxies if forward is not None: forward.close() # Update stdout/stderr with captured values if applicable if not invoke_shell: stdout_buf = ''.join(stdout_buf).strip() stderr_buf = ''.join(stderr_buf).strip() # Tie off "loose" output by printing a newline. Helps to ensure any # following print()s aren't on the same line as a trailing line prefix # or similar. However, don't add an extra newline if we've already # ended up with one, as that adds a entire blank line instead. if output.running \ and (output.stdout and stdout_buf and not stdout_buf.endswith("\n")) \ or (output.stderr and stderr_buf and not stderr_buf.endswith("\n")): print("") return stdout_buf, stderr_buf, status
def _execute(channel, command, pty=True, combine_stderr=True, invoke_shell=False): """ Execute ``command`` over ``channel``. ``pty`` controls whether a pseudo-terminal is created. ``combine_stderr`` controls whether we call ``channel.set_combine_stderr``. ``invoke_shell`` controls whether we use ``exec_command`` or ``invoke_shell`` (plus a handful of other things, such as always forcing a pty.) Returns a three-tuple of (``stdout``, ``stderr``, ``status``), where ``stdout``/``stderr`` are captured output strings and ``status`` is the program's return code, if applicable. """ with char_buffered(sys.stdin): # Combine stdout and stderr to get around oddball mixing issues if combine_stderr or env.combine_stderr: channel.set_combine_stderr(True) # Assume pty use, and allow overriding of this either via kwarg or env # var. (invoke_shell always wants a pty no matter what.) using_pty = True if not invoke_shell and (not pty or not env.always_use_pty): using_pty = False # Request pty with size params (default to 80x24, obtain real # parameters if on POSIX platform) if using_pty: rows, cols = _pty_size() channel.get_pty(width=cols, height=rows) # Kick off remote command if invoke_shell: channel.invoke_shell() if command: channel.sendall(command + "\n") else: channel.exec_command(command) # Init stdout, stderr capturing. Must use lists instead of strings as # strings are immutable and we're using these as pass-by-reference stdout, stderr = [], [] if invoke_shell: stdout = stderr = None workers = (ThreadHandler('out', output_loop, channel, "recv", stdout), ThreadHandler('err', output_loop, channel, "recv_stderr", stderr), ThreadHandler('in', input_loop, channel, using_pty)) while True: if channel.exit_status_ready(): break else: for worker in workers: e = worker.exception if e: raise e[0], e[1], e[2] # Obtain exit code of remote program now that we're done. status = channel.recv_exit_status() # Wait for threads to exit so we aren't left with stale threads for worker in workers: worker.thread.join() # Close channel channel.close() # Update stdout/stderr with captured values if applicable if not invoke_shell: stdout = ''.join(stdout).strip() stderr = ''.join(stderr).strip() # Tie off "loose" output by printing a newline. Helps to ensure any # following print()s aren't on the same line as a trailing line prefix # or similar. However, don't add an extra newline if we've already # ended up with one, as that adds a entire blank line instead. if output.running \ and (output.stdout and stdout and not stdout.endswith("\n")) \ or (output.stderr and stderr and not stderr.endswith("\n")): print("") return stdout, stderr, status
class LocalTunnel(object): """ Adapted from PR #939 of Fabric: https://github.com/fabric/fabric/pull/939 Forward a local port to a given host and port on the remote side. :param remote_port: Remote port to forward connections to. :type remote_port: int :param remote_host: Host to connect to. Optional, default is ``localhost``. :type remote_host: unicode :param bind_port: Local port to bind to. Optional, default is same as ``remote_port``. :type bind_port: int :param bind_host: Local address to bind to. Optional, default is ``localhost``. """ def __init__(self, remote_port, remote_host=None, bind_port=None, bind_host=None, remote_cmd=None): self.remote_port = remote_port self.remote_host = remote_host or 'localhost' self.bind_port = bind_port or remote_port self.bind_host = bind_host or 'localhost' self.remote_cmd = remote_cmd self.sockets = [] self.channels = [] self.threads = [] self.listening_socket = None self.listening_thread = None def get_channel(self, transport, remote_addr, local_peer): channel = transport.open_channel('direct-tcpip', remote_addr, local_peer) if channel is None: raise Exception('Incoming request to %s:%d was rejected by the SSH server.' % remote_addr) return channel @needs_host def connect(self): def listener_thread_main(thead_sock, callback, *a, **kw): try: while True: selsockets = select.select([thead_sock], [], [], 1) if thead_sock in selsockets[0]: callback(thead_sock, *a, **kw) except socket.error as e: #Sockets return bad file descriptor if closed. #Maybe there is a cleaner way of doing this? if e.errno != socket.EBADF: raise except select.error as e: if e[0] != socket.EBADF: raise listening_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) listening_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) listening_socket.bind((self.bind_host, self.bind_port)) listening_socket.listen(1) def accept(listen_sock, transport, remote_addr): accept_sock, local_peer = listen_sock.accept() channel = self.get_channel(transport, remote_addr, local_peer) handler = ThreadHandler('fwd', _forwarder, channel, accept_sock) self.sockets.append(accept_sock) self.channels.append(channel) self.threads.append(handler) self.sockets = [] self.channels = [] self.threads = [] self.listening_socket = listening_socket self.listening_thread = ThreadHandler('local_bind', listener_thread_main, listening_socket, accept, connections[env.host_string].get_transport(), (self.remote_host, self.remote_port)) def close(self): for sock, chan, th in zip(self.sockets, self.channels, self.threads): sock.close() if not chan.closed: chan.close() th.thread.join() th.raise_if_needed() self.listening_socket.close() self.listening_thread.thread.join() self.listening_thread.raise_if_needed()
channel = transport.open_channel('direct-tcpip', remote_addr, local_peer) if channel is None: raise Exception('Incoming request to %s:%d was rejected by the SSH server.' % self.remote_addr) th = ThreadHandler('fwd', _forwarder, channel, sock) sockets.append(sock) channels.append(channel) threads.append(th) listening_thread = ThreadHandler('local_bind', listener_thread_main, listening_socket, accept, connections[env.host_string].get_transport(), (remote_host, remote_port)) try: yield finally: for sock, chan, th in zip(sockets, channels, threads): sock.close() chan.close() th.thread.join() th.raise_if_needed() listening_socket.close() listening_thread.thread.join() listening_thread.raise_if_needed()