This is done by creating a Unix domain socket to which smartcard messages are transferred, using the vscard protocol. A further system library, spiceccid, is used to provide an interface into pcsc-lite, specifically the pcsc-lite daemon, so that regular Unix applications can access the passed through smartcard information. Signed-off-by: Jeremy White <jwhite@xxxxxxxxxxxxxxx> --- configure.ac | 21 ++ examples/spiceqxl.xorg.conf.example | 3 + src/Makefile.am | 3 +- src/qxl.h | 2 + src/qxl_driver.c | 14 +- src/spiceccid/Makefile.am | 29 ++ src/spiceccid/spice.pcsc.conf.template | 7 + src/spiceccid/spiceccid.c | 455 ++++++++++++++++++++++++++++++++ src/spiceqxl_smartcard.c | 193 ++++++++++++++ src/spiceqxl_smartcard.h | 31 +++ 10 files changed, 756 insertions(+), 2 deletions(-) create mode 100644 src/spiceccid/Makefile.am create mode 100644 src/spiceccid/spice.pcsc.conf.template create mode 100644 src/spiceccid/spiceccid.c create mode 100644 src/spiceqxl_smartcard.c create mode 100644 src/spiceqxl_smartcard.h diff --git a/configure.ac b/configure.ac index 14e0597..d9da852 100644 --- a/configure.ac +++ b/configure.ac @@ -137,8 +137,27 @@ if test "x$enable_xspice" = "xyes"; then else enable_xspice=no fi + +AC_ARG_ENABLE([ccid], + [AS_HELP_STRING([--enable-ccid], + [Build the spiceccid SmartCard driver (default is no)])], + [enable_ccid=$enableval], + [enable_ccid=no]) +AC_ARG_WITH(ccid-module-dir, + [AS_HELP_STRING([--with-ccid-module-dir=DIR ], + [Specify the install path for spiceccid driver (default is $libdir/pcsc/drivers/serial)])], + [ cciddir="$withval" ], + [ cciddir="$libdir/pcsc/drivers/serial" ]) +AC_SUBST(cciddir) +if test "x$enable_ccid" != "xno"; then + PKG_CHECK_MODULES(LIBPCSCLITE, [libpcsclite]) + PKG_CHECK_MODULES(LIBCACARD, [libcacard]) +fi + + AM_CONDITIONAL(BUILD_XSPICE, test "x$enable_xspice" = "xyes") AM_CONDITIONAL(BUILD_QXL, test "x$enable_qxl" = "xyes") +AM_CONDITIONAL(BUILD_SPICECCID, test "x$enable_ccid" = "xyes") AC_ARG_ENABLE([udev], AS_HELP_STRING([--disable-udev], [Disable libudev support [default=auto]]), @@ -168,6 +187,7 @@ fi AC_CONFIG_FILES([ Makefile src/Makefile + src/spiceccid/Makefile src/uxa/Makefile scripts/Makefile examples/Makefile @@ -187,4 +207,5 @@ echo " KMS: ${DRM_MODE} Build qxl: ${enable_qxl} Build xspice: ${enable_xspice} + Build spiceccid: ${enable_ccid} " diff --git a/examples/spiceqxl.xorg.conf.example b/examples/spiceqxl.xorg.conf.example index 597a5bd..d15f7f2 100644 --- a/examples/spiceqxl.xorg.conf.example +++ b/examples/spiceqxl.xorg.conf.example @@ -143,6 +143,9 @@ Section "Device" # to the client. Default is no mixing. #Option "SpicePlaybackFIFODir" "/tmp/" + # A unix domain name for a unix domain socket + # to communicate with a spiceccid smartcard driver + #Option "SpiceSmartCardFile" "/tmp/spice.pcsc.comm" EndSection Section "InputDevice" diff --git a/src/Makefile.am b/src/Makefile.am index bf50ae1..6c72bbd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -25,7 +25,7 @@ # _ladir passes a dummy rpath to libtool so the thing will actually link # TODO: -nostdlib/-Bstatic/-lgcc platform magic, not installing the .a, etc. -SUBDIRS=uxa +SUBDIRS=uxa spiceccid AM_CFLAGS = $(SPICE_PROTOCOL_CFLAGS) $(XORG_CFLAGS) $(PCIACCESS_CFLAGS) $(CWARNFLAGS) $(DRM_CFLAGS) @LIBUDEV_CFLAGS@ @@ -96,6 +96,7 @@ spiceqxl_drv_la_SOURCES = \ spiceqxl_uinput.c \ spiceqxl_uinput.h \ spiceqxl_audio.c \ + spiceqxl_smartcard.c \ spiceqxl_audio.h \ spiceqxl_inputs.c \ spiceqxl_inputs.h \ diff --git a/src/qxl.h b/src/qxl.h index 603faca..54995cf 100644 --- a/src/qxl.h +++ b/src/qxl.h @@ -157,6 +157,7 @@ enum { OPTION_FRAME_BUFFER_SIZE, OPTION_SURFACE_BUFFER_SIZE, OPTION_COMMAND_BUFFER_SIZE, + OPTION_SPICE_SMARTCARD_FILE, #endif OPTION_COUNT, }; @@ -352,6 +353,7 @@ struct _qxl_screen_t char playback_fifo_dir[PATH_MAX]; void *playback_opaque; + char smartcard_file[PATH_MAX]; #endif /* XSPICE */ uint32_t deferred_fps; diff --git a/src/qxl_driver.c b/src/qxl_driver.c index 165f468..9ad8921 100644 --- a/src/qxl_driver.c +++ b/src/qxl_driver.c @@ -55,6 +55,7 @@ #include "spiceqxl_io_port.h" #include "spiceqxl_spice_server.h" #include "spiceqxl_audio.h" +#include "spiceqxl_smartcard.h" #include "spiceqxl_vdagent.h" #endif /* XSPICE */ @@ -152,8 +153,10 @@ const OptionInfoRec DefaultOptions[] = "SurfaceBufferSize", OPTV_INTEGER, {DEFAULT_SURFACE_BUFFER_SIZE}, FALSE}, { OPTION_COMMAND_BUFFER_SIZE, "CommandBufferSize", OPTV_INTEGER, {DEFAULT_COMMAND_BUFFER_SIZE}, FALSE}, + { OPTION_SPICE_SMARTCARD_FILE, + "SpiceSmartcardFile", OPTV_STRING, {0}, FALSE}, #endif - + { -1, NULL, OPTV_NONE, {0}, FALSE } }; @@ -659,6 +662,7 @@ spiceqxl_screen_init (ScrnInfoPtr pScrn, qxl_screen_t *qxl) } qxl_add_spice_display_interface (qxl); qxl_add_spice_playback_interface (qxl); + qxl_add_spice_smartcard_interface (qxl); spiceqxl_vdagent_init (qxl); } else @@ -1034,6 +1038,7 @@ qxl_pre_init (ScrnInfoPtr pScrn, int flags) unsigned int max_x, max_y; #ifdef XSPICE const char *playback_fifo_dir; + const char *smartcard_file; #endif /* In X server 1.7.5, Xorg -configure will cause this @@ -1089,6 +1094,13 @@ qxl_pre_init (ScrnInfoPtr pScrn, int flags) else qxl->playback_fifo_dir[0] = '\0'; + smartcard_file = get_str_option(qxl->options, OPTION_SPICE_SMARTCARD_FILE, + "XSPICE_SMARTCARD_FILE"); + if (smartcard_file) + strncpy(qxl->smartcard_file, smartcard_file, sizeof(qxl->smartcard_file)); + else + qxl->smartcard_file[0] = '\0'; + qxl->surface0_size = get_int_option (qxl->options, OPTION_FRAME_BUFFER_SIZE, "QXL_FRAME_BUFFER_SIZE") << 20L; qxl->vram_size = diff --git a/src/spiceccid/Makefile.am b/src/spiceccid/Makefile.am new file mode 100644 index 0000000..437e992 --- /dev/null +++ b/src/spiceccid/Makefile.am @@ -0,0 +1,29 @@ +# Copyright 2014 Jeremy White for CodeWeavers, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# on the rights to use, copy, modify, merge, publish, distribute, sub +# license, and/or sell copies of the Software, and to permit persons to whom +# the Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +AM_CFLAGS = $(LIBPCSCLITE_CFLAGS) + +if BUILD_SPICECCID +libspiceccid_la_LTLIBRARIES = libspiceccid.la +libspiceccid_la_LDFLAGS = $(LIBPCSCLITE_LDFLAGS) +libspiceccid_la_SOURCES = spiceccid.c +libspiceccid_ladir = @cciddir@/ +endif diff --git a/src/spiceccid/spice.pcsc.conf.template b/src/spiceccid/spice.pcsc.conf.template new file mode 100644 index 0000000..345cdf5 --- /dev/null +++ b/src/spiceccid/spice.pcsc.conf.template @@ -0,0 +1,7 @@ +# Spice CCID Reader +# This configuration file is the format required by the pcscd deamon for +# serial devices; so the qxl driver looks like a serial device to pcscd. +FRIENDLYNAME "Spice ccid" +DEVICENAME /tmp/spice.pcsc.comm +LIBPATH /usr/lib/pcsc/drivers/serial/libspiceccid.so +CHANNELID 1 diff --git a/src/spiceccid/spiceccid.c b/src/spiceccid/spiceccid.c new file mode 100644 index 0000000..ef9c931 --- /dev/null +++ b/src/spiceccid/spiceccid.c @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2014 CodeWeavers, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Jeremy White <jwhite@xxxxxxxxxxxxxxx> + */ + +/*---------------------------------------------------------------------------- + Chip/Smart Card Interface Devices driver for Spice + + This driver is built to interface to pcsc-lite as a serial smartcard + device. + It translates the IFD (Interface device) ABI into the Spice protocol. +----------------------------------------------------------------------------*/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <pthread.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include "cacard/vscard_common.h" +#include "ifdhandler.h" +#include <arpa/inet.h> + +typedef struct apdu_list { + void *data; + int len; + struct apdu_list *next; +} apdu_t; + +#define MAX_LUNS 2 +typedef struct smartcard_ccid { + int fd; + int lun; + pthread_t tid; + int state; + char atr[36]; + int atr_len; + pthread_mutex_t apdu_lock; + apdu_t *apdu_list; +} smartcard_ccid_t; + +#define STATE_OPEN 1 +#define STATE_READER_ADDED 2 +#define STATE_READER_REMOVED 4 + +#if ! defined(MIN) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#endif + + +smartcard_ccid_t luns[MAX_LUNS] = { { -1 }, { -1 } }; + +RESPONSECODE IFDHCloseChannel(DWORD Lun); + +static void push_apdu(smartcard_ccid_t *ccid, void *data, int len) +{ + apdu_t *a = malloc(sizeof(*a)); + apdu_t **p; + + a->data = malloc(len); + a->len = len; + a->next = NULL; + memcpy(a->data, data, len); + + pthread_mutex_lock(&ccid->apdu_lock); + for (p = &ccid->apdu_list; *p; p = &(*p)->next) + ; + *p = a; + + pthread_mutex_unlock(&ccid->apdu_lock); +} + +static apdu_t * pop_apdu(smartcard_ccid_t *ccid) +{ + apdu_t *p; + pthread_mutex_lock(&ccid->apdu_lock); + p = ccid->apdu_list; + if (ccid->apdu_list) + ccid->apdu_list = p->next; + pthread_mutex_unlock(&ccid->apdu_lock); + return p; +} + +static void free_apdu(apdu_t *a) +{ + free(a->data); + free(a); +} + +static void send_reply(smartcard_ccid_t *ccid, uint32_t code) +{ + uint32_t reply[4]; + + reply[0] = htonl(VSC_Error); // type + reply[1] = htonl(ccid->lun); // reader id + reply[2] = htonl(sizeof(uint32_t)); // length + reply[3] = htonl(code); // Error code + + if (write(ccid->fd, (char *) reply, sizeof(reply)) != sizeof(reply)) { + fprintf(stderr, "Error: lun %d fd %d write failed; errno %d\n", ccid->lun, ccid->fd, errno); + IFDHCloseChannel(ccid->lun); + } +} + +static int send_tx_buffer(smartcard_ccid_t *ccid, void *data, int len) +{ + uint32_t *reply, *p; + int write_len = sizeof(*reply) * 3 + len; + + reply = malloc(write_len); + p = reply; + + *p++ = htonl(VSC_APDU); // type + *p++ = htonl(ccid->lun); // reader id + *p++ = htonl(len); + memcpy(p, data, len); + + if (write(ccid->fd, (char *) reply, write_len) != write_len) { + fprintf(stderr, "Error: lun %d fd %d write failed; errno %d\n", ccid->lun, ccid->fd, errno); + IFDHCloseChannel(ccid->lun); + free(reply); + return 0; + } + free(reply); + return 1; +} + +static void process_reader_add(smartcard_ccid_t *ccid, VSCMsgHeader *h, char *data) +{ + ccid->state |= STATE_READER_ADDED; + + pthread_mutex_init(&ccid->apdu_lock, NULL); + ccid->apdu_list = NULL; + + send_reply(ccid, VSC_SUCCESS); +} + +static void process_reader_remove(smartcard_ccid_t *ccid, VSCMsgHeader *h) +{ + apdu_t *p; + + ccid->state |= STATE_READER_REMOVED; + ccid->state &= ~STATE_READER_ADDED; + + while (p = pop_apdu(ccid)) + free_apdu(p); + + pthread_mutex_destroy(&ccid->apdu_lock); + + send_reply(ccid, VSC_SUCCESS); +} + +static void process_atr(smartcard_ccid_t *ccid, VSCMsgHeader *h, char *data) +{ + ccid->atr_len = MIN(h->length, sizeof(ccid->atr)); + + memset(ccid->atr, 0, sizeof(ccid->atr)); + memcpy(ccid->atr, data, ccid->atr_len); + + send_reply(ccid, VSC_SUCCESS); +} + +static void process_apdu(smartcard_ccid_t *ccid, VSCMsgHeader *h, char *data) +{ + push_apdu(ccid, data, h->length); +} + +static void process_card_remove(smartcard_ccid_t *ccid, VSCMsgHeader *h) +{ + ccid->atr_len = 0; + memset(ccid->atr, 0, sizeof(ccid->atr)); + send_reply(ccid, VSC_SUCCESS); +} + +static int process_message(smartcard_ccid_t *ccid, char *buf, int len) +{ + VSCMsgHeader h; + uint32_t *p = (uint32_t *) buf; + + h.type = ntohl(*p++); + h.reader_id = ntohl(*p++); + h.length = ntohl(*p++); + + if (len < sizeof(h) || len < sizeof(h) + h.length) + return 0; + + switch (h.type) { + case VSC_ReaderAdd: + process_reader_add(ccid, &h, h.length > 0 ? buf + sizeof(h) : NULL); + break; + + case VSC_ReaderRemove: + process_reader_remove(ccid, &h); + break; + + case VSC_ATR: + process_atr(ccid, &h, h.length > 0 ? buf + sizeof(h) : NULL); + break; + + case VSC_CardRemove: + process_card_remove(ccid, &h); + break; + + case VSC_APDU: + process_apdu(ccid, &h, h.length > 0 ? buf + sizeof(h) : NULL); + break; + + default: + fprintf(stderr, "spiceccid %s: unknown smartcard message %d / %d\n", __FUNCTION__, h.type, sizeof(h) + h.length); + + } + + return(MIN(len, h.length + sizeof(h))); +} + +static void * lun_thread(void *arg) +{ + char buf[8096]; + int pos = 0; + smartcard_ccid_t *ccid = (smartcard_ccid_t *) arg; + int rc; + + while (1) { + rc = read(ccid->fd, buf + pos, sizeof(buf) - pos); + if (rc == -1) + if (errno == EINTR) + continue; + else + break; + + if (rc == 0) + break; + + pos += rc; + + do { + rc = process_message(ccid, buf, pos); + pos -= rc; + } while (rc > 0); + } + + return NULL; +} + + +static void send_init(smartcard_ccid_t *ccid) +{ + uint32_t msg[6]; + + msg[0] = htonl(VSC_Init); // type + msg[1] = htonl(ccid->lun); // reader id + msg[2] = htonl(sizeof(uint32_t) * 3); // length + msg[3] = htonl(VSCARD_MAGIC); // VSCD + msg[4] = htonl(VSCARD_VERSION); // VSCD + msg[5] = 0; // capabilities + + if (write(ccid->fd, (char *) msg, sizeof(msg)) != sizeof(msg)) { + fprintf(stderr, "Error: lun %d fd %d write failed; errno %d\n", ccid->lun, ccid->fd, errno); + IFDHCloseChannel(ccid->lun); + } +} + +/*---------------------------------------------------------------------------- + IFDHCreateChannelByName + The pcsc daemon should invoke this function passing in the path name + configured in reader.conf. +*/ +RESPONSECODE IFDHCreateChannelByName(DWORD Lun, LPSTR DeviceName) +{ + int i; + struct sockaddr_un addr; + + for (i = 0; i < MAX_LUNS; i++) + if (luns[i].fd != -1 && luns[i].lun == Lun) + return IFD_COMMUNICATION_ERROR; + + for (i = 0; i < MAX_LUNS; i++) + if (luns[i].fd == -1) + break; + + if (i >= MAX_LUNS) + return IFD_COMMUNICATION_ERROR; + + luns[i].fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (luns[i].fd < 0) + return IFD_NO_SUCH_DEVICE; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, DeviceName, sizeof(addr.sun_path) - 1); + if (connect(luns[i].fd, (struct sockaddr *) &addr, sizeof(addr))) { + close(luns[i].fd); + return IFD_COMMUNICATION_ERROR; + } + + if (pthread_create(&luns[i].tid, NULL, &lun_thread, &luns[i])) { + close(luns[i].fd); + return IFD_COMMUNICATION_ERROR; + } + + luns[i].lun = Lun; + luns[i].state = STATE_OPEN; + + return IFD_SUCCESS; +} + +RESPONSECODE IFDHCreateChannel(DWORD Lun, DWORD Channel) +{ + fprintf(stderr, "spiceccid %s unsupported: Lun %ld, Channel %ld\n", __FUNCTION__, Lun, Channel); + return IFD_ERROR_NOT_SUPPORTED; +} + +RESPONSECODE IFDHCloseChannel(DWORD Lun) +{ + int i; + + for (i = 0; i < MAX_LUNS; i++) { + if (luns[i].fd != -1 && luns[i].lun == Lun) { + pthread_cancel(luns[i].tid); + close(luns[i].fd); + luns[i].fd = -1; + luns[i].lun = 0; + luns[i].atr_len = 0; + break; + } + } + + if (i == MAX_LUNS) + return IFD_NO_SUCH_DEVICE; + + return IFD_SUCCESS; +} + +RESPONSECODE IFDHGetCapabilities(DWORD Lun, DWORD Tag, PDWORD Length, PUCHAR Value) +{ + fprintf(stderr, "spiceccid %s unsupported: Lun %ld, Tag %ld, Length %ld, Value %p\n", __FUNCTION__, Lun, Tag, *Length, Value); + /* TODO - explore supporting TAG_IFD_POLLING_THREAD */ + return IFD_ERROR_NOT_SUPPORTED; +} + +RESPONSECODE IFDHSetCapabilities(DWORD Lun, DWORD Tag, DWORD Length, PUCHAR Value) +{ + return IFD_ERROR_NOT_SUPPORTED; +} + +RESPONSECODE IFDHPowerICC(DWORD Lun, DWORD Action, PUCHAR Atr, PDWORD AtrLength) +{ + int i; + + for (i = 0; i < MAX_LUNS; i++) + if (luns[i].fd != -1 && luns[i].lun == Lun) + if (Action == IFD_POWER_UP) { + if (*AtrLength >= luns[i].atr_len) { + memcpy(Atr, luns[i].atr, luns[i].atr_len); + *AtrLength = luns[i].atr_len; + } + send_init(&luns[i]); + return IFD_SUCCESS; + } + + return IFD_ERROR_NOT_SUPPORTED; +} + +#define TX_MAX_SLEEP 5000 +#define TX_SLEEP_INTERVAL 1000 +RESPONSECODE IFDHTransmitToICC(DWORD Lun, SCARD_IO_HEADER SendPci, + PUCHAR TxBuffer, DWORD TxLength, PUCHAR RxBuffer, PDWORD + RxLength, PSCARD_IO_HEADER RecvPci) +{ + apdu_t *p; + int i, j; + + for (i = 0; i < MAX_LUNS; i++) + if (luns[i].fd != -1 && luns[i].lun == Lun) { + while (p = pop_apdu(&luns[i])) + free_apdu(p); + + if (send_tx_buffer(&luns[i], TxBuffer, TxLength)) { + for (j = 0; j < TX_MAX_SLEEP; j++) + if (p = pop_apdu(&luns[i])) + break; + else + usleep(TX_SLEEP_INTERVAL); + + if (p) { + memcpy(RxBuffer, p->data, MIN(p->len, *RxLength)); + *RxLength = MIN(p->len, *RxLength); + free_apdu(p); + return IFD_SUCCESS; + } + + return IFD_RESPONSE_TIMEOUT; + } + } + return IFD_NO_SUCH_DEVICE; +} + +RESPONSECODE IFDHICCPresence(DWORD Lun) +{ + int i; + + for (i = 0; i < MAX_LUNS; i++) + if (luns[i].fd != -1 && luns[i].lun == Lun) { + if (luns[i].atr_len > 0 && luns[i].state & STATE_READER_ADDED) + return IFD_SUCCESS; + + return IFD_ICC_NOT_PRESENT; + } + + return IFD_NO_SUCH_DEVICE; +} + +RESPONSECODE IFDHSetProtocolParameters(DWORD Lun, DWORD Protocol, UCHAR Flags, + UCHAR PTS1, UCHAR PTS2, UCHAR PTS3) +{ + if (Protocol == SCARD_PROTOCOL_T1) + return IFD_SUCCESS; + + return IFD_NOT_SUPPORTED; +} + +RESPONSECODE IFDHControl(DWORD Lun, DWORD dwControlCode, PUCHAR + TxBuffer, DWORD TxLength, PUCHAR RxBuffer, DWORD RxLength, + LPDWORD pdwBytesReturned) +{ + fprintf(stderr, "spiceccid %s unsupported: Lun %ld\n", __FUNCTION__, Lun); + return IFD_ERROR_NOT_SUPPORTED; +} diff --git a/src/spiceqxl_smartcard.c b/src/spiceqxl_smartcard.c new file mode 100644 index 0000000..1d03841 --- /dev/null +++ b/src/spiceqxl_smartcard.c @@ -0,0 +1,193 @@ +/* + * Copyright 2014 Jeremy White for CodeWeavers Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * on the rights to use, copy, modify, merge, publish, distribute, sub + * license, and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> + + +#include "spiceqxl_smartcard.h" + +typedef struct XSpiceSmartcardCharDeviceInstance { + SpiceCharDeviceInstance base; + qxl_screen_t *qxl; + int listen_fd; + int fd; + SpiceWatch *listen_watch; + SpiceWatch *watch; +} XSpiceSmartcardCharDeviceInstance; + +XSpiceSmartcardCharDeviceInstance smartcard_sin = { + .base = { + .subtype = "smartcard" + } +}; + +static int smartcard_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len) +{ + int written; + + if (smartcard_sin.fd == -1) + return 0; + + written = write(smartcard_sin.fd, buf, len); + if (written != len) + ErrorF("%s: ERROR: short write to smartcard socket - TODO buffering\n", __FUNCTION__); + + return written; +} + +static int smartcard_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len) +{ + int rc; + + if (smartcard_sin.fd == -1) + return 0; + + rc = read(smartcard_sin.fd, buf, len); + if (rc <= 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + return 0; + } + ErrorF("smartcard socket died: %s\n", strerror(errno)); + + smartcard_sin.qxl->core->watch_remove(smartcard_sin.watch); + close(smartcard_sin.fd); + smartcard_sin.fd = -1; + smartcard_sin.watch = NULL; + } + + return rc; +} + +static void on_read_available(int fd, int event, void *opaque) +{ + spice_server_char_device_wakeup(&smartcard_sin.base); +} + +static void on_accept_available(int fd, int event, void *opaque) +{ + qxl_screen_t *qxl = (qxl_screen_t *) opaque; + int flags; + int client_fd; + + client_fd = accept(fd, NULL, NULL); + if (client_fd < 0) + return; + + if (smartcard_sin.fd != -1) { + ErrorF("smartcard error: a new connection came in while an old one was active.\n"); + close(client_fd); + return; + } + + flags = fcntl(client_fd, F_GETFL, 0); + if (flags < 0) + flags = 0; + flags |= O_NONBLOCK; + fcntl(client_fd, F_SETFL, flags); + + smartcard_sin.fd = client_fd; + smartcard_sin.watch = qxl->core->watch_add(smartcard_sin.fd, SPICE_WATCH_EVENT_READ, on_read_available, qxl); + +} + + +#if SPICE_SERVER_VERSION >= 0x000c02 +static void smartcard_event(SpiceCharDeviceInstance *sin, uint8_t event) +{ + ErrorF("%s: unimplemented; event is %d\n", __FUNCTION__, event); +} +#endif + +static void smartcard_state(SpiceCharDeviceInstance *sin, int connected) +{ + ErrorF("%s: unimplemented; connected is %d\n", __FUNCTION__, connected); +} + +static SpiceCharDeviceInterface smartcard_interface = { + .base.type = SPICE_INTERFACE_CHAR_DEVICE, + .base.description = "Xspice virtual channel char device", + .base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR, + .base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR, + .state = smartcard_state, + .write = smartcard_write, + .read = smartcard_read, +#if SPICE_SERVER_VERSION >= 0x000c02 + .event = smartcard_event, +#endif +}; + +int +qxl_add_spice_smartcard_interface (qxl_screen_t *qxl) +{ + int rc; + struct sockaddr_un addr; + + if (qxl->smartcard_file[0] == 0) { + ErrorF("smartcard: no file given, smartcard is disabled\n"); + return 0; + } + + smartcard_sin.fd = -1; + smartcard_sin.listen_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (smartcard_sin.listen_fd < 0) { + ErrorF("smartcard: unable to open socket: %s\n", strerror(errno)); + return errno; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, qxl->smartcard_file, sizeof(addr.sun_path) - 1); + unlink(qxl->smartcard_file); + + if (bind(smartcard_sin.listen_fd, (struct sockaddr *) &addr, sizeof(addr))) { + ErrorF("smartcard: unable to bind to unix domain %s: %s\n", qxl->smartcard_file, strerror(errno)); + close(smartcard_sin.listen_fd); + return errno; + } + + if (listen(smartcard_sin.listen_fd, 1)) { + ErrorF("smartcard: unable to listen to unix domain %s: %s\n", qxl->smartcard_file, strerror(errno)); + close(smartcard_sin.listen_fd); + return errno; + } + + smartcard_sin.listen_watch = qxl->core->watch_add(smartcard_sin.listen_fd, SPICE_WATCH_EVENT_READ, on_accept_available, qxl); + + smartcard_sin.base.base.sif = &smartcard_interface.base; + smartcard_sin.qxl = qxl; + + rc = spice_server_add_interface(qxl->spice_server, &smartcard_sin.base.base); + if (rc < 0) + return errno; + + return 0; +} diff --git a/src/spiceqxl_smartcard.h b/src/spiceqxl_smartcard.h new file mode 100644 index 0000000..62df5a8 --- /dev/null +++ b/src/spiceqxl_smartcard.h @@ -0,0 +1,31 @@ +/* + * Copyright 2014 Jeremy White for CodeWeavers Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * on the rights to use, copy, modify, merge, publish, distribute, sub + * license, and/or sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef QXL_SPICE_SMARTCARD_H +#define QXL_SPICE_SMARTCARD_H + +#include "qxl.h" +#include <spice.h> + +int qxl_add_spice_smartcard_interface(qxl_screen_t *qxl); + +#endif -- 1.7.10.4 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx http://lists.freedesktop.org/mailman/listinfo/spice-devel