summaryrefslogtreecommitdiffstats
path: root/src/gtk-helpers/secrets.c
blob: 9de228482bbb0235018db73c36d20be3d6518125 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
/*
    Copyright (C) 2012  ABRT Team
    Copyright (C) 2012  RedHat inc.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "internal_libreport.h"
#include "internal_libreport_gtk.h"

#include <gio/gio.h>

#define SECRETS_SERVICE_BUS "org.freedesktop.secrets"
#define SECRETS_NAME_SPACE(interface) "org.freedesktop.Secret."interface

/* label and alias have to be same because of ksecrets */
#define SECRETS_COLLECTION_LABEL SECRETS_COLLECTION_ALIAS
#define SECRETS_COLLECTION_ALIAS "default"

#define SECRETS_SEARCH_ATTRIBUTE "libreportEventConfig"
#define SECRETS_OPTION_VALUE_DELIMITER '='

/* 5s timeout*/
#define SECRETS_CALL_DEFAULT_TIMEOUT 5000

/* Well known errors for which we have workarounds */
#define NOT_IMPLEMENTED_READ_ALIAS_ERROR "org.freedesktop.DBus.Error.UnknownMethod"
#define INVALID_PROPERTIES_ARGUMENTS_ERROR "org.freedesktop.DBus.Error.InvalidArgs"
#define GNOME_KEYRING_NOT_HAVING_SECRET_ERROR "org.freedesktop.DBus.Error.Failed"

/*
   Data structure:
   ===============
   Secret Service groups data into Collections. Each collection
   has a label and may have alternative names (aliases).
   There is a default collection, named "default".
   Libreport uses it to store our data.

   Collection is a set of Items identified by labels (label is a string).
   Libreport uses event names as labels for items which hold event config data.
   One item holds all configuration items for one event.

   Item has a set of (name,value) pairs called lookup attributes.
   Libreport uses 'libreportEventConfig' attribute for searching. The
   attribute hodls an event name.
   Item has a secret value. The value holds an arbitrary binary data.
   Libreport uses the value to store configuration items.
   The value consists from several c-strings where each option is
   represented by one c-string (bytes followed by '\0').
   Each option string is concatenation of option name, delimiter and option value.


   For example, "report_Bugzilla" item will have the lookup attribute
   like ("libreportEventConfig", "report_Bugzilla") and the secret value
   like ("Bugzilla_URL=https://bugzilla.redhat.com\0Bugzilla_Login=foo\0")



   DBus API:
   =========
   Secret Service requires user to first open a Session using OpenSession()
   call over session dbus on "/org/freedesktop/secrets" object,
   "org.freedesktop.Secret.Service" interface.
   This creates a new object on "org.freedesktop.secrets" bus.
   Apparently, this object is not used for anything except final Close() call.

   We obtain a dbus object name for the default collection by calling
   ReadAlias() over session dbus on "/org/freedesktop/secrets" object,
   "org.freedesktop.Secret.Service" interface. This object is accessible
   on session dbus. It has "org.freedesktop.Secret.Collection" interface.

   Before we can search for items on the collection, we need to unlock collection
   (this usually causes a "gimme pwd NOW!" prompt to appear if we are in X).
   This is done by Unlock() call. It returns a path to prompt dbus object
   if unlocking is truly needed. In which case we need to call Prompt() on it,
   and wait for dbus signal "Completed" from the prompt object.

   We can SearchItems() and CreateItem() on the default collection.
   SearchItems() returns the list of dbus object names of found items.
   They are accessible on session dbus and have "org.freedesktop.Secret.Item"
   interface. (We use the first found item, if there are more than one.)

   We retrieve attributes from a found item (they are stored as dbus
   object's properties).

   CreateItem() creates an item in a collection and returns a tuple consisting
   from a prompt path and a new item path. We pass item's properties (label and
   attributes) as a dictionary. Dictionary keys are strings and values are
   variants. Label is stored under key "org.freedesktop.Secret.Item.Label" and
   attributes are stored under key "org.freedesktop.Secret.Item.Attributes".
   This function may require to perform a prompt. If the prompt path is the
   special value "/" no prompt is required.
*/

enum {
    /* Find and immediately return collection */
    GET_COLLECTION_FLAGS_NONE = 0,
    /* Return unlocked collection or NULL. */
    GET_COLLECTION_UNLOCK     = 1 << 0,
    /* If collection doesn't exist, create a new one return it. */
    GET_COLLECTION_CREATE     = 1 << 1,
};

enum secrets_service_state
{
    /* intial state */
    SBS_INITIAL = 0,
    /* after opening a secrets service session (different than D-Bus session) */
    SBS_CONNECTED,
    /* secrets service is not available do not bother user anymore */
    SBS_UNAVAILABLE,
};

struct secrets_object
{
    GDBusProxy *proxy;
    /* http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties */
    GDBusProxy *property_proxy;
    const gchar *interface_name;

    /* various data */
    void *tag;
};

/* DBus session bus connection */
static GDBusConnection *g_connection;

/* State of the service */
static enum secrets_service_state g_state;

/* http://standards.freedesktop.org/secret-service/re01.html */
/* dbus object "path:/org/freedesktop/secrets, iface:xorg.freedesktop.Secret.Service" */
static struct secrets_object *g_service_object;

/* http://standards.freedesktop.org/secret-service/ch06.html */
/* proxy for dbus object "path:/org/freedesktop/secrets/session/ssss, iface:xorg.freedesktop.Secret.Session" */
/* where ssss is an auto-generated session specific identifier. */
static GDBusProxy *g_session_proxy;

/******************************************************************************/
/* helpers                                                                    */
/******************************************************************************/
static GDBusProxy *get_dbus_proxy(const gchar *path, const gchar *interface)
{
    GError *error = NULL;
    GDBusProxy *const proxy = g_dbus_proxy_new_sync(g_connection,
                                                    G_DBUS_PROXY_FLAGS_NONE,
                                                    /* GDBusInterfaceInfo */ NULL,
                                                    SECRETS_SERVICE_BUS,
                                                    path,
                                                    interface,
                                                    /* GCancellable */ NULL,
                                                    &error);

    if (error)
    {
        error_msg(_("Can't connect over DBus to name '%s' path '%s' interface '%s': %s"),
                    SECRETS_SERVICE_BUS, path, interface, error->message);
        g_error_free(error);
        /* proxy is NULL in this case */
    }
    return proxy;
}

