Index: configure.in =================================================================== RCS file: /cvs/gnome/gtk+/configure.in,v retrieving revision 1.419.2.4 diff -u -r1.419.2.4 configure.in --- configure.in 8 Feb 2005 21:39:42 -0000 1.419.2.4 +++ configure.in 27 Feb 2005 13:10:16 -0000 @@ -1495,6 +1495,16 @@ GTK_DEP_LIBS="$GDK_EXTRA_LIBS $GTK_DEP_LIBS_FOR_X `$PKG_CONFIG --libs $GDK_PIXBUF_PACKAGES $GDK_PACKAGES $GTK_PACKAGES` $GTK_EXTRA_LIBS $GDK_PIXBUF_EXTRA_LIBS" GTK_DEP_CFLAGS="`$PKG_CONFIG --cflags gthread-2.0 $GDK_PIXBUF_PACKAGES $GDK_PACKAGES $GTK_PACKAGES` $GDK_PIXBUF_EXTRA_CFLAGS $GDK_EXTRA_CFLAGS $GTK_EXTRA_CFLAGS" +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 =================================================================== RCS file: /cvs/gnome/gtk+/gtk/Makefile.am,v retrieving revision 1.266.2.1 diff -u -r1.266.2.1 Makefile.am --- gtk/Makefile.am 13 Jan 2005 15:18:21 -0000 1.266.2.1 +++ gtk/Makefile.am 27 Feb 2005 13:10:17 -0000 @@ -520,6 +520,10 @@ gtkwindow.c \ xembed.h +if ENABLE_MIGRATION +gtk_c_sources += gtkmigration.c +endif + if OS_UNIX gtk_private_h_sources += gtkfilesystemunix.h gtk_c_sources += gtkfilesystemunix.c Index: gtk/gtkmain.c =================================================================== RCS file: /cvs/gnome/gtk+/gtk/gtkmain.c,v retrieving revision 1.255 diff -u -r1.255 gtkmain.c --- gtk/gtkmain.c 27 Dec 2004 05:25:15 -0000 1.255 +++ gtk/gtkmain.c 27 Feb 2005 13:10:19 -0000 @@ -491,6 +491,10 @@ _gtk_accel_map_init (); _gtk_rc_init (); +#ifdef ENABLE_MIGRATION + gtk_migration_init (); +#endif + /* Set the 'initialized' flag. */ gtk_initialized = TRUE; Index: gtk/gtkwindow.c =================================================================== RCS file: /cvs/gnome/gtk+/gtk/gtkwindow.c,v retrieving revision 1.281.2.4 diff -u -r1.281.2.4 gtkwindow.c --- gtk/gtkwindow.c 21 Feb 2005 04:21:49 -0000 1.281.2.4 +++ gtk/gtkwindow.c 27 Feb 2005 13:10:56 -0000 @@ -731,6 +731,8 @@ add_tab_bindings (binding_set, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_DIR_TAB_BACKWARD); } +extern void gtk_migration_mark_window (GtkWidget *w); + static void gtk_window_init (GtkWindow *window) { @@ -790,6 +792,10 @@ "event", G_CALLBACK (gtk_window_event), NULL); + +#ifdef ENABLE_MIGRATION + gtk_migration_mark_window (window); +#endif } static void --- /dev/null 2005-02-20 01:07:50.714416160 +0000 +++ gtk/gtkmigration.c 2005-02-27 15:05:04.052757352 +0000 @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2003, 2005 Philip Blundell + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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; +}