parameters: (
          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)
docstring: 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.
docstring_summary: Starts an interactive SSH session to the given host as the given user on the
      given port.
docstring_tokens: Starts an interactive SSH session to the given host as the given user on the given port .
function: 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:
          "-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)
function_tokens: 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 ) : 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: "-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 )