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 #ifdef HAVE_OSX_KEYCHAIN
69 /* Log an OSStatus from the macOS keychain as a readable message. */
70 static void
71 gnc_log_osx_keychain_error (const gchar *context, OSStatus status)
72 {
73  CFStringRef osx_resultstring = SecCopyErrorMessageString (status, NULL);
74  char resultstring[256] = "";
75  if (osx_resultstring)
76  {
77  CFStringGetCString (osx_resultstring, resultstring,
78  sizeof resultstring, kCFStringEncodingUTF8);
79  CFRelease (osx_resultstring);
80  }
81  PWARN ("OS X keychain error in %s (status %d): %s",
82  context, (int) status, resultstring);
83 }
84 #endif
85 
86 void gnc_keyring_set_password (const gchar *access_method,
87  const gchar *server,
88  guint32 port,
89  const gchar *service,
90  const gchar *user,
91  const gchar* password)
92 {
93 #ifdef HAVE_LIBSECRET
94  GError* error = NULL;
95  gchar* label = NULL;
96 
97  g_return_if_fail(access_method != NULL && server != NULL &&
98  service != NULL && user != NULL && password != NULL);
99 
100  label = g_strdup_printf("GnuCash password for %s://%s@%s", access_method, user, server);
101 
102  if (port == 0)
103  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
104  label, password, NULL, &error,
105  "protocol", access_method,
106  "server", server,
107  "user", user,
108  NULL);
109  else
110  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
111  label, password, NULL, &error,
112  "protocol", access_method,
113  "server", server,
114  "port", port,
115  "user", user,
116  NULL);
117 
118  g_free(label);
119 
120  if (error != NULL)
121  {
122  PWARN ("libsecret error: %s", error->message);
123  PWARN ("The user will be prompted for a password again next time.");
124  g_error_free(error);
125  }
126 #elif HAVE_GNOME_KEYRING
127  GnomeKeyringResult gkr_result;
128  guint32 item_id = 0;
129 
130  g_return_if_fail(access_method != NULL && server != NULL &&
131  service != NULL && user != NULL && password != NULL);
132 
133  gkr_result = gnome_keyring_set_network_password_sync
134  (NULL, user, NULL, server, service,
135  access_method, NULL, port, password, &item_id);
136 
137  if (gkr_result != GNOME_KEYRING_RESULT_OK)
138  {
139  PWARN ("Gnome-keyring error: %s",
140  gnome_keyring_result_to_message(gkr_result));
141  PWARN ("The user will be prompted for a password again next time.");
142  }
143 #endif /* HAVE_GNOME_KEYRING */
144 #ifdef HAVE_OSX_KEYCHAIN
145  OSStatus status;
146  SecKeychainItemRef *itemRef = NULL;
147 
148  g_return_if_fail(access_method != NULL && server != NULL &&
149  service != NULL && user != NULL && password != NULL);
150  /* mysql and postgres aren't valid protocols on Mac OS X.
151  * So we use the security domain parameter to allow us to
152  * distinguish between these two.
153  */
154  // FIXME I'm not sure this works if a password was already in the keychain
155  // I may have to do a lookup first and if it exists, run some
156  // update function instead
157  status =
158  SecKeychainAddInternetPassword (NULL, /* keychain */
159  strlen(server), server, /* servername */
160  strlen(access_method),
161  access_method, /* securitydomain */
162  strlen(user), user, /* accountname */
163  strlen(service), service, /* path */
164  port, /* port */
165  kSecProtocolTypeAny, /* protocol */
166  kSecAuthenticationTypeDefault, /* auth type */
167  strlen(password),
168  password, /* passworddata */
169  itemRef );
170 
171  if ( status != noErr )
172  {
173  gnc_log_osx_keychain_error ("gnc_keyring_set_password", status);
174  PWARN ( "The user will be prompted for a password again next time." );
175  }
176 #endif /* HAVE_OSX_KEYCHAIN */
177 }
178 
179 
180 gboolean gnc_keyring_get_password ( GtkWidget *parent,
181  const gchar *access_method,
182  const gchar *server,
183  guint32 port,
184  const gchar *service,
185  gchar **user,
186  gchar **password)
187 {
188  gboolean password_found = FALSE;
189  gchar *db_path, *heading;
190 #ifdef HAVE_LIBSECRET
191  GError* error = NULL;
192  char* libsecret_password;
193 #elif HAVE_GNOME_KEYRING
194  GnomeKeyringResult gkr_result;
195  GList *found_list = NULL;
196  GnomeKeyringNetworkPasswordData *found;
197 #endif
198 #ifdef HAVE_OSX_KEYCHAIN
199  void *password_data;
200  UInt32 password_length;
201  OSStatus status;
202 #endif
203 
204  g_return_val_if_fail (user != NULL, FALSE);
205  g_return_val_if_fail (password != NULL, FALSE);
206 
207  *password = NULL;
208 
209 #ifdef HAVE_LIBSECRET
210  /* Workaround for https://bugs.gnucash.org/show_bug.cgi?id=746873
211  * and by extension for https://bugs.gnucash.org/show_bug.cgi?id=748625
212  * Store a dummy password and delete it again. This forces libsecret
213  * to open the keychain, where only a call to secret_password_lookup_sync
214  * sometimes fails to do so. More details can be found in the bug reports
215  * referenced above. */
216  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
217  "Dummy password", "dummy", NULL, &error,
218  "protocol", PROJECT_NAME,
219  "server", PROJECT_NAME,
220  "user", PROJECT_NAME,
221  NULL);
222  secret_password_clear_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
223  "protocol", PROJECT_NAME,
224  "server", PROJECT_NAME,
225  "user", PROJECT_NAME,
226  NULL);
227 
228  /* Note: only use the port attribute if it was set by the user. */
229  if (port == 0)
230  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
231  "protocol", access_method,
232  "server", server,
233  "user", *user,
234  NULL);
235  else
236  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
237  "protocol", access_method,
238  "server", server,
239  "port", port,
240  "user", *user,
241  NULL);
242 
243  if (libsecret_password != NULL) {
244  *password = g_strdup (libsecret_password);
245  secret_password_free (libsecret_password);
246  return TRUE;
247  }
248 
249  /* No password found yet. Perhaps it was written with a port equal to 0.
250  * Gnucash versions prior to 2.6.7 did this unfortunately... */
251  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
252  "protocol", access_method,
253  "server", server,
254  "port", 0,
255  "user", *user,
256  NULL);
257 
258  if (libsecret_password != NULL) {
259  *password = g_strdup (libsecret_password);
260  secret_password_free (libsecret_password);
261 
262  /* Ok, got an password with 0 port.
263  Store a copy in a more recent gnucash style. */
264  gnc_keyring_set_password(access_method, server, port, service, *user, *password);
265  return TRUE;
266  }
267 
268  /* No password was found while querying libsecret using the gnucash schema,
269  Look for a password stored via gnome-keyring instead */
270  if (port == 0)
271  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_COMPAT_NETWORK, NULL, &error,
272  "protocol", access_method,
273  "server", server,
274  "object", service,
275  "user", *user,
276  NULL);
277  else
278  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_COMPAT_NETWORK, NULL, &error,
279  "protocol", access_method,
280  "server", server,
281  "port", port,
282  "object", service,
283  "user", *user,
284  NULL);
285 
286  if (libsecret_password != NULL) {
287  *password = g_strdup (libsecret_password);
288  secret_password_free (libsecret_password);
289 
290  /* Ok, got an old gnome-keyring password.
291  * Store a copy of it in a libsecret compatible format. */
292  gnc_keyring_set_password(access_method, server, port, service, *user, *password);
293  return TRUE;
294  }
295 
296  /* Something went wrong while attempting to access libsecret
297  * Log the error message and carry on... */
298  if (error != NULL) {
299  PWARN ("libsecret access failed: %s.", error->message);
300  g_error_free(error);
301  }
302 
303 #elif HAVE_GNOME_KEYRING
304  gkr_result = gnome_keyring_find_network_password_sync
305  ( *user, NULL, server, service,
306  access_method, NULL, port, &found_list );
307 
308  if (gkr_result == GNOME_KEYRING_RESULT_OK)
309  {
310  found = (GnomeKeyringNetworkPasswordData *) found_list->data;
311  if (found->password)
312  *password = g_strdup(found->password);
313  gnome_keyring_network_password_list_free(found_list);
314  return TRUE;
315  }
316 
317  /* Something went wrong while attempting to access libsecret
318  * Log the error message and carry on... */
319  PWARN ("Gnome-keyring access failed: %s.",
320  gnome_keyring_result_to_message(gkr_result));
321  gnome_keyring_network_password_list_free(found_list);
322 #endif /* HAVE_LIBSECRET or HAVE_GNOME_KEYRING */
323 
324 #ifdef HAVE_OSX_KEYCHAIN
325  /* mysql and postgres aren't valid protocols on Mac OS X.
326  * So we use the security domain parameter to allow us to
327  * distinguish between these two.
328  */
329  if (*user != NULL)
330  {
331  status = SecKeychainFindInternetPassword( NULL,
332  strlen(server), server,
333  strlen(access_method), access_method,
334  strlen(*user), *user,
335  strlen(service), service,
336  port,
337  kSecProtocolTypeAny,
338  kSecAuthenticationTypeDefault,
339  &password_length, &password_data,
340  NULL);
341 
342  if ( status == noErr )
343  {
344  *password = g_strndup(password_data, password_length);
345  SecKeychainItemFreeContent(NULL, password_data);
346  return TRUE;
347  }
348  else
349  {
350  gnc_log_osx_keychain_error ("gnc_keyring_get_password", status);
351  }
352  }
353 #endif /* HAVE_OSX_KEYCHAIN */
354 
355  /* If we got here, either no proper password store is
356  * available on this system, or we couldn't retrieve
357  * a password from it. In both cases, just ask the user
358  * to enter one
359  */
360 
361  if ( port == 0 )
362  db_path = g_strdup_printf ( "%s://%s/%s", access_method, server, service );
363  else
364  db_path = g_strdup_printf ( "%s://%s:%d/%s", access_method, server, port, service );
365  heading = g_strdup_printf ( /* Translators: %s is a path to a database or any other url,
366  like mysql://user@server.somewhere/somedb, https://www.somequotes.com/thequotes */
367  _("Enter a user name and password to connect to: %s"),
368  db_path );
369 
370  password_found = gnc_get_username_password ( parent, heading,
371  *user, NULL,
372  user, password );
373  g_free ( db_path );
374  g_free ( heading );
375 
376  if ( password_found )
377  {
378  /* User entered new user/password information
379  * Let's try to add it to a password store.
380  */
381  gchar *newuser = g_strdup( *user );
382  gchar *newpassword = g_strdup( *password );
383  gnc_keyring_set_password ( access_method,
384  server,
385  port,
386  service,
387  newuser,
388  newpassword );
389  g_free ( newuser );
390  g_free ( newpassword );
391  }
392 
393  return password_found;
394 }
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:86
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:180