Merge branch 'debian' of www.project-moonshot.org:/srv/git/moonshot-ui into debian
authorDan Breslau <dbreslau@painless-security.com>
Fri, 28 Oct 2016 16:35:35 +0000 (12:35 -0400)
committerDan Breslau <dbreslau@painless-security.com>
Fri, 28 Oct 2016 16:35:35 +0000 (12:35 -0400)
35 files changed:
.gitignore [new file with mode: 0755]
Makefile.am
configure.ac
libmoonshot/libmoonshot-dbus.c
libmoonshot/libmoonshot.def [new file with mode: 0644]
libmoonshot/libmoonshot.h
libmoonshot/libmoonshot.vapi
po/POTFILES.in
src/moonshot-add-dialog.vala [deleted file]
src/moonshot-crypto-utils.c [new file with mode: 0644]
src/moonshot-custom-vbox.vala
src/moonshot-id.vala
src/moonshot-idcard-store.vala
src/moonshot-idcard-widget.vala
src/moonshot-identities-manager.vala
src/moonshot-identity-dialog.vala [new file with mode: 0644]
src/moonshot-identity-management-view.vala
src/moonshot-identity-manager-app.vala
src/moonshot-identity-request.vala
src/moonshot-keyring-store.vala
src/moonshot-local-flat-file-store.vala
src/moonshot-logger.vala [new file with mode: 0644]
src/moonshot-password-dialog.vala
src/moonshot-provisioning-common.vala
src/moonshot-server-linux.vala [new file with mode: 0644]
src/moonshot-server-msrpc.vala [new file with mode: 0644]
src/moonshot-server.vala [deleted file]
src/moonshot-settings.vala [new file with mode: 0644]
src/moonshot-trust-anchor-dialog.vala [new file with mode: 0644]
src/moonshot-utils.vala
src/moonshot-warning-dialog.vala [new file with mode: 0644]
src/moonshot-webp-parser.vala
webprovisioning/blank-test.msht [new file with mode: 0644]
webprovisioning/cert-test.msht [new file with mode: 0644]
webprovisioning/complex-test.msht

diff --git a/.gitignore b/.gitignore
new file mode 100755 (executable)
index 0000000..fbc5605
--- /dev/null
@@ -0,0 +1,125 @@
+*~
+stamp-h1
+*.stamp
+*.exe
+*.o
+.deps
+.dirstamp
+po/stamp-po
+po/remove-potcdate.*
+po/moonshot.pot
+po/insert-header.sin
+po/*.header
+po/*.sed
+po/[Rr]ules-quot
+po/POTFILES
+Makefile.in
+Makefile
+!examples/msvc/Makefile
+po/Makevars.template
+configure
+config.h*
+config.log
+config.status
+build-aux
+autom4te.cache
+aclocal.m4
+ABOUT-NLS
+.libs/
+*.la
+*.lo
+libtool
+src/moonshot-add-dialog.c
+src/moonshot-custom-vbox.c
+src/moonshot-id.c
+src/moonshot-idcard-widget.c
+src/moonshot-idcard-store.c
+src/moonshot-identity-dialog.c
+src/moonshot-identity-request.c
+src/moonshot-identity-management-view.c
+src/moonshot-identity-manager-app.c
+src/moonshot-identities-manager.c
+src/moonshot-keyring-store.c
+src/moonshot-local-flat-file-store.c
+src/moonshot-logger.c
+src/moonshot-password-dialog.c
+src/moonshot-provisioning-common-new.vala
+src/moonshot-provisioning-common.c
+src/moonshot-server-linux.c
+src/moonshot-server-msrpc.c
+src/moonshot-settings.c
+src/moonshot-trust-anchor-dialog.c
+src/moonshot-utils.c
+src/moonshot-warning-dialog.c
+src/moonshot-webp-parser.c
+src/moonshot-window.c
+src/msrpc-client.c
+src/dbus-client
+src/dbus-client.c
+src/moonshot
+src/moonshot-webp
+m4/codeset.m4
+m4/gettext.m4
+m4/glibc2.m4
+m4/glibc21.m4
+m4/iconv.m4
+m4/intdiv0.m4
+m4/intl.m4
+m4/intldir.m4
+m4/intlmacosx.m4
+m4/intmax.m4
+m4/inttypes-pri.m4
+m4/inttypes_h.m4
+m4/lcmessage.m4
+m4/lib-ld.m4
+m4/lib-link.m4
+m4/lib-prefix.m4
+m4/libtool.m4
+m4/lock.m4
+m4/longlong.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
+m4/nls.m4
+m4/po.m4
+m4/printf-posix.m4
+m4/progtest.m4
+m4/size_max.m4
+m4/stdint_h.m4
+m4/uintmax_t.m4
+m4/visibility.m4
+m4/wchar_t.m4
+m4/wint_t.m4
+m4/xsize.m4
+moonshot.msi
+moonshot.wixpdb
+po/Makefile.in.in
+moonshot-ui.spec
+
+app.wixobj
+org.janet.Moonshot.service
+share/
+moonshot.desktop
+moonshot-webp.desktop
+libmoonshot/moonshot-msrpc.h
+libmoonshot/moonshot-msrpc_c.c
+libmoonshot/moonshot-msrpc_s.c
+libmoonshot/libmoonshot.exp
+libmoonshot/libmoonshot.lib
+*.tar.gz
+
+*.tar.bz2
+*.stackdump
+
+*.sav
+*.save
+*.patch
+*.tar.xz
+
+examples/client
+examples/service-selection
+
+po/moonshot-ui.pot
+
+tests/basic
\ No newline at end of file
index cee5250..0335016 100644 (file)
@@ -24,19 +24,29 @@ AM_CPPFLAGS =  \
        -I$(top_srcdir)/libmoonshot \
        -I$(top_builddir)/libmoonshot
 
+
+if LOG4VALA
+MOONSHOT_LOG_PKG= --pkg log4vala-0.1 -X -llog4vala-0.1 --define=USE_LOG4VALA
+MOONSHOT_LOG_LIBS= -llog4vala-0.1
+else
+MOONSHOT_LOG_PKG=
+MOONSHOT_LOG_LIBS=
+endif
+
+
 AM_VALAFLAGS = -g \
         config.vapi \
-       --vapidir=$(top_srcdir)/vapi \
-        --pkg gio-2.0
-
+        --vapidir=$(top_srcdir)/vapi \
+        --pkg gio-2.0 \
+        $(MOONSHOT_LOG_PKG)
 
 libmoonshot_libmoonshot_la_CPPFLAGS = \
         $(libmoonshot_CFLAGS) \
-        $(AM_CPPFLAGS)  $(GEE_CFLAGS)
+        $(AM_CPPFLAGS) 
 
 libmoonshot_libmoonshot_la_SOURCES = libmoonshot/libmoonshot-common.c
 
-libmoonshot_libmoonshot_la_LIBADD = $(libmoonshot_LIBS) $(GEE_LIBS)
+libmoonshot_libmoonshot_la_LIBADD = $(libmoonshot_LIBS)
 libmoonshot_libmoonshot_la_LDFLAGS = -no-undefined -version-info 1:0:0
 
 include_HEADERS = libmoonshot/libmoonshot.h
@@ -49,31 +59,39 @@ src_moonshot_SOURCES = \
         src/moonshot-keyring-store.vala \
         src/moonshot-idcard-store.vala \
         src/moonshot-id.vala \
-        src/moonshot-add-dialog.vala \
+        src/moonshot-identity-dialog.vala \
         src/moonshot-idcard-widget.vala \
         src/moonshot-custom-vbox.vala \
         src/moonshot-identities-manager.vala \
         src/moonshot-identity-request.vala \
-        src/moonshot-server.vala \
+        src/moonshot-server-linux.vala \
+        src/moonshot-settings.vala \
         src/moonshot-password-dialog.vala \
         src/moonshot-provisioning-common.vala \
+        src/moonshot-trust-anchor-dialog.vala \
         src/moonshot-utils.vala \
-        src/moonshot-futils.c
+        src/moonshot-futils.c \
+        src/moonshot-crypto-utils.c \
+        src/moonshot-logger.vala \
+        src/moonshot-warning-dialog.vala
 
 src_moonshot_webp_SOURCES = \
+        src/moonshot-crypto-utils.c \
         src/moonshot-webp-parser.vala \
         src/moonshot-provisioning-common.vala \
-        src/moonshot-id.vala
+        src/moonshot-id.vala \
+        src/moonshot-logger.vala
+
 
-src_moonshot_VALAFLAGS = --pkg gdk-2.0 --pkg gtk+-2.0 $(AM_VALAFLAGS)
-src_moonshot_CPPFLAGS = $(moonshot_CFLAGS) $(AM_CPPFLAGS)   $(GEE_CFLAGS)
-src_moonshot_LDADD = $(moonshot_LIBS)  $(GEE_LIBS)
-src_moonshot_LDFLAGS = -g -O0
+src_moonshot_VALAFLAGS = --pkg $(GTK_VERSION) --pkg $(GEE_VERSION)   $(AM_VALAFLAGS)
+src_moonshot_CPPFLAGS = $(moonshot_CFLAGS) $(AM_CPPFLAGS)  
+src_moonshot_LDADD = $(moonshot_LIBS)
+src_moonshot_LDFLAGS = -g -O0 $(MOONSHOT_LOG_LIBS)
 
-src_moonshot_webp_VALAFLAGS = --vapidir=$(top_srcdir)/libmoonshot  --pkg gtk+-2.0 --pkg gdk-2.0 --pkg libmoonshot $(AM_VALAFLAGS)
-src_moonshot_webp_CPPFLAGS = $(moonshot_CFLAGS) $(AM_CPPFLAGS) $(GEE_CFLAGS)
-src_moonshot_webp_LDADD = $(moonshot_LIBS) ${top_builddir}/libmoonshot/libmoonshot.la $(GEE_LIBS)
-src_moonshot_webp_LDFLAGS =
+src_moonshot_webp_VALAFLAGS = --vapidir=$(top_srcdir)/libmoonshot  --pkg $(GEE_VERSION) --pkg libmoonshot $(AM_VALAFLAGS)
+src_moonshot_webp_CPPFLAGS = $(moonshot_CFLAGS) $(AM_CPPFLAGS)
+src_moonshot_webp_LDADD = $(moonshot_LIBS) ${top_builddir}/libmoonshot/libmoonshot.la
+src_moonshot_webp_LDFLAGS = $(MOONSHOT_LOG_LIBS)
 
 if OS_WIN32
 
@@ -105,8 +123,8 @@ if OS_LINUX
 
 AM_CPPFLAGS += -I/usr/include/gnome-keyring-1
 AM_VALAFLAGS += --pkg moonshot-gnome-keyring --define=GNOME_KEYRING
-src_moonshot_LDFLAGS += -lgnome-keyring
-src_moonshot_webp_LDFLAGS += -lgnome-keyring
+src_moonshot_LDFLAGS += -lgnome-keyring -lcrypto
+src_moonshot_webp_LDFLAGS += -lgnome-keyring -lcrypto
 
 ## Installing mime type data
 mimedir = $(datadir)/mime/packages
@@ -168,7 +186,7 @@ $(dbusservice_DATA): $(dbusservice_in_files) Makefile
 
 libmoonshot_libmoonshot_la_SOURCES += libmoonshot/libmoonshot-dbus.c
 
-CLEANFILES = $(dbusservice_DATA)
+CLEANFILES = $(dbusservice_DATA)  src_moonshot_vala.stamp src_moonshot_vala.stamp-t
 
 if IPC_DBUS_GLIB
 AM_VALAFLAGS += \
@@ -187,12 +205,6 @@ if GIO_VAPI_USES_ARRAYS
 AM_VALAFLAGS += --define=GIO_VAPI_USES_ARRAYS
 endif
 
-if GEE_0_8
-AM_VALAFLAGS += --pkg gee-0.8
-else
-AM_VALAFLAGS += --pkg gee-1.0
-endif
-
 EXTRA_DIST = webprovisioning/moonshot.xml $(dbusservice_in_files) \
        webprovisioning/complex-test.msht webprovisioning/sample.msht \
        README.webprovisioning README.windows \
@@ -206,11 +218,11 @@ noinst_PROGRAMS = \
         tests/basic
 
 examples_service_selection_SOURCES = examples/service-selection.c
-examples_service_selection_CPPFLAGS = $(moonshot_CFLAGS) $(AM_CPPFLAGS) $(GEE_CFLAGS)
-examples_service_selection_LDADD = ${top_builddir}/libmoonshot/libmoonshot.la $(moonshot_LIBS) $(GEE_LIBS)
+examples_service_selection_CPPFLAGS = $(moonshot_CFLAGS) $(AM_CPPFLAGS)
+examples_service_selection_LDADD = ${top_builddir}/libmoonshot/libmoonshot.la $(moonshot_LIBS)
 
 examples_client_SOURCES = examples/client.c
-examples_client_CPPFLAGS = $(libmoonshot_CFLAGS) $(AM_CPPFLAGS) $(GEE_CFLAGS)
+examples_client_CPPFLAGS = $(libmoonshot_CFLAGS) $(AM_CPPFLAGS)
 examples_client_LDADD = ${top_builddir}/libmoonshot/libmoonshot.la
 
 tests_basic_SOURCES = tests/basic.c
index 96fbc9b..c4f66ed 100644 (file)
@@ -1,6 +1,6 @@
 AC_PREREQ([2.63])
 AC_INIT([Moonshot-ui],
-        [0.7.2],
+        [1.0.0],
         [moonshot-community@jiscmail.ac.uk],
         [moonshot-ui],
         [http://www.project-moonshot.org/])
@@ -27,7 +27,6 @@ AC_PROG_CC
 AM_PROG_CC_C_O
 AM_PROG_VALAC([0.9])
 
-gee_0_8=no
 # Platform checks
 AC_CANONICAL_HOST
 
@@ -38,10 +37,6 @@ case "$host" in
     linux=no
     SERVER_IPC_MODULE="msrpc-glib2-1.0"
     CLIENT_IPC_MODULE="msrpc-mingw-1.0"
-    PKG_CHECK_MODULES([GTK],
-            [gtk+-2.0 >= 2.18],
-            [GTK_VERSION="gtk+-2.0"]
-    )
     ;;
     
   *darwin*) 
@@ -60,14 +55,6 @@ case "$host" in
                        [gtk-mac-integration >= 1.0.1]
        )
 
-# TODO: Move to GTK+-3
-#            [gtk+-3.0 >= 2.0],
-#            [GTK_VERSION="gtk+-3.0"]
-    PKG_CHECK_MODULES([GTK],
-            [gtk+-2.0 >= 2.18],
-            [GTK_VERSION="gtk+-2.0"]
-    )
-
     ;;
   *)
     macos=no
@@ -84,19 +71,46 @@ case "$host" in
             [SERVER_IPC_MODULE="gio-2.0"],
             [SERVER_IPC_MODULE="dbus-glib-1"]
     )
-# TODO: Move to GTK+-3
-#            [gtk+-3.0 >= 2.0],
-#            [GTK_VERSION="gtk+-3.0"]
-    PKG_CHECK_MODULES([GTK],
-            [gtk+-2.0 >= 2.18],
-            [GTK_VERSION="gtk+-2.0"]
-    )
-    PKG_CHECK_MODULES(GEE, [gee-0.8 >= 0.5],gee_0_8=yes,
-       [PKG_CHECK_MODULES(GEE,[gee-1.0])])
 
     ;;
 esac
-AM_CONDITIONAL(GEE_0_8,[test "$gee_0_8" = "yes"])
+
+
+# For all platforms: Use Gtk+3.0 if available; else revert to Gtk+2.0
+PKG_CHECK_MODULES([GTK],
+        [gtk+-3.0 >= 3.1],
+        [GTK_VERSION="gtk+-3.0"],
+            [PKG_CHECK_MODULES([GTK],
+                        [gtk+-2.0 >= 2.18],
+                        [GTK_VERSION="gtk+-2.0"]
+            )]                            
+)
+AC_SUBST(GTK_VERSION)
+
+
+# For all platforms: Use gee-0.8 if available; else revert to gee-1.0
+# (yes, gee-0.8 is an upgrade from gee-1.0!)
+PKG_CHECK_MODULES([LIB_GEE],
+        [gee-0.8 >= 0.16],
+        [GEE_VERSION="gee-0.8"],
+            [PKG_CHECK_MODULES([LIB_GEE],
+                        [gee-1.0 >= 0.5],
+                        [GEE_VERSION="gee-1.0"]
+            )]                            
+)
+AC_SUBST(GEE_VERSION)
+
+#enable the optional use of Log4Vala (Must have the package installed!)
+AC_ARG_ENABLE([log4vala],
+[  --enable-log4vala    Enable use of log4vala for logging],
+[case "${enableval}" in
+  yes) log4vala=true ;;
+  no)  log4vala=false ;;
+  *) AC_MSG_ERROR([bad value ${enableval} for --enable-log4vala]) ;;
+esac],[log4vala=false])
+AM_CONDITIONAL([LOG4VALA], [test x$log4vala = xtrue])
+
+
 AM_CONDITIONAL([OS_LINUX], [test "$linux" = "yes"])
 AM_CONDITIONAL([OS_WIN32], [test "$win32" = "yes"])
 AM_CONDITIONAL([OS_MACOS], [test "$macos" = "yes"])
@@ -222,7 +236,9 @@ PKG_CHECK_MODULES(moonshot,[
         atk >= 1.20
         glib-2.0 >= 2.22
         gobject-2.0 >= 2.22
+        libssl
         $GTK_VERSION
+        $GEE_VERSION
         $SERVER_IPC_MODULE
                $MAC
 ])
index 84f8975..52d1b0e 100644 (file)
@@ -34,6 +34,7 @@
 
 #include <assert.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <unistd.h>
 #include <dbus/dbus-glib.h>
@@ -435,6 +436,9 @@ int moonshot_install_id_card (const char     *display_name,
                        G_TYPE_INVALID);
 
     g_object_unref (dbus_proxy);
+    g_free(rules_patterns_strv);
+    g_free(rules_always_confirm_strv);
+    g_free(services_strv);
 
     if (g_error != NULL) {
         *error = moonshot_error_new (MOONSHOT_ERROR_IPC_ERROR,
@@ -444,3 +448,53 @@ int moonshot_install_id_card (const char     *display_name,
 
     return success;
 }
+
+int moonshot_confirm_ca_certificate (const char           *identity_name,
+                                     const char           *realm,
+                                     const unsigned char  *ca_hash,
+                                     int                   hash_len,
+                                     MoonshotError       **error)
+{
+    GError     *g_error = NULL;
+    int         success = 99;
+    int         confirmed = 99;
+    char        hash_str[65];
+    DBusGProxy *dbus_proxy = get_dbus_proxy (error);
+    int         out = 0;
+    int         i;
+
+    if (*error != NULL) {
+        return FALSE;
+    }
+
+    g_return_val_if_fail (DBUS_IS_G_PROXY (dbus_proxy), FALSE);
+
+    /* Convert hash byte array to string */
+    out = 0;
+    for (i = 0; i < hash_len; i++) {
+        sprintf(&(hash_str[out]), "%02X", ca_hash[i]);
+        out += 2;
+    }
+
+    dbus_g_proxy_call_with_timeout (dbus_proxy,
+                                    "ConfirmCaCertificate",
+                                    INFINITE_TIMEOUT,
+                                    &g_error,
+                                    G_TYPE_STRING, identity_name,
+                                    G_TYPE_STRING, realm,
+                                    G_TYPE_STRING, hash_str,
+                                    G_TYPE_INVALID,
+                                    G_TYPE_INT,   &confirmed,
+                                    G_TYPE_BOOLEAN, &success,
+                                    G_TYPE_INVALID);
+
+    g_object_unref (dbus_proxy);
+
+    if (g_error != NULL) {
+        *error = moonshot_error_new (MOONSHOT_ERROR_IPC_ERROR,
+                                     g_error->message);
+        return FALSE;
+    }
+
+    return (int) confirmed;
+}
diff --git a/libmoonshot/libmoonshot.def b/libmoonshot/libmoonshot.def
new file mode 100644 (file)
index 0000000..98f4393
--- /dev/null
@@ -0,0 +1,7 @@
+LIBRARY libmoonshot-0.dll
+EXPORTS
+    moonshot_error_free
+    moonshot_error_new
+    moonshot_get_default_identity
+    moonshot_get_identity
+    moonshot_confirm_ca_certificate
index d7830c2..5b2524d 100644 (file)
@@ -187,4 +187,19 @@ int moonshot_install_id_card (const char     *display_name,
                               int             force_flat_file_store,
                               MoonshotError **error);
 
+
+
+/**
+ * moonshot_confirm_ca_certificate
+ * @
+ * Return value: %TRUE if the certificate is approved; %FALSE otherwise
+ */
+
+int moonshot_confirm_ca_certificate (const char           *identity_name,
+                                     const char           *realm,
+                                     const unsigned char  *sha256,
+                                     int                   sha256_length,
+                                     MoonshotError       **error);
+
+
 #endif
index 8662596..840e19f 100644 (file)
@@ -51,4 +51,11 @@ namespace Moonshot {
                                  string? server_cert,
                                  int force_flat_file_store,
                                  out Moonshot.Error error);
+
+    [CCode (cname = "moonshot_confirm_ca_certificate")]
+    public bool moonshot_confirm_ca_certificate (string identity_name,
+                                                 string realm,
+                                                 string ca_hash,
+                                                 out uint32 confirmed,
+                                                 out Moonshot.Error error);
 }
index b330862..1a705ee 100644 (file)
@@ -1,2 +1,11 @@
 # List of source files which contain translatable strings.
+src/moonshot-idcard-widget.vala
+src/moonshot-identities-manager.vala
+src/moonshot-identity-dialog.vala
 src/moonshot-identity-management-view.vala
+src/moonshot-identity-manager-app.vala
+src/moonshot-password-dialog.vala
+src/moonshot-server-linux.vala
+src/moonshot-trust-anchor-dialog.vala
+src/moonshot-warning-dialog.vala
+src/moonshot-webp-parser.vala
diff --git a/src/moonshot-add-dialog.vala b/src/moonshot-add-dialog.vala
deleted file mode 100644 (file)
index ad4f241..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (c) 2011-2014, JANET(UK)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. 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.
- *
- * 3. Neither the name of JANET(UK) 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 HOLDER 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.
-*/
-using Gtk;
-
-class AddIdentityDialog : Dialog
-{
-    static const string displayname_labeltext = _("Display Name");
-    static const string issuer_labeltext = _("Issuer");
-    static const string username_labeltext = _("Username");
-    static const string password_labeltext = _("Password");
-    private Entry displayname_entry;
-    private Label displayname_label;
-    private Entry issuer_entry;
-    private Label issuer_label;
-    private Entry username_entry;
-    private Label username_label;
-    private Entry password_entry;
-    private Label password_label;
-    private CheckButton remember_checkbutton;
-    private Label message_label;
-    public bool complete;
-    
-    public string display_name {
-        get { return displayname_entry.get_text(); }
-    }
-
-    public string issuer {
-        get { return issuer_entry.get_text (); }
-    }
-
-     public string username {
-        get { return username_entry.get_text (); }
-    }
-
-    public string password {
-        get { return password_entry.get_text (); }
-    }
-
-    public bool store_password {
-        get { return remember_checkbutton.active; }
-    }
-
-    public AddIdentityDialog ()
-    {
-        this.set_title (_("Add ID Card"));
-        this.set_modal (true);
-
-        this.add_buttons (_("Add ID Card"), ResponseType.OK,
-#if VALA_0_12
-                          Stock.CANCEL, ResponseType.CANCEL);
-#else
-                          STOCK_CANCEL, ResponseType.CANCEL);
-#endif
-
-        var content_area = this.get_content_area ();
-        ((Box) content_area).set_spacing (12);
-        
-        displayname_label = new Label (@"$displayname_labeltext:");
-        displayname_label.set_alignment (1, (float) 0.5);
-        displayname_entry = new Entry ();
-        issuer_label = new Label (@"$issuer_labeltext:");
-        issuer_label.set_alignment (1, (float) 0.5);
-        this.issuer_entry = new Entry ();
-        username_label = new Label (@"$username_labeltext:");
-        username_label.set_alignment (1, (float) 0.5);
-        this.username_entry = new Entry ();
-        password_label = new Label (@"$password_labeltext:");
-        password_label.set_alignment (1, (float) 0.5);
-        this.password_entry = new Entry ();
-        password_entry.set_invisible_char ('*');
-        password_entry.set_visibility (false);
-        this.remember_checkbutton = new CheckButton.with_label (_("Remember password"));
-        this.message_label = new Label("");
-        message_label.set_visible(false);
-
-        set_atk_relation (displayname_label, displayname_entry, Atk.RelationType.LABEL_FOR);
-        set_atk_relation (issuer_label, issuer_entry, Atk.RelationType.LABEL_FOR);
-        set_atk_relation (username_label, username_entry, Atk.RelationType.LABEL_FOR);
-        set_atk_relation (password_entry, password_entry, Atk.RelationType.LABEL_FOR);
-
-        var table = new Table (6, 2, false);
-        table.set_col_spacings (10);
-        table.set_row_spacings (10);
-        
-        table.attach_defaults (message_label, 0, 2, 0, 1);
-        table.attach_defaults (displayname_label, 0, 1, 1, 2);
-        table.attach_defaults (displayname_entry, 1, 2, 1, 2);
-        table.attach_defaults (issuer_label, 0, 1, 2, 3);
-        table.attach_defaults (issuer_entry, 1, 2, 2, 3);
-        table.attach_defaults (username_label, 0, 1, 3, 4);
-        table.attach_defaults (username_entry, 1, 2, 3, 4);
-        table.attach_defaults (password_label, 0, 1, 4, 5);
-        table.attach_defaults (password_entry, 1, 2, 4, 5);
-        table.attach_defaults (remember_checkbutton,  1, 2, 5, 6);
-
-        this.response.connect(on_response);
-        var vbox = new VBox (false, 0);
-        vbox.set_border_width (6);
-        vbox.pack_start (table, false, false, 0);
-
-        ((Container) content_area).add (vbox);
-
-        this.set_border_width (6);
-        this.set_resizable (false);
-        this.show_all ();
-    }
-
-    private static string update_preamble(string preamble)
-    {
-        if (preamble == "")
-            return _("Missing required field: ");
-        return _("Missing required fields: ");
-    }
-
-    private static string update_message(string old_message, string new_item)
-    {
-        string message;
-        if (old_message == "")
-            message = new_item;
-        else
-            message = old_message + ", " + new_item;
-        return message;
-    }
-
-    private static void check_field(string field, Label label, string fieldname, ref string preamble, ref string message)
-    {
-        if (field != "") {
-            label.set_markup(@"$fieldname:");
-            return;
-        }
-        label.set_markup(@"<span foreground=\"red\">$fieldname:</span>");
-        preamble = update_preamble(preamble);
-        message = update_message(message, fieldname);
-    }
-
-    private bool check_fields()
-    {
-        string preamble = "";
-        string message = "";
-        string password_test = store_password ? password : "not required";
-        check_field(display_name, displayname_label, displayname_labeltext, ref preamble, ref message);
-        check_field(issuer, issuer_label, issuer_labeltext, ref preamble, ref message);
-        check_field(username, username_label, username_labeltext, ref preamble, ref message);
-        check_field(password_test, password_label, password_labeltext, ref preamble, ref message);
-        if (message != "") {
-            message_label.set_visible(true);
-            message_label.set_markup(@"<span foreground=\"red\">$preamble$message</span>");
-            return false;
-        }
-        return true;
-    }
-
-    private void on_response (Dialog source, int response_id)
-    {
-        switch (response_id) {
-        case ResponseType.OK:
-            complete = check_fields();
-            break;
-        case ResponseType.CANCEL:
-            complete = true;
-            break;
-        }
-    }
-
-    private void set_atk_relation (Widget widget, Widget target_widget, Atk.RelationType relationship)
-    {
-        var atk_widget = widget.get_accessible ();
-        var atk_target_widget = target_widget.get_accessible ();
-
-        atk_widget.add_relationship (relationship, atk_target_widget);
-    }
-}
diff --git a/src/moonshot-crypto-utils.c b/src/moonshot-crypto-utils.c
new file mode 100644 (file)
index 0000000..a14c610
--- /dev/null
@@ -0,0 +1,29 @@
+#include <string.h>
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+
+#include <stdio.h>
+
+char* get_cert_valid_before(const unsigned char* buf, int len, char* datebuf, int datebuf_len)
+{
+    datebuf[0]='\0';
+
+    unsigned char *p = (unsigned char*) buf;
+    X509* x = d2i_X509(NULL, &p, len);
+    if (x == NULL) {
+        return "Error calling d2i_X509()!";
+    }
+
+    BIO* out_bio = BIO_new(BIO_s_mem());
+    ASN1_TIME* time = X509_get_notAfter(x);
+
+    if (ASN1_TIME_print(out_bio, time)) {
+        int write = BIO_read(out_bio, datebuf, datebuf_len - 1);
+        datebuf[write]='\0';
+    }
+
+    datebuf[datebuf_len - 1] = '\0';
+    BIO_free(out_bio);
+    X509_free(x);
+    return "";
+}
index 13a6db3..49942ee 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,38 +33,63 @@ using Gtk;
 
 class CustomVBox : VBox
 {
-    public IdCardWidget current_idcard { get; set; default = null; }
+    static MoonshotLogger logger = get_logger("CustomVBox");
     private IdentityManagerView main_window; 
-
-    public CustomVBox (IdentityManagerView window, bool homogeneous, int spacing)
+    int next_pos = 0;
+    
+    public CustomVBox(IdentityManagerView window, bool homogeneous, int spacing)
     {
         main_window = window;
-        set_homogeneous (homogeneous);
-        set_spacing (spacing);
+        set_homogeneous(homogeneous);
+        set_spacing(spacing);
     }
 
-    public void receive_expanded_event (IdCardWidget id_card_widget)
+    internal void receive_expanded_event(IdCardWidget id_card_widget)
     {
-        var list = get_children ();
+        var list = get_children();
         foreach (Widget id_card in list)
         {
             if (id_card != id_card_widget)
-                ((IdCardWidget) id_card).collapse ();
+                ((IdCardWidget) id_card).collapse();
         }
-        current_idcard = id_card_widget;
         
-        if (current_idcard != null && main_window.request_queue.length > 0)
-            current_idcard.send_button.set_sensitive (true);
         check_resize();
     }
 
-    public void add_id_card_widget (IdCardWidget id_card_widget)
+    internal void receive_collapsed_event(IdCardWidget id_card_widget)
     {
-        pack_start (id_card_widget, false, false);
+        check_resize();
     }
 
-    public void remove_id_card_widget (IdCardWidget id_card_widget)
+    public void add_id_card_widget(IdCardWidget id_card_widget)
     {
-        remove (id_card_widget);
+        pack_start(id_card_widget, false, false);
+        id_card_widget.position = next_pos++;
+    }
+
+    public IdCardWidget? find_idcard_widget(IdCard? card) {
+        if (card == null) {
+            return null;
+        }
+        foreach (var w in get_children()) {
+            IdCardWidget widget = (IdCardWidget) w;
+            if (widget.id_card == card) {
+                return widget;
+            }
+        }
+        return null;
     }
+
+    internal void clear()
+    {
+        logger.trace("clear");
+
+        var children = get_children();
+        foreach (var id_card_widget in children) {
+            remove(id_card_widget);
+        }
+
+        next_pos = 0;
+   }
+    
 }
index 2e0af57..42916c5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
 */
+
+using Gee;
+
+extern char* get_cert_valid_before(uchar* inbuf, int inlen, char* outbuf, int outlen);
+
+
+// A TrustAnchor object can be imported or installed via the API, but cannot
+// be modified by the user, other than being cleared. Hence the fields are read-only.
 public class TrustAnchor : Object
 {
-  public string ca_cert {get; set; default = "";}
-  public string subject {get; set; default = "";}
-  public string subject_alt  {get; set; default = "";}
-  public string server_cert  {get; set; default = "";}
-  public int Compare(TrustAnchor other)
-  {
-    if (this.ca_cert != other.ca_cert)
-      return 1;
-    if (this.subject != other.subject)
-      return 1;
-    if (this.subject_alt != other.subject_alt)
-      return 1;
-    if (this.server_cert != other.server_cert)
-      return 1;
-    return 0;
-  }
+    private static const string CERT_HEADER = "-----BEGIN CERTIFICATE-----";
+    private static const string CERT_FOOTER = "-----END CERTIFICATE-----";
+
+    public enum TrustAnchorType {
+        EMPTY,
+        CA_CERT,
+        SERVER_CERT
+    }
+    private string _ca_cert = "";
+    private string _subject = "";
+    private string _subject_alt = "";
+    private string _server_cert = "";
+    private string _datetime_added = "";
+
+    private static string fixup (string s) {
+        return (s == null ? "" : s.strip());
+    }
+
+    public TrustAnchor(string ca_cert, string server_cert, string subject, string subject_alt) {
+        _ca_cert = fixup(ca_cert);
+        _server_cert = fixup(server_cert);
+        _subject = fixup(subject);
+        _subject_alt = fixup(subject_alt);
+
+        // If we're reading from store, this will be overridden (see set_datetime_added)
+        _datetime_added = "";
+
+        // Work around a Portal bug that littered some credential files with this cruft.
+        string cruft = 
+"""<!-- Remove the begin and end lines from the PEM output of
+openssl to produce this format.  Alternatively, base64 encode a DER format certificate -->""";
+        _ca_cert = _ca_cert.replace(cruft, "");
+    }
+
+    public TrustAnchor.empty() {
+    }
+
+
+    public string ca_cert {
+        get {
+            return _ca_cert;
+        }
+    }
+
+    public string subject {
+        get {
+            return _subject;
+        }
+    }
+
+    public string subject_alt  {
+        get {
+            return _subject_alt;
+        }
+    }
+
+
+    public string server_cert {
+        get {
+            return _server_cert;
+        }
+    }
+
+    public string datetime_added {
+        get {
+            return _datetime_added;
+        }
+    }
+
+    public bool is_empty() {
+        return ca_cert == "" && server_cert == "";
+    }
+
+    public TrustAnchorType get_anchor_type() {
+        return (server_cert != "" ? TrustAnchorType.SERVER_CERT 
+                : (ca_cert != "" ? TrustAnchorType.CA_CERT : TrustAnchorType.EMPTY));
+    }
+
+    internal void set_datetime_added(string datetime) {
+        _datetime_added = fixup(datetime);
+    }
+
+    internal static string format_datetime_now() {
+        DateTime now = new DateTime.now_utc();
+        string dt = now.format("%b %d %T %Y %Z");
+        return dt;
+    }
+
+    internal void update_server_fingerprint(string fingerprint) {
+        this._server_cert = fingerprint;
+        string ta_datetime_added = TrustAnchor.format_datetime_now();
+        this.set_datetime_added(ta_datetime_added);
+    }
+
+    public int Compare(TrustAnchor other)
+    {
+        if (this.ca_cert != other.ca_cert) {
+            // IdCard.logger.trace("TrustAnchor.Compare: this.ca_cert='%s'; other.ca_cert='%s'".printf(this.ca_cert, other.ca_cert));
+            return 1;
+        }
+        if (this.subject != other.subject) {
+            // IdCard.logger.trace("TrustAnchor.Compare: this.subject='%s'; other.subject='%s'".printf(this.subject, other.subject));
+            return 1;
+        }
+        if (this.subject_alt != other.subject_alt) {
+            // IdCard.logger.trace("TrustAnchor.Compare: this.subject_alt='%s'; other.subject_alt='%s'".printf(this.subject_alt, other.subject_alt));
+            return 1;
+        }
+        if (this.server_cert != other.server_cert) {
+            // IdCard.logger.trace("TrustAnchor.Compare: this.server_cert=%s'; other.server_cert='%s'".printf(this.server_cert, other.server_cert));
+            return 1;
+        }
+
+        // Do not compare the datetime_added fields; it's not essential.
+
+        return 0;
+    }
+
+    public string? get_expiration_date(out string? err_out=null)
+    {
+        if (&err_out != null) {
+            err_out = null;
+        }
+
+        if (this.ca_cert == "") {
+            if (&err_out != null) {
+                err_out = "Trust anchor does not have a ca_certificate";
+                return null;
+            }
+        }
+
+        string cert = this.ca_cert;
+        cert.chomp();
+
+        uchar[] binary = Base64.decode(cert);
+        IdCard.logger.trace("get_expiration_date: encoded length=%d; decoded length=%d".printf(cert.length, binary.length));
+
+        char buf[64];
+        string err = (string) get_cert_valid_before(binary, binary.length, buf, 64);
+        if (err != "") {
+            IdCard.logger.error(@"get_expiration_date: get_cert_valid_before returned '$err'");
+            if (&err_out != null) {
+                err_out = err;
+            }
+            return null;
+        }
+            
+        string date = (string) buf;
+        IdCard.logger.trace(@"get_expiration_date: get_cert_valid_before returned '$date'");
+
+        return date;
+    }
 }
 