static GVariant *dbus_call_sync(GDBusProxy *proxy, const gchar *method, GVariant *args)
{
    GError *error = NULL;
    GVariant *const resp = g_dbus_proxy_call_sync(proxy,
                                                  method,
                                                  args,
                                                  G_DBUS_PROXY_FLAGS_NONE,
                                                  SECRETS_CALL_DEFAULT_TIMEOUT,
                                                  /* GCancellable */ NULL,
                                                  &error);
    if (error)
    {
        error_msg(_("Can't call method '%s' over DBus on path '%s' interface '%s': %s"),
                    method,
                    g_dbus_proxy_get_object_path(proxy),
                    g_dbus_proxy_get_interface_name(proxy),
                    error->message);
        g_error_free(error);
        /* resp is NULL in this case */
    }
    return resp;
}

/* Compares D-Bus error name against a name passsed as type argument
 *
 * @param error an error occured in any D-Bus method
 * @param type a checked error type
 * @returns true if a D-Bus name of error param equals to a value of type param
 */
static bool is_dbus_remote_error(GError *error, const char *type)
{
    /* returns a malloced string and return value can be NULL */
    char *remote_type = g_dbus_error_get_remote_error(error);
    const bool result = (remote_type && strcmp(type, remote_type) == 0);
    g_free(remote_type);
    return result;
}

/******************************************************************************/
/* struct secrets_object                                                      */
/******************************************************************************/

static struct secrets_object *secrets_object_new_from_proxy(GDBusProxy *proxy)
{
    struct secrets_object *obj = xzalloc(sizeof(*obj));
    obj->proxy = proxy;
    obj->interface_name = g_dbus_proxy_get_interface_name(proxy);

    return obj;
}

static struct secrets_object *secrets_object_new(const gchar *path,
                                                 const gchar *interface)
{
    GDBusProxy *const proxy = get_dbus_proxy(path, interface);

    if (!proxy)
        return NULL;

    return secrets_object_new_from_proxy(proxy);
}

static bool secrets_object_change_path(struct secrets_object *obj,
                                       const gchar *path)
{
    /* Do not set a new path if is the same as the current path */
    if (strcmp(path, g_dbus_proxy_get_object_path(obj->proxy)) == 0)
        return true;

    GDBusProxy *const proxy = get_dbus_proxy(path, obj->interface_name);

    if (!proxy)
        return false;

    /* release the old DBus property proxy */
    if (obj->property_proxy)
    {
        g_object_unref(obj->property_proxy);
        obj->property_proxy = NULL;
    }

    /* release the old proxy */
    if (obj->proxy)
        g_object_unref(obj->proxy);

    obj->proxy = proxy;
    obj->interface_name = g_dbus_proxy_get_interface_name(proxy);
    return true;
}

static void secrets_object_delete(struct secrets_object *obj)
{
    if (!obj)
        return;

    if (obj->proxy)
        g_object_unref(obj->proxy);

    if (obj->property_proxy)
        g_object_unref(obj->property_proxy);

    free(obj);
}

static GDBusProxy *secrets_object_get_properties_proxy(struct secrets_object *obj)
{
    if (!obj->property_proxy)
        obj->property_proxy = get_dbus_proxy(g_dbus_proxy_get_object_path(obj->proxy),
                                             "org.freedesktop.DBus.Properties");

    return obj->property_proxy;
}

/*******************************************************************************/
/* struct secrets_service                                                        */
/*******************************************************************************/

static void secrets_service_set_unavailable(void);

/*
 * Initialize all global variables
 */
static enum secrets_service_state secrets_service_connect(void)
{
    GError *error = NULL;
    g_connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);

    if (!g_connection)
    {
        log("Failed to open connection to D-Bus session bus: %s", error->message);
        g_error_free(error);
        return SBS_UNAVAILABLE;
    }

    g_service_object = secrets_object_new("/org/freedesktop/secrets", SECRETS_NAME_SPACE("Service"));

    if (!g_service_object)
        return SBS_UNAVAILABLE;

    /* OpenSession (IN String algorithm, IN Variant input, OUT Variant output, OUT ObjectPath result); */
    /*   Open a unique session for the caller application. */
    /*   algorithm : The algorithm the caller wishes to use. */
    /*   input     : Input arguments for the algorithm. */
    /*   output    : Output of the session algorithm negotiation. */
    /*   result    : The object path of the session, if session was created. */
    GVariant *const resp = dbus_call_sync(g_service_object->proxy,
                                          "OpenSession",
                                          g_variant_new("(sv)", "plain", g_variant_new_string("")));

    if (!resp)
        return SBS_UNAVAILABLE;

    GVariant *const var = g_variant_get_child_value(resp, 1);

    g_variant_unref(resp);

    const gchar *const session_path = g_variant_get_string(var, NULL);
    g_session_proxy = get_dbus_proxy(session_path, SECRETS_NAME_SPACE("Session"));

    g_variant_unref(var);

    if (!g_session_proxy)
        return SBS_UNAVAILABLE;

    return g_state;
}

static void secrets_service_close_connection(void)
{
    if (g_state != SBS_CONNECTED)
        return;

    /* Close (void);  */
    /*   Close this session. */
    GVariant *const resp = dbus_call_sync(g_session_proxy, "Close", g_variant_new("()"));

    if (resp)
        g_variant_unref(resp);
    /*else : don't take care about errors */

    g_object_unref(g_session_proxy);
    g_session_proxy = NULL;

    secrets_object_delete(g_service_object);
    g_service_object = NULL;

    g_object_unref(g_connection);
    g_connection = NULL;

    g_state = SBS_INITIAL;
}

