aboutsummaryrefslogtreecommitdiffstats
path: root/protocols/jabber
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/jabber')
-rw-r--r--protocols/jabber/Makefile2
-rw-r--r--protocols/jabber/conference.c246
-rw-r--r--protocols/jabber/io.c60
-rw-r--r--protocols/jabber/iq.c35
-rw-r--r--protocols/jabber/jabber.c76
-rw-r--r--protocols/jabber/jabber.h64
-rw-r--r--protocols/jabber/jabber_util.c178
-rw-r--r--protocols/jabber/message.c31
-rw-r--r--protocols/jabber/presence.c71
-rw-r--r--protocols/jabber/xmltree.c8
-rw-r--r--protocols/jabber/xmltree.h8
11 files changed, 680 insertions, 99 deletions
diff --git a/protocols/jabber/Makefile b/protocols/jabber/Makefile
index e81e6c5a..e042f812 100644
--- a/protocols/jabber/Makefile
+++ b/protocols/jabber/Makefile
@@ -9,7 +9,7 @@
-include ../../Makefile.settings
# [SH] Program variables
-objects = io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o
+objects = conference.o io.o iq.o jabber.o jabber_util.o message.o presence.o sasl.o xmltree.o
CFLAGS += -Wall
LFLAGS += -r
diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c
new file mode 100644
index 00000000..3fc9ee70
--- /dev/null
+++ b/protocols/jabber/conference.c
@@ -0,0 +1,246 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Jabber module - Conference rooms *
+* *
+* Copyright 2007 Wilmer van der Gaast <wilmer@gaast.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., *
+* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
+* *
+\***************************************************************************/
+
+#include "jabber.h"
+
+struct groupchat *jabber_chat_join( struct im_connection *ic, char *room, char *nick, char *password )
+{
+ struct jabber_chat *jc;
+ struct xt_node *node;
+ struct groupchat *c;
+ char *roomjid;
+
+ roomjid = g_strdup_printf( "%s/%s", room, nick );
+ node = xt_new_node( "x", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_MUC );
+ node = jabber_make_packet( "presence", NULL, roomjid, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ g_free( roomjid );
+ xt_free_node( node );
+ return NULL;
+ }
+ xt_free_node( node );
+
+ jc = g_new0( struct jabber_chat, 1 );
+ jc->name = jabber_normalize( room );
+
+ if( ( jc->me = jabber_buddy_add( ic, roomjid ) ) == NULL )
+ {
+ g_free( roomjid );
+ g_free( jc->name );
+ g_free( jc );
+ return NULL;
+ }
+
+ /* roomjid isn't normalized yet, and we need an original version
+ of the nick to send a proper presence update. */
+ jc->my_full_jid = roomjid;
+
+ c = imcb_chat_new( ic, room );
+ c->data = jc;
+
+ return c;
+}
+
+void jabber_chat_free( struct groupchat *c )
+{
+ struct jabber_chat *jc = c->data;
+
+ jabber_buddy_remove_bare( c->ic, jc->name );
+
+ g_free( jc->my_full_jid );
+ g_free( jc->name );
+ g_free( jc );
+
+ imcb_chat_free( c );
+}
+
+int jabber_chat_msg( struct groupchat *c, char *message, int flags )
+{
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+ struct xt_node *node;
+
+ node = xt_new_node( "body", message, NULL );
+ node = jabber_make_packet( "message", "groupchat", jc->name, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ xt_free_node( node );
+ return 0;
+ }
+ xt_free_node( node );
+
+ return 1;
+}
+
+int jabber_chat_leave( struct groupchat *c, const char *reason )
+{
+ struct im_connection *ic = c->ic;
+ struct jabber_chat *jc = c->data;
+ struct xt_node *node;
+
+ node = xt_new_node( "x", NULL, NULL );
+ xt_add_attr( node, "xmlns", XMLNS_MUC );
+ node = jabber_make_packet( "presence", "unavailable", jc->my_full_jid, node );
+
+ if( !jabber_write_packet( ic, node ) )
+ {
+ xt_free_node( node );
+ return 0;
+ }
+ xt_free_node( node );
+
+ return 1;
+}
+
+/* Not really the same syntax as the normal pkt_ functions, but this isn't
+ called by the xmltree parser directly and this way I can add some extra
+ parameters so we won't have to repeat too many things done by the caller
+ already. */
+void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node )
+{
+ struct groupchat *chat;
+ struct xt_node *c;
+ char *type = xt_find_attr( node, "type" );
+ struct jabber_chat *jc;
+ char *s;
+
+ if( ( chat = jabber_chat_by_name( ic, bud->bare_jid ) ) == NULL )
+ {
+ /* How could this happen?? We could do kill( self, 11 )
+ now or just wait for the OS to do it. :-) */
+ return;
+ }
+
+ jc = chat->data;
+
+ if( type == NULL && !( bud->flags & JBFLAG_IS_CHATROOM ) )
+ {
+ bud->flags |= JBFLAG_IS_CHATROOM;
+ /* If this one wasn't set yet, this buddy just joined the chat.
+ Slightly hackish way of finding out eh? ;-) */
+
+ /* This is pretty messy... Here it sets ext_jid to the real
+ JID of the participant. Works for non-anonymized channels.
+ Might break if someone joins a chat twice, though. */
+ for( c = node->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+ if( ( s = xt_find_attr( c, "xmlns" ) ) &&
+ ( strcmp( s, XMLNS_MUC_USER ) == 0 ) )
+ {
+ c = xt_find_node( c->children, "item" );
+ if( ( s = xt_find_attr( c, "jid" ) ) )
+ {
+ /* Yay, found what we need. :-) */
+ bud->ext_jid = jabber_normalize( s );
+ break;
+ }
+ }
+
+ /* Make up some other handle, if necessary. */
+ if( bud->ext_jid == NULL )
+ {
+ if( bud == jc->me )
+ {
+ bud->ext_jid = jabber_normalize( ic->acc->user );
+ }
+ else
+ {
+ int i;
+
+ /* Don't want the nick to be at the end, so let's
+ think of some slightly different notation to use
+ for anonymous groupchat participants in BitlBee. */
+ bud->ext_jid = g_strdup_printf( "%s=%s", bud->resource, bud->bare_jid );
+
+ /* And strip any unwanted characters. */
+ for( i = 0; bud->resource[i]; i ++ )
+ if( bud->ext_jid[i] == '=' || bud->ext_jid[i] == '@' )
+ bud->ext_jid[i] = '_';
+
+ /* Some program-specific restrictions. */
+ imcb_clean_handle( ic, bud->ext_jid );
+ }
+ bud->flags |= JBFLAG_IS_ANONYMOUS;
+ }
+
+ if( bud != jc->me )
+ {
+ imcb_add_buddy( ic, bud->ext_jid, NULL );
+ imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource );
+ }
+
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0; /* Should NEVER be NULL, but who knows... */
+ imcb_chat_add_buddy( chat, bud->ext_jid );
+ if( s ) *s = '/';
+ }
+ else if( type ) /* type can only be NULL or "unavailable" in this function */
+ {
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0;
+ imcb_chat_remove_buddy( chat, bud->ext_jid, NULL );
+ if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS )
+ imcb_remove_buddy( ic, bud->ext_jid, NULL );
+ if( s ) *s = '/';
+
+ if( bud == jc->me )
+ jabber_chat_free( chat );
+ }
+}
+
+void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node )
+{
+ struct xt_node *body = xt_find_node( node->children, "body" );
+ struct groupchat *chat;
+ char *s;
+
+ if( bud == NULL )
+ {
+ s = xt_find_attr( node, "from" ); /* pkt_message() already NULL-checked this one. */
+ if( strchr( s, '/' ) == NULL )
+ /* This is fine, the groupchat itself isn't in jd->buddies. */
+ imcb_log( ic, "System message from groupchat %s: %s", s, body? body->text : "NULL" );
+ else
+ /* This, however, isn't fine! */
+ imcb_log( ic, "Groupchat message from unknown participant %s: %s", s, body ? body->text : "NULL" );
+
+ return;
+ }
+ else if( ( chat = jabber_chat_by_name( ic, bud->bare_jid ) ) == NULL )
+ {
+ /* How could this happen?? We could do kill( self, 11 )
+ now or just wait for the OS to do it. :-) */
+ return;
+ }
+
+ if( body && body->text_len > 0 )
+ {
+ s = strchr( bud->ext_jid, '/' );
+ if( s ) *s = 0;
+ imcb_chat_msg( chat, bud->ext_jid, body->text, 0, jabber_get_timestamp( node ) );
+ if( s ) *s = '/';
+ }
+}
diff --git a/protocols/jabber/io.c b/protocols/jabber/io.c
index 925463a4..61cd142e 100644
--- a/protocols/jabber/io.c
+++ b/protocols/jabber/io.c
@@ -44,6 +44,15 @@ int jabber_write( struct im_connection *ic, char *buf, int len )
struct jabber_data *jd = ic->proto_data;
gboolean ret;
+ if( jd->flags & JFLAG_XMLCONSOLE )
+ {
+ char *msg;
+
+ msg = g_strdup_printf( "TX: %s", buf );
+ imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
+ g_free( msg );
+ }
+
if( jd->tx_len == 0 )
{
/* If the queue is empty, allocate a new buffer. */
@@ -426,56 +435,61 @@ static xt_status jabber_pkt_proceed_tls( struct xt_node *node, gpointer data )
static xt_status jabber_pkt_stream_error( struct xt_node *node, gpointer data )
{
struct im_connection *ic = data;
- struct xt_node *c;
- char *s, *type = NULL, *text = NULL;
int allow_reconnect = TRUE;
+ struct jabber_error *err;
- for( c = node->children; c; c = c->next )
- {
- if( !( s = xt_find_attr( c, "xmlns" ) ) ||
- strcmp( s, XMLNS_STREAM_ERROR ) != 0 )
- continue;
-
- if( strcmp( c->name, "text" ) != 0 )
- {
- type = c->name;
- }
- /* Only use the text if it doesn't have an xml:lang attribute,
- if it's empty or if it's set to something English. */
- else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
- !*s || strncmp( s, "en", 2 ) == 0 )
- {
- text = c->text;
- }
- }
+ err = jabber_error_parse( node, XMLNS_STREAM_ERROR );
/* Tssk... */
- if( type == NULL )
+ if( err->code == NULL )
{
imcb_error( ic, "Unknown stream error reported by server" );
imc_logout( ic, allow_reconnect );
+ jabber_error_free( err );
return XT_ABORT;
}
/* We know that this is a fatal error. If it's a "conflict" error, we
should turn off auto-reconnect to make sure we won't get some nasty
infinite loop! */
- if( strcmp( type, "conflict" ) == 0 )
+ if( strcmp( err->code, "conflict" ) == 0 )
{
imcb_error( ic, "Account and resource used from a different location" );
allow_reconnect = FALSE;
}
else
{
- imcb_error( ic, "Stream error: %s%s%s", type, text ? ": " : "", text ? text : "" );
+ imcb_error( ic, "Stream error: %s%s%s", err->code, err->text ? ": " : "",
+ err->text ? err->text : "" );
}
+ jabber_error_free( err );
imc_logout( ic, allow_reconnect );
return XT_ABORT;
}
+static xt_status jabber_xmlconsole( struct xt_node *node, gpointer data )
+{
+ struct im_connection *ic = data;
+ struct jabber_data *jd = ic->proto_data;
+
+ if( jd->flags & JFLAG_XMLCONSOLE )
+ {
+ char *msg, *pkt;
+
+ pkt = xt_to_string( node );
+ msg = g_strdup_printf( "RX: %s", pkt );
+ imcb_buddy_msg( ic, JABBER_XMLCONSOLE_HANDLE, msg, 0, 0 );
+ g_free( msg );
+ g_free( pkt );
+ }
+
+ return XT_NEXT;
+}
+
static const struct xt_handler_entry jabber_handlers[] = {
+ { NULL, "stream:stream", jabber_xmlconsole },
{ "stream:stream", "<root>", jabber_end_of_stream },
{ "message", "stream:stream", jabber_pkt_message },
{ "presence", "stream:stream", jabber_pkt_presence },
diff --git a/protocols/jabber/iq.c b/protocols/jabber/iq.c
index 2aa9d432..4738817a 100644
--- a/protocols/jabber/iq.c
+++ b/protocols/jabber/iq.c
@@ -98,26 +98,25 @@ xt_status jabber_pkt_iq( struct xt_node *node, gpointer data )
}
else if( strcmp( s, XMLNS_DISCOVER ) == 0 )
{
+ const char *features[] = { XMLNS_VERSION,
+ XMLNS_TIME,
+ XMLNS_CHATSTATES,
+ XMLNS_MUC,
+ NULL };
+ const char **f;
+
c = xt_new_node( "identity", NULL, NULL );
xt_add_attr( c, "category", "client" );
xt_add_attr( c, "type", "pc" );
xt_add_attr( c, "name", "BitlBee" );
xt_add_child( reply, c );
- c = xt_new_node( "feature", NULL, NULL );
- xt_add_attr( c, "var", XMLNS_VERSION );
- xt_add_child( reply, c );
-
- c = xt_new_node( "feature", NULL, NULL );
- xt_add_attr( c, "var", XMLNS_TIME );
- xt_add_child( reply, c );
-
- c = xt_new_node( "feature", NULL, NULL );
- xt_add_attr( c, "var", XMLNS_CHATSTATES );
- xt_add_child( reply, c );
-
- /* Later this can be useful to announce things like
- MUC support. */
+ for( f = features; *f; f ++ )
+ {
+ c = xt_new_node( "feature", NULL, NULL );
+ xt_add_attr( c, "var", *f );
+ xt_add_child( reply, c );
+ }
}
else
{
@@ -372,15 +371,13 @@ static xt_status jabber_parse_roster( struct im_connection *ic, struct xt_node *
imcb_add_buddy( ic, jid, ( group && group->text_len ) ?
group->text : NULL );
- imcb_rename_buddy( ic, jid, name );
+ if( name )
+ imcb_rename_buddy( ic, jid, name );
}
else if( strcmp( sub, "remove" ) == 0 )
{
- /* Don't have any API call for this yet! So let's
- just try to handle this as well as we can. */
jabber_buddy_remove_bare( ic, jid );
- imcb_buddy_status( ic, jid, 0, NULL, NULL );
- /* FIXME! */
+ imcb_remove_buddy( ic, jid, NULL );
}
}
diff --git a/protocols/jabber/jabber.c b/protocols/jabber/jabber.c
index edad5dbd..ab26efc9 100644
--- a/protocols/jabber/jabber.c
+++ b/protocols/jabber/jabber.c
@@ -54,6 +54,9 @@ static void jabber_init( account_t *acc )
s = set_add( &acc->set, "tls", "try", set_eval_tls, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "xmlconsole", "false", set_eval_bool, acc );
+ s->flags |= ACC_SET_OFFLINE_ONLY;
}
static void jabber_login( account_t *acc )
@@ -188,6 +191,14 @@ static void jabber_login( account_t *acc )
imcb_error( ic, "Could not connect to server" );
imc_logout( ic, TRUE );
}
+
+ if( set_getbool( &acc->set, "xmlconsole" ) )
+ {
+ jd->flags |= JFLAG_XMLCONSOLE;
+ /* Shouldn't really do this at this stage already, maybe. But
+ I think this shouldn't break anything. */
+ imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ }
}
static void jabber_logout( struct im_connection *ic )
@@ -196,6 +207,9 @@ static void jabber_logout( struct im_connection *ic )
jabber_end_stream( ic );
+ while( ic->groupchats )
+ jabber_chat_free( ic->groupchats );
+
if( jd->r_inpa >= 0 )
b_event_remove( jd->r_inpa );
if( jd->w_inpa >= 0 )
@@ -223,9 +237,16 @@ static int jabber_buddy_msg( struct im_connection *ic, char *who, char *message,
struct jabber_data *jd = ic->proto_data;
struct jabber_buddy *bud;
struct xt_node *node;
+ char *s;
int st;
- bud = jabber_buddy_by_jid( ic, who, 0 );
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ return jabber_write( ic, message, strlen( message ) );
+
+ if( ( s = strchr( who, '=' ) ) && jabber_chat_by_name( ic, s + 1 ) )
+ bud = jabber_buddy_by_ext_jid( ic, who, 0 );
+ else
+ bud = jabber_buddy_by_jid( ic, who, 0 );
node = xt_new_node( "body", message, NULL );
node = jabber_make_packet( "message", "chat", bud ? bud->full_jid : who, node );
@@ -310,12 +331,34 @@ static void jabber_set_away( struct im_connection *ic, char *state_txt, char *me
static void jabber_add_buddy( struct im_connection *ic, char *who, char *group )
{
+ struct jabber_data *jd = ic->proto_data;
+
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ {
+ jd->flags |= JFLAG_XMLCONSOLE;
+ imcb_add_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ return;
+ }
+
if( jabber_add_to_roster( ic, who, NULL ) )
presence_send_request( ic, who, "subscribe" );
}
static void jabber_remove_buddy( struct im_connection *ic, char *who, char *group )
{
+ struct jabber_data *jd = ic->proto_data;
+
+ if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
+ {
+ jd->flags &= ~JFLAG_XMLCONSOLE;
+ /* Not necessary for now. And for now the code isn't too
+ happy if the buddy is completely gone right after calling
+ this function already.
+ imcb_remove_buddy( ic, JABBER_XMLCONSOLE_HANDLE, NULL );
+ */
+ return;
+ }
+
/* We should always do this part. Clean up our administration a little bit. */
jabber_buddy_remove_bare( ic, who );
@@ -323,6 +366,30 @@ static void jabber_remove_buddy( struct im_connection *ic, char *who, char *grou
presence_send_request( ic, who, "unsubscribe" );
}
+static struct groupchat *jabber_chat_join_( struct im_connection *ic, char *room, char *nick, char *password )
+{
+ if( strchr( room, '@' ) == NULL )
+ imcb_error( ic, "Invalid room name: %s", room );
+ else if( jabber_chat_by_name( ic, room ) )
+ imcb_error( ic, "Already present in chat `%s'", room );
+ else
+ return jabber_chat_join( ic, room, nick, password );
+
+ return NULL;
+}
+
+static void jabber_chat_msg_( struct groupchat *c, char *message, int flags )
+{
+ if( c && message )
+ jabber_chat_msg( c, message, flags );
+}
+
+static void jabber_chat_leave_( struct groupchat *c )
+{
+ if( c )
+ jabber_chat_leave( c, NULL );
+}
+
static void jabber_keepalive( struct im_connection *ic )
{
/* Just any whitespace character is enough as a keepalive for XMPP sessions. */
@@ -387,16 +454,15 @@ void jabber_initmodule()
ret->logout = jabber_logout;
ret->buddy_msg = jabber_buddy_msg;
ret->away_states = jabber_away_states;
-// ret->get_status_string = jabber_get_status_string;
ret->set_away = jabber_set_away;
// ret->set_info = jabber_set_info;
ret->get_info = jabber_get_info;
ret->add_buddy = jabber_add_buddy;
ret->remove_buddy = jabber_remove_buddy;
-// ret->chat_msg = jabber_chat_msg;
+ ret->chat_msg = jabber_chat_msg_;
// ret->chat_invite = jabber_chat_invite;
-// ret->chat_leave = jabber_chat_leave;
-// ret->chat_open = jabber_chat_open;
+ ret->chat_leave = jabber_chat_leave_;
+ ret->chat_join = jabber_chat_join_;
ret->keepalive = jabber_keepalive;
ret->send_typing = jabber_send_typing;
ret->handle_cmp = g_strcasecmp;
diff --git a/protocols/jabber/jabber.h b/protocols/jabber/jabber.h
index 42f57ae1..7af7f98e 100644
--- a/protocols/jabber/jabber.h
+++ b/protocols/jabber/jabber.h
@@ -31,16 +31,17 @@
typedef enum
{
- JFLAG_STREAM_STARTED = 1, /* Set when we detected the beginning of the stream
+ JFLAG_STREAM_STARTED = 1, /* Set when we detected the beginning of the stream
and want to do auth. */
- JFLAG_AUTHENTICATED = 2, /* Set when we're successfully authenticatd. */
- JFLAG_STREAM_RESTART = 4, /* Set when we want to restart the stream (after
+ JFLAG_AUTHENTICATED = 2, /* Set when we're successfully authenticatd. */
+ JFLAG_STREAM_RESTART = 4, /* Set when we want to restart the stream (after
SASL or TLS). */
- JFLAG_WAIT_SESSION = 8, /* Set if we sent a <session> tag and need a reply
+ JFLAG_WAIT_SESSION = 8, /* Set if we sent a <session> tag and need a reply
before we continue. */
- JFLAG_WAIT_BIND = 16, /* ... for <bind> tag. */
- JFLAG_WANT_TYPING = 32, /* Set if we ever sent a typing notification, this
+ JFLAG_WAIT_BIND = 16, /* ... for <bind> tag. */
+ JFLAG_WANT_TYPING = 32, /* Set if we ever sent a typing notification, this
activates all XEP-85 related code. */
+ JFLAG_XMLCONSOLE = 64, /* If the user added an xmlconsole buddy. */
} jabber_flags_t;
typedef enum
@@ -49,12 +50,12 @@ typedef enum
sure it gets sent only once. */
JBFLAG_DOES_XEP85 = 2, /* Set this when the resource seems to support
XEP85 (typing notification shite). */
+ JBFLAG_IS_CHATROOM = 4, /* It's convenient to use this JID thingy for
+ groupchat state info too. */
+ JBFLAG_IS_ANONYMOUS = 8, /* For anonymous chatrooms, when we don't have
+ have a real JID. */
} jabber_buddy_flags_t;
-#define JABBER_PORT_DEFAULT "5222"
-#define JABBER_PORT_MIN 5220
-#define JABBER_PORT_MAX 5229
-
struct jabber_data
{
struct im_connection *ic;
@@ -100,6 +101,9 @@ struct jabber_buddy
char *full_jid;
char *resource;
+ char *ext_jid; /* The JID to use in BitlBee. The real JID if possible, */
+ /* otherwise something similar to the conference JID. */
+
int priority;
struct jabber_away_state *away_state;
char *away_message;
@@ -110,6 +114,20 @@ struct jabber_buddy
struct jabber_buddy *next;
};
+struct jabber_chat
+{
+ int flags;
+ char *name;
+ char *my_full_jid; /* Separate copy because of case sensitivity. */
+ struct jabber_buddy *me;
+};
+
+#define JABBER_XMLCONSOLE_HANDLE "xmlconsole"
+
+#define JABBER_PORT_DEFAULT "5222"
+#define JABBER_PORT_MIN 5220
+#define JABBER_PORT_MAX 5229
+
/* Prefixes to use for packet IDs (mainly for IQ packets ATM). Usually the
first one should be used, but when storing a packet in the cache, a
"special" kind of ID is assigned to make it easier later to figure out
@@ -131,8 +149,11 @@ struct jabber_buddy
#define XMLNS_VERSION "jabber:iq:version" /* XEP-0092 */
#define XMLNS_TIME "jabber:iq:time" /* XEP-0090 */
#define XMLNS_VCARD "vcard-temp" /* XEP-0054 */
+#define XMLNS_DELAY "jabber:x:delay" /* XEP-0091 */
#define XMLNS_CHATSTATES "http://jabber.org/protocol/chatstates" /* 0085 */
#define XMLNS_DISCOVER "http://jabber.org/protocol/disco#info" /* 0030 */
+#define XMLNS_MUC "http://jabber.org/protocol/muc" /* XEP-0045 */
+#define XMLNS_MUC_USER "http://jabber.org/protocol/muc#user"/* XEP-0045 */
/* iq.c */
xt_status jabber_pkt_iq( struct xt_node *node, gpointer data );
@@ -163,18 +184,29 @@ void jabber_cache_clean( struct im_connection *ic );
const struct jabber_away_state *jabber_away_state_by_code( char *code );
const struct jabber_away_state *jabber_away_state_by_name( char *name );
void jabber_buddy_ask( struct im_connection *ic, char *handle );
-char *jabber_normalize( char *orig );
+char *jabber_normalize( const char *orig );
typedef enum
{
GET_BUDDY_CREAT = 1, /* Try to create it, if necessary. */
- GET_BUDDY_EXACT = 2, /* Get an exact message (only makes sense with bare JIDs). */
+ GET_BUDDY_EXACT = 2, /* Get an exact match (only makes sense with bare JIDs). */
+ GET_BUDDY_FIRST = 4, /* No selection, simply get the first resource for this JID. */
} get_buddy_flags_t;
+struct jabber_error
+{
+ char *code, *text, *type;
+};
+
struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid );
struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags );
+struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid, get_buddy_flags_t flags );
int jabber_buddy_remove( struct im_connection *ic, char *full_jid );
int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid );
+struct groupchat *jabber_chat_by_name( struct im_connection *ic, const char *name );
+time_t jabber_get_timestamp( struct xt_node *xt );
+struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns );
+void jabber_error_free( struct jabber_error *err );
extern const struct jabber_away_state jabber_away_state_list[];
@@ -192,4 +224,12 @@ xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data );
xt_status sasl_pkt_result( struct xt_node *node, gpointer data );
gboolean sasl_supported( struct im_connection *ic );
+/* conference.c */
+struct groupchat *jabber_chat_join( struct im_connection *ic, char *room, char *nick, char *password );
+void jabber_chat_free( struct groupchat *c );
+int jabber_chat_msg( struct groupchat *ic, char *message, int flags );
+int jabber_chat_leave( struct groupchat *c, const char *reason );
+void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node );
+void jabber_chat_pkt_message( struct im_connection *ic, struct jabber_buddy *bud, struct xt_node *node );
+
#endif
diff --git a/protocols/jabber/jabber_util.c b/protocols/jabber/jabber_util.c
index 3c0e71f4..56491c4f 100644
--- a/protocols/jabber/jabber_util.c
+++ b/protocols/jabber/jabber_util.c
@@ -47,7 +47,7 @@ char *set_eval_priority( set_t *set, char *value )
convenient, they have one disadvantage: If I would just
call p_s_u() now to send the new prio setting, it would
send the old setting because the set->value gets changed
- when the eval returns a non-NULL value.
+ after the (this) eval returns a non-NULL value.
So now I can choose between implementing post-set
functions next to evals, or just do this little hack: */
@@ -128,7 +128,7 @@ struct xt_node *jabber_make_error_packet( struct xt_node *orig, char *err_cond,
/* Cache a node/packet for later use. Mainly useful for IQ packets if you need
them when you receive the response. Use this BEFORE sending the packet so
- it'll get a new id= tag, and do NOT free() the packet after writing it! */
+ it'll get a new id= tag, and do NOT free() the packet after sending it! */
void jabber_cache_add( struct im_connection *ic, struct xt_node *node, jabber_cache_event func )
{
struct jabber_data *jd = ic->proto_data;
@@ -251,7 +251,7 @@ void jabber_buddy_ask( struct im_connection *ic, char *handle )
}
/* Returns a new string. Don't leak it! */
-char *jabber_normalize( char *orig )
+char *jabber_normalize( const char *orig )
{
int len, i;
char *new;
@@ -319,6 +319,8 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_
}
else
{
+ /* Keep in mind that full_jid currently isn't really
+ a full JID... */
new->bare_jid = g_strdup( full_jid );
g_hash_table_insert( jd->buddies, new->bare_jid, new );
}
@@ -332,7 +334,8 @@ struct jabber_buddy *jabber_buddy_add( struct im_connection *ic, char *full_jid_
else
{
/* Let's waste some more bytes of RAM instead of to make
- memory management a total disaster here.. */
+ memory management a total disaster here. And it saves
+ me one g_free() call in this function. :-P */
new->full_jid = full_jid;
}
@@ -352,6 +355,8 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
if( ( s = strchr( jid, '/' ) ) )
{
+ int none_found = 0;
+
*s = 0;
if( ( bud = g_hash_table_lookup( jd->buddies, jid ) ) )
{
@@ -369,8 +374,16 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
break;
}
}
+ else
+ {
+ /* This hack is there to make sure that O_CREAT will
+ work if there's already another resouce present
+ for this JID, even if it's an unknown buddy. This
+ is done to handle conferences properly. */
+ none_found = 1;
+ }
- if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && imcb_find_buddy( ic, jid ) )
+ if( bud == NULL && ( flags & GET_BUDDY_CREAT ) && ( imcb_find_buddy( ic, jid ) || !none_found ) )
{
*s = '/';
bud = jabber_buddy_add( ic, jid );
@@ -417,6 +430,38 @@ struct jabber_buddy *jabber_buddy_by_jid( struct im_connection *ic, char *jid_,
}
}
+/* I'm keeping a separate ext_jid attribute to save a JID that makes sense
+ to export to BitlBee. This is mainly for groupchats right now. It's
+ a bit of a hack, but I just think having the user nickname in the hostname
+ part of the hostmask doesn't look nice on IRC. Normally you can convert
+ a normal JID to ext_jid by swapping the part before and after the / and
+ replacing the / with a =. But there should be some stripping (@s are
+ allowed in Jabber nicks...). */
+struct jabber_buddy *jabber_buddy_by_ext_jid( struct im_connection *ic, char *jid_, get_buddy_flags_t flags )
+{
+ struct jabber_buddy *bud;
+ char *s, *jid;
+
+ jid = jabber_normalize( jid_ );
+
+ if( ( s = strchr( jid, '=' ) ) == NULL )
+ return NULL;
+
+ for( bud = jabber_buddy_by_jid( ic, s + 1, GET_BUDDY_FIRST ); bud; bud = bud->next )
+ {
+ /* Hmmm, could happen if not all people in the chat are anonymized? */
+ if( bud->ext_jid == NULL )
+ continue;
+
+ if( strcmp( bud->ext_jid, jid ) == 0 )
+ break;
+ }
+
+ g_free( jid );
+
+ return bud;
+}
+
/* Remove one specific full JID from our list. Use this when a buddy goes
off-line (because (s)he can still be online from a different location.
XXX: See above, we should accept bare JIDs too... */
@@ -440,6 +485,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
{
g_hash_table_remove( jd->buddies, bud->bare_jid );
g_free( bud->bare_jid );
+ g_free( bud->ext_jid );
g_free( bud->full_jid );
g_free( bud->away_message );
g_free( bud );
@@ -472,6 +518,7 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
item, because we're removing the first. */
g_hash_table_replace( jd->buddies, bi->bare_jid, bi->next );
+ g_free( bi->ext_jid );
g_free( bi->full_jid );
g_free( bi->away_message );
g_free( bi );
@@ -494,39 +541,144 @@ int jabber_buddy_remove( struct im_connection *ic, char *full_jid_ )
/* Remove a buddy completely; removes all resources that belong to the
specified bare JID. Use this when removing someone from the contact
list, for example. */
-int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid_ )
+int jabber_buddy_remove_bare( struct im_connection *ic, char *bare_jid )
{
struct jabber_data *jd = ic->proto_data;
struct jabber_buddy *bud, *next;
- char *bare_jid;
- if( strchr( bare_jid_, '/' ) )
+ if( strchr( bare_jid, '/' ) )
return 0;
- bare_jid = jabber_normalize( bare_jid_ );
-
- if( ( bud = g_hash_table_lookup( jd->buddies, bare_jid ) ) )
+ if( ( bud = jabber_buddy_by_jid( ic, bare_jid, GET_BUDDY_FIRST ) ) )
{
/* Most important: Remove the hash reference. We don't know
this buddy anymore. */
g_hash_table_remove( jd->buddies, bud->bare_jid );
+ g_free( bud->bare_jid );
/* Deallocate the linked list of resources. */
while( bud )
{
+ /* ext_jid && anonymous means that this buddy is
+ specific to one groupchat (the one we're
+ currently cleaning up) so it can be deleted
+ completely. */
+ if( bud->ext_jid && bud->flags & JBFLAG_IS_ANONYMOUS )
+ imcb_remove_buddy( ic, bud->ext_jid, NULL );
+
next = bud->next;
+ g_free( bud->ext_jid );
g_free( bud->full_jid );
g_free( bud->away_message );
g_free( bud );
bud = next;
}
- g_free( bare_jid );
return 1;
}
else
{
- g_free( bare_jid );
return 0;
}
}
+
+struct groupchat *jabber_chat_by_name( struct im_connection *ic, const char *name )
+{
+ char *normalized = jabber_normalize( name );
+ struct groupchat *ret;
+ struct jabber_chat *jc;
+
+ for( ret = ic->groupchats; ret; ret = ret->next )
+ {
+ jc = ret->data;
+ if( strcmp( normalized, jc->name ) == 0 )
+ break;
+ }
+ g_free( normalized );
+
+ return ret;
+}
+
+time_t jabber_get_timestamp( struct xt_node *xt )
+{
+ struct tm tp, utc;
+ struct xt_node *c;
+ time_t res, tres;
+ char *s = NULL;
+
+ for( c = xt->children; ( c = xt_find_node( c, "x" ) ); c = c->next )
+ {
+ if( ( s = xt_find_attr( c, "xmlns" ) ) && strcmp( s, XMLNS_DELAY ) == 0 )
+ break;
+ }
+
+ if( !c || !( s = xt_find_attr( c, "stamp" ) ) )
+ return 0;
+
+ memset( &tp, 0, sizeof( tp ) );
+ if( sscanf( s, "%4d%2d%2dT%2d:%2d:%2d", &tp.tm_year, &tp.tm_mon, &tp.tm_mday,
+ &tp.tm_hour, &tp.tm_min, &tp.tm_sec ) != 6 )
+ return 0;
+
+ tp.tm_year -= 1900;
+ tp.tm_mon --;
+ tp.tm_isdst = -1; /* GRRRRRRRRRRR */
+
+ res = mktime( &tp );
+ /* Problem is, mktime() just gave us the GMT timestamp for the
+ given local time... While the given time WAS NOT local. So
+ we should fix this now.
+
+ Now I could choose between messing with environment variables
+ (kludgy) or using timegm() (not portable)... Or doing the
+ following, which I actually prefer... */
+ gmtime_r( &res, &utc );
+ utc.tm_isdst = -1; /* Once more: GRRRRRRRRRRRRRRRRRR!!! */
+ if( utc.tm_hour == tp.tm_hour && utc.tm_min == tp.tm_min )
+ /* Sweet! We're in UTC right now... */
+ return res;
+
+ tres = mktime( &utc );
+ res += res - tres;
+
+ /* Yes, this is a hack. And it will go wrong around DST changes.
+ BUT this is more likely to be threadsafe than messing with
+ environment variables, and possibly more portable... */
+
+ return res;
+}
+
+struct jabber_error *jabber_error_parse( struct xt_node *node, char *xmlns )
+{
+ struct jabber_error *err = g_new0( struct jabber_error, 1 );
+ struct xt_node *c;
+ char *s;
+
+ err->type = xt_find_attr( node, "type" );
+
+ for( c = node->children; c; c = c->next )
+ {
+ if( !( s = xt_find_attr( c, "xmlns" ) ) ||
+ strcmp( s, xmlns ) != 0 )
+ continue;
+
+ if( strcmp( c->name, "text" ) != 0 )
+ {
+ err->code = c->name;
+ }
+ /* Only use the text if it doesn't have an xml:lang attribute,
+ if it's empty or if it's set to something English. */
+ else if( !( s = xt_find_attr( c, "xml:lang" ) ) ||
+ !*s || strncmp( s, "en", 2 ) == 0 )
+ {
+ err->text = c->text;
+ }
+ }
+
+ return err;
+}
+
+void jabber_error_free( struct jabber_error *err )
+{
+ g_free( err );
+}
diff --git a/protocols/jabber/message.c b/protocols/jabber/message.c
index 19edbdfd..fab62a91 100644
--- a/protocols/jabber/message.c
+++ b/protocols/jabber/message.c
@@ -29,25 +29,33 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )
char *from = xt_find_attr( node, "from" );
char *type = xt_find_attr( node, "type" );
struct xt_node *body = xt_find_node( node->children, "body" ), *c;
+ struct jabber_buddy *bud = NULL;
char *s;
+ if( !from )
+ return XT_HANDLED; /* Consider this packet corrupted. */
+
+ bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT );
+
if( type && strcmp( type, "error" ) == 0 )
{
/* Handle type=error packet. */
}
- else if( type && strcmp( type, "groupchat" ) == 0 )
+ else if( type && from && strcmp( type, "groupchat" ) == 0 )
{
- /* TODO! */
+ jabber_chat_pkt_message( ic, bud, node );
}
else /* "chat", "normal", "headline", no-type or whatever. Should all be pretty similar. */
{
- struct jabber_buddy *bud = NULL;
GString *fullmsg = g_string_new( "" );
if( ( s = strchr( from, '/' ) ) )
{
- if( ( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) ) )
+ if( bud )
+ {
bud->last_act = time( NULL );
+ from = bud->ext_jid ? : bud->bare_jid;
+ }
else
*s = 0; /* We need to generate a bare JID now. */
}
@@ -75,26 +83,31 @@ xt_status jabber_pkt_message( struct xt_node *node, gpointer data )
fullmsg = g_string_append( fullmsg, body->text );
if( fullmsg->len > 0 )
- imcb_buddy_msg( ic, bud ? bud->bare_jid : from, fullmsg->str, 0, 0 );
+ imcb_buddy_msg( ic, from, fullmsg->str,
+ 0, jabber_get_timestamp( node ) );
g_string_free( fullmsg, TRUE );
/* Handling of incoming typing notifications. */
- if( xt_find_node( node->children, "composing" ) )
+ if( bud == NULL )
+ {
+ /* Can't handle these for unknown buddies. */
+ }
+ else if( xt_find_node( node->children, "composing" ) )
{
bud->flags |= JBFLAG_DOES_XEP85;
- imcb_buddy_typing( ic, bud ? bud->bare_jid : from, OPT_TYPING );
+ imcb_buddy_typing( ic, from, OPT_TYPING );
}
/* No need to send a "stopped typing" signal when there's a message. */
else if( xt_find_node( node->children, "active" ) && ( body == NULL ) )
{
bud->flags |= JBFLAG_DOES_XEP85;
- imcb_buddy_typing( ic, bud ? bud->bare_jid : from, 0 );
+ imcb_buddy_typing( ic, from, 0 );
}
else if( xt_find_node( node->children, "paused" ) )
{
bud->flags |= JBFLAG_DOES_XEP85;
- imcb_buddy_typing( ic, bud ? bud->bare_jid : from, OPT_THINKING );
+ imcb_buddy_typing( ic, from, OPT_THINKING );
}
if( s )
diff --git a/protocols/jabber/presence.c b/protocols/jabber/presence.c
index 49c66a3d..71a044b5 100644
--- a/protocols/jabber/presence.c
+++ b/protocols/jabber/presence.c
@@ -30,15 +30,22 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
char *type = xt_find_attr( node, "type" ); /* NULL should mean the person is online. */
struct xt_node *c;
struct jabber_buddy *bud;
+ int is_chat = 0, is_away = 0;
char *s;
if( !from )
return XT_HANDLED;
+ if( ( s = strchr( from, '/' ) ) )
+ {
+ *s = 0;
+ if( jabber_chat_by_name( ic, from ) )
+ is_chat = 1;
+ *s = '/';
+ }
+
if( type == NULL )
{
- int is_away = 0;
-
if( !( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT | GET_BUDDY_CREAT ) ) )
{
if( set_getbool( &ic->irc->set, "debug" ) )
@@ -71,30 +78,55 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
else
bud->priority = 0;
- if( bud == jabber_buddy_by_jid( ic, bud->bare_jid, 0 ) )
+ if( is_chat )
+ jabber_chat_pkt_presence( ic, bud, node );
+ else if( bud == jabber_buddy_by_jid( ic, bud->bare_jid, 0 ) )
imcb_buddy_status( ic, bud->bare_jid, OPT_LOGGED_IN | is_away,
( is_away && bud->away_state ) ? bud->away_state->full_name : NULL,
bud->away_message );
}
else if( strcmp( type, "unavailable" ) == 0 )
{
- if( jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) == NULL )
+ if( ( bud = jabber_buddy_by_jid( ic, from, GET_BUDDY_EXACT ) ) == NULL )
{
if( set_getbool( &ic->irc->set, "debug" ) )
imcb_log( ic, "WARNING: Received presence information from unknown JID: %s", from );
return XT_HANDLED;
}
+ /* Handle this before we delete the JID. */
+ if( is_chat )
+ {
+ jabber_chat_pkt_presence( ic, bud, node );
+ }
+
jabber_buddy_remove( ic, from );
- if( ( s = strchr( from, '/' ) ) )
+ if( is_chat )
+ {
+ /* Nothing else to do for now? */
+ }
+ else if( ( s = strchr( from, '/' ) ) )
{
*s = 0;
- /* Only count this as offline if there's no other resource
- available anymore. */
- if( jabber_buddy_by_jid( ic, from, 0 ) == NULL )
+ /* If another resource is still available, send its presence
+ information. */
+ if( ( bud = jabber_buddy_by_jid( ic, from, 0 ) ) )
+ {
+ if( bud->away_state && ( *bud->away_state->code == 0 ||
+ strcmp( bud->away_state->code, "chat" ) == 0 ) )
+ is_away = OPT_AWAY;
+
+ imcb_buddy_status( ic, bud->bare_jid, OPT_LOGGED_IN | is_away,
+ ( is_away && bud->away_state ) ? bud->away_state->full_name : NULL,
+ bud->away_message );
+ }
+ else
+ {
+ /* Otherwise, count him/her as offline now. */
imcb_buddy_status( ic, from, 0, NULL, NULL );
+ }
*s = '/';
}
@@ -125,7 +157,17 @@ xt_status jabber_pkt_presence( struct xt_node *node, gpointer data )
}
else if( strcmp( type, "error" ) == 0 )
{
- /* What to do with it? */
+ struct jabber_error *err;
+
+ if( ( c = xt_find_node( node->children, "error" ) ) )
+ {
+ err = jabber_error_parse( c, XMLNS_STANZA_ERROR );
+ imcb_error( ic, "Stanza (%s) error: %s%s%s", node->name,
+ err->code, err->text ? ": " : "",
+ err->text ? err->text : "" );
+ jabber_error_free( err );
+ }
+ /* What else to do with it? */
}
return XT_HANDLED;
@@ -139,6 +181,7 @@ int presence_send_update( struct im_connection *ic )
struct xt_node *node;
char *show = jd->away_state->code;
char *status = jd->away_message;
+ struct groupchat *c;
int st;
node = jabber_make_packet( "presence", NULL, NULL, NULL );
@@ -150,6 +193,16 @@ int presence_send_update( struct im_connection *ic )
st = jabber_write_packet( ic, node );
+ /* Have to send this update to all groupchats too, the server won't
+ do this automatically. */
+ for( c = ic->groupchats; c && st; c = c->next )
+ {
+ struct jabber_chat *jc = c->data;
+
+ xt_add_attr( node, "to", jc->my_full_jid );
+ st = jabber_write_packet( ic, node );
+ }
+
xt_free_node( node );
return st;
}
diff --git a/protocols/jabber/xmltree.c b/protocols/jabber/xmltree.c
index 7e74cccb..62549eb5 100644
--- a/protocols/jabber/xmltree.c
+++ b/protocols/jabber/xmltree.c
@@ -443,7 +443,7 @@ void xt_free( struct xt_parser *xt )
/* To find a node's child with a specific name, pass the node's children
list, not the node itself! The reason you have to do this by hand: So
that you can also use this function as a find-next. */
-struct xt_node *xt_find_node( struct xt_node *node, char *name )
+struct xt_node *xt_find_node( struct xt_node *node, const char *name )
{
while( node )
{
@@ -456,7 +456,7 @@ struct xt_node *xt_find_node( struct xt_node *node, char *name )
return node;
}
-char *xt_find_attr( struct xt_node *node, char *key )
+char *xt_find_attr( struct xt_node *node, const char *key )
{
int i;
@@ -525,7 +525,7 @@ void xt_add_child( struct xt_node *parent, struct xt_node *child )
}
}
-void xt_add_attr( struct xt_node *node, char *key, char *value )
+void xt_add_attr( struct xt_node *node, const char *key, const char *value )
{
int i;
@@ -552,7 +552,7 @@ void xt_add_attr( struct xt_node *node, char *key, char *value )
node->attr[i].value = g_strdup( value );
}
-int xt_remove_attr( struct xt_node *node, char *key )
+int xt_remove_attr( struct xt_node *node, const char *key )
{
int i, last;
diff --git a/protocols/jabber/xmltree.h b/protocols/jabber/xmltree.h
index 70850c1d..b8b61641 100644
--- a/protocols/jabber/xmltree.h
+++ b/protocols/jabber/xmltree.h
@@ -86,12 +86,12 @@ void xt_print( struct xt_node *node );
struct xt_node *xt_dup( struct xt_node *node );
void xt_free_node( struct xt_node *node );
void xt_free( struct xt_parser *xt );
-struct xt_node *xt_find_node( struct xt_node *node, char *name );
-char *xt_find_attr( struct xt_node *node, char *key );
+struct xt_node *xt_find_node( struct xt_node *node, const char *name );
+char *xt_find_attr( struct xt_node *node, const char *key );
struct xt_node *xt_new_node( char *name, char *text, struct xt_node *children );
void xt_add_child( struct xt_node *parent, struct xt_node *child );
-void xt_add_attr( struct xt_node *node, char *key, char *value );
-int xt_remove_attr( struct xt_node *node, char *key );
+void xt_add_attr( struct xt_node *node, const char *key, const char *value );
+int xt_remove_attr( struct xt_node *node, const char *key );
#endif