agent: Extend cmd KEYINFO to return data from sshcontrol.
authorWerner Koch <wk@gnupg.org>
Thu, 8 Aug 2013 19:22:38 +0000 (21:22 +0200)
committerWerner Koch <wk@gnupg.org>
Thu, 8 Aug 2013 19:44:52 +0000 (21:44 +0200)
* agent/command-ssh.c (struct control_file_s): Rename to
ssh_control_file_s.
(ssh_open_control_file, ssh_close_control_file)
(ssh_read_control_file, ssh_search_control_file): New.
(control_file_t):  Rename and move to ...
* agent/agent.h (ssh_control_file_t): here.
* agent/command.c (do_one_keyinfo): Add args is_ssh, ttl, disabled,
and confirm. Rename unknown keytype indicator from '-' to 'X'.  Extend
output.
(cmd_keyinfo): Add options --ssh-list and --with-ssh.
--

This extension allows the development of frontends to manage the
sshcontrol file.

Signed-off-by: Werner Koch <wk@gnupg.org>
(cherry picked from commit 50c98c7ed6b542857ee2f902eca36cda37407737)

Conflicts in agent/command.c (due to less information printed by
keyinfo) solved.

agent/agent.h
agent/command-ssh.c
agent/command.c

index 4afe6e9..938a9aa 100644 (file)
@@ -126,7 +126,14 @@ struct
 #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
 #define DBG_ASSUAN  (opt.debug & DBG_ASSUAN_VALUE)
 
+/* Forward reference for local definitions in command.c.  */
 struct server_local_s;
+
+/* Declaration of objects from command-ssh.c.  */
+struct ssh_control_file_s;
+typedef struct ssh_control_file_s *ssh_control_file_t;
+
+/* Forward reference for local definitions in call-scd.c.  */
 struct scd_local_s;
 
 /* Collection of data per session (aka connection). */
@@ -226,6 +233,16 @@ int serve_mmapped_ssh_request (ctrl_t ctrl,
 #endif /*HAVE_W32_SYSTEM*/
 
 /*-- command-ssh.c --*/
+ssh_control_file_t ssh_open_control_file (void);
+void ssh_close_control_file (ssh_control_file_t cf);
+gpg_error_t ssh_read_control_file (ssh_control_file_t cf,
+                                   char *r_hexgrip, int *r_disabled,
+                                   int *r_ttl, int *r_confirm);
+gpg_error_t ssh_search_control_file (ssh_control_file_t cf,
+                                     const char *hexgrip,
+                                     int *r_disabled,
+                                     int *r_ttl, int *r_confirm);
+
 void start_command_handler_ssh (ctrl_t, gnupg_fd_t);
 
 /*-- findkey.c --*/
index 6533730..a767697 100644 (file)
@@ -1,5 +1,6 @@
 /* command-ssh.c - gpg-agent's ssh-agent emulation layer
  * Copyright (C) 2004, 2005, 2006, 2009, 2012 Free Software Foundation, Inc.
+ * Copyright (C) 2013 Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -88,7 +89,7 @@ static const char sshcontrolblurb[] =
 "# the format of the entries is fixed and checked by gpg-agent. A\n"
 "# non-comment line starts with optional white spaces, followed by the\n"
 "# keygrip of the key given as 40 hex digits, optionally followed by a\n"
-"# the caching TTL in seconds and another optional field for arbitrary\n"
+"# caching TTL in seconds, and another optional field for arbitrary\n"
 "# flags.   Prepend the keygrip with an '!' mark to disable it.\n"
 "\n";
 
@@ -187,8 +188,8 @@ struct ssh_key_type_spec
 };
 
 
-/* An object used to access the sshcontrol file.  */
-struct control_file_s
+/* Definition of an object to access the sshcontrol file.  */
+struct ssh_control_file_s
 {
   char *fname;  /* Name of the file.  */
   FILE *fp;     /* This is never NULL. */
@@ -201,8 +202,6 @@ struct control_file_s
     char hexgrip[40+1];  /* The hexgrip of the item (uppercase).  */
   } item;
 };
-typedef struct control_file_s *control_file_t;
-
 
 
 /* Prototypes.  */
@@ -731,10 +730,10 @@ file_to_buffer (const char *filename, unsigned char **buffer, size_t *buffer_n)
    control file object stored at R_CF.  On error an error code is
    returned and NULL is stored at R_CF.  */
 static gpg_error_t
