GnuCash  4.12-404-geb24099a91
Files | Functions
GUIUtility

Files

file  gnc-keyring.h
 Functions to save and retrieve passwords.
 

Functions

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. More...
 
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. More...
 

Detailed Description

Function Documentation

◆ gnc_keyring_get_password()

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.

This is deliberately generic: the remote service can be a database, website, anything.

If a trusted keystore infrastructure is found (such as the Gnome's keyring or Mac OS X' keychain) this infrastructure will be queried first.

If no such infrastructure is available or the query didn't return a valid result, the user will be prompted for his password.

Warning
When the user is prompted for a password, he can also change the username. So whenever you call this function, read both the username and password values before you continue !
Parameters
parentUsed to transition from in case the user is prompted for a password.
access_methodService type the user attempts to access. Can things like 'mysql', 'postgres' and so on.
serverServer the user wishes to connect to.
portPort the service listens on. If set to 0, it will be ignored in the search for a password.
serviceThe service the user wishes to access on the server. This can be a database name or a path.
userThe user name to access the service. Remember, although you pass it to search for the password, it can have changed when the function returns.
passwordThe password to access the service.
Returns
a boolean indicating whether or not a valid password has been retrieved. The function will return FALSE when the user explicitly cancels the password dialog or if it wasn't called properly. Otherwise it will return TRUE.

access_method, server, port, service and user will be the parameters passed to the trusted keystore (if available) to find the unique password for this service.

Definition at line 165 of file gnc-keyring.c.

