* call-scd.c (unescape_status_string): New. Actual a copy of
authorWerner Koch <wk@gnupg.org>
Thu, 24 Feb 2005 21:40:48 +0000 (21:40 +0000)
committerWerner Koch <wk@gnupg.org>
Thu, 24 Feb 2005 21:40:48 +0000 (21:40 +0000)
../g10/call-agent.c
(card_getattr_cb, agent_card_getattr): New.

* command-ssh.c (card_key_available): New.
(ssh_handler_request_identities): First see whether a card key is
available.

* app.c (app_getattr): Return APPTYPE or SERIALNO type even if the
application does dot support the getattr call.

* app.c (select_application): Return an error code and the
application context in an new arg.
* command.c (open_card): Adjusted for that.  Don't use the
fallback if no card is present.  Return an error if the card has
been removed without a reset.
(do_reset, cmd_serialno): Clear that error flag.
(TEST_CARD_REMOVAL): New. Use it with all command handlers.
(scd_update_reader_status_file): Set the error flag on all changes.

agent/ChangeLog
agent/agent.h
agent/call-scd.c
agent/command-ssh.c
scd/ChangeLog
scd/app.c
scd/command.c

index 775a444..118559c 100644 (file)
@@ -1,5 +1,13 @@
 2005-02-24  Werner Koch  <wk@g10code.com>
 
+       * call-scd.c (unescape_status_string): New. Actual a copy of
+       ../g10/call-agent.c
+       (card_getattr_cb, agent_card_getattr): New.
+
+       * command-ssh.c (card_key_available): New.
+       (ssh_handler_request_identities): First see whether a card key is
+       available.
+
        * gpg-agent.c (handle_connections): Need to check for events if
        select returns with -1.
 
index 0661cc4..39e479e 100644 (file)
@@ -259,6 +259,7 @@ int agent_card_pkdecrypt (ctrl_t ctrl,
 int agent_card_readcert (ctrl_t ctrl,
                          const char *id, char **r_buf, size_t *r_buflen);
 int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf);
+gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result);
 int agent_card_scd (ctrl_t ctrl, const char *cmdline,
                     int (*getpin_cb)(void *, const char *, char*, size_t),
                     void *getpin_cb_arg, void *assuan_context);
index bffdbcb..f7d32f7 100644 (file)
@@ -1,5 +1,5 @@
 /* call-scd.c - fork of the scdaemon to do SC operations
- *     Copyright (C) 2001, 2002 Free Software Foundation, Inc.
+ *     Copyright (C) 2001, 2002, 2005 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -66,7 +66,7 @@ static pth_mutex_t scd_lock;
 static int active_connection_fd = -1;
 static int active_connection = 0;
 
-/* callback parameter for learn card */
+/* Callback parameter for learn card */
 struct learn_parm_s {
   void (*kpinfo_cb)(void*, const char *);
   void *kpinfo_cb_arg;
@@ -266,6 +266,41 @@ agent_reset_scd (ctrl_t ctrl)
 }
 
 
+\f
+/* Return a new malloced string by unescaping the string S.  Escaping
+   is percent escaping and '+'/space mapping.  A binary Nul will
+   silently be replaced by a 0xFF.  Function returns NULL to indicate
+   an out of memory status. */
+static char *
+unescape_status_string (const unsigned char *s)
+{
+  char *buffer, *d;
+
+  buffer = d = xtrymalloc (strlen (s)+1);
+  if (!buffer)
+    return NULL;
+  while (*s)
+    {
+      if (*s == '%' && s[1] && s[2])
+        { 
+          s++;
+          *d = xtoi_2 (s);
+          if (!*d)
+            *d = '\xff';
+          d++;
+          s += 2;
+        }
+      else if (*s == '+')
+        {
+          *d++ = ' ';
+          s++;
+        }
+      else
+        *d++ = *s++;
+    }
+  *d = 0; 
+  return buffer;
+}
 
 
 \f
