/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2005 Raffaele Sandrini
 * Copyright (C) 2005 Red Hat, Inc.
 * Copyright (C) 2002, 2003 George Lebl
 * Copyright (C) 2001 Queen of England,
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 * Authors:
 *      Raffaele Sandrini <rasa@gmx.ch>
 *      George Lebl <jirka@5z.com>
 *      Mark McLoughlin <mark@skynet.ie>
 */

#ifdef HAVE_CONFIG_H
	#include <config.h>
#endif

#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>

#include <X11/Xauth.h>
#include <gdk/gdk.h>

#include "mdm.h"

#define MDM_PROTOCOL_UPDATE_INTERVAL 1 /* seconds */

#define MDM_PROTOCOL_SOCKET_PATH "/var/run/mdm_socket"

#define MDM_PROTOCOL_MSG_CLOSE "CLOSE"
#define MDM_PROTOCOL_MSG_VERSION "VERSION"
#define MDM_PROTOCOL_MSG_AUTHENTICATE "AUTH_LOCAL"
#define MDM_PROTOCOL_MSG_QUERY_ACTION "QUERY_LOGOUT_ACTION"
#define MDM_PROTOCOL_MSG_SET_ACTION "SET_SAFE_LOGOUT_ACTION"
#define MDM_PROTOCOL_MSG_FLEXI_XSERVER "FLEXI_XSERVER"

#define MDM_ACTION_STR_NONE "NONE"
#define MDM_ACTION_STR_SHUTDOWN "HALT"
#define MDM_ACTION_STR_REBOOT "REBOOT"
#define MDM_ACTION_STR_SUSPEND "SUSPEND"

typedef struct {
	int fd;
	char* auth_cookie;

	MdmLogoutAction available_actions;
	MdmLogoutAction current_actions;

	time_t last_update;
} MdmProtocolData;

static MdmProtocolData mdm_protocol_data = {
	0,
	NULL,
	MDM_LOGOUT_ACTION_NONE,
	MDM_LOGOUT_ACTION_NONE,
	0
};

static char* mdm_send_protocol_msg(MdmProtocolData* data, const char* msg)
{
	GString* retval;
	char buf[256];
	char* p;
	int len;

	p = g_strconcat(msg, "\n", NULL);

	if (write(data->fd, p, strlen(p)) < 0)
	{
		g_free(p);

		g_warning("Failed to send message to MDM: %s", g_strerror(errno));

		return NULL;
	}

	g_free(p);

	p = NULL;
	retval = NULL;

	while ((len = read(data->fd, buf, sizeof(buf) - 1)) > 0)
	{
		buf[len] = '\0';

		if (!retval)
		{
			retval = g_string_new(buf);
		}
		else
		{
			retval = g_string_append(retval, buf);
		}

		if ((p = strchr(retval->str, '\n')))
		{
			break;
		}
	}

	if (p)
	{
		*p = '\0';
	}

	return retval ? g_string_free(retval, FALSE) : NULL;
}

static char* get_display_number(void)
{
	const char* display_name;
	char* retval;
	char* p;

	display_name = gdk_display_get_name(gdk_display_get_default());

	p = strchr(display_name, ':');

	if (!p)
	{
		return g_strdup("0");
	}

	while (*p == ':')
	{
		p++;
	}

	retval = g_strdup(p);

	p = strchr(retval, '.');

	if (p != NULL)
	{
		*p = '\0';
	}

	return retval;
}

