view plugins/gaim-remote/remote.c @ 9460:eae7e049d639

[gaim-migrate @ 10285] " This patch completely eliminates the use of the old numeric prpl identifiers. I've noticed that some plugins (both in gaim's cvs and external) continue to use the constants even though they are essentially no longer valid - code that relies on them being unique will fail - the new built-in prpls (SILC and novell) ,and any recent external prpls don't have numeric values. The once side effect that the removal causes is that the code for importing of the old .gaimrc and *.blist files is a little more kludgy (using magic numbers instead of the old constants). IMHO this isn't a big deal as the code will go away eventually anyway. **This patch also fixes the raw plugin so that i compiles once again. **Someone should make sure that the gaim-remote and raw plugins still work. (If the raw doesn't work, it may be unrelated as it hasn't been updated since the latest significant MSN changes - i just made a minor change to make it compile)" --Daniel Atallah (13:44:44) Me: what think you of getting rid of prpl numbers? (13:45:05) Sean: what do you mean? (13:45:27) Me: right now they are listed as deprecated, but still used in a few of our own plugins as well as in some 3rd party stuff (13:45:32) Me: and the enum still exists in the core (13:45:44) Me: and the newwer prpls such as novell don't use them (13:45:59) Me: datallah has submitted a patch to get rid of them entirely (13:46:31) Me: the only thing it would break would be the old blist import, which he hacked into working with magic numbers, but which i tend to think we could remove entirely anyway (13:50:03) Sean: Let's get rid of them, then. (13:50:10) Sean: and take out old .blist code too (13:50:14) Me: cool (13:52:37) Sean: we should get rid of .gaimrc code too, sometime. (13:53:02) Sean: If anyone's seriously just now upgrading from 0.59.x, or whatever, they deserve to lose their preferences. (12:27:56) LSchiere: Paco-Paco: what do you think of getting rid of the deprecated prpl numbers? (12:28:14) Paco-Paco: LSchiere: I think it shouldh ave happened long ago committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Tue, 06 Jul 2004 18:06:43 +0000
parents 294ae6548d4e
children 204f5d66a863
line wrap: on
line source

/**
 * Remote control plugin for Gaim
 *
 * Copyright (C) 2003 Christian Hammond.
 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * @todo Make this a core plugin!
 */
#include "internal.h"
#include "gtkinternal.h"

#ifndef _WIN32
# include <sys/un.h>
#endif

#include <signal.h>
#include <getopt.h>

#include "conversation.h"
#include "core.h"
#include "debug.h"
#include "prpl.h"
#include "notify.h"

/* XXX */
#include "gtkconv.h"
#include "gtkplugin.h"
#include "gaim.h"
#include "ui.h"

#include <gaim-remote/remote.h>

#define REMOTE_PLUGIN_ID "gtk-remote"

struct UI {
	GIOChannel *channel;
	guint inpa;
};

#ifndef _WIN32
static gint UI_fd = -1;
static guint watcher = 0;
#endif
static int gaim_session = 0;
static GSList *uis = NULL;

