[gnome-keyring] Implement support for prompting for unlock options
- From: Stefan Walter <stefw src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gnome-keyring] Implement support for prompting for unlock options
- Date: Mon, 1 Feb 2010 02:33:18 +0000 (UTC)
commit 3aab72fc09aa0d70eb0915285d589e5a76d0a8f8
Author: Stef Walter <stef memberwebs com>
Date: Mon Feb 1 02:27:40 2010 +0000
Implement support for prompting for unlock options
daemon/dbus/gkd-secret-unlock.c | 151 ++++++++++++++++++++++++++++++--------
daemon/prompt/Makefile.am | 2 +
daemon/prompt/gkd-prompt-tool.c | 76 ++++++++++++--------
daemon/prompt/gkd-prompt.c | 50 +++++++++++++
daemon/prompt/gkd-prompt.h | 8 ++
daemon/prompt/gkd-prompt.ui | 123 +-------------------------------
6 files changed, 228 insertions(+), 182 deletions(-)
---
diff --git a/daemon/dbus/gkd-secret-unlock.c b/daemon/dbus/gkd-secret-unlock.c
index 6fad92a..78d57c4 100644
--- a/daemon/dbus/gkd-secret-unlock.c
+++ b/daemon/dbus/gkd-secret-unlock.c
@@ -80,13 +80,31 @@ location_string_for_collection (GP11Object *collection)
return location;
}
+static gchar*
+label_string_for_collection (GP11Object *collection)
+{
+ GError *error = NULL;
+ gpointer data;
+ gsize n_data;
+
+ data = gp11_object_get_data (collection, CKA_LABEL, &n_data, &error);
+ if (!data) {
+ g_warning ("couldn't get label for collection: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ if (!data || !n_data)
+ return g_strdup (_("Unnamed"));
+ else /* gp11_object_get_data returns null terminated */
+ return data;
+}
+
static void
prepare_unlock_prompt (GkdSecretUnlock *self, GP11Object *coll)
{
+ GP11Attributes *template;
GError *error = NULL;
GkdPrompt *prompt;
- gpointer data;
- gsize n_data;
gchar *label;
gchar *text;
@@ -95,17 +113,7 @@ prepare_unlock_prompt (GkdSecretUnlock *self, GP11Object *coll)
prompt = GKD_PROMPT (self);
- data = gp11_object_get_data (coll, CKA_LABEL, &n_data, &error);
- if (!data) {
- g_warning ("couldn't get label for collection: %s", error->message);
- g_clear_error (&error);
- }
-
- if (!data || !n_data)
- label = g_strdup (_("Unnamed"));
- else
- label = g_strndup (data, n_data);
- g_free (data);
+ label = label_string_for_collection (coll);
gkd_prompt_reset (prompt);
@@ -121,10 +129,21 @@ prepare_unlock_prompt (GkdSecretUnlock *self, GP11Object *coll)
gkd_prompt_hide_widget (prompt, "name_area");
gkd_prompt_hide_widget (prompt, "confirm_area");
- gkd_prompt_hide_widget (prompt, "details_area");
+ gkd_prompt_show_widget (prompt, "details_area");
gkd_prompt_show_widget (prompt, "password_area");
+ gkd_prompt_show_widget (prompt, "lock_area");
g_free (label);
+
+ /* Setup the unlock options */
+ template = gp11_object_get_template (coll, CKA_G_CREDENTIAL_TEMPLATE, &error);
+ if (template) {
+ gkd_prompt_set_unlock_options (prompt, template);
+ gp11_attributes_unref (template);
+ } else {
+ g_warning ("couldn't get credential template for collection: %s", error->message);
+ g_clear_error (&error);
+ }
}
static void
@@ -154,21 +173,70 @@ check_locked_collection (GP11Object *collection, gboolean *locked)
return TRUE;
}
+static void
+attach_credential_to_login (GP11Object *collection, GP11Object *cred)
+{
+ GError *error = NULL;
+ gpointer value;
+ gsize n_value;
+ gchar *location;
+ gchar *label;
+ gchar *display;
+
+ g_assert (GP11_IS_OBJECT (collection));
+ g_assert (GP11_IS_OBJECT (cred));
+
+ location = location_string_for_collection (collection);
+ label = label_string_for_collection (collection);
+ display = g_strdup_printf (_("Unlock password for %s keyring"), label);
+ g_free (label);
+
+ value = gp11_object_get_data_full (cred, CKA_VALUE, egg_secure_realloc, NULL, &n_value, &error);
+ if (value) {
+ if (g_utf8_validate (value, n_value, NULL))
+ gkd_login_attach_secret (display, value, "keyring", location, NULL);
+ else
+ g_warning ("couldn't save non utf-8 unlock credentials in login keyring");
+ egg_secure_clear (value, n_value);
+ egg_secure_free (value);
+
+ } else {
+ g_warning ("couldn't read unlock credentials to save in login keyring: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_free (location);
+ g_free (display);
+}
+
+static void
+common_unlock_attributes (GP11Attributes *attrs, GP11Object *collection)
+{
+ g_assert (attrs);
+ g_assert (GP11_IS_OBJECT (collection));
+ gp11_attributes_add_ulong (attrs, CKA_CLASS, CKO_G_CREDENTIAL);
+ gp11_attributes_add_ulong (attrs, CKA_G_OBJECT, gp11_object_get_handle (collection));
+}
+
static gboolean
authenticate_collection (GkdSecretUnlock *self, GP11Object *collection, gboolean *locked)
{
DBusError derr = DBUS_ERROR_INIT;
GkdSecretSecret *master;
- gboolean result;
+ GP11Attributes *template;
+ GP11Object *cred;
+ gboolean transient;
g_assert (GKD_SECRET_IS_UNLOCK (self));
g_assert (GP11_IS_OBJECT (collection));
g_assert (locked);
- /* Bail out early, just checking locked status */
- if (!gkd_prompt_has_response (GKD_PROMPT (self))) {
- return check_locked_collection (collection, locked);
- }
+ if (!check_locked_collection (collection, locked))
+ return FALSE;
+
+ /* Shortcut if already unlocked, or just checking locked status */
+ if (!*locked || !gkd_prompt_has_response (GKD_PROMPT (self)))
+ return TRUE;
master = gkd_secret_prompt_get_secret (GKD_SECRET_PROMPT (self), "password");
if (master == NULL) {
@@ -176,14 +244,33 @@ authenticate_collection (GkdSecretUnlock *self, GP11Object *collection, gboolean
return FALSE;
}
- result = gkd_secret_unlock_with_secret (collection, master, &derr);
+ /* The various unlock options */
+ template = gp11_attributes_new ();
+ common_unlock_attributes (template, collection);
+ gkd_prompt_get_unlock_options (GKD_PROMPT (self), template);
+
+ /* If it's supposed to save non-transient, then we override that */
+ if (!gp11_attributes_find_boolean (template, CKA_GNOME_TRANSIENT, &transient))
+ transient = TRUE;
+
+ cred = gkd_secret_session_create_credential (master->session, NULL, template, master, &derr);
gkd_secret_secret_free (master);
- if (result) {
+ if (cred) {
+ /* Save it to the login keyring */
+ if (!transient)
+ attach_credential_to_login (collection, cred);
+ g_object_unref (cred);
+
+ /* Save away the unlock options for next time */
+ gp11_object_set_template (collection, CKA_G_CREDENTIAL_TEMPLATE, template, NULL);
+ gp11_attributes_unref (template);
+
*locked = FALSE;
return TRUE; /* Operation succeeded, and unlocked */
} else {
+ gp11_attributes_unref (template);
if (dbus_error_has_name (&derr, INTERNAL_ERROR_DENIED)) {
dbus_error_free (&derr);
*locked = TRUE;
@@ -434,11 +521,10 @@ gkd_secret_unlock_with_secret (GP11Object *collection, GkdSecretSecret *master,
if (check_locked_collection (collection, &locked) && !locked)
return TRUE;
- attrs = gp11_attributes_newv (CKA_CLASS, GP11_ULONG, CKO_G_CREDENTIAL,
- CKA_G_OBJECT, GP11_ULONG, gp11_object_get_handle (collection),
- CKA_GNOME_TRANSIENT, GP11_BOOLEAN, TRUE,
- CKA_TOKEN, GP11_BOOLEAN, TRUE,
- GP11_INVALID);
+ attrs = gp11_attributes_new ();
+ common_unlock_attributes (attrs, collection);
+ gp11_attributes_add_boolean (attrs, CKA_GNOME_TRANSIENT, TRUE);
+ gp11_attributes_add_boolean (attrs, CKA_TOKEN, TRUE);
cred = gkd_secret_session_create_credential (master->session, NULL, attrs, master, derr);
@@ -453,6 +539,7 @@ gboolean
gkd_secret_unlock_with_password (GP11Object *collection, const guchar *password,
gsize n_password, DBusError *derr)
{
+ GP11Attributes *attrs;
GError *error = NULL;
GP11Session *session;
GP11Object *cred;
@@ -467,13 +554,13 @@ gkd_secret_unlock_with_password (GP11Object *collection, const guchar *password,
session = gp11_object_get_session (collection);
g_return_val_if_fail (session, FALSE);
- cred = gp11_session_create_object (session, &error, CKA_CLASS, GP11_ULONG, CKO_G_CREDENTIAL,
- CKA_G_OBJECT, GP11_ULONG, gp11_object_get_handle (collection),
- CKA_GNOME_TRANSIENT, GP11_BOOLEAN, TRUE,
- CKA_TOKEN, GP11_BOOLEAN, TRUE,
- CKA_VALUE, n_password, password,
- GP11_INVALID);
+ attrs = gp11_attributes_new_full (egg_secure_realloc);
+ common_unlock_attributes (attrs, collection);
+ gp11_attributes_add_boolean (attrs, CKA_GNOME_TRANSIENT, TRUE);
+ gp11_attributes_add_boolean (attrs, CKA_TOKEN, TRUE);
+ gp11_attributes_add_data (attrs, CKA_VALUE, password, n_password);
+ cred = gp11_session_create_object_full (session, attrs, NULL, &error);
if (cred == NULL) {
if (error->code == CKR_PIN_INCORRECT) {
dbus_set_error_const (derr, INTERNAL_ERROR_DENIED, "The password was incorrect.");
diff --git a/daemon/prompt/Makefile.am b/daemon/prompt/Makefile.am
index 01c0a2f..44821ea 100644
--- a/daemon/prompt/Makefile.am
+++ b/daemon/prompt/Makefile.am
@@ -68,10 +68,12 @@ gnome_keyring_prompt_SOURCES = \
gnome_keyring_prompt_LDADD = \
$(top_builddir)/egg/libegg-prompt.la \
$(top_builddir)/egg/libegg-entry-buffer.la \
+ $(top_builddir)/gcr/libgcr.la \
$(LIBGCRYPT_LIBS) \
$(GTK_LIBS)
gnome_keyring_prompt_CFLAGS = \
-DUIDIR=\""$(uidir)"\" \
+ -DGCR_API_SUBJECT_TO_CHANGE \
$(LIBGCRYPT_CFLAGS) \
$(GTK_CFLAGS)
diff --git a/daemon/prompt/gkd-prompt-tool.c b/daemon/prompt/gkd-prompt-tool.c
index a50300e..029704f 100644
--- a/daemon/prompt/gkd-prompt-tool.c
+++ b/daemon/prompt/gkd-prompt-tool.c
@@ -29,6 +29,8 @@
#include "egg/egg-libgcrypt.h"
#include "egg/egg-secure-memory.h"
+#include "gcr/gcr-unlock-options-widget.h"
+
#include <gcrypt.h>
#include <glib/gi18n.h>
@@ -305,41 +307,35 @@ prepare_security (GtkBuilder *builder, GtkDialog *dialog)
}
static void
-on_auto_check_unlock_toggled (GtkToggleButton *check, GtkBuilder *builder)
-{
- GtkWidget *area;
-
- area = GTK_WIDGET (gtk_builder_get_object (builder, "options_area"));
- gtk_widget_set_sensitive (area, !gtk_toggle_button_get_active (check));
-}
-
-static void
-on_timeout_choices_toggled (GtkToggleButton *unused, GtkBuilder *builder)
+prepare_lock (GtkBuilder *builder, GtkDialog *dialog)
{
- GtkWidget *spin, *after, *idle;
+ GtkWidget *unlock, *area;
+ gboolean unlock_auto, unlock_global;
+ gint unlock_idle, unlock_timeout;
- spin = GTK_WIDGET (gtk_builder_get_object (builder, "lock_minutes_spin"));
- after = GTK_WIDGET (gtk_builder_get_object (builder, "lock_after_choice"));
- idle = GTK_WIDGET (gtk_builder_get_object (builder, "lock_idle_choice"));
- gtk_widget_set_sensitive (spin, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (after)) ||
- gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(idle)));
+ unlock = gcr_unlock_options_widget_new ();
+ area = GTK_WIDGET (gtk_builder_get_object (builder, "lock_area"));
+ g_object_set_data (G_OBJECT (dialog), "unlock-options-widget", unlock);
+ gtk_container_add (GTK_CONTAINER (area), unlock);
+ gtk_widget_show (unlock);
-}
+ unlock_auto = g_key_file_get_boolean (input_data, "unlock-options", "unlock-auto", NULL);
-static void
-prepare_lock (GtkBuilder *builder, GtkDialog *dialog)
-{
- GtkWidget *check;
+ /* Defaults to TRUE */
+ if (!g_key_file_has_key (input_data, "unlock-options", "unlock-global", NULL))
+ unlock_global = TRUE;
+ else
+ unlock_global = g_key_file_get_boolean (input_data, "unlock-options", "unlock-global", NULL);
- check = GTK_WIDGET (gtk_builder_get_object (builder, "auto_unlock_check"));
- g_signal_connect (check, "toggled", G_CALLBACK (on_auto_check_unlock_toggled), builder);
- on_auto_check_unlock_toggled (GTK_TOGGLE_BUTTON (check), builder);
+ unlock_idle = g_key_file_get_integer (input_data, "unlock-options", "unlock-idle", NULL);
+ unlock_timeout = g_key_file_get_integer (input_data, "unlock-options", "unlock-timeout", NULL);
- check = GTK_WIDGET (gtk_builder_get_object (builder, "lock_after_choice"));
- g_signal_connect (check, "toggled", G_CALLBACK (on_timeout_choices_toggled), builder);
- check = GTK_WIDGET (gtk_builder_get_object (builder, "lock_idle_choice"));
- g_signal_connect (check, "toggled", G_CALLBACK (on_timeout_choices_toggled), builder);
- on_timeout_choices_toggled (GTK_TOGGLE_BUTTON (check), builder);
+ g_object_set (unlock,
+ "unlock-auto", unlock_auto,
+ "unlock-global", unlock_global,
+ "unlock-idle", unlock_idle,
+ "unlock-timeout", unlock_timeout,
+ NULL);
}
static GtkDialog*
@@ -554,11 +550,33 @@ gather_response (gint response)
}
static void
+gather_unlock_options (GtkBuilder *builder, GtkDialog *dialog)
+{
+ gboolean unlock_auto, unlock_global;
+ gint unlock_timeout, unlock_idle;
+
+ GtkWidget *unlock = g_object_get_data (G_OBJECT (dialog), "unlock-options-widget");
+
+ g_object_get (unlock,
+ "unlock-auto", &unlock_auto,
+ "unlock-global", &unlock_global,
+ "unlock-timeout", &unlock_timeout,
+ "unlock-idle", &unlock_idle,
+ NULL);
+
+ g_key_file_set_boolean (output_data, "unlock-options", "unlock-auto", unlock_auto);
+ g_key_file_set_boolean (output_data, "unlock-options", "unlock-global", unlock_global);
+ g_key_file_set_integer (output_data, "unlock-options", "unlock-timeout", unlock_timeout);
+ g_key_file_set_integer (output_data, "unlock-options", "unlock-idle", unlock_idle);
+}
+
+static void
gather_dialog (GtkBuilder *builder, GtkDialog *dialog)
{
gather_password (builder, "password");
gather_password (builder, "confirm");
gather_password (builder, "original");
+ gather_unlock_options (builder, dialog);
}
static void
diff --git a/daemon/prompt/gkd-prompt.c b/daemon/prompt/gkd-prompt.c
index 999964e..d62397f 100644
--- a/daemon/prompt/gkd-prompt.c
+++ b/daemon/prompt/gkd-prompt.c
@@ -31,6 +31,8 @@
#include "egg/egg-secure-memory.h"
#include "egg/egg-spawn.h"
+#include "pkcs11/pkcs11i.h"
+
#include <gcrypt.h>
#define DEBUG_PROMPT 1
@@ -811,6 +813,54 @@ gkd_prompt_get_transport_password (GkdPrompt *self, const gchar *password_type,
return TRUE;
}
+void
+gkd_prompt_get_unlock_options (GkdPrompt *self, GP11Attributes *attrs)
+{
+ gboolean bval;
+ gint ival;
+
+ g_return_if_fail (GKD_IS_PROMPT (self));
+ g_return_if_fail (attrs);
+ g_return_if_fail (self->pv->output);
+
+ bval = g_key_file_get_boolean (self->pv->output, "unlock-options", "unlock-auto", NULL);
+ gp11_attributes_add_boolean (attrs, CKA_GNOME_TRANSIENT, !bval);
+
+ bval = TRUE;
+ if (g_key_file_has_key (self->pv->output, "unlock-options", "unlock-global", NULL))
+ bval = g_key_file_get_boolean (self->pv->output, "unlock-options", "unlock-global", NULL);
+ gp11_attributes_add_boolean (attrs, CKA_TOKEN, bval);
+
+ ival = g_key_file_get_integer (self->pv->output, "unlock-options", "unlock-idle", NULL);
+ gp11_attributes_add_ulong (attrs, CKA_G_DESTRUCT_IDLE, ival <= 0 ? 0 : ival);
+
+ ival = g_key_file_get_integer (self->pv->output, "unlock-options", "unlock-timeout", NULL);
+ gp11_attributes_add_ulong (attrs, CKA_G_DESTRUCT_AFTER, ival <= 0 ? 0 : ival);
+}
+
+void
+gkd_prompt_set_unlock_options (GkdPrompt *self, GP11Attributes *attrs)
+{
+ gboolean bval;
+ gulong uval;
+
+ g_return_if_fail (GKD_IS_PROMPT (self));
+ g_return_if_fail (attrs);
+ g_return_if_fail (self->pv->input);
+
+ if (gp11_attributes_find_boolean (attrs, CKA_GNOME_TRANSIENT, &bval))
+ g_key_file_set_boolean (self->pv->input, "unlock-options", "unlock-auto", !bval);
+
+ if (gp11_attributes_find_boolean (attrs, CKA_TOKEN, &bval))
+ g_key_file_set_boolean (self->pv->input, "unlock-options", "unlock-global", bval);
+
+ if (gp11_attributes_find_ulong (attrs, CKA_G_DESTRUCT_IDLE, &uval))
+ g_key_file_set_boolean (self->pv->input, "unlock-options", "unlock-idle", (int)uval);
+
+ if (gp11_attributes_find_ulong (attrs, CKA_G_DESTRUCT_AFTER, &uval))
+ g_key_file_set_boolean (self->pv->input, "unlock-options", "unlock-timeout", (int)uval);
+}
+
/* ----------------------------------------------------------------------------------
* ATTENTION QUEUES
*/
diff --git a/daemon/prompt/gkd-prompt.h b/daemon/prompt/gkd-prompt.h
index 4cc8a91..df0a9c5 100644
--- a/daemon/prompt/gkd-prompt.h
+++ b/daemon/prompt/gkd-prompt.h
@@ -24,6 +24,8 @@
#include <glib-object.h>
+#include <gp11/gp11.h>
+
typedef enum {
GKD_RESPONSE_FAILURE = -1,
GKD_RESPONSE_NONE = 0,
@@ -109,6 +111,12 @@ gboolean gkd_prompt_get_transport_password (GkdPrompt *self,
gpointer *value,
gsize *n_value);
+void gkd_prompt_get_unlock_options (GkdPrompt *self,
+ GP11Attributes *attrs);
+
+void gkd_prompt_set_unlock_options (GkdPrompt *self,
+ GP11Attributes *attrs);
+
gboolean gkd_prompt_is_widget_selected (GkdPrompt *prompt,
const gchar *widget);
diff --git a/daemon/prompt/gkd-prompt.ui b/daemon/prompt/gkd-prompt.ui
index 9c5de3b..772cfd3 100644
--- a/daemon/prompt/gkd-prompt.ui
+++ b/daemon/prompt/gkd-prompt.ui
@@ -264,132 +264,13 @@ An application wants access to the keyring 'xxx', but it is locked.</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
- <object class="GtkFrame" id="lock_area">
+ <object class="GtkAlignment" id="lock_area">
<property name="visible">True</property>
- <property name="label_xalign">0</property>
- <property name="shadow_type">none</property>
<child>
- <object class="GtkAlignment" id="options_area">
- <property name="visible">True</property>
- <property name="top_padding">3</property>
- <property name="left_padding">12</property>
- <child>
- <object class="GtkVBox" id="vbox4">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkCheckButton" id="per_application_check">
- <property name="label" translatable="yes">Prompt me for each application that accesses this keyring.</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkRadioButton" id="lock_logout_choice">
- <property name="label" translatable="yes">Lock this keyring when I log out.</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- </object>
- <packing>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkHBox" id="hbox2">
- <property name="visible">True</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkVBox" id="vbox5">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <property name="spacing">6</property>
- <child>
- <object class="GtkRadioButton" id="lock_after_choice">
- <property name="label" translatable="yes">Lock this keyring after</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- <property name="group">lock_logout_choice</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkRadioButton" id="lock_idle_choice">
- <property name="label" translatable="yes">Lock this keyring if idle for</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="active">True</property>
- <property name="draw_indicator">True</property>
- <property name="group">lock_logout_choice</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <object class="GtkSpinButton" id="lock_minutes_spin">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">•</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <object class="GtkLabel" id="label2">
- <property name="visible">True</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">minutes.</property>
- </object>
- <packing>
- <property name="position">2</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="position">2</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child type="label">
- <object class="GtkCheckButton" id="auto_unlock_check">
- <property name="label" translatable="yes">Automatically unlock this keyring whenever I'm logged in.</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="draw_indicator">True</property>
- </object>
+ <placeholder/>
</child>
</object>
<packing>
- <property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]