@@ -122,14 +122,14 @@ to solve the problem:
122122 rsync -e '/path/to/ssh-ident' ...
123123 scp -S '/path/to/ssh-ident' ...
124124
125- 4) Replace the real ssh on the system with ssh-ident, and set the
125+ 4) Replace the real ssh on the system with ssh-ident, and set the
126126 BINARY_SSH configuration parameter to the original value.
127127
128128 On Debian based system, you can make this change in a way that
129129 will survive automated upgrades and audits by running:
130130
131131 dpkg-divert --divert /usr/bin/ssh.ssh-ident --rename /usr/bin/ssh
132-
132+
133133 After which, you will need to use:
134134
135135 BINARY_SSH="/usr/bin/ssh.ssh-ident"
@@ -177,10 +177,10 @@ To have multiple identities, all I have to do is:
177177 SSH_ADD_OPTIONS = {
178178 # Regardless, ask for confirmation before using any of the
179179 # work keys.
180- "work": "-c",
180+ "work": [ "-c"] ,
181181 # Forget about secret keys after ten minutes. ssh-ident will
182182 # automatically ask you your passphrase again if they are needed.
183- "secret": "-t 600",
183+ "secret": [ "-t", " 600"] ,
184184 }
185185
186186 # This is option - dont' include any SSH_OPTIONS if you don't
@@ -190,21 +190,21 @@ To have multiple identities, all I have to do is:
190190 "SSH_OPTIONS": {
191191 # Disable forwarding of the agent, but enable X forwarding,
192192 # when using the work profile.
193- "work": "-Xa",
193+ "work": [ "-Xa"] ,
194194
195195 # Always forward the agent when using the secret identity.
196- "secret": "-A"
196+ "secret": [ "-A"]
197197 },
198198
199199 # Options to pass to ssh by default.
200200 # If you don't specify anything, UserRoaming=no is passed, due
201201 # to CVE-2016-0777. Leave it empty to disable this.
202- "SSH_DEFAULT_OPTIONS": "-oUseRoaming=no",
202+ "SSH_DEFAULT_OPTIONS": [ "-oUseRoaming=no"] ,
203203
204204 # Which options to use by default if no match with SSH_ADD_OPTIONS
205205 # was found. Note that ssh-ident hard codes -t 7200 to prevent your
206206 # keys from remaining in memory for too long.
207- SSH_ADD_DEFAULT_OPTIONS = "-t 7200"
207+ SSH_ADD_DEFAULT_OPTIONS = [ "-t", " 7200"]
208208
209209 # Output verbosity
210210 # valid values are: LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG
@@ -417,7 +417,7 @@ class Config(object):
417417 # the specified options to the ssh command run.
418418 "SSH_OPTIONS" : {},
419419 # Additional options to append to ssh by default.
420- "SSH_DEFAULT_OPTIONS" : "-oUseRoaming=no" ,
420+ "SSH_DEFAULT_OPTIONS" : [ "-oUseRoaming=no" ] ,
421421
422422 # Complete path of full ssh binary to use. If not set, ssh-ident will
423423 # try to find the correct binary in PATH.
@@ -438,7 +438,7 @@ class Config(object):
438438 "SSH_ADD_OPTIONS" : {},
439439 # ssh-add default options. By default, don't keep a key longer
440440 # than 2 hours.
441- "SSH_ADD_DEFAULT_OPTIONS" : "-t 7200" ,
441+ "SSH_ADD_DEFAULT_OPTIONS" : [ "-t" , " 7200"] ,
442442
443443 # Like BatchMode in ssh, see man 5 ssh_config.
444444 # In BatchMode ssh-ident will not print any output and not ask for
@@ -653,7 +653,7 @@ class AgentManager(object):
653653 file = sys .stderr , loglevel = LOG_INFO )
654654 self .LoadKeyFiles (toload )
655655 else :
656- print ("All keys already loaded" , file = sys .stderr , loglevel = LOG_INFO )
656+ print ("All keys already loaded" , file = sys .stderr , loglevel = LOG_DEBUG )
657657
658658 def FindUnloadedKeys (self , keys ):
659659 """Determines which keys have not been loaded yet.
@@ -685,40 +685,38 @@ class AgentManager(object):
685685 Args:
686686 keys: iterable of strings, each string a path to a key to load.
687687 """
688- keys = " " .join (keys )
689688 options = self .config .Get ("SSH_ADD_OPTIONS" ).get (
690689 self .identity , self .config .Get ("SSH_ADD_DEFAULT_OPTIONS" ))
691- self .RunShellCommandInAgent (
692- self .agent_file , "ssh-add {0} {1}" .format (options , keys ))
690+ try :
691+ self .RunShellCommandInAgent (self .agent_file , ["ssh-add" ] + options + list (keys ), disable_io = True )
692+ except subprocess .CalledProcessError :
693+ print ("Error occurs during keys loading, exiting..." , file = sys .stderr )
694+ exit (- 1 )
693695
694696 def GetLoadedKeys (self ):
695697 """Returns an iterable of strings, each the fingerprint of a loaded key."""
696- retval , stdout = self .RunShellCommandInAgent (self .agent_file , "ssh-add -l" )
697- if retval != 0 :
698+ try :
699+ stdout = self .RunShellCommandInAgent (self .agent_file , ["ssh-add" , "-l" ])
700+ fingerprints = []
701+ for line in stdout .decode ("utf-8" ).split ("\n " ):
702+ try :
703+ _ , fingerprint , _ = line .split (" " , 2 )
704+ fingerprints .append (fingerprint )
705+ except ValueError :
706+ continue
707+ return fingerprints
708+ except subprocess .CalledProcessError :
698709 return []
699710
700- fingerprints = []
701- for line in stdout .decode ("utf-8" ).split ("\n " ):
702- try :
703- _ , fingerprint , _ = line .split (" " , 2 )
704- fingerprints .append (fingerprint )
705- except ValueError :
706- continue
707- return fingerprints
708-
709711 @staticmethod
710712 def GetPublicKeyFingerprint (key ):
711713 """Returns the fingerprint of a public key as a string."""
712- retval , stdout = AgentManager .RunShellCommand (
713- "ssh-keygen -l -f {0} |tr -s ' '" .format (key ))
714- if retval :
715- return None
716-
717714 try :
715+ stdout = AgentManager .RunShellCommand (["ssh-keygen" , "-l" , "-f" , key ])
718716 _ , fingerprint , _ = stdout .decode ("utf-8" ).split (" " , 2 )
719- except ValueError :
717+ return fingerprint
718+ except subprocess .CalledProcessError , ValueError :
720719 return None
721- return fingerprint
722720
723721 @staticmethod
724722 def GetAgentFile (path , identity ):
@@ -750,66 +748,73 @@ class AgentManager(object):
750748
751749 print ("Preparing new agent for identity {0}" .format (identity ), file = sys .stderr ,
752750 loglevel = LOG_DEBUG )
753- retval = subprocess .call (
754- ["/usr/bin/env" , "-i" , "/bin/sh" , "-c" , "ssh-agent > {0}" .format (agentfile )])
751+ agentenv = AgentManager .RunShellCommand (["ssh-agent" ])
752+ with open (agentfile , 'w' ) as env :
753+ env .write (agentenv )
755754 return agentfile
756755
757756 @staticmethod
758757 def IsAgentFileValid (agentfile ):
759758 """Returns true if the specified agentfile refers to a running agent."""
760- retval , output = AgentManager .RunShellCommandInAgent (
761- agentfile , "ssh-add -l >/dev/null 2>/dev/null" )
762- if retval & 0xff not in [0 , 1 ]:
763- print ("Agent in {0} not running" .format (agentfile ), file = sys .stderr ,
764- loglevel = LOG_DEBUG )
759+ try :
760+ AgentManager .RunShellCommandInAgent (agentfile , ["ssh-add" , "-l" ])
761+ return True
762+ except subprocess .CalledProcessError :
765763 return False
766- return True
767764
768765 @staticmethod
769- def RunShellCommand (command ):
770- """Runs a shell command, returns (status, stdout), (int, string)."""
771- command = ["/bin/sh" , "-c" , command ]
772- process = subprocess .Popen (command , stdout = subprocess .PIPE )
773- stdout , stderr = process .communicate ()
774- return process .wait (), stdout
766+ def RunShellCommand (command , disable_io = False , ** kwargs ):
767+ """Runs a shell command, returns (stdout), (string)."""
768+ for io in ["stdin" , "stdout" , "stderr" ]:
769+ kwargs [io ] = subprocess .PIPE
770+ if disable_io :
771+ kwargs ["preexec_fn" ] = os .setsid
772+ subprocess .check_call (command , ** kwargs )
773+ return None
774+ else :
775+ process = subprocess .Popen (command , ** kwargs )
776+ stdout , stderr = process .communicate ()
777+ retr = process .returncode
778+ if retr != 0 :
779+ raise subprocess .CalledProcessError (retr , command , stderr )
780+ return stdout
781+ return stdout
775782
776783 @staticmethod
777- def RunShellCommandInAgent (agentfile , command ):
778- """Runs a shell command with an agent configured in the environment."""
779- command = ["/bin/sh" , "-c" ,
780- ". {0} >/dev/null 2>/dev/null; {1}" .format (agentfile , command )]
781- process = subprocess .Popen (command , stdout = subprocess .PIPE )
782- stdout , stderr = process .communicate ()
783- return process .wait (), stdout
784+ def GetAgentEnv (agentfile ):
785+ env = {}
786+ with open (agentfile , 'r' ) as agentenv :
787+ for line in agentenv :
788+ line = line .split (";" )[0 ]
789+ line = line .split ("=" )
790+ if len (line ) == 2 :
791+ name , value = line
792+ env [name ] = value
793+ for e in ["TERM" ]:
794+ if e in os .environ :
795+ env [e ] = os .environ [e ]
796+ return env
784797
785798 @staticmethod
786- def EscapeShellArguments (argv ):
787- """Escapes all arguments to the shell, returns a string."""
788- escaped = []
789- for arg in argv :
790- escaped .append ("'{0}'" .format (arg .replace ("'" , "'\" '\" '" )))
791- return " " .join (escaped )
792-
793- def GetShellArgs (self ):
794- """Returns the flags to be passed to the shell to run a command."""
795- shell_args = "-c"
796- if ShouldPrint (self .config , LOG_DEBUG ):
797- shell_args = "-xc"
798- return shell_args
799+ def RunShellCommandInAgent (agentfile , command , disable_io = False , ** kwargs ):
800+ """Runs a shell command with an agent configured in the environment."""
801+ env = AgentManager .GetAgentEnv (agentfile )
802+ for e in ["SSH_ASKPASS" , "DISPLAY" , "XDG_CURRENT_DESKTOP" , "KDE_SESSION_VERSION" , "HOME" ]:
803+ if e in os .environ :
804+ env [e ] = os .environ [e ]
805+ kwargs ["env" ] = env
806+ return AgentManager .RunShellCommand (command , disable_io = disable_io , ** kwargs )
799807
800808 def RunSSH (self , argv ):
801809 """Execs ssh with the specified arguments."""
802810 additional_flags = self .config .Get ("SSH_OPTIONS" ).get (
803811 self .identity , self .config .Get ("SSH_DEFAULT_OPTIONS" ))
804812 if (self .ssh_config ):
805- additional_flags += " -F {0}" .format (self .ssh_config )
806-
807- command = [
808- "/bin/sh" , self .GetShellArgs (),
809- ". {0} >/dev/null 2>/dev/null; exec {1} {2} {3}" .format (
810- self .agent_file , self .config .Get ("BINARY_SSH" ),
811- additional_flags , self .EscapeShellArguments (argv ))]
812- os .execv ("/bin/sh" , command )
813+ additional_flags += ["-F" ] + self .ssh_config
814+ env = AgentManager .GetAgentEnv (self .agent_file )
815+ cmd = self .config .Get ("BINARY_SSH" )
816+ args = additional_flags + argv
817+ os .execve (cmd , args , env )
813818
814819def AutodetectBinary (argv , config ):
815820 """Detects the correct binary to run and sets BINARY_SSH accordingly,
@@ -845,7 +850,7 @@ def AutodetectBinary(argv, config):
845850 # The logic here is pretty straightforward:
846851 # - Try to eliminate the path of ssh-ident from PATH.
847852 # - Search for a binary with the same name of ssh-ident to run.
848- #
853+ #
849854 # If this fails, we may end up in some sort of loop, where ssh-ident
850855 # tries to run itself. This should normally be detected later on,
851856 # where the code checks for the next binary to run.
@@ -897,7 +902,7 @@ def AutodetectBinary(argv, config):
897902 ssh-ident was invoked in place of the binary {0} (determined from argv[0]).
898903 Neither this binary nor 'ssh' could be found in $PATH.
899904
900- PATH="{1}"
905+ PATH="{1}"
901906
902907 You need to adjust your setup for ssh-ident to work: consider setting
903908 BINARY_SSH or BINARY_DIR in your config, or running ssh-ident some
@@ -931,11 +936,6 @@ def ParseCommandLine(argv, config):
931936 break
932937
933938def main (argv ):
934- # Replace stdout and stderr with /dev/tty, so we don't mess up with scripts
935- # that use ssh in case we error out or similar.
936- sys .stdout = open ("/dev/tty" , "w" )
937- sys .stderr = open ("/dev/tty" , "w" )
938-
939939 config = Config ().Load ()
940940 # overwrite python's print function with the wrapper SshIdentPrint
941941 global print
@@ -956,8 +956,8 @@ def main(argv):
956956 message = textwrap .dedent ("""\
957957 ssh-ident found '{0}' as the next command to run.
958958 Based on argv[0] ({1}), it seems like this will create a
959- loop.
960-
959+ loop.
960+
961961 Please use BINARY_SSH, BINARY_DIR, or change the way
962962 ssh-ident is invoked (eg, a different argv[0]) to make
963963 it work correctly.""" )
0 commit comments