static gboolean mdm_authenticate_connection(MdmProtocolData* data)
{
	#define MDM_MIT_MAGIC_COOKIE_LEN 16

	const char* xau_path;
	FILE* f;
	Xauth* xau;
	char* display_number;
	gboolean retval;

	if (data->auth_cookie)
	{
		char* msg;
		char* response;

		msg = g_strdup_printf(MDM_PROTOCOL_MSG_AUTHENTICATE " %s", data->auth_cookie);
		response = mdm_send_protocol_msg(data, msg);
		g_free(msg);

		if (response && !strcmp(response, "OK"))
		{
			g_free(response);
			return TRUE;
		}
		else
		{
			g_free(response);
			g_free(data->auth_cookie);
			data->auth_cookie = NULL;
		}
	}

	if (!(xau_path = XauFileName()))
	{
		return FALSE;
	}

	if (!(f = fopen(xau_path, "r")))
	{
		return FALSE;
	}

	retval = FALSE;
	display_number = get_display_number();

	while ((xau = XauReadAuth(f)))
	{
		char buffer[40]; /* 2*16 == 32, so 40 is enough */
		char* msg;
		char* response;
		int   i;

		if (xau->family != FamilyLocal || strncmp(xau->number, display_number, xau->number_length) || strncmp(xau->name, "MIT-MAGIC-COOKIE-1", xau->name_length) || xau->data_length != MDM_MIT_MAGIC_COOKIE_LEN)
		{
			XauDisposeAuth(xau);
			continue;
		}

		for (i = 0; i < MDM_MIT_MAGIC_COOKIE_LEN; i++)
		{
			g_snprintf(buffer + 2 * i, 3, "%02x", (guint)(guchar) xau->data[i]);
		}

		XauDisposeAuth(xau);

		msg = g_strdup_printf(MDM_PROTOCOL_MSG_AUTHENTICATE " %s", buffer);
		response = mdm_send_protocol_msg(data, msg);
		g_free(msg);

		if (response && !strcmp(response, "OK"))
		{
			data->auth_cookie = g_strdup(buffer);
			g_free(response);
			retval = TRUE;
			break;
		}

		g_free(response);
	}

	g_free(display_number);

	fclose(f);

	return retval;

	#undef MDM_MIT_MAGIC_COOKIE_LEN
}

static void mdm_shutdown_protocol_connection(MdmProtocolData *data)
{
	if (data->fd)
	{
		close(data->fd);
	}

	data->fd = 0;
}

static gboolean mdm_init_protocol_connection(MdmProtocolData* data)
{
	struct sockaddr_un addr;
	char* response;

	g_assert(data->fd <= 0);

	if (g_file_test(MDM_PROTOCOL_SOCKET_PATH, G_FILE_TEST_EXISTS))
	{
		strcpy(addr.sun_path, MDM_PROTOCOL_SOCKET_PATH);
	}
	else if (g_file_test("/tmp/.mdm_socket", G_FILE_TEST_EXISTS))
	{
		strcpy(addr.sun_path, "/tmp/.mdm_socket");
	}
	else
	{
		return FALSE;
	}

	data->fd = socket(AF_UNIX, SOCK_STREAM, 0);

	if (data->fd < 0)
	{
		g_warning("Failed to create MDM socket: %s", g_strerror(errno));

		mdm_shutdown_protocol_connection(data);

		return FALSE;
	}

	addr.sun_family = AF_UNIX;

	if (connect(data->fd, (struct sockaddr*) &addr, sizeof(addr)) < 0)
	{
		g_warning("Failed to establish a connection with MDM: %s", g_strerror(errno));

		mdm_shutdown_protocol_connection(data);

		return FALSE;
	}

	response = mdm_send_protocol_msg(data, MDM_PROTOCOL_MSG_VERSION);

	if (!response || strncmp(response, "MDM ", strlen("MDM ") != 0))
	{
		g_free(response);

		g_warning("Failed to get protocol version from MDM");
		mdm_shutdown_protocol_connection(data);

		return FALSE;
	}

	g_free(response);

	if (!mdm_authenticate_connection(data))
	{
		g_warning("Failed to authenticate with MDM");
		mdm_shutdown_protocol_connection(data);
		return FALSE;
	}

	return TRUE;
}

