Coverage Report

Created: 2025-07-01 06:08

/src/opensc/src/libopensc/reader-ctapi.c
Line
Count
Source (jump to first uncovered line)
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
0
{
516
0
  int i;
517
0
  struct ctapi_global_private_data *gpriv;
518
0
  scconf_block **blocks = NULL, *conf_block = NULL;
519
520
0
  gpriv = calloc(1, sizeof(struct ctapi_global_private_data));
521
0
  if (gpriv == NULL)
522
0
    return SC_ERROR_OUT_OF_MEMORY;
523
0
  ctx->reader_drv_data = gpriv;
524
525
0
  conf_block = sc_get_conf_block(ctx, "reader_driver", "ctapi", 1);
526
0
  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
0
  return 0;
534
0
}
535
536
static int ctapi_finish(sc_context_t *ctx)
537
0
{
538
0
  struct ctapi_global_private_data *priv = (struct ctapi_global_private_data *) ctx->reader_drv_data;
539
540
0
  if (priv) {
541
0
    int i;
542
543
0
    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
0
    if (priv->module_count)
550
0
      free(priv->modules);
551
0
    free(priv);
552
0
  }
553
554
0
  return 0;
555
0
}
556
557
struct sc_reader_driver * sc_get_ctapi_driver(void)
558
0
{
559
0
  ctapi_ops.init = ctapi_init;
560
0
  ctapi_ops.finish = ctapi_finish;
561
0
  ctapi_ops.detect_readers = NULL;
562
0
  ctapi_ops.transmit = ctapi_transmit;
563
0
  ctapi_ops.detect_card_presence = ctapi_detect_card_presence;
564
0
  ctapi_ops.lock = ctapi_lock;
565
0
  ctapi_ops.unlock = ctapi_unlock;
566
0
  ctapi_ops.release = ctapi_release;
567
0
  ctapi_ops.connect = ctapi_connect;
568
0
  ctapi_ops.disconnect = ctapi_disconnect;
569
0
  ctapi_ops.perform_verify = ctbcs_pin_cmd;
570
0
  ctapi_ops.perform_pace = NULL;
571
0
  ctapi_ops.use_reader = NULL;
572
573
0
  return &ctapi_drv;
574
0
}
575
#endif