@@ -375,14 +410,6 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno)
   if (rc)
     return rc;
 
-  /* Hmm, do we really need this reset - scddaemon should do this or
-     we can do this if we for some reason figure out that the
-     operation might have failed due to a missing RESET.  Hmmm, I feel
-     this is really SCdaemon's duty */
-/*    rc = assuan_transact (scd_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); */
-/*    if (rc) */
-/*      return unlock_scd (map_assuan_err (rc)); */
-
   rc = assuan_transact (scd_ctx, "SERIALNO",
                         NULL, NULL, NULL, NULL,
                         get_serialno_cb, &serialno);
@@ -395,6 +422,8 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno)
   return unlock_scd (0);
 }
 
+
+
 \f
 static AssuanError
 membuf_data_cb (void *opaque, const void *buffer, size_t length)
@@ -644,6 +673,90 @@ agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf)
 }
 
 
+\f
+/* Type used with the card_getattr_cb.  */
+struct card_getattr_parm_s {
+  const char *keyword;  /* Keyword to look for.  */
+  size_t keywordlen;    /* strlen of KEYWORD.  */
+  char *data;           /* Malloced and unescaped data.  */
+  int error;            /* ERRNO value or 0 on success. */
+};
+
+/* Callback function for agent_card_getattr.  */
+static assuan_error_t
+card_getattr_cb (void *opaque, const char *line)
+{
+  struct card_getattr_parm_s *parm = opaque;
+  const char *keyword = line;
+  int keywordlen;
+
+  if (parm->data)
+    return 0; /* We want only the first occurrence.  */
+
+  for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+    ;
+  while (spacep (line))
+    line++;
+
+  if (keywordlen == parm->keywordlen
+      && !memcmp (keyword, parm->keyword, keywordlen))
+    {
+      parm->data = unescape_status_string (line);
+      if (!parm->data)
+        parm->error = errno;
+    }
+  
+  return 0;
+}
+
+
+/* Call the agent to retrieve a single line data object. On success
+   the object is malloced and stored at RESULT; it is guaranteed that
+   NULL is never stored in this case.  On error an error code is
+   returned and NULL stored at RESULT. */
+gpg_error_t
+agent_card_getattr (ctrl_t ctrl, const char *name, char **result)
+{
+  int err;
+  struct card_getattr_parm_s parm;
+  char line[ASSUAN_LINELENGTH];
+
+  *result = NULL;
+
+  if (!*name)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  memset (&parm, 0, sizeof parm);
+  parm.keyword = name;
+  parm.keywordlen = strlen (name);
+
+  /* We assume that NAME does not need escaping. */
+  if (8 + strlen (name) > DIM(line)-1)
+    return gpg_error (GPG_ERR_TOO_LARGE);
+  stpcpy (stpcpy (line, "GETATTR "), name); 
+
+  err = start_scd (ctrl);
+  if (err)
+    return err;
+
+  err = map_assuan_err (assuan_transact (scd_ctx, line,
+                                         NULL, NULL, NULL, NULL,
+                                         card_getattr_cb, &parm));
+  if (!err && parm.error)
+    err = gpg_error_from_errno (parm.error);
+  
+  if (!err && !parm.data)
+    err = gpg_error (GPG_ERR_NO_DATA);
+  
+  if (!err)
+    *result = parm.data;
+  else
+    xfree (parm.data);
+
+  return unlock_scd (err);
+}
+
+
 
 \f
 static AssuanError
index 8ea042e..2c0d25e 100644 (file)
@@ -1201,7 +1201,7 @@ sexp_key_extract (gcry_sexp_t sexp,
   return err;
 }
 
-/* Extract the car from SEXP, and create a newly created C-string it,
+/* Extract the car from SEXP, and create a newly created C-string 
    which is to be stored in IDENTIFIER.  */
 static gpg_error_t
 sexp_extract_identifier (gcry_sexp_t sexp, const char **identifier)
