GnuCash c935c2f+
Loading...
Searching...
No Matches
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. */
45G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_GUI;
46
47#ifdef HAVE_LIBSECRET
48const SecretSchema* gnucash_get_secret_schema(void) G_GNUC_CONST;
49const 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
68void 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
167gboolean 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.
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.
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250