2002-03-26 11:38:40 +00:00
|
|
|
|
/*
|
|
|
|
|
* reader-ctapi.c: Reader driver for CT-API
|
|
|
|
|
*
|
|
|
|
|
* Copyright (C) 2002 Juha Yrj<EFBFBD>l<EFBFBD> <juha.yrjola@iki.fi>
|
|
|
|
|
*
|
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
*/
|
|
|
|
|
|
2002-04-05 10:44:51 +00:00
|
|
|
|
#include "internal.h"
|
2002-03-26 11:38:40 +00:00
|
|
|
|
#include "ctbcs.h"
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2004-10-18 21:35:24 +00:00
|
|
|
|
#include <opensc/scdl.h>
|
2002-03-26 11:38:40 +00:00
|
|
|
|
|
|
|
|
|
#define GET_SLOT_PTR(s, i) (&(s)->slot[(i)])
|
|
|
|
|
#define GET_PRIV_DATA(r) ((struct ctapi_private_data *) (r)->drv_data)
|
|
|
|
|
#define GET_SLOT_DATA(r) ((struct ctapi_slot_data *) (r)->drv_data)
|
|
|
|
|
|
2005-01-16 14:24:57 +00:00
|
|
|
|
#ifdef _WIN32
|
|
|
|
|
typedef char pascal CT_INIT_TYPE(unsigned short ctn, unsigned short Pn);
|
|
|
|
|
typedef char pascal CT_CLOSE_TYPE(unsigned short ctn);
|
|
|
|
|
typedef char pascal CT_DATA_TYPE(unsigned short ctn, unsigned char *dad, \
|
|
|
|
|
unsigned char *sad, unsigned short lc, \
|
|
|
|
|
unsigned char *cmd, unsigned short *lr, \
|
|
|
|
|
unsigned char *rsp);
|
|
|
|
|
#else
|
|
|
|
|
typedef char CT_INIT_TYPE(unsigned short ctn, unsigned short Pn);
|
|
|
|
|
typedef char CT_CLOSE_TYPE(unsigned short ctn);
|
|
|
|
|
typedef char CT_DATA_TYPE(unsigned short ctn, unsigned char *dad, \
|
|
|
|
|
unsigned char *sad, unsigned short lc, \
|
|
|
|
|
unsigned char *cmd, unsigned short *lr, \
|
|
|
|
|
unsigned char *rsp);
|
|
|
|
|
#endif
|
|
|
|
|
|
2002-03-26 11:38:40 +00:00
|
|
|
|
struct ctapi_module {
|
|
|
|
|
char *name;
|
|
|
|
|
void *dlhandle;
|
|
|
|
|
int ctn_count;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ctapi_global_private_data {
|
|
|
|
|
int module_count;
|
|
|
|
|
struct ctapi_module *modules;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ctapi_functions {
|
2005-01-16 14:24:57 +00:00
|
|
|
|
CT_INIT_TYPE *CT_init;
|
|
|
|
|
CT_CLOSE_TYPE *CT_close;
|
|
|
|
|
CT_DATA_TYPE *CT_data;
|
2002-03-26 11:38:40 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Reader specific private data */
|
|
|
|
|
struct ctapi_private_data {
|
|
|
|
|
struct ctapi_functions funcs;
|
|
|
|
|
unsigned short ctn;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ctapi_slot_data {
|
2002-03-27 13:13:54 +00:00
|
|
|
|
void *filler;
|
2002-03-26 11:38:40 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static int refresh_slot_attributes(struct sc_reader *reader,
|
|
|
|
|
struct sc_slot_info *slot)
|
|
|
|
|
{
|
|
|
|
|
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
|
|
|
|
|
char rv;
|
|
|
|
|
u8 cmd[5], rbuf[256], sad, dad;
|
|
|
|
|
unsigned short lr;
|
|
|
|
|
|
|
|
|
|
cmd[0] = CTBCS_CLA;
|
|
|
|
|
cmd[1] = CTBCS_INS_STATUS;
|
|
|
|
|
cmd[2] = CTBCS_P1_CT_KERNEL;
|
|
|
|
|
cmd[3] = CTBCS_P2_STATUS_ICC;
|
|
|
|
|
cmd[4] = 0x00;
|
|
|
|
|
dad = 1;
|
|
|
|
|
sad = 2;
|
|
|
|
|
lr = 256;
|
|
|
|
|
|
|
|
|
|
slot->flags = 0;
|
|
|
|
|
|
|
|
|
|
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
|
|
|
|
|
if (rv || rbuf[lr-2] != 0x90) {
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(reader->ctx, "Error getting status of terminal: %d\n", rv);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
return SC_ERROR_TRANSMIT_FAILED;
|
|
|
|
|
}
|
|
|
|
|
if (rbuf[0] == CTBCS_DATA_STATUS_CARD_CONNECT)
|
|
|
|
|
slot->flags = SC_SLOT_CARD_PRESENT;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_transmit(struct sc_reader *reader, struct sc_slot_info *slot,
|
|
|
|
|
const u8 *sendbuf, size_t sendsize,
|
2002-12-23 18:47:27 +00:00
|
|
|
|
u8 *recvbuf, size_t *recvsize,
|
|
|
|
|
int control)
|
2002-03-26 11:38:40 +00:00
|
|
|
|
{
|
|
|
|
|
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
|
|
|
|
|
u8 dad, sad;
|
|
|
|
|
unsigned short lr;
|
|
|
|
|
char rv;
|
|
|
|
|
|
2002-12-23 18:47:27 +00:00
|
|
|
|
dad = control? 1 : 0;
|
2002-03-26 11:38:40 +00:00
|
|
|
|
sad = 2;
|
|
|
|
|
lr = *recvsize;
|
|
|
|
|
|
|
|
|
|
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, sendsize, (u8 *) sendbuf, &lr, recvbuf);
|
|
|
|
|
if (rv != 0) {
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(reader->ctx, "Error transmitting APDU: %d\n", rv);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
return SC_ERROR_TRANSMIT_FAILED;
|
|
|
|
|
}
|
|
|
|
|
*recvsize = lr;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_detect_card_presence(struct sc_reader *reader, struct sc_slot_info *slot)
|
|
|
|
|
{
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
r = refresh_slot_attributes(reader, slot);
|
|
|
|
|
if (r)
|
|
|
|
|
return r;
|
2003-01-19 17:47:07 +00:00
|
|
|
|
return slot->flags;
|
2002-03-26 11:38:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_connect(struct sc_reader *reader, struct sc_slot_info *slot)
|
|
|
|
|
{
|
|
|
|
|
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
|
|
|
|
|
char rv;
|
|
|
|
|
u8 cmd[9], rbuf[256], sad, dad;
|
|
|
|
|
unsigned short lr;
|
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
cmd[0] = CTBCS_CLA;
|
|
|
|
|
cmd[1] = CTBCS_INS_REQUEST;
|
|
|
|
|
cmd[2] = CTBCS_P1_INTERFACE1;
|
|
|
|
|
cmd[3] = CTBCS_P2_REQUEST_GET_ATR;
|
|
|
|
|
cmd[4] = 0x00;
|
|
|
|
|
dad = 1;
|
|
|
|
|
sad = 2;
|
|
|
|
|
lr = 256;
|
|
|
|
|
|
|
|
|
|
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
|
|
|
|
|
if (rv || rbuf[lr-2] != 0x90) {
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(reader->ctx, "Error activating card: %d\n", rv);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
return SC_ERROR_TRANSMIT_FAILED;
|
|
|
|
|
}
|
|
|
|
|
if (lr < 2)
|
|
|
|
|
SC_FUNC_RETURN(reader->ctx, 0, SC_ERROR_INTERNAL);
|
|
|
|
|
lr -= 2;
|
|
|
|
|
if (lr > SC_MAX_ATR_SIZE)
|
|
|
|
|
lr = SC_MAX_ATR_SIZE;
|
|
|
|
|
memcpy(slot->atr, rbuf, lr);
|
|
|
|
|
slot->atr_len = lr;
|
|
|
|
|
r = _sc_parse_atr(reader->ctx, slot);
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
if (slot->atr_info.Fi > 0) {
|
|
|
|
|
/* Perform PPS negotiation */
|
|
|
|
|
cmd[1] = CTBCS_INS_RESET;
|
|
|
|
|
cmd[4] = 0x03;
|
|
|
|
|
cmd[5] = 0xFF;
|
|
|
|
|
cmd[6] = 0x10;
|
|
|
|
|
cmd[7] = (slot->atr_info.FI << 4) | slot->atr_info.DI;
|
|
|
|
|
cmd[8] = 0x00;
|
|
|
|
|
dad = 1;
|
|
|
|
|
sad = 2;
|
|
|
|
|
lr = 256;
|
|
|
|
|
|
|
|
|
|
rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 9, cmd, &lr, rbuf);
|
|
|
|
|
if (rv) {
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(reader->ctx, "Error negotiating PPS: %d\n", rv);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
return SC_ERROR_TRANSMIT_FAILED;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_disconnect(struct sc_reader *reader, struct sc_slot_info *slot,
|
|
|
|
|
int action)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_lock(struct sc_reader *reader, struct sc_slot_info *slot)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_unlock(struct sc_reader *reader, struct sc_slot_info *slot)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_release(struct sc_reader *reader)
|
|
|
|
|
{
|
|
|
|
|
struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
|
|
|
|
|
|
2002-04-04 09:20:44 +00:00
|
|
|
|
priv->funcs.CT_close(priv->ctn);
|
|
|
|
|
|
2002-03-26 11:38:40 +00:00
|
|
|
|
free(priv);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct sc_reader_operations ctapi_ops;
|
|
|
|
|
|
2003-12-18 16:35:28 +00:00
|
|
|
|
static struct sc_reader_driver ctapi_drv = {
|
2002-03-26 11:38:40 +00:00
|
|
|
|
"CT-API module",
|
|
|
|
|
"ctapi",
|
|
|
|
|
&ctapi_ops
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct ctapi_module * add_module(struct ctapi_global_private_data *gpriv,
|
|
|
|
|
const char *name, void *dlhandle)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
i = gpriv->module_count;
|
2002-04-19 14:23:31 +00:00
|
|
|
|
gpriv->modules = (struct ctapi_module *) realloc(gpriv->modules, sizeof(struct ctapi_module) * (i+1));
|
2002-03-26 11:38:40 +00:00
|
|
|
|
gpriv->modules[i].name = strdup(name);
|
|
|
|
|
gpriv->modules[i].dlhandle = dlhandle;
|
|
|
|
|
gpriv->modules[i].ctn_count = 0;
|
|
|
|
|
gpriv->module_count++;
|
|
|
|
|
|
|
|
|
|
return &gpriv->modules[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_load_module(struct sc_context *ctx,
|
|
|
|
|
struct ctapi_global_private_data *gpriv,
|
|
|
|
|
scconf_block *conf)
|
|
|
|
|
{
|
|
|
|
|
const char *val;
|
|
|
|
|
struct ctapi_functions funcs;
|
|
|
|
|
struct ctapi_module *mod;
|
|
|
|
|
const scconf_list *list;
|
2002-03-31 15:26:25 +00:00
|
|
|
|
void *dlh;
|
2002-03-26 11:38:40 +00:00
|
|
|
|
int r;
|
|
|
|
|
|
|
|
|
|
list = scconf_find_list(conf, "ports");
|
|
|
|
|
if (list == NULL) {
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(ctx, "No ports configured.\n");
|
2002-03-26 11:38:40 +00:00
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val = conf->name->data;
|
2004-10-18 21:35:24 +00:00
|
|
|
|
dlh = scdl_open(val);
|
|
|
|
|
if (!dlh) {
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(ctx, "Unable to open shared library '%s'\n", val);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
return -1;
|
|
|
|
|
}
|
2004-10-18 21:35:24 +00:00
|
|
|
|
|
2005-01-16 14:24:57 +00:00
|
|
|
|
funcs.CT_init = (CT_INIT_TYPE *) scdl_get_address(dlh, "CT_init");
|
2004-10-18 21:35:24 +00:00
|
|
|
|
if (!funcs.CT_init)
|
2002-03-26 11:38:40 +00:00
|
|
|
|
goto symerr;
|
2005-01-16 14:24:57 +00:00
|
|
|
|
funcs.CT_close = (CT_CLOSE_TYPE *) scdl_get_address(dlh, "CT_close");
|
2004-10-18 21:35:24 +00:00
|
|
|
|
if (!funcs.CT_close)
|
2002-03-26 11:38:40 +00:00
|
|
|
|
goto symerr;
|
2005-01-16 14:24:57 +00:00
|
|
|
|
funcs.CT_data = (CT_DATA_TYPE *) scdl_get_address(dlh, "CT_data");
|
2005-01-04 19:45:05 +00:00
|
|
|
|
if (!funcs.CT_data)
|
2002-03-26 11:38:40 +00:00
|
|
|
|
goto symerr;
|
2004-10-18 21:35:24 +00:00
|
|
|
|
|
2002-03-26 11:38:40 +00:00
|
|
|
|
mod = add_module(gpriv, val, dlh);
|
|
|
|
|
for (; list != NULL; list = list->next) {
|
|
|
|
|
int port;
|
|
|
|
|
char namebuf[128];
|
|
|
|
|
char rv;
|
|
|
|
|
struct sc_reader *reader;
|
|
|
|
|
struct ctapi_private_data *priv;
|
|
|
|
|
struct sc_slot_info *slot;
|
|
|
|
|
|
|
|
|
|
if (sscanf(list->data, "%d", &port) != 1) {
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(ctx, "Port '%s' is not a number.\n", list->data);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
rv = funcs.CT_init(mod->ctn_count, port);
|
|
|
|
|
if (rv) {
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(ctx, "CT_init() failed with %d\n", rv);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2002-04-19 14:23:31 +00:00
|
|
|
|
reader = (struct sc_reader *) malloc(sizeof(struct sc_reader));
|
|
|
|
|
priv = (struct ctapi_private_data *) malloc(sizeof(struct ctapi_private_data));
|
2002-03-26 11:38:40 +00:00
|
|
|
|
memset(reader, 0, sizeof(*reader));
|
|
|
|
|
reader->drv_data = priv;
|
|
|
|
|
reader->ops = &ctapi_ops;
|
|
|
|
|
reader->driver = &ctapi_drv;
|
|
|
|
|
reader->slot_count = 1;
|
|
|
|
|
snprintf(namebuf, sizeof(namebuf), "CT-API %s, port %d", mod->name, port);
|
|
|
|
|
reader->name = strdup(namebuf);
|
|
|
|
|
priv->funcs = funcs;
|
|
|
|
|
priv->ctn = mod->ctn_count;
|
|
|
|
|
r = _sc_add_reader(ctx, reader);
|
|
|
|
|
if (r) {
|
|
|
|
|
funcs.CT_close(mod->ctn_count);
|
|
|
|
|
free(priv);
|
|
|
|
|
free(reader->name);
|
|
|
|
|
free(reader);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
slot = &reader->slot[0];
|
|
|
|
|
slot->id = 0;
|
|
|
|
|
slot->capabilities = 0;
|
|
|
|
|
slot->atr_len = 0;
|
|
|
|
|
slot->drv_data = NULL;
|
|
|
|
|
|
|
|
|
|
refresh_slot_attributes(reader, slot);
|
|
|
|
|
|
|
|
|
|
mod->ctn_count++;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
symerr:
|
2003-08-25 14:21:18 +00:00
|
|
|
|
sc_error(ctx, "Unable to resolve CT-API symbols.\n");
|
2004-10-18 21:35:24 +00:00
|
|
|
|
scdl_close(dlh);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int ctapi_init(struct sc_context *ctx, void **reader_data)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
struct ctapi_global_private_data *gpriv;
|
|
|
|
|
scconf_block **blocks = NULL, *conf_block = NULL;
|
|
|
|
|
|
2002-04-19 14:23:31 +00:00
|
|
|
|
gpriv = (struct ctapi_global_private_data *) malloc(sizeof(struct ctapi_global_private_data));
|
2002-03-26 11:38:40 +00:00
|
|
|
|
if (gpriv == NULL)
|
|
|
|
|
return SC_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
memset(gpriv, 0, sizeof(*gpriv));
|
|
|
|
|
*reader_data = gpriv;
|
|
|
|
|
|
|
|
|
|
for (i = 0; ctx->conf_blocks[i] != NULL; i++) {
|
|
|
|
|
blocks = scconf_find_blocks(ctx->conf, ctx->conf_blocks[i],
|
|
|
|
|
"reader_driver", "ctapi");
|
|
|
|
|
conf_block = blocks[0];
|
|
|
|
|
free(blocks);
|
|
|
|
|
if (conf_block != NULL)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (conf_block == NULL)
|
|
|
|
|
return 0;
|
|
|
|
|
blocks = scconf_find_blocks(ctx->conf, conf_block, "module", NULL);
|
|
|
|
|
for (i = 0; blocks != NULL && blocks[i] != NULL; i++)
|
|
|
|
|
ctapi_load_module(ctx, gpriv, blocks[i]);
|
|
|
|
|
free(blocks);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2002-04-05 14:46:44 +00:00
|
|
|
|
static int ctapi_finish(struct sc_context *ctx, void *prv_data)
|
2002-03-26 11:38:40 +00:00
|
|
|
|
{
|
|
|
|
|
struct ctapi_global_private_data *priv = (struct ctapi_global_private_data *) prv_data;
|
|
|
|
|
|
|
|
|
|
if (priv) {
|
2002-04-05 14:46:44 +00:00
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < priv->module_count; i++) {
|
|
|
|
|
struct ctapi_module *mod = &priv->modules[i];
|
|
|
|
|
|
|
|
|
|
free(mod->name);
|
2004-10-20 06:53:14 +00:00
|
|
|
|
scdl_close(mod->dlhandle);
|
2002-04-05 14:46:44 +00:00
|
|
|
|
}
|
|
|
|
|
if (priv->module_count)
|
|
|
|
|
free(priv->modules);
|
2002-03-26 11:38:40 +00:00
|
|
|
|
free(priv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2003-12-18 16:35:28 +00:00
|
|
|
|
struct sc_reader_driver * sc_get_ctapi_driver(void)
|
2002-03-26 11:38:40 +00:00
|
|
|
|
{
|
|
|
|
|
ctapi_ops.init = ctapi_init;
|
|
|
|
|
ctapi_ops.finish = ctapi_finish;
|
|
|
|
|
ctapi_ops.transmit = ctapi_transmit;
|
|
|
|
|
ctapi_ops.detect_card_presence = ctapi_detect_card_presence;
|
|
|
|
|
ctapi_ops.lock = ctapi_lock;
|
|
|
|
|
ctapi_ops.unlock = ctapi_unlock;
|
|
|
|
|
ctapi_ops.release = ctapi_release;
|
|
|
|
|
ctapi_ops.connect = ctapi_connect;
|
|
|
|
|
ctapi_ops.disconnect = ctapi_disconnect;
|
2003-06-23 12:56:36 +00:00
|
|
|
|
ctapi_ops.perform_verify = ctbcs_pin_cmd;
|
2002-03-26 11:38:40 +00:00
|
|
|
|
|
|
|
|
|
return &ctapi_drv;
|
|
|
|
|
}
|