/* AIM URI's ARE FUN :-D */
static const char *
gaim_remote_handle_uri(const char *uri)
{
	const char *username;
	GString *str;
	GList *conn;
	GaimConnection *gc = NULL;
	GaimAccount *account;

	gaim_debug(GAIM_DEBUG_INFO, "gaim_remote_handle_uri", "Handling URI: %s\n", uri);

	/* Well, we'd better check to make sure we have at least one
	   AIM account connected. */
	for (conn = gaim_connections_get_all(); conn != NULL; conn = conn->next) {
		gc = conn->data;
		account = gaim_connection_get_account(gc);
		username = gaim_account_get_username(account);

		if (strcmp(gaim_account_get_protocol_id(account), "prpl-oscar") == 0 &&
			username != NULL && isalpha(*username)) {

			break;
		}
	}

	if (gc == NULL)
		return _("Not connected to AIM");

 	/* aim:goim?screenname=screenname&message=message */
	if (!g_ascii_strncasecmp(uri, "aim:goim?", strlen("aim:goim?"))) {
		char *who, *what;
		GaimConversation *c;
		uri = uri + strlen("aim:goim?");
		
		if (!(who = strstr(uri, "screenname="))) {
			return _("No screenname given.");
		}
		/* spaces are encoded as +'s */
		who = who + strlen("screenname=");
		str = g_string_new(NULL);
		while (*who && (*who != '&')) {
			g_string_append_c(str, *who == '+' ? ' ' : *who);
			who++;
		}
		who = g_strdup(str->str);
		g_string_free(str, TRUE);
		
		what = strstr(uri, "message=");
		if (what) {
			what = what + strlen("message=");
			str = g_string_new(NULL);
			while (*what && (*what != '&' || !g_ascii_strncasecmp(what, "&amp;", 5))) {
				g_string_append_c(str, *what == '+' ? ' ' : *what);
				what++;
			}
			what = g_strdup(str->str);
			g_string_free(str, TRUE);
		}

		c = gaim_conversation_new(GAIM_CONV_IM, gc->account, who);
		g_free(who);

		if (what) {
			GaimGtkConversation *gtkconv = GAIM_GTK_CONVERSATION(c);

			gtk_text_buffer_insert_at_cursor(gtkconv->entry_buffer, what, -1);
			g_free(what);
		}
	} else if (!g_ascii_strncasecmp(uri, "aim:addbuddy?", strlen("aim:addbuddy?"))) {
		char *who, *group;
		uri = uri + strlen("aim:addbuddy?");
		/* spaces are encoded as +'s */

		if (!(who = strstr(uri, "screenname="))) {
			return _("No screenname given.");
		}
		who = who + strlen("screenname=");
		str = g_string_new(NULL);
		while (*who && (*who != '&')) {
			g_string_append_c(str, *who == '+' ? ' ' : *who);
			who++;
		}
		who = g_strdup(str->str);
		g_string_free(str, TRUE);

		group = strstr(uri, "group=");
		if (group) {
			group = group + strlen("group=");
			str = g_string_new(NULL);
			while (*group && (*group != '&' || !g_ascii_strncasecmp(group, "&amp;", 5))) {
				g_string_append_c(str, *group == '+' ? ' ' : *group);
				group++;
			}
			group = g_strdup(str->str);
			g_string_free(str, TRUE);
		}

		gaim_debug(GAIM_DEBUG_MISC, "gaim_remote_handle_uri", "who: %s\n", who);
		gaim_blist_request_add_buddy(gc->account, who, group, NULL);
		g_free(who);
		if (group)
			g_free(group);
	} else if (!g_ascii_strncasecmp(uri, "aim:gochat?", strlen("aim:gochat?"))) {
		char *room;
		GHashTable *components;
		int exch = 5;
		
		uri = uri + strlen("aim:gochat?");
		/* spaces are encoded as +'s */
		
		if (!(room = strstr(uri, "roomname="))) {
			return _("No roomname given.");
		}
		room = room + strlen("roomname=");
		str = g_string_new(NULL);
		while (*room && (*room != '&')) {
			g_string_append_c(str, *room == '+' ? ' ' : *room);
			room++;
		}
		room = g_strdup(str->str);
		g_string_free(str, TRUE);
		components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
				g_free);
		g_hash_table_replace(components, g_strdup("room"), room);
		g_hash_table_replace(components, g_strdup("exchange"),
				g_strdup_printf("%d", exch));

		serv_join_chat(gc, components);
		g_hash_table_destroy(components);
	} else {
		return _("Invalid AIM URI");
	}
	
	
	return NULL;
}



#if 0
static guchar *
UI_build(guint32 *len, guchar type, guchar subtype, va_list args)
{
	guchar *buffer;
	guint32 pos;
	int size;
	void *data;

	*len = sizeof(guchar) * 2 + 4;
	buffer = g_malloc(*len);
	pos = 0;

	memcpy(buffer + pos, &type, sizeof(type)); pos += sizeof(type);
	memcpy(buffer + pos, &subtype, sizeof(subtype)); pos += sizeof(subtype);

	/* we come back and do size last */
	pos += 4;

	size = va_arg(args, int);
	while (size != -1) {
		*len += size;
		buffer = g_realloc(buffer, *len);

		data = va_arg(args, void *);
		memcpy(buffer + pos, data, size);
		pos += size;

		size = va_arg(args, int);
	}

	pos -= sizeof(guchar) * 2 + 4;

	/* now we do size */
	memcpy(buffer + sizeof(guchar) * 2, &pos, 4);

	return buffer;
}

static gint
UI_write(struct UI *ui, guchar *data, gint len)
{
	GError *error = NULL;
	gint sent;
	/* we'll let the write silently fail because the read will pick it up as dead */
	g_io_channel_write_chars(ui->channel, data, len, &sent, &error);
	if (error)
		g_error_free(error);
	return sent;
}

static void
UI_build_write(struct UI *ui, guchar type, guchar subtype, ...)
{
	va_list ap;
	gchar *data;
	guint32 len;

	va_start(ap, subtype);
	data = UI_build(&len, type, subtype, ap);
	va_end(ap);

	UI_write(ui, data, len);

	g_free(data);
}