static void secrets_service_set_unavailable(void)
{
    secrets_service_close_connection();

    g_state = SBS_UNAVAILABLE;
}

/*******************************************************************************/
/* org.freedesktop.Secret.Prompt                                               */
/* http://standards.freedesktop.org/secret-service/re05.html                   */
/*******************************************************************************/

struct prompt_source
{
    GSource source;
    GDBusProxy *prompt_proxy;
    GMainLoop *loop;
};

struct prompt_call_back_args
{
    gboolean dismissed;
    GVariant *result;
    GMainLoop *loop;
};

static void prompt_quit_loop(GMainLoop *loop)
{
    if (g_main_loop_is_running(loop))
        g_main_loop_quit(loop);
}

static gboolean prompt_g_idle_prepare(GSource *source_data, gint *timeout)
{
    return TRUE;
}

static gboolean prompt_g_idle_check(GSource *source)
{
    return TRUE;
}

/* one shot dispatch function which runs a prompt in an event loop */
static gboolean prompt_g_idle_dispatch(GSource *source,
                                       GSourceFunc callback,
                                       gpointer user_data)
{
    struct prompt_source *const prompt_source = (struct prompt_source *)source;
    GDBusProxy *const prompt_proxy = prompt_source->prompt_proxy;

    /* Prompt (IN String window-id); */
    /*   Perform the prompt. A window should appear. */
    /*   window-id : Platform specific window handle to use for showing the prompt. */
    GVariant *const resp = dbus_call_sync(prompt_proxy,
                                          "Prompt",
                                          g_variant_new("(s)", ""));

    if (!resp)
        /* We have to kill the loop because a dbus call failed and the signal */
        /* which stops the loop won't be received */
        prompt_quit_loop(prompt_source->loop);
    else
        g_variant_unref(resp);

    /* always return FALSE, it means that this source is to be removed from idle loop */
    return FALSE;
}

/* Prompt Completed signal callback */
static void prompt_call_back(GDBusConnection *connection, const gchar *sender_name,
                             const gchar *object_path, const gchar *interface_name,
                             const gchar *signal_name, GVariant *parameters,
                             gpointer user_data)
{
    struct prompt_call_back_args *const args = (struct prompt_call_back_args *)user_data;
    GVariant *result = NULL;

    /* Completed (IN Boolean dismissed, IN Variant result); */
    /*   The prompt and operation completed. */
    /*   dismissed : Whether the prompt and operation were dismissed or not. */
    /*   result    : The possibly empty, operation specific, result. */
    g_variant_get(parameters, "(bv)", &args->dismissed, &result);

    if (!args->dismissed)
        args->result = result;
    else
        /* if the prompt or operation were dismissed we don't care about result */
        g_variant_unref(result);

    prompt_quit_loop(args->loop);
}

/*
 * If a prompt path is '/' then it isn't required to perform prompt.
 * Even more, prompt can't be performed because the path is not valid.
 */
static bool is_prompt_required(const char *prompt_path)
{
    return prompt_path && !(prompt_path[0] == '/' && prompt_path[1] == '\0');
}

/*
 * Perform a prompt
 *
 * @param prompt_path An object path pointing to a prompt
 * @param result A prompt result (can be NULL)
 * @param dismissed A dismissed flag (can't be NULL)
 * @return returns TRUE if no errors occurred; otherwise false
 */
static GVariant *secrets_prompt(const char *prompt_path,
                                bool *dismissed)
{
    static GSourceFuncs idle_funcs = {
        .prepare = prompt_g_idle_prepare,
        .check = prompt_g_idle_check,
        .dispatch = prompt_g_idle_dispatch,
        .finalize = NULL,
        .closure_callback = NULL,
        .closure_marshal = NULL,
    };

    /* TODO : leak */
    GDBusProxy *const prompt_proxy = get_dbus_proxy(prompt_path, SECRETS_NAME_SPACE("Prompt"));

    *dismissed = false;
    if (!prompt_proxy)
        return NULL;

    /* We have to use the thread default main context because a dbus signal callback */
    /* will be invoked in the thread default main loop */
    GMainContext *context = g_main_context_get_thread_default();
    GMainLoop *loop = g_main_loop_new(context, FALSE);

    struct prompt_call_back_args args = {
        .result=NULL,
        .dismissed=FALSE,
        .loop=loop
    };

    /* g_dbus_connection_signal_subscribe() doesn't report any error */
    const guint signal_ret =
        g_dbus_connection_signal_subscribe(g_connection, SECRETS_SERVICE_BUS, SECRETS_NAME_SPACE("Prompt"),
                                           "Completed", prompt_path, NULL, G_DBUS_SIGNAL_FLAGS_NONE,
                                           prompt_call_back, &args, NULL);

    /* prepare the prompt source */
    /* The promp source is implemented as idle source because we have to perform */
    /* a prompt in some event loop. We need an event loop because a promp operation is */
    /* perfomed assynchronously and we have to wait on the Completed signal. */
    /* Idle source simply performs a prompt after the loop is stared. */
    struct prompt_source *const prompt_source =
        (struct prompt_source*)g_source_new(&idle_funcs, sizeof(*prompt_source));

    prompt_source->prompt_proxy = prompt_proxy;

    g_source_attach((GSource*)prompt_source, context);

    /* the loop is exited when the Completed signal is received */
    /* the loop may sucks in infinite loop if the signal is never received */
    /* TODO : if timeout is required use g_timeout_source_new() and g_source_attach() */
    g_main_loop_run(loop);

    /* destroy prompt source */
    g_object_unref(prompt_proxy);
    g_source_destroy((GSource*)prompt_source);

    g_dbus_connection_signal_unsubscribe(g_connection, signal_ret);
    g_main_loop_unref(loop);

    *dismissed = args.dismissed;
    return args.result;
}

/*******************************************************************************/
/* org.freedesktop.Secret.Service                                              */
/* http://standards.freedesktop.org/secret-service/re01.html                   */
/*******************************************************************************/

