def openssh_connect(user, host, port=22, config=None, command=None, password=None, env=None, socket=None, sshfp=False, randomart=False, identities=None, additional_args=None, debug=False): """ Starts an interactive SSH session to the given host as the given user on the given port. If *command* isn't given, the equivalent of "which ssh" will be used to determine the full path to the ssh executable. Otherwise *command* will be used. If a password is given, that will be passed to SSH when prompted. If *env* (dict) is given, that will be used for the shell env when opening the SSH connection. If *socket* (a file path) is given, this will be passed to the SSH command as -S<socket>. If the socket does not exist, ssh's Master mode switch will be set (-M) automatically. This allows sessions to be duplicated automatically. If *sshfp* resolves to True, SSHFP (DNS-based host verification) support will be enabled. If *randomart* resolves to True, the VisualHostKey (randomart hash) option will be enabled to display randomart when the connection is made. If *identities* given (may be a list or just a single string), it/those will be passed to the ssh command to use when connecting (e.g. -i/identity/path). If *additional_args* is given this value (or values if it is a list) will be added to the arguments passed to the ssh command. If *debug* is ``True`` then '-vvv' will be passed to the ssh command. """ try: int(port) except ValueError: print(_("The port must be an integer < 65535")) sys.exit(1) import tempfile, io # NOTE: Figure out if we really want to use the env forwarding feature if not env: # Unless we enable SendEnv in ssh these will do nothing env = { 'TERM': 'xterm', 'LANG': 'en_US.UTF-8', } try: # Get the default rows/cols right from the start env['LINES'] = os.environ['LINES'] env['COLUMNS'] = os.environ['COLUMNS'] # Also pass on some useful (but harmless) Gate One-specific vars: env['GO_TERM'] = os.environ['GO_TERM'] env['GO_LOCATION'] = os.environ['GO_LOCATION'] env['GO_SESSION'] = os.environ['GO_SESSION'] except KeyError: pass # These variables aren't set # Get the user's ssh directory if 'GO_USER' in os.environ: # Try to use Gate One's provided user first go_user = os.environ['GO_USER'] else: # Fall back to the executing user (for testing outside of Gate One) go_user = os.environ['USER'] if 'GO_USER_DIR' in os.environ: users_dir = os.path.join(os.environ['GO_USER_DIR'], go_user) if isinstance(users_dir, bytes): users_dir = users_dir.decode('utf-8') users_ssh_dir = os.path.join(users_dir, '.ssh') else: # Fall back to using the default OpenSSH location for ssh stuff if POSIX: users_dir = os.environ['HOME'] if isinstance(users_dir, bytes): users_dir = users_dir.decode('utf-8') users_ssh_dir = os.path.join(users_dir, '.ssh') else: # Assume Windows. TODO: Double-check this is the right default path users_ssh_dir = os.path.join(os.environ['USERPROFILE'], '.ssh') if not os.path.exists(users_ssh_dir): mkdir_p(users_ssh_dir) if config: ssh_config_path = config else: ssh_config_path = os.path.join(users_ssh_dir, 'config') if not os.path.exists(ssh_config_path): # Create it (an empty one so ssh doesn't error out) with open(ssh_config_path, 'w') as f: f.write('\n') args = [ "-x", # No X11 forwarding, thanks :) "-F'%s'" % ssh_config_path, # It's OK if it doesn't exist # This is so people won't have to worry about user management when # running one-Gate One-per-server... "-oNoHostAuthenticationForLocalhost=yes", # This ensure's that the executing user's identity won't be used: "-oIdentitiesOnly=yes", # This ensures the other end can tell we're a Gate One terminal and # possibly use the session ID with plugins (could be interesting). "-oSendEnv='GO_TERM GO_LOCATION GO_SESSION'", "-p", str(port), "-l", user, ] if debug: args.append('-vvv') # If we're given specific identities use them exclusively if identities: if isinstance(identities, (unicode, str)): # Only one identity present, turn it into a list if os.path.sep not in identities: # Turn the short identity name into an absolute path identities = os.path.join(users_ssh_dir, identities) identities = [identities] # Make it a list else: # No identities given. Get them from the user's dir (if any) identities = get_identities(users_ssh_dir, only_defaults=True) # Now make sure we use them in the connection... if identities: print( _("The following SSH identities are being used for this " "connection:")) for identity in identities: if os.path.sep not in identity: # Turn the short identity name into an absolute path identity = os.path.join(users_ssh_dir, identity) args.insert(3, "-i%s" % identity) print(_("\t\x1b[1m%s\x1b[0m" % os.path.split(identity)[1])) args.insert( 3, # Make sure we're using publickey auth first "-oPreferredAuthentications='publickey,keyboard-interactive,password'" ) else: args.insert( 3, # Don't use publickey "-oPreferredAuthentications='keyboard-interactive,password'") if sshfp: args.insert(3, "-oVerifyHostKeyDNS=yes") if randomart: args.insert(3, "-oVisualHostKey=yes") if not command: if 'PATH' in env: command = which("ssh", path=env['PATH']) else: env['PATH'] = os.environ['PATH'] command = which("ssh") if '[' in host: # IPv6 address # Have to remove the brackets which is silly. See bug: # https://bugzilla.mindrot.org/show_bug.cgi?id=1602 host = host.strip('[]') if socket: # Only set Master mode if we don't have a socket for this session. # This allows us to duplicate a session without having to code # anything special to pre-recognize this condition in gateone.py or # gateone.js. It makes everything automagical :) socket_path = socket.replace(r'%r', user) # Replace just like ssh does socket_path = socket_path.replace(r'%h', host) socket_path = socket_path.replace(r'%p', str(port)) # The %SHORT_SOCKET% replacement is special: It replaces the equivalent # of ssh's %r@%h:%p with a shortened hash of the same value. For # example: user@somehost:22 would become 'ud6U2Q'. This is to avoid the # potential of a really long FQDN (%h) resulting in a "ControlPath too # long" error with the ssh command. user_at_host_port = "%s@%s:%s" % (user, host, port) hashed = short_hash(user_at_host_port) socket_path = socket_path.replace(r'%SHORT_SOCKET%', hashed) if not os.path.exists(socket_path): args.insert(0, "-M") else: print("\x1b]0;%s@%s (child)\007" % (user, host)) print( _("\x1b]_;notice|Existing ssh session detected for ssh://%s@%s:%s;" " utilizing existing tunnel.\007" % (user, host, port))) socket = socket.replace(r'%SHORT_SOCKET%', hashed) socket_arg = "-S'%s'" % socket # Also make sure the base directory exists basedir = os.path.split(socket)[0] mkdir_p(basedir) os.chmod(basedir, 0o700) # 0700 for good security practices args.insert(1, socket_arg) # After -M so it is easier to see in ps if additional_args: if isinstance(additional_args, (list, tuple)): args.extend(additional_args) else: args.extend(additional_args.split()) args.insert(0, command) # Command has to go first args.append(host) # Host should be last if password: # Create a temporary script to use with SSH_ASKPASS temp = tempfile.NamedTemporaryFile(delete=False) os.chmod(temp.name, 0o700) temp.write( ('#!/bin/sh\necho "{0}"\n'.format(password)).encode('utf-8')) temp.close() env['SSH_ASKPASS'] = temp.name env['DISPLAY'] = ':9999' # TODO: Get this using the user's actual X11 # This removes the temporary file in a timely manner from subprocess import Popen Popen("sleep 15 && /bin/rm -f %s" % temp.name, shell=True) # 15 seconds should be enough even for slow connections/servers # It's a tradeoff: Lower number, more secure. Higher number, less # likely to fail script_path = None if 'GO_TERM' in os.environ.keys(): term = os.environ['GO_TERM'] location = os.environ['GO_LOCATION'] if socket: # Emit our special optional escape sequence to tell ssh.py the path # to the SSH socket print("\x1b]_;ssh|set;ssh_socket;{0}\007".format(socket)) if 'GO_SESSION_DIR' in os.environ.keys(): # Save a file indicating our session is attached to GO_TERM ssh_session = 'ssh:%s:%s:%s@%s:%s' % (location, term, user, host, port) script_path = os.path.join(os.environ['GO_SESSION_DIR'], os.environ['GO_SESSION'], ssh_session) if not script_path: # Just use a generic temp file temp = tempfile.NamedTemporaryFile(prefix="ssh_connect", delete=False) script_path = "%s" % temp.name temp.close() # Will be written to below if password: # SSH_ASKPASS needs some special handling # Make sure setsid gets set in our shell script args.insert(0, 'exec setsid') # Create our little shell script to wrap the SSH command cmd = "" for arg in args: if isinstance(arg, bytes): cmd += arg.decode('utf-8') + ' ' else: cmd += arg + ' ' script = wrapper_script.format(socket=socket, cmd=cmd, temp=script_path) # This whole if/else block is here to enable users running Python 2.7 to # remove the 'from __future__ import unicode_literals' line to work around # this bug: http://bugs.python.org/issue9161 if isinstance(script, bytes): with io.open(script_path, 'wb') as f: f.write(script) # Save it to disk else: with io.open(script_path, 'w', encoding='utf-8') as f: f.write(script) # Save it to disk # NOTE: We wrap in a shell script so we can execute it and immediately quit. # By doing this instead of keeping ssh_connect.py running we can save a lot # of memory (depending on how many terminals are open). os.chmod(script_path, 0o700) # 0700 for good security practices # Execute then immediately quit so we don't use up any more memory than we # need. # setup default execvpe args args = ['-c', script_path, '&&', 'rm', '-f', script_path] # if we detect /bin/sh linked to busybox then make sure we insert the 'sh' # at the beginning of the args list if os.path.islink('/bin/sh'): args.insert(0, 'sh') os.execvpe('/bin/sh', args, env) os._exit(0)
def openssh_connect( user, host, port=22, config=None, command=None, password=None, env=None, socket=None, sshfp=False, randomart=False, identities=None, additional_args=None, ): """ Starts an interactive SSH session to the given host as the given user on the given port. If *command* isn't given, the equivalent of "which ssh" will be used to determine the full path to the ssh executable. Otherwise *command* will be used. If a password is given, that will be passed to SSH when prompted. If *env* (dict) is given, that will be used for the shell env when opening the SSH connection. If *socket* (a file path) is given, this will be passed to the SSH command as -S<socket>. If the socket does not exist, ssh's Master mode switch will be set (-M) automatically. This allows sessions to be duplicated automatically. If *sshfp* resolves to True, SSHFP (DNS-based host verification) support will be enabled. If *randomart* resolves to True, the VisualHostKey (randomart hash) option will be enabled to display randomart when the connection is made. If *identities* given (may be a list or just a single string), it/those will be passed to the ssh command to use when connecting (e.g. -i/identity/path). If *additional_args* is given this value (or values if it is a list) will be added to the arguments passed to the ssh command. """ try: int(port) except ValueError: print(_("The port must be an integer < 65535")) sys.exit(1) signal.signal(signal.SIGCHLD, signal.SIG_IGN) # No zombies # NOTE: Figure out if we really want to use the env forwarding feature if not env: # Unless we enable SendEnv in ssh these will do nothing env = {"TERM": "xterm", "LANG": "en_US.UTF-8"} # Get the default rows/cols right from the start try: env["LINES"] = os.environ["LINES"] env["COLUMNS"] = os.environ["COLUMNS"] except KeyError: pass # These variables aren't set # Get the user's ssh directory if "GO_USER" in os.environ: # Try to use Gate One's provided user first go_user = os.environ["GO_USER"] else: # Fall back to the executing user (for testing outside of Gate One) go_user = os.environ["USER"] if "GO_USER_DIR" in os.environ: users_dir = os.path.join(os.environ["GO_USER_DIR"], go_user) users_ssh_dir = os.path.join(users_dir, ".ssh") else: # Fall back to using the default OpenSSH location for ssh stuff if POSIX: users_ssh_dir = os.path.join(os.environ["HOME"], ".ssh") else: # Assume Windows. TODO: Double-check this is the right default path users_ssh_dir = os.path.join(os.environ["USERPROFILE"], ".ssh") if not os.path.exists(users_ssh_dir): mkdir_p(users_ssh_dir) ssh_config_path = os.path.join(users_ssh_dir, "config") if not os.path.exists(ssh_config_path): # Create it (an empty one so ssh doesn't error out) with open(ssh_config_path, "w") as f: f.write("\n") args = [ "-x", # No X11 forwarding, thanks :) "-F'%s'" % ssh_config_path, # It's OK if it doesn't exist # This is so people won't have to worry about user management when # running one-Gate One-per-server... "-oNoHostAuthenticationForLocalhost=yes", # This ensure's that the executing user's identity won't be used: "-oIdentityFile='/dev/null'", # This ensures the other end can tell we're a Gate One terminal "-oSendEnv=GO_TERM", "-p", str(port), "-l", user, ] # If we're given specific identities use them exclusively if identities: if isinstance(identities, (unicode, str)): # Only one identity present, turn it into a list if os.path.sep not in identities: # Turn the short identity name into an absolute path identities = os.path.join(users_ssh_dir, identities) identities = [identities] # Make it a list else: # No identities given. Get them from the user's dir (if any) identities = get_identities(users_ssh_dir, only_defaults=True) # Now make sure we use them in the connection... if identities: print(_("The following SSH identities are being used for this " "connection:")) for identity in identities: if os.path.sep not in identity: # Turn the short identity name into an absolute path identity = os.path.join(users_ssh_dir, identity) args.insert(3, "-i%s" % identity) print(_("\t\x1b[1m%s\x1b[0m" % os.path.split(identity)[1])) args.insert( 3, # Make sure we're using publickey auth first "-oPreferredAuthentications='publickey,keyboard-interactive,password'", ) else: args.insert(3, "-oPreferredAuthentications='keyboard-interactive,password'") # Don't use publickey if sshfp: args.insert(3, "-oVerifyHostKeyDNS=yes") if randomart: args.insert(3, "-oVisualHostKey=yes") if not command: if "PATH" in env: command = which("ssh", path=env["PATH"]) else: env["PATH"] = os.environ["PATH"] command = which("ssh") if "[" in host: # IPv6 address # Have to remove the brackets which is silly. See bug: # https://bugzilla.mindrot.org/show_bug.cgi?id=1602 host = host.strip("[]") if socket: # Only set Master mode if we don't have a socket for this session. # This allows us to duplicate a session without having to code # anything special to pre-recognize this condition in gateone.py or # gateone.js. It makes everything automagical :) socket_path = socket.replace(r"%r", user) # Replace just like ssh does socket_path = socket_path.replace(r"%h", host) socket_path = socket_path.replace(r"%p", str(port)) # The %SHORT_SOCKET% replacement is special: It replaces the equivalent # of ssh's %r@%h:%p with a shortened hash of the same value. For # example: user@somehost:22 would become 'ud6U2Q'. This is to avoid the # potential of a really long FQDN (%h) resulting in a "ControlPath too # long" error with the ssh command. user_at_host_port = "%s@%s:%s" % (user, host, port) hashed = short_hash(user_at_host_port) socket_path = socket_path.replace(r"%SHORT_SOCKET%", hashed) if not os.path.exists(socket_path): args.insert(0, "-M") else: print("\x1b]0;%s@%s (child)\007" % (user, host)) print( _( "\x1b]_;notice|Existing ssh session detected for ssh://%s@%s:%s;" " utilizing existing tunnel.\007" % (user, host, port) ) ) socket = socket.replace(r"%SHORT_SOCKET%", hashed) socket_arg = "-S'%s'" % socket # Also make sure the base directory exists basedir = os.path.split(socket)[0] mkdir_p(basedir) os.chmod(basedir, 0o700) # 0700 for good security practices args.insert(1, socket_arg) # After -M so it is easier to see in ps if additional_args: if isinstance(additional_args, list): args.extend(additional_args) elif isinstance(additional_args, basestring): args.extend(additional_args.split()) args.insert(0, command) # Command has to go first args.append(host) # Host should be last if password: # Create a temporary script to use with SSH_ASKPASS temp = tempfile.NamedTemporaryFile(delete=False) os.chmod(temp.name, 0o700) temp.write('#!/bin/sh\necho "%s"\n' % password) temp.close() env["SSH_ASKPASS"] = temp.name env["DISPLAY"] = ":9999" # This removes the temporary file in a timely manner Popen("sleep 15 && /bin/rm -f %s" % temp.name, shell=True) # 15 seconds should be enough even for slow connections/servers # It's a tradeoff: Lower number, more secure. Higher number, less # likely to fail script_path = None if "GO_TERM" in os.environ.keys(): if "GO_SESSION_DIR" in os.environ.keys(): # Save a file indicating our session is attached to GO_TERM term = os.environ["GO_TERM"] ssh_session = "ssh:%s:%s@%s:%s" % (term, user, host, port) script_path = os.path.join(os.environ["GO_SESSION_DIR"], ssh_session) if not script_path: # Just use a generic temp file temp = tempfile.NamedTemporaryFile(prefix="ssh_connect", delete=False) script_path = "%s" % temp.name temp.close() # Will be written to below # Create our little shell script to wrap the SSH command script = wrapper_script.format(socket=socket, cmd=" ".join(args), temp=script_path) with open(script_path, "w") as f: f.write(script) # Save it to disk # NOTE: We wrap in a shell script so we can execute it and immediately quit. # By doing this instead of keeping ssh_connect.py running we can save a lot # of memory (depending on how many terminals are open). os.chmod(script_path, 0o700) # 0700 for good security practices if password: # SSH_ASKPASS needs some special handling os.setsid() # This is the key # Execute then immediately quit so we don't use up any more memory than we # need. os.execvpe(script_path, [], env) os._exit(0)
def openssh_connect( user, host, port=22, config=None, command=None, password=None, env=None, socket=None, sshfp=False, randomart=False, identities=None, additional_args=None, debug=False): """ Starts an interactive SSH session to the given host as the given user on the given port. If *command* isn't given, the equivalent of "which ssh" will be used to determine the full path to the ssh executable. Otherwise *command* will be used. If a password is given, that will be passed to SSH when prompted. If *env* (dict) is given, that will be used for the shell env when opening the SSH connection. If *socket* (a file path) is given, this will be passed to the SSH command as -S<socket>. If the socket does not exist, ssh's Master mode switch will be set (-M) automatically. This allows sessions to be duplicated automatically. If *sshfp* resolves to True, SSHFP (DNS-based host verification) support will be enabled. If *randomart* resolves to True, the VisualHostKey (randomart hash) option will be enabled to display randomart when the connection is made. If *identities* given (may be a list or just a single string), it/those will be passed to the ssh command to use when connecting (e.g. -i/identity/path). If *additional_args* is given this value (or values if it is a list) will be added to the arguments passed to the ssh command. If *debug* is ``True`` then '-vvv' will be passed to the ssh command. """ try: int(port) except ValueError: print(_("The port must be an integer < 65535")) sys.exit(1) import tempfile, io # NOTE: Figure out if we really want to use the env forwarding feature if not env: # Unless we enable SendEnv in ssh these will do nothing env = { 'TERM': 'xterm', 'LANG': 'en_US.UTF-8', } try: # Get the default rows/cols right from the start env['LINES'] = os.environ['LINES'] env['COLUMNS'] = os.environ['COLUMNS'] # Also pass on some useful (but harmless) Gate One-specific vars: env['GO_TERM'] = os.environ['GO_TERM'] env['GO_LOCATION'] = os.environ['GO_LOCATION'] env['GO_SESSION'] = os.environ['GO_SESSION'] except KeyError: pass # These variables aren't set # Get the user's ssh directory if 'GO_USER' in os.environ: # Try to use Gate One's provided user first go_user = os.environ['GO_USER'] else: # Fall back to the executing user (for testing outside of Gate One) go_user = os.environ['USER'] if 'GO_USER_DIR' in os.environ: users_dir = os.path.join(os.environ['GO_USER_DIR'], go_user) if isinstance(users_dir, bytes): users_dir = users_dir.decode('utf-8') users_ssh_dir = os.path.join(users_dir, '.ssh') else: # Fall back to using the default OpenSSH location for ssh stuff if POSIX: users_dir = os.environ['HOME'] if isinstance(users_dir, bytes): users_dir = users_dir.decode('utf-8') users_ssh_dir = os.path.join(users_dir, '.ssh') else: # Assume Windows. TODO: Double-check this is the right default path users_ssh_dir = os.path.join(os.environ['USERPROFILE'], '.ssh') if not os.path.exists(users_ssh_dir): mkdir_p(users_ssh_dir) ssh_config_path = os.path.join(users_ssh_dir, 'config') if not os.path.exists(ssh_config_path): # Create it (an empty one so ssh doesn't error out) with open(ssh_config_path, 'w') as f: f.write('\n') args = [ "-x", # No X11 forwarding, thanks :) "-F'%s'" % ssh_config_path, # It's OK if it doesn't exist # This is so people won't have to worry about user management when # running one-Gate One-per-server... "-oNoHostAuthenticationForLocalhost=yes", # This ensure's that the executing user's identity won't be used: "-oIdentityFile='/dev/null'", # This ensures the other end can tell we're a Gate One terminal and # possibly use the session ID with plugins (could be interesting). "-oSendEnv='GO_TERM GO_LOCATION GO_SESSION'", "-p", str(port), "-l", user, ] if debug: args.append('-vvv') # If we're given specific identities use them exclusively if identities: if isinstance(identities, (unicode, str)): # Only one identity present, turn it into a list if os.path.sep not in identities: # Turn the short identity name into an absolute path identities = os.path.join(users_ssh_dir, identities) identities = [identities] # Make it a list else: # No identities given. Get them from the user's dir (if any) identities = get_identities(users_ssh_dir, only_defaults=True) # Now make sure we use them in the connection... if identities: print(_( "The following SSH identities are being used for this " "connection:")) for identity in identities: if os.path.sep not in identity: # Turn the short identity name into an absolute path identity = os.path.join(users_ssh_dir, identity) args.insert(3, "-i%s" % identity) print(_("\t\x1b[1m%s\x1b[0m" % os.path.split(identity)[1])) args.insert(3, # Make sure we're using publickey auth first "-oPreferredAuthentications='publickey,keyboard-interactive,password'" ) else: args.insert( 3, # Don't use publickey "-oPreferredAuthentications='keyboard-interactive,password'" ) if sshfp: args.insert(3, "-oVerifyHostKeyDNS=yes") if randomart: args.insert(3, "-oVisualHostKey=yes") if not command: if 'PATH' in env: command = which("ssh", path=env['PATH']) else: env['PATH'] = os.environ['PATH'] command = which("ssh") if '[' in host: # IPv6 address # Have to remove the brackets which is silly. See bug: # https://bugzilla.mindrot.org/show_bug.cgi?id=1602 host = host.strip('[]') if socket: # Only set Master mode if we don't have a socket for this session. # This allows us to duplicate a session without having to code # anything special to pre-recognize this condition in gateone.py or # gateone.js. It makes everything automagical :) socket_path = socket.replace(r'%r', user) # Replace just like ssh does socket_path = socket_path.replace(r'%h', host) socket_path = socket_path.replace(r'%p', str(port)) # The %SHORT_SOCKET% replacement is special: It replaces the equivalent # of ssh's %r@%h:%p with a shortened hash of the same value. For # example: user@somehost:22 would become 'ud6U2Q'. This is to avoid the # potential of a really long FQDN (%h) resulting in a "ControlPath too # long" error with the ssh command. user_at_host_port = "%s@%s:%s" % (user, host, port) hashed = short_hash(user_at_host_port) socket_path = socket_path.replace(r'%SHORT_SOCKET%', hashed) if not os.path.exists(socket_path): args.insert(0, "-M") else: print("\x1b]0;%s@%s (child)\007" % (user, host)) print(_( "\x1b]_;notice|Existing ssh session detected for ssh://%s@%s:%s;" " utilizing existing tunnel.\007" % (user, host, port) )) socket = socket.replace(r'%SHORT_SOCKET%', hashed) socket_arg = "-S'%s'" % socket # Also make sure the base directory exists basedir = os.path.split(socket)[0] mkdir_p(basedir) os.chmod(basedir, 0o700) # 0700 for good security practices args.insert(1, socket_arg) # After -M so it is easier to see in ps if additional_args: if isinstance(additional_args, (list, tuple)): args.extend(additional_args) else: args.extend(additional_args.split()) args.insert(0, command) # Command has to go first args.append(host) # Host should be last if password: # Create a temporary script to use with SSH_ASKPASS temp = tempfile.NamedTemporaryFile(delete=False) os.chmod(temp.name, 0o700) temp.write(('#!/bin/sh\necho "{0}"\n'.format(password)).encode('utf-8')) temp.close() env['SSH_ASKPASS'] = temp.name env['DISPLAY'] = ':9999' # TODO: Get this using the user's actual X11 # This removes the temporary file in a timely manner from subprocess import Popen Popen("sleep 15 && /bin/rm -f %s" % temp.name, shell=True) # 15 seconds should be enough even for slow connections/servers # It's a tradeoff: Lower number, more secure. Higher number, less # likely to fail script_path = None if 'GO_TERM' in os.environ.keys(): term = os.environ['GO_TERM'] location = os.environ['GO_LOCATION'] if socket: # Emit our special optional escape sequence to tell ssh.py the path # to the SSH socket print("\x1b]_;ssh|set;ssh_socket;{0}\007".format(socket)) if 'GO_SESSION_DIR' in os.environ.keys(): # Save a file indicating our session is attached to GO_TERM ssh_session = 'ssh:%s:%s:%s@%s:%s' % ( location, term, user, host, port) script_path = os.path.join( os.environ['GO_SESSION_DIR'], os.environ['GO_SESSION'], ssh_session) if not script_path: # Just use a generic temp file temp = tempfile.NamedTemporaryFile(prefix="ssh_connect", delete=False) script_path = "%s" % temp.name temp.close() # Will be written to below if password: # SSH_ASKPASS needs some special handling # Make sure setsid gets set in our shell script args.insert(0, 'exec setsid') # Create our little shell script to wrap the SSH command cmd = "" for arg in args: if isinstance(arg, bytes): cmd += arg.decode('utf-8') + ' ' else: cmd += arg + ' ' script = wrapper_script.format( socket=socket, cmd=cmd, temp=script_path) # This whole if/else block is here to enable users running Python 2.7 to # remove the 'from __future__ import unicode_literals' line to work around # this bug: http://bugs.python.org/issue9161 if isinstance(script, bytes): with io.open(script_path, 'wb') as f: f.write(script) # Save it to disk else: with io.open(script_path, 'w', encoding='utf-8') as f: f.write(script) # Save it to disk # NOTE: We wrap in a shell script so we can execute it and immediately quit. # By doing this instead of keeping ssh_connect.py running we can save a lot # of memory (depending on how many terminals are open). os.chmod(script_path, 0o700) # 0700 for good security practices # Execute then immediately quit so we don't use up any more memory than we # need. os.execvpe('/bin/sh', [ '-c', script_path, '&&', 'rm', '-f', script_path], env) os._exit(0)
if not command: if 'PATH' in env: command = which("ssh", path=env['PATH']) else: env['PATH'] = os.environ['PATH'] command = which("ssh") if '[' in host: # IPv6 address # Have to remove the brackets which is silly. See bug: # https://bugzilla.mindrot.org/show_bug.cgi?id=1602 host = host.strip('[]') if socket: # Only set Master mode if we don't have a socket for this session. # This allows us to duplicate a session without having to code # anything special to pre-recognize this condition in gateone.py or # gateone.js. It makes everything automagical :) socket_path = socket.replace(r'%r', user) # Replace just like ssh does socket_path = socket_path.replace(r'%h', host) socket_path = socket_path.replace(r'%p', str(port)) # The %SHORT_SOCKET% replacement is special: It replaces the equivalent # of ssh's %r@%h:%p with a shortened hash of the same value. For # example: user@somehost:22 would become 'ud6U2Q'. This is to avoid the # potential of a really long FQDN (%h) resulting in a "ControlPath too # long" error with the ssh command. user_at_host_port = "%s@%s:%s" % (user, host, port) hashed = short_hash(user_at_host_port) socket_path = socket_path.replace(r'%SHORT_SOCKET%', hashed) if not os.path.exists(socket_path): args.insert(0, "-M") else: print("\x1b]0;%s@%s (child)\007" % (user, host)) print(_(