# HG changeset patch # User Eric Warmenhoven # Date 976776866 0 # Node ID 0f7b837d88d2bed140978e501eb349d4d7f10faa # Parent b2f63d1a989b56877a3d16cd9414b24d14fb3eec [gaim-migrate @ 1267] A jabber plugin. You need to make PLUGIN_LIBS='-lxode -ljabber', probably -DHAVE_CONFIG_H in there too. Oh yeah, you need libxode and libjabber installed, but that was implied already. Don't use this, it doesn't work well. committer: Tailor Script diff -r b2f63d1a989b -r 0f7b837d88d2 plugins/jabber.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/jabber.c Thu Dec 14 06:54:26 2000 +0000 @@ -0,0 +1,778 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ +/* + * gaim + * + * Some code copyright (C) 1998-1999, Mark Spencer + * libfaim code copyright 1998, 1999 Adam Fritzler + * + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "multi.h" +#include "prpl.h" +#include "gaim.h" +#include + +/* The priv member of gjconn's is a gaim_connection for now. */ +#define GJ_GC(x) ((struct gaim_connection *)(x)->priv) + +#define IQ_NONE -1 +#define IQ_AUTH 0 +#define IQ_ROSTER 1 + +typedef struct gjconn_struct +{ + /* Core structure */ + pool p; /* Memory allocation pool */ + int state; /* Connection state flag */ + int fd; /* Connection file descriptor */ + jid user; /* User info */ + char *pass; /* User passwd */ + + /* Stream stuff */ + int id; /* id counter for jab_getid() function */ + char idbuf[9]; /* temporary storage for jab_getid() */ + char *sid; /* stream id from server, for digest auth */ + XML_Parser parser; /* Parser instance */ + xmlnode current; /* Current node in parsing instance.. */ + + /* Event callback ptrs */ + void (*on_state)(struct gjconn_struct *j, int state); + void (*on_packet)(struct gjconn_struct *j, jpacket p); + + void *priv; + +} *gjconn, gjconn_struct; + +typedef void (*gjconn_state_h)(gjconn j, int state); +typedef void (*gjconn_packet_h)(gjconn j, jpacket p); + +static gjconn gjab_new(char *user, char *pass, void *priv); +static void gjab_delete(gjconn j); +static void gjab_state_handler(gjconn j, gjconn_state_h h); +static void gjab_packet_handler(gjconn j, gjconn_packet_h h); +static void gjab_start(gjconn j); +static void gjab_stop(gjconn j); +static int gjab_getfd(gjconn j); +static jid gjab_getjid(gjconn j); +static char *gjab_getsid(gjconn j); +static char *gjab_getid(gjconn j); +static void gjab_send(gjconn j, xmlnode x); +static void gjab_send_raw(gjconn j, const char *str); +static void gjab_recv(gjconn j); +static char *gjab_auth(gjconn j); + +struct jabber_data { + gjconn jc; +}; + +static char *jabber_name() { + return "Jabber"; +} + +char *name() { + return "Jabber"; +} + +char *description() { + return "Allows gaim to use the Jabber protocol"; +} + +#define STATE_EVT(arg) if(j->on_state) { (j->on_state)(j, (arg) ); } + +static gjconn gjab_new(char *user, char *pass, void *priv) +{ + pool p; + gjconn j; + + if(!user) + return(NULL); + + p = pool_new(); + if(!p) + return(NULL); + j = pmalloc_x(p, sizeof(gjconn_struct), 0); + if(!j) + return(NULL); + j->p = p; + + j->user = jid_new(p, user); + j->pass = pstrdup(p, pass); + + j->state = JCONN_STATE_OFF; + j->id = 1; + j->fd = -1; + + j->priv = priv; + + return j; +} + +static void gjab_delete(gjconn j) +{ + if(!j) + return; + + gjab_stop(j); + pool_free(j->p); +} + +static void gjab_state_handler(gjconn j, gjconn_state_h h) +{ + if(!j) + return; + + j->on_state = h; +} + +static void gjab_packet_handler(gjconn j, gjconn_packet_h h) +{ + if(!j) + return; + + j->on_packet = h; +} + +static void gjab_stop(gjconn j) +{ + if(!j || j->state == JCONN_STATE_OFF) + return; + + j->state = JCONN_STATE_OFF; + close(j->fd); + j->fd = -1; + XML_ParserFree(j->parser); +} + +static int gjab_getfd(gjconn j) +{ + if(j) + return j->fd; + else + return -1; +} + +static jid gjab_getjid(gjconn j) +{ + if(j) + return(j->user); + else + return NULL; +} + +static char *gjab_getsid(gjconn j) +{ + if(j) + return(j->sid); + else + return NULL; +} + +static char *gjab_getid(gjconn j) +{ + snprintf(j->idbuf, 8, "%d", j->id++); + return &j->idbuf[0]; +} + +static void gjab_send(gjconn j, xmlnode x) +{ + if (j && j->state != JCONN_STATE_OFF) { + char *buf = xmlnode2str(x); + if (buf) + write(j->fd, buf, strlen(buf)); + debug_printf("gjab_send: %s\n", buf); + } +} + +static void gjab_send_raw(gjconn j, const char *str) +{ + if (j && j->state != JCONN_STATE_OFF) { + write(j->fd, str, strlen(str)); + debug_printf("gjab_send_raw: %s\n", str); + } +} + +static void gjab_reqroster(gjconn j) +{ + xmlnode x; + char *id; + + x = jutil_iqnew(JPACKET__GET, NS_ROSTER); + id = gjab_getid(j); + xmlnode_put_attrib(x, "id", id); + + gjab_send(j, x); + xmlnode_free(x); +} + +static char *gjab_auth(gjconn j) +{ + xmlnode x,y,z; + char *hash, *user, *id; + + if(!j) + return NULL; + + x = jutil_iqnew(JPACKET__SET, NS_AUTH); + id = gjab_getid(j); + xmlnode_put_attrib(x, "id", id); + y = xmlnode_get_tag(x,"query"); + + user = j->user->user; + + if (user) { + z = xmlnode_insert_tag(y, "username"); + xmlnode_insert_cdata(z, user, -1); + } + + z = xmlnode_insert_tag(y, "resource"); + xmlnode_insert_cdata(z, j->user->resource, -1); + + if (j->sid) { + z = xmlnode_insert_tag(y, "digest"); + hash = pmalloc(x->p, strlen(j->sid)+strlen(j->pass)+1); + strcpy(hash, j->sid); + strcat(hash, j->pass); + hash = shahash(hash); + xmlnode_insert_cdata(z, hash, 40); + } else { + z = xmlnode_insert_tag(y, "password"); + xmlnode_insert_cdata(z, j->pass, -1); + } + + gjab_send(j, x); + xmlnode_free(x); + + return id; +} + +static void gjab_recv(gjconn j) +{ + static char buf[4096]; + int len; + + if(!j || j->state == JCONN_STATE_OFF) + return; + + if ((len = read(j->fd, buf, sizeof(buf)-1))) { + buf[len] = '\0'; + debug_printf("input: %s\n", buf); + XML_Parse(j->parser, buf, len, 0); + } else if (len < 0) { + STATE_EVT(JCONN_STATE_OFF); + gjab_stop(j); + } +} + +static void startElement(void *userdata, const char *name, const char **attribs) +{ + xmlnode x; + gjconn j = (gjconn)userdata; + + if(j->current) { + /* Append the node to the current one */ + x = xmlnode_insert_tag(j->current, name); + xmlnode_put_expat_attribs(x, attribs); + + j->current = x; + } else { + x = xmlnode_new_tag(name); + xmlnode_put_expat_attribs(x, attribs); + if(strcmp(name, "stream:stream") == 0) { + /* special case: name == stream:stream */ + /* id attrib of stream is stored for digest auth */ + j->sid = xmlnode_get_attrib(x, "id"); + /* STATE_EVT(JCONN_STATE_AUTH) */ + } else { + j->current = x; + } + } +} + +static void endElement(void *userdata, const char *name) +{ + gjconn j = (gjconn)userdata; + xmlnode x; + jpacket p; + + if(j->current == NULL) { + /* we got */ + STATE_EVT(JCONN_STATE_OFF) + return; + } + + x = xmlnode_get_parent(j->current); + + if(!x) { + /* it is time to fire the event */ + p = jpacket_new(j->current); + + if(j->on_packet) + (j->on_packet)(j, p); + else + xmlnode_free(j->current); + } + + j->current = x; +} + +static void charData(void *userdata, const char *s, int slen) +{ + gjconn j = (gjconn)userdata; + + if (j->current) + xmlnode_insert_cdata(j->current, s, slen); +} + +static void gjab_start(gjconn j) +{ + xmlnode x; + char *t,*t2; + + if(!j || j->state != JCONN_STATE_OFF) + return; + + j->parser = XML_ParserCreate(NULL); + XML_SetUserData(j->parser, (void *)j); + XML_SetElementHandler(j->parser, startElement, endElement); + XML_SetCharacterDataHandler(j->parser, charData); + + j->fd = make_netsocket(5222, j->user->server, NETSOCKET_CLIENT); + if(j->fd < 0) { + STATE_EVT(JCONN_STATE_OFF) + return; + } + j->state = JCONN_STATE_CONNECTED; + STATE_EVT(JCONN_STATE_CONNECTED) + + /* start stream */ + x = jutil_header(NS_CLIENT, j->user->server); + t = xmlnode2str(x); + /* this is ugly, we can create the string here instead of jutil_header */ + /* what do you think about it? -madcat */ + t2 = strstr(t,"/>"); + *t2++ = '>'; + *t2 = '\0'; + gjab_send_raw(j,""); + gjab_send_raw(j,t); + xmlnode_free(x); + + j->state = JCONN_STATE_ON; + STATE_EVT(JCONN_STATE_ON) +} + +static void jabber_callback(gpointer data, gint source, GdkInputCondition condition) { + struct gaim_connection *gc = (struct gaim_connection *)data; + struct jabber_data *jd = (struct jabber_data *)gc->proto_data; + + debug_printf("jabber_callback!\n"); + + gjab_recv(jd->jc); +} + +static void jabber_handlemessage(gjconn j, jpacket p) +{ + xmlnode y; + + char *from = NULL, *msg = NULL; + + from = jid_full(p->from); + if ((y = xmlnode_get_tag(p->x, "body"))) { + msg = xmlnode_get_data(y); + } + + if (!from || !msg) { + return; + } + + debug_printf("jabber: msg from %s: %s\n", from, msg); + + serv_got_im(GJ_GC(j), from, msg, 0); + + return; +} + +static void jabber_handlepresence(gjconn j, jpacket p) +{ + char *to, *from, *type; + struct buddy *b; + + to = xmlnode_get_attrib(p->x, "to"); + from = xmlnode_get_attrib(p->x, "from"); + type = xmlnode_get_attrib(p->x, "type"); + + debug_printf("jabber: presence: %s, %s %s\n", to, from, type); + + if (!(b = find_buddy(GJ_GC(j), from))) + add_buddy(GJ_GC(j), "Extra", from, from); + + if (type && (strcasecmp(type, "unavailable") == 0)) + serv_got_update(GJ_GC(j), from, 0, 0, 0, 0, 0, 0); + else + serv_got_update(GJ_GC(j), from, 1, 0, 0, 0, 0, 0); + + return; +} + +static void jabber_handleroster(gjconn j, xmlnode querynode) +{ + xmlnode x; + + x = xmlnode_get_firstchild(querynode); + while (x) { + xmlnode g; + char *jid, *name, *sub, *ask; + + jid = xmlnode_get_attrib(x, "jid"); + name = xmlnode_get_attrib(x, "name"); + if (name) + printf("name = %s\n", name); + sub = xmlnode_get_attrib(x, "subscription"); + ask = xmlnode_get_attrib(x, "ask"); + + if (ask) { + /* XXX do something */ + debug_printf("jabber: unhandled subscription request (%s/%s/%s/%s)\n", jid, name, sub, ask); + } + + if ((g = xmlnode_get_firstchild(x))) { + while (g) { + if (strncasecmp(xmlnode_get_name(g), "group", 5) == 0) { + struct buddy *b; + char *groupname; + + groupname = xmlnode_get_data(xmlnode_get_firstchild(g)); + if (!(b = find_buddy(GJ_GC(j), jid))) { + printf("adding buddy: %s\n", jid); + b = add_buddy(GJ_GC(j), groupname, jid, name?name:jid); + } else { + printf("updating buddy: %s/%s\n", jid, name); + g_snprintf(b->name, sizeof(b->name), "%s", jid); + g_snprintf(b->show, sizeof(b->show), "%s", name?name:jid); + } + //serv_got_update(GJ_GC(j), b->name, 1, 0, 0, 0, 0, 0); + } + g = xmlnode_get_nextsibling(g); + } + } else { + struct buddy *b; + if (!(b = find_buddy(GJ_GC(j), jid))) { + b = add_buddy(GJ_GC(j), "Extra", jid, name?name:jid); + } + } + + x = xmlnode_get_nextsibling(x); + } +} + +static void jabber_handlepacket(gjconn j, jpacket p) +{ + switch (p->type) { + case JPACKET_MESSAGE: + jabber_handlemessage(j, p); + break; + case JPACKET_PRESENCE: + jabber_handlepresence(j, p); + break; + case JPACKET_IQ: { + + if (jpacket_subtype(p) == JPACKET__RESULT) { + xmlnode querynode; + char *xmlns; + + querynode = xmlnode_get_tag(p->x, "query"); + xmlns = xmlnode_get_attrib(querynode, "xmlns"); + + /* XXX this just doesn't look right */ + if (!xmlns || NSCHECK(querynode, NS_AUTH)) { + xmlnode x; + + debug_printf("auth success\n"); + x = jutil_presnew(0, NULL, NULL); + gjab_send(j, x); + xmlnode_free(x); + + account_online(GJ_GC(j)); + serv_finish_login(GJ_GC(j)); + + gjab_reqroster(j); + + } else if (NSCHECK(querynode, NS_ROSTER)) { + jabber_handleroster(j, querynode); + } else { + debug_printf("jabber:iq:query: %s\n", xmlns); + } + + } else { + xmlnode x; + + debug_printf("auth failed\n"); + x = xmlnode_get_tag(p->x, "error"); + if (x) { + debug_printf("error %d: %s\n\n", + atoi(xmlnode_get_attrib(x, "code")), + xmlnode_get_data(xmlnode_get_firstchild(x))); + hide_login_progress(GJ_GC(j), xmlnode_get_data(xmlnode_get_firstchild(x))); + + } else + hide_login_progress(GJ_GC(j), "unknown error"); + + signoff(GJ_GC(j)); + } + break; + } + default: + debug_printf("jabber: packet type %d (%s)\n", p->type, xmlnode2str(p->x)); + } + + xmlnode_free(p->x); + + return; +} + +static void jabber_handlestate(gjconn j, int state) +{ + switch (state) { + case JCONN_STATE_OFF: + debug_printf("jabber: connection closed\n"); + hide_login_progress(GJ_GC(j), "Unable to connect"); + signoff(GJ_GC(j)); + break; + case JCONN_STATE_CONNECTED: + debug_printf("jabber: connected.\n"); + set_login_progress(GJ_GC(j), 1, "Connected"); + break; + case JCONN_STATE_ON: + debug_printf("jabber: logging in...\n"); + set_login_progress(GJ_GC(j), 1, "Logging in..."); + gjab_auth(j); + break; + default: + debug_printf("state change: %d\n", state); + } + return; +} + +static void jabber_login(struct aim_user *user) { + struct gaim_connection *gc = new_gaim_conn(user); + struct jabber_data *jd = gc->proto_data = g_new0(struct jabber_data, 1); + + debug_printf("jabber_login (u=%s/p=%s)\n", user->username, user->password); + + set_login_progress(gc, 1, "Connecting"); + while (gtk_events_pending()) + gtk_main_iteration(); + + if (!(jd->jc = gjab_new(user->username, user->password, gc))) { + debug_printf("jabber: unable to connect (jab_new failed)\n"); + hide_login_progress(gc, "Unable to connect"); + signoff(gc); + return; + } + + gjab_state_handler(jd->jc, jabber_handlestate); + gjab_packet_handler(jd->jc, jabber_handlepacket); + gjab_start(jd->jc); + + + gc->user = user; /* XXX I assume this is okay...OSCAR does it */ + + gc->inpa = gdk_input_add(jd->jc->fd, + GDK_INPUT_READ | GDK_INPUT_EXCEPTION, + jabber_callback, gc); + + return; + + //signoff(gc); + +#if 0 + struct yahoo_options opt; + struct yahoo_context *ctxt; + opt.connect_mode = YAHOO_CONNECT_NORMAL; + opt.proxy_host = NULL; + ctxt = yahoo_init(user->username, user->password, &opt); + yd->ctxt = ctxt; + + set_login_progress(gc, 1, "Connecting"); + while (gtk_events_pending()) + gtk_main_iteration(); + + if (!ctxt || !yahoo_connect(ctxt)) { + debug_printf("Yahoo: Unable to connect\n"); + hide_login_progress(gc, "Unable to connect"); + signoff(gc); + return; + } + + debug_printf("Yahoo: connected\n"); + + set_login_progress(gc, 3, "Getting Config"); + while (gtk_events_pending()) + gtk_main_iteration(); + + yahoo_get_config(ctxt); + + if (yahoo_cmd_logon(ctxt, YAHOO_STATUS_AVAILABLE)) { + debug_printf("Yahoo: Unable to login\n"); + hide_login_progress(gc, "Unable to login"); + signoff(gc); + return; + } + + if (ctxt->buddies) { + struct yahoo_buddy **buddies; + + for (buddies = ctxt->buddies; *buddies; buddies++) { + struct yahoo_buddy *bud = *buddies; + struct buddy *b; + struct group *g; + + b = find_buddy(gc, bud->id); + if (!b) add_buddy(gc, bud->group, bud->id, bud->id); + } + } + + debug_printf("Yahoo: logged in %s\n", gc->username); + account_online(gc); + serv_finish_login(gc); + + gc->inpa = gdk_input_add(ctxt->sockfd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, + yahoo_callback, gc); +#endif +} + +static void jabber_close(struct gaim_connection *gc) { +#if 0 + struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; + if (gc->inpa) + gdk_input_remove(gc->inpa); + gc->inpa = -1; + yahoo_cmd_logoff(yd->ctxt); + g_free(yd); +#endif +} + +static void jabber_send_im(struct gaim_connection *gc, char *who, char *message, int away) { + xmlnode x, y; + + if (!who || !message) + return; + + x = xmlnode_new_tag("message"); + xmlnode_put_attrib(x, "to", who); + + xmlnode_put_attrib(x, "type", "chat"); + + if (message && strlen(message)) { + y = xmlnode_insert_tag(x, "body"); + xmlnode_insert_cdata(y, message, -1); + } + + gjab_send(((struct jabber_data *)gc->proto_data)->jc, x); +} + +static void jabber_keepalive(struct gaim_connection *gc) { +#if 0 + yahoo_cmd_ping(((struct yahoo_data *)gc->proto_data)->ctxt); +#endif +} + +static void jabber_add_buddy(struct gaim_connection *gc, char *name) { +#if 0 + struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data; + struct yahoo_buddy *tmpbuddy; + struct group *g = find_group_by_buddy(gc, name); + char *group = NULL; + + if (g) { + group = g->name; + } else if (yd->ctxt && yd->ctxt->buddies[0]) { + tmpbuddy = yd->ctxt->buddies[0]; + group = tmpbuddy->group; + } + + if (group) + yahoo_add_buddy(yd->ctxt, name, gc->username, group, ""); +#endif +} + +static struct prpl *my_protocol = NULL; + +void Jabber_init(struct prpl *ret) { + /* the NULL's aren't required but they're nice to have */ + ret->protocol = PROTO_JABBER; + ret->name = jabber_name; + ret->list_icon = NULL; + ret->action_menu = NULL; + ret->login = jabber_login; + ret->close = jabber_close; + ret->send_im = jabber_send_im; + ret->set_info = NULL; + ret->get_info = NULL; + ret->set_away = NULL; + ret->get_away_msg = NULL; + ret->set_dir = NULL; + ret->get_dir = NULL; + ret->dir_search = NULL; + ret->set_idle = NULL; + ret->change_passwd = NULL; + ret->add_buddy = jabber_add_buddy; + ret->add_buddies = NULL; + ret->remove_buddy = NULL; + ret->add_permit = NULL; + ret->add_deny = NULL; + ret->rem_permit = NULL; + ret->rem_deny = NULL; + ret->set_permit_deny = NULL; + ret->warn = NULL; + ret->accept_chat = NULL; + ret->join_chat = NULL; + ret->chat_invite = NULL; + ret->chat_leave = NULL; + ret->chat_whisper = NULL; + ret->chat_send = NULL; + ret->keepalive = jabber_keepalive; + + my_protocol = ret; +} + +char *gaim_plugin_init(GModule *handle) { + load_protocol(Jabber_init); + return NULL; +} + +void gaim_plugin_remove() { + struct prpl *p = find_prpl(PROTO_JABBER); + if (p == my_protocol) + unload_protocol(p); +}