static void
UI_broadcast(guchar *data, gint len)
{
	GSList *u = uis;
	while (u) {
		struct UI *ui = u->data;
		UI_write(ui, data, len);
		u = u->next;
	}
}

static void
UI_build_broadcast(guchar type, guchar subtype, ...)
{
	va_list ap;
	gchar *data;
	guint32 len;

	if (!uis)
		return;

	va_start(ap, subtype);
	data = UI_build(&len, type, subtype, ap);
	va_end(ap);

	UI_broadcast(data, len);

	g_free(data);
}
#endif

#ifndef _WIN32
static void
meta_handler(struct UI *ui, guchar subtype, gchar *data)
{
	GaimRemotePacket *p;
	GError *error = NULL;
	switch (subtype) {
	case CUI_META_LIST:
		break;
	case CUI_META_QUIT:
		while (uis) {
			ui = uis->data;
			uis = g_slist_remove(uis, ui);
			g_io_channel_shutdown(ui->channel, TRUE, &error);
			g_source_remove(ui->inpa);
			g_free(ui);
		}
		g_timeout_add(0, gaim_core_quit_cb, NULL);
		break;
	case CUI_META_DETACH:
		uis = g_slist_remove(uis, ui);
		g_io_channel_shutdown(ui->channel, TRUE, &error);
		g_source_remove(ui->inpa);
		g_free(ui);
		break;
	case CUI_META_PING:
		p = gaim_remote_packet_new(CUI_TYPE_META, CUI_META_ACK);
		gaim_remote_session_send_packet(g_io_channel_unix_get_fd(ui->channel),
										p);
		gaim_remote_packet_free(p);
		break;
	default:
		gaim_debug(GAIM_DEBUG_WARNING, "cui",
				   "Unhandled meta subtype %d\n", subtype);
		break;
	}

	if(error)
		g_error_free(error);
}

static void
plugin_handler(struct UI *ui, guchar subtype, gpointer data)
{
#ifdef GAIM_PLUGINS
	guint id;
	GaimPlugin *p;

	switch (subtype) {
		/*
	case CUI_PLUGIN_LIST:
		break;
		*/
	case CUI_PLUGIN_LOAD:
		gaim_plugin_load(gaim_plugin_probe(data));
		break;
	case CUI_PLUGIN_UNLOAD:
		memcpy(&id, data, sizeof(id));
		p = g_list_nth_data(gaim_plugins_get_loaded(), id);
		if (p) {
			gaim_plugin_unload(p);
		}
		break;
	default:
		gaim_debug(GAIM_DEBUG_WARNING, "cui",
				   "Unhandled plugin subtype %d\n", subtype);
		break;
	}
#endif
}

static void
user_handler(struct UI *ui, guchar subtype, gchar *data)
{
	guint id;
	GaimAccount *account;

	switch (subtype) {
		/*
	case CUI_USER_LIST:
		break;
	case CUI_USER_ADD:
		break;
	case CUI_USER_REMOVE:
		break;
	case CUI_USER_MODIFY:
		break;
		*/
	case CUI_USER_SIGNON:
		if (!data)
			return;
		memcpy(&id, data, sizeof(id));
		account = g_list_nth_data(gaim_accounts_get_all(), id);
		if (account)
			gaim_account_connect(account);
		/* don't need to do anything here because the UI will get updates from other handlers */
		break;
	default:
		gaim_debug(GAIM_DEBUG_WARNING, "cui",
				   "Unhandled user subtype %d\n", subtype);
		break;
	}
}

static void
message_handler(struct UI *ui, guchar subtype, gchar *data)
{
	switch (subtype) {
	case CUI_MESSAGE_LIST:
		break;
	case CUI_MESSAGE_SEND:
		if (!data)
			return;
		{
			guint id;
			GaimConnection *gc;
			guint len;
			char *who, *msg;
			gint flags;
			int pos = 0;

			memcpy(&id, data + pos, sizeof(id));
			pos += sizeof(id);
			gc = g_list_nth_data(gaim_connections_get_all(), id);
			if (!gc)
				return;

			memcpy(&len, data + pos, sizeof(len));
			pos += sizeof(len);
			who = g_strndup(data + pos, len + 1);
			pos += len;

			memcpy(&len, data + pos, sizeof(len));
			pos += sizeof(len);
			msg = g_strndup(data + pos, len + 1);
			pos += len;

			memcpy(&flags, data + pos, sizeof(flags));
			serv_send_im(gc, who, msg, flags);

			g_free(who);
			g_free(msg);
		}
		break;
	case CUI_MESSAGE_RECV:
		break;
	default:
		gaim_debug(GAIM_DEBUG_WARNING, "cui",
				   "Unhandled message subtype %d\n", subtype);
		break;
	}
}