static GVariant *secrets_service_unlock(const gchar *const *objects,
                                        bool *dismissed)
{
    /* Unlock (IN Array<ObjectPath> objects, OUT Array<ObjectPath> unlocked, OUT ObjectPath prompt); */
    /*   Unlock the specified objects. */
    /*   objects  :  Objects to unlock. */
    /*   unlocked :  Objects that were unlocked without a prompt. */
    /*   prompt   :  A prompt object which can be used to unlock the remaining objects, or the special value '/' when no prompt is necessary. */
    GVariant *const var = dbus_call_sync(g_service_object->proxy,
                                         "Unlock",
                                         g_variant_new("(^ao)", objects));
    if (!var)
        return false;

    gchar *prompt = NULL;
    GVariant *result = NULL;
    /* TODO : leak */
    g_variant_get(var, "(@aoo)", &result, &prompt);

    *dismissed = false;

    if (is_prompt_required(prompt))
    {
        g_variant_unref(result);
        result = secrets_prompt(prompt, dismissed);
    }

    g_variant_unref(var);

    return result;
}

static bool secrets_service_unlock_object(struct secrets_object *obj,
                                          bool *dismissed)
{
    /* objects to unlock */
    const gchar *const locked[] = {
        g_dbus_proxy_get_object_path(obj->proxy),
        NULL
    };

    GVariant *result = secrets_service_unlock(locked, dismissed);

    if (result)
    {
        gsize length = 0;
        const gchar *const *unlocked = g_variant_get_objv(result, &length);

        /* ksecrets doesn't set dismissed correctly */
        if (length)
        {
            const bool succ = secrets_object_change_path(obj, *unlocked);
            g_free((gpointer)unlocked);
            g_variant_unref(result);

            return succ;
        }
        else
        {
            *dismissed = true;
            g_variant_unref(result);
        }
    }

    /* the function is successful if result is not NULL or */
    /* if user dismissed any prompt */
    return result || *dismissed;
}

/*
 * KSecretsService doesn't implement ReadAlias
 *
 * The function iterates over all collections and compares their Labels
 * with given alias.
 *
 * Collection is null on error or if it doesn't exist
 */
static bool secrets_service_find_collection(const char *alias, struct secrets_object **collection)
{
    /* do not unref this proxy, the proxy is owned by secrets object */
    GDBusProxy *const properties = secrets_object_get_properties_proxy(g_service_object);

    if (!properties)
        return false;

    /* cannot use g_dbus_proxy_get_cached_property() because of */
    /* errors in case when secrets service was restarted during session */
    /* http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties */
    /* org.freedesktop.DBus.Properties.Get (IN String interface_name, IN String property_name, OUT Variant value); */
    GVariant *const prop = dbus_call_sync(properties,
                                          "Get",
                                           g_variant_new("(ss)",
                                                         SECRETS_NAME_SPACE("Service"),
                                                         "Collections")
                                          );
    if (!prop)
        return false;

    *collection = NULL;

    /* Returned value */
    GVariant *const value = g_variant_get_child_value(prop, 0);
    /* Contentet of returned value */
    GVariant *const list = g_variant_get_child_value(value, 0);

    GVariantIter *iter = NULL;
    g_variant_get(g_variant_get_child_value(value, 0), "ao", &iter);

    bool found = false;
    gchar *path = NULL;
    while (g_variant_iter_loop(iter, "o", &path))
    {
        GDBusProxy *const collection_proxy = get_dbus_proxy(path,
                                                            SECRETS_NAME_SPACE("Collection"));
        /* an error occurred */
        if (!collection_proxy)
            break;

        GVariant *const lbl_var = g_dbus_proxy_get_cached_property(collection_proxy,
                                                                  "Label");

        const gchar *const label = g_variant_get_string(lbl_var, NULL);
        found = label && (strcmp(label, alias) == 0);

        g_variant_unref(lbl_var);

        if (found)
        {
            *collection = secrets_object_new_from_proxy(collection_proxy);
            /* the collection_proxy variable will be unrefed byt the collection object */
            break;
        }

        g_object_unref(collection_proxy);
    }

    g_variant_iter_free(iter);
    g_variant_unref(list);
    g_variant_unref(value);
    g_variant_unref(prop);

    /* If coolection is not found no error could occure */
    /* If coolection is found and variable is null then error occurred */
    return !found || *collection;
}

/*
 * Collection is null on error or if it doesn't exist
 */
static bool secrets_service_read_alias(const char *alias, struct secrets_object **collection)
{
    GError *error = NULL;
    /* use dbus_call_sync if KSecrets doesn't require special handling */
    /* ReadAlias (IN String name, OUT ObjectPath collection); */
    /*   Get the collection with the given alias. */
    /*   name       : An alias, such as 'default'. */
    /*   collection : The collection or the path '/' if no such collection exists. */
    GVariant *const resp =
        g_dbus_proxy_call_sync(g_service_object->proxy, "ReadAlias", g_variant_new("(s)", alias),
                               G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT,
                               NULL, &error);

    if (!error)
    {
        GVariant *const obj_path = g_variant_get_child_value(resp, 0);
        const gchar *const coll_path = g_variant_get_string(obj_path, NULL);

        /* found if the path is not "/" */
        const bool found = coll_path[0] != '/' || coll_path[1] != '\0';

        if (found)
            *collection = secrets_object_new(coll_path, SECRETS_NAME_SPACE("Collection"));

        g_variant_unref(obj_path);
        g_variant_unref(resp);

        /* return TRUE if collection was not found or if collection object is not NULL */
        return !found || *collection;
    }
    else if (is_dbus_remote_error(error, NOT_IMPLEMENTED_READ_ALIAS_ERROR))
    {
        /* this code branch can be safely removed if KSecrets provides ReadAlias method*/
        VERB1 log("D-Bus Secrets Service ReadAlias method failed,"
                  "going to search on the client side : %s", error->message);
        g_error_free(error);

        /* ksecrets doesn't implement ReadAlias, therefore search by label is performed */
        return secrets_service_find_collection(alias, collection);
    }

    error_msg(_("D-Bus Secrets Service ReadAlias('%s') method failed: %s"), alias, error->message);
    g_error_free(error);
    return false;
}

