From 7a295e2c5db878dbd076c2f582cbae8aa738c3b7 Mon Sep 17 00:00:00 2001 From: Xan Lopez Date: Tue, 15 Dec 2009 10:31:47 +0100 Subject: Move profile migration tools from src/ to lib/ We'll use them from embed/ for form password saving, and embed/ can't use code from src/ --- lib/Makefile.am | 14 ++ lib/ephy-nss-glue.c | 309 ++++++++++++++++++++++++++++++++ lib/ephy-nss-glue.h | 29 +++ lib/ephy-profile-migration.c | 410 +++++++++++++++++++++++++++++++++++++++++++ lib/ephy-profile-migration.h | 25 +++ src/Makefile.am | 16 +- src/ephy-nss-glue.c | 309 -------------------------------- src/ephy-nss-glue.h | 29 --- src/ephy-profile-migration.c | 410 ------------------------------------------- src/ephy-profile-migration.h | 25 --- 10 files changed, 788 insertions(+), 788 deletions(-) create mode 100644 lib/ephy-nss-glue.c create mode 100644 lib/ephy-nss-glue.h create mode 100644 lib/ephy-profile-migration.c create mode 100644 lib/ephy-profile-migration.h delete mode 100644 src/ephy-nss-glue.c delete mode 100644 src/ephy-nss-glue.h delete mode 100644 src/ephy-profile-migration.c delete mode 100644 src/ephy-profile-migration.h diff --git a/lib/Makefile.am b/lib/Makefile.am index 3ea7e1c07..58c363a73 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -20,6 +20,7 @@ NOINST_H_FILES = \ ephy-node-common.h \ ephy-object-helpers.h \ ephy-prefs.h \ + ephy-profile-migration.h \ ephy-print-utils.h \ ephy-shlib-loader.h \ ephy-signal-accumulator.h \ @@ -57,6 +58,7 @@ libephymisc_la_SOURCES = \ ephy-node-db.c \ ephy-object-helpers.c \ ephy-prefs.h \ + ephy-profile-migration.c \ ephy-print-utils.c \ ephy-shlib-loader.c \ ephy-signal-accumulator.c \ @@ -98,6 +100,18 @@ libephymisc_la_LIBADD += \ $(SPELLCHECKER_LIBS) endif +if ENABLE_NSS +NOINST_H_FILES += \ + ephy-nss-glue.h \ + $(NULL) + +libephymisc_la_SOURCES += \ + ephy-nss-glue.c + $(NULL) + +libephymisc_la_CFLAGS += $(NSS_CFLAGS) +endif # ENABLE_NSS + BUILT_SOURCES = \ ephy-lib-type-builtins.c \ ephy-lib-type-builtins.h \ diff --git a/lib/ephy-nss-glue.c b/lib/ephy-nss-glue.c new file mode 100644 index 000000000..e1c65b6c0 --- /dev/null +++ b/lib/ephy-nss-glue.c @@ -0,0 +1,309 @@ +/* + * Copyright © 2009 Igalia S.L. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Portions of this file based on Chromium code. + * License block as follows: + * + * Copyright (c) 2009 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * The LICENSE file from Chromium can be found in the LICENSE.chromium + * file. + */ + +#include "config.h" + +#include "ephy-nss-glue.h" + +#include "ephy-file-helpers.h" + +#include +#include +#include +#include + +static gboolean nss_initialized = FALSE; +static PK11SlotInfo *db_slot = NULL; + +static char* +ask_for_nss_password (PK11SlotInfo *slot, + PRBool retry, + void *arg) +{ + GtkWidget *dialog; + GtkWidget *entry; + gint result; + char *password = NULL; + + if (retry) + return NULL; + + dialog = gtk_message_dialog_new (NULL, + 0, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_OK_CANCEL, + _("Master password needed")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("The passwords from the previous version (Gecko) are locked with a master password. If you want Epiphany to import them, please enter your master password below.")); + entry = gtk_entry_new (); + gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); + gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + entry); + gtk_widget_show (entry); + + result = gtk_dialog_run (GTK_DIALOG (dialog)); + + switch (result) { + case GTK_RESPONSE_OK: + password = PL_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); + break; + default: + break; + } + + gtk_widget_destroy (dialog); + return password; +} + +gboolean ephy_nss_glue_init () +{ + char *config_dir, *modspec; + SECStatus rv; + + config_dir = g_build_filename (ephy_dot_dir (), + "mozilla", "epiphany", + NULL); + rv = NSS_Init (config_dir); + + if (rv < 0) { + g_free (config_dir); + return FALSE; + } + + modspec = g_strdup_printf ("configDir='%s' tokenDescription='Firefox NSS database' " + "flags=readOnly", config_dir); + db_slot = SECMOD_OpenUserDB (modspec); + g_free (config_dir); + g_free (modspec); + + if (!db_slot) + return FALSE; + + /* It's possibly to set a master password for NSS through the + certificate manager extension, so we must support that too */ + PK11_SetPasswordFunc (ask_for_nss_password); + + nss_initialized = TRUE; + + return TRUE; +} + +void ephy_nss_glue_close () +{ + if (db_slot) { + PK11_FreeSlot (db_slot); + db_slot = NULL; + } + + PK11_SetPasswordFunc (NULL); + + NSS_Shutdown (); + + nss_initialized = FALSE; +} + +typedef struct SDRResult +{ + SECItem keyid; + SECAlgorithmID alg; + SECItem data; +} SDRResult; + +static SEC_ASN1Template g_template[] = { + { SEC_ASN1_SEQUENCE, 0, NULL, sizeof (SDRResult) }, + { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, keyid) }, + { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(SDRResult, alg), + SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, + { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, data) }, + { 0 } +}; + +static SECStatus +unpadBlock(SECItem *data, int blockSize, SECItem *result) +{ + SECStatus rv = SECSuccess; + int padLength; + int i; + + result->data = 0; + result->len = 0; + + /* Remove the padding from the end if the input data */ + if (data->len == 0 || data->len % blockSize != 0) { rv = SECFailure; goto loser; } + + padLength = data->data[data->len-1]; + if (padLength > blockSize) { rv = SECFailure; goto loser; } + + /* verify padding */ + for (i=data->len - padLength; (uint32)i < data->len; i++) { + if (data->data[i] != padLength) { + rv = SECFailure; + goto loser; + } + } + + result->len = data->len - padLength; + result->data = (unsigned char *)PORT_Alloc(result->len); + if (!result->data) { rv = SECFailure; goto loser; } + + PORT_Memcpy(result->data, data->data, result->len); + + if (padLength < 2) { + /* Chromium returns an error here, but it seems to be harmless and + if we continue we'll be able to import the password + correctly */ + /* return SECWouldBlock; */ + } + +loser: + return rv; +} + +static SECStatus +pk11Decrypt (PK11SlotInfo *slot, PLArenaPool *arena, + CK_MECHANISM_TYPE type, PK11SymKey *key, + SECItem *params, SECItem *in, SECItem *result) +{ + PK11Context *ctx = 0; + SECItem paddedResult; + SECStatus rv; + + paddedResult.len = 0; + paddedResult.data = 0; + + ctx = PK11_CreateContextBySymKey (type, CKA_DECRYPT, key, params); + if (!ctx) { + rv = SECFailure; + goto loser; + } + + paddedResult.len = in->len; + paddedResult.data = (unsigned char*)PORT_ArenaAlloc (arena, paddedResult.len); + + rv = PK11_CipherOp (ctx, paddedResult.data, + (int*)&paddedResult.len, paddedResult.len, + in->data, in->len); + if (rv != SECSuccess) + goto loser; + + PK11_Finalize(ctx); + + /* Remove the padding */ + rv = unpadBlock (&paddedResult, PK11_GetBlockSize(type, 0), result); + if (rv) + goto loser; + +loser: + if (ctx) + PK11_DestroyContext (ctx, PR_TRUE); + + return rv; +} + +static SECStatus +PK11SDR_DecryptWithSlot (PK11SlotInfo *slot, SECItem *data, SECItem *result, void *cx) +{ + SECStatus rv = SECSuccess; + PK11SymKey *key = NULL; + CK_MECHANISM_TYPE type; + SDRResult sdrResult; + SECItem *params = NULL; + SECItem possibleResult = { (SECItemType)0, NULL, 0 }; + PLArenaPool *arena = NULL; + + arena = PORT_NewArena (SEC_ASN1_DEFAULT_ARENA_SIZE); + if (!arena) { + rv = SECFailure; + goto loser; + } + + /* Decode the incoming data */ + memset (&sdrResult, 0, sizeof sdrResult); + rv = SEC_QuickDERDecodeItem (arena, &sdrResult, g_template, data); + if (rv != SECSuccess) + goto loser; /* Invalid format */ + + /* Get the parameter values from the data */ + params = PK11_ParamFromAlgid (&sdrResult.alg); + if (!params) { + rv = SECFailure; + goto loser; + } + + /* Use triple-DES (Should look up the algorithm) */ + type = CKM_DES3_CBC; + key = PK11_FindFixedKey (slot, type, &sdrResult.keyid, cx); + if (!key) { + rv = SECFailure; + } else { + rv = pk11Decrypt (slot, arena, type, key, params, + &sdrResult.data, result); + } + + loser: + if (arena) + PORT_FreeArena (arena, PR_TRUE); + + if (key) + PK11_FreeSymKey(key); + + if (params) + SECITEM_ZfreeItem(params, PR_TRUE); + + if (possibleResult.data) + SECITEM_ZfreeItem(&possibleResult, PR_FALSE); + + return rv; +} + +char * ephy_nss_glue_decrypt (const unsigned char *data, gsize length) +{ + char *plain = NULL; + SECItem request, reply; + SECStatus result; + + result = PK11_Authenticate (db_slot, PR_TRUE, NULL); + if (result != SECSuccess) + return NULL; + + request.data = (unsigned char*)data; + request.len = length; + reply.data = NULL; + reply.len = 0; + + result = PK11SDR_DecryptWithSlot (db_slot, &request, &reply, NULL); + if (result == SECSuccess) + plain = g_strndup ((const char*)reply.data, reply.len); + + SECITEM_FreeItem (&reply, PR_FALSE); + + return plain; +} + diff --git a/lib/ephy-nss-glue.h b/lib/ephy-nss-glue.h new file mode 100644 index 000000000..0c157226a --- /dev/null +++ b/lib/ephy-nss-glue.h @@ -0,0 +1,29 @@ +/* + * Copyright © 2009 Igalia S.L. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef EPHY_NSS_GLUE_H +#define EPHY_NSS_GLUE_H + +#include + +gboolean ephy_nss_glue_init (void); +void ephy_nss_glue_close (void); +char * ephy_nss_glue_decrypt (const unsigned char *, gsize); + +#endif diff --git a/lib/ephy-profile-migration.c b/lib/ephy-profile-migration.c new file mode 100644 index 000000000..c0c319d04 --- /dev/null +++ b/lib/ephy-profile-migration.c @@ -0,0 +1,410 @@ +/* + * Copyright © 2009 Xan López + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* Portions of this file based on Chromium code. + * License block as follows: + * + * Copyright (c) 2009 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * The LICENSE file from Chromium can be found in the LICENSE.chromium + * file. + */ + +#include "config.h" + +#include "ephy-profile-migration.h" + +#include "ephy-file-helpers.h" +#ifdef ENABLE_NSS +#include "ephy-nss-glue.h" +#endif + +#include +#include +#include + +/* + * What to do to add new migration steps: + * - Bump PROFILE_MIGRATION_VERSION + * - Add your function at the end of the 'migrators' array + */ + +#define PROFILE_MIGRATION_VERSION 3 + +typedef void (*EphyProfileMigrator) (void); + +static void +migrate_cookies () +{ + const char *cookies_file_sqlite = "cookies.sqlite"; + const char *cookies_file_txt = "cookies.txt"; + char *src_sqlite = NULL, *src_txt = NULL, *dest = NULL; + + dest = g_build_filename (ephy_dot_dir (), cookies_file_sqlite, NULL); + /* If we already have a cookies.sqlite file, do nothing */ + if (g_file_test (dest, G_FILE_TEST_EXISTS)) + goto out; + + src_sqlite = g_build_filename (ephy_dot_dir (), "mozilla", + "epiphany", cookies_file_sqlite, NULL); + src_txt = g_build_filename (ephy_dot_dir (), "mozilla", + "epiphany", cookies_file_txt, NULL); + + /* First check if we have a cookies.sqlite file in Mozilla */ + if (g_file_test (src_sqlite, G_FILE_TEST_EXISTS)) { + GFile *gsrc, *gdest; + + /* Copy the file */ + gsrc = g_file_new_for_path (src_sqlite); + gdest = g_file_new_for_path (dest); + + if (!g_file_copy (gsrc, gdest, 0, NULL, NULL, NULL, NULL)) + g_warning (_("Failed to copy cookies file from Mozilla.")); + + g_object_unref (gsrc); + g_object_unref (gdest); + } else if (g_file_test (src_txt, G_FILE_TEST_EXISTS)) { + /* Create a SoupCookieJarSQLite with the contents of the txt file */ + GSList *cookies, *p; + SoupCookieJar *txt, *sqlite; + + txt = soup_cookie_jar_text_new (src_txt, TRUE); + sqlite = soup_cookie_jar_sqlite_new (dest, FALSE); + cookies = soup_cookie_jar_all_cookies (txt); + + for (p = cookies; p; p = p->next) { + SoupCookie *cookie = (SoupCookie*)p->data; + /* Cookie is stolen, so we won't free it */ + soup_cookie_jar_add_cookie (sqlite, cookie); + } + + g_slist_free (cookies); + g_object_unref (txt); + g_object_unref (sqlite); + } + + out: + g_free (src_sqlite); + g_free (src_txt); + g_free (dest); +} + +#ifdef ENABLE_NSS +static gchar* +_g_utf8_substr(const gchar* string, gint start, gint end) +{ + gchar *start_ptr, *output; + gsize len_in_bytes; + glong str_len = g_utf8_strlen (string, -1); + + if (start > str_len || end > str_len) + return NULL; + + start_ptr = g_utf8_offset_to_pointer (string, start); + len_in_bytes = g_utf8_offset_to_pointer (string, end) - start_ptr + 1; + output = g_malloc0 (len_in_bytes + 1); + + return g_utf8_strncpy (output, start_ptr, end - start + 1); +} + +static char* +decrypt (const char *data) +{ + unsigned char *plain; + gsize out_len; + + /* The old style password is encoded in base64. They are identified + * by a leading '~'. Otherwise, we should decrypt the text. + */ + plain = g_base64_decode (data, &out_len); + if (data[0] != '~') { + char *decrypted; + + decrypted = ephy_nss_glue_decrypt (plain, out_len); + g_free (plain); + plain = (unsigned char*)decrypted; + } + + return (char*)plain; +} + +static void +parse_and_decrypt_signons (const char *signons) +{ + int version; + gchar **lines; + int i; + guint length; + + lines = g_strsplit (signons, "\r\n", -1); + if (!g_ascii_strncasecmp (lines[0], "#2c", 3)) + version = 1; + else if (!g_ascii_strncasecmp (lines[0], "#2d", 3)) + version = 2; + else if (!g_ascii_strncasecmp (lines[0], "#2e", 3)) + version = 3; + else + goto out; + + /* Skip the never-saved list */ + for (i = 1; lines[i] && !g_str_equal (lines[i], "."); i++) { + ; + } + + i++; + + /* + * Read saved passwords. The information is stored in blocks + * separated by lines that only contain a dot. We find a block by + * the separator and parse them one by one. + */ + length = g_strv_length (lines); + + while (i < length) { + size_t begin = i; + size_t end = i + 1; + const char *realmBracketBegin = " ("; + const char *realmBracketEnd = ")"; + SoupURI *uri = NULL; + char *realm = NULL; + + while (lines[end] && !g_str_equal (lines[end], ".")) + end++; + + i = end + 1; + + /* A block has at least five lines */ + if (end - begin < 5) + continue; + + /* The first line is the site URL. + * For HTTP authentication logins, the URL may contain http realm, + * which will be in bracket: + * sitename:8080 (realm) + */ + if (g_strstr_len (lines[begin], -1, realmBracketBegin)) { + char *start_ptr, *end_ptr; + char *full_url, *url; + glong start, end; + + /* In this case the scheme may not exist. We assume that the + * scheme is HTTP. + */ + if (!g_strstr_len (lines[begin], -1, "://")) + full_url = g_strconcat ("http://", lines[begin], NULL); + else + full_url = g_strdup (lines[begin]); + + start_ptr = g_strstr_len (full_url, -1, realmBracketBegin); + start = g_utf8_pointer_to_offset (full_url, start_ptr); + url = _g_utf8_substr (full_url, 0, start); + url = g_strstrip (url); + uri = soup_uri_new (url); + g_free (url); + + start += strlen (realmBracketBegin); + end_ptr = g_strstr_len (full_url, -1, realmBracketEnd); + end = g_utf8_pointer_to_offset (full_url, end_ptr); + realm = _g_utf8_substr (full_url, start, end); + + g_free (full_url); + } else { + /* Don't have HTTP realm. It is the URL that the following + * password belongs to. + */ + uri = soup_uri_new (lines[begin]); + } + + if (!SOUP_URI_VALID_FOR_HTTP (uri)) { + soup_uri_free (uri); + g_free (realm); + continue; + } + + ++begin; + + /* There may be multiple username/password pairs for this site. + * In this case, they are saved in one block without a separated + * line (contains a dot). + */ + while (begin + 4 < end) { + char *username = NULL; + char *password = NULL; + guint32 item_id; + + /* The username */ + begin++; /* Skip username element */ + username = decrypt (lines[begin++]); + + /* The password */ + /* The element name has a leading '*' */ + if (lines[begin][0] == '*') { + begin++; /* Skip password element */ + password = decrypt (lines[begin++]); + } else { + /* Maybe the file is broken, skip to the next block */ + g_free (username); + break; + } + + /* The action attribute for from the form element. This line + * exists in version 2 or above + */ + if (version >= 2) { + if (begin < end) + /* Skip it */ ; + begin++; + + /* Version 3 has an extra line for further use */ + if (version == 3) + begin++; + } + + if (username && password) + gnome_keyring_set_network_password_sync (NULL, + username, + realm, + uri->host, + NULL, + uri->scheme, + NULL, + uri->port, + password, + &item_id); + g_free (username); + g_free (password); + } + + soup_uri_free (uri); + g_free (realm); + } + + out: + g_strfreev (lines); +} +#endif + +static void +migrate_passwords () +{ +#ifdef ENABLE_NSS + char *dest, *contents, *gecko_passwords_backup; + gsize length; + GError *error = NULL; + + dest = g_build_filename (ephy_dot_dir (), + "mozilla", "epiphany", "signons3.txt", + NULL); + if (!g_file_test (dest, G_FILE_TEST_EXISTS)) { + g_free (dest); + dest = g_build_filename (ephy_dot_dir (), + "mozilla", "epiphany", "signons2.txt", + NULL); + if (!g_file_test (dest, G_FILE_TEST_EXISTS)) { + g_free (dest); + return; + } + } + + if (!ephy_nss_glue_init ()) + return; + + if (!g_file_get_contents (dest, &contents, &length, &error)) { + g_free (dest); + } + + parse_and_decrypt_signons (contents); + + /* Save the contents in a backup directory for future data + extraction when we support more features */ + gecko_passwords_backup = g_build_filename (ephy_dot_dir (), + "gecko-passwords.txt", NULL); + + if (!g_file_set_contents (gecko_passwords_backup, contents, + -1, &error)) { + g_error_free (error); + } + + g_free (gecko_passwords_backup); + g_free (contents); + + ephy_nss_glue_close (); +#endif +} + +const EphyProfileMigrator migrators[] = { + migrate_cookies, + migrate_passwords, + /* Yes, again! Version 2 had some bugs, so we need to run + migrate_passwords again to possibly migrate more passwords*/ + migrate_passwords +}; + +#define PROFILE_MIGRATION_FILE ".migrated" + +void +_ephy_profile_migrate () +{ + int latest, i; + char *migrated_file, *contents; + + /* Figure out the latest migration that occured */ + migrated_file = g_build_filename (ephy_dot_dir (), + PROFILE_MIGRATION_FILE, + NULL); + if (g_file_test (migrated_file, G_FILE_TEST_EXISTS)) { + gsize size; + int result; + + g_file_get_contents (migrated_file, &contents, &size, NULL); + result = sscanf(contents, "%d", &latest); + g_free (contents); + + if (result != 1) { + g_warning (_("Failed to read latest migration marker, aborting profile migration.")); + g_free (migrated_file); + return; + } + } else + /* Never migrated */ + latest = 0; + + for (i = latest; i < PROFILE_MIGRATION_VERSION; i++) { + EphyProfileMigrator m; + + /* No need to run the password migration twice in a row. It + appears twice in the list for the benefit of people that were + using the development snapshots, since an early version didn't + migrate all passwords correctly. */ + if (i == 1) + continue; + + m = migrators[i]; + m(); + } + + /* Write down the latest migration */ + contents = g_strdup_printf ("%d", PROFILE_MIGRATION_VERSION); + g_file_set_contents (migrated_file, contents, -1, NULL); + g_free (contents); + g_free (migrated_file); +} + diff --git a/lib/ephy-profile-migration.h b/lib/ephy-profile-migration.h new file mode 100644 index 000000000..51114a88d --- /dev/null +++ b/lib/ephy-profile-migration.h @@ -0,0 +1,25 @@ +/* + * Copyright © 2009 Xan López + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef EPHY_PROFILE_MIGRATION_H +#define EPHY_PROFILE_MIGRATION_H + +void _ephy_profile_migrate (void); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index c8bc0d44a..2ba6f2392 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,7 +28,6 @@ NOINST_H_FILES = \ ephy-location-action.h \ ephy-navigation-action.h \ ephy-password-info.h \ - ephy-profile-migration.h \ ephy-tabs-menu.h \ ephy-toolbars-model.h \ ephy-toolbar.h \ @@ -75,8 +74,7 @@ libephymain_la_SOURCES = \ ephy-lockdown.c \ ephy-navigation-action.c \ ephy-notebook.c \ - ephy-password-info.c \ - ephy-profile-migration.c \ + ephy-password-info.c \ ephy-session.c \ ephy-shell.c \ ephy-statusbar.c \ @@ -140,18 +138,6 @@ libephymain_la_SOURCES += \ libephymain_la_CFLAGS += $(SEED_CFLAGS) endif # ENABLE_SEED -if ENABLE_NSS -NOINST_H_FILES += \ - ephy-nss-glue.h \ - $(NULL) - -libephymain_la_SOURCES += \ - ephy-nss-glue.c - $(NULL) - -libephymain_la_CFLAGS += $(NSS_CFLAGS) -endif # ENABLE_NSS - epiphany_SOURCES = ephy-main.c epiphany_CPPFLAGS = \ diff --git a/src/ephy-nss-glue.c b/src/ephy-nss-glue.c deleted file mode 100644 index e1c65b6c0..000000000 --- a/src/ephy-nss-glue.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright © 2009 Igalia S.L. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -/* Portions of this file based on Chromium code. - * License block as follows: - * - * Copyright (c) 2009 The Chromium Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - * - * The LICENSE file from Chromium can be found in the LICENSE.chromium - * file. - */ - -#include "config.h" - -#include "ephy-nss-glue.h" - -#include "ephy-file-helpers.h" - -#include -#include -#include -#include - -static gboolean nss_initialized = FALSE; -static PK11SlotInfo *db_slot = NULL; - -static char* -ask_for_nss_password (PK11SlotInfo *slot, - PRBool retry, - void *arg) -{ - GtkWidget *dialog; - GtkWidget *entry; - gint result; - char *password = NULL; - - if (retry) - return NULL; - - dialog = gtk_message_dialog_new (NULL, - 0, - GTK_MESSAGE_QUESTION, - GTK_BUTTONS_OK_CANCEL, - _("Master password needed")); - gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), - _("The passwords from the previous version (Gecko) are locked with a master password. If you want Epiphany to import them, please enter your master password below.")); - entry = gtk_entry_new (); - gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); - gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), - entry); - gtk_widget_show (entry); - - result = gtk_dialog_run (GTK_DIALOG (dialog)); - - switch (result) { - case GTK_RESPONSE_OK: - password = PL_strdup (gtk_entry_get_text (GTK_ENTRY (entry))); - break; - default: - break; - } - - gtk_widget_destroy (dialog); - return password; -} - -gboolean ephy_nss_glue_init () -{ - char *config_dir, *modspec; - SECStatus rv; - - config_dir = g_build_filename (ephy_dot_dir (), - "mozilla", "epiphany", - NULL); - rv = NSS_Init (config_dir); - - if (rv < 0) { - g_free (config_dir); - return FALSE; - } - - modspec = g_strdup_printf ("configDir='%s' tokenDescription='Firefox NSS database' " - "flags=readOnly", config_dir); - db_slot = SECMOD_OpenUserDB (modspec); - g_free (config_dir); - g_free (modspec); - - if (!db_slot) - return FALSE; - - /* It's possibly to set a master password for NSS through the - certificate manager extension, so we must support that too */ - PK11_SetPasswordFunc (ask_for_nss_password); - - nss_initialized = TRUE; - - return TRUE; -} - -void ephy_nss_glue_close () -{ - if (db_slot) { - PK11_FreeSlot (db_slot); - db_slot = NULL; - } - - PK11_SetPasswordFunc (NULL); - - NSS_Shutdown (); - - nss_initialized = FALSE; -} - -typedef struct SDRResult -{ - SECItem keyid; - SECAlgorithmID alg; - SECItem data; -} SDRResult; - -static SEC_ASN1Template g_template[] = { - { SEC_ASN1_SEQUENCE, 0, NULL, sizeof (SDRResult) }, - { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, keyid) }, - { SEC_ASN1_INLINE | SEC_ASN1_XTRN, offsetof(SDRResult, alg), - SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) }, - { SEC_ASN1_OCTET_STRING, offsetof(SDRResult, data) }, - { 0 } -}; - -static SECStatus -unpadBlock(SECItem *data, int blockSize, SECItem *result) -{ - SECStatus rv = SECSuccess; - int padLength; - int i; - - result->data = 0; - result->len = 0; - - /* Remove the padding from the end if the input data */ - if (data->len == 0 || data->len % blockSize != 0) { rv = SECFailure; goto loser; } - - padLength = data->data[data->len-1]; - if (padLength > blockSize) { rv = SECFailure; goto loser; } - - /* verify padding */ - for (i=data->len - padLength; (uint32)i < data->len; i++) { - if (data->data[i] != padLength) { - rv = SECFailure; - goto loser; - } - } - - result->len = data->len - padLength; - result->data = (unsigned char *)PORT_Alloc(result->len); - if (!result->data) { rv = SECFailure; goto loser; } - - PORT_Memcpy(result->data, data->data, result->len); - - if (padLength < 2) { - /* Chromium returns an error here, but it seems to be harmless and - if we continue we'll be able to import the password - correctly */ - /* return SECWouldBlock; */ - } - -loser: - return rv; -} - -static SECStatus -pk11Decrypt (PK11SlotInfo *slot, PLArenaPool *arena, - CK_MECHANISM_TYPE type, PK11SymKey *key, - SECItem *params, SECItem *in, SECItem *result) -{ - PK11Context *ctx = 0; - SECItem paddedResult; - SECStatus rv; - - paddedResult.len = 0; - paddedResult.data = 0; - - ctx = PK11_CreateContextBySymKey (type, CKA_DECRYPT, key, params); - if (!ctx) { - rv = SECFailure; - goto loser; - } - - paddedResult.len = in->len; - paddedResult.data = (unsigned char*)PORT_ArenaAlloc (arena, paddedResult.len); - - rv = PK11_CipherOp (ctx, paddedResult.data, - (int*)&paddedResult.len, paddedResult.len, - in->data, in->len); - if (rv != SECSuccess) - goto loser; - - PK11_Finalize(ctx); - - /* Remove the padding */ - rv = unpadBlock (&paddedResult, PK11_GetBlockSize(type, 0), result); - if (rv) - goto loser; - -loser: - if (ctx) - PK11_DestroyContext (ctx, PR_TRUE); - - return rv; -} - -static SECStatus -PK11SDR_DecryptWithSlot (PK11SlotInfo *slot, SECItem *data, SECItem *result, void *cx) -{ - SECStatus rv = SECSuccess; - PK11SymKey *key = NULL; - CK_MECHANISM_TYPE type; - SDRResult sdrResult; - SECItem *params = NULL; - SECItem possibleResult = { (SECItemType)0, NULL, 0 }; - PLArenaPool *arena = NULL; - - arena = PORT_NewArena (SEC_ASN1_DEFAULT_ARENA_SIZE); - if (!arena) { - rv = SECFailure; - goto loser; - } - - /* Decode the incoming data */ - memset (&sdrResult, 0, sizeof sdrResult); - rv = SEC_QuickDERDecodeItem (arena, &sdrResult, g_template, data); - if (rv != SECSuccess) - goto loser; /* Invalid format */ - - /* Get the parameter values from the data */ - params = PK11_ParamFromAlgid (&sdrResult.alg); - if (!params) { - rv = SECFailure; - goto loser; - } - - /* Use triple-DES (Should look up the algorithm) */ - type = CKM_DES3_CBC; - key = PK11_FindFixedKey (slot, type, &sdrResult.keyid, cx); - if (!key) { - rv = SECFailure; - } else { - rv = pk11Decrypt (slot, arena, type, key, params, - &sdrResult.data, result); - } - - loser: - if (arena) - PORT_FreeArena (arena, PR_TRUE); - - if (key) - PK11_FreeSymKey(key); - - if (params) - SECITEM_ZfreeItem(params, PR_TRUE); - - if (possibleResult.data) - SECITEM_ZfreeItem(&possibleResult, PR_FALSE); - - return rv; -} - -char * ephy_nss_glue_decrypt (const unsigned char *data, gsize length) -{ - char *plain = NULL; - SECItem request, reply; - SECStatus result; - - result = PK11_Authenticate (db_slot, PR_TRUE, NULL); - if (result != SECSuccess) - return NULL; - - request.data = (unsigned char*)data; - request.len = length; - reply.data = NULL; - reply.len = 0; - - result = PK11SDR_DecryptWithSlot (db_slot, &request, &reply, NULL); - if (result == SECSuccess) - plain = g_strndup ((const char*)reply.data, reply.len); - - SECITEM_FreeItem (&reply, PR_FALSE); - - return plain; -} - diff --git a/src/ephy-nss-glue.h b/src/ephy-nss-glue.h deleted file mode 100644 index 0c157226a..000000000 --- a/src/ephy-nss-glue.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © 2009 Igalia S.L. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef EPHY_NSS_GLUE_H -#define EPHY_NSS_GLUE_H - -#include - -gboolean ephy_nss_glue_init (void); -void ephy_nss_glue_close (void); -char * ephy_nss_glue_decrypt (const unsigned char *, gsize); - -#endif diff --git a/src/ephy-profile-migration.c b/src/ephy-profile-migration.c deleted file mode 100644 index c0c319d04..000000000 --- a/src/ephy-profile-migration.c +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright © 2009 Xan López - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -/* Portions of this file based on Chromium code. - * License block as follows: - * - * Copyright (c) 2009 The Chromium Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - * - * The LICENSE file from Chromium can be found in the LICENSE.chromium - * file. - */ - -#include "config.h" - -#include "ephy-profile-migration.h" - -#include "ephy-file-helpers.h" -#ifdef ENABLE_NSS -#include "ephy-nss-glue.h" -#endif - -#include -#include -#include - -/* - * What to do to add new migration steps: - * - Bump PROFILE_MIGRATION_VERSION - * - Add your function at the end of the 'migrators' array - */ - -#define PROFILE_MIGRATION_VERSION 3 - -typedef void (*EphyProfileMigrator) (void); - -static void -migrate_cookies () -{ - const char *cookies_file_sqlite = "cookies.sqlite"; - const char *cookies_file_txt = "cookies.txt"; - char *src_sqlite = NULL, *src_txt = NULL, *dest = NULL; - - dest = g_build_filename (ephy_dot_dir (), cookies_file_sqlite, NULL); - /* If we already have a cookies.sqlite file, do nothing */ - if (g_file_test (dest, G_FILE_TEST_EXISTS)) - goto out; - - src_sqlite = g_build_filename (ephy_dot_dir (), "mozilla", - "epiphany", cookies_file_sqlite, NULL); - src_txt = g_build_filename (ephy_dot_dir (), "mozilla", - "epiphany", cookies_file_txt, NULL); - - /* First check if we have a cookies.sqlite file in Mozilla */ - if (g_file_test (src_sqlite, G_FILE_TEST_EXISTS)) { - GFile *gsrc, *gdest; - - /* Copy the file */ - gsrc = g_file_new_for_path (src_sqlite); - gdest = g_file_new_for_path (dest); - - if (!g_file_copy (gsrc, gdest, 0, NULL, NULL, NULL, NULL)) - g_warning (_("Failed to copy cookies file from Mozilla.")); - - g_object_unref (gsrc); - g_object_unref (gdest); - } else if (g_file_test (src_txt, G_FILE_TEST_EXISTS)) { - /* Create a SoupCookieJarSQLite with the contents of the txt file */ - GSList *cookies, *p; - SoupCookieJar *txt, *sqlite; - - txt = soup_cookie_jar_text_new (src_txt, TRUE); - sqlite = soup_cookie_jar_sqlite_new (dest, FALSE); - cookies = soup_cookie_jar_all_cookies (txt); - - for (p = cookies; p; p = p->next) { - SoupCookie *cookie = (SoupCookie*)p->data; - /* Cookie is stolen, so we won't free it */ - soup_cookie_jar_add_cookie (sqlite, cookie); - } - - g_slist_free (cookies); - g_object_unref (txt); - g_object_unref (sqlite); - } - - out: - g_free (src_sqlite); - g_free (src_txt); - g_free (dest); -} - -#ifdef ENABLE_NSS -static gchar* -_g_utf8_substr(const gchar* string, gint start, gint end) -{ - gchar *start_ptr, *output; - gsize len_in_bytes; - glong str_len = g_utf8_strlen (string, -1); - - if (start > str_len || end > str_len) - return NULL; - - start_ptr = g_utf8_offset_to_pointer (string, start); - len_in_bytes = g_utf8_offset_to_pointer (string, end) - start_ptr + 1; - output = g_malloc0 (len_in_bytes + 1); - - return g_utf8_strncpy (output, start_ptr, end - start + 1); -} - -static char* -decrypt (const char *data) -{ - unsigned char *plain; - gsize out_len; - - /* The old style password is encoded in base64. They are identified - * by a leading '~'. Otherwise, we should decrypt the text. - */ - plain = g_base64_decode (data, &out_len); - if (data[0] != '~') { - char *decrypted; - - decrypted = ephy_nss_glue_decrypt (plain, out_len); - g_free (plain); - plain = (unsigned char*)decrypted; - } - - return (char*)plain; -} - -static void -parse_and_decrypt_signons (const char *signons) -{ - int version; - gchar **lines; - int i; - guint length; - - lines = g_strsplit (signons, "\r\n", -1); - if (!g_ascii_strncasecmp (lines[0], "#2c", 3)) - version = 1; - else if (!g_ascii_strncasecmp (lines[0], "#2d", 3)) - version = 2; - else if (!g_ascii_strncasecmp (lines[0], "#2e", 3)) - version = 3; - else - goto out; - - /* Skip the never-saved list */ - for (i = 1; lines[i] && !g_str_equal (lines[i], "."); i++) { - ; - } - - i++; - - /* - * Read saved passwords. The information is stored in blocks - * separated by lines that only contain a dot. We find a block by - * the separator and parse them one by one. - */ - length = g_strv_length (lines); - - while (i < length) { - size_t begin = i; - size_t end = i + 1; - const char *realmBracketBegin = " ("; - const char *realmBracketEnd = ")"; - SoupURI *uri = NULL; - char *realm = NULL; - - while (lines[end] && !g_str_equal (lines[end], ".")) - end++; - - i = end + 1; - - /* A block has at least five lines */ - if (end - begin < 5) - continue; - - /* The first line is the site URL. - * For HTTP authentication logins, the URL may contain http realm, - * which will be in bracket: - * sitename:8080 (realm) - */ - if (g_strstr_len (lines[begin], -1, realmBracketBegin)) { - char *start_ptr, *end_ptr; - char *full_url, *url; - glong start, end; - - /* In this case the scheme may not exist. We assume that the - * scheme is HTTP. - */ - if (!g_strstr_len (lines[begin], -1, "://")) - full_url = g_strconcat ("http://", lines[begin], NULL); - else - full_url = g_strdup (lines[begin]); - - start_ptr = g_strstr_len (full_url, -1, realmBracketBegin); - start = g_utf8_pointer_to_offset (full_url, start_ptr); - url = _g_utf8_substr (full_url, 0, start); - url = g_strstrip (url); - uri = soup_uri_new (url); - g_free (url); - - start += strlen (realmBracketBegin); - end_ptr = g_strstr_len (full_url, -1, realmBracketEnd); - end = g_utf8_pointer_to_offset (full_url, end_ptr); - realm = _g_utf8_substr (full_url, start, end); - - g_free (full_url); - } else { - /* Don't have HTTP realm. It is the URL that the following - * password belongs to. - */ - uri = soup_uri_new (lines[begin]); - } - - if (!SOUP_URI_VALID_FOR_HTTP (uri)) { - soup_uri_free (uri); - g_free (realm); - continue; - } - - ++begin; - - /* There may be multiple username/password pairs for this site. - * In this case, they are saved in one block without a separated - * line (contains a dot). - */ - while (begin + 4 < end) { - char *username = NULL; - char *password = NULL; - guint32 item_id; - - /* The username */ - begin++; /* Skip username element */ - username = decrypt (lines[begin++]); - - /* The password */ - /* The element name has a leading '*' */ - if (lines[begin][0] == '*') { - begin++; /* Skip password element */ - password = decrypt (lines[begin++]); - } else { - /* Maybe the file is broken, skip to the next block */ - g_free (username); - break; - } - - /* The action attribute for from the form element. This line - * exists in version 2 or above - */ - if (version >= 2) { - if (begin < end) - /* Skip it */ ; - begin++; - - /* Version 3 has an extra line for further use */ - if (version == 3) - begin++; - } - - if (username && password) - gnome_keyring_set_network_password_sync (NULL, - username, - realm, - uri->host, - NULL, - uri->scheme, - NULL, - uri->port, - password, - &item_id); - g_free (username); - g_free (password); - } - - soup_uri_free (uri); - g_free (realm); - } - - out: - g_strfreev (lines); -} -#endif - -static void -migrate_passwords () -{ -#ifdef ENABLE_NSS - char *dest, *contents, *gecko_passwords_backup; - gsize length; - GError *error = NULL; - - dest = g_build_filename (ephy_dot_dir (), - "mozilla", "epiphany", "signons3.txt", - NULL); - if (!g_file_test (dest, G_FILE_TEST_EXISTS)) { - g_free (dest); - dest = g_build_filename (ephy_dot_dir (), - "mozilla", "epiphany", "signons2.txt", - NULL); - if (!g_file_test (dest, G_FILE_TEST_EXISTS)) { - g_free (dest); - return; - } - } - - if (!ephy_nss_glue_init ()) - return; - - if (!g_file_get_contents (dest, &contents, &length, &error)) { - g_free (dest); - } - - parse_and_decrypt_signons (contents); - - /* Save the contents in a backup directory for future data - extraction when we support more features */ - gecko_passwords_backup = g_build_filename (ephy_dot_dir (), - "gecko-passwords.txt", NULL); - - if (!g_file_set_contents (gecko_passwords_backup, contents, - -1, &error)) { - g_error_free (error); - } - - g_free (gecko_passwords_backup); - g_free (contents); - - ephy_nss_glue_close (); -#endif -} - -const EphyProfileMigrator migrators[] = { - migrate_cookies, - migrate_passwords, - /* Yes, again! Version 2 had some bugs, so we need to run - migrate_passwords again to possibly migrate more passwords*/ - migrate_passwords -}; - -#define PROFILE_MIGRATION_FILE ".migrated" - -void -_ephy_profile_migrate () -{ - int latest, i; - char *migrated_file, *contents; - - /* Figure out the latest migration that occured */ - migrated_file = g_build_filename (ephy_dot_dir (), - PROFILE_MIGRATION_FILE, - NULL); - if (g_file_test (migrated_file, G_FILE_TEST_EXISTS)) { - gsize size; - int result; - - g_file_get_contents (migrated_file, &contents, &size, NULL); - result = sscanf(contents, "%d", &latest); - g_free (contents); - - if (result != 1) { - g_warning (_("Failed to read latest migration marker, aborting profile migration.")); - g_free (migrated_file); - return; - } - } else - /* Never migrated */ - latest = 0; - - for (i = latest; i < PROFILE_MIGRATION_VERSION; i++) { - EphyProfileMigrator m; - - /* No need to run the password migration twice in a row. It - appears twice in the list for the benefit of people that were - using the development snapshots, since an early version didn't - migrate all passwords correctly. */ - if (i == 1) - continue; - - m = migrators[i]; - m(); - } - - /* Write down the latest migration */ - contents = g_strdup_printf ("%d", PROFILE_MIGRATION_VERSION); - g_file_set_contents (migrated_file, contents, -1, NULL); - g_free (contents); - g_free (migrated_file); -} - diff --git a/src/ephy-profile-migration.h b/src/ephy-profile-migration.h deleted file mode 100644 index 51114a88d..000000000 --- a/src/ephy-profile-migration.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright © 2009 Xan López - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef EPHY_PROFILE_MIGRATION_H -#define EPHY_PROFILE_MIGRATION_H - -void _ephy_profile_migrate (void); - -#endif -- cgit