[epiphany] Import passwords from the ephy/gecko profile on first run.



commit eea4e5155ed3102ee21c57958a29603ed0f5166f
Author: Xan Lopez <xan gnome org>
Date:   Wed Aug 26 11:44:28 2009 +0300

    Import passwords from the ephy/gecko profile on first run.
    
    This feature requires NSS, so NSS is added as an optional dependency,
    enabled by default. Can be disabled with --disable-nss, but then
    passwords won't be imported.

 LICENSE.chromium             |   27 +++++
 configure.ac                 |   21 ++++
 src/Makefile.am              |   16 +++
 src/ephy-nss-glue.c          |  259 +++++++++++++++++++++++++++++++++++++++++
 src/ephy-nss-glue.h          |   29 +++++
 src/ephy-profile-migration.c |  262 +++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 611 insertions(+), 3 deletions(-)
---
diff --git a/LICENSE.chromium b/LICENSE.chromium
new file mode 100644
index 0000000..9314092
--- /dev/null
+++ b/LICENSE.chromium
@@ -0,0 +1,27 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/configure.ac b/configure.ac
index b06ecb7..b7171c4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -277,6 +277,26 @@ AM_CONDITIONAL([ENABLE_SEED],[test "$enable_seed" = "yes"])
 
 AC_SUBST([EPIPHANY_FEATURES])
 
+# ***
+# NSS
+# ***
+
+AC_MSG_CHECKING([whether NSS support is requested])
+AC_ARG_ENABLE([nss],
+        [AS_HELP_STRING([--enable-nss], [Enable NSS support (default: enabled)])],
+        [], [enable_nss=yes])
+AC_MSG_RESULT([$enable_nss])
+
+if test "$enable_nss" = "yes"; then
+   EPIPHANY_FEATURES="$EPIPHANY_FEATURES nss"
+   PKG_CHECK_MODULES([NSS], [nss])
+
+   AC_DEFINE([ENABLE_NSS], [1], [Define to compile with NSS support])
+fi
+
+AM_CONDITIONAL([ENABLE_NSS],[test "$enable_nss" = "yes"])
+AC_SUBST([EPIPHANY_FEATURES])
+
 # *******************
 # Additional features
 # *******************
@@ -482,5 +502,6 @@ Epiphany was configured with the following options:
  	NetworkManager support     : $enable_network_manager
  	GObject introspection      : $enable_introspection
  	Seed support               : $enable_seed
+ 	NSS support                : $enable_nss
  	Build tests                : $enable_tests
 "
diff --git a/src/Makefile.am b/src/Makefile.am
index 3303266..492a768 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -140,6 +140,18 @@ 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 = \
@@ -181,6 +193,10 @@ if ENABLE_SEED
 epiphany_LDADD += $(SEED_LIBS)
 endif # ENABLE_SEED
 
+if ENABLE_NSS
+epiphany_LDADD += $(NSS_LIBS)
+endif # ENABLE_NSS
+
 if ENABLE_NETWORK_MANAGER
 epiphany_LDADD += \
 	$(NETWORK_MANAGER_LIBS)
diff --git a/src/ephy-nss-glue.c b/src/ephy-nss-glue.c
new file mode 100644
index 0000000..a5f58cc
--- /dev/null
+++ b/src/ephy-nss-glue.c
@@ -0,0 +1,259 @@
+/*
+ *  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 <nss.h>
+#include <pk11pub.h>
+#include <pk11sdr.h>
+
+static gboolean nss_initialized = FALSE;
+static PK11SlotInfo *db_slot = NULL;
+
+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;
+
+  nss_initialized = TRUE;
+
+  return TRUE;
+}
+
+void ephy_nss_glue_close ()
+{
+  if (db_slot) {
+    PK11_FreeSlot (db_slot);
+    db_slot = 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) {
+    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
new file mode 100644
index 0000000..0c15722
--- /dev/null
+++ b/src/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 <glib.h>
+
+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
index 621aa53..777e8e6 100644
--- a/src/ephy-profile-migration.c
+++ b/src/ephy-profile-migration.c
@@ -17,12 +17,28 @@
  *
  */
 
+/* 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-file-helpers.h"
 #include "ephy-profile-migration.h"
 
+#include "ephy-file-helpers.h"
+#ifdef ENABLE_NSS
+#include "ephy-nss-glue.h"
+#endif
+
 #include <glib/gi18n.h>
+#include <gnome-keyring.h>
 #include <libsoup/soup-gnome.h>
 
 /*
@@ -31,7 +47,7 @@
  *  - Add your function at the end of the 'migrators' array
  */
 
-#define PROFILE_MIGRATION_VERSION 1
+#define PROFILE_MIGRATION_VERSION 2
 
 typedef void (*EphyProfileMigrator) (void);
 
@@ -91,8 +107,248 @@ migrate_cookies ()
   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;
+
+  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.
+   */
+  while (lines[i]) {
+    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);
+      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_cookies,
+  migrate_passwords
 };
 
 #define PROFILE_MIGRATION_FILE ".migrated"



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]