/*
 * Creates a new secrets service collection.
 *
 * @param label a collection label
 * @param alias a collection alias, empty string means default (see Secrets Service API)
 * @param dismissed a dismissed flag, true value means that user dismissed creation
 *                  the param never holds true if error occurred
 * @return on error returns false, otherwise returns true
 */
static struct secrets_object *secrets_service_create_collection(const gchar *label,
                                                                const gchar *alias,
                                                                bool *dismissed)
{
    GVariantBuilder *const prop_builder = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);

    g_variant_builder_add(prop_builder, "{sv}", "org.freedesktop.Secret.Collection.Label",
                          g_variant_new_string(label) );

    /* CreateCollection (IN Dict<String,Variant> properties, IN String alias, */
    /*                   OUT ObjectPath collection, OUT ObjectPath prompt); */
    /*   Create a new collection with the specified properties. */
    /*   properties : Properties for the new collection. This allows setting the */
    /*                new collection's properties upon its creation. All */
    /*                READWRITE properties are useable. Specify the property */
    /*                names in full interface.Property form. */
    /*   alias      : If creating this connection for a well known alias then */
    /*                a string like default. If an collection with this */
    /*                well-known alias already exists, then that collection */
    /*                will be returned instead of creating a new collection. */
    /*                Any readwrite properties provided to this function will */
    /*                be set on the collection. */
    /*                Set this to an empty string if the new collection should */
    /*                not be associated with a well known alias. */
    /*  collection  : The new collection object, or '/' if prompting is */
    /*                necessary. */
    /*  prompt      : A prompt object if prompting is necessary, or '/' if no */
    /*                prompt was needed. */
    GVariant *const resp = dbus_call_sync(g_service_object->proxy,
                                          "CreateCollection",
                                           g_variant_new("(@a{sv}s)",
                                                         g_variant_builder_end(prop_builder),
                                                         alias)
                                         );

    if (!resp)
        return NULL;

    struct secrets_object *collection = NULL;

    gchar *prompt = NULL;
    GVariant *collection_var = NULL;
    /* TODO : leak */
    g_variant_get(resp, "(@oo)", &collection_var, &prompt);

    if (is_prompt_required(prompt))
    {
        g_variant_unref(collection_var);
        collection_var = secrets_prompt(prompt, dismissed);
    }

    if (collection_var)
    {
        gsize length = 0;
        const gchar *const coll_path = g_variant_get_string(collection_var, &length);

        /* ksecrets doens't properly set dismissed */
        /* if dismissed is false and result is empty */
        /* then ksecrets didn't properly set dismissed */
        if (length)
            collection = secrets_object_new(coll_path, SECRETS_NAME_SPACE("Collection"));
        else
            *dismissed = true;

        g_variant_unref(collection_var);
    }

    g_variant_unref(resp);

    return collection;
}

/*******************************************************************************/
/* org.freedesktop.Secret.Collection                                           */
/* http://standards.freedesktop.org/secret-service/re02.html                   */
/*******************************************************************************/

static GVariant *create_item_call_args(const char *event_name,
                                       GVariant *attributes,
                                       GVariant *secret,
                                       bool full_property_name)
{
    /* gnome-keyring accepts properties with namespace */
    /* {
     *   "org.freedesktop.Secret.Item.Label": 'event name',
     *   "org.freedesktop.Secret.Item.Attributes": {
     *                                               "BugzillaURL": "Value1",
     *                                               "BugzillaPassword": "Value2"
     *                                             }
     * }
     */

    /* ksecrets accepts properties without namespace */
    /* {
     *   "Label": 'event name',
     *   "Attributes": {
     *                   "BugzillaURL": "Value1",
     *                   "BugzillaPassword": "Value2"
     *                 }
     * }
     */
    const char *const lbl_name = full_property_name ? SECRETS_NAME_SPACE("Item.Label")
                                                    : "Label";
    const char *const att_name = full_property_name ? SECRETS_NAME_SPACE("Item.Attributes")
                                                    : "Attributes";

    GVariantBuilder *const prop_builder = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);

    g_variant_builder_add(prop_builder, "{sv}", lbl_name, g_variant_new_string(event_name));
    g_variant_builder_add(prop_builder, "{sv}", att_name, attributes);

    GVariant *const prop = g_variant_builder_end(prop_builder);

    return g_variant_new("(@a{sv}@(oayays)b)", prop, secret, FALSE);
}

static GVariant *create_secret_from_options(GDBusProxy *session, GList *options, bool store_passwords)
{
    GVariantBuilder *const value_builder = g_variant_builder_new(G_VARIANT_TYPE("ay"));
    for (GList *iter = g_list_first(options); iter; iter = g_list_next(iter))
    {
        event_option_t *const op = (event_option_t *)iter->data;
        /* TODO : is it still necessary? Passwords are encrypted now. */
        if (op->eo_type != OPTION_TYPE_PASSWORD || store_passwords)
        {
            const char *byte = op->eo_name;
            while(byte[0] != '\0')
                g_variant_builder_add(value_builder, "y", *byte++);

            g_variant_builder_add(value_builder, "y", SECRETS_OPTION_VALUE_DELIMITER);

            byte = op->eo_value;
            while(byte[0] != '\0')
                g_variant_builder_add(value_builder, "y", *byte++);

            g_variant_builder_add(value_builder, "y", '\0');
        }
    }

    GVariant *const value = g_variant_builder_end(value_builder);

    return g_variant_new("(oay@ays)",
                         g_dbus_proxy_get_object_path(session),
                         /* param */ NULL,
                         value,
                         "application/libreport");
}

static GVariant *create_lookup_attributes(const char *event_name)
{
    GVariantBuilder *const attr_builder = g_variant_builder_new(G_VARIANT_TYPE_DICTIONARY);
    /* add extra attribute used for searching */
    g_variant_builder_add(attr_builder, "{ss}", SECRETS_SEARCH_ATTRIBUTE, event_name);

    /* An example of attributes:               */
    /* {                                       */
    /*   "libreportEventConfig": "event_name", */
    /* }                                       */
    return g_variant_builder_end(attr_builder);
}