@@ -1404,6 +1404,7 @@ ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size,
 }
                              
 
+/* Write the public key KEY_PUBLIC to STREAM in SSH key format. */
 static gpg_error_t
 ssh_send_key_public (estream_t stream, gcry_sexp_t key_public)
 {
@@ -1516,7 +1517,78 @@ key_secret_to_public (gcry_sexp_t *key_public,
   return err;
 }
 
-\f
+
+/* Chec whether a smartcard is available and whether it has a usable
+   key.  Store a copy of that key at R_PK and return 0.  If no key is
+   available store NULL at R_PK and return an error code.  */
+static gpg_error_t
+card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk)
+{
+  gpg_error_t err;
+  char *appname;
+  unsigned char *sbuf;
+  size_t sbuflen;
+  gcry_sexp_t pk;
+
+  *r_pk = NULL;
+
+  /* First see whether a card is available and whether the application
+     is supported.  */
+  err = agent_card_getattr (ctrl, "APPTYPE", &appname);
+  if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED )
+    {
+      /* Ask for the serial number to reset the card.  */
+      err = agent_card_serialno (ctrl, &appname);
+      if (err)
+        {
+          if (opt.verbose)
+            log_info (_("can't get serial number of card: %s\n"),
+                      gpg_strerror (err));
+          return err;
+        }
+      log_info (_("detected card with S/N: %s\n"), appname);
+      xfree (appname);
+      err = agent_card_getattr (ctrl, "APPTYPE", &appname);
+    }
+  if (err)
+    {
+      log_error (_("error getting application type of card: %s\n"),
+                 gpg_strerror (err));
+      return err;
+    }
+  if (strcmp (appname, "OPENPGP"))
+    {
+      log_info (_("card application `%s' is not supported\n"), appname);
+      xfree (appname);
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+  xfree (appname);
+  appname = NULL;
+
+  /* Read the public key.  */
+  err = agent_card_readkey (ctrl, "OPENPGP.3", &sbuf);
+  if (err)
+    {
+      if (opt.verbose)
+        log_info (_("no suitable card key found: %s\n"), gpg_strerror (err));
+      return err;
+    }
+
+  sbuflen = gcry_sexp_canon_len (sbuf, 0, NULL, NULL);
+  err = gcry_sexp_sscan (&pk, NULL, sbuf, sbuflen);
+  xfree (sbuf);
+  if (err)
+    {
+      log_error ("failed to build S-Exp from received card key: %s\n",
+                 gpg_strerror (err));
+      return err;
+    }
+  
+  *r_pk = pk;
+  return 0;
+}
+
+
 
 /*
   Request handler.  
@@ -1589,91 +1661,95 @@ ssh_handler_request_identities (ctrl_t ctrl,
       goto out;
     }
 
-  /* Iterate over key files.  */
 
-  /* FIXME: make sure that buffer gets deallocated properly.  */
+
+  /* First check whether a key is currently available in the card
+     reader - this should be allowed even without being listed in
+     sshcontrol.txt. */
+
+  if (!card_key_available (ctrl, &key_public))
+    {
+      err = ssh_send_key_public (key_blobs, key_public);
+      gcry_sexp_release (key_public);
+      key_public = NULL;
+      if (err)
+        goto out;
+      
+      key_counter++;
+    }
+
+
+  /* Then look at all the registered an allowed keys. */
+
 
   /* Fixme: We should better iterate over the control file and check
      whether the key file is there.  This is better in resepct to
      performance if tehre are a lot of key sin our key storage. */
-
+  /* FIXME: make sure that buffer gets deallocated properly.  */
   err = open_control_file (&ctrl_fp, 0);
   if (err)
     goto out;
 
