GnuCash  5.6-150-g038405b370+
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 180 of file gnc-keyring.c.

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 }
#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

◆ 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 86 of file gnc-keyring.c.

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 }
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250