static bool secrets_collection_create_text_item(struct secrets_object *collection,
                                                const char *event_name,
                                                GVariant *attributes,
                                                GVariant *secret,
                                                bool *dismissed)
{
    bool succ = false;
    GError *error = NULL;

    /* not dismissed by default */
    *dismissed = false;

    /* ksecrets silently drops properties with full names */
    /* gnome-keyring returns an error if a property key is not full property name */
    /* the first iteration sends property names without namespace */
    /* if NO error was returned service is ksecrets and everything is ok */
    /* if error was returned service is gnome-keyring and the second iteration */
    /*   must be performed */
    /* the second iteration sends property names with namespace */
    static bool const options[] = {FALSE, TRUE};
    for (size_t choice = 0; choice < sizeof(options)/sizeof(*options); ++choice)
    {
        if (error)
        {   /* this code is here because I want to know */
            /* if an error occurred from outside the loop */
            /* it cannot be on the end of the loop because */
            /* I need unfreed error after the last iteration */
            g_error_free(error);
            error = NULL;
        }

        /* CreateItem (IN Dict<String,Variant> properties, IN Secret secret, */
        /*             IN Boolean replace, OUT ObjectPath item, */
        /*             OUT ObjectPath prompt); */
        /*   Create an item with the given attributes, secret and label. If */
        /*   replace is set, then it replaces an item already present with the */
        /*   same values for the attributes. */
        /*   properties : The properties for the new item. This allows setting */
        /*                the new item's properties upon its creation. All */
        /*                READWRITE properties are useable. Specify the */
        /*                property names in full interface.Property form. */
        /*   secret     : The secret to store in the item, encoded with the */
        /*                included session. */
        /*   replace    : Whether to replace an item with the same attributes */
        /*                or not. */
        /*   item       : The item created, or the special value '/' if a prompt */
        /*                is necessary. */
        /*   prompt     : A prompt object, or the special value '/' if no prompt */
        /*                is necessary. */
        GVariant *const resp = g_dbus_proxy_call_sync(collection->proxy, "CreateItem",
                                      create_item_call_args(event_name,
                                                            g_variant_ref(attributes),
                                                            g_variant_ref(secret),
                                                            options[choice]),
                                      G_DBUS_PROXY_FLAGS_NONE, SECRETS_CALL_DEFAULT_TIMEOUT,
                                      NULL, &error);

        if (error)
        {
            if (is_dbus_remote_error(error, INVALID_PROPERTIES_ARGUMENTS_ERROR))
            {  /* it is OK - we know this error and we can safely continue */
               VERB2 log("CreateItem failed, going to use other property names: %s", error->message);
               continue;
            }

            /* if the error wasn't about invalid properties we have an another problem */
            error_msg(_("Can't create a secret item for event '%s': %s"), event_name, error->message);
            g_error_free(error);
            break;
        }

        gchar *prompt = NULL;
        gchar *item = NULL;
        /* TODO : leak */
        g_variant_get(resp, "(oo)", &item, &prompt);

        /* if prompt is no required the function is successfull */
        /* therefore set return value to 'true' */
        succ = true;

        if (is_prompt_required(prompt))
            succ = secrets_prompt(prompt, dismissed) != NULL;

        g_variant_unref(resp);

        /* a dbus call was successfull, we don't need to try next type of property names */
        /* breaking the loop, nothing else to do */
        break;
    }

    g_variant_unref(attributes);
    g_variant_unref(secret);

    return succ;
}

/*
 * Performs org.freedesktop.Secret.Collection.SearchItems and returns the first
 * item from result. A found item can be possibly locked.
 */
static bool secrets_collection_search_one_item(const struct secrets_object *collection,
                                               GVariant *search_attrs,
                                               struct secrets_object **item)
{
    /* SearchItems (IN Dict<String,String> attributes, OUT Array<ObjectPath> results); */
    /*   Search for items in this collection matching the lookup attributes. */
    /*   attributes : Attributes to match. */
    /*   results    : Items that matched the attributes. */
    GVariant *const resp = dbus_call_sync(collection->proxy,
                                          "SearchItems",
                                          g_variant_new("(@a{ss})", search_attrs));

    if (!resp)
        return false;

    /* even if no item is found the function finishes successfully */
    bool found = false;
    *item = NULL;

    /* gnome-keyring returns (unlocked,locked) */
    /* ksecretsservice returns (unlocked) */
    /* all result childs has to be taken into account */
    const gsize n_results = g_variant_n_children(resp);

    for (gsize child = 0; !*item && child < n_results; ++child)
    {   /*               ^^^^^^ break if the item object was created */

        GVariant *const paths = g_variant_get_child_value(resp, child);

        /* NULL terminated list of path */
        const gchar *const *item_path_vector = g_variant_get_objv(paths, NULL);

        /* the first valid object path will be returned */
        if (*item_path_vector)
        {
            found = true;
            *item = secrets_object_new(*item_path_vector, SECRETS_NAME_SPACE("Item"));
            /* contniue on error - gnome-keyring unlocked result can */
            /*                     hold an invalid resp */
        }

        g_free((gpointer)item_path_vector);
        g_variant_unref(paths);
    }

    g_variant_unref(resp);

    /* If item is not found no error could occure */
    /* If item is found and variable is null then error occurred */
    return !found || *item;
}

/*******************************************************************************/
/* utility functions                                                           */
/*******************************************************************************/

/*
 * Gets an unlocked default collection if it exists
 *
 * Collection is null when error occurred or when user dismissed unlocking
 *
 * @return on error false
 */
