/* xsmp.c * Copyright (C) 2007 Novell, Inc. * * 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 * Lesser 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "gsm-client-xsmp.h" #include "gsm-xsmp.h" #include #include #include #include #ifdef HAVE_X11_XTRANS_XTRANS_H /* Get the proto for _IceTransNoListen */ #define ICE_t #define TRANS_SERVER #include #undef ICE_t #undef TRANS_SERVER #endif /* HAVE_X11_XTRANS_XTRANS_H */ static IceListenObj *xsmp_sockets; static int num_xsmp_sockets, num_local_xsmp_sockets; static gboolean update_iceauthority (gboolean adding); static gboolean accept_ice_connection (GIOChannel *source, GIOCondition condition, gpointer data); static Status accept_xsmp_connection (SmsConn conn, SmPointer manager_data, unsigned long *mask_ret, SmsCallbacks *callbacks_ret, char **failure_reason_ret); static void ice_error_handler (IceConn conn, Bool swap, int offending_minor_opcode, unsigned long offending_sequence_num, int error_class, int severity, IcePointer values); static void ice_io_error_handler (IceConn conn); static void sms_error_handler (SmsConn sms_conn, Bool swap, int offending_minor_opcode, unsigned long offending_sequence_num, int error_class, int severity, IcePointer values); /** * gsm_xsmp_init: * * Initializes XSMP. Notably, it creates the XSMP listening socket and * sets the SESSION_MANAGER environment variable to point to it. **/ char * gsm_xsmp_init (void) { char error[256]; mode_t saved_umask; char *network_id_list; int i; /* Set up sane error handlers */ IceSetErrorHandler (ice_error_handler); IceSetIOErrorHandler (ice_io_error_handler); SmsSetErrorHandler (sms_error_handler); /* Initialize libSM; we pass NULL for hostBasedAuthProc to disable * host-based authentication. */ if (!SmsInitialize (PACKAGE, VERSION, accept_xsmp_connection, NULL, NULL, sizeof (error), error)) g_error("Could not initialize libSM: %s", error); #ifdef HAVE_X11_XTRANS_XTRANS_H /* By default, IceListenForConnections will open one socket for each * transport type known to X. We don't want connections from remote * hosts, so for security reasons it would be best if ICE didn't * even open any non-local sockets. So we use an internal ICElib * method to disable them here. Unfortunately, there is no way to * ask X what transport types it knows about, so we're forced to * guess. */ _IceTransNoListen ("tcp"); #endif /* Create the XSMP socket. Older versions of IceListenForConnections * have a bug which causes the umask to be set to 0 on certain types * of failures. Probably not an issue on any modern systems, but * we'll play it safe. */ saved_umask = umask (0); umask (saved_umask); if (!IceListenForConnections (&num_xsmp_sockets, &xsmp_sockets, sizeof (error), error)) g_error ("Could not create ICE listening socket: %s", error); umask (saved_umask); /* Find the local sockets in the returned socket list and move them * to the start of the list. */ for (i = num_local_xsmp_sockets = 0; i < num_xsmp_sockets; i++) { char *id = IceGetListenConnectionString (xsmp_sockets[i]); if (!strncmp (id, "local/", sizeof ("local/") - 1) || !strncmp (id, "unix/", sizeof ("unix/") - 1)) { if (i > num_local_xsmp_sockets) { IceListenObj tmp = xsmp_sockets[i]; xsmp_sockets[i] = xsmp_sockets[num_local_xsmp_sockets]; xsmp_sockets[num_local_xsmp_sockets] = tmp; } num_local_xsmp_sockets++; } free (id); } if (num_local_xsmp_sockets == 0) g_error ("IceListenForConnections did not return a local listener!"); #ifdef HAVE_X11_XTRANS_XTRANS_H if (num_local_xsmp_sockets != num_xsmp_sockets) { /* Xtrans was apparently compiled with support for some * non-local transport besides TCP (which we disabled above); we * won't create IO watches on those extra sockets, so * connections to them will never be noticed, but they're still * there, which is inelegant. * * If the g_warning below is triggering for you and you want to * stop it, the fix is to add additional _IceTransNoListen() * calls above. */ network_id_list = IceComposeNetworkIdList (num_xsmp_sockets - num_local_xsmp_sockets, xsmp_sockets + num_local_xsmp_sockets); g_warning ("IceListenForConnections returned %d non-local listeners: %s", num_xsmp_sockets - num_local_xsmp_sockets, network_id_list); free (network_id_list); } #endif /* Update .ICEauthority with new auth entries for our socket */ if (!update_iceauthority (TRUE)) { /* FIXME: is this really fatal? Hm... */ g_error ("Could not update ICEauthority file %s", IceAuthFileName ()); } network_id_list = IceComposeNetworkIdList (num_local_xsmp_sockets, xsmp_sockets); return network_id_list; } /** * gsm_xsmp_run: * * Sets the XSMP server to start accepting connections. **/ void gsm_xsmp_run (void) { GIOChannel *channel; int i; for (i = 0; i < num_local_xsmp_sockets; i++) { channel = g_io_channel_unix_new (IceGetListenConnectionNumber (xsmp_sockets[i])); g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR, accept_ice_connection, xsmp_sockets[i]); g_io_channel_unref (channel); } } /** * gsm_xsmp_shutdown: * * Shuts down the XSMP server and closes the ICE listening socket **/ void gsm_xsmp_shutdown (void) { update_iceauthority (FALSE); IceFreeListenObjs (num_xsmp_sockets, xsmp_sockets); xsmp_sockets = NULL; } /** * gsm_xsmp_generate_client_id: * * Generates a new XSMP client ID. * * Return value: an XSMP client ID. **/ char * gsm_xsmp_generate_client_id (void) { static int sequence = -1; static guint rand1 = 0, rand2 = 0; static pid_t pid = 0; struct timeval tv; /* The XSMP spec defines the ID as: * * Version: "1" * Address type and address: * "1" + an IPv4 address as 8 hex digits * "2" + a DECNET address as 12 hex digits * "6" + an IPv6 address as 32 hex digits * Time stamp: milliseconds since UNIX epoch as 13 decimal digits * Process-ID type and process-ID: * "1" + POSIX PID as 10 decimal digits * Sequence number as 4 decimal digits * * XSMP client IDs are supposed to be globally unique: if * SmsGenerateClientID() is unable to determine a network * address for the machine, it gives up and returns %NULL. * GNOME and KDE have traditionally used a fourth address * format in this case: * "0" + 16 random hex digits * * We don't even bother trying SmsGenerateClientID(), since the * user's IP address is probably "192.168.1.*" anyway, so a random * number is actually more likely to be globally unique. */ if (!rand1) { rand1 = g_random_int (); rand2 = g_random_int (); pid = getpid (); } sequence = (sequence + 1) % 10000; gettimeofday (&tv, NULL); return g_strdup_printf ("10%.04x%.04x%.10lu%.3u%.10lu%.4d", rand1, rand2, (unsigned long) tv.tv_sec, (unsigned) tv.tv_usec, (unsigned long) pid, sequence); } /* This is called (by glib via xsmp->ice_connection_watch) when a * connection is first received on the ICE listening socket. (We * expect that the client will then initiate XSMP on the connection; * if it does not, GsmClientXSMP will eventually time out and close * the connection.) * * FIXME: it would probably make more sense to not create a * GsmClientXSMP object until accept_xsmp_connection, below (and to do * the timing-out here in xsmp.c). */ static gboolean accept_ice_connection (GIOChannel *source, GIOCondition condition, gpointer data) { IceListenObj listener = data; IceConn ice_conn; IceAcceptStatus status; GsmClientXSMP *client; g_debug ("accept_ice_connection()"); ice_conn = IceAcceptConnection (listener, &status); if (status != IceAcceptSuccess) { g_debug ("IceAcceptConnection returned %d", status); return TRUE; } client = gsm_client_xsmp_new (ice_conn); ice_conn->context = client; return TRUE; } /* This is called (by libSM) when XSMP is initiated on an ICE * connection that was already accepted by accept_ice_connection. */ static Status accept_xsmp_connection (SmsConn sms_conn, SmPointer manager_data, unsigned long *mask_ret, SmsCallbacks *callbacks_ret, char **failure_reason_ret) { IceConn ice_conn; GsmClientXSMP *client; /* FIXME: what about during shutdown but before gsm_xsmp_shutdown? */ if (!xsmp_sockets) { g_debug ("In shutdown, rejecting new client"); *failure_reason_ret = strdup (_("Refusing new client connection because the session is currently being shut down\n")); return FALSE; } ice_conn = SmsGetIceConnection (sms_conn); client = ice_conn->context; g_return_val_if_fail (client != NULL, TRUE); gsm_client_xsmp_connect (client, sms_conn, mask_ret, callbacks_ret); return TRUE; } /* ICEauthority stuff */ /* Various magic numbers stolen from iceauth.c */ #define GSM_ICE_AUTH_RETRIES 10 #define GSM_ICE_AUTH_INTERVAL 2 /* 2 seconds */ #define GSM_ICE_AUTH_LOCK_TIMEOUT 600 /* 10 minutes */ #define GSM_ICE_MAGIC_COOKIE_AUTH_NAME "MIT-MAGIC-COOKIE-1" #define GSM_ICE_MAGIC_COOKIE_LEN 16 static IceAuthFileEntry * auth_entry_new (const char *protocol, const char *network_id) { IceAuthFileEntry *file_entry; IceAuthDataEntry data_entry; file_entry = malloc (sizeof (IceAuthFileEntry)); file_entry->protocol_name = strdup (protocol); file_entry->protocol_data = NULL; file_entry->protocol_data_length = 0; file_entry->network_id = strdup (network_id); file_entry->auth_name = strdup (GSM_ICE_MAGIC_COOKIE_AUTH_NAME); file_entry->auth_data = IceGenerateMagicCookie (GSM_ICE_MAGIC_COOKIE_LEN); file_entry->auth_data_length = GSM_ICE_MAGIC_COOKIE_LEN; /* Also create an in-memory copy, which is what the server will * actually use for checking client auth. */ data_entry.protocol_name = file_entry->protocol_name; data_entry.network_id = file_entry->network_id; data_entry.auth_name = file_entry->auth_name; data_entry.auth_data = file_entry->auth_data; data_entry.auth_data_length = file_entry->auth_data_length; IceSetPaAuthData (1, &data_entry); return file_entry; } static gboolean update_iceauthority (gboolean adding) { char *filename = IceAuthFileName (); char **our_network_ids; FILE *fp; IceAuthFileEntry *auth_entry; GSList *entries, *e; int i; gboolean ok = FALSE; if (IceLockAuthFile (filename, GSM_ICE_AUTH_RETRIES, GSM_ICE_AUTH_INTERVAL, GSM_ICE_AUTH_LOCK_TIMEOUT) != IceAuthLockSuccess) return FALSE; our_network_ids = g_malloc (num_local_xsmp_sockets * sizeof (char *)); for (i = 0; i < num_local_xsmp_sockets; i++) our_network_ids[i] = IceGetListenConnectionString (xsmp_sockets[i]); entries = NULL; fp = fopen (filename, "r+"); if (fp) { while ((auth_entry = IceReadAuthFileEntry (fp)) != NULL) { /* Skip/delete entries with no network ID (invalid), or with * our network ID; if we're starting up, an entry with our * ID must be a stale entry left behind by an old process, * and if we're shutting down, it won't be valid in the * future, so either way we want to remove it from the list. */ if (!auth_entry->network_id) { IceFreeAuthFileEntry (auth_entry); continue; } for (i = 0; i < num_local_xsmp_sockets; i++) { if (!strcmp (auth_entry->network_id, our_network_ids[i])) { IceFreeAuthFileEntry (auth_entry); break; } } if (i != num_local_xsmp_sockets) continue; entries = g_slist_prepend (entries, auth_entry); } rewind (fp); } else { int fd; if (g_file_test (filename, G_FILE_TEST_EXISTS)) { g_warning ("Unable to read ICE authority file: %s", filename); goto cleanup; } fd = open (filename, O_CREAT | O_WRONLY, 0600); fp = fdopen (fd, "w"); if (!fp) { g_warning ("Unable to write to ICE authority file: %s", filename); if (fd != -1) close (fd); goto cleanup; } } if (adding) { for (i = 0; i < num_local_xsmp_sockets; i++) { entries = g_slist_append (entries, auth_entry_new ("ICE", our_network_ids[i])); entries = g_slist_prepend (entries, auth_entry_new ("XSMP", our_network_ids[i])); } } for (e = entries; e; e = e->next) { IceAuthFileEntry *auth_entry = e->data; IceWriteAuthFileEntry (fp, auth_entry); IceFreeAuthFileEntry (auth_entry); } g_slist_free (entries); fclose (fp); ok = TRUE; cleanup: IceUnlockAuthFile (filename); for (i = 0; i < num_local_xsmp_sockets; i++) free (our_network_ids[i]); g_free (our_network_ids); return ok; } /* Error handlers */ static void ice_error_handler (IceConn conn, Bool swap, int offending_minor_opcode, unsigned long offending_sequence, int error_class, int severity, IcePointer values) { g_debug ("ice_error_handler (%p, %s, %d, %lx, %d, %d)", conn, swap ? "TRUE" : "FALSE", offending_minor_opcode, offending_sequence, error_class, severity); if (severity == IceCanContinue) return; /* FIXME: the ICElib docs are completely vague about what we're * supposed to do in this case. Need to verify that calling * IceCloseConnection() here is guaranteed to cause neither * free-memory-reads nor leaks. */ IceCloseConnection (conn); } static void ice_io_error_handler (IceConn conn) { g_debug ("ice_io_error_handler (%p)", conn); /* We don't need to do anything here; the next call to * IceProcessMessages() for this connection will receive * IceProcessMessagesIOError and we can handle the error there. */ } static void sms_error_handler (SmsConn conn, Bool swap, int offending_minor_opcode, unsigned long offending_sequence_num, int error_class, int severity, IcePointer values) { g_debug ("sms_error_handler (%p, %s, %d, %lx, %d, %d)", conn, swap ? "TRUE" : "FALSE", offending_minor_opcode, offending_sequence_num, error_class, severity); /* We don't need to do anything here; if the connection needs to be * closed, libSM will do that itself. */ }