Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial import.

  • Loading branch information...
commit 248ee57e53cfda58ce4812f6db97d546599fac45 0 parents
@bard authored
38 Makefile
@@ -0,0 +1,38 @@
+# If Lua and libpurple on your system aren't installed with package
+# config support, enter their paths manually below. Remember that
+# libpurple depends on libglib2.0.
+
+INC_LUA=$(shell pkg-config lua5.1 --cflags)
+LIB_LUA=$(shell pkg-config lua5.1 --libs)
+
+INC_PURPLE=$(shell pkg-config purple --cflags)
+LIB_PURPLE=$(shell pkg-config purple --libs)
+
+
+# No modification should be needed below
+
+CFLAGS=$(INC_LUA) $(INC_PURPLE) -g -O2 -Wall
+LDLIBS=$(LIB_LUA) $(LIB_PURPLE)
+
+all: purplebridge base64.so
+
+run: all
+ ./purplebridge
+
+%.o: %.c
+ $(CC) -c $(CFLAGS) $(^) -o $(@)
+
+base64.so: lbase64.o
+ $(CC) -o $(@) -shared $(^)
+
+purplebridge: main.o lpurple.o lglib.o tcp_service.o
+ $(CC) $(LDLIBS) $(^) -o $(@)
+
+clean:
+ rm -f *.o *.so
+ rm -f purplebridge
+
+tags:
+ find -name '*.[ch]' | etags -
+
+.PHONY: clean all run tags
1  README
@@ -0,0 +1 @@
+Please see http://github.com/bard/purplebridge/wikis/home
8 callbacks.h
@@ -0,0 +1,8 @@
+#ifndef __MAIN_H__
+#define __CALLBACKS_H__
+
+void on_client_connection(GIOChannel *source);
+void on_client_activity(GIOChannel *source, const char *data);
+void on_client_disconnection(GIOChannel *source);
+
+#endif /* __CALLBACKS_H__ */
4 defines.h
@@ -0,0 +1,4 @@
+#define CUSTOM_USER_DIRECTORY "/dev/null"
+#define CUSTOM_PLUGIN_PATH "/usr/lib/purple-2"
+#define PLUGIN_SAVE_PREF "/purple/nullclient/plugins/saved"
+#define UI_ID "nullclient"
119 lbase64.c
@@ -0,0 +1,119 @@
+/*
+* lbase64.c
+* base64 encoding and decoding for Lua 5.1
+* Luiz Henrique de Figueiredo <lhf@tecgraf.puc-rio.br>
+* 27 Jun 2007 19:04:40
+* This code is hereby placed in the public domain.
+*/
+
+#include <string.h>
+
+#include "lua.h"
+#include "lauxlib.h"
+
+#define MYNAME "base64"
+#define MYVERSION MYNAME " library for " LUA_VERSION " / Jun 2007"
+
+#define uint unsigned int
+
+static const char code[]=
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static void encode(luaL_Buffer *b, uint c1, uint c2, uint c3, int n)
+{
+ unsigned long tuple=c3+256UL*(c2+256UL*c1);
+ int i;
+ char s[4];
+ for (i=0; i<4; i++) {
+ s[3-i] = code[tuple % 64];
+ tuple /= 64;
+ }
+ for (i=n+1; i<4; i++) s[i]='=';
+ luaL_addlstring(b,s,4);
+}
+
+static int Lencode(lua_State *L) /** encode(s) */
+{
+ size_t l;
+ const unsigned char *s=(const unsigned char*)luaL_checklstring(L,1,&l);
+ luaL_Buffer b;
+ int n;
+ luaL_buffinit(L,&b);
+ for (n=l/3; n--; s+=3) encode(&b,s[0],s[1],s[2],3);
+ switch (l%3)
+ {
+ case 1: encode(&b,s[0],0,0,1); break;
+ case 2: encode(&b,s[0],s[1],0,2); break;
+ }
+ luaL_pushresult(&b);
+ return 1;
+}
+
+static void decode(luaL_Buffer *b, int c1, int c2, int c3, int c4, int n)
+{
+ unsigned long tuple=c4+64L*(c3+64L*(c2+64L*c1));
+ char s[3];
+ switch (--n)
+ {
+ case 3: s[2]=tuple;
+ case 2: s[1]=tuple >> 8;
+ case 1: s[0]=tuple >> 16;
+ }
+ luaL_addlstring(b,s,n);
+}
+
+static int Ldecode(lua_State *L) /** decode(s) */
+{
+ size_t l;
+ const char *s=luaL_checklstring(L,1,&l);
+ luaL_Buffer b;
+ int n=0;
+ char t[4];
+ luaL_buffinit(L,&b);
+ for (;;)
+ {
+ int c=*s++;
+ switch (c)
+ {
+ const char *p;
+ default:
+ p=strchr(code,c); if (p==NULL) return 0;
+ t[n++]= p-code;
+ if (n==4)
+ {
+ decode(&b,t[0],t[1],t[2],t[3],4);
+ n=0;
+ }
+ break;
+ case '=':
+ switch (n)
+ {
+ case 1: decode(&b,t[0],0,0,0,1); break;
+ case 2: decode(&b,t[0],t[1],0,0,2); break;
+ case 3: decode(&b,t[0],t[1],t[2],0,3); break;
+ }
+ case 0:
+ luaL_pushresult(&b);
+ return 1;
+ case '\n': case '\r': case '\t': case ' ': case '\f': case '\b':
+ break;
+ }
+ }
+ return 0;
+}
+
+static const luaL_reg R[] =
+{
+ { "encode", Lencode },
+ { "decode", Ldecode },
+ { NULL, NULL }
+};
+
+LUALIB_API int luaopen_base64(lua_State *L)
+{
+ luaL_openlib(L,MYNAME,R,0);
+ lua_pushliteral(L,"version"); /** version */
+ lua_pushliteral(L,MYVERSION);
+ lua_settable(L,-3);
+ return 1;
+}
30 lglib.c
@@ -0,0 +1,30 @@
+#include "lglib.h"
+
+
+/* Lua-callables */
+
+static int l_g_io_channel_write(lua_State *L) {
+ guint num_written = 0;
+ unsigned int datalen = 0;
+ GIOChannel *channel;
+ const char *data;
+
+ channel = (GIOChannel *) lua_touserdata(L, 1);
+ data = luaL_checklstring(L, 2, &datalen);
+
+ g_io_channel_write(channel, data, datalen, &num_written);
+ //g_io_channel_flush(source, &err);
+ return 0;
+}
+
+
+static const struct luaL_reg glib [] = {
+ {"io_channel_write", l_g_io_channel_write},
+ {NULL, NULL}
+};
+
+int luaopen_glib(lua_State *L) {
+ luaL_openlib(L, "glib", glib, 0);
+ return 0;
+}
+
13 lglib.h
@@ -0,0 +1,13 @@
+#ifndef __LUA_GLIB__
+#define __LUA_GLIB__
+
+#include <glib.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+int luaopen_glib(lua_State *L);
+
+#endif /* __LUA_GLIB__ */
+
52 lpurple.c
@@ -0,0 +1,52 @@
+#include "lpurple.h"
+
+
+/* Lua-callables */
+
+static int l_get_protocols(lua_State *L) {
+ GList *iter;
+ int i;
+
+ lua_newtable(L);
+
+ iter = purple_plugins_get_protocols();
+ for (i = 1; iter; iter = iter->next) {
+ PurplePlugin *plugin = iter->data;
+ PurplePluginInfo *info = plugin->info;
+ if (info && info->name) {
+ lua_pushnumber(L, i++);
+ lua_pushstring(L, info->id);
+ lua_settable(L, -3);
+ }
+ }
+
+ return 1;
+}
+
+static int l_signon(lua_State *L) {
+ const char *prpl = luaL_checkstring(L, 1);
+ const char *name = luaL_checkstring(L, 2);
+ const char *password = luaL_checkstring(L, 3);
+ PurpleAccount *account;
+ PurpleSavedStatus *status;
+
+ account = purple_account_new(name, prpl);
+ purple_account_set_password(account, password);
+ purple_account_set_enabled(account, UI_ID, TRUE);
+ status = purple_savedstatus_new(NULL, PURPLE_STATUS_AVAILABLE);
+ purple_savedstatus_activate(status);
+
+ return 0;
+}
+
+static const struct luaL_reg purplelib [] = {
+ {"get_protocols", l_get_protocols},
+ {"signon", l_signon},
+ {NULL, NULL}
+};
+
+int luaopen_purple(lua_State *L) {
+ luaL_openlib(L, "purple", purplelib, 0);
+ return 0;
+}
+
15 lpurple.h
@@ -0,0 +1,15 @@
+#ifndef __LUA_PURPLE__
+#define __LUA_PURPLE__
+
+#include <purple.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#include "defines.h"
+
+int luaopen_purple(lua_State *L);
+
+#endif /* __LUA_PURPLE__ */
+
319 main.c
@@ -0,0 +1,319 @@
+/*
+ * 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 Street, Fifth Floor, Boston, MA 02111-1301 USA
+ *
+ */
+
+#include "purple.h"
+#include "lpurple.h"
+#include "lglib.h"
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#include <glib.h>
+
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "defines.h"
+#include "tcp_service.h"
+
+
+lua_State *L;
+
+
+
+/**
+ * The following eventloop functions are used in both pidgin and purple-text. If your
+ * application uses glib mainloop, you can safely use this verbatim.
+ */
+#define PURPLE_GLIB_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR)
+#define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)
+
+typedef struct _PurpleGLibIOClosure {
+ PurpleInputFunction function;
+ guint result;
+ gpointer data;
+} PurpleGLibIOClosure;
+
+static void purple_glib_io_destroy(gpointer data)
+{
+ g_free(data);
+}
+
+static gboolean purple_glib_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ PurpleGLibIOClosure *closure = data;
+ PurpleInputCondition purple_cond = 0;
+
+ if (condition & PURPLE_GLIB_READ_COND)
+ purple_cond |= PURPLE_INPUT_READ;
+ if (condition & PURPLE_GLIB_WRITE_COND)
+ purple_cond |= PURPLE_INPUT_WRITE;
+
+ closure->function(closure->data, g_io_channel_unix_get_fd(source),
+ purple_cond);
+
+ return TRUE;
+}
+
+static guint glib_input_add(gint fd, PurpleInputCondition condition, PurpleInputFunction function,
+ gpointer data)
+{
+ PurpleGLibIOClosure *closure = g_new0(PurpleGLibIOClosure, 1);
+ GIOChannel *channel;
+ GIOCondition cond = 0;
+
+ closure->function = function;
+ closure->data = data;
+
+ if (condition & PURPLE_INPUT_READ)
+ cond |= PURPLE_GLIB_READ_COND;
+ if (condition & PURPLE_INPUT_WRITE)
+ cond |= PURPLE_GLIB_WRITE_COND;
+
+ channel = g_io_channel_unix_new(fd);
+ closure->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond,
+ purple_glib_io_invoke, closure, purple_glib_io_destroy);
+
+ g_io_channel_unref(channel);
+ return closure->result;
+}
+
+static PurpleEventLoopUiOps glib_eventloops =
+{
+ g_timeout_add,
+ g_source_remove,
+ glib_input_add,
+ g_source_remove,
+ NULL,
+#if GLIB_CHECK_VERSION(2,14,0)
+ g_timeout_add_seconds,
+#else
+ NULL,
+#endif
+
+ /* padding */
+ NULL,
+ NULL,
+ NULL
+};
+/*** End of the eventloop functions. ***/
+
+/*** Conversation uiops ***/
+static void
+null_write_conv(PurpleConversation *conv, const char *who, const char *alias,
+ const char *message, PurpleMessageFlags flags, time_t mtime)
+{
+ const char *name;
+ if (alias && *alias)
+ name = alias;
+ else if (who && *who)
+ name = who;
+ else
+ name = NULL;
+
+ printf("(%s) %s %s: %s\n", purple_conversation_get_name(conv),
+ purple_utf8_strftime("(%H:%M:%S)", localtime(&mtime)),
+ name, message);
+}
+
+static PurpleConversationUiOps null_conv_uiops =
+{
+ NULL, /* create_conversation */
+ NULL, /* destroy_conversation */
+ NULL, /* write_chat */
+ NULL, /* write_im */
+ null_write_conv, /* write_conv */
+ NULL, /* chat_add_users */
+ NULL, /* chat_rename_user */
+ NULL, /* chat_remove_users */
+ NULL, /* chat_update_user */
+ NULL, /* present */
+ NULL, /* has_focus */
+ NULL, /* custom_smiley_add */
+ NULL, /* custom_smiley_write */
+ NULL, /* custom_smiley_close */
+ NULL, /* send_confirm */
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+null_ui_init(void)
+{
+ /**
+ * This should initialize the UI components for all the modules. Here we
+ * just initialize the UI for conversations.
+ */
+ purple_conversations_set_ui_ops(&null_conv_uiops);
+}
+
+static PurpleCoreUiOps null_core_uiops =
+{
+ NULL,
+ NULL,
+ null_ui_init,
+ NULL,
+
+ /* padding */
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+init_libpurple(void)
+{
+ /* Set a custom user directory (optional) */
+ purple_util_set_user_dir(CUSTOM_USER_DIRECTORY);
+
+ /* We do not want any debugging for now to keep the noise to a minimum. */
+ purple_debug_set_enabled(FALSE);
+
+ /* Set the core-uiops, which is used to
+ * - initialize the ui specific preferences.
+ * - initialize the debug ui.
+ * - initialize the ui components for all the modules.
+ * - uninitialize the ui components for all the modules when the core terminates.
+ */
+ purple_core_set_ui_ops(&null_core_uiops);
+
+ /* Set the uiops for the eventloop. If your client is glib-based, you can safely
+ * copy this verbatim. */
+ purple_eventloop_set_ui_ops(&glib_eventloops);
+
+ /* Set path to search for plugins. The core (libpurple) takes care of loading the
+ * core-plugins, which includes the protocol-plugins. So it is not essential to add
+ * any path here, but it might be desired, especially for ui-specific plugins. */
+ purple_plugins_add_search_path(CUSTOM_PLUGIN_PATH);
+
+ /* Now that all the essential stuff has been set, let's try to init the core. It's
+ * necessary to provide a non-NULL name for the current ui to the core. This name
+ * is used by stuff that depends on this ui, for example the ui-specific plugins. */
+ if (!purple_core_init(UI_ID)) {
+ /* Initializing the core failed. Terminate. */
+ fprintf(stderr,
+ "libpurple initialization failed. Dumping core.\n"
+ "Please report this!\n");
+ abort();
+ }
+
+ /* Create and load the buddylist. */
+ purple_set_blist(purple_blist_new());
+ purple_blist_load();
+
+ /* Load the preferences. */
+ purple_prefs_load();
+
+ /* Load the desired plugins. The client should save the list of loaded plugins in
+ * the preferences using purple_plugins_save_loaded(PLUGIN_SAVE_PREF) */
+ purple_plugins_load_saved(PLUGIN_SAVE_PREF);
+
+ /* Load the pounces. */
+ purple_pounces_load();
+
+ g_debug("libpurple initialized.");
+}
+
+/* Lua-callables */
+
+void l_error(lua_State *L, const char *fmt, ...) {
+ va_list argp;
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
+ lua_close(L);
+ exit(EXIT_FAILURE);
+}
+
+/* Callbacks */
+
+void on_client_connection(GIOChannel *source) {
+ lua_getglobal(L, "on_client_connection");
+ lua_pushlightuserdata(L, source);
+ if(lua_pcall(L, 1, 0, 0) != 0)
+ l_error(L, "error running function `on_client_connection': %s", lua_tostring(L, -1));
+}
+
+void on_client_activity(GIOChannel *source, const char *data) {
+ lua_getglobal(L, "on_client_activity");
+ lua_pushlightuserdata(L, source);
+ lua_pushstring(L, data);
+ if(lua_pcall(L, 2, 0, 0) != 0)
+ l_error(L, "error running function `on_client_activity': %s", lua_tostring(L, -1));
+}
+
+void on_client_disconnection(GIOChannel *source) {
+ lua_getglobal(L, "on_client_disconnection");
+ lua_pushlightuserdata(L, source);
+ if(lua_pcall(L, 1, 0, 0) != 0)
+ l_error(L, "error running function `on_client_disconnection': %s", lua_tostring(L, -1));
+}
+
+static void
+signed_on(PurpleConnection *gc, gpointer null)
+{
+ PurpleAccount *account = purple_connection_get_account(gc);
+
+ lua_getglobal(L, "on_network_signon");
+ lua_pushstring(L, account->username);
+ lua_pushstring(L, account->protocol_id);
+ if(lua_pcall(L, 2, 0, 0) != 0)
+ l_error(L, "error running function `on_network_signon': %s", lua_tostring(L, -1));
+}
+
+static void
+connect_to_signals(void)
+{
+ static int handle;
+ purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
+ PURPLE_CALLBACK(signed_on), NULL);
+}
+
+int main(int argc, char *argv[])
+{
+ GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+ L = lua_open();
+ luaL_openlibs(L);
+ luaopen_purple(L);
+ luaopen_glib(L);
+
+ /* libpurple's built-in DNS resolution forks processes to perform
+ * blocking lookups without blocking the main process. It does not
+ * handle SIGCHLD itself, so if the UI does not you quickly get an army
+ * of zombie subprocesses marching around.
+ */
+ signal(SIGCHLD, SIG_IGN);
+
+ init_libpurple();
+
+ tcp_socket_init(5222);
+
+ connect_to_signals();
+
+ if (luaL_loadfile(L, "main.lua") || lua_pcall(L, 0, 0, 0))
+ l_error(L, "cannot run file: %s", lua_tostring(L, -1));
+
+ g_main_loop_run(loop);
+
+ return 0;
+}
+
31 main.lua
@@ -0,0 +1,31 @@
+require('server')
+
+function debug(msg)
+ print('** (lua): DEBUG: ' .. msg)
+ print()
+end
+
+function on_client_connection(conn)
+ debug('Client connected')
+ --sessions[conn] = session_create(conn)
+ sess = Session:new(conn)
+end
+
+function on_client_activity(conn, data)
+ --session_receive(sessions[con])
+ sess:receive(data)
+end
+
+function on_client_disconnection(conn)
+ --session_close(sessions[conn])
+ sess:close()
+end
+
+-- local protocols = {}
+-- for i,name in ipairs(purple.get_protocols()) do
+-- table.insert(protocols, name)
+-- end
+-- debug('found protocol plugins: ' .. table.concat(protocols, ', '))
+
+--purple.signon('prpl-msn', 'milly.tom@hotmail.it', 'potina')
+
334 server.lua
@@ -0,0 +1,334 @@
+require 'lxp'
+require 'base64'
+
+-- Utilities
+----------------------------------------------------------------------
+
+local function dbg(msg)
+ print(msg)
+ print()
+end
+
+local function warn(msg)
+ print('*** WARNING ***: ' .. msg)
+ print()
+end
+
+local function get_child(el, tag)
+ for i, child in ipairs(el) do
+ if child.tag == tag then
+ return child
+ end
+ end
+end
+
+local function has_child(el, tag)
+ return get_child(el, tag)
+end
+
+local function ser(el)
+ if not el.tag then
+ return el
+ end
+
+ local ins = table.insert
+ local s = {}
+ ins(s, '<' .. el.tag)
+
+ if el.attr then
+ for k,v in pairs(el.attr) do
+ if type(k) ~= 'number' then
+ ins(s, ' ' .. k .. '="' .. v .. '"')
+ end
+ end
+ end
+
+ if table.getn(el) > 0 then
+ ins(s, '>')
+
+ for i,child in ipairs(el) do
+ ins(s, ser(child))
+ end
+
+ ins(s, '</' .. el.tag .. '>')
+ else
+ ins(s, '/>')
+ end
+
+ return table.concat(s)
+end
+
+
+----------------------------------------------------------------------
+-- Raw I/O
+
+local function to_client(thing)
+ local data
+ if type(thing) == 'table' then
+ data = ser(thing)
+ else
+ data = thing
+ end
+
+ dbg('(to_client) BRIDGE -> CLIENT: ' .. data)
+end
+
+----------------------------------------------------------------------
+-- Us -> Purple
+
+
+
+----------------------------------------------------------------------
+-- Purple -> XMPP
+
+local function roster_retrieved(el, contacts)
+ dbg('(roster_retrieved) PURPLE -> BRIDGE: [contacts]')
+
+ local response = {
+ tag = 'iq',
+ attr = { type = 'result', id = el.attr.id }
+ }
+
+ for i,contact in ipairs(contacts) do
+ table.insert(
+ response, {
+ tag = 'item',
+ attr = {
+ jid = contact,
+ subscription = 'both'
+ }
+ })
+ end
+
+ to_client(response)
+end
+
+local function got_auth_result(el, error)
+ if not error then
+ to_client([[<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>]])
+ end
+end
+
+
+----------------------------------------------------------------------
+-- Client -> Us
+
+local function stream(attr)
+ local attrvals = {}
+ for k,v in pairs(attr) do
+ if type(k)~= 'number' then table.insert(attrvals, k .. ': ' .. v) end
+ end
+
+ dbg('(stream) CLIENT -> BRIDGE: <stream> (' .. table.concat(attrvals, ', ') ..')')
+
+ if attr.to == 'msn' then
+ to_client('<stream:features xmlns:stream="http://etherx.jabber.org/streams"><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">' ..
+ '<mechanism>PLAIN</mechanism></mechanisms>' ..
+ '</stream:features>')
+ else
+ warn('foreign network not recognized (' .. attr.to .. ')')
+ end
+end
+
+local function message(el)
+ -- does not handle groupchat...
+ if el.attr.type == 'chat' or el.attr.type == 'normal' then
+ purple.send_message(el)
+ end
+end
+
+local function iq(el)
+ if el.attr.type == 'get' and has_child(el, 'jabber:iq:roster|query') then
+ purple.retrieve_roster(el, roster_retrieved)
+ end
+end
+
+local function presence(el)
+ if not el.attr.type then
+ purple.set_status(el)
+ elseif has_child('http://jabber.org/protocol/muc|user') then
+ purple.join_room(el)
+ elseif el.attr.type == 'subscribe' then
+ purple.subscribe_to_presence(el)
+ elseif el.attr.type == 'unsubscribe' then
+ purple.unsubscribe_from_presence(el)
+ end
+end
+
+local function auth(el)
+ local authstr, address, user, pwd, protocol
+
+ assert(el.attr.mechanism == 'PLAIN')
+
+ authstr = base64.decode(el[1])
+ address, user, pwd = authstr:match("([^%z]+)%z([^%z]+)%z([^%z]+)")
+ protocol = address:match('@(.+)')
+ purple.signon('prpl-' .. protocol, user:gsub('%%', '@'), pwd)
+end
+
+function on_network_signon(username, protocol)
+ dbg('(on_network_signon) ' .. username .. ' logged on on ' .. protocol)
+end
+
+local function element(el)
+ local data = ser(el)
+ dbg('(element) CLIENT -> BRIDGE: ' .. data)
+
+ if el.tag == 'jabber:client|message' then
+ message(el)
+ elseif el.tag == 'jabber:client|iq' then
+ iq(el)
+ elseif el.tag == 'jabber:client|presence' then
+ presence(el)
+ elseif el.tag == 'urn:ietf:params:xml:ns:xmpp-sasl|auth' then
+ auth(el)
+ end
+end
+
+
+-- XMPP session
+----------------------------------------------------------------------
+
+Session = {}
+
+function Session:stream(attr)
+ -- local attrvals = {}
+ -- for k,v in pairs(attr) do
+ -- if type(k)~= 'number' then table.insert(attrvals, k .. ': ' .. v) end
+ -- end
+
+ -- dbg('CLIENT -> BRIDGE: <stream> (' .. table.concat(attrvals, ', ') ..')')
+
+ if attr.to == 'msn' then
+ self:to_client('<stream:features xmlns:stream="http://etherx.jabber.org/streams"><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">' ..
+ '<mechanism>PLAIN</mechanism></mechanisms>' ..
+ '</stream:features>')
+ else
+ warn('foreign network not recognized (' .. attr.to .. ')')
+ end
+end
+
+function Session:to_client(thing)
+ local data
+ if type(thing) == 'table' then
+ data = ser(thing)
+ else
+ data = thing
+ end
+
+ glib.io_channel_write(self.conn, data)
+ dbg('(Session:to_client) BRIDGE -> CLIENT: ' .. data)
+end
+
+function Session:new(conn)
+ o = {}
+ setmetatable(o, self)
+ self.__index = self
+ self.conn = conn
+
+ local stack = {{}}
+
+ local function start_element(p, tag, attr)
+ if tag == 'http://etherx.jabber.org/streams|stream' then
+ self:stream(attr)
+ return
+ end
+
+ table.insert(stack, {tag = tag, attr = attr})
+ end
+
+ local function end_element(p, tag)
+ if tag == 'http://etherx.jabber.org/streams|stream' then
+ return
+ end
+
+ local el = table.remove(stack)
+ assert(el.tag == tag)
+ local level = table.getn(stack)
+ table.insert(stack[level], el)
+
+ if level == 1 then
+ element(stack[1][1])
+ stack = {{}}
+ end
+ end
+
+ local function character_data(p, txt)
+ local el = stack[table.getn(stack)]
+ local n = table.getn(el)
+ local level = table.getn(stack)
+ if level == 1 then
+ return
+ end
+
+ if type(el[n]) == "string" then
+ el[n] = el[n] .. txt
+ else
+ table.insert(el, txt)
+ end
+ end
+
+ self.parser = lxp.new({ StartElement = start_element,
+ EndElement = end_element,
+ CharacterData = character_data }, '|')
+ return self
+end
+
+function Session:receive(data)
+ dbg('(Session:receive) CLIENT -> BRIDGE: ' .. data)
+ self.parser:parse(data)
+end
+
+function Session:close()
+ self.parser:close()
+end
+
+
+-- Test
+----------------------------------------------------------------------
+
+-- command line if not called as library
+if (arg ~= nil) then
+
+ purple = {}
+ function purple.signon(user, pwd, callback)
+ dbg('PURPLE <- BRIDGE: signon("' .. user .. '", "' .. pwd .. '")')
+ callback(nil)
+ end
+
+ function purple.retrieve_roster(el, callback)
+ dbg('PURPLE <- BRIDGE: retrieve_roster()')
+ callback(el, {'someone', 'sometwo'})
+ end
+
+ function purple.set_status(el)
+ dbg('PURPLE <- BRIDGE: set_status()')
+ end
+
+ function purple.subscribe_to_presence(el)
+ dbg('PURPLE <- BRIDGE: subscribe_to_presence() of ' .. el.attr.to)
+ end
+
+ function purple.send_message(el)
+ local body = get_child(el, 'jabber:client|body')
+ local content = body[1]
+
+ dbg('PURPLE <- BRIDGE: send_message() to ' .. el.attr.to ..
+ ' with content "' .. content .. '"...')
+ end
+
+ local s = Session:new()
+ print('------------------------------------------------------------')
+ s:receive([[<?xml version='1.0'?>]])
+ s:receive([[<stream:stream xmlns='jabber:client' ]])
+ s:receive([[xmlns:stream='http://etherx.jabber.org/streams' to='msn' xml:lang='en' version='1.0'>]])
+ s:receive([[<auth mechanism="PLAIN" xmlns="urn:ietf:params:xml:ns:xmpp-sasl">dXNlciVob3RtYWlsLmNvbQBzZWNyZXQ=</auth>]])
+ s:receive([[<presence/>]])
+ s:receive([[<iq type='get' id='rost1'><query xmlns='jabber:iq:roster'/></iq>]])
+ s:receive([[<message to='me@here'><body>hello</body></message>]])
+ s:receive([[<message to='someone@there' type='chat'><body>again</body></message>]])
+ s:receive([[</stream:stream>]])
+ s:close()
+else
+ module('server',package.seeall)
+end
152 tcp_service.c
@@ -0,0 +1,152 @@
+/*
+ * $Id: $
+ *
+ * TCP Service -- Handles all socket level issues.
+ */
+
+#include <glib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "callbacks.h"
+
+/****************************************************************************
+ * Forward references
+ ***************************************************************************/
+gboolean tcp_new_connection (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+gboolean tcp_client_activity (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+/****************************************************************************
+ * Implementation.
+ ***************************************************************************/
+void
+tcp_enable_reuseaddr (gint sock)
+{
+ gint tmp = 1;
+ if (sock < 0)
+ return;
+ if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (gchar *)&tmp,
+ sizeof (tmp)) == -1)
+ perror ("Bah! Bad setsockopt ()\n");
+}
+
+void
+tcp_enable_nbio (gint fd)
+{
+ if (fcntl (fd, F_SETOWN, getpid()) == -1)
+ perror ("fcntl (F_SETOWN) error\n");
+ if (fcntl (fd, F_SETFL, FNDELAY) == -1)
+ perror ("fcntl (F_SETFL, FNDELAY\n");
+}
+
+void
+tcp_socket_init (gint port)
+{
+ gint s;
+ struct sockaddr_in addr;
+ GIOChannel *channel;
+
+ g_debug("initializing tcp");
+
+ if (port <= 0)
+ return ;
+
+ s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (s == -1)
+ return;
+ tcp_enable_reuseaddr (s);
+ memset (&addr, 0, sizeof (addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons ((u_short)port);
+ addr.sin_addr.s_addr = INADDR_ANY;
+ if (bind (s, (struct sockaddr *)&addr, sizeof (addr)) == -1) {
+ close (s);
+ return ;
+ } if (listen (s, 5) == -1) {
+ close (s);
+ return ;
+ }
+ channel = g_io_channel_unix_new (s);
+ g_io_add_watch (channel, G_IO_IN, tcp_new_connection, NULL);
+}
+
+/****************************************************************************
+ * Callback functions
+ ***************************************************************************/
+/**
+ * tcp_new_connection
+ * Will accept a new connection and add the client to the list of clients.
+ * actual client communication is handled elsewhere.
+ **/
+gboolean
+tcp_new_connection (GIOChannel *source, GIOCondition cond, gpointer data)
+{
+ gint new; /* new socket descriptor */
+ guint client;
+ GIOChannel *new_channel;
+ struct sockaddr_in client_addr;
+
+ g_debug("new connection");
+
+ if (cond == G_IO_IN) {
+ if ( (new = accept (g_io_channel_unix_get_fd (source),
+ (struct sockaddr *)&client_addr, &client)) < 0) {
+ g_warning ("Unable to accept new connection.");
+ return FALSE;
+ }
+ new_channel = g_io_channel_unix_new (new);
+ g_io_channel_set_encoding(new_channel, NULL, NULL);
+ g_io_channel_set_buffered(new_channel, FALSE);
+ g_io_add_watch (new_channel, G_IO_IN | G_IO_HUP, tcp_client_activity,
+ NULL);
+ on_client_connection(new_channel);
+ }
+ return TRUE;
+}
+
+/**
+ * tcp_client_activity
+ * Handles input from the clients and passes it off to the parser/dispatcher.
+ * This will get the raw data from the socket, make sure there is a NULL
+ * terminator, and call the command dispatcher.
+ * If the client has disconnected (condition G_IO_HUP), close the channel and remove
+ * this callback by returning FALSE.
+ **/
+gboolean
+tcp_client_activity (GIOChannel *source, GIOCondition cond, gpointer data)
+{
+ gchar buf[1024];
+ guint num_read = 0;
+
+ if (cond == G_IO_IN) {
+ if (g_io_channel_read (source, buf, sizeof (buf), &num_read) == G_IO_ERROR_NONE) {
+ if(num_read == 0) {
+ g_warning("Zero chars read. Connection lost? Closing channel.\n");
+ on_client_disconnection(source);
+ g_io_channel_close(source);
+ return FALSE;
+ } else {
+ buf[num_read] = '\0'; /* Make sure its null terminated */
+ on_client_activity(source, buf);
+ }
+ }
+ } else if (cond == G_IO_HUP) {
+ g_print ("Connection lost.\n");
+ on_client_disconnection(source);
+ g_io_channel_close(source);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
8 tcp_service.h
@@ -0,0 +1,8 @@
+#ifndef __TCP_SERVICE_H__
+#define __TCP_SERVICE_H__
+
+#include <glib.h>
+
+void tcp_socket_init (gint port);
+
+#endif /* __TCP_SERVICE_H__ */
Please sign in to comment.
Something went wrong with that request. Please try again.