static void mdm_parse_query_response(MdmProtocolData* data, const char* response)
{
	char** actions;
	int i;

	data->available_actions = MDM_LOGOUT_ACTION_NONE;
	data->current_actions = MDM_LOGOUT_ACTION_NONE;

	if (strncmp(response, "OK ", 3) != 0)
	{
		return;
	}

	response += 3;

	actions = g_strsplit(response, ";", -1);

	for (i = 0; actions[i]; i++)
	{
		MdmLogoutAction action = MDM_LOGOUT_ACTION_NONE;
		gboolean selected = FALSE;
		char* str = actions [i];
		int len;

		len = strlen(str);

		if (!len)
		{
			continue;
		}

		if (str[len - 1] == '!')
		{
			selected = TRUE;
			str[len - 1] = '\0';
		}

		if (!strcmp(str, MDM_ACTION_STR_SHUTDOWN))
		{
				action = MDM_LOGOUT_ACTION_SHUTDOWN;
		}
		else if (!strcmp(str, MDM_ACTION_STR_REBOOT))
		{
				action = MDM_LOGOUT_ACTION_REBOOT;
		}
		else if (!strcmp(str, MDM_ACTION_STR_SUSPEND))
		{
				action = MDM_LOGOUT_ACTION_SUSPEND;
		}

		data->available_actions |= action;

		if (selected)
		{
			data->current_actions |= action;
		}
	}

	g_strfreev(actions);
}

static void mdm_update_logout_actions(MdmProtocolData* data)
{
	time_t current_time;
	char* response;

	current_time = time(NULL);

	if (current_time <= (data->last_update + MDM_PROTOCOL_UPDATE_INTERVAL))
	{
		return;
	}

	data->last_update = current_time;

	if (!mdm_init_protocol_connection(data))
	{
		return;
	}

	if ((response = mdm_send_protocol_msg(data, MDM_PROTOCOL_MSG_QUERY_ACTION)))
	{
		mdm_parse_query_response(data, response);
		g_free(response);
	}

	mdm_shutdown_protocol_connection(data);
}

gboolean mdm_is_available(void)
{
	if (!mdm_init_protocol_connection(&mdm_protocol_data))
	{
		return FALSE;
	}

	mdm_shutdown_protocol_connection(&mdm_protocol_data);

	return TRUE;
}

gboolean mdm_supports_logout_action(MdmLogoutAction action)
{
	mdm_update_logout_actions(&mdm_protocol_data);

	return (mdm_protocol_data.available_actions & action) != 0;
}

MdmLogoutAction mdm_get_logout_action(void)
{
	mdm_update_logout_actions(&mdm_protocol_data);

	return mdm_protocol_data.current_actions;
}

void mdm_set_logout_action(MdmLogoutAction action)
{
	char* action_str = NULL;
	char* msg;
	char* response;

	if (!mdm_init_protocol_connection(&mdm_protocol_data))
	{
		return;
	}

	switch (action)
	{
		case MDM_LOGOUT_ACTION_NONE:
			action_str = MDM_ACTION_STR_NONE;
			break;
		case MDM_LOGOUT_ACTION_SHUTDOWN:
			action_str = MDM_ACTION_STR_SHUTDOWN;
			break;
		case MDM_LOGOUT_ACTION_REBOOT:
			action_str = MDM_ACTION_STR_REBOOT;
			break;
		case MDM_LOGOUT_ACTION_SUSPEND:
			action_str = MDM_ACTION_STR_SUSPEND;
			break;
	}

	msg = g_strdup_printf(MDM_PROTOCOL_MSG_SET_ACTION " %s", action_str);

	response = mdm_send_protocol_msg(&mdm_protocol_data, msg);

	g_free(msg);
	g_free(response);

	mdm_protocol_data.last_update = 0;

	mdm_shutdown_protocol_connection(&mdm_protocol_data);
}

void mdm_new_login(void)
{
    char* response;

    if (!mdm_init_protocol_connection(&mdm_protocol_data))
    {
        return;
    }

    response = mdm_send_protocol_msg(&mdm_protocol_data, MDM_PROTOCOL_MSG_FLEXI_XSERVER);

    g_free(response);

    mdm_protocol_data.last_update = 0;

    mdm_shutdown_protocol_connection(&mdm_protocol_data);
}