static bool get_default_collection(struct secrets_object **collection,
                                   int flags,
                                   bool *dismissed)
{
    bool succ = secrets_service_read_alias(SECRETS_COLLECTION_ALIAS, collection);

    if (!succ)
        return false;

    /* ReadAlias was successful*/
    if (*collection != NULL && flags & GET_COLLECTION_UNLOCK)
    {   /* the default collection was found */
        succ = secrets_service_unlock_object(*collection, dismissed);

        if (!succ || *dismissed)
        {
            secrets_object_delete(*collection);
            *collection = NULL;
        }
    }

    if (*collection == NULL && flags & GET_COLLECTION_CREATE)
    {   /* the default collection wasn't found */
        /* a method caller requires to create a new collection */
        /* if the default collection doesn't exist */
        VERB2 log("going to create a new default collection '"SECRETS_COLLECTION_LABEL"'"
                  " with alias '"SECRETS_COLLECTION_ALIAS"'");

        *collection = secrets_service_create_collection(SECRETS_COLLECTION_LABEL,
                                                        SECRETS_COLLECTION_ALIAS,
                                                        dismissed);

        /* create collection function succeded if a collection is not NULL */
        /* or if the call was dismissed */
        /* if dismissed is false and collection is null then an error occurred */
        succ = *collection || *dismissed;
    }

    return succ;
}

static void load_event_options_from_item(GDBusProxy *session,
                                         GList *options,
                                         struct secrets_object *item)
{
    {   /* for backward compatibility */
        /* load configuration from lookup attributes but don't store configuration in lookup attributes */
        GVariant *const attributes =
            g_dbus_proxy_get_cached_property(item->proxy, "Attributes");

        GVariantIter *iter = NULL;
        g_variant_get(attributes, "a{ss}", &iter);

        gchar *name = NULL;
        gchar *value = NULL;
        while (g_variant_iter_loop(iter, "{ss}", &name, &value))
        {
            event_option_t *const option = get_event_option_from_list(name, options);
            if (option)
            {
                free(option->eo_value);
                option->eo_value = xstrdup(value);
                VERB2 log("loaded event option : '%s' => '%s'", name, option->eo_value);
            }
        }

        g_variant_unref(attributes);
    }

    {   /* read configuration from the secret value */
        GError *error = NULL;
        GVariant *const resp = g_dbus_proxy_call_sync(item->proxy,
                                                      "GetSecret",
                                                      g_variant_new("(o)",
                                                        g_dbus_proxy_get_object_path(session)),
                                                      G_DBUS_PROXY_FLAGS_NONE,
                                                      SECRETS_CALL_DEFAULT_TIMEOUT,
                                                      /* GCancellable */ NULL,
                                                      &error);

        if (error)
        {   /* if the error is NOT the known error produced by the gnome-keyring */
            /* when no secret value is assigned to an item */
            /* then let you user known that the error occurred */
            if (is_dbus_remote_error(error, GNOME_KEYRING_NOT_HAVING_SECRET_ERROR))
                error_msg(_("can't get secret value of '%s': %s"), (const char *)item->tag, error->message);
            else
            {
                VERB1 log("can't get secret value: %s", error->message);
            }

            g_error_free(error);
            return;
        }

        if (g_variant_n_children(resp) == 0)
        {
            VERB1 log("response from GetSecret() method doesn't contain data: '%s'", g_variant_print(resp, TRUE));
            return;
        }

        /* All dbus responses are returned inside of an variant */
        GVariant *const secret = g_variant_get_child_value(resp, 0);

        /* DBus represents a structure as a tuple of values */
        /* struct Secret
         * {
         *   (o)  object path
         *   (ay) byte array params
         *   (ay) byte array value
         *   (s)  string content type
         * }
         */
        if (g_variant_n_children(secret) != 4)
        {
            VERB1 log("secret value has invalid structure: '%s'", g_variant_print(secret, TRUE));
            return;
        }

        /* get (ay) byte array value */
        GVariant *const secret_value = g_variant_get_child_value(secret, 2);

        gsize nelems;
        gconstpointer data = g_variant_get_fixed_array(secret_value, &nelems, sizeof(guchar));

        while(nelems > 0)
        {
            const size_t sz = strlen(data) + 1;
            if (sz > nelems)
            {   /* check next size, if is too big then trailing 0 is probably missing */
                VERB1 log("broken secret value, atttempts to read %zdB but only %zdB are available)", sz, nelems);
                break;
            }
            nelems -= sz;

            char *name = xstrdup(data);
            char *value = strchr(name, SECRETS_OPTION_VALUE_DELIMITER);
            if (!value)
            {
                VERB1 log("secret value has invalid format: missing delimiter after option name");
                goto next_option;
            }

            *value++ = '\0';

            event_option_t *const option = get_event_option_from_list(name, options);
            if (option)
            {
                free(option->eo_value);
                option->eo_value = xstrdup(value);
                VERB2 log("loaded event option : '%s' => '%s'", name, option->eo_value);
            }

next_option:
            free(name);
            data += sz;
        }

        g_variant_unref(secret_value);
        g_variant_unref(secret);
        g_variant_unref(resp);
    }
}

static bool find_item_by_event_name(const struct secrets_object *collection,
                                    const char *event_name,
                                    struct secrets_object **item)
{
    return secrets_collection_search_one_item(collection, create_lookup_attributes(event_name), item);
}

static void load_settings(GDBusProxy *session, struct secrets_object *collection,
                          const char *event_name, event_config_t *ec)
{
    struct secrets_object *item = NULL;
    bool dismissed = false;
    VERB2 log("looking for event config : '%s'", event_name);
    bool succ = find_item_by_event_name(collection, event_name, &item);
    if (succ && item)
    {
        succ = secrets_service_unlock_object(item, &dismissed);
        if (succ && !dismissed)
        {
            VERB1 log("loading event config : '%s'", event_name);
            item->tag = (void *)event_name;
            load_event_options_from_item(session, ec->options, item);
        }
        secrets_object_delete(item);
        item = NULL;
    }

    if (!succ || dismissed)
        secrets_service_set_unavailable();
}

static void load_event_config(gpointer key, gpointer value, gpointer user_data)
{
    char *const event_name = (char*)key;
    event_config_t *const ec = (event_config_t *)value;
    struct secrets_object *const collection = (struct secrets_object *)user_data;

    if (is_event_config_user_storage_available())
        load_settings(g_session_proxy, collection, event_name, ec);
}

