Coverage Report

Created: 2026-02-26 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opensc/src/libopensc/reader-ctapi.c
Line
Count
Source
1
/*
2
 * reader-ctapi.c: Reader driver for CT-API
3
 *
4
 * Copyright (C) 2002  Juha Yrjölä <juha.yrjola@iki.fi>
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * This library is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with this library; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
 */
20
21
#ifdef HAVE_CONFIG_H
22
#include "config.h"
23
#endif
24
25
#ifdef ENABLE_CTAPI
26
#include <assert.h>
27
#include <stdlib.h>
28
#include <string.h>
29
30
#include "common/libscdl.h"
31
#include "internal.h"
32
#include "ctbcs.h"
33
34
0
#define GET_PRIV_DATA(r) ((struct ctapi_private_data *) (r)->drv_data)
35
36
#ifdef _WIN32
37
typedef char pascal CT_INIT_TYPE(unsigned short ctn, unsigned short Pn);
38
typedef char pascal CT_CLOSE_TYPE(unsigned short ctn);
39
typedef char pascal CT_DATA_TYPE(unsigned short ctn, unsigned char *dad, \
40
       unsigned char *sad, unsigned short lc, \
41
       unsigned char *cmd, unsigned short *lr, \
42
       unsigned char *rsp);
43
#else
44
typedef char CT_INIT_TYPE(unsigned short ctn, unsigned short Pn);
45
typedef char CT_CLOSE_TYPE(unsigned short ctn);
46
typedef char CT_DATA_TYPE(unsigned short ctn, unsigned char *dad, \
47
       unsigned char *sad, unsigned short lc, \
48
       unsigned char *cmd, unsigned short *lr, \
49
       unsigned char *rsp);
50
#endif
51
52
struct ctapi_module {
53
  char *name;
54
  void *dlhandle;
55
  int ctn_count;
56
};
57
58
struct ctapi_global_private_data {
59
  int module_count;
60
  struct ctapi_module *modules;
61
};
62
63
struct ctapi_functions {
64
  CT_INIT_TYPE *CT_init;
65
  CT_CLOSE_TYPE *CT_close;
66
  CT_DATA_TYPE *CT_data;
67
};
68
69
/* Reader specific private data */
70
0
#define CTAPI_FU_KEYBOARD 0x1
71
0
#define CTAPI_FU_DISPLAY  0x2
72
0
#define CTAPI_FU_BIOMETRIC  0x4
73
0
#define CTAPI_FU_PRINTER  0x8
74
75
struct ctapi_private_data {
76
  struct ctapi_functions funcs;
77
  unsigned short ctn;
78
  int ctapi_functional_units;
79
  int slot;
80
};
81
82
/* Reset reader */
83
static int ctapi_reset(sc_reader_t *reader)
84
0
{
85
0
  struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
86
0
  char rv;
87
0
  u8 cmd[5], rbuf[256], sad, dad;
88
0
  unsigned short lr;
89
90
0
  cmd[0] = CTBCS_CLA;
91
0
  cmd[1] = CTBCS_INS_RESET;
92
0
  cmd[2] = priv->slot ? CTBCS_P1_INTERFACE1 + priv->slot : CTBCS_P1_CT_KERNEL;
93
0
  cmd[3] = 0x00; /* No response. We might also use 0x01 (return ATR) or 0x02 (return historical bytes) here */
94
0
  cmd[4] = 0x00;
95
0
  dad = 1;
96
0
  sad = 2;
97
0
  lr = 256;
98
99
0
  rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
100
0
  if (rv || (lr < 2)) {
101
0
    sc_log(reader->ctx, "Error getting status of terminal: %d, using defaults", rv);
102
0
    return SC_ERROR_TRANSMIT_FAILED;
103
0
  }
104
0
  if (rbuf[lr-2] != 0x90) {
105
0
    sc_log(reader->ctx, "SW1/SW2: 0x%x/0x%x", rbuf[lr-2], rbuf[lr-1]);
106
0
    return SC_ERROR_TRANSMIT_FAILED;
107
0
  }
108
0
  return 0;
109
0
}
110
111
112
static int refresh_attributes(sc_reader_t *reader)
113
0
{
114
0
  struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
115
0
  char rv;
116
0
  u8 cmd[5], rbuf[256], sad, dad;
117
0
  unsigned short lr;
118
119
0
  if (reader->ctx->flags & SC_CTX_FLAG_TERMINATE)
120
0
    return SC_ERROR_NOT_ALLOWED;
121
122
0
  cmd[0] = CTBCS_CLA;
123
0
  cmd[1] = CTBCS_INS_STATUS;
124
0
  cmd[2] = CTBCS_P1_CT_KERNEL;
125
0
  cmd[3] = CTBCS_P2_STATUS_ICC;
126
0
  cmd[4] = 0x00;
127
0
  dad = 1;
128
0
  sad = 2;
129
0
  lr = 256;
130
131
0
  reader->flags = 0;
132
133
0
  rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
134
0
  if (rv || (lr < 3) || (rbuf[lr-2] != 0x90)) {
135
0
    sc_log(reader->ctx, "Error getting status of terminal: %d/%d/0x%x", rv, lr, rbuf[lr-2]);
136
0
    return SC_ERROR_TRANSMIT_FAILED;
137
0
  }
138
0
  if (lr < 4) {
139
0
    if (rbuf[0] & CTBCS_DATA_STATUS_CARD)
140
0
      reader->flags = SC_READER_CARD_PRESENT;
141
0
  } else {
142
0
    if (rbuf[0] != CTBCS_P2_STATUS_ICC) {
143
      /* Should we be more tolerant here? I do not think so... */
144
0
      sc_log(reader->ctx, "Invalid data object returned on CTBCS_P2_STATUS_ICC: 0x%x", rbuf[0]);
145
0
    return SC_ERROR_TRANSMIT_FAILED;
146
0
    }
147
    /* Fixme - should not be reached */
148
0
    sc_log(reader->ctx, "Returned status for  %d slots", rbuf[1]);
149
0
    reader->flags = SC_READER_CARD_PRESENT;
150
0
  }
151
152
0
  return 0;
153
0
}
154
155
static int ctapi_internal_transmit(sc_reader_t *reader,
156
       const u8 *sendbuf, size_t sendsize,
157
       u8 *recvbuf, size_t *recvsize,
158
       unsigned long control)