static gint
gaim_recv(GIOChannel *source, gchar *buf, gint len)
{
	gint total = 0;
	guint cur;

	GError *error = NULL;

	while (total < len) {
		if (g_io_channel_read_chars(source, buf + total, len - total, (gsize *) &cur, &error) != G_IO_STATUS_NORMAL) {
			if (error)
				g_error_free(error);
			return -1;
		}
		if (cur == 0)
			return total;
		total += cur;
	}

	return total;
}

static void
remote_handler(struct UI *ui, guchar subtype, gchar *data, int len)
{
	const char *resp;
	char *send;
	switch (subtype) {
	case CUI_REMOTE_CONNECTIONS:
		break;
	case CUI_REMOTE_URI:
		send = g_malloc(len + 1);
		memcpy(send, data, len);
		send[len] = 0;
		resp = gaim_remote_handle_uri(send);
		g_free(send);
		/* report error */
		break;
	default:
		gaim_debug(GAIM_DEBUG_WARNING, "cui",
				   "Unhandled remote subtype %d\n", subtype);
		break;
	}
}

static gboolean
UI_readable(GIOChannel *source, GIOCondition cond, gpointer data)
{
	struct UI *ui = data;

	gchar type;
	gchar subtype;
	gint len;

	GError *error = NULL;

	gchar *in;

	/* no byte order worries! this'll change if we go to TCP */
	if (gaim_recv(source, &type, sizeof(type)) != sizeof(type)) {
		gaim_debug(GAIM_DEBUG_ERROR, "cui", "UI has abandoned us!\n");
		uis = g_slist_remove(uis, ui);
		g_io_channel_shutdown(ui->channel, TRUE, &error);
		if(error) {
			g_error_free(error);
			error = NULL;
		}
		g_source_remove(ui->inpa);
		g_free(ui);
		return FALSE;
	}

	if (gaim_recv(source, &subtype, sizeof(subtype)) != sizeof(subtype)) {
		gaim_debug(GAIM_DEBUG_ERROR, "cui", "UI has abandoned us!\n");
		uis = g_slist_remove(uis, ui);
		g_io_channel_shutdown(ui->channel, TRUE, &error);
		if(error) {
			g_error_free(error);
			error = NULL;
		}
		g_source_remove(ui->inpa);
		g_free(ui);
		return FALSE;
	}

	if (gaim_recv(source, (gchar *)&len, sizeof(len)) != sizeof(len)) {
		gaim_debug(GAIM_DEBUG_ERROR, "cui", "UI has abandoned us!\n");
		uis = g_slist_remove(uis, ui);
		g_io_channel_shutdown(ui->channel, TRUE, &error);
		if(error) {
			g_error_free(error);
			error = NULL;
		}
		g_source_remove(ui->inpa);
		g_free(ui);
		return FALSE;
	}

	if (len) {
		in = g_new0(gchar, len);
		if (gaim_recv(source, in, len) != len) {
			gaim_debug(GAIM_DEBUG_ERROR, "cui", "UI has abandoned us!\n");
			uis = g_slist_remove(uis, ui);
			g_io_channel_shutdown(ui->channel, TRUE, &error);
			if(error) {
				g_error_free(error);
				error = NULL;
			}
			g_source_remove(ui->inpa);
			g_free(ui);
			return FALSE;
		}
	} else
		in = NULL;

	switch (type) {
		case CUI_TYPE_META:
			meta_handler(ui, subtype, in);
			break;
		case CUI_TYPE_PLUGIN:
			plugin_handler(ui, subtype, in);
			break;
		case CUI_TYPE_USER:
			user_handler(ui, subtype, in);
			break;
			/*
		case CUI_TYPE_CONN:
			conn_handler(ui, subtype, in);
			break;
		case CUI_TYPE_BUDDY:
			buddy_handler(ui, subtype, in);
			break;
			*/
		case CUI_TYPE_MESSAGE:
			message_handler(ui, subtype, in);
			break;
			/*
		case CUI_TYPE_CHAT:
			chat_handler(ui, subtype, in);
			break;
			*/   
	        case CUI_TYPE_REMOTE:
			remote_handler(ui, subtype, in, len);
			break; 
        default:
			gaim_debug(GAIM_DEBUG_WARNING, "cui",
					   "Unhandled type %d\n", type);
			break;
	}

	if (in)
		g_free(in);
	return TRUE;
}