-#warning Really need to fix this fixme.
-  /*
- FIXME:  First check whether a key is currently available in the card reader - this should be allowed even without being listed in sshcontrol.txt.
-  */
-
-  while (1)
+  while ( (dir_entry = readdir (dir)) )
     {
-      dir_entry = readdir (dir);
-      if (dir_entry)
-       {
-         if ((strlen (dir_entry->d_name) == 44)
-             && (! strncmp (dir_entry->d_name + 40, ".key", 4)))
-           {
-              char hexgrip[41];
-              int disabled;
-
-              /* We do only want to return keys listed in our control
-                 file. */
-              strncpy (hexgrip, dir_entry->d_name, 40);
-              hexgrip[40] = 0;
-              if ( strlen (hexgrip) != 40 )
-                continue;
-              if (search_control_file (ctrl_fp, hexgrip, &disabled)
-                  || disabled)
-                continue;
-
-             strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40);
-
-             /* Read file content.  */
-             err = file_to_buffer (key_path, &buffer, &buffer_n);
-             if (err)
-               break;
+      if ((strlen (dir_entry->d_name) == 44)
+          && (! strncmp (dir_entry->d_name + 40, ".key", 4)))
+        {
+          char hexgrip[41];
+          int disabled;
+
+          /* We do only want to return keys listed in our control
+             file. */
+          strncpy (hexgrip, dir_entry->d_name, 40);
+          hexgrip[40] = 0;
+          if ( strlen (hexgrip) != 40 )
+            continue;
+          if (search_control_file (ctrl_fp, hexgrip, &disabled)
+              || disabled)
+            continue;
+
+          strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40);
+
+          /* Read file content.  */
+          err = file_to_buffer (key_path, &buffer, &buffer_n);
+          if (err)
+            goto out;
              
-             err = gcry_sexp_sscan (&key_secret, NULL, buffer, buffer_n);
-             if (err)
-               break;
+          err = gcry_sexp_sscan (&key_secret, NULL, buffer, buffer_n);
+          if (err)
+            goto out;
 
-             xfree (buffer);
-             buffer = NULL;
+          xfree (buffer);
+          buffer = NULL;
 
-             err = sexp_extract_identifier (key_secret, &key_type);
-             if (err)
-               break;
+          err = sexp_extract_identifier (key_secret, &key_type);
+          if (err)
+            goto out;
 
-             err = ssh_key_type_lookup (NULL, key_type, &spec);
-             if (err)
-               break;
+          err = ssh_key_type_lookup (NULL, key_type, &spec);
+          if (err)
+            goto out;
 
-             xfree ((void *) key_type);
-             key_type = NULL;
+          xfree ((void *) key_type);
+          key_type = NULL;
 
-             err = key_secret_to_public (&key_public, spec, key_secret);
-             if (err)
-               break;
+          err = key_secret_to_public (&key_public, spec, key_secret);
+          if (err)
+            goto out;
 
-             gcry_sexp_release (key_secret);
-             key_secret = NULL;
+          gcry_sexp_release (key_secret);
+          key_secret = NULL;
              
-             err = ssh_send_key_public (key_blobs, key_public);
-             if (err)
-               break;
+          err = ssh_send_key_public (key_blobs, key_public);
+          if (err)
+            goto out;
 
-             gcry_sexp_release (key_public);
-             key_public = NULL;
+          gcry_sexp_release (key_public);
+          key_public = NULL;
 
-             key_counter++;
-           }
-       }
-      else
-       break;
+          key_counter++;
+        }
     }
-  if (err)
-    goto out;
   
   ret = es_fseek (key_blobs, 0, SEEK_SET);
   if (ret)
index c78bd01..dc394b6 100644 (file)
@@ -1,5 +1,8 @@
 2005-02-24  Werner Koch  <wk@g10code.com>
 
+       * app.c (app_getattr): Return APPTYPE or SERIALNO type even if the
+       application does dot support the getattr call.
+
        * app-openpgp.c (get_one_do): Never try to get a non cacheable
        object from the cache.
        (get_one_do): Add new arg to return an error code.  Changed all
