@@ -218,6 +218,7 @@ class GpgServer:
218218 commands : Dict [bytes , 'NoneCallback' ]
219219 seen_data : bool
220220 config_loaded : bool
221+ agent_unrestricted_socket_path : Optional [str ]
221222 agent_socket_path : Optional [str ]
222223 agent_reader : Optional [asyncio .StreamReader ]
223224 agent_writer : Optional [asyncio .StreamWriter ]
@@ -245,6 +246,7 @@ class GpgServer:
245246 'seen_data' ,
246247 'config_loaded' ,
247248 'agent_socket_path' ,
249+ 'agent_unrestricted_socket_path' ,
248250 'agent_reader' ,
249251 'agent_writer' ,
250252 'source_keyring_dir' ,
@@ -275,6 +277,7 @@ def __init__(self, reader: asyncio.StreamReader,
275277
276278 self .log = logging .getLogger ('splitgpg2.Server' )
277279 self .agent_socket_path = None
280+ self .agent_unrestricted_socket_path = None
278281 self .agent_reader : Optional [asyncio .StreamReader ] = None
279282 self .agent_writer : Optional [asyncio .StreamWriter ] = None
280283
@@ -456,14 +459,20 @@ async def connect_agent(self) -> None:
456459
457460 dirs = subprocess .check_output (
458461 ['gpgconf' , * self .homedir_opts (), '--list-dirs' , '-o/dev/stdout' ])
459- if self .allow_keygen :
460- socket_field = b'agent-socket:'
461- else :
462- socket_field = b'agent-extra-socket'
462+ unrestricted_socket_field = b'agent-socket'
463+ socket_field = unrestricted_socket_field if self .allow_keygen else b'agent-extra-socket'
463464 # search for agent-socket:/run/user/1000/gnupg/S.gpg-agent
464- agent_socket_path = [d .split (b':' , 1 )[1 ] for d in dirs .splitlines ()
465- if d .startswith (socket_field )][0 ]
466- self .agent_socket_path = agent_socket_path .decode ()
465+ for d in dirs .splitlines ():
466+ key , value = d .split (b':' )
467+ if key == socket_field :
468+ self .agent_socket_path = value .decode ("UTF-8" , "surrogateescape" )
469+ if key == unrestricted_socket_field :
470+ self .agent_unrestricted_socket_path = value .decode ("UTF-8" , "surrogateescape" )
471+ if ((self .agent_unrestricted_socket_path is not None ) and
472+ (self .agent_socket_path is not None )):
473+ break
474+ else :
475+ raise RuntimeError ("bad output from gpgconf" )
467476
468477 self .agent_reader , self .agent_writer = await asyncio .open_unix_connection (
469478 path = self .agent_socket_path )
@@ -472,7 +481,7 @@ async def connect_agent(self) -> None:
472481 self .notify ('connected' )
473482
474483 # wait for agent hello
475- await self .handle_agent_response ({} )
484+ self . client_write ( await self .read_hello ( self . agent_reader ) )
476485
477486 def close (self , reason : str , log_level : int = logging .ERROR ) -> None :
478487 self .log .log (log_level , '%s; Closing!' , reason )
@@ -559,8 +568,8 @@ def default_options() -> Dict[bytes, Tuple[OptionHandlingType, Optional[bytes]]]
559568 b'lc-messages' : (OptionHandlingType .fake , b'OK' ),
560569 b'putenv' : (OptionHandlingType .fake , b'OK' ),
561570 b'pinentry-mode' : (OptionHandlingType .fake , b'ERR 67108924 Not supported <GPG Agent>' ),
562- b'allow-pinentry-notify' : (OptionHandlingType .verify , None ),
563- b'agent-awareness' : (OptionHandlingType .verify , b'2.1.0' )
571+ b'allow-pinentry-notify' : (OptionHandlingType .fake , b'OK' ),
572+ b'agent-awareness' : (OptionHandlingType .verify , b'2.1.0' ),
564573 }
565574
566575 @staticmethod
@@ -738,11 +747,13 @@ async def command_HAVEKEY(self, untrusted_args: Optional[bytes]) -> None:
738747 raise Filtered
739748 # upper keygrip limit is arbitary
740749 args = self .verify_keygrip_arguments (1 , 200 , untrusted_args , True )
741- await self .send_agent_command (b'HAVEKEY' , args )
750+ unrestricted = args .startswith (b'--list' ) and not self .allow_keygen
751+ await self .send_agent_command (b'HAVEKEY' , args , unrestricted )
742752
743753 async def command_KEYINFO (self , untrusted_args : Optional [bytes ]) -> None :
744754 args = self .verify_keygrip_arguments (1 , 1 , untrusted_args , True )
745- await self .send_agent_command (b'KEYINFO' , args )
755+ unrestricted = args .startswith (b'--list' ) and not self .allow_keygen
756+ await self .send_agent_command (b'KEYINFO' , args , unrestricted )
746757
747758 async def command_GENKEY (self , untrusted_args : Optional [bytes ]) -> None :
748759 if not self .allow_keygen :
@@ -817,7 +828,8 @@ async def setkeydesc(self, keygrip: bytes) -> None:
817828 key .fingerprint ,
818829 subkey_desc )
819830
820- self .agent_write (b'SETKEYDESC %s\n ' % self .percent_plus_escape (desc ))
831+ assert self .agent_writer is not None , "no writer?"
832+ self .agent_write (b'SETKEYDESC %s\n ' % self .percent_plus_escape (desc ), self .agent_writer )
821833
822834 assert self .agent_reader is not None
823835 untrusted_line = await self .agent_reader .readline ()
@@ -1014,43 +1026,68 @@ def get_inquires_for_command(self, command: bytes) -> Dict[bytes, 'ArgCallback']
10141026 }
10151027 return {}
10161028
1017- async def send_agent_command (self , command : bytes , args : Optional [bytes ]) -> None :
1029+ async def send_agent_command (self , command : bytes , args : Optional [bytes ],
1030+ unrestricted : bool = False ) -> None :
10181031 """ Sends command to local gpg agent and handle the response """
10191032 expected_inquires = self .get_inquires_for_command (command )
1020- if args :
1021- if not self .command_argument_regex .match (args ):
1022- raise AssertionError ("BUG: corrupt command about to be sent to agent!" )
1023- cmd_with_args = command + b' ' + args + b'\n '
1033+ assert self .agent_reader is not None , "no reader?"
1034+ assert self .agent_writer is not None , "no writer?"
1035+ if unrestricted and not self .allow_keygen :
1036+ reader , writer = await asyncio .open_unix_connection (
1037+ self .agent_unrestricted_socket_path )
1038+ await self .read_hello (reader )
10241039 else :
1025- cmd_with_args = command + b'\n '
1026- self .agent_write (cmd_with_args )
1027- while True :
1028- more_expected = await self .handle_agent_response (
1029- expected_inquires = expected_inquires )
1030- if not more_expected :
1031- break
1040+ reader , writer = self .agent_reader , self .agent_writer
1041+ try :
1042+ if args :
1043+ if not self .command_argument_regex .match (args ):
1044+ raise AssertionError ("BUG: corrupt command about to be sent to agent!" )
1045+ cmd_with_args = command + b' ' + args + b'\n '
1046+ else :
1047+ cmd_with_args = command + b'\n '
1048+ self .agent_write (cmd_with_args , writer )
1049+ while True :
1050+ more_expected = await self .handle_agent_response (expected_inquires , reader )
1051+ if not more_expected :
1052+ break
1053+ finally :
1054+ if reader is not self .agent_reader :
1055+ writer .close ()
10321056
1033- def agent_write (self , data : bytes ) -> None :
1034- writer = self .agent_writer
1057+ async def read_hello (self , agent_reader : asyncio .StreamReader ) -> bytes :
1058+ while True :
1059+ line = await agent_reader .readline ()
1060+ if not line .endswith (b'\n ' ):
1061+ raise ProtocolError ("premature EOF from agent connection" )
1062+ if b'\n ' in line [:- 1 ]:
1063+ raise ProtocolError ("newline in readline() result???" )
1064+ if line .startswith (b'#' ):
1065+ continue
1066+ if line == b'OK' or line .startswith (b'OK ' ):
1067+ return line
1068+ raise ProtocolError ("agent responded with something other than 'OK'"
1069+ " to initial connection" )
1070+
1071+ def agent_write (self , data : bytes , writer : asyncio .StreamWriter ) -> None :
10351072 assert writer is not None , 'agent_write called with no agent writer?'
10361073 self .log_io ('A <<<' , data )
10371074 writer .write (data )
10381075
10391076 async def handle_agent_response (self ,
1040- expected_inquires : Dict [bytes , 'ArgCallback' ]) -> bool :
1077+ expected_inquires : Dict [bytes , 'ArgCallback' ],
1078+ agent_reader : asyncio .StreamReader ) -> bool :
10411079 """ Receive and handle one agent response. Return whether there are
10421080 more expected """
1043- assert self .agent_reader is not None
10441081 assert self .client_writer is not None
10451082 if self .client_writer .is_closing ():
10461083 # If something went wrong, agent might send back junk.
10471084 # Discard all remaining data from agent and return.
1048- while await self . agent_reader .read (1024 ):
1085+ while await agent_reader .read (1024 ):
10491086 pass
10501087 return False
10511088 # We generally consider the agent as trusted. But since the client can
10521089 # determine part of the response we handle this here as untrusted.
1053- untrusted_line = await self . agent_reader .readline ()
1090+ untrusted_line = await agent_reader .readline ()
10541091 untrusted_line = untrusted_line .rstrip (b'\n ' )
10551092 self .log_io ('A >>>' , untrusted_line )
10561093 if untrusted_line .startswith (b'#' ):
@@ -1291,7 +1328,9 @@ async def inquire_command_D(self, validate_sexp: 'SExprValidator', *,
12911328 raise Filtered from e
12921329 args = untrusted_sexp
12931330
1294- self .agent_write (b'D ' + self .escape_D (self .serialize_sexpr (args )) + b'\n ' )
1331+ assert self .agent_writer is not None , "no writer?"
1332+ self .agent_write (b'D ' + self .escape_D (self .serialize_sexpr (args )) + b'\n ' ,
1333+ self .agent_writer )
12951334 self .seen_data = True
12961335 return True
12971336
@@ -1389,7 +1428,8 @@ def serialize_item(item: 'SExpr') -> bytes:
13891428 async def inquire_command_END (self , * , untrusted_args : bytes ) -> bool :
13901429 if untrusted_args :
13911430 raise Filtered ('unexpected arguments to END' )
1392- self .agent_write (b'END\n ' )
1431+ assert self .agent_writer is not None , "no writer?"
1432+ self .agent_write (b'END\n ' , self .agent_writer )
13931433 return False
13941434
13951435 # endregion
0 commit comments