pinentry: Add a handler for SETKEYINFO
[pyassuan.git] / bin / pinentry.py
index 1840fa29faa175e642b35cb58b01b04423d6276d..d2e57e0170adef28c984a080a150cadf717569dd 100755 (executable)
@@ -1,20 +1,20 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2011 W. Trevor King <wking@drexel.edu>
+# Copyright (C) 2012 W. Trevor King <wking@drexel.edu>
 #
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
+# This file is part of pyassuan.
 #
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
+# pyassuan is free software: you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
 #
-# You should have received a copy of the GNU Lesser General Public
-# License along with this program.  If not, see
-# <http://www.gnu.org/licenses/>.
+# pyassuan is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# pyassuan.  If not, see <http://www.gnu.org/licenses/>.
 
 """Simple pinentry program for getting pins from a terminal.
 """
@@ -45,7 +45,7 @@ class PinEntry (_server.AssuanServer):
     for details on the pinentry interface.
 
     Alternatively, you can just watch the logs and guess ;).  Here's a
-    trace when driven by GnuPG 2.0.17 (libgcrypt 1.4.6)::
+    trace when driven by GnuPG 2.0.28 (libgcrypt 1.6.3)::
 
       S: OK Your orders please
       C: OPTION grab
@@ -58,17 +58,31 @@ class PinEntry (_server.AssuanServer):
       S: OK
       C: OPTION lc-messages=en_US.UTF-8
       S: OK
+      C: OPTION allow-external-password-cache
+      S: OK
       C: OPTION default-ok=_OK
       S: OK
       C: OPTION default-cancel=_Cancel
       S: OK
+      C: OPTION default-yes=_Yes
+      S: OK
+      C: OPTION default-no=_No
+      S: OK
       C: OPTION default-prompt=PIN:
       S: OK
-      C: OPTION touch-file=/tmp/gpg-7lElMX/S.gpg-agent
+      C: OPTION default-pwmngr=_Save in password manager
+      S: OK
+      C: OPTION default-cf-visi=Do you really want to make your passphrase visible on the screen?
+      S: OK
+      C: OPTION default-tt-visi=Make passphrase visible
+      S: OK
+      C: OPTION default-tt-hide=Hide passphrase
       S: OK
       C: GETINFO pid
       S: D 14309
       S: OK
+      C: SETKEYINFO u/S9464F2C2825D2FE3
+      S: OK
       C: SETDESC Enter passphrase%0A
       S: OK
       C: SETPROMPT Passphrase
@@ -78,15 +92,6 @@ class PinEntry (_server.AssuanServer):
       S: OK
       C: BYE
       S: OK closing connection
