/*
Copyright (C) 2011 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see .
*/
#include "config.h"
#include
#include
#ifdef USE_SMARTCARD
#include
#endif
#include "spice-client.h"
#include "smartcard-manager.h"
#include "smartcard-manager-priv.h"
#include "spice-marshal.h"
/**
* SECTION:smartcard-manager
* @short_description: smartcard management
* @title: Spice Smartcard Manager
* @section_id:
* @see_also:
* @stability: Stable
* @include: spice-client.h
*
* #SpiceSmartcardManager monitors smartcard reader plugging/unplugging,
* and smartcard insertions/removals. It also provides methods to handle
* software smartcards (to emulate a smartcard reader/smartcard on the
* guest using 3 certificates available to the client).
*/
G_STATIC_ASSERT(sizeof(SpiceSmartcardManagerClass) == sizeof(GObjectClass) + 14 * sizeof(gpointer));
struct _SpiceSmartcardManagerPrivate {
guint monitor_id;
/* software smartcard reader, the certificates to use for this reader
* were given at the channel creation time. This reader has no physical
* existence, it's all controlled by explicit software
* insertion/removal of cards
*/
#ifdef USE_SMARTCARD
VReader *software_reader;
#endif
};
G_DEFINE_TYPE_WITH_PRIVATE(SpiceSmartcardManager, spice_smartcard_manager, G_TYPE_OBJECT)
#ifdef USE_SMARTCARD
G_DEFINE_BOXED_TYPE(VReader, spice_smartcard_reader, vreader_reference, vreader_free)
#else
typedef GObject VReader;
G_DEFINE_BOXED_TYPE(VReader, spice_smartcard_reader, g_object_ref, g_object_unref)
#endif
/* Properties */
enum {
PROP_0,
};
/* Signals */
enum {
SPICE_SMARTCARD_MANAGER_READER_ADDED,
SPICE_SMARTCARD_MANAGER_READER_REMOVED,
SPICE_SMARTCARD_MANAGER_CARD_INSERTED,
SPICE_SMARTCARD_MANAGER_CARD_REMOVED,
SPICE_SMARTCARD_MANAGER_LAST_SIGNAL,
};
static guint signals[SPICE_SMARTCARD_MANAGER_LAST_SIGNAL];
#ifdef USE_SMARTCARD
typedef gboolean (*SmartcardSourceFunc)(VEvent *event, gpointer user_data);
static gboolean smartcard_monitor_dispatch(VEvent *event, gpointer user_data);
#endif
/* ------------------------------------------------------------------ */
static void spice_smartcard_manager_init(SpiceSmartcardManager *smartcard_manager)
{
SpiceSmartcardManagerPrivate *priv;
priv = spice_smartcard_manager_get_instance_private(smartcard_manager);
smartcard_manager->priv = priv;
}
static void spice_smartcard_manager_dispose(GObject *gobject)
{
/* Chain up to the parent class */
if (G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->dispose)
G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->dispose(gobject);
}
static void spice_smartcard_manager_finalize(GObject *gobject)
{
SpiceSmartcardManager *manager = SPICE_SMARTCARD_MANAGER(gobject);
SpiceSmartcardManagerPrivate *priv = manager->priv;
if (priv->monitor_id != 0) {
g_source_remove(priv->monitor_id);
priv->monitor_id = 0;
}
#ifdef USE_SMARTCARD
g_clear_pointer(&priv->software_reader, vreader_free);
#endif
/* Chain up to the parent class */
if (G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->finalize)
G_OBJECT_CLASS(spice_smartcard_manager_parent_class)->finalize(gobject);
}
static void spice_smartcard_manager_class_init(SpiceSmartcardManagerClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
/**
* SpiceSmartcardManager::reader-added:
* @manager: the #SpiceSmartcardManager that emitted the signal
* @vreader: #VReader boxed object corresponding to the added reader
*
* The #SpiceSmartcardManager::reader-added signal is emitted whenever
* a new smartcard reader (software or hardware) has been plugged in.
**/
signals[SPICE_SMARTCARD_MANAGER_READER_ADDED] =
g_signal_new("reader-added",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET(SpiceSmartcardManagerClass, reader_added),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE,
1,
SPICE_TYPE_SMARTCARD_READER);
/**
* SpiceSmartcardManager::reader-removed:
* @manager: the #SpiceSmartcardManager that emitted the signal
* @vreader: #VReader boxed object corresponding to the removed reader
*
* The #SpiceSmartcardManager::reader-removed signal is emitted whenever
* a smartcard reader (software or hardware) has been removed.
**/
signals[SPICE_SMARTCARD_MANAGER_READER_REMOVED] =
g_signal_new("reader-removed",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET(SpiceSmartcardManagerClass, reader_removed),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE,
1,
SPICE_TYPE_SMARTCARD_READER);
/**
* SpiceSmartcardManager::card-inserted:
* @manager: the #SpiceSmartcardManager that emitted the signal
* @vreader: #VReader boxed object corresponding to the reader a new
* card was inserted in
*
* The #SpiceSmartcardManager::card-inserted signal is emitted whenever
* a smartcard is inserted in a reader
**/
signals[SPICE_SMARTCARD_MANAGER_CARD_INSERTED] =
g_signal_new("card-inserted",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET(SpiceSmartcardManagerClass, card_inserted),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE,
1,
SPICE_TYPE_SMARTCARD_READER);
/**
* SpiceSmartcardManager::card-removed:
* @manager: the #SpiceSmartcardManager that emitted the signal
* @vreader: #VReader boxed object corresponding to the reader a card
* was removed from
*
* The #SpiceSmartcardManager::card-removed signal is emitted whenever
* a smartcard was removed from a reader.
**/
signals[SPICE_SMARTCARD_MANAGER_CARD_REMOVED] =
g_signal_new("card-removed",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET(SpiceSmartcardManagerClass, card_removed),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE,
1,
SPICE_TYPE_SMARTCARD_READER);
gobject_class->dispose = spice_smartcard_manager_dispose;
gobject_class->finalize = spice_smartcard_manager_finalize;
}
/* ------------------------------------------------------------------ */
/* private api */
static SpiceSmartcardManager *spice_smartcard_manager_new(void)
{
return g_object_new(SPICE_TYPE_SMARTCARD_MANAGER, NULL);
}
/* ------------------------------------------------------------------ */
/* public api */
/**
* spice_smartcard_manager_get:
*
* #SpiceSmartcardManager is a singleton, use this function to get a pointer
* to it. A new SpiceSmartcardManager instance will be created the first
* time this function is called
*
* Returns: (transfer none): a weak reference to the #SpiceSmartcardManager
*/
SpiceSmartcardManager *spice_smartcard_manager_get(void)
{
static GOnce manager_singleton_once = G_ONCE_INIT;
return g_once(&manager_singleton_once,
(GThreadFunc)spice_smartcard_manager_new,
NULL);
}
#ifdef USE_SMARTCARD
static gboolean smartcard_monitor_dispatch(VEvent *event, gpointer user_data)
{
g_return_val_if_fail(event != NULL, TRUE);
SpiceSmartcardManager *manager = SPICE_SMARTCARD_MANAGER(user_data);
switch (event->type) {
case VEVENT_READER_INSERT:
if (spice_smartcard_reader_is_software((SpiceSmartcardReader*)event->reader)) {
g_warn_if_fail(manager->priv->software_reader == NULL);
manager->priv->software_reader = vreader_reference(event->reader);
}
SPICE_DEBUG("smartcard: reader-added");
g_signal_emit(G_OBJECT(user_data),
signals[SPICE_SMARTCARD_MANAGER_READER_ADDED],
0, event->reader);
break;
case VEVENT_READER_REMOVE:
if (spice_smartcard_reader_is_software((SpiceSmartcardReader*)event->reader)) {
g_warn_if_fail(manager->priv->software_reader != NULL);
g_clear_pointer(&manager->priv->software_reader, vreader_free);
}
SPICE_DEBUG("smartcard: reader-removed");
g_signal_emit(G_OBJECT(user_data),
signals[SPICE_SMARTCARD_MANAGER_READER_REMOVED],
0, event->reader);
break;
case VEVENT_CARD_INSERT:
SPICE_DEBUG("smartcard: card-inserted");
g_signal_emit(G_OBJECT(user_data),
signals[SPICE_SMARTCARD_MANAGER_CARD_INSERTED],
0, event->reader);
break;
case VEVENT_CARD_REMOVE:
SPICE_DEBUG("smartcard: card-removed");
g_signal_emit(G_OBJECT(user_data),
signals[SPICE_SMARTCARD_MANAGER_CARD_REMOVED],
0, event->reader);
break;
case VEVENT_LAST:
break;
}
return TRUE;
}
/* ------------------------------------------------------------------ */
/* smartcard monitoring GSource */
struct _SmartcardSource {
GSource parent_source;
VEvent *pending_event;
};
typedef struct _SmartcardSource SmartcardSource;
static gboolean smartcard_source_prepare(GSource *source, gint *timeout)
{
SmartcardSource *smartcard_source = (SmartcardSource *)source;
if (smartcard_source->pending_event == NULL)
smartcard_source->pending_event = vevent_get_next_vevent();
if (timeout != NULL)
*timeout = -1;
return (smartcard_source->pending_event != NULL);
}
static gboolean smartcard_source_check(GSource *source)
{
return smartcard_source_prepare(source, NULL);
}
static gboolean smartcard_source_dispatch(GSource *source,
GSourceFunc callback,
gpointer user_data)
{
SmartcardSource *smartcard_source = (SmartcardSource *)source;
SmartcardSourceFunc smartcard_callback = (SmartcardSourceFunc)callback;
g_return_val_if_fail(smartcard_source->pending_event != NULL, FALSE);
if (callback) {
gboolean event_consumed;
event_consumed = smartcard_callback(smartcard_source->pending_event,
user_data);
if (event_consumed) {
vevent_delete(smartcard_source->pending_event);
smartcard_source->pending_event = NULL;
}
}
return TRUE;
}
static void smartcard_source_finalize(GSource *source)
{
SmartcardSource *smartcard_source = (SmartcardSource *)source;
g_clear_pointer(&smartcard_source->pending_event, vevent_delete);
}
static GSource *smartcard_monitor_source_new(void)
{
static GSourceFuncs source_funcs = {
.prepare = smartcard_source_prepare,
.check = smartcard_source_check,
.dispatch = smartcard_source_dispatch,
.finalize = smartcard_source_finalize
};
GSource *source;
source = g_source_new(&source_funcs, sizeof(SmartcardSource));
g_source_set_name(source, "Smartcard event source");
return source;
}
static guint smartcard_monitor_add(SmartcardSourceFunc callback,
gpointer user_data)
{
GSource *source;
guint id;
source = smartcard_monitor_source_new();
g_source_set_callback(source, (GSourceFunc)callback, user_data, NULL);
id = g_source_attach(source, NULL);
g_source_unref(source);
return id;
}
static void
spice_smartcard_manager_update_monitor(void)
{
SpiceSmartcardManager *self = spice_smartcard_manager_get();
SpiceSmartcardManagerPrivate *priv = self->priv;
if (priv->monitor_id != 0)
return;
priv->monitor_id = smartcard_monitor_add(smartcard_monitor_dispatch, self);
}
#define SPICE_SOFTWARE_READER_NAME "Spice Software Smartcard"
typedef struct {
SpiceSession *session;
GCancellable *cancellable;
GError *err;
} SmartcardManagerInitArgs;
static void smartcard_reader_free(gpointer data)
{
g_boxed_free(SPICE_TYPE_SMARTCARD_READER, data);
}
/* spice-server only supports one smartcard reader being in use */
static void smartcard_check_reader_count(void)
{
GList *readers;
readers = spice_smartcard_manager_get_readers(spice_smartcard_manager_get());
if (g_list_length(readers) > 1) {
g_warning("Multiple smartcard readers are plugged in, only the first one will be shared with the VM");
}
g_list_free_full(readers, smartcard_reader_free);
}
static gboolean smartcard_manager_init(SmartcardManagerInitArgs *args)
{
gchar *emul_args = NULL;
VCardEmulOptions *options = NULL;
VCardEmulError emul_init_status;
gchar *dbname = NULL;
GStrv certificates = NULL;
gboolean retval = FALSE;
SPICE_DEBUG("smartcard_manager_init");
g_return_val_if_fail(SPICE_IS_SESSION(args->session), FALSE);
g_object_get(G_OBJECT(args->session),
"smartcard-db", &dbname,
"smartcard-certificates", &certificates,
NULL);
if ((certificates == NULL) || (g_strv_length(certificates) != 3))
goto init;
if (dbname) {
emul_args = g_strdup_printf("db=\"%s\" use_hw=no "
"soft=(,%s,CAC,,%s,%s,%s)",
dbname, SPICE_SOFTWARE_READER_NAME,
certificates[0], certificates[1],
certificates[2]);
} else {
emul_args = g_strdup_printf("use_hw=no soft=(,%s,CAC,,%s,%s,%s)",
SPICE_SOFTWARE_READER_NAME,
certificates[0], certificates[1],
certificates[2]);
}
options = vcard_emul_options(emul_args);
if (options == NULL) {
args->err = g_error_new(SPICE_CLIENT_ERROR,
SPICE_CLIENT_ERROR_FAILED,
"vcard_emul_options() failed!");
goto end;
}
if (g_cancellable_set_error_if_cancelled(args->cancellable, &args->err))
goto end;
init:
SPICE_DEBUG("vcard_emul_init");
emul_init_status = vcard_emul_init(options);
if ((emul_init_status != VCARD_EMUL_OK)
&& (emul_init_status != VCARD_EMUL_INIT_ALREADY_INITED)) {
args->err = g_error_new(SPICE_CLIENT_ERROR,
SPICE_CLIENT_ERROR_FAILED,
"Failed to initialize smartcard");
goto end;
}
smartcard_check_reader_count();
retval = TRUE;
end:
SPICE_DEBUG("smartcard_manager_init end: %d", retval);
g_free(emul_args);
g_free(dbname);
g_strfreev(certificates);
return retval;
}
static void smartcard_manager_init_helper(GTask *task,
gpointer object,
gpointer task_data,
GCancellable *cancellable)
{
static GOnce smartcard_manager_once = G_ONCE_INIT;
SmartcardManagerInitArgs args;
args.session = SPICE_SESSION(object);
args.cancellable = cancellable;
args.err = NULL;
g_once(&smartcard_manager_once,
(GThreadFunc)smartcard_manager_init,
&args);
if (args.err != NULL)
g_task_return_error(task, args.err);
else
g_task_return_boolean(task, TRUE);
}
G_GNUC_INTERNAL
void spice_smartcard_manager_init_async(SpiceSession *session,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer opaque)
{
GTask *task = g_task_new(session, cancellable, callback, opaque);
g_task_run_in_thread(task, smartcard_manager_init_helper);
g_object_unref(task);
}
G_GNUC_INTERNAL
gboolean spice_smartcard_manager_init_finish(SpiceSession *session,
GAsyncResult *result,
GError **err)
{
GTask *task = G_TASK(result);
g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
g_return_val_if_fail(G_IS_TASK(task), FALSE);
SPICE_DEBUG("smartcard_manager_finish");
spice_smartcard_manager_update_monitor();
return g_task_propagate_boolean(task, err);
}
/**
* spice_smartcard_reader_is_software:
* @reader: a #SpiceSmartcardReader
*
* Tests if @reader is a software (emulated) smartcard reader.
*
* Returns: TRUE if @reader is a software (emulated) smartcard reader,
* FALSE otherwise
*/
gboolean spice_smartcard_reader_is_software(SpiceSmartcardReader *reader)
{
g_return_val_if_fail(reader != NULL, FALSE);
return (strcmp(vreader_get_name((VReader*)reader), SPICE_SOFTWARE_READER_NAME) == 0);
}
/**
* spice_smartcard_reader_insert_card:
* @reader: a #SpiceSmartcardReader
*
* Simulates insertion of a smartcard in the software smartcard reader
* @reader. If @reader is not a software smartcard reader, FALSE will be
* returned.
*
* Returns: TRUE if insertion of a card was successfully simulated, FALSE
* otherwise
*/
gboolean spice_smartcard_reader_insert_card(SpiceSmartcardReader *reader)
{
VCardEmulError status;
g_return_val_if_fail(spice_smartcard_reader_is_software(reader), FALSE);
status = vcard_emul_force_card_insert((VReader *)reader);
return (status == VCARD_EMUL_OK);
}
/**
* spice_smartcard_reader_remove_card:
* @reader: a #SpiceSmartcardReader
*
* Simulates removal of a smartcard from the software smartcard reader
* @reader. If @reader is not a software smartcard reader, FALSE will be
* returned.
*
* Returns: TRUE if removal of a card was successfully simulated, FALSE
* otherwise
*/
gboolean spice_smartcard_reader_remove_card(SpiceSmartcardReader *reader)
{
VCardEmulError status;
g_return_val_if_fail(spice_smartcard_reader_is_software(reader), FALSE);
status = vcard_emul_force_card_remove((VReader *)reader);
return (status == VCARD_EMUL_OK);
}
/**
* spice_smartcard_manager_get_readers:
* @manager: a #SpiceSmartcardManager
*
* Gets the list of smartcard readers that are currently available, they
* can be either software (emulated) readers, or hardware ones.
*
* Returns: (element-type SpiceSmartcardReader) (transfer full): a newly
* allocated list of SpiceSmartcardReader instances, or NULL if none were
* found. When no longer needed, the list must be freed after unreferencing
* its elements with g_boxed_free()
*
* Since: 0.20
*/
GList *spice_smartcard_manager_get_readers(SpiceSmartcardManager *manager)
{
GList *readers = NULL;
VReaderList *vreader_list;
VReaderListEntry *entry;
vreader_list = vreader_get_reader_list();
if (vreader_list == NULL)
return NULL;
for (entry = vreader_list_get_first(vreader_list);
entry != NULL;
entry = vreader_list_get_next(entry)) {
VReader *reader;
reader = vreader_list_get_reader(entry);
g_warn_if_fail(reader != NULL);
readers = g_list_prepend(readers, vreader_reference(reader));
}
vreader_list_delete(vreader_list);
return g_list_reverse(readers);
}
/**
* spice_smartcard_manager_insert_card:
* @manager: a #SpiceSmartcardManager
*
* Simulates the insertion of a smartcard in the guest. Valid certificates
* must have been set in #SpiceSession:smartcard-certificates for software
* smartcard support to work. At the moment, only one software smartcard
* reader is supported, that's why there is no parameter to indicate which
* reader to insert the card in.
*
* Returns: TRUE if smartcard insertion was successfully simulated, FALSE
* if this failed, or if software smartcard support isn't enabled.
*
* Since: 0.20
*/
gboolean spice_smartcard_manager_insert_card(SpiceSmartcardManager *manager)
{
SpiceSmartcardReader *reader;
g_return_val_if_fail (manager->priv->software_reader != NULL, FALSE);
reader = (SpiceSmartcardReader *)manager->priv->software_reader;
return spice_smartcard_reader_insert_card(reader);
}
/**
* spice_smartcard_manager_remove_card:
* @manager: a #SpiceSmartcardManager
*
* Simulates the removal of a smartcard in the guest. At the moment, only
* one software smartcard reader is supported, that's why there is no
* parameter to indicate which reader to insert the card in.
*
* Returns: TRUE if smartcard removal was successfully simulated, FALSE
* if this failed, or if software smartcard support isn't enabled.
*
* Since: 0.20
*/
gboolean spice_smartcard_manager_remove_card(SpiceSmartcardManager *manager)
{
SpiceSmartcardReader *reader;
g_return_val_if_fail (manager->priv->software_reader != NULL, FALSE);
reader = (SpiceSmartcardReader *)manager->priv->software_reader;
return spice_smartcard_reader_remove_card(reader);
}
#else
gboolean spice_smartcard_reader_is_software(SpiceSmartcardReader *reader)
{
return TRUE;
}
G_GNUC_INTERNAL
void spice_smartcard_manager_init_async(SpiceSession *session,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer opaque)
{
SPICE_DEBUG("using fake smartcard backend");
}
G_GNUC_INTERNAL
gboolean spice_smartcard_manager_init_finish(SpiceSession *session,
GAsyncResult *result,
GError **err)
{
g_return_val_if_fail(SPICE_IS_SESSION(session), FALSE);
return TRUE;
}
gboolean spice_smartcard_manager_insert_card(SpiceSmartcardManager *manager)
{
return FALSE;
}
gboolean spice_smartcard_manager_remove_card(SpiceSmartcardManager *manager)
{
return FALSE;
}
gboolean spice_smartcard_reader_insert_card(SpiceSmartcardReader *reader)
{
return FALSE;
}
gboolean spice_smartcard_reader_remove_card(SpiceSmartcardReader *reader)
{
return FALSE;
}
GList *spice_smartcard_manager_get_readers(SpiceSmartcardManager *manager)
{
return NULL;
}
#endif /* USE_SMARTCARD */