GnuCash  5.6-150-g038405b370+
gnc-keyring.c
1 /*
2  * gnc-keyring.c -- utility functions to store and retrieve passwords.
3  *
4  * Copyright (C) 2010 Geert Janssens <janssens.geert@telenet.be>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, contact:
18  *
19  * Free Software Foundation Voice: +1-617-542-5942
20  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
21  * Boston, MA 02110-1301, USA gnu@gnu.org
22  */
23 
24 #include <config.h>
25 #include <glib/gi18n.h>
26 #include "qof.h"
27 #include "gnc-ui.h"
28 #include "gnc-keyring.h"
29 #ifdef HAVE_LIBSECRET
30 #include <libsecret/secret.h>
31 #elif HAVE_GNOME_KEYRING
32 #define GNOME_KEYRING_DEPRECATED
33 #define GNOME_KEYRING_DEPRECATED_FOR(x)
34 #include <gnome-keyring.h>
35 #endif
36 #ifdef HAVE_OSX_KEYCHAIN
37 #include <Security/Security.h>
38 #include <CoreFoundation/CoreFoundation.h>
39 #include <Carbon/Carbon.h>
40 // SecKeychain* are deprecated
41 #pragma GCC diagnostic warning "-Wdeprecated-declarations"
42 #endif
43 
44 /* This static indicates the debugging module that this .o belongs to. */
45 G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_GUI;
46 
47 #ifdef HAVE_LIBSECRET
48 const SecretSchema* gnucash_get_secret_schema(void) G_GNUC_CONST;
49 const SecretSchema* gnucash_get_secret_schema(void)
50 {
51  static const SecretSchema secret_schema = {
52  "org.gnucash.password", SECRET_SCHEMA_NONE,
53  {
54  { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING },
55  { "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
56  { "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
57  { "user", SECRET_SCHEMA_ATTRIBUTE_STRING },
58  { "NULL", 0 },
59  }
60  };
61 
62  return &secret_schema;
63 }
64 
65 #define SECRET_SCHEMA_GNUCASH gnucash_get_secret_schema()
66 #endif
67 
68 void gnc_keyring_set_password (const gchar *access_method,
69  const gchar *server,
70  guint32 port,
71  const gchar *service,
72  const gchar *user,
73  const gchar* password)
74 {
75 #ifdef HAVE_LIBSECRET
76  GError* error = NULL;
77  gchar* label = NULL;
78 
79  g_return_if_fail(access_method != NULL && server != NULL &&
80  service != NULL && user != NULL && password != NULL);
81 
82  label = g_strdup_printf("GnuCash password for %s://%s@%s", access_method, user, server);
83 
84  if (port == 0)
85  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
86  label, password, NULL, &error,
87  "protocol", access_method,
88  "server", server,
89  "user", user,
90  NULL);
91  else
92  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
93  label, password, NULL, &error,
94  "protocol", access_method,
95  "server", server,
96  "port", port,
97  "user", user,
98  NULL);
99 
100  g_free(label);
101 
102  if (error != NULL)
103  {
104  PWARN ("libsecret error: %s", error->message);
105  PWARN ("The user will be prompted for a password again next time.");
106  g_error_free(error);
107  }
108 #elif HAVE_GNOME_KEYRING
109  GnomeKeyringResult gkr_result;
110  guint32 item_id = 0;
111 
112  g_return_if_fail(access_method != NULL && server != NULL &&
113  service != NULL && user != NULL && password != NULL);
114 
115  gkr_result = gnome_keyring_set_network_password_sync
116  (NULL, user, NULL, server, service,
117  access_method, NULL, port, password, &item_id);
118 
119  if (gkr_result != GNOME_KEYRING_RESULT_OK)
120  {
121  PWARN ("Gnome-keyring error: %s",
122  gnome_keyring_result_to_message(gkr_result));
123  PWARN ("The user will be prompted for a password again next time.");
124  }
125 #endif /* HAVE_GNOME_KEYRING */
126 #ifdef HAVE_OSX_KEYCHAIN
127  OSStatus status;
128  SecKeychainItemRef *itemRef = NULL;
129 
130  g_return_if_fail(access_method != NULL && server != NULL &&
131  service != NULL && user != NULL && password != NULL);
132  /* mysql and postgres aren't valid protocols on Mac OS X.
133  * So we use the security domain parameter to allow us to
134  * distinguish between these two.
135  */
136  // FIXME I'm not sure this works if a password was already in the keychain
137  // I may have to do a lookup first and if it exists, run some
138  // update function instead
139  status =
140  SecKeychainAddInternetPassword (NULL, /* keychain */
141  strlen(server), server, /* servername */
142  strlen(access_method),
143  access_method, /* securitydomain */
144  strlen(user), user, /* accountname */
145  strlen(service), service, /* path */
146  port, /* port */
147  kSecProtocolTypeAny, /* protocol */
148  kSecAuthenticationTypeDefault, /* auth type */
149  strlen(password),
150  password, /* passworddata */
151  itemRef );
152 
153  if ( status != noErr )
154  {
155  CFStringRef osx_resultstring = SecCopyErrorMessageString( status, NULL );
156  const gchar *resultstring =
157  CFStringGetCStringPtr(osx_resultstring,
158  GetApplicationTextEncoding());
159  PWARN ( "OS X keychain error: %s", resultstring );
160  PWARN ( "The user will be prompted for a password again next time." );
161  CFRelease ( osx_resultstring );
162  }
163 #endif /* HAVE_OSX_KEYCHAIN */
164 }
165 
166 
167 gboolean gnc_keyring_get_password ( GtkWidget *parent,
168  const gchar *access_method,
169  const gchar *server,
170  guint32 port,
171  const gchar *service,
172  gchar **user,
173  gchar **password)
174 {
175  gboolean password_found = FALSE;
176  gchar *db_path, *heading;
177 #ifdef HAVE_LIBSECRET
178  GError* error = NULL;
179  char* libsecret_password;
180 #elif HAVE_GNOME_KEYRING
181  GnomeKeyringResult gkr_result;
182  GList *found_list = NULL;
183  GnomeKeyringNetworkPasswordData *found;
184 #endif
185 #ifdef HAVE_OSX_KEYCHAIN
186  void *password_data;
187  UInt32 password_length;
188  OSStatus status;
189 #endif
190 
191  g_return_val_if_fail (user != NULL, FALSE);
192  g_return_val_if_fail (password != NULL, FALSE);
193 
194  *password = NULL;
195 
196 #ifdef HAVE_LIBSECRET
197  /* Workaround for https://bugs.gnucash.org/show_bug.cgi?id=746873
198  * and by extension for https://bugs.gnucash.org/show_bug.cgi?id=748625
199  * Store a dummy password and delete it again. This forces libsecret
200  * to open the keychain, where only a call to secret_password_lookup_sync
201  * sometimes fails to do so. More details can be found in the bug reports
202  * referenced above. */
203  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
204  "Dummy password", "dummy", NULL, &error,
205  "protocol", PROJECT_NAME,
206  "server", PROJECT_NAME,
207  "user", PROJECT_NAME,
208  NULL);
209  secret_password_clear_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
210  "protocol", PROJECT_NAME,
211  "server", PROJECT_NAME,
212  "user", PROJECT_NAME,
213  NULL);
214 
215  /* Note: only use the port attribute if it was set by the user. */
216  if (port == 0)
217  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
218  "protocol", access_method,
219  "server", server,
220  "user", *user,
221  NULL);
222  else
223  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
224  "protocol", access_method,
225  "server", server,
226  "port", port,
227  "user", *user,
228  NULL);
229 
230  if (libsecret_password != NULL) {
231  *password = g_strdup (libsecret_password);
232  secret_password_free (libsecret_password);
233  return TRUE;
234  }
235 
236  /* No password found yet. Perhaps it was written with a port equal to 0.
237  * Gnucash versions prior to 2.6.7 did this unfortunately... */
238  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
239  "protocol", access_method,
240  "server", server,
241  "port", 0,
242  "user", *user,
243  NULL);
244 
245  if (libsecret_password != NULL) {
246  *password = g_strdup (libsecret_password);
247  secret_password_free (libsecret_password);
248 
249  /* Ok, got an password with 0 port.
250  Store a copy in a more recent gnucash style. */
251  gnc_keyring_set_password(access_method, server, port, service, *user, *password);
252  return TRUE;
253  }
254 
255  /* No password was found while querying libsecret using the gnucash schema,
256  Look for a password stored via gnome-keyring instead */
257  if (port == 0)
258  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_COMPAT_NETWORK, NULL, &error,
259  "protocol", access_method,
260  "server", server,
261  "object", service,
262  "user", *user,
263  NULL);
264  else
265  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_COMPAT_NETWORK, NULL, &error,
266  "protocol", access_method,
267  "server", server,
268  "port", port,
269  "object", service,
270  "user", *user,
271  NULL);
272 
273  if (libsecret_password != NULL) {
274  *password = g_strdup (libsecret_password);
275  secret_password_free (libsecret_password);
276 
277  /* Ok, got an old gnome-keyring password.
278  * Store a copy of it in a libsecret compatible format. */
279  gnc_keyring_set_password(access_method, server, port, service, *user, *password);
280  return TRUE;
281  }
282 
283  /* Something went wrong while attempting to access libsecret
284  * Log the error message and carry on... */
285  if (error != NULL) {
286  PWARN ("libsecret access failed: %s.", error->message);
287  g_error_free(error);
288  }
289 
290 #elif HAVE_GNOME_KEYRING
291  gkr_result = gnome_keyring_find_network_password_sync
292  ( *user, NULL, server, service,
293  access_method, NULL, port, &found_list );
294 
295  if (gkr_result == GNOME_KEYRING_RESULT_OK)
296  {
297  found = (GnomeKeyringNetworkPasswordData *) found_list->data;
298  if (found->password)
299  *password = g_strdup(found->password);
300  gnome_keyring_network_password_list_free(found_list);
301  return TRUE;
302  }
303 
304  /* Something went wrong while attempting to access libsecret
305  * Log the error message and carry on... */
306  PWARN ("Gnome-keyring access failed: %s.",
307  gnome_keyring_result_to_message(gkr_result));
308  gnome_keyring_network_password_list_free(found_list);
309 #endif /* HAVE_LIBSECRET or HAVE_GNOME_KEYRING */
310 
311 #ifdef HAVE_OSX_KEYCHAIN
312  /* mysql and postgres aren't valid protocols on Mac OS X.
313  * So we use the security domain parameter to allow us to
314  * distinguish between these two.
315  */
316  if (*user != NULL)
317  {
318  status = SecKeychainFindInternetPassword( NULL,
319  strlen(server), server,
320  strlen(access_method), access_method,
321  strlen(*user), *user,
322  strlen(service), service,
323  port,
324  kSecProtocolTypeAny,
325  kSecAuthenticationTypeDefault,
326  &password_length, &password_data,
327  NULL);
328 
329  if ( status == noErr )
330  {
331  *password = g_strndup(password_data, password_length);
332  SecKeychainItemFreeContent(NULL, password_data);
333  return TRUE;
334  }
335  else
336  {
337  CFStringRef osx_resultstring = SecCopyErrorMessageString( status, NULL );
338  const gchar *resultstring = CFStringGetCStringPtr(osx_resultstring,
339  GetApplicationTextEncoding());
340  PWARN ( "OS X keychain error: %s", resultstring );
341  CFRelease ( osx_resultstring );
342  }
343  }
344 #endif /* HAVE_OSX_KEYCHAIN */
345 
346  /* If we got here, either no proper password store is
347  * available on this system, or we couldn't retrieve
348  * a password from it. In both cases, just ask the user
349  * to enter one
350  */
351 
352  if ( port == 0 )
353  db_path = g_strdup_printf ( "%s://%s/%s", access_method, server, service );
354  else
355  db_path = g_strdup_printf ( "%s://%s:%d/%s", access_method, server, port, service );
356  heading = g_strdup_printf ( /* Translators: %s is a path to a database or any other url,
357  like mysql://user@server.somewhere/somedb, https://www.somequotes.com/thequotes */
358  _("Enter a user name and password to connect to: %s"),
359  db_path );
360 
361  password_found = gnc_get_username_password ( parent, heading,
362  *user, NULL,
363  user, password );
364  g_free ( db_path );
365  g_free ( heading );
366 
367  if ( password_found )
368  {
369  /* User entered new user/password information
370  * Let's try to add it to a password store.
371  */
372  gchar *newuser = g_strdup( *user );
373  gchar *newpassword = g_strdup( *password );
374  gnc_keyring_set_password ( access_method,
375  server,
376  port,
377  service,
378  newuser,
379  newpassword );
380  g_free ( newuser );
381  g_free ( newpassword );
382  }
383 
384  return password_found;
385 }
Functions to save and retrieve passwords.
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
void gnc_keyring_set_password(const gchar *access_method, const gchar *server, guint32 port, const gchar *service, const gchar *user, const gchar *password)
Attempt to store a password in some trusted keystore.
Definition: gnc-keyring.c:68
gboolean gnc_keyring_get_password(GtkWidget *parent, const gchar *access_method, const gchar *server, guint32 port, const gchar *service, gchar **user, gchar **password)
Attempt to retrieve a password to connect to a remote service.
Definition: gnc-keyring.c:167