-
-    Some drivers (e.g. ``gpgme``) have ``gpg-agent`` set ``ttyname``
-    to the terminal running ``gpgme``.  I don't like this, because the
-    pinentry program doesn't always play nicely with whatever is going
-    on in that terminal.  I'd rather have a free terminal that had
-    just been running Bash, and I export ``GPG_TTY`` to point to the
-    desired terminal.  To ignore the requested ``ttyname`` and use
-    whatever is in ``GPG_TTY``, initialize with ``override_ttyname``
-    set to ``True``.
     """
     _digit_regexp = _re.compile(r'\d+')
 
@@ -94,14 +99,13 @@ class PinEntry (_server.AssuanServer):
     _tpgrp_regexp = _re.compile(r'\d+ \(\S+\) . \d+ \d+ \d+ \d+ (\d+)')
 
     def __init__(self, name='pinentry', strict_options=False,
-                 single_request=True, override_ttyname=False, **kwargs):
+                 single_request=True, **kwargs):
         self.strings = {}
         self.connection = {}
         super(PinEntry, self).__init__(
             name=name, strict_options=strict_options,
             single_request=single_request, **kwargs)
         self.valid_options.append('ttyname')
-        self.override_ttyname = override_ttyname
 
     def reset(self):
         super(PinEntry, self).reset()
@@ -113,16 +117,7 @@ class PinEntry (_server.AssuanServer):
     def _connect(self):
         self.logger.info('connecting to user')
         self.logger.debug('options:\n{}'.format(_pprint.pformat(self.options)))
-        tty_name = None
-        if self.override_ttyname:
-            tty_name = _os.getenv('GPG_TTY')
-            if tty_name:
-                self.logger.debug('override ttyname with {}'.format(tty_name))
-            else:
-                self.logger.debug(
-                    'GPG_TTY not set, fallback to ttyname option')
-        if not tty_name:
-            tty_name = self.options.get('ttyname', None)
+        tty_name = self.options.get('ttyname', None)
         if tty_name:
             self.connection['tpgrp'] = self._get_pgrp(tty_name)
             self.logger.info(
@@ -235,13 +230,17 @@ class PinEntry (_server.AssuanServer):
 
     def _handle_GETINFO(self, arg):
         if arg == 'pid':
-            yield _common.Response('D', str(_os.getpid()))
+            yield _common.Response('D', str(_os.getpid()).encode('ascii'))
         elif arg == 'version':
-            yield _common.Response('D', __version__)
+            yield _common.Response('D', __version__.encode('ascii'))
         else:
             raise _error.AssuanError(message='Invalid parameter')
         yield _common.Response('OK')
 
+    def _handle_SETKEYINFO(self, arg):
+        self.strings['key info'] = arg
+        yield _common.Response('OK')
+
     def _handle_SETDESC(self, arg):
         self.strings['description'] = arg
         yield _common.Response('OK')
@@ -275,7 +274,7 @@ class PinEntry (_server.AssuanServer):
 
         This indicator is updated as the passphrase is typed.  The
         clients needs to implement an inquiry named "QUALITY" which
-        gets passed the current passpharse (percent-plus escaped) and
+        gets passed the current passphrase (percent-plus escaped) and
         should send back a string with a single numerical vauelue
         between -100 and 100.  Negative values will be displayed in
         red.
@@ -292,17 +291,33 @@ class PinEntry (_server.AssuanServer):
             S: OK
 
         With STRING being a percent escaped string shown as the tooltip.
+
+        Here is a real world example of these commands in use:
+
+            C: SETQUALITYBAR Quality%3a
+            S: OK
+            C: SETQUALITYBAR_TT The quality of the text entered above.%0aPlease ask your administrator for details about the criteria.
+            S: OK
         """
-        raise NotImplementedError()
+        self.strings['qualitybar'] = arg
+        yield _common.Response('OK')
+
+    def _handle_SETQUALITYBAR_TT(self, arg):
+        self.strings['qualitybar_tooltip'] = arg
+        yield _common.Response('OK')
 
     def _handle_GETPIN(self, arg):
         try:
             self._connect()
             self._write(self.strings['description'])
+            if 'key info' in self.strings:
+                self._write('key: {}'.format(self.strings['key info']))
+            if 'qualitybar' in self.strings:
+                self._write(self.strings['qualitybar'])
             pin = self._prompt(self.strings['prompt'], add_colon=False)
         finally:
             self._disconnect()
-        yield _common.Response('D', pin)
+        yield _common.Response('D', pin.encode('ascii'))
         yield _common.Response('OK')
 
     def _handle_CONFIRM(self, arg):
@@ -341,7 +356,10 @@ if __name__ == '__main__':
     import logging
     import traceback
 
-    parser = argparse.ArgumentParser(description=__doc__, version=__version__)
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument(
+        '-v', '--version', action='version',
+        version='%(prog)s {}'.format(__version__))
     parser.add_argument(
         '-V', '--verbose', action='count', default=0,
         help='increase verbosity')
@@ -351,7 +369,7 @@ if __name__ == '__main__':
 
     args = parser.parse_args()
 
-    p = PinEntry(override_ttyname=True)
+    p = PinEntry()
 
     if args.verbose:
         p.logger.setLevel(max(