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