172 {
173  gboolean password_found = FALSE;
174  gchar *db_path, *heading;
175 #ifdef HAVE_LIBSECRET
176  GError* error = NULL;
177  char* libsecret_password;
178 #elif HAVE_GNOME_KEYRING
179  GnomeKeyringResult gkr_result;
180  GList *found_list = NULL;
181  GnomeKeyringNetworkPasswordData *found;
182 #endif
183 #ifdef HAVE_OSX_KEYCHAIN
184  void *password_data;
185  UInt32 password_length;
186  OSStatus status;
187 #endif
188 
189  g_return_val_if_fail (user != NULL, FALSE);
190  g_return_val_if_fail (password != NULL, FALSE);
191 
192  *password = NULL;
193 
194 #ifdef HAVE_LIBSECRET
195  /* Workaround for https://bugs.gnucash.org/show_bug.cgi?id=746873
196  * and by extension for https://bugs.gnucash.org/show_bug.cgi?id=748625
197  * Store a dummy password and delete it again. This forces libsecret
198  * to open the keychain, where only a call to secret_password_lookup_sync
199  * sometimes fails to do so. More details can be found in the bug reports
200  * referenced above. */
201  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
202  "Dummy password", "dummy", NULL, &error,
203  "protocol", "gnucash",
204  "server", "gnucash",
205  "user", "gnucash",
206  NULL);
207  secret_password_clear_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
208  "protocol", "gnucash",
209  "server", "gnucash",
210  "user", "gnucash",
211  NULL);
212 
213  /* Note: only use the port attribute if it was set by the user. */
214  if (port == 0)
215  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
216  "protocol", access_method,
217  "server", server,
218  "user", *user,
219  NULL);
220  else
221  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
222  "protocol", access_method,
223  "server", server,
224  "port", port,
225  "user", *user,
226  NULL);
227 
228  if (libsecret_password != NULL) {
229  *password = g_strdup (libsecret_password);
230  secret_password_free (libsecret_password);
231  return TRUE;
232  }
233 
234  /* No password found yet. Perhaps it was written with a port equal to 0.
235  * Gnucash versions prior to 2.6.7 did this unfortunately... */
236  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_GNUCASH, NULL, &error,
237  "protocol", access_method,
238  "server", server,
239  "port", 0,
240  "user", *user,
241  NULL);
242 
243  if (libsecret_password != NULL) {
244  *password = g_strdup (libsecret_password);
245  secret_password_free (libsecret_password);
246 
247  /* Ok, got an password with 0 port.
248  Store a copy in a more recent gnucash style. */
249  gnc_keyring_set_password(access_method, server, port, service, *user, *password);
250  return TRUE;
251  }
252 
253  /* No password was found while querying libsecret using the gnucash schema,
254  Look for a password stored via gnome-keyring instead */
255  if (port == 0)
256  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_COMPAT_NETWORK, NULL, &error,
257  "protocol", access_method,
258  "server", server,
259  "object", service,
260  "user", *user,
261  NULL);
262  else
263  libsecret_password = secret_password_lookup_sync (SECRET_SCHEMA_COMPAT_NETWORK, NULL, &error,
264  "protocol", access_method,
265  "server", server,
266  "port", port,
267  "object", service,
268  "user", *user,
269  NULL);
270 
271  if (libsecret_password != NULL) {
272  *password = g_strdup (libsecret_password);
273  secret_password_free (libsecret_password);
274 
275  /* Ok, got an old gnome-keyring password.
276  * Store a copy of it in a libsecret compatible format. */
277  gnc_keyring_set_password(access_method, server, port, service, *user, *password);
278  return TRUE;
279  }
280 
281  /* Something went wrong while attempting to access libsecret
282  * Log the error message and carry on... */
283  if (error != NULL) {
284  PWARN ("libsecret access failed: %s.", error->message);
285  g_error_free(error);
286  }
287 
288 #elif HAVE_GNOME_KEYRING
289  gkr_result = gnome_keyring_find_network_password_sync
290  ( *user, NULL, server, service,
291  access_method, NULL, port, &found_list );
292 
293  if (gkr_result == GNOME_KEYRING_RESULT_OK)
294  {
295  found = (GnomeKeyringNetworkPasswordData *) found_list->data;
296  if (found->password)
297  *password = g_strdup(found->password);
298  gnome_keyring_network_password_list_free(found_list);
299  return TRUE;
300  }
301 
302  /* Something went wrong while attempting to access libsecret
303  * Log the error message and carry on... */
304  PWARN ("Gnome-keyring access failed: %s.",
305  gnome_keyring_result_to_message(gkr_result));
306  gnome_keyring_network_password_list_free(found_list);
307 #endif /* HAVE_LIBSECRET or HAVE_GNOME_KEYRING */
308 
309 #ifdef HAVE_OSX_KEYCHAIN
310  /* mysql and postgres aren't valid protocols on Mac OS X.
311  * So we use the security domain parameter to allow us to
312  * distinguish between these two.
313  */
314  if (*user != NULL)
315  {
316  status = SecKeychainFindInternetPassword( NULL,
317  strlen(server), server,
318  strlen(access_method), access_method,
319  strlen(*user), *user,
320  strlen(service), service,
321  port,
322  kSecProtocolTypeAny,
323  kSecAuthenticationTypeDefault,
324  &password_length, &password_data,
325  NULL);
326 
327  if ( status == noErr )
328  {
329  *password = g_strndup(password_data, password_length);
330  SecKeychainItemFreeContent(NULL, password_data);
331  return TRUE;
332  }
333  else
334  {
335  CFStringRef osx_resultstring = SecCopyErrorMessageString( status, NULL );
336  const gchar *resultstring = CFStringGetCStringPtr(osx_resultstring,
337  GetApplicationTextEncoding());
338  PWARN ( "OS X keychain error: %s", resultstring );
339  CFRelease ( osx_resultstring );
340  }
341  }
342 #endif /* HAVE_OSX_KEYCHAIN */
343 
344  /* If we got here, either no proper password store is
345  * available on this system, or we couldn't retrieve
346  * a password from it. In both cases, just ask the user
347  * to enter one
348  */
349 
350  if ( port == 0 )
351  db_path = g_strdup_printf ( "%s://%s/%s", access_method, server, service );
352  else
353  db_path = g_strdup_printf ( "%s://%s:%d/%s", access_method, server, port, service );
354  heading = g_strdup_printf ( /* Translators: %s is a path to a database or any other url,
355  like mysql://user@server.somewhere/somedb, https://www.somequotes.com/thequotes */
356  _("Enter a user name and password to connect to: %s"),
357  db_path );
358 
359  password_found = gnc_get_username_password ( parent, heading,
360  *user, NULL,
361  user, password );
362  g_free ( db_path );
363  g_free ( heading );
364 
365  if ( password_found )
366  {
367  /* User entered new user/password information
368  * Let's try to add it to a password store.
369  */
370  gchar *newuser = g_strdup( *user );
371  gchar *newpassword = g_strdup( *password );
372  gnc_keyring_set_password ( access_method,
373  server,
374  port,
375  service,
376  newuser,
377  newpassword );
378  g_free ( newuser );
379  g_free ( newpassword );
380  }
381 
382  return password_found;
383 }
#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:66

