/src/glib-2.88.3/gio/gdbusauthmechanismsha1.c
Line | Count | Source |
1 | | /* GDBus - GLib D-Bus Library |
2 | | * |
3 | | * Copyright (C) 2008-2010 Red Hat, Inc. |
4 | | * |
5 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
6 | | * |
7 | | * This library is free software; you can redistribute it and/or |
8 | | * modify it under the terms of the GNU Lesser General Public |
9 | | * License as published by the Free Software Foundation; either |
10 | | * version 2.1 of the License, or (at your option) any later version. |
11 | | * |
12 | | * This library is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | | * Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General |
18 | | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
19 | | * |
20 | | * Author: David Zeuthen <davidz@redhat.com> |
21 | | */ |
22 | | |
23 | | #include "config.h" |
24 | | |
25 | | #include <string.h> |
26 | | #include <fcntl.h> |
27 | | #include <errno.h> |
28 | | #include <sys/types.h> |
29 | | |
30 | | #include <glib/gstdio.h> |
31 | | |
32 | | #ifdef G_OS_UNIX |
33 | | #include <unistd.h> |
34 | | #endif |
35 | | #ifdef G_OS_WIN32 |
36 | | #include <io.h> |
37 | | #include "gwin32sid.h" |
38 | | #endif |
39 | | |
40 | | #ifndef O_CLOEXEC |
41 | | #define O_CLOEXEC 0 |
42 | | #endif |
43 | | |
44 | | #include "gdbusauthmechanismsha1.h" |
45 | | #include "gcredentials.h" |
46 | | #include "gdbuserror.h" |
47 | | #include "glocalfileinfo.h" |
48 | | #include "gioenumtypes.h" |
49 | | #include "gioerror.h" |
50 | | #include "gdbusprivate.h" |
51 | | #include "glib-private.h" |
52 | | |
53 | | #include "glibintl.h" |
54 | | |
55 | | /* |
56 | | * Arbitrary timeouts for keys in the keyring. |
57 | | * For interoperability, these match the reference implementation, libdbus. |
58 | | * To make them easier to compare, their names also match libdbus |
59 | | * (see dbus/dbus-keyring.c). |
60 | | */ |
61 | | |
62 | | /* |
63 | | * Maximum age of a key before we create a new key to use in challenges: |
64 | | * 5 minutes. |
65 | | */ |
66 | 0 | #define NEW_KEY_TIMEOUT_SECONDS (60*5) |
67 | | |
68 | | /* |
69 | | * Time before we drop a key from the keyring: 7 minutes. |
70 | | * Authentication will succeed if it takes less than |
71 | | * EXPIRE_KEYS_TIMEOUT_SECONDS - NEW_KEY_TIMEOUT_SECONDS (2 minutes) |
72 | | * to complete. |
73 | | * The spec says "delete any cookies that are old (the timeout can be |
74 | | * fairly short)". |
75 | | */ |
76 | 0 | #define EXPIRE_KEYS_TIMEOUT_SECONDS (NEW_KEY_TIMEOUT_SECONDS + (60*2)) |
77 | | |
78 | | /* |
79 | | * Maximum amount of time a key can be in the future due to clock skew |
80 | | * with a shared home directory: 5 minutes. |
81 | | * The spec says "a reasonable time in the future". |
82 | | */ |
83 | 0 | #define MAX_TIME_TRAVEL_SECONDS (60*5) |
84 | | |
85 | | |
86 | | struct _GDBusAuthMechanismSha1Private |
87 | | { |
88 | | gboolean is_client; |
89 | | gboolean is_server; |
90 | | GDBusAuthMechanismState state; |
91 | | gchar *reject_reason; /* non-NULL iff (state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED) */ |
92 | | |
93 | | /* used on the client side */ |
94 | | gchar *to_send; |
95 | | |
96 | | /* used on the server side */ |
97 | | gchar *cookie; |
98 | | gchar *server_challenge; |
99 | | }; |
100 | | |
101 | | static gint mechanism_get_priority (void); |
102 | | static const gchar *mechanism_get_name (void); |
103 | | |
104 | | static gboolean mechanism_is_supported (GDBusAuthMechanism *mechanism); |
105 | | static gchar *mechanism_encode_data (GDBusAuthMechanism *mechanism, |
106 | | const gchar *data, |
107 | | gsize data_len, |
108 | | gsize *out_data_len); |
109 | | static gchar *mechanism_decode_data (GDBusAuthMechanism *mechanism, |
110 | | const gchar *data, |
111 | | gsize data_len, |
112 | | gsize *out_data_len); |
113 | | static GDBusAuthMechanismState mechanism_server_get_state (GDBusAuthMechanism *mechanism); |
114 | | static void mechanism_server_initiate (GDBusAuthMechanism *mechanism, |
115 | | const gchar *initial_response, |
116 | | gsize initial_response_len); |
117 | | static void mechanism_server_data_receive (GDBusAuthMechanism *mechanism, |
118 | | const gchar *data, |
119 | | gsize data_len); |
120 | | static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, |
121 | | gsize *out_data_len); |
122 | | static gchar *mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism); |
123 | | static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); |
124 | | static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); |
125 | | static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, |
126 | | GDBusConnectionFlags conn_flags, |
127 | | gsize *out_initial_response_len); |
128 | | static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism, |
129 | | const gchar *data, |
130 | | gsize data_len); |
131 | | static gchar *mechanism_client_data_send (GDBusAuthMechanism *mechanism, |
132 | | gsize *out_data_len); |
133 | | static void mechanism_client_shutdown (GDBusAuthMechanism *mechanism); |
134 | | |
135 | | /* ---------------------------------------------------------------------------------------------------- */ |
136 | | |
137 | 0 | G_DEFINE_TYPE_WITH_PRIVATE (GDBusAuthMechanismSha1, _g_dbus_auth_mechanism_sha1, G_TYPE_DBUS_AUTH_MECHANISM) |
138 | 0 |
|
139 | 0 | /* ---------------------------------------------------------------------------------------------------- */ |
140 | 0 |
|
141 | 0 | static void |
142 | 0 | _g_dbus_auth_mechanism_sha1_finalize (GObject *object) |
143 | 0 | { |
144 | 0 | GDBusAuthMechanismSha1 *mechanism = G_DBUS_AUTH_MECHANISM_SHA1 (object); |
145 | |
|
146 | 0 | g_free (mechanism->priv->reject_reason); |
147 | 0 | g_free (mechanism->priv->to_send); |
148 | |
|
149 | 0 | g_free (mechanism->priv->cookie); |
150 | 0 | g_free (mechanism->priv->server_challenge); |
151 | |
|
152 | 0 | if (G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize != NULL) |
153 | 0 | G_OBJECT_CLASS (_g_dbus_auth_mechanism_sha1_parent_class)->finalize (object); |
154 | 0 | } |
155 | | |
156 | | static void |
157 | | _g_dbus_auth_mechanism_sha1_class_init (GDBusAuthMechanismSha1Class *klass) |
158 | 0 | { |
159 | 0 | GObjectClass *gobject_class; |
160 | 0 | GDBusAuthMechanismClass *mechanism_class; |
161 | |
|
162 | 0 | gobject_class = G_OBJECT_CLASS (klass); |
163 | 0 | gobject_class->finalize = _g_dbus_auth_mechanism_sha1_finalize; |
164 | |
|
165 | 0 | mechanism_class = G_DBUS_AUTH_MECHANISM_CLASS (klass); |
166 | 0 | mechanism_class->get_priority = mechanism_get_priority; |
167 | 0 | mechanism_class->get_name = mechanism_get_name; |
168 | 0 | mechanism_class->is_supported = mechanism_is_supported; |
169 | 0 | mechanism_class->encode_data = mechanism_encode_data; |
170 | 0 | mechanism_class->decode_data = mechanism_decode_data; |
171 | 0 | mechanism_class->server_get_state = mechanism_server_get_state; |
172 | 0 | mechanism_class->server_initiate = mechanism_server_initiate; |
173 | 0 | mechanism_class->server_data_receive = mechanism_server_data_receive; |
174 | 0 | mechanism_class->server_data_send = mechanism_server_data_send; |
175 | 0 | mechanism_class->server_get_reject_reason = mechanism_server_or_client_get_reject_reason; |
176 | 0 | mechanism_class->server_shutdown = mechanism_server_shutdown; |
177 | 0 | mechanism_class->client_get_state = mechanism_client_get_state; |
178 | 0 | mechanism_class->client_initiate = mechanism_client_initiate; |
179 | 0 | mechanism_class->client_data_receive = mechanism_client_data_receive; |
180 | 0 | mechanism_class->client_data_send = mechanism_client_data_send; |
181 | 0 | mechanism_class->client_get_reject_reason = mechanism_server_or_client_get_reject_reason; |
182 | 0 | mechanism_class->client_shutdown = mechanism_client_shutdown; |
183 | 0 | } |
184 | | |
185 | | static void |
186 | | _g_dbus_auth_mechanism_sha1_init (GDBusAuthMechanismSha1 *mechanism) |
187 | 0 | { |
188 | 0 | mechanism->priv = _g_dbus_auth_mechanism_sha1_get_instance_private (mechanism); |
189 | 0 | } |
190 | | |
191 | | /* ---------------------------------------------------------------------------------------------------- */ |
192 | | |
193 | | static gint |
194 | | mechanism_get_priority (void) |
195 | 0 | { |
196 | 0 | return 0; |
197 | 0 | } |
198 | | |
199 | | static const gchar * |
200 | | mechanism_get_name (void) |
201 | 0 | { |
202 | 0 | return "DBUS_COOKIE_SHA1"; |
203 | 0 | } |
204 | | |
205 | | static gboolean |
206 | | mechanism_is_supported (GDBusAuthMechanism *mechanism) |
207 | 0 | { |
208 | 0 | g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), FALSE); |
209 | 0 | return TRUE; |
210 | 0 | } |
211 | | |
212 | | static gchar * |
213 | | mechanism_encode_data (GDBusAuthMechanism *mechanism, |
214 | | const gchar *data, |
215 | | gsize data_len, |
216 | | gsize *out_data_len) |
217 | 0 | { |
218 | 0 | return NULL; |
219 | 0 | } |
220 | | |
221 | | |
222 | | static gchar * |
223 | | mechanism_decode_data (GDBusAuthMechanism *mechanism, |
224 | | const gchar *data, |
225 | | gsize data_len, |
226 | | gsize *out_data_len) |
227 | 0 | { |
228 | 0 | return NULL; |
229 | 0 | } |
230 | | |
231 | | /* ---------------------------------------------------------------------------------------------------- */ |
232 | | |
233 | | static gint |
234 | | random_ascii (void) |
235 | 0 | { |
236 | 0 | gint ret; |
237 | 0 | ret = g_random_int_range (0, 60); |
238 | 0 | if (ret < 25) |
239 | 0 | ret += 'A'; |
240 | 0 | else if (ret < 50) |
241 | 0 | ret += 'a' - 25; |
242 | 0 | else |
243 | 0 | ret += '0' - 50; |
244 | 0 | return ret; |
245 | 0 | } |
246 | | |
247 | | static gchar * |
248 | | random_ascii_string (guint len) |
249 | 0 | { |
250 | 0 | GString *challenge; |
251 | 0 | guint n; |
252 | |
|
253 | 0 | challenge = g_string_new (NULL); |
254 | 0 | for (n = 0; n < len; n++) |
255 | 0 | g_string_append_c (challenge, random_ascii ()); |
256 | 0 | return g_string_free (challenge, FALSE); |
257 | 0 | } |
258 | | |
259 | | static gchar * |
260 | | random_blob (guint len) |
261 | 0 | { |
262 | 0 | GString *challenge; |
263 | 0 | guint n; |
264 | |
|
265 | 0 | challenge = g_string_new (NULL); |
266 | 0 | for (n = 0; n < len; n++) |
267 | 0 | g_string_append_c (challenge, g_random_int_range (0, 256)); |
268 | 0 | return g_string_free (challenge, FALSE); |
269 | 0 | } |
270 | | |
271 | | /* ---------------------------------------------------------------------------------------------------- */ |
272 | | |
273 | | /* ensure keyring dir exists and permissions are correct */ |
274 | | static gchar * |
275 | | ensure_keyring_directory (GError **error) |
276 | 0 | { |
277 | 0 | gchar *path; |
278 | 0 | const gchar *e; |
279 | 0 | gboolean is_setuid; |
280 | 0 | #ifdef G_OS_UNIX |
281 | 0 | struct stat statbuf; |
282 | 0 | #endif |
283 | |
|
284 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
285 | | |
286 | 0 | e = g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR"); |
287 | 0 | if (e != NULL) |
288 | 0 | { |
289 | 0 | path = g_strdup (e); |
290 | 0 | } |
291 | 0 | else |
292 | 0 | { |
293 | 0 | path = g_build_filename (g_get_home_dir (), |
294 | 0 | ".dbus-keyrings", |
295 | 0 | NULL); |
296 | 0 | } |
297 | |
|
298 | 0 | #ifdef G_OS_UNIX |
299 | 0 | if (stat (path, &statbuf) != 0) |
300 | 0 | { |
301 | 0 | int errsv = errno; |
302 | |
|
303 | 0 | if (errsv != ENOENT) |
304 | 0 | { |
305 | 0 | g_set_error (error, |
306 | 0 | G_IO_ERROR, |
307 | 0 | g_io_error_from_errno (errsv), |
308 | 0 | _("Error when getting information for directory ā%sā: %s"), |
309 | 0 | path, |
310 | 0 | g_strerror (errsv)); |
311 | 0 | g_clear_pointer (&path, g_free); |
312 | 0 | return NULL; |
313 | 0 | } |
314 | 0 | } |
315 | 0 | else if (S_ISDIR (statbuf.st_mode)) |
316 | 0 | { |
317 | 0 | if (g_getenv ("G_DBUS_COOKIE_SHA1_KEYRING_DIR_IGNORE_PERMISSION") == NULL && |
318 | 0 | (statbuf.st_mode & 0777) != 0700) |
319 | 0 | { |
320 | 0 | g_set_error (error, |
321 | 0 | G_IO_ERROR, |
322 | 0 | G_IO_ERROR_FAILED, |
323 | 0 | _("Permissions on directory ā%sā are malformed. Expected mode 0700, got 0%o"), |
324 | 0 | path, |
325 | 0 | (guint) (statbuf.st_mode & 0777)); |
326 | 0 | g_clear_pointer (&path, g_free); |
327 | 0 | return NULL; |
328 | 0 | } |
329 | | |
330 | 0 | return g_steal_pointer (&path); |
331 | 0 | } |
332 | | #else /* if !G_OS_UNIX */ |
333 | | /* On non-Unix platforms, check that it exists as a directory, but donāt do |
334 | | * permissions checks at the moment. */ |
335 | | if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) |
336 | | { |
337 | | #ifdef __GNUC__ |
338 | | #pragma GCC diagnostic push |
339 | | #pragma GCC diagnostic warning "-Wcpp" |
340 | | #warning Please implement permission checking on this non-UNIX platform |
341 | | #pragma GCC diagnostic pop |
342 | | #endif /* __GNUC__ */ |
343 | | return g_steal_pointer (&path); |
344 | | } |
345 | | #endif /* if !G_OS_UNIX */ |
346 | | |
347 | | /* Only create the directory if not running as setuid */ |
348 | 0 | is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) (); |
349 | 0 | if (!is_setuid && |
350 | 0 | g_mkdir_with_parents (path, 0700) != 0) |
351 | 0 | { |
352 | 0 | int errsv = errno; |
353 | 0 | g_set_error (error, |
354 | 0 | G_IO_ERROR, |
355 | 0 | g_io_error_from_errno (errsv), |
356 | 0 | _("Error creating directory ā%sā: %s"), |
357 | 0 | path, |
358 | 0 | g_strerror (errsv)); |
359 | 0 | g_clear_pointer (&path, g_free); |
360 | 0 | return NULL; |
361 | 0 | } |
362 | 0 | else if (is_setuid) |
363 | 0 | { |
364 | 0 | g_set_error (error, |
365 | 0 | G_IO_ERROR, |
366 | 0 | G_IO_ERROR_PERMISSION_DENIED, |
367 | 0 | _("Error creating directory ā%sā: %s"), |
368 | 0 | path, |
369 | 0 | _("Operation not supported")); |
370 | 0 | g_clear_pointer (&path, g_free); |
371 | 0 | return NULL; |
372 | 0 | } |
373 | | |
374 | 0 | return g_steal_pointer (&path); |
375 | 0 | } |
376 | | |
377 | | /* ---------------------------------------------------------------------------------------------------- */ |
378 | | |
379 | | /* looks up an entry in the keyring */ |
380 | | static gchar * |
381 | | keyring_lookup_entry (const gchar *cookie_context, |
382 | | gint cookie_id, |
383 | | GError **error) |
384 | 0 | { |
385 | 0 | gchar *ret; |
386 | 0 | gchar *keyring_dir; |
387 | 0 | gchar *contents; |
388 | 0 | gchar *path; |
389 | 0 | guint n; |
390 | 0 | gchar **lines; |
391 | |
|
392 | 0 | g_return_val_if_fail (cookie_context != NULL, NULL); |
393 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
394 | | |
395 | 0 | ret = NULL; |
396 | 0 | path = NULL; |
397 | 0 | contents = NULL; |
398 | 0 | lines = NULL; |
399 | |
|
400 | 0 | keyring_dir = ensure_keyring_directory (error); |
401 | 0 | if (keyring_dir == NULL) |
402 | 0 | goto out; |
403 | | |
404 | 0 | path = g_build_filename (keyring_dir, cookie_context, NULL); |
405 | |
|
406 | 0 | if (!g_file_get_contents (path, |
407 | 0 | &contents, |
408 | 0 | NULL, |
409 | 0 | error)) |
410 | 0 | { |
411 | 0 | g_prefix_error (error, |
412 | 0 | _("Error opening keyring ā%sā for reading: "), |
413 | 0 | path); |
414 | 0 | goto out; |
415 | 0 | } |
416 | 0 | g_assert (contents != NULL); |
417 | | |
418 | 0 | lines = g_strsplit (contents, "\n", 0); |
419 | 0 | for (n = 0; lines[n] != NULL; n++) |
420 | 0 | { |
421 | 0 | const gchar *line = lines[n]; |
422 | 0 | gchar **tokens; |
423 | 0 | gchar *endp; |
424 | 0 | gint line_id; |
425 | |
|
426 | 0 | if (line[0] == '\0') |
427 | 0 | continue; |
428 | | |
429 | 0 | tokens = g_strsplit (line, " ", 0); |
430 | 0 | if (g_strv_length (tokens) != 3) |
431 | 0 | { |
432 | 0 | g_set_error (error, |
433 | 0 | G_IO_ERROR, |
434 | 0 | G_IO_ERROR_FAILED, |
435 | 0 | _("Line %d of the keyring at ā%sā with content ā%sā is malformed"), |
436 | 0 | n + 1, |
437 | 0 | path, |
438 | 0 | line); |
439 | 0 | g_strfreev (tokens); |
440 | 0 | goto out; |
441 | 0 | } |
442 | | |
443 | 0 | line_id = g_ascii_strtoll (tokens[0], &endp, 10); |
444 | 0 | if (*endp != '\0') |
445 | 0 | { |
446 | 0 | g_set_error (error, |
447 | 0 | G_IO_ERROR, |
448 | 0 | G_IO_ERROR_FAILED, |
449 | 0 | _("First token of line %d of the keyring at ā%sā with content ā%sā is malformed"), |
450 | 0 | n + 1, |
451 | 0 | path, |
452 | 0 | line); |
453 | 0 | g_strfreev (tokens); |
454 | 0 | goto out; |
455 | 0 | } |
456 | | |
457 | 0 | (void)g_ascii_strtoll (tokens[1], &endp, 10); /* do not care what the timestamp is */ |
458 | 0 | if (*endp != '\0') |
459 | 0 | { |
460 | 0 | g_set_error (error, |
461 | 0 | G_IO_ERROR, |
462 | 0 | G_IO_ERROR_FAILED, |
463 | 0 | _("Second token of line %d of the keyring at ā%sā with content ā%sā is malformed"), |
464 | 0 | n + 1, |
465 | 0 | path, |
466 | 0 | line); |
467 | 0 | g_strfreev (tokens); |
468 | 0 | goto out; |
469 | 0 | } |
470 | | |
471 | 0 | if (line_id == cookie_id) |
472 | 0 | { |
473 | | /* YAY, success */ |
474 | 0 | ret = tokens[2]; /* steal pointer */ |
475 | 0 | tokens[2] = NULL; |
476 | 0 | g_strfreev (tokens); |
477 | 0 | goto out; |
478 | 0 | } |
479 | | |
480 | 0 | g_strfreev (tokens); |
481 | 0 | } |
482 | | |
483 | | /* BOOH, didn't find the cookie */ |
484 | 0 | g_set_error (error, |
485 | 0 | G_IO_ERROR, |
486 | 0 | G_IO_ERROR_FAILED, |
487 | 0 | _("Didnāt find cookie with id %d in the keyring at ā%sā"), |
488 | 0 | cookie_id, |
489 | 0 | path); |
490 | |
|
491 | 0 | out: |
492 | 0 | g_free (keyring_dir); |
493 | 0 | g_free (path); |
494 | 0 | g_free (contents); |
495 | 0 | g_strfreev (lines); |
496 | 0 | return ret; |
497 | 0 | } |
498 | | |
499 | | /* function for logging important events that the system administrator should take notice of */ |
500 | | G_GNUC_PRINTF(1, 2) |
501 | | static void |
502 | | _log (const gchar *message, |
503 | | ...) |
504 | 0 | { |
505 | 0 | gchar *s; |
506 | 0 | va_list var_args; |
507 | |
|
508 | 0 | va_start (var_args, message); |
509 | 0 | s = g_strdup_vprintf (message, var_args); |
510 | 0 | va_end (var_args); |
511 | | |
512 | | /* TODO: might want to send this to syslog instead */ |
513 | 0 | g_printerr ("GDBus-DBUS_COOKIE_SHA1: %s\n", s); |
514 | 0 | g_free (s); |
515 | 0 | } |
516 | | |
517 | | /* Returns FD for lock file, if it was created exclusively (didn't exist already, |
518 | | * and was created successfully) */ |
519 | | static gint |
520 | | create_lock_exclusive (const gchar *lock_path, |
521 | | gint64 *mtime_nsec, |
522 | | GError **error) |
523 | 0 | { |
524 | 0 | int errsv; |
525 | 0 | gint ret; |
526 | |
|
527 | 0 | ret = g_open (lock_path, O_CREAT | O_EXCL | O_CLOEXEC, 0600); |
528 | 0 | errsv = errno; |
529 | 0 | if (ret < 0) |
530 | 0 | { |
531 | 0 | GLocalFileStat stat_buf; |
532 | | |
533 | | /* Get the modification time to distinguish between the lock being stale |
534 | | * or highly contested. */ |
535 | 0 | if (mtime_nsec != NULL && |
536 | 0 | g_local_file_stat (lock_path, G_LOCAL_FILE_STAT_FIELD_MTIME, G_LOCAL_FILE_STAT_FIELD_ALL, &stat_buf) == 0) |
537 | 0 | *mtime_nsec = _g_stat_mtime (&stat_buf) * G_USEC_PER_SEC * 1000 + _g_stat_mtim_nsec (&stat_buf); |
538 | 0 | else if (mtime_nsec != NULL) |
539 | 0 | *mtime_nsec = 0; |
540 | |
|
541 | 0 | g_set_error (error, |
542 | 0 | G_IO_ERROR, |
543 | 0 | g_io_error_from_errno (errsv), |
544 | 0 | _("Error creating lock file ā%sā: %s"), |
545 | 0 | lock_path, |
546 | 0 | g_strerror (errsv)); |
547 | 0 | return -1; |
548 | 0 | } |
549 | | |
550 | 0 | return ret; |
551 | 0 | } |
552 | | |
553 | | static gint |
554 | | keyring_acquire_lock (const gchar *path, |
555 | | GError **error) |
556 | 0 | { |
557 | 0 | gchar *lock = NULL; |
558 | 0 | gint ret; |
559 | 0 | guint num_tries; |
560 | 0 | int errsv; |
561 | 0 | gint64 lock_mtime_nsec = 0, lock_mtime_nsec_prev = 0; |
562 | | |
563 | | /* Total possible sleep period = max_tries * timeout_usec = 0.5s */ |
564 | 0 | const guint max_tries = 50; |
565 | 0 | const guint timeout_usec = 1000 * 10; |
566 | |
|
567 | 0 | g_return_val_if_fail (path != NULL, -1); |
568 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, -1); |
569 | | |
570 | 0 | ret = -1; |
571 | 0 | lock = g_strconcat (path, ".lock", NULL); |
572 | | |
573 | | /* This is what the D-Bus spec says |
574 | | * (https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha) |
575 | | * |
576 | | * Create a lockfile name by appending ".lock" to the name of the |
577 | | * cookie file. The server should attempt to create this file using |
578 | | * O_CREAT | O_EXCL. If file creation fails, the lock |
579 | | * fails. Servers should retry for a reasonable period of time, |
580 | | * then they may choose to delete an existing lock to keep users |
581 | | * from having to manually delete a stale lock. [1] |
582 | | * |
583 | | * [1] : Lockfiles are used instead of real file locking fcntl() because |
584 | | * real locking implementations are still flaky on network filesystems |
585 | | */ |
586 | |
|
587 | 0 | for (num_tries = 0; num_tries < max_tries; num_tries++) |
588 | 0 | { |
589 | 0 | lock_mtime_nsec_prev = lock_mtime_nsec; |
590 | | |
591 | | /* Ignore the error until the final call. */ |
592 | 0 | ret = create_lock_exclusive (lock, &lock_mtime_nsec, NULL); |
593 | 0 | if (ret >= 0) |
594 | 0 | break; |
595 | | |
596 | | /* sleep 10ms, then try again */ |
597 | 0 | g_usleep (timeout_usec); |
598 | | |
599 | | /* If the mtime of the lock file changed, donāt count the retry, as it |
600 | | * seems like thereās contention between processes for the lock file, |
601 | | * rather than a stale lock file from a crashed process. */ |
602 | 0 | if (num_tries > 0 && lock_mtime_nsec != lock_mtime_nsec_prev) |
603 | 0 | num_tries--; |
604 | 0 | } |
605 | |
|
606 | 0 | if (num_tries == max_tries) |
607 | 0 | { |
608 | | /* ok, we slept 50*10ms = 0.5 seconds. Conclude that the lock file must be |
609 | | * stale (nuke it from orbit) |
610 | | */ |
611 | 0 | if (g_unlink (lock) != 0) |
612 | 0 | { |
613 | 0 | errsv = errno; |
614 | 0 | g_set_error (error, |
615 | 0 | G_IO_ERROR, |
616 | 0 | g_io_error_from_errno (errsv), |
617 | 0 | _("Error deleting stale lock file ā%sā: %s"), |
618 | 0 | lock, |
619 | 0 | g_strerror (errsv)); |
620 | 0 | goto out; |
621 | 0 | } |
622 | | |
623 | 0 | _log ("Deleted stale lock file '%s'", lock); |
624 | | |
625 | | /* Try one last time to create it, now that we've deleted the stale one */ |
626 | 0 | ret = create_lock_exclusive (lock, NULL, error); |
627 | 0 | if (ret < 0) |
628 | 0 | goto out; |
629 | 0 | } |
630 | | |
631 | 0 | out: |
632 | 0 | g_free (lock); |
633 | 0 | return ret; |
634 | 0 | } |
635 | | |
636 | | static gboolean |
637 | | keyring_release_lock (const gchar *path, |
638 | | gint lock_fd, |
639 | | GError **error) |
640 | 0 | { |
641 | 0 | gchar *lock; |
642 | 0 | gboolean ret; |
643 | |
|
644 | 0 | g_return_val_if_fail (path != NULL, FALSE); |
645 | 0 | g_return_val_if_fail (lock_fd != -1, FALSE); |
646 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
647 | | |
648 | 0 | ret = FALSE; |
649 | 0 | lock = g_strdup_printf ("%s.lock", path); |
650 | 0 | if (close (lock_fd) != 0) |
651 | 0 | { |
652 | 0 | int errsv = errno; |
653 | 0 | g_set_error (error, |
654 | 0 | G_IO_ERROR, |
655 | 0 | g_io_error_from_errno (errsv), |
656 | 0 | _("Error closing (unlinked) lock file ā%sā: %s"), |
657 | 0 | lock, |
658 | 0 | g_strerror (errsv)); |
659 | 0 | goto out; |
660 | 0 | } |
661 | 0 | if (g_unlink (lock) != 0) |
662 | 0 | { |
663 | 0 | int errsv = errno; |
664 | 0 | g_set_error (error, |
665 | 0 | G_IO_ERROR, |
666 | 0 | g_io_error_from_errno (errsv), |
667 | 0 | _("Error unlinking lock file ā%sā: %s"), |
668 | 0 | lock, |
669 | 0 | g_strerror (errsv)); |
670 | 0 | goto out; |
671 | 0 | } |
672 | | |
673 | 0 | ret = TRUE; |
674 | |
|
675 | 0 | out: |
676 | 0 | g_free (lock); |
677 | 0 | return ret; |
678 | 0 | } |
679 | | |
680 | | |
681 | | /* adds an entry to the keyring, taking care of locking and deleting stale/future entries */ |
682 | | static gboolean |
683 | | keyring_generate_entry (const gchar *cookie_context, |
684 | | gint *out_id, |
685 | | gchar **out_cookie, |
686 | | GError **error) |
687 | 0 | { |
688 | 0 | gboolean ret; |
689 | 0 | gchar *keyring_dir; |
690 | 0 | gchar *path; |
691 | 0 | gchar *contents; |
692 | 0 | GError *local_error = NULL; |
693 | 0 | gchar **lines; |
694 | 0 | gint max_line_id; |
695 | 0 | GString *new_contents; |
696 | 0 | gint64 now; |
697 | 0 | gboolean have_id; |
698 | 0 | gint use_id; |
699 | 0 | gchar *use_cookie; |
700 | 0 | gboolean changed_file; |
701 | 0 | gint lock_fd; |
702 | |
|
703 | 0 | g_return_val_if_fail (cookie_context != NULL, FALSE); |
704 | 0 | g_return_val_if_fail (out_id != NULL, FALSE); |
705 | 0 | g_return_val_if_fail (out_cookie != NULL, FALSE); |
706 | 0 | g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
707 | | |
708 | 0 | ret = FALSE; |
709 | 0 | path = NULL; |
710 | 0 | contents = NULL; |
711 | 0 | lines = NULL; |
712 | 0 | new_contents = NULL; |
713 | 0 | have_id = FALSE; |
714 | 0 | use_id = 0; |
715 | 0 | use_cookie = NULL; |
716 | 0 | lock_fd = -1; |
717 | |
|
718 | 0 | keyring_dir = ensure_keyring_directory (error); |
719 | 0 | if (keyring_dir == NULL) |
720 | 0 | goto out; |
721 | | |
722 | 0 | path = g_build_filename (keyring_dir, cookie_context, NULL); |
723 | |
|
724 | 0 | lock_fd = keyring_acquire_lock (path, error); |
725 | 0 | if (lock_fd == -1) |
726 | 0 | goto out; |
727 | | |
728 | 0 | contents = NULL; |
729 | 0 | if (!g_file_get_contents (path, |
730 | 0 | &contents, |
731 | 0 | NULL, |
732 | 0 | &local_error)) |
733 | 0 | { |
734 | 0 | if (local_error->domain == G_FILE_ERROR && local_error->code == G_FILE_ERROR_NOENT) |
735 | 0 | { |
736 | | /* file doesn't have to exist */ |
737 | 0 | g_clear_error (&local_error); |
738 | 0 | } |
739 | 0 | else |
740 | 0 | { |
741 | 0 | g_propagate_prefixed_error (error, |
742 | 0 | g_steal_pointer (&local_error), |
743 | 0 | _("Error opening keyring ā%sā for writing: "), |
744 | 0 | path); |
745 | 0 | goto out; |
746 | 0 | } |
747 | 0 | } |
748 | | |
749 | 0 | new_contents = g_string_new (NULL); |
750 | 0 | now = g_get_real_time () / G_USEC_PER_SEC; |
751 | 0 | changed_file = FALSE; |
752 | |
|
753 | 0 | max_line_id = 0; |
754 | 0 | if (contents != NULL) |
755 | 0 | { |
756 | 0 | guint n; |
757 | 0 | lines = g_strsplit (contents, "\n", 0); |
758 | 0 | for (n = 0; lines[n] != NULL; n++) |
759 | 0 | { |
760 | 0 | const gchar *line = lines[n]; |
761 | 0 | gchar **tokens; |
762 | 0 | gchar *endp; |
763 | 0 | gint line_id; |
764 | 0 | gint64 line_when; |
765 | 0 | gboolean keep_entry; |
766 | |
|
767 | 0 | if (line[0] == '\0') |
768 | 0 | continue; |
769 | | |
770 | 0 | tokens = g_strsplit (line, " ", 0); |
771 | 0 | if (g_strv_length (tokens) != 3) |
772 | 0 | { |
773 | 0 | g_set_error (error, |
774 | 0 | G_IO_ERROR, |
775 | 0 | G_IO_ERROR_FAILED, |
776 | 0 | _("Line %d of the keyring at ā%sā with content ā%sā is malformed"), |
777 | 0 | n + 1, |
778 | 0 | path, |
779 | 0 | line); |
780 | 0 | g_strfreev (tokens); |
781 | 0 | goto out; |
782 | 0 | } |
783 | | |
784 | 0 | line_id = g_ascii_strtoll (tokens[0], &endp, 10); |
785 | 0 | if (*endp != '\0') |
786 | 0 | { |
787 | 0 | g_set_error (error, |
788 | 0 | G_IO_ERROR, |
789 | 0 | G_IO_ERROR_FAILED, |
790 | 0 | _("First token of line %d of the keyring at ā%sā with content ā%sā is malformed"), |
791 | 0 | n + 1, |
792 | 0 | path, |
793 | 0 | line); |
794 | 0 | g_strfreev (tokens); |
795 | 0 | goto out; |
796 | 0 | } |
797 | | |
798 | 0 | line_when = g_ascii_strtoll (tokens[1], &endp, 10); |
799 | 0 | if (*endp != '\0') |
800 | 0 | { |
801 | 0 | g_set_error (error, |
802 | 0 | G_IO_ERROR, |
803 | 0 | G_IO_ERROR_FAILED, |
804 | 0 | _("Second token of line %d of the keyring at ā%sā with content ā%sā is malformed"), |
805 | 0 | n + 1, |
806 | 0 | path, |
807 | 0 | line); |
808 | 0 | g_strfreev (tokens); |
809 | 0 | goto out; |
810 | 0 | } |
811 | | |
812 | | |
813 | | /* D-Bus spec says: |
814 | | * |
815 | | * Once the lockfile has been created, the server loads the |
816 | | * cookie file. It should then delete any cookies that are |
817 | | * old (the timeout can be fairly short), or more than a |
818 | | * reasonable time in the future (so that cookies never |
819 | | * accidentally become permanent, if the clock was set far |
820 | | * into the future at some point). If no recent keys remain, |
821 | | * the server may generate a new key. |
822 | | * |
823 | | */ |
824 | 0 | keep_entry = TRUE; |
825 | 0 | if (line_when > now) |
826 | 0 | { |
827 | | /* Oddball case: entry is more recent than our current wall-clock time.. |
828 | | * This is OK, it means that another server on another machine but with |
829 | | * same $HOME wrote the entry. */ |
830 | 0 | if (line_when - now > MAX_TIME_TRAVEL_SECONDS) |
831 | 0 | { |
832 | 0 | keep_entry = FALSE; |
833 | 0 | _log ("Deleted SHA1 cookie from %" G_GUINT64_FORMAT " seconds in the future", line_when - now); |
834 | 0 | } |
835 | 0 | } |
836 | 0 | else |
837 | 0 | { |
838 | | /* Discard entry if it's too old. */ |
839 | 0 | if (now - line_when > EXPIRE_KEYS_TIMEOUT_SECONDS) |
840 | 0 | { |
841 | 0 | keep_entry = FALSE; |
842 | 0 | } |
843 | 0 | } |
844 | |
|
845 | 0 | if (!keep_entry) |
846 | 0 | { |
847 | 0 | changed_file = FALSE; |
848 | 0 | } |
849 | 0 | else |
850 | 0 | { |
851 | 0 | g_string_append_printf (new_contents, |
852 | 0 | "%d %" G_GUINT64_FORMAT " %s\n", |
853 | 0 | line_id, |
854 | 0 | line_when, |
855 | 0 | tokens[2]); |
856 | 0 | max_line_id = MAX (line_id, max_line_id); |
857 | | /* Only reuse entry if not older than 5 minutes. |
858 | | * |
859 | | * (We need a bit of grace time compared to 7 minutes above.. otherwise |
860 | | * there's a race where we reuse the 6min59.9 secs old entry and a |
861 | | * split-second later another server purges the now 7 minute old entry.) |
862 | | */ |
863 | 0 | if (now - line_when < NEW_KEY_TIMEOUT_SECONDS) |
864 | 0 | { |
865 | 0 | if (!have_id) |
866 | 0 | { |
867 | 0 | use_id = line_id; |
868 | 0 | use_cookie = tokens[2]; /* steal memory */ |
869 | 0 | tokens[2] = NULL; |
870 | 0 | have_id = TRUE; |
871 | 0 | } |
872 | 0 | } |
873 | 0 | } |
874 | 0 | g_strfreev (tokens); |
875 | 0 | } |
876 | 0 | } /* for each line */ |
877 | | |
878 | 0 | ret = TRUE; |
879 | |
|
880 | 0 | if (have_id) |
881 | 0 | { |
882 | 0 | *out_id = use_id; |
883 | 0 | *out_cookie = use_cookie; |
884 | 0 | use_cookie = NULL; |
885 | 0 | } |
886 | 0 | else |
887 | 0 | { |
888 | 0 | gchar *raw_cookie; |
889 | 0 | *out_id = max_line_id + 1; |
890 | 0 | raw_cookie = random_blob (32); |
891 | 0 | *out_cookie = _g_dbus_hexencode (raw_cookie, 32); |
892 | 0 | g_free (raw_cookie); |
893 | |
|
894 | 0 | g_string_append_printf (new_contents, |
895 | 0 | "%d %" G_GINT64_FORMAT " %s\n", |
896 | 0 | *out_id, |
897 | 0 | g_get_real_time () / G_USEC_PER_SEC, |
898 | 0 | *out_cookie); |
899 | 0 | changed_file = TRUE; |
900 | 0 | } |
901 | | |
902 | | /* and now actually write the cookie file if there are changes (this is atomic) */ |
903 | 0 | if (changed_file) |
904 | 0 | { |
905 | 0 | if (!g_file_set_contents_full (path, |
906 | 0 | new_contents->str, |
907 | 0 | -1, |
908 | 0 | G_FILE_SET_CONTENTS_CONSISTENT, |
909 | 0 | 0600, |
910 | 0 | error)) |
911 | 0 | { |
912 | 0 | *out_id = 0; |
913 | 0 | g_free (*out_cookie); |
914 | 0 | *out_cookie = 0; |
915 | 0 | ret = FALSE; |
916 | 0 | goto out; |
917 | 0 | } |
918 | 0 | } |
919 | | |
920 | 0 | out: |
921 | | /* Any error should have been propagated to @error by now */ |
922 | 0 | g_assert (local_error == NULL); |
923 | | |
924 | 0 | if (lock_fd != -1) |
925 | 0 | { |
926 | 0 | if (!keyring_release_lock (path, lock_fd, &local_error)) |
927 | 0 | { |
928 | 0 | if (error != NULL) |
929 | 0 | { |
930 | 0 | if (*error == NULL) |
931 | 0 | { |
932 | 0 | *error = local_error; |
933 | 0 | } |
934 | 0 | else |
935 | 0 | { |
936 | 0 | g_prefix_error (error, |
937 | 0 | _("(Additionally, releasing the lock for ā%sā also failed: %s) "), |
938 | 0 | path, |
939 | 0 | local_error->message); |
940 | 0 | g_error_free (local_error); |
941 | 0 | } |
942 | 0 | } |
943 | 0 | else |
944 | 0 | { |
945 | 0 | g_error_free (local_error); |
946 | 0 | } |
947 | 0 | } |
948 | 0 | } |
949 | |
|
950 | 0 | g_free (keyring_dir); |
951 | 0 | g_free (path); |
952 | 0 | g_strfreev (lines); |
953 | 0 | g_free (contents); |
954 | 0 | if (new_contents != NULL) |
955 | 0 | g_string_free (new_contents, TRUE); |
956 | 0 | g_free (use_cookie); |
957 | 0 | return ret; |
958 | 0 | } |
959 | | |
960 | | /* ---------------------------------------------------------------------------------------------------- */ |
961 | | |
962 | | static gchar * |
963 | | generate_sha1 (const gchar *server_challenge, |
964 | | const gchar *client_challenge, |
965 | | const gchar *cookie) |
966 | 0 | { |
967 | 0 | GString *str; |
968 | 0 | gchar *sha1; |
969 | |
|
970 | 0 | str = g_string_new (server_challenge); |
971 | 0 | g_string_append_c (str, ':'); |
972 | 0 | g_string_append (str, client_challenge); |
973 | 0 | g_string_append_c (str, ':'); |
974 | 0 | g_string_append (str, cookie); |
975 | 0 | sha1 = g_compute_checksum_for_string (G_CHECKSUM_SHA1, str->str, -1); |
976 | 0 | g_string_free (str, TRUE); |
977 | |
|
978 | 0 | return sha1; |
979 | 0 | } |
980 | | |
981 | | /* ---------------------------------------------------------------------------------------------------- */ |
982 | | |
983 | | static GDBusAuthMechanismState |
984 | | mechanism_server_get_state (GDBusAuthMechanism *mechanism) |
985 | 0 | { |
986 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
987 | |
|
988 | 0 | g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); |
989 | 0 | g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, G_DBUS_AUTH_MECHANISM_STATE_INVALID); |
990 | | |
991 | 0 | return m->priv->state; |
992 | 0 | } |
993 | | |
994 | | static void |
995 | | mechanism_server_initiate (GDBusAuthMechanism *mechanism, |
996 | | const gchar *initial_response, |
997 | | gsize initial_response_len) |
998 | 0 | { |
999 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1000 | |
|
1001 | 0 | g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); |
1002 | 0 | g_return_if_fail (!m->priv->is_server && !m->priv->is_client); |
1003 | | |
1004 | 0 | m->priv->is_server = TRUE; |
1005 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1006 | |
|
1007 | 0 | if (initial_response != NULL && initial_response_len > 0) |
1008 | 0 | { |
1009 | 0 | #ifdef G_OS_UNIX |
1010 | 0 | gint64 uid; |
1011 | 0 | gchar *endp; |
1012 | |
|
1013 | 0 | uid = g_ascii_strtoll (initial_response, &endp, 10); |
1014 | 0 | if (*endp == '\0') |
1015 | 0 | { |
1016 | 0 | if (uid == getuid ()) |
1017 | 0 | { |
1018 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND; |
1019 | 0 | } |
1020 | 0 | } |
1021 | | #elif defined(G_OS_WIN32) |
1022 | | gchar *sid; |
1023 | | |
1024 | | sid = _g_win32_current_process_sid_string (NULL); |
1025 | | |
1026 | | if (g_strcmp0 (initial_response, sid) == 0) |
1027 | | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND; |
1028 | | |
1029 | | g_free (sid); |
1030 | | #else |
1031 | | #error Please implement for your OS |
1032 | | #endif |
1033 | 0 | } |
1034 | 0 | } |
1035 | | |
1036 | | static void |
1037 | | mechanism_server_data_receive (GDBusAuthMechanism *mechanism, |
1038 | | const gchar *data, |
1039 | | gsize data_len) |
1040 | 0 | { |
1041 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1042 | 0 | gchar **tokens; |
1043 | 0 | const gchar *client_challenge; |
1044 | 0 | const gchar *alleged_sha1; |
1045 | 0 | gchar *sha1; |
1046 | |
|
1047 | 0 | g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); |
1048 | 0 | g_return_if_fail (m->priv->is_server && !m->priv->is_client); |
1049 | 0 | g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA); |
1050 | | |
1051 | 0 | tokens = NULL; |
1052 | 0 | sha1 = NULL; |
1053 | |
|
1054 | 0 | tokens = g_strsplit (data, " ", 0); |
1055 | 0 | if (g_strv_length (tokens) != 2) |
1056 | 0 | { |
1057 | 0 | g_free (m->priv->reject_reason); |
1058 | 0 | m->priv->reject_reason = g_strdup_printf ("Malformed data '%s'", data); |
1059 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1060 | 0 | goto out; |
1061 | 0 | } |
1062 | | |
1063 | 0 | client_challenge = tokens[0]; |
1064 | 0 | alleged_sha1 = tokens[1]; |
1065 | |
|
1066 | 0 | sha1 = generate_sha1 (m->priv->server_challenge, client_challenge, m->priv->cookie); |
1067 | |
|
1068 | 0 | if (g_strcmp0 (sha1, alleged_sha1) == 0) |
1069 | 0 | { |
1070 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; |
1071 | 0 | } |
1072 | 0 | else |
1073 | 0 | { |
1074 | 0 | g_free (m->priv->reject_reason); |
1075 | 0 | m->priv->reject_reason = g_strdup_printf ("SHA-1 mismatch"); |
1076 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1077 | 0 | } |
1078 | |
|
1079 | 0 | out: |
1080 | 0 | g_strfreev (tokens); |
1081 | 0 | g_free (sha1); |
1082 | 0 | } |
1083 | | |
1084 | | static gchar * |
1085 | | mechanism_server_data_send (GDBusAuthMechanism *mechanism, |
1086 | | gsize *out_data_len) |
1087 | 0 | { |
1088 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1089 | 0 | gchar *s; |
1090 | 0 | gint cookie_id; |
1091 | 0 | const gchar *cookie_context; |
1092 | 0 | GError *error; |
1093 | |
|
1094 | 0 | g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); |
1095 | 0 | g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); |
1096 | 0 | g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL); |
1097 | | |
1098 | 0 | s = NULL; |
1099 | 0 | *out_data_len = 0; |
1100 | | |
1101 | | /* TODO: use GDBusAuthObserver here to get the cookie context to use? */ |
1102 | 0 | cookie_context = "org_gtk_gdbus_general"; |
1103 | |
|
1104 | 0 | cookie_id = -1; |
1105 | 0 | error = NULL; |
1106 | 0 | if (!keyring_generate_entry (cookie_context, |
1107 | 0 | &cookie_id, |
1108 | 0 | &m->priv->cookie, |
1109 | 0 | &error)) |
1110 | 0 | { |
1111 | 0 | g_free (m->priv->reject_reason); |
1112 | 0 | m->priv->reject_reason = g_strdup_printf ("Error adding entry to keyring: %s", error->message); |
1113 | 0 | g_error_free (error); |
1114 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1115 | 0 | goto out; |
1116 | 0 | } |
1117 | | |
1118 | 0 | m->priv->server_challenge = random_ascii_string (16); |
1119 | 0 | s = g_strdup_printf ("%s %d %s", |
1120 | 0 | cookie_context, |
1121 | 0 | cookie_id, |
1122 | 0 | m->priv->server_challenge); |
1123 | 0 | *out_data_len = strlen (s); |
1124 | |
|
1125 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA; |
1126 | |
|
1127 | 0 | out: |
1128 | 0 | return s; |
1129 | 0 | } |
1130 | | |
1131 | | static gchar * |
1132 | | mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism) |
1133 | 0 | { |
1134 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1135 | |
|
1136 | 0 | g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); |
1137 | 0 | g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); |
1138 | | |
1139 | 0 | return g_strdup (m->priv->reject_reason); |
1140 | 0 | } |
1141 | | |
1142 | | static void |
1143 | | mechanism_server_shutdown (GDBusAuthMechanism *mechanism) |
1144 | 0 | { |
1145 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1146 | |
|
1147 | 0 | g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); |
1148 | 0 | g_return_if_fail (m->priv->is_server && !m->priv->is_client); |
1149 | | |
1150 | 0 | m->priv->is_server = FALSE; |
1151 | 0 | } |
1152 | | |
1153 | | /* ---------------------------------------------------------------------------------------------------- */ |
1154 | | |
1155 | | static GDBusAuthMechanismState |
1156 | | mechanism_client_get_state (GDBusAuthMechanism *mechanism) |
1157 | 0 | { |
1158 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1159 | |
|
1160 | 0 | g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), G_DBUS_AUTH_MECHANISM_STATE_INVALID); |
1161 | 0 | g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, G_DBUS_AUTH_MECHANISM_STATE_INVALID); |
1162 | | |
1163 | 0 | return m->priv->state; |
1164 | 0 | } |
1165 | | |
1166 | | static gchar * |
1167 | | mechanism_client_initiate (GDBusAuthMechanism *mechanism, |
1168 | | GDBusConnectionFlags conn_flags, |
1169 | | gsize *out_initial_response_len) |
1170 | 0 | { |
1171 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1172 | 0 | gchar *initial_response; |
1173 | |
|
1174 | 0 | g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); |
1175 | 0 | g_return_val_if_fail (!m->priv->is_server && !m->priv->is_client, NULL); |
1176 | | |
1177 | 0 | m->priv->is_client = TRUE; |
1178 | |
|
1179 | 0 | *out_initial_response_len = 0; |
1180 | |
|
1181 | 0 | #ifdef G_OS_UNIX |
1182 | 0 | initial_response = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) getuid ()); |
1183 | | #elif defined (G_OS_WIN32) |
1184 | | initial_response = _g_win32_current_process_sid_string (NULL); |
1185 | | #else |
1186 | | #error Please implement for your OS |
1187 | | #endif |
1188 | 0 | if (initial_response) |
1189 | 0 | { |
1190 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA; |
1191 | 0 | *out_initial_response_len = strlen (initial_response); |
1192 | 0 | } |
1193 | 0 | else |
1194 | 0 | { |
1195 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1196 | 0 | } |
1197 | |
|
1198 | 0 | return initial_response; |
1199 | 0 | } |
1200 | | |
1201 | | /* Context names must be valid ASCII, nonzero length, and may not contain the |
1202 | | * characters slash ("/"), backslash ("\"), space (" "), newline ("\n"), |
1203 | | * carriage return ("\r"), tab ("\t"), or period ("."). |
1204 | | * |
1205 | | * See https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha */ |
1206 | | static gboolean |
1207 | | validate_cookie_context (const char *cookie_context) |
1208 | 0 | { |
1209 | 0 | size_t i = 0; |
1210 | |
|
1211 | 0 | g_return_val_if_fail (cookie_context != NULL, FALSE); |
1212 | | |
1213 | 0 | for (i = 0; cookie_context[i] != '\0'; i++) |
1214 | 0 | { |
1215 | 0 | if ((uint8_t) cookie_context[i] >= 128 || |
1216 | 0 | cookie_context[i] == '/' || |
1217 | 0 | cookie_context[i] == '\\' || |
1218 | 0 | cookie_context[i] == ' ' || |
1219 | 0 | cookie_context[i] == '\n' || |
1220 | 0 | cookie_context[i] == '\r' || |
1221 | 0 | cookie_context[i] == '\t' || |
1222 | 0 | cookie_context[i] == '.') |
1223 | 0 | return FALSE; |
1224 | 0 | } |
1225 | | |
1226 | 0 | return (i > 0); |
1227 | 0 | } |
1228 | | |
1229 | | static void |
1230 | | mechanism_client_data_receive (GDBusAuthMechanism *mechanism, |
1231 | | const gchar *data, |
1232 | | gsize data_len) |
1233 | 0 | { |
1234 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1235 | 0 | gchar **tokens; |
1236 | 0 | const gchar *cookie_context; |
1237 | 0 | int64_t cookie_id; |
1238 | 0 | const gchar *server_challenge; |
1239 | 0 | gchar *client_challenge; |
1240 | 0 | gchar *endp; |
1241 | 0 | gchar *cookie; |
1242 | 0 | GError *error; |
1243 | 0 | gchar *sha1; |
1244 | |
|
1245 | 0 | g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); |
1246 | 0 | g_return_if_fail (m->priv->is_client && !m->priv->is_server); |
1247 | 0 | g_return_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA); |
1248 | | |
1249 | 0 | tokens = NULL; |
1250 | 0 | cookie = NULL; |
1251 | 0 | client_challenge = NULL; |
1252 | |
|
1253 | 0 | tokens = g_strsplit (data, " ", 0); |
1254 | 0 | if (g_strv_length (tokens) != 3) |
1255 | 0 | { |
1256 | 0 | g_free (m->priv->reject_reason); |
1257 | 0 | m->priv->reject_reason = g_strdup_printf ("Malformed data '%s'", data); |
1258 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1259 | 0 | goto out; |
1260 | 0 | } |
1261 | | |
1262 | 0 | cookie_context = tokens[0]; |
1263 | 0 | if (!validate_cookie_context (tokens[0])) |
1264 | 0 | { |
1265 | 0 | g_free (m->priv->reject_reason); |
1266 | 0 | m->priv->reject_reason = g_strdup_printf ("Malformed cookie_context '%s'", tokens[0]); |
1267 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1268 | 0 | goto out; |
1269 | 0 | } |
1270 | | |
1271 | 0 | cookie_id = g_ascii_strtoll (tokens[1], &endp, 10); |
1272 | 0 | if (*endp != '\0' || endp == tokens[1] || cookie_id < 0 || cookie_id > UINT32_MAX) |
1273 | 0 | { |
1274 | 0 | g_free (m->priv->reject_reason); |
1275 | 0 | m->priv->reject_reason = g_strdup_printf ("Malformed cookie_id '%s'", tokens[1]); |
1276 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1277 | 0 | goto out; |
1278 | 0 | } |
1279 | 0 | server_challenge = tokens[2]; |
1280 | |
|
1281 | 0 | error = NULL; |
1282 | 0 | cookie = keyring_lookup_entry (cookie_context, (unsigned int) cookie_id, &error); |
1283 | 0 | if (cookie == NULL) |
1284 | 0 | { |
1285 | 0 | g_free (m->priv->reject_reason); |
1286 | 0 | m->priv->reject_reason = g_strdup_printf ("Problems looking up entry in keyring: %s", error->message); |
1287 | 0 | g_error_free (error); |
1288 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; |
1289 | 0 | goto out; |
1290 | 0 | } |
1291 | | |
1292 | 0 | client_challenge = random_ascii_string (16); |
1293 | 0 | sha1 = generate_sha1 (server_challenge, client_challenge, cookie); |
1294 | 0 | m->priv->to_send = g_strdup_printf ("%s %s", client_challenge, sha1); |
1295 | 0 | g_free (sha1); |
1296 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND; |
1297 | |
|
1298 | 0 | out: |
1299 | 0 | g_strfreev (tokens); |
1300 | 0 | g_free (cookie); |
1301 | 0 | g_free (client_challenge); |
1302 | 0 | } |
1303 | | |
1304 | | static gchar * |
1305 | | mechanism_client_data_send (GDBusAuthMechanism *mechanism, |
1306 | | gsize *out_data_len) |
1307 | 0 | { |
1308 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1309 | |
|
1310 | 0 | g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); |
1311 | 0 | g_return_val_if_fail (m->priv->is_client && !m->priv->is_server, NULL); |
1312 | 0 | g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND, NULL); |
1313 | | |
1314 | 0 | g_assert (m->priv->to_send != NULL); |
1315 | | |
1316 | 0 | m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED; |
1317 | |
|
1318 | 0 | *out_data_len = strlen (m->priv->to_send); |
1319 | 0 | return g_strdup (m->priv->to_send); |
1320 | 0 | } |
1321 | | |
1322 | | static void |
1323 | | mechanism_client_shutdown (GDBusAuthMechanism *mechanism) |
1324 | 0 | { |
1325 | 0 | GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); |
1326 | |
|
1327 | 0 | g_return_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism)); |
1328 | 0 | g_return_if_fail (m->priv->is_client && !m->priv->is_server); |
1329 | | |
1330 | 0 | m->priv->is_client = FALSE; |
1331 | 0 | } |
1332 | | |
1333 | | /* ---------------------------------------------------------------------------------------------------- */ |