static bool save_options(struct secrets_object *collection,
                         const char *event_name,
                         GList *options,
                         bool store_passwords,
                         bool *dismissed)
{
    struct secrets_object *item = NULL;
    bool succ = find_item_by_event_name(collection, event_name, &item);

    if (!succ)
        return succ;

    GVariant *const secret = create_secret_from_options(g_session_proxy, options, store_passwords);

    if (item)
    {   /* item exists, change a secret */
        VERB2 log("updating event config : '%s'", event_name);
        /* can't be dismissed */
        *dismissed = false;
        GVariant *const args = g_variant_new("(@(oayays))", secret);
        GVariant *const resp = dbus_call_sync(item->proxy, "SetSecret", args);
        succ = resp;

        if (succ)
            g_variant_unref(resp);

        secrets_object_delete(item);
    }
    else
    {   /* create a new item */
        GVariant *const attributes = create_lookup_attributes(event_name);
        VERB2 log("creating event config : '%s'", event_name);
        succ = secrets_collection_create_text_item(collection, event_name, attributes, secret, dismissed);
    }

    return succ;
}

static void save_event_config(const char *event_name,
                              GList *options,
                              bool store_passwords)
{
    bool dismissed = false;

    /* We have to handle four cases:
     *   1. The default collection exists and is unlocked
     *      collection is set to some value different than NULL
     *      succ is TRUE - call was successful
     *      dismissed is FALSE - a prompt was confirmed
     *      In this case everything is ok and we are happy
     *
     *   2. The default collection doesn't exist.
     *      collection is NULL
     *      succ is TRUE - call was successful
     *      dismissed is FALSE - no collection no prompt no dismissed
     *      In this case everything is ok and we are happy
     *
     *   3. User dismissed a prompt for unlocking of the default collection
     *      collection is NULL - collection is not unlocked thus we can't read it
     *      succ is TRUE - call was successful
     *      dismissed is TRUE - USER DISMISSED A PROMPT, he don't want to unlock the default collection !!
     *      We the set service state to unavailable in order to not bother user with prompts anymore
     *
     *   4. An error occurred
     *      collection is NULL
     *      succ is TRUE - call was NOT successful
     *      dismissed variable holds FALSE - no prompt no dismissed
     *      Set the service state to unavailable in order to not bother user with error messages anymore
     */
    struct secrets_object *collection = NULL;
    const int flags = GET_COLLECTION_UNLOCK | GET_COLLECTION_CREATE;
    bool succ = get_default_collection(&collection, flags, &dismissed);

    if (collection)
    {
        succ = save_options(collection, event_name, options, store_passwords, &dismissed);
        secrets_object_delete(collection);
        collection = NULL;
    }

    if (!succ || dismissed)
        secrets_service_set_unavailable();
}

/******************************************************************************/
/* Public interface                                                           */
/******************************************************************************/

bool is_event_config_user_storage_available()
{
    if (g_state == SBS_INITIAL)
        g_state = secrets_service_connect();

    return g_state != SBS_UNAVAILABLE;
}

/*
 * Loads event config options for passed event
 *
 * @param name Event name
 * @param config Event config
 */
void load_single_event_config_data_from_user_storage(event_config_t *config)
{
    GHashTable *tmp = g_hash_table_new_full(
                /*hash_func*/ g_str_hash,
                /*key_equal_func:*/ g_str_equal,
                /*key_destroy_func:*/ g_free,
                /*value_destroy_func:*/ NULL);

    g_hash_table_insert(tmp, xstrdup(ec_get_name(config)), config);

    load_event_config_data_from_user_storage(tmp);

    g_hash_table_destroy(tmp);

    return;
}

/*
 * Loads event config options for passed events
 *
 * @param event_config_list Events configs
 */
void load_event_config_data_from_user_storage(GHashTable *event_config_list)
{
    if (is_event_config_user_storage_available())
    {
        bool dismissed = false;
        struct secrets_object *collection = NULL;

        /* We have to handle four cases:
         *   1. The default collection exists and is unlocked
         *      collection is set to some value different than NULL
         *      succ is TRUE - call was successful
         *      dismissed is FALSE - a prompt was confirmed
         *      In this case everything is ok and we are happy
         *
         *   2. The default collection doesn't exist.
         *      collection is NULL
         *      succ is TRUE - call was successful
         *      dismissed is FALSE - no collection no prompt no dismissed
         *      In this case everything is ok and we are happy
         *
         *   3. User dismissed a prompt for unlocking of the default collection
         *      collection is NULL - collection is not unlocked thus we can't read it
         *      succ is TRUE - call was successful
         *      dismissed is TRUE - USER DISMISSED A PROMPT, he don't want to unlock the default collection !!
         *      We the set service state to unavailable in order to not bother user with prompts anymore
         *
         *   4. An error occurred
         *      collection is NULL
         *      succ is TRUE - call was NOT successful
         *      dismissed variable holds FALSE - no prompt no dismissed
         *      Set the service state to unavailable in order to not bother user with error messages anymore
         */
        const int flags = GET_COLLECTION_FLAGS_NONE;
        const bool succ = get_default_collection(&collection, flags, &dismissed);

        if (collection)
        {
            g_hash_table_foreach(event_config_list, &load_event_config, collection);
            secrets_object_delete(collection);
            collection = NULL;
        }

        if (!succ || dismissed)
            secrets_service_set_unavailable();
    }
    else
    {
        VERB1 log("Can't load user's configuration due to unavailability of D-Bus Secrets Service");
    }
}

/*
 * Saves an event_config options to some kind of session storage.
 *
 * @param event_name Lookup key
 * @param event_config Event data
 * @param store_passwords If TRUE stores options with passwords, otherwise skips them
 */
void save_event_config_data_to_user_storage(const char *event_name,
                                            const event_config_t *event_config,
                                            bool store_passwords)
{
    if (is_event_config_user_storage_available())
        save_event_config(event_name, event_config->options, store_passwords);
    else
    {
        VERB1 log("Can't save user's configuration due to unavailability of D-Bus secrets API");
    }
}