aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bitlbee.h4
-rw-r--r--debian/changelog9
-rwxr-xr-xdebian/rules2
-rw-r--r--doc/CHANGES20
-rw-r--r--doc/user-guide/commands.xml65
-rw-r--r--irc.c1
-rw-r--r--lib/Makefile2
-rw-r--r--lib/misc.c3
-rw-r--r--lib/oauth.c453
-rw-r--r--lib/oauth.h90
-rw-r--r--lib/url.c2
-rw-r--r--lib/url.h2
-rw-r--r--lib/xmltree.c22
-rw-r--r--protocols/jabber/conference.c4
-rw-r--r--protocols/msn/passport.c4
-rw-r--r--protocols/msn/sb.c49
-rw-r--r--protocols/nogaim.c59
-rw-r--r--protocols/oscar/oscar.c139
-rw-r--r--protocols/twitter/twitter.c152
-rw-r--r--protocols/twitter/twitter.h1
-rw-r--r--protocols/twitter/twitter_http.c20
-rw-r--r--protocols/twitter/twitter_http.h4
-rw-r--r--protocols/twitter/twitter_lib.c12
-rw-r--r--protocols/yahoo/yahoo.c9
24 files changed, 908 insertions, 220 deletions
diff --git a/bitlbee.h b/bitlbee.h
index 88081e45..faecc349 100644
--- a/bitlbee.h
+++ b/bitlbee.h
@@ -37,10 +37,10 @@
#define _WIN32_WINNT 0x0501
#define PACKAGE "BitlBee"
-#define BITLBEE_VERSION "1.2.6"
+#define BITLBEE_VERSION "1.2.7"
#define VERSION BITLBEE_VERSION
#define BITLBEE_VER(a,b,c) (((a) << 16) + ((b) << 8) + (c))
-#define BITLBEE_VERSION_CODE BITLBEE_VER(1, 2, 6)
+#define BITLBEE_VERSION_CODE BITLBEE_VER(1, 2, 7)
#define MAX_STRING 511
diff --git a/debian/changelog b/debian/changelog
index f969b410..56d0a551 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,12 @@
+bitlbee (1.2.6a-1) unstable; urgency=low
+
+ * New upstream version.
+ * Native support for Twitter.
+ * Fixed /WHOIS response format. (Closes: #576120)
+ * Problems with bitlbee-skype are solved by now. (Closes: #575572)
+
+ -- Wilmer van der Gaast <wilmer@peer.gaast.net> Tue, 20 Apr 2010 00:34:51 +0200
+
bitlbee (1.2.5-1) unstable; urgency=low
* New upstream version.
diff --git a/debian/rules b/debian/rules
index 788e5006..0c656fd8 100755
--- a/debian/rules
+++ b/debian/rules
@@ -98,7 +98,7 @@ binary-indep: install-indep
cd debian/bitlbee-dev; \
find usr -type f -exec md5sum {} \; > DEBIAN/md5sums
- dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev
+ dpkg-gencontrol -ldebian/changelog -isp -pbitlbee-dev -Pdebian/bitlbee-dev -v1:$(BITLBEE_VERSION)-0
dpkg --build debian/bitlbee-dev ..
diff --git a/doc/CHANGES b/doc/CHANGES
index 94330bf9..7359a050 100644
--- a/doc/CHANGES
+++ b/doc/CHANGES
@@ -3,6 +3,26 @@ found in the bzr commit logs, for example you can try:
http://bugs.bitlbee.org/bitlbee/timeline?daysback=90&changeset=on
+Version 1.2.7:
+- Fixed problems with MSN Messenger authentication. ("Could not parse
+ Passport server response")
+- Fixed broken typing notifications when talking to GTalk contacts.
+- Fixed an issue with non-anonymous Jabber chatrooms polluting the nick
+ namespace, sometimes generating odd warning messages.
+- Restored ability to start groupchats on ICQ.
+- Added show_offline setting that will also show offline contacts in the
+ control channel.
+- OAuth support for Twitter: This means the module will keep working after
+ June (this also changes "via API" under your tweets into "via BitlBee").
+
+Finished 15 May 2010
+
+Version 1.2.6a:
+- Fixed a typo that renders the Twitter groupchat mode unusable. A last-
+ minute change that came a few minutes late.
+
+Finished 19 Apr 2010
+
Version 1.2.6:
- Native (very basic) support for Twitter, implemented by Geert Mulders.
Currently supported are posting tweets, reading the ones of people you
diff --git a/doc/user-guide/commands.xml b/doc/user-guide/commands.xml
index c9e3168d..2ef2840b 100644
--- a/doc/user-guide/commands.xml
+++ b/doc/user-guide/commands.xml
@@ -78,6 +78,10 @@
<para>
To send tweets yourself, send them to the twitter_(yourusername) contact, or just write in the groupchat channel if you enabled that option.
</para>
+
+ <para>
+ Since Twitter now requires OAuth authentication, you should not enter your Twitter password into BitlBee. Just type a bogus password. The first time you log in, BitlBee will start OAuth authentication. (See <emphasis>help set oauth</emphasis>.)
+ </para>
</description>
</bitlbee-command>
@@ -775,7 +779,7 @@
<bitlbee-setting name="halfop_buddies" type="string" scope="global">
<default>encrypted</default>
- <possible-values>encrypted, trusted, notaway, false</possible-values>
+ <possible-values>encrypted, trusted, notaway, online, false</possible-values>
<description>
<para>
@@ -783,7 +787,7 @@
</para>
<para>
- If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key.
+ If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key. On "online", the flag is set whenever the user is online; only meaningful in conjunction with "show_offline".
</para>
</description>
</bitlbee-setting>
@@ -833,7 +837,7 @@
<bitlbee-setting name="op_buddies" type="string" scope="global">
<default>trusted</default>
- <possible-values>encrypted, trusted, notaway, false</possible-values>
+ <possible-values>encrypted, trusted, notaway, online, false</possible-values>
<description>
<para>
@@ -841,7 +845,7 @@
</para>
<para>
- If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key.
+ If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key. On "online", the flag is set whenever the user is online; only meaningful in conjunction with "show_offline".
</para>
</description>
@@ -881,6 +885,21 @@
</description>
</bitlbee-setting>
+ <bitlbee-setting name="message_length" type="integer" scope="account">
+ <default>140</default>
+
+ <description>
+ <para>
+ Since Twitter rejects messages longer than 140 characters, BitlBee can count message length and emit a warning instead of waiting for Twitter to reject it.
+ </para>
+
+ <para>
+ You can change this limit here but this won't disable length checks on Twitter's side. You can also set it to 0 to disable the check in case you believe BitlBee doesn't count the characters correctly.
+ </para>
+ </description>
+
+ </bitlbee-setting>
+
<bitlbee-setting name="mode" type="string" scope="account">
<possible-values>one, many, chat</possible-values>
<default>one</default>
@@ -924,6 +943,25 @@
</description>
</bitlbee-setting>
+ <bitlbee-setting name="oauth" type="boolean" scope="account">
+ <default>true</default>
+
+ <description>
+ <para>
+ This enables OAuth authentication for Twitter accounts. From June 2010 this will be mandatory.
+ </para>
+
+ <para>
+ With OAuth enabled, you shouldn't tell BitlBee your Twitter password. Just add your account with a bogus password and type <emphasis>account on</emphasis>. BitlBee will then give you a URL to authenticate with Twitter. If this succeeds, Twitter will return a PIN code which you can give back to BitlBee to finish the process.
+ </para>
+
+ <para>
+ The resulting access token will be saved permanently, so you have to do this only once.
+ </para>
+ </description>
+
+ </bitlbee-setting>
+
<bitlbee-setting name="ops" type="string" scope="global">
<default>both</default>
<possible-values>both, root, user, none</possible-values>
@@ -1059,6 +1097,19 @@
</description>
</bitlbee-setting>
+ <bitlbee-setting name="show_offline" type="boolean" scope="global">
+ <default>false</default>
+
+ <description>
+ <para>
+ If enabled causes BitlBee to also show offline users in Channel. You may want to adjust the settings "voice_buddies", "halfop_buddies" and "op_buddies" according to your liking. A reasonable candidate is "voice_buddies=online", "halfop_buddies=false", "op_buddies=notaway".
+ </para>
+ <para>
+ This option takes effect as soon as you reconnect.
+ </para>
+ </description>
+ </bitlbee-setting>
+
<bitlbee-setting name="simulate_netsplit" type="boolean" scope="global">
<default>true</default>
@@ -1180,8 +1231,8 @@
</bitlbee-setting>
<bitlbee-setting name="voice_buddies" type="string" scope="global">
- <default>trusted</default>
- <possible-values>encrypted, trusted, notaway, false</possible-values>
+ <default>notaway</default>
+ <possible-values>encrypted, trusted, notaway, online, false</possible-values>
<description>
<para>
@@ -1189,7 +1240,7 @@
</para>
<para>
- If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key.
+ If "false", the flag is never set. On "notaway", the flag is removed for users marked as "away" and set for all others. On "encrypted", the flag is set for users with whom we have an encrypted connection. On "trusted", it is set only for encrypted connections using a trusted key. On "online", the flag is set whenever the user is online; only meaningful in conjunction with "show_offline".
</para>
</description>
diff --git a/irc.c b/irc.c
index d8656026..858f3edd 100644
--- a/irc.c
+++ b/irc.c
@@ -207,6 +207,7 @@ irc_t *irc_new( int fd )
s = set_add( &irc->set, "query_order", "lifo", NULL, irc );
s = set_add( &irc->set, "root_nick", irc->mynick, set_eval_root_nick, irc );
s = set_add( &irc->set, "save_on_quit", "true", set_eval_bool, irc );
+ s = set_add( &irc->set, "show_offline", "false", set_eval_bool, irc );
s = set_add( &irc->set, "simulate_netsplit", "true", set_eval_bool, irc );
s = set_add( &irc->set, "strip_html", "true", NULL, irc );
s = set_add( &irc->set, "timezone", "local", set_eval_timezone, irc );
diff --git a/lib/Makefile b/lib/Makefile
index 03fef1ab..441634cd 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -9,7 +9,7 @@
-include ../Makefile.settings
# [SH] Program variables
-objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o
+objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o oauth.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o
CFLAGS += -Wall
LFLAGS += -r
diff --git a/lib/misc.c b/lib/misc.c
index af453020..263d840d 100644
--- a/lib/misc.c
+++ b/lib/misc.c
@@ -315,8 +315,7 @@ void http_encode( char *s )
for( i = j = 0; t[i]; i ++, j ++ )
{
- /* if( t[i] <= ' ' || ((unsigned char *)t)[i] >= 128 || t[i] == '%' ) */
- if( !isalnum( t[i] ) )
+ if( !isalnum( t[i] ) && !strchr( "._-~", t[i] ) )
{
sprintf( s + j, "%%%02X", ((unsigned char*)t)[i] );
j += 2;
diff --git a/lib/oauth.c b/lib/oauth.c
new file mode 100644
index 00000000..c60a5a52
--- /dev/null
+++ b/lib/oauth.c
@@ -0,0 +1,453 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple OAuth client (consumer) implementation. *
+* *
+* Copyright 2010 Wilmer van der Gaast <wilmer@gaast.net> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library 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 *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+\***************************************************************************/
+
+#include <glib.h>
+#include <gmodule.h>
+#include <stdlib.h>
+#include <string.h>
+#include "http_client.h"
+#include "base64.h"
+#include "misc.h"
+#include "sha1.h"
+#include "url.h"
+#include "oauth.h"
+
+#define HMAC_BLOCK_SIZE 64
+
+static char *oauth_sign( const char *method, const char *url,
+ const char *params, struct oauth_info *oi )
+{
+ sha1_state_t sha1;
+ uint8_t hash[sha1_hash_size];
+ uint8_t key[HMAC_BLOCK_SIZE+1];
+ char *s;
+ int i;
+
+ /* Create K. If our current key is >64 chars we have to hash it,
+ otherwise just pad. */
+ memset( key, 0, HMAC_BLOCK_SIZE );
+ i = strlen( oi->sp->consumer_secret ) + 1 + ( oi->token_secret ? strlen( oi->token_secret ) : 0 );
+ if( i > HMAC_BLOCK_SIZE )
+ {
+ sha1_init( &sha1 );
+ sha1_append( &sha1, (uint8_t*) oi->sp->consumer_secret, strlen( oi->sp->consumer_secret ) );
+ sha1_append( &sha1, (uint8_t*) "&", 1 );
+ if( oi->token_secret )
+ sha1_append( &sha1, (uint8_t*) oi->token_secret, strlen( oi->token_secret ) );
+ sha1_finish( &sha1, key );
+ }
+ else
+ {
+ g_snprintf( (gchar*) key, HMAC_BLOCK_SIZE + 1, "%s&%s",
+ oi->sp->consumer_secret, oi->token_secret ? : "" );
+ }
+
+ /* Inner part: H(K XOR 0x36, text) */
+ sha1_init( &sha1 );
+
+ for( i = 0; i < HMAC_BLOCK_SIZE; i ++ )
+ key[i] ^= 0x36;
+ sha1_append( &sha1, key, HMAC_BLOCK_SIZE );
+
+ /* OAuth: text = method&url&params, all http_encoded. */
+ sha1_append( &sha1, (const uint8_t*) method, strlen( method ) );
+ sha1_append( &sha1, (const uint8_t*) "&", 1 );
+
+ s = g_new0( char, strlen( url ) * 3 + 1 );
+ strcpy( s, url );
+ http_encode( s );
+ sha1_append( &sha1, (const uint8_t*) s, strlen( s ) );
+ sha1_append( &sha1, (const uint8_t*) "&", 1 );
+ g_free( s );
+
+ s = g_new0( char, strlen( params ) * 3 + 1 );
+ strcpy( s, params );
+ http_encode( s );
+ sha1_append( &sha1, (const uint8_t*) s, strlen( s ) );
+ g_free( s );
+
+ sha1_finish( &sha1, hash );
+
+ /* Final result: H(K XOR 0x5C, inner stuff) */
+ sha1_init( &sha1 );
+ for( i = 0; i < HMAC_BLOCK_SIZE; i ++ )
+ key[i] ^= 0x36 ^ 0x5c;
+ sha1_append( &sha1, key, HMAC_BLOCK_SIZE );
+ sha1_append( &sha1, hash, sha1_hash_size );
+ sha1_finish( &sha1, hash );
+
+ /* base64_encode + HTTP escape it (both consumers
+ need it that away) and we're done. */
+ s = base64_encode( hash, sha1_hash_size );
+ s = g_realloc( s, strlen( s ) * 3 + 1 );
+ http_encode( s );
+
+ return s;
+}
+
+static char *oauth_nonce()
+{
+ unsigned char bytes[9];
+
+ random_bytes( bytes, sizeof( bytes ) );
+ return base64_encode( bytes, sizeof( bytes ) );
+}
+
+void oauth_params_add( GSList **params, const char *key, const char *value )
+{
+ char *item;
+
+ item = g_strdup_printf( "%s=%s", key, value );
+ *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp );
+}
+
+void oauth_params_del( GSList **params, const char *key )
+{
+ int key_len = strlen( key );
+ GSList *l, *n;
+
+ for( l = *params; l; l = n )
+ {
+ n = l->next;
+
+ if( strncmp( (char*) l->data, key, key_len ) == 0 &&
+ ((char*)l->data)[key_len] == '=' )
+ {
+ g_free( l->data );
+ *params = g_slist_remove( *params, l->data );
+ }
+ }
+}
+
+void oauth_params_set( GSList **params, const char *key, const char *value )
+{
+ oauth_params_del( params, key );
+ oauth_params_add( params, key, value );
+}
+
+const char *oauth_params_get( GSList **params, const char *key )
+{
+ int key_len = strlen( key );
+ GSList *l;
+
+ for( l = *params; l; l = l->next )
+ {
+ if( strncmp( (char*) l->data, key, key_len ) == 0 &&
+ ((char*)l->data)[key_len] == '=' )
+ return (const char*) l->data + key_len + 1;
+ }
+
+ return NULL;
+}
+
+static void oauth_params_parse( GSList **params, char *in )
+{
+ char *amp, *eq, *s;
+
+ while( in && *in )
+ {
+ eq = strchr( in, '=' );
+ if( !eq )
+ break;
+
+ *eq = '\0';
+ if( ( amp = strchr( eq + 1, '&' ) ) )
+ *amp = '\0';
+
+ s = g_strdup( eq + 1 );
+ http_decode( s );
+ oauth_params_add( params, in, s );
+ g_free( s );
+
+ *eq = '=';
+ if( amp == NULL )
+ break;
+
+ *amp = '&';
+ in = amp + 1;
+ }
+}
+
+void oauth_params_free( GSList **params )
+{
+ while( params && *params )
+ {
+ g_free( (*params)->data );
+ *params = g_slist_remove( *params, (*params)->data );
+ }
+}
+
+char *oauth_params_string( GSList *params )
+{
+ GSList *l;
+ GString *str = g_string_new( "" );
+
+ for( l = params; l; l = l->next )
+ {
+ char *s, *eq;
+
+ s = g_malloc( strlen( l->data ) * 3 + 1 );
+ strcpy( s, l->data );
+ if( ( eq = strchr( s, '=' ) ) )
+ http_encode( eq + 1 );
+ g_string_append( str, s );
+ g_free( s );
+
+ if( l->next )
+ g_string_append_c( str, '&' );
+ }
+
+ return g_string_free( str, FALSE );
+}
+
+void oauth_info_free( struct oauth_info *info )
+{
+ if( info )
+ {
+ g_free( info->auth_url );
+ g_free( info->request_token );
+ g_free( info->token );
+ g_free( info->token_secret );
+ g_free( info );
+ }
+}
+
+static void oauth_add_default_params( GSList **params, const struct oauth_service *sp )
+{
+ char *s;
+
+ oauth_params_set( params, "oauth_consumer_key", sp->consumer_key );
+ oauth_params_set( params, "oauth_signature_method", "HMAC-SHA1" );
+
+ s = g_strdup_printf( "%d", (int) time( NULL ) );
+ oauth_params_set( params, "oauth_timestamp", s );
+ g_free( s );
+
+ s = oauth_nonce();
+ oauth_params_set( params, "oauth_nonce", s );
+ g_free( s );
+
+ oauth_params_set( params, "oauth_version", "1.0" );
+}
+
+static void *oauth_post_request( const char *url, GSList **params_, http_input_function func, struct oauth_info *oi )
+{
+ GSList *params = NULL;
+ char *s, *params_s, *post;
+ void *req;
+ url_t url_p;
+
+ if( !url_set( &url_p, url ) )
+ {
+ oauth_params_free( params_ );
+ return NULL;
+ }
+
+ if( params_ )
+ params = *params_;
+
+ oauth_add_default_params( &params, oi->sp );
+
+ params_s = oauth_params_string( params );
+ oauth_params_free( &params );
+
+ s = oauth_sign( "POST", url, params_s, oi );
+ post = g_strdup_printf( "%s&oauth_signature=%s", params_s, s );
+ g_free( params_s );
+ g_free( s );
+
+ s = g_strdup_printf( "POST %s HTTP/1.0\r\n"
+ "Host: %s\r\n"
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %zd\r\n"
+ "\r\n"
+ "%s", url_p.file, url_p.host, strlen( post ), post );
+ g_free( post );
+
+ req = http_dorequest( url_p.host, url_p.port, url_p.proto == PROTO_HTTPS,
+ s, func, oi );
+ g_free( s );
+
+ return req;
+}
+
+static void oauth_request_token_done( struct http_request *req );
+
+struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data )
+{
+ struct oauth_info *st = g_new0( struct oauth_info, 1 );
+ GSList *params = NULL;
+
+ st->func = func;
+ st->data = data;
+ st->sp = sp;
+
+ oauth_params_add( &params, "oauth_callback", "oob" );
+
+ if( !oauth_post_request( sp->url_request_token, &params, oauth_request_token_done, st ) )
+ {
+ oauth_info_free( st );
+ return NULL;
+ }
+
+ return st;
+}
+
+static void oauth_request_token_done( struct http_request *req )
+{
+ struct oauth_info *st = req->data;
+
+ st->http = req;
+
+ if( req->status_code == 200 )
+ {
+ GSList *params = NULL;
+
+ st->auth_url = g_strdup_printf( "%s?%s", st->sp->url_authorize, req->reply_body );
+ oauth_params_parse( &params, req->reply_body );
+ st->request_token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+ oauth_params_free( &params );
+ }
+
+ st->stage = OAUTH_REQUEST_TOKEN;
+ st->func( st );
+}
+
+static void oauth_access_token_done( struct http_request *req );
+
+gboolean oauth_access_token( const char *pin, struct oauth_info *st )
+{
+ GSList *params = NULL;
+
+ oauth_params_add( &params, "oauth_token", st->request_token );
+ oauth_params_add( &params, "oauth_verifier", pin );
+
+ return oauth_post_request( st->sp->url_access_token, &params, oauth_access_token_done, st ) != NULL;
+}
+
+static void oauth_access_token_done( struct http_request *req )
+{
+ struct oauth_info *st = req->data;
+
+ st->http = req;
+
+ if( req->status_code == 200 )
+ {
+ GSList *params = NULL;
+
+ oauth_params_parse( &params, req->reply_body );
+ st->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+ st->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
+ oauth_params_free( &params );
+ }
+
+ st->stage = OAUTH_ACCESS_TOKEN;
+ if( st->func( st ) )
+ {
+ /* Don't need these anymore, but keep the rest. */
+ g_free( st->auth_url );
+ st->auth_url = NULL;
+ g_free( st->request_token );
+ st->request_token = NULL;
+ }
+}
+
+char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args )
+{
+ GSList *params = NULL, *l;
+ char *sig = NULL, *params_s, *s;
+ GString *ret = NULL;
+
+ oauth_params_add( &params, "oauth_token", oi->token );
+ oauth_add_default_params( &params, oi->sp );
+
+ /* Start building the OAuth header. 'key="value", '... */
+ ret = g_string_new( "OAuth " );
+ for( l = params; l; l = l->next )
+ {
+ char *kv = l->data;
+ char *eq = strchr( kv, '=' );
+ char esc[strlen(kv)*3+1];
+
+ if( eq == NULL )
+ break; /* WTF */
+
+ strcpy( esc, eq + 1 );
+ http_encode( esc );
+
+ g_string_append_len( ret, kv, eq - kv + 1 );
+ g_string_append_c( ret, '"' );
+ g_string_append( ret, esc );
+ g_string_append( ret, "\", " );
+ }
+
+ /* Now, before generating the signature, add GET/POST arguments to params
+ since they should be included in the base signature string (but not in
+ the HTTP header). */
+ if( args )
+ oauth_params_parse( &params, args );
+ if( ( s = strchr( url, '?' ) ) )
+ {
+ s = g_strdup( s + 1 );
+ oauth_params_parse( &params, s );
+ g_free( s );
+ }
+
+ /* Append the signature and we're done! */
+ params_s = oauth_params_string( params );
+ sig = oauth_sign( method, url, params_s, oi );
+ g_string_append_printf( ret, "oauth_signature=\"%s\"", sig );
+ g_free( params_s );
+
+ oauth_params_free( &params );
+ g_free( sig );
+
+ return ret ? g_string_free( ret, FALSE ) : NULL;
+}
+
+char *oauth_to_string( struct oauth_info *oi )
+{
+ GSList *params = NULL;
+ char *ret;
+
+ oauth_params_add( &params, "oauth_token", oi->token );
+ oauth_params_add( &params, "oauth_token_secret", oi->token_secret );
+ ret = oauth_params_string( params );
+ oauth_params_free( &params );
+
+ return ret;
+}
+
+struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp )
+{
+ struct oauth_info *oi = g_new0( struct oauth_info, 1 );
+ GSList *params = NULL;
+
+ oauth_params_parse( &params, in );
+ oi->token = g_strdup( oauth_params_get( &params, "oauth_token" ) );
+ oi->token_secret = g_strdup( oauth_params_get( &params, "oauth_token_secret" ) );
+ oauth_params_free( &params );
+ oi->sp = sp;
+
+ return oi;
+}
diff --git a/lib/oauth.h b/lib/oauth.h
new file mode 100644
index 00000000..5dfe0ae5
--- /dev/null
+++ b/lib/oauth.h
@@ -0,0 +1,90 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple OAuth client (consumer) implementation. *
+* *
+* Copyright 2010 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. *
+* *
+\***************************************************************************/
+
+/* http://oauth.net/core/1.0a/ */
+
+struct oauth_info;
+
+/* Callback function called twice during the access token request process.
+ Return FALSE if something broke and the process must be aborted. */
+typedef gboolean (*oauth_cb)( struct oauth_info * );
+
+typedef enum
+{
+ OAUTH_INIT,
+ OAUTH_REQUEST_TOKEN,
+ OAUTH_ACCESS_TOKEN,
+} oauth_stage_t;
+
+struct oauth_info
+{
+ oauth_stage_t stage;
+ const struct oauth_service *sp;
+
+ oauth_cb func;
+ void *data;
+
+ struct http_request *http;
+
+ char *auth_url;
+ char *request_token;
+
+ char *token;
+ char *token_secret;
+};
+
+struct oauth_service
+{
+ char *url_request_token;
+ char *url_access_token;
+ char *url_authorize;
+
+ char *consumer_key;
+ char *consumer_secret;
+};
+
+/* http://oauth.net/core/1.0a/#auth_step1 (section 6.1)
+ Request an initial anonymous token which can be used to construct an
+ authorization URL for the user. This is passed to the callback function
+ in a struct oauth_info. */
+struct oauth_info *oauth_request_token( const struct oauth_service *sp, oauth_cb func, void *data );
+
+/* http://oauth.net/core/1.0a/#auth_step3 (section 6.3)
+ The user gets a PIN or so which we now exchange for the final access
+ token. This is passed to the callback function in the same
+ struct oauth_info. */
+gboolean oauth_access_token( const char *pin, struct oauth_info *st );
+
+/* http://oauth.net/core/1.0a/#anchor12 (section 7)
+ Generate an OAuth Authorization: HTTP header. access_token should be
+ saved/fetched using the functions above. args can be a string with
+ whatever's going to be in the POST body of the request. GET args will
+ automatically be grabbed from url. */
+char *oauth_http_header( struct oauth_info *oi, const char *method, const char *url, char *args );
+
+/* Shouldn't normally be required unless the process is aborted by the user. */
+void oauth_info_free( struct oauth_info *info );
+
+/* Convert to and back from strings, for easier saving. */
+char *oauth_to_string( struct oauth_info *oi );
+struct oauth_info *oauth_from_string( char *in, const struct oauth_service *sp );
diff --git a/lib/url.c b/lib/url.c
index de9966b4..9e330f8c 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -26,7 +26,7 @@
#include "url.h"
/* Convert an URL to a url_t structure */
-int url_set( url_t *url, char *set_url )
+int url_set( url_t *url, const char *set_url )
{
char s[MAX_STRING+1];
char *i;
diff --git a/lib/url.h b/lib/url.h
index 8c038c91..55107ad2 100644
--- a/lib/url.h
+++ b/lib/url.h
@@ -41,4 +41,4 @@ typedef struct url
char pass[MAX_STRING+1];
} url_t;
-int url_set( url_t *url, char *set_url );
+int url_set( url_t *url, const char *set_url );
diff --git a/lib/xmltree.c b/lib/xmltree.c
index 67fe46e1..31f8ee9c 100644
--- a/lib/xmltree.c
+++ b/lib/xmltree.c
@@ -448,7 +448,11 @@ struct xt_node *xt_find_node( struct xt_node *node, const char *name )
{
while( node )
{
- if( g_strcasecmp( node->name, name ) == 0 )
+ char *colon;
+
+ if( g_strcasecmp( node->name, name ) == 0 ||
+ ( ( colon = strchr( node->name, ':' ) ) &&
+ g_strcasecmp( colon + 1, name ) == 0 ) )
break;
node = node->next;
@@ -460,6 +464,7 @@ struct xt_node *xt_find_node( struct xt_node *node, const char *name )
char *xt_find_attr( struct xt_node *node, const char *key )
{
int i;
+ char *colon;
if( !node )
return NULL;
@@ -468,6 +473,21 @@ char *xt_find_attr( struct xt_node *node, const char *key )
if( g_strcasecmp( node->attr[i].key, key ) == 0 )
break;
+ /* This is an awful hack that only takes care of namespace prefixes
+ inside a tag. Since IMHO excessive namespace usage in XMPP is
+ massive overkill anyway (this code exists for almost four years
+ now and never really missed it): Meh. */
+ if( !node->attr[i].key && strcmp( key, "xmlns" ) == 0 &&
+ ( colon = strchr( node->name, ':' ) ) )
+ {
+ *colon = '\0';
+ for( i = 0; node->attr[i].key; i ++ )
+ if( strncmp( node->attr[i].key, "xmlns:", 6 ) == 0 &&
+ strcmp( node->attr[i].key + 6, node->name ) == 0 )
+ break;
+ *colon = ':';
+ }
+
return node->attr[i].value;
}
diff --git a/protocols/jabber/conference.c b/protocols/jabber/conference.c
index f434c58a..affe8aef 100644
--- a/protocols/jabber/conference.c
+++ b/protocols/jabber/conference.c
@@ -271,8 +271,10 @@ void jabber_chat_pkt_presence( struct im_connection *ic, struct jabber_buddy *bu
bud->flags |= JBFLAG_IS_ANONYMOUS;
}
- if( bud != jc->me )
+ if( bud != jc->me && bud->flags & JBFLAG_IS_ANONYMOUS )
{
+ /* If JIDs are anonymized, add them to the local
+ list for the duration of this chat. */
imcb_add_buddy( ic, bud->ext_jid, NULL );
imcb_buddy_nick_hint( ic, bud->ext_jid, bud->resource );
}
diff --git a/protocols/msn/passport.c b/protocols/msn/passport.c
index 565d15f3..7c896db1 100644
--- a/protocols/msn/passport.c
+++ b/protocols/msn/passport.c
@@ -144,7 +144,9 @@ static xt_status passport_xt_extract_token( struct xt_node *node, gpointer data
struct msn_auth_data *mad = data;
char *s;
- if( ( s = xt_find_attr( node, "Id" ) ) && strcmp( s, "PPToken1" ) == 0 )
+ if( ( s = xt_find_attr( node, "Id" ) ) &&
+ ( strncmp( s, "Compact", 7 ) == 0 ||
+ strncmp( s, "PPToken", 7 ) == 0 ) )
mad->token = g_memdup( node->text, node->text_len + 1 );
return XT_HANDLED;
diff --git a/protocols/msn/sb.c b/protocols/msn/sb.c
index e2ee8570..49eed601 100644
--- a/protocols/msn/sb.c
+++ b/protocols/msn/sb.c
@@ -327,9 +327,13 @@ static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition c
struct im_connection *ic = sb->ic;
struct msn_data *md = ic->proto_data;
- if( msn_handler( sb->handler ) == -1 )
+ if( msn_handler( sb->handler ) != -1 )
+ return TRUE;
+
+ if( sb->msgq != NULL )
{
time_t now = time( NULL );
+ char buf[1024];
if( now - md->first_sb_failure > 600 )
{
@@ -346,37 +350,28 @@ static gboolean msn_sb_callback( gpointer data, gint source, b_input_condition c
imcb_log( ic, "Warning: Many switchboard failures on MSN connection. "
"There might be problems delivering your messages." );
- if( sb->msgq != NULL )
+ if( md->msgq == NULL )
{
- char buf[1024];
-
- if( md->msgq == NULL )
- {
- md->msgq = sb->msgq;
- }
- else
- {
- GSList *l;
-
- for( l = md->msgq; l->next; l = l->next );
- l->next = sb->msgq;
- }
- sb->msgq = NULL;
+ md->msgq = sb->msgq;
+ }
+ else
+ {
+ GSList *l;
- debug( "Moved queued messages back to the main queue, creating a new switchboard to retry." );
- g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
- if( !msn_write( ic, buf, strlen( buf ) ) )
- return FALSE;
+ for( l = md->msgq; l->next; l = l->next );
+ l->next = sb->msgq;
}
+ sb->msgq = NULL;
- msn_sb_destroy( sb );
-
- return FALSE;
- }
- else
- {
- return TRUE;
+ debug( "Moved queued messages back to the main queue, "
+ "creating a new switchboard to retry." );
+ g_snprintf( buf, sizeof( buf ), "XFR %d SB\r\n", ++md->trId );
+ if( !msn_write( ic, buf, strlen( buf ) ) )
+ return FALSE;
}
+
+ msn_sb_destroy( sb );
+ return FALSE;
}
static int msn_sb_command( gpointer data, char **cmd, int num_parts )
diff --git a/protocols/nogaim.c b/protocols/nogaim.c
index ebb00418..5535e093 100644
--- a/protocols/nogaim.c
+++ b/protocols/nogaim.c
@@ -656,7 +656,18 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
g_free( u->status_msg );
u->away = u->status_msg = NULL;
- if( ( flags & OPT_LOGGED_IN ) && !u->online )
+ if( set_getbool( &ic->irc->set, "show_offline" ) && !u->online )
+ {
+ /* always set users as online */
+ irc_spawn( ic->irc, u );
+ u->online = 1;
+ if( !( flags & OPT_LOGGED_IN ) )
+ {
+ /* set away message if user isn't really online */
+ u->away = g_strdup( "User is offline" );
+ }
+ }
+ else if( ( flags & OPT_LOGGED_IN ) && !u->online )
{
irc_spawn( ic->irc, u );
u->online = 1;
@@ -665,14 +676,30 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
{
struct groupchat *c;
- irc_kill( ic->irc, u );
- u->online = 0;
-
- /* 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 );
+ if( set_getbool( &ic->irc->set, "show_offline" ) )
+ {
+ /* keep offline users in channel and set away message to "offline" */
+ u->away = g_strdup( "User is offline" );
+
+ /* Keep showing him/her in the control channel but not in groupchats. */
+ for( c = ic->groupchats; c; c = c->next )
+ {
+ if( remove_chat_buddy_silent( c, handle ) && c->joined )
+ irc_part( c->ic->irc, u, c->channel );
+ }
+ }
+ else
+ {
+ /* kill offline users */
+ irc_kill( ic->irc, u );
+ u->online = 0;
+
+ /* 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 );
+ }
}
-
+
if( flags & OPT_AWAY )
{
if( state && message )
@@ -699,7 +726,7 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
/* LISPy... */
if( ( u->online ) && /* Don't touch offline people */
- ( ( ( u->online != oo ) && !u->away ) || /* Do joining people */
+ ( ( u->online != oo ) || /* Do joining people */
( ( u->online == oo ) && ( oa == !u->away ) ) ) ) /* Do people changing state */
{
char *from;
@@ -713,6 +740,17 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
from = g_strdup_printf( "%s!%s@%s", ic->irc->mynick, ic->irc->mynick,
ic->irc->myhost );
}
+
+ if(!strcmp(set_getstr(&ic->irc->set, "voice_buddies"), "online")) {
+ irc_write( ic->irc, ":%s MODE %s +v %s", from, ic->irc->channel, u->nick );
+ }
+ if(!strcmp(set_getstr(&ic->irc->set, "halfop_buddies"), "online")) {
+ irc_write( ic->irc, ":%s MODE %s +h %s", from, ic->irc->channel, u->nick );
+ }
+ if(!strcmp(set_getstr(&ic->irc->set, "op_buddies"), "online")) {
+ irc_write( ic->irc, ":%s MODE %s +o %s", from, ic->irc->channel, u->nick );
+ }
+
if(!strcmp(set_getstr(&ic->irc->set, "voice_buddies"), "notaway")) {
irc_write( ic->irc, ":%s MODE %s %cv %s", from, ic->irc->channel,
u->away?'-':'+', u->nick );
@@ -725,6 +763,7 @@ void imcb_buddy_status( struct im_connection *ic, const char *handle, int flags,
irc_write( ic->irc, ":%s MODE %s %co %s", from, ic->irc->channel,
u->away?'-':'+', u->nick );
}
+
g_free( from );
}
}
@@ -1155,7 +1194,7 @@ static char *format_timestamp( irc_t *irc, time_t msg_ts )
else
return g_strdup_printf( "\x02[\x02\x02\x02%04d-%02d-%02d "
"%02d:%02d:%02d\x02]\x02 ",
- msg.tm_year + 1900, msg.tm_mon, msg.tm_mday,
+ msg.tm_year + 1900, msg.tm_mon + 1, msg.tm_mday,
msg.tm_hour, msg.tm_min, msg.tm_sec );
}
diff --git a/protocols/oscar/oscar.c b/protocols/oscar/oscar.c
index dbab5c4f..8a944e99 100644
--- a/protocols/oscar/oscar.c
+++ b/protocols/oscar/oscar.c
@@ -204,7 +204,6 @@ static int gaim_parse_buddyrights(aim_session_t *, aim_frame_t *, ...);
static int gaim_parse_locerr (aim_session_t *, aim_frame_t *, ...);
static int gaim_icbm_param_info (aim_session_t *, aim_frame_t *, ...);
static int gaim_parse_genericerr (aim_session_t *, aim_frame_t *, ...);
-static int gaim_memrequest (aim_session_t *, aim_frame_t *, ...);
static int gaim_selfinfo (aim_session_t *, aim_frame_t *, ...);
static int gaim_offlinemsg (aim_session_t *, aim_frame_t *, ...);
static int gaim_offlinemsgdone (aim_session_t *, aim_frame_t *, ...);
@@ -569,7 +568,6 @@ static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) {
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_ERROR, gaim_parse_genericerr, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BUD, AIM_CB_BUD_ERROR, gaim_parse_genericerr, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_BOS, AIM_CB_BOS_ERROR, gaim_parse_genericerr, 0);
- aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, 0x1f, gaim_memrequest, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_GEN, AIM_CB_GEN_SELFINFO, gaim_selfinfo, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSG, gaim_offlinemsg, 0);
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ICQ, AIM_CB_ICQ_OFFLINEMSGCOMPLETE, gaim_offlinemsgdone, 0);
@@ -603,143 +601,9 @@ static int gaim_parse_auth_resp(aim_session_t *sess, aim_frame_t *fr, ...) {
return 1;
}
-struct pieceofcrap {
- struct im_connection *ic;
- unsigned long offset;
- unsigned long len;
- char *modname;
- int fd;
- aim_conn_t *conn;
- unsigned int inpa;
-};
-
-static gboolean damn_you(gpointer data, gint source, b_input_condition c)
-{
- struct pieceofcrap *pos = data;
- struct oscar_data *od = pos->ic->proto_data;
- char in = '\0';
- int x = 0;
- unsigned char m[17];
-
- while (read(pos->fd, &in, 1) == 1) {
- if (in == '\n')
- x++;
- else if (in != '\r')
- x = 0;
- if (x == 2)
- break;
- in = '\0';
- }
- if (in != '\n') {
- imcb_error(pos->ic, "Gaim was unable to get a valid hash for logging into AIM."
- " You may be disconnected shortly.");
- b_event_remove(pos->inpa);
- closesocket(pos->fd);
- g_free(pos);
- return FALSE;
- }
- /* [WvG] Wheeeee! Who needs error checking anyway? ;-) */
- read(pos->fd, m, 16);
- m[16] = '\0';
- b_event_remove(pos->inpa);
- closesocket(pos->fd);
- aim_sendmemblock(od->sess, pos->conn, 0, 16, m, AIM_SENDMEMBLOCK_FLAG_ISHASH);
- g_free(pos);
-
- return FALSE;
-}
-
-static gboolean straight_to_hell(gpointer data, gint source, b_input_condition cond) {
- struct pieceofcrap *pos = data;
- char buf[BUF_LONG];
-
- if (source < 0) {
- imcb_error(pos->ic, "Gaim was unable to get a valid hash for logging into AIM."
- " You may be disconnected shortly.");
- if (pos->modname)
- g_free(pos->modname);
- g_free(pos);
- return FALSE;
- }
-
- g_snprintf(buf, sizeof(buf), "GET " AIMHASHDATA
- "?offset=%ld&len=%ld&modname=%s HTTP/1.0\n\n",
- pos->offset, pos->len, pos->modname ? pos->modname : "");
- write(pos->fd, buf, strlen(buf));
- if (pos->modname)
- g_free(pos->modname);
- pos->inpa = b_input_add(pos->fd, GAIM_INPUT_READ, damn_you, pos);
- return FALSE;
-}
-
/* size of icbmui.ocm, the largest module in AIM 3.5 */
#define AIM_MAX_FILE_SIZE 98304
-int gaim_memrequest(aim_session_t *sess, aim_frame_t *fr, ...) {
- va_list ap;
- struct pieceofcrap *pos;
- guint32 offset, len;
- char *modname;
- int fd;
-
- va_start(ap, fr);
- offset = (guint32)va_arg(ap, unsigned long);
- len = (guint32)va_arg(ap, unsigned long);
- modname = va_arg(ap, char *);
- va_end(ap);
-
- if (len == 0) {
- aim_sendmemblock(sess, fr->conn, offset, len, NULL,
- AIM_SENDMEMBLOCK_FLAG_ISREQUEST);
- return 1;
- }
- /* uncomment this when you're convinced it's right. remember, it's been wrong before.
- if (offset > AIM_MAX_FILE_SIZE || len > AIM_MAX_FILE_SIZE) {
- char *buf;
- int i = 8;
- if (modname)
- i += strlen(modname);
- buf = g_malloc(i);
- i = 0;
- if (modname) {
- memcpy(buf, modname, strlen(modname));
- i += strlen(modname);
- }
- buf[i++] = offset & 0xff;
- buf[i++] = (offset >> 8) & 0xff;
- buf[i++] = (offset >> 16) & 0xff;
- buf[i++] = (offset >> 24) & 0xff;
- buf[i++] = len & 0xff;
- buf[i++] = (len >> 8) & 0xff;
- buf[i++] = (len >> 16) & 0xff;
- buf[i++] = (len >> 24) & 0xff;
- aim_sendmemblock(sess, command->conn, offset, i, buf, AIM_SENDMEMBLOCK_FLAG_ISREQUEST);
- g_free(buf);
- return 1;
- }
- */
-
- pos = g_new0(struct pieceofcrap, 1);
- pos->ic = sess->aux_data;
- pos->conn = fr->conn;
-
- pos->offset = offset;
- pos->len = len;
- pos->modname = modname ? g_strdup(modname) : NULL;
-
- fd = proxy_connect("gaim.sourceforge.net", 80, straight_to_hell, pos);
- if (fd < 0) {
- if (pos->modname)
- g_free(pos->modname);
- g_free(pos);
- imcb_error(sess->aux_data, "Gaim was unable to get a valid hash for logging into AIM."
- " You may be disconnected shortly.");
- }
- pos->fd = fd;
-
- return 1;
-}
-
static int gaim_parse_login(aim_session_t *sess, aim_frame_t *fr, ...) {
#if 0
struct client_info_s info = {"gaim", 4, 1, 2010, "us", "en", 0x0004, 0x0000, 0x04b};
@@ -2650,7 +2514,8 @@ struct groupchat *oscar_chat_with(struct im_connection * ic, char *who)
static int chat_id = 0;
char * chatname;
- chatname = g_strdup_printf("%s%d", ic->acc->user, chat_id++);
+ chatname = g_strdup_printf("%s%s_%d", isdigit(*ic->acc->user) ? "icq_" : "",
+ ic->acc->user, chat_id++);
ret = oscar_chat_join(ic, chatname, NULL, NULL);
diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c
index 29be8a96..98e85641 100644
--- a/protocols/twitter/twitter.c
+++ b/protocols/twitter/twitter.c
@@ -22,11 +22,11 @@
****************************************************************************/
#include "nogaim.h"
+#include "oauth.h"
#include "twitter.h"
#include "twitter_http.h"
#include "twitter_lib.h"
-
/**
* Main loop function
*/
@@ -50,22 +50,121 @@ gboolean twitter_main_loop(gpointer data, gint fd, b_input_condition cond)
return (ic->flags & OPT_LOGGED_IN) == OPT_LOGGED_IN;
}
+static void twitter_main_loop_start( struct im_connection *ic )
+{
+ struct twitter_data *td = ic->proto_data;
+
+ imcb_log( ic, "Connecting to Twitter" );
+
+ // Run this once. After this queue the main loop function.
+ twitter_main_loop(ic, -1, 0);
+
+ // Queue the main_loop
+ // Save the return value, so we can remove the timeout on logout.
+ td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic);
+}
+
+
+static const struct oauth_service twitter_oauth =
+{
+ "http://api.twitter.com/oauth/request_token",
+ "http://api.twitter.com/oauth/access_token",
+ "http://api.twitter.com/oauth/authorize",
+ .consumer_key = "xsDNKJuNZYkZyMcu914uEA",
+ .consumer_secret = "FCxqcr0pXKzsF9ajmP57S3VQ8V6Drk4o2QYtqMcOszo",
+};
+
+static gboolean twitter_oauth_callback( struct oauth_info *info );
+
+static void twitter_oauth_start( struct im_connection *ic )
+{
+ struct twitter_data *td = ic->proto_data;
+
+ imcb_log( ic, "Requesting OAuth request token" );
+
+ td->oauth_info = oauth_request_token( &twitter_oauth, twitter_oauth_callback, ic );
+}
+
+static gboolean twitter_oauth_callback( struct oauth_info *info )
+{
+ struct im_connection *ic = info->data;
+ struct twitter_data *td;
+
+ if( !g_slist_find( twitter_connections, ic ) )
+ return FALSE;
+
+ td = ic->proto_data;
+ if( info->stage == OAUTH_REQUEST_TOKEN )
+ {
+ char name[strlen(ic->acc->user)+9], *msg;
+
+ if( info->request_token == NULL )
+ {
+ imcb_error( ic, "OAuth error: %s", info->http->status_string );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ sprintf( name, "twitter_%s", ic->acc->user );
+ msg = g_strdup_printf( "To finish OAuth authentication, please visit "
+ "%s and respond with the resulting PIN code.",
+ info->auth_url );
+ imcb_buddy_msg( ic, name, msg, 0, 0 );
+ g_free( msg );
+ }
+ else if( info->stage == OAUTH_ACCESS_TOKEN )
+ {
+ if( info->token == NULL || info->token_secret == NULL )
+ {
+ imcb_error( ic, "OAuth error: %s", info->http->status_string );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+
+ /* IM mods didn't do this so far and it's ugly but I should
+ be able to get away with it... */
+ g_free( ic->acc->pass );
+ ic->acc->pass = oauth_to_string( info );
+
+ twitter_main_loop_start( ic );
+ }
+
+ return TRUE;
+}
+
+
static char *set_eval_mode( set_t *set, char *value )
{
if( g_strcasecmp( value, "one" ) == 0 ||
g_strcasecmp( value, "many" ) == 0 ||
- g_strcasecmp( value, "char" ) == 0 )
+ g_strcasecmp( value, "chat" ) == 0 )
return value;
else
return NULL;
}
+static gboolean twitter_length_check( struct im_connection *ic, gchar *msg )
+{
+ int max = set_getint( &ic->acc->set, "message_length" ), len;
+
+ if( max == 0 || ( len = g_utf8_strlen( msg, -1 ) ) <= max )
+ return TRUE;
+
+ imcb_error( ic, "Maximum message length exceeded: %d > %d", len, max );
+
+ return FALSE;
+}
+
static void twitter_init( account_t *acc )
{
set_t *s;
+ s = set_add( &acc->set, "message_length", "140", set_eval_int, acc );
+
s = set_add( &acc->set, "mode", "one", set_eval_mode, acc );
s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add( &acc->set, "oauth", "true", set_eval_bool, acc );
}
/**
@@ -79,25 +178,24 @@ static void twitter_login( account_t *acc )
char name[strlen(acc->user)+9];
twitter_connections = g_slist_append( twitter_connections, ic );
-
+ ic->proto_data = td;
+ ic->flags |= OPT_DOES_HTML;
+
td->user = acc->user;
- td->pass = acc->pass;
+ if( !set_getbool( &acc->set, "oauth" ) )
+ td->pass = g_strdup( acc->pass );
+ else if( strstr( acc->pass, "oauth_token=" ) )
+ td->oauth_info = oauth_from_string( acc->pass, &twitter_oauth );
td->home_timeline_id = 0;
-
- ic->proto_data = td;
-
- imcb_log( ic, "Connecting to Twitter" );
-
- // Run this once. After this queue the main loop function.
- twitter_main_loop(ic, -1, 0);
-
- // Queue the main_loop
- // Save the return value, so we can remove the timeout on logout.
- td->main_loop_id = b_timeout_add(60000, twitter_main_loop, ic);
sprintf( name, "twitter_%s", acc->user );
imcb_add_buddy( ic, name, NULL );
imcb_buddy_status( ic, name, OPT_LOGGED_IN, NULL, NULL );
+
+ if( td->pass || td->oauth_info )
+ twitter_main_loop_start( ic );
+ else
+ twitter_oauth_start( ic );
}
/**
@@ -118,6 +216,8 @@ static void twitter_logout( struct im_connection *ic )
if( td )
{
+ oauth_info_free( td->oauth_info );
+ g_free( td->pass );
g_free( td );
}
@@ -129,12 +229,28 @@ static void twitter_logout( struct im_connection *ic )
*/
static int twitter_buddy_msg( struct im_connection *ic, char *who, char *message, int away )
{
+ struct twitter_data *td = ic->proto_data;
+
if (g_strncasecmp(who, "twitter_", 8) == 0 &&
g_strcasecmp(who + 8, ic->acc->user) == 0)
- twitter_post_status(ic, message);
+ {
+ if( set_getbool( &ic->acc->set, "oauth" ) &&
+ td->oauth_info && td->oauth_info->token == NULL )
+ {
+ if( !oauth_access_token( message, td->oauth_info ) )
+ {
+ imcb_error( ic, "OAuth error: %s", "Failed to send access token request" );
+ imc_logout( ic, TRUE );
+ return FALSE;
+ }
+ }
+ else if( twitter_length_check(ic, message) )
+ twitter_post_status(ic, message);
+ }
else
+ {
twitter_direct_messages_new(ic, who, message);
-
+ }
return( 0 );
}
@@ -159,7 +275,7 @@ static void twitter_remove_buddy( struct im_connection *ic, char *who, char *gro
static void twitter_chat_msg( struct groupchat *c, char *message, int flags )
{
- if( c && message )
+ if( c && message && twitter_length_check(c->ic, message))
twitter_post_status(c->ic, message);
}
diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h
index 88caa104..24f61e42 100644
--- a/protocols/twitter/twitter.h
+++ b/protocols/twitter/twitter.h
@@ -36,6 +36,7 @@ struct twitter_data
{
char* user;
char* pass;
+ struct oauth_info *oauth_info;
guint64 home_timeline_id;
gint main_loop_id;
struct groupchat *home_timeline_gc;
diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c
index 3632140f..51f437df 100644
--- a/protocols/twitter/twitter_http.c
+++ b/protocols/twitter/twitter_http.c
@@ -28,15 +28,17 @@
* *
****************************************************************************/
-#include "twitter_http.h"
#include "twitter.h"
#include "bitlbee.h"
#include "url.h"
#include "misc.h"
#include "base64.h"
+#include "oauth.h"
#include <ctype.h>
#include <errno.h>
+#include "twitter_http.h"
+
char *twitter_url_append(char *url, char *key, char* value);
@@ -44,7 +46,7 @@ char *twitter_url_append(char *url, char *key, char* value);
* Do a request.
* This is actually pretty generic function... Perhaps it should move to the lib/http_client.c
*/
-void *twitter_http(char *url_string, http_input_function func, gpointer data, int is_post, char* user, char* pass, char** arguments, int arguments_len)
+void *twitter_http(char *url_string, http_input_function func, gpointer data, int is_post, char* user, char* pass, struct oauth_info* oi, char** arguments, int arguments_len)
{
url_t *url = g_new0( url_t, 1 );
char *tmp;
@@ -109,7 +111,19 @@ void *twitter_http(char *url_string, http_input_function func, gpointer data, in
is_post ? "POST" : "GET", url->file, url->host );
// If a pass and user are given we append them to the request.
- if (userpass_base64)
+ if (oi)
+ {
+ char *full_header;
+
+ full_header = oauth_http_header(oi, is_post ? "POST" : "GET",
+ url_string, url_arguments);
+
+ tmp = g_strdup_printf("%sAuthorization: %s\r\n", request, full_header);
+ g_free(request);
+ g_free(full_header);
+ request = tmp;
+ }
+ else if (userpass_base64)
{
tmp = g_strdup_printf("%sAuthorization: Basic %s\r\n", request, userpass_base64);
g_free(request);
diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h
index ec4a0b7c..5ef2530f 100644
--- a/protocols/twitter/twitter_http.h
+++ b/protocols/twitter/twitter_http.h
@@ -27,8 +27,10 @@
#include "nogaim.h"
#include "http_client.h"
+struct oauth_info;
+
void *twitter_http(char *url_string, http_input_function func, gpointer data, int is_post,
- char* user, char* pass, char** arguments, int arguments_len);
+ char* user, char* pass, struct oauth_info *oi, char** arguments, int arguments_len);
#endif //_TWITTER_HTTP_H
diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c
index d58afd73..ee6e39fe 100644
--- a/protocols/twitter/twitter_lib.c
+++ b/protocols/twitter/twitter_lib.c
@@ -129,7 +129,7 @@ void twitter_get_friends_ids(struct im_connection *ic, int next_cursor)
char* args[2];
args[0] = "cursor";
args[1] = g_strdup_printf ("%d", next_cursor);
- twitter_http(TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, td->user, td->pass, args, 2);
+ twitter_http(TWITTER_FRIENDS_IDS_URL, twitter_http_get_friends_ids, ic, 0, td->user, td->pass, td->oauth_info, args, 2);
g_free(args[1]);
}
@@ -395,7 +395,7 @@ void twitter_get_home_timeline(struct im_connection *ic, int next_cursor)
args[3] = g_strdup_printf ("%llu", (long long unsigned int) td->home_timeline_id);
}
- twitter_http(TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, td->user, td->pass, args, td->home_timeline_id ? 4 : 2);
+ twitter_http(TWITTER_HOME_TIMELINE_URL, twitter_http_get_home_timeline, ic, 0, td->user, td->pass, td->oauth_info, args, td->home_timeline_id ? 4 : 2);
g_free(args[1]);
if (td->home_timeline_id) {
@@ -509,7 +509,7 @@ static void twitter_http_get_home_timeline(struct http_request *req)
if (req->status_code == 200)
{
td->http_fails = 0;
- if (!ic->flags & OPT_LOGGED_IN)
+ if (!(ic->flags & OPT_LOGGED_IN))
imcb_connected(ic);
}
else if (req->status_code == 401)
@@ -619,7 +619,7 @@ void twitter_get_statuses_friends(struct im_connection *ic, int next_cursor)
args[0] = "cursor";
args[1] = g_strdup_printf ("%d", next_cursor);
- twitter_http(TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, td->user, td->pass, args, 2);
+ twitter_http(TWITTER_SHOW_FRIENDS_URL, twitter_http_get_statuses_friends, ic, 0, td->user, td->pass, td->oauth_info, args, 2);
g_free(args[1]);
}
@@ -653,7 +653,7 @@ void twitter_post_status(struct im_connection *ic, char* msg)
char* args[2];
args[0] = "status";
args[1] = msg;
- twitter_http(TWITTER_STATUS_UPDATE_URL, twitter_http_post_status, ic, 1, td->user, td->pass, args, 2);
+ twitter_http(TWITTER_STATUS_UPDATE_URL, twitter_http_post_status, ic, 1, td->user, td->pass, td->oauth_info, args, 2);
// g_free(args[1]);
}
@@ -671,7 +671,7 @@ void twitter_direct_messages_new(struct im_connection *ic, char *who, char *msg)
args[2] = "text";
args[3] = msg;
// Use the same callback as for twitter_post_status, since it does basically the same.
- twitter_http(TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post_status, ic, 1, td->user, td->pass, args, 4);
+ twitter_http(TWITTER_DIRECT_MESSAGES_NEW_URL, twitter_http_post_status, ic, 1, td->user, td->pass, td->oauth_info, args, 4);
// g_free(args[1]);
// g_free(args[3]);
}
diff --git a/protocols/yahoo/yahoo.c b/protocols/yahoo/yahoo.c
index 749e9f15..4826adb4 100644
--- a/protocols/yahoo/yahoo.c
+++ b/protocols/yahoo/yahoo.c
@@ -137,10 +137,15 @@ static void byahoo_login( account_t *acc )
{
struct im_connection *ic = imcb_new( acc );
struct byahoo_data *yd = ic->proto_data = g_new0( struct byahoo_data, 1 );
+ char *s;
yd->logged_in = FALSE;
yd->current_status = YAHOO_STATUS_AVAILABLE;
+ if( ( s = strchr( acc->user, '@' ) ) && g_strcasecmp( s, "@yahoo.com" ) == 0 )
+ imcb_error( ic, "Your Yahoo! username should just be a username. "
+ "Do not include any @domain part." );
+
imcb_log( ic, "Connecting" );
yd->y2_id = yahoo_init( acc->user, acc->pass );
yahoo_login( yd->y2_id, yd->current_status );
@@ -828,6 +833,10 @@ void ext_yahoo_got_conf_invite( int id, const char *ignored,
char txt[1024];
YList *m;
+ if( g_strcasecmp( who, ic->acc->user ) == 0 )
+ /* WTF, Yahoo! seems to echo these now? */
+ return;
+
inv = g_malloc( sizeof( struct byahoo_conf_invitation ) );
memset( inv, 0, sizeof( struct byahoo_conf_invitation ) );
inv->name = g_strdup( room );