1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3    Copyright (C) 2011 Red Hat, Inc.
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "config.h"
19 
20 #include "spice-client.h"
21 #include "spice-common.h"
22 
23 #include "spice-channel-priv.h"
24 #include "smartcard-manager.h"
25 #include "smartcard-manager-priv.h"
26 #include "spice-session-priv.h"
27 
28 /**
29  * SECTION:channel-smartcard
30  * @short_description: smartcard authentication
31  * @title: Smartcard Channel
32  * @section_id:
33  * @see_also: #SpiceSmartcardManager, #SpiceSession
34  * @stability: Stable
35  * @include: spice-client.h
36  *
37  * The Spice protocol defines a set of messages to forward smartcard
38  * information from the Spice client to the VM. This channel handles
39  * these messages. While it's mainly focus on smartcard readers and
40  * smartcards, it's also possible to use it with a software smartcard
41  * (ie a set of 3 certificates from the client machine).
42  * This class doesn't provide useful methods, see #SpiceSession properties
43  * for a way to enable/disable this channel, and #SpiceSmartcardManager
44  * if you want to detect smartcard reader hotplug/unplug, and smartcard
45  * insertion/removal.
46  */
47 
48 struct _SpiceSmartcardChannelMessage {
49 #ifdef USE_SMARTCARD
50     VSCMsgType message_type;
51 #endif
52     SpiceMsgOut *message;
53 };
54 typedef struct _SpiceSmartcardChannelMessage SpiceSmartcardChannelMessage;
55 
56 
57 struct _SpiceSmartcardChannelPrivate {
58     /* track readers that have been added but for which we didn't receive
59      * an ack from the spice server yet. We rely on the fact that the
60      * readers in this list are ordered by the time we sent the request to
61      * the server. When we get an ack from the server for a reader addition,
62      * we can pop the 1st entry to get the reader the ack corresponds to. */
63     GList *pending_reader_additions;
64 
65     /* used to removals of readers that were not ack'ed yet by the spice
66      * server */
67     GHashTable *pending_reader_removals;
68 
69     /* used to track card insertions on readers that were not ack'ed yet
70      * by the spice server */
71     GHashTable *pending_card_insertions;
72 
73     /* next commands to be sent to the spice server. This is needed since
74      * we have to wait for a command answer before sending the next one
75      */
76     GQueue *message_queue;
77 
78     /* message that is currently being processed by the spice server (ie last
79      * message that was sent to the server)
80      */
81     SpiceSmartcardChannelMessage *in_flight_message;
82 };
83 
84 G_DEFINE_TYPE_WITH_PRIVATE(SpiceSmartcardChannel, spice_smartcard_channel, SPICE_TYPE_CHANNEL)
85 
86 enum {
87 
88     SPICE_SMARTCARD_LAST_SIGNAL,
89 };
90 
91 static void spice_smartcard_channel_up(SpiceChannel *channel);
92 static void handle_smartcard_msg(SpiceChannel *channel, SpiceMsgIn *in);
93 static void smartcard_message_free(SpiceSmartcardChannelMessage *message);
94 
95 /* ------------------------------------------------------------------ */
96 #ifdef USE_SMARTCARD
97 static void reader_added_cb(SpiceSmartcardManager *manager, VReader *reader,
98                             gpointer user_data);
99 static void reader_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
100                               gpointer user_data);
101 static void card_inserted_cb(SpiceSmartcardManager *manager, VReader *reader,
102                              gpointer user_data);
103 static void card_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
104                             gpointer user_data);
105 #endif
106 
spice_smartcard_channel_init(SpiceSmartcardChannel * channel)107 static void spice_smartcard_channel_init(SpiceSmartcardChannel *channel)
108 {
109     SpiceSmartcardChannelPrivate *priv;
110 
111     channel->priv = spice_smartcard_channel_get_instance_private(channel);
112     priv = channel->priv;
113     priv->message_queue = g_queue_new();
114 
115 #ifdef USE_SMARTCARD
116     priv->pending_card_insertions =
117         g_hash_table_new_full(g_direct_hash, g_direct_equal,
118                               (GDestroyNotify)vreader_free, NULL);
119     priv->pending_reader_removals =
120          g_hash_table_new_full(g_direct_hash, g_direct_equal,
121                                (GDestroyNotify)vreader_free, NULL);
122 #endif
123 }
124 
spice_smartcard_channel_constructed(GObject * object)125 static void spice_smartcard_channel_constructed(GObject *object)
126 {
127     SpiceSession *s = spice_channel_get_session(SPICE_CHANNEL(object));
128 
129     g_return_if_fail(s != NULL);
130 
131 #ifdef USE_SMARTCARD
132     if (!spice_session_is_for_migration(s)) {
133         SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(object);
134         SpiceSmartcardManager *manager = spice_smartcard_manager_get();
135 
136         spice_g_signal_connect_object(G_OBJECT(manager), "reader-added",
137                                       (GCallback)reader_added_cb, channel, 0);
138         spice_g_signal_connect_object(G_OBJECT(manager), "reader-removed",
139                                       (GCallback)reader_removed_cb, channel, 0);
140         spice_g_signal_connect_object(G_OBJECT(manager), "card-inserted",
141                                       (GCallback)card_inserted_cb, channel, 0);
142         spice_g_signal_connect_object(G_OBJECT(manager), "card-removed",
143                                       (GCallback)card_removed_cb, channel, 0);
144     }
145 #endif
146 
147     if (G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->constructed)
148         G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->constructed(object);
149 
150 }
151 
spice_smartcard_channel_finalize(GObject * obj)152 static void spice_smartcard_channel_finalize(GObject *obj)
153 {
154     SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(obj);
155     SpiceSmartcardChannelPrivate *c = channel->priv;
156 
157     g_clear_pointer(&c->pending_card_insertions, g_hash_table_destroy);
158     g_clear_pointer(&c->pending_reader_removals, g_hash_table_destroy);
159     if (c->message_queue != NULL) {
160         g_queue_foreach(c->message_queue, (GFunc)smartcard_message_free, NULL);
161         g_queue_free(c->message_queue);
162         c->message_queue = NULL;
163     }
164     g_clear_pointer(&c->in_flight_message, smartcard_message_free);
165     g_clear_pointer(&c->pending_reader_additions, g_list_free);
166 
167     if (G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->finalize)
168         G_OBJECT_CLASS(spice_smartcard_channel_parent_class)->finalize(obj);
169 }
170 
spice_smartcard_channel_reset(SpiceChannel * channel,gboolean migrating)171 static void spice_smartcard_channel_reset(SpiceChannel *channel, gboolean migrating)
172 {
173     SpiceSmartcardChannel *smartcard_channel = SPICE_SMARTCARD_CHANNEL(channel);
174     SpiceSmartcardChannelPrivate *c = smartcard_channel->priv;
175 
176     g_hash_table_remove_all(c->pending_card_insertions);
177     g_hash_table_remove_all(c->pending_reader_removals);
178 
179     if (c->message_queue != NULL) {
180         g_queue_foreach(c->message_queue, (GFunc)smartcard_message_free, NULL);
181         g_queue_clear(c->message_queue);
182     }
183 
184     g_clear_pointer(&c->in_flight_message, smartcard_message_free);
185     g_clear_pointer(&c->pending_reader_additions, g_list_free);
186 
187     SPICE_CHANNEL_CLASS(spice_smartcard_channel_parent_class)->channel_reset(channel, migrating);
188 }
189 
channel_set_handlers(SpiceChannelClass * klass)190 static void channel_set_handlers(SpiceChannelClass *klass)
191 {
192     static const spice_msg_handler handlers[] = {
193         [ SPICE_MSG_SMARTCARD_DATA ] = handle_smartcard_msg,
194     };
195     spice_channel_set_handlers(klass, handlers, G_N_ELEMENTS(handlers));
196 }
197 
spice_smartcard_channel_class_init(SpiceSmartcardChannelClass * klass)198 static void spice_smartcard_channel_class_init(SpiceSmartcardChannelClass *klass)
199 {
200     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
201     SpiceChannelClass *channel_class = SPICE_CHANNEL_CLASS(klass);
202 
203     gobject_class->finalize     = spice_smartcard_channel_finalize;
204     gobject_class->constructed  = spice_smartcard_channel_constructed;
205 
206     channel_class->channel_up   = spice_smartcard_channel_up;
207     channel_class->channel_reset = spice_smartcard_channel_reset;
208 
209     channel_set_handlers(SPICE_CHANNEL_CLASS(klass));
210 }
211 
212 /* ------------------------------------------------------------------ */
213 /* private api                                                        */
214 
215 static void
smartcard_message_free(SpiceSmartcardChannelMessage * message)216 smartcard_message_free(SpiceSmartcardChannelMessage *message)
217 {
218     if (message->message)
219         spice_msg_out_unref(message->message);
220     g_free(message);
221 }
222 
223 #ifdef USE_SMARTCARD
is_attached_to_server(VReader * reader)224 static gboolean is_attached_to_server(VReader *reader)
225 {
226     return (vreader_get_id(reader) != (vreader_id_t)-1);
227 }
228 
229 static gboolean
spice_channel_has_pending_card_insertion(SpiceSmartcardChannel * channel,VReader * reader)230 spice_channel_has_pending_card_insertion(SpiceSmartcardChannel *channel,
231                                          VReader *reader)
232 {
233     return (g_hash_table_lookup(channel->priv->pending_card_insertions, reader) != NULL);
234 }
235 
236 static void
spice_channel_queue_card_insertion(SpiceSmartcardChannel * channel,VReader * reader)237 spice_channel_queue_card_insertion(SpiceSmartcardChannel *channel,
238                                    VReader *reader)
239 {
240     vreader_reference(reader);
241     g_hash_table_insert(channel->priv->pending_card_insertions,
242                         reader, reader);
243 }
244 
245 static void
spice_channel_drop_pending_card_insertion(SpiceSmartcardChannel * channel,VReader * reader)246 spice_channel_drop_pending_card_insertion(SpiceSmartcardChannel *channel,
247                                           VReader *reader)
248 {
249     g_hash_table_remove(channel->priv->pending_card_insertions, reader);
250 }
251 
252 static gboolean
spice_channel_has_pending_reader_removal(SpiceSmartcardChannel * channel,VReader * reader)253 spice_channel_has_pending_reader_removal(SpiceSmartcardChannel *channel,
254                                          VReader *reader)
255 {
256     return (g_hash_table_lookup(channel->priv->pending_reader_removals, reader) != NULL);
257 }
258 
259 static void
spice_channel_queue_reader_removal(SpiceSmartcardChannel * channel,VReader * reader)260 spice_channel_queue_reader_removal(SpiceSmartcardChannel *channel,
261                                    VReader *reader)
262 {
263     vreader_reference(reader);
264     g_hash_table_insert(channel->priv->pending_reader_removals,
265                         reader, reader);
266 }
267 
268 static void
spice_channel_drop_pending_reader_removal(SpiceSmartcardChannel * channel,VReader * reader)269 spice_channel_drop_pending_reader_removal(SpiceSmartcardChannel *channel,
270                                           VReader *reader)
271 {
272     g_hash_table_remove(channel->priv->pending_reader_removals, reader);
273 }
274 
275 static SpiceSmartcardChannelMessage *
smartcard_message_new(VSCMsgType msg_type,SpiceMsgOut * msg_out)276 smartcard_message_new(VSCMsgType msg_type, SpiceMsgOut *msg_out)
277 {
278     SpiceSmartcardChannelMessage *message;
279 
280     message = g_new0(SpiceSmartcardChannelMessage, 1);
281     message->message = msg_out;
282     message->message_type = msg_type;
283 
284     return message;
285 }
286 
287 /* Indicates that handling of the message that is currently in flight has
288  * been completed. If needed, sends the next queued command to the server. */
289 static void
smartcard_message_complete_in_flight(SpiceSmartcardChannel * channel)290 smartcard_message_complete_in_flight(SpiceSmartcardChannel *channel)
291 {
292     g_return_if_fail(channel->priv->in_flight_message != NULL);
293 
294     smartcard_message_free(channel->priv->in_flight_message);
295     channel->priv->in_flight_message = g_queue_pop_head(channel->priv->message_queue);
296     if (channel->priv->in_flight_message != NULL) {
297         spice_msg_out_send(channel->priv->in_flight_message->message);
298         channel->priv->in_flight_message->message = NULL;
299     }
300 }
301 
smartcard_message_send(SpiceSmartcardChannel * channel,VSCMsgType msg_type,SpiceMsgOut * msg_out,gboolean queue)302 static void smartcard_message_send(SpiceSmartcardChannel *channel,
303                                    VSCMsgType msg_type,
304                                    SpiceMsgOut *msg_out, gboolean queue)
305 {
306     SpiceSmartcardChannelMessage *message;
307 
308     if (spice_channel_get_read_only(SPICE_CHANNEL(channel)))
309         return;
310 
311     CHANNEL_DEBUG(channel, "send message %u, %s",
312                   msg_type, queue ? "queued" : "now");
313     if (!queue) {
314         spice_msg_out_send(msg_out);
315         return;
316     }
317 
318     message = smartcard_message_new(msg_type, msg_out);
319     if (channel->priv->in_flight_message == NULL) {
320         g_return_if_fail(g_queue_is_empty(channel->priv->message_queue));
321         channel->priv->in_flight_message = message;
322         spice_msg_out_send(channel->priv->in_flight_message->message);
323         channel->priv->in_flight_message->message = NULL;
324     } else {
325         g_queue_push_tail(channel->priv->message_queue, message);
326     }
327 }
328 
329 static void
send_msg_generic_with_data(SpiceSmartcardChannel * channel,VReader * reader,VSCMsgType msg_type,const uint8_t * data,gsize data_len,gboolean serialize_msg)330 send_msg_generic_with_data(SpiceSmartcardChannel *channel, VReader *reader,
331                            VSCMsgType msg_type,
332                            const uint8_t *data, gsize data_len,
333                            gboolean serialize_msg)
334 {
335     SpiceMsgOut *msg_out;
336     VSCMsgHeader header = {
337         .type = msg_type,
338         .length = data_len
339     };
340 
341     if(vreader_get_id(reader) == -1)
342         header.reader_id = VSCARD_UNDEFINED_READER_ID;
343     else
344         header.reader_id = vreader_get_id(reader);
345 
346     msg_out = spice_msg_out_new(SPICE_CHANNEL(channel),
347                                 SPICE_MSGC_SMARTCARD_DATA);
348     msg_out->marshallers->msgc_smartcard_header(msg_out->marshaller, &header);
349     if ((data != NULL) && (data_len != 0)) {
350         spice_marshaller_add(msg_out->marshaller, data, data_len);
351     }
352 
353     smartcard_message_send(channel, msg_type, msg_out, serialize_msg);
354 }
355 
send_msg_generic(SpiceSmartcardChannel * channel,VReader * reader,VSCMsgType msg_type)356 static void send_msg_generic(SpiceSmartcardChannel *channel, VReader *reader,
357                              VSCMsgType msg_type)
358 {
359     send_msg_generic_with_data(channel, reader, msg_type, NULL, 0, TRUE);
360 }
361 
send_msg_atr(SpiceSmartcardChannel * channel,VReader * reader)362 static void send_msg_atr(SpiceSmartcardChannel *channel, VReader *reader)
363 {
364 #define MAX_ATR_LEN 40 //this should be defined in libcacard
365     uint8_t atr[MAX_ATR_LEN];
366     int atr_len = MAX_ATR_LEN;
367 
368     g_return_if_fail(vreader_get_id(reader) != VSCARD_UNDEFINED_READER_ID);
369     vreader_power_on(reader, atr, &atr_len);
370     send_msg_generic_with_data(channel, reader, VSC_ATR, atr, atr_len, TRUE);
371 }
372 
reader_added_cb(SpiceSmartcardManager * manager,VReader * reader,gpointer user_data)373 static void reader_added_cb(SpiceSmartcardManager *manager, VReader *reader,
374                             gpointer user_data)
375 {
376     SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
377     const char *reader_name = vreader_get_name(reader);
378 
379     if (vreader_get_id(reader) != -1 ||
380         g_list_find(channel->priv->pending_reader_additions, reader))
381         return;
382 
383     channel->priv->pending_reader_additions =
384         g_list_append(channel->priv->pending_reader_additions, reader);
385 
386     send_msg_generic_with_data(channel, reader, VSC_ReaderAdd,
387                                (uint8_t*)reader_name, strlen(reader_name), TRUE);
388 }
389 
reader_removed_cb(SpiceSmartcardManager * manager,VReader * reader,gpointer user_data)390 static void reader_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
391                               gpointer user_data)
392 {
393     SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
394 
395     if (is_attached_to_server(reader)) {
396         send_msg_generic(channel, reader, VSC_ReaderRemove);
397     } else {
398         spice_channel_queue_reader_removal(channel, reader);
399     }
400 }
401 
402 /* ------------------------------------------------------------------ */
403 /* callbacks                                                          */
card_inserted_cb(SpiceSmartcardManager * manager,VReader * reader,gpointer user_data)404 static void card_inserted_cb(SpiceSmartcardManager *manager, VReader *reader,
405                              gpointer user_data)
406 {
407     SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
408 
409     if (is_attached_to_server(reader)) {
410         send_msg_atr(channel, reader);
411     } else {
412         spice_channel_queue_card_insertion(channel, reader);
413     }
414 }
415 
card_removed_cb(SpiceSmartcardManager * manager,VReader * reader,gpointer user_data)416 static void card_removed_cb(SpiceSmartcardManager *manager, VReader *reader,
417                             gpointer user_data)
418 {
419     SpiceSmartcardChannel *channel = SPICE_SMARTCARD_CHANNEL(user_data);
420 
421     if (is_attached_to_server(reader)) {
422         send_msg_generic(channel, reader, VSC_CardRemove);
423     } else {
424         /* this does nothing when reader has no card insertion pending */
425         spice_channel_drop_pending_card_insertion(channel, reader);
426     }
427 }
428 #endif /* USE_SMARTCARD */
429 
spice_smartcard_channel_up_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)430 static void spice_smartcard_channel_up_cb(GObject *source_object,
431                                           GAsyncResult *res,
432                                           gpointer user_data)
433 {
434     SpiceChannel *channel = SPICE_CHANNEL(user_data);
435 #ifdef USE_SMARTCARD
436     SpiceSmartcardManager *manager = spice_smartcard_manager_get();
437     GList *l, *list = NULL;
438 #endif
439     GError *error = NULL;
440 
441     g_return_if_fail(channel != NULL);
442     g_return_if_fail(SPICE_IS_SESSION(source_object));
443 
444     spice_smartcard_manager_init_finish(SPICE_SESSION(source_object),
445                                         res, &error);
446     if (error) {
447         g_warning("%s", error->message);
448         goto end;
449     }
450 
451 #ifdef USE_SMARTCARD
452     list = spice_smartcard_manager_get_readers(manager);
453     for (l = list; l != NULL; l = l->next) {
454         VReader *reader = l->data;
455         gboolean has_card = vreader_card_is_present(reader) == VREADER_OK;
456 
457         reader_added_cb(manager, reader, channel);
458         if (has_card)
459             card_inserted_cb(manager, reader, channel);
460 
461         g_boxed_free(SPICE_TYPE_SMARTCARD_READER, reader);
462     }
463 #endif
464 
465 end:
466 #ifdef USE_SMARTCARD
467     g_list_free(list);
468 #endif
469     g_clear_error(&error);
470 }
471 
spice_smartcard_channel_up(SpiceChannel * channel)472 static void spice_smartcard_channel_up(SpiceChannel *channel)
473 {
474     if (spice_session_is_for_migration(spice_channel_get_session(channel)))
475         return;
476 
477     spice_smartcard_manager_init_async(spice_channel_get_session(channel),
478                                        g_cancellable_new(),
479                                        spice_smartcard_channel_up_cb,
480                                        channel);
481 }
482 
handle_smartcard_msg(SpiceChannel * channel,SpiceMsgIn * in)483 static void handle_smartcard_msg(SpiceChannel *channel, SpiceMsgIn *in)
484 {
485 #ifdef USE_SMARTCARD
486     SpiceSmartcardChannel *smartcard_channel = SPICE_SMARTCARD_CHANNEL(channel);
487     SpiceSmartcardChannelPrivate *priv = smartcard_channel->priv;
488     SpiceMsgSmartcard *msg = spice_msg_in_parsed(in);
489     VReader *reader;
490 
491     CHANNEL_DEBUG(channel, "handle msg %u", msg->type);
492     switch (msg->type) {
493         case VSC_Error:
494             g_return_if_fail(priv->in_flight_message != NULL);
495             CHANNEL_DEBUG(channel, "in flight %u", priv->in_flight_message->message_type);
496             switch (priv->in_flight_message->message_type) {
497                 case VSC_ReaderAdd:
498                     g_return_if_fail(priv->pending_reader_additions != NULL);
499                     reader = priv->pending_reader_additions->data;
500                     g_return_if_fail(reader != NULL);
501                     g_return_if_fail(vreader_get_id(reader) == -1);
502                     priv->pending_reader_additions =
503                         g_list_delete_link(priv->pending_reader_additions,
504                                            priv->pending_reader_additions);
505                     vreader_set_id(reader, msg->reader_id);
506 
507                     if (spice_channel_has_pending_card_insertion(smartcard_channel, reader)) {
508                         send_msg_atr(smartcard_channel, reader);
509                         spice_channel_drop_pending_card_insertion(smartcard_channel, reader);
510                     }
511 
512                     if (spice_channel_has_pending_reader_removal(smartcard_channel, reader)) {
513                         send_msg_generic(smartcard_channel, reader, VSC_CardRemove);
514                         spice_channel_drop_pending_reader_removal(smartcard_channel, reader);
515                     }
516                     break;
517                 case VSC_APDU:
518                 case VSC_ATR:
519                 case VSC_CardRemove:
520                 case VSC_Error:
521                 case VSC_ReaderRemove:
522                     break;
523                 default:
524                     g_warning("Unexpected message: %u", priv->in_flight_message->message_type);
525                     break;
526             }
527             smartcard_message_complete_in_flight(smartcard_channel);
528 
529             break;
530 
531         case VSC_APDU:
532         case VSC_Init: {
533             const unsigned int APDU_BUFFER_SIZE = 270;
534             VReaderStatus reader_status;
535             uint8_t data_out[APDU_BUFFER_SIZE + sizeof(uint32_t)];
536             int data_out_len = sizeof(data_out);
537 
538             g_return_if_fail(msg->reader_id != VSCARD_UNDEFINED_READER_ID);
539             reader = vreader_get_reader_by_id(msg->reader_id);
540             g_return_if_fail(reader != NULL); //FIXME: add log message
541 
542             reader_status = vreader_xfr_bytes(reader,
543                                               msg->data, msg->length,
544                                               data_out, &data_out_len);
545             if (reader_status == VREADER_OK) {
546                 send_msg_generic_with_data(smartcard_channel,
547                                            reader, VSC_APDU,
548                                            data_out, data_out_len, FALSE);
549             } else {
550                 uint32_t error_code;
551                 error_code = GUINT32_TO_LE(reader_status);
552                 send_msg_generic_with_data(smartcard_channel,
553                                            reader, VSC_Error,
554                                            (uint8_t*)&error_code,
555                                            sizeof (error_code), FALSE);
556             }
557             break;
558         }
559         default:
560             g_return_if_reached();
561     }
562 #endif
563 }
564