Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
backend_kwallet.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Henrik Andersson.
4 Copyright (C) 2010, 2013-2014, 2016 Tobias Ellinghaus.
5 Copyright (C) 2011 Antony Dovgal.
6 Copyright (C) 2011-2012 johannes hanika.
7 Copyright (C) 2012 Michal Babej.
8 Copyright (C) 2012 Richard Wonka.
9 Copyright (C) 2014, 2016 Roman Lebedev.
10 Copyright (C) 2020 Diederik Ter Rahe.
11 Copyright (C) 2020 Heiko Bauke.
12 Copyright (C) 2020 Pascal Obry.
13 Copyright (C) 2022 Aurélien PIERRE.
14 Copyright (C) 2022 Martin Bařinka.
15 Copyright (C) 2022 Miloš Komarčević.
16
17 darktable is free software: you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation, either version 3 of the License, or
20 (at your option) any later version.
21
22 darktable is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 GNU General Public License for more details.
26
27 You should have received a copy of the GNU General Public License
28 along with darktable. If not, see <http://www.gnu.org/licenses/>.
29*/
30
31// This file contains code taken from
32// http://src.chromium.org/viewvc/chrome/trunk/src/chrome/browser/password_manager/native_backend_kwallet_x.cc?revision=50034&view=markup
33
34// The original copyright notice was as follows:
35
36// Copyright (c) 2010 The Chromium Authors. All rights reserved.
37//
38// Redistribution and use in source and binary forms, with or without
39// modification, are permitted provided that the following conditions are
40// met:
41//
42// * Redistributions of source code must retain the above copyright
43// notice, this list of conditions and the following disclaimer.
44// * Redistributions in binary form must reproduce the above
45// copyright notice, this list of conditions and the following disclaimer
46// in the documentation and/or other materials provided with the
47// distribution.
48// * Neither the name of Google Inc. nor the names of its
49// contributors may be used to endorse or promote products derived from
50// this software without specific prior written permission.
51//
52// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
53// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
54// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
55// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
56// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
57// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
58// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
59// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
60// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
61// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
62// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
63
64// Since the BSD license permits I hereby release this stuff under GPL.
65// See the top of this file.
66
67// Connect do dbus interface of KWallet
68// http://websvn.kde.org/trunk/KDE/kdelibs/kdeui/util/org.kde.KWallet.xml?revision=1054210&view=markup
69// http://websvn.kde.org/trunk/KDE/kdelibs/kdeui/util/kwallet.cpp?revision=1107541&view=markup
70
71#ifdef HAVE_CONFIG_H
72#include "common/darktable.h"
73#include "config.h"
74#endif
75
76#include "backend_kwallet.h"
77#include "control/conf.h"
78
79#include <string.h>
80
81static const gchar *app_id = "ansel";
82static const gchar *kwallet_folder = "ansel credentials";
83
84static const gchar *kwallet_service_name = "org.kde.kwalletd";
85static const gchar *kwallet_path = "/modules/kwalletd";
86static const gchar *kwallet_interface = "org.kde.KWallet";
87static const gchar *klauncher_service_name = "org.kde.klauncher";
88static const gchar *klauncher_path = "/KLauncher";
89static const gchar *klauncher_interface = "org.kde.KLauncher";
90
91// Invalid handle returned by get_wallet_handle().
92static const gint invalid_kwallet_handle = -1;
93
94// http://doc.qt.nokia.com/4.6/datastreamformat.html
95// A QString has the length in the first 4 bytes, then the string in UTF-16 encoding
96// Has to be stored as big endian!
97static gchar *char2qstring(const gchar *in, gsize *size)
98{
99 glong read, written;
100 GError *error = NULL;
101 gunichar2 *out = g_utf8_to_utf16(in, -1, &read, &written, &error);
102
103 if(error)
104 {
105 dt_print(DT_DEBUG_PWSTORAGE, "[pwstorage_kwallet] ERROR: error converting string: %s\n", error->message);
106 dt_free(out);
107 g_error_free(error);
108 return NULL;
109 }
110
111 glong i;
112 for(i = 0; i < written; ++i)
113 {
114 out[i] = g_htons(out[i]);
115 }
116
117 guint bytes = sizeof(gunichar2) * written;
118 guint BE_bytes = GUINT_TO_BE(bytes);
119 *size = sizeof(guint) + bytes;
120 gchar *result = g_malloc(*size);
121
122 memcpy(result, &BE_bytes, sizeof(guint));
123 memcpy(result + sizeof(guint), out, bytes);
124
125 dt_free(out);
126 return result;
127}
128
129// For cleaner code ...
130static gboolean check_error(GError *error)
131{
132 if(error)
133 {
134 dt_print(DT_DEBUG_PWSTORAGE, "[pwstorage_kwallet] ERROR: failed to complete kwallet call: %s\n",
135 error->message);
136 g_error_free(error);
137 return TRUE;
138 }
139 return FALSE;
140}
141
142// If kwalletd isn't running: try to start it
144{
145 GError *error = NULL;
146
147 // Sadly kwalletd doesn't use DBUS activation, so we have to make a call to
148 // klauncher to start it.
149 /*
150 * signature:
151 *
152 * in s serviceName,
153 * in as urls,
154 * in as envs,
155 * in s startup_id,
156 * in b blind,
157 *
158 * out i arg_0,
159 * out s dbusServiceName,
160 * out s error,
161 * out i pid
162 */
163 GVariant *ret = g_dbus_connection_call_sync(context->connection, klauncher_service_name, klauncher_path,
164 klauncher_interface, "start_service_by_desktop_name",
165 g_variant_new("(sasassb)", "kwalletd", NULL, NULL, "", FALSE),
166 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
167
168 if(check_error(error))
169 {
170 return FALSE;
171 }
172
173 GVariant *child = g_variant_get_child_value(ret, 2);
174 gchar *error_string = g_variant_dup_string(child, NULL);
175 g_variant_unref(child);
176 g_variant_unref(ret);
177
178 if(error_string && error_string[0] != '\0')
179 {
180 dt_print(DT_DEBUG_PWSTORAGE, "[pwstorage_kwallet] ERROR: error launching kwalletd: %s\n", error_string);
181 dt_free(error_string);
182 return FALSE;
183 }
184
185 dt_free(error_string);
186
187 return TRUE;
188}
189
190// Initialize the connection to KWallet
192{
193 GError *error = NULL;
194
195 // Make a proxy to KWallet.
196 if(context->proxy) g_object_unref(context->proxy);
197
198 context->proxy = g_dbus_proxy_new_sync(context->connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
200
201 if(check_error(error))
202 {
203 context->proxy = NULL;
204 return FALSE;
205 }
206
207 // Check KWallet is enabled.
208 GVariant *ret
209 = g_dbus_proxy_call_sync(context->proxy, "isEnabled", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
210
211 if(IS_NULL_PTR(ret)) return FALSE;
212 GVariant *child = g_variant_get_child_value(ret, 0);
213 gboolean is_enabled = g_variant_get_boolean(child);
214 g_variant_unref(child);
215 g_variant_unref(ret);
216
217 if(check_error(error) || !is_enabled) return FALSE;
218
219 // Get the wallet name.
220 dt_free(context->wallet_name);
221
222 ret = g_dbus_proxy_call_sync(context->proxy, "networkWallet", NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL,
223 &error);
224
225 child = g_variant_get_child_value(ret, 0);
226 context->wallet_name = g_variant_dup_string(child, NULL);
227 g_variant_unref(child);
228 g_variant_unref(ret);
229
230 if(check_error(error) || !context->wallet_name)
231 {
232 context->wallet_name = NULL; // yes, it's stupid. go figure.
233 return FALSE;
234 }
235
236 return TRUE;
237}
238
239// General initialization. Takes care of all the other stuff.
241{
242 backend_kwallet_context_t *context = g_malloc0(sizeof(backend_kwallet_context_t));
243
244 GError *error = NULL;
245 context->connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
246 if(check_error(error))
247 {
248 dt_free(context);
249 return NULL;
250 }
251
252 if(!init_kwallet(context))
253 {
254 // kwalletd may not be running. Try to start it and try again.
255 if(!start_kwallet(context) || !init_kwallet(context))
256 {
257 g_object_unref(context->connection);
258 dt_free(context);
259 return NULL;
260 }
261 }
262
263 return context;
264}
265
268{
270 g_object_unref(c->connection);
271 g_object_unref(c->proxy);
272 dt_free(c->wallet_name);
273 dt_free(c);
274}
275
276// get the handle for connections to KWallet
278{
279 // Open the wallet.
280 int handle = invalid_kwallet_handle;
281 GError *error = NULL;
282
283 /* signature:
284 *
285 * in s wallet,
286 * in x wId,
287 * in s appid,
288 *
289 * out i arg_0
290 */
291 GVariant *ret = g_dbus_proxy_call_sync(context->proxy, "open",
292 g_variant_new("(sxs)", context->wallet_name, 0LL, app_id),
293 G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
294
295 if(check_error(error))
296 {
297 g_variant_unref(ret);
299 }
300
301 GVariant *child = g_variant_get_child_value(ret, 0);
302 handle = g_variant_get_int32(child);
303 g_variant_unref(child);
304 g_variant_unref(ret);
305
306 // Check if our folder exists.
307 gboolean has_folder = FALSE;
308
309 /* signature:
310 *
311 * in i handle,
312 * in s folder,
313 * in s appid,
314 *
315 * out b arg_0
316 */
317 ret = g_dbus_proxy_call_sync(context->proxy, "hasFolder",
318 g_variant_new("(iss)", handle, kwallet_folder, app_id), G_DBUS_CALL_FLAGS_NONE,
319 -1, NULL, &error);
320
321 if(check_error(error))
322 {
323 g_variant_unref(ret);
325 }
326
327 child = g_variant_get_child_value(ret, 0);
328 has_folder = g_variant_get_boolean(child);
329 g_variant_unref(child);
330 g_variant_unref(ret);
331
332 // Create it if it didn't.
333 if(!has_folder)
334 {
335
336 gboolean success = FALSE;
337
338 /* signature:
339 *
340 * in i handle,
341 * in s folder,
342 * in s appid,
343 *
344 * out b arg_0
345 */
346 ret = g_dbus_proxy_call_sync(context->proxy, "createFolder",
347 g_variant_new("(iss)", handle, kwallet_folder, app_id),
348 G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
349
350 if(check_error(error))
351 {
352 g_variant_unref(ret);
354 }
355
356 child = g_variant_get_child_value(ret, 0);
357 success = g_variant_get_boolean(child);
358 g_variant_unref(child);
359 g_variant_unref(ret);
360
361 if(!success) return invalid_kwallet_handle;
362 }
363
364 return handle;
365}
366
367// Store (key,value) pairs from a GHashTable in the kwallet.
368// Every 'slot' has to take care of it's own data.
369gboolean dt_pwstorage_kwallet_set(const backend_kwallet_context_t *context, const gchar *slot,
370 GHashTable *table)
371{
372 printf("slot %s\n", slot);
373
374 GArray *byte_array = g_array_new(FALSE, FALSE, sizeof(gchar));
375
376 GHashTableIter iter;
377 g_hash_table_iter_init(&iter, table);
378 gpointer key, value;
379
380 guint size = g_hash_table_size(table);
381
382 size = GINT_TO_BE(size);
383
384 g_array_append_vals(byte_array, &size, sizeof(guint) / sizeof(gchar));
385
386 while(g_hash_table_iter_next(&iter, &key, &value))
387 {
388 dt_print(DT_DEBUG_PWSTORAGE, "[pwstorage_kwallet_set] storing (%s, %s)\n", (gchar *)key, (gchar *)value);
389 gsize length;
390 gchar *new_key = char2qstring(key, &length);
391 if(IS_NULL_PTR(new_key))
392 {
393 gchar *byte_array_data = g_array_free(byte_array, FALSE);
394 dt_free(byte_array_data);
395 return FALSE;
396 }
397 g_array_append_vals(byte_array, new_key, length);
398 dt_free(new_key);
399
400 gchar *new_value = char2qstring(value, &length);
401 if(IS_NULL_PTR(new_value))
402 {
403 gchar *byte_array_data = g_array_free(byte_array, FALSE);
404 dt_free(byte_array_data);
405 return FALSE;
406 }
407 g_array_append_vals(byte_array, new_value, length);
408 dt_free(new_value);
409 }
410
411 int wallet_handle = get_wallet_handle(context);
412 GError *error = NULL;
413
414 /* signature:
415 *
416 * in i handle,
417 * in s folder,
418 * in s key,
419 * in ay value,
420 * in s appid,
421 *
422 * out i arg_0
423 */
424 GVariant *ret = g_dbus_proxy_call_sync(
425 context->proxy, "writeMap",
426 g_variant_new("(iss@ays)", wallet_handle, kwallet_folder, slot,
427 g_variant_new_from_data(G_VARIANT_TYPE_BYTESTRING, byte_array->data, byte_array->len,
428 TRUE, g_free, byte_array->data),
429 app_id),
430 G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
431
432 g_array_free(byte_array, FALSE);
433
434 if(check_error(error))
435 {
436 g_variant_unref(ret);
437 return FALSE;
438 }
439
440 GVariant *child = g_variant_get_child_value(ret, 0);
441 int return_code = g_variant_get_int32(child);
442 g_variant_unref(child);
443 g_variant_unref(ret);
444
445 if(return_code != 0)
446 dt_print(DT_DEBUG_PWSTORAGE, "[pwstorage_kwallet_set] Warning: bad return code %d from kwallet\n",
447 return_code);
448
449 return return_code == 0;
450}
451
452static gchar *array2string(const gchar *pos, guint *length)
453{
454 memcpy(length, pos, sizeof(gint));
455 *length = GUINT_FROM_BE(*length);
456 pos += sizeof(gint);
457 guint j;
458
459 gunichar2 *tmp_string = (gunichar2 *)malloc(*length);
460 memcpy(tmp_string, pos, *length);
461
462 for(j = 0; j < ((*length) / sizeof(gunichar2)); j++)
463 {
464 tmp_string[j] = g_ntohs(tmp_string[j]);
465 }
466
467 glong read, written;
468 GError *error = NULL;
469 gchar *out = g_utf16_to_utf8(tmp_string, *length / sizeof(gunichar2), &read, &written, &error);
470
471 dt_free(tmp_string);
472
473 if(error)
474 {
475 dt_print(DT_DEBUG_PWSTORAGE, "[pwstorage_kwallet] ERROR: Error converting string: %s\n", error->message);
476 g_error_free(error);
477 return NULL;
478 }
479
480 *length += sizeof(gint);
481 return out;
482}
483
484// Get the (key,value) pairs back from KWallet.
485GHashTable *dt_pwstorage_kwallet_get(const backend_kwallet_context_t *context, const gchar *slot)
486{
487 GHashTable *table = g_hash_table_new_full(g_str_hash, g_str_equal, dt_free_gpointer, dt_free_gpointer);
488 GError *error = NULL;
489
490 // Is there an entry in the wallet?
491 gboolean has_entry = FALSE;
492 int wallet_handle = get_wallet_handle(context);
493
494 /* signature:
495 *
496 * in i handle,
497 * in s folder,
498 * in s key,
499 * in s appid,
500 *
501 * out b arg_0
502 */
503 GVariant *ret = g_dbus_proxy_call_sync(context->proxy, "hasEntry",
504 g_variant_new("(isss)", wallet_handle, kwallet_folder, slot, app_id),
505 G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
506
507 if(check_error(error))
508 {
509 g_variant_unref(ret);
510 return table;
511 }
512
513 GVariant *child = g_variant_get_child_value(ret, 0);
514 has_entry = g_variant_get_boolean(child);
515 g_variant_unref(child);
516 g_variant_unref(ret);
517
518 if(!has_entry) return table;
519
520 /* signature:
521 *
522 * in i handle,
523 * in s folder,
524 * in s key,
525 * in s appid,
526 *
527 * out a{sv} arg_0)
528 */
529 ret = g_dbus_proxy_call_sync(context->proxy, "readMapList",
530 g_variant_new("(isss)", wallet_handle, kwallet_folder, slot, app_id),
531 G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
532
533 if(check_error(error))
534 {
535 g_variant_unref(ret);
536 return table;
537 }
538
539 child = g_variant_get_child_value(ret, 0);
540
541 // we are only interested in the first child. i am not even sure that there can legally be more than one
542 if(g_variant_n_children(child) < 1)
543 {
544 g_variant_unref(child);
545 g_variant_unref(ret);
546 return table;
547 }
548
549 GVariant *element = g_variant_get_child_value(child, 0);
550 GVariant *v = NULL;
551 g_variant_get(element, "{sv}", NULL, &v);
552
553 const gchar *byte_array = g_variant_get_data(v);
554 if(!byte_array)
555 {
556 g_variant_unref(v);
557 g_variant_unref(element);
558 g_variant_unref(child);
559 g_variant_unref(ret);
560 return table;
561 }
562
563 int entries = GINT_FROM_BE(*((int *)byte_array));
564 byte_array += sizeof(gint);
565
566 for(int i = 0; i < entries; i++)
567 {
568 guint length;
569 gchar *key = array2string(byte_array, &length);
570
571 byte_array += length;
572
573 gchar *value = array2string(byte_array, &length);
574
575 byte_array += length;
576
577 dt_print(DT_DEBUG_PWSTORAGE, "[pwstorage_kwallet_get] reading (%s, %s)\n", (gchar *)key, (gchar *)value);
578
579 g_hash_table_insert(table, key, value);
580 }
581
582 g_variant_unref(v);
583 g_variant_unref(element);
584 g_variant_unref(child);
585 g_variant_unref(ret);
586
587 return table;
588}
589// clang-format off
590// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
591// vim: shiftwidth=2 expandtab tabstop=2 cindent
592// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
593// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
static const gint invalid_kwallet_handle
const backend_kwallet_context_t * dt_pwstorage_kwallet_new()
static gboolean check_error(GError *error)
void dt_pwstorage_kwallet_destroy(const backend_kwallet_context_t *context)
static const gchar * app_id
static gboolean start_kwallet(backend_kwallet_context_t *context)
static const gchar * kwallet_folder
GHashTable * dt_pwstorage_kwallet_get(const backend_kwallet_context_t *context, const gchar *slot)
static const gchar * klauncher_service_name
gboolean dt_pwstorage_kwallet_set(const backend_kwallet_context_t *context, const gchar *slot, GHashTable *table)
static const gchar * kwallet_path
static const gchar * kwallet_interface
static gboolean init_kwallet(backend_kwallet_context_t *context)
static gchar * array2string(const gchar *pos, guint *length)
static gchar * char2qstring(const gchar *in, gsize *size)
static int get_wallet_handle(const backend_kwallet_context_t *context)
static const gchar * klauncher_interface
static const gchar * kwallet_service_name
static const gchar * klauncher_path
const dt_colormatrix_t dt_aligned_pixel_t out
char * key
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_PWSTORAGE
Definition darktable.h:721
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
#define dt_free(ptr)
Definition darktable.h:456
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
const float v
size_t size
Definition mipmap_cache.c:3
GDBusConnection * connection