* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "iso7816.h"
#include "apdu.h" /* Required for apdu_*_reader (). */
#include "atr.h"
-#include "exechelp.h"
#ifdef HAVE_LIBUSB
#include "ccid-driver.h"
#endif
-#include "asshelp.h"
-#include "server-help.h"
+#include "../common/asshelp.h"
+#include "../common/server-help.h"
/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */
#define MAXLEN_PIN 100
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
-
-/* Macro to flag a removed card. ENODEV is also tested to catch the
- case of a removed reader. */
-#define TEST_CARD_REMOVAL(c,r) \
- do { \
- int _r = (r); \
- if (gpg_err_code (_r) == GPG_ERR_CARD_NOT_PRESENT \
- || gpg_err_code (_r) == GPG_ERR_CARD_REMOVED \
- || gpg_err_code (_r) == GPG_ERR_CARD_RESET \
- || gpg_err_code (_r) == GPG_ERR_ENODEV ) \
- update_card_removed ((c)->server_local->vreader_idx, 1); \
- } while (0)
-
-#define IS_LOCKED(c) \
- (locked_session \
- && locked_session != (c)->server_local \
- && (c)->server_local->vreader_idx != -1 \
- && locked_session->ctrl_backlink \
- && ((c)->server_local->vreader_idx \
- == locked_session->ctrl_backlink->server_local->vreader_idx))
-
-
-/* This structure is used to keep track of user readers. To
- eventually accommodate this structure for RFID cards, where more
- than one card is used per reader, we name it virtual reader. */
-struct vreader_s
-{
- int valid; /* True if the other objects are valid. */
- int slot; /* APDU slot number of the reader or -1 if not open. */
-
- int reset_failed; /* A reset failed. */
-
- int any; /* Flag indicating whether any status check has been
- done. This is set once to indicate that the status
- tracking for the slot has been initialized. */
- unsigned int status; /* Last status of the reader. */
- unsigned int changed; /* Last change counter of the reader. */
-};
+#define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local)
/* Data used to associate an Assuan context with local server data.
assuan_context_t assuan_ctx;
#ifdef HAVE_W32_SYSTEM
- unsigned long event_signal; /* Or 0 if not used. */
+ void *event_signal; /* Or NULL if not used. */
#else
int event_signal; /* Or 0 if not used. */
#endif
- /* Index into the vreader table (command.c) or -1 if not open. */
- int vreader_idx;
-
/* True if the card has been removed and a reset is required to
continue operation. */
int card_removed;
- /* Flag indicating that the application context needs to be released
- at the next opportunity. */
- int app_ctx_marked_for_release;
-
- /* A disconnect command has been sent. */
- int disconnect_allowed;
-
/* If set to true we will be terminate ourself at the end of the
this session. */
int stopme;
};
-/* The table with information on all used virtual readers. */
-static struct vreader_s vreader_table[10];
-
-
/* To keep track of all running sessions, we link all active server
contexts and the anchor in this variable. */
static struct server_local_s *session_list;
in this variable. */
static struct server_local_s *locked_session;
-/* While doing a reset we need to make sure that the ticker does not
- call scd_update_reader_status_file while we are using it. */
-static npth_mutex_t status_file_update_lock;
-
-\f
-/*-- Local prototypes --*/
-static void update_reader_status_file (int set_card_removed_flag);
-
-
\f
-
-/* This function must be called once to initialize this module. This
- has to be done before a second thread is spawned. We can't do the
- static initialization because Pth emulation code might not be able
- to do a static init; in particular, it is not possible for W32. */
-void
-initialize_module_command (void)
-{
- static int initialized;
- int err;
-
- if (!initialized)
- {
- err = npth_mutex_init (&status_file_update_lock, NULL);
- if (!err)
- initialized = 1;
- }
-}
-
-
-/* Helper to return the slot number for a given virtual reader index
- VRDR. In case on an error -1 is returned. */
-static int
-vreader_slot (int vrdr)
-{
- if (vrdr == -1 || !(vrdr >= 0 && vrdr < DIM(vreader_table)))
- return -1;
- if (!vreader_table [vrdr].valid)
- return -1;
- return vreader_table[vrdr].slot;
-}
-
-
-/* Update the CARD_REMOVED element of all sessions using the virtual
- reader given by VRDR to VALUE. */
-static void
-update_card_removed (int vrdr, int value)
-{
- struct server_local_s *sl;
-
- if (vrdr == -1)
- return;
-
- for (sl=session_list; sl; sl = sl->next_session)
- if (sl->ctrl_backlink
- && sl->ctrl_backlink->server_local->vreader_idx == vrdr)
- {
- sl->card_removed = value;
- }
- /* Let the card application layer know about the removal. */
- if (value)
- application_notify_card_reset (vreader_slot (vrdr));
-}
-
-
/* Convert the STRING into a newly allocated buffer while translating
the hex numbers. Stops at the first invalid character. Blanks and
colons are allowed to separate the hex digits. Returns NULL on
/* Reset the card and free the application context. With SEND_RESET
set to true actually send a RESET to the reader; this is the normal
- way of calling the function. */
+ way of calling the function. If KEEP_LOCK is set and the session
+ is locked that lock wil not be released. */
static void
-do_reset (ctrl_t ctrl, int send_reset)
+do_reset (ctrl_t ctrl, int send_reset, int keep_lock)
{
- int vrdr = ctrl->server_local->vreader_idx;
- int slot;
- int err;
-
- if (!(vrdr == -1 || (vrdr >= 0 && vrdr < DIM(vreader_table))))
- BUG ();
+ app_t app = ctrl->app_ctx;
- /* If there is an active application, release it. Tell all other
- sessions using the same application to release the
- application. */
- if (ctrl->app_ctx)
- {
- release_application (ctrl->app_ctx);
- ctrl->app_ctx = NULL;
- if (send_reset)
- {
- struct server_local_s *sl;
-
- for (sl=session_list; sl; sl = sl->next_session)
- if (sl->ctrl_backlink
- && sl->ctrl_backlink->server_local->vreader_idx == vrdr)
- {
- sl->app_ctx_marked_for_release = 1;
- }
- }
- }
-
- /* If we want a real reset for the card, send the reset APDU and
- tell the application layer about it. */
- slot = vreader_slot (vrdr);
- if (slot != -1 && send_reset && !IS_LOCKED (ctrl) )
- {
- application_notify_card_reset (slot);
- switch (apdu_reset (slot))
- {
- case 0:
- break;
- case SW_HOST_NO_CARD:
- case SW_HOST_CARD_INACTIVE:
- break;
- default:
- apdu_close_reader (slot);
- vreader_table[vrdr].slot = slot = -1;
- break;
- }
- }
+ if (app)
+ app_reset (app, ctrl, IS_LOCKED (ctrl)? 0: send_reset);
/* If we hold a lock, unlock now. */
- if (locked_session && ctrl->server_local == locked_session)
+ if (!keep_lock && locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESET\n");
}
-
- /* Reset the card removed flag for the current reader. We need to
- take the lock here so that the ticker thread won't concurrently
- try to update the file. Calling update_reader_status_file is
- required to get hold of the new status of the card in the vreader
- table. */
- err = npth_mutex_lock (&status_file_update_lock);
- if (err)
- {
- log_error ("failed to acquire status_file_update lock\n");
- ctrl->server_local->vreader_idx = -1;
- return;
- }
- update_reader_status_file (0); /* Update slot status table. */
- update_card_removed (vrdr, 0); /* Clear card_removed flag. */
- err = npth_mutex_unlock (&status_file_update_lock);
- if (err)
- log_error ("failed to release status_file_update lock: %s\n",
- strerror (err));
-
- /* Do this last, so that the update_card_removed above does its job. */
- ctrl->server_local->vreader_idx = -1;
}
-
\f
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
- (void) line;
-
- do_reset (ctrl, 1);
+ do_reset (ctrl, 1, has_option (line, "--keep-lock"));
return 0;
}
#ifdef HAVE_W32_SYSTEM
if (!*value)
return gpg_error (GPG_ERR_ASS_PARAMETER);
- ctrl->server_local->event_signal = strtoul (value, NULL, 16);
+#ifdef _WIN64
+ ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16);
+#else
+ ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16);
+#endif
#else
int i = *value? atoi (value) : -1;
if (i < 0)
}
-/* Return the index of the current reader or open the reader if no
- other sessions are using that reader. If it is not possible to
- open the reader -1 is returned. Note, that we currently support
- only one reader but most of the code (except for this function)
- should be able to cope with several readers. */
-static int
-get_current_reader (void)
-{
- struct vreader_s *vr;
-
- /* We only support one reader for now. */
- vr = &vreader_table[0];
-
- /* Initialize the vreader item if not yet done. */
- if (!vr->valid)
- {
- vr->slot = -1;
- vr->valid = 1;
- }
-
- /* Try to open the reader. */
- if (vr->slot == -1)
- {
- vr->slot = apdu_open_reader (opt.reader_port);
-
- /* If we still don't have a slot, we have no readers.
- Invalidate for now until a reader is attached. */
- if (vr->slot == -1)
- {
- vr->valid = 0;
- }
- }
-
- /* Return the vreader index or -1. */
- return vr->valid ? 0 : -1;
-}
-
-
/* If the card has not yet been opened, do it. */
static gpg_error_t
-open_card (ctrl_t ctrl, const char *apptype)
+open_card (ctrl_t ctrl)
{
- gpg_error_t err;
- int vrdr;
-
/* If we ever got a card not present error code, return that. Only
the SERIALNO command and a reset are able to clear from that
state. */
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
- /* If the application has been marked for release do it now. We
- can't do it immediately in do_reset because the application may
- still be in use. */
- if (ctrl->server_local->app_ctx_marked_for_release)
- {
- ctrl->server_local->app_ctx_marked_for_release = 0;
- release_application (ctrl->app_ctx);
- ctrl->app_ctx = NULL;
- }
+ if (ctrl->app_ctx)
+ return 0;
+
+ return select_application (ctrl, NULL, &ctrl->app_ctx, 0, NULL, 0);
+}
+
+/* Explicitly open a card for a specific use of APPTYPE or SERIALNO. */
+static gpg_error_t
+open_card_with_request (ctrl_t ctrl, const char *apptype, const char *serialno)
+{
+ gpg_error_t err;
+ unsigned char *serialno_bin = NULL;
+ size_t serialno_bin_len = 0;
+ app_t app = ctrl->app_ctx;
/* If we are already initialized for one specific application we
need to check that the client didn't requested a specific
application different from the one in use before we continue. */
- if (ctrl->app_ctx)
- {
- return check_application_conflict
- (ctrl, vreader_slot (ctrl->server_local->vreader_idx), apptype);
- }
+ if (apptype && ctrl->app_ctx)
+ return check_application_conflict (apptype, ctrl->app_ctx);
- /* Setup the vreader and select the application. */
- if (ctrl->server_local->vreader_idx != -1)
- vrdr = ctrl->server_local->vreader_idx;
- else
- vrdr = get_current_reader ();
- ctrl->server_local->vreader_idx = vrdr;
- if (vrdr == -1)
- err = gpg_error (GPG_ERR_CARD);
- else
- {
- /* Fixme: We should move the apdu_connect call to
- select_application. */
- int sw;
- int slot = vreader_slot (vrdr);
-
- ctrl->server_local->disconnect_allowed = 0;
- sw = apdu_connect (slot);
- if (sw && sw != SW_HOST_ALREADY_CONNECTED)
- {
- if (sw == SW_HOST_NO_CARD)
- err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
- else if (sw == SW_HOST_CARD_INACTIVE)
- err = gpg_error (GPG_ERR_CARD_RESET);
- else
- err = gpg_error (GPG_ERR_ENODEV);
- }
- else
- err = select_application (ctrl, slot, apptype, &ctrl->app_ctx);
- }
+ /* Re-scan USB devices. Release APP, before the scan. */
+ ctrl->app_ctx = NULL;
+ release_application (app, 0);
+
+ if (serialno)
+ serialno_bin = hex_to_buffer (serialno, &serialno_bin_len);
+
+ err = select_application (ctrl, apptype, &ctrl->app_ctx, 1,
+ serialno_bin, serialno_bin_len);
+ xfree (serialno_bin);
- TEST_CARD_REMOVAL (ctrl, err);
return err;
}
static const char hlp_serialno[] =
- "SERIALNO [<apptype>]\n"
+ "SERIALNO [--demand=<serialno>] [<apptype>]\n"
"\n"
"Return the serial number of the card using a status response. This\n"
"function should be used to check for the presence of a card.\n"
"\n"
+ "If --demand is given, an application on the card with SERIALNO is\n"
+ "selected and an error is returned if no such card available.\n"
+ "\n"
"If APPTYPE is given, an application of that type is selected and an\n"
"error is returned if the application is not supported or available.\n"
"The default is to auto-select the application using a hardwired\n"
"preference system. Note, that a future extension to this function\n"
- "may allow to specify a list and order of applications to try.\n"
+ "may enable specifying a list and order of applications to try.\n"
"\n"
"This function is special in that it can be used to reset the card.\n"
"Most other functions will return an error when a card change has\n"
cmd_serialno (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
+ struct server_local_s *sl;
int rc = 0;
char *serial;
- time_t stamp;
- int retries = 0;
+ const char *demand;
+
+ if ( IS_LOCKED (ctrl) )
+ return gpg_error (GPG_ERR_LOCKED);
+
+ if ((demand = has_option_name (line, "--demand")))
+ {
+ if (*demand != '=')
+ return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
+ line = (char *)++demand;
+ for (; *line && !spacep (line); line++)
+ ;
+ if (*line)
+ *line++ = 0;
+ }
+ else
+ demand = NULL;
+
+ line = skip_options (line);
/* Clear the remove flag so that the open_card is able to reread it. */
- retry:
if (ctrl->server_local->card_removed)
+ ctrl->server_local->card_removed = 0;
+
+ if ((rc = open_card_with_request (ctrl, *line? line:NULL, demand)))
{
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
- do_reset (ctrl, 1);
+ ctrl->server_local->card_removed = 1;
+ return rc;
}
- if ((rc = open_card (ctrl, *line? line:NULL)))
+ /* Success, clear the card_removed flag for all sessions. */
+ for (sl=session_list; sl; sl = sl->next_session)
{
- /* In case of an inactive card, retry once. */
- if (gpg_err_code (rc) == GPG_ERR_CARD_RESET && retries++ < 1)
- goto retry;
- return rc;
+ ctrl_t c = sl->ctrl_backlink;
+
+ if (c != ctrl)
+ c->server_local->card_removed = 0;
}
- rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp);
- if (rc)
- return rc;
+ serial = app_get_serialno (ctrl->app_ctx);
+ if (!serial)
+ return gpg_error (GPG_ERR_INV_VALUE);
- rc = print_assuan_status (ctx, "SERIALNO", "%s %lu",
- serial, (unsigned long)stamp);
+ rc = assuan_write_status (ctx, "SERIALNO", serial);
xfree (serial);
return rc;
}
"used without the force options, the command might do an INQUIRE\n"
"like this:\n"
"\n"
- " INQUIRE KNOWNCARDP <hexstring_with_serialNumber> <timestamp>\n"
+ " INQUIRE KNOWNCARDP <hexstring_with_serialNumber>\n"
"\n"
"The client should just send an \"END\" if the processing should go on\n"
"or a \"CANCEL\" to force the function to terminate with a Cancel\n"
"error message.\n"
"\n"
- "With the option --keypairinfo only KEYPARIINFO lstatus lines are\n"
+ "With the option --keypairinfo only KEYPARIINFO status lines are\n"
"returned.\n"
"\n"
"The response of this command is a list of status lines formatted as\n"
" P15 = PKCS-15 structure used\n"
" DINSIG = DIN SIG\n"
" OPENPGP = OpenPGP card\n"
+ " PIV = PIV card\n"
" NKS = NetKey card\n"
"\n"
"are implemented. These strings are aliases for the AID\n"
int rc = 0;
int only_keypairinfo = has_option (line, "--keypairinfo");
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
/* Unless the force option is used we try a shortcut by identifying
knows about this card */
if (!only_keypairinfo)
{
- int slot;
const char *reader;
char *serial;
- time_t stamp;
+ app_t app = ctrl->app_ctx;
- slot = vreader_slot (ctrl->server_local->vreader_idx);
- reader = apdu_get_reader_name (slot);
+ if (!app)
+ return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+
+ reader = apdu_get_reader_name (app->slot);
if (!reader)
return out_of_core ();
send_status_direct (ctrl, "READER", reader);
/* No need to free the string of READER. */
- rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp);
- if (rc)
- return rc;
+ serial = app_get_serialno (ctrl->app_ctx);
+ if (!serial)
+ return gpg_error (GPG_ERR_INV_VALUE);
- rc = print_assuan_status (ctx, "SERIALNO", "%s %lu",
- serial, (unsigned long)stamp);
+ rc = assuan_write_status (ctx, "SERIALNO", serial);
if (rc < 0)
{
xfree (serial);
{
char *command;
- rc = gpgrt_asprintf (&command, "KNOWNCARDP %s %lu",
- serial, (unsigned long)stamp);
+ rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial);
if (rc < 0)
{
xfree (serial);
xfree (serial);
return rc;
}
- /* Not canceled, so we have to proceeed. */
+ /* Not canceled, so we have to proceed. */
}
xfree (serial);
}
if (!rc)
rc = app_write_learn_status (ctrl->app_ctx, ctrl, only_keypairinfo);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
unsigned char *cert;
size_t ncert;
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
line = xstrdup (line); /* Need a copy of the line. */
- rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert);
+ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert);
if (rc)
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
xfree (line);
return rc;
}
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
static const char hlp_readkey[] =
- "READKEY <keyid>\n"
+ "READKEY [--advanced] <keyid>\n"
"\n"
"Return the public key for the given cert or key ID as a standard\n"
"S-expression.\n"
+ "In --advanced mode it returns the S-expression in advanced format.\n"
"\n"
- "Note, that this function may even be used on a locked card.";
+ "Note that this function may even be used on a locked card.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
+ int advanced = 0;
unsigned char *cert = NULL;
size_t ncert, n;
ksba_cert_t kc = NULL;
unsigned char *pk;
size_t pklen;
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
+ if (has_option (line, "--advanced"))
+ advanced = 1;
+
+ line = skip_options (line);
+
line = xstrdup (line); /* Need a copy of the line. */
/* If the application supports the READKEY function we use that.
Otherwise we use the old way by extracting it from the
certificate. */
- rc = app_readkey (ctrl->app_ctx, line, &pk, &pklen);
+ rc = app_readkey (ctrl->app_ctx, ctrl, advanced, line, &pk, &pklen);
if (!rc)
{ /* Yeah, got that key - send it back. */
rc = assuan_send_data (ctx, pk, pklen);
log_error ("app_readkey failed: %s\n", gpg_strerror (rc));
else
{
- rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert);
+ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert);
if (rc)
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
}
leave:
ksba_cert_release (kc);
xfree (cert);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
line = skip_options (line);
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
/* We have to use a copy of the key ID because the function may use
if (!keyidstr)
return out_of_core ();
- rc = app_sign (ctrl->app_ctx,
+ rc = app_sign (ctrl->app_ctx, ctrl,
keyidstr, hash_algo,
pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
return rc; /* that is already an assuan error code */
}
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
size_t outdatalen;
char *keyidstr;
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
if (!keyidstr)
return out_of_core ();
- rc = app_auth (ctrl->app_ctx,
- keyidstr,
- pin_cb, ctx,
+ rc = app_auth (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen);
xfree (keyidstr);
return rc; /* that is already an assuan error code */
}
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
char *keyidstr;
unsigned int infoflags;
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
- rc = app_decipher (ctrl->app_ctx,
- keyidstr,
- pin_cb, ctx,
+ rc = app_decipher (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen, &infoflags);
return rc; /* that is already an assuan error code */
}
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
int rc;
const char *keyword;
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
keyword = line;
is locked. */
rc = app_getattr (ctrl->app_ctx, ctrl, keyword);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
static const char hlp_setattr[] =
"SETATTR <name> <value> \n"
"\n"
- "This command is used to store data on a a smartcard. The allowed\n"
+ "This command is used to store data on a smartcard. The allowed\n"
"names and values are depend on the currently selected smartcard\n"
"application. NAME and VALUE must be percent and '+' escaped.\n"
"\n"
size_t nbytes;
char *line, *linebuf;
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
/* We need to use a copy of LINE, because PIN_CB uses the same
line++;
nbytes = percent_plus_unescape_inplace (line, 0);
- rc = app_setattr (ctrl->app_ctx, keyword, pin_cb, ctx,
+ rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx,
(const unsigned char*)line, nbytes);
xfree (linebuf);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
"application. The actual certifciate is requested using the inquiry\n"
"\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n"
"\n"
- "In almost all cases a a PIN will be requested. See the related\n"
+ "In almost all cases a PIN will be requested. See the related\n"
"writecert function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
unsigned char *certdata;
size_t certdatalen;
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
line = skip_options (line);
if (!*line)
line++;
*line = 0;
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
xfree (certid);
xfree (certdata);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
static const char hlp_writekey[] =
"WRITEKEY [--force] <keyid> \n"
"\n"
- "This command is used to store a secret key on a a smartcard. The\n"
+ "This command is used to store a secret key on a smartcard. The\n"
"allowed keyids depend on the currently selected smartcard\n"
"application. The actual keydata is requested using the inquiry\n"
"\"KEYDATA\" and need to be provided without any protection. With\n"
unsigned char *keydata;
size_t keydatalen;
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
line = skip_options (line);
if (!*line)
line++;
*line = 0;
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
xfree (keyid);
xfree (keydata);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
const char *s;
time_t timestamp;
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
force = has_option (line, "--force");
if ((s=has_option_name (line, "--timestamp")))
line++;
*line = 0;
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
keyno = xtrystrdup (keyno);
if (!keyno)
return out_of_core ();
- rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0,
+ rc = app_genkey (ctrl->app_ctx, ctrl, keyno, NULL,
+ force? APP_GENKEY_FLAG_FORCE : 0,
timestamp, pin_cb, ctx);
xfree (keyno);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
"number of requested bytes missing");
nbytes = strtoul (line, NULL, 0);
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
if (!buffer)
return out_of_core ();
- rc = app_get_challenge (ctrl->app_ctx, nbytes, buffer);
+ rc = app_get_challenge (ctrl->app_ctx, ctrl, nbytes, buffer);
if (!rc)
{
rc = assuan_send_data (ctx, buffer, nbytes);
}
xfree (buffer);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
\f
static const char hlp_passwd[] =
- "PASSWD [--reset] [--nullpin] <chvno>\n"
+ "PASSWD [--reset] [--nullpin] [--clear] <chvno>\n"
"\n"
"Change the PIN or, if --reset is given, reset the retry counter of\n"
"the card holder verification vector CHVNO. The option --nullpin is\n"
- "used for TCOS cards to set the initial PIN. The format of CHVNO\n"
- "depends on the card application.";
+ "used for TCOS cards to set the initial PIN. The option --clear clears\n"
+ "the security status associated with the PIN so that the PIN needs to\n"
+ "be presented again. The format of CHVNO depends on the card application.";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
flags |= APP_CHANGE_FLAG_RESET;
if (has_option (line, "--nullpin"))
flags |= APP_CHANGE_FLAG_NULLPIN;
-
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
+ if (has_option (line, "--clear"))
+ flags |= APP_CHANGE_FLAG_CLEAR;
line = skip_options (line);
line++;
*line = 0;
- if ((rc = open_card (ctrl, NULL)))
+ /* Do not allow other flags aside of --clear. */
+ if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR))
+ return set_error (GPG_ERR_UNSUPPORTED_OPERATION,
+ "--clear used with other options");
+
+ if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
log_error ("command passwd failed: %s\n", gpg_strerror (rc));
xfree (chvnostr);
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
" entry system, only the regular CHV will get blocked and not the\n"
" dangerous CHV3. IDSTR is the usual card's serial number in hex\n"
" notation; an optional fingerprint part will get ignored. There\n"
- " is however a special mode if the IDSTR is sffixed with the\n"
+ " is however a special mode if the IDSTR is suffixed with the\n"
" literal string \"[CHV3]\": In this case the Admin PIN is checked\n"
" if and only if the retry counter is still at 3.\n"
"\n"
int rc;
char *idstr;
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
if (!ctrl->app_ctx)
if (!idstr)
return out_of_core ();
- rc = app_check_pin (ctrl->app_ctx, idstr, pin_cb, ctx);
+ rc = app_check_pin (ctrl->app_ctx, ctrl, idstr, pin_cb, ctx);
xfree (idstr);
if (rc)
log_error ("app_check_pin failed: %s\n", gpg_strerror (rc));
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
{
rc = 0;
npth_sleep (1); /* Better implement an event mechanism. However,
- for card operations this should be
- sufficient. */
- /* FIXME: Need to check that the connection is still alive.
- This can be done by issuing status messages. */
- goto retry;
+ for card operations this should be
+ sufficient. */
+ /* Send a progress so that we can detect a connection loss. */
+ rc = send_status_printf (ctrl, "PROGRESS", "scd_locked . 0 0");
+ if (!rc)
+ goto retry;
}
#endif /*USE_NPTH*/
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
- "version - Return the version of the program.\n"
- "pid - Return the process id of the server.\n"
- "\n"
- "socket_name - Return the name of the socket.\n"
- "\n"
- "status - Return the status of the current reader (in the future, may\n"
- "also return the status of all readers). The status is a list of\n"
- "one-character flags. The following flags are currently defined:\n"
- " 'u' Usable card present. This is the normal state during operation.\n"
- " 'r' Card removed. A reset is necessary.\n"
- "These flags are exclusive.\n"
- "\n"
- "reader_list - Return a list of detected card readers. Does\n"
- " currently only work with the internal CCID driver.\n"
- "\n"
- "deny_admin - Returns OK if admin commands are not allowed or\n"
- " GPG_ERR_GENERAL if admin commands are allowed.\n"
- "\n"
- "app_list - Return a list of supported applications. One\n"
- " application per line, fields delimited by colons,\n"
- " first field is the name.";
+ " version - Return the version of the program.\n"
+ " pid - Return the process id of the server.\n"
+ " socket_name - Return the name of the socket.\n"
+ " connections - Return number of active connections.\n"
+ " status - Return the status of the current reader (in the future,\n"
+ " may also return the status of all readers). The status\n"
+ " is a list of one-character flags. The following flags\n"
+ " are currently defined:\n"
+ " 'u' Usable card present.\n"
+ " 'r' Card removed. A reset is necessary.\n"
+ " These flags are exclusive.\n"
+ " reader_list - Return a list of detected card readers. Does\n"
+ " currently only work with the internal CCID driver.\n"
+ " deny_admin - Returns OK if admin commands are not allowed or\n"
+ " GPG_ERR_GENERAL if admin commands are allowed.\n"
+ " app_list - Return a list of supported applications. One\n"
+ " application per line, fields delimited by colons,\n"
+ " first field is the name.\n"
+ " card_list - Return a list of serial numbers of active cards,\n"
+ " using a status response.";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
+ else if (!strcmp (line, "connections"))
+ {
+ char numbuf[20];
+
+ snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ());
+ rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
else if (!strcmp (line, "status"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
- int vrdr = ctrl->server_local->vreader_idx;
- char flag = 'r';
+ char flag;
- if (!ctrl->server_local->card_removed && vrdr != -1)
- {
- struct vreader_s *vr;
-
- if (!(vrdr >= 0 && vrdr < DIM(vreader_table)))
- BUG ();
+ if (open_card (ctrl))
+ flag = 'r';
+ else
+ flag = 'u';
- vr = &vreader_table[vrdr];
- if (vr->valid && vr->any && (vr->status & 1))
- flag = 'u';
- }
rc = assuan_send_data (ctx, &flag, 1);
}
else if (!strcmp (line, "reader_list"))
rc = 0;
xfree (s);
}
+ else if (!strcmp (line, "card_list"))
+ {
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ app_send_card_list (ctrl);
+ }
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
cmd_restart (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
+ app_t app = ctrl->app_ctx;
(void)line;
- if (ctrl->app_ctx)
+ if (app)
{
- release_application (ctrl->app_ctx);
ctrl->app_ctx = NULL;
+ release_application (app, 0);
}
if (locked_session && ctrl->server_local == locked_session)
{
static const char hlp_disconnect[] =
"DISCONNECT\n"
"\n"
- "Disconnect the card if it is not any longer used by other\n"
- "connections and the backend supports a disconnect operation.";
+ "Disconnect the card if the backend supports a disconnect operation.";
static gpg_error_t
cmd_disconnect (assuan_context_t ctx, char *line)
{
(void)line;
- ctrl->server_local->disconnect_allowed = 1;
+ if (!ctrl->app_ctx)
+ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+ apdu_disconnect (ctrl->app_ctx->slot);
return 0;
}
cmd_apdu (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
+ app_t app;
int rc;
unsigned char *apdu;
size_t apdulen;
int handle_more;
const char *s;
size_t exlen;
- int slot;
if (has_option (line, "--dump-atr"))
with_atr = 2;
line = skip_options (line);
- if ( IS_LOCKED (ctrl) )
- return gpg_error (GPG_ERR_LOCKED);
-
- if ((rc = open_card (ctrl, NULL)))
+ if ((rc = open_card (ctrl)))
return rc;
- slot = vreader_slot (ctrl->server_local->vreader_idx);
+ app = ctrl->app_ctx;
+ if (!app)
+ return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
if (with_atr)
{
size_t atrlen;
char hexbuf[400];
- atr = apdu_get_atr (slot, &atrlen);
+ atr = apdu_get_atr (app->slot, &atrlen);
if (!atr || atrlen > sizeof hexbuf - 2 )
{
rc = gpg_error (GPG_ERR_INV_CARD);
unsigned char *result = NULL;
size_t resultlen;
- rc = apdu_send_direct (slot, exlen,
+ rc = apdu_send_direct (app->slot, exlen,
apdu, apdulen, handle_more,
- &result, &resultlen);
+ NULL, &result, &resultlen);
if (rc)
log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc));
else
xfree (apdu);
leave:
- TEST_CARD_REMOVAL (ctrl, rc);
return rc;
}
else
{
rc = assuan_init_socket_server (ctx, INT2FD(fd),
- ASSUAN_SOCKET_SERVER_ACCEPTED);
+ ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
session_list = ctrl->server_local;
ctrl->server_local->ctrl_backlink = ctrl;
ctrl->server_local->assuan_ctx = ctx;
- ctrl->server_local->vreader_idx = -1;
-
- /* We open the reader right at startup so that the ticker is able to
- update the status file. */
- if (ctrl->server_local->vreader_idx == -1)
- {
- ctrl->server_local->vreader_idx = get_current_reader ();
- }
/* Command processing loop. */
for (;;)
}
/* Cleanup. We don't send an explicit reset to the card. */
- do_reset (ctrl, 0);
+ do_reset (ctrl, 0, 0);
/* Release the server object. */
if (session_list == ctrl->server_local)
p = buf;
n = 0;
- while ( (value = va_arg (arg_ptr, const unsigned char *)) )
+ while ( (value = va_arg (arg_ptr, const unsigned char *))
+ && n < DIM (buf)-2 )
{
valuelen = va_arg (arg_ptr, size_t);
if (!valuelen)
}
for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
{
- if (*value < ' ' || *value == '+')
+ if (*value == '+' || *value == '\"' || *value == '%'
+ || *value < ' ')
{
sprintf (p, "%%%02X", *value);
p += 3;
+ n += 2;
}
else if (*value == ' ')
*p++ = '+';
}
+/* This status functions expects a printf style format string. No
+ * filtering of the data is done instead the orintf formatted data is
+ * send using assuan_send_status. */
+gpg_error_t
+send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...)
+{
+ gpg_error_t err;
+ va_list arg_ptr;
+ assuan_context_t ctx;
+
+ if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx))
+ return 0;
+
+ va_start (arg_ptr, format);
+ err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
+ va_end (arg_ptr);
+ return err;
+}
+
+
+void
+popup_prompt (void *opaque, int on)
+{
+ ctrl_t ctrl = opaque;
+
+ if (ctrl)
+ {
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+
+ if (ctx)
+ {
+ const char *cmd;
+ gpg_error_t err;
+ unsigned char *value;
+ size_t valuelen;
+
+ if (on)
+ cmd = "POPUPPINPADPROMPT --ack";
+ else
+ cmd = "DISMISSPINPADPROMPT";
+ err = assuan_inquire (ctx, cmd, &value, &valuelen, 100);
+ if (!err)
+ xfree (value);
+ }
+ }
+}
+
+
/* Helper to send the clients a status change notification. */
-static void
-send_client_notifications (void)
+void
+send_client_notifications (app_t app, int removal)
{
struct {
pid_t pid;
struct server_local_s *sl;
for (sl=session_list; sl; sl = sl->next_session)
- {
- if (sl->event_signal && sl->assuan_ctx)
- {
- pid_t pid = assuan_get_pid (sl->assuan_ctx);
+ if (sl->ctrl_backlink && sl->ctrl_backlink->app_ctx == app)
+ {
+ pid_t pid;
#ifdef HAVE_W32_SYSTEM
- HANDLE handle = (void *)sl->event_signal;
-
- for (kidx=0; kidx < killidx; kidx++)
- if (killed[kidx].pid == pid
- && killed[kidx].handle == handle)
- break;
- if (kidx < killidx)
- log_info ("event %lx (%p) already triggered for client %d\n",
- sl->event_signal, handle, (int)pid);
- else
- {
- log_info ("triggering event %lx (%p) for client %d\n",
- sl->event_signal, handle, (int)pid);
- if (!SetEvent (handle))
- log_error ("SetEvent(%lx) failed: %s\n",
- sl->event_signal, w32_strerror (-1));
- if (killidx < DIM (killed))
- {
- killed[killidx].pid = pid;
- killed[killidx].handle = handle;
- killidx++;
- }
- }
-#else /*!HAVE_W32_SYSTEM*/
- int signo = sl->event_signal;
-
- if (pid != (pid_t)(-1) && pid && signo > 0)
- {
- for (kidx=0; kidx < killidx; kidx++)
- if (killed[kidx].pid == pid
- && killed[kidx].signo == signo)
- break;
- if (kidx < killidx)
- log_info ("signal %d already sent to client %d\n",
- signo, (int)pid);
- else
- {
- log_info ("sending signal %d to client %d\n",
- signo, (int)pid);
- kill (pid, signo);
- if (killidx < DIM (killed))
- {
- killed[killidx].pid = pid;
- killed[killidx].signo = signo;
- killidx++;
- }
- }
- }
-#endif /*!HAVE_W32_SYSTEM*/
- }
- }
-}
-
-
-
-/* This is the core of scd_update_reader_status_file but the caller
- needs to take care of the locking. */
-static void
-update_reader_status_file (int set_card_removed_flag)
-{
- int idx;
- unsigned int status, changed;
-
- /* Note, that we only try to get the status, because it does not
- make sense to wait here for a operation to complete. If we are
- busy working with a card, delays in the status file update should
- be acceptable. */
- for (idx=0; idx < DIM(vreader_table); idx++)
- {
- struct vreader_s *vr = vreader_table + idx;
- struct server_local_s *sl;
- int sw_apdu;
+ HANDLE handle;
+#else
+ int signo;
+#endif
- if (!vr->valid || vr->slot == -1)
- continue; /* Not valid or reader not yet open. */
+ if (removal)
+ {
+ sl->ctrl_backlink->app_ctx = NULL;
+ sl->card_removed = 1;
+ release_application (app, 1);
+ }
- sw_apdu = apdu_get_status (vr->slot, 0, &status, &changed);
- if (sw_apdu == SW_HOST_NO_READER)
- {
- /* Most likely the _reader_ has been unplugged. */
- application_notify_card_reset (vr->slot);
- apdu_close_reader (vr->slot);
- vr->slot = -1;
- status = 0;
- changed = vr->changed;
- }
- else if (sw_apdu)
- {
- /* Get status failed. Ignore that. */
+ if (!sl->event_signal || !sl->assuan_ctx)
continue;
- }
- if (!vr->any || vr->status != status || vr->changed != changed )
- {
- char *fname;
- char templ[50];
- FILE *fp;
-
- log_info ("updating reader %d (%d) status: 0x%04X->0x%04X (%u->%u)\n",
- idx, vr->slot, vr->status, status, vr->changed, changed);
- vr->status = status;
- vr->changed = changed;
-
- /* FIXME: Should this be IDX instead of vr->slot? This
- depends on how client sessions will associate the reader
- status with their session. */
- snprintf (templ, sizeof templ, "reader_%d.status", vr->slot);
- fname = make_filename (gnupg_homedir (), templ, NULL );
- fp = fopen (fname, "w");
- if (fp)
- {
- fprintf (fp, "%s\n",
- (status & 1)? "USABLE":
- (status & 4)? "ACTIVE":
- (status & 2)? "PRESENT": "NOCARD");
- fclose (fp);
- }
- xfree (fname);
+ pid = assuan_get_pid (sl->assuan_ctx);
- /* If a status script is executable, run it. */
+#ifdef HAVE_W32_SYSTEM
+ handle = sl->event_signal;
+ for (kidx=0; kidx < killidx; kidx++)
+ if (killed[kidx].pid == pid
+ && killed[kidx].handle == handle)
+ break;
+ if (kidx < killidx)
+ log_info ("event %p (%p) already triggered for client %d\n",
+ sl->event_signal, handle, (int)pid);
+ else
{
- const char *args[9], *envs[2];
- char numbuf1[30], numbuf2[30], numbuf3[30];
- char *homestr, *envstr;
- gpg_error_t err;
-
- homestr = make_filename (gnupg_homedir (), NULL);
- if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0)
- log_error ("out of core while building environment\n");
- else
+ log_info ("triggering event %p (%p) for client %d\n",
+ sl->event_signal, handle, (int)pid);
+ if (!SetEvent (handle))
+ log_error ("SetEvent(%p) failed: %s\n",
+ sl->event_signal, w32_strerror (-1));
+ if (killidx < DIM (killed))
{
- envs[0] = envstr;
- envs[1] = NULL;
-
- sprintf (numbuf1, "%d", vr->slot);
- sprintf (numbuf2, "0x%04X", vr->status);
- sprintf (numbuf3, "0x%04X", status);
- args[0] = "--reader-port";
- args[1] = numbuf1;
- args[2] = "--old-code";
- args[3] = numbuf2;
- args[4] = "--new-code";
- args[5] = numbuf3;
- args[6] = "--status";
- args[7] = ((status & 1)? "USABLE":
- (status & 4)? "ACTIVE":
- (status & 2)? "PRESENT": "NOCARD");
- args[8] = NULL;
-
- fname = make_filename (gnupg_homedir (), "scd-event", NULL);
- err = gnupg_spawn_process_detached (fname, args, envs);
- if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
- log_error ("failed to run event handler '%s': %s\n",
- fname, gpg_strerror (err));
- xfree (fname);
- xfree (envstr);
+ killed[killidx].pid = pid;
+ killed[killidx].handle = handle;
+ killidx++;
}
- xfree (homestr);
}
+#else /*!HAVE_W32_SYSTEM*/
+ signo = sl->event_signal;
- /* Set the card removed flag for all current sessions. */
- if (vr->any && vr->status == 0 && set_card_removed_flag)
- update_card_removed (idx, 1);
-
- vr->any = 1;
-
- /* Send a signal to all clients who applied for it. */
- send_client_notifications ();
- }
-
- /* Check whether a disconnect is pending. */
- if (opt.card_timeout)
- {
- for (sl=session_list; sl; sl = sl->next_session)
- if (!sl->disconnect_allowed)
- break;
- if (session_list && !sl)
- {
- /* FIXME: Use a real timeout. */
- /* At least one connection and all allow a disconnect. */
- log_info ("disconnecting card in reader %d (%d)\n",
- idx, vr->slot);
- apdu_disconnect (vr->slot);
- }
- }
-
- }
-}
-
-/* This function is called by the ticker thread to check for changes
- of the reader stati. It updates the reader status files and if
- requested by the caller also send a signal to the caller. */
-void
-scd_update_reader_status_file (void)
-{
- int err;
- err = npth_mutex_lock (&status_file_update_lock);
- if (err)
- return; /* locked - give up. */
- update_reader_status_file (1);
- err = npth_mutex_unlock (&status_file_update_lock);
- if (err)
- log_error ("failed to release status_file_update lock: %s\n",
- strerror (err));
+ if (pid != (pid_t)(-1) && pid && signo > 0)
+ {
+ for (kidx=0; kidx < killidx; kidx++)
+ if (killed[kidx].pid == pid
+ && killed[kidx].signo == signo)
+ break;
+ if (kidx < killidx)
+ log_info ("signal %d already sent to client %d\n",
+ signo, (int)pid);
+ else
+ {
+ log_info ("sending signal %d to client %d\n",
+ signo, (int)pid);
+ kill (pid, signo);
+ if (killidx < DIM (killed))
+ {
+ killed[killidx].pid = pid;
+ killed[killidx].signo = signo;
+ killidx++;
+ }
+ }
+ }
+#endif /*!HAVE_W32_SYSTEM*/
+ }
}