From b020ea5e3fa5f0af1ef8fd1306164fa72e19b1b7 Mon Sep 17 00:00:00 2001 From: Stefan Walter Date: Sun, 20 Jul 2008 02:56:06 +0000 Subject: [PATCH] Auto login of sessions when and where necessary. * gp11/gp11.h: * gp11/gp11-call.c: * gp11/gp11-marshal.list: * gp11/gp11-private.h: * gp11/gp11-session.c: * gp11/gp11-slot.c: * gp11/Makefile.am: * gp11/tests/gp11-test-module.c: * gp11/tests/unit-test-gp11-session.c: Auto login of sessions when and where necessary. svn path=/trunk/; revision=1185 --- gp11/Makefile.am | 13 +++++- gp11/gp11-call.c | 74 ++++++++++++++++++++++++++++--- gp11/gp11-marshal.list | 2 + gp11/gp11-private.h | 13 +++++- gp11/gp11-session.c | 22 +++++++++- gp11/gp11-slot.c | 87 ++++++++++++++++++++++++++++++++++++- gp11/gp11.h | 16 +++++++ gp11/tests/gp11-test-module.c | 32 +++++++++----- gp11/tests/unit-test-gp11-session.c | 61 ++++++++++++++++++++++++++ 9 files changed, 297 insertions(+), 23 deletions(-) create mode 100644 gp11/gp11-marshal.list diff --git a/gp11/Makefile.am b/gp11/Makefile.am index ee16547..25f5b6b 100644 --- a/gp11/Makefile.am +++ b/gp11/Makefile.am @@ -11,6 +11,9 @@ INCLUDES = \ $(GTHREAD_CFLAGS) \ $(GLIB_CFLAGS) +BUILT_SOURCES = \ + gp11-marshal.c gp11-marshal.h + lib_LTLIBRARIES = libgp11.la libgp11_la_SOURCES = \ @@ -21,7 +24,8 @@ libgp11_la_SOURCES = \ gp11-module.c \ gp11-object.c \ gp11-session.c \ - gp11-slot.c + gp11-slot.c \ + $(BUILT_SOURCES) libgp11_la_LDFLAGS = \ -version-info $(GP11_LT_RELEASE) \ @@ -33,6 +37,13 @@ libgp11_la_LIBADD = \ $(GIO_LIBS) \ $(GLIB_LIBS) +gp11-marshal.h: gp11-marshal.list $(GLIB_GENMARSHAL) + $(GLIB_GENMARSHAL) $< --header --prefix=_gp11_marshal > $@ + +gp11-marshal.c: gp11-marshal.list $(GLIB_GENMARSHAL) + echo "#include \"gp11-marshal.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=_gp11_marshal >> $@ + pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = gp11-$(GP11_MAJOR).pc diff --git a/gp11/gp11-call.c b/gp11/gp11-call.c index 824d6f6..937ec24 100644 --- a/gp11/gp11-call.c +++ b/gp11/gp11-call.c @@ -1,6 +1,7 @@ #include "gp11-private.h" +#include static GThreadPool *thread_pool = NULL; static GAsyncQueue *completed_queue = NULL; @@ -48,12 +49,27 @@ static void process_async_call (gpointer data, gpointer unused) { GP11Call *call = GP11_CALL (data); + CK_ULONG pin_len; + g_assert (GP11_IS_CALL (call)); - g_assert (call->rv == CKR_OK); + /* Try to login to the token, with the provided password */ + if (call->do_login) { + call->do_login = FALSE; + + pin_len = call->password ? strlen (call->password) : 0; + call->rv = (call->args->pkcs11->C_Login) (call->args->handle, CKU_USER, + (CK_UTF8CHAR_PTR)call->password, + pin_len); - call->rv = perform_call (call->func, call->cancellable, - call->args); + /* Fix the result so that we'll try the login again */ + if (call->rv == CKR_PIN_INCORRECT) + call->rv = CKR_USER_NOT_LOGGED_IN; + + /* An actual call */ + } else { + call->rv = perform_call (call->func, call->cancellable, call->args); + } g_async_queue_push (completed_queue, call); @@ -69,9 +85,28 @@ process_result (GP11Call *call, gpointer unused) if (call->cancellable) { /* Don't call the callback when cancelled */ if (g_cancellable_is_cancelled (call->cancellable)) - return; + call->rv = CKR_FUNCTION_CANCELED; + } + + /* + * Now if this is a session call, and the slot wants does + * auto-login, then we try to get a password and do auto login. + */ + if (call->rv == CKR_USER_NOT_LOGGED_IN && GP11_IS_SESSION (call->object)) { + g_free (call->password); + call->password = NULL; + call->do_login = _gp11_slot_token_authentication (GP11_SESSION (call->object)->slot, + &call->password); + } + + /* If we're supposed to do a login, then queue this call again */ + if (call->do_login) { + g_object_ref (call); + g_thread_pool_push (thread_pool, call, NULL); + return; } + /* All done, finish processing */ if (call->callback) { g_assert (G_IS_OBJECT (call->object)); (call->callback) (G_OBJECT (call->object), G_ASYNC_RESULT (call), @@ -154,6 +189,10 @@ _gp11_call_finalize (GObject *obj) call->destroy = NULL; call->args = NULL; + if (call->password) + g_free (call->password); + call->password = NULL; + G_OBJECT_CLASS (_gp11_call_parent_class)->finalize (obj); } @@ -247,7 +286,9 @@ _gp11_call_sync (gpointer object, gpointer func, gpointer data, GCancellable *cancellable, GError **err) { GP11Arguments *args = (GP11Arguments*)data; - GP11Module *module; + gchar *password = NULL; + GP11Module *module = NULL; + CK_ULONG pin_len; CK_RV rv; g_assert (G_IS_OBJECT (object)); @@ -261,6 +302,29 @@ _gp11_call_sync (gpointer object, gpointer func, gpointer data, g_object_unref (module); rv = perform_call ((GP11CallFunc)func, cancellable, args); + + /* + * Now if this is a session call, and the slot wants does + * auto-login, then we try to get a password and do auto login. + */ + if (rv == CKR_USER_NOT_LOGGED_IN && GP11_IS_SESSION (object)) { + + do { + if (!_gp11_slot_token_authentication (GP11_SESSION (object)->slot, + &password)) { + rv = CKR_USER_NOT_LOGGED_IN; + } else { + pin_len = password ? strlen (password) : 0; + rv = (args->pkcs11->C_Login) (args->handle, CKU_USER, + (CK_UTF8CHAR_PTR)password, pin_len); + } + } while (rv == CKR_PIN_INCORRECT); + + /* If we logged in successfully then try again */ + if (rv == CKR_OK) + rv = perform_call ((GP11CallFunc)func, cancellable, args); + } + if (rv == CKR_OK) return TRUE; diff --git a/gp11/gp11-marshal.list b/gp11/gp11-marshal.list new file mode 100644 index 0000000..7e7cf5d --- /dev/null +++ b/gp11/gp11-marshal.list @@ -0,0 +1,2 @@ +BOOLEAN:POINTER +BOOLEAN:OBJECT,POINTER diff --git a/gp11/gp11-private.h b/gp11/gp11-private.h index 24ac16c..d822524 100644 --- a/gp11/gp11-private.h +++ b/gp11/gp11-private.h @@ -26,6 +26,13 @@ void _gp11_attributes_add_take (GP11Attributes *att CK_ATTRIBUTE_PTR _gp11_attributes_raw (GP11Attributes *attrs); /* ---------------------------------------------------------------------------- + * SLOT + */ + +gboolean _gp11_slot_token_authentication (GP11Slot *slot, + gchar **password); + +/* ---------------------------------------------------------------------------- * CALL */ @@ -42,7 +49,7 @@ typedef struct _GP11Arguments { } GP11Arguments; -#define GP11_ARGUMENTS_INIT { NULL, NULL, 0 } +#define GP11_ARGUMENTS_INIT { NULL, NULL, 0 } #define GP11_TYPE_CALL (_gp11_call_get_type()) #define GP11_CALL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GP11_TYPE_CALL, GP11Call)) @@ -67,6 +74,10 @@ struct _GP11Call { gpointer object; GAsyncReadyCallback callback; gpointer user_data; + + /* For authenticating */ + gboolean do_login; + gchar *password; }; struct _GP11CallClass { diff --git a/gp11/gp11-session.c b/gp11/gp11-session.c index 3845edd..f8a2962 100644 --- a/gp11/gp11-session.c +++ b/gp11/gp11-session.c @@ -14,7 +14,8 @@ enum { enum { PROP_0, PROP_MODULE, - PROP_HANDLE + PROP_HANDLE, + PROP_SLOT }; G_DEFINE_TYPE (GP11Session, gp11_session, G_TYPE_OBJECT); @@ -44,6 +45,9 @@ gp11_session_get_property (GObject *obj, guint prop_id, GValue *value, case PROP_HANDLE: g_value_set_uint (value, session->handle); break; + case PROP_SLOT: + g_value_set_object(value, session->slot); + break; } } @@ -63,6 +67,11 @@ gp11_session_set_property (GObject *obj, guint prop_id, const GValue *value, g_return_if_fail (!session->handle); session->handle = g_value_get_uint (value); break; + case PROP_SLOT: + g_return_if_fail (!session->slot); + session->slot = g_value_dup_object (value); + g_return_if_fail (session->slot); + break; } } @@ -91,6 +100,10 @@ gp11_session_dispose (GObject *obj) session->handle = 0; } + if (session->slot) + g_object_unref (session->slot); + session->slot = NULL; + if (session->module) g_object_unref (session->module); session->module = NULL; @@ -129,6 +142,10 @@ gp11_session_class_init (GP11SessionClass *klass) g_param_spec_uint ("handle", "Session Handle", "PKCS11 Session Handle", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, PROP_SLOT, + g_param_spec_object ("slot", "Slot that this session uses", "PKCS11 Slot", + GP11_TYPE_SLOT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + signals[DISCARD_HANDLE] = g_signal_new ("discard-handle", GP11_TYPE_SESSION, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GP11SessionClass, discard_handle), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); @@ -151,7 +168,8 @@ GP11Session* gp11_session_from_handle (GP11Slot *slot, CK_SESSION_HANDLE handle) { g_return_val_if_fail (GP11_IS_SLOT (slot), NULL); - return g_object_new (GP11_TYPE_SESSION, "module", slot->module, "handle", handle, NULL); + return g_object_new (GP11_TYPE_SESSION, "module", slot->module, + "handle", handle, "slot", slot, NULL); } GP11SessionInfo* diff --git a/gp11/gp11-slot.c b/gp11/gp11-slot.c index a3ada5d..cf4212a 100644 --- a/gp11/gp11-slot.c +++ b/gp11/gp11-slot.c @@ -3,6 +3,7 @@ #include "gp11.h" #include "gp11-private.h" +#include "gp11-marshal.h" #include @@ -10,12 +11,23 @@ enum { PROP_0, PROP_MODULE, PROP_HANDLE, - PROP_REUSE_SESSIONS + PROP_REUSE_SESSIONS, + PROP_AUTO_LOGIN +}; + +enum { + AUTHENTICATE_TOKEN, +#ifdef UNIMPLEMENTED + AUTHENTICATE_KEY, + SLOT_EVENT +#endif + LAST_SIGNAL }; typedef struct _GP11SlotPrivate { - gboolean reuse_sessions; + gboolean auto_login; GHashTable *open_sessions; + GP11TokenInfo *token_info; } GP11SlotPrivate; G_DEFINE_TYPE (GP11Slot, gp11_slot, G_TYPE_OBJECT); @@ -29,6 +41,8 @@ typedef struct _SessionPool { GSList *sessions; /* list of CK_SESSION_HANDLE */ } SessionPool; +static guint signals[LAST_SIGNAL] = { 0 }; + /* ---------------------------------------------------------------------------- * HELPERS */ @@ -206,6 +220,14 @@ make_session_object (GP11Slot *slot, guint flags, CK_SESSION_HANDLE handle) return session; } +static void +ensure_token_info (GP11Slot *slot) +{ + GP11SlotPrivate *pv = GP11_SLOT_GET_PRIVATE (slot); + if (!pv->token_info) + pv->token_info = gp11_slot_get_token_info (slot); +} + /* ---------------------------------------------------------------------------- * OBJECT */ @@ -230,6 +252,9 @@ gp11_slot_get_property (GObject *obj, guint prop_id, GValue *value, case PROP_HANDLE: g_value_set_uint (value, slot->handle); break; + case PROP_AUTO_LOGIN: + g_value_set_boolean (value, pv->auto_login); + break; case PROP_REUSE_SESSIONS: g_value_set_boolean (value, pv->open_sessions != NULL); break; @@ -240,6 +265,7 @@ static void gp11_slot_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) { + GP11SlotPrivate *pv = GP11_SLOT_GET_PRIVATE (obj); GP11Slot *slot = GP11_SLOT (obj); switch (prop_id) { @@ -253,6 +279,9 @@ gp11_slot_set_property (GObject *obj, guint prop_id, const GValue *value, g_return_if_fail (!slot->handle); slot->handle = g_value_get_uint (value); break; + case PROP_AUTO_LOGIN: + pv->auto_login = g_value_get_boolean (value); + break; case PROP_REUSE_SESSIONS: if (g_value_get_boolean (value)) create_session_table (slot); @@ -308,14 +337,54 @@ gp11_slot_class_init (GP11SlotClass *klass) g_param_spec_uint ("handle", "Handle", "PKCS11 Slot ID", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (gobject_class, PROP_AUTO_LOGIN, + g_param_spec_boolean ("auto-login", "Auto Login", "Auto Login to Token when necessary", + FALSE, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_REUSE_SESSIONS, g_param_spec_boolean ("reuse-sessions", "Reuse Sessions", "Reuse sessions?", FALSE, G_PARAM_READWRITE)); + signals[AUTHENTICATE_TOKEN] = g_signal_new ("authenticate-token", GP11_TYPE_SLOT, + G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GP11SlotClass, authenticate_token), + g_signal_accumulator_true_handled, NULL, _gp11_marshal_BOOLEAN__POINTER, + G_TYPE_BOOLEAN, 1, G_TYPE_POINTER); + g_type_class_add_private (gobject_class, sizeof (GP11SlotPrivate)); } /* ---------------------------------------------------------------------------- + * INTERNAL + */ + +gboolean +_gp11_slot_token_authentication (GP11Slot *slot, gchar **password) +{ + GP11SlotPrivate *pv = GP11_SLOT_GET_PRIVATE (slot); + gboolean ret = FALSE; + + g_return_val_if_fail (GP11_IS_SLOT (slot), FALSE); + g_return_val_if_fail (password, FALSE); + + if (!pv->auto_login) + return FALSE; + + /* + * If it's a protected authentication path style token, then + * we don't prompt here, the hardware/software is expected + * to prompt the user in some other way. + */ + ensure_token_info (slot); + if (pv->token_info && (pv->token_info->flags & CKF_PROTECTED_AUTHENTICATION_PATH)) { + *password = NULL; + return TRUE; + } + + g_signal_emit (slot, signals[AUTHENTICATE_TOKEN], 0, password, &ret); + return ret; +} + +/* ---------------------------------------------------------------------------- * PUBLIC */ @@ -363,6 +432,20 @@ gp11_slot_set_reuse_sessions (GP11Slot *slot, gboolean reuse) g_object_set (slot, "reuse-sessions", reuse, NULL); } +gboolean +gp11_slot_get_auto_login (GP11Slot *slot) +{ + gboolean auto_login = FALSE; + g_object_get (slot, "auto-login", &auto_login, NULL); + return auto_login; +} + +void +gp11_slot_set_auto_login (GP11Slot *slot, gboolean auto_login) +{ + g_object_set (slot, "auto-login", auto_login, NULL); +} + GP11SlotInfo* gp11_slot_get_info (GP11Slot *slot) { diff --git a/gp11/gp11.h b/gp11/gp11.h index 1a1fa3c..fa11648 100644 --- a/gp11/gp11.h +++ b/gp11/gp11.h @@ -297,7 +297,15 @@ struct _GP11Slot { struct _GP11SlotClass { GObjectClass parent; + gboolean (*authenticate_token) (GP11Slot *slot, gchar **password); + +#ifdef UNIMPLEMENTED + gboolean (*authenticate_key) (GP11Slot *slot, GP11Object *object, + gchar **password); + void (*slot_event) (GP11Slot *slot); +#endif + }; GType gp11_slot_get_type (void) G_GNUC_CONST; @@ -307,6 +315,13 @@ gboolean gp11_slot_get_reuse_sessions (GP11Slot *slot); void gp11_slot_set_reuse_sessions (GP11Slot *slot, gboolean reuse); +gboolean gp11_slot_get_auto_login (GP11Slot *slot); + +void gp11_slot_set_auto_login (GP11Slot *slot, + gboolean auto_login); + +gint gp11_slot_get_max_pin_length (GP11Slot *slot); + GP11SlotInfo* gp11_slot_get_info (GP11Slot *slot); GP11TokenInfo* gp11_slot_get_token_info (GP11Slot *slot); @@ -382,6 +397,7 @@ typedef struct _GP11SessionClass GP11SessionClass; struct _GP11Session { GObject parent; + GP11Slot *slot; GP11Module *module; CK_SESSION_HANDLE handle; }; diff --git a/gp11/tests/gp11-test-module.c b/gp11/tests/gp11-test-module.c index d800241..533a8ad 100644 --- a/gp11/tests/gp11-test-module.c +++ b/gp11/tests/gp11-test-module.c @@ -104,8 +104,6 @@ test_C_Initialize (CK_VOID_PTR pInitArgs) static CK_RV test_C_Finalize (CK_VOID_PTR pReserved) { - - g_assert (pReserved == NULL && "Invalid reserved pointer"); g_assert (initialized == TRUE && "Finalize without being initialized"); @@ -199,8 +197,6 @@ const static CK_SLOT_INFO TEST_INFO_TWO = { static CK_RV test_C_GetSlotInfo (CK_SLOT_ID slotID, CK_SLOT_INFO_PTR pInfo) { - - g_assert (pInfo != NULL && "Invalid pInfo"); if (slotID == TEST_SLOT_ONE) { @@ -220,7 +216,7 @@ const static CK_TOKEN_INFO TEST_TOKEN_ONE = { "TEST MANUFACTURER ", "TEST MODEL ", "TEST SERIAL ", - CKF_LOGIN_REQUIRED | CKF_USER_PIN_INITIALIZED | CKF_CLOCK_ON_TOKEN | CKF_PROTECTED_AUTHENTICATION_PATH | CKF_TOKEN_INITIALIZED, + CKF_LOGIN_REQUIRED | CKF_USER_PIN_INITIALIZED | CKF_CLOCK_ON_TOKEN | CKF_TOKEN_INITIALIZED, 1, 2, 3, @@ -239,8 +235,6 @@ const static CK_TOKEN_INFO TEST_TOKEN_ONE = { static CK_RV test_C_GetTokenInfo (CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) { - - g_assert (pInfo != NULL && "Invalid pInfo"); if (slotID == TEST_SLOT_ONE) { @@ -297,8 +291,6 @@ static CK_RV test_C_GetMechanismInfo (CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, CK_MECHANISM_INFO_PTR pInfo) { - - g_assert (slotID == TEST_SLOT_ONE && "Invalid slotID"); g_assert (pInfo != NULL && "Invalid pInfo"); @@ -507,7 +499,7 @@ test_C_CreateObject (CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, { GP11Attributes *attrs; Session *session; - gboolean token; + gboolean token, priv; CK_ULONG i; g_assert (phObject != NULL); @@ -521,6 +513,13 @@ test_C_CreateObject (CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, for (i = 0; i < ulCount; ++i) gp11_attributes_add_data (attrs, pTemplate[i].type, pTemplate[i].pValue, pTemplate[i].ulValueLen); + if (gp11_attributes_find_boolean (attrs, CKA_PRIVATE, &priv) && priv) { + if (!session->logged_in) { + gp11_attributes_unref (attrs); + return CKR_USER_NOT_LOGGED_IN; + } + } + *phObject = ++unique_identifier; if (gp11_attributes_find_boolean (attrs, CKA_TOKEN, &token) && token) g_hash_table_insert (the_objects, GUINT_TO_POINTER (*phObject), attrs); @@ -543,19 +542,28 @@ test_C_CopyObject (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, static CK_RV test_C_DestroyObject (CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject) { + GP11Attributes *attrs; Session *session; + gboolean priv; session = g_hash_table_lookup (the_sessions, GUINT_TO_POINTER (hSession)); g_assert (session != NULL && "No such session found"); if (!session) return CKR_SESSION_HANDLE_INVALID; - if (!g_hash_table_remove (the_objects, GUINT_TO_POINTER (hObject)) && - !g_hash_table_remove (session->objects, GUINT_TO_POINTER (hObject))) { + attrs = g_hash_table_lookup (the_objects, GUINT_TO_POINTER (hObject)); + if (!attrs) + attrs = g_hash_table_lookup (session->objects, GUINT_TO_POINTER (hObject)); + if (!attrs) { g_assert_not_reached (); /* "no such object found" */ return CKR_OBJECT_HANDLE_INVALID; } + if (gp11_attributes_find_boolean (attrs, CKA_PRIVATE, &priv) && priv) { + if (!session->logged_in) + return CKR_USER_NOT_LOGGED_IN; + } + return CKR_OK; } diff --git a/gp11/tests/unit-test-gp11-session.c b/gp11/tests/unit-test-gp11-session.c index 69ac1a9..a5f4099 100644 --- a/gp11/tests/unit-test-gp11-session.c +++ b/gp11/tests/unit-test-gp11-session.c @@ -194,3 +194,64 @@ DEFINE_TEST(login_logout) result = NULL; } + +static gboolean +authenticate_token (GP11Slot *slot, gchar **password, gpointer unused) +{ + g_assert (unused == GUINT_TO_POINTER (35)); + g_assert (password != NULL); + g_assert (*password == NULL); + g_assert (GP11_IS_SLOT (slot)); + + *password = g_strdup ("booo"); + return TRUE; +} + +DEFINE_TEST(auto_login) +{ + GP11Object *object; + GAsyncResult *result = NULL; + GError *err = NULL; + GP11Attributes *attrs; + gboolean ret; + + attrs = gp11_attributes_newv (CKA_CLASS, GP11_ULONG, CKO_DATA, + CKA_LABEL, GP11_STRING, "TEST OBJECT", + CKA_PRIVATE, GP11_BOOLEAN, CK_TRUE, + -1); + + /* Try to do something that requires a login */ + object = gp11_session_create_object_full (session, attrs, NULL, &err); + g_assert (!object); + g_assert (err && err->code == CKR_USER_NOT_LOGGED_IN); + g_clear_error (&err); + + /* Setup for auto login */ + g_assert (gp11_slot_get_auto_login (slot) == FALSE); + gp11_slot_set_auto_login (slot, TRUE); + g_assert (gp11_slot_get_auto_login (slot) == TRUE); + + g_signal_connect (slot, "authenticate-token", G_CALLBACK (authenticate_token), GUINT_TO_POINTER (35)); + + /* Try again to do something that requires a login */ + object = gp11_session_create_object_full (session, attrs, NULL, &err); + SUCCESS_RES (object, err); + g_object_unref (object); + + /* We should now be logged in, try to log out */ + ret = gp11_session_logout (session, &err); + SUCCESS_RES (ret, err); + + /* Now try the same thing, but asyncronously */ + gp11_session_create_object_async (session, attrs, NULL, fetch_async_result, &result); + WAIT_UNTIL (result); + g_assert (result != NULL); + object = gp11_session_create_object_finish (session, result, &err); + SUCCESS_RES (object, err); + g_object_unref (result); + g_object_unref (object); + + /* We should now be logged in, try to log out */ + ret = gp11_session_logout (session, &err); + SUCCESS_RES (ret, err); +} -- 2.7.4