◆ gnc_keyring_set_password()

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.

At this point that can be Gnome's keyring or Mac OS X' keychain. If no such keystore is available, this function does nothing.

All the parameters passed (except for the password) will be used to create a unique key, so the password can later be retrieved again with the same parameters.

Parameters
access_methodService type the user attempts to access. Can things like 'mysql', 'postgres' and so on.
serverServer the user wishes to connect to.
portPort the service listens on. If set to 0, it will be ignored in the search for a password.
serviceThe service the user wishes to access on the server. This can be a database name or a path.
userThe username to access the service.
passwordThe password to access the service.

Definition at line 66 of file gnc-keyring.c.

72 {
73 #ifdef HAVE_LIBSECRET
74  GError* error = NULL;
75  gchar* label = NULL;
76 
77  g_return_if_fail(access_method != NULL && server != NULL &&
78  service != NULL && user != NULL && password != NULL);
79 
80  label = g_strdup_printf("GnuCash password for %s://%s@%s", access_method, user, server);
81 
82  if (port == 0)
83  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
84  label, password, NULL, &error,
85  "protocol", access_method,
86  "server", server,
87  "user", user,
88  NULL);
89  else
90  secret_password_store_sync (SECRET_SCHEMA_GNUCASH, SECRET_COLLECTION_DEFAULT,
91  label, password, NULL, &error,
92  "protocol", access_method,
93  "server", server,
94  "port", port,
95  "user", user,
96  NULL);
97 
98  g_free(label);
99 
100  if (error != NULL)
101  {
102  PWARN ("libsecret error: %s", error->message);
103  PWARN ("The user will be prompted for a password again next time.");
104  g_error_free(error);
105  }
106 #elif HAVE_GNOME_KEYRING
107  GnomeKeyringResult gkr_result;
108  guint32 item_id = 0;
109 
110  g_return_if_fail(access_method != NULL && server != NULL &&
111  service != NULL && user != NULL && password != NULL);
112 
113  gkr_result = gnome_keyring_set_network_password_sync
114  (NULL, user, NULL, server, service,
115  access_method, NULL, port, password, &item_id);
116 
117  if (gkr_result != GNOME_KEYRING_RESULT_OK)
118  {
119  PWARN ("Gnome-keyring error: %s",
120  gnome_keyring_result_to_message(gkr_result));
121  PWARN ("The user will be prompted for a password again next time.");
122  }
123 #endif /* HAVE_GNOME_KEYRING */
124 #ifdef HAVE_OSX_KEYCHAIN
125  OSStatus status;
126  SecKeychainItemRef *itemRef = NULL;
127 
128  g_return_if_fail(access_method != NULL && server != NULL &&
129  service != NULL && user != NULL && password != NULL);
130  /* mysql and postgres aren't valid protocols on Mac OS X.
131  * So we use the security domain parameter to allow us to
132  * distinguish between these two.
133  */
134  // FIXME I'm not sure this works if a password was already in the keychain
135  // I may have to do a lookup first and if it exists, run some
136  // update function instead
137  status =
138  SecKeychainAddInternetPassword (NULL, /* keychain */
139  strlen(server), server, /* servername */
140  strlen(access_method),
141  access_method, /* securitydomain */
142  strlen(user), user, /* accountname */
143  strlen(service), service, /* path */
144  port, /* port */
145  kSecProtocolTypeAny, /* protocol */
146  kSecAuthenticationTypeDefault, /* auth type */
147  strlen(password),
148  password, /* passworddata */
149  itemRef );
150 
151  if ( status != noErr )
152  {
153  CFStringRef osx_resultstring = SecCopyErrorMessageString( status, NULL );
154  const gchar *resultstring =
155  CFStringGetCStringPtr(osx_resultstring,
156  GetApplicationTextEncoding());
157  PWARN ( "OS X keychain error: %s", resultstring );
158  PWARN ( "The user will be prompted for a password again next time." );
159  CFRelease ( osx_resultstring );
160  }
161 #endif /* HAVE_OSX_KEYCHAIN */
162 }
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250