/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2007 Novell, Inc.
* Copyright (C) 2008 Red Hat, Inc.
* Copyright (C) 2008 William Jon McCann <jmccann@redhat.com>
*
* 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib-object.h>
#include <X11/ICE/ICElib.h>
#include <X11/ICE/ICEutil.h>
#include <X11/ICE/ICEconn.h>
#include <X11/SM/SMlib.h>
/* Get the proto for _IceTransNoListen */
#define ICE_t
#define TRANS_SERVER
#include <X11/Xtrans/Xtrans.h>
#undef ICE_t
#undef TRANS_SERVER
#include "gsm-xsmp-server.h"
#include "gsm-xsmp-client.h"
#include "gsm-util.h"
/* 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
#define GSM_XSMP_SERVER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSM_TYPE_XSMP_SERVER, GsmXsmpServerPrivate))
struct GsmXsmpServerPrivate
{
GsmStore *client_store;
IceListenObj *xsmp_sockets;
int num_xsmp_sockets;
int num_local_xsmp_sockets;
gboolean stopping;
};
enum {
PROP_0,
PROP_CLIENT_STORE
};
static void gsm_xsmp_server_class_init (GsmXsmpServerClass *klass);
static void gsm_xsmp_server_init (GsmXsmpServer *xsmp_server);
static void gsm_xsmp_server_finalize (GObject *object);
static gpointer xsmp_server_object = NULL;
G_DEFINE_TYPE (GsmXsmpServer, gsm_xsmp_server, G_TYPE_OBJECT)
typedef struct {
GsmXsmpServer *server;
IceListenObj listener;
} GsmIceConnectionData;
typedef struct {
guint watch_id;
guint protocol_timeout;
} GsmIceConnectionWatch;
static void
disconnect_ice_connection (IceConn ice_conn)
{
IceSetShutdownNegotiation (ice_conn, FALSE);
IceCloseConnection (ice_conn);
}
static void
free_ice_connection_watch (GsmIceConnectionWatch *data)
{
if (data->watch_id) {
g_source_remove (data->watch_id);
data->watch_id = 0;
}
if (data->protocol_timeout) {
g_source_remove (data->protocol_timeout);
data->protocol_timeout = 0;
}
g_free (data);
}
static gboolean
ice_protocol_timeout (IceConn ice_conn)
{
GsmIceConnectionWatch *data;
g_debug ("GsmXsmpServer: ice_protocol_timeout for IceConn %p with status %d",
ice_conn, IceConnectionStatus (ice_conn));
data = ice_conn->context;
free_ice_connection_watch (data);
disconnect_ice_connection (ice_conn);
return FALSE;
}
static gboolean
auth_iochannel_watch (GIOChannel *source,
GIOCondition condition,
IceConn ice_conn)
{
GsmIceConnectionWatch *data;
gboolean keep_going;
data = ice_conn->context;
switch (IceProcessMessages (ice_conn, NULL, NULL)) {
case IceProcessMessagesSuccess:
keep_going = TRUE;
break;
case IceProcessMessagesIOError:
g_debug ("GsmXsmpServer: IceProcessMessages returned IceProcessMessagesIOError");
free_ice_connection_watch (data);
disconnect_ice_connection (ice_conn);
keep_going = FALSE;
break;
case IceProcessMessagesConnectionClosed:
g_debug ("GsmXsmpServer: IceProcessMessages returned IceProcessMessagesConnectionClosed");
free_ice_connection_watch (data);
keep_going = FALSE;
break;
default:
g_assert_not_reached ();
}
return keep_going;
}
/* IceAcceptConnection returns a new ICE connection that is in a "pending" state,
* this is because authentification may be necessary.
* So we've to authenticate it, before accept_xsmp_connection() is called.
* Then each GsmXSMPClient will have its own IceConn watcher
*/
static void
auth_ice_connection (IceConn ice_conn)
{
GIOChannel *channel;
GsmIceConnectionWatch *data;
int fd;
g_debug ("GsmXsmpServer: auth_ice_connection()");
fd = IceConnectionNumber (ice_conn);
fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
channel = g_io_channel_unix_new (fd);
data = g_new0 (GsmIceConnectionWatch, 1);
ice_conn->context = data;
data->protocol_timeout = g_timeout_add_seconds (5,
(GSourceFunc)ice_protocol_timeout,
ice_conn);
data->watch_id = g_io_add_watch (channel,
G_IO_IN | G_IO_ERR,
(GIOFunc)auth_iochannel_watch,
ice_conn);
g_io_channel_unref (channel);
}
/* This is called (by glib via xsmp->ice_connection_watch) when a
* connection is first received on the ICE listening socket.
*/
static gboolean
accept_ice_connection (GIOChannel *source,
GIOCondition condition,
GsmIceConnectionData *data)
{
IceConn ice_conn;
IceAcceptStatus status;
g_debug ("GsmXsmpServer: accept_ice_connection()");
ice_conn = IceAcceptConnection (data->listener, &status);
if (status != IceAcceptSuccess) {
g_debug ("GsmXsmpServer: IceAcceptConnection returned %d", status);
return TRUE;
}
auth_ice_connection (ice_conn);
return TRUE;
}
void
gsm_xsmp_server_start (GsmXsmpServer *server)
{
GIOChannel *channel;
int i;
for (i = 0; i < server->priv->num_local_xsmp_sockets; i++) {
GsmIceConnectionData *data;
data = g_new0 (GsmIceConnectionData, 1);
data->server = server;
data->listener = server->priv->xsmp_sockets[i];
channel = g_io_channel_unix_new (IceGetListenConnectionNumber (server->priv->xsmp_sockets[i]));
g_io_add_watch_full (channel,
G_PRIORITY_DEFAULT,
G_IO_IN | G_IO_HUP | G_IO_ERR,
(GIOFunc)accept_ice_connection,
data,
(GDestroyNotify)g_free);
g_io_channel_unref (channel);
}
}
void
gsm_xsmp_server_stop_accepting_new_clients (GsmXsmpServer *server)
{
g_return_if_fail (GSM_IS_XSMP_SERVER (server));
g_debug ("gsm_xsmp_server_stop_accepting_new_clients");
server->priv->stopping = TRUE;
}
void
gsm_xsmp_server_start_accepting_new_clients (GsmXsmpServer *server)
{
g_return_if_fail (GSM_IS_XSMP_SERVER (server));
g_debug ("gsm_xsmp_server_start");
server->priv->stopping = FALSE;
}
static void
gsm_xsmp_server_set_client_store (GsmXsmpServer *xsmp_server,
GsmStore *store)
{
g_return_if_fail (GSM_IS_XSMP_SERVER (xsmp_server));
if (store != NULL) {
g_object_ref (store);
}
if (xsmp_server->priv->client_store != NULL) {
g_object_unref (xsmp_server->priv->client_store);
}
xsmp_server->priv->client_store = store;
}
static void
gsm_xsmp_server_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GsmXsmpServer *self;
self = GSM_XSMP_SERVER (object);
switch (prop_id) {
case PROP_CLIENT_STORE:
gsm_xsmp_server_set_client_store (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gsm_xsmp_server_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GsmXsmpServer *self;
self = GSM_XSMP_SERVER (object);
switch (prop_id) {
case PROP_CLIENT_STORE:
g_value_set_object (value, self->priv->client_store);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/* 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,
GsmXsmpServer *server,
unsigned long *mask_ret,
SmsCallbacks *callbacks_ret,
char **failure_reason_ret)
{
IceConn ice_conn;
GsmClient *client;
GsmIceConnectionWatch *data;
if (server->priv->stopping) {
g_debug ("GsmXsmpServer: 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);
data = ice_conn->context;
/* Each GsmXSMPClient has its own IceConn watcher */
free_ice_connection_watch (data);
client = gsm_xsmp_client_new (ice_conn);
gsm_store_add (server->priv->client_store, gsm_client_peek_id (client), G_OBJECT (client));
/* the store will own the ref */
g_object_unref (client);
gsm_xsmp_client_connect (GSM_XSMP_CLIENT (client), sms_conn, mask_ret, callbacks_ret);
return TRUE;
}
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 ("GsmXsmpServer: 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 ("GsmXsmpServer: 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 ("GsmXsmpServer: 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.
*/
}
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 (GsmXsmpServer *server,
gboolean adding)
{
char *filename;
char **our_network_ids;
FILE *fp;
IceAuthFileEntry *auth_entry;
GSList *entries;
GSList *e;
int i;
int ret;
gboolean ok = FALSE;
filename = IceAuthFileName ();
do {
ret = IceLockAuthFile (filename,
GSM_ICE_AUTH_RETRIES,
GSM_ICE_AUTH_INTERVAL,
GSM_ICE_AUTH_LOCK_TIMEOUT);
} while (ret != IceAuthLockSuccess && errno == EINTR);
if (ret != IceAuthLockSuccess) {
g_warning ("IceLockAuthFile failed: %m");
return FALSE;
}
our_network_ids = g_malloc (server->priv->num_local_xsmp_sockets * sizeof (char *));
for (i = 0; i < server->priv->num_local_xsmp_sockets; i++) {
our_network_ids[i] = IceGetListenConnectionString (server->priv->xsmp_sockets[i]);
}
entries = NULL;
fp = fopen (filename, "r+");
if (fp != NULL) {
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 < server->priv->num_local_xsmp_sockets; i++) {
if (!strcmp (auth_entry->network_id, our_network_ids[i])) {
IceFreeAuthFileEntry (auth_entry);
break;
}
}
if (i != server->priv->num_local_xsmp_sockets) {
continue;
}
entries = g_slist_prepend (entries, auth_entry);
}
rewind (fp);
} else {
int fd;
if (errno != ENOENT) {
g_warning ("Unable to read ICE authority file %s: %m", 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 < server->priv->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 < server->priv->num_local_xsmp_sockets; i++) {
free (our_network_ids[i]);
}
g_free (our_network_ids);
return ok;
}
static void
setup_listener (GsmXsmpServer *server)
{
char error[256];
mode_t saved_umask;
char *network_id_list;
int i;
int res;
/* 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.
*/
res = SmsInitialize (PACKAGE,
VERSION,
(SmsNewClientProc)accept_xsmp_connection,
server,
NULL,
sizeof (error),
error);
if (! res) {
gsm_util_init_error (TRUE, "Could not initialize libSM: %s", error);
}
/* 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");
/* 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);
res = IceListenForConnections (&server->priv->num_xsmp_sockets,
&server->priv->xsmp_sockets,
sizeof (error),
error);
if (! res) {
gsm_util_init_error (TRUE, _("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 = server->priv->num_local_xsmp_sockets = 0; i < server->priv->num_xsmp_sockets; i++) {
char *id = IceGetListenConnectionString (server->priv->xsmp_sockets[i]);
if (!strncmp (id, "local/", sizeof ("local/") - 1) ||
!strncmp (id, "unix/", sizeof ("unix/") - 1)) {
if (i > server->priv->num_local_xsmp_sockets) {
IceListenObj tmp;
tmp = server->priv->xsmp_sockets[i];
server->priv->xsmp_sockets[i] = server->priv->xsmp_sockets[server->priv->num_local_xsmp_sockets];
server->priv->xsmp_sockets[server->priv->num_local_xsmp_sockets] = tmp;
}
server->priv->num_local_xsmp_sockets++;
}
free (id);
}
if (server->priv->num_local_xsmp_sockets == 0) {
gsm_util_init_error (TRUE, "IceListenForConnections did not return a local listener!");
}
if (server->priv->num_local_xsmp_sockets != server->priv->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 (server->priv->num_xsmp_sockets - server->priv->num_local_xsmp_sockets,
server->priv->xsmp_sockets + server->priv->num_local_xsmp_sockets);
g_warning ("IceListenForConnections returned %d non-local listeners: %s",
server->priv->num_xsmp_sockets - server->priv->num_local_xsmp_sockets,
network_id_list);
free (network_id_list);
}
/* Update .ICEauthority with new auth entries for our socket */
if (!update_iceauthority (server, TRUE)) {
/* FIXME: is this really fatal? Hm... */
gsm_util_init_error (TRUE,
"Could not update ICEauthority file %s",
IceAuthFileName ());
}
network_id_list = IceComposeNetworkIdList (server->priv->num_local_xsmp_sockets,
server->priv->xsmp_sockets);
gsm_util_setenv ("SESSION_MANAGER", network_id_list);
g_debug ("GsmXsmpServer: SESSION_MANAGER=%s\n", network_id_list);
free (network_id_list);
}
static GObject *
gsm_xsmp_server_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties)
{
GsmXsmpServer *xsmp_server;
xsmp_server = GSM_XSMP_SERVER (G_OBJECT_CLASS (gsm_xsmp_server_parent_class)->constructor (type,
n_construct_properties,
construct_properties));
setup_listener (xsmp_server);
return G_OBJECT (xsmp_server);
}
static void
gsm_xsmp_server_class_init (GsmXsmpServerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = gsm_xsmp_server_get_property;
object_class->set_property = gsm_xsmp_server_set_property;
object_class->constructor = gsm_xsmp_server_constructor;
object_class->finalize = gsm_xsmp_server_finalize;
g_object_class_install_property (object_class,
PROP_CLIENT_STORE,
g_param_spec_object ("client-store",
NULL,
NULL,
GSM_TYPE_STORE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_type_class_add_private (klass, sizeof (GsmXsmpServerPrivate));
}
static void
gsm_xsmp_server_init (GsmXsmpServer *xsmp_server)
{
xsmp_server->priv = GSM_XSMP_SERVER_GET_PRIVATE (xsmp_server);
}
static void
gsm_xsmp_server_finalize (GObject *object)
{
GsmXsmpServer *xsmp_server;
g_return_if_fail (object != NULL);
g_return_if_fail (GSM_IS_XSMP_SERVER (object));
xsmp_server = GSM_XSMP_SERVER (object);
g_return_if_fail (xsmp_server->priv != NULL);
IceFreeListenObjs (xsmp_server->priv->num_xsmp_sockets,
xsmp_server->priv->xsmp_sockets);
if (xsmp_server->priv->client_store != NULL) {
g_object_unref (xsmp_server->priv->client_store);
}
G_OBJECT_CLASS (gsm_xsmp_server_parent_class)->finalize (object);
}
GsmXsmpServer *
gsm_xsmp_server_new (GsmStore *client_store)
{
if (xsmp_server_object != NULL) {
g_object_ref (xsmp_server_object);
} else {
xsmp_server_object = g_object_new (GSM_TYPE_XSMP_SERVER,
"client-store", client_store,
NULL);
g_object_add_weak_pointer (xsmp_server_object,
(gpointer *) &xsmp_server_object);
}
return GSM_XSMP_SERVER (xsmp_server_object);
}