+
 public struct Rule
 {
-  public string pattern;
-  public string always_confirm;
-  public int Compare(Rule other) {
-    if (this.pattern != other.pattern)
-      return 1;
-    if (this.always_confirm != other.always_confirm)
-      return 1;
-    return 0;
-  }
+    public string pattern;
+    public string always_confirm;
+    public int Compare(Rule other) {
+        if (this.pattern != other.pattern)
+            return 1;
+        if (this.always_confirm != other.always_confirm)
+            return 1;
+        return 0;
+    }
 }
 
 public class IdCard : Object
 {
-  public const string NO_IDENTITY = "No Identity";
+    internal static MoonshotLogger logger = get_logger("IdCard");
 
-  private string _nai;
-  
-  public string display_name { get; set; default = ""; }
+    public const string NO_IDENTITY = "No Identity";
+
+    private string _username = "";
+    private string _issuer = "";
+
+    public string display_name { get; set; default = ""; }
   
-  public string username { get; set; default = ""; }
+    public string username { 
+        public get {
+            return _username;
+        }
+        public set {
+            _username = value;
+            update_nai();
+        }
+    }
+
+    public string issuer { 
+        public get {
+            return _issuer;
+        }
+        public set {
+            _issuer = value;
+            update_nai();
+        }
+    }
+
+    private void update_nai() {
+        _nai = username + "@" + issuer;
+    }
+
 #if GNOME_KEYRING
-  private unowned string _password;
-  public string password {
-    get {
-      return (_password!=null) ? _password : "";
-    }
-    set {
-      if (_password != null) {
-        GnomeKeyring.memory_free((void *)_password);
-        _password = null;
-      }
-      if (value != null)
-        _password = GnomeKeyring.memory_strdup(value); 
-    }
-  }
+    private unowned string _password;
+    public string password {
+        get {
+            return (_password!=null) ? _password : "";
+        }
+        set {
+            if (_password != null) {
+                GnomeKeyring.memory_free((void *)_password);
+                _password = null;
+            }
+            if (value != null)
+                _password = GnomeKeyring.memory_strdup(value); 
+        }
+    }
 #else
-  public string password { get; set; default = null; }
+    public string password { get; set; default = null; }
 #endif
 
-  public string issuer { get; set; default = ""; }
-  
-  public Rule[] rules {get; set; default = {};}
-  public string[] services { get; set; default = {}; }
-  public bool temporary {get; set; default = false; }
+    private Rule[] _rules = new Rule[0];
+    public Rule[] rules {
+        get {return _rules;}
+        internal set {_rules = value ?? new Rule[0] ;}
+    }
+
+    private ArrayList<string> _services = new ArrayList<string>();
+
+    internal ArrayList<string> services {
+         get {return  _services;}
+    }
+
+    // Returns the list of services as a string, using the given separator.
+    internal string get_services_string(string sep) {
+        if (_services.is_empty) {
+            return "";
+        }
+
+        // ArrayList.to_array() seems to be unreliable -- it causes segfaults 
+        // semi-randomly. (Possibly because it returns an unowned ref?)
+        // return string.joinv(sep, _services.to_array());
+        // 
+        // This problem may be related to the one noted elsewhere as the
+        // "Centos vala array property bug".
+
+        string[] svcs = new string[_services.size];
+        for (int i = 0; i < _services.size; i++) {
+            svcs[i] = _services[i];
+        }
+
+        return string.joinv(sep, svcs);
+    }
+
+    internal void update_services(string[] services) {
+        _services.clear();
+
+        // Doesn't exist in older versions of libgee:
+        // _services.add_all_array(services);
 
-  public TrustAnchor trust_anchor  { get; set; default = new TrustAnchor (); }
+        if (services != null) {
+            foreach (string s in services) {
+                _services.add(s);
+            }
+        }
+    } 
+
+    internal void update_services_from_list(ArrayList<string> services) {
+        if (services == this._services) {
+            // Don't try to update from self.
+            return;
+        }
+
+        _services.clear();
+
+        if (services != null) {
+            _services.add_all(services);
+        }
+    } 
+
+
+    public bool temporary {get; set; default = false; }
+
+    private TrustAnchor _trust_anchor = new TrustAnchor.empty();
+    public TrustAnchor trust_anchor  { 
+        get {
+            return _trust_anchor;
+        }
+    }
+
+    // For use by storage implementations.
+    internal void set_trust_anchor_from_store(TrustAnchor ta) {
+        _trust_anchor = ta;
+    }
+
+    internal void clear_trust_anchor() {
+        _trust_anchor = new TrustAnchor.empty();
+    }
   
-  public unowned string nai { get {  _nai = username + "@" + issuer; return _nai;}}
-
-  public bool store_password { get; set; default = false; }
-
-  public bool IsNoIdentity() 
-  {
-    return (display_name == NO_IDENTITY);
-  }
-
-  public enum DiffFlags {
-    DISPLAY_NAME,
-    USERNAME,
-    PASSWORD,
-    ISSUER,
-    RULES,
-    SERVICES,
-    TRUST_ANCHOR;
-  }
-
-  public int Compare(IdCard other)
-  {
-    int diff = 0;
-    if (this.display_name != other.display_name)
-      diff |= 1 << DiffFlags.DISPLAY_NAME;
-    if (this.username != other.username)
-      diff |= 1 << DiffFlags.USERNAME;
-    if (this.password != other.password)
-      diff |= 1 << DiffFlags.PASSWORD;
-    if (this.issuer != other.issuer)
-      diff |= 1 << DiffFlags.ISSUER;
-    if (CompareRules(this.rules, other.rules)!=0)
-      diff |= 1 << DiffFlags.RULES;
-    if (CompareStringArray(this.services, other.services)!=0)
-      diff |= 1 << DiffFlags.SERVICES;
-    if (this.trust_anchor.Compare(other.trust_anchor)!=0)
-      diff |= 1 << DiffFlags.TRUST_ANCHOR;
-    stdout.printf("Diff Flags: %x\n", diff);
-    return diff;
-  }
-
-  public static IdCard NewNoIdentity() 
-  { 
-    IdCard card = new IdCard();
-    card.display_name = NO_IDENTITY;
-    return card;
-  }
-
-  ~IdCard() {
-    password = null;
-  }
+    public string nai { public get; private set;}
+
+    public bool store_password { get; set; default = false; }
+
+    // uuid is currently used only for debugging. Must be unique, even between cards with same nai and display name.
+    public string uuid {
+        public get {return _uuid;}
+    }
+    private string _uuid = generate_uuid();
+
+    internal static string generate_uuid() {
+        uint32 rand1 = Random.next_int();
+        uint32 rand2 = Random.next_int();
+        return "%08X.%08X::%s".printf(rand1, rand2, TrustAnchor.format_datetime_now());
+    }
+
+    public bool is_no_identity() 
+    {
+        return (display_name == NO_IDENTITY);
+    }
+
+    public enum DiffFlags {
+        DISPLAY_NAME,
+        USERNAME,
+        PASSWORD,
+        ISSUER,
+        RULES,
+        SERVICES,
+        TRUST_ANCHOR;
+    }
+
+    public int Compare(IdCard other)
+    {
+        int diff = 0;
+        if (this.display_name != other.display_name)
+            diff |= 1 << DiffFlags.DISPLAY_NAME;
+
+        if (this.username != other.username)
+            diff |= 1 << DiffFlags.USERNAME;
+
+        if (this.password != other.password)
+            diff |= 1 << DiffFlags.PASSWORD;
+
+        if (this.issuer != other.issuer)
+            diff |= 1 << DiffFlags.ISSUER;
+
+        if (CompareRules(this.rules, other.rules)!=0)
+            diff |= 1 << DiffFlags.RULES;
+
+        if (CompareStringArrayList(this._services, other._services)!=0)
+            diff |= 1 << DiffFlags.SERVICES;
+
+        if (this.trust_anchor.Compare(other.trust_anchor)!=0)
+            diff |= 1 << DiffFlags.TRUST_ANCHOR;
+
+        // stdout.printf("Diff Flags: %x\n", diff);
+        if (this.display_name == other.display_name && diff != 0) {
+            logger.trace("Compare: Two IDs with display_name '%s', but diff_flags=%0x".printf(this.display_name, diff));
+        }
+        return diff;
+    }
+
+    public static IdCard NewNoIdentity() 
+    { 
+        IdCard card = new IdCard();
+        card.display_name = NO_IDENTITY;
+        return card;
+    }
+
+    ~IdCard() {
+        password = null;
+    }
+
+    internal void add_rule(Rule rule) {
+        _rules += rule;
+    }
 }
 
 public int CompareRules(Rule[] a, Rule[] b)
 {
-  if (a.length != b.length)
-    return 1;
-  for (int i=0; i<a.length; i++) {
-    if (a[i].Compare(b[i]) != 0)
-      return 1;
-  }
-  return 0;
+    if (a.length != b.length) {
+        return 1;
+    }
+
+    for (int i = 0; i < a.length; i++) {
+        if (a[i].Compare(b[i]) != 0) {
+            return 1;
+        }
+    }
+    return 0;
 }
 
-public int CompareStringArray(string[] a, string [] b)
+public int CompareStringArrayList(ArrayList<string> a, ArrayList<string> b)
 {
-  if (a.length != b.length)
-    return 1;
-  for (int i=0; i<a.length; i++) {
-    if (a[i] != b[i])
-      return 1;
-  }
-  return 0;
+    if (a.size != b.size) {
+        return 1;
+    }
+
+    for (int i = 0; i < a.size; i++) {
+        if (a[i] != b[i]) {
+            return 1;
+        }
+    }
+    return 0;
 }
index 3d6c863..b9c9368 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -42,5 +42,8 @@ public interface IIdentityCardStore : Object {
     public abstract IdCard? update_card(IdCard card);
     public abstract StoreType get_store_type();
     public abstract LinkedList<IdCard> get_card_list(); 
+
+    // Note that (at least right now) store_id_cards() will re-load the cards after saving them.
+    internal abstract void store_id_cards(); 
 }
 
