diff options
Diffstat (limited to 'protocols')
-rw-r--r-- | protocols/jabber/Makefile | 2 | ||||
-rw-r--r-- | protocols/jabber/conference.c | 246 | ||||
-rw-r--r-- | protocols/jabber/io.c | 60 | ||||
-rw-r--r-- | protocols/jabber/iq.c | 35 | ||||
-rw-r--r-- | protocols/jabber/jabber.c | 76 | ||||
-rw-r--r-- | protocols/jabber/jabber.h | 64 | ||||
-rw-r--r-- | protocols/jabber/jabber_util.c | 178 | ||||
-rw-r--r-- | protocols/jabber/message.c | 31 | ||||
-rw-r--r-- | protocols/jabber/presence.c | 71 | ||||
-rw-r--r-- | protocols/jabber/xmltree.c | 8 | ||||
-rw-r--r-- | protocols/jabber/xmltree.h | 8 | ||||
-rw-r--r-- | protocols/msn/sb.c | 2 | ||||
-rw-r--r-- | protocols/nogaim.c | 142 | ||||
-rw-r--r-- | protocols/nogaim.h | 9 | ||||
-rw-r--r-- | protocols/oscar/oscar.c | 3 | ||||
-rw-r--r-- | protocols/yahoo/yahoo.c | 14 |
16 files changed, 787 insertions, 162 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 diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c index 1693cb95..cb9e2cab 100644 --- a/protocols/msn/sb.c +++ b/protocols/msn/sb.c @@ -231,7 +231,7 @@ void msn_sb_destroy( struct msn_switchboard *sb ) if( sb->chat ) { - imcb_chat_removed( sb->chat ); + imcb_chat_free( sb->chat ); } if( sb->handler ) diff --git a/protocols/nogaim.c b/protocols/nogaim.c index d0395fa9..d1aceb1a 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -98,7 +98,6 @@ void register_protocol (struct prpl *p) protocols = g_list_append(protocols, p); } - struct prpl *find_protocol(const char *name) { GList *gl; @@ -288,7 +287,7 @@ void cancel_auto_reconnect( account_t *a ) void imc_logout( struct im_connection *ic, int allow_reconnect ) { irc_t *irc = ic->irc; - user_t *t, *u = irc->users; + user_t *t, *u; account_t *a; /* Nested calls might happen sometimes, this is probably the best @@ -305,6 +304,7 @@ void imc_logout( struct im_connection *ic, int allow_reconnect ) ic->acc->prpl->logout( ic ); b_event_remove( ic->inpa ); + u = irc->users; while( u ) { if( u->ic == ic ) @@ -427,7 +427,6 @@ struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle ) void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname ) { user_t *u = user_findhandle( ic, handle ); - char *s, newnick[MAX_NICK_LENGTH+1]; if( !u || !realname ) return; @@ -439,31 +438,54 @@ void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname ) if( ( ic->flags & OPT_LOGGED_IN ) && set_getbool( &ic->irc->set, "display_namechanges" ) ) imcb_log( ic, "User `%s' changed name to `%s'", u->nick, u->realname ); + } +} + +void imcb_remove_buddy( struct im_connection *ic, char *handle, char *group ) +{ + user_t *u; + + if( ( u = user_findhandle( ic, handle ) ) ) + user_del( ic->irc, u->nick ); +} + +/* Mainly meant for ICQ (and now also for Jabber conferences) to allow IM + modules to suggest a nickname for a handle. */ +void imcb_buddy_nick_hint( struct im_connection *ic, char *handle, char *nick ) +{ + user_t *u = user_findhandle( ic, handle ); + char newnick[MAX_NICK_LENGTH+1], *orig_nick; + + if( u && !u->online && !nick_saved( ic->acc, handle ) ) + { + /* Only do this if the person isn't online yet (which should + be the case if we just added it) and if the user hasn't + assigned a nickname to this buddy already. */ + + strncpy( newnick, nick, MAX_NICK_LENGTH ); + newnick[MAX_NICK_LENGTH] = 0; - if( !u->online && !nick_saved( ic->acc, handle ) ) + /* Some processing to make sure this string is a valid IRC nickname. */ + nick_strip( newnick ); + if( set_getbool( &ic->irc->set, "lcnicks" ) ) + nick_lc( newnick ); + + if( strcmp( u->nick, newnick ) != 0 ) { - /* Detect numeric handles: */ - for( s = u->user; isdigit( *s ); s++ ); + /* Only do this if newnick is different from the current one. + If rejoining a channel, maybe we got this nick already + (and dedupe would only add an underscore. */ + nick_dedupe( ic->acc, handle, newnick ); - if( *s == 0 ) - { - /* If we reached the end of the string, it only contained numbers. - Seems to be an ICQ# then, so hopefully realname contains - something more useful. */ - strcpy( newnick, realname ); - - /* Some processing to make sure this string is a valid IRC nickname. */ - nick_strip( newnick ); - if( set_getbool( &ic->irc->set, "lcnicks" ) ) - nick_lc( newnick ); - - u->nick = g_strdup( newnick ); - } + /* u->nick will be freed halfway the process, so it can't be + passed as an argument. */ + orig_nick = g_strdup( u->nick ); + user_rename( ic->irc, orig_nick, newnick ); + g_free( orig_nick ); } } } - /* prpl.c */ struct show_got_added_data @@ -553,8 +575,8 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags, irc_kill( ic->irc, u ); u->online = 0; - /* Remove him/her from the conversations to prevent PART messages after he/she QUIT already */ - for( c = ic->conversations; c; c = c->next ) + /* Remove him/her from the groupchats to prevent PART messages after he/she QUIT already */ + for( c = ic->groupchats; c; c = c->next ) remove_chat_buddy_silent( c, handle ); } @@ -669,10 +691,10 @@ void imcb_buddy_typing( struct im_connection *ic, char *handle, u_int32_t flags } } -void imcb_chat_removed( struct groupchat *c ) +void imcb_chat_free( struct groupchat *c ) { struct im_connection *ic = c->ic; - struct groupchat *l = NULL; + struct groupchat *l; GList *ir; if( set_getbool( &ic->irc->set, "debug" ) ) @@ -692,10 +714,13 @@ void imcb_chat_removed( struct groupchat *c ) /* irc_part( ic->irc, u, c->channel ); */ } + /* Find the previous chat in the linked list. */ + for( l = ic->groupchats; l && l->next != c; l = l->next ); + if( l ) l->next = c->next; else - ic->conversations = c->next; + ic->groupchats = c->next; for( ir = c->in_room; ir; ir = ir->next ) g_free( ir->data ); @@ -740,13 +765,13 @@ struct groupchat *imcb_chat_new( struct im_connection *ic, char *handle ) /* This one just creates the conversation structure, user won't see anything yet */ - if( ic->conversations ) + if( ic->groupchats ) { - for( c = ic->conversations; c->next; c = c->next ); + for( c = ic->groupchats; c->next; c = c->next ); c = c->next = g_new0( struct groupchat, 1 ); } else - ic->conversations = c = g_new0( struct groupchat, 1 ); + ic->groupchats = c = g_new0( struct groupchat, 1 ); c->ic = ic; c->title = g_strdup( handle ); @@ -795,6 +820,7 @@ void imcb_chat_add_buddy( struct groupchat *b, char *handle ) } } +/* This function is one BIG hack... :-( EREWRITE */ void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason ) { user_t *u; @@ -806,6 +832,9 @@ void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason ) /* It might be yourself! */ if( g_strcasecmp( handle, b->ic->acc->user ) == 0 ) { + if( b->joined == 0 ) + return; + u = user_find( b->ic->irc, b->ic->irc->nick ); b->joined = 0; me = 1; @@ -815,9 +844,8 @@ void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason ) u = user_findhandle( b->ic, handle ); } - if( remove_chat_buddy_silent( b, handle ) ) - if( ( b->joined || me ) && u ) - irc_part( b->ic->irc, u, b->channel ); + if( me || ( remove_chat_buddy_silent( b, handle ) && b->joined && u ) ) + irc_part( b->ic->irc, u, b->channel ); } static int remove_chat_buddy_silent( struct groupchat *b, const char *handle ) @@ -844,24 +872,6 @@ static int remove_chat_buddy_silent( struct groupchat *b, const char *handle ) /* Misc. BitlBee stuff which shouldn't really be here */ -struct groupchat *chat_by_channel( char *channel ) -{ - struct im_connection *ic; - struct groupchat *c; - GSList *l; - - /* This finds the connection which has a conversation which belongs to this channel */ - for( l = connections; l; l = l->next ) - { - ic = l->data; - for( c = ic->conversations; c && g_strcasecmp( c->channel, channel ) != 0; c = c->next ); - if( c ) - return c; - } - - return NULL; -} - char *set_eval_away_devoice( set_t *set, char *value ) { irc_t *irc = set->data; @@ -1094,3 +1104,35 @@ void imc_rem_block( struct im_connection *ic, char *handle ) ic->acc->prpl->rem_deny( ic, handle ); } + +void imcb_clean_handle( struct im_connection *ic, char *handle ) +{ + /* Accepts a handle and does whatever is necessary to make it + BitlBee-friendly. Currently this means removing everything + outside 33-127 (ASCII printable excl spaces), @ (only one + is allowed) and ! and : */ + char out[strlen(handle)+1]; + int s, d; + + s = d = 0; + while( handle[s] ) + { + if( handle[s] > ' ' && handle[s] != '!' && handle[s] != ':' && + ( handle[s] & 0x80 ) == 0 ) + { + if( handle[s] == '@' ) + { + /* See if we got an @ already? */ + out[d] = 0; + if( strchr( out, '@' ) ) + continue; + } + + out[d++] = handle[s]; + } + s ++; + } + out[d] = handle[s]; + + strcpy( handle, out ); +} diff --git a/protocols/nogaim.h b/protocols/nogaim.h index ff4cdb96..5bf6d922 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -60,6 +60,7 @@ #define OPT_LOGGING_OUT 0x00000002 #define OPT_AWAY 0x00000004 #define OPT_DOES_HTML 0x00000010 +#define OPT_LOCALBUDDY 0x00000020 /* For nicks local to one groupchat */ #define OPT_TYPING 0x00000100 /* Some pieces of code make assumptions */ #define OPT_THINKING 0x00000200 /* about these values... Stupid me! */ @@ -90,7 +91,7 @@ struct im_connection /* BitlBee */ irc_t *irc; - struct groupchat *conversations; + struct groupchat *groupchats; }; struct groupchat { @@ -103,7 +104,6 @@ struct groupchat { GList *in_room; GList *ignored; - /* BitlBee */ struct groupchat *next; char *channel; /* The title variable contains the ID you gave when you created the @@ -264,6 +264,7 @@ G_MODULE_EXPORT void imcb_add_buddy( struct im_connection *ic, char *handle, cha G_MODULE_EXPORT void imcb_remove_buddy( struct im_connection *ic, char *handle, char *group ); G_MODULE_EXPORT struct buddy *imcb_find_buddy( struct im_connection *ic, char *handle ); G_MODULE_EXPORT void imcb_rename_buddy( struct im_connection *ic, char *handle, char *realname ); +G_MODULE_EXPORT void imcb_buddy_nick_hint( struct im_connection *ic, char *handle, char *nick ); /* Buddy activity */ /* To manipulate the status of a handle. @@ -275,6 +276,7 @@ G_MODULE_EXPORT void imcb_buddy_status( struct im_connection *ic, const char *ha /* Call when a handle says something. 'flags' and 'sent_at may be just 0. */ G_MODULE_EXPORT void imcb_buddy_msg( struct im_connection *ic, char *handle, char *msg, u_int32_t flags, time_t sent_at ); G_MODULE_EXPORT void imcb_buddy_typing( struct im_connection *ic, char *handle, u_int32_t flags ); +G_MODULE_EXPORT void imcb_clean_handle( struct im_connection *ic, char *handle ); /* Groupchats */ G_MODULE_EXPORT void imcb_chat_invited( struct im_connection *ic, char *handle, char *who, char *msg, GList *data ); @@ -290,8 +292,7 @@ G_MODULE_EXPORT void imcb_chat_add_buddy( struct groupchat *b, char *handle ); G_MODULE_EXPORT void imcb_chat_remove_buddy( struct groupchat *b, char *handle, char *reason ); /* To tell BitlBee 'who' said 'msg' in 'c'. 'flags' and 'sent_at' can be 0. */ G_MODULE_EXPORT void imcb_chat_msg( struct groupchat *c, char *who, char *msg, u_int32_t flags, time_t sent_at ); -G_MODULE_EXPORT void imcb_chat_removed( struct groupchat *c ); -struct groupchat *chat_by_channel( char *channel ); +G_MODULE_EXPORT void imcb_chat_free( struct groupchat *c ); /* Actions, or whatever. */ int imc_set_away( struct im_connection *ic, char *away ); diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c index 2b8b4853..96983738 100644 --- a/protocols/oscar/oscar.c +++ b/protocols/oscar/oscar.c @@ -2030,6 +2030,7 @@ static int gaim_ssi_parselist(aim_session_t *sess, aim_frame_t *fr, ...) { imcb_add_buddy(ic, curitem->name, NULL); if (realname) { + imcb_buddy_nick_hint(ic, curitem->name, realname); imcb_rename_buddy(ic, curitem->name, realname); g_free(realname); } @@ -2522,7 +2523,7 @@ void oscar_chat_kill(struct im_connection *ic, struct chat_connection *cc) struct oscar_data *od = (struct oscar_data *)ic->proto_data; /* Notify the conversation window that we've left the chat */ - imcb_chat_removed(cc->cnv); + imcb_chat_free(cc->cnv); /* Destroy the chat_connection */ od->oscar_chats = g_slist_remove(od->oscar_chats, cc); diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c index 69fc29bb..28a72877 100644 --- a/protocols/yahoo/yahoo.c +++ b/protocols/yahoo/yahoo.c @@ -144,8 +144,8 @@ static void byahoo_logout( struct im_connection *ic ) struct byahoo_data *yd = (struct byahoo_data *) ic->proto_data; GSList *l; - while( ic->conversations ) - imcb_chat_removed( ic->conversations ); + while( ic->groupchats ) + imcb_chat_free( ic->groupchats ); for( l = yd->buddygroups; l; l = l->next ) { @@ -317,7 +317,7 @@ static void byahoo_chat_leave( struct groupchat *c ) struct byahoo_data *yd = (struct byahoo_data *) c->ic->proto_data; yahoo_conference_logoff( yd->y2_id, NULL, c->data, c->title ); - imcb_chat_removed( c ); + imcb_chat_free( c ); } static struct groupchat *byahoo_chat_with( struct im_connection *ic, char *who ) @@ -797,7 +797,7 @@ static void byahoo_accept_conf( gpointer w, struct byahoo_conf_invitation *inv ) static void byahoo_reject_conf( gpointer w, struct byahoo_conf_invitation *inv ) { yahoo_conference_decline( inv->yid, NULL, inv->members, inv->name, "User rejected groupchat" ); - imcb_chat_removed( inv->c ); + imcb_chat_free( inv->c ); g_free( inv->name ); g_free( inv ); } @@ -840,7 +840,7 @@ void ext_yahoo_conf_userjoin( int id, const char *ignored, const char *who, cons struct im_connection *ic = byahoo_get_ic_by_id( id ); struct groupchat *c; - for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next ); + for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); if( c ) imcb_chat_add_buddy( c, (char*) who ); @@ -852,7 +852,7 @@ void ext_yahoo_conf_userleave( int id, const char *ignored, const char *who, con struct im_connection *ic = byahoo_get_ic_by_id( id ); struct groupchat *c; - for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next ); + for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); if( c ) imcb_chat_remove_buddy( c, (char*) who, "" ); @@ -864,7 +864,7 @@ void ext_yahoo_conf_message( int id, const char *ignored, const char *who, const char *m = byahoo_strip( msg ); struct groupchat *c; - for( c = ic->conversations; c && strcmp( c->title, room ) != 0; c = c->next ); + for( c = ic->groupchats; c && strcmp( c->title, room ) != 0; c = c->next ); if( c ) imcb_chat_msg( c, (char*) who, (char*) m, 0, 0 ); |