@@ -13,6 +16,7 @@
        been removed without a reset.
        (do_reset, cmd_serialno): Clear that error flag.
        (TEST_CARD_REMOVAL): New. Use it with all command handlers.
+       (scd_update_reader_status_file): Set the error flag on all changes.
 
        * scdaemon.c (ticker_thread): Termintate if a shutdown is pending.
 
index 857f9e1..384ee21 100644 (file)
--- a/scd/app.c
+++ b/scd/app.c
@@ -305,6 +305,35 @@ app_getattr (APP app, CTRL ctrl, const char *name)
     return gpg_error (GPG_ERR_INV_VALUE);
   if (!app->initialized)
     return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+
+  if (app->apptype && name && !strcmp (name, "APPTYPE"))
+    {
+      send_status_info (ctrl, "APPTYPE",
+                        app->apptype, strlen (app->apptype), NULL, 0);
+      return 0;
+    }
+  if (name && !strcmp (name, "SERIALNO"))
+    {
+      char *serial_and_stamp;
+      char *serial;
+      time_t stamp;
+      int rc;
+      
+      rc = app_get_serial_and_stamp (app, &serial, &stamp);
+      if (rc)
+        return rc;
+      rc = asprintf (&serial_and_stamp, "%s %lu",
+                     serial, (unsigned long)stamp);
+      rc = (rc < 0)? gpg_error_from_errno (errno) : 0;
+      xfree (serial);
+      if (rc)
+        return rc;
+      send_status_info (ctrl, "SERIALNO",
+                        serial_and_stamp, strlen (serial_and_stamp), NULL, 0);
+      free (serial_and_stamp);
+      return 0;
+    }
+
   if (!app->fnc.getattr)
     return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
   return app->fnc.getattr (app, ctrl, name);
index a4fb968..63e3e28 100644 (file)
@@ -239,7 +239,7 @@ percent_plus_unescape (unsigned char *string)
    operations are done on the same card unless he calls this function.
  */
 static int
-cmd_serialno (ASSUAN_CONTEXT ctx, char *line)
+cmd_serialno (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc = 0;
@@ -248,7 +248,8 @@ cmd_serialno (ASSUAN_CONTEXT ctx, char *line)
   time_t stamp;
 
   /* Clear the remove flag so that the open_card is able to reread it.  */
-  ctrl->server_local->card_removed = 0;
+  if (ctrl->server_local->card_removed)
+    do_reset (ctrl, 0);
 
   if ((rc = open_card (ctrl, *line? line:NULL)))
     return rc;
@@ -1092,7 +1093,6 @@ cmd_checkpin (ASSUAN_CONTEXT ctx, char *line)
 
 
 
-
 \f
 /* Tell the assuan library about our commands */
 static int
@@ -1299,10 +1299,6 @@ scd_update_reader_status_file (void)
             char templ[50];
             FILE *fp;
 
-            last[slot].any = 1;
-            last[slot].status = status;
-            last[slot].changed = changed;
-
             log_info ("updating status of slot %d to 0x%04X\n", slot, status);
             
             sprintf (templ, "reader_%d.status", slot);
@@ -1318,7 +1314,19 @@ scd_update_reader_status_file (void)
               }
             xfree (fname);
 
-            /* Send a signal to the primary client, if any. */
+            /* Set the card removed flag.  We will set this on any
+               card change because a reset or SERIALNO request must be
+               done in any case.  */
+            if (primary_connection && primary_connection->server_local
+                && last[slot].any )
+              primary_connection->server_local->card_removed = 1;
+
+            last[slot].any = 1;
+            last[slot].status = status;
+            last[slot].changed = changed;
+
+
+            /* Send a signal to the primary client, if any.  */
             if (primary_connection && primary_connection->server_local
                 && primary_connection->server_local->assuan_ctx)
               {