index 4f9e43e..25976b3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,153 +33,171 @@ using Gtk;
 
 class IdCardWidget : Box
 {
-    public IdCard id_card { get; set; default = null; }
+    // static MoonshotLogger logger = get_logger("IdCardWidget");
+
+    private static const ShadowType ARROW_SHADOW = ShadowType.NONE;
 
+    private IdentityManagerView manager_view;
+
+    public IdCard id_card { get; set; default = null; }
     private VBox main_vbox;
-    private HBox table;
-    public Button delete_button { get; private set; default = null; }
-    public Button details_button { get; private set; default = null; }
-    public Button send_button { get; private set; default = null; }
-    private HButtonBox hbutton_box;
+    private HBox hbox;
     private EventBox event_box;
+    private bool   is_selected = false;
+    private Arrow arrow;
     
-    private Label label;
+    private VBox details;
 
-    public signal void expanded ();
-    public signal void remove_id ();
-    public signal void details_id ();
-    public signal void send_id ();
+    internal int _position = 0;
+    internal int position {
+        get {return _position;}
+        set {_position = value; set_idcard_color();}
+    }
 
-    public void collapse ()
-    {
-        this.hbutton_box.set_visible (false);
+    public signal void expanded();
+    public signal void collapsed();
 
-        set_idcard_color ();
+    internal void select()
+    {
+        expand();
+        this.expanded();
     }
 
-    public void expand ()
+    internal void unselect()
     {
-        this.hbutton_box.set_visible (true);
-
-        set_idcard_color ();
-        this.expanded ();
+        collapse();
+        this.collapsed();
     }
 
-    private bool button_press_cb ()
+    public void expand()
     {
-        if (hbutton_box.get_visible ())
-            collapse ();
-        else
-            expand ();
+        is_selected = true;
+        details.show_all();
 
-        return false;
+        set_idcard_color();
+        arrow.set(ArrowType.DOWN, ARROW_SHADOW);
     }
 
-    private void delete_button_cb ()
+    public void collapse()
     {
-       this.remove_id ();
-    }
+        is_selected = false;
+        details.hide();
 
-    private void details_button_cb ()
-    {
-       this.details_id ();
+        set_idcard_color();
+        arrow.set(ArrowType.RIGHT, ARROW_SHADOW);
     }
 
-    private void send_button_cb ()
+    private bool button_press_cb()
     {
-       this.send_id ();
+        if (is_selected)
+            unselect();
+        else
+            select();
+
+        return false;
     }
 
-    private void set_idcard_color ()
+    private void set_idcard_color()
     {
-        var color = Gdk.Color ();
+        var color = Gdk.Color();
 
-        if (hbutton_box.get_visible () == false)
+        if (is_selected)
         {
-            color.red = 65535;
-            color.green = 65535;
-            color.blue = 65535;
+                color.red = 0xd9 << 8;
+                color.green = 0xf7 << 8;
+                color.blue = 65535;
         }
-        else
-        {
-            color.red = 33333;
-            color.green = 33333;
-            color.blue = 60000;
+        else {
+            if (position % 2 == 0)
+            {
+                color.red = color.green = color.blue = 0xf2 << 8;
+            }
+            else
+            {
+                color.red = 65535;
+                color.green = 65535;
+                color.blue = 65535;
+
+            }
         }
-        var state = this.get_state ();
-        this.event_box.modify_bg (state, color);
+        this.event_box.modify_bg(StateType.NORMAL, color);
+        this.arrow.modify_bg(StateType.NORMAL, color);
     }
     
-    public void
-    update_id_card_label ()
+    private void
+    make_id_card_label(Label label)
     {
-        string services_text = "";
+        var display_name = (manager_view.selection_in_progress() && this.id_card.is_no_identity()
+                            ? _("Do not use a Moonshot identity for this service") : this.id_card.display_name);
+        var label_text = Markup.printf_escaped("<span rise='8000'><big>%s</big></span>", display_name);
 
-        var display_name = Markup.printf_escaped ("<big>%s</big>", this.id_card.display_name);
-        for (int i=0; i<id_card.services.length; i++)
-        {
-            var service = id_card.services[i];
-            
-            if (i == (id_card.services.length - 1))
-              services_text = services_text + Markup.printf_escaped ("<i>%s</i>", service);
-            else
-              services_text = services_text + Markup.printf_escaped ("<i>%s, </i>", service);
-        }
-        label.set_markup (display_name + "\n" + services_text);
+        label.set_markup(label_text);
     }
 
-    public IdCardWidget (IdCard id_card)
+    public IdCardWidget(IdCard id_card, IdentityManagerView manager_view)
     {
         this.id_card = id_card;
-
-        var image = new Image.from_pixbuf (get_pixbuf(id_card));
-
-        label = new Label (null);
-        label.set_alignment ((float) 0, (float) 0.5);
-        label.set_ellipsize (Pango.EllipsizeMode.END);
-        update_id_card_label();
-
-        table = new Gtk.HBox (false, 6);
-        table.pack_start (image, false, false, 0);
-        table.pack_start (label, true, true, 0);
-
-        this.delete_button = new Button.with_label (_("Delete"));
-        this.details_button = new Button.with_label (_("View details"));
-        this.send_button = new Button.with_label (_("Send"));
-        set_atk_name_description (delete_button, _("Delete"), _("Delete this ID Card"));
-        set_atk_name_description (details_button, _("Details"), _("View the details of this ID Card"));
-        set_atk_name_description (send_button, _("Send"), _("Send this ID Card"));
-        this.hbutton_box = new HButtonBox ();
-        hbutton_box.pack_end (delete_button);
-        hbutton_box.pack_end (details_button);
-        hbutton_box.pack_end (send_button);
-        send_button.set_sensitive (false);
-
-        delete_button.clicked.connect (delete_button_cb);
-        details_button.clicked.connect (details_button_cb);
-        send_button.clicked.connect (send_button_cb);
-
-        this.main_vbox = new VBox (false, 12);
-        main_vbox.pack_start (table, true, true, 0);
-        main_vbox.pack_start (hbutton_box, false, false, 0);
-        main_vbox.set_border_width (12);
-
-        event_box = new EventBox ();
-        event_box.add (main_vbox);
-        event_box.button_press_event.connect (button_press_cb);
-        this.pack_start (event_box, true, true);
-
-        this.show_all ();
-        this.hbutton_box.hide ();
-
-        set_idcard_color ();
-    }
-
-    private void set_atk_name_description (Widget widget, string name, string description)
-    {
-       var atk_widget = widget.get_accessible ();
-
-       atk_widget.set_name (name);
-       atk_widget.set_description (description);
+        this.manager_view = manager_view;
+
+        var display_name_label = new Label(null);
+        display_name_label.set_alignment((float) 0, (float) 0.5);
+        display_name_label.set_ellipsize(Pango.EllipsizeMode.END);
+        make_id_card_label(display_name_label);
+
+        var details_wrapper = new VBox(false, 0);
+        details_wrapper.pack_start(display_name_label, false, false, 0);
+        this.details = new VBox(false, 0);
+        details_wrapper.pack_start(details, false, false, 0);
+
+        if (!this.id_card.is_no_identity()) {
+            var upper_details_text = _("Username") + ":  " + id_card.username;
+            upper_details_text += "\n" + _("Realm:") + "  " + id_card.issuer;
+            if (!id_card.trust_anchor.is_empty()) {
+                upper_details_text += "\n" + _("Trust anchor: Enterprise provisioned");
+            }
+            Label upper_details = new Label(upper_details_text);
+            upper_details.set_alignment(0, 0);
+            details.pack_start(upper_details);
+        }
+        var services_hbox = new HBox(false, 6);
+        Label services_label = new Label(_("Services: "));
+        services_label.set_alignment(0, 0);
+
+        string services_text = this.id_card.get_services_string("\n");
+        Label service_list = new Label(services_text);
+        service_list.set_alignment(0, 0);
+        service_list.set_ellipsize(Pango.EllipsizeMode.END);
+        service_list.set_max_width_chars(50);
+        services_hbox.pack_start(services_label, false, false, 0);
+        services_hbox.pack_start(service_list, false, false, 0);
+        details.pack_start(services_hbox);
+
+        hbox = new Gtk.HBox(false, 6);
+        var image = new Image.from_pixbuf(get_pixbuf(id_card));
+        if (this.id_card.is_no_identity()) {
+            image.clear();
+            // Use padding to make the image size =  48x48 (size = 2x padding)
+            image.set_padding(24, 24);
+        }
+        hbox.pack_start(image, false, false, 0);
+        hbox.pack_start(details_wrapper, true, true, 0);
+        this.arrow = new Arrow(ArrowType.RIGHT, ARROW_SHADOW);
+        this.arrow.set_alignment((float) 0.5, (float) 0);
+        hbox.pack_start(arrow, false, false);
+
+        this.main_vbox = new VBox(false, 12);
+        main_vbox.pack_start(hbox, true, true, 0);
+        main_vbox.set_border_width(12);
+
+        event_box = new EventBox();
+        event_box.add(main_vbox);
+        event_box.button_press_event.connect(button_press_cb);
+        event_box.set_visible(false);
+        this.pack_start(event_box, true, true);
+
+        this.show_all();
+        details.hide();
+
+        set_idcard_color();
     }
 }
index 292fb0e..06f7ef6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -64,7 +64,7 @@ public class PasswordHashTable : Object {
     private HashTable<string, Password> password_table;
 
     private static string ComputeHashKey(IdCard card, IIdentityCardStore store) {
-        return "%s_store_%d".printf( card.display_name, store.get_store_type() );
+        return "%s_store_%d".printf( card.nai, store.get_store_type() );
     }
 
     public void CachePassword(IdCard card, IIdentityCardStore store) {
@@ -86,79 +86,130 @@ public class PasswordHashTable : Object {
 }
 
 public class IdentityManagerModel : Object {
+    static MoonshotLogger logger = get_logger("IdentityManagerModel");
+
     private const string FILE_NAME = "identities.txt";
     private PasswordHashTable password_table;
     private IIdentityCardStore store;
     public LinkedList<IdCard>  get_card_list() {
-         var identities = store.get_card_list();
-         identities.sort( (a, b) => {
-             IdCard id_a = (IdCard )a;
-             IdCard id_b = (IdCard )b;
-             if (id_a.IsNoIdentity() && !id_b.IsNoIdentity()) {
-                return -1;
-             } else if (id_b.IsNoIdentity() && !id_a.IsNoIdentity()) {
-                return 1;
-             }
-             return strcmp(id_a.display_name, id_b.display_name);
-         });
-         if (identities.is_empty || !identities[0].IsNoIdentity())
-             identities.insert(0, IdCard.NewNoIdentity());
-         foreach (IdCard id_card in identities) {
-             if (!id_card.store_password) {
-                 password_table.RetrievePassword(id_card, store);
-             }
-         }
-         return identities;
+        var identities = store.get_card_list();
+        identities.sort((a, b) => {
+                IdCard id_a = (IdCard )a;
+                IdCard id_b = (IdCard )b;
+                if (id_a.is_no_identity() && !id_b.is_no_identity()) {
+                    return -1;
+                } else if (id_b.is_no_identity() && !id_a.is_no_identity()) {
+                    return 1;
+                }
+                return strcmp(id_a.display_name, id_b.display_name);
+            });
+        if (identities.is_empty || !identities[0].is_no_identity())
+            identities.insert(0, IdCard.NewNoIdentity());
+        foreach (IdCard id_card in identities) {
+            if (!id_card.store_password) {
+                password_table.RetrievePassword(id_card, store);
+            }
+        }
+        return identities;
     }
     public signal void card_list_changed();
 
     /* This method finds a valid display name */
-    public bool display_name_is_valid (string name,
-                                       out string? candidate)
+    public bool display_name_is_valid(string name,
+                                      out string? candidate)
     {
         if (&candidate != null)
-          candidate = null;
+            candidate = null;
         foreach (IdCard id_card in this.store.get_card_list())
         {
-          if (id_card.display_name == name)
-          {
-            if (&candidate != null)
+            if (id_card.display_name == name)
             {
-              for (int i=0; i<1000; i++)
-              {
-                string tmp = "%s %d".printf (name, i);
-                if (display_name_is_valid (tmp, null))
+                if (&candidate != null)
                 {
-                  candidate = tmp;
-                  break;
+                    for (int i = 0; i < 1000; i++)
+                    {
+                        string tmp = "%s %d".printf(name, i);
+                        if (display_name_is_valid(tmp, null))
+                        {
+                            candidate = tmp;
+                            break;
+                        }
+                    }
                 }
-              }
+                return false;
             }
-            return false;
-          }
         }
         return true;
     }
 
-    private bool remove_duplicates(IdCard card)
+    private bool remove_duplicates(IdCard new_card, out ArrayList<IdCard>? old_duplicates)
+    {
+        ArrayList<IdCard> dups = new ArrayList<IdCard>();
+        var cards = this.store.get_card_list();
+        foreach (IdCard id_card in cards) {
+            if ((new_card != id_card) && (id_card.nai == new_card.nai)) {
+                dups.add(id_card);
+            }
+        }
+
+        foreach (IdCard id_card in dups) {
+            logger.trace("removing duplicate id for '%s'\n".printf(new_card.nai));
+            remove_card_internal(id_card);
+
+            if (new_card.trust_anchor.Compare(id_card.trust_anchor) == 0) {
+                logger.trace("Old and new cards have same trust anchor. Re-using the datetime_added field from the old card.");
+                new_card.trust_anchor.set_datetime_added(id_card.trust_anchor.datetime_added);
+            }
+        }
+
+        if (&old_duplicates != null) {
+            old_duplicates = dups;
+        }
+
+        return (dups.size > 0);
+    }
+
+
+    public bool find_duplicate_nai_sets(out ArrayList<ArrayList<IdCard>> duplicates)
     {
-        bool duplicate_found = false;
+        var nais = new HashMap<string, ArrayList<IdCard>>();
+
+        duplicates = new ArrayList<ArrayList<IdCard>>();
+        LinkedList<IdCard> card_list = get_card_list() ;
+        if (card_list == null) {
+            return false;
+        }
+
         bool found = false;
-        do {
-           var cards = this.store.get_card_list();
-           found = false;
-           foreach (IdCard id_card in cards) {
-               if ((card != id_card) && (id_card.nai == card.nai)) {
-                  stdout.printf("removing duplicate id for '%s'\n", card.nai);
-                  remove_card_internal(id_card);
-                  found = duplicate_found = true;
-                  break;
-               }
-           }
-        } while (found);
-        return duplicate_found;
+        foreach (IdCard id_card in card_list) {
+            logger.trace(@"load_id_cards: Loading card with display name '$(id_card.display_name)'");
+
+            //!!TODO: This uniqueness check really belongs somewhere else -- like where we add
+            // IDs, and/or read them from storage. However, we should never hit this.
+
+            if (nais.has_key(id_card.nai)) {
+                ArrayList<IdCard> list = nais.get(id_card.nai);
+                list.add(id_card);
+            }
+            else {
+                ArrayList<IdCard> list = new ArrayList<IdCard>();
+                list.add(id_card);
+                nais.set(id_card.nai, list);
+            }
+        }
+
+        duplicates = new ArrayList<ArrayList<IdCard>>();
+        foreach (Map.Entry<string, ArrayList<IdCard>> entry in nais.entries) {
+            var list = entry.value;
+            if (list.size > 1) {
+                duplicates.add(list);
+                found = true;
+            }
+        }
+        return found;
     }
 
+
     public IdCard? find_id_card(string nai, bool force_flat_file_store) {
         IdCard? retval = null;
         IIdentityCardStore.StoreType saved_store_type = get_store_type();
@@ -178,9 +229,11 @@ public class IdentityManagerModel : Object {
         return retval;
     }
 
-    public void add_card(IdCard card, bool force_flat_file_store) {
-        if (card.temporary)
+    public void add_card(IdCard card, bool force_flat_file_store, out ArrayList<IdCard>? old_duplicates=null) {
+        if (card.temporary) {
+            logger.trace("add_card: card is temporary; returning.");
             return;
+        }
 
         string candidate;
         IIdentityCardStore.StoreType saved_store_type = get_store_type();
@@ -188,21 +241,27 @@ public class IdentityManagerModel : Object {
         if (force_flat_file_store)
             set_store_type(IIdentityCardStore.StoreType.FLAT_FILE);
 
-        remove_duplicates(card);
+        remove_duplicates(card, out old_duplicates);
 
-        if (!display_name_is_valid (card.display_name, out candidate))
+        if (!display_name_is_valid(card.display_name, out candidate))
         {
-          card.display_name = candidate;
+            card.display_name = candidate;
         }
 
         if (!card.store_password)
             password_table.CachePassword(card, store);
+
+        logger.trace("add_card: Adding card '%s' with services: '%s'"
+                     .printf(card.display_name, card.get_services_string("; ")));
+
         store.add_card(card);
         set_store_type(saved_store_type);
         card_list_changed();
-     }
+    }
+
+    public IdCard update_card(IdCard card) {
+        logger.trace("update_card");
 
-     public IdCard update_card(IdCard card) {
         IdCard retval;
         if (card.temporary) {
             retval = card;
@@ -216,60 +275,80 @@ public class IdentityManagerModel : Object {
         retval = store.update_card(card);
         card_list_changed();
         return retval;
-     }
+    }
 
-     private bool remove_card_internal(IdCard card) {
-         if (card.temporary)
-             return false;
-         password_table.RemovePassword(card, store);
-         return store.remove_card(card);
-     }
+    private bool remove_card_internal(IdCard card) {
+        if (card.temporary)
+            return false;
+        password_table.RemovePassword(card, store);
+        return store.remove_card(card);
+    }
 
-     public bool remove_card(IdCard card) {
-         if (remove_card_internal(card)) {
+    public bool remove_card(IdCard card) {
+        if (remove_card_internal(card)) {
+            logger.trace(@"remove_card: Removed '$(card.display_name)'");
             card_list_changed();
             return true;
-         }
-         return false;
-     }
-
-     public void set_store_type(IIdentityCardStore.StoreType type) {
-         if ((store != null) && (store.get_store_type() == type))
-             return;
-         switch (type) {
-#if GNOME_KEYRING
-             case IIdentityCardStore.StoreType.KEYRING:
-                 store = new KeyringStore();
-                 break;
-#endif
-             case IIdentityCardStore.StoreType.FLAT_FILE:
-             default:
-                 store = new LocalFlatFileStore();
-                 break;
-         }
-     }
-
-     public IIdentityCardStore.StoreType get_store_type() {
-         return store.get_store_type();
-     }
-
-     public bool HasNonTrivialIdentities() {
-         foreach (IdCard card in this.store.get_card_list()) {
-             // The 'NoIdentity' card is non-trivial if it has services or rules.
-             // All other cards are automatically non-trivial.
-             if ((!card.IsNoIdentity()) || 
-                 (card.services.length > 0) ||
-                 (card.rules.length > 0)) {
-                 return true;
-             }
-         }
-         return false;
-     }
+        }
+        logger.warn(@"remove_card: Couldn't remove '$(card.display_name)'");
+        return false;
+    }
+
+    // The name is misleading: This not only sets the store type,
+    // it also creates a new store instance, which loads the card data.
+    public void set_store_type(IIdentityCardStore.StoreType type) {
+        if ((store != null) && (store.get_store_type() == type))
+            return;
+        switch (type) {
+            #if GNOME_KEYRING
+        case IIdentityCardStore.StoreType.KEYRING:
+            store = new KeyringStore();
+            break;
+            #endif
+        case IIdentityCardStore.StoreType.FLAT_FILE:
+        default:
+            store = new LocalFlatFileStore();
+            break;
+        }
+
+        // Loop through the loaded IDs. If any trust anchors are old enough that we didn't record
+        // the datetime_added, add it now.
+        string before_now = _("Before ") + TrustAnchor.format_datetime_now();
+        bool save_needed = false;
+        foreach (IdCard id in this.store.get_card_list()) {
+            if (!id.trust_anchor.is_empty() && id.trust_anchor.datetime_added == "") {
+                logger.trace("set_store_type : Set ta_datetime_added for old trust anchor on '%s' to '%s'".printf(id.display_name, before_now));
+                id.trust_anchor.set_datetime_added(before_now);
+                save_needed = true;
+            }
+        }
+        if (save_needed) {
+            this.store.store_id_cards();
+        }
+    }
+
+    public IIdentityCardStore.StoreType get_store_type() {
+        return store.get_store_type();
+    }
+
+    public bool HasNonTrivialIdentities() {
+        foreach (IdCard card in this.store.get_card_list()) {
+            // The 'NoIdentity' card is non-trivial if it has services or rules.
+            // All other cards are automatically non-trivial.
+            if ((!card.is_no_identity()) || 
+                (card.services.size > 0) ||
+                (card.rules.length > 0)) {
+                return true;
+            }
+        }
+        return false;
+    }
 
 
     private IdentityManagerApp parent;
 
     public IdentityManagerModel(IdentityManagerApp parent_app, IIdentityCardStore.StoreType store_type) {
+        logger.trace("IdentityManagerModel: store_type=" + store_type.to_string());
         parent = parent_app;
         password_table = new PasswordHashTable();
         set_store_type(store_type);
diff --git a/src/moonshot-identity-dialog.vala b/src/moonshot-identity-dialog.vala
new file mode 100644 (file)
index 0000000..6f59f2f
--- /dev/null
@@ -0,0 +1,568 @@
+/*
+ * Copyright (c) 2016, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of JANET(UK) 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 HOLDER 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.
+*/
+
+using Gee;
+using Gtk;
+
+
+// Defined here as workaround for emacs vala-mode indentation failure.
+#if VALA_0_12
+static const string CANCEL = Stock.CANCEL;
+#else
+static const string CANCEL = STOCK_CANCEL;
+#endif
+
+
+// For use when exporting certificates.
+static string export_directory = null;
+
+class IdentityDialog : Dialog
+{
+    private static Gdk.Color white = make_color(65535, 65535, 65535);
+    private static Gdk.Color selected_color = make_color(0xd9 << 8, 0xf7 << 8, 65535);
+
+    private static MoonshotLogger logger = get_logger("IdentityDialog");
+
+    static const string displayname_labeltext = _("Display Name");
+    static const string realm_labeltext = _("Realm");
+    static const string username_labeltext = _("Username");
+    static const string password_labeltext = _("Password");
+
+    private Entry displayname_entry;
+    private Label displayname_label;
+    private Entry realm_entry;
+    private Label realm_label;
+    private Entry username_entry;
+    private Label username_label;
+    private Entry password_entry;
+    private Label password_label;
+    private CheckButton remember_checkbutton;
+    private Label message_label;
+    public bool complete;
+    private IdCard card;
+    private ArrayList<string> services;
+
+    private Label selected_item = null;
+
+    // Whether to clear the card's TrustAnchor after the user selects OK
+    internal bool clear_trust_anchor = false;
+
+    public string display_name {
+        get { return displayname_entry.get_text(); }
+    }
+
+    public string issuer {
+        get { return realm_entry.get_text(); }
+    }
+
+    public string username {
+        get { return username_entry.get_text(); }
+    }
+
+    public string password {
+        get { return password_entry.get_text(); }
+    }
+
+    public bool store_password {
+        get { return remember_checkbutton.active; }
+    }
+
+    /**
+     * Don't leave passwords in memory longer than necessary.
+     * This may not actually erase the password data bytes, but it seems to be the best we can do.
+     */
+    public void clear_password() {
+        clear_password_entry(password_entry);
+    }
+
+    internal ArrayList<string> get_services()
+    {
+        return services;
+    }
+
+    public IdentityDialog(IdentityManagerView parent)
+    {
+        this.with_idcard(null, _("Add ID Card"), parent);
+    }
+
+    public IdentityDialog.with_idcard(IdCard? a_card, string title, IdentityManagerView parent)
+    {
+        bool is_new_card = false;
+        if (a_card == null)
+        {
+            is_new_card = true;
+        }
+
+        card = a_card ?? new IdCard();
+        this.set_title(title);
+        this.set_modal(true);
+        this.set_transient_for(parent);
+
+        this.add_buttons(CANCEL, ResponseType.CANCEL, _("OK"), ResponseType.OK);
+        Box content_area = (Box) this.get_content_area();
+
+        displayname_label = new Label(@"$displayname_labeltext:");
+        displayname_label.set_alignment(0, (float) 0.5);
+        displayname_entry = new Entry();
+        displayname_entry.set_text(card.display_name);
+        displayname_entry.set_width_chars(40);
+
+        realm_label = new Label(@"$realm_labeltext:");
+        realm_label.set_alignment(0, (float) 0.5);
+        realm_entry = new Entry();
+        realm_entry.set_text(card.issuer);
+        realm_entry.set_width_chars(60);
+
+        username_label = new Label(@"$username_labeltext:");
+        username_label.set_alignment(0, (float) 0.5);
+        username_entry = new Entry();
+        username_entry.set_text(card.username);
+        username_entry.set_width_chars(40);
+
+        password_label = new Label(@"$password_labeltext:");
+        password_label.set_alignment(0, (float) 0.5);
+
+        remember_checkbutton = new CheckButton.with_label(_("Remember password"));
+        remember_checkbutton.active = card.store_password;
+
+        password_entry = new Entry();
+        password_entry.set_invisible_char('*');
+        password_entry.set_visibility(false);
+        password_entry.set_width_chars(40);
+        password_entry.set_text(card.password);
+
+        message_label = new Label("");
+        message_label.set_visible(false);
+
+        set_atk_relation(displayname_label, displayname_entry, Atk.RelationType.LABEL_FOR);
+        set_atk_relation(realm_label, realm_entry, Atk.RelationType.LABEL_FOR);
+        set_atk_relation(username_label, username_entry, Atk.RelationType.LABEL_FOR);
+        set_atk_relation(password_label, password_entry, Atk.RelationType.LABEL_FOR);
+
+        content_area.pack_start(message_label, false, false, 6);
+        add_as_vbox(content_area, displayname_label, displayname_entry);
+        add_as_vbox(content_area, username_label, username_entry);
+        add_as_vbox(content_area, realm_label, realm_entry);
+        add_as_vbox(content_area, password_label, password_entry);
+
+        var remember_hbox = new HBox(false, 40);
+        remember_hbox.pack_start(new HBox(false, 0), false, false, 0);
+        remember_hbox.pack_start(remember_checkbutton, false, false, 0);
+        content_area.pack_start(remember_hbox, false, false, 2);
+
+        this.response.connect(on_response);
+        content_area.set_border_width(6);
+
+        this.services = new ArrayList<string>();
+        this.services.add_all(card.services);
+
+        if (!is_new_card)
+        {
+            Widget trust_anchor_box = make_trust_anchor_box(card);
+            content_area.pack_start(trust_anchor_box, false, false, 15);
+
+            var services_vbox = make_services_vbox();
+            content_area.pack_start(services_vbox);
+            var services_vbox_bottom_spacer = new Alignment(0, 0, 0, 0);
+            services_vbox_bottom_spacer.set_size_request(0, 12);
+            content_area.pack_start(services_vbox_bottom_spacer, false, false, 0);
+        }
+
+        if (card.is_no_identity())
+        {
+            displayname_entry.set_sensitive(false);
+            realm_entry.set_sensitive(false);
+            username_entry.set_sensitive(false);
+            password_entry.set_sensitive(false);
+            remember_checkbutton.set_sensitive(false);
+        }
+
+        this.destroy.connect(() => {
+                logger.trace("Destroying IdentityDialog; clearing its password.");
+                this.clear_password();
+            });
+
+
+        this.set_border_width(6);
+        this.set_resizable(false);
+        set_bg_color(this);
+        this.show_all();
+    }
+
+    private Widget make_trust_anchor_box(IdCard id)
+    {
+
+        int nrows = 7;
+        int ncolumns = 2;
+        string ta_label_prefix = _("Trust anchor: ");
+        string none = _("None");
+
+        HBox trust_anchor_box = new HBox(false, 0);
+
+        Label ta_label = new Label(ta_label_prefix
+                                   + (id.trust_anchor.is_empty() ? none : _("Enterprise provisioned")));
+        ta_label.set_alignment(0, 0.5f);
+
+        if (id.trust_anchor.is_empty()) {
+            trust_anchor_box.pack_start(ta_label, false, false, 0);
+            return trust_anchor_box;
+        }
+
+
+        AttachOptions fill_and_expand = AttachOptions.EXPAND | AttachOptions.FILL;
+        AttachOptions fill = AttachOptions.FILL;
+
+        Table ta_table = new Table(nrows, ncolumns, false);
+        int row = 0;
+
+        var ta_clear_button = new Button.with_label(_("Clear Trust Anchor"));
+        ta_clear_button.clicked.connect((w) => {
+                var result = WarningDialog.confirm(this,
+                                                   Markup.printf_escaped(
+                                                       "<span font-weight='heavy'>" 
+                                                       + _("You are about to clear the trust anchor fingerprint for '%s'.") 
+                                                       + "</span>",
+                                                       id.display_name)
+                                                   + _("\n\nAre you sure you want to do this?"),
+                                                   "clear_trust_anchor");
+
+                if (result)
+                {
+                    clear_trust_anchor = true;
+
+                    // Clearing the trust_anchor_box's children, and then re-packing
+                    // a label into it, doesn't seem to work. Instead, let's clear out
+                    // the table's children, and then re-insert a label into it.
+                    var children = ta_table.get_children();
+                    foreach (var child in children) {
+                        ta_table.remove(child);
+                    }
+
+                    ta_table.resize(1, ncolumns);
+                    ta_label.set_text(ta_label_prefix + none);
+                    ta_table.attach(ta_label, 0, 1, 0, 1, 
+                                    fill_and_expand, fill_and_expand, 0, 0);
+
+                }
+            }
+            );
+
+        ta_table.attach(ta_label, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 0, 0);
+        ta_table.attach(ta_clear_button, 1, 2, row, row + 1, fill, fill, 0, 0);
+        row++;
+
+        Label added_label = new Label(_("Added: " + id.trust_anchor.datetime_added));
+        added_label.set_alignment(0, 0.5f);
+        ta_table.attach(added_label, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 20, 5);
+        row++;
+
+        if (id.trust_anchor.get_anchor_type() == TrustAnchor.TrustAnchorType.SERVER_CERT) {
+            Widget fingerprint = make_ta_fingerprint_widget(id.trust_anchor.server_cert);
+            ta_table.attach(fingerprint, 0, 2, row, row + 2, fill_and_expand, fill_and_expand, 5, 5);
+        }
+        else {
+            Label ca_cert_label = new Label(_("CA Certificate:"));
+            ca_cert_label.set_alignment(0, 0.5f);
+            var export_button = new Button.with_label(_("Export Certificate"));
+            export_button.clicked.connect((w) => {export_certificate(id);});
+
+            ta_table.attach(ca_cert_label, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 20, 0);
+            ta_table.attach(export_button, 1, 2, row, row + 1, fill, fill, 0, 0);
+            row++;
+
+            if (id.trust_anchor.subject != "") {
+                Label subject_label = new Label(_("Subject: ") + id.trust_anchor.subject);
+                subject_label.set_alignment(0, 0.5f);
+                ta_table.attach(subject_label, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 40, 5);
+                row++;
+            }
+
+            if (id.trust_anchor.subject_alt != "") {
+                Label subject_alt_label = new Label(_("Subject-Alt: ") + id.trust_anchor.subject_alt);
+                subject_alt_label.set_alignment(0, 0.5f);
+                ta_table.attach(subject_alt_label, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 40, 5);
+                row++;
+            }
+
+            Label expiration_label = new Label(_("Expiration date: ") + id.trust_anchor.get_expiration_date());
+            expiration_label.set_alignment(0, 0.5f);
+            ta_table.attach(expiration_label, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 40, 5);
+            row++;
+
+            //!!TODO: What goes here?
+            Label constraint_label = new Label(_("Constraint: "));
+            constraint_label.set_alignment(0, 0.5f);
+            ta_table.attach(constraint_label, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 20, 0);
+            row++;
+        }
+
+        trust_anchor_box.pack_start(ta_table, false, false, 0);
+        return trust_anchor_box;
+    }
+
+    private static void add_as_vbox(Box content_area, Label label, Entry entry)
+    {
+        VBox vbox = new VBox(false, 2);
+
+        vbox.pack_start(label, false, false, 0);
+        vbox.pack_start(entry, false, false, 0);
+
+        // Hack to prevent the text entries from stretching horizontally
+        HBox hbox = new HBox(false, 0);
+        hbox.pack_start(vbox, false, false, 0);
+        content_area.pack_start(hbox, false, false, 6);
+    }
+
+    private static string update_preamble(string preamble)
+    {
+        if (preamble == "")
+            return _("Missing required field: ");
+        return _("Missing required fields: ");
+    }
+
+    private static string update_message(string old_message, string new_item)
+    {
+        string message;
+        if (old_message == "")
+            message = new_item;
+        else
+            message = old_message + ", " + new_item;
+        return message;
+    }
+
+    private static void check_field(string field, Label label, string fieldname, ref string preamble, ref string message)
+    {
+        if (field != "") {
+            label.set_markup(@"$fieldname:");
+            return;
+        }
+        label.set_markup(@"<span foreground=\"red\">$fieldname:</span>");
+        preamble = update_preamble(preamble);
+        message = update_message(message, fieldname);
+    }
+
+    private bool check_fields()
+    {
+        string preamble = "";
+        string message = "";
+        string password_test = store_password ? password : "not required";
+        if (!card.is_no_identity())
+        {
+            check_field(display_name, displayname_label, displayname_labeltext, ref preamble, ref message);
+            check_field(username, username_label, username_labeltext, ref preamble, ref message);
+            check_field(issuer, realm_label, realm_labeltext, ref preamble, ref message);
+            check_field(password_test, password_label, password_labeltext, ref preamble, ref message);
+        }
+        if (message != "") {
+            message_label.set_visible(true);
+            message_label.set_markup(@"<span foreground=\"red\">$preamble$message</span>");
+            return false;
+        }
+        return true;
+    }
+
+    private void on_response(Dialog source, int response_id)
+    {
+        switch (response_id) {
+        case ResponseType.OK:
+            complete = check_fields();
+            break;
+        case ResponseType.CANCEL:
+            complete = true;
+            break;
+        }
+    }
+
+    private VBox make_services_vbox()
+    {
+        logger.trace("make_services_vbox");
+
+        var services_vbox_alignment = new Alignment(0, 0, 1, 0);
+        var services_vscroll = new ScrolledWindow(null, null);
+        services_vscroll.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
+        services_vscroll.set_shadow_type(ShadowType.IN);
+        services_vscroll.set_size_request(0, 60);
+        services_vscroll.add_with_viewport(services_vbox_alignment);
+
+#if VALA_0_12
+        var remove_button = new Button.from_stock(Stock.REMOVE);
+#else
+        var remove_button = new Button.from_stock(STOCK_REMOVE);
+#endif
+        remove_button.set_sensitive(false);
+
+
+        var services_table = new Table(card.services.size, 1, false);
+        services_table.set_row_spacings(1);
+        services_table.set_col_spacings(0);
+        set_bg_color(services_table);
+
+        var table_button_hbox = new HBox(false, 6);
+        table_button_hbox.pack_start(services_vscroll, true, true, 4);
+
+        // Hack to prevent the button from growing vertically
+        VBox fixed_height = new VBox(false, 0);
+        fixed_height.pack_start(remove_button, false, false, 0);
+        table_button_hbox.pack_start(fixed_height, false, false, 0);
+
+        // A table doesn't have a background color, so put it in an EventBox, and
+        // set the EventBox's background color instead.
+        EventBox table_bg = new EventBox();
+        set_bg_color(table_bg);
+        table_bg.add(services_table);
+        services_vbox_alignment.add(table_bg);
+
+        var services_vbox_title = new Label(_("Services:"));
+        services_vbox_title.set_alignment(0, 0.5f);
+
+        var services_vbox = new VBox(false, 6);
+        services_vbox.pack_start(services_vbox_title, false, false, 0);
+        services_vbox.pack_start(table_button_hbox, true, true, 0);
+
+        int i = 0;
+        foreach (string service in services)
+        {
+            var label = new Label(service);
+            label.set_alignment((float) 0, (float) 0);
+            label.xpad = 3;
+
+            EventBox event_box = new EventBox();
+            event_box.modify_bg(StateType.NORMAL, white);
+            event_box.add(label);
+            event_box.button_press_event.connect(() =>
+                {
+                    var state = label.get_state();
+                    logger.trace("button_press_callback: Label state=" + state.to_string() + " setting bg to " + white.to_string());
+
+                    if (selected_item == label)
+                    {
+                        // Deselect
+                        selected_item.parent.modify_bg(state, white);
+                        selected_item = null;
+                        remove_button.set_sensitive(false);
+                    }
+                    else
+                    {
+                        if (selected_item != null)
+                        {
+                            // Deselect
+                            selected_item.parent.modify_bg(state, white);
+                            selected_item = null;
+                        }
+
+                        // Select
+                        selected_item = label;
+                        selected_item.parent.modify_bg(state, selected_color);
+                        remove_button.set_sensitive(true);
+                    }
+                    return false;
+                });
+
+            services_table.attach_defaults(event_box, 0, 1, i, i+1);
+            i++;
+        }
+
+        remove_button.clicked.connect((remove_button) =>
+            {
+                var result = WarningDialog.confirm(this,
+                                                   Markup.printf_escaped(
+                                                       "<span font-weight='heavy'>"
+                                                       + _("You are about to remove the service\n'%s'.") 
+                                                       + "</span>",
+                                                       selected_item.label)
+                                                   + _("\n\nAre you sure you want to do this?"),
+                                                   "delete_service");
+
+                if (result)
+                {
+                    if (card != null) {
+                        services.remove(selected_item.label);
+                        services_table.remove(selected_item.parent);
+                        selected_item = null;
+                        remove_button.set_sensitive(false);
+                    }
+                }
+
+            });
+
+        return services_vbox;
+    }
+
+    private void export_certificate(IdCard id) 
+    {
+        var dialog = new FileChooserDialog("Save File",
+                                           this,
+                                           FileChooserAction.SAVE,
+                                           _("Cancel"),ResponseType.CANCEL,
+                                           _("Save"), ResponseType.ACCEPT,
+                                           null);
+        dialog.set_do_overwrite_confirmation(true);
+        if (export_directory != null) {
+            dialog.set_current_folder(export_directory);
+        }
+        // Remove slashes from the default filename.
+        string default_filename = 
+            (id.display_name + ".pem").replace(Path.DIR_SEPARATOR_S, "_");
+        dialog.set_current_name(default_filename);
+        if (dialog.run() == ResponseType.ACCEPT)
+        {
+            // Export the certificate in PEM format.
+
+            const string CERT_HEADER = "-----BEGIN CERTIFICATE-----\n";
+            const string CERT_FOOTER = "\n-----END CERTIFICATE-----\n";
+
+            // Strip any embedded newlines in the certificate...
+            string cert = id.trust_anchor.ca_cert.replace("\n", "");
+
+            // Re-embed newlines every 64 chars.
+            string newcert = CERT_HEADER;
+            while (cert.length > 63) {
+                newcert += cert[0:64];
+                newcert += "\n";
+                cert = cert[64:cert.length];
+            }
+            if (cert.length > 0) {
+                newcert += cert;
+            }
+            newcert += CERT_FOOTER;
+
+            string filename = dialog.get_filename();
+            var file  = File.new_for_path(filename);
+            var stream = file.replace(null, false, FileCreateFlags.PRIVATE);
+            stream.write(newcert.data);
+
+            // Save the parent directory to use as default for next save
+            export_directory = file.get_parent().get_path();
+        }
+        dialog.destroy();
+    }
+}
index d9d7c5a..110dd84 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 */
 using Gee;
 using Gtk;
+using WebProvisioning;
 
 public class IdentityManagerView : Window {
-    private const int WINDOW_WIDTH = 400;
+    static MoonshotLogger logger = get_logger("IdentityManagerView");
+
+    bool use_flat_file_store = false;
+
+    // The latest year in which Moonshot sources were modified.
+    private static int LATEST_EDIT_YEAR = 2016;
+
+    public static Gdk.Color white = make_color(65535, 65535, 65535);
+
+    private const int WINDOW_WIDTH = 700;
     private const int WINDOW_HEIGHT = 500;
     protected IdentityManagerApp parent_app;
-#if OS_MACOS
-       public OSXApplication osxApp;
-#endif
+    #if OS_MACOS
+        public OSXApplication osxApp;
+    #endif
     private UIManager ui_manager = new UIManager();
     private Entry search_entry;
-    private VBox vbox_right;
-    private VBox login_vbox;
-    private VBox services_vbox;
     private CustomVBox custom_vbox;
-    private VBox services_internal_vbox;
-
-    private Entry issuer_entry;
-    private Entry username_entry;
-    private Entry password_entry;
-    private Label prompting_service;
-    private Label no_identity_title;
-    private CheckButton remember_checkbutton;
-    private Button update_password_button;
+    private VBox service_prompt_vbox;
+    private Button edit_button;
+    private Button remove_button;
 
+    private Button send_button;
+    
     private Gtk.ListStore* listmodel;
     private TreeModelFilter filter;
 
-    public IdentityManagerModel identities_manager;
+    internal IdentityManagerModel identities_manager;
     private unowned SList<IdCard>    candidates;
 
-    public GLib.Queue<IdentityRequest> request_queue;
+    private GLib.Queue<IdentityRequest> request_queue;
+
+    internal CheckButton remember_identity_binding = null;
 
-    private HashTable<Gtk.Button, string> service_button_map;
+    private IdCard selected_card = null;
+
+    private string import_directory = null;
 
     private enum Columns
     {
@@ -75,46 +82,64 @@ public class IdentityManagerView : Window {
         N_COLUMNS
     }
 
-    private const string layout =
-"<menubar name='MenuBar'>" +
-"        <menu name='FileMenu' action='FileMenuAction'>" +
-"            <menuitem name='AddIdCard' action='AddIdCardAction' />" +
-"            <separator />" +
-"            <menuitem name='Quit' action='QuitAction' />" +
-"        </menu>" +
-"" +
-"        <menu name='HelpMenu' action='HelpMenuAction'>" +
-"             <menuitem name='About' action='AboutAction' />" +
-"        </menu>" +
-"</menubar>";
-
-    public IdentityManagerView(IdentityManagerApp app) {
-       parent_app = app;
-#if OS_MACOS
-               osxApp = OSXApplication.get_instance();
-#endif
-          identities_manager = parent_app.model;
-       request_queue = new GLib.Queue<IdentityRequest>();
-       service_button_map = new HashTable<Gtk.Button, string> (direct_hash, direct_equal);
-       this.title = "Moonshot Identity Selector";
-       this.set_position (WindowPosition.CENTER);
-       set_default_size (WINDOW_WIDTH, WINDOW_HEIGHT);
-       build_ui();
-       setup_list_model(); 
-       load_id_cards(); 
-       connect_signals();
+    private const string menu_layout =
+    "<menubar name='MenuBar'>" +
+    "        <menu name='HelpMenu' action='HelpMenuAction'>" +
+    "             <menuitem name='About' action='AboutAction' />" +
+    "        </menu>" +
+    "</menubar>";
+
+    public IdentityManagerView(IdentityManagerApp app, bool use_flat_file_store) {
+        parent_app = app;
+        this.use_flat_file_store = use_flat_file_store;
+
+        #if OS_MACOS
+            osxApp = OSXApplication.get_instance();
+        #endif
+        identities_manager = parent_app.model;
+        request_queue = new GLib.Queue<IdentityRequest>();
+        this.title = _("Moonshot Identity Selector");
+        this.set_position(WindowPosition.CENTER);
+        set_default_size(WINDOW_WIDTH, WINDOW_HEIGHT);
+        build_ui();
+        setup_list_model(); 
+        load_id_cards();
+        connect_signals();
+        report_duplicate_nais(); 
     }
     
-    public void on_card_list_changed () {
+    private void report_duplicate_nais() {
+        ArrayList<ArrayList<IdCard>> duplicates;
+        identities_manager.find_duplicate_nai_sets(out duplicates);
+        foreach (ArrayList<IdCard> list in duplicates) {
+            string message = _("The following identities use the same Network Access Identifier (NAI),\n'%s'.").printf(list.get(0).nai)
+                + _("\n\nDuplicate NAIs are not allowed. Please remove identities you don't need, or modify") 
+                + _(" user ID or issuer fields so that they are no longer the same NAI.");
+
+            foreach (var card in list) {
+                message += "\n\nDisplay Name: '%s'\nServices:\n     %s".printf(card.display_name, card.get_services_string(",\n     "));
+            }
+            var msg_dialog = new Gtk.MessageDialog(this,
+                                                   Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                                   Gtk.MessageType.INFO,
+                                                   Gtk.ButtonsType.OK,
+                                                   message);
+            msg_dialog.run();
+            msg_dialog.destroy();
+        }
+    }
+
+    private void on_card_list_changed() {
+        logger.trace("on_card_list_changed");
         load_id_cards();
     }
     
-    private bool visible_func (TreeModel model, TreeIter iter)
+    private bool visible_func(TreeModel model, TreeIter iter)
     {
         IdCard id_card;
 
-        model.get (iter,
-                   Columns.IDCARD_COL, out id_card);
+        model.get(iter,
+                  Columns.IDCARD_COL, out id_card);
 
         if (id_card == null)
             return false;
@@ -131,7 +156,7 @@ public class IdentityManagerView : Window {
                 return false;
         }
         
-        string entry_text = search_entry.get_text ();
+        string entry_text = search_entry.get_text();
         if (entry_text == null || entry_text == "")
         {
             return true;
@@ -143,31 +168,31 @@ public class IdentityManagerView : Window {
                 continue;
          
 
-            string search_text_casefold = search_text.casefold ();
+            string search_text_casefold = search_text.casefold();
 
             if (id_card.issuer != null)
             {
-              string issuer_casefold = id_card.issuer;
+                string issuer_casefold = id_card.issuer;
 
-              if (issuer_casefold.contains (search_text_casefold))
-                  return true;
+                if (issuer_casefold.contains(search_text_casefold))
+                    return true;
             }
 
             if (id_card.display_name != null)
             {
-                string display_name_casefold = id_card.display_name.casefold ();
+                string display_name_casefold = id_card.display_name.casefold();
               
-                if (display_name_casefold.contains (search_text_casefold))
+                if (display_name_casefold.contains(search_text_casefold))
                     return true;
             }
             
-            if (id_card.services.length > 0)
+            if (id_card.services.size > 0)
             {
                 foreach (string service in id_card.services)
                 {
-                    string service_casefold = service.casefold ();
+                    string service_casefold = service.casefold();
 
-                    if (service_casefold.contains (search_text_casefold))
+                    if (service_casefold.contains(search_text_casefold))
                         return true;
                 }
             }
@@ -175,84 +200,38 @@ public class IdentityManagerView : Window {
         return false;
     }
 
-    private void setup_list_model ()
+    private void setup_list_model()
     {
-      this.listmodel = new Gtk.ListStore (Columns.N_COLUMNS, typeof (IdCard),
-                                                          typeof (Gdk.Pixbuf),
-                                                          typeof (string),
-                                                          typeof (string),
-                                                          typeof (string));
-      this.filter = new TreeModelFilter (listmodel, null);
+        this.listmodel = new Gtk.ListStore(Columns.N_COLUMNS, typeof(IdCard),
+                                           typeof(Gdk.Pixbuf),
+                                           typeof(string),
+                                           typeof(string),
+                                           typeof(string));
+        this.filter = new TreeModelFilter(listmodel, null);
 
-      filter.set_visible_func (visible_func);
-    }
-
-    private void search_entry_icon_press_cb (EntryIconPosition pos, Gdk.Event event)
-    {
-        if (pos == EntryIconPosition.PRIMARY)
-        {
-            print ("Search entry icon pressed\n");
-        }
-        else
-        {
-            this.search_entry.set_text ("");
-        }
+        filter.set_visible_func(visible_func);
     }
 
-    private void search_entry_text_changed_cb ()
+    private void search_entry_text_changed_cb()
     {
-        this.filter.refilter ();
-        redraw_id_card_widgets ();
-
-        var has_text = this.search_entry.get_text_length () > 0;
-        this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, has_text);
-        this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, has_text);
-
-        this.vbox_right.set_visible (false);
+        this.filter.refilter();
+        redraw_id_card_widgets();
     }
 
-    private bool search_entry_key_press_event_cb (Gdk.EventKey e)
+    private bool search_entry_key_press_event_cb(Gdk.EventKey e)
     {
         if(Gdk.keyval_name(e.keyval) == "Escape")
-           this.search_entry.set_text("");
+            this.search_entry.set_text("");
 
         // Continue processing this event, since the
         // text entry functionality needs to see it too.
         return false;
     }
 
-    private void update_password_cb()
-    {
-        if (this.custom_vbox.current_idcard != null) {
-            var identity = this.custom_vbox.current_idcard.id_card;
-            var dialog = new AddPasswordDialog(identity, null);
-            var result = dialog.run ();
-
-            switch (result) {
-            case ResponseType.OK:
-                identity.password = dialog.password;
-                identity.store_password = dialog.remember;
-                if (dialog.remember)
-                    identity.temporary = false;
-                identity = identities_manager.update_card(identity);
-                break;
-            default:
-                break;
-            }
-            dialog.destroy ();
-        }
-    }
+    private void load_id_cards() {
+        logger.trace("load_id_cards");
 
-    private void load_id_cards () {
-        string current_idcard_nai = null;
-        if (this.custom_vbox.current_idcard != null) {
-            current_idcard_nai = custom_vbox.current_idcard.id_card.nai;
-            custom_vbox.current_idcard = null;
-        }
-        var children = this.custom_vbox.get_children ();
-        foreach (var id_card_widget in children) {
-        remove_id_card_widget((IdCardWidget)id_card_widget);
-        }   
+        custom_vbox.clear();
         this.listmodel->clear();
         LinkedList<IdCard> card_list = identities_manager.get_card_list() ;
         if (card_list == null) {
@@ -260,79 +239,36 @@ public class IdentityManagerView : Window {
         }
 
         foreach (IdCard id_card in card_list) {
-            add_id_card_data (id_card);
-            IdCardWidget id_card_widget = add_id_card_widget (id_card);
-            if (id_card_widget.id_card.nai == current_idcard_nai) {
-                fill_details(id_card_widget);
-                id_card_widget.expand();
-            }
+            logger.trace(@"load_id_cards: Loading card with display name '$(id_card.display_name)'");
+            add_id_card_data(id_card);
+            add_id_card_widget(id_card);
         }
-        if (custom_vbox.current_idcard == null)
-            fill_details(null);
     }
     
-    private void fill_details (IdCardWidget? id_card_widget)
-    {
-        var vr_children = this.vbox_right.get_children();
-        foreach (var vr_child in vr_children)
-            this.vbox_right.remove(vr_child);
-        if (id_card_widget != null) {
-            var id_card = id_card_widget.id_card;
-            if (id_card.display_name == IdCard.NO_IDENTITY) {
-               this.vbox_right.pack_start(no_identity_title, false, true, 0);
-            } else {
-                this.issuer_entry.set_text (id_card.issuer);
-               this.username_entry.set_text (id_card.username);
-               this.password_entry.set_text (id_card.password ?? "");
-               this.vbox_right.pack_start(login_vbox, false, true, 0);
-               this.remember_checkbutton.active = id_card.store_password;
-            }
-            this.vbox_right.pack_start (services_vbox, false, true, 0);
-
-            var children = this.services_internal_vbox.get_children ();
-            foreach (var hbox in children)
-               services_internal_vbox.remove(hbox);
-            fill_services_vbox (id_card_widget.id_card);
-        }
-    }
-
-    private void show_details (IdCard id_card)
-    {
-       this.vbox_right.set_visible (!vbox_right.get_visible ());
-
-       if (this.vbox_right.get_visible () == false)
-       {
-           this.resize (WINDOW_WIDTH, WINDOW_HEIGHT);
-       }
-    }
-
-    private void details_identity_cb (IdCardWidget id_card_widget)
-    {
-       fill_details (id_card_widget);
-       show_details (id_card_widget.id_card);
-    }
-
-    private IdCard get_id_card_data (AddIdentityDialog dialog)
+    private IdCard update_id_card_data(IdentityDialog dialog, IdCard id_card)
     {
-        var id_card = new IdCard ();
-
         id_card.display_name = dialog.display_name;
         id_card.issuer = dialog.issuer;
         id_card.username = dialog.username;
         id_card.password = dialog.password;
         id_card.store_password = dialog.store_password;
-        id_card.services = {};
+
+        id_card.update_services_from_list(dialog.get_services());
+
+        if (dialog.clear_trust_anchor) {
+            id_card.clear_trust_anchor();
+        }
 
         return id_card;
     }
 
-    private void add_id_card_data (IdCard id_card)
+    private void add_id_card_data(IdCard id_card)
     {
         TreeIter   iter;
         Gdk.Pixbuf pixbuf;
-        this.listmodel->append (out iter);
+        this.listmodel->append(out iter);
         pixbuf = get_pixbuf(id_card);
-        listmodel->set (iter,
+        listmodel->set(iter,
                        Columns.IDCARD_COL, id_card,
                        Columns.LOGO_COL, pixbuf,
                        Columns.ISSUER_COL, id_card.issuer,
@@ -340,198 +276,292 @@ public class IdentityManagerView : Window {
                        Columns.PASSWORD_COL, id_card.password);
     }
 
-    private void remove_id_card_data (IdCard id_card)
+    private IdCardWidget add_id_card_widget(IdCard id_card)
     {
-        TreeIter iter;
-        string issuer;
+        logger.trace("add_id_card_widget: id_card.nai='%s'; selected nai='%s'"
+                     .printf(id_card.nai, 
+                             this.selected_card == null ? "[null selection]" : this.selected_card.nai));
+
+
+        var id_card_widget = new IdCardWidget(id_card, this);
+        this.custom_vbox.add_id_card_widget(id_card_widget);
+        id_card_widget.expanded.connect(this.widget_selected_cb);
+        id_card_widget.collapsed.connect(this.widget_unselected_cb);
+
+        if (this.selected_card != null && this.selected_card.nai == id_card.nai) {
+            logger.trace(@"add_id_card_widget: Expanding selected idcard widget");
+            id_card_widget.expand();
+
+            // After a card is added, modified, or deleted, we reload all the cards.
+            // (I'm not sure why, or if it's necessary to do this.) This means that the
+            // selected_card may now point to a card instance that's not in the current list.
+            // Hence the only way to carry the selection across reloads is to identify
+            // the selected card by its NAI. And hence we need to reset what our idea of the
+            // "selected card" is.
+            // There should be a better way to do this, especially since we're not great
+            // at preventing duplicate NAIs.
+            this.selected_card = id_card;
+        }
+        return id_card_widget;
+    }
 
-        if (listmodel->get_iter_first (out iter))
-        {
-            do
-            {
-                listmodel->get (iter,
-                               Columns.ISSUER_COL, out issuer);
+    private void widget_selected_cb(IdCardWidget id_card_widget)
+    {
+        logger.trace(@"widget_selected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'");
 
-                if (id_card.issuer == issuer)
-                {
-                    listmodel->remove (iter);
-                    break;
-                }
-            }
-            while (listmodel->iter_next (ref iter));
-        }
+        this.selected_card = id_card_widget.id_card;
+        bool allow_removes = !id_card_widget.id_card.is_no_identity();
+        this.remove_button.set_sensitive(allow_removes);
+        this.edit_button.set_sensitive(true);
+        this.custom_vbox.receive_expanded_event(id_card_widget);
+
+        if (this.selection_in_progress())
+             this.send_button.set_sensitive(true);
     }
 
-    private IdCardWidget add_id_card_widget (IdCard id_card)
+    private void widget_unselected_cb(IdCardWidget id_card_widget)
     {
-        var id_card_widget = new IdCardWidget (id_card);
-        this.custom_vbox.add_id_card_widget (id_card_widget);
-        id_card_widget.details_id.connect (details_identity_cb);
-        id_card_widget.remove_id.connect (remove_identity_cb);
-        id_card_widget.send_id.connect ((w) => send_identity_cb (w.id_card));
-        id_card_widget.expanded.connect (this.custom_vbox.receive_expanded_event);
-        id_card_widget.expanded.connect (fill_details);
-        return id_card_widget;
+        logger.trace(@"widget_unselected_cb: id_card_widget.id_card.display_name='$(id_card_widget.id_card.display_name)'");
+
+        this.selected_card = null;
+        this.remove_button.set_sensitive(false);
+        this.edit_button.set_sensitive(false);
+        this.custom_vbox.receive_collapsed_event(id_card_widget);
+
+        this.send_button.set_sensitive(false);
     }
 
-    public bool add_identity (IdCard id_card, bool force_flat_file_store)
+    public bool add_identity(IdCard id_card, bool force_flat_file_store, out ArrayList<IdCard>? old_duplicates=null)
     {
-#if OS_MACOS
+        #if OS_MACOS
         /* 
          * TODO: We should have a confirmation dialog, but currently it will crash on Mac OS
          * so for now we will install silently
          */
         var ret = Gtk.ResponseType.YES;
-#else
+        #else
         Gtk.MessageDialog dialog;
         IdCard? prev_id = identities_manager.find_id_card(id_card.nai, force_flat_file_store);
+        logger.trace("add_identity(flat=%s, card='%s'): find_id_card returned %s"
+                     .printf(force_flat_file_store.to_string(), id_card.display_name, (prev_id != null ? prev_id.display_name : "null")));
         if (prev_id!=null) {
             int flags = prev_id.Compare(id_card);
+            logger.trace("add_identity: compare returned " + flags.to_string());
             if (flags == 0) {
+                if (&old_duplicates != null) {
+                    old_duplicates = new ArrayList<IdCard>();
+                }
+
                 return false; // no changes, no need to update
-            } else if ((flags & (1<<IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
-                dialog = new Gtk.MessageDialog (this,
-                                            Gtk.DialogFlags.DESTROY_WITH_PARENT,
-                                            Gtk.MessageType.QUESTION,
-                                            Gtk.ButtonsType.YES_NO,
-                                            _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
-                                            prev_id.display_name,
-                                            prev_id.nai,
-                                            id_card.display_name);
+            } else if ((flags & (1 << IdCard.DiffFlags.DISPLAY_NAME)) != 0) {
+                dialog = new Gtk.MessageDialog(this,
+                                               Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                               Gtk.MessageType.QUESTION,
+                                               Gtk.ButtonsType.YES_NO,
+                                               _("Would you like to replace ID Card '%s' using nai '%s' with the new ID Card '%s'?"),
+                                               prev_id.display_name,
+                                               prev_id.nai,
+                                               id_card.display_name);
             } else {
-                dialog = new Gtk.MessageDialog (this,
-                                            Gtk.DialogFlags.DESTROY_WITH_PARENT,
-                                            Gtk.MessageType.QUESTION,
-                                            Gtk.ButtonsType.YES_NO,
-                                            _("Would you like to update ID Card '%s' using nai '%s'?"),
-                                            id_card.display_name,
-                                            id_card.nai);
+                dialog = new Gtk.MessageDialog(this,
+                                               Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                               Gtk.MessageType.QUESTION,
+                                               Gtk.ButtonsType.YES_NO,
+                                               _("Would you like to update ID Card '%s' using nai '%s'?"),
+                                               id_card.display_name,
+                                               id_card.nai);
             }
         } else {
-            dialog = new Gtk.MessageDialog (this,
-                                            Gtk.DialogFlags.DESTROY_WITH_PARENT,
-                                            Gtk.MessageType.QUESTION,
-                                            Gtk.ButtonsType.YES_NO,
-                                            _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
-                                            id_card.display_name);
+            dialog = new Gtk.MessageDialog(this,
+                                           Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                           Gtk.MessageType.QUESTION,
+                                           Gtk.ButtonsType.YES_NO,
+                                           _("Would you like to add '%s' ID Card to the ID Card Organizer?"),
+                                           id_card.display_name);
         }
-        var ret = dialog.run ();
-        dialog.destroy ();
-#endif
+        var ret = dialog.run();
+        dialog.destroy();
+        #endif
 
         if (ret == Gtk.ResponseType.YES) {
-            this.identities_manager.add_card (id_card, force_flat_file_store);
+            this.identities_manager.add_card(id_card, force_flat_file_store, out old_duplicates);
             return true;
         }
-
-        return false;
+        else {
+            if (&old_duplicates != null) {
+                old_duplicates = new ArrayList<IdCard>();
+            }
+            return false;
+        }
     }
 
-    private void add_identity_manual_cb ()
+    private void add_identity_cb()
     {
-        var dialog = new AddIdentityDialog ();
+        var dialog = new IdentityDialog(this);
         int result = ResponseType.CANCEL;
         while (!dialog.complete)
-            result = dialog.run ();
+            result = dialog.run();
 
         switch (result) {
         case ResponseType.OK:
-            this.identities_manager.add_card (get_id_card_data (dialog), false);
+            this.identities_manager.add_card(update_id_card_data(dialog, new IdCard()), false);
             break;
         default:
             break;
         }
-        dialog.destroy ();
+        dialog.destroy();
     }
 
-    private void remove_id_card_widget (IdCardWidget id_card_widget) {
-       this.custom_vbox.remove_id_card_widget (id_card_widget);
+    private void edit_identity_cb(IdCard card)
+    {
+        var dialog = new IdentityDialog.with_idcard(card, _("Edit Identity"), this);
+        int result = ResponseType.CANCEL;
+        while (!dialog.complete)
+            result = dialog.run();
+
+        switch (result) {
+        case ResponseType.OK:
+            this.identities_manager.update_card(update_id_card_data(dialog, card));
+
+            // Make sure we haven't created a duplicate NAI via this update.
+            report_duplicate_nais();
+            break;
+        default:
+            break;
+        }
+        dialog.destroy();
     }
 
-    private void remove_identity (IdCardWidget id_card_widget)
+    private void remove_identity(IdCard id_card)
     {
-        var id_card = id_card_widget.id_card;
-        remove_id_card_widget (id_card_widget);
+        logger.trace(@"remove_identity: id_card.display_name='$(id_card.display_name)'");
 
+        this.selected_card = null;
         this.identities_manager.remove_card(id_card);
+
+        // Nothing is selected, so disable buttons
+        this.edit_button.set_sensitive(false);
+        this.remove_button.set_sensitive(false);
+        this.send_button.set_sensitive(false);
     }
 
-    private void redraw_id_card_widgets ()
+    private void redraw_id_card_widgets()
     {
         TreeIter iter;
         IdCard id_card;
 
-        var children = this.custom_vbox.get_children ();
-        foreach (var id_card_widget in children)
-            remove_id_card_widget((IdCardWidget )id_card_widget); //id_card_widget.destroy();
+        this.custom_vbox.clear();
 
-        if (filter.get_iter_first (out iter))
+        if (filter.get_iter_first(out iter))
         {
             do
             {
-                filter.get (iter,
-                            Columns.IDCARD_COL, out id_card);
+                filter.get(iter,
+                           Columns.IDCARD_COL, out id_card);
 
-                add_id_card_widget (id_card);
+                add_id_card_widget(id_card);
             }
-            while (filter.iter_next (ref iter));
+            while (filter.iter_next(ref iter));
         }
     }
 
-    private void remove_identity_cb (IdCardWidget id_card_widget)
+    private void remove_identity_cb(IdCard id_card)
     {
-        var id_card = id_card_widget.id_card;
-
-        var dialog = new MessageDialog (this,
-                                        DialogFlags.DESTROY_WITH_PARENT,
-                                        MessageType.QUESTION,
-                                        Gtk.ButtonsType.YES_NO,
-                                        _("Are you sure you want to delete %s ID Card?"), id_card.issuer);
-        var result = dialog.run ();
-        switch (result) {
-        case ResponseType.YES:
-            remove_identity (id_card_widget);
-            break;
-        default:
-            break;
-        }
-        dialog.destroy ();
+        bool remove = WarningDialog.confirm(this, 
+                                            Markup.printf_escaped(
+                                                "<span font-weight='heavy'>" + _("You are about to remove the identity '%s'.") + "</span>",
+                                                id_card.display_name)
+                                            + "\n\n" + _("Are you sure you want to do this?"),
+                                            "delete_idcard");
+        if (remove) 
+            remove_identity(id_card);
     }
 
-    public void set_prompting_service(string service)
+    private void set_prompting_service(string service)
     {
-        prompting_service.set_label( _("Identity requested for service: %s").printf(service) );
+        clear_selection_prompts();
+
+        var prompting_service = new Label(_("Identity requested for service:\n%s").printf(service));
+        prompting_service.set_line_wrap(true);
+
+        // left-align
+        prompting_service.set_alignment(0, (float )0.5);
+
+        var selection_prompt = new Label(_("Select your identity:"));
+        selection_prompt.set_alignment(0, 1);
+
+        this.service_prompt_vbox.pack_start(prompting_service, false, false, 12);
+        this.service_prompt_vbox.pack_start(selection_prompt, false, false, 2);
+        this.service_prompt_vbox.show_all();
+    }
+
+    private void clear_selection_prompts()
+    {
+        var list = service_prompt_vbox.get_children();
+        foreach (Widget w in list)
+        {
+            service_prompt_vbox.remove(w);
+        }
     }
 
+
     public void queue_identity_request(IdentityRequest request)
     {
-        if (this.request_queue.is_empty())
+        bool queue_was_empty = !this.selection_in_progress();
+        this.request_queue.push_tail(request);
+
+        if (queue_was_empty)
         { /* setup widgets */
             candidates = request.candidates;
             filter.refilter();
-            redraw_id_card_widgets ();
+            redraw_id_card_widgets();
             set_prompting_service(request.service);
-            show ();
+            remember_identity_binding.show();
+
+            if (this.custom_vbox.find_idcard_widget(this.selected_card) != null) {
+                // A widget is already selected, and has not been filtered out of the display via search
+                send_button.set_sensitive(true);
+            }
+
+            make_visible();
         }
-        this.request_queue.push_tail (request);
+    }
+
+
+    /** Makes the window visible, or at least, notifies the user that the window
+     * wants to be visible.
+     *
+     * This differs from show() in that show() does not guarantee that the 
+     * window will be moved to the foreground. Actually, neither does this
+     * method, because the user's settings and window manager may affect the
+     * behavior significantly.
+     */
+    public void make_visible()
+    {
+        set_urgency_hint(true);
+        present();
     }
 
     public IdCard check_add_password(IdCard identity, IdentityRequest request, IdentityManagerModel model)
     {
+        logger.trace(@"check_add_password");
         IdCard retval = identity;
         bool idcard_has_pw = (identity.password != null) && (identity.password != "");
         bool request_has_pw = (request.password != null) && (request.password != "");
-        if ((!idcard_has_pw) && (!identity.IsNoIdentity())) {
+        if ((!idcard_has_pw) && (!identity.is_no_identity())) {
             if (request_has_pw) {
                 identity.password = request.password;
                 retval = model.update_card(identity);
             } else {
-                var dialog = new AddPasswordDialog (identity, request);
-                var result = dialog.run ();
+                var dialog = new AddPasswordDialog(identity, request);
+                var result = dialog.run();
 
                 switch (result) {
                 case ResponseType.OK:
                     identity.password = dialog.password;
+                    // Don't leave passwords in memory longer than necessary.
+                    // (This may not actually clear the data, but it's the best we can do.)
+                    dialog.clear_password();
                     identity.store_password = dialog.remember;
                     if (dialog.remember)
                         identity.temporary = false;
@@ -541,28 +571,32 @@ public class IdentityManagerView : Window {
                     identity = null;
                     break;
                 }
-                dialog.destroy ();
+                // Do this again, in case OK button wasn't selected.
+                dialog.clear_password();
+                dialog.destroy();
             }
         }
         return retval;
     }
 
-    public void send_identity_cb (IdCard id)
+    private void send_identity_cb(IdCard id)
     {
-        IdCard identity = id;
-        return_if_fail (request_queue.length > 0);
+        return_if_fail(this.selection_in_progress());
+
+        var request = this.request_queue.pop_head();
+        var identity = check_add_password(id, request, identities_manager);
+        send_button.set_sensitive(false);
 
-       candidates = null;
-        var request = this.request_queue.pop_head ();
-        identity = check_add_password(identity, request, identities_manager);
-        if (this.request_queue.is_empty())
+        candidates = null;
+      
+        if (!this.selection_in_progress())
         {
             candidates = null;
-            prompting_service.set_label(_(""));
+            clear_selection_prompts();
             if (!parent_app.explicitly_launched) {
 // The following occasionally causes the app to exit without sending the dbus
 // reply, so for now we just don't exit
-//                Gtk.main_quit ();
+//                Gtk.main_quit();
 // just hide instead
                 this.hide();
             }
@@ -572,113 +606,24 @@ public class IdentityManagerView : Window {
             set_prompting_service(next.service);
         }
         filter.refilter();
-        redraw_id_card_widgets ();
+        redraw_id_card_widgets();
 
-        if ((identity != null) && (!identity.IsNoIdentity()))
+        if ((identity != null) && (!identity.is_no_identity()))
             parent_app.default_id_card = identity;
 
-        request.return_identity (identity);
-    }
-
-    private void label_make_bold (Label label)
-    {
-        var font_desc = new Pango.FontDescription ();
-
-        font_desc.set_weight (Pango.Weight.BOLD);
+        request.return_identity(identity, remember_identity_binding.active);
 
-        /* This will only affect the weight of the font, the rest is
-         * from the current state of the widget, which comes from the
-         * theme or user prefs, since the font desc only has the
-         * weight flag turned on.
-         */
-        label.modify_font (font_desc);
-    }
-
-    private void fill_services_vbox (IdCard id_card)
-    {
-        int i = 0;
-        var n_columns = id_card.services.length;
-
-        var services_table = new Table (n_columns, 2, false);
-        services_table.set_col_spacings (10);
-        services_table.set_row_spacings (10);
-        this.services_internal_vbox.add (services_table);
-        
-        service_button_map.remove_all ();
-
-        foreach (string service in id_card.services)
-        {
-            var label = new Label (service);
-            label.set_alignment (0, (float) 0.5);
-#if VALA_0_12
-            var remove_button = new Button.from_stock (Stock.REMOVE);
-#else
-            var remove_button = new Button.from_stock (STOCK_REMOVE);
-#endif
-
-
-            service_button_map.insert (remove_button, service);
-            
-            remove_button.clicked.connect ((remove_button) =>
-            {
-              var candidate = service_button_map.lookup (remove_button);
-              if (candidate == null)
-                return;
-              var dialog = new Gtk.MessageDialog (this,
-                                      Gtk.DialogFlags.DESTROY_WITH_PARENT,
-                                      Gtk.MessageType.QUESTION,
-                                      Gtk.ButtonsType.YES_NO,
-                                      _("Are you sure you want to stop '%s' ID Card from being used with %s?"),
-                                      custom_vbox.current_idcard.id_card.display_name,
-                                      candidate);
-              var ret = dialog.run();
-              dialog.hide();
-              
-              if (ret == Gtk.ResponseType.YES)
-              {
-                IdCard idcard = custom_vbox.current_idcard.id_card;
-                if (idcard != null) {
-                  SList<string> services = new SList<string>();
-                
-                  foreach (string srv in idcard.services)
-                  {
-                    if (srv == candidate)
-                      continue;
-                    services.append (srv);
-                  }
-                
-                  idcard.services = new string[services.length()];
-                  for (int j=0; j<idcard.services.length; j++)
-                  {
-                    idcard.services[j] = services.nth_data(j);
-                  }
-                
-                  identities_manager.update_card(idcard);
-                }
-              }
-              
-            });
-            services_table.attach_defaults (label, 0, 1, i, i+1);
-            services_table.attach_defaults (remove_button, 1, 2, i, i+1);
-            i++;
-        }
-        this.services_internal_vbox.show_all ();
+        remember_identity_binding.active = true;
+        remember_identity_binding.hide();
     }
 
-    private void on_about_action ()
+    private void on_about_action()
     {
-        string[] authors = {
-            "Javier Jardón <jjardon@codethink.co.uk>",
-            "Sam Thursfield <samthursfield@codethink.co.uk>",
-            "Alberto Ruiz <alberto.ruiz@codethink.co.uk>",
-            null
-        };
-
-        string copyright = "Copyright 2011 JANET";
+        string copyright = "Copyright (c) 2011, %d JANET".printf(LATEST_EDIT_YEAR);
 
         string license =
-"""
-Copyright (c) 2011, JANET(UK)
+        """
+Copyright (c) 2011, %d JANET(UK)
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -707,247 +652,314 @@ 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.
-""";
-
-        Gtk.show_about_dialog (this,
-            "comments", _("Moonshot project UI"),
-            "copyright", copyright,
-            "website", Config.PACKAGE_URL,
-            "version", Config.PACKAGE_VERSION,
-            "license", license,
-            "website-label", _("Visit the Moonshot project web site"),
-            "authors", authors,
-            "translator-credits", _("translator-credits"),
-            null
-        );
+""".printf(LATEST_EDIT_YEAR);
+
+        AboutDialog about = new AboutDialog();
+
+        about.set_comments(_("Moonshot project UI"));
+        about.set_copyright(copyright);
+        about.set_website(Config.PACKAGE_URL);
+        about.set_website_label(_("Visit the Moonshot project web site"));
+
+        // Note: The package version is configured at the top of moonshot/ui/configure.ac
+        about.set_version(Config.PACKAGE_VERSION);
+        about.set_license(license);
+        about.set_modal(true);
+        about.set_transient_for(this);
+        about.response.connect((a, b) => {about.destroy();});
+        set_bg_color(about);
+        
+        about.run();
     }
 
     private Gtk.ActionEntry[] create_actions() {
         Gtk.ActionEntry[] actions = new Gtk.ActionEntry[0];
 
-        Gtk.ActionEntry filemenu = { "FileMenuAction",
-                                     null,
-                                     N_("_File"),
-                                     null, null, null };
-        actions += filemenu;
-        Gtk.ActionEntry add = { "AddIdCardAction",
-#if VALA_0_12
-                                Stock.ADD,
-#else
-                                STOCK_ADD,
-#endif
-                                N_("Add ID Card"),
-                                null,
-                                N_("Add a new ID Card"),
-                                add_identity_manual_cb };
-        actions += add;
-        Gtk.ActionEntry quit = { "QuitAction",
-#if VALA_0_12
-                                 Stock.QUIT,
-#else
-                                 STOCK_QUIT,
-#endif
-                                 N_("Quit"),
-                                 "<control>Q",
-                                 N_("Quit the application"),
-                                 Gtk.main_quit };
-        actions += quit;
-
         Gtk.ActionEntry helpmenu = { "HelpMenuAction",
                                      null,
                                      N_("_Help"),
                                      null, null, null };
+
+        // Pick up the translated version of the name, if any
+        helpmenu.label = dgettext(null, helpmenu.label);
         actions += helpmenu;
+
         Gtk.ActionEntry about = { "AboutAction",
-#if VALA_0_12
+                                  #if VALA_0_12
                                   Stock.ABOUT,
-#else
+                                  #else
                                   STOCK_ABOUT,
-#endif
+                                  #endif
                                   N_("About"),
                                   null,
                                   N_("About this application"),
                                   on_about_action };
+
+        about.label = dgettext(null, about.label);
         actions += about;
 
         return actions;
     }
 
 
-    private void create_ui_manager ()
+    private void create_ui_manager()
     {
-        Gtk.ActionGroup action_group = new Gtk.ActionGroup ("GeneralActionGroup");
-        action_group.add_actions (create_actions (), this);
-        ui_manager.insert_action_group (action_group, 0);
+        Gtk.ActionGroup action_group = new Gtk.ActionGroup("GeneralActionGroup");
+        action_group.add_actions(create_actions(), this);
+        ui_manager.insert_action_group(action_group, 0);
         try
         {
-            ui_manager.add_ui_from_string (layout, -1);
+            ui_manager.add_ui_from_string(menu_layout, -1);
         }
         catch (Error e)
         {
-            stderr.printf ("%s\n", e.message);
+            stderr.printf("%s\n", e.message);
+            logger.error("create_ui_manager: Caught error: " + e.message);
         }
-        ui_manager.ensure_update ();
+        ui_manager.ensure_update();
     }
 
     private void build_ui()
     {
-        create_ui_manager ();
+        set_bg_color(this);
+
+        create_ui_manager();
+
+        int num_rows = 18;
+        int num_cols = 8;
+        int button_width = 1;
 
+        Table top_table = new Table(num_rows, 10, false);
+        top_table.set_border_width(12);
+
+        AttachOptions fill_and_expand = AttachOptions.EXPAND | AttachOptions.FILL;
+        AttachOptions fill = AttachOptions.FILL;
+        int row = 0;
+
+        service_prompt_vbox = new VBox(false, 0);
+        top_table.attach(service_prompt_vbox, 0, 1, row, row + 1, fill_and_expand, fill_and_expand, 12, 0);
+        row++;
+
+        string search_tooltip_text = _("Search for an identity or service");
         this.search_entry = new Entry();
 
-        set_atk_name_description (search_entry, _("Search entry"), _("Search for a specific ID Card"));
-        this.search_entry.set_icon_from_pixbuf (EntryIconPosition.PRIMARY,
-                                                find_icon_sized ("edit-find", Gtk.IconSize.MENU));
-//                                                find_icon_sized ("edit-find-symbolic", Gtk.IconSize.MENU));
-        this.search_entry.set_icon_tooltip_text (EntryIconPosition.PRIMARY,
-                                                 _("Search identity or service"));
-        this.search_entry.set_icon_sensitive (EntryIconPosition.PRIMARY, false);
+        set_atk_name_description(search_entry, _("Search entry"), _("Search for a specific ID Card"));
+        this.search_entry.set_icon_from_pixbuf(EntryIconPosition.SECONDARY,
+                                               find_icon_sized("edit-find", Gtk.IconSize.MENU));
+        this.search_entry.set_icon_tooltip_text(EntryIconPosition.SECONDARY,
+                                                search_tooltip_text);
 
-        this.search_entry.set_icon_from_pixbuf (EntryIconPosition.SECONDARY,
-                                                find_icon_sized ("process-stop", Gtk.IconSize.MENU));
-//                                                find_icon_sized ("edit-clear-symbolic", Gtk.IconSize.MENU));
-        this.search_entry.set_icon_tooltip_text (EntryIconPosition.SECONDARY,
-                                                 _("Clear the current search"));
-        this.search_entry.set_icon_sensitive (EntryIconPosition.SECONDARY, false);
+        this.search_entry.set_tooltip_text(search_tooltip_text);
 
+        this.search_entry.set_icon_sensitive(EntryIconPosition.SECONDARY, false);
 
-        this.search_entry.icon_press.connect (search_entry_icon_press_cb);
-        this.search_entry.notify["text"].connect (search_entry_text_changed_cb);
+        this.search_entry.notify["text"].connect(search_entry_text_changed_cb);
         this.search_entry.key_press_event.connect(search_entry_key_press_event_cb);
+        this.search_entry.set_width_chars(24);
+
+        var search_label_markup ="<small>" + search_tooltip_text + "</small>";
+        var full_search_label = new Label(null);
+        full_search_label.set_markup(search_label_markup);
+        full_search_label.set_alignment(1, 0);
+
+        var search_vbox = new VBox(false, 0);
+        search_vbox.pack_start(search_entry, false, false, 0);
+        var search_spacer = new Alignment(0, 0, 0, 0);
+        search_spacer.set_size_request(0, 2);
+        search_vbox.pack_start(search_spacer, false, false, 0);
+        search_vbox.pack_start(full_search_label, false, false, 0);
+
+        // Overlap with the service_prompt_box
+        top_table.attach(search_vbox, 5, num_cols - button_width, row - 1, row + 1, fill_and_expand, fill, 0, 12);
+        row++;
+
+        this.custom_vbox = new CustomVBox(this, false, 2);
+
+        var viewport = new Viewport(null, null);
+        viewport.set_border_width(2);
+        viewport.set_shadow_type(ShadowType.NONE);
+        viewport.add(custom_vbox);
+        var id_scrollwin = new ScrolledWindow(null, null);
+        id_scrollwin.set_policy(PolicyType.NEVER, PolicyType.AUTOMATIC);
+        id_scrollwin.set_shadow_type(ShadowType.IN);
+        id_scrollwin.add_with_viewport(viewport);
+        top_table.attach(id_scrollwin, 0, num_cols - 1, row, num_rows - 1, fill_and_expand, fill_and_expand, 6, 0);
+
+        // Right below id_scrollwin:
+        remember_identity_binding = new CheckButton.with_label(_("Remember my identity choice for this service"));
+        remember_identity_binding.active = true;
+        top_table.attach(remember_identity_binding, 0, num_cols / 2, num_rows - 1, num_rows, fill_and_expand, fill_and_expand, 3, 0);
+
+        var add_button = new Button.with_label(_("Add"));
+        add_button.clicked.connect((w) => {add_identity_cb();});
+        top_table.attach(make_rigid(add_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
+        row++;
+
+        var import_button = new Button.with_label(_("Import"));
+        import_button.clicked.connect((w) => {import_identities_cb();});
+        top_table.attach(make_rigid(import_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
+        row++;
+
+        this.edit_button = new Button.with_label(_("Edit"));
+        edit_button.clicked.connect((w) => {edit_identity_cb(this.selected_card);});
+        edit_button.set_sensitive(false);
+        top_table.attach(make_rigid(edit_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
+        row++;
+
+        this.remove_button = new Button.with_label(_("Remove"));
+        remove_button.clicked.connect((w) => {remove_identity_cb(this.selected_card);});
+        remove_button.set_sensitive(false);
+        top_table.attach(make_rigid(remove_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
+        row++;
+
+        // push the send button down another row.
+        row++;
+        this.send_button = new Button.with_label(_("Send"));
+        send_button.clicked.connect((w) => {send_identity_cb(this.selected_card);});
+        // send_button.set_visible(false);
+        send_button.set_sensitive(false);
+        top_table.attach(make_rigid(send_button), num_cols - button_width, num_cols, row, row + 1, fill, fill, 0, 0);
+        row++;
+
+        var main_vbox = new VBox(false, 0);
 
-        this.custom_vbox = new CustomVBox (this, false, 6);
-
-        var viewport = new Viewport (null, null);
-        viewport.set_border_width (6);
-        viewport.set_shadow_type (ShadowType.NONE);
-        viewport.add (custom_vbox);
-        var scroll = new ScrolledWindow (null, null);
-        scroll.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
-        scroll.set_shadow_type (ShadowType.IN);
-        scroll.add_with_viewport (viewport);
-        this.prompting_service = new Label (_(""));
-        // left-align
-        prompting_service.set_alignment(0, (float )0.5);
-
-        var vbox_left = new VBox (false, 0);
-        vbox_left.pack_start (search_entry, false, false, 6);
-        vbox_left.pack_start (scroll, true, true, 0);
-        vbox_left.pack_start (prompting_service, false, false, 6);
-        vbox_left.set_size_request (WINDOW_WIDTH, 0);
-
-        this.no_identity_title = new Label (_("No Identity: Send this identity to services which should not use Moonshot"));
-        no_identity_title.set_alignment(0, (float ) 0.5);
-        no_identity_title.set_line_wrap(true);
-        no_identity_title.show();
-
-        var login_vbox_title = new Label (_("Login: "));
-        label_make_bold (login_vbox_title);
-        login_vbox_title.set_alignment (0, (float) 0.5);
-        var issuer_label = new Label (_("Issuer:"));
-        issuer_label.set_alignment (1, (float) 0.5);
-        this.issuer_entry = new Entry ();
-        issuer_entry.set_can_focus (false);
-        var username_label = new Label (_("Username:"));
-        username_label.set_alignment (1, (float) 0.5);
-        this.username_entry = new Entry ();
-        username_entry.set_can_focus (false);
-        var password_label = new Label (_("Password:"));
-        password_label.set_alignment (1, (float) 0.5);
-        this.password_entry = new Entry ();
-        password_entry.set_invisible_char ('*');
-        password_entry.set_visibility (false);
-        password_entry.set_sensitive (false);
-        this.remember_checkbutton = new CheckButton.with_label (_("Remember password"));
-        remember_checkbutton.set_sensitive(false);
-        this.update_password_button = new Button.with_label (_("Update Password"));
-        this.update_password_button.clicked.connect(update_password_cb);
-
-        set_atk_relation (issuer_label, issuer_entry, Atk.RelationType.LABEL_FOR);
-        set_atk_relation (username_label, username_entry, Atk.RelationType.LABEL_FOR);
-        set_atk_relation (password_entry, password_entry, Atk.RelationType.LABEL_FOR);
-
-        var login_table = new Table (5, 2, false);
-        login_table.set_col_spacings (10);
-        login_table.set_row_spacings (10);
-        login_table.attach_defaults (issuer_label, 0, 1, 0, 1);
-        login_table.attach_defaults (issuer_entry, 1, 2, 0, 1);
-        login_table.attach_defaults (username_label, 0, 1, 1, 2);
-        login_table.attach_defaults (username_entry, 1, 2, 1, 2);
-        login_table.attach_defaults (password_label, 0, 1, 2, 3);
-        login_table.attach_defaults (password_entry, 1, 2, 2, 3);
-        login_table.attach_defaults (remember_checkbutton,  1, 2, 3, 4);
-        login_table.attach_defaults (update_password_button, 0, 1, 4, 5);
-        var login_vbox_alignment = new Alignment (0, 0, 0, 0);
-        login_vbox_alignment.set_padding (0, 0, 12, 0);
-        login_vbox_alignment.add (login_table);
-        this.login_vbox = new VBox (false, 6);
-        login_vbox.pack_start (login_vbox_title, false, true, 0);
-        login_vbox.pack_start (login_vbox_alignment, false, true, 0);
-
-        var services_vbox_title = new Label (_("Services:"));
-        label_make_bold (services_vbox_title);
-        services_vbox_title.set_alignment (0, (float) 0.5);
-        var services_vbox_alignment = new Alignment (0, 0, 0, 0);
-        services_vbox_alignment.set_padding (0, 0, 12, 0);
-        this.services_internal_vbox = new VBox (true, 6);
-        services_vbox_alignment.add (services_internal_vbox);
-        this.services_vbox = new VBox (false, 6);
-        services_vbox.pack_start (services_vbox_title, false, true, 0);
-        services_vbox.pack_start (services_vbox_alignment, false, true, 0);
-
-        this.vbox_right = new VBox (false, 18);
-        vbox_right.pack_start (login_vbox, false, true, 0);
-        vbox_right.pack_start (services_vbox, false, true, 0);
-
-        var hbox = new HBox (false, 12);
-        hbox.pack_start (vbox_left, false, false, 0);
-        hbox.pack_start (vbox_right, true, true, 0);
-
-        var main_vbox = new VBox (false, 0);
-        main_vbox.set_border_width (12);
 #if OS_MACOS
         // hide the  File | Quit menu item which is now on the Mac Menu
-        Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
-        quit_item.hide();
+//        Gtk.Widget quit_item =  this.ui_manager.get_widget("/MenuBar/FileMenu/Quit");
+//        quit_item.hide();
         
-               Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
-               osxApp.set_menu_bar(menushell);
-               osxApp.set_use_quartz_accelerators(true);
-               osxApp.sync_menu_bar();
-               osxApp.ready(); 
+        Gtk.MenuShell menushell = this.ui_manager.get_widget("/MenuBar") as Gtk.MenuShell;
+
+        osxApp.set_menu_bar(menushell);
+        osxApp.set_use_quartz_accelerators(true);
+        osxApp.sync_menu_bar();
+        osxApp.ready();
 #else
-        var menubar = this.ui_manager.get_widget ("/MenuBar");
-        main_vbox.pack_start (menubar, false, false, 0);
+        var menubar = this.ui_manager.get_widget("/MenuBar");
+        main_vbox.pack_start(menubar, false, false, 0);
+        set_bg_color(menubar);
 #endif
-        main_vbox.pack_start (hbox, true, true, 0);
-        add (main_vbox);
+        main_vbox.pack_start(top_table, true, true, 6);
+
+        add(main_vbox);
         main_vbox.show_all();
-        this.vbox_right.hide ();
-  } 
 
-    private void set_atk_name_description (Widget widget, string name, string description)
+        if (!this.selection_in_progress())
+            remember_identity_binding.hide();
+    } 
+
+    internal bool selection_in_progress() {
+        return !this.request_queue.is_empty();
+    }
+
+    private void set_atk_name_description(Widget widget, string name, string description)
     {
-       var atk_widget = widget.get_accessible ();
+        var atk_widget = widget.get_accessible();
 
-       atk_widget.set_name (name);
-       atk_widget.set_description (description);
+        atk_widget.set_name(name);
+        atk_widget.set_description(description);
     }
 
     private void connect_signals()
     {
-        this.destroy.connect (Gtk.main_quit);
+        this.destroy.connect(() => {
+                logger.trace("Destroy event; calling Gtk.main_quit()");
+                Gtk.main_quit();
+            });
         this.identities_manager.card_list_changed.connect(this.on_card_list_changed);
+        this.delete_event.connect(() => {return confirm_quit();});
+    }
+
+    private bool confirm_quit() {
+        logger.trace("delete_event intercepted; selection_in_progress()=" + selection_in_progress().to_string());
+
+        if (selection_in_progress()) {
+            var result = WarningDialog.confirm(this,
+                                               Markup.printf_escaped(
+                                                   "<span font-weight='heavy'>" + _("Do you wish to use the %s service?") + "</span>",
+                                                   this.request_queue.peek_head().service)
+                                               + "\n\n" + _("Select Yes to select an ID for this service, or No to cancel"),
+                                               "close_moonshot_window");
+            if (result) {
+                // Prevent other handlers from handling this event; this keeps the window open.
+                return true; 
+            }
+        }
+
+        // Allow the window deletion to proceed.
+        return false;
     }
 
-    private static void set_atk_relation (Widget widget, Widget target_widget, Atk.RelationType relationship)
+    private static Widget make_rigid(Button button) 
     {
-        var atk_widget = widget.get_accessible ();
-        var atk_target_widget = target_widget.get_accessible ();
+        // Hack to prevent the button from growing vertically
+        VBox fixed_height = new VBox(false, 0);
+        fixed_height.pack_start(button, false, false, 0);
 
-        atk_widget.add_relationship (relationship, atk_target_widget);
+        return fixed_height;
     }
-}
 
+    private void import_identities_cb() {
+        var dialog = new FileChooserDialog(_("Import File"),
+                                           this,
+                                           FileChooserAction.OPEN,
+                                           _("Cancel"),ResponseType.CANCEL,
+                                           _("Open"), ResponseType.ACCEPT,
+                                           null);
+
+        if (import_directory != null) {
+            dialog.set_current_folder(import_directory);
+        }
+
+        if (dialog.run() == ResponseType.ACCEPT)
+        {
+            // Save the parent directory to use as default for next save
+            string filename = dialog.get_filename();
+            var file  = File.new_for_path(filename);
+            import_directory = file.get_parent().get_path();
+
+            int import_count = 0;
+
+            var webp = new Parser(filename);
+            dialog.destroy();
+            webp.parse();
+            logger.trace(@"import_identities_cb: Have $(webp.cards.length) IdCards");
+            foreach (IdCard card in webp.cards)
+            {
+
+                if (card == null) {
+                    logger.trace(@"import_identities_cb: Skipping null IdCard");
+                    continue;
+                }
 
+                if (!card.trust_anchor.is_empty()) {
+                    string ta_datetime_added = TrustAnchor.format_datetime_now();
+                    card.trust_anchor.set_datetime_added(ta_datetime_added);
+                    logger.trace("import_identities_cb : Set ta_datetime_added for '%s' to '%s'; ca_cert='%s'; server_cert='%s'"
+                                 .printf(card.display_name, ta_datetime_added, card.trust_anchor.ca_cert, card.trust_anchor.server_cert));
+                }
+
+
+                bool result = add_identity(card, use_flat_file_store);
+                if (result) {
+                    logger.trace(@"import_identities_cb: Added or updated '$(card.display_name)'");
+                    import_count++;
+                }
+                else {
+                    logger.trace(@"import_identities_cb: Did not add or update '$(card.display_name)'");
+                }
+            }
+            if (import_count == 0) {
+                var msg_dialog = new Gtk.MessageDialog(this,
+                                                       Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                                       Gtk.MessageType.INFO,
+                                                       Gtk.ButtonsType.OK,
+                                                       _("Import completed. No identities were added or updated."));
+                msg_dialog.run();
+                msg_dialog.destroy();
+            }
+        }
+        dialog.destroy();
+    }
+
+}
index a013cfb..c4c8437 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -43,40 +43,63 @@ interface IIdentityManager : GLib.Object {
 }
 #endif
 
+
 public class IdentityManagerApp {
+    public static MoonshotLogger logger = get_logger("IdentityManagerApp");
+
     public IdentityManagerModel model;
     public IdCard default_id_card;
     public bool explicitly_launched;
     public IdentityManagerView view;
     private MoonshotServer ipc_server;
+    private bool name_is_owned;
+    private bool show_requested;
+    public bool use_flat_file_store {public get; private set;}
+    public bool headless {public get; private set;}
 
 #if OS_MACOS
-       public OSXApplication osxApp;
+    public OSXApplication osxApp;
   
     // the signal handler function.
     // the current instance of our app class is passed in the 
     // id_manager_app_instanceparameter 
-       public static bool on_osx_open_files (OSXApplication osx_app_instance, 
-                                        string file_name, 
-                                        IdentityManagerApp id_manager_app_instance ) {
-    int added_cards = id_manager_app_instance.ipc_server.install_from_file(file_name);
-    return true;
-       }
+    public static bool on_osx_open_files(OSXApplication osx_app_instance, 
+                                         string file_name, 
+                                         IdentityManagerApp id_manager_app_instance ) {
+        int added_cards = id_manager_app_instance.ipc_server.install_from_file(file_name);
+        return true;
+    }
 #endif
 
-    private const int WINDOW_WIDTH = 400;
-    private const int WINDOW_HEIGHT = 500;
+    /** If we're successfully registered with DBus, then show the UI. Otherwise, wait until we're registered. */
     public void show() {
-        if (view != null) view.show();    
+        if (name_is_owned) {
+            if (view != null) {
+                view.make_visible();
+            }
+        }
+        else {
+            show_requested = true;
+        }
     }
-       
-    public IdentityManagerApp (bool headless, bool use_flat_file_store) {
+    
+#if USE_LOG4VALA
+    // Call this from main() to ensure that the logger is initialized
+    internal IdentityManagerApp.dummy() {}
+#endif
+
+    public IdentityManagerApp(bool headless, bool use_flat_file_store) {
+        this.headless = headless;
+
         use_flat_file_store |= UserForcesFlatFileStore();
+        this.use_flat_file_store = use_flat_file_store;
+
 #if GNOME_KEYRING
         bool keyring_available = (!use_flat_file_store) && GnomeKeyring.is_available();
 #else
         bool keyring_available = false;
 #endif
+
         IIdentityCardStore.StoreType store_type;
         if (headless || use_flat_file_store || !keyring_available)
             store_type = IIdentityCardStore.StoreType.FLAT_FILE;
@@ -90,31 +113,38 @@ public class IdentityManagerApp {
             model.set_store_type(IIdentityCardStore.StoreType.KEYRING);
 
         if (!headless)
-            view = new IdentityManagerView(this);
-        LinkedList<IdCard> card_list = model.get_card_list() ;
+            view = new IdentityManagerView(this, use_flat_file_store);
+        LinkedList<IdCard> card_list = model.get_card_list();
         if (card_list.size > 0)
             this.default_id_card = card_list.last();
 
-        init_ipc_server ();
+        init_ipc_server();
 
 #if OS_MACOS
-
         osxApp = OSXApplication.get_instance();
-        // The 'correct' way of connrcting wont work in Mac OS with Vala 0.12  e.g.    
-        //             osxApp.ns_application_open_file.connect(install_from_file);
+        // The 'correct' way of connecting won't work in Mac OS with Vala 0.12; e.g.
+        //     osxApp.ns_application_open_file.connect(install_from_file);
         // so we have to use this old way
         Signal.connect(osxApp, "NSApplicationOpenFile", (GLib.Callback)(on_osx_open_files), this);
-
 #endif
     }
 
-    public bool add_identity (IdCard id, bool force_flat_file_store) {
-        if (view != null) return view.add_identity(id, force_flat_file_store);
-        model.add_card(id, force_flat_file_store);
-        return true;
+    public bool add_identity(IdCard id, bool force_flat_file_store, out ArrayList<IdCard>? old_duplicates=null) {
+        if (view != null) 
+        {
+            logger.trace("add_identity: calling view.add_identity");
+            return view.add_identity(id, force_flat_file_store, out old_duplicates);
+        }
+        else {
+            logger.trace("add_identity: calling model.add_card");
+            model.add_card(id, force_flat_file_store, out old_duplicates);
+            return true;
+        }
     }
 
-    public void select_identity (IdentityRequest request) {
+    public void select_identity(IdentityRequest request) {
+        logger.trace("select_identity: request.nai=%s".printf(request.nai ?? "[null]"));
+
         IdCard identity = null;
 
         if (request.select_default)
@@ -133,6 +163,7 @@ public class IdentityManagerApp {
                 /* If NAI matches, use this id card */
                 if (has_nai && request.nai == id.nai)
                 {
+                    logger.trace("select_identity: request has nai; returning " + id.display_name);
                     identity = id;
                     break;
                 }
@@ -140,13 +171,9 @@ public class IdentityManagerApp {
                 /* If any service matches we add id card to the candidate list */
                 if (has_srv)
                 {
-                    foreach (string srv in id.services)
-                    {
-                        if (request.service == srv)
-                        {
-                            request.candidates.append (id);
-                            continue;
-                        }
+                    if (id.services.contains(request.service)) {
+                        logger.trace(@"select_identity: request has service '$(request.service); matched on '$(id.display_name)'");
+                        request.candidates.append(id);
                     }
                 }
             }
@@ -154,53 +181,26 @@ public class IdentityManagerApp {
             /* If more than one candidate we dissasociate service from all ids */
             if ((identity == null) && has_srv && request.candidates.length() > 1)
             {
+                logger.trace(@"select_identity: multiple candidates; removing service '$(request.service) from all.");
                 foreach (IdCard id in request.candidates)
                 {
-                    int i = 0;
-                    SList<string> services_list = null;
-                    bool has_service = false;
-
-                    foreach (string srv in id.services)
-                    {
-                        if (srv == request.service)
-                        {
-                            has_service = true;
-                            continue;
-                        }
-                        services_list.append (srv);
-                    }
-                    
-                    if (!has_service)
-                        continue;
-
-                    if (services_list.length () == 0)
-                    {
-                        id.services = {};
-                        continue;
-                    }
-
-                    string[] services = new string[services_list.length ()];
-                    foreach (string srv in services_list)
-                    {
-                        services[i] = srv;
-                        i++;
-                    }
-
-                    id.services = services;
+                    id.services.remove(request.service);
                 }
             }
 
             /* If there are no candidates we use the service matching rules */
-            if ((identity==null) && (request.candidates.length () == 0))
+            if ((identity == null) && (request.candidates.length() == 0))
             {
+                logger.trace("select_identity: No candidates; using service matching rules.");
                 foreach (IdCard id in model.get_card_list())
                 {
                     foreach (Rule rule in id.rules)
                     {
-                        if (!match_service_pattern (request.service, rule.pattern))
+                        if (!match_service_pattern(request.service, rule.pattern))
                             continue;
 
-                        request.candidates.append (id);
+                        logger.trace(@"select_identity: ID $(id.display_name) matched on service matching rules.");
+                        request.candidates.append(id);
 
                         if (rule.always_confirm == "true")
                             confirm = true;
@@ -209,6 +209,7 @@ public class IdentityManagerApp {
             }
             
             if ((identity == null) && has_nai) {
+                logger.trace("select_identity: Creating temp identity");
                 // create a temp identity
                 string[] components = request.nai.split("@", 2);
                 identity = new IdCard();
@@ -220,10 +221,11 @@ public class IdentityManagerApp {
                 identity.temporary = true;
             }
             if (identity == null) {
-                if (request.candidates.length () != 1) {
+                if (request.candidates.length() != 1) {
+                    logger.trace("select_identity: Have %u candidates; user must make selection.".printf(request.candidates.length()));
                     confirm = true;
                 } else {
-                    identity = request.candidates.nth_data (0);                    
+                    identity = request.candidates.nth_data(0);                    
                 }
             }
 
@@ -231,7 +233,7 @@ public class IdentityManagerApp {
             {
                 if (!explicitly_launched)
                     show();
-               view.queue_identity_request(request);
+                view.queue_identity_request(request);
                 return;
             }
         }
@@ -240,105 +242,165 @@ public class IdentityManagerApp {
         GLib.Idle.add(
             () => {
                 if (view != null) {
+                    logger.trace("select_identity (Idle handler): calling check_add_password");
                     identity = view.check_add_password(identity, request, model);
                 }
-                request.return_identity (identity);
+                request.return_identity(identity);
 // The following occasionally causes the app to exit without sending the dbus
 // reply, so for now we just don't exit
 //                if (!explicitly_launched)
-//                    Idle.add( () => { Gtk.main_quit(); return false; } );
+//                    Idle.add(() => { Gtk.main_quit(); return false; } );
                 return false;
             }
-        );
+            );
         return;
     }
 
-    private bool match_service_pattern (string service, string pattern)
-    {
-        var pspec = new PatternSpec (pattern);
-        return pspec.match_string (service);
+    private bool match_service_pattern(string service, string pattern) {
+        var pspec = new PatternSpec(pattern);
+        return pspec.match_string(service);
     }   
     
 #if IPC_MSRPC
-    private void init_ipc_server ()
-    {
+    private void init_ipc_server() {
         // Errors will currently be sent via g_log - ie. to an
         // obtrusive message box, on Windows
         //
-        this.ipc_server = MoonshotServer.get_instance ();
-        MoonshotServer.start (this);
+        this.ipc_server = MoonshotServer.get_instance();
+        MoonshotServer.start(this);
     }
 #elif IPC_DBUS_GLIB
-    private void init_ipc_server ()
-    {
+    private void init_ipc_server() {
         try {
-            var conn = DBus.Bus.get (DBus.BusType.SESSION);
-            dynamic DBus.Object bus = conn.get_object ("org.freedesktop.DBus",
-                                                       "/org/freedesktop/DBus",
-                                                       "org.freedesktop.DBus");
+            var conn = DBus.Bus.get(DBus.BusType.SESSION);
+            dynamic DBus.Object bus = conn.get_object("org.freedesktop.DBus",
+                                                      "/org/freedesktop/DBus",
+                                                      "org.freedesktop.DBus");
 
             // try to register service in session bus
-            uint reply = bus.request_name ("org.janet.Moonshot", (uint) 0);
+            uint reply = bus.request_name("org.janet.Moonshot", (uint) 0);
             if (reply == DBus.RequestNameReply.PRIMARY_OWNER)
             {
-                this.ipc_server = new MoonshotServer (this);
-                conn.register_object ("/org/janet/moonshot", ipc_server);
+                this.ipc_server = new MoonshotServer(this);
+                logger.trace("init_ipc_server(IPC_DBUS_GLIB) : Constructed new MoonshotServer");
+                conn.register_object("/org/janet/moonshot", ipc_server);
             } else {
-                bool shown=false;
+                logger.trace("init_ipc_server: reply != PRIMARY_OWNER");
+                bool shown = false;
                 GLib.Error e;
-                DBus.Object manager_proxy = conn.get_object ("org.janet.Moonshot",
-                                                             "/org/janet/moonshot",
-                                                             "org.janet.Moonshot");
+                DBus.Object manager_proxy = conn.get_object("org.janet.Moonshot",
+                                                            "/org/janet/moonshot",
+                                                            "org.janet.Moonshot");
                 if (manager_proxy != null)
                     manager_proxy.call("ShowUi", out e, GLib.Type.INVALID, typeof(bool), out shown, GLib.Type.INVALID);
 
                 if (!shown) {
-                    GLib.error ("Couldn't own name org.janet.Moonshot on dbus or show previously launched identity manager.");
+                    GLib.error("Couldn't own name org.janet.Moonshot on dbus or show previously launched identity manager.");
                 } else {
-                    stdout.printf("Showed previously launched identity manager.\n");
+                    stdout.printf(_("Showed previously launched identity manager.\n"));
                     GLib.Process.exit(0);
                 }
             }
         }
         catch (DBus.Error e)
         {
-            stderr.printf ("%s\n", e.message);
+            logger.trace("bus_acquired_cb");
+            try {
+                conn.register_object ("/org/janet/moonshot", ipc_server);
+            }
+            catch (Error e)
+            {
+                stderr.printf ("%s\n", e.message);
+                logger.error("bus_acquired_cb: Caught error: " + e.message);
+            }
         }
     }
 #else
-    private void bus_acquired_cb (DBusConnection conn)
-    {
+    private void bus_acquired_cb(DBusConnection conn) {
+        logger.trace("bus_acquired_cb");
         try {
-            conn.register_object ("/org/janet/moonshot", ipc_server);
+            conn.register_object("/org/janet/moonshot", ipc_server);
         }
         catch (Error e)
         {
-            stderr.printf ("%s\n", e.message);
+            this.ipc_server = new MoonshotServer (this);
+            logger.trace("init_ipc_server: Constructed new MoonshotServer");
+            GLib.Bus.own_name (GLib.BusType.SESSION,
+                               "org.janet.Moonshot",
+                               GLib.BusNameOwnerFlags.NONE,
+                               bus_acquired_cb,
+                               (conn, name) => {logger.trace("init_ipc_server: name_acquired_closure");},
+                               (conn, name) => {
+                                   logger.trace("init_ipc_server: name_lost_closure");
+                                   bool shown=false;
+                                   try {
+                                       IIdentityManager manager = Bus.get_proxy_sync (BusType.SESSION, name, "/org/janet/moonshot");
+                                       shown = manager.show_ui();
+                                   } catch (IOError e) {
+                                       logger.error("init_ipc_server.name_lost_closure: Caught error: ");
+                                   }
+                                   if (!shown) {
+                                       logger.error("init_ipc_server.name_lost_closure: Couldn't own name %s on dbus or show previously launched identity manager".printf(name));
+                                       GLib.error ("Couldn't own name %s on dbus or show previously launched identity manager.", name);
+                                   } else {
+                                       logger.trace("init_ipc_server.name_lost_closure: Showed previously launched identity manager.");
+                                       stdout.printf("Showed previously launched identity manager.\n");
+                                       GLib.Process.exit(0);
+                                   }
+                               });
         }
     }
 
-    private void init_ipc_server ()
-    {
-        this.ipc_server = new MoonshotServer (this);
-        GLib.Bus.own_name (GLib.BusType.SESSION,
-                           "org.janet.Moonshot",
-                           GLib.BusNameOwnerFlags.NONE,
-                           bus_acquired_cb,
-                           (conn, name) => {},
-                           (conn, name) => {
-                               bool shown=false;
-                               try {
-                                   IIdentityManager manager = Bus.get_proxy_sync (BusType.SESSION, name, "/org/janet/moonshot");
-                                   shown = manager.show_ui();
-                               } catch (IOError e) {
-                               }
-                               if (!shown) {
-                                   GLib.error ("Couldn't own name %s on dbus or show previously launched identity manager.", name);
-                               } else {
-                                   stdout.printf("Showed previously launched identity manager.\n");
-                                   GLib.Process.exit(0);
-                               }
-                           });
+    private void init_ipc_server() {
+        this.ipc_server = new MoonshotServer(this);
+        bool shown = false;
+        GLib.Bus.own_name(GLib.BusType.SESSION,
+                          "org.janet.Moonshot",
+                          GLib.BusNameOwnerFlags.NONE,
+                          bus_acquired_cb,
+
+                          // Name acquired callback:
+                          (conn, name) => {
+                              logger.trace(@"init_ipc_server: name_acquired_closure; show_requested=$show_requested");
+
+                              name_is_owned = true;
+
+                              // Now that we know that we own the name, it's safe to show the UI.
+                              if (show_requested) {
+                                  show();
+                                  show_requested = false;
+                              }
+                              shown = true;
+                          },
+
+                          // Name lost callback:
+                          (conn, name) => {
+                              logger.trace("init_ipc_server: name_lost_closure");
+
+                              // This callback usually means that another moonshot is already running.
+                              // But it *might* mean that we lost the name for some other reason
+                              // (though it's unclear to me yet what those reasons are.)
+                              // Clearing these flags seems like a good idea for that case. -- dbreslau
+                              name_is_owned = false;
+                              show_requested = false;
+
+                              try {
+                                  if (!shown) {
+                                      IIdentityManager manager = Bus.get_proxy_sync(BusType.SESSION, name, "/org/janet/moonshot");
+                                      shown = manager.show_ui();
+                                  }
+                              } catch (IOError e) {
+                                  logger.error("init_ipc_server.name_lost_closure: Caught IOError: " + e.message);
+                              }
+                              if (!shown) {
+                                  logger.error("init_ipc_server.name_lost_closure: Couldn't own name %s on dbus or show previously launched identity manager".printf(name));
+                                  GLib.error("Couldn't own name %s on dbus or show previously launched identity manager.", name);
+                              } else {
+                                  logger.trace("init_ipc_server.name_lost_closure: Showed previously launched identity manager.");
+                                  stdout.printf("Showed previously launched identity manager.\n");
+                                  GLib.Process.exit(0);
+                              }
+                          });
     }
 #endif
 }
@@ -346,75 +408,84 @@ public class IdentityManagerApp {
 static bool explicitly_launched = true;
 static bool use_flat_file_store = false;
 const GLib.OptionEntry[] options = {
-    {"dbus-launched",0,GLib.OptionFlags.REVERSE,GLib.OptionArg.NONE,
-     ref explicitly_launched,"launch for dbus rpc use",null},
-    {"flat-file-store",0,0,GLib.OptionArg.NONE,
-     ref use_flat_file_store,"force use of flat file identity store (used by default only for headless operation)",null},
+    {"dbus-launched", 0, GLib.OptionFlags.REVERSE, GLib.OptionArg.NONE,
+     ref explicitly_launched, "launch for dbus rpc use", null},
+    {"flat-file-store", 0, 0, GLib.OptionArg.NONE,
+     ref use_flat_file_store, "force use of flat file identity store (used by default only for headless operation)", null},
     {null}
 };
 
 
-public static int main(string[] args){
+public static int main(string[] args) {
+
+#if USE_LOG4VALA
+    // Initialize the logger.
+    new IdentityManagerApp.dummy();
+#endif
+
 #if IPC_MSRPC
-       bool headless = false;
+    bool headless = false;
 #else
-        bool headless = GLib.Environment.get_variable("DISPLAY") == null;
+    bool headless = GLib.Environment.get_variable("DISPLAY") == null;
 #endif
 
-        if (headless) {
-            try {
-                var opt_context = new OptionContext(null);
-                opt_context.set_help_enabled (true);
-                opt_context.add_main_entries (options, null);
-                opt_context.parse(ref args);
-            } catch (OptionError e) {
-                stdout.printf(_("error: %s\n"),e.message);
-                stdout.printf(_("Run '%s --help' to see a full list of available options\n"), args[0]);
-                return -1;
-            }
-            explicitly_launched = false;
-        } else {
-            try {
-                if (!Gtk.init_with_args(ref args, _(""), options, null)) {
-                    stdout.printf(_("unable to initialize window\n"));
-                    return -1;
-                }
-            } catch (GLib.Error e) {
-                stdout.printf(_("error: %s\n"),e.message);
-                stdout.printf(_("Run '%s --help' to see a full list of available options\n"), args[0]);
+    if (headless) {
+        try {
+            var opt_context = new OptionContext(null);
+            opt_context.set_help_enabled(true);
+            opt_context.add_main_entries(options, null);
+            opt_context.parse(ref args);
+        } catch (OptionError e) {
+            stdout.printf(_("error: %s\n"),e.message);
+            stdout.printf(_("Run '%s --help' to see a full list of available options\n"), args[0]);
+            return -1;
+        }
+        explicitly_launched = false;
+    } else {
+        try {
+            if (!Gtk.init_with_args(ref args, _(""), options, null)) {
+                stdout.printf(_("unable to initialize window\n"));
                 return -1;
             }
-            gtk_available = true;
+        } catch (GLib.Error e) {
+            stdout.printf(_("error: %s\n"),e.message);
+            stdout.printf(_("Run '%s --help' to see a full list of available options\n"), args[0]);
+            return -1;
         }
+        gtk_available = true;
+    }
 
 #if OS_WIN32
-        // Force specific theme settings on Windows without requiring a gtkrc file
-        Gtk.Settings settings = Gtk.Settings.get_default ();
-        settings.set_string_property ("gtk-theme-name", "ms-windows", "moonshot");
-        settings.set_long_property ("gtk-menu-images", 0, "moonshot");
+    // Force specific theme settings on Windows without requiring a gtkrc file
+    Gtk.Settings settings = Gtk.Settings.get_default();
+    settings.set_string_property("gtk-theme-name", "ms-windows", "moonshot");
+    settings.set_long_property("gtk-menu-images", 0, "moonshot");
 #endif
 
-        Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
-        Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
-        Intl.textdomain (Config.GETTEXT_PACKAGE);
+    //TODO?? Do we need to call Intl.setlocale(LocaleCategory.MESSAGES, "");
+    Intl.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
+    Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "UTF-8");
+    Intl.textdomain(Config.GETTEXT_PACKAGE);
+       
        
-          
-        var app = new IdentityManagerApp(headless, use_flat_file_store);
-        app.explicitly_launched = explicitly_launched;
+    var app = new IdentityManagerApp(headless, use_flat_file_store);
+    app.explicitly_launched = explicitly_launched;
+    IdentityManagerApp.logger.trace(@"main: explicitly_launched=$explicitly_launched");
         
-       if (app.explicitly_launched) {
-            app.show();
-        }
+    if (app.explicitly_launched) {
+        app.show();
+    }
 
-        if (headless) {
+    if (headless) {
 #if !IPC_MSRPC
-            MainLoop loop = new MainLoop();
-            loop.run();
+        MainLoop loop = new MainLoop();
+        loop.run();
 #endif
-        } else {
-            Gtk.main();
-        }
-
-        return 0;
     }
+    else {
+        Gtk.main();
+    }
+
+    return 0;
+}
 
index 2137cce..4068903 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
 */
-public delegate void ReturnIdentityCallback (IdentityRequest request);
+public delegate void ReturnIdentityCallback(IdentityRequest request);
 
 public class IdentityRequest : Object {
+    static MoonshotLogger logger = get_logger("IdentityRequest");
+
     public IdCard? id_card = null;
     public bool complete = false;
     public bool select_default = false;
@@ -44,10 +46,10 @@ public class IdentityRequest : Object {
 
     ReturnIdentityCallback callback = null;
 
-    public IdentityRequest (IdentityManagerApp           app,
-                            string                       nai,
-                            string                       password,
-                            string                       service)
+    public IdentityRequest(IdentityManagerApp           app,
+                           string                       nai,
+                           string                       password,
+                           string                       service)
     {
         this.parent_app = app;
         this.nai = nai;
@@ -55,23 +57,23 @@ public class IdentityRequest : Object {
         this.service = service;
     }
 
-    public IdentityRequest.default (IdentityManagerApp app)
+    public IdentityRequest.default(IdentityManagerApp app)
     {
         this.parent_app = app;
         this.select_default = true;
     }
 
-    public void set_callback (owned ReturnIdentityCallback cb)
+    public void set_callback(owned ReturnIdentityCallback cb)
     {
-#if VALA_0_12
-        this.callback = ((owned) cb);
-#else
-        this.callback = ((IdCard) => cb (IdCard));
-#endif
+        #if VALA_0_12
+            this.callback = ((owned) cb);
+        #else
+            this.callback = ((IdCard) => cb(IdCard));
+        #endif
     }
 
-    public bool execute () {
-        parent_app.select_identity (this);
+    public bool execute() {
+        parent_app.select_identity(this);
 
         /* This function works as a GSourceFunc, so it can be passed to
          * the main loop from other threads
@@ -79,36 +81,28 @@ public class IdentityRequest : Object {
         return false;
     }
 
-    public void return_identity (IdCard? id_card) {
+    public void return_identity(IdCard? id_card, bool update_card = true) {
         this.id_card = id_card;
         this.complete = true;
 
         /* update id_card service list */
-        if (id_card != null && this.service != null && this.service != "")
+        if (update_card && id_card != null && this.service != null && this.service != "")
         {
-            bool duplicate_service = false;
-
-            foreach (string service in id_card.services)
-            {
-                if (service == this.service)
-                    duplicate_service = true;
-            }
+            bool duplicate_service = id_card.services.contains(this.service);
+            logger.trace("return_identity: duplicate_service=" + duplicate_service.to_string());
             if (duplicate_service == false)
             {
-                string[] services = new string[id_card.services.length + 1];
-
-                for (int i = 0; i < id_card.services.length; i++)
-                    services[i] = id_card.services[i];
-
-                services[id_card.services.length] = this.service;
-                id_card.services = services;
+                logger.trace("return_identity: calling add_service");
+                id_card.services.add(this.service);
+                logger.trace("return_identity: back from add_service");
 
-                this.id_card = this.parent_app.model.update_card (id_card);
+                this.id_card = this.parent_app.model.update_card(id_card);
             }
         }
 
-        return_if_fail (callback != null);
-        callback (this);
+        return_if_fail(callback != null);
+        logger.trace("return_identity: invoking callback");
+        callback(this);
     }
 
 #if OS_WIN32
index ab1ff1e..7ae0d22 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,30 +33,42 @@ using Gee;
 
 #if GNOME_KEYRING
 public class KeyringStore : Object, IIdentityCardStore {
+    static MoonshotLogger logger = get_logger("KeyringStore");
+
     private LinkedList<IdCard> id_card_list;
     private const string keyring_store_attribute = "Moonshot";
     private const string keyring_store_version = "1.0";
     private const GnomeKeyring.ItemType item_type = GnomeKeyring.ItemType.GENERIC_SECRET;
 
     public void add_card(IdCard card) {
+        logger.trace("add_card: Adding card '%s' with services: '%s'"
+                     .printf(card.display_name, card.get_services_string("; ")));
+
         id_card_list.add(card);
-        store_id_cards ();
+        store_id_cards();
     }
 
     public IdCard? update_card(IdCard card) {
+        logger.trace("update_card");
+
         id_card_list.remove(card);
         id_card_list.add(card);
-        store_id_cards ();
-        foreach (IdCard idcard in id_card_list)
-            if (idcard.display_name == card.display_name)
+
+        store_id_cards();
+        foreach (IdCard idcard in id_card_list) {
+            if (idcard.display_name == card.display_name) {
                 return idcard;
+            }
+        }
+
+        logger.error(@"update_card: card '$(card.display_name)' was not found after re-loading!");
         return null;
     }
 
     public bool remove_card(IdCard card) {
         bool retval = id_card_list.remove(card);
         if (retval)
-            store_id_cards ();
+            store_id_cards();
         return retval;
     }
 
@@ -70,9 +82,9 @@ public class KeyringStore : Object, IIdentityCardStore {
 
     /* clear all keyring-stored ids (in preparation to store current list) */
     private void clear_keyring() {
-       GnomeKeyring.AttributeList match = new GnomeKeyring.AttributeList();
-       match.append_string(keyring_store_attribute, keyring_store_version);
-       GLib.List<GnomeKeyring.Found> items;
+        GnomeKeyring.AttributeList match = new GnomeKeyring.AttributeList();
+        match.append_string(keyring_store_attribute, keyring_store_version);
+        GLib.List<GnomeKeyring.Found> items;
         GnomeKeyring.find_items_sync(item_type, match, out items);
         foreach(unowned GnomeKeyring.Found entry in items) {
             GnomeKeyring.Result result = GnomeKeyring.item_delete_sync(null, entry.item_id);
@@ -85,55 +97,73 @@ public class KeyringStore : Object, IIdentityCardStore {
     private void load_id_cards() {
         id_card_list.clear();
 
-       GnomeKeyring.AttributeList match = new GnomeKeyring.AttributeList();
-       match.append_string(keyring_store_attribute, keyring_store_version);
-       GLib.List<GnomeKeyring.Found> items;
+        GnomeKeyring.AttributeList match = new GnomeKeyring.AttributeList();
+        match.append_string(keyring_store_attribute, keyring_store_version);
+        GLib.List<GnomeKeyring.Found> items;
         GnomeKeyring.find_items_sync(item_type, match, out items);
         foreach(unowned GnomeKeyring.Found entry in items) {
-            IdCard id_card = new IdCard ();
+            IdCard id_card = new IdCard();
             int i;
             int rules_patterns_index = -1;
             int rules_always_confirm_index = -1;
             string store_password = null;
-            for (i=0; i<entry.attributes.len; i++) {
+            string ca_cert = "";
+            string server_cert = "";
+            string subject = "";
+            string subject_alt = "";
+            string ta_datetime_added = "";
+            for (i = 0; i < entry.attributes.len; i++) {
                 var attribute = ((GnomeKeyring.Attribute *) entry.attributes.data)[i];
-               string value = attribute.string_value;
-               if (attribute.name == "Issuer") {
+                string value = "";
+                if (attribute.type == GnomeKeyring.AttributeType.STRING) {
+                    value = attribute.string_value;
+                }
+
+                if (attribute.name == "Issuer") {
                     id_card.issuer = value;
-               } else if (attribute.name == "Username") {
+                } else if (attribute.name == "Username") {
                     id_card.username = value;
-               } else if (attribute.name == "DisplayName") {
+                } else if (attribute.name == "DisplayName") {
                     id_card.display_name = value;
-               } else if (attribute.name == "Services") {
-                    id_card.services = value.split(";");
+                } else if (attribute.name == "Services") {
+                    id_card.update_services(value.split(";"));
                 } else if (attribute.name == "Rules-Pattern") {
                     rules_patterns_index = i;
                 } else if (attribute.name == "Rules-AlwaysConfirm") {
                     rules_always_confirm_index = i;
                 } else if (attribute.name == "CA-Cert") {
-                    id_card.trust_anchor.ca_cert = value.strip();
+                    ca_cert = value.strip();
                 } else if (attribute.name == "Server-Cert") {
-                    id_card.trust_anchor.server_cert = value;
+                    server_cert = value;
                 } else if (attribute.name == "Subject") {
-                    id_card.trust_anchor.subject = value;
+                    subject = value;
                 } else if (attribute.name == "Subject-Alt") {
-                    id_card.trust_anchor.subject_alt = value;
+                    subject_alt = value;
                 } else if (attribute.name == "StorePassword") {
                     store_password = value;
+                } else if (attribute.name == "TA_DateTime_Added") {
+                    ta_datetime_added = value;
                 }
             }
+
+            var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt);
+            if (ta_datetime_added != "") {
+                ta.set_datetime_added(ta_datetime_added);
+            }
+            id_card.set_trust_anchor_from_store(ta);
+
             if ((rules_always_confirm_index != -1) && (rules_patterns_index != -1)) {
                 string rules_patterns_all = ((GnomeKeyring.Attribute *) entry.attributes.data)[rules_patterns_index].string_value;
                 string rules_always_confirm_all = ((GnomeKeyring.Attribute *) entry.attributes.data)[rules_always_confirm_index].string_value;
                 string [] rules_always_confirm = rules_always_confirm_all.split(";");
                 string [] rules_patterns = rules_patterns_all.split(";");
                 if (rules_patterns.length == rules_always_confirm.length) {
-                   Rule[] rules = new Rule[rules_patterns.length];
-                   for (int j=0; j<rules_patterns.length; j++) {
-                       rules[j].pattern = rules_patterns[j];
-                       rules[j].always_confirm = rules_always_confirm[j];
-                   }
-                   id_card.rules = rules;
+                    Rule[] rules = new Rule[rules_patterns.length];
+                    for (int j = 0; j < rules_patterns.length; j++) {
+                        rules[j].pattern = rules_patterns[j];
+                        rules[j].always_confirm = rules_always_confirm[j];
+                    }
+                    id_card.rules = rules;
                 }
             }
 
@@ -146,26 +176,27 @@ public class KeyringStore : Object, IIdentityCardStore {
                 id_card.password = entry.secret;
             else
                 id_card.password = null;
+
             id_card_list.add(id_card);
         }
     }
 
-    public void store_id_cards () {
+    internal void store_id_cards() {
+        logger.trace("store_id_cards");
         clear_keyring();
         foreach (IdCard id_card in this.id_card_list) {
             /* workaround for Centos vala array property bug: use temp array */
             var rules = id_card.rules;
-            var services_array = id_card.services;
             string[] rules_patterns = new string[rules.length];
             string[] rules_always_conf = new string[rules.length];
             
-            for (int i=0; i<rules.length; i++) {
+            for (int i = 0; i < rules.length; i++) {
                 rules_patterns[i] = rules[i].pattern;
                 rules_always_conf[i] = rules[i].always_confirm;
             }
             string patterns = string.joinv(";", rules_patterns);
             string always_conf = string.joinv(";", rules_always_conf);
-            string services = string.joinv(";", services_array);
+            string services = id_card.get_services_string(";");
             GnomeKeyring.AttributeList attributes = new GnomeKeyring.AttributeList();
             uint32 item_id;
             attributes.append_string(keyring_store_attribute, keyring_store_version);
@@ -179,12 +210,13 @@ public class KeyringStore : Object, IIdentityCardStore {
             attributes.append_string("Server-Cert", id_card.trust_anchor.server_cert);
             attributes.append_string("Subject", id_card.trust_anchor.subject);
             attributes.append_string("Subject-Alt", id_card.trust_anchor.subject_alt);
+            attributes.append_string("TA_DateTime_Added", id_card.trust_anchor.datetime_added);
             attributes.append_string("StorePassword", id_card.store_password ? "yes" : "no");
 
             GnomeKeyring.Result result = GnomeKeyring.item_create_sync(null,
-                item_type, id_card.display_name, attributes,
-                id_card.store_password ? id_card.password : "",
-                true, out item_id);
+                                                                       item_type, id_card.display_name, attributes,
+                                                                       id_card.store_password ? id_card.password : "",
+                                                                       true, out item_id);
             if (result != GnomeKeyring.Result.OK) {
                 stdout.printf("GnomeKeyring.item_create_sync() failed. result: %d", result);
             }
@@ -192,7 +224,7 @@ public class KeyringStore : Object, IIdentityCardStore {
         load_id_cards();
     }
 
-    public KeyringStore () {
+    public KeyringStore() {
         id_card_list = new LinkedList<IdCard>();
         load_id_cards();
     }
index 8e4db0a..742ca74 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * 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.
-*/
+ */
 using Gee; 
 
 public class LocalFlatFileStore : Object, IIdentityCardStore {
+    static MoonshotLogger logger = get_logger("LocalFlatFileStore");
+
     private LinkedList<IdCard> id_card_list;
     private const string FILE_NAME = "identities.txt";
 
     public void add_card(IdCard card) {
         id_card_list.add(card);
-        store_id_cards ();
+        store_id_cards();
     }
 
     public IdCard? update_card(IdCard card) {
         id_card_list.remove(card);
         id_card_list.add(card);
-        store_id_cards ();
-        foreach(IdCard idcard in id_card_list)
-            if (idcard.display_name == card.display_name)
+        store_id_cards();
+        foreach(IdCard idcard in id_card_list) {
+            if (idcard.display_name == card.display_name) {
                 return idcard;
+            }
+        }
+        logger.error(@"update_card: card '$(card.display_name)' was not found after re-loading!");
         return null;
     }
 
     public bool remove_card(IdCard card) {
         if (id_card_list.remove(card)) {
-            store_id_cards ();
+            store_id_cards();
             return true;
         }
         return false;
@@ -68,134 +73,155 @@ public class LocalFlatFileStore : Object, IIdentityCardStore {
      
     private void load_id_cards() {
         id_card_list.clear();
-        var key_file = new KeyFile ();
-        var path = get_data_dir ();
-        var filename = Path.build_filename (path, FILE_NAME);
+        var key_file = new KeyFile();
+        var path = get_data_dir();
+        var filename = Path.build_filename(path, FILE_NAME);
+        logger.trace("load_id_cards: attempting to load from " + filename);
         
         try {
-            key_file.load_from_file (filename, KeyFileFlags.NONE);
+            key_file.load_from_file(filename, KeyFileFlags.NONE);
         }
         catch (Error e) {
             stdout.printf("Error: %s\n", e.message);
             return;
         }
 
-        var identities_uris = key_file.get_groups ();
+        var identities_uris = key_file.get_groups();
         foreach (string identity in identities_uris) {
             try {
-                IdCard id_card = new IdCard ();
-
-                id_card.issuer = key_file.get_string (identity, "Issuer");
-                id_card.username = key_file.get_string (identity, "Username");
-                id_card.password = key_file.get_string (identity, "Password");
-                id_card.services = key_file.get_string_list (identity, "Services");
-                id_card.display_name = key_file.get_string (identity, "DisplayName");
-                if (key_file.has_key (identity, "StorePassword")) {
-                    id_card.store_password = (key_file.get_string (identity, "StorePassword") == "yes");
+                IdCard id_card = new IdCard();
+
+                id_card.issuer = key_file.get_string(identity, "Issuer");
+                id_card.username = key_file.get_string(identity, "Username");
+                id_card.password = key_file.get_string(identity, "Password");
+                id_card.update_services(key_file.get_string_list(identity, "Services"));
+                id_card.display_name = key_file.get_string(identity, "DisplayName");
+                if (key_file.has_key(identity, "StorePassword")) {
+                    id_card.store_password = (key_file.get_string(identity, "StorePassword") == "yes");
                 } else {
                     id_card.store_password = (id_card.password != null) && (id_card.password != "");
                 }
                 
-                if (key_file.has_key (identity, "Rules-Patterns") &&
-                    key_file.has_key (identity, "Rules-AlwaysConfirm")) {
-                    string [] rules_patterns =    key_file.get_string_list (identity, "Rules-Patterns");
-                    string [] rules_always_conf = key_file.get_string_list (identity, "Rules-AlwaysConfirm");
+                if (key_file.has_key(identity, "Rules-Patterns") &&
+                    key_file.has_key(identity, "Rules-AlwaysConfirm")) {
+                    string [] rules_patterns =    key_file.get_string_list(identity, "Rules-Patterns");
+                    string [] rules_always_conf = key_file.get_string_list(identity, "Rules-AlwaysConfirm");
                     
                     if (rules_patterns.length == rules_always_conf.length) {
-                      Rule[] rules = new Rule[rules_patterns.length];
-                      for (int i = 0; i < rules_patterns.length; i++) {
-                        rules[i] = {rules_patterns[i], rules_always_conf[i]};
-                      }
-                      id_card.rules = rules;
+                        Rule[] rules = new Rule[rules_patterns.length];
+                        for (int i = 0; i < rules_patterns.length; i++) {
+                            rules[i] = {rules_patterns[i], rules_always_conf[i]};
+                        }
+                        id_card.rules = rules;
                     }
                 }
                 
                 // Trust anchor 
-                id_card.trust_anchor.ca_cert = key_file.get_string (identity, "CA-Cert").strip();
-                id_card.trust_anchor.subject = key_file.get_string (identity, "Subject");
-                id_card.trust_anchor.subject_alt = key_file.get_string (identity, "SubjectAlt");
-                id_card.trust_anchor.server_cert = key_file.get_string (identity, "ServerCert");
-
-                id_card_list.add (id_card);
+                string ca_cert = key_file.get_string(identity, "CA-Cert").strip();
+                string server_cert = key_file.get_string(identity, "ServerCert");
+                string subject = key_file.get_string(identity, "Subject");
+                string subject_alt = key_file.get_string(identity, "SubjectAlt");
+                var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt);
+                string ta_datetime_added = get_string_setting(identity, "TA_DateTime_Added", "", key_file);
+                if (ta_datetime_added != "") {
+                    ta.set_datetime_added(ta_datetime_added);
+                }
+                id_card.set_trust_anchor_from_store(ta);
+                id_card_list.add(id_card);
             }
             catch (Error e) {
-                stdout.printf ("Error:  %s\n", e.message);
+                logger.error("load_id_cards: Error while loading keyfile: %s\n".printf(e.message));
+                stdout.printf("Error:  %s\n", e.message);
             }
         }
     }
 
     private string get_data_dir() {
         string path;
-        path = Path.build_filename (Environment.get_user_data_dir (),
-                                    Config.PACKAGE_TARNAME);
+        path = Path.build_filename(Environment.get_user_data_dir(),
+                                   Config.PACKAGE_TARNAME);
                                     
-        if (!FileUtils.test (path, FileTest.EXISTS)) {
-            DirUtils.create_with_parents (path, 0700);
+        if (!FileUtils.test(path, FileTest.EXISTS)) {
+            DirUtils.create_with_parents(path, 0700);
         }
         return path;
     }
     
-    public void store_id_cards () {
-        var key_file = new KeyFile ();
+    internal void store_id_cards() {
+        var key_file = new KeyFile();
         foreach (IdCard id_card in this.id_card_list) {
+            logger.trace(@"store_id_cards: Storing '$(id_card.display_name)'");
+
             /* workaround for Centos vala array property bug: use temp arrays */
             var rules = id_card.rules;
-            var services = id_card.services;
-            string[] empty = {};
             string[] rules_patterns = new string[rules.length];
             string[] rules_always_conf = new string[rules.length];
             
-            for (int i=0; i<rules.length; i++) {
-              rules_patterns[i] = rules[i].pattern;
-              rules_always_conf[i] = rules[i].always_confirm;
+            for (int i = 0; i < rules.length; i++) {
+                rules_patterns[i] = rules[i].pattern;
+                rules_always_conf[i] = rules[i].always_confirm;
             }
 
-            key_file.set_string (id_card.display_name, "Issuer", id_card.issuer ?? "");
-            key_file.set_string (id_card.display_name, "DisplayName", id_card.display_name ?? "");
-            key_file.set_string (id_card.display_name, "Username", id_card.username ?? "");
+            key_file.set_string(id_card.display_name, "Issuer", id_card.issuer ?? "");
+            key_file.set_string(id_card.display_name, "DisplayName", id_card.display_name ?? "");
+            key_file.set_string(id_card.display_name, "Username", id_card.username ?? "");
             if (id_card.store_password && (id_card.password != null))
-              key_file.set_string (id_card.display_name, "Password", id_card.password);
+                key_file.set_string(id_card.display_name, "Password", id_card.password);
             else
-              key_file.set_string (id_card.display_name, "Password", "");
-            key_file.set_string_list (id_card.display_name, "Services", services ?? empty);
+                key_file.set_string(id_card.display_name, "Password", "");
+
+            // Using id_card.services.to_array() seems to cause a crash, possibly due to
+            // an unowned reference to the array.
+            string[] svcs = new string[id_card.services.size];
+            for (int i = 0; i < id_card.services.size; i++) {
+                svcs[i] = id_card.services[i];
+            }
+
+            key_file.set_string_list(id_card.display_name, "Services", svcs);
 
             if (rules.length > 0) {
-              key_file.set_string_list (id_card.display_name, "Rules-Patterns", rules_patterns);
-              key_file.set_string_list (id_card.display_name, "Rules-AlwaysConfirm", rules_always_conf);
+                key_file.set_string_list(id_card.display_name, "Rules-Patterns", rules_patterns);
+                key_file.set_string_list(id_card.display_name, "Rules-AlwaysConfirm", rules_always_conf);
             }
-            key_file.set_string (id_card.display_name, "StorePassword", id_card.store_password ? "yes" : "no");
+            key_file.set_string(id_card.display_name, "StorePassword", id_card.store_password ? "yes" : "no");
             
             // Trust anchor 
-            key_file.set_string (id_card.display_name, "CA-Cert", id_card.trust_anchor.ca_cert ?? "");
-            key_file.set_string (id_card.display_name, "Subject", id_card.trust_anchor.subject ?? "");
-            key_file.set_string (id_card.display_name, "SubjectAlt", id_card.trust_anchor.subject_alt ?? "");
-            key_file.set_string (id_card.display_name, "ServerCert", id_card.trust_anchor.server_cert ?? "");
+            key_file.set_string(id_card.display_name, "CA-Cert", id_card.trust_anchor.ca_cert);
+            key_file.set_string(id_card.display_name, "Subject", id_card.trust_anchor.subject);
+            key_file.set_string(id_card.display_name, "SubjectAlt", id_card.trust_anchor.subject_alt);
+            key_file.set_string(id_card.display_name, "ServerCert", id_card.trust_anchor.server_cert);
+            if (id_card.trust_anchor.datetime_added != "") {
+                key_file.set_string(id_card.display_name, "TA_DateTime_Added", id_card.trust_anchor.datetime_added);
+            }
+            logger.trace(@"store_id_cards: Stored '$(id_card.display_name)'");
         }
 
-        var text = key_file.to_data (null);
+        var text = key_file.to_data(null);
 
         try {
-            var path = get_data_dir ();
-            var filename = Path.build_filename (path, FILE_NAME);
+            var path = get_data_dir();
+            var filename = Path.build_filename(path, FILE_NAME);
+            logger.trace("store_id_cards: attempting to store to " + filename);
             var file  = File.new_for_path(filename);
             var stream = file.replace(null, false, FileCreateFlags.PRIVATE);
-#if GIO_VAPI_USES_ARRAYS
+            #if GIO_VAPI_USES_ARRAYS
             stream.write(text.data);
-#else
+            #else
             var bits = text.data;
             stream.write(&bits[0], bits.length);
-#endif
-        }
+            #endif
+                }
         catch (Error e) {
-            stdout.printf ("Error:  %s\n", e.message);
+            logger.error("store_id_cards: Error while saving keyfile: %s\n".printf(e.message));
+            stdout.printf("Error:  %s\n", e.message);
         }
 
         load_id_cards();
     }
 
-     public LocalFlatFileStore () {
+    public LocalFlatFileStore() {
         id_card_list = new LinkedList<IdCard>();
         load_id_cards();
-     }
- }
+    }
+}
 
diff --git a/src/moonshot-logger.vala b/src/moonshot-logger.vala
new file mode 100644 (file)
index 0000000..4c4c7fc
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2011-2016, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of JANET(UK) 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 HOLDER 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.
+ */
+
+
+public MoonshotLogger get_logger(string name) {
+    return new MoonshotLogger(name);
+}
+
+
+#if USE_LOG4VALA
+
+static void glib_default_log_handler(string? log_domain, LogLevelFlags log_level, string message)
+{
+    Log4Vala.Logger logger = Log4Vala.Logger.get_logger(log_domain ?? "Glib");
+    stderr.printf(log_level.to_string() + " : " + message + "\n");
+    logger.error("Glib error level: " + log_level.to_string() + " : " + message);
+}
+
+/** Logger class that wraps the Log4Vala logger */
+public class MoonshotLogger : Object {
+    static bool logger_is_initialized = false;
+
+    private Log4Vala.Logger logger;
+
+    public MoonshotLogger(string name) {
+        if (!logger_is_initialized) {
+            Log.set_default_handler(glib_default_log_handler);
+
+#if IPC_MSRPC
+            // Look for config file in the app's current directory.
+            string conf_file = "log4vala.conf";
+#else
+            string conf_file = GLib.Environment.get_variable("MOONSHOT_UI_LOG_CONFIG");
+#endif
+            Log4Vala.init(conf_file);
+            logger_is_initialized = true;
+        }
+
+        logger = Log4Vala.Logger.get_logger(name);
+    }
+
+    /**
+     * Log a trace message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void trace(string message, Error? e = null) {
+        logger.trace(message, e);
+    }
+
+
+    /**
+     * Log a debug message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void debug(string message, Error? e = null) {
+        logger.debug(message, e);
+    }
+
+
+    /**
+     * Log an info message.
+     * @param e optional Error to be logged
+     */
+    public void info(string message, Error? e = null) {
+        logger.info(message, e);
+    }
+
+    /**
+     * Log a warning message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void warn(string message, Error? e = null) {
+        logger.warn(message, e);
+    }
+
+    /**
+     * Log an error message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void error(string message, Error? e = null) {
+        logger.error(message, e);
+    }
+
+    /**
+     * Log a fatal message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void fatal(string message, Error? e = null) {
+        logger.fatal(message, e);
+    }
+}
+
+
+#else
+
+/** Logger that currently does nothing, but may eventually write to stdout or a file if enabled */
+public class MoonshotLogger : Object {
+
+    internal MoonshotLogger(string name) {
+    }
+
+    /**
+     * Log a trace message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void trace(string message, Error? e = null) {
+    }
+
+
+    /**
+     * Log a debug message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void debug(string message, Error? e = null) {
+    }
+
+
+    /**
+     * Log an info message.
+     * @param e optional Error to be logged
+     */
+    public void info(string message, Error? e = null) {
+    }
+
+    /**
+     * Log a warning message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void warn(string message, Error? e = null) {
+    }
+
+    /**
+     * Log an error message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void error(string message, Error? e = null) {
+    }
+
+    /**
+     * Log a fatal message.
+     * @param message log message
+     * @param e optional Error to be logged
+     */
+    public void fatal(string message, Error? e = null) {
+    }
+}
+
+#endif
index 772d12c..5bf2e46 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,90 +33,100 @@ using Gtk;
 
 class AddPasswordDialog : Dialog
 {
+    private static Gdk.Color white = make_color(65535, 65535, 65535);
+
     private Entry password_entry;
     private CheckButton remember_checkbutton;
 
     public string password {
-        get { return password_entry.get_text (); }
+        get { return password_entry.get_text(); }
+    }
+
+    /**
+     * Don't leave passwords in memory longer than necessary.
+     * This may not actually erase the password data bytes, but it seems to be the best we can do.
+     */
+    public void clear_password() {
+        clear_password_entry(password_entry);
     }
 
     public bool remember {
-        get { return remember_checkbutton.get_active (); }
+        get { return remember_checkbutton.get_active(); }
     }
 
-    public AddPasswordDialog (IdCard id_card, IdentityRequest? request)
+    public AddPasswordDialog(IdCard id_card, IdentityRequest? request)
     {
-        this.set_title (_("Please enter password for ") + id_card.display_name);
-        this.set_modal (true);
-
-       if (request != null) {
-            this.add_buttons (_("Send"), ResponseType.OK,
-                              _("Return to application"), ResponseType.CANCEL);
-        } else {
-            this.add_buttons (_("Done"), ResponseType.OK,
-                              _("Cancel"), ResponseType.CANCEL);
-        }
-        this.set_default_response (ResponseType.OK);
-
-        var content_area = this.get_content_area ();
-        ((Box) content_area).set_spacing (12);
-        Label service_label = null;
-        Label service_value = null;
-       if (request != null) {
-            service_label = new Label (_("for use with:"));
-            service_label.set_alignment (1, (float) 0.5);
-            service_value = new Label (request.service);
-            service_value.set_alignment (0, (float) 0.5);
-        }
-
-        var nai_label = new Label (_("Network Access Identifier:"));
-        nai_label.set_alignment (1, (float) 0.5);
-        var nai_value = new Label (id_card.nai);
-        nai_value.set_alignment (0, (float) 0.5);
-
-        var password_label = new Label (_("Password:"));
-        password_label.set_alignment (1, (float) 0.5);
-        this.password_entry = new Entry ();
-        password_entry.set_invisible_char ('*');
-        password_entry.set_visibility (false);
+        this.set_title(_("Moonshot - Password"));
+        this.set_modal(true);
+        set_bg_color(this);
+
+        this.add_buttons(_("Cancel"), ResponseType.CANCEL,
+                         _("Connect"), ResponseType.OK);
+
+        this.set_default_response(ResponseType.OK);
+
+        var content_area = this.get_content_area();
+        ((Box) content_area).set_spacing(12);
+        set_bg_color(content_area);
+
+        Label dialog_label = new Label(_("Enter the password for ") + id_card.display_name);
+        dialog_label.set_alignment(0, 0);
+
+        var nai_label = new Label(_("User (NAI):"));
+        nai_label.set_alignment(0, 1);
+        var nai_value = new Label(id_card.nai);
+        nai_value.set_alignment(0, 0);
+
+        var password_label = new Label(_("Password:"));
+        password_label.set_alignment(0, (float) 1);
+        this.password_entry = new Entry();
+        password_entry.set_invisible_char('*');
+        password_entry.set_visibility(false);
         password_entry.activates_default = true;
-        remember_checkbutton = new CheckButton.with_label (_("Remember password"));
+        remember_checkbutton = new CheckButton.with_label(_("Remember password"));
 
-        set_atk_relation (password_entry, password_entry, Atk.RelationType.LABEL_FOR);
+        set_atk_relation(password_label, password_entry, Atk.RelationType.LABEL_FOR);
 
-        var table = new Table (4, 2, false);
+        var table = new Table(6, 1, false);
+        AttachOptions opts = AttachOptions.EXPAND | AttachOptions.FILL;
         int row = 0;
-        table.set_col_spacings (10);
-        table.set_row_spacings (10);
-        if (request != null) {
-            table.attach_defaults (service_label, 0, 1, row, row + 1);
-            table.attach_defaults (service_value, 1, 2, row, row + 1);
-            row++;
-        }
-        table.attach_defaults (nai_label, 0, 1, row, row+1);
-        table.attach_defaults (nai_value, 1, 2, row, row+1);
+        table.set_col_spacings(6);
+        table.set_row_spacings(0);
+        table.attach(dialog_label, 0, 1, row, row + 1, opts, opts, 0, 2);
+//            table.attach_defaults(service_value, 1, 2, row, row + 1);
+        row++;
+
+        VBox nai_vbox = new VBox(false, 0);
+        nai_vbox.pack_start(nai_label, false, false, 0);
+        nai_vbox.pack_start(nai_value, false, false, 0);
+        table.attach(nai_vbox, 0, 1, row, row + 1, opts, opts, 0, 12);
         row++;
-        table.attach_defaults (password_label, 0, 1, row, row+1);
-        table.attach_defaults (password_entry, 1, 2, row, row+1);
+
+        VBox password_vbox = new VBox(false, 1);
+        var empty_box2 = new VBox(false, 0);
+        empty_box2.set_size_request(0, 0);
+        password_vbox.pack_start(empty_box2, false, false, 3);
+        password_vbox.pack_start(password_label, false, false, 0);
+        password_vbox.pack_start(password_entry, false, false, 0);
+        table.attach(password_vbox, 0, 1, row, row + 1, opts, opts, 0, 0);
         row++;
-        table.attach_defaults (remember_checkbutton,  1, 2, row, row+1);
 
-        var vbox = new VBox (false, 0);
-        vbox.set_border_width (6);
-        vbox.pack_start (table, false, false, 0);
+        table.attach(remember_checkbutton,  0, 1, row, row + 1, opts, opts, 20, 2);
+        row++;
 
-        ((Container) content_area).add (vbox);
+        var empty_box3 = new VBox(false, 0);
+        empty_box3.set_size_request(0, 0);
+        table.attach(empty_box3,  0, 1, row, row + 1, opts, opts, 0, 10);
+        row++;
 
-        this.set_border_width (6);
-        //this.set_resizable (false);
-        this.show_all ();
-    }
+        var vbox = new VBox(false, 0);
+        vbox.set_border_width(6);
+        vbox.pack_start(table, false, false, 0);
 
-    private void set_atk_relation (Widget widget, Widget target_widget, Atk.RelationType relationship)
-    {
-        var atk_widget = widget.get_accessible ();
-        var atk_target_widget = target_widget.get_accessible ();
+        ((Container) content_area).add(vbox);
 
-        atk_widget.add_relationship (relationship, atk_target_widget);
+        this.set_border_width(6);
+        //this.set_resizable(false);
+        this.show_all();
     }
 }
index de891cd..2311cab 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * SUCH DAMAGE.
 */
 
+
+
 namespace WebProvisioning
-{ 
-  IdCard card;
-  IdCard[] cards;
-
-  bool
-  check_stack (SList<string> stack, string[] reference)
-  {
-    if (stack.length () < reference.length)
-      return false;
-    
-    for (int i = 0; i<reference.length; i++)
-    {
-      if (stack.nth_data(i) != reference[i])
-        return false;
-    }
+{
+    bool check_stack(SList<string> stack, string[] reference) {
 
-    return true;
-  }
-
-  bool
-  always_confirm_handler (SList<string> stack)
-  {
-    string[] always_confirm_path = {"always-confirm", "rule", "selection-rules", "identity", "identities"};
-    
-    return check_stack (stack, always_confirm_path);
-  }
-  
-  bool
-  pattern_handler (SList<string> stack)
-  {
-    string[] pattern_path = {"pattern", "rule", "selection-rules", "identity", "identities"};
-    
-    return check_stack (stack, pattern_path);
-  }
-
-  bool
-  server_cert_handler (SList<string> stack)
-  {
-    string[] server_cert_path = {"server-cert", "trust-anchor", "identity", "identities"};
-    
-    return check_stack (stack, server_cert_path);
-  }
-
-  bool
-  subject_alt_handler (SList<string> stack)
-  {
-    string[] subject_alt_path = {"subject-alt", "trust-anchor", "identity", "identities"};
-    
-    return check_stack (stack, subject_alt_path);
-  }
-
-  bool
-  subject_handler (SList<string> stack)
-  {
-    string[] subject_path = {"subject", "trust-anchor", "identity", "identities"};
-    
-    return check_stack (stack, subject_path);
-  }
-  
-  bool
-  ca_cert_handler (SList<string> stack)
-  {
-    string[] ca_path = {"ca-cert", "trust-anchor", "identity", "identities"};
-    
-    return check_stack (stack, ca_path);
-  }
-
-  bool
-  realm_handler (SList<string> stack)
-  {
-    string[] realm_path = {"realm", "identity", "identities"};
-    
-    return check_stack (stack, realm_path);
-  }
-
-  bool
-  password_handler (SList<string> stack)
-  {
-    string[] password_path = {"password", "identity", "identities"};
-    
-    return check_stack (stack, password_path);
-  }
-
-  bool
-  user_handler (SList<string> stack)
-  {
-    string[] user_path = {"user", "identity", "identities"};
-    
-    return check_stack (stack, user_path);
-  }
-
-  bool
-  display_name_handler (SList<string> stack)
-  {
-    string[] display_name_path = {"display-name", "identity", "identities"};
-    
-    return check_stack (stack, display_name_path);
-  }
-  
-  public void
-  start_element_func (MarkupParseContext context,
-                      string element_name,
-                      string[] attribute_names,
-                      string[] attribute_values) throws MarkupError
-  {
-    if (element_name == "identity")
-    {
-      IdCard[] tmp_cards = cards;
-
-      cards = new IdCard[tmp_cards.length + 1];
-      for (int i=0; i<tmp_cards.length; i++)
-      {
-        cards[i] = tmp_cards[i];
-      }
-      card = new IdCard();
-      cards[tmp_cards.length] = card;
-    }
-    else if (element_name == "rule")
-    {
-      Rule[] tmp_rules = card.rules;
-      card.rules = new Rule[tmp_rules.length + 1];
-      for (int i=0; i<tmp_rules.length; i++)
-      {
-        card.rules[i] = tmp_rules[i];
-      }
-      
-      card.rules[tmp_rules.length] = Rule();
-    }
-  }
-
-  public void
-  text_element_func (MarkupParseContext context,
-                     string             text,
-                     size_t             text_len) throws MarkupError
-  {
-    unowned SList<string> stack = context.get_element_stack ();
-    
-    if (text_len < 1)
-      return;
-    
-    if (stack.nth_data(0) == "display-name" && display_name_handler (stack))
-    {
-      card.display_name = text;
-    }
-    else if (stack.nth_data(0) == "user" && user_handler (stack))
-    {
-      card.username = text;
+        if (stack.length() < reference.length)
+            return false;
+
+        for (int i = 0; i < reference.length; i++)
+        {
+            if (stack.nth_data(i) != reference[i])
+                return false;
+        }
+
+        return true;
     }
-    else if (stack.nth_data(0) == "password" && password_handler (stack))
+
+    bool always_confirm_handler(SList<string> stack)
     {
-      card.password = text;
+        string[] always_confirm_path = {"always-confirm", "rule", "selection-rules", "identity", "identities"};
+
+        return check_stack(stack, always_confirm_path);
     }
-    else if (stack.nth_data(0) == "realm" && realm_handler (stack))
+
+    bool
+    pattern_handler(SList<string> stack)
     {
-      card.issuer = text;
+        string[] pattern_path = {"pattern", "rule", "selection-rules", "identity", "identities"};
+
+        return check_stack(stack, pattern_path);
     }
-    else if (stack.nth_data(0) == "service")
+
+    bool server_cert_handler(SList<string> stack)
     {
-      string[] services = card.services;
-      card.services = new string[services.length + 1];
-      for (int i = 0; i<services.length; i++)
-      {
-        card.services[i] = services[i];
-      }
-      card.services[services.length] = text;
+        string[] server_cert_path = {"server-cert", "trust-anchor", "identity", "identities"};
+
+        return check_stack(stack, server_cert_path);
     }
-    /* Rules */
-    else if (stack.nth_data(0) == "pattern" && pattern_handler (stack))
+
+    bool subject_alt_handler(SList<string> stack)
     {
-      /* use temp array to workaround valac 0.10 bug accessing array property length */ 
-      var temp = card.rules;
-      card.rules[temp.length - 1].pattern = text;
+        string[] subject_alt_path = {"subject-alt", "trust-anchor", "identity", "identities"};
+
+        return check_stack(stack, subject_alt_path);
     }
-    else if (stack.nth_data(0) == "always-confirm" && always_confirm_handler (stack))
+
+    bool subject_handler(SList<string> stack)
     {
-      if (text == "true" || text == "false") {
-        /* use temp array to workaround valac 0.10 bug accessing array property length*/
-        var temp = card.rules;
-        card.rules[temp.length - 1].always_confirm = text;
-      }
+        string[] subject_path = {"subject", "trust-anchor", "identity", "identities"};
+
+        return check_stack(stack, subject_path);
     }
-    /*Trust anchor*/
-    else if (stack.nth_data(0) == "ca-cert" && ca_cert_handler (stack))
+
+    bool ca_cert_handler(SList<string> stack)
     {
-      card.trust_anchor.ca_cert = text;
+        string[] ca_path = {"ca-cert", "trust-anchor", "identity", "identities"};
+
+        return check_stack(stack, ca_path);
     }
-    else if (stack.nth_data(0) == "subject" && subject_handler (stack))
+
+    bool realm_handler(SList<string> stack)
     {
-      card.trust_anchor.subject = text;
+        string[] realm_path = {"realm", "identity", "identities"};
+
+        return check_stack(stack, realm_path);
     }
-    else if (stack.nth_data(0) == "subject-alt" && subject_alt_handler (stack))
+
+    bool password_handler(SList<string> stack)
     {
-      card.trust_anchor.subject_alt = text;
+        string[] password_path = {"password", "identity", "identities"};
+
+        return check_stack(stack, password_path);
     }
-    else if (stack.nth_data(0) == "server-cert" && server_cert_handler (stack))
+
+    bool user_handler(SList<string> stack)
     {
-      card.trust_anchor.server_cert = text;
+        string[] user_path = {"user", "identity", "identities"};
+
+        return check_stack(stack, user_path);
     }
-  }
-
-  class Parser
-  {
-    private MarkupParser parser;
-    private string       text;
-    private string       path;
-    public Parser (string path)
+
+    bool display_name_handler(SList<string> stack)
     {
-      text = "";
-      this.path = path;
-
-      var file = File.new_for_path (path);
-    
-      try
-      {
-        var dis = new DataInputStream (file.read ());
-        string line;
-        while ((line = dis.read_line (null)) != null)
-          text += line;
-      }
-      catch (GLib.Error e)
-      {
-        error ("Could not retreive file size");
-      }
-      
-      parser = {start_element_func, null, text_element_func, null, null};
+        string[] display_name_path = {"display-name", "identity", "identities"};
+
+        return check_stack(stack, display_name_path);
     }
-    
-    public void
-    parse ()
+
+    public class Parser : Object
     {
-      var ctx = new MarkupParseContext(parser, 0, null, null);
-      
-      try
-      {
-        ctx.parse (text, text.length);
-      }
-      catch (GLib.Error e)
-      {
-        error ("Could not parse %s, invalid content", path);
-      } 
+        private static MoonshotLogger logger = new MoonshotLogger("WebProvisioning");
+
+        private void start_element_func(MarkupParseContext context,
+                                        string element_name,
+                                        string[] attribute_names,
+                                        string[] attribute_values) throws MarkupError
+        {
+            if (element_name == "identity")
+            {
+                card = new IdCard();
+                _cards += card;
+
+                ta_ca_cert = "";
+                ta_server_cert = "";
+                ta_subject = "";
+                ta_subject_alt = "";
+            }
+            else if (element_name == "rule")
+            {
+                card.add_rule(Rule());
+            }
+        }
+
+        private void end_element_func(MarkupParseContext context,
+                                      string element_name) throws MarkupError
+        {
+            if (element_name == "identity")
+            {
+                if (ta_ca_cert != "" || ta_server_cert != "") {
+                    var ta = new TrustAnchor(ta_ca_cert,
+                                             ta_server_cert,
+                                             ta_subject,
+                                             ta_subject_alt);
+                    // Set the datetime_added in moonshot-server.vala, since it doesn't get sent via IPC
+                    card.set_trust_anchor_from_store(ta);
+                }
+            }
+        }
+
+        private void
+        text_element_func(MarkupParseContext context,
+                          string             text,
+                          size_t             text_len) throws MarkupError {
+            unowned SList<string> stack = context.get_element_stack();
+
+            if (text_len < 1)
+                return;
+
+            if (stack.nth_data(0) == "display-name" && display_name_handler(stack))
+            {
+                card.display_name = text;
+            }
+            else if (stack.nth_data(0) == "user" && user_handler(stack))
+            {
+                card.username = text;
+            }
+            else if (stack.nth_data(0) == "password" && password_handler(stack))
+            {
+                card.password = text;
+            }
+            else if (stack.nth_data(0) == "realm" && realm_handler(stack))
+            {
+                card.issuer = text;
+            }
+            else if (stack.nth_data(0) == "service")
+            {
+                card.services.add(text);
+            }
+
+            /* Rules */
+            else if (stack.nth_data(0) == "pattern" && pattern_handler(stack))
+            {
+                /* use temp array to workaround valac 0.10 bug accessing array property length */
+                var temp = card.rules;
+                card.rules[temp.length - 1].pattern = text;
+            }
+            else if (stack.nth_data(0) == "always-confirm" && always_confirm_handler(stack))
+            {
+                if (text == "true" || text == "false") {
+                    /* use temp array to workaround valac 0.10 bug accessing array property length*/
+                    var temp = card.rules;
+                    card.rules[temp.length - 1].always_confirm = text;
+                }
+            }
+            else if (stack.nth_data(0) == "ca-cert" && ca_cert_handler(stack))
+            {
+                ta_ca_cert = text ?? "";
+            }
+            else if (stack.nth_data(0) == "server-cert" && server_cert_handler(stack))
+            {
+                ta_server_cert = text ?? "";
+            }
+            else if (stack.nth_data(0) == "subject" && subject_handler(stack))
+            {
+                ta_subject = text;
+            }
+            else if (stack.nth_data(0) == "subject-alt" && subject_alt_handler(stack))
+            {
+                ta_subject_alt = text;
+            }
+        }
+
+        private const MarkupParser parser = {
+            start_element_func, end_element_func, text_element_func, null, null
+        };
+
+        private MarkupParseContext ctx;
+
+        private string       text;
+        private string       path;
+
+        private string ta_ca_cert;
+        private string ta_server_cert;
+        private string ta_subject;
+        private string ta_subject_alt;
+
+        private IdCard card;
+        private IdCard[] _cards = {};
+
+        public IdCard[] cards {
+            get {return _cards;}
+            private set {_cards = value ?? new IdCard[0] ;}
+        }
+
+        public Parser(string path) {
+
+            ctx = new MarkupParseContext(parser, 0, this, null);
+
+            text = "";
+            this.path = path;
+
+            var file = File.new_for_path(path);
+
+            try
+            {
+                var dis = new DataInputStream(file.read());
+                string line;
+                while ((line = dis.read_line(null)) != null) {
+                    text += line;
+
+                    // Preserve newlines.
+                    //
+                    // This may add an extra newline at EOF. Maybe use
+                    // dis.read_upto("\n", ...) followed by dis.read_byte() instead?
+                    text += "\n";
+                }
+            }
+            catch(GLib.Error e)
+            {
+                error("Could not retreive file size");
+            }
+        }
+
+        public void parse() {
+            try
+            {
+                ctx.parse(text, text.length);
+            }
+            catch(GLib.Error e)
+            {
+                error("Could not parse %s, invalid content", path);
+            }
+        }
     }
-  }
 }
diff --git a/src/moonshot-server-linux.vala b/src/moonshot-server-linux.vala
new file mode 100644 (file)
index 0000000..4aecef2
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2011-2016, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of JANET(UK) 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 HOLDER 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.
+*/
+
+using Gee;
+
+[DBus (name = "org.janet.Moonshot")]
+public class MoonshotServer : Object {
+
+    static MoonshotLogger logger = get_logger("MoonshotServer");
+
+    private string app_name = "Moonshot";
+
+    private IdentityManagerApp parent_app;
+
+    public MoonshotServer(IdentityManagerApp app)
+    {
+        logger.trace("MoonshotServer.<constructor>; app=" + (app == null ? "null" : "non-null"));
+        this.parent_app = app;
+    }
+
+    public bool show_ui()
+    {
+        logger.trace("MoonshotServer.show_ui");
+
+        if (parent_app.view == null) {
+            stderr.printf(app_name, "show_ui: parent_app.view is null!\n");
+            logger.warn("show_ui: parent_app.view is null!");
+            return false;
+        }
+        parent_app.show();
+        parent_app.explicitly_launched = true;
+        logger.trace("MoonshotServer.show_ui: returning true");
+        return true;
+    }
+
+    public async bool get_identity(string nai,
+                                   string password,
+                                   string service,
+                                   out string nai_out,
+                                   out string password_out,
+                                   out string server_certificate_hash,
+                                   out string ca_certificate,
+                                   out string subject_name_constraint,
+                                   out string subject_alt_name_constraint)
+    {
+        logger.trace(@"MoonshotServer.get_identity: nai='$nai'; service='$service'");
+        var request = new IdentityRequest(parent_app,
+                                          nai,
+                                          password,
+                                          service);
+        logger.trace(@"MoonshotServer.get_identity: Calling request.execute()");
+        request.set_callback((IdentityRequest) => get_identity.callback());
+        request.execute();
+        logger.trace(@"MoonshotServer.get_identity: Back from request.execute()");
+        yield;
+        logger.trace(@"MoonshotServer.get_identity: back from yield");
+
+        nai_out = "";
+        password_out = "";
+        server_certificate_hash = "";
+        ca_certificate = "";
+        subject_name_constraint = "";
+        subject_alt_name_constraint = "";
+
+        var id_card = request.id_card;
+
+        if ((id_card != null) && (!id_card.is_no_identity())) {
+            nai_out = id_card.nai;
+            if ((request.password != null) && (request.password != ""))
+                password_out = request.password;
+            else
+                password_out = id_card.password;
+
+            server_certificate_hash = id_card.trust_anchor.server_cert;
+            ca_certificate = id_card.trust_anchor.ca_cert;
+            subject_name_constraint = id_card.trust_anchor.subject;
+            subject_alt_name_constraint = id_card.trust_anchor.subject_alt;
+
+            if (nai_out == null)
+                nai_out = "";
+            if (password_out == null)
+                password_out = "";
+            if (server_certificate_hash == null)
+                server_certificate_hash = "";
+            if (ca_certificate == null)
+                ca_certificate = "";
+            if (subject_name_constraint == null)
+                subject_name_constraint = "";
+            if (subject_alt_name_constraint == null)
+                subject_alt_name_constraint = "";
+
+            logger.trace(@"MoonshotServer.get_identity: returning with nai_out=$nai_out");
+
+            return true;
+        }
+
+        logger.trace("MoonshotServer.get_identity: returning false");
+        return false;
+    }
+
+    public async bool get_default_identity(out string nai_out,
+                                           out string password_out,
+                                           out string server_certificate_hash,
+                                           out string ca_certificate,
+                                           out string subject_name_constraint,
+                                           out string subject_alt_name_constraint)
+    {
+        logger.trace("MoonshotServer.get_default_identity");
+        var request = new IdentityRequest.default(parent_app);
+        request.set_callback((IdentityRequest) => get_default_identity.callback());
+        request.execute();
+        yield;
+
+        nai_out = "";
+        password_out = "";
+        server_certificate_hash = "";
+        ca_certificate = "";
+        subject_name_constraint = "";
+        subject_alt_name_constraint = "";
+
+        if (request.id_card != null)
+        {
+            nai_out = request.id_card.nai;
+            password_out = request.id_card.password;
+
+            server_certificate_hash = request.id_card.trust_anchor.server_cert;
+            ca_certificate = request.id_card.trust_anchor.ca_cert;
+            subject_name_constraint = request.id_card.trust_anchor.subject;
+            subject_alt_name_constraint = request.id_card.trust_anchor.subject_alt;
+
+            if (nai_out == null)
+                nai_out = "";
+            if (password_out == null)
+                password_out = "";
+            if (server_certificate_hash == null)
+                server_certificate_hash = "";
+            if (ca_certificate == null)
+                ca_certificate = "";
+            if (subject_name_constraint == null)
+                subject_name_constraint = "";
+            if (subject_alt_name_constraint == null)
+                subject_alt_name_constraint = "";
+
+            logger.trace("MoonshotServer.get_default_identity: returning true");
+            return true;
+        }
+
+        return false;
+    }
+
+    public bool install_id_card(string   display_name,
+                                string   user_name,
+                                string   ?password,
+                                string   ?realm,
+                                string[] ?rules_patterns,
+                                string[] ?rules_always_confirm,
+                                string[] ?services,
+                                string   ?ca_cert,
+                                string   ?subject,
+                                string   ?subject_alt,
+                                string   ?server_cert,
+                                int      force_flat_file_store)
+    {
+        IdCard idcard = new IdCard();
+
+        idcard.display_name = display_name;
+        idcard.username = user_name;
+        idcard.password = password;
+        if ((password != null) && (password != ""))
+            idcard.store_password = true;
+        idcard.issuer = realm;
+        idcard.update_services(services);
+        var ta = new TrustAnchor(ca_cert, server_cert, subject, subject_alt);
+
+        if (!ta.is_empty()) {
+            // We have to set the datetime_added here, because it isn't delivered via IPC.
+            string ta_datetime_added = TrustAnchor.format_datetime_now();
+            ta.set_datetime_added(ta_datetime_added);
+            logger.trace("install_id_card : Set ta_datetime_added for '%s' to '%s'; ca_cert='%s'; server_cert='%s'".printf(idcard.display_name, ta.datetime_added, ta.ca_cert, ta.server_cert));
+        }
+        idcard.set_trust_anchor_from_store(ta);
+
+        logger.trace("install_id_card: Card '%s' has services: '%s'"
+                     .printf(idcard.display_name, idcard.get_services_string("; ")));
+
+        logger.trace(@"Installing IdCard named '$(idcard.display_name)'; ca_cert='$(idcard.trust_anchor.ca_cert)'; server_cert='$(idcard.trust_anchor.server_cert)'");
+
+
+        if (rules_patterns.length == rules_always_confirm.length)
+        {
+            /* workaround Centos vala array property bug: use temp array */
+            Rule[] rules = new Rule[rules_patterns.length];
+         
+            for (int i = 0; i < rules.length; i++)
+            { 
+                rules[i].pattern = rules_patterns[i];
+                rules[i].always_confirm = rules_always_confirm[i];
+            }
+            idcard.rules = rules;
+        }
+
+        ArrayList<IdCard>? old_duplicates = null;
+        var ret = parent_app.add_identity(idcard, (force_flat_file_store != 0), out old_duplicates);
+
+        if (old_duplicates != null) {
+            // Printing to stdout here is ugly behavior; but it's old behavior that
+            // may be expected. (TODO: Do we need to keep this?)
+            foreach (IdCard id_card in old_duplicates) {
+                stdout.printf("removed duplicate id for '%s'\n", id_card.nai);
+            }
+        }
+        return ret;
+    }
+
+
+    public int install_from_file(string file_name)
+    {
+        var webp = new WebProvisioning.Parser(file_name);
+
+        webp.parse();
+        bool result = false;
+        int installed_cards = 0;
+        foreach (IdCard card in webp.cards)
+        {
+            string[] rules_patterns = {};
+            string[] rules_always_confirm = {};
+        
+            if (card.rules.length > 0)
+            {
+                int i = 0;
+                rules_patterns = new string[card.rules.length];
+                rules_always_confirm = new string[card.rules.length];
+                foreach (Rule r in card.rules)
+                {
+                    rules_patterns[i] = r.pattern;
+                    rules_always_confirm[i] = r.always_confirm;
+                    i++;
+                }
+            } 
+
+
+            // prevent a crash by holding the reference to otherwise
+            // unowned array(?)
+
+            // string[] svcs = card.services.to_array();
+            // string[] svcs = card.services.to_array()[:];
+            string[] svcs = new string[card.services.size];
+            for (int i = 0; i < card.services.size; i++) {
+                svcs[i] = card.services[i];
+            }
+
+            logger.trace(@"install_from_file: Adding card with display name '$(card.display_name)'");
+            result = install_id_card(card.display_name,
+                                     card.username,
+                                     card.password,
+                                     card.issuer,
+                                     rules_patterns,
+                                     rules_always_confirm,
+                                     svcs,
+                                     card.trust_anchor.ca_cert,
+                                     card.trust_anchor.subject,
+                                     card.trust_anchor.subject_alt,
+                                     card.trust_anchor.server_cert,
+                                     0);
+            if (result) {
+                installed_cards++;
+            }
+        }
+        return installed_cards;
+    }
+
+    public async bool confirm_ca_certificate(string nai,
+                                             string realm,
+                                             string ca_hash,
+                                             out int confirmed)
+    {
+        logger.trace(@"MoonshotServer.confirm_ca_certificate: nai='$nai'; realm='$realm'; ca_hash='$ca_hash'");
+
+        var request = new TrustAnchorConfirmationRequest(parent_app, nai, realm, ca_hash);
+        request.set_callback((TrustAnchorConfirmationRequest) => confirm_ca_certificate.callback());
+        request.execute();
+        yield;
+
+        confirmed = (request.confirmed ? 1 : 0);
+        logger.trace(@"MoonshotServer.confirm_ca_certificate: confirmed=$confirmed");
+        return true;
+    }
+}
diff --git a/src/moonshot-server-msrpc.vala b/src/moonshot-server-msrpc.vala
new file mode 100644 (file)
index 0000000..b396ac9
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2011-2016, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of JANET(UK) 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 HOLDER 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.
+*/
+
+using Gee;
+
+
+using Rpc;
+using MoonshotRpcInterface;
+
+/* This class must be a singleton, because we use a global RPC
+ * binding handle. I cannot picture a situation where more than
+ * one instance of the same interface would be needed so this
+ * shouldn't be a problem.
+ *
+ * Shutdown is automatically done by the RPC runtime when the
+ * process ends
+ */
+public class MoonshotServer : Object {
+    private static IdentityManagerApp parent_app;
+
+    private static MoonshotServer instance = null;
+
+    public static void start(IdentityManagerApp app)
+    {
+        parent_app = app;
+        Rpc.server_start(MoonshotRpcInterface.spec, "/org/janet/Moonshot", Rpc.Flags.PER_USER);
+    }
+
+    public static MoonshotServer get_instance()
+    {
+        if (instance == null)
+            instance = new MoonshotServer();
+        return instance;
+    }
+
+    [CCode (cname = "moonshot_get_identity_rpc")]
+    public static void get_identity(Rpc.AsyncCall call,
+                                    string nai,
+                                    string password,
+                                    string service,
+                                    ref string nai_out,
+                                    ref string password_out,
+                                    ref string server_certificate_hash,
+                                    ref string ca_certificate,
+                                    ref string subject_name_constraint,
+                                    ref string subject_alt_name_constraint)
+    {
+        logger.trace("(static) get_identity");
+
+        bool result = false;
+
+        var request = new IdentityRequest(parent_app,
+                                          nai,
+                                          password,
+                                          service);
+
+        // Pass execution to the main loop and block the RPC thread
+        request.mutex = new Mutex();
+        request.cond = new Cond();
+        request.set_callback(return_identity_cb);
+
+        request.mutex.lock();
+        Idle.add(request.execute);
+
+        while (request.complete == false)
+            request.cond.wait(request.mutex);
+
+        nai_out = "";
+        password_out = "";
+        server_certificate_hash = "";
+        ca_certificate = "";
+        subject_name_constraint = "";
+        subject_alt_name_constraint = "";
+
+        var id_card = request.id_card;
+
+        if (id_card != null) {
+            // The strings are freed by the RPC runtime
+            nai_out = id_card.nai;
+            password_out = id_card.password;
+            server_certificate_hash = id_card.trust_anchor.server_cert;
+            ca_certificate = id_card.trust_anchor.ca_cert;
+            subject_name_constraint = id_card.trust_anchor.subject;
+            subject_alt_name_constraint = id_card.trust_anchor.subject_alt;
+
+            return_if_fail(nai_out != null);
+            return_if_fail(password_out != null);
+            return_if_fail(server_certificate_hash != null);
+            return_if_fail(ca_certificate != null);
+            return_if_fail(subject_name_constraint != null);
+            return_if_fail(subject_alt_name_constraint != null);
+
+            result = true;
+        }
+
+        // The outputs must be set before this function is called. For this
+        // reason they are 'ref' not 'out' parameters - Vala assigns to the
+        // 'out' parameters only at the end of the function, which is too
+        // late.
+        call.return(&result);
+
+        request.cond.signal();
+        request.mutex.unlock();
+    }
+
+    [CCode (cname = "moonshot_get_default_identity_rpc")]
+    public static void get_default_identity(Rpc.AsyncCall call,
+                                            ref string nai_out,
+                                            ref string password_out,
+                                            ref string server_certificate_hash,
+                                            ref string ca_certificate,
+                                            ref string subject_name_constraint,
+                                            ref string subject_alt_name_constraint)
+    {
+        logger.trace("(static) get_default_identity");
+
+        bool result;
+
+        var request = new IdentityRequest.default(parent_app);
+        request.mutex = new Mutex();
+        request.cond = new Cond();
+        request.set_callback(return_identity_cb);
+
+        request.mutex.lock();
+        Idle.add(request.execute);
+
+        while (request.complete == false)
+            request.cond.wait(request.mutex);
+
+        nai_out = "";
+        password_out = "";
+        server_certificate_hash = "";
+        ca_certificate = "";
+        subject_name_constraint = "";
+        subject_alt_name_constraint = "";
+
+        if (request.id_card != null)
+        {
+            nai_out = request.id_card.nai;
+            password_out = request.id_card.password;
+            server_certificate_hash = "certificate";
+
+            return_if_fail(nai_out != null);
+            return_if_fail(password_out != null);
+            return_if_fail(server_certificate_hash != null);
+            return_if_fail(ca_certificate != null);
+            return_if_fail(subject_name_constraint != null);
+            return_if_fail(subject_alt_name_constraint != null);
+
+            result = true;
+        }
+        else
+        {
+            result = false;
+        }
+
+        call.return(&result);
+
+        request.cond.signal();
+        request.mutex.unlock();
+    }
+
+    // Called from the main loop thread when an identity has
+    // been selected
+    static void return_identity_cb(IdentityRequest request) {
+        // Notify the RPC thread that the request is complete
+        request.mutex.lock();
+        request.cond.signal();
+
+        // Block the main loop until the RPC call has returned
+        // to avoid any races
+        request.cond.wait(request.mutex);
+        request.mutex.unlock();
+    }
+
+    [CCode (cname = "moonshot_install_id_card_rpc")]
+    public static bool install_id_card(string     display_name,
+                                       string     user_name,
+                                       string     password,
+                                       string     realm,
+                                       string[]   rules_patterns,
+                                       string[]   rules_always_confirm,
+                                       string[]   services,
+                                       string     ca_cert,
+                                       string     subject,
+                                       string     subject_alt,
+                                       string     server_cert,
+                                       bool       force_flat_file_store)
+    {
+        logger.trace("(static) install_id_card");
+        IdCard idcard = new IdCard();
+
+        bool success = false;
+        Mutex mutex = new Mutex();
+        Cond cond = new Cond();
+
+        idcard.display_name = display_name;
+        idcard.username = user_name;
+        idcard.password = password;
+        idcard.issuer = realm;
+        idcard.services = services;
+        idcard.trust_anchor.ca_cert = ca_cert;
+        idcard.trust_anchor.subject = subject;
+        idcard.trust_anchor.subject_alt = subject_alt;
+        idcard.trust_anchor.server_cert = server_cert;
+
+        if (rules_patterns.length == rules_always_confirm.length)
+        {
+            idcard.rules = new Rule[rules_patterns.length];
+         
+            for (int i = 0; i < idcard.rules.length; i++)
+            { 
+                idcard.rules[i].pattern = rules_patterns[i];
+                idcard.rules[i].always_confirm = rules_always_confirm[i];
+            }
+        }
+
+        mutex.lock();
+
+        ArrayList<IdCard>? old_duplicates = null;
+        // Defer addition to the main loop thread.
+        Idle.add(() => {
+                mutex.lock();
+                success = parent_app.add_identity(idcard, force_flat_file_store, out old_duplicates);
+                foreach (IdCard id_card in old_duplicates) {
+                    stdout.printf("removing duplicate id for '%s'\n", new_card.nai);
+                }
+                cond.signal();
+                mutex.unlock();
+                return false;
+            });
+
+        cond.wait(mutex);
+        mutex.unlock();
+
+        return success;
+    }
+}
diff --git a/src/moonshot-server.vala b/src/moonshot-server.vala
deleted file mode 100644 (file)
index 74288f7..0000000
+++ /dev/null
@@ -1,477 +0,0 @@
-/*
- * Copyright (c) 2011-2014, JANET(UK)
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. 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.
- *
- * 3. Neither the name of JANET(UK) 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 HOLDER 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.
-*/
-#if IPC_DBUS
-
-[DBus (name = "org.janet.Moonshot")]
-public class MoonshotServer : Object {
-
-    private IdentityManagerApp parent_app;
-
-    public MoonshotServer (IdentityManagerApp app)
-    {
-        this.parent_app = app;
-    }
-
-    public bool show_ui()
-    {
-        if (parent_app.view == null) {
-            return false;
-        }
-        parent_app.show();
-        parent_app.explicitly_launched = true;
-        return true;
-    }
-
-    public async bool get_identity (string nai,
-                                    string password,
-                                    string service,
-                                    out string nai_out,
-                                    out string password_out,
-                                    out string server_certificate_hash,
-                                    out string ca_certificate,
-                                    out string subject_name_constraint,
-                                    out string subject_alt_name_constraint)
-    {
-        var request = new IdentityRequest (parent_app,
-                                           nai,
-                                           password,
-                                           service);
-        request.set_callback ((IdentityRequest) => get_identity.callback());
-        request.execute ();
-        yield;
-
-        nai_out = "";
-        password_out = "";
-        server_certificate_hash = "";
-        ca_certificate = "";
-        subject_name_constraint = "";
-        subject_alt_name_constraint = "";
-
-        var id_card = request.id_card;
-
-        if ((id_card != null) && (id_card.display_name != IdCard.NO_IDENTITY)) {
-            nai_out = id_card.nai;
-            if ((request.password!=null) && (request.password != ""))
-                password_out = request.password;
-            else
-                password_out = id_card.password;
-
-            server_certificate_hash = id_card.trust_anchor.server_cert;
-            ca_certificate = id_card.trust_anchor.ca_cert;
-            subject_name_constraint = id_card.trust_anchor.subject;
-            subject_alt_name_constraint = id_card.trust_anchor.subject_alt;
-
-            if (nai_out == null)
-                nai_out = "";
-            if (password_out == null)
-                password_out = "";
-            if (server_certificate_hash == null)
-                server_certificate_hash = "";
-            if (ca_certificate == null)
-                ca_certificate = "";
-            if (subject_name_constraint == null)
-                subject_name_constraint = "";
-            if (subject_alt_name_constraint == null)
-                subject_alt_name_constraint = "";
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public async bool get_default_identity (out string nai_out,
-                                            out string password_out,
-                                            out string server_certificate_hash,
-                                            out string ca_certificate,
-                                            out string subject_name_constraint,
-                                            out string subject_alt_name_constraint)
-    {
-        var request = new IdentityRequest.default (parent_app);
-        request.set_callback ((IdentityRequest) => get_default_identity.callback());
-        request.execute ();
-        yield;
-
-        nai_out = "";
-        password_out = "";
-        server_certificate_hash = "";
-        ca_certificate = "";
-        subject_name_constraint = "";
-        subject_alt_name_constraint = "";
-
-        if (request.id_card != null)
-        {
-            nai_out = request.id_card.nai;
-            password_out = request.id_card.password;
-
-            server_certificate_hash = request.id_card.trust_anchor.server_cert;
-            ca_certificate = request.id_card.trust_anchor.ca_cert;
-            subject_name_constraint = request.id_card.trust_anchor.subject;
-            subject_alt_name_constraint = request.id_card.trust_anchor.subject_alt;
-
-            if (nai_out == null)
-                nai_out = "";
-            if (password_out == null)
-                password_out = "";
-            if (server_certificate_hash == null)
-                server_certificate_hash = "";
-            if (ca_certificate == null)
-                ca_certificate = "";
-            if (subject_name_constraint == null)
-                subject_name_constraint = "";
-            if (subject_alt_name_constraint == null)
-                subject_alt_name_constraint = "";
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public bool install_id_card (string   display_name,
-                                 string   user_name,
-                                 string   ?password,
-                                 string   ?realm,
-                                 string[] ?rules_patterns,
-                                 string[] ?rules_always_confirm,
-                                 string[] ?services,
-                                 string   ?ca_cert,
-                                 string   ?subject,
-                                 string   ?subject_alt,
-                                 string   ?server_cert,
-                                 int      force_flat_file_store)
-    {
-      IdCard idcard = new IdCard ();
-
-      idcard.display_name = display_name;
-      idcard.username = user_name;
-      idcard.password = password;
-      if ((password != null) && (password != ""))
-        idcard.store_password = true;
-      idcard.issuer = realm;
-      idcard.services = services;
-      idcard.trust_anchor.ca_cert = ca_cert;
-      idcard.trust_anchor.subject = subject;
-      idcard.trust_anchor.subject_alt = subject_alt;
-      idcard.trust_anchor.server_cert = server_cert;
-
-      if (rules_patterns.length == rules_always_confirm.length)
-      {
-        /* workaround Centos vala array property bug: use temp array */
-        Rule[] rules = new Rule[rules_patterns.length];
-         
-        for (int i=0; i<rules.length; i++)
-        { 
-          rules[i].pattern = rules_patterns[i];
-          rules[i].always_confirm = rules_always_confirm[i];
-        }
-        idcard.rules = rules;
-      }
-
-      return parent_app.add_identity (idcard, force_flat_file_store!=0);
-    }
-
-
-    public int install_from_file (string file_name)
-    {
-    var webp = new WebProvisioning.Parser (file_name);
-
-    webp.parse();
-    bool result = false;
-    int installed_cards = 0;
-    foreach (IdCard card in WebProvisioning.cards)
-    {
-      string[] rules_patterns = {};
-      string[] rules_always_confirm = {};
-        
-      if (card.rules.length > 0)
-      {
-        int i = 0;
-        rules_patterns = new string[card.rules.length];
-        rules_always_confirm = new string[card.rules.length];
-        foreach (Rule r in card.rules)
-        {
-          rules_patterns[i] = r.pattern;
-          rules_always_confirm[i] = r.always_confirm;
-          i++;
-        }
-      } 
-
-      result = install_id_card (card.display_name,
-                                card.username,
-                                card.password,
-                                card.issuer,
-                                rules_patterns,
-                                rules_always_confirm,
-                                card.services,
-                                card.trust_anchor.ca_cert,
-                                card.trust_anchor.subject,
-                                card.trust_anchor.subject_alt,
-                                card.trust_anchor.server_cert,
-                                0);
-      if (result) {
-        installed_cards++;
-      }
-    }
-    return installed_cards;
-  }
-}
-
-
-#elif IPC_MSRPC
-
-using Rpc;
-using MoonshotRpcInterface;
-
-/* This class must be a singleton, because we use a global RPC
- * binding handle. I cannot picture a situation where more than
- * one instance of the same interface would be needed so this
- * shouldn't be a problem.
- *
- * Shutdown is automatically done by the RPC runtime when the
- * process ends
- */
-public class MoonshotServer : Object {
-    private static IdentityManagerApp parent_app;
-
-    private static MoonshotServer instance = null;
-
-    public static void start (IdentityManagerApp app)
-    {
-        parent_app = app;
-        Rpc.server_start (MoonshotRpcInterface.spec, "/org/janet/Moonshot", Rpc.Flags.PER_USER);
-    }
-
-    public static MoonshotServer get_instance ()
-    {
-        if (instance == null)
-            instance = new MoonshotServer ();
-        return instance;
-    }
-
-    [CCode (cname = "moonshot_get_identity_rpc")]
-    public static void get_identity (Rpc.AsyncCall call,
-                                     string nai,
-                                     string password,
-                                     string service,
-                                     ref string nai_out,
-                                     ref string password_out,
-                                     ref string server_certificate_hash,
-                                     ref string ca_certificate,
-                                     ref string subject_name_constraint,
-                                     ref string subject_alt_name_constraint)
-    {
-        bool result = false;
-
-        var request = new IdentityRequest (parent_app,
-                                           nai,
-                                           password,
-                                           service);
-
-        // Pass execution to the main loop and block the RPC thread
-        request.mutex = new Mutex ();
-        request.cond = new Cond ();
-        request.set_callback (return_identity_cb);
-
-        request.mutex.lock ();
-        Idle.add (request.execute);
-
-        while (request.complete == false)
-            request.cond.wait (request.mutex);
-
-        nai_out = "";
-        password_out = "";
-        server_certificate_hash = "";
-        ca_certificate = "";
-        subject_name_constraint = "";
-        subject_alt_name_constraint = "";
-
-        var id_card = request.id_card;
-
-        if (id_card != null) {
-            // The strings are freed by the RPC runtime
-            nai_out = id_card.nai;
-            password_out = id_card.password;
-            server_certificate_hash = id_card.trust_anchor.server_cert;
-            ca_certificate = id_card.trust_anchor.ca_cert;
-            subject_name_constraint = id_card.trust_anchor.subject;
-            subject_alt_name_constraint = id_card.trust_anchor.subject_alt;
-
-            return_if_fail (nai_out != null);
-            return_if_fail (password_out != null);
-            return_if_fail (server_certificate_hash != null);
-            return_if_fail (ca_certificate != null);
-            return_if_fail (subject_name_constraint != null);
-            return_if_fail (subject_alt_name_constraint != null);
-
-            result = true;
-        }
-
-        // The outputs must be set before this function is called. For this
-        // reason they are 'ref' not 'out' parameters - Vala assigns to the
-        // 'out' parameters only at the end of the function, which is too
-        // late.
-        call.return (&result);
-
-        request.cond.signal ();
-        request.mutex.unlock ();
-    }
-
-    [CCode (cname = "moonshot_get_default_identity_rpc")]
-    public static void get_default_identity (Rpc.AsyncCall call,
-                                             ref string nai_out,
-                                             ref string password_out,
-                                             ref string server_certificate_hash,
-                                             ref string ca_certificate,
-                                             ref string subject_name_constraint,
-                                             ref string subject_alt_name_constraint)
-    {
-        bool result;
-
-        var request = new IdentityRequest.default (parent_app);
-        request.mutex = new Mutex ();
-        request.cond = new Cond ();
-        request.set_callback (return_identity_cb);
-
-        request.mutex.lock ();
-        Idle.add (request.execute);
-
-        while (request.complete == false)
-            request.cond.wait (request.mutex);
-
-        nai_out = "";
-        password_out = "";
-        server_certificate_hash = "";
-        ca_certificate = "";
-        subject_name_constraint = "";
-        subject_alt_name_constraint = "";
-
-        if (request.id_card != null)
-        {
-            nai_out = request.id_card.nai;
-            password_out = request.id_card.password;
-            server_certificate_hash = "certificate";
-
-            return_if_fail (nai_out != null);
-            return_if_fail (password_out != null);
-            return_if_fail (server_certificate_hash != null);
-            return_if_fail (ca_certificate != null);
-            return_if_fail (subject_name_constraint != null);
-            return_if_fail (subject_alt_name_constraint != null);
-
-            result = true;
-        }
-        else
-        {
-            result = false;
-        }
-
-        call.return (&result);
-
-        request.cond.signal ();
-        request.mutex.unlock ();
-    }
-
-    // Called from the main loop thread when an identity has
-    // been selected
-    static void return_identity_cb (IdentityRequest request) {
-        // Notify the RPC thread that the request is complete
-        request.mutex.lock ();
-        request.cond.signal ();
-
-        // Block the main loop until the RPC call has returned
-        // to avoid any races
-        request.cond.wait (request.mutex);
-        request.mutex.unlock ();
-    }
-
-    [CCode (cname = "moonshot_install_id_card_rpc")]
-    public static bool install_id_card (string     display_name,
-                                        string     user_name,
-                                        string     password,
-                                        string     realm,
-                                        string[]   rules_patterns,
-                                        string[]   rules_always_confirm,
-                                        string[]   services,
-                                        string     ca_cert,
-                                        string     subject,
-                                        string     subject_alt,
-                                        string     server_cert,
-                                        bool       force_flat_file_store)
-    {
-        IdCard idcard = new IdCard ();
-        bool success = false;
-        Mutex mutex = new Mutex();
-        Cond cond = new Cond();
-
-        idcard.display_name = display_name;
-        idcard.username = user_name;
-        idcard.password = password;
-        idcard.issuer = realm;
-        idcard.services = services;
-        idcard.trust_anchor.ca_cert = ca_cert;
-        idcard.trust_anchor.subject = subject;
-        idcard.trust_anchor.subject_alt = subject_alt;
-        idcard.trust_anchor.server_cert = server_cert;
-
-        if (rules_patterns.length == rules_always_confirm.length)
-        {
-            idcard.rules = new Rule[rules_patterns.length];
-         
-            for (int i=0; i<idcard.rules.length; i++)
-            { 
-                idcard.rules[i].pattern = rules_patterns[i];
-                idcard.rules[i].always_confirm = rules_always_confirm[i];
-            }
-        }
-
-        mutex.lock ();
-
-        // Defer addition to the main loop thread.
-        Idle.add (() => {
-            mutex.lock ();
-            success = parent_app.add_identity (idcard, force_flat_file_store);
-            cond.signal ();
-            mutex.unlock ();
-            return false;
-        });
-
-        cond.wait (mutex);
-        mutex.unlock ();
-
-        return success;
-    }
-
-}
-
-
-#endif
diff --git a/src/moonshot-settings.vala b/src/moonshot-settings.vala
new file mode 100644 (file)
index 0000000..4dea622
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2011-2016, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of JANET(UK) 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 HOLDER 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.
+*/
+
+using Gtk;
+
+   
+private MoonshotLogger logger()
+{
+    return get_logger("MoonshotSettings");
+}
+
+static const string KEY_FILE_NAME="moonshot-ui.config";
+
+private KeyFile get_keyfile()
+{
+    KeyFile key_file = new KeyFile();
+    string config_dir = Environment.get_user_config_dir();
+    logger().trace("get_keyfile: config_dir=" + config_dir);
+
+    File dir = File.new_for_path(config_dir);
+    string path = dir.get_child(KEY_FILE_NAME).get_path();
+
+    try {
+        if (key_file.load_from_file(path, KeyFileFlags.NONE))
+            logger().trace("get_keyfile: load_from_file returned successfully");
+        else
+            logger().trace("get_keyfile: load_from_file returned false");            
+    }
+    catch (FileError e) {
+        logger().trace("get_keyfile: FileError: " + e.message);
+    }
+    catch (KeyFileError e) {
+        logger().trace("get_keyfile: KeyFileError: " + e.message);
+    }
+
+    return key_file;
+}
+
+
+private void save_keyfile(KeyFile key_file)
+{
+    string config_dir = Environment.get_user_config_dir();
+    File dest = null;
+
+    // Make the directory if it doesn't already exist; ignore errors.
+       try {
+               File dir = File.new_for_path(config_dir);
+        dest = dir.get_child(KEY_FILE_NAME);
+               dir.make_directory_with_parents();
+       } catch (Error e) {
+        logger().trace("save_keyfile: make_directory_with_parents threw error (this is usually ignorable) : " + e.message);
+       }
+
+    // It would be nice to use key_file.save_to_file, but the binding doesn't exist
+    // in earlier versions of valac
+    // key_file.save_to_file(path.get_path());
+
+    string data = key_file.to_data();
+    try {
+        logger().trace("save_keyfile: saving to file path '%s'".printf(dest.get_path()));
+        // FileOutputStream s = dest.create(FileCreateFlags.REPLACE_DESTINATION | FileCreateFlags.PRIVATE);
+        // var ds = new DataOutputStream(s);
+        // ds.put_string(data);
+        string new_etag;
+        dest.replace_contents(data.data, null, false, FileCreateFlags.REPLACE_DESTINATION | FileCreateFlags.PRIVATE, out new_etag);
+    }
+    catch(Error e) {
+        logger().error("save_keyfile: error when writing to file: " + e.message);
+    }
+
+    // streams close automatically
+}
+
+internal void set_bool_setting(string group_name, string key_name, bool value, KeyFile? key_file=null)
+{
+    KeyFile tmp_key_file = null;
+    if (key_file == null) {
+        // Use tmp_key_file to hold an owned reference (since key_file is unowned)
+        tmp_key_file = get_keyfile();
+        key_file = tmp_key_file;
+    }
+
+    key_file.set_boolean(group_name, key_name, value);
+
+    if (tmp_key_file != null) {
+        // This is a "one-shot" settings update; save it now.
+        save_keyfile(key_file);
+    }
+}
+
+internal bool get_bool_setting(string group_name, string key_name, bool default=false, KeyFile? key_file=null)
+{
+    KeyFile tmp_key_file = null;
+    if (key_file == null) {
+        // Use tmp_key_file to hold an owned reference (since key_file is unowned)
+        tmp_key_file = get_keyfile();
+        key_file = tmp_key_file;
+    }
+
+    if (key_file == null)
+        return default;
+
+    try {
+        if (!key_file.has_key(group_name, key_name))
+        {
+            logger().info(@"get_bool_setting : key file doesn't contain key '$key_name' in group '$group_name'");
+            return default;
+        }
+    }
+    catch(KeyFileError e) {
+        logger().info(@"get_bool_setting : KeyFileError checking if key '$key_name' exists in group '$group_name' (maybe ignorable?) : " + e.message);
+    }
+
+    try {
+        // throws KeyFileError if key is not found
+        return key_file.get_boolean(group_name, key_name);
+    }
+    catch (KeyFileError e) {
+        logger().info("get_bool_setting got KeyFileError (may be ignorable) : " + e.message);
+    }
+    return default;
+}
+
+
+internal void set_string_setting(string group_name, string key_name, string value, KeyFile? key_file=null)
+{
+    KeyFile tmp_key_file = null;
+    if (key_file == null) {
+        // Use tmp_key_file to hold an owned reference (since key_file is unowned)
+        tmp_key_file = get_keyfile();
+        key_file = tmp_key_file;
+    }
+
+    key_file.set_string(group_name, key_name, value);
+    if (tmp_key_file != null) {
+        // This is a "one-shot" settings update; save it now.
+        save_keyfile(key_file);
+    }
+}
+
+internal string get_string_setting(string group_name, string key_name, string default="", KeyFile? key_file=null)
+{
+    KeyFile tmp_key_file = null;
+    if (key_file == null) {
+        // Use tmp_key_file to hold an owned reference (since key_file is unowned)
+        tmp_key_file = get_keyfile();
+        key_file = tmp_key_file;
+    }
+
+    if (key_file == null)
+        return default;
+
+    try {
+        if (!key_file.has_key(group_name, key_name))
+        {
+            logger().info(@"get_string_setting : key file doesn't contain key '$key_name' in group '$group_name'");
+            return default;
+        }
+    }
+    catch(KeyFileError e) {
+        logger().info(@"get_string_setting : KeyFileError checking if key '$key_name' exists in group '$group_name' (maybe ignorable?) : " + e.message);
+    }
+
+    try {
+        // throws KeyFileError if key is not found
+        return key_file.get_string(group_name, key_name);
+    }
+    catch (KeyFileError e) {
+        logger().info("get_string_setting got KeyFileError (may be ignorable) : " + e.message);
+    }
+    return default;
+}
diff --git a/src/moonshot-trust-anchor-dialog.vala b/src/moonshot-trust-anchor-dialog.vala
new file mode 100644 (file)
index 0000000..04fc1e0
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2011-2016, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of JANET(UK) 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 HOLDER 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.
+*/
+using Gtk;
+
+public delegate void TrustAnchorConfirmationCallback(TrustAnchorConfirmationRequest request);
+
+public class TrustAnchorConfirmationRequest : GLib.Object {
+    static MoonshotLogger logger = get_logger("TrustAnchorConfirmationRequest");
+
+    IdentityManagerApp parent_app;
+    string userid;
+    string realm;
+    string fingerprint;
+    public bool confirmed = false;
+
+    TrustAnchorConfirmationCallback callback = null;
+
+    public TrustAnchorConfirmationRequest(IdentityManagerApp parent_app,
+                                          string userid,
+                                          string realm,
+                                          string fingerprint)
+    {
+        this.parent_app = parent_app;
+        this.userid = userid;
+        this.realm = realm;
+        this.fingerprint = fingerprint;
+    }
+
+    public void set_callback(owned TrustAnchorConfirmationCallback cb)
+    {
+//        #if VALA_0_12
+            this.callback = ((owned) cb);
+//        #else
+//            this.callback = ((IdCard) => cb(IdCard));
+//        #endif
+    }
+
+    public bool execute() {
+
+        string nai = userid + "@" + realm;
+        IdCard? card = parent_app.model.find_id_card(nai, parent_app.use_flat_file_store);
+        if (card == null) {
+            logger.warn(@"execute: Could not find ID card for NAI $nai; returning false.");
+            return_confirmation(false);
+            return false;
+        }
+        
+        if (!(card.trust_anchor.is_empty() || card.trust_anchor.get_anchor_type() == TrustAnchor.TrustAnchorType.SERVER_CERT)) {
+            logger.warn(@"execute: Trust anchor type for NAI $nai is not empty or SERVER_CERT; returning true.");
+            return_confirmation(true);
+            return false;
+        }
+
+        logger.trace("execute: expected cert='%s'; fingerprint='%s'".printf(card.trust_anchor.server_cert, fingerprint));
+        if (card.trust_anchor.server_cert == fingerprint) {
+            logger.trace(@"execute: Fingerprint for $nai matches stored value; returning true.");
+            return_confirmation(true);
+            return false;
+        }
+
+        if (parent_app.headless) {
+            logger.trace(@"execute: Running in headless mode; returning false.");
+            return_confirmation(false);
+            return false;
+        }
+
+        var dialog = new TrustAnchorDialog(card, userid, realm, fingerprint);
+        var response = dialog.run();
+        dialog.destroy();
+        bool is_confirmed = (response == ResponseType.OK);
+
+        if (is_confirmed) {
+            logger.trace(@"execute: Fingerprint confirmed; updating stored value.");
+
+            card.trust_anchor.update_server_fingerprint(fingerprint);
+            parent_app.model.update_card(card);
+        }            
+
+        return_confirmation(is_confirmed);
+
+        /* This function works as a GSourceFunc, so it can be passed to
+         * the main loop from other threads
+         */
+        return false;
+    }
+
+    private void return_confirmation(bool confirmed) {
+        return_if_fail(callback != null);
+
+        this.confirmed = confirmed;
+        logger.trace(@"return_confirmation: confirmed=$confirmed");
+
+        // Send back the confirmation (we can't directly run the
+        // callback because we may be being called from a 'yield')
+        GLib.Idle.add(
+            () => {
+                logger.trace("return_confirmation[Idle handler]: invoking callback");
+                callback(this);
+                return false;
+            }
+            );
+    }
+}
+
+
+
+class TrustAnchorDialog : Dialog
+{
+    private static Gdk.Color white = make_color(65535, 65535, 65535);
+
+    public bool complete = false;
+
+    public TrustAnchorDialog(IdCard card,
+                             string userid,
+                             string realm,
+                             string fingerprint)
+    {
+        string server_ta_label_text = _("Server’s trust anchor certificate (SHA-256 fingerprint):");
+
+        this.set_title(_("Trust Anchor"));
+        this.set_modal(true);
+//        this.set_transient_for(parent);
+        set_bg_color(this);
+
+        this.add_buttons(_("Cancel"), ResponseType.CANCEL,
+                         _("Confirm"), ResponseType.OK);
+
+        this.set_default_response(ResponseType.CANCEL);
+
+        var content_area = this.get_content_area();
+        ((Box) content_area).set_spacing(12);
+        set_bg_color(content_area);
+
+        Label dialog_label = new Label("");
+        dialog_label.set_alignment(0, 0);
+
+        string label_markup;
+        if (card.trust_anchor.server_cert == "") {
+            label_markup = "<span font-weight='heavy'>" 
+            + _("You are using this identity for the first time with the following trust anchor:") + "</span>";
+        }
+        else {
+            // The server's fingerprint isn't what we're expecting this server to provide.
+            label_markup = "<span font-weight='heavy'>" +
+            _("WARNING: The certificate we received for the authentication server for %s").printf(card.issuer)
+            + _(" is different than expected.  Either the server certificate has changed, or an")
+            + _(" attack may be underway.  If you proceed to the wrong server, your login credentials may be compromised.")
+            + "</span>";
+        }
+
+        dialog_label.set_markup(label_markup);
+        dialog_label.set_line_wrap(true);
+        dialog_label.set_width_chars(60);
+                                                   
+        var user_label = new Label(_("Username: ") + userid);
+        user_label.set_alignment(0, 0.5f);
+
+        var realm_label = new Label(_("Realm: ") + realm);
+        realm_label.set_alignment(0, 0.5f);
+
+        string confirm_text = _("\nPlease check with your realm administrator for the correct fingerprint")
+        + _(" for your authentication server.  If it matches the above fingerprint,")
+        + _(" confirm the change.  If not, then cancel.");
+
+        Label confirm_label = new Label(confirm_text);
+        confirm_label.set_alignment(0, 0.5f);
+        confirm_label.set_line_wrap(true);
+        confirm_label.set_width_chars(60);
+
+        var trust_anchor_display = make_ta_fingerprint_widget(fingerprint, server_ta_label_text);
+
+        var vbox = new VBox(false, 0);
+        vbox.set_border_width(6);
+        vbox.pack_start(dialog_label, true, true, 12);
+        vbox.pack_start(user_label, true, true, 2);
+        vbox.pack_start(realm_label, true, true, 2);
+        vbox.pack_start(trust_anchor_display, true, true, 0);
+        vbox.pack_start(confirm_label, true, true, 12);
+
+        ((Container) content_area).add(vbox);
+
+        this.set_border_width(6);
+        this.set_resizable(false);
+
+        this.response.connect(on_response);
+
+        this.show_all();
+    }
+
+    private void on_response(Dialog source, int response_id)
+    {
+        switch (response_id) {
+        case ResponseType.OK:
+            complete = true;
+            break;
+        case ResponseType.CANCEL:
+            complete = true;
+            break;
+        }
+    }
+}
index 72df968..7652469 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
 */
+
+using Gtk;
+using Pango;
+
 #if OS_WIN32
-extern string? g_win32_get_package_installation_directory_of_module (void *module);
+extern string? g_win32_get_package_installation_directory_of_module(void *module);
 #endif
 
-public Gdk.Pixbuf? find_icon_sized (string name, Gtk.IconSize icon_size)
+public Gdk.Pixbuf? find_icon_sized(string name, Gtk.IconSize icon_size)
 {
     int width, height;
-    Gtk.icon_size_lookup (icon_size, out width, out height);
-    return find_icon (name, width);
+    Gtk.icon_size_lookup(icon_size, out width, out height);
+    return find_icon(name, width);
 }
 
 /* Portability hack: making Gtk icon themes work on Windows is
@@ -52,27 +56,27 @@ public Gdk.Pixbuf? get_pixbuf(IdCard id)
     return find_icon("avatar-default", 48);
 }
 
-public Gdk.Pixbuf? find_icon (string name, int size)
+public Gdk.Pixbuf? find_icon(string name, int size)
 {
     if (!gtk_available)
         return null;
     try
     {
-#if OS_WIN32
-        string? base_path = g_win32_get_package_installation_directory_of_module (null);
+        #if OS_WIN32
+        string? base_path = g_win32_get_package_installation_directory_of_module(null);
 
         // Hack to allow running within the source tree
-        int last_dir_index = base_path.last_index_of_char ('\\');
-        if (base_path.substring (last_dir_index) == "\\.libs" || base_path.substring (last_dir_index) == "src")
+        int last_dir_index = base_path.last_index_of_char('\\');
+        if (base_path.substring(last_dir_index) == "\\.libs" || base_path.substring(last_dir_index) == "src")
             base_path = base_path.slice(0, last_dir_index);
 
-        string? filename = Path.build_filename (base_path, "share", "icons", "%s.png".printf (name));
-        return new Gdk.Pixbuf.from_file_at_size (filename, size, size);
+        string? filename = Path.build_filename(base_path, "share", "icons", "%s.png".printf(name));
+        return new Gdk.Pixbuf.from_file_at_size(filename, size, size);
 
-#else
-        var icon_theme = Gtk.IconTheme.get_default ();
-        return icon_theme.load_icon (name, size, Gtk.IconLookupFlags.FORCE_SIZE);
-#endif
+        #else
+        var icon_theme = Gtk.IconTheme.get_default();
+        return icon_theme.load_icon(name, size, Gtk.IconLookupFlags.FORCE_SIZE);
+        #endif
     }
     catch (Error e)
     {
@@ -100,3 +104,103 @@ public bool UserForcesFlatFileStore()
     }
     return false;
 }
+
+internal Gdk.Color make_color(uint16 red, uint16 green, uint16 blue)
+{
+    Gdk.Color color = Gdk.Color();
+    color.red = red;
+    color.green = green;
+    color.blue = blue;
+
+    return color;
+}
+
+internal void set_atk_relation(Widget widget, Widget target_widget, Atk.RelationType relationship)
+{
+    var atk_widget = widget.get_accessible();
+    var atk_target_widget = target_widget.get_accessible();
+
+    atk_widget.add_relationship(relationship, atk_target_widget);
+}
+
+
+internal Widget make_ta_fingerprint_widget(string server_cert, string? label_text = null)
+{
+    var fingerprint_label = new Label(label_text ?? _("SHA-256 fingerprint:"));
+    fingerprint_label.set_alignment(0, 0.5f);
+
+    var fingerprint = new TextView();
+    var fontdesc = FontDescription.from_string("monospace 10");
+    fingerprint.modify_font(fontdesc);
+    fingerprint.set_editable(false);
+    fingerprint.set_left_margin(3);
+    var buffer = fingerprint.get_buffer();
+    buffer.set_text(colonize(server_cert, 16), -1);
+    fingerprint.wrap_mode = Gtk.WrapMode.WORD_CHAR;
+
+    set_atk_relation(fingerprint_label, fingerprint, Atk.RelationType.LABEL_FOR);
+
+    var fingerprint_width_constraint = new ScrolledWindow(null, null);
+    fingerprint_width_constraint.set_policy(PolicyType.NEVER, PolicyType.NEVER);
+    fingerprint_width_constraint.set_shadow_type(ShadowType.IN);
+    fingerprint_width_constraint.set_size_request(360, 60);
+    fingerprint_width_constraint.add_with_viewport(fingerprint);
+
+    var vbox = new VBox(false, 0);
+    vbox.pack_start(fingerprint_label, true, true, 2);
+    vbox.pack_start(fingerprint_width_constraint, true, true, 2);
+    return vbox;
+}
+
+    // Yeah, it doesn't mean "colonize" the way you might think... :-)
+internal static string colonize(string input, int bytes_per_line) {
+    return_if_fail(input.length % 2 == 0);
+
+    string result = "";
+    int i = 0;
+    int line_bytes = 0;
+    while (i < input.length) {
+        if (line_bytes == bytes_per_line) {
+            result += "\n";
+            line_bytes = 0;
+        }
+        else if (i > 0) {
+            result += ":";
+        }
+        result += input[i : i + 2];
+        i += 2;
+        line_bytes++;
+    }
+    return result;
+}
+
+internal static void clear_password_entry(Entry entry) {
+    
+    // Overwrite the entry with random data
+    var len = entry.get_text().length;
+    var random_chars = new char[len + 1];
+    for (int i = 0; i < len; i++) {
+        random_chars[i] = (char) Random.int_range(40, 127);
+    }
+    random_chars[len] = 0;
+    string r = (string) random_chars;
+    var buf = entry.get_buffer();
+    buf.set_text(r.data);
+
+    // Now delete the data
+    buf.delete_text(0, len);
+}
+
+static Gdk.Color white;
+static void set_bg_color(Widget w)
+{
+#if OS_WIN32
+
+    if (white == null) {
+        white = make_color(65535, 65535, 65535);
+    }
+
+    w.modify_bg(StateType.NORMAL, white);
+
+#endif
+}
diff --git a/src/moonshot-warning-dialog.vala b/src/moonshot-warning-dialog.vala
new file mode 100644 (file)
index 0000000..ad147bd
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011-2016, JANET(UK)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * 3. Neither the name of JANET(UK) 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 HOLDER 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.
+*/
+using Gtk;
+
+static const string GROUP_NAME="WarningDialogs";
+
+// MessageDialog doesn't allow subclassing, so we merely wrap the
+// constructor for it the dialog, and then run it, returning the result.
+class WarningDialog 
+{
+    private static MoonshotLogger _logger = null;
+    private static MoonshotLogger logger()
+        {
+            if (_logger == null) {
+                _logger = get_logger("WarningDialog");
+            }
+            return _logger;
+        }
+
+    public static bool confirm(Window parent, string message, string dialog_name)
+    {
+
+        if (get_bool_setting(GROUP_NAME, dialog_name, false))
+        {
+            logger().trace(@"confirm: Settings group $GROUP_NAME has 'true' for key $dialog_name; skipping dialog and returning true.");
+            return true;
+        }
+
+        Gdk.Color white = make_color(65535, 65535, 65535);
+
+        MessageDialog dialog = new Gtk.MessageDialog(parent,
+                                                     Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                                     Gtk.MessageType.WARNING,
+                                                     Gtk.ButtonsType.YES_NO,
+                                                     "");
+
+        var content_area = dialog.get_content_area();
+        CheckButton remember_checkbutton = null;
+
+        if (dialog_name != null && dialog_name != "")
+        {
+            remember_checkbutton = new CheckButton.with_label(_("Do not show this message again"));
+            // remember_checkbutton.set_focus_on_click(false);
+            // remember_checkbutton.set_can_focus(false);
+            // remember_checkbutton.has_focus = false;
+            remember_checkbutton.set_receives_default(false);
+            Container action_area = (Container) dialog.get_action_area();
+
+            Button yes_button = (Button) dialog.get_widget_for_response(ResponseType.YES);
+            yes_button.grab_default();
+            yes_button.grab_focus();
+
+// Not sure if 0.26 is the minimum for MessageDialog.get_message_area. 0.16 sure isn't :-(
+#if VALA_0_26
+            var message_area = dialog.get_message_area();
+            ((Box)message_area).pack_start(remember_checkbutton, false, false, 12);
+#else
+            HBox hbox = new HBox(false, 0);
+            hbox.pack_start(new HBox(false, 0), true, true, 20);
+            hbox.pack_start(remember_checkbutton, false, false, 12);
+            ((Box)content_area).pack_start(hbox, true, true, 12);
+#endif
+        }
+
+        // dialog.set_modal(true);
+        dialog.set_title(_("Warning"));
+        set_bg_color(dialog);
+
+        // ((Box) content_area).set_spacing(12);
+        set_bg_color(content_area);
+
+        content_area.show_all();
+
+        dialog.set_markup(message);
+
+        var ret = dialog.run();
+
+        if (ret == Gtk.ResponseType.YES && remember_checkbutton != null && remember_checkbutton.active)
+        {
+            set_bool_setting(GROUP_NAME, dialog_name, true);
+        }
+
+        dialog.destroy();
+        return (ret == Gtk.ResponseType.YES);
+    }
+}
index 1fce688..de52aff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2014, JANET(UK)
+ * Copyright (c) 2011-2016, JANET(UK)
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,90 +33,103 @@ using Moonshot;
 
 namespace WebProvisioning
 { 
+    private MoonshotLogger logger;
 
-
-  public static int main (string[] args)
-  {
-    int arg_index = -1;
-    int force_flat_file_store = 0;
-    bool bad_switch = false;
-    for (arg_index = 1; arg_index < args.length; arg_index++) {
-      string arg = args[arg_index];
-      unichar c = arg.get_char();
-      if (c=='-') {
-          arg = arg.next_char();
-          c = arg.get_char();
-          switch (c) {
-            case 'f':
-              force_flat_file_store = 1;
-              break;
-            default:
-              bad_switch = true;
-              break;
-          }
-      } else {
-        break; // arg is not a switch; presume it's the file
-      }
-    }
-    if (bad_switch || (arg_index != args.length - 1))
-    {
-      stdout.printf (_("Usage %s [-f] WEB_PROVISIONING_FILE\n -f: add identities to flat file store.\n"), args[0]);
-      return -1;
-    }
-    string webp_file = args[arg_index];
-    
-    if (!FileUtils.test (webp_file, FileTest.EXISTS | FileTest.IS_REGULAR))
+    public static int main(string[] args)
     {
-      stdout.printf (_("%s does not exist\n"), webp_file);
-      return -1;
-    }
-    
-    var webp = new Parser (webp_file);
-    webp.parse();
+        logger = new MoonshotLogger("WebProvisioning (WebpParser)");
+
+        int arg_index = -1;
+        int force_flat_file_store = 0;
+        bool bad_switch = false;
+        for (arg_index = 1; arg_index < args.length; arg_index++) {
+            string arg = args[arg_index];
+            unichar c = arg.get_char();
+            if (c == '-') {
+                arg = arg.next_char();
+                c = arg.get_char();
+                switch (c) {
+                case 'f':
+                    force_flat_file_store = 1;
+                    break;
+                default:
+                    bad_switch = true;
+                    break;
+                }
+            } else {
+                break; // arg is not a switch; presume it's the file
+            }
+        }
+        if (bad_switch || (arg_index != args.length - 1))
+        {
+            stdout.printf(_("Usage %s [-f] WEB_PROVISIONING_FILE\n -f: add identities to flat file store.\n"), args[0]);
+            return -1;
+        }
+        string webp_file = args[arg_index];
     
-    foreach (IdCard card in cards)
-    {
-      Moonshot.Error error;
-      string[] rules_patterns = {};
-      string[] rules_always_confirm = {};
-        
-      /* use temp arrays to workaround centos array property bug */
-      var rules = card.rules;
-      var services = card.services;
-      if (rules.length > 0)
-      {
-        int i = 0;
-        rules_patterns = new string[rules.length];
-        rules_always_confirm = new string[rules.length];
-        foreach (Rule r in rules)
+        if (!FileUtils.test(webp_file, FileTest.EXISTS | FileTest.IS_REGULAR))
         {
-          rules_patterns[i] = r.pattern;
-          rules_always_confirm[i] = r.always_confirm;
-          i++;
+            stdout.printf(_("%s does not exist\n"), webp_file);
+            return -1;
         }
-      }
+    
+        var webp = new Parser(webp_file);
+        webp.parse();
+        logger.trace(@"Have $(webp.cards.length) IdCards");
+        foreach (IdCard card in webp.cards)
+        {
 
-      Moonshot.install_id_card (card.display_name,
-                                card.username,
-                                card.password,
-                                card.issuer,
-                                rules_patterns,
-                                rules_always_confirm,
-                                services,
-                                card.trust_anchor.ca_cert,
-                                card.trust_anchor.subject,
-                                card.trust_anchor.subject_alt,
-                                card.trust_anchor.server_cert,
-                                force_flat_file_store,
-                                out error);
+            if (card == null) {
+                logger.trace(@"Skipping null IdCard");
+                continue;
+            }
 
-      if (error != null)
-      {
-        stderr.printf ("Error: %s", error.message);
-        continue;
-      }
-    }
+            Moonshot.Error error;
+            string[] rules_patterns = {};
+            string[] rules_always_confirm = {};
+        
+            /* use temp arrays to workaround centos array property bug */
+            var rules = card.rules;
+            string[] svcs = new string[card.services.size];
+            for (int i = 0; i < card.services.size; i++) {
+                svcs[i] = card.services[i];
+            }
+
+            if (rules.length > 0)
+            {
+                int i = 0;
+                rules_patterns = new string[rules.length];
+                rules_always_confirm = new string[rules.length];
+                foreach (Rule r in rules)
+                {
+                    rules_patterns[i] = r.pattern;
+                    rules_always_confirm[i] = r.always_confirm;
+                    i++;
+                }
+            }
+
+            logger.trace(@"Installing IdCard named '$(card.display_name)'");
+            Moonshot.install_id_card(card.display_name,
+                                     card.username,
+                                     card.password,
+                                     card.issuer,
+                                     rules_patterns,
+                                     rules_always_confirm,
+                                     svcs,
+                                     card.trust_anchor.ca_cert,
+                                     card.trust_anchor.subject,
+                                     card.trust_anchor.subject_alt,
+                                     card.trust_anchor.server_cert,
+                                     force_flat_file_store,
+                                     out error);
+
+            if (error != null)
+            {
+                stderr.printf("Error: %s", error.message);
+                continue;
+            }
+        }
     
-    return 0;
-  }
+        return 0;
+    }
 }
diff --git a/webprovisioning/blank-test.msht b/webprovisioning/blank-test.msht
new file mode 100644 (file)
index 0000000..5287550
--- /dev/null
@@ -0,0 +1,24 @@
+<!-- See https://bugs.launchpad.net/moonshot-ui/+bug/1430980, including comments -->
+
+<identities>
+
+  <identity>
+    <display-name>Not No Identity</display-name>
+    <user>someone</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+      <service>something/painless-security.com</service>
+      <service>somethingelse/painless-security.com</service>
+    </services>
+  </identity>
+
+  <identity>
+    <display-name>No Identity</display-name>
+    <services>
+      <service>a_service/painless-security.com</service>
+      <service>another_service/painless-security.com</service>
+    </services>
+  </identity>
+
+</identities> 
diff --git a/webprovisioning/cert-test.msht b/webprovisioning/cert-test.msht
new file mode 100644 (file)
index 0000000..ab56e5f
--- /dev/null
@@ -0,0 +1,120 @@
+<identities>
+    <identity>
+    <display-name>No Trust Anchor</display-name>
+    <user>user5</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+  </identity>
+
+  <identity>
+    <display-name>Bad CA Certificate</display-name>
+    <user>user1</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+    <selection-rules>
+    </selection-rules>
+    <trust-anchor>
+     <!-- PEM encoded, minus header and footer -->
+      <ca-cert>MIIE5DCCA8ygAwIBAgIJAOz4PYh7hLBrMA0GCSqGSIb3DQEBBQUAMIGTMQswCQYD
+VQQGEwJGUjEPMA0GA1UECBMGUmFkaXVzMRIwEAYDVQQHEwlTb21ld2hlcmUxFTAT
+BgNVBAoTDEV4YW1wbGUgSW5jLjEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBs
+ZS5vcmcxJjAkBgNVBAMTHUV4YW1wbGUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4X
+DTE2MTAwNDE3MDYwMFoXDTE2MTIwMzE3MDYwMFowgZMxCzAJBgNVBAYTAkZSMQ8w
+DQYDVQQIEwZSYWRpdXMxEjAQBgNVBAcTCVNvbWV3aGVyZTEVMBMGA1UEChMMRXhh
+bXBsZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLm9yZzEmMCQG
+A1UEAxMdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQC3wTX5gGxa/Ge1RN4ZDx67WIeFxmKv6ZkS0c1p
+H2BS7RDGmBrWMG+RMX/kUdSNL1tarnbHknPYzKSdoTsM7bWLQpj1fV8nI4ZAF4Tp
+QG8jQHFmpTfHPjDv+E6wEfilyfXRDpym8ITQfLXTn85zvK82F+153Aqh+BrCyOvJ
+gANAoulArphg0UH6eyBuD+dezqXsinIMXfXZheTXi/TL3oSGjYkwF//WZUpmK5Kx
+w1NupFMjjCJzWf0MtDgG4Gl83JJMzoy/pSKvzWeglvaI8tt64iUkbKNstJT9C0G2
+y+fVM9aVeRAQ+1O1LRaKgqhQMTrs/LPQNUapRKGvkwXlCR6lAgMBAAGjggE3MIIB
+MzAdBgNVHQ4EFgQUMin4EfnT51ecs4f1KjpamdjQUDcwgcgGA1UdIwSBwDCBvYAU
+Min4EfnT51ecs4f1KjpamdjQUDehgZmkgZYwgZMxCzAJBgNVBAYTAkZSMQ8wDQYD
+VQQIEwZSYWRpdXMxEjAQBgNVBAcTCVNvbWV3aGVyZTEVMBMGA1UEChMMRXhhbXBs
+ZSBJbmMuMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLm9yZzEmMCQGA1UE
+AxMdRXhhbXBsZSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCCQDs+D2Ie4SwazAPBgNV
+HRMBAf8EBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly93d3cuZXhhbXBs
+ZS5vcmcvZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcNAQEFBQADggEBAHzr/erMvZ76
+7FHpf5H3G/TL57k5POnDlTnolXmQdK2eaq0xLhaPuilvNa3txGGI0iBJAD20K5ss
+2o7ULHaAeuNAZ5+zuRx2xZrtLV3FQkugQZb70K/lECf3uCX4S/SqTeOo5VfPjaTM
+6MMgU9Tmvo9a1q7xHzm2yEqzhCbP7dZ4BmUPw9QIkqbirlcQ2GgxOah5m94e2ETf
+4SOBwM+5Lg+CAaCoIC0gpX2R3H+n4edslmiCoyU1r/Q6RQXiyWPdI6jjln08Jdt5
+0/rSpmhObSL0L7/z53+ka7jqW1ZxizVKYJiEIH9Y9Aw/vgf5nhnTgfTPfyPESyCO
+fi186fRC+Zs=</ca-cert>
+      <subject>Painless Security Server Certificate</subject>
+      <!-- Or alternatively -->
+      <server-cert></server-cert>
+    </trust-anchor>
+  </identity>
+  <identity>
+    <display-name>Good CA Certificate</display-name>
+    <user>user2</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+    <selection-rules>
+    </selection-rules>
+    <trust-anchor>
+      <ca-cert>
+      <!-- DER format, base64-encoded -->
+MIIE9jCCA96gAwIBAgIJANI5K4+KXvQyMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYD
+VQQGEwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMR
+UGFpbmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFp
+bmxlc3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwg
+SW5jLjAeFw0xNjA4MzAxOTU4MjlaFw0xOTEyMTMxOTU4MjlaMIGaMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMRUGFp
+bmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFpbmxl
+c3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwgSW5j
+LjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ6tW6x+aO4n7VJu5W55
+DeNKn7+89oaaTgSRs6jg3C+RpTmXugPonh9+YRbuWpSNeru2eWGFNYFs01OjaDUw
+CxPcFDgF3xP/wAyCsI7WUgmkz8991PUGo9RxVbkDxMePHNaLGQVNz/+EJgK/Ycfy
+jYvenb/BGvcBmMftk2HsHio65ZsHsGMfW2Wcg/ehvKeDRZ3WR1ujhkzIFWdgdH3E
+u/yI2pHEfxQQ3PuYcQz43YZyIwhwzwnQG8qTK2jWkMF+wzRKRYfLdRD8nUUingvu
+IbngXLs71JqQHmbXzw1WTJClXtfF6R2VZuZ6PT8ZK1bDFPvTgnkUcAk70H+VnDM5
+K48CAwEAAaOCATswggE3MB0GA1UdDgQWBBQoFRKLJrZvkNmqvw8DNuTLPyru/DCB
+zwYDVR0jBIHHMIHEgBQoFRKLJrZvkNmqvw8DNuTLPyru/KGBoKSBnTCBmjELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAk1BMQ8wDQYDVQQHEwZNYWxkZW4xGjAYBgNVBAoT
+EVBhaW5sZXNzIFNlY3VyaXR5MS8wLQYJKoZIhvcNAQkBFiBwb3N0bWFzdGVyQHBh
+aW5sZXNzLXNlY3VyaXR5LmNvbTEgMB4GA1UEAxMXUGFpbmxlc3MgU2VjdXJpdHks
+IEluYy6CCQDSOSuPil70MjAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeG
+JWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcN
+AQEFBQADggEBAIN38UOXvwd89+a89V+/rjeN8JfpcjafLf0c2I3nex9OxBWji5bf
+cWTNfm1t9GpS4HhVT2tl5xdxyW5UrE9Q+oFadN0LxtRPbGU+Gvt4pVo8Pst6/2P8
+PA3/OA1UchIpZR6EWQQsws4esNLLwDbj48MkQdVCjpp1cVpFVmJUzYYFD9h9EMK2
+kxpGf5wfp9LI2A5/qACNQPBDfRsR+dcNBsBbmD1LulqputUPuKPXnVbHWL28VZUY
+PITHl2Ndbmk6znSu7ILef3CGyeXqTTj+Jo+5AQz3sneko6oMn8PqfRj1h0uUyykT
+lavp3iTNstQs/rdqdI+lPYMokDKXRSD3pK8=
+</ca-cert>
+      <subject>Painless Security Server Certificate</subject>
+    </trust-anchor>
+  </identity>
+    <identity>
+    <display-name>Good Fingerprint</display-name>
+    <user>user3</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+    <trust-anchor>
+      <server-cert>F2FCC5FAD4CCB7A7236A8AEEF5E94E0C0FB27BEC29DE0AE03C5B455D08D4DE77</server-cert>
+    </trust-anchor>
+  </identity>
+    <identity>
+    <display-name>Bad Fingerprint</display-name>
+    <user>user4</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+    <trust-anchor>
+      <server-cert>4242424242424242424242424242424242424242424242424242424242424242</server-cert>
+    </trust-anchor>
+  </identity>
+
+</identities> 
index 2c11172..e5279b9 100644 (file)
@@ -1,12 +1,21 @@
 <identities>
+    <identity>
+    <display-name>A Simple Card</display-name>
+    <user>user5</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+    </services>
+  </identity>
+
   <identity>
     <display-name>Unique Name</display-name>
     <user>user1</user>
     <password></password>
-    <realm>foo.baz</realm>
+    <realm>painless-security.com</realm>
     <services>
-      <service>irc@jabber.project-moonshot.org</service>
-      <service>xmpp@jabber.project-moonshot.org</service>
+      <service>irc/painless-security.com</service>
+      <service>xmpp/painless-security.com</service>
     </services>
     <selection-rules>
       <rule>
         <always-confirm>true</always-confirm>
       </rule>
       <rule>
-        <pattern>imap@*moonshot.org</pattern>
+        <pattern>imap/*moonshot.org</pattern>
         <always-confirm>false</always-confirm>
       </rule>
     </selection-rules>
     <trust-anchor>
-      <ca-cert>ABCDEFGHIJKLMNOPQRSTUVWXYZ123455678910</ca-cert>
-      <subject>Foo</subject>
-      <subject-alt>Bar</subject-alt>
+     <!-- PEM encoded, minus header and footer -->
+      <ca-cert>MIIE9jCCA96gAwIBAgIJAJ6SVDCP6o2nMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYD
+VQQGEwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMR
+UGFpbmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFp
+bmxlc3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwg
+SW5jLjAeFw0xNjA4MDExNjIxMDVaFw0xOTExMTQxNjIxMDVaMIGaMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMRUGFp
+bmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFpbmxl
+c3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwgSW5j
+LjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKPiSkw1y6zMJFjnoPjd
+5Bh9EA1NhQcoNxJAtgYEJtpH9a2tfjnXXncXpbIMIfMgv2VKRAxvKb+knCfSCRtU
+PM9i998+ZhJY9o6SSFomlMvdaClauPvBhQvQMmJmp1WINgMUHPpzsGlj04kkl7jw
+iK/oDxp1becikKc10Gr9W03aEJtOaiSqC45zeIgnz9GoQ2tJvz2DDBcddaaT1mSV
+n/lk4ahPC4XaJ08Jn1L6XkVVyDGD38Rwg7r1SFI7ByBFvvQh93Fa48Z7ik0I8s48
+U1euHak2gSJ4zfzLndvGy05qMjhRTlxQu+Rt1g7CS3CLcJqqYzWNrEJWpD8Wn7iA
+MIUCAwEAAaOCATswggE3MB0GA1UdDgQWBBR1qlvY7r2DqhHu5s+sCUPeqBcQuzCB
+zwYDVR0jBIHHMIHEgBR1qlvY7r2DqhHu5s+sCUPeqBcQu6GBoKSBnTCBmjELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAk1BMQ8wDQYDVQQHEwZNYWxkZW4xGjAYBgNVBAoT
+EVBhaW5sZXNzIFNlY3VyaXR5MS8wLQYJKoZIhvcNAQkBFiBwb3N0bWFzdGVyQHBh
+aW5sZXNzLXNlY3VyaXR5LmNvbTEgMB4GA1UEAxMXUGFpbmxlc3MgU2VjdXJpdHks
+IEluYy6CCQCeklQwj+qNpzAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeG
+JWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcN
+AQEFBQADggEBAB6J5Zxvq96SdIsfEajqU+pANBiA2VTZCpxfIMAKz8KfyzWzFvCM
+8epvYDliyOjw1zR9cYxhQqOcbPHrjLXheVvCePd3jCUOv+tt1Nw2gS2DiMuq37DO
+BZOTlPJ3m2NnvJVO3NjB2I+Pk9v3YlG6mkiVc9dNWgO20SqT2Y+KvHqA5Of8Cb/s
+uIBftctvGpIyEnqSmU7KB0nhIWe65Bsu60hjHHfX1qhJE7qGKbqNaHujssQ/SBXJ
+g7HUhtywv8z3TFoYW0MoBpKGM2Ojc9kQ8f0rYvUKTiD1UfjQoll/Io5xwKy7FXtn
+musuCxXeWkqDtw0clWg6vkf5Tb9v/JQ2PW0=</ca-cert>
+      <subject>Painless Security Server Certificate</subject>
       <!-- Or alternatively -->
-      <server-cert>ABCDEFGHIJKLMNOPQRSTUVWXYZ123455678910</server-cert>
+      <server-cert></server-cert>
     </trust-anchor>
   </identity>
   <identity>
     <display-name>Another Unique Name</display-name>
     <user>user2</user>
     <password></password>
-    <realm>foo.bar</realm>
+    <realm>painless-security.com</realm>
     <services>
-      <service>irc@jabber.project-moonshot.org</service>
-      <service>email@project-moonshot.org</service>
+      <service>irc/painless-security.com</service>
+      <service>email/painless-security.com</service>
     </services>
     <selection-rules>
       <rule>
-        <pattern>*@project-moonshot.org</pattern>
+        <pattern>*/painless-security.com</pattern>
         <always-confirm>true</always-confirm>
       </rule>
     </selection-rules>
     <trust-anchor>
-      <ca-cert>ABCDEFGHIJKLMNOPQRSTUVWXYZ123455678910</ca-cert>
-      <subject>Foo</subject>
-      <subject-alt>Bar</subject-alt>
-      <!-- Or alternatively -->
-      <server-cert>ABCDEFGHIJKLMNOPQRSTUVWXYZ123455678910</server-cert>
+      <ca-cert>
+      <!-- DER format, base64-encoded -->
+MIIE9jCCA96gAwIBAgIJANI5K4+KXvQyMA0GCSqGSIb3DQEBBQUAMIGaMQswCQYD
+VQQGEwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMR
+UGFpbmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFp
+bmxlc3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwg
+SW5jLjAeFw0xNjA4MzAxOTU4MjlaFw0xOTEyMTMxOTU4MjlaMIGaMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCTUExDzANBgNVBAcTBk1hbGRlbjEaMBgGA1UEChMRUGFp
+bmxlc3MgU2VjdXJpdHkxLzAtBgkqhkiG9w0BCQEWIHBvc3RtYXN0ZXJAcGFpbmxl
+c3Mtc2VjdXJpdHkuY29tMSAwHgYDVQQDExdQYWlubGVzcyBTZWN1cml0eSwgSW5j
+LjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ6tW6x+aO4n7VJu5W55
+DeNKn7+89oaaTgSRs6jg3C+RpTmXugPonh9+YRbuWpSNeru2eWGFNYFs01OjaDUw
+CxPcFDgF3xP/wAyCsI7WUgmkz8991PUGo9RxVbkDxMePHNaLGQVNz/+EJgK/Ycfy
+jYvenb/BGvcBmMftk2HsHio65ZsHsGMfW2Wcg/ehvKeDRZ3WR1ujhkzIFWdgdH3E
+u/yI2pHEfxQQ3PuYcQz43YZyIwhwzwnQG8qTK2jWkMF+wzRKRYfLdRD8nUUingvu
+IbngXLs71JqQHmbXzw1WTJClXtfF6R2VZuZ6PT8ZK1bDFPvTgnkUcAk70H+VnDM5
+K48CAwEAAaOCATswggE3MB0GA1UdDgQWBBQoFRKLJrZvkNmqvw8DNuTLPyru/DCB
+zwYDVR0jBIHHMIHEgBQoFRKLJrZvkNmqvw8DNuTLPyru/KGBoKSBnTCBmjELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAk1BMQ8wDQYDVQQHEwZNYWxkZW4xGjAYBgNVBAoT
+EVBhaW5sZXNzIFNlY3VyaXR5MS8wLQYJKoZIhvcNAQkBFiBwb3N0bWFzdGVyQHBh
+aW5sZXNzLXNlY3VyaXR5LmNvbTEgMB4GA1UEAxMXUGFpbmxlc3MgU2VjdXJpdHks
+IEluYy6CCQDSOSuPil70MjAMBgNVHRMEBTADAQH/MDYGA1UdHwQvMC0wK6ApoCeG
+JWh0dHA6Ly93d3cuZXhhbXBsZS5jb20vZXhhbXBsZV9jYS5jcmwwDQYJKoZIhvcN
+AQEFBQADggEBAIN38UOXvwd89+a89V+/rjeN8JfpcjafLf0c2I3nex9OxBWji5bf
+cWTNfm1t9GpS4HhVT2tl5xdxyW5UrE9Q+oFadN0LxtRPbGU+Gvt4pVo8Pst6/2P8
+PA3/OA1UchIpZR6EWQQsws4esNLLwDbj48MkQdVCjpp1cVpFVmJUzYYFD9h9EMK2
+kxpGf5wfp9LI2A5/qACNQPBDfRsR+dcNBsBbmD1LulqputUPuKPXnVbHWL28VZUY
+PITHl2Ndbmk6znSu7ILef3CGyeXqTTj+Jo+5AQz3sneko6oMn8PqfRj1h0uUyykT
+lavp3iTNstQs/rdqdI+lPYMokDKXRSD3pK8=
+</ca-cert>
+      <subject>Painless Security Server Certificate</subject>
     </trust-anchor>
   </identity>
     <identity>
     <display-name>Yet Another Unique Name</display-name>
     <user>user3</user>
     <password></password>
-    <realm>foo.com</realm>
+    <realm>painless-security.com</realm>
     <services>
-      <service>irc@jabber.project-moonshot.org</service>
-      <service>email@project-moonshot.org</service>
+      <service>irc/painless-security.com</service>
+      <service>email/painless-security.com</service>
     </services>
     <trust-anchor>
-      <ca-cert>ABCDEFGHIJKLMNOPQRSTUVWXYZ123455678910</ca-cert>
-      <subject>Foo</subject>
-      <subject-alt>Bar</subject-alt>
-      <!-- Or alternatively -->
-      <server-cert>ABCDEFGHIJKLMNOPQRSTUVWXYZ123455678910</server-cert>
+      <server-cert>3838E17EC9A2A06D7B6030E3C5727E3466EAB4BB4159DCE7CF6297ADAFC8A56F</server-cert>
+    </trust-anchor>
+  </identity>
+    <identity>
+    <display-name>A Not Really Unique Name</display-name>
+    <user>user4</user>
+    <password></password>
+    <realm>painless-security.com</realm>
+    <services>
+      <service>ssh/painless-security.com</service>
+      <service>email/painless-security.com</service>
+    </services>
+    <trust-anchor>
+      <server-cert>4242424242424242424242424242424242424242424242424242424242424242</server-cert>
     </trust-anchor>
   </identity>
+
 </identities>