-open_control_file (control_file_t *r_cf, int append)
+open_control_file (ssh_control_file_t *r_cf, int append)
 {
   gpg_error_t err;
-  control_file_t cf;
+  ssh_control_file_t cf;
 
   cf = xtrycalloc (1, sizeof *cf);
   if (!cf)
@@ -796,7 +795,7 @@ open_control_file (control_file_t *r_cf, int append)
 
 
 static void
-rewind_control_file (control_file_t cf)
+rewind_control_file (ssh_control_file_t cf)
 {
   fseek (cf->fp, 0, SEEK_SET);
   cf->lnr = 0;
@@ -805,7 +804,7 @@ rewind_control_file (control_file_t cf)
 
 
 static void
-close_control_file (control_file_t cf)
+close_control_file (ssh_control_file_t cf)
 {
   if (!cf)
     return;
@@ -819,7 +818,7 @@ close_control_file (control_file_t cf)
 /* Read the next line from the control file and store the data in CF.
    Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */
 static gpg_error_t
-read_control_file_item (control_file_t cf)
+read_control_file_item (ssh_control_file_t cf)
 {
   int c, i, n;
   char *p, *pend, line[256];
@@ -922,7 +921,7 @@ read_control_file_item (control_file_t cf)
    a specified TTL for that key is stored there.  If R_CONFIRM is not
    NULL it is set to 1 if the key has the confirm flag set. */
 static gpg_error_t
-search_control_file (control_file_t cf, const char *hexgrip,
+search_control_file (ssh_control_file_t cf, const char *hexgrip,
                      int *r_disabled, int *r_ttl, int *r_confirm)
 {
   gpg_error_t err;
@@ -966,7 +965,7 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr,
                    int ttl, int confirm)
 {
   gpg_error_t err;
-  control_file_t cf;
+  ssh_control_file_t cf;
   int disabled;
 
   (void)ctrl;
@@ -1002,7 +1001,7 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr,
 static int
 ttl_from_sshcontrol (const char *hexgrip)
 {
-  control_file_t cf;
+  ssh_control_file_t cf;
   int disabled, ttl;
 
   if (!hexgrip || strlen (hexgrip) != 40)
@@ -1025,7 +1024,7 @@ ttl_from_sshcontrol (const char *hexgrip)
 static int
 confirm_flag_from_sshcontrol (const char *hexgrip)
 {
-  control_file_t cf;
+  ssh_control_file_t cf;
   int disabled, confirm;
 
   if (!hexgrip || strlen (hexgrip) != 40)
@@ -1045,6 +1044,87 @@ confirm_flag_from_sshcontrol (const char *hexgrip)
 }
 
 
+\f
+
+/* Open the ssh control file for reading.  This is a public version of
+   open_control_file.  The caller must use ssh_close_control_file to
+   release the retruned handle.  */
+ssh_control_file_t
+ssh_open_control_file (void)
+{
+  ssh_control_file_t cf;
+
+  /* Then look at all the registered and non-disabled keys. */
+  if (open_control_file (&cf, 0))
+    return NULL;
+  return cf;
+}
+
+/* Close an ssh control file handle.  This is the public version of
+   close_control_file.  CF may be NULL.  */
+void
+ssh_close_control_file (ssh_control_file_t cf)
+{
+  close_control_file (cf);
+}
+
+/* Read the next item from the ssh control file.  The function returns
+   0 if a item was read, GPG_ERR_EOF on eof or another error value.
+   R_HEXGRIP shall either be null or a BUFFER of at least 41 byte.
+   R_DISABLED, R_TTLm and R_CONFIRM return flags from the control
+   file; they are only set on success. */
+gpg_error_t
+ssh_read_control_file (ssh_control_file_t cf,
+                       char *r_hexgrip,
+                       int *r_disabled, int *r_ttl, int *r_confirm)
+{
+  gpg_error_t err;
+
+  do
+    err = read_control_file_item (cf);
+  while (!err && !cf->item.valid);
+  if (!err)
+    {
+      if (r_hexgrip)
+        strcpy (r_hexgrip, cf->item.hexgrip);
+      if (r_disabled)
+        *r_disabled = cf->item.disabled;
+      if (r_ttl)
+        *r_ttl = cf->item.ttl;
+      if (r_confirm)
+        *r_confirm = cf->item.confirm;
+    }
+  return err;
+}
+
+
+/* Search for a key with HEXGRIP in sshcontrol and return all
+   info.  */
+gpg_error_t
+ssh_search_control_file (ssh_control_file_t cf,
+                         const char *hexgrip,
+                         int *r_disabled, int *r_ttl, int *r_confirm)
+{
+  gpg_error_t err;
+  int i;
+  const char *s;
+  char uphexgrip[41];
+
+  /* We need to make sure that HEXGRIP is all uppercase.  The easiest
+     way to do this and also check its length is by copying to a
+     second buffer. */
+  for (i=0, s=hexgrip; i < 40; s++, i++)
+    uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
+  uphexgrip[i] = 0;
+  if (i != 40)
+    err = gpg_error (GPG_ERR_INV_LENGTH);
+  else
+    err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm);
+  if (gpg_err_code (err) == GPG_ERR_EOF)
+    err = gpg_error (GPG_ERR_NOT_FOUND);
+  return err;
+}
+
 
 \f
 
@@ -2187,7 +2267,7 @@ ssh_handler_request_identities (ctrl_t ctrl,
   gcry_sexp_t key_public;
   gpg_error_t err;
   int ret;
-  control_file_t cf = NULL;
+  ssh_control_file_t cf = NULL;
   char *cardsn;
   gpg_error_t ret_err;
 
index 97cf507..2405c54 100644 (file)
@@ -1,6 +1,7 @@
 /* command.c - gpg-agent command handler
  * Copyright (C) 2001, 2002, 2003, 2004, 2005,
  *               2006, 2008, 2009  Free Software Foundation, Inc.
+ * Copyright (C) 2013 Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -833,22 +834,25 @@ cmd_readkey (assuan_context_t ctx, char *line)
 
 \f
 static const char hlp_keyinfo[] =
-  "KEYINFO [--list] [--data] [--ssh-fpr] <keygrip>\n"
+  "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr] [--with-ssh] <keygrip>\n"
   "\n"
   "Return information about the key specified by the KEYGRIP.  If the\n"
   "key is not available GPG_ERR_NOT_FOUND is returned.  If the option\n"
   "--list is given the keygrip is ignored and information about all\n"
-  "available keys are returned.  The information is returned as a\n"
-  "status line unless --data was specified, with this format:\n"
+  "available keys are returned.  If --ssh-list is given information\n"
+  "about all keys listed in the sshcontrol are returned.  With --with-ssh\n"
+  "information from sshcontrol is always added to the info. Unless --data\n"
+  "is given, the information is returned as a status line using the format:\n"
   "\n"
-  "  KEYINFO <keygrip> <type> <serialno> <idstr> - - <fpr>\n"
+  "  KEYINFO <keygrip> <type> <serialno> <idstr> - - <fpr> <ttl> <flags>\n"
   "\n"
   "KEYGRIP is the keygrip.\n"
   "\n"
-  "TYPE is describes the type of the key:\n"
+  "TYPE describes the type of the key:\n"
   "    'D' - Regular key stored on disk,\n"
-  "    'T' - Key is stored on a smartcard (token).\n"
-  "    '-' - Unknown type.\n"
+  "    'T' - Key is stored on a smartcard (token),\n"
+  "    'X' - Unknown type,\n"
+  "    '-' - Key is missing.\n"
   "\n"
   "SERIALNO is an ASCII string with the serial number of the\n"
   "         smartcard.  If the serial number is not known a single\n"
@@ -858,13 +862,21 @@ static const char hlp_keyinfo[] =
   "      is not known a dash is used instead.\n"
   "\n"
   "FPR returns the formatted ssh-style fingerprint of the key.  It is only\n"
-  "    print if the option --ssh-fpr has been used. '-' is printed if the\n"
-  "    fingerprint is not available.\n"
+  "    printed if the option --ssh-fpr has been used.  It defaults to '-'.\n"
+  "\n"
+  "TTL is the TTL in seconds for that key or '-' if n/a.\n"
+  "\n"
+  "FLAGS is a word consisting of one-letter flags:\n"
+  "      'D' - The key has been disabled,\n"
+  "      'S' - The key is listed in sshcontrol (requires --with-ssh),\n"
+  "      'c' - Use of the key needs to be confirmed,\n"
+  "      '-' - No flags given.\n"
   "\n"
   "More information may be added in the future.";
 static gpg_error_t
 do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
-                int data, int with_ssh_fpr)
+                int data, int with_ssh_fpr, int in_ssh,
+                int ttl, int disabled, int confirm)
 {
   gpg_error_t err;
   char hexgrip[40+1];
@@ -874,21 +886,56 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
   char *serialno = NULL;
   char *idstr = NULL;
   const char *keytypestr;
+  int missing_key = 0;
+  char ttlbuf[20];
+  char flagsbuf[5];
 
   err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info);
   if (err)
-    goto leave;
+    {
+      if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+        missing_key = 1;
+      else
+        goto leave;
+    }
 
   /* Reformat the grip so that we use uppercase as good style. */
   bin2hex (grip, 20, hexgrip);
 
-  if (keytype == PRIVATE_KEY_CLEAR
-      || keytype == PRIVATE_KEY_PROTECTED)
-    keytypestr = "D";
-  else if (keytype == PRIVATE_KEY_SHADOWED)
-    keytypestr = "T";
+  if (ttl > 0)
+    snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl);
   else
-    keytypestr = "-";
+    strcpy (ttlbuf, "-");
+
+  *flagsbuf = 0;
+  if (disabled)
+    strcat (flagsbuf, "D");
+  if (in_ssh)
+    strcat (flagsbuf, "S");
+  if (confirm)
+    strcat (flagsbuf, "c");
+  if (!*flagsbuf)
+    strcpy (flagsbuf, "-");
+
+
+  if (missing_key)
+    {
+      keytypestr = "-";
+    }
+  else
+    {
+      switch (keytype)
+        {
+        case PRIVATE_KEY_CLEAR: keytypestr = "D";
+          break;
+        case PRIVATE_KEY_PROTECTED: keytypestr = "D";
+          break;
+        case PRIVATE_KEY_SHADOWED: keytypestr = "T";
+          break;
+        default: keytypestr = "X";
+          break;
+        }
+    }
 
   /* Compute the ssh fingerprint if requested.  */
   if (with_ssh_fpr)
@@ -921,16 +968,21 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
                               "-",
                              "-",
                               fpr? fpr : "-",
+                              ttlbuf,
+                              flagsbuf,
                               NULL);
   else
     {
       char *string;
 
-      string = xtryasprintf ("%s %s %s %s - - %s\n",
+      string = xtryasprintf ("%s %s %s %s - - %s %s %s\n",
                              hexgrip, keytypestr,
                              serialno? serialno : "-",
                              idstr? idstr : "-",
-                             fpr? fpr : "-");
+                             fpr? fpr : "-",
+                             ttlbuf,
+                             flagsbuf);
+
       if (!string)
         err = gpg_error_from_syserror ();
       else
@@ -955,18 +1007,44 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
   unsigned char grip[20];
   DIR *dir = NULL;
   int list_mode;
-  int opt_data, opt_ssh_fpr;
+  int opt_data, opt_ssh_fpr, opt_with_ssh;
+  ssh_control_file_t cf = NULL;
+  char hexgrip[41];
+  int disabled, ttl, confirm, is_ssh;
 
-  list_mode = has_option (line, "--list");
+  if (has_option (line, "--ssh-list"))
+    list_mode = 2;
+  else
+    list_mode = has_option (line, "--list");
   opt_data = has_option (line, "--data");
   opt_ssh_fpr = has_option (line, "--ssh-fpr");
+  opt_with_ssh = has_option (line, "--with-ssh");
   line = skip_options (line);
 
-  if (list_mode)
+  if (opt_with_ssh || list_mode == 2)
+    cf = ssh_open_control_file ();
+
+  if (list_mode == 2)
+    {
+      if (cf)
+        {
+          while (!ssh_read_control_file (cf, hexgrip,
+                                         &disabled, &ttl, &confirm))
+            {
+              if (hex2bin (hexgrip, grip, 20) < 0 )
+                continue; /* Bad hex string.  */
+              err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1,
+                                    ttl, disabled, confirm);
+              if (err)
+                goto leave;
+            }
+        }
+      err = 0;
+    }
+  else if (list_mode)
     {
       char *dirname;
       struct dirent *dir_entry;
-      char hexgrip[41];
 
       dirname = make_filename_try (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, NULL);
       if (!dirname)
@@ -994,7 +1072,19 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
           if ( hex2bin (hexgrip, grip, 20) < 0 )
             continue; /* Bad hex string.  */
 
-          err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr);
+          disabled = ttl = confirm = is_ssh = 0;
+          if (opt_with_ssh)
+            {
+              err = ssh_search_control_file (cf, hexgrip,
+                                             &disabled, &ttl, &confirm);
+              if (!err)
+                is_ssh = 1;
+              else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+                goto leave;
+            }
+
+          err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
+                                ttl, disabled, confirm);
           if (err)
             goto leave;
         }
@@ -1005,10 +1095,23 @@ cmd_keyinfo (assuan_context_t ctx, char *line)
       err = parse_keygrip (ctx, line, grip);
       if (err)
         goto leave;
-      err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr);
+      disabled = ttl = confirm = is_ssh = 0;
+      if (opt_with_ssh)
+        {
+          err = ssh_search_control_file (cf, line,
+                                         &disabled, &ttl, &confirm);
+          if (!err)
+            is_ssh = 1;
+          else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
+            goto leave;
+        }
+
+      err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
+                            ttl, disabled, confirm);
     }
 
  leave:
+  ssh_close_control_file (cf);
   if (dir)
     closedir (dir);
   if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)