mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
c2f02797cd
* scd/app.c (app_new_register): Handle serial number correctly. -- GnuPG-bug-id: 5442 Signed-off-by: NIIBE Yutaka <gniibe@fsij.org> Backported-from-master: c3a9ee0b658887ca9baa4514187b17857fdf6586
1357 lines
38 KiB
C
1357 lines
38 KiB
C
/* app.c - Application selection.
|
||
* Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
|
||
*
|
||
* This file is part of GnuPG.
|
||
*
|
||
* GnuPG 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.
|
||
*
|
||
* GnuPG 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 this program; if not, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include <config.h>
|
||
#include <errno.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <npth.h>
|
||
|
||
#include "scdaemon.h"
|
||
#include "../common/exechelp.h"
|
||
#include "iso7816.h"
|
||
#include "apdu.h"
|
||
#include "../common/tlv.h"
|
||
|
||
static npth_mutex_t app_list_lock;
|
||
static app_t app_top;
|
||
|
||
|
||
/* List of all supported apps. */
|
||
static struct
|
||
{
|
||
apptype_t apptype;
|
||
char const *name;
|
||
} supported_app_list[] =
|
||
{{ APPTYPE_OPENPGP , "openpgp" },
|
||
{ APPTYPE_NKS , "nks" },
|
||
{ APPTYPE_P15 , "p15" },
|
||
{ APPTYPE_GELDKARTE, "geldkarte" },
|
||
{ APPTYPE_DINSIG , "dinsig" },
|
||
{ APPTYPE_SC_HSM , "sc-hsm" },
|
||
{ APPTYPE_NONE , NULL }
|
||
/* APPTYPE_UNDEFINED is special and not listed here. */
|
||
};
|
||
|
||
|
||
|
||
static void
|
||
print_progress_line (void *opaque, const char *what, int pc, int cur, int tot)
|
||
{
|
||
ctrl_t ctrl = opaque;
|
||
char line[100];
|
||
|
||
if (ctrl)
|
||
{
|
||
snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot);
|
||
send_status_direct (ctrl, "PROGRESS", line);
|
||
}
|
||
}
|
||
|
||
|
||
/* Map an application type to a string. Never returns NULL. */
|
||
const char *
|
||
strapptype (apptype_t t)
|
||
{
|
||
int i;
|
||
|
||
for (i=0; supported_app_list[i].apptype; i++)
|
||
if (supported_app_list[i].apptype == t)
|
||
return supported_app_list[i].name;
|
||
return t == APPTYPE_UNDEFINED? "undefined" : t? "?" : "none";
|
||
}
|
||
|
||
|
||
/* Return the apptype for NAME. */
|
||
static apptype_t
|
||
apptype_from_name (const char *name)
|
||
{
|
||
int i;
|
||
|
||
if (!name)
|
||
return APPTYPE_NONE;
|
||
|
||
for (i=0; supported_app_list[i].apptype; i++)
|
||
if (!ascii_strcasecmp (supported_app_list[i].name, name))
|
||
return supported_app_list[i].apptype;
|
||
if (!ascii_strcasecmp ("undefined", name))
|
||
return APPTYPE_UNDEFINED;
|
||
return APPTYPE_NONE;
|
||
}
|
||
|
||
|
||
/* Lock the reader SLOT. This function shall be used right before
|
||
calling any of the actual application functions to serialize access
|
||
to the reader. We do this always even if the reader is not
|
||
actually used. This allows an actual connection to assume that it
|
||
never shares a reader (while performing one command). Returns 0 on
|
||
success; only then the unlock_reader function must be called after
|
||
returning from the handler. */
|
||
static gpg_error_t
|
||
lock_app (app_t app, ctrl_t ctrl)
|
||
{
|
||
if (npth_mutex_lock (&app->lock))
|
||
{
|
||
gpg_error_t err = gpg_error_from_syserror ();
|
||
log_error ("failed to acquire APP lock for %p: %s\n",
|
||
app, gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
apdu_set_progress_cb (app->slot, print_progress_line, ctrl);
|
||
apdu_set_prompt_cb (app->slot, popup_prompt, ctrl);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Release a lock on the reader. See lock_reader(). */
|
||
static void
|
||
unlock_app (app_t app)
|
||
{
|
||
apdu_set_progress_cb (app->slot, NULL, NULL);
|
||
apdu_set_prompt_cb (app->slot, NULL, NULL);
|
||
|
||
if (npth_mutex_unlock (&app->lock))
|
||
{
|
||
gpg_error_t err = gpg_error_from_syserror ();
|
||
log_error ("failed to release APP lock for %p: %s\n",
|
||
app, gpg_strerror (err));
|
||
}
|
||
}
|
||
|
||
|
||
/* This function may be called to print information pertaining to the
|
||
current state of this module to the log. */
|
||
void
|
||
app_dump_state (void)
|
||
{
|
||
app_t a;
|
||
|
||
npth_mutex_lock (&app_list_lock);
|
||
for (a = app_top; a; a = a->next)
|
||
log_info ("app_dump_state: app=%p type='%s'\n", a, strapptype (a->apptype));
|
||
npth_mutex_unlock (&app_list_lock);
|
||
}
|
||
|
||
/* Check whether the application NAME is allowed. This does not mean
|
||
we have support for it though. */
|
||
static int
|
||
is_app_allowed (const char *name)
|
||
{
|
||
strlist_t l;
|
||
|
||
for (l=opt.disabled_applications; l; l = l->next)
|
||
if (!strcmp (l->d, name))
|
||
return 0; /* no */
|
||
return 1; /* yes */
|
||
}
|
||
|
||
|
||
static gpg_error_t
|
||
check_conflict (app_t app, const char *name)
|
||
{
|
||
if (!app || !name
|
||
|| (app->apptype && app->apptype == apptype_from_name (name)))
|
||
return 0;
|
||
|
||
if (app->apptype && app->apptype == APPTYPE_UNDEFINED)
|
||
return 0;
|
||
|
||
log_info ("application '%s' in use - can't switch\n",
|
||
strapptype (app->apptype));
|
||
|
||
return gpg_error (GPG_ERR_CONFLICT);
|
||
}
|
||
|
||
/* This function is used by the serialno command to check for an
|
||
application conflict which may appear if the serialno command is
|
||
used to request a specific application and the connection has
|
||
already done a select_application. */
|
||
gpg_error_t
|
||
check_application_conflict (const char *name, app_t app)
|
||
{
|
||
return check_conflict (app, name);
|
||
}
|
||
|
||
|
||
gpg_error_t
|
||
app_reset (app_t app, ctrl_t ctrl, int send_reset)
|
||
{
|
||
gpg_error_t err = 0;
|
||
|
||
if (send_reset)
|
||
{
|
||
int sw;
|
||
|
||
lock_app (app, ctrl);
|
||
sw = apdu_reset (app->slot);
|
||
if (sw)
|
||
err = gpg_error (GPG_ERR_CARD_RESET);
|
||
|
||
app->reset_requested = 1;
|
||
unlock_app (app);
|
||
|
||
scd_kick_the_loop ();
|
||
gnupg_sleep (1);
|
||
}
|
||
else
|
||
{
|
||
ctrl->app_ctx = NULL;
|
||
release_application (app, 0);
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
static gpg_error_t
|
||
app_new_register (int slot, ctrl_t ctrl, const char *name,
|
||
int periodical_check_needed)
|
||
{
|
||
gpg_error_t err = 0;
|
||
app_t app = NULL;
|
||
unsigned char *result = NULL;
|
||
size_t resultlen;
|
||
int want_undefined;
|
||
|
||
/* Need to allocate a new one. */
|
||
app = xtrycalloc (1, sizeof *app);
|
||
if (!app)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_info ("error allocating context: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
app->slot = slot;
|
||
app->card_status = (unsigned int)-1;
|
||
|
||
if (npth_mutex_init (&app->lock, NULL))
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error ("error initializing mutex: %s\n", gpg_strerror (err));
|
||
xfree (app);
|
||
return err;
|
||
}
|
||
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
{
|
||
xfree (app);
|
||
return err;
|
||
}
|
||
|
||
want_undefined = (name && !strcmp (name, "undefined"));
|
||
|
||
/* Try to read the GDO file first to get a default serial number.
|
||
We skip this if the undefined application has been requested. */
|
||
if (!want_undefined)
|
||
{
|
||
err = iso7816_select_file (slot, 0x3F00, 1);
|
||
if (gpg_err_code (err) == GPG_ERR_CARD)
|
||
{
|
||
/* Might be SW==0x7D00. Let's test whether it is a Yubikey
|
||
* by selecting its manager application and then reading the
|
||
* config. */
|
||
static char const yk_aid[] =
|
||
{ 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/
|
||
static char const otp_aid[] =
|
||
{ 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01 }; /*OTP*/
|
||
unsigned char *buf;
|
||
size_t buflen;
|
||
const unsigned char *s0;
|
||
unsigned char formfactor;
|
||
size_t n;
|
||
|
||
if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid,
|
||
0x0001)
|
||
&& !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0,
|
||
NULL, &buf, &buflen))
|
||
{
|
||
app->cardtype = CARDTYPE_YUBIKEY;
|
||
if (opt.verbose)
|
||
{
|
||
log_info ("Yubico: config=");
|
||
log_printhex (buf, buflen, "");
|
||
}
|
||
|
||
/* We skip the first byte which seems to be the total
|
||
* length of the config data. */
|
||
if (buflen > 1)
|
||
{
|
||
s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */
|
||
formfactor = (s0 && n == 1)? *s0 : 0;
|
||
|
||
s0 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */
|
||
if (s0 && n <= 4)
|
||
{
|
||
app->serialno = xtrymalloc (3 + 1 + 4);
|
||
if (app->serialno)
|
||
{
|
||
app->serialnolen = 3 + 1 + 4;
|
||
app->serialno[0] = 0xff;
|
||
app->serialno[1] = 0x02;
|
||
app->serialno[2] = 0x0;
|
||
app->serialno[3] = formfactor;
|
||
memset (app->serialno + 4, 0, 4 - n);
|
||
memcpy (app->serialno + 4 + 4 - n, s0, n);
|
||
err = app_munge_serialno (app);
|
||
}
|
||
}
|
||
|
||
s0 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */
|
||
if (s0 && n == 3)
|
||
app->cardversion = ((s0[0]<<16)|(s0[1]<<8)|s0[2]);
|
||
else if (!s0)
|
||
{
|
||
/* No version - this is not a Yubikey 5. We now
|
||
* switch to the OTP app and take the first
|
||
* three bytes of the response as version
|
||
* number. */
|
||
xfree (buf);
|
||
buf = NULL;
|
||
if (!iso7816_select_application_ext (slot,
|
||
otp_aid, sizeof otp_aid,
|
||
1, &buf, &buflen)
|
||
&& buflen > 3)
|
||
app->cardversion = ((buf[0]<<16)|(buf[1]<<8)|buf[2]);
|
||
}
|
||
}
|
||
xfree (buf);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
unsigned char *atr;
|
||
size_t atrlen;
|
||
|
||
/* This is heuristics to identify different implementations. */
|
||
atr = apdu_get_atr (slot, &atrlen);
|
||
if (atr)
|
||
{
|
||
if (atrlen == 21 && atr[2] == 0x11)
|
||
app->cardtype = CARDTYPE_GNUK;
|
||
else if (atrlen == 21 && atr[7] == 0x75)
|
||
app->cardtype = CARDTYPE_ZEITCONTROL;
|
||
xfree (atr);
|
||
}
|
||
}
|
||
|
||
if (!err && app->cardtype != CARDTYPE_YUBIKEY)
|
||
err = iso7816_select_file (slot, 0x2F02, 0);
|
||
if (!err && app->cardtype != CARDTYPE_YUBIKEY)
|
||
err = iso7816_read_binary (slot, 0, 0, &result, &resultlen);
|
||
if (!err && app->cardtype != CARDTYPE_YUBIKEY)
|
||
{
|
||
size_t n;
|
||
const unsigned char *p;
|
||
|
||
p = find_tlv_unchecked (result, resultlen, 0x5A, &n);
|
||
if (p)
|
||
resultlen -= (p-result);
|
||
if (p && n > resultlen && n == 0x0d && resultlen+1 == n)
|
||
{
|
||
/* The object it does not fit into the buffer. This is an
|
||
invalid encoding (or the buffer is too short. However, I
|
||
have some test cards with such an invalid encoding and
|
||
therefore I use this ugly workaround to return something
|
||
I can further experiment with. */
|
||
log_info ("enabling BMI testcard workaround\n");
|
||
n--;
|
||
}
|
||
|
||
if (p && n <= resultlen)
|
||
{
|
||
/* The GDO file is pretty short, thus we simply reuse it for
|
||
storing the serial number. */
|
||
memmove (result, p, n);
|
||
app->serialno = result;
|
||
app->serialnolen = n;
|
||
err = app_munge_serialno (app);
|
||
if (err)
|
||
goto leave;
|
||
}
|
||
else
|
||
xfree (result);
|
||
result = NULL;
|
||
}
|
||
}
|
||
|
||
/* For certain error codes, there is no need to try more. */
|
||
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT
|
||
|| gpg_err_code (err) == GPG_ERR_ENODEV)
|
||
goto leave;
|
||
|
||
/* Figure out the application to use. */
|
||
if (want_undefined)
|
||
{
|
||
/* We switch to the "undefined" application only if explicitly
|
||
requested. */
|
||
app->apptype = APPTYPE_UNDEFINED;
|
||
err = 0;
|
||
}
|
||
else
|
||
err = gpg_error (GPG_ERR_NOT_FOUND);
|
||
|
||
/* Fixme: Use a table like we do in 2.3. */
|
||
if (err && is_app_allowed ("openpgp")
|
||
&& (!name || !strcmp (name, "openpgp")))
|
||
err = app_select_openpgp (app);
|
||
if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks")))
|
||
err = app_select_nks (app);
|
||
if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15")))
|
||
err = app_select_p15 (app);
|
||
if (err && is_app_allowed ("geldkarte")
|
||
&& (!name || !strcmp (name, "geldkarte")))
|
||
err = app_select_geldkarte (app);
|
||
if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig")))
|
||
err = app_select_dinsig (app);
|
||
if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm")))
|
||
err = app_select_sc_hsm (app);
|
||
if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)
|
||
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
|
||
|
||
leave:
|
||
if (err)
|
||
{
|
||
if (name)
|
||
log_info ("can't select application '%s': %s\n",
|
||
name, gpg_strerror (err));
|
||
else
|
||
log_info ("no supported card application found: %s\n",
|
||
gpg_strerror (err));
|
||
unlock_app (app);
|
||
xfree (app);
|
||
return err;
|
||
}
|
||
|
||
app->periodical_check_needed = periodical_check_needed;
|
||
|
||
npth_mutex_lock (&app_list_lock);
|
||
app->next = app_top;
|
||
app_top = app;
|
||
npth_mutex_unlock (&app_list_lock);
|
||
unlock_app (app);
|
||
return 0;
|
||
}
|
||
|
||
/* If called with NAME as NULL, select the best fitting application
|
||
and return a context; otherwise select the application with NAME
|
||
and return a context. Returns an error code and stores NULL at
|
||
R_APP if no application was found or no card is present. */
|
||
gpg_error_t
|
||
select_application (ctrl_t ctrl, const char *name, app_t *r_app,
|
||
int scan, const unsigned char *serialno_bin,
|
||
size_t serialno_bin_len)
|
||
{
|
||
gpg_error_t err = 0;
|
||
app_t a, a_prev = NULL;
|
||
|
||
*r_app = NULL;
|
||
|
||
if (scan || !app_top)
|
||
{
|
||
struct dev_list *l;
|
||
int new_app = 0;
|
||
|
||
/* Scan the devices to find new device(s). */
|
||
err = apdu_dev_list_start (opt.reader_port, &l);
|
||
if (err)
|
||
return err;
|
||
|
||
while (1)
|
||
{
|
||
int slot;
|
||
int periodical_check_needed_this;
|
||
|
||
slot = apdu_open_reader (l, !app_top);
|
||
if (slot < 0)
|
||
break;
|
||
|
||
periodical_check_needed_this = apdu_connect (slot);
|
||
if (periodical_check_needed_this < 0)
|
||
{
|
||
/* We close a reader with no card. */
|
||
err = gpg_error (GPG_ERR_ENODEV);
|
||
}
|
||
else
|
||
{
|
||
err = app_new_register (slot, ctrl, name,
|
||
periodical_check_needed_this);
|
||
new_app++;
|
||
}
|
||
|
||
if (err)
|
||
apdu_close_reader (slot);
|
||
}
|
||
|
||
apdu_dev_list_finish (l);
|
||
|
||
/* If new device(s), kick the scdaemon loop. */
|
||
if (new_app)
|
||
scd_kick_the_loop ();
|
||
}
|
||
|
||
npth_mutex_lock (&app_list_lock);
|
||
for (a = app_top; a; a = a->next)
|
||
{
|
||
lock_app (a, ctrl);
|
||
if (serialno_bin == NULL)
|
||
break;
|
||
if (a->serialnolen == serialno_bin_len
|
||
&& !memcmp (a->serialno, serialno_bin, a->serialnolen))
|
||
break;
|
||
unlock_app (a);
|
||
a_prev = a;
|
||
}
|
||
|
||
if (a)
|
||
{
|
||
err = check_conflict (a, name);
|
||
if (!err)
|
||
{
|
||
a->ref_count++;
|
||
*r_app = a;
|
||
if (a_prev)
|
||
{
|
||
a_prev->next = a->next;
|
||
a->next = app_top;
|
||
app_top = a;
|
||
}
|
||
}
|
||
unlock_app (a);
|
||
}
|
||
else
|
||
err = gpg_error (GPG_ERR_ENODEV);
|
||
|
||
npth_mutex_unlock (&app_list_lock);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
char *
|
||
get_supported_applications (void)
|
||
{
|
||
const char *list[] = {
|
||
"openpgp",
|
||
"nks",
|
||
"p15",
|
||
"geldkarte",
|
||
"dinsig",
|
||
"sc-hsm",
|
||
/* Note: "undefined" is not listed here because it needs special
|
||
treatment by the client. */
|
||
NULL
|
||
};
|
||
int idx;
|
||
size_t nbytes;
|
||
char *buffer, *p;
|
||
|
||
for (nbytes=1, idx=0; list[idx]; idx++)
|
||
nbytes += strlen (list[idx]) + 1 + 1;
|
||
|
||
buffer = xtrymalloc (nbytes);
|
||
if (!buffer)
|
||
return NULL;
|
||
|
||
for (p=buffer, idx=0; list[idx]; idx++)
|
||
if (is_app_allowed (list[idx]))
|
||
p = stpcpy (stpcpy (p, list[idx]), ":\n");
|
||
*p = 0;
|
||
|
||
return buffer;
|
||
}
|
||
|
||
|
||
/* Deallocate the application. */
|
||
static void
|
||
deallocate_app (app_t app)
|
||
{
|
||
app_t a, a_prev = NULL;
|
||
|
||
for (a = app_top; a; a = a->next)
|
||
if (a == app)
|
||
{
|
||
if (a_prev == NULL)
|
||
app_top = a->next;
|
||
else
|
||
a_prev->next = a->next;
|
||
break;
|
||
}
|
||
else
|
||
a_prev = a;
|
||
|
||
if (app->ref_count)
|
||
log_error ("trying to release context used yet (%d)\n", app->ref_count);
|
||
|
||
if (app->fnc.deinit)
|
||
{
|
||
app->fnc.deinit (app);
|
||
app->fnc.deinit = NULL;
|
||
}
|
||
|
||
xfree (app->serialno);
|
||
|
||
unlock_app (app);
|
||
xfree (app);
|
||
}
|
||
|
||
/* Free the resources associated with the application APP. APP is
|
||
allowed to be NULL in which case this is a no-op. Note that we are
|
||
using reference counting to track the users of the application and
|
||
actually deferring the deallocation to allow for a later reuse by
|
||
a new connection. */
|
||
void
|
||
release_application (app_t app, int locked_already)
|
||
{
|
||
if (!app)
|
||
return;
|
||
|
||
/* We don't deallocate app here. Instead, we keep it. This is
|
||
useful so that a card does not get reset even if only one session
|
||
is using the card - this way the PIN cache and other cached data
|
||
are preserved. */
|
||
|
||
if (!locked_already)
|
||
lock_app (app, NULL);
|
||
|
||
if (!app->ref_count)
|
||
log_bug ("trying to release an already released context\n");
|
||
|
||
--app->ref_count;
|
||
if (!locked_already)
|
||
unlock_app (app);
|
||
}
|
||
|
||
|
||
|
||
/* The serial number may need some cosmetics. Do it here. This
|
||
function shall only be called once after a new serial number has
|
||
been put into APP->serialno.
|
||
|
||
Prefixes we use:
|
||
|
||
FF 00 00 = For serial numbers starting with an FF
|
||
FF 01 00 = Some german p15 cards return an empty serial number so the
|
||
serial number from the EF(TokenInfo) is used instead.
|
||
FF 7F 00 = No serialno.
|
||
|
||
All other serial number not starting with FF are used as they are.
|
||
*/
|
||
gpg_error_t
|
||
app_munge_serialno (app_t app)
|
||
{
|
||
if (app->serialnolen && app->serialno[0] == 0xff)
|
||
{
|
||
/* The serial number starts with our special prefix. This
|
||
requires that we put our default prefix "FF0000" in front. */
|
||
unsigned char *p = xtrymalloc (app->serialnolen + 3);
|
||
if (!p)
|
||
return gpg_error_from_syserror ();
|
||
memcpy (p, "\xff\0", 3);
|
||
memcpy (p+3, app->serialno, app->serialnolen);
|
||
app->serialnolen += 3;
|
||
xfree (app->serialno);
|
||
app->serialno = p;
|
||
}
|
||
else if (!app->serialnolen)
|
||
{
|
||
unsigned char *p = xtrymalloc (3);
|
||
if (!p)
|
||
return gpg_error_from_syserror ();
|
||
memcpy (p, "\xff\x7f", 3);
|
||
app->serialnolen = 3;
|
||
xfree (app->serialno);
|
||
app->serialno = p;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/* Retrieve the serial number of the card. The serial number is
|
||
returned as a malloced string (hex encoded) in SERIAL. Caller must
|
||
free SERIAL unless the function returns an error. */
|
||
char *
|
||
app_get_serialno (app_t app)
|
||
{
|
||
char *serial;
|
||
|
||
if (!app)
|
||
return NULL;
|
||
|
||
if (!app->serialnolen)
|
||
serial = xtrystrdup ("FF7F00");
|
||
else
|
||
serial = bin2hex (app->serialno, app->serialnolen, NULL);
|
||
|
||
return serial;
|
||
}
|
||
|
||
|
||
/* Return an allocated string with the serial number in a format to be
|
||
* show to the user. With NOFALLBACK set to true return NULL if such an
|
||
* abbreviated S/N is not available, else return the full serial
|
||
* number as a hex string. May return NULL on malloc problem. */
|
||
char *
|
||
app_get_dispserialno (app_t app, int nofallback)
|
||
{
|
||
char *result, *p;
|
||
unsigned long sn;
|
||
|
||
if (app && app->serialno && app->serialnolen == 3+1+4
|
||
&& !memcmp (app->serialno, "\xff\x02\x00", 3))
|
||
{
|
||
/* This is a 4 byte S/N of a Yubikey which seems to be printed
|
||
* on the token in decimal. Maybe they will print larger S/N
|
||
* also in decimal but we can't be sure, thus do it only for
|
||
* these 32 bit numbers. */
|
||
sn = app->serialno[4] * 16777216;
|
||
sn += app->serialno[5] * 65536;
|
||
sn += app->serialno[6] * 256;
|
||
sn += app->serialno[7];
|
||
if ((app->cardversion >> 16) >= 5)
|
||
result = xtryasprintf ("%lu %03lu %03lu",
|
||
(sn/1000000ul),
|
||
(sn/1000ul % 1000ul),
|
||
(sn % 1000ul));
|
||
else
|
||
result = xtryasprintf ("%lu", sn);
|
||
}
|
||
else if (app && app->cardtype == CARDTYPE_YUBIKEY)
|
||
{
|
||
/* Get back the printed Yubikey number from the OpenPGP AID
|
||
* Example: D2760001240100000006120808620000
|
||
*/
|
||
result = app_get_serialno (app);
|
||
if (result && strlen (result) >= 28 && !strncmp (result+16, "0006", 4))
|
||
{
|
||
sn = atoi_4 (result+20) * 10000;
|
||
sn += atoi_4 (result+24);
|
||
if ((app->cardversion >> 16) >= 5)
|
||
p = xtryasprintf ("%lu %03lu %03lu",
|
||
(sn/1000000ul),
|
||
(sn/1000ul % 1000ul),
|
||
(sn % 1000ul));
|
||
else
|
||
p = xtryasprintf ("%lu", sn);
|
||
if (p)
|
||
{
|
||
xfree (result);
|
||
result = p;
|
||
}
|
||
}
|
||
else if (nofallback)
|
||
{
|
||
xfree (result);
|
||
result = NULL;
|
||
}
|
||
}
|
||
else if (app && app->apptype == APPTYPE_OPENPGP)
|
||
{
|
||
/* Extract number from standard OpenPGP AID. */
|
||
result = app_get_serialno (app);
|
||
if (result && strlen (result) > 16+12)
|
||
{
|
||
memcpy (result, result+16, 4);
|
||
result[4] = ' ';
|
||
memcpy (result+5, result+20, 8);
|
||
result[13] = 0;
|
||
}
|
||
else if (nofallback)
|
||
{
|
||
xfree (result);
|
||
result = NULL;
|
||
}
|
||
}
|
||
else if (nofallback)
|
||
result = NULL; /* No Abbreviated S/N. */
|
||
else
|
||
result = app_get_serialno (app);
|
||
|
||
return result;
|
||
}
|
||
|
||
|
||
/* Write out the application specifig status lines for the LEARN
|
||
command. */
|
||
gpg_error_t
|
||
app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->fnc.learn_status)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
|
||
/* We do not send APPTYPE if only keypairinfo is requested. */
|
||
if (app->apptype && !(flags & 1))
|
||
send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype));
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.learn_status (app, ctrl, flags);
|
||
unlock_app (app);
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Read the certificate with id CERTID (as returned by learn_status in
|
||
the CERTINFO status lines) and return it in the freshly allocated
|
||
buffer put into CERT and the length of the certificate put into
|
||
CERTLEN. */
|
||
gpg_error_t
|
||
app_readcert (app_t app, ctrl_t ctrl, const char *certid,
|
||
unsigned char **cert, size_t *certlen)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.readcert)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.readcert (app, certid, cert, certlen);
|
||
unlock_app (app);
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Read the key with ID KEYID. On success a canonical encoded
|
||
S-expression with the public key will get stored at PK and its
|
||
length (for assertions) at PKLEN; the caller must release that
|
||
buffer. On error NULL will be stored at PK and PKLEN and an error
|
||
code returned.
|
||
|
||
This function might not be supported by all applications. */
|
||
gpg_error_t
|
||
app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid,
|
||
unsigned char **pk, size_t *pklen)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (pk)
|
||
*pk = NULL;
|
||
if (pklen)
|
||
*pklen = 0;
|
||
|
||
if (!app || !keyid || !pk || !pklen)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.readkey)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err= app->fnc.readkey (app, ctrl, keyid,
|
||
advanced? APP_READKEY_FLAG_ADVANCED : 0,
|
||
pk, pklen);
|
||
unlock_app (app);
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Perform a GETATTR operation. */
|
||
gpg_error_t
|
||
app_getattr (app_t app, ctrl_t ctrl, const char *name)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !name || !*name)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
|
||
if (app->apptype && name && !strcmp (name, "APPTYPE"))
|
||
{
|
||
send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype));
|
||
return 0;
|
||
}
|
||
if (name && !strcmp (name, "SERIALNO"))
|
||
{
|
||
char *serial;
|
||
|
||
serial = app_get_serialno (app);
|
||
if (!serial)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
|
||
send_status_direct (ctrl, "SERIALNO", serial);
|
||
xfree (serial);
|
||
return 0;
|
||
}
|
||
|
||
if (!app->fnc.getattr)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.getattr (app, ctrl, name);
|
||
unlock_app (app);
|
||
return err;
|
||
}
|
||
|
||
/* Perform a SETATTR operation. */
|
||
gpg_error_t
|
||
app_setattr (app_t app, ctrl_t ctrl, const char *name,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg,
|
||
const unsigned char *value, size_t valuelen)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !name || !*name || !value)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.setattr)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.setattr (app, ctrl, name, pincb, pincb_arg, value, valuelen);
|
||
unlock_app (app);
|
||
return err;
|
||
}
|
||
|
||
/* Create the signature and return the allocated result in OUTDATA.
|
||
If a PIN is required the PINCB will be used to ask for the PIN; it
|
||
should return the PIN in an allocated buffer and put it into PIN. */
|
||
gpg_error_t
|
||
app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg,
|
||
const void *indata, size_t indatalen,
|
||
unsigned char **outdata, size_t *outdatalen )
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.sign)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.sign (app, ctrl, keyidstr, hashalgo,
|
||
pincb, pincb_arg,
|
||
indata, indatalen,
|
||
outdata, outdatalen);
|
||
unlock_app (app);
|
||
if (opt.verbose)
|
||
log_info ("operation sign result: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
/* Create the signature using the INTERNAL AUTHENTICATE command and
|
||
return the allocated result in OUTDATA. If a PIN is required the
|
||
PINCB will be used to ask for the PIN; it should return the PIN in
|
||
an allocated buffer and put it into PIN. */
|
||
gpg_error_t
|
||
app_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg,
|
||
const void *indata, size_t indatalen,
|
||
unsigned char **outdata, size_t *outdatalen )
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.auth)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.auth (app, ctrl, keyidstr,
|
||
pincb, pincb_arg,
|
||
indata, indatalen,
|
||
outdata, outdatalen);
|
||
unlock_app (app);
|
||
if (opt.verbose)
|
||
log_info ("operation auth result: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
|
||
If a PIN is required the PINCB will be used to ask for the PIN; it
|
||
should return the PIN in an allocated buffer and put it into PIN. */
|
||
gpg_error_t
|
||
app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg,
|
||
const void *indata, size_t indatalen,
|
||
unsigned char **outdata, size_t *outdatalen,
|
||
unsigned int *r_info)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
*r_info = 0;
|
||
|
||
if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.decipher)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.decipher (app, ctrl, keyidstr,
|
||
pincb, pincb_arg,
|
||
indata, indatalen,
|
||
outdata, outdatalen,
|
||
r_info);
|
||
unlock_app (app);
|
||
if (opt.verbose)
|
||
log_info ("operation decipher result: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Perform the WRITECERT operation. */
|
||
gpg_error_t
|
||
app_writecert (app_t app, ctrl_t ctrl,
|
||
const char *certidstr,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg,
|
||
const unsigned char *data, size_t datalen)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !certidstr || !*certidstr || !pincb)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.writecert)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.writecert (app, ctrl, certidstr,
|
||
pincb, pincb_arg, data, datalen);
|
||
unlock_app (app);
|
||
if (opt.verbose)
|
||
log_info ("operation writecert result: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Perform the WRITEKEY operation. */
|
||
gpg_error_t
|
||
app_writekey (app_t app, ctrl_t ctrl,
|
||
const char *keyidstr, unsigned int flags,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg,
|
||
const unsigned char *keydata, size_t keydatalen)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !keyidstr || !*keyidstr || !pincb)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.writekey)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.writekey (app, ctrl, keyidstr, flags,
|
||
pincb, pincb_arg, keydata, keydatalen);
|
||
unlock_app (app);
|
||
if (opt.verbose)
|
||
log_info ("operation writekey result: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Perform a SETATTR operation. */
|
||
gpg_error_t
|
||
app_genkey (app_t app, ctrl_t ctrl, const char *keynostr,
|
||
const char *keytype, unsigned int flags, time_t createtime,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !keynostr || !*keynostr || !pincb)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.genkey)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.genkey (app, ctrl, keynostr, keytype, flags,
|
||
createtime, pincb, pincb_arg);
|
||
unlock_app (app);
|
||
if (opt.verbose)
|
||
log_info ("operation genkey result: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Perform a GET CHALLENGE operation. This function is special as it
|
||
directly accesses the card without any application specific
|
||
wrapper. */
|
||
gpg_error_t
|
||
app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !nbytes || !buffer)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = iso7816_get_challenge (app->slot, nbytes, buffer);
|
||
unlock_app (app);
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */
|
||
gpg_error_t
|
||
app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr,
|
||
unsigned int flags,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !chvnostr || !*chvnostr || !pincb)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.change_pin)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.change_pin (app, ctrl, chvnostr, flags, pincb, pincb_arg);
|
||
unlock_app (app);
|
||
if (opt.verbose)
|
||
log_info ("operation change_pin result: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Perform a VERIFY operation without doing anything else. This may
|
||
be used to initialize a the PIN cache for long lasting other
|
||
operations. Its use is highly application dependent. */
|
||
gpg_error_t
|
||
app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr,
|
||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||
void *pincb_arg)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (!app || !keyidstr || !*keyidstr || !pincb)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (!app->ref_count)
|
||
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
|
||
if (!app->fnc.check_pin)
|
||
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
|
||
err = lock_app (app, ctrl);
|
||
if (err)
|
||
return err;
|
||
err = app->fnc.check_pin (app, ctrl, keyidstr, pincb, pincb_arg);
|
||
unlock_app (app);
|
||
if (opt.verbose)
|
||
log_info ("operation check_pin result: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
static void
|
||
report_change (int slot, int old_status, int cur_status)
|
||
{
|
||
char *homestr, *envstr;
|
||
char *fname;
|
||
char templ[50];
|
||
estream_t fp;
|
||
|
||
snprintf (templ, sizeof templ, "reader_%d.status", slot);
|
||
fname = make_filename (gnupg_homedir (), templ, NULL );
|
||
fp = es_fopen (fname, "w");
|
||
if (fp)
|
||
{
|
||
es_fprintf (fp, "%s\n",
|
||
(cur_status & 1)? "USABLE":
|
||
(cur_status & 4)? "ACTIVE":
|
||
(cur_status & 2)? "PRESENT": "NOCARD");
|
||
es_fclose (fp);
|
||
}
|
||
xfree (fname);
|
||
|
||
homestr = make_filename (gnupg_homedir (), NULL);
|
||
if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0)
|
||
log_error ("out of core while building environment\n");
|
||
else
|
||
{
|
||
gpg_error_t err;
|
||
const char *args[9], *envs[2];
|
||
char numbuf1[30], numbuf2[30], numbuf3[30];
|
||
|
||
envs[0] = envstr;
|
||
envs[1] = NULL;
|
||
|
||
sprintf (numbuf1, "%d", slot);
|
||
sprintf (numbuf2, "0x%04X", old_status);
|
||
sprintf (numbuf3, "0x%04X", cur_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] = ((cur_status & 1)? "USABLE":
|
||
(cur_status & 4)? "ACTIVE":
|
||
(cur_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);
|
||
}
|
||
xfree (homestr);
|
||
}
|
||
|
||
int
|
||
scd_update_reader_status_file (void)
|
||
{
|
||
app_t a, app_next;
|
||
int periodical_check_needed = 0;
|
||
|
||
npth_mutex_lock (&app_list_lock);
|
||
for (a = app_top; a; a = app_next)
|
||
{
|
||
int sw;
|
||
unsigned int status;
|
||
|
||
lock_app (a, NULL);
|
||
app_next = a->next;
|
||
|
||
if (a->reset_requested)
|
||
status = 0;
|
||
else
|
||
{
|
||
sw = apdu_get_status (a->slot, 0, &status);
|
||
if (sw == SW_HOST_NO_READER)
|
||
{
|
||
/* Most likely the _reader_ has been unplugged. */
|
||
status = 0;
|
||
}
|
||
else if (sw)
|
||
{
|
||
/* Get status failed. Ignore that. */
|
||
if (a->periodical_check_needed)
|
||
periodical_check_needed = 1;
|
||
unlock_app (a);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (a->card_status != status)
|
||
{
|
||
report_change (a->slot, a->card_status, status);
|
||
send_client_notifications (a, status == 0);
|
||
|
||
if (status == 0)
|
||
{
|
||
log_debug ("Removal of a card: %d\n", a->slot);
|
||
apdu_close_reader (a->slot);
|
||
deallocate_app (a);
|
||
}
|
||
else
|
||
{
|
||
a->card_status = status;
|
||
if (a->periodical_check_needed)
|
||
periodical_check_needed = 1;
|
||
unlock_app (a);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (a->periodical_check_needed)
|
||
periodical_check_needed = 1;
|
||
unlock_app (a);
|
||
}
|
||
}
|
||
npth_mutex_unlock (&app_list_lock);
|
||
|
||
return periodical_check_needed;
|
||
}
|
||
|
||
/* 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. */
|
||
gpg_error_t
|
||
initialize_module_command (void)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
if (npth_mutex_init (&app_list_lock, NULL))
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error ("app: error initializing mutex: %s\n", gpg_strerror (err));
|
||
return err;
|
||
}
|
||
|
||
return apdu_init ();
|
||
}
|
||
|
||
void
|
||
app_send_card_list (ctrl_t ctrl)
|
||
{
|
||
app_t a;
|
||
char buf[65];
|
||
|
||
npth_mutex_lock (&app_list_lock);
|
||
for (a = app_top; a; a = a->next)
|
||
{
|
||
if (DIM (buf) < 2 * a->serialnolen + 1)
|
||
continue;
|
||
|
||
bin2hex (a->serialno, a->serialnolen, buf);
|
||
send_status_direct (ctrl, "SERIALNO", buf);
|
||
}
|
||
npth_mutex_unlock (&app_list_lock);
|
||
}
|