159
0
{
160
0
  struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
161
0
  u8 dad, sad;
162
0
  unsigned short lr;
163
0
  char rv;
164
165
0
  if (reader->ctx->flags & SC_CTX_FLAG_TERMINATE)
166
0
    return SC_ERROR_NOT_ALLOWED;
167
168
0
  if (control)
169
0
    dad = 1;
170
0
  else
171
0
    dad = 0;
172
173
0
  sad = 2;
174
0
  lr = *recvsize;
175
176
0
  rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, (unsigned short)sendsize, (u8 *) sendbuf, &lr, recvbuf);
177
0
  if (rv != 0) {
178
0
    sc_log(reader->ctx, "Error transmitting APDU: %d", rv);
179
0
    return SC_ERROR_TRANSMIT_FAILED;
180
0
  }
181
0
  *recvsize = lr;
182
183
0
  return 0;
184
0
}
185
186
static int ctapi_transmit(sc_reader_t *reader, sc_apdu_t *apdu)
187
0
{
188
0
  size_t ssize, rsize, rbuflen = 0;
189
0
  u8 *sbuf = NULL, *rbuf = NULL;
190
0
  int r;
191
192
0
  rsize = rbuflen = apdu->resplen + 2;
193
0
  rbuf = malloc(rbuflen);
194
0
  if (rbuf == NULL) {
195
0
    r = SC_ERROR_OUT_OF_MEMORY;
196
0
    goto out;
197
0
  }
198
  /* encode and log the APDU */
199
0
  r = sc_apdu_get_octets(reader->ctx, apdu, &sbuf, &ssize, SC_PROTO_RAW);
200
0
  if (r != SC_SUCCESS)
201
0
    goto out;
202
0
  sc_apdu_log(reader->ctx, sbuf, ssize, 1);
203
0
  r = ctapi_internal_transmit(reader, sbuf, ssize,
204
0
          rbuf, &rsize, apdu->control);
205
0
  if (r < 0) {
206
    /* unable to transmit ... most likely a reader problem */
207
0
    sc_log(reader->ctx, "unable to transmit");
208
0
    goto out;
209
0
  }
210
0
  sc_apdu_log(reader->ctx, rbuf, rsize, 0);
211
  /* set response */
212
0
  r = sc_apdu_set_resp(reader->ctx, apdu, rbuf, rsize);
213
0
out:
214
0
  if (sbuf != NULL) {
215
0
    sc_mem_clear(sbuf, ssize);
216
0
    free(sbuf);
217
0
  }
218
0
  if (rbuf != NULL) {
219
0
    sc_mem_clear(rbuf, rbuflen);
220
0
    free(rbuf);
221
0
  }
222
223
0
  return r;
224
0
}
225
226
static int ctapi_detect_card_presence(sc_reader_t *reader)
227
0
{
228
0
  int r;
229
230
0
  r = refresh_attributes(reader);
231
0
  if (r)
232
0
    return r;
233
0
  return reader->flags;
234
0
}
235
236
static int ctapi_connect(sc_reader_t *reader)
237
0
{
238
0
  struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
239
0
  char rv;
240
0
  u8 cmd[9], rbuf[256], sad, dad;
241
0
  unsigned short lr;
242
243
0
  if (reader->ctx->flags & SC_CTX_FLAG_TERMINATE)
244
0
    return SC_ERROR_NOT_ALLOWED;
245
246
0
  cmd[0] = CTBCS_CLA;
247
0
  cmd[1] = CTBCS_INS_REQUEST;
248
0
  cmd[2] = CTBCS_P1_INTERFACE1;
249
0
  cmd[3] = CTBCS_P2_REQUEST_GET_ATR;
250
0
  cmd[4] = 0x00;
251
0
  dad = 1;
252
0
  sad = 2;
253
0
  lr = 256;
254
255
0
  rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
256
0
  if (rv || rbuf[lr-2] != 0x90) {
257
0
    sc_log(reader->ctx, "Error activating card: %d", rv);
258
0
    return SC_ERROR_TRANSMIT_FAILED;
259
0
  }
260
0
  if (lr < 2)
261
0
    LOG_FUNC_RETURN(reader->ctx, SC_ERROR_INTERNAL);
262
0
  lr -= 2;
263
0
  if (lr > SC_MAX_ATR_SIZE)
264
0
    return SC_ERROR_INTERNAL;
265
0
  reader->atr.len = lr;
266
0
  memcpy(reader->atr.value, rbuf, lr);
267
0
  _sc_parse_atr(reader);
268
269
0
  return 0;
270
0
}
271
272
static int ctapi_disconnect(sc_reader_t *reader)
273
0
{
274
0
  return 0;
275
0
}
276
277
static int ctapi_lock(sc_reader_t *reader)
278
0
{
279
0
  return 0;
280
0
}
281
282
static int ctapi_unlock(sc_reader_t *reader)
283
0
{
284
0
  return 0;
285
0
}
286
287
static int ctapi_release(sc_reader_t *reader)
288
0
{
289
0
  struct ctapi_private_data *priv = GET_PRIV_DATA(reader);
290
291
292
0
  if (!(reader->ctx->flags & SC_CTX_FLAG_TERMINATE))
293
0
    priv->funcs.CT_close(priv->ctn);
294
295
0
  free(priv);
296
0
  return 0;
297
0
}
298
299
static struct sc_reader_operations ctapi_ops;
300
301
static struct sc_reader_driver ctapi_drv = {
302
  "CT-API module",
303
  "ctapi",
304
  &ctapi_ops,
305
  NULL
306
};
307
308
static struct ctapi_module * add_module(struct ctapi_global_private_data *gpriv,
309
    const char *name, void *dlhandle)