static gboolean
socket_readable(GIOChannel *source, GIOCondition cond, gpointer data)
{
	struct sockaddr_un saddr;
	guint len = sizeof(saddr);
	gint fd;

	struct UI *ui;

	if ((fd = accept(UI_fd, (struct sockaddr *)&saddr, &len)) == -1)
		return FALSE;

	ui = g_new0(struct UI, 1);
	uis = g_slist_append(uis, ui);

	ui->channel = g_io_channel_unix_new(fd);
	ui->inpa = g_io_add_watch(ui->channel, G_IO_IN | G_IO_HUP | G_IO_ERR, UI_readable, ui);
	g_io_channel_unref(ui->channel);

	gaim_debug(GAIM_DEBUG_MISC, "cui", "Got one\n");
	return TRUE;
}

static gint
open_socket(char **error)
{
	struct sockaddr_un saddr;
	gint fd;
	
	while (gaim_remote_session_exists(gaim_session))
		gaim_session++;
	
	gaim_debug(GAIM_DEBUG_MISC, "cui", "Session: %d\n", gaim_session);
	
	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {
		mode_t m = umask(0177);
		saddr.sun_family = AF_UNIX;

		g_snprintf(saddr.sun_path, sizeof(saddr.sun_path), "%s" G_DIR_SEPARATOR_S "gaim_%s.%d",
				g_get_tmp_dir(), g_get_user_name(), gaim_session);
		if (bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) != -1)
			listen(fd, 100);
		else {
			*error = g_strdup_printf(_("Failed to assign %s to a socket:\n%s"),
					   saddr.sun_path, strerror(errno));
			g_log(NULL, G_LOG_LEVEL_CRITICAL,
			      "Failed to assign %s to a socket (Error: %s)",	
			      saddr.sun_path, strerror(errno));
			umask(m);
			return -1;
		}
		umask(m);
	} else
		g_log(NULL, G_LOG_LEVEL_CRITICAL, "Unable to open socket: %s", strerror(errno));
	return fd;
}
#endif /*! _WIN32*/

static gboolean
plugin_load(GaimPlugin *plugin)
{
#ifndef _WIN32
	GIOChannel *channel;
	char *buf;

	if ((UI_fd = open_socket(&buf)) < 0) {
		gaim_notify_error(NULL, NULL, _("Unable to open socket"), buf);
		g_free(buf);
		return FALSE;
	}

	channel = g_io_channel_unix_new(UI_fd);
	watcher = g_io_add_watch(channel, G_IO_IN, socket_readable, NULL);
	g_io_channel_unref(channel);

	return TRUE;
#else
	return FALSE;
#endif
}

static gboolean
plugin_unload(GaimPlugin *plugin)
{
	/* don't save prefs after plugins are gone... */
#ifndef _WIN32
	char buf[1024];

	g_source_remove(watcher);
	close(UI_fd);

	g_snprintf(buf, sizeof(buf), "%s" G_DIR_SEPARATOR_S "gaim_%s.%d",
			g_get_tmp_dir(), g_get_user_name(), gaim_session);

	unlink(buf);

	gaim_debug(GAIM_DEBUG_MISC, "core", "Removed core\n");

	return TRUE;
#else
	return FALSE;
#endif
}

static GaimPluginInfo info =
{
	GAIM_PLUGIN_API_VERSION,                          /**< api_version    */
	GAIM_PLUGIN_STANDARD,                             /**< type           */
	GAIM_GTK_PLUGIN_TYPE,                             /**< ui_requirement */
	0,                                                /**< flags          */
	NULL,                                             /**< dependencies   */
	GAIM_PRIORITY_DEFAULT,                            /**< priority       */

	REMOTE_PLUGIN_ID,                                 /**< id             */
	N_("Remote Control"),                             /**< name           */
	VERSION,                                          /**< version        */
	                                                  /**  summary        */
	N_("Provides remote control for gaim applications."),
	                                                  /**  description    */
	N_("Gives Gaim the ability to be remote-controlled through third-party "
	   "applications or through the gaim-remote tool."),
	"Sean Egan <sean.egan@binghamton.edu>",
	GAIM_WEBSITE,                                     /**< homepage       */

	plugin_load,                                      /**< load           */
	plugin_unload,                                    /**< unload         */
	NULL,                                             /**< destroy        */

	NULL,                                             /**< ui_info        */
	NULL,                                             /**< extra_info     */
	NULL,
	NULL
};

static void
__init_plugin(GaimPlugin *plugin)
{
}

/* This may be horribly wrong. Oh the mayhem! */
GAIM_INIT_PLUGIN(remote, __init_plugin, info)