1567 lines
55 KiB
Diff
1567 lines
55 KiB
Diff
|
From deb35ed74342b05be7cd45c458a06b98c7a4d1ac Mon Sep 17 00:00:00 2001
|
||
|
From: Andre Heinecke <aheinecke@intevation.de>
|
||
|
Date: Mon, 19 Jan 2015 10:28:13 +0100
|
||
|
Subject: [PATCH] Use secrets service for cups auth_info
|
||
|
|
||
|
When a printer requires auth_info (e.g. a printer connected
|
||
|
over the samba protocol) it is now possible to save the
|
||
|
credentials necessary for printing if a secrets service
|
||
|
is available over dbus.
|
||
|
The auth_info is then stored / loaded from the default
|
||
|
collection of that secrets service.
|
||
|
If no such service is available the user is not shown
|
||
|
the option to remember the password and the behavior
|
||
|
remains the same as before.
|
||
|
---
|
||
|
gtk/gtkmarshalers.list | 1 -
|
||
|
gtk/gtkprintbackend.c | 46 +-
|
||
|
gtk/gtkprintbackend.h | 9 +-
|
||
|
modules/printbackends/cups/Makefile.am | 6 +-
|
||
|
modules/printbackends/cups/gtkcupssecretsutils.c | 1044 ++++++++++++++++++++++
|
||
|
modules/printbackends/cups/gtkcupssecretsutils.h | 41 +
|
||
|
modules/printbackends/cups/gtkprintbackendcups.c | 165 +++-
|
||
|
7 files changed, 1289 insertions(+), 23 deletions(-)
|
||
|
create mode 100644 modules/printbackends/cups/gtkcupssecretsutils.c
|
||
|
create mode 100644 modules/printbackends/cups/gtkcupssecretsutils.h
|
||
|
|
||
|
diff --git a/gtk/gtkmarshalers.list b/gtk/gtkmarshalers.list
|
||
|
index 77873cb..533a266 100644
|
||
|
--- a/gtk/gtkmarshalers.list
|
||
|
+++ b/gtk/gtkmarshalers.list
|
||
|
@@ -111,4 +111,3 @@ VOID:UINT,STRING,UINT
|
||
|
VOID:UINT,UINT
|
||
|
VOID:VOID
|
||
|
OBJECT:OBJECT,INT,INT
|
||
|
-VOID:POINTER,POINTER,POINTER,POINTER,STRING
|
||
|
diff --git a/gtk/gtkprintbackend.c b/gtk/gtkprintbackend.c
|
||
|
index c487d33..7e61ca4 100644
|
||
|
--- a/gtk/gtkprintbackend.c
|
||
|
+++ b/gtk/gtkprintbackend.c
|
||
|
@@ -51,6 +51,7 @@ struct _GtkPrintBackendPrivate
|
||
|
GtkPrintBackendStatus status;
|
||
|
char **auth_info_required;
|
||
|
char **auth_info;
|
||
|
+ gboolean store_auth_info;
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
@@ -366,7 +367,8 @@ static void request_password (GtkPrintBack
|
||
|
gpointer auth_info_default,
|
||
|
gpointer auth_info_display,
|
||
|
gpointer auth_info_visible,
|
||
|
- const gchar *prompt);
|
||
|
+ const gchar *prompt,
|
||
|
+ gboolean can_store_auth_info);
|
||
|
|
||
|
static void
|
||
|
gtk_print_backend_class_init (GtkPrintBackendClass *class)
|
||
|
@@ -445,9 +447,9 @@ gtk_print_backend_class_init (GtkPrintBackendClass *class)
|
||
|
G_TYPE_FROM_CLASS (class),
|
||
|
G_SIGNAL_RUN_LAST,
|
||
|
G_STRUCT_OFFSET (GtkPrintBackendClass, request_password),
|
||
|
- NULL, NULL,
|
||
|
- _gtk_marshal_VOID__POINTER_POINTER_POINTER_POINTER_STRING,
|
||
|
- G_TYPE_NONE, 5, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_STRING);
|
||
|
+ NULL, NULL, NULL,
|
||
|
+ G_TYPE_NONE, 6, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER,
|
||
|
+ G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
@@ -670,12 +672,24 @@ gtk_print_backend_print_stream (GtkPrintBackend *backend,
|
||
|
void
|
||
|
gtk_print_backend_set_password (GtkPrintBackend *backend,
|
||
|
gchar **auth_info_required,
|
||
|
- gchar **auth_info)
|
||
|
+ gchar **auth_info,
|
||
|
+ gboolean store_auth_info)
|
||
|
{
|
||
|
g_return_if_fail (GTK_IS_PRINT_BACKEND (backend));
|
||
|
|
||
|
if (GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password)
|
||
|
- GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password (backend, auth_info_required, auth_info);
|
||
|
+ GTK_PRINT_BACKEND_GET_CLASS (backend)->set_password (backend,
|
||
|
+ auth_info_required,
|
||
|
+ auth_info,
|
||
|
+ store_auth_info);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+store_auth_info_toggled (GtkCheckButton *chkbtn,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ gboolean *data = (gboolean *) user_data;
|
||
|
+ *data = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (chkbtn));
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
@@ -702,9 +716,9 @@ password_dialog_response (GtkWidget *dialog,
|
||
|
gint i;
|
||
|
|
||
|
if (response_id == GTK_RESPONSE_OK)
|
||
|
- gtk_print_backend_set_password (backend, priv->auth_info_required, priv->auth_info);
|
||
|
+ gtk_print_backend_set_password (backend, priv->auth_info_required, priv->auth_info, priv->store_auth_info);
|
||
|
else
|
||
|
- gtk_print_backend_set_password (backend, priv->auth_info_required, NULL);
|
||
|
+ gtk_print_backend_set_password (backend, priv->auth_info_required, NULL, FALSE);
|
||
|
|
||
|
for (i = 0; i < g_strv_length (priv->auth_info_required); i++)
|
||
|
if (priv->auth_info[i] != NULL)
|
||
|
@@ -729,10 +743,11 @@ request_password (GtkPrintBackend *backend,
|
||
|
gpointer auth_info_default,
|
||
|
gpointer auth_info_display,
|
||
|
gpointer auth_info_visible,
|
||
|
- const gchar *prompt)
|
||
|
+ const gchar *prompt,
|
||
|
+ gboolean can_store_auth_info)
|
||
|
{
|
||
|
GtkPrintBackendPrivate *priv = backend->priv;
|
||
|
- GtkWidget *dialog, *box, *main_box, *label, *icon, *vbox, *entry;
|
||
|
+ GtkWidget *dialog, *box, *main_box, *label, *icon, *vbox, *entry, *chkbtn;
|
||
|
GtkWidget *focus = NULL;
|
||
|
gchar *markup;
|
||
|
gint length;
|
||
|
@@ -745,6 +760,7 @@ request_password (GtkPrintBackend *backend,
|
||
|
priv->auth_info_required = g_strdupv (ai_required);
|
||
|
length = g_strv_length (ai_required);
|
||
|
priv->auth_info = g_new0 (gchar *, length + 1);
|
||
|
+ priv->store_auth_info = FALSE;
|
||
|
|
||
|
dialog = gtk_dialog_new_with_buttons ( _("Authentication"), NULL, GTK_DIALOG_MODAL,
|
||
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
||
|
@@ -813,6 +829,16 @@ request_password (GtkPrintBackend *backend,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
+ if (can_store_auth_info)
|
||
|
+ {
|
||
|
+ chkbtn = gtk_check_button_new_with_mnemonic (_("_Remember password"));
|
||
|
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (chkbtn), FALSE);
|
||
|
+ gtk_box_pack_start (GTK_BOX (vbox), chkbtn, FALSE, FALSE, 6);
|
||
|
+ g_signal_connect (chkbtn, "toggled",
|
||
|
+ G_CALLBACK (store_auth_info_toggled),
|
||
|
+ &(priv->store_auth_info));
|
||
|
+ }
|
||
|
+
|
||
|
if (focus != NULL)
|
||
|
{
|
||
|
gtk_widget_grab_focus (focus);
|
||
|
diff --git a/gtk/gtkprintbackend.h b/gtk/gtkprintbackend.h
|
||
|
index bdf5b69..50bec43 100644
|
||
|
--- a/gtk/gtkprintbackend.h
|
||
|
+++ b/gtk/gtkprintbackend.h
|
||
|
@@ -125,12 +125,14 @@ struct _GtkPrintBackendClass
|
||
|
gpointer auth_info_default,
|
||
|
gpointer auth_info_display,
|
||
|
gpointer auth_info_visible,
|
||
|
- const gchar *prompt);
|
||
|
+ const gchar *prompt,
|
||
|
+ gboolean can_store_auth_info);
|
||
|
|
||
|
/* not a signal */
|
||
|
void (*set_password) (GtkPrintBackend *backend,
|
||
|
gchar **auth_info_required,
|
||
|
- gchar **auth_info);
|
||
|
+ gchar **auth_info,
|
||
|
+ gboolean store_auth_info);
|
||
|
|
||
|
/* Padding for future expansion */
|
||
|
void (*_gtk_reserved1) (void);
|
||
|
@@ -155,7 +157,8 @@ GList * gtk_print_backend_load_modules (void);
|
||
|
void gtk_print_backend_destroy (GtkPrintBackend *print_backend);
|
||
|
void gtk_print_backend_set_password (GtkPrintBackend *backend,
|
||
|
gchar **auth_info_required,
|
||
|
- gchar **auth_info);
|
||
|
+ gchar **auth_info,
|
||
|
+ gboolean can_store_auth_info);
|
||
|
|
||
|
/* Backend-only functions for GtkPrintBackend */
|
||
|
|
||
|
diff --git a/modules/printbackends/cups/Makefile.am b/modules/printbackends/cups/Makefile.am
|
||
|
index 463199e..e904f50 100644
|
||
|
--- a/modules/printbackends/cups/Makefile.am
|
||
|
+++ b/modules/printbackends/cups/Makefile.am
|
||
|
@@ -27,12 +27,14 @@ backend_LTLIBRARIES = libprintbackend-cups.la
|
||
|
libprintbackend_cups_la_SOURCES = \
|
||
|
gtkprintbackendcups.c \
|
||
|
gtkprintercups.c \
|
||
|
- gtkcupsutils.c
|
||
|
+ gtkcupsutils.c \
|
||
|
+ gtkcupssecretsutils.c
|
||
|
|
||
|
noinst_HEADERS = \
|
||
|
gtkprintbackendcups.h \
|
||
|
gtkprintercups.h \
|
||
|
- gtkcupsutils.h
|
||
|
+ gtkcupsutils.h \
|
||
|
+ gtkcupssecretsutils.h
|
||
|
|
||
|
libprintbackend_cups_la_LDFLAGS = -avoid-version -module $(no_undefined)
|
||
|
libprintbackend_cups_la_LIBADD = $(LDADDS) $(CUPS_LIBS)
|
||
|
diff --git a/modules/printbackends/cups/gtkcupssecretsutils.c b/modules/printbackends/cups/gtkcupssecretsutils.c
|
||
|
new file mode 100644
|
||
|
index 0000000..2ed0db5
|
||
|
--- /dev/null
|
||
|
+++ b/modules/printbackends/cups/gtkcupssecretsutils.c
|
||
|
@@ -0,0 +1,1044 @@
|
||
|
+/* gtkcupssecretsutils.h: Helper to use a secrets service for printer passwords
|
||
|
+ * Copyright (C) 2014, Intevation GmbH
|
||
|
+ *
|
||
|
+ * This library is free software; you can redistribute it and/or
|
||
|
+ * modify it under the terms of the GNU Lesser General Public
|
||
|
+ * License as published by the Free Software Foundation; either
|
||
|
+ * version 2 of the License, or (at your option) any later version.
|
||
|
+ *
|
||
|
+ * This library is distributed in the hope that it will be useful,
|
||
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
+ * Lesser General Public License for more details.
|
||
|
+ *
|
||
|
+ * You should have received a copy of the GNU Lesser General Public
|
||
|
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||
|
+ */
|
||
|
+
|
||
|
+#include <glib.h>
|
||
|
+#include <gio/gio.h>
|
||
|
+#include <string.h>
|
||
|
+
|
||
|
+#include <gtk/gtk.h>
|
||
|
+
|
||
|
+#include "gtkcupssecretsutils.h"
|
||
|
+
|
||
|
+#define SECRETS_BUS "org.freedesktop.secrets"
|
||
|
+#define SECRETS_IFACE(interface) "org.freedesktop.Secret."interface
|
||
|
+#define SECRETS_PATH "/org/freedesktop/secrets"
|
||
|
+#define SECRETS_TIMEOUT 5000
|
||
|
+
|
||
|
+typedef enum
|
||
|
+{
|
||
|
+ SECRETS_SERVICE_ACTION_QUERY,
|
||
|
+ SECRETS_SERVICE_ACTION_STORE
|
||
|
+} SecretsServiceAction;
|
||
|
+
|
||
|
+typedef struct
|
||
|
+{
|
||
|
+ GDBusConnection *dbus_connection;
|
||
|
+ SecretsServiceAction action;
|
||
|
+ gchar **auth_info,
|
||
|
+ **auth_info_labels,
|
||
|
+ **auth_info_required,
|
||
|
+ *printer_uri,
|
||
|
+ *session_path,
|
||
|
+ *collection_path;
|
||
|
+ GDBusProxy *item_proxy;
|
||
|
+ guint prompt_subscription;
|
||
|
+} SecretsServiceData;
|
||
|
+
|
||
|
+/**
|
||
|
+ * create_attributes:
|
||
|
+ * @printer_uri: URI for the printer
|
||
|
+ * @additional_labels: Optional labels for additional attributes
|
||
|
+ * @additional_attrs: Optional additional attributes
|
||
|
+ *
|
||
|
+ * Creates a GVariant dictionary with key / value pairs that
|
||
|
+ * can be used to identify a secret item.
|
||
|
+ *
|
||
|
+ * Returns: A GVariant dictionary of string pairs or NULL on error.
|
||
|
+ */
|
||
|
+static GVariant *
|
||
|
+create_attributes (const gchar *printer_uri,
|
||
|
+ gchar **additional_attrs,
|
||
|
+ gchar **additional_labels)
|
||
|
+{
|
||
|
+ GVariantBuilder *attr_builder = NULL;
|
||
|
+ GVariant *ret = NULL;
|
||
|
+
|
||
|
+ if (printer_uri == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING,
|
||
|
+ g_print ("create_attributes called with invalid parameters.\n"));
|
||
|
+ return NULL;
|
||
|
+ }
|
||
|
+
|
||
|
+ attr_builder = g_variant_builder_new (G_VARIANT_TYPE_DICTIONARY);
|
||
|
+ /* The printer uri is the main identifying part */
|
||
|
+ g_variant_builder_add (attr_builder, "{ss}", "uri", printer_uri);
|
||
|
+
|
||
|
+ if (additional_labels != NULL)
|
||
|
+ {
|
||
|
+ int i;
|
||
|
+ for (i = 0; additional_labels[i] != NULL; i++)
|
||
|
+ {
|
||
|
+ g_variant_builder_add (attr_builder, "{ss}",
|
||
|
+ additional_labels[i],
|
||
|
+ additional_attrs[i]);
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = g_variant_builder_end (attr_builder);
|
||
|
+ g_variant_builder_unref (attr_builder);
|
||
|
+
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+get_secret_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GError *error = NULL;
|
||
|
+ GVariant *output,
|
||
|
+ *attributes;
|
||
|
+ gchar **auth_info = NULL,
|
||
|
+ *key = NULL,
|
||
|
+ *value = NULL;
|
||
|
+ GVariantIter *iter = NULL;
|
||
|
+ guint i;
|
||
|
+ gint pw_field = -1;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ output = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
|
||
|
+ res,
|
||
|
+ &error);
|
||
|
+ if (output == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ attributes = g_dbus_proxy_get_cached_property (task_data->item_proxy,
|
||
|
+ "Attributes");
|
||
|
+ if (attributes == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Failed to lookup attributes.\n"));
|
||
|
+ g_variant_unref (output);
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Iterate over the attributes to fill the auth info */
|
||
|
+ g_variant_get (attributes, "a{ss}", &iter);
|
||
|
+
|
||
|
+ auth_info = g_new0 (gchar *,
|
||
|
+ g_strv_length (task_data->auth_info_required) + 1);
|
||
|
+
|
||
|
+ while (g_variant_iter_loop (iter, "{ss}", &key, &value))
|
||
|
+ {
|
||
|
+ /* Match attributes with required auth info */
|
||
|
+ for (i = 0; task_data->auth_info_required[i] != NULL; i++)
|
||
|
+ {
|
||
|
+ if ((strcmp (key, "user") == 0 ||
|
||
|
+ strcmp (key, "username") == 0) &&
|
||
|
+ strcmp (task_data->auth_info_required[i],
|
||
|
+ "username") == 0)
|
||
|
+ {
|
||
|
+ auth_info[i] = g_strdup (value);
|
||
|
+ }
|
||
|
+ else if (strcmp (key, "domain") == 0 &&
|
||
|
+ strcmp (task_data->auth_info_required[i], "domain") == 0)
|
||
|
+ {
|
||
|
+ auth_info[i] = g_strdup (value);
|
||
|
+ }
|
||
|
+ else if ((strcmp (key, "hostname") == 0 ||
|
||
|
+ strcmp (key, "server") == 0 ) &&
|
||
|
+ strcmp (task_data->auth_info_required[i], "hostname") == 0)
|
||
|
+ {
|
||
|
+ auth_info[i] = g_strdup (value);
|
||
|
+ }
|
||
|
+ else if (strcmp (task_data->auth_info_required[i], "password") == 0)
|
||
|
+ {
|
||
|
+ pw_field = i;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ if (pw_field == -1)
|
||
|
+ {
|
||
|
+ /* should not happen... */
|
||
|
+ GTK_NOTE (PRINTING, g_print ("No password required?.\n"));
|
||
|
+ g_variant_unref (output);
|
||
|
+ goto fail;
|
||
|
+ }
|
||
|
+ else
|
||
|
+ {
|
||
|
+ GVariant *secret,
|
||
|
+ *s_value;
|
||
|
+ gconstpointer ba_passwd = NULL;
|
||
|
+ gsize len = 0;
|
||
|
+
|
||
|
+ secret = g_variant_get_child_value (output, 0);
|
||
|
+ g_variant_unref (output);
|
||
|
+ if (secret == NULL || g_variant_n_children (secret) != 4)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Get secret response invalid.\n"));
|
||
|
+ if (secret != NULL)
|
||
|
+ g_variant_unref (secret);
|
||
|
+ goto fail;
|
||
|
+ }
|
||
|
+ s_value = g_variant_get_child_value (secret, 2);
|
||
|
+ ba_passwd = g_variant_get_fixed_array (s_value,
|
||
|
+ &len,
|
||
|
+ sizeof (guchar));
|
||
|
+
|
||
|
+ g_variant_unref (secret);
|
||
|
+
|
||
|
+ if (ba_passwd == NULL)
|
||
|
+ {
|
||
|
+ /* No secret or the secret is not a zero terminated value */
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Invalid / no secret found.\n"));
|
||
|
+ g_variant_unref (s_value);
|
||
|
+ goto fail;
|
||
|
+ }
|
||
|
+
|
||
|
+ auth_info[pw_field] = g_strndup (ba_passwd, len);
|
||
|
+ g_variant_unref (s_value);
|
||
|
+ }
|
||
|
+
|
||
|
+ for (i = 0; task_data->auth_info_required[i] != NULL; i++)
|
||
|
+ {
|
||
|
+ if (auth_info[i] == NULL)
|
||
|
+ {
|
||
|
+ /* Error out if we did not find everything */
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Failed to lookup required attribute: %s.\n",
|
||
|
+ task_data->auth_info_required[i]));
|
||
|
+ goto fail;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ g_task_return_pointer (task, auth_info, NULL);
|
||
|
+ return;
|
||
|
+
|
||
|
+fail:
|
||
|
+ /* Error out */
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Failed to lookup secret.\n"));
|
||
|
+ for (i = 0; i < g_strv_length (task_data->auth_info_required); i++)
|
||
|
+ {
|
||
|
+ /* Not all fields of auth_info are neccessarily written so we can not
|
||
|
+ use strfreev here */
|
||
|
+ g_free (auth_info[i]);
|
||
|
+ }
|
||
|
+ g_free (auth_info);
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+create_item_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ GError *error = NULL;
|
||
|
+ GVariant *output;
|
||
|
+ gchar *item = NULL;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+
|
||
|
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
|
||
|
+ res,
|
||
|
+ &error);
|
||
|
+ if (output == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ g_variant_get (output, "(&o&o)", &item, NULL);
|
||
|
+ if (item != NULL && strlen (item) > 1)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Successfully stored auth info.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ g_variant_unref (output);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+do_store_auth_info (GTask *task)
|
||
|
+{
|
||
|
+ GVariant *attributes = NULL,
|
||
|
+ *properties = NULL,
|
||
|
+ *secret = NULL;
|
||
|
+ gchar **additional_attrs = NULL,
|
||
|
+ **additional_labels = NULL,
|
||
|
+ *password = NULL;
|
||
|
+ SecretsServiceData *task_data = g_task_get_task_data (task);
|
||
|
+ guint i,
|
||
|
+ length,
|
||
|
+ additional_count = 0;
|
||
|
+ GVariantBuilder *prop_builder = NULL;
|
||
|
+
|
||
|
+ length = g_strv_length (task_data->auth_info_labels);
|
||
|
+
|
||
|
+ additional_attrs = g_new0 (gchar *, length + 1);
|
||
|
+ additional_labels = g_new0 (gchar *, length + 1);
|
||
|
+ /* The labels user and server are chosen to be compatible with
|
||
|
+ the attributes used by system-config-printer */
|
||
|
+ for (i = 0; task_data->auth_info_labels[i] != NULL; i++)
|
||
|
+ {
|
||
|
+ if (g_strcmp0 (task_data->auth_info_labels[i], "username") == 0)
|
||
|
+ {
|
||
|
+ additional_attrs[additional_count] = task_data->auth_info[i];
|
||
|
+ additional_labels[additional_count++] = "user";
|
||
|
+ }
|
||
|
+ else if (g_strcmp0 (task_data->auth_info_labels[i], "hostname") == 0)
|
||
|
+ {
|
||
|
+ additional_attrs[additional_count] = task_data->auth_info[i];
|
||
|
+ additional_labels[additional_count++] = "server";
|
||
|
+ }
|
||
|
+ else if (g_strcmp0 (task_data->auth_info_labels[i], "password") == 0)
|
||
|
+ {
|
||
|
+ password = task_data->auth_info[i];
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ attributes = create_attributes (task_data->printer_uri,
|
||
|
+ additional_attrs,
|
||
|
+ additional_labels);
|
||
|
+ g_free (additional_labels);
|
||
|
+ g_free (additional_attrs);
|
||
|
+ if (attributes == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Failed to create attributes.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (password == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("No secret to store.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ prop_builder = g_variant_builder_new (G_VARIANT_TYPE_DICTIONARY);
|
||
|
+
|
||
|
+ g_variant_builder_add (prop_builder, "{sv}", SECRETS_IFACE ("Item.Label"),
|
||
|
+ g_variant_new_string (task_data->printer_uri));
|
||
|
+ g_variant_builder_add (prop_builder, "{sv}", SECRETS_IFACE ("Item.Attributes"),
|
||
|
+ attributes);
|
||
|
+
|
||
|
+ properties = g_variant_builder_end (prop_builder);
|
||
|
+
|
||
|
+ g_variant_builder_unref (prop_builder);
|
||
|
+
|
||
|
+ secret = g_variant_new ("(oay@ays)",
|
||
|
+ task_data->session_path,
|
||
|
+ NULL,
|
||
|
+ g_variant_new_bytestring (password),
|
||
|
+ "text/plain");
|
||
|
+
|
||
|
+ g_dbus_connection_call (task_data->dbus_connection,
|
||
|
+ SECRETS_BUS,
|
||
|
+ task_data->collection_path,
|
||
|
+ SECRETS_IFACE ("Collection"),
|
||
|
+ "CreateItem",
|
||
|
+ g_variant_new ("(@a{sv}@(oayays)b)",
|
||
|
+ properties,
|
||
|
+ secret,
|
||
|
+ TRUE),
|
||
|
+ G_VARIANT_TYPE ("(oo)"),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ create_item_cb,
|
||
|
+ task);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+prompt_completed_cb (GDBusConnection *connection,
|
||
|
+ const gchar *sender_name,
|
||
|
+ const gchar *object_path,
|
||
|
+ const gchar *interface_name,
|
||
|
+ const gchar *signal_name,
|
||
|
+ GVariant *parameters,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GVariant *dismissed;
|
||
|
+ gboolean is_dismissed = TRUE;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ g_dbus_connection_signal_unsubscribe (task_data->dbus_connection,
|
||
|
+ task_data->prompt_subscription);
|
||
|
+ task_data->prompt_subscription = 0;
|
||
|
+
|
||
|
+ dismissed = g_variant_get_child_value (parameters, 0);
|
||
|
+
|
||
|
+ if (dismissed == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Invalid prompt signal.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ g_variant_get (dismissed, "b", &is_dismissed);
|
||
|
+ g_variant_unref (dismissed);
|
||
|
+
|
||
|
+ if (is_dismissed)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Collection unlock dismissed.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Prompt successfull, proceed to get or store secret */
|
||
|
+ switch (task_data->action)
|
||
|
+ {
|
||
|
+ case SECRETS_SERVICE_ACTION_STORE:
|
||
|
+ do_store_auth_info (task);
|
||
|
+ break;
|
||
|
+
|
||
|
+ case SECRETS_SERVICE_ACTION_QUERY:
|
||
|
+ g_dbus_proxy_call (task_data->item_proxy,
|
||
|
+ "GetSecret",
|
||
|
+ g_variant_new ("(o)",
|
||
|
+ task_data->session_path),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ get_secret_cb,
|
||
|
+ task);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+prompt_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GError *error = NULL;
|
||
|
+ GVariant *output;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
|
||
|
+ res,
|
||
|
+ &error);
|
||
|
+ if (output == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ g_variant_unref (output);
|
||
|
+
|
||
|
+ /* Connect to the prompt's completed signal */
|
||
|
+ task_data->prompt_subscription =
|
||
|
+ g_dbus_connection_signal_subscribe (task_data->dbus_connection,
|
||
|
+ NULL,
|
||
|
+ SECRETS_IFACE ("Prompt"),
|
||
|
+ "Completed",
|
||
|
+ NULL,
|
||
|
+ NULL,
|
||
|
+ G_DBUS_SIGNAL_FLAGS_NONE,
|
||
|
+ prompt_completed_cb,
|
||
|
+ task,
|
||
|
+ NULL);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+unlock_collection_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GError *error = NULL;
|
||
|
+ GVariant *output;
|
||
|
+ const gchar *prompt_path;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
|
||
|
+ res,
|
||
|
+ &error);
|
||
|
+ if (output == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ g_variant_get (output, "(@ao&o)", NULL, &prompt_path);
|
||
|
+
|
||
|
+ if (prompt_path != NULL && strlen (prompt_path) > 1)
|
||
|
+ {
|
||
|
+ g_dbus_connection_call (task_data->dbus_connection,
|
||
|
+ SECRETS_BUS,
|
||
|
+ prompt_path,
|
||
|
+ SECRETS_IFACE ("Prompt"),
|
||
|
+ "Prompt",
|
||
|
+ g_variant_new ("(s)", "0"),
|
||
|
+ G_VARIANT_TYPE ("()"),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ prompt_cb,
|
||
|
+ task);
|
||
|
+ }
|
||
|
+ else
|
||
|
+ {
|
||
|
+ switch (task_data->action)
|
||
|
+ {
|
||
|
+ case SECRETS_SERVICE_ACTION_STORE:
|
||
|
+ do_store_auth_info (task);
|
||
|
+ break;
|
||
|
+
|
||
|
+ case SECRETS_SERVICE_ACTION_QUERY:
|
||
|
+ /* Prompt successfull proceed to get secret */
|
||
|
+ g_dbus_proxy_call (task_data->item_proxy,
|
||
|
+ "GetSecret",
|
||
|
+ g_variant_new ("(o)",
|
||
|
+ task_data->session_path),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ get_secret_cb,
|
||
|
+ task);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ g_variant_unref (output);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+unlock_read_alias_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GError *error = NULL;
|
||
|
+ GVariant *output,
|
||
|
+ *subresult;
|
||
|
+ gsize path_len = 0;
|
||
|
+ const gchar *collection_path;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
|
||
|
+ res,
|
||
|
+ &error);
|
||
|
+ if (output == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ subresult = g_variant_get_child_value (output, 0);
|
||
|
+ g_variant_unref (output);
|
||
|
+
|
||
|
+ if (subresult == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Invalid ReadAlias response.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ collection_path = g_variant_get_string (subresult, &path_len);
|
||
|
+
|
||
|
+ const gchar * const to_unlock[] =
|
||
|
+ {
|
||
|
+ collection_path, NULL
|
||
|
+ };
|
||
|
+
|
||
|
+ task_data->collection_path = g_strdup (collection_path);
|
||
|
+
|
||
|
+ g_dbus_connection_call (task_data->dbus_connection,
|
||
|
+ SECRETS_BUS,
|
||
|
+ SECRETS_PATH,
|
||
|
+ SECRETS_IFACE ("Service"),
|
||
|
+ "Unlock",
|
||
|
+ g_variant_new ("(^ao)", to_unlock),
|
||
|
+ G_VARIANT_TYPE ("(aoo)"),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ unlock_collection_cb,
|
||
|
+ task);
|
||
|
+
|
||
|
+ g_variant_unref (subresult);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+item_proxy_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GError *error = NULL;
|
||
|
+ GDBusProxy *item_proxy;
|
||
|
+ GVariant *locked;
|
||
|
+ gboolean is_locked;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ item_proxy = g_dbus_proxy_new_finish (res,
|
||
|
+ &error);
|
||
|
+ if (item_proxy == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ task_data->item_proxy = item_proxy;
|
||
|
+
|
||
|
+ locked = g_dbus_proxy_get_cached_property (item_proxy, "Locked");
|
||
|
+
|
||
|
+ if (locked == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Failed to look up \"Locked\" property on item.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ g_variant_get (locked, "b", &is_locked);
|
||
|
+ g_variant_unref (locked);
|
||
|
+
|
||
|
+ if (is_locked)
|
||
|
+ {
|
||
|
+ /* Go down the unlock -> lookup path */
|
||
|
+ g_dbus_connection_call (task_data->dbus_connection,
|
||
|
+ SECRETS_BUS,
|
||
|
+ SECRETS_PATH,
|
||
|
+ SECRETS_IFACE ("Service"),
|
||
|
+ "ReadAlias",
|
||
|
+ g_variant_new ("(s)", "default"),
|
||
|
+ G_VARIANT_TYPE ("(o)"),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ unlock_read_alias_cb,
|
||
|
+ task);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Unlocked proceed to get or store secret */
|
||
|
+ switch (task_data->action)
|
||
|
+ {
|
||
|
+ case SECRETS_SERVICE_ACTION_STORE:
|
||
|
+ do_store_auth_info (task);
|
||
|
+ break;
|
||
|
+
|
||
|
+ case SECRETS_SERVICE_ACTION_QUERY:
|
||
|
+ g_dbus_proxy_call (item_proxy,
|
||
|
+ "GetSecret",
|
||
|
+ g_variant_new ("(o)",
|
||
|
+ task_data->session_path),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ get_secret_cb,
|
||
|
+ task);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+search_items_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GError *error = NULL;
|
||
|
+ GVariant *output;
|
||
|
+ gsize array_cnt,
|
||
|
+ i;
|
||
|
+ gboolean found_item = FALSE;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
|
||
|
+ res,
|
||
|
+ &error);
|
||
|
+ if (output == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ array_cnt = g_variant_n_children (output);
|
||
|
+
|
||
|
+ for (i = 0; i < array_cnt; i++)
|
||
|
+ {
|
||
|
+ GVariant * const item_paths = g_variant_get_child_value (output, i);
|
||
|
+ const gchar **items = NULL;
|
||
|
+
|
||
|
+ if (item_paths == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING,
|
||
|
+ g_print ("SearchItems returned invalid result.\n"));
|
||
|
+ continue;
|
||
|
+ }
|
||
|
+
|
||
|
+ items = g_variant_get_objv (item_paths, NULL);
|
||
|
+
|
||
|
+ if (*items == NULL)
|
||
|
+ {
|
||
|
+ g_variant_unref (item_paths);
|
||
|
+ g_free ((gpointer) items);
|
||
|
+ continue;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Access the first found item. */
|
||
|
+ found_item = TRUE;
|
||
|
+ g_dbus_proxy_new (task_data->dbus_connection,
|
||
|
+ G_DBUS_PROXY_FLAGS_NONE,
|
||
|
+ NULL,
|
||
|
+ SECRETS_BUS,
|
||
|
+ *items,
|
||
|
+ SECRETS_IFACE ("Item"),
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ item_proxy_cb,
|
||
|
+ task);
|
||
|
+ g_free ((gpointer) items);
|
||
|
+ g_variant_unref (item_paths);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ g_variant_unref (output);
|
||
|
+
|
||
|
+ if (!found_item)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("No match found in secrets service.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+open_session_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ GVariant *output,
|
||
|
+ *session_variant;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GError *error = NULL;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ output = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
|
||
|
+ res,
|
||
|
+ &error);
|
||
|
+ if (output == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ session_variant = g_variant_get_child_value (output, 1);
|
||
|
+
|
||
|
+ if (session_variant == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Invalid session path response.\n"));
|
||
|
+ g_variant_unref (output);
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ task_data->session_path = g_variant_dup_string (session_variant, NULL);
|
||
|
+
|
||
|
+ if (task_data->session_path == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Invalid session path string value.\n"));
|
||
|
+ g_variant_unref (session_variant);
|
||
|
+ g_variant_unref (output);
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ g_variant_unref (session_variant);
|
||
|
+ g_variant_unref (output);
|
||
|
+
|
||
|
+ switch (task_data->action)
|
||
|
+ {
|
||
|
+ case SECRETS_SERVICE_ACTION_QUERY:
|
||
|
+ {
|
||
|
+ /* Search for the secret item */
|
||
|
+ GVariant *secrets_attrs;
|
||
|
+
|
||
|
+ secrets_attrs = create_attributes (task_data->printer_uri, NULL, NULL);
|
||
|
+ if (secrets_attrs == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Failed to create attributes.\n"));
|
||
|
+ g_task_return_pointer (task, NULL, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ g_dbus_connection_call (task_data->dbus_connection,
|
||
|
+ SECRETS_BUS,
|
||
|
+ SECRETS_PATH,
|
||
|
+ SECRETS_IFACE ("Service"),
|
||
|
+ "SearchItems",
|
||
|
+ g_variant_new ("(@a{ss})", secrets_attrs),
|
||
|
+ G_VARIANT_TYPE ("(aoao)"),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ search_items_cb,
|
||
|
+ task);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+
|
||
|
+ case SECRETS_SERVICE_ACTION_STORE:
|
||
|
+ {
|
||
|
+ /* Look up / unlock the default collection for storing */
|
||
|
+ g_dbus_connection_call (task_data->dbus_connection,
|
||
|
+ SECRETS_BUS,
|
||
|
+ SECRETS_PATH,
|
||
|
+ SECRETS_IFACE ("Service"),
|
||
|
+ "ReadAlias",
|
||
|
+ g_variant_new ("(s)", "default"),
|
||
|
+ G_VARIANT_TYPE ("(o)"),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ unlock_read_alias_cb,
|
||
|
+ task);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+get_connection_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+ GError *error = NULL;
|
||
|
+
|
||
|
+ task = user_data;
|
||
|
+ task_data = g_task_get_task_data (task);
|
||
|
+
|
||
|
+ task_data->dbus_connection = g_bus_get_finish (res, &error);
|
||
|
+ if (task_data->dbus_connection == NULL)
|
||
|
+ {
|
||
|
+ g_task_return_error (task, error);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Now open a session */
|
||
|
+ g_dbus_connection_call (task_data->dbus_connection,
|
||
|
+ SECRETS_BUS,
|
||
|
+ SECRETS_PATH,
|
||
|
+ SECRETS_IFACE ("Service"),
|
||
|
+ "OpenSession",
|
||
|
+ g_variant_new ("(sv)", "plain",
|
||
|
+ g_variant_new_string ("")),
|
||
|
+ G_VARIANT_TYPE ("(vo)"),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ g_task_get_cancellable (task),
|
||
|
+ open_session_cb,
|
||
|
+ task);
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * gtk_cups_secrets_service_watch:
|
||
|
+ * @appeared: The callback to call when the service interface appears
|
||
|
+ * @vanished: The callback to call when the service interface vanishes
|
||
|
+ * @user_data: A reference to the watching printbackend
|
||
|
+ *
|
||
|
+ * Registers a watch for the secrets service interface.
|
||
|
+ *
|
||
|
+ * Returns: The watcher id
|
||
|
+ */
|
||
|
+guint
|
||
|
+gtk_cups_secrets_service_watch (GBusNameAppearedCallback appeared,
|
||
|
+ GBusNameVanishedCallback vanished,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ return g_bus_watch_name (G_BUS_TYPE_SESSION,
|
||
|
+ SECRETS_BUS,
|
||
|
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
||
|
+ appeared,
|
||
|
+ vanished,
|
||
|
+ user_data,
|
||
|
+ NULL);
|
||
|
+}
|
||
|
+
|
||
|
+void
|
||
|
+cleanup_task_data (gpointer data)
|
||
|
+{
|
||
|
+ gint i;
|
||
|
+ SecretsServiceData *task_data = data;
|
||
|
+
|
||
|
+ g_free (task_data->collection_path);
|
||
|
+ g_strfreev (task_data->auth_info_labels);
|
||
|
+ g_strfreev (task_data->auth_info_required);
|
||
|
+ g_free (task_data->printer_uri);
|
||
|
+
|
||
|
+ if (task_data->auth_info != NULL)
|
||
|
+ {
|
||
|
+ for (i = 0; task_data->auth_info[i] != NULL; i++)
|
||
|
+ {
|
||
|
+ memset (task_data->auth_info[i], 0, strlen (task_data->auth_info[i]));
|
||
|
+ g_clear_pointer (&task_data->auth_info[i], g_free);
|
||
|
+ }
|
||
|
+ g_clear_pointer (&task_data->auth_info, g_free);
|
||
|
+ }
|
||
|
+
|
||
|
+ if (task_data->prompt_subscription != 0)
|
||
|
+ {
|
||
|
+ g_dbus_connection_signal_unsubscribe (task_data->dbus_connection,
|
||
|
+ task_data->prompt_subscription);
|
||
|
+ task_data->prompt_subscription = 0;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (task_data->session_path != NULL)
|
||
|
+ {
|
||
|
+ g_dbus_connection_call (task_data->dbus_connection,
|
||
|
+ SECRETS_BUS,
|
||
|
+ task_data->session_path,
|
||
|
+ SECRETS_IFACE ("Session"),
|
||
|
+ "Close",
|
||
|
+ NULL,
|
||
|
+ G_VARIANT_TYPE ("()"),
|
||
|
+ G_DBUS_CALL_FLAGS_NONE,
|
||
|
+ SECRETS_TIMEOUT,
|
||
|
+ NULL,
|
||
|
+ NULL,
|
||
|
+ NULL);
|
||
|
+ }
|
||
|
+
|
||
|
+ g_clear_object (&task_data->dbus_connection);
|
||
|
+ g_clear_pointer (&task_data->session_path, g_free);
|
||
|
+ g_clear_object (&task_data->item_proxy);
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * gtk_cups_secrets_service_query_task:
|
||
|
+ * @source_object: Source object for this task
|
||
|
+ * @cancellable: Cancellable to cancel this task
|
||
|
+ * @callback: Callback to call once the query is finished
|
||
|
+ * @user_data: The user_data passed to the callback
|
||
|
+ * @printer_uri: URI of the printer
|
||
|
+ * @auth_info_required: Info required for authentication
|
||
|
+ *
|
||
|
+ * Checks if a secrets service as described by the secrets-service standard
|
||
|
+ * is available and if so it tries to find the authentication info in the
|
||
|
+ * default collection of the service.
|
||
|
+ *
|
||
|
+ * This is the entry point to a chain of async calls to open a session,
|
||
|
+ * search the secret, unlock the collection (if necessary) and finally
|
||
|
+ * to lookup the secret.
|
||
|
+ *
|
||
|
+ * See: http://standards.freedesktop.org/secret-service/ for documentation
|
||
|
+ * of the used API.
|
||
|
+ */
|
||
|
+void
|
||
|
+gtk_cups_secrets_service_query_task (gpointer source_object,
|
||
|
+ GCancellable *cancellable,
|
||
|
+ GAsyncReadyCallback callback,
|
||
|
+ gpointer user_data,
|
||
|
+ const gchar *printer_uri,
|
||
|
+ gchar **auth_info_required)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+
|
||
|
+ task_data = g_new0 (SecretsServiceData, 1);
|
||
|
+ task_data->action = SECRETS_SERVICE_ACTION_QUERY;
|
||
|
+ task_data->printer_uri = g_strdup (printer_uri);
|
||
|
+ task_data->auth_info_required = g_strdupv (auth_info_required);
|
||
|
+
|
||
|
+ task = g_task_new (source_object, cancellable, callback, user_data);
|
||
|
+
|
||
|
+ g_task_set_task_data (task, task_data, cleanup_task_data);
|
||
|
+
|
||
|
+ g_bus_get (G_BUS_TYPE_SESSION, cancellable,
|
||
|
+ get_connection_cb, task);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+store_done_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task = (GTask *) res;
|
||
|
+ GError *error = NULL;
|
||
|
+
|
||
|
+ g_task_propagate_pointer (task, &error);
|
||
|
+
|
||
|
+ if (error != NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING,
|
||
|
+ g_print ("Failed to store auth info: %s\n", error->message));
|
||
|
+ g_error_free (error);
|
||
|
+ }
|
||
|
+
|
||
|
+ g_object_unref (task);
|
||
|
+ GTK_NOTE (PRINTING,
|
||
|
+ g_print ("gtk_cups_secrets_service_store finished.\n"));
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * gtk_cups_secrets_service_store:
|
||
|
+ * @auth_info: Auth info that should be stored
|
||
|
+ * @auth_info_labels: The keys to use for the auth info
|
||
|
+ * @printer_uri: URI of the printer
|
||
|
+ *
|
||
|
+ * Tries to store the auth_info in a secrets service.
|
||
|
+ */
|
||
|
+void
|
||
|
+gtk_cups_secrets_service_store (gchar **auth_info,
|
||
|
+ gchar **auth_info_labels,
|
||
|
+ const gchar *printer_uri)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ SecretsServiceData *task_data;
|
||
|
+
|
||
|
+ if (auth_info == NULL || auth_info_labels == NULL || printer_uri == NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING,
|
||
|
+ g_print ("Invalid call to gtk_cups_secrets_service_store.\n"));
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ task_data = g_new0 (SecretsServiceData, 1);
|
||
|
+ task_data->action = SECRETS_SERVICE_ACTION_STORE;
|
||
|
+ task_data->printer_uri = g_strdup (printer_uri);
|
||
|
+ task_data->auth_info = g_strdupv (auth_info);
|
||
|
+ task_data->auth_info_labels = g_strdupv (auth_info_labels);
|
||
|
+
|
||
|
+ task = g_task_new (NULL, NULL, store_done_cb, NULL);
|
||
|
+
|
||
|
+ g_task_set_task_data (task, task_data, cleanup_task_data);
|
||
|
+
|
||
|
+ g_bus_get (G_BUS_TYPE_SESSION, NULL,
|
||
|
+ get_connection_cb, task);
|
||
|
+}
|
||
|
diff --git a/modules/printbackends/cups/gtkcupssecretsutils.h b/modules/printbackends/cups/gtkcupssecretsutils.h
|
||
|
new file mode 100644
|
||
|
index 0000000..1a0424a
|
||
|
--- /dev/null
|
||
|
+++ b/modules/printbackends/cups/gtkcupssecretsutils.h
|
||
|
@@ -0,0 +1,41 @@
|
||
|
+/* gtkcupssecretsutils.h: Helper to use a secrets service for printer passwords
|
||
|
+ * Copyright (C) 2014 Intevation GmbH
|
||
|
+ *
|
||
|
+ * This library is free software; you can redistribute it and/or
|
||
|
+ * modify it under the terms of the GNU Lesser General Public
|
||
|
+ * License as published by the Free Software Foundation; either
|
||
|
+ * version 2 of the License, or (at your option) any later version.
|
||
|
+ *
|
||
|
+ * This library is distributed in the hope that it will be useful,
|
||
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
+ * Lesser General Public License for more details.
|
||
|
+ *
|
||
|
+ * You should have received a copy of the GNU Lesser General Public
|
||
|
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||
|
+ */
|
||
|
+#ifndef __GTK_SECRETS_UTILS_H__
|
||
|
+#define __GTK_SECRETS_UTILS_H__
|
||
|
+
|
||
|
+#include <glib.h>
|
||
|
+
|
||
|
+#include "gtkcupsutils.h"
|
||
|
+
|
||
|
+G_BEGIN_DECLS
|
||
|
+
|
||
|
+void gtk_cups_secrets_service_query_task (gpointer source_object,
|
||
|
+ GCancellable *cancellable,
|
||
|
+ GAsyncReadyCallback callback,
|
||
|
+ gpointer user_data,
|
||
|
+ const gchar *printer_uri,
|
||
|
+ gchar **auth_info_required);
|
||
|
+guint gtk_cups_secrets_service_watch (GBusNameAppearedCallback appeared,
|
||
|
+ GBusNameVanishedCallback vanished,
|
||
|
+ gpointer user_data);
|
||
|
+void gtk_cups_secrets_service_store (gchar **auth_info,
|
||
|
+ gchar **auth_info_labels,
|
||
|
+ const gchar *printer_uri);
|
||
|
+
|
||
|
+G_END_DECLS
|
||
|
+
|
||
|
+#endif /* __GTK_SECRETS_UTILS_H__ */
|
||
|
diff --git a/modules/printbackends/cups/gtkprintbackendcups.c b/modules/printbackends/cups/gtkprintbackendcups.c
|
||
|
index cebdc17..7e51f2f 100644
|
||
|
--- a/modules/printbackends/cups/gtkprintbackendcups.c
|
||
|
+++ b/modules/printbackends/cups/gtkprintbackendcups.c
|
||
|
@@ -60,6 +60,7 @@
|
||
|
#include "gtkprintercups.h"
|
||
|
|
||
|
#include "gtkcupsutils.h"
|
||
|
+#include "gtkcupssecretsutils.h"
|
||
|
|
||
|
|
||
|
typedef struct _GtkPrintBackendCupsClass GtkPrintBackendCupsClass;
|
||
|
@@ -152,6 +153,9 @@ struct _GtkPrintBackendCups
|
||
|
gchar *avahi_service_browser_paths[2];
|
||
|
GCancellable *avahi_cancellable;
|
||
|
#endif
|
||
|
+ gboolean secrets_service_available;
|
||
|
+ guint secrets_service_watch_id;
|
||
|
+ GCancellable *secrets_service_cancellable;
|
||
|
};
|
||
|
|
||
|
static GObjectClass *backend_parent_class;
|
||
|
@@ -212,16 +216,26 @@ static cairo_surface_t * cups_printer_create_cairo_surface (GtkPrinter
|
||
|
|
||
|
static void gtk_print_backend_cups_set_password (GtkPrintBackend *backend,
|
||
|
gchar **auth_info_required,
|
||
|
- gchar **auth_info);
|
||
|
+ gchar **auth_info,
|
||
|
+ gboolean store_auth_info);
|
||
|
|
||
|
void overwrite_and_free (gpointer data);
|
||
|
static gboolean is_address_local (const gchar *address);
|
||
|
static gboolean request_auth_info (gpointer data);
|
||
|
+static void lookup_auth_info (gpointer data);
|
||
|
|
||
|
#ifdef HAVE_CUPS_API_1_6
|
||
|
static void avahi_request_printer_list (GtkPrintBackendCups *cups_backend);
|
||
|
#endif
|
||
|
|
||
|
+static void secrets_service_appeared_cb (GDBusConnection *connection,
|
||
|
+ const gchar *name,
|
||
|
+ const gchar *name_owner,
|
||
|
+ gpointer user_data);
|
||
|
+static void secrets_service_vanished_cb (GDBusConnection *connection,
|
||
|
+ const gchar *name,
|
||
|
+ gpointer user_data);
|
||
|
+
|
||
|
static void
|
||
|
gtk_print_backend_cups_register_type (GTypeModule *module)
|
||
|
{
|
||
|
@@ -788,6 +802,13 @@ gtk_print_backend_cups_init (GtkPrintBackendCups *backend_cups)
|
||
|
#endif
|
||
|
|
||
|
cups_get_local_default_printer (backend_cups);
|
||
|
+
|
||
|
+ backend_cups->secrets_service_available = FALSE;
|
||
|
+ backend_cups->secrets_service_cancellable = g_cancellable_new ();
|
||
|
+ backend_cups->secrets_service_watch_id =
|
||
|
+ gtk_cups_secrets_service_watch (secrets_service_appeared_cb,
|
||
|
+ secrets_service_vanished_cb,
|
||
|
+ backend_cups);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
@@ -820,6 +841,12 @@ gtk_print_backend_cups_finalize (GObject *object)
|
||
|
g_clear_object (&backend_cups->dbus_connection);
|
||
|
#endif
|
||
|
|
||
|
+ g_clear_object (&backend_cups->secrets_service_cancellable);
|
||
|
+ if (backend_cups->secrets_service_watch_id != 0)
|
||
|
+ {
|
||
|
+ g_bus_unwatch_name (backend_cups->secrets_service_watch_id);
|
||
|
+ }
|
||
|
+
|
||
|
backend_parent_class->finalize (object);
|
||
|
}
|
||
|
|
||
|
@@ -935,7 +962,8 @@ httpGetHostname(http_t *http,
|
||
|
static void
|
||
|
gtk_print_backend_cups_set_password (GtkPrintBackend *backend,
|
||
|
gchar **auth_info_required,
|
||
|
- gchar **auth_info)
|
||
|
+ gchar **auth_info,
|
||
|
+ gboolean store_auth_info)
|
||
|
{
|
||
|
GtkPrintBackendCups *cups_backend = GTK_PRINT_BACKEND_CUPS (backend);
|
||
|
GList *l;
|
||
|
@@ -988,6 +1016,17 @@ gtk_print_backend_cups_set_password (GtkPrintBackend *backend,
|
||
|
for (i = 0; i < length; i++)
|
||
|
dispatch->request->auth_info[i] = g_strdup (auth_info[i]);
|
||
|
}
|
||
|
+ /* Save the password if the user requested it */
|
||
|
+ if (password != NULL && store_auth_info)
|
||
|
+ {
|
||
|
+ const gchar *printer_uri =
|
||
|
+ gtk_cups_request_ipp_get_string (dispatch->request,
|
||
|
+ IPP_TAG_URI,
|
||
|
+ "printer-uri");
|
||
|
+
|
||
|
+ gtk_cups_secrets_service_store (auth_info, auth_info_required,
|
||
|
+ printer_uri);
|
||
|
+ }
|
||
|
dispatch->backend->authentication_lock = FALSE;
|
||
|
dispatch->request->need_auth_info = FALSE;
|
||
|
}
|
||
|
@@ -1115,7 +1154,7 @@ request_password (gpointer data)
|
||
|
g_free (printer_name);
|
||
|
|
||
|
g_signal_emit_by_name (dispatch->backend, "request-password",
|
||
|
- auth_info_required, auth_info_default, auth_info_display, auth_info_visible, prompt);
|
||
|
+ auth_info_required, auth_info_default, auth_info_display, auth_info_visible, prompt, FALSE);
|
||
|
|
||
|
g_free (prompt);
|
||
|
}
|
||
|
@@ -1223,6 +1262,98 @@ check_auth_info (gpointer user_data)
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
+static void
|
||
|
+lookup_auth_info_cb (GObject *source_object,
|
||
|
+ GAsyncResult *res,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GTask *task;
|
||
|
+ GtkPrintCupsDispatchWatch *dispatch;
|
||
|
+ gchar **auth_info;
|
||
|
+ GError *error = NULL;
|
||
|
+ gint i;
|
||
|
+
|
||
|
+ task = (GTask *) res;
|
||
|
+ dispatch = user_data;
|
||
|
+ auth_info = g_task_propagate_pointer (task, &error);
|
||
|
+
|
||
|
+ if (auth_info == NULL)
|
||
|
+ {
|
||
|
+ if (error != NULL)
|
||
|
+ {
|
||
|
+ GTK_NOTE (PRINTING,
|
||
|
+ g_print ("Failed to look up auth info: %s\n", error->message));
|
||
|
+ g_error_free (error);
|
||
|
+ }
|
||
|
+ else
|
||
|
+ {
|
||
|
+ /* Error note should have been shown by the function causing this */
|
||
|
+ GTK_NOTE (PRINTING, g_print ("Failed to look up auth info.\n"));
|
||
|
+ }
|
||
|
+ dispatch->backend->authentication_lock = FALSE;
|
||
|
+ g_object_unref (task);
|
||
|
+ request_auth_info (dispatch);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ gtk_print_backend_cups_set_password (GTK_PRINT_BACKEND (dispatch->backend),
|
||
|
+ dispatch->request->auth_info_required, auth_info,
|
||
|
+ FALSE);
|
||
|
+ for (i = 0; auth_info[i] != NULL; i++)
|
||
|
+ {
|
||
|
+ overwrite_and_free (auth_info[i]);
|
||
|
+ auth_info[i] = NULL;
|
||
|
+ }
|
||
|
+ g_clear_pointer (auth_info, g_free);
|
||
|
+
|
||
|
+ g_object_unref (task);
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+lookup_auth_info (gpointer user_data)
|
||
|
+{
|
||
|
+ GtkPrintCupsDispatchWatch *dispatch;
|
||
|
+ gsize length,
|
||
|
+ i;
|
||
|
+ gboolean need_secret_auth_info = FALSE;
|
||
|
+ const gchar *printer_uri;
|
||
|
+
|
||
|
+ dispatch = user_data;
|
||
|
+
|
||
|
+ if (dispatch->backend->authentication_lock)
|
||
|
+ return;
|
||
|
+
|
||
|
+ length = g_strv_length (dispatch->request->auth_info_required);
|
||
|
+
|
||
|
+ for (i = 0; i < length; i++)
|
||
|
+ {
|
||
|
+ if (g_strcmp0 (dispatch->request->auth_info_required[i], "password") == 0)
|
||
|
+ {
|
||
|
+ need_secret_auth_info = TRUE;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ g_idle_add (check_auth_info, user_data);
|
||
|
+
|
||
|
+ if (dispatch->backend->secrets_service_available && need_secret_auth_info)
|
||
|
+ {
|
||
|
+ dispatch->backend->authentication_lock = TRUE;
|
||
|
+ printer_uri = gtk_cups_request_ipp_get_string (dispatch->request,
|
||
|
+ IPP_TAG_URI,
|
||
|
+ "printer-uri");
|
||
|
+ gtk_cups_secrets_service_query_task (dispatch->backend,
|
||
|
+ dispatch->backend->secrets_service_cancellable,
|
||
|
+ lookup_auth_info_cb,
|
||
|
+ dispatch,
|
||
|
+ printer_uri,
|
||
|
+ dispatch->request->auth_info_required);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ request_auth_info (user_data);
|
||
|
+}
|
||
|
+
|
||
|
static gboolean
|
||
|
request_auth_info (gpointer user_data)
|
||
|
{
|
||
|
@@ -1299,7 +1430,8 @@ request_auth_info (gpointer user_data)
|
||
|
auth_info_default,
|
||
|
auth_info_display,
|
||
|
auth_info_visible,
|
||
|
- prompt);
|
||
|
+ prompt,
|
||
|
+ dispatch->backend->secrets_service_available);
|
||
|
|
||
|
for (i = 0; i < length; i++)
|
||
|
{
|
||
|
@@ -1312,8 +1444,6 @@ request_auth_info (gpointer user_data)
|
||
|
g_free (printer_name);
|
||
|
g_free (prompt);
|
||
|
|
||
|
- g_idle_add (check_auth_info, user_data);
|
||
|
-
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
@@ -1514,7 +1644,7 @@ cups_request_execute (GtkPrintBackendCups *print_backend,
|
||
|
{
|
||
|
dispatch->callback = callback;
|
||
|
dispatch->callback_data = user_data;
|
||
|
- request_auth_info (dispatch);
|
||
|
+ lookup_auth_info (dispatch);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
@@ -5762,3 +5892,24 @@ cups_printer_get_capabilities (GtkPrinter *printer)
|
||
|
|
||
|
return capabilities;
|
||
|
}
|
||
|
+
|
||
|
+static void
|
||
|
+secrets_service_appeared_cb (GDBusConnection *connection,
|
||
|
+ const gchar *name,
|
||
|
+ const gchar *name_owner,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GtkPrintBackendCups *backend = GTK_PRINT_BACKEND_CUPS (user_data);
|
||
|
+
|
||
|
+ backend->secrets_service_available = TRUE;
|
||
|
+}
|
||
|
+
|
||
|
+static void
|
||
|
+secrets_service_vanished_cb (GDBusConnection *connection,
|
||
|
+ const gchar *name,
|
||
|
+ gpointer user_data)
|
||
|
+{
|
||
|
+ GtkPrintBackendCups *backend = GTK_PRINT_BACKEND_CUPS (user_data);
|
||
|
+
|
||
|
+ backend->secrets_service_available = FALSE;
|
||
|
+}
|
||
|
--
|
||
|
1.9.1
|
||
|
|