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

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

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

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