blob: e3b9ea63872d0cda46b62ddbc320e9999d836a98 [file] [log] [blame]
Harald Welte753c8aa2020-10-20 23:04:59 +02001/* (C) 2018-2020 by Harald Welte <laforge@gnumonks.org>
Harald Welte3dcdd202019-03-09 13:06:46 +01002 *
3 * All Rights Reserved
4 *
5 * SPDX-License-Identifier: GPL-2.0+
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
Harald Welte3dcdd202019-03-09 13:06:46 +010017 */
18
Harald Welte45c948c2018-09-23 19:26:52 +020019
20#include <osmocom/core/linuxlist.h>
21#include <osmocom/core/talloc.h>
22#include <osmocom/core/utils.h>
23
24#include <csv.h>
Harald Welte753c8aa2020-10-20 23:04:59 +020025#include <regex.h>
26#include <errno.h>
Harald Welte45c948c2018-09-23 19:26:52 +020027
28#include "bankd.h"
29
30struct pcsc_slot_name {
31 struct llist_head list;
32 /* RSPRO bank slot number */
33 struct bank_slot slot;
34 /* String name of the reader in PC/SC world */
Harald Welte753c8aa2020-10-20 23:04:59 +020035 const char *name_regex;
Harald Welte45c948c2018-09-23 19:26:52 +020036};
37
Harald Welte753c8aa2020-10-20 23:04:59 +020038/* return a talloc-allocated string containing human-readable POSIX regex error */
39static char *get_regerror(void *ctx, int errcode, regex_t *compiled)
40{
41 size_t len = regerror(errcode, compiled, NULL, 0);
42 char *buffer = talloc_size(ctx, len);
43 OSMO_ASSERT(buffer);
44 regerror(errcode, compiled, buffer, len);
45 return buffer;
46}
47
Harald Welte45c948c2018-09-23 19:26:52 +020048enum parser_state_name {
49 ST_NONE,
50 ST_BANK_NR,
51 ST_SLOT_NR,
52 ST_PCSC_NAME,
53};
54struct parser_state {
55 struct bankd *bankd;
56 enum parser_state_name state;
57 struct pcsc_slot_name *cur;
58};
59
60
61static void parser_state_init(struct parser_state *ps)
62{
63 ps->state = ST_BANK_NR;
64 ps->cur = NULL;
65}
66
67static void cb1(void *s, size_t len, void *data)
68{
69 char *field = (char *) s;
70 struct parser_state *ps = data;
71
72 switch (ps->state) {
73 case ST_BANK_NR:
74 OSMO_ASSERT(!ps->cur);
75 ps->cur = talloc_zero(ps->bankd, struct pcsc_slot_name);
76 OSMO_ASSERT(ps->cur);
77 ps->cur->slot.bank_id = atoi(field);
78 ps->state = ST_SLOT_NR;
79 break;
80 case ST_SLOT_NR:
81 OSMO_ASSERT(ps->cur);
82 ps->cur->slot.slot_nr = atoi(field);
83 ps->state = ST_PCSC_NAME;
84 break;
85 case ST_PCSC_NAME:
86 OSMO_ASSERT(ps->cur);
Harald Welte753c8aa2020-10-20 23:04:59 +020087 ps->cur->name_regex = talloc_strdup(ps->cur, field);
Harald Welte45c948c2018-09-23 19:26:52 +020088 break;
89 default:
90 OSMO_ASSERT(0);
91 }
92}
93
94static void cb2(int c, void *data)
95{
96 struct parser_state *ps = data;
97 struct pcsc_slot_name *sn = ps->cur;
Harald Welte753c8aa2020-10-20 23:04:59 +020098 regex_t compiled_name;
99 int rc;
Harald Welte45c948c2018-09-23 19:26:52 +0200100
Harald Welte7293e7b2021-12-08 21:29:11 +0100101 LOGP(DMAIN, LOGL_INFO, "PC/SC slot name: %u/%u -> regex '%s'\n",
102 sn->slot.bank_id, sn->slot.slot_nr, sn->name_regex);
Harald Welte753c8aa2020-10-20 23:04:59 +0200103
104 memset(&compiled_name, 0, sizeof(compiled_name));
105
106 rc = regcomp(&compiled_name, sn->name_regex, REG_EXTENDED);
107 if (rc != 0) {
108 char *errmsg = get_regerror(sn, rc, &compiled_name);
Harald Welte7293e7b2021-12-08 21:29:11 +0100109 LOGP(DMAIN, LOGL_ERROR, "Error compiling regex '%s': %s - Ignoring\n",
110 sn->name_regex, errmsg);
Harald Welte753c8aa2020-10-20 23:04:59 +0200111 talloc_free(errmsg);
112 talloc_free(sn);
113 } else {
114 llist_add_tail(&sn->list, &ps->bankd->pcsc_slot_names);
115 }
116 regfree(&compiled_name);
Harald Welte45c948c2018-09-23 19:26:52 +0200117
118 ps->state = ST_BANK_NR;
119 ps->cur = NULL;
120}
121
122int bankd_pcsc_read_slotnames(struct bankd *bankd, const char *csv_file)
123{
124 FILE *fp;
125 struct csv_parser p;
126 char buf[1024];
127 size_t bytes_read;
128 struct parser_state ps;
129
130 if (csv_init(&p, CSV_APPEND_NULL) != 0)
131 return -1;
132
133 fp = fopen(csv_file, "rb");
134 if (!fp)
135 return -1;
136
137 parser_state_init(&ps);
138 ps.bankd = bankd;
139
140 while ((bytes_read = fread(buf, 1, sizeof(buf), fp)) > 0) {
141 if (csv_parse(&p, buf, bytes_read, cb1, cb2, &ps) != bytes_read) {
Harald Welte7293e7b2021-12-08 21:29:11 +0100142 LOGP(DMAIN, LOGL_FATAL, "Error parsing bankd PC/SC CSV: %s\n",
143 csv_strerror(csv_error(&p)));
Harald Welte45c948c2018-09-23 19:26:52 +0200144 fclose(fp);
145 return -1;
146 }
147 }
148
149 csv_fini(&p, cb1, cb2, &ps);
150 fclose(fp);
151 csv_free(&p);
152
153 return 0;
154}
155
156const char *bankd_pcsc_get_slot_name(struct bankd *bankd, const struct bank_slot *slot)
157{
158 struct pcsc_slot_name *cur;
159
160 llist_for_each_entry(cur, &bankd->pcsc_slot_names, list) {
161 if (bank_slot_equals(&cur->slot, slot))
Harald Welte753c8aa2020-10-20 23:04:59 +0200162 return cur->name_regex;
Harald Welte45c948c2018-09-23 19:26:52 +0200163 }
164 return NULL;
165}
Harald Welte297d72e2019-03-28 18:42:35 +0100166
167
168#include <wintypes.h>
169#include <winscard.h>
170#include <pcsclite.h>
171
Harald Welte753c8aa2020-10-20 23:04:59 +0200172#define LOGW_PCSC_ERROR(w, rv, text) \
173 LOGW((w), text ": %s (0x%lX)\n", pcsc_stringify_error(rv), rv)
174
Harald Welte297d72e2019-03-28 18:42:35 +0100175#define PCSC_ERROR(w, rv, text) \
176if (rv != SCARD_S_SUCCESS) { \
Harald Welte753c8aa2020-10-20 23:04:59 +0200177 LOGW_PCSC_ERROR(w, rv, text); \
Harald Welte297d72e2019-03-28 18:42:35 +0100178 goto end; \
179} else { \
Harald Welte168d7242021-12-08 15:25:42 +0100180 LOGW((w), text ": OK\n"); \
Harald Welte297d72e2019-03-28 18:42:35 +0100181}
182
Harald Weltebbd18bd2019-12-16 13:11:50 +0100183static int pcsc_get_atr(struct bankd_worker *worker)
184{
185 long rc;
186 char pbReader[MAX_READERNAME];
187 /* use DWORD type as this is what the PC/SC API expects */
188 DWORD dwReaderLen = sizeof(pbReader);
189 DWORD dwAtrLen = worker->card.atr_len = sizeof(worker->card.atr);
190 DWORD dwState, dwProt;
191
192 rc = SCardStatus(worker->reader.pcsc.hCard, pbReader, &dwReaderLen, &dwState, &dwProt,
193 worker->card.atr, &dwAtrLen);
194 PCSC_ERROR(worker, rc, "SCardStatus")
195 worker->card.atr_len = dwAtrLen;
196 LOGW(worker, "Card ATR: %s\n", osmo_hexdump_nospc(worker->card.atr, worker->card.atr_len));
197end:
198 return rc;
199}
200
Harald Welte753c8aa2020-10-20 23:04:59 +0200201
202static int pcsc_connect_slot_regex(struct bankd_worker *worker)
203{
204 DWORD dwReaders = SCARD_AUTOALLOCATE;
205 LPSTR mszReaders = NULL;
206 regex_t compiled_name;
207 int result = -1;
208 LONG rc;
209 char *p;
210
211 LOGW(worker, "Attempting to find card/slot using regex '%s'\n", worker->reader.name);
212
213 rc = regcomp(&compiled_name, worker->reader.name, REG_EXTENDED);
214 if (rc != 0) {
215 LOGW(worker, "Error compiling RegEx over name '%s'\n", worker->reader.name);
216 return -EINVAL;
217 }
218
219 rc = SCardListReaders(worker->reader.pcsc.hContext, NULL, (LPSTR)&mszReaders, &dwReaders);
220 if (rc != SCARD_S_SUCCESS) {
221 LOGW_PCSC_ERROR(worker, rc, "SCardListReaders");
222 goto out_regfree;
223 }
224
225 p = mszReaders;
226 while (*p) {
227 DWORD dwActiveProtocol;
228 int r = regexec(&compiled_name, p, 0, NULL, 0);
229 if (r == 0) {
230 LOGW(worker, "Attempting to open card/slot '%s'\n", p);
231 rc = SCardConnect(worker->reader.pcsc.hContext, p, SCARD_SHARE_SHARED,
232 SCARD_PROTOCOL_T0, &worker->reader.pcsc.hCard,
233 &dwActiveProtocol);
234 if (rc == SCARD_S_SUCCESS)
235 result = 0;
236 else
237 LOGW_PCSC_ERROR(worker, rc, "SCardConnect");
238 break;
239 }
240 p += strlen(p) + 1;
241 }
242
243 SCardFreeMemory(worker->reader.pcsc.hContext, mszReaders);
244
245out_regfree:
246 regfree(&compiled_name);
247
248 return result;
249}
250
251
Harald Welte297d72e2019-03-28 18:42:35 +0100252static int pcsc_open_card(struct bankd_worker *worker)
253{
254 long rc;
255
256 if (!worker->reader.pcsc.hContext) {
257 LOGW(worker, "Attempting to open PC/SC context\n");
258 /* The PC/SC context must be created inside the thread where we'll later use it */
259 rc = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &worker->reader.pcsc.hContext);
260 PCSC_ERROR(worker, rc, "SCardEstablishContext")
261 }
262
263 if (!worker->reader.pcsc.hCard) {
Harald Welte753c8aa2020-10-20 23:04:59 +0200264 rc = pcsc_connect_slot_regex(worker);
265 if (rc != 0)
266 goto end;
Harald Welte297d72e2019-03-28 18:42:35 +0100267 }
268
Harald Weltebbd18bd2019-12-16 13:11:50 +0100269 rc = pcsc_get_atr(worker);
Harald Welte753c8aa2020-10-20 23:04:59 +0200270
Harald Weltebbd18bd2019-12-16 13:11:50 +0100271end:
272 return rc;
273}
274
275static int pcsc_reset_card(struct bankd_worker *worker, bool cold_reset)
276{
277 long rc;
278 DWORD dwActiveProtocol;
279
280 LOGW(worker, "Resetting card in '%s' (%s)\n", worker->reader.name,
281 cold_reset ? "cold reset" : "warm reset");
282 rc = SCardReconnect(worker->reader.pcsc.hCard, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0,
283 cold_reset ? SCARD_UNPOWER_CARD : SCARD_RESET_CARD, &dwActiveProtocol);
284 PCSC_ERROR(worker, rc, "SCardReconnect");
285
286 rc = pcsc_get_atr(worker);
Harald Welte297d72e2019-03-28 18:42:35 +0100287end:
288 return rc;
289}
290
291static int pcsc_transceive(struct bankd_worker *worker, const uint8_t *out, size_t out_len,
292 uint8_t *in, size_t *in_len)
293{
294 const SCARD_IO_REQUEST *pioSendPci = SCARD_PCI_T0;
295 SCARD_IO_REQUEST pioRecvPci;
296 long rc;
297
298 rc = SCardTransmit(worker->reader.pcsc.hCard, pioSendPci, out, out_len, &pioRecvPci, in, in_len);
Harald Welte50a09722021-12-08 15:33:12 +0100299 /* don't use PCSC_ERROR here as we don't want to log every successful SCardTransmit */
300 if (rc != SCARD_S_SUCCESS)
301 LOGW_PCSC_ERROR(worker, rc, "SCardTransmit");
Harald Welte297d72e2019-03-28 18:42:35 +0100302
Harald Welte297d72e2019-03-28 18:42:35 +0100303 return rc;
304}
305
306static void pcsc_cleanup(struct bankd_worker *worker)
307{
308 if (worker->reader.pcsc.hCard) {
309 SCardDisconnect(worker->reader.pcsc.hCard, SCARD_UNPOWER_CARD);
310 worker->reader.pcsc.hCard = 0;
311 }
312 if (worker->reader.pcsc.hContext) {
313 SCardReleaseContext(worker->reader.pcsc.hContext);
314 worker->reader.pcsc.hContext = 0;
315 }
316}
317
318const struct bankd_driver_ops pcsc_driver_ops = {
319 .open_card = pcsc_open_card,
Harald Weltebbd18bd2019-12-16 13:11:50 +0100320 .reset_card = pcsc_reset_card,
Harald Welte297d72e2019-03-28 18:42:35 +0100321 .transceive = pcsc_transceive,
322 .cleanup = pcsc_cleanup,
323};