Index: configure.in =================================================================== --- configure.in.orig 2006-10-03 17:54:09.000000000 +0100 +++ configure.in 2006-10-30 12:58:33.000000000 +0000 @@ -1529,6 +1529,16 @@ GTK_EXTRA_CFLAGS="$msnative_struct" fi +AC_ARG_ENABLE(display-migration, + [AC_HELP_STRING([--enable-display-migration], + [include support for GPE_CHANGE_DISPLAY protocol])], + enable_migration=yes, enable_migration=no) +if test "$enable_migration" = "yes"; then + AC_DEFINE([ENABLE_MIGRATION], 1, [Define if display migration is enabled]) + GTK_DEP_LIBS="$GTK_DEP_LIBS -lgcrypt" +fi +AM_CONDITIONAL(ENABLE_MIGRATION, test $enable_migration = "yes") + AC_SUBST(GTK_PACKAGES) AC_SUBST(GTK_EXTRA_LIBS) AC_SUBST(GTK_EXTRA_CFLAGS) Index: gtk/Makefile.am =================================================================== --- gtk/Makefile.am.orig 2006-10-02 18:27:53.000000000 +0100 +++ gtk/Makefile.am 2006-10-30 12:59:14.000000000 +0000 @@ -589,6 +589,11 @@ gtkwindow-decorate.c \ gtkwindow.c \ $(gtk_clipboard_dnd_c_sources) + +if ENABLE_MIGRATION +gtk_base_c_sources += gtkmigration.c +endif + gtk_c_sources = $(gtk_base_c_sources) gtk_all_c_sources = $(gtk_base_c_sources) Index: gtk/gtkmain.c =================================================================== --- gtk/gtkmain.c.orig 2006-09-03 06:31:21.000000000 +0100 +++ gtk/gtkmain.c 2006-10-30 12:56:34.000000000 +0000 @@ -507,6 +507,10 @@ _gtk_accel_map_init (); _gtk_rc_init (); +#ifdef ENABLE_MIGRATION + gtk_migration_init (); +#endif + /* Set the 'initialized' flag. */ gtk_initialized = TRUE; Index: gtk/gtkmigration.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ gtk/gtkmigration.c 2006-10-30 12:56:34.000000000 +0000 @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2003, 2005 Philip Blundell <philb@gnu.org> + * + * 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. + */ + +#include <stdlib.h> +#include <ctype.h> +#include <libintl.h> +#include <string.h> +#include <assert.h> + +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include <gcrypt.h> + +#include "gtk.h" +#include "gdk.h" +#include "x11/gdkx.h" + +#define _(x) gettext(x) + +static GdkAtom string_gdkatom, display_change_gdkatom; +static GdkAtom rsa_challenge_gdkatom; + +#define DISPLAY_CHANGE_SUCCESS 0 +#define DISPLAY_CHANGE_UNABLE_TO_CONNECT 1 +#define DISPLAY_CHANGE_NO_SUCH_SCREEN 2 +#define DISPLAY_CHANGE_AUTHENTICATION_BAD 3 +#define DISPLAY_CHANGE_INDETERMINATE_ERROR 4 + +static gboolean no_auth; + +static GSList *all_widgets; + +static gboolean gtk_migration_initialised; + +#define CHALLENGE_LEN 64 + +gchar *gtk_migration_auth_challenge_string; + +static unsigned char challenge_bytes[CHALLENGE_LEN]; +static unsigned long challenge_seq; + +#define hexbyte(x) ((x) >= 10 ? (x) + 'a' - 10 : (x) + '0') + +struct rsa_key +{ + gcry_mpi_t n, e, d, p, q, u; +}; + +static gcry_mpi_t +mpi_from_sexp (gcry_sexp_t r, char *tag) +{ + gcry_sexp_t s = gcry_sexp_find_token (r, tag, 0); + return gcry_sexp_nth_mpi (s, 1, GCRYMPI_FMT_USG); +} + +static char * +hex_from_mpi (gcry_mpi_t m) +{ + char *buf; + gcry_mpi_aprint (GCRYMPI_FMT_HEX, (void *)&buf, NULL, m); + return buf; +} + +static void +gtk_migration_crypt_create_hash (char *display, char *challenge, size_t len, char *result) +{ + size_t dlen = strlen (display); + gchar *buf = g_malloc (dlen + 1 + len); + strcpy (buf, display); + memcpy (buf + dlen + 1, challenge, len); + gcry_md_hash_buffer (GCRY_MD_SHA1, result, buf, len + dlen + 1); + g_free (buf); +} + +static int +do_encode_md (const unsigned char *digest, size_t digestlen, int algo, + unsigned int nbits, gcry_mpi_t *r_val) +{ + int nframe = (nbits+7) / 8; + unsigned char *frame; + int i, n; + unsigned char asn[100]; + size_t asnlen; + + asnlen = sizeof(asn); + if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) + return -1; + + if (digestlen + asnlen + 4 > nframe ) + return -1; + + /* We encode the MD in this way: + * + * 0 1 PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) + * + * PAD consists of FF bytes. + */ + frame = g_malloc (nframe); + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* block type */ + i = nframe - digestlen - asnlen -3 ; + assert ( i > 1 ); + memset ( frame+n, 0xff, i ); n += i; + frame[n++] = 0; + memcpy ( frame+n, asn, asnlen ); n += asnlen; + memcpy ( frame+n, digest, digestlen ); n += digestlen; + assert ( n == nframe ); + + gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, nframe, &nframe); + g_free (frame); + return 0; +} + +static gboolean +gtk_migration_crypt_check_signature (struct rsa_key *k, char *hash, char *sigbuf) +{ + gcry_mpi_t mpi, mpi2; + gcry_sexp_t data, sig, key; + int rc; + + do_encode_md (hash, 20, GCRY_MD_SHA1, 1024, &mpi); + + gcry_sexp_build (&data, NULL, "(data (value %m))", mpi); + + gcry_mpi_release (mpi); + + gcry_sexp_build (&key, NULL, "(public-key (rsa (n %m) (e %m)))", k->n, k->e); + + if (gcry_mpi_scan (&mpi2, GCRYMPI_FMT_HEX, sigbuf, 0, NULL)) + { + gcry_sexp_release (data); + return FALSE; + } + + gcry_sexp_build (&sig, NULL, "(sig-val (rsa (s %m)))", mpi2); + + rc = gcry_pk_verify (sig, data, key); + + gcry_sexp_release (data); + gcry_sexp_release (key); + gcry_sexp_release (sig); + gcry_mpi_release (mpi2); + + if (rc) + return FALSE; + + return TRUE; +} + +static void +gtk_migration_auth_update_challenge (void) +{ + int i; + unsigned char *p; + + if (gtk_migration_auth_challenge_string == NULL) + gtk_migration_auth_challenge_string = g_malloc ((CHALLENGE_LEN * 2) + 9); + + p = gtk_migration_auth_challenge_string; + + for (i = 0; i < CHALLENGE_LEN; i++) + { + *p++ = hexbyte (challenge_bytes[i] >> 4); + *p++ = hexbyte (challenge_bytes[i] & 15); + } + + sprintf (p, "%08lx", challenge_seq++); +} + +static void +gtk_migration_auth_generate_challenge (void) +{ + gcry_randomize (challenge_bytes, sizeof (challenge_bytes), GCRY_STRONG_RANDOM); + gtk_migration_auth_update_challenge (); +} + +static struct rsa_key * +parse_pubkey (char *s) +{ + struct rsa_key *r; + gcry_mpi_t n, e; + gchar *sp; + + sp = strtok (s, " \n"); + gcry_mpi_scan (&e, GCRYMPI_FMT_HEX, sp, 0, NULL); + sp = strtok (NULL, " \n"); + gcry_mpi_scan (&n, GCRYMPI_FMT_HEX, sp, 0, NULL); + + r = g_malloc0 (sizeof (struct rsa_key)); + r->e = e; + r->n = n; + return r; +} + +static struct rsa_key * +lookup_pubkey (u_int32_t id) +{ + const gchar *home_dir = g_get_home_dir (); + gchar *filename = g_strdup_printf ("%s/.gpe/migrate/public", home_dir); + FILE *fp = fopen (filename, "r"); + struct rsa_key *r = NULL; + + if (fp) + { + while (!feof (fp)) + { + char buffer[4096]; + if (fgets (buffer, 4096, fp)) + { + char *p; + u_int32_t this_id = strtoul (buffer, &p, 16); + if (p != buffer && *p == ' ') + { +#ifdef DEBUG + fprintf (stderr, "found id %x\n", this_id); +#endif + if (this_id == id) + { + r = parse_pubkey (++p); + break; + } + } + } + } + fclose (fp); + } + + g_free (filename); + return r; +} + +static void +free_pubkey (struct rsa_key *k) +{ + gcry_mpi_release (k->n); + gcry_mpi_release (k->e); + + g_free (k); +} + +static gboolean +gtk_migration_auth_validate_request (char *display, char *data) +{ + u_int32_t key_id; + char *ep; + char *p; + struct rsa_key *k; + char hash[20]; + gboolean rc; + + p = strchr (data, ' '); + if (p == NULL) + return FALSE; + *p++ = 0; + + key_id = strtoul (data, &ep, 16); + if (*ep) + return FALSE; + + k = lookup_pubkey (key_id); + if (k == NULL) + return FALSE; + + gtk_migration_crypt_create_hash (display, gtk_migration_auth_challenge_string, + strlen (gtk_migration_auth_challenge_string), hash); + + rc = gtk_migration_crypt_check_signature (k, hash, p); + + free_pubkey (k); + + return rc; +} + +static int +do_change_display (GtkWidget *w, char *display_name) +{ + GdkDisplay *newdisplay; + guint screen_nr = 1; + guint i; + + if (display_name[0] == 0) + return DISPLAY_CHANGE_INDETERMINATE_ERROR; + + i = strlen (display_name) - 1; + while (i > 0 && isdigit (display_name[i])) + i--; + + if (display_name[i] == '.') + { + screen_nr = atoi (display_name + i + 1); + display_name[i] = 0; + } + + newdisplay = gdk_display_open (display_name); + if (newdisplay) + { + GdkScreen *screen = gdk_display_get_screen (newdisplay, screen_nr); + if (screen) + { + gtk_window_set_screen (GTK_WINDOW (w), screen); + gdk_display_manager_set_default_display (gdk_display_manager_get (), + newdisplay); + return DISPLAY_CHANGE_SUCCESS; + } + else + return DISPLAY_CHANGE_NO_SUCH_SCREEN; + } + + return DISPLAY_CHANGE_UNABLE_TO_CONNECT; +} + +static void +set_challenge_on_window (GdkWindow *window) +{ + gdk_property_change (window, rsa_challenge_gdkatom, string_gdkatom, + 8, GDK_PROP_MODE_REPLACE, gtk_migration_auth_challenge_string, + strlen (gtk_migration_auth_challenge_string)); +} + +static void +update_challenge_on_windows (void) +{ + GSList *i; + + gtk_migration_auth_update_challenge (); + + for (i = all_widgets; i; i = i->next) + { + GtkWidget *w = GTK_WIDGET (i->data); + if (w->window) + set_challenge_on_window (w->window); + } +} + +static void +reset_state (GdkWindow *window) +{ + gdk_property_change (window, display_change_gdkatom, string_gdkatom, + 8, GDK_PROP_MODE_REPLACE, NULL, 0); +} + +static void +generate_response (GdkDisplay *gdisplay, Display *dpy, Window window, int code) +{ + XClientMessageEvent ev; + Atom atom = gdk_x11_atom_to_xatom_for_display (gdisplay, + display_change_gdkatom); + + memset (&ev, 0, sizeof (ev)); + + ev.type = ClientMessage; + ev.window = window; + ev.message_type = atom; + ev.format = 32; + + ev.data.l[0] = window; + ev.data.l[1] = code; + + XSendEvent (dpy, DefaultRootWindow (dpy), False, SubstructureNotifyMask, (XEvent *)&ev); +} + +static int +handle_request (GdkWindow *gwindow, char *prop) +{ + GtkWidget *widget; + char *target, *auth_method, *auth_data; + char *p; + + target = prop; + auth_method = "NULL"; + auth_data = NULL; + + p = strchr (prop, ' '); + if (p) + { + *p = 0; + auth_method = ++p; + + p = strchr (p, ' '); + if (p) + { + *p = 0; + auth_data = ++p; + } + } + + if (no_auth == FALSE) + { + if (!strcasecmp (auth_method, "null")) + return DISPLAY_CHANGE_AUTHENTICATION_BAD; + else if (!strcasecmp (auth_method, "rsa-sig")) + { + if (gtk_migration_auth_validate_request (target, auth_data) == FALSE) + return DISPLAY_CHANGE_AUTHENTICATION_BAD; + } + else + return DISPLAY_CHANGE_AUTHENTICATION_BAD; + } + + gdk_window_get_user_data (gwindow, (gpointer*) &widget); + + if (widget) + return do_change_display (widget, target); + + return DISPLAY_CHANGE_INDETERMINATE_ERROR; +} + +static GdkFilterReturn +filter_func (GdkXEvent *xevp, GdkEvent *ev, gpointer p) +{ + XPropertyEvent *xev = (XPropertyEvent *)xevp; + + if (xev->type == PropertyNotify) + { + GdkDisplay *gdisplay; + Atom atom; + + gdisplay = gdk_x11_lookup_xdisplay (xev->display); + if (gdisplay) + { + atom = gdk_x11_atom_to_xatom_for_display (gdisplay, display_change_gdkatom); + + if (xev->atom == atom) + { + GdkWindow *gwindow; + + gwindow = gdk_window_lookup_for_display (gdisplay, xev->window); + + if (gwindow) + { + GdkAtom actual_type; + gint actual_format; + gint actual_length; + unsigned char *prop = NULL; + + if (gdk_property_get (gwindow, display_change_gdkatom, string_gdkatom, + 0, G_MAXLONG, FALSE, &actual_type, &actual_format, + &actual_length, &prop)) + { + if (actual_length != 0) + { + if (actual_type == string_gdkatom && actual_length > 8) + { + gchar *buf = g_malloc (actual_length + 1); + int rc; + + memcpy (buf, prop, actual_length); + buf[actual_length] = 0; + + rc = handle_request (gwindow, buf); + + g_free (buf); + generate_response (gdisplay, xev->display, xev->window, rc); + + if (rc == DISPLAY_CHANGE_SUCCESS) + update_challenge_on_windows (); + } + + reset_state (gwindow); + } + } + + if (prop) + g_free (prop); + } + } + + return GDK_FILTER_REMOVE; + } + } + + return GDK_FILTER_CONTINUE; +} + +static void +unrealize_window (GtkWidget *w) +{ + all_widgets = g_slist_remove (all_widgets, w); +} + +void +gtk_migration_mark_window (GtkWidget *w) +{ + if (! gtk_migration_initialised) + { + g_warning ("gtk_migration not initialised yet"); + return; + } + + if (GTK_WIDGET_REALIZED (w)) + { + GdkWindow *window = w->window; + + gdk_window_add_filter (window, filter_func, NULL); + + reset_state (window); + set_challenge_on_window (window); + + all_widgets = g_slist_append (all_widgets, w); + + g_signal_connect (G_OBJECT (w), "unrealize", G_CALLBACK (unrealize_window), NULL); + } + else + g_signal_connect (G_OBJECT (w), "realize", G_CALLBACK (gtk_migration_mark_window), NULL); +} + +void +gtk_migration_init (void) +{ + if (getenv ("GPE_DISPLAY_MIGRATION_NO_AUTH") != NULL) + no_auth = TRUE; + + string_gdkatom = gdk_atom_intern ("STRING", FALSE); + display_change_gdkatom = gdk_atom_intern ("_GPE_DISPLAY_CHANGE", FALSE); + rsa_challenge_gdkatom = gdk_atom_intern ("_GPE_DISPLAY_CHANGE_RSA_CHALLENGE", FALSE); + + gtk_migration_auth_generate_challenge (); + + gtk_migration_initialised = TRUE; +} Index: gtk/gtkwindow.c =================================================================== --- gtk/gtkwindow.c.orig 2006-10-03 16:51:46.000000000 +0100 +++ gtk/gtkwindow.c 2006-10-30 12:56:34.000000000 +0000 @@ -50,6 +50,9 @@ #include "x11/gdkx.h" #endif +extern void gtk_migration_mark_window (GtkWidget *w); + + enum { SET_FOCUS, FRAME_EVENT, @@ -823,6 +826,10 @@ g_signal_connect (window->screen, "composited_changed", G_CALLBACK (gtk_window_on_composited_changed), window); + +#ifdef ENABLE_MIGRATION + gtk_migration_mark_window (window); +#endif } static void