diff options
Diffstat (limited to 'src/vscclient.c')
-rw-r--r-- | src/vscclient.c | 785 |
1 files changed, 785 insertions, 0 deletions
diff --git a/src/vscclient.c b/src/vscclient.c new file mode 100644 index 0000000..0652684 --- /dev/null +++ b/src/vscclient.c @@ -0,0 +1,785 @@ +/* + * Tester for VSCARD protocol, client side. + * + * Can be used with ccid-card-passthru. + * + * Copyright (c) 2011 Red Hat. + * Written by Alon Levy. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifndef _WIN32 +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <unistd.h> +#define closesocket(x) close(x) +#else +#include <getopt.h> +#endif + +#include "glib-compat.h" + +#include "vscard_common.h" + +#include "vreader.h" +#include "vcard_emul.h" +#include "vevent.h" + +static int verbose; + +static void +print_byte_array( + uint8_t *arrBytes, + unsigned int nSize +) { + int i; + for (i = 0; i < nSize; i++) { + printf("%02X ", arrBytes[i]); + } + printf("\n"); +} + +static void +print_usage(void) { + printf("vscclient [-c <certname> .. -e <emul_args> -d <level>%s] " + "<host> <port>\n", +#ifdef USE_PASSTHRU + " -p"); + printf(" -p use passthrough mode\n"); +#else + ""); +#endif + vcard_emul_usage(); +} + +static GIOChannel *channel_socket; +static GByteArray *socket_to_send; +static CompatGMutex socket_to_send_lock; +static guint socket_tag; + +static void +update_socket_watch(void); + +static gboolean +do_socket_send(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + gsize bw; + GError *err = NULL; + + g_return_val_if_fail(socket_to_send->len != 0, FALSE); + g_return_val_if_fail(condition & G_IO_OUT, FALSE); + + g_io_channel_write_chars(channel_socket, + (gchar *)socket_to_send->data, socket_to_send->len, &bw, &err); + if (err != NULL) { + g_error("Error while sending socket %s", err->message); + return FALSE; + } + g_byte_array_remove_range(socket_to_send, 0, bw); + + if (socket_to_send->len == 0) { + update_socket_watch(); + return FALSE; + } + return TRUE; +} + +static gboolean +socket_prepare_sending(gpointer user_data) +{ + update_socket_watch(); + + return FALSE; +} + +static int +send_msg( + VSCMsgType type, + uint32_t reader_id, + const void *msg, + unsigned int length +) { + VSCMsgHeader mhHeader; + + g_mutex_lock(&socket_to_send_lock); + + if (verbose > 10) { + printf("sending type=%d id=%u, len =%u (0x%x)\n", + type, reader_id, length, length); + } + + mhHeader.type = htonl(type); + mhHeader.reader_id = 0; + mhHeader.length = htonl(length); + g_byte_array_append(socket_to_send, (guint8 *)&mhHeader, sizeof(mhHeader)); + g_byte_array_append(socket_to_send, (guint8 *)msg, length); + g_idle_add(socket_prepare_sending, NULL); + + g_mutex_unlock(&socket_to_send_lock); + + return 0; +} + +static VReader *pending_reader; +static CompatGMutex pending_reader_lock; +static CompatGCond pending_reader_condition; + +#define MAX_ATR_LEN 40 +static gpointer +event_thread(gpointer arg) +{ + unsigned char atr[MAX_ATR_LEN]; + int atr_len; + VEvent *event; + unsigned int reader_id; + + + while (1) { + const char *reader_name; + + event = vevent_wait_next_vevent(); + if (event == NULL) { + break; + } + reader_id = vreader_get_id(event->reader); + if (reader_id == VSCARD_UNDEFINED_READER_ID && + event->type != VEVENT_READER_INSERT) { + /* ignore events from readers qemu has rejected */ + /* if qemu is still deciding on this reader, wait to see if need to + * forward this event */ + g_mutex_lock(&pending_reader_lock); + if (!pending_reader || (pending_reader != event->reader)) { + /* wasn't for a pending reader, this reader has already been + * rejected by qemu */ + g_mutex_unlock(&pending_reader_lock); + vevent_delete(event); + continue; + } + /* this reader hasn't been told its status from qemu yet, wait for + * that status */ + while (pending_reader != NULL) { + g_cond_wait(&pending_reader_condition, &pending_reader_lock); + } + g_mutex_unlock(&pending_reader_lock); + /* now recheck the id */ + reader_id = vreader_get_id(event->reader); + if (reader_id == VSCARD_UNDEFINED_READER_ID) { + /* this reader was rejected */ + vevent_delete(event); + continue; + } + /* reader was accepted, now forward the event */ + } + switch (event->type) { + case VEVENT_READER_INSERT: + /* tell qemu to insert a new CCID reader */ + /* wait until qemu has responded to our first reader insert + * before we send a second. That way we won't confuse the responses + * */ + g_mutex_lock(&pending_reader_lock); + while (pending_reader != NULL) { + g_cond_wait(&pending_reader_condition, &pending_reader_lock); + } + pending_reader = vreader_reference(event->reader); + g_mutex_unlock(&pending_reader_lock); + reader_name = vreader_get_name(event->reader); + if (verbose > 10) { + printf(" READER INSERT: %s\n", reader_name); + } + send_msg(VSC_ReaderAdd, + reader_id, /* currerntly VSCARD_UNDEFINED_READER_ID */ + NULL, 0 /* TODO reader_name, strlen(reader_name) */); + break; + case VEVENT_READER_REMOVE: + /* future, tell qemu that an old CCID reader has been removed */ + if (verbose > 10) { + printf(" READER REMOVE: %u\n", reader_id); + } + send_msg(VSC_ReaderRemove, reader_id, NULL, 0); + break; + case VEVENT_CARD_INSERT: + /* get the ATR (intended as a response to a power on from the + * reader */ + atr_len = MAX_ATR_LEN; + vreader_power_on(event->reader, atr, &atr_len); + /* ATR call functions as a Card Insert event */ + if (verbose > 10) { + printf(" CARD INSERT %u: ", reader_id); + print_byte_array(atr, atr_len); + } + send_msg(VSC_ATR, reader_id, atr, atr_len); + break; + case VEVENT_CARD_REMOVE: + /* Card removed */ + if (verbose > 10) { + printf(" CARD REMOVE %u:\n", reader_id); + } + send_msg(VSC_CardRemove, reader_id, NULL, 0); + break; + default: + break; + } + vevent_delete(event); + } + return NULL; +} + + +static unsigned int +get_id_from_string(char *string, unsigned int default_id) +{ + unsigned int id = atoi(string); + + /* don't accidentally swith to zero because no numbers have been supplied */ + if ((id == 0) && *string != '0') { + return default_id; + } + return id; +} + +static int +on_host_init(VSCMsgHeader *mhHeader, VSCMsgInit *incoming) +{ + uint32_t *capabilities = (incoming->capabilities); + int num_capabilities = + 1 + ((mhHeader->length - sizeof(VSCMsgInit)) / sizeof(uint32_t)); + int i; + + incoming->version = ntohl(incoming->version); + if (incoming->version != VSCARD_VERSION) { + if (verbose > 0) { + printf("warning: host has version %d, we have %d\n", + verbose, VSCARD_VERSION); + } + } + if (incoming->magic != VSCARD_MAGIC) { + printf("unexpected magic: got %d, expected %d\n", + incoming->magic, VSCARD_MAGIC); + return -1; + } + for (i = 0 ; i < num_capabilities; ++i) { + capabilities[i] = ntohl(capabilities[i]); + } + /* Future: check capabilities */ + /* remove whatever reader might be left in qemu, + * in case of an unclean previous exit. */ + send_msg(VSC_ReaderRemove, VSCARD_MINIMAL_READER_ID, NULL, 0); + /* launch the event_thread. This will trigger reader adds for all the + * existing readers */ + g_thread_new("vsc/event", event_thread, NULL); + return 0; +} + + +enum { + STATE_HEADER, + STATE_MESSAGE, +}; + +#define APDUBufSize 270 + +static gboolean +do_socket_read(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + int rv; + int dwSendLength; + int dwRecvLength; + uint8_t pbRecvBuffer[APDUBufSize]; + static uint8_t pbSendBuffer[APDUBufSize]; + VReaderStatus reader_status; + VReader *reader = NULL; + static VSCMsgHeader mhHeader; + VSCMsgError *error_msg; + GError *err = NULL; + + static gchar *buf; + static gsize br, to_read; + static int state = STATE_HEADER; + + if (state == STATE_HEADER && to_read == 0) { + buf = (gchar *)&mhHeader; + to_read = sizeof(mhHeader); + } + + if (to_read > 0) { + g_io_channel_read_chars(source, (gchar *)buf, to_read, &br, &err); + if (err != NULL) { + g_error("error while reading: %s", err->message); + } + buf += br; + to_read -= br; + if (to_read != 0) { + return TRUE; + } + } + + if (state == STATE_HEADER) { + mhHeader.type = ntohl(mhHeader.type); + mhHeader.reader_id = ntohl(mhHeader.reader_id); + mhHeader.length = ntohl(mhHeader.length); + if (verbose) { + printf("Header: type=%d, reader_id=%u length=%d (0x%x)\n", + mhHeader.type, mhHeader.reader_id, mhHeader.length, + mhHeader.length); + } + switch (mhHeader.type) { + case VSC_APDU: + case VSC_Flush: + case VSC_Error: + case VSC_Init: + buf = (gchar *)pbSendBuffer; + to_read = mhHeader.length; + state = STATE_MESSAGE; + return TRUE; + default: + fprintf(stderr, "Unexpected message of type 0x%X\n", mhHeader.type); + return FALSE; + } + } + + if (state == STATE_MESSAGE) { + switch (mhHeader.type) { + case VSC_APDU: + if (verbose) { + printf(" recv APDU: "); + print_byte_array(pbSendBuffer, mhHeader.length); + } + /* Transmit received APDU */ + dwSendLength = mhHeader.length; + dwRecvLength = sizeof(pbRecvBuffer); + reader = vreader_get_reader_by_id(mhHeader.reader_id); + reader_status = vreader_xfr_bytes(reader, + pbSendBuffer, dwSendLength, + pbRecvBuffer, &dwRecvLength); + if (reader_status == VREADER_OK) { + mhHeader.length = dwRecvLength; + if (verbose) { + printf(" send response: "); + print_byte_array(pbRecvBuffer, mhHeader.length); + } + send_msg(VSC_APDU, mhHeader.reader_id, + pbRecvBuffer, dwRecvLength); + } else { + rv = reader_status; /* warning: not meaningful */ + send_msg(VSC_Error, mhHeader.reader_id, &rv, sizeof(uint32_t)); + } + vreader_free(reader); + reader = NULL; /* we've freed it, don't use it by accident + again */ + break; + case VSC_Flush: + /* TODO: actually flush */ + send_msg(VSC_FlushComplete, mhHeader.reader_id, NULL, 0); + break; + case VSC_Error: + error_msg = (VSCMsgError *) pbSendBuffer; + if (error_msg->code == VSC_SUCCESS) { + g_mutex_lock(&pending_reader_lock); + if (pending_reader) { + vreader_set_id(pending_reader, mhHeader.reader_id); + vreader_free(pending_reader); + pending_reader = NULL; + g_cond_signal(&pending_reader_condition); + } + g_mutex_unlock(&pending_reader_lock); + break; + } + printf("warning: qemu refused to add reader\n"); + if (error_msg->code == VSC_CANNOT_ADD_MORE_READERS) { + /* clear pending reader, qemu can't handle any more */ + g_mutex_lock(&pending_reader_lock); + if (pending_reader) { + pending_reader = NULL; + /* make sure the event loop doesn't hang */ + g_cond_signal(&pending_reader_condition); + } + g_mutex_unlock(&pending_reader_lock); + } + break; + case VSC_Init: + if (on_host_init(&mhHeader, (VSCMsgInit *)pbSendBuffer) < 0) { + return FALSE; + } + break; + default: + g_assert_not_reached(); + return FALSE; + } + + state = STATE_HEADER; + } + + + return TRUE; +} + +static gboolean +do_socket(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + /* not sure if two watches work well with a single win32 sources */ + if (condition & G_IO_OUT) { + if (!do_socket_send(source, condition, data)) { + return FALSE; + } + } + + if (condition & G_IO_IN) { + if (!do_socket_read(source, condition, data)) { + return FALSE; + } + } + + return TRUE; +} + +static void +update_socket_watch(void) +{ + gboolean out = socket_to_send->len > 0; + + if (socket_tag != 0) { + g_source_remove(socket_tag); + } + + socket_tag = g_io_add_watch(channel_socket, + G_IO_IN | (out ? G_IO_OUT : 0), do_socket, NULL); +} + +static gboolean +do_command(GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + char *string; + VCardEmulError error; + static unsigned int default_reader_id; + unsigned int reader_id; + VReader *reader = NULL; + GError *err = NULL; + + g_assert(condition & G_IO_IN); + + reader_id = default_reader_id; + g_io_channel_read_line(source, &string, NULL, NULL, &err); + if (err != NULL) { + g_error("Error while reading command: %s", err->message); + } + + if (string != NULL) { + if (strncmp(string, "exit", 4) == 0) { + /* remove all the readers */ + VReaderList *list = vreader_get_reader_list(); + VReaderListEntry *reader_entry; + printf("Active Readers:\n"); + for (reader_entry = vreader_list_get_first(list); reader_entry; + reader_entry = vreader_list_get_next(reader_entry)) { + VReader *reader = vreader_list_get_reader(reader_entry); + vreader_id_t reader_id; + reader_id = vreader_get_id(reader); + if (reader_id == -1) { + continue; + } + /* be nice and signal card removal first (qemu probably should + * do this itself) */ + if (vreader_card_is_present(reader) == VREADER_OK) { + send_msg(VSC_CardRemove, reader_id, NULL, 0); + } + send_msg(VSC_ReaderRemove, reader_id, NULL, 0); + } + exit(0); + } else if (strncmp(string, "insert", 6) == 0) { + if (string[6] == ' ') { + reader_id = get_id_from_string(&string[7], reader_id); + } + reader = vreader_get_reader_by_id(reader_id); + if (reader != NULL) { + error = vcard_emul_force_card_insert(reader); + printf("insert %s, returned %d\n", + vreader_get_name(reader), error); + } else { + printf("no reader by id %u found\n", reader_id); + } + } else if (strncmp(string, "remove", 6) == 0) { + if (string[6] == ' ') { + reader_id = get_id_from_string(&string[7], reader_id); + } + reader = vreader_get_reader_by_id(reader_id); + if (reader != NULL) { + error = vcard_emul_force_card_remove(reader); + printf("remove %s, returned %d\n", + vreader_get_name(reader), error); + } else { + printf("no reader by id %u found\n", reader_id); + } + } else if (strncmp(string, "select", 6) == 0) { + if (string[6] == ' ') { + reader_id = get_id_from_string(&string[7], + VSCARD_UNDEFINED_READER_ID); + } + if (reader_id != VSCARD_UNDEFINED_READER_ID) { + reader = vreader_get_reader_by_id(reader_id); + } + if (reader) { + printf("Selecting reader %u, %s\n", reader_id, + vreader_get_name(reader)); + default_reader_id = reader_id; + } else { + printf("Reader with id %u not found\n", reader_id); + } + } else if (strncmp(string, "debug", 5) == 0) { + if (string[5] == ' ') { + verbose = get_id_from_string(&string[6], 0); + } + printf("debug level = %d\n", verbose); + } else if (strncmp(string, "list", 4) == 0) { + VReaderList *list = vreader_get_reader_list(); + VReaderListEntry *reader_entry; + printf("Active Readers:\n"); + for (reader_entry = vreader_list_get_first(list); reader_entry; + reader_entry = vreader_list_get_next(reader_entry)) { + VReader *reader = vreader_list_get_reader(reader_entry); + vreader_id_t reader_id; + reader_id = vreader_get_id(reader); + if (reader_id == -1) { + continue; + } + printf("%3u %s %s\n", reader_id, + vreader_card_is_present(reader) == VREADER_OK ? + "CARD_PRESENT" : " ", + vreader_get_name(reader)); + } + printf("Inactive Readers:\n"); + for (reader_entry = vreader_list_get_first(list); reader_entry; + reader_entry = vreader_list_get_next(reader_entry)) { + VReader *reader = vreader_list_get_reader(reader_entry); + vreader_id_t reader_id; + reader_id = vreader_get_id(reader); + if (reader_id != -1) { + continue; + } + + printf("INA %s %s\n", + vreader_card_is_present(reader) == VREADER_OK ? + "CARD_PRESENT" : " ", + vreader_get_name(reader)); + } + vreader_list_delete(list); + } else if (*string != 0) { + printf("valid commands:\n"); + printf("insert [reader_id]\n"); + printf("remove [reader_id]\n"); + printf("select reader_id\n"); + printf("list\n"); + printf("debug [level]\n"); + printf("exit\n"); + } + } + vreader_free(reader); + printf("> "); + fflush(stdout); + + return TRUE; +} + + +/* just for ease of parsing command line arguments. */ +#define MAX_CERTS 100 + +static int +connect_to_qemu( + const char *host, + const char *port +) { + struct addrinfo hints; + struct addrinfo *server = NULL; + int ret, sock; + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + /* Error */ + fprintf(stderr, "Error opening socket!\n"); + return -1; + } + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + + ret = getaddrinfo(host, port, &hints, &server); + + if (ret != 0) { + /* Error */ + fprintf(stderr, "getaddrinfo failed\n"); + goto cleanup_socket; + } + + if (connect(sock, server->ai_addr, server->ai_addrlen) < 0) { + /* Error */ + fprintf(stderr, "Could not connect\n"); + goto cleanup_socket; + } + if (verbose) { + printf("Connected (sizeof Header=%zd)!\n", sizeof(VSCMsgHeader)); + } + + freeaddrinfo(server); + return sock; + +cleanup_socket: + if (server) { + freeaddrinfo(server); + } + closesocket(sock); + return -1; +} + +int +main( + int argc, + char *argv[] +) { + GMainLoop *loop; + GIOChannel *channel_stdin; + char *qemu_host; + char *qemu_port; + + VCardEmulOptions *command_line_options = NULL; + + char *cert_names[MAX_CERTS]; + char *emul_args = NULL; + int cert_count = 0; + int c, sock; + +#ifdef _WIN32 + WSADATA Data; + + if (WSAStartup(MAKEWORD(2, 2), &Data) != 0) { + c = WSAGetLastError(); + fprintf(stderr, "WSAStartup: %d\n", c); + return 1; + } +#endif +#if !GLIB_CHECK_VERSION(2, 31, 0) + if (!g_thread_supported()) { + g_thread_init(NULL); + } +#endif + + while ((c = getopt(argc, argv, "c:e:pd:")) != -1) { + switch (c) { + case 'c': + if (cert_count >= MAX_CERTS) { + printf("too many certificates (max = %d)\n", MAX_CERTS); + exit(5); + } + cert_names[cert_count++] = optarg; + break; + case 'e': + emul_args = optarg; + break; + case 'p': + print_usage(); + exit(4); + break; + case 'd': + verbose = get_id_from_string(optarg, 1); + break; + } + } + + if (argc - optind != 2) { + print_usage(); + exit(4); + } + + if (cert_count > 0) { + char *new_args; + int len, i; + /* if we've given some -c options, we clearly we want do so some + * software emulation. add that emulation now. this is NSS Emulator + * specific */ + if (emul_args == NULL) { + emul_args = (char *)"db=\"/etc/pki/nssdb\""; + } +#define SOFT_STRING ",soft=(,Virtual Reader,CAC,," + /* 2 == close paren & null */ + len = strlen(emul_args) + strlen(SOFT_STRING) + 2; + for (i = 0; i < cert_count; i++) { + len += strlen(cert_names[i])+1; /* 1 == comma */ + } + new_args = g_malloc(len); + strcpy(new_args, emul_args); + strcat(new_args, SOFT_STRING); + for (i = 0; i < cert_count; i++) { + strcat(new_args, cert_names[i]); + strcat(new_args, ","); + } + strcat(new_args, ")"); + emul_args = new_args; + } + if (emul_args) { + command_line_options = vcard_emul_options(emul_args); + } + + qemu_host = g_strdup(argv[argc - 2]); + qemu_port = g_strdup(argv[argc - 1]); + sock = connect_to_qemu(qemu_host, qemu_port); + if (sock == -1) { + fprintf(stderr, "error opening socket, exiting.\n"); + exit(5); + } + + socket_to_send = g_byte_array_new(); + vcard_emul_init(command_line_options); + loop = g_main_loop_new(NULL, TRUE); + + printf("> "); + fflush(stdout); + +#ifdef _WIN32 + channel_stdin = g_io_channel_win32_new_fd(STDIN_FILENO); +#else + channel_stdin = g_io_channel_unix_new(STDIN_FILENO); +#endif + g_io_add_watch(channel_stdin, G_IO_IN, do_command, NULL); +#ifdef _WIN32 + channel_socket = g_io_channel_win32_new_socket(sock); +#else + channel_socket = g_io_channel_unix_new(sock); +#endif + g_io_channel_set_encoding(channel_socket, NULL, NULL); + /* we buffer ourself for thread safety reasons */ + g_io_channel_set_buffered(channel_socket, FALSE); + + /* Send init message, Host responds (and then we send reader attachments) */ + VSCMsgInit init = { + .version = htonl(VSCARD_VERSION), + .magic = VSCARD_MAGIC, + .capabilities = {0} + }; + send_msg(VSC_Init, 0, &init, sizeof(init)); + + g_main_loop_run(loop); + g_main_loop_unref(loop); + + g_io_channel_unref(channel_stdin); + g_io_channel_unref(channel_socket); + g_byte_array_free(socket_to_send, TRUE); + + closesocket(sock); + return 0; +} |