1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Copyright © 2017 Gabriel Ivascu <gabrielivascu@gnome.org>
4 *
5 * This file is part of Epiphany.
6 *
7 * Epiphany is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Epiphany 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
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22 #include "ephy-sync-utils.h"
23
24 #include "ephy-settings.h"
25
26 #include <errno.h>
27 #include <glib/gi18n.h>
28 #include <inttypes.h>
29 #include <json-glib/json-glib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <webkit2/webkit2.h>
33 #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
34 #include <sys/random.h>
35 #elif defined(__OpenBSD__)
36 #include <unistd.h>
37 #endif
38
39 static const char hex_digits[] = "0123456789abcdef";
40
41 const SecretSchema *
ephy_sync_utils_get_secret_schema(void)42 ephy_sync_utils_get_secret_schema (void)
43 {
44 static const SecretSchema schema = {
45 "org.epiphany.SyncSecrets", SECRET_SCHEMA_NONE,
46 {
47 { EPHY_SYNC_SECRET_ACCOUNT_KEY, SECRET_SCHEMA_ATTRIBUTE_STRING },
48 { "NULL", 0 },
49 }
50 };
51
52 return &schema;
53 }
54
55 char *
ephy_sync_utils_encode_hex(const guint8 * data,gsize data_len)56 ephy_sync_utils_encode_hex (const guint8 *data,
57 gsize data_len)
58 {
59 char *encoded;
60
61 g_assert (data);
62
63 encoded = g_malloc (data_len * 2 + 1);
64 for (gsize i = 0; i < data_len; i++) {
65 guint8 byte = data[i];
66
67 encoded[2 * i] = hex_digits[byte >> 4];
68 encoded[2 * i + 1] = hex_digits[byte & 0xf];
69 }
70 encoded[data_len * 2] = 0;
71
72 return encoded;
73 }
74
75 guint8 *
ephy_sync_utils_decode_hex(const char * hex)76 ephy_sync_utils_decode_hex (const char *hex)
77 {
78 guint8 *decoded;
79
80 g_assert (hex);
81
82 decoded = g_malloc (strlen (hex) / 2);
83 for (gsize i = 0, j = 0; i < strlen (hex); i += 2, j++)
84 sscanf (hex + i, "%2hhx", decoded + j);
85
86 return decoded;
87 }
88
89 static void
base64_to_base64_urlsafe(char * text)90 base64_to_base64_urlsafe (char *text)
91 {
92 g_assert (text);
93
94 /* / and + are inappropriate for URLs and file systems paths, so they have to
95 * be omitted to make the base64 string safe. / is replaced with _ and + is
96 * replaced with -.
97 */
98 g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=/", '-');
99 g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=-", '_');
100 }
101
102 char *
ephy_sync_utils_base64_urlsafe_encode(const guint8 * data,gsize data_len,gboolean should_strip)103 ephy_sync_utils_base64_urlsafe_encode (const guint8 *data,
104 gsize data_len,
105 gboolean should_strip)
106 {
107 char *base64;
108 char *out;
109 gsize start = 0;
110 gssize end;
111
112 g_assert (data);
113
114 base64 = g_base64_encode (data, data_len);
115 end = strlen (base64) - 1;
116
117 /* Strip the data of any leading or trailing '=' characters. */
118 if (should_strip) {
119 while (start < strlen (base64) && base64[start] == '=')
120 start++;
121
122 while (end >= 0 && base64[end] == '=')
123 end--;
124 }
125
126 out = g_strndup (base64 + start, end - start + 1);
127 base64_to_base64_urlsafe (out);
128
129 g_free (base64);
130
131 return out;
132 }
133
134 static void
base64_urlsafe_to_base64(char * text)135 base64_urlsafe_to_base64 (char *text)
136 {
137 g_assert (text);
138
139 /* Replace '-' with '+' and '_' with '/' */
140 g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=_", '+');
141 g_strcanon (text, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789=+", '/');
142 }
143
144 guint8 *
ephy_sync_utils_base64_urlsafe_decode(const char * text,gsize * out_len,gboolean should_fill)145 ephy_sync_utils_base64_urlsafe_decode (const char *text,
146 gsize *out_len,
147 gboolean should_fill)
148 {
149 guint8 *out;
150 char *to_decode;
151 char *suffix = NULL;
152
153 g_assert (text);
154 g_assert (out_len);
155
156 /* Fill the text with trailing '=' characters up to the proper length. */
157 if (should_fill)
158 suffix = g_strnfill ((4 - strlen (text) % 4) % 4, '=');
159
160 to_decode = g_strconcat (text, suffix, NULL);
161 base64_urlsafe_to_base64 (to_decode);
162 out = g_base64_decode (to_decode, out_len);
163
164 g_free (suffix);
165 g_free (to_decode);
166
167 return out;
168 }
169
170 /*
171 * This is mainly required by Nettle's RSA support.
172 * From Nettle's documentation: random_ctx and random is a randomness generator.
173 * random(random_ctx, length, dst) should generate length random octets and store them at dst.
174 * We don't use random_ctx.
175 */
176 void
ephy_sync_utils_generate_random_bytes(void * random_ctx,gsize num_bytes,guint8 * out)177 ephy_sync_utils_generate_random_bytes (void *random_ctx,
178 gsize num_bytes,
179 guint8 *out)
180 {
181 gssize ret;
182
183 g_assert (num_bytes > 0);
184 g_assert (out);
185
186 #ifdef __OpenBSD__
187 if (getentropy (out, num_bytes) == -1) {
188 g_error ("Failed to get entropy: %s", g_strerror (errno));
189 }
190 #else
191 do {
192 ret = getrandom (out, num_bytes, 0);
193 } while (ret < (gssize)num_bytes && errno == EINTR);
194
195 if (ret != (gssize)num_bytes)
196 g_error ("Failed to generate randomness: %s", g_strerror (errno));
197 #endif
198 }
199
200 char *
ephy_sync_utils_get_audience(const char * url)201 ephy_sync_utils_get_audience (const char *url)
202 {
203 g_autoptr (WebKitSecurityOrigin) origin = webkit_security_origin_new_for_uri (url);
204
205 return webkit_security_origin_to_string (origin);
206 }
207
208 char *
ephy_sync_utils_get_random_sync_id(void)209 ephy_sync_utils_get_random_sync_id (void)
210 {
211 char *id;
212 char *base64;
213 guint8 *bytes;
214 gsize bytes_len;
215
216 /* The sync id is a base64-urlsafe string. Base64 uses 4 chars to represent 3 bytes,
217 * therefore we need ceil(len * 3 / 4) bytes to cover the requested length. */
218 bytes_len = (EPHY_SYNC_BSO_ID_LEN + 3) / 4 * 3;
219 bytes = g_malloc (bytes_len);
220
221 ephy_sync_utils_generate_random_bytes (NULL, bytes_len, bytes);
222 base64 = ephy_sync_utils_base64_urlsafe_encode (bytes, bytes_len, FALSE);
223 id = g_strndup (base64, EPHY_SYNC_BSO_ID_LEN);
224
225 g_free (base64);
226 g_free (bytes);
227
228 return id;
229 }
230
231 char *
ephy_sync_utils_make_client_record(const char * device_bso_id,const char * device_id,const char * device_name)232 ephy_sync_utils_make_client_record (const char *device_bso_id,
233 const char *device_id,
234 const char *device_name)
235 {
236 JsonNode *node;
237 JsonObject *object;
238 JsonArray *array;
239 char *protocol;
240 char *retval;
241
242 g_assert (device_bso_id);
243 g_assert (device_id);
244 g_assert (device_name);
245
246 array = json_array_new ();
247 protocol = g_strdup_printf ("1.%" PRIu32, EPHY_SYNC_STORAGE_VERSION);
248 json_array_add_string_element (array, protocol);
249
250 object = json_object_new ();
251 json_object_set_string_member (object, "id", device_bso_id);
252 json_object_set_string_member (object, "fxaDeviceId", device_id);
253 json_object_set_string_member (object, "name", device_name);
254 json_object_set_string_member (object, "type", "desktop");
255 json_object_set_string_member (object, "version", VERSION);
256 json_object_set_array_member (object, "protocols", array);
257 json_object_set_string_member (object, "os", "Linux");
258 json_object_set_string_member (object, "appPackage", "org.gnome.epiphany");
259 json_object_set_string_member (object, "application", "Epiphany");
260
261 node = json_node_new (JSON_NODE_OBJECT);
262 json_node_take_object (node, object);
263 retval = json_to_string (node, FALSE);
264
265 g_free (protocol);
266 json_node_unref (node);
267
268 return retval;
269 }
270
271 void
ephy_sync_utils_set_device_id(const char * id)272 ephy_sync_utils_set_device_id (const char *id)
273 {
274 id = id ? id : "";
275 g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_DEVICE_ID, id);
276 }
277
278 char *
ephy_sync_utils_get_device_id(void)279 ephy_sync_utils_get_device_id (void)
280 {
281 return g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_DEVICE_ID);
282 }
283
284 char *
ephy_sync_utils_get_device_bso_id(void)285 ephy_sync_utils_get_device_bso_id (void)
286 {
287 char *device_bso_id;
288 char *device_id;
289
290 device_id = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_DEVICE_ID);
291 if (!g_strcmp0 (device_id, "")) {
292 /* This should never be reached. */
293 return g_strnfill (EPHY_SYNC_BSO_ID_LEN, '0');
294 }
295
296 device_bso_id = g_strndup (device_id, EPHY_SYNC_BSO_ID_LEN);
297 g_free (device_id);
298
299 return device_bso_id;
300 }
301
302 void
ephy_sync_utils_set_device_name(const char * name)303 ephy_sync_utils_set_device_name (const char *name)
304 {
305 name = name ? name : "";
306 g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_DEVICE_NAME, name);
307 }
308
309 char *
ephy_sync_utils_get_device_name(void)310 ephy_sync_utils_get_device_name (void)
311 {
312 char *name;
313
314 name = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_DEVICE_NAME);
315 if (g_strcmp0 (name, ""))
316 return name;
317
318 g_free (name);
319 /* Translators: First %s is the name of the user currently logged in on the
320 * machine. The second %s is the machine's name. You can use the variables
321 * in a different order by changing them to %2$s and %1$s. */
322 name = g_strdup_printf (_("%s’s GNOME Web on %s"), g_get_user_name (), g_get_host_name ());
323 g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_DEVICE_NAME, name);
324
325 return name;
326 }
327
328 void
ephy_sync_utils_set_sync_user(const char * user)329 ephy_sync_utils_set_sync_user (const char *user)
330 {
331 user = user ? user : "";
332 g_settings_set_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER, user);
333 }
334
335 char *
ephy_sync_utils_get_sync_user(void)336 ephy_sync_utils_get_sync_user (void)
337 {
338 char *user = g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_USER);
339
340 if (!g_strcmp0 (user, "")) {
341 g_free (user);
342 return NULL;
343 }
344
345 return user;
346 }
347
348 gboolean
ephy_sync_utils_user_is_signed_in(void)349 ephy_sync_utils_user_is_signed_in (void)
350 {
351 char *user = ephy_sync_utils_get_sync_user ();
352
353 if (user) {
354 g_free (user);
355 return TRUE;
356 }
357
358 return FALSE;
359 }
360
361 void
ephy_sync_utils_set_sync_time(gint64 time)362 ephy_sync_utils_set_sync_time (gint64 time)
363 {
364 time = time > 0 ? time : 0;
365 g_settings_set_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_TIME, time);
366 }
367
368 gint64
ephy_sync_utils_get_sync_time(void)369 ephy_sync_utils_get_sync_time (void)
370 {
371 return g_settings_get_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_TIME);
372 }
373
374 guint
ephy_sync_utils_get_sync_frequency(void)375 ephy_sync_utils_get_sync_frequency (void)
376 {
377 /* Minutes. */
378 return g_settings_get_uint (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_FREQUENCY);
379 }
380
381 gboolean
ephy_sync_utils_sync_with_firefox(void)382 ephy_sync_utils_sync_with_firefox (void)
383 {
384 return g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_WITH_FIREFOX);
385 }
386
387 gboolean
ephy_sync_utils_bookmarks_sync_is_enabled(void)388 ephy_sync_utils_bookmarks_sync_is_enabled (void)
389 {
390 return g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_ENABLED);
391 }
392
393 void
ephy_sync_utils_set_bookmarks_sync_time(gint64 time)394 ephy_sync_utils_set_bookmarks_sync_time (gint64 time)
395 {
396 g_settings_set_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_TIME, time);
397 }
398
399 gint64
ephy_sync_utils_get_bookmarks_sync_time(void)400 ephy_sync_utils_get_bookmarks_sync_time (void)
401 {
402 return g_settings_get_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_TIME);
403 }
404
405 void
ephy_sync_utils_set_bookmarks_sync_is_initial(gboolean is_initial)406 ephy_sync_utils_set_bookmarks_sync_is_initial (gboolean is_initial)
407 {
408 g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_INITIAL, is_initial);
409 }
410
411 gboolean
ephy_sync_utils_get_bookmarks_sync_is_initial(void)412 ephy_sync_utils_get_bookmarks_sync_is_initial (void)
413 {
414 return g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_BOOKMARKS_INITIAL);
415 }
416
417 gboolean
ephy_sync_utils_passwords_sync_is_enabled(void)418 ephy_sync_utils_passwords_sync_is_enabled (void)
419 {
420 return g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_ENABLED);
421 }
422
423 void
ephy_sync_utils_set_passwords_sync_time(gint64 time)424 ephy_sync_utils_set_passwords_sync_time (gint64 time)
425 {
426 g_settings_set_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_TIME, time);
427 }
428
429 gint64
ephy_sync_utils_get_passwords_sync_time(void)430 ephy_sync_utils_get_passwords_sync_time (void)
431 {
432 return g_settings_get_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_TIME);
433 }
434
435 void
ephy_sync_utils_set_passwords_sync_is_initial(gboolean is_initial)436 ephy_sync_utils_set_passwords_sync_is_initial (gboolean is_initial)
437 {
438 g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_INITIAL, is_initial);
439 }
440
441 gboolean
ephy_sync_utils_get_passwords_sync_is_initial(void)442 ephy_sync_utils_get_passwords_sync_is_initial (void)
443 {
444 return g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_PASSWORDS_INITIAL);
445 }
446
447 gboolean
ephy_sync_utils_history_sync_is_enabled(void)448 ephy_sync_utils_history_sync_is_enabled (void)
449 {
450 return g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_HISTORY_ENABLED);
451 }
452
453 void
ephy_sync_utils_set_history_sync_time(gint64 time)454 ephy_sync_utils_set_history_sync_time (gint64 time)
455 {
456 g_settings_set_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_HISTORY_TIME, time);
457 }
458
459 gint64
ephy_sync_utils_get_history_sync_time(void)460 ephy_sync_utils_get_history_sync_time (void)
461 {
462 return g_settings_get_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_HISTORY_TIME);
463 }
464
465 void
ephy_sync_utils_set_history_sync_is_initial(gboolean is_initial)466 ephy_sync_utils_set_history_sync_is_initial (gboolean is_initial)
467 {
468 g_settings_set_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_HISTORY_INITIAL, is_initial);
469 }
470
471 gboolean
ephy_sync_utils_get_history_sync_is_initial(void)472 ephy_sync_utils_get_history_sync_is_initial (void)
473 {
474 return g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_HISTORY_INITIAL);
475 }
476
477 gboolean
ephy_sync_utils_open_tabs_sync_is_enabled(void)478 ephy_sync_utils_open_tabs_sync_is_enabled (void)
479 {
480 return g_settings_get_boolean (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_OPEN_TABS_ENABLED);
481 }
482
483 void
ephy_sync_utils_set_open_tabs_sync_time(gint64 time)484 ephy_sync_utils_set_open_tabs_sync_time (gint64 time)
485 {
486 g_settings_set_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_OPEN_TABS_TIME, time);
487 }
488
489 gint64
ephy_sync_utils_get_open_tabs_sync_time(void)490 ephy_sync_utils_get_open_tabs_sync_time (void)
491 {
492 return g_settings_get_int64 (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_OPEN_TABS_TIME);
493 }
494
495 char *
ephy_sync_utils_get_token_server(void)496 ephy_sync_utils_get_token_server (void)
497 {
498 return g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_TOKEN_SERVER);
499 }
500
501 char *
ephy_sync_utils_get_accounts_server(void)502 ephy_sync_utils_get_accounts_server (void)
503 {
504 return g_settings_get_string (EPHY_SETTINGS_SYNC, EPHY_PREFS_SYNC_ACCOUNTS_SERVER);
505 }
506