310
0
{
311
0
  int i;
312
0
  struct ctapi_module *p;
313
314
0
  i = gpriv->module_count;
315
0
  p = (struct ctapi_module *) realloc(gpriv->modules, sizeof(struct ctapi_module) * (i+1));
316
0
  if (!p) {
317
0
    return NULL;
318
0
  }
319
0
  gpriv->modules = p;
320
0
  gpriv->modules[i].name = strdup(name);
321
0
  gpriv->modules[i].dlhandle = dlhandle;
322
0
  gpriv->modules[i].ctn_count = 0;
323
0
  gpriv->module_count++;
324
325
0
  return &gpriv->modules[i];
326
0
}
327
328
static int ctapi_load_module(sc_context_t *ctx,
329
    struct ctapi_global_private_data *gpriv, scconf_block *conf)
330
0
{
331
0
  const char *val;
332
0
  struct ctapi_functions funcs;
333
0
  struct ctapi_module *mod;
334
0
  const scconf_list *list;
335
0
  scconf_block *conf_block = NULL;
336
0
  void *dlh;
337
0
  int r, i, NumUnits;
338
0
  u8 cmd[5], rbuf[256], sad, dad;
339
0
  unsigned short lr;
340
341
0
  list = scconf_find_list(conf, "ports");
342
0
  if (list == NULL) {
343
0
    sc_log(ctx, "No ports configured.");
344
0
    return -1;
345
0
  }
346
347
0
  val = conf->name->data;
348
0
  dlh = sc_dlopen(val);
349
0
  if (!dlh) {
350
0
    sc_log(ctx, "Unable to open shared library '%s': %s", val, sc_dlerror());
351
0
    return -1;
352
0
  }
353
354
0
  funcs.CT_init = (CT_INIT_TYPE *) sc_dlsym(dlh, "CT_init");
355
0
  if (!funcs.CT_init)
356
0
    goto symerr;
357
0
  funcs.CT_close = (CT_CLOSE_TYPE *) sc_dlsym(dlh, "CT_close");
358
0
  if (!funcs.CT_close)
359
0
    goto symerr;
360
0
  funcs.CT_data = (CT_DATA_TYPE *) sc_dlsym(dlh, "CT_data");
361
0
  if (!funcs.CT_data)
362
0
    goto symerr;
363
364
0
  mod = add_module(gpriv, val, dlh);
365
0
  if (!mod)
366
0
    goto symerr;
367
0
  for (; list != NULL; list = list->next) {
368
0
    int port;
369
0
    char namebuf[128];
370
0
    char rv;
371
0
    sc_reader_t *reader;
372
0
    struct ctapi_private_data *priv;
373
374
0
    if (sscanf(list->data, "%d", &port) != 1) {
375
0
      sc_log(ctx, "Port '%s' is not a number.", list->data);
376
0
      continue;
377
0
    }
378
0
    rv = funcs.CT_init((unsigned short)mod->ctn_count, (unsigned short)port);
379
0
    if (rv) {
380
0
      sc_log(ctx, "CT_init() failed with %d", rv);
381
0
      continue;
382
0
    }
383
384
0
    reader = calloc(1, sizeof(sc_reader_t));
385
0
    priv = calloc(1, sizeof(struct ctapi_private_data));
386
0
    if (!priv || !reader) {
387
0
      free(reader);
388
0
      free(priv);
389
0
      return SC_ERROR_OUT_OF_MEMORY;
390
0
    }
391
0
    reader->drv_data = priv;
392
0
    reader->ops = &ctapi_ops;
393
0
    reader->driver = &ctapi_drv;
394
0
    snprintf(namebuf, sizeof(namebuf), "CT-API %s, port %d", mod->name, port);
395
0
    reader->name = strdup(namebuf);
396
0
    priv->funcs = funcs;
397
0
    priv->ctn = mod->ctn_count;
398
399
0
    reader->max_send_size = SC_READER_SHORT_APDU_MAX_SEND_SIZE;
400
0
    reader->max_recv_size = SC_READER_SHORT_APDU_MAX_RECV_SIZE;
401
402
0
    conf_block = sc_get_conf_block(ctx, "reader_driver", "ctapi", 1);
403
0
    if (conf_block) {
404
0
      reader->max_send_size = scconf_get_int(conf_block, "max_send_size", reader->max_send_size);
405
0
      reader->max_recv_size = scconf_get_int(conf_block, "max_recv_size", reader->max_recv_size);
406
0
      if (scconf_get_bool(conf_block, "enable_escape", 0))
407
0
        reader->flags |= SC_READER_ENABLE_ESCAPE;
408
0
    }
409
410
0
    r = _sc_add_reader(ctx, reader);
411
0
    if (r) {
412
0
      funcs.CT_close((unsigned short)mod->ctn_count);
413
0
      free(priv);
414
0
      free(reader->name);
415
0
      free(reader);
416
0
      break;
417
0
    }
418
419
    /* Detect functional units of the reader according to CT-BCS spec version 1.0
420
    (14.04.2004, http://www.teletrust.de/down/mct1-0_t4.zip) */
421
0
    cmd[0] = CTBCS_CLA;
422
0
    cmd[1] = CTBCS_INS_STATUS;
423
0
    cmd[2] = CTBCS_P1_CT_KERNEL;
424
0
    cmd[3] = CTBCS_P2_STATUS_TFU;
425
0
    cmd[4] = 0x00;
426
0
    dad = 1;
427
0
    sad = 2;
428
0
    lr = 256;
429
430
0
    rv = priv->funcs.CT_data(priv->ctn, &dad, &sad, 5, cmd, &lr, rbuf);
431
0
    if (rv || (lr < 4) || (rbuf[lr-2] != 0x90)) {
432
0
      sc_log(reader->ctx, "Error getting status of terminal: %d, using defaults", rv);
433
0
    }
434
0
    if (rbuf[0] != CTBCS_P2_STATUS_TFU) {
435
      /* Number of slots might also detected by using CTBCS_P2_STATUS_ICC.
436
         If you think that's important please do it... ;) */
437
0
      sc_log(reader->ctx, "Invalid data object returned on CTBCS_P2_STATUS_TFU: 0x%x", rbuf[0]);
438
0
    }
439
0
    NumUnits = rbuf[1];
440
0
    if (NumUnits + 4 > lr) {
441
0
      sc_log(reader->ctx, "Invalid data returned: %d functional units, size %d", NumUnits, rv);
442
0
    }
443
0
    priv->ctapi_functional_units = 0;
444
0
    for(i = 0; i < NumUnits; i++) {
445
0
      switch(rbuf[i+2]) {
446
0
        case CTBCS_P1_INTERFACE1:
447
0
        case CTBCS_P1_INTERFACE2:
448
0
        case CTBCS_P1_INTERFACE3:
449
0
        case CTBCS_P1_INTERFACE4:
450
0
        case CTBCS_P1_INTERFACE5:
451
0
        case CTBCS_P1_INTERFACE6:
452
0
        case CTBCS_P1_INTERFACE7:
453
0
        case CTBCS_P1_INTERFACE8:
454
0
        case CTBCS_P1_INTERFACE9:
455
0
        case CTBCS_P1_INTERFACE10:
456
0
        case CTBCS_P1_INTERFACE11:
457
0
        case CTBCS_P1_INTERFACE12:
458
0
        case CTBCS_P1_INTERFACE13:
459
0
        case CTBCS_P1_INTERFACE14:
460
        /* Maybe a weak point here if multiple interfaces are present and not returned
461
           in the "canonical" order. This is not forbidden by the specs, but why should
462
           anyone want to do that? */
463
0
          sc_log(reader->ctx, "Found slot id 0x%x", rbuf[i+2]);
464
0
          break;
465
466
0
        case CTBCS_P1_DISPLAY:
467
0
          priv->ctapi_functional_units |= CTAPI_FU_DISPLAY;
468
0
          sc_log(reader->ctx, "Display detected");
469
0
          break;
470
471
0
        case CTBCS_P1_KEYPAD:
472
0
          priv->ctapi_functional_units |= CTAPI_FU_KEYBOARD;
473
0
          sc_log(reader->ctx, "Keypad detected");
474
0
          break;
475
476
0
        case CTBCS_P1_PRINTER:
477
0
          priv->ctapi_functional_units |= CTAPI_FU_PRINTER;
478
0
          sc_log(reader->ctx, "Printer detected");
479
0
          break;
480
481
0
        case CTBCS_P1_FINGERPRINT:
482
0
        case CTBCS_P1_VOICEPRINT:
483
0
        case CTBCS_P1_DSV:
484
0
        case CTBCS_P1_FACE_RECOGNITION:
485
0
        case CTBCS_P1_IRISSCAN:
486
0
          priv->ctapi_functional_units |= CTAPI_FU_BIOMETRIC;
487
0
          sc_log(reader->ctx, "Biometric sensor detected");
488
0
          break;
489
490
0
        default:
491
0
          sc_log(reader->ctx, "Unknown functional unit 0x%x", rbuf[i+2]);
492
0
      }
493
0
    }
494
    /* CT-BCS does not define Keyboard/Display for each slot, so I assume
495
    those additional units can be used for each slot */
496
0
    if (priv->ctapi_functional_units) {
497
0
      if (priv->ctapi_functional_units & CTAPI_FU_KEYBOARD)
498
0
        reader->capabilities |= SC_READER_CAP_PIN_PAD;
499
0
      if (priv->ctapi_functional_units & CTAPI_FU_DISPLAY)
500
0
        reader->capabilities |= SC_READER_CAP_DISPLAY;
501
0
    }
502
503
0
    ctapi_reset(reader);
504
0
    refresh_attributes(reader);
505
0
    mod->ctn_count++;
506
0
  }
507
0
  return 0;
508
0
symerr:
509
0
  sc_log(ctx, "Unable to resolve CT-API symbols.");
510
0
  sc_dlclose(dlh);
511
0
  return -1;
512
0
}
513
514
static int ctapi_init(sc_context_t *ctx)
515
283
{
516
283
  int i;
517
283
  struct ctapi_global_private_data *gpriv;
518
283
  scconf_block **blocks = NULL, *conf_block = NULL;
519
520
283
  gpriv = calloc(1, sizeof(struct ctapi_global_private_data));
521
283
  if (gpriv == NULL)
522
0
    return SC_ERROR_OUT_OF_MEMORY;
523
283
  ctx->reader_drv_data = gpriv;
524
525
283
  conf_block = sc_get_conf_block(ctx, "reader_driver", "ctapi", 1);
526
283
  if (conf_block)   {
527
0
    blocks = scconf_find_blocks(ctx->conf, conf_block, "module", NULL);
528
0
    for (i = 0; blocks != NULL && blocks[i] != NULL; i++)
529
0
      ctapi_load_module(ctx, gpriv, blocks[i]);
530
0
    free(blocks);
531
0
  }
532
533
283
  return 0;
534
283
}
535
536
static int ctapi_finish(sc_context_t *ctx)
537
283
{
538
283
  struct ctapi_global_private_data *priv = (struct ctapi_global_private_data *) ctx->reader_drv_data;
539
540
283
  if (priv) {
541
283
    int i;
542
543
283
    for (i = 0; i < priv->module_count; i++) {
544
0
      struct ctapi_module *mod = &priv->modules[i];
545
546
0
      free(mod->name);
547
0
      sc_dlclose(mod->dlhandle);
548
0
    }
549
283
    if (priv->module_count)
550
0
      free(priv->modules);
551
283
    free(priv);
552
283
  }
553
554
283
  return 0;
555
283
}
556
557
struct sc_reader_driver * sc_get_ctapi_driver(void)
558
283
{
559
283
  ctapi_ops.init = ctapi_init;
560
283
  ctapi_ops.finish = ctapi_finish;
561
283
  ctapi_ops.detect_readers = NULL;
562
283
  ctapi_ops.transmit = ctapi_transmit;
563
283
  ctapi_ops.detect_card_presence = ctapi_detect_card_presence;
564
283
  ctapi_ops.lock = ctapi_lock;
565
283
  ctapi_ops.unlock = ctapi_unlock;
566
283
  ctapi_ops.release = ctapi_release;
567
283
  ctapi_ops.connect = ctapi_connect;
568
283
  ctapi_ops.disconnect = ctapi_disconnect;
569
283
  ctapi_ops.perform_verify = ctbcs_pin_cmd;
570
283
  ctapi_ops.perform_pace = NULL;
571
283
  ctapi_ops.use_reader = NULL;
572
573
283
  return &ctapi_drv;
574
283
}
575
#endif