Re: [gdm-list] PoC for multiple pam prompts



Ludwig Nussel wrote:
> I've started to implement multiple pam prompts in gdmlogin. It looks
> like this[1]:
> http://www.suse.de/~lnussel/gdm/gdm1.png
> http://www.suse.de/~lnussel/gdm/gdm2.png
> 
> The patch works but is not ready for production yet. gdmgreeter not
> supported yet. I post it here for discussion and for the brave for
> testing.

Updated patchs attached. gdmgreeter now at least works again with
serialized prompts.

cu
Ludwig

-- 
 (o_   Ludwig Nussel
 //\   SUSE LINUX Products GmbH, Development
 V_/_  http://www.suse.de/



Index: gdm2/daemon/gdm.h
===================================================================
--- gdm2.orig/daemon/gdm.h
+++ gdm2/daemon/gdm.h
@@ -31,6 +31,7 @@
 				 * leaves MAX_PASS undefined. */
 
 #define STX 0x2			/* Start of txt */
+#define EOX 0x3			/* End of txt */
 #define BEL 0x7			/* Bell, used to interrupt login for
 				 * say timed login or something similar */
 
@@ -91,8 +92,9 @@ enum {
 
 /* This will change if there are incompatible
  * protocol changes */
-#define GDM_GREETER_PROTOCOL_VERSION "3"
+#define GDM_GREETER_PROTOCOL_VERSION "4"
 
+#define GDM_PAM_FORM   'p'
 #define GDM_MSG        'D'
 #define GDM_NOECHO     'U'
 #define GDM_PROMPT     'N'
Index: gdm2/daemon/slave.c
===================================================================
--- gdm2.orig/daemon/slave.c
+++ gdm2/daemon/slave.c
@@ -95,6 +95,8 @@
 #include "gdmconsolekit.h"
 #endif
 
+#define dbg(fmt, args...) gdm_info("%s:%d %s() - " fmt "\n", __FILE__, __LINE__, __FUNCTION__, ##args)
+
 /* Some per slave globals */
 static GdmDisplay *d = 0;
 static gchar *login = NULL;
@@ -1995,10 +1997,12 @@ gdm_slave_wait_for_login (void)
 			g_free (login);
 			login = NULL;
 			/* clear any error */
+#if 0
 			gdm_slave_greeter_ctl_no_ret (GDM_ERRBOX, "");
 			gdm_slave_greeter_ctl_no_ret
 				(GDM_MSG,
 				 _("You must authenticate as root to run configuration."));
+#endif
 
 			/* we always allow root for this */
 			oldAllowRoot = gdm_get_value_bool (GDM_KEY_ALLOW_ROOT);
@@ -2018,8 +2022,10 @@ gdm_slave_wait_for_login (void)
 						 d->attached);
 			gdm_set_value_bool (GDM_KEY_ALLOW_ROOT, oldAllowRoot);
 
+#if 0
 			/* Clear message */
 			gdm_slave_greeter_ctl_no_ret (GDM_MSG, "");
+#endif
 
 			if G_UNLIKELY (do_restart_greeter) {
 				g_free (login);
@@ -5127,7 +5133,6 @@ check_for_interruption (const char *msg)
 	return FALSE;
 }
 
-
 char * 
 gdm_slave_greeter_ctl (char cmd, const char *str)
 {
@@ -5170,6 +5175,8 @@ gdm_slave_greeter_ctl (char cmd, const c
     /* user responses take kind of random amount of time */
     gdm_random_tick ();
 
+    dbg("%c%s -> %s", cmd, str, buf);
+
     if ( ! ve_string_empty (buf)) {
 	    return buf;
     } else {
Index: gdm2/daemon/verify-pam.c
===================================================================
--- gdm2.orig/daemon/verify-pam.c
+++ gdm2/daemon/verify-pam.c
@@ -47,6 +47,16 @@
 #include <bsm/adt_event.h>
 #endif	/* HAVE_ADT */
 
+#define dbg(fmt, args...) gdm_info("%s:%d %s() - " fmt "\n", __FILE__, __LINE__, __FUNCTION__, ##args)
+#if 0
+#define gdm_slave_greeter_ctl_no_ret(op, msg) gdm_slave_greeter_ctl_no_ret(op, msg); dbg("sent %c %s", op, msg)
+#define gdm_slave_greeter_ctl(op, msg) gdm_slave_greeter_ctl(op, msg); dbg("sent %c %s", op, msg)
+#endif
+#if 1
+#define openlog(a,b,c)
+#define closelog()
+#endif
+
 /* Evil, but this way these things are passed to the child session */
 static pam_handle_t *pamh = NULL;
 
@@ -66,6 +76,10 @@ static char *selected_user = NULL;
 static gboolean opened_session = FALSE;
 static gboolean did_setcred = FALSE;
 
+/* used if there is a problem in a conversation to abort also all
+ * following as they cannot succeed anymore anyways */
+static gboolean abort_conversations;
+
 extern char *gdm_ack_question_response;
 
 #ifdef	HAVE_ADT
@@ -474,6 +488,18 @@ perhaps_translate_message (const char *m
 /* Internal PAM conversation function. Interfaces between the PAM
  * authentication system and the actual greeter program */
 
+static inline char msg_style2gdm(int msg_style)
+{
+    switch (msg_style) {
+	case PAM_PROMPT_ECHO_ON: return GDM_PROMPT;
+	case PAM_PROMPT_ECHO_OFF: return GDM_NOECHO;
+	case PAM_ERROR_MSG: return GDM_ERRBOX;
+	case PAM_TEXT_INFO: return GDM_MSG;
+    }
+    gdm_assert_not_reached();
+    return 0;
+}
+
 static int 
 gdm_verify_pam_conv (int num_msg, const struct pam_message **msg,
 		     struct pam_response **resp,
@@ -482,10 +508,17 @@ gdm_verify_pam_conv (int num_msg, const 
     int replies = 0;
     int i;
     char *s = NULL;
+    char* str = NULL;
+    size_t sendbuflen = 0;
     struct pam_response *reply = NULL;
     const void *p;
     const char *login;
 
+    dbg("num_msg %d", num_msg);
+
+    if(!num_msg) /* weird */
+	return PAM_SUCCESS;
+
     if (pamh == NULL)
 	return PAM_CONV_ERR;
     
@@ -494,21 +527,15 @@ gdm_verify_pam_conv (int num_msg, const 
        maybe it will listen */
     /* well, it actually happens if there are multiple pam modules
      * with conversations */
-    if ( ! gdm_slave_action_pending () || selected_user)
+    if ( abort_conversations || ! gdm_slave_action_pending () || selected_user)
         return PAM_CONV_ERR;
 
-    reply = malloc (sizeof (struct pam_response) * num_msg);
-    
-    if (reply == NULL)
-	return PAM_CONV_ERR;
-
-    memset (reply, 0, sizeof (struct pam_response) * num_msg);
-
     /* Here we set the login if it wasn't already set,
      * this is kind of anal, but this way we guarantee that
      * the greeter always is up to date on the login */
     if (pam_get_item (pamh, PAM_USER, &p) == PAM_SUCCESS) {
-	    login = (const char *)p;
+	login = (const char *)p;
+	if(login)
 	    gdm_slave_greeter_ctl_no_ret (GDM_SETLOGIN, login);
     }
 
@@ -516,88 +543,111 @@ gdm_verify_pam_conv (int num_msg, const 
     closelog ();
     openlog ("gdm", LOG_PID, LOG_DAEMON);
     
+    if (gdm_slave_greeter_check_interruption ()) {
+	gdm_fail("unexpected interruption in pam conversation");
+	return PAM_CONV_ERR;
+    }
+
+    /* check for valid form first */
+    if(num_msg > 255-'\n') {
+	gdm_error("too many prompts");
+	return PAM_CONV_ERR;
+    }
+    sendbuflen = 1 /* num_msg */;
     for (replies = 0; replies < num_msg; replies++) {
-	const char *m = (*msg)[replies].msg;
-	m = perhaps_translate_message (m);
-	
 	switch ((*msg)[replies].msg_style) {
-	    
-	/* PAM requested textual input with echo on */
-	case PAM_PROMPT_ECHO_ON:
- 	    if (strcmp (m, _("Username:")) == 0) {
-		    if ( ve_string_empty (selected_user)) {
-			    /* this is an evil hack, but really there is no way we'll
-			    know this is a username prompt.  However we SHOULD NOT
-			    rely on this working.  The pam modules can set their
-			    prompt to whatever they wish to */
-			    gdm_slave_greeter_ctl_no_ret
-				    (GDM_MSG, _("Please enter your username"));
-			    s = gdm_slave_greeter_ctl (GDM_PROMPT, m);
-			    /* this will clear the message */
-			    gdm_slave_greeter_ctl_no_ret (GDM_MSG, "");
+	    case PAM_PROMPT_ECHO_OFF:
+	    case PAM_PROMPT_ECHO_ON:
+	    case PAM_ERROR_MSG:
+	    case PAM_TEXT_INFO:
+		{
+		    const char *m = (*msg)[replies].msg;
+		    size_t len;
+		    m = perhaps_translate_message (m);
+		    len = strlen(m);
+		    if(len > 255-'\n') {
+			gdm_error("pam prompt too long");
+			return PAM_CONV_ERR;
 		    }
-	    } else {
-		    s = gdm_slave_greeter_ctl (GDM_PROMPT, m);
-	    }
+		    if (!len) len = 1;
+		    sendbuflen += 1 /* type */ + len + 1 /* \0 */;
+		}
+		break;
+	    default:
+		return PAM_CONV_ERR;
+	}
+    }
 
-	    if (gdm_slave_greeter_check_interruption ()) {
-		    g_free (s);
-		    for (i = 0; i < replies; i++)
-			    if (reply[replies].resp != NULL)
-				    free (reply[replies].resp);
-		    free (reply);
-		    return PAM_CONV_ERR;
-	    }
+    s = str = malloc(sendbuflen);
 
-	    reply[replies].resp_retcode = PAM_SUCCESS;
-	    reply[replies].resp = strdup (ve_sure_string (s));
-	    g_free (s);
-	    break;
-	    
-	case PAM_PROMPT_ECHO_OFF:
-	    if (strcmp (m, _("Password:")) == 0)
-		    did_we_ask_for_password = TRUE;
-	    /* PAM requested textual input with echo off */
-	    s = gdm_slave_greeter_ctl (GDM_NOECHO, m);
-	    if (gdm_slave_greeter_check_interruption ()) {
-		    g_free (s);
-		    for (i = 0; i < replies; i++)
-			    if (reply[replies].resp != NULL)
-				    free (reply[replies].resp);
-		    free (reply);
-		    return PAM_CONV_ERR;
-	    }
-	    reply[replies].resp_retcode = PAM_SUCCESS;
-	    reply[replies].resp = strdup (ve_sure_string (s));
-	    g_free (s);
-	    break;
-	    
-	case PAM_ERROR_MSG:
-	    /* PAM sent a message that should displayed to the user */
-	    gdm_slave_greeter_ctl_no_ret (GDM_ERRDLG, m);
-	    reply[replies].resp_retcode = PAM_SUCCESS;
-	    reply[replies].resp = NULL;
-	    break;
-	case PAM_TEXT_INFO:
-	    /* PAM sent a message that should displayed to the user */
-	    gdm_slave_greeter_ctl_no_ret (GDM_MSG, m);
-	    reply[replies].resp_retcode = PAM_SUCCESS;
-	    reply[replies].resp = NULL;
-	    break;
-	    
-	default:
-	    /* PAM has been smoking serious crack */
-	    for (i = 0; i < replies; i++)
-		    if (reply[replies].resp != NULL)
-			    free (reply[replies].resp);
-	    free (reply);
-	    return PAM_CONV_ERR;
-	}
+    /* prevent low values from beeing interpreted as control chars */
+    *s++ = num_msg + '\n';
+
+    for (replies = 0; replies < num_msg; replies++) {
+
+	const char *m = (*msg)[replies].msg;
+	m = perhaps_translate_message (m);
+	m = ve_sure_string(m);
 	
+	*s++ = msg_style2gdm((*msg)[replies].msg_style);
+	strcpy(s, m);
+	// XXX: remove EOX from string
+	s[strlen(m)] = EOX;
+	s += strlen(m)+1;
+    }
+
+    s = gdm_slave_greeter_ctl(GDM_PAM_FORM, str);
+
+    if (gdm_slave_greeter_check_interruption ()) {
+	g_free(s);
+	goto error;
+    }
+
+    if(!s || s[0] == '\n')
+    {
+	g_free(s);
+	gdm_error("empty reply from greeter");
+	goto error;
+    }
+
+    free(str);
+    str = s;
+
+    reply = malloc (sizeof (struct pam_response) * num_msg);
+    
+    if (reply == NULL)
+	return PAM_CONV_ERR;
+
+    memset (reply, 0, sizeof (struct pam_response) * num_msg);
+
+    for (replies = 0; replies < num_msg; ++replies) {
+	char* eos = strchr(s, EOX);
+	if(!eos || !s[0] || s[0] == '\n') {
+	    gdm_error("invalid reply from greeter");
+	    goto error;
+	}
+	*eos = '\0';
+	dbg("< %d %s", replies, s);
+	reply[replies].resp = strdup(s);
+	reply[replies].resp_retcode = PAM_SUCCESS;
+	s = eos + 1;
     }
 
     *resp = reply;
     return PAM_SUCCESS;
+
+error:
+    if(reply)
+	for (i = 0; i < replies; ++i)
+	    if (reply[replies].resp != NULL)
+		free (reply[replies].resp);
+
+    free (reply);
+    free(str);
+
+    abort_conversations = TRUE;
+
+    return PAM_CONV_ERR;
 }
 
 
@@ -902,6 +952,7 @@ authenticate_again:
 
     /* Start authentication session */
     did_we_ask_for_password = FALSE;
+    abort_conversations = FALSE;
     if ((pamerr = pam_authenticate (pamh, null_tok)) != PAM_SUCCESS) {
 	    if ( ! ve_string_empty (selected_user)) {
 		    pam_handle_t *tmp_pamh;
@@ -996,6 +1047,14 @@ authenticate_again:
 	    goto pamerr;
     }
 
+    if(!p) {
+	/* huh? pam modules forgot to ask for the user */
+	pamerr = PAM_PERM_DENIED;
+	if (gdm_slave_action_pending ())
+		gdm_error ("PAM_USER unset after pam conversation");
+	goto pamerr;
+    }
+
     login = g_strdup ((const char *)p);
     /* kind of anal, the greeter likely already knows, but it could have
        been changed */
@@ -1196,7 +1255,11 @@ authenticate_again:
 		    } else {
 			    msg = g_strdup (basemsg);
 		    }
-		    gdm_slave_greeter_ctl_no_ret (GDM_ERRBOX, msg);
+		    gdm_slave_greeter_ctl_no_ret (GDM_ERRDLG, msg);
+		    g_free (msg);
+	    } else if (pamh) {
+		    char* msg = g_strdup_printf( _("Authentication failed:\n%s"), pam_strerror(pamh, pamerr));
+		    gdm_slave_greeter_ctl_no_ret (GDM_ERRDLG, msg);
 		    g_free (msg);
 	    } else {
 		    gdm_slave_greeter_ctl_no_ret (GDM_ERRDLG, _("Authentication failed"));
Index: gdm2/gui/gdmlogin.c
===================================================================
--- gdm2.orig/gui/gdmlogin.c
+++ gdm2/gui/gdmlogin.c
@@ -61,8 +61,11 @@
 #include "gdmwm.h"
 #include "gdmlanguages.h"
 #include "gdmconfig.h"
+#include "gdmpamform.h"
 #include "misc.h"
 
+#define dbg(fmt, args...) do { gdm_common_info("%s:%d %s() - " fmt, __FILE__, __LINE__, __FUNCTION__, ##args); } while(0)
+
 /* set the DOING_GDM_DEVELOPMENT env variable if you aren't running
  * within the protocol */
 static gboolean DOING_GDM_DEVELOPMENT = FALSE;
@@ -96,21 +99,18 @@ static GtkWidget *logo_image = NULL;
 static GtkWidget *table = NULL;
 static GtkWidget *welcome;
 static GtkWidget *title;
-static GtkWidget *label;
 static GtkWidget *icon_button = NULL;
 static GtkWidget *title_box = NULL;
 static GtkWidget *clock_label = NULL;
-static GtkWidget *entry;
+static GtkWidget *gui_form_vbox;
 static GtkWidget *ok_button;
 static GtkWidget *start_again_button;
-static GtkWidget *msg;
 static GtkWidget *auto_timed_msg;
-static GtkWidget *err_box;
-static guint err_box_clear_handler = 0;
 static gboolean require_quarter = FALSE;
 static GtkWidget *icon_win = NULL;
 static GtkWidget *sessmenu;
 static GtkWidget *langmenu;
+static GtkWidget *statusbar;
 
 static gboolean login_is_local = FALSE;
 
@@ -149,6 +149,11 @@ static gboolean back_prog_delayed = FALS
 
 static guint timed_handler_id = 0;
 
+PamForm pam_form[256-'\n'] = {{0, NULL,  NULL}};
+unsigned num_pam_form;
+
+GtkWidget* pam_form_vbox = NULL;
+
 #if FIXME
 static char *selected_browser_user = NULL;
 #endif
@@ -179,8 +184,18 @@ static void back_prog_launch_after_timeo
 static void back_prog_run (void);
 static void back_prog_stop (void);
 
+static void gui_process_form(void);
 static void process_operation (guchar op_code, const gchar *args);
 
+static gboolean pop_statusbar (gpointer data);
+static void update_statusbar_timeout (const char* text);
+static void update_statusbar (const char* text);
+static guint statusbar_timeout_id;
+
+
+static gboolean username_prompt_focus_in(GtkWidget *widget, GdkEventFocus *event);
+static gboolean username_prompt_focus_out (GtkWidget *widget, GdkEventFocus *event);
+
 /* 
  * This function is called when the background program exits.
  * It will add a timer to restart the program after the
@@ -522,6 +537,7 @@ gdm_event (GSignalInvocationHint *ihint,
 	    && event->button.button == 3)
 		event->button.button = 1;
 
+#if 0
 	/* Support Ctrl-U for blanking the username/password entry */
 	if (event->type == GDK_KEY_PRESS &&
 	    (event->key.state & GDK_CONTROL_MASK) &&
@@ -530,6 +546,7 @@ gdm_event (GSignalInvocationHint *ihint,
 
 		gtk_entry_set_text (GTK_ENTRY (entry), "");
 	}
+#endif
 
 	return TRUE;
 }      
@@ -839,9 +856,15 @@ dance (gpointer data)
 }
 
 static gboolean
-evil (const char *user)
+evil (GtkWidget* entry)
 {
 	static gboolean old_lock;
+	const char* user;
+
+	if(!GTK_IS_ENTRY(entry))
+	    return FALSE;
+
+	user = gtk_entry_get_text (GTK_ENTRY (entry));
 
 	if (dance_handler == 0 &&
 	    /* do not translate */
@@ -882,6 +905,18 @@ evil (const char *user)
 	return FALSE;
 }
 
+static gboolean
+gdm_evil_handler_pressed (GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+    if (event->keyval == GDK_Return 
+    && (event->state & (GDK_CONTROL_MASK|GDK_MOD1_MASK|GDK_SHIFT_MASK)) == 0) {
+	return evil(widget);
+    }
+
+    return FALSE;
+}
+
+#if 0
 static void
 gdm_login_enter (GtkWidget *entry)
 {
@@ -937,16 +972,45 @@ gdm_login_enter (GtkWidget *entry)
 	fflush (stdout);
 	g_free (tmp);
 }
+#endif
 
 static void
 gdm_login_ok_button_press (GtkButton *button, GtkWidget *entry)
 {
-	gdm_login_enter (entry);
+    unsigned i;
+
+    dbg("%d form items", num_pam_form);
+    fputc(STX, stdout);
+    for(i = 0; i < num_pam_form; ++i) {
+	char* s = NULL;
+	switch(pam_form[i].style) {
+	    case GDM_NOECHO:
+	    case GDM_PROMPT:
+		s = ve_locale_from_utf8(gtk_entry_get_text(GTK_ENTRY(pam_form[i].widget)));
+	}
+	if(pam_form[i].widget)
+	    gtk_widget_set_sensitive(GTK_WIDGET(pam_form[i].widget), FALSE);
+	dbg("-> %d %d %s", i, pam_form[i].style, s);
+	fputs(ve_sure_string(s), stdout);
+	fputc(EOX, stdout);
+	g_free(s);
+    }
+    fputc('\n', stdout);
+    fflush (stdout);
+
+#if 0
+    /* don't do that to prevent resizes */
+    gtk_container_remove(GTK_CONTAINER(gui_form_vbox), pam_form_vbox);
+    pam_form_vbox = NULL;
+#endif
+
+    dbg("OK!");
 }
 
 static void
-gdm_login_start_again_button_press (GtkButton *button, GtkWidget *entry)
+gdm_login_start_again_button_press (GtkButton *button, gpointer data)
 {
+	unsigned i;
 	GtkTreeSelection *selection;
 
 	if (browser != NULL) {
@@ -958,6 +1022,18 @@ gdm_login_start_again_button_press (GtkB
 		g_free (selected_user);
 	selected_user = NULL;
 
+	for(i = 0; i < num_pam_form; ++i) {
+	    if(pam_form[i].widget)
+		gtk_widget_set_sensitive(GTK_WIDGET(pam_form[i].widget), FALSE);
+	}
+#if 0
+	/* don't do that to prevent resizes */
+	gtk_container_remove(GTK_CONTAINER(gui_form_vbox), pam_form_vbox);
+	pam_form_vbox = NULL;
+#endif
+
+	gtk_widget_set_sensitive (ok_button, FALSE);
+
 	printf ("%c%c%c\n", STX, BEL,
 		GDM_INTERRUPT_CANCEL);
 	fflush (stdout);
@@ -984,6 +1060,57 @@ gdm_login_focus_out (GtkWidget *widget, 
 	return FALSE;
 }
 
+static gboolean
+username_prompt_focus_in(GtkWidget *widget, GdkEventFocus *event)
+{
+    update_statusbar(_("Please enter your username"));
+    return FALSE;
+}
+
+static gboolean
+username_prompt_focus_out (GtkWidget *widget, GdkEventFocus *event)
+{
+    update_statusbar(NULL);
+    return FALSE;
+}
+
+static gboolean
+pop_statusbar(gpointer data)
+{
+    guint id = GPOINTER_TO_INT(data);
+    gtk_statusbar_pop(GTK_STATUSBAR(statusbar), id);
+    return TRUE;
+}
+
+static void update_statusbar_timeout (const char* text)
+{
+    guint id = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), "timeout");
+
+    gtk_statusbar_pop(GTK_STATUSBAR(statusbar), id);
+
+    if(!text)
+	return;
+
+    gtk_statusbar_push(GTK_STATUSBAR(statusbar), id, text);
+
+    if(statusbar_timeout_id)
+	g_source_remove (statusbar_timeout_id);
+
+    statusbar_timeout_id = g_timeout_add (5000, pop_statusbar, GINT_TO_POINTER(id));
+}
+
+static void update_statusbar(const char* text)
+{
+    guint id = gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), "static");
+
+    gtk_statusbar_pop(GTK_STATUSBAR(statusbar), id);
+
+    if(!text)
+	return;
+
+    gtk_statusbar_push(GTK_STATUSBAR(statusbar), id, text);
+}
+
 static void 
 gdm_login_session_handler (GtkWidget *widget) 
 {
@@ -993,7 +1120,8 @@ gdm_login_session_handler (GtkWidget *wi
 
     s = g_strdup_printf (_("%s session selected"), gdm_session_name (current_session));
 
-    gtk_label_set_text (GTK_LABEL (msg), s);
+    update_statusbar_timeout(s);
+
     g_free (s);
 
     login_window_resize (FALSE /* force */);
@@ -1097,7 +1225,7 @@ gdm_login_language_handler (GtkWidget *w
 			  TRUE /* makrup */);
     s = g_strdup_printf (_("%s language selected"), name);
     g_free (name);
-    gtk_label_set_markup (GTK_LABEL (msg), s);
+    update_statusbar_timeout(s);
     g_free (s);
 
     login_window_resize (FALSE /* force */);
@@ -1375,6 +1503,7 @@ gdm_login_theme_menu_new (void)
     return menu;
 }
 
+#if 0
 static gboolean
 err_box_clear (gpointer data)
 {
@@ -1384,6 +1513,7 @@ err_box_clear (gpointer data)
 	err_box_clear_handler = 0;
 	return FALSE;
 }
+#endif
 
 static void
 browser_set_user (const char *user)
@@ -1393,6 +1523,8 @@ browser_set_user (const char *user)
   GtkTreeIter iter = {0};
   GtkTreeModel *tm = NULL;
 
+  dbg("%s %s", __FUNCTION__, user);
+
   if (browser == NULL)
     return;
 
@@ -1525,6 +1657,153 @@ gdm_login_ctrl_handler (GIOChannel *sour
     return TRUE;
 }
 
+static void gui_process_form(void)
+{
+    int i;
+    int firstentry = -1;
+    GtkWidget* preventry = NULL;
+
+    dbg("num_pam_form %d", num_pam_form);
+
+    if(pam_form_vbox)
+	gtk_container_remove(GTK_CONTAINER(gui_form_vbox), pam_form_vbox);
+
+    pam_form_vbox = gtk_vbox_new(FALSE, 0);
+
+    for (i = 0; i < num_pam_form; ++i)
+    {
+	unsigned style = pam_form[i].style;
+	char* msg = ve_locale_to_utf8(pam_form[i].msg);
+
+	dbg("%c %s", style, msg);
+
+	switch(style)
+	{
+	    case GDM_NOECHO:
+	    case GDM_PROMPT:
+		{
+		    GtkWidget* hbox;
+		    GtkWidget* label;
+		    GtkWidget* entry;
+		    gboolean is_username;
+		    
+		    if((is_username = !strcmp(msg, _("Username:"))))
+			msg = _("_Username:");
+
+		    hbox = gtk_hbox_new(FALSE, 4);
+		    label = gtk_label_new_with_mnemonic(msg);
+		    pam_form[i].widget = entry = gtk_entry_new();
+		    gtk_widget_set_size_request (entry, 200, -1);
+
+		    gtk_entry_set_max_length (GTK_ENTRY (entry), PW_ENTRY_SIZE);
+		    gtk_entry_set_visibility(GTK_ENTRY(entry), (style == GDM_PROMPT));
+
+		    gdm_common_setup_blinking_entry (entry);
+
+		    gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+
+		    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+		    gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, TRUE, 0);
+
+		    gtk_box_pack_start(GTK_BOX(pam_form_vbox), hbox, TRUE, TRUE, 0);
+
+		    if(firstentry == -1)
+			firstentry = i;
+
+		    if(is_username)
+		    {
+			msg = NULL; // static string from gettext, prevent g_free()
+
+			if(selected_user)
+			    gtk_entry_set_text (GTK_ENTRY(entry), selected_user);
+
+			/* TODO: set callback for evil() */
+
+			g_signal_connect (G_OBJECT (entry), "focus_in_event", 
+				G_CALLBACK (username_prompt_focus_in),
+				NULL);
+			g_signal_connect (G_OBJECT (entry), "focus_out_event", 
+				G_CALLBACK (username_prompt_focus_out),
+				NULL);
+
+			g_signal_connect (G_OBJECT (entry), "key_press_event",
+				G_CALLBACK (gdm_evil_handler_pressed), NULL);
+
+			gdm_common_login_sound (gdm_config_get_string (GDM_KEY_SOUND_PROGRAM),
+				gdm_config_get_string (GDM_KEY_SOUND_ON_LOGIN_FILE),
+				gdm_config_get_bool   (GDM_KEY_SOUND_ON_LOGIN));
+		    }
+
+		    if(preventry)
+		    {
+			g_signal_connect_swapped((gpointer)preventry, "activate",
+				G_CALLBACK(gtk_widget_grab_focus),
+				entry);
+		    }
+
+		    preventry = entry;
+
+		} break;
+	    case GDM_MSG:
+	    case GDM_ERRBOX:
+		{
+		    GtkWidget* hbox;
+		    GtkWidget* label;
+		    GtkWidget* icon;
+
+		    hbox = gtk_hbox_new(FALSE, 0);
+		    label = gtk_label_new(msg);
+
+		    gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+
+		    if(style == GDM_ERRBOX)
+			icon = gtk_image_new_from_stock(
+				GTK_STOCK_DIALOG_ERROR,
+				GTK_ICON_SIZE_BUTTON);
+		    else
+			icon = gtk_image_new_from_stock(
+				GTK_STOCK_DIALOG_INFO,
+				GTK_ICON_SIZE_BUTTON);
+
+		    gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
+		    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+
+		    gtk_box_pack_start(GTK_BOX(pam_form_vbox), hbox, TRUE, TRUE, 2);
+
+		} break;
+	}
+
+	g_free(msg);
+    }
+
+    gtk_widget_show_all(pam_form_vbox);
+
+    gtk_box_pack_start(GTK_BOX(gui_form_vbox), pam_form_vbox, TRUE, TRUE, 0);
+    gtk_box_reorder_child(GTK_BOX(gui_form_vbox), pam_form_vbox, 1);
+
+    if(firstentry != -1)
+    {
+	gtk_widget_grab_focus(pam_form[firstentry].widget);	
+    }
+    else
+    {
+	gtk_widget_grab_focus(ok_button);	
+    }
+
+    // enter in last entry behaves like klicking the ok button
+    if(preventry)
+    {
+	g_signal_connect_swapped((gpointer)preventry, "activate",
+		G_CALLBACK (gtk_button_clicked),
+		ok_button);
+    }
+
+    gtk_widget_set_sensitive (start_again_button, TRUE);
+    gtk_widget_set_sensitive (ok_button, TRUE);
+
+    login_window_resize(FALSE);
+}
+
 static void
 process_operation (guchar       op_code,
 		   const gchar *args)
@@ -1532,13 +1811,13 @@ process_operation (guchar       op_code,
     char *tmp;
     gint i, x, y;
     GtkWidget *dlg;
-    static gboolean replace_msg = TRUE;
-    static gboolean messages_to_give = FALSE;
     gint lookup_status = SESSION_LOOKUP_SUCCESS;
     gchar *firstmsg = NULL;
     gchar *secondmsg = NULL;
     gint save_session = GTK_RESPONSE_NO;
     
+    dbg("code %c", op_code);
+
     /* Parse opcode */
     switch (op_code) {
     case GDM_SETLOGIN:
@@ -1551,7 +1830,27 @@ process_operation (guchar       op_code,
 	printf ("%c\n", STX);
 	fflush (stdout);
 	break;
+    
+    case GDM_PAM_FORM:
+
+	if(parse_form(args))
+	    gui_process_form();
+	break;
+
+    case GDM_ERRBOX:
+    case GDM_MSG:
+	// TODO: check whether a form is already active and switch
+	// to dialog if so?
+    case GDM_PROMPT:
+    case GDM_NOECHO:
+	    memset(pam_form, 0, sizeof(pam_form));
+	    pam_form[0].style = op_code;
+	    pam_form[0].msg = args;
+	    num_pam_form = 1;
+	    gui_process_form();
+	break;
 
+#if 0
     case GDM_PROMPT:
 	tmp = ve_locale_to_utf8 (args);
 	if (tmp != NULL && strcmp (tmp, _("Username:")) == 0) {
@@ -1675,6 +1974,7 @@ process_operation (guchar       op_code,
 
 	login_window_resize (FALSE /* force */);
 	break;
+#endif
 
     case GDM_ERRDLG:
 	/* we should be now fine for focusing new windows */
@@ -1822,17 +2122,31 @@ process_operation (guchar       op_code,
 
 	first_prompt = TRUE;
 
-	gtk_widget_set_sensitive (entry, TRUE);
 	gtk_widget_set_sensitive (ok_button, FALSE);
 	gtk_widget_set_sensitive (start_again_button, FALSE);
 
+	gtk_container_remove(GTK_CONTAINER(gui_form_vbox), pam_form_vbox);
+	pam_form_vbox = NULL;
+	num_pam_form = 0;
+
 	if (browser_ok && gdm_config_get_bool (GDM_KEY_BROWSER))
 	    gtk_widget_set_sensitive (GTK_WIDGET (browser), TRUE);
 
-	tmp = ve_locale_to_utf8 (args);
-	gtk_label_set_text (GTK_LABEL (msg), tmp);
-	g_free (tmp);
-	gtk_widget_show (GTK_WIDGET (msg));
+	if(!ve_string_empty(args)) {
+	    tmp = ve_locale_to_utf8 (args);
+	    dlg = ve_hig_dialog_new (NULL /* parent */,
+		    GTK_DIALOG_MODAL /* flags */,
+		    GTK_MESSAGE_INFO,
+		    GTK_BUTTONS_OK,
+		    "%s", tmp);
+	    gdm_wm_center_window (GTK_WINDOW (dlg));
+
+	    gdm_wm_no_login_focus_push ();
+	    gtk_dialog_run (GTK_DIALOG (dlg));
+	    gtk_widget_destroy (dlg);
+	    gdm_wm_no_login_focus_pop ();
+	    g_free (tmp);
+	}
 
 	printf ("%c\n", STX);
 	fflush (stdout);
@@ -1870,31 +2184,6 @@ process_operation (guchar       op_code,
 	/* Hide the login window now */
 	gtk_widget_hide (login);
 
-	if (messages_to_give) {
-		const char *oldtext;
-		oldtext = gtk_label_get_text (GTK_LABEL (msg));
-
-		if ( ! ve_string_empty (oldtext)) {
-			/* we should be now fine for focusing new windows */
-			gdm_wm_focus_new_windows (TRUE);
-
-			dlg = ve_hig_dialog_new (NULL /* parent */,
-						 GTK_DIALOG_MODAL /* flags */,
-						 GTK_MESSAGE_INFO,
-						 GTK_BUTTONS_OK,
-						 oldtext,
-						 "");
-			gtk_window_set_modal (GTK_WINDOW (dlg), TRUE);
-			gdm_wm_center_window (GTK_WINDOW (dlg));
-
-			gdm_wm_no_login_focus_push ();
-			gtk_dialog_run (GTK_DIALOG (dlg));
-			gtk_widget_destroy (dlg);
-			gdm_wm_no_login_focus_pop ();
-		}
-		messages_to_give = FALSE;
-	}
-
 	gdm_kill_thingies ();
 
 	gdk_flush ();
@@ -2047,15 +2336,8 @@ user_selected (GtkTreeSelection *selecti
 	char *login = NULL;
 	gtk_tree_model_get (tm, &iter, GREETER_ULIST_LOGIN_COLUMN,
 			      &login, -1);
+	dbg("login %s selecting_user %d", login, selecting_user);
 	if (login != NULL) {
-		const char *str = gtk_label_get_text (GTK_LABEL (label));
-
-		if (selecting_user &&
-		    str != NULL &&
-		    (strcmp (str, _("Username:")) == 0 ||
-		     strcmp (str, _("_Username:")) == 0)) {
-			gtk_entry_set_text (GTK_ENTRY (entry), login);
-		}
 #ifdef FIXME
 		selected_browser_user = g_strdup (login);
 #endif
@@ -2069,12 +2351,6 @@ user_selected (GtkTreeSelection *selecti
   }
 }
 
-static void
-browser_change_focus (GtkWidget *widget, GdkEventButton *event, gpointer data)
-{
-    gtk_widget_grab_focus (entry);	
-}
-
 static gboolean
 gdm_login_handle_pressed (GtkWidget *widget, GdkEventButton *event)
 {
@@ -2295,6 +2571,7 @@ window_browser_event (GtkWidget *window,
 	return FALSE;
 }
 
+#if 0
 static gboolean
 key_release_event (GtkWidget *entry, GdkEventKey *event, gpointer data)
 {
@@ -2307,6 +2584,7 @@ key_release_event (GtkWidget *entry, Gdk
 		return TRUE;
 	}
 
+#if 0 // don't try to be smart. maybe it's an optional entry field
 	/*
 	 * Set ok button to sensitive only if there are characters in
 	 * the entry field
@@ -2316,9 +2594,11 @@ key_release_event (GtkWidget *entry, Gdk
 		gtk_widget_set_sensitive (ok_button, TRUE);
 	else
 		gtk_widget_set_sensitive (ok_button, FALSE);
+#endif
 
 	return FALSE;
 }
+#endif
 
 static void
 gdm_set_welcomemsg (void)
@@ -2343,7 +2623,7 @@ gdm_login_gui_init (void)
     GtkTreeSelection *selection;
     GtkWidget *frame1, *frame2, *ebox;
     GtkWidget *mbox, *menu, *menubar, *item;
-    GtkWidget *stack, *hline1, *hline2, *handle;
+    GtkWidget *handle;
     GtkWidget *bbox = NULL;
     GtkWidget /**help_button,*/ *button_box;
     gint rows, i;
@@ -2584,10 +2864,6 @@ gdm_login_gui_init (void)
 			      G_CALLBACK (user_selected),
 			      NULL);
 
-	    g_signal_connect (browser, "button_release_event",
-			      G_CALLBACK (browser_change_focus),
-			      NULL);
-
 	    browser_model = (GtkTreeModel *)gtk_list_store_new (3,
 			     GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING);
 
@@ -2665,11 +2941,18 @@ gdm_login_gui_init (void)
 	    gtk_widget_set_size_request (logo_image, lw, lh);
     gtk_widget_show (GTK_WIDGET (logo_image));
 
+#if 0
     stack = gtk_table_new (7, 1, FALSE);
     gtk_widget_ref (stack);
     g_object_set_data_full (G_OBJECT (login), "stack", stack,
 			    (GDestroyNotify) gtk_widget_unref);
     gtk_widget_show (stack);
+#endif
+
+    gui_form_vbox = gtk_vbox_new(FALSE, 0);
+    g_object_set_data_full (G_OBJECT (login), "gui_form_vbox", gui_form_vbox,
+			    (GDestroyNotify) gtk_widget_unref);
+    gtk_widget_show (gui_form_vbox);
 
     /* Welcome msg */
     welcome = gtk_label_new (NULL);
@@ -2679,10 +2962,15 @@ gdm_login_gui_init (void)
     g_object_set_data_full (G_OBJECT (login), "welcome", welcome,
 			    (GDestroyNotify) gtk_widget_unref);
     gtk_widget_show (welcome);
+#if 0
     gtk_table_attach (GTK_TABLE (stack), welcome, 0, 1, 0, 1,
 		      (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
 		      (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 15);
+#endif
+    gtk_box_pack_start(GTK_BOX(gui_form_vbox), welcome, TRUE, TRUE, 0);
+
 
+#if 0
     /* Put in error box here */
     err_box = gtk_label_new (NULL);
     gtk_widget_set_name (err_box, "Error box");
@@ -2767,14 +3055,21 @@ gdm_login_gui_init (void)
     g_object_set_data_full (G_OBJECT (login), "msg", msg,
 			    (GDestroyNotify) gtk_widget_unref);
     gtk_widget_show (msg);
+#endif
+
+    /* cursor blinking is evil on remote displays, don't do it forever */
+    gdm_common_setup_blinking ();
 
     auto_timed_msg = gtk_label_new ("");
     gtk_widget_set_name (auto_timed_msg, "Message");
     gtk_label_set_line_wrap (GTK_LABEL (auto_timed_msg), TRUE);
     gtk_label_set_justify (GTK_LABEL (auto_timed_msg), GTK_JUSTIFY_LEFT);
+#if 0
     gtk_table_attach (GTK_TABLE (stack), auto_timed_msg, 0, 1, 7, 8,
 		      (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
 		      (GtkAttachOptions) (GTK_FILL), 0, 10);
+#endif
+    gtk_box_pack_start(GTK_BOX(gui_form_vbox), auto_timed_msg, TRUE, TRUE, 0);
     gtk_widget_set_size_request (auto_timed_msg, -1, 15);
     
     gtk_widget_ref (auto_timed_msg);
@@ -2786,17 +3081,16 @@ gdm_login_gui_init (void)
     gtk_widget_show (help_button);*/
 
     ok_button = gtk_button_new_from_stock (GTK_STOCK_OK);
-    GTK_WIDGET_UNSET_FLAGS (ok_button, GTK_CAN_FOCUS);
     g_signal_connect (G_OBJECT (ok_button), "clicked",
 		      G_CALLBACK (gdm_login_ok_button_press),
-		      entry);
+		      NULL);
     gtk_widget_show (ok_button);
 
     start_again_button = gtk_button_new_with_mnemonic (_("_Start Again"));
     GTK_WIDGET_UNSET_FLAGS (start_again_button, GTK_CAN_FOCUS);
     g_signal_connect (G_OBJECT (start_again_button), "clicked",
 		      G_CALLBACK (gdm_login_start_again_button_press),
-		      entry);
+		      /* entry */ NULL);
     gtk_widget_show (start_again_button);
 
     button_box = gtk_hbutton_box_new ();
@@ -2816,9 +3110,12 @@ gdm_login_gui_init (void)
 		      FALSE, TRUE, 0);
     gtk_widget_show (button_box);
     
+#if 0
     gtk_table_attach (GTK_TABLE (stack), button_box, 0, 1, 8, 9,
 		      (GtkAttachOptions) (GTK_FILL),
 		      (GtkAttachOptions) (GTK_FILL), 10, 10);
+#endif
+    gtk_box_pack_start(GTK_BOX(gui_form_vbox), button_box, TRUE, TRUE, 0);
 
     /* Put it nicely together */
 
@@ -2829,20 +3126,22 @@ gdm_login_gui_init (void)
 	    gtk_table_attach (GTK_TABLE (table), logo_frame, 0, 1, 1, 2,
 			      (GtkAttachOptions) (GTK_FILL),
 			      (GtkAttachOptions) (GTK_FILL), 0, 0);
-	    gtk_table_attach (GTK_TABLE (table), stack, 1, 2, 1, 2,
+	    gtk_table_attach (GTK_TABLE (table), gui_form_vbox, 1, 2, 1, 2,
 			      (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
 			      (GtkAttachOptions) (GTK_FILL), 0, 0);
     } else {
 	    gtk_table_attach (GTK_TABLE (table), logo_frame, 0, 1, 0, 1,
 			      (GtkAttachOptions) (0),
 			      (GtkAttachOptions) (GTK_FILL), 0, 0);
-	    gtk_table_attach (GTK_TABLE (table), stack, 1, 2, 0, 1,
+	    gtk_table_attach (GTK_TABLE (table), gui_form_vbox, 1, 2, 0, 1,
 			      (GtkAttachOptions) (0),
 			      (GtkAttachOptions) (GTK_FILL), 0, 0);
     }
+
+    statusbar = gtk_statusbar_new();
+    gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(statusbar), FALSE);
+    gtk_box_pack_start (GTK_BOX (mbox), statusbar, TRUE, TRUE, 0);
     
-    gtk_widget_grab_focus (entry);	
-    gtk_window_set_focus (GTK_WINDOW (login), entry);	
     g_object_set (G_OBJECT (login),
 		  "allow_grow", TRUE,
 		  "allow_shrink", TRUE,
@@ -2863,11 +3162,8 @@ gdm_login_gui_init (void)
 		      G_CALLBACK (gdm_login_focus_out),
 		      NULL);
 
-    gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
-
     /* normally disable the prompt first */
     if ( ! DOING_GDM_DEVELOPMENT) {
-	    gtk_widget_set_sensitive (entry, FALSE);
 	    gtk_widget_set_sensitive (ok_button, FALSE);
 	    gtk_widget_set_sensitive (start_again_button, FALSE);
     }
@@ -3392,6 +3688,9 @@ main (int argc, char *argv[])
 
     gtk_init (&argc, &argv);
 
+    /* AHHH, much better now! the clueless won't notice anyways */
+    gtk_rc_parse_string("gtk-key-theme-name = \"Emacs\"");
+
     if (ve_string_empty (g_getenv ("GDM_IS_LOCAL")))
 	disable_sys_config_chooser_buttons = TRUE;
 
Index: gdm2/gui/gdmuser.c
===================================================================
--- gdm2.orig/gui/gdmuser.c
+++ gdm2/gui/gdmuser.c
@@ -34,6 +34,10 @@
 #include "gdmuser.h"
 #include "gdmconfig.h"
 
+#define dbg(fmt, args...) do { gdm_common_info("%s:%d %s() - " fmt, __FILE__, __LINE__, __FUNCTION__, ##args); } while(0)
+#undef printf
+#define printf(fmt, args...) do { dbg("send " fmt, ##args); printf(fmt, ##args); } while(0)
+
 static time_t time_started;
 
 static GdmUser * 
@@ -332,6 +336,8 @@ gdm_users_init (GList **users,
     gboolean found_include = FALSE;
     int i;
 
+    dbg("start");
+
     time_started = time (NULL);
 	
     includes = g_strsplit (gdm_config_get_string (GDM_KEY_INCLUDE), ",", 0);
@@ -375,5 +381,7 @@ gdm_users_init (GList **users,
 
     g_strfreev (includes);
     g_strfreev (excludes);
+
+    dbg("done");
 }
 
Index: gdm2/gui/Makefile.am
===================================================================
--- gdm2.orig/gui/Makefile.am
+++ gdm2/gui/Makefile.am
@@ -62,6 +62,8 @@ libgdmwm_a_SOURCES = \
 	gdmwm.h
 
 libgdmcommon_a_SOURCES = \
+	gdmpamform.c		\
+	gdmpamform.h		\
 	gdmlanguages.c		\
 	gdmlanguages.h		\
 	gdmuser.c		\
Index: gdm2/gui/gdmpamform.c
===================================================================
--- /dev/null
+++ gdm2/gui/gdmpamform.c
@@ -0,0 +1,68 @@
+/* GDM - The Gnome Display Manager
+ * Copyright (C) 2007 SUSE Linux Products GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+
+#include "gdm.h"
+#include "gdmcommon.h"
+#include "gdmpamform.h"
+
+gboolean parse_form(const char* args)
+{
+    unsigned num_msg = *args++;
+    unsigned i = 0;
+
+    num_msg -= '\n';
+
+    memset(pam_form, 0, sizeof(pam_form));
+
+    while(i < num_msg)
+    {
+	unsigned style = *args++;
+	char* s = strchr(args, EOX);
+	if(!s) {
+	    printf ("%c\n", STX);
+	    fflush (stdout);
+	    gdm_common_fail_greeter("invalid message in GDM_PAM_FORM");
+	    return FALSE;
+	}
+	*s = '\0';
+	switch(style) {
+	    case GDM_PROMPT:
+	    case GDM_NOECHO:
+	    case GDM_ERRBOX:
+	    case GDM_MSG:
+		break;
+
+	    default:
+		printf ("%c\n", STX);
+		fflush (stdout);
+		gdm_common_fail_greeter("Unexpected message type in GDM_PAM_FORM: 0x%hhx", style);
+		return FALSE;
+	}
+	pam_form[i].style = style;
+	pam_form[i].msg = args;
+	args += strlen(args)+1;
+	++i;
+    }
+    num_pam_form = num_msg;
+    
+    return TRUE;
+}
Index: gdm2/gui/gdmpamform.h
===================================================================
--- /dev/null
+++ gdm2/gui/gdmpamform.h
@@ -0,0 +1,35 @@
+/* GDM - The Gnome Display Manager
+ * Copyright (C) 2007 SUSE Linux Products GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef GDM_PAMFORM_H
+#define GDM_PAMFORM_H
+
+typedef struct {
+    unsigned style;
+    const char* msg;
+    GtkWidget* widget;
+} PamForm;
+
+extern PamForm pam_form[256-'\n']; /* can't have more than that anyways */
+extern unsigned num_pam_form;
+
+gboolean parse_form(const char* args);
+
+#endif /* GDM_PAMFORM_H */
+
+/* EOF */
Index: gdm2/gui/greeter/greeter.c
===================================================================
--- gdm2.orig/gui/greeter/greeter.c
+++ gdm2/gui/greeter/greeter.c
@@ -45,6 +45,7 @@
 #include "gdmcommon.h"
 #include "gdmconfig.h"
 #include "gdmsession.h"
+#include "gdmpamform.h"
 
 #include "greeter.h"
 #include "greeter_configuration.h"
@@ -61,6 +62,8 @@
 #include "greeter_session.h"
 #include "greeter_system.h"
 
+#define dbg(fmt, args...) do { gdm_common_info("%s:%d %s() - " fmt, __FILE__, __LINE__, __FUNCTION__, ##args); } while(0)
+
 gboolean DOING_GDM_DEVELOPMENT = FALSE;
 
 GtkWidget *window;
@@ -87,8 +90,20 @@ extern GtkButton *gtk_start_again_button
 gboolean greeter_probably_login_prompt = FALSE;
 static gboolean first_prompt = TRUE;
 
+static void gui_process_form(void);
 static void process_operation (guchar opcode, const gchar *args);
 
+static void entry_activate_handler (GtkWidget* widget, gpointer user_data);
+
+PamForm pam_form[256-'\n'] = {{0, NULL,  NULL}};
+unsigned num_pam_form;
+
+#define FORM_SUPPORTED -1U
+unsigned form_serialize_idx = FORM_SUPPORTED;
+static GtkWidget* pam_form_vbox;
+static char* form_args; // strings in pam_form point in here
+static char* form_reply_str;
+
 void
 greeter_ignore_buttons (gboolean val)
 {
@@ -135,6 +150,8 @@ greeter_ctrl_handler (GIOChannel *source
     gchar *p;
     gsize len;
 
+    dbg("");
+
     /* If this is not incoming i/o then return */
     if (cond != G_IO_IN) 
       return TRUE;
@@ -165,6 +182,222 @@ greeter_ctrl_handler (GIOChannel *source
 }
 
 static void
+form_serialize(void)
+{
+    unsigned i, needed_empty_replies = 0;
+    gboolean got_prompt = FALSE;
+
+    dbg("%d", form_serialize_idx);
+
+    if(form_serialize_idx >= num_pam_form) {
+	gdm_common_fail_greeter("serialize index out of range");
+    }
+
+    for(i = form_serialize_idx; !got_prompt && i < num_pam_form; ++i)
+    {
+	switch(pam_form[i].style)
+	{
+	    case GDM_MSG:
+		greeter_item_pam_message (pam_form[i].msg);
+		++needed_empty_replies;
+		break;
+	    case GDM_ERRBOX:
+		greeter_item_pam_error (pam_form[i].msg);
+		++needed_empty_replies;
+		break;
+	    default:
+		got_prompt = TRUE;
+		process_operation(pam_form[i].style, pam_form[i].msg);
+		break;
+	}
+    }
+    form_serialize_idx = i;
+
+    if(needed_empty_replies)
+    {
+	if(form_reply_str)
+	    form_reply_str = g_realloc(form_reply_str, strlen(form_reply_str)+1+needed_empty_replies);
+	else
+	    form_reply_str = g_malloc0(needed_empty_replies+1);
+	memset(form_reply_str+strlen(form_reply_str), EOX, needed_empty_replies);
+    }
+
+    if(!got_prompt)
+    {
+	GreeterItemInfo *entry_info = greeter_lookup_id ("user-pw-entry");
+	if (entry_info && entry_info->item &&
+		GNOME_IS_CANVAS_WIDGET (entry_info->item) &&
+		GTK_IS_ENTRY (GNOME_CANVAS_WIDGET (entry_info->item)->widget))
+	{
+	   entry_activate_handler(GNOME_CANVAS_WIDGET(entry_info->item)->widget, NULL);
+	}
+    }
+}
+
+static void
+gui_process_form(void)
+{
+    int i;
+    int firstentry = -1;
+    GtkWidget* preventry = NULL;
+    GtkWidget* form_frame;
+    GreeterItemInfo* item = greeter_lookup_id ("pam-form");
+
+    dbg("num_pam_form %d", num_pam_form);
+    
+
+    if (item && item->item
+    && GNOME_IS_CANVAS_WIDGET (item->item)
+    && GTK_IS_FRAME (GNOME_CANVAS_WIDGET (item->item)->widget)) {
+	form_frame = GNOME_CANVAS_WIDGET (item->item)->widget;
+    }
+    else
+    {
+	return;
+    }
+
+    pam_form_vbox = gtk_bin_get_child(GTK_BIN(form_frame));
+
+    if(pam_form_vbox)
+	gtk_container_remove(GTK_CONTAINER(form_frame), pam_form_vbox);
+
+    pam_form_vbox = gtk_vbox_new(FALSE, 0);
+
+    for (i = 0; i < num_pam_form; ++i)
+    {
+	unsigned style = pam_form[i].style;
+	char* msg = ve_locale_to_utf8(pam_form[i].msg);
+
+	dbg("%c %s", style, msg);
+
+	switch(style)
+	{
+	    case GDM_NOECHO:
+	    case GDM_PROMPT:
+		{
+		    GtkWidget* hbox;
+		    GtkWidget* label;
+		    GtkWidget* entry;
+		    gboolean is_username;
+		    
+		    if((is_username = !strcmp(msg, _("Username:"))))
+			msg = _("_Username:");
+
+		    hbox = gtk_hbox_new(FALSE, 4);
+		    label = gtk_label_new_with_mnemonic(msg);
+		    pam_form[i].widget = entry = gtk_entry_new();
+		    //gtk_widget_set_size_request (entry, 200, -1);
+
+		    gtk_entry_set_max_length (GTK_ENTRY (entry), PW_ENTRY_SIZE);
+		    gtk_entry_set_visibility(GTK_ENTRY(entry), (style == GDM_PROMPT));
+
+		    gdm_common_setup_blinking_entry (entry);
+
+		    gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry);
+
+		    gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
+		    gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, TRUE, 0);
+
+		    gtk_box_pack_start(GTK_BOX(pam_form_vbox), hbox, TRUE, TRUE, 0);
+
+		    if(firstentry == -1)
+			firstentry = i;
+
+		    if(is_username)
+		    {
+			msg = NULL; // static string from gettext, prevent g_free()
+
+#warning FIXME
+#if 0
+			if(selected_user)
+			    gtk_entry_set_text (GTK_ENTRY(entry), selected_user);
+
+			g_signal_connect (G_OBJECT (entry), "focus_in_event", 
+				G_CALLBACK (username_prompt_focus_in),
+				NULL);
+			g_signal_connect (G_OBJECT (entry), "focus_out_event", 
+				G_CALLBACK (username_prompt_focus_out),
+				NULL);
+
+			g_signal_connect (G_OBJECT (entry), "key_press_event",
+				G_CALLBACK (gdm_evil_handler_pressed), NULL);
+#endif
+
+			gdm_common_login_sound (gdm_config_get_string (GDM_KEY_SOUND_PROGRAM),
+				gdm_config_get_string (GDM_KEY_SOUND_ON_LOGIN_FILE),
+				gdm_config_get_bool   (GDM_KEY_SOUND_ON_LOGIN));
+		    }
+
+		    if(preventry)
+		    {
+			g_signal_connect_swapped((gpointer)preventry, "activate",
+				G_CALLBACK(gtk_widget_grab_focus),
+				entry);
+		    }
+
+		    preventry = entry;
+
+		} break;
+	    case GDM_MSG:
+	    case GDM_ERRBOX:
+		{
+		    GtkWidget* hbox;
+		    GtkWidget* label;
+		    GtkWidget* icon;
+
+		    hbox = gtk_hbox_new(FALSE, 0);
+		    label = gtk_label_new(msg);
+
+		    gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+
+		    if(style == GDM_ERRBOX)
+			icon = gtk_image_new_from_stock(
+				GTK_STOCK_DIALOG_ERROR,
+				GTK_ICON_SIZE_BUTTON);
+		    else
+			icon = gtk_image_new_from_stock(
+				GTK_STOCK_DIALOG_INFO,
+				GTK_ICON_SIZE_BUTTON);
+
+		    gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, FALSE, 0);
+		    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+
+		    gtk_box_pack_start(GTK_BOX(pam_form_vbox), hbox, TRUE, TRUE, 2);
+
+		} break;
+	}
+
+	g_free(msg);
+    }
+
+    gtk_widget_show_all(pam_form_vbox);
+
+    gtk_container_add(GTK_CONTAINER(form_frame), pam_form_vbox);
+
+    if(firstentry != -1)
+    {
+	gtk_widget_grab_focus(pam_form[firstentry].widget);	
+    }
+    else
+    {
+	gtk_widget_grab_focus(GTK_WIDGET(gtk_ok_button));	
+    }
+
+    // enter in last entry behaves like klicking the ok button
+    if(preventry)
+    {
+	g_signal_connect_swapped((gpointer)preventry, "activate",
+		G_CALLBACK (gtk_button_clicked),
+		gtk_ok_button);
+    }
+
+    if(gtk_start_again_button)
+	gtk_widget_set_sensitive (GTK_WIDGET(gtk_start_again_button), TRUE);
+    if(gtk_ok_button)
+	gtk_widget_set_sensitive (GTK_WIDGET(gtk_ok_button), TRUE);
+}
+
+static void
 process_operation (guchar       op_code,
 		   const gchar *args)
 {
@@ -180,6 +413,8 @@ process_operation (guchar       op_code,
     gchar *secondmsg = NULL;
     gint save_session = GTK_RESPONSE_NO;
 
+    dbg("%c %s", op_code, args);
+
     /* Parse opcode */
     switch (op_code) {
     case GDM_SETLOGIN:
@@ -191,6 +426,33 @@ process_operation (guchar       op_code,
 	fflush (stdout);
 	break;
 
+    case GDM_PAM_FORM:
+	{
+	    GreeterItemInfo* item = greeter_lookup_id ("pam-form");
+	    if (item && item->item
+		    && GNOME_IS_CANVAS_WIDGET (item->item)
+		    && GTK_IS_FRAME (GNOME_CANVAS_WIDGET (item->item)->widget)) {
+		form_serialize_idx = FORM_SUPPORTED;
+	    } else {
+		form_serialize_idx = 0;
+		g_free(form_reply_str);
+		form_reply_str = NULL;
+		g_free(form_args);
+		// make copy as we leave this function
+		form_args = g_strdup(args);
+		args = form_args;
+	    }
+
+	    if(parse_form(args))
+	    {
+		if(form_serialize_idx == FORM_SUPPORTED)
+		    gui_process_form();
+		else
+		    form_serialize();
+	    }
+	}
+	break;
+
     case GDM_PROMPT:
 	tmp = ve_locale_to_utf8 (args);
 	if (tmp != NULL && strcmp (tmp, _("Username:")) == 0) {
@@ -540,6 +802,45 @@ key_press_event (GtkWidget *widget, GdkE
   return FALSE;
 }
 
+static void
+entry_activate_handler (GtkWidget *widget,
+                    gpointer         user_data)
+{
+    const char* str;
+    char* tmp;
+
+    g_return_if_fail(GTK_IS_ENTRY(widget));
+
+    if ((str = greeter_item_pam_login (GTK_ENTRY (widget))))
+    {
+	if(form_serialize_idx != FORM_SUPPORTED)
+	{
+	    if(form_reply_str)
+		form_reply_str = g_realloc(form_reply_str, strlen(form_reply_str)+2+strlen(str));
+	    else
+		form_reply_str = g_malloc0(strlen(str)+1);
+	    tmp = ve_locale_from_utf8(str);
+	    sprintf(form_reply_str+strlen(form_reply_str), "%s%c", tmp, EOX);
+	    g_free(tmp);
+	}
+    }
+
+    dbg("%d %d %s", form_serialize_idx, num_pam_form, form_reply_str);
+
+    if(form_serialize_idx < num_pam_form)
+    {
+	form_serialize();
+    }
+    else
+    {
+	fputc(STX, stdout);
+	puts(form_reply_str);
+	fflush (stdout);
+	g_free(form_reply_str);
+	form_reply_str = NULL;
+    }
+}
+
 /*
  * The buttons with these handlers never appear in the F10 menu,
  * so they can make use of callback data.
@@ -548,6 +849,7 @@ static void
 greeter_ok_handler (GreeterItemInfo *info,
                     gpointer         user_data)
 {
+    const char* str;
    if (ignore_buttons == FALSE)
      {
        GreeterItemInfo *entry_info = greeter_lookup_id ("user-pw-entry");
@@ -557,8 +859,7 @@ greeter_ok_handler (GreeterItemInfo *inf
          {
            GtkWidget *entry;
            entry = GNOME_CANVAS_WIDGET (entry_info->item)->widget;
-           greeter_ignore_buttons (TRUE);
-           greeter_item_pam_login (GTK_ENTRY (entry), entry_info);
+	   entry_activate_handler(entry, NULL);
          }
     }
 }
@@ -580,6 +881,8 @@ greeter_cancel_handler (GreeterItemInfo 
 static void
 greeter_setup_items (void)
 {
+  GreeterItemInfo* entry_info;
+
   greeter_item_clock_setup ();
   greeter_item_pam_setup ();
 
@@ -588,6 +891,18 @@ greeter_setup_items (void)
 
   greeter_item_capslock_setup (window);
   greeter_item_timed_setup ();
+
+
+  entry_info = greeter_lookup_id ("user-pw-entry");
+  if (entry_info && entry_info->item &&
+      GNOME_IS_CANVAS_WIDGET (entry_info->item) &&
+      GTK_IS_ENTRY (GNOME_CANVAS_WIDGET (entry_info->item)->widget))
+  {
+    g_signal_connect((gpointer)GNOME_CANVAS_WIDGET (entry_info->item)->widget, "activate",
+	G_CALLBACK (entry_activate_handler),
+	NULL);
+  }
+
   greeter_item_register_action_callback ("ok_button",
 					 greeter_ok_handler,
 					 (gpointer) window);
@@ -1126,6 +1441,7 @@ gdm_event (GSignalInvocationHint *ihint,
             && event->button.button == 3)
                 event->button.button = 1;
 
+#if 0
         /* Support Ctrl-U for blanking the username/password entry */
         if (event->type == GDK_KEY_PRESS &&
             (event->key.state & GDK_CONTROL_MASK) &&
@@ -1142,6 +1458,7 @@ gdm_event (GSignalInvocationHint *ihint,
 			gtk_entry_set_text (GTK_ENTRY (entry), "");
 		}
 	}
+#endif
 
         return TRUE;
 }
@@ -1493,6 +1810,16 @@ main (int argc, char *argv[])
   greeter_item_ulist_enable ();
   greeter_item_ulist_check_show_userlist ();
 
+  if G_UNLIKELY (DOING_GDM_DEVELOPMENT) {
+    memset(pam_form, 0, sizeof(pam_form));
+    pam_form[0].style = GDM_PROMPT;
+    pam_form[0].msg = "Username:";
+    pam_form[1].style = GDM_NOECHO;
+    pam_form[1].msg = "Password:";
+    num_pam_form = 2;
+    gui_process_form();
+  }
+
   /* can it ever happen that it'd be NULL here ??? */
   if G_UNLIKELY (window->window != NULL)
     {
Index: gdm2/gui/greeter/greeter_canvas_item.c
===================================================================
--- gdm2.orig/gui/greeter/greeter_canvas_item.c
+++ gdm2/gui/greeter/greeter_canvas_item.c
@@ -446,8 +446,9 @@ greeter_item_create_canvas_item (Greeter
     break;
 
   case GREETER_ITEM_TYPE_ENTRY:
+#if 1
     entry = gtk_entry_new ();
-    gtk_widget_set_name (entry, "user-pw-entry");
+    gtk_widget_set_name (entry, item->id);
     gtk_entry_set_has_frame (GTK_ENTRY (entry), FALSE);
 
     if (gdm_config_get_bool (GDM_KEY_ENTRY_INVISIBLE))
@@ -490,6 +491,15 @@ greeter_item_create_canvas_item (Greeter
 	    /* FIXME: how to make this accessible??? */
     }
 
+    /* cursor blinking is evil on remote displays, don't do it forever */
+    gdm_common_setup_blinking_entry (entry);
+#else
+    entry =  gtk_frame_new("form frame");
+    gtk_widget_set_name (entry, item->id);
+    get_gdk_color_from_rgb (&c, item->data.text.colors[GREETER_ITEM_STATE_NORMAL]);
+    gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &c);
+#endif
+
     item->item = gnome_canvas_item_new (group,
 					GNOME_TYPE_CANVAS_WIDGET,
 					"widget", entry,
@@ -499,9 +509,6 @@ greeter_item_create_canvas_item (Greeter
 					"width", (double)rect.width,
 					NULL);
 
-    /* cursor blinking is evil on remote displays, don't do it forever */
-    gdm_common_setup_blinking_entry (entry);
-
     break;
 
   case GREETER_ITEM_TYPE_LIST:
Index: gdm2/gui/greeter/greeter_parser.c
===================================================================
--- gdm2.orig/gui/greeter/greeter_parser.c
+++ gdm2/gui/greeter/greeter_parser.c
@@ -430,11 +430,17 @@ parse_stock (xmlNodePtr node,
 	    }
 	    
 	    if (is_error)
+	    {
+		      g_free(*translated_text);
+		      *translated_text = g_strdup("XXX");
+		      is_error = FALSE;
+	    }
+	    if (is_error)
 	      {
 		g_set_error (error,
 		     GREETER_PARSER_ERROR,
 		     GREETER_PARSER_ERROR_BAD_SPEC,
-		     "Bad stock label type");
+		     "Bad stock label type %s", prop);
 		xmlFree (prop);
 		return FALSE;
 	      }
Index: gdm2/gui/greeter/greeter_item_pam.c
===================================================================
--- gdm2.orig/gui/greeter/greeter_item_pam.c
+++ gdm2/gui/greeter/greeter_item_pam.c
@@ -37,6 +37,8 @@
 #include "gdmwm.h"
 #include "gdmcommon.h"
 
+#define dbg(fmt, args...) do { gdm_common_info("%s:%d %s() - " fmt, __FILE__, __LINE__, __FUNCTION__, ##args); } while(0)
+
 static gboolean messages_to_give = FALSE;
 static gboolean replace_msg = TRUE;
 static guint err_box_clear_handler = 0;
@@ -117,8 +119,8 @@ set_text (GreeterItemInfo *info, const c
 					      info->item);
 }
 
-void
-greeter_item_pam_login (GtkEntry *entry, GreeterItemInfo *info)
+const char*
+greeter_item_pam_login (GtkEntry *entry)
 {
   const char *str;
   char *tmp;
@@ -132,6 +134,7 @@ greeter_item_pam_login (GtkEntry *entry,
   greeter_ignore_buttons (TRUE);
 
   str = gtk_entry_get_text (GTK_ENTRY (entry));
+  dbg("%s", str);
   if (greeter_probably_login_prompt &&
       /* evilness */
       evil (entry, str))
@@ -139,19 +142,9 @@ greeter_item_pam_login (GtkEntry *entry,
       /* obviously being 100% reliable is not an issue for
          this test */
       gtk_entry_set_text (GTK_ENTRY (entry), "");
-      return;
+      return NULL;
     }
 
-  if (greeter_probably_login_prompt &&
-      ve_string_empty (str) &&
-      greeter_item_timed_is_timed ())
-    {
-      /* timed interruption */
-      printf ("%c%c%c\n", STX, BEL, GDM_INTERRUPT_TIMED_LOGIN);
-      fflush (stdout);
-      return;
-    }
-  
   gtk_widget_set_sensitive (GTK_WIDGET (entry), FALSE);
 
   /* clear the err_box */
@@ -165,11 +158,9 @@ greeter_item_pam_login (GtkEntry *entry,
     greeter_item_pam_error_set (FALSE);
     set_text (error_info, "");
   }
-  
-  tmp = ve_locale_from_utf8 (str);
-  printf ("%c%s\n", STX, tmp);
-  fflush (stdout);
-  g_free (tmp);
+  dbg("%s", str);
+
+  return str;
 }
 
 static gboolean
@@ -184,6 +175,7 @@ pam_key_release_event (GtkWidget *entry,
        const char *login_string;
 	GtkWidget *entry = GNOME_CANVAS_WIDGET (entry_info->item)->widget;
 
+#if 0
        if ((event->keyval == GDK_Tab ||
             event->keyval == GDK_KP_Tab) &&
            (event->state & (GDK_CONTROL_MASK|GDK_MOD1_MASK|GDK_SHIFT_MASK)) == 0)
@@ -191,6 +183,7 @@ pam_key_release_event (GtkWidget *entry,
 		greeter_item_pam_login (GTK_ENTRY (entry), entry_info);
 		return TRUE;
            }
+#endif
 
        if (gtk_ok_button != NULL)
           {
@@ -235,8 +228,10 @@ greeter_item_pam_setup (void)
           gtk_widget_set_sensitive (entry, FALSE);
 	}
 
+#if 0
       g_signal_connect (entry, "activate",
 			G_CALLBACK (greeter_item_pam_login), entry_info);
+#endif
       g_signal_connect (G_OBJECT (entry), "key_release_event",
 		        G_CALLBACK (pam_key_release_event), NULL);
     }
Index: gdm2/gui/greeter/greeter_item_pam.h
===================================================================
--- gdm2.orig/gui/greeter/greeter_item_pam.h
+++ gdm2/gui/greeter/greeter_item_pam.h
@@ -29,7 +29,7 @@ void greeter_item_pam_message (const cha
 void greeter_item_pam_error (const char *message);
 void greeter_item_pam_set_user (const char *user);
 void greeter_item_pam_leftover_messages (void);
-void greeter_item_pam_login (GtkEntry *entry, GreeterItemInfo *info);
+const char* greeter_item_pam_login (GtkEntry *entry);
 
 extern gchar *greeter_current_user;
 


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