Changes in / [17f057d:6e9ae72]
- Files:
-
- 2 added
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
doc/user-guide/commands.xml
r17f057d r6e9ae72 1126 1126 <description> 1127 1127 <para> 1128 This enables OAuth authentication for Twitter accounts. From June 2010 this will be mandatory.1129 </para> 1130 1131 <para> 1132 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 returna PIN code which you can give back to BitlBee to finish the process.1133 </para> 1134 1135 <para> 1136 The resulting access token will be saved permanently, so you have to do this only once. 1128 This enables OAuth authentication for accounts that support it; right now Twitter and Google Talk (if you have 2-factor authentication enabled on your account) support it. 1129 </para> 1130 1131 <para> 1132 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 the service. If this succeeds, you will get a PIN code which you can give back to BitlBee to finish the process. 1133 </para> 1134 1135 <para> 1136 The resulting access token will be saved permanently, so you have to do this only once. If for any reason you want to/have to reauthenticate, you can use <emphasis>account set</emphasis> to reset the account password to something random. 1137 1137 </para> 1138 1138 </description> -
irc_im.c
r17f057d r6e9ae72 442 442 { 443 443 irc_user_t *iu = data; 444 char *msg = g_string_free( iu->pastebuf, FALSE );444 char *msg; 445 445 GSList *l; 446 447 msg = g_string_free( iu->pastebuf, FALSE ); 448 iu->pastebuf = NULL; 449 iu->pastebuf_timer = 0; 446 450 447 451 for( l = irc_plugins; l; l = l->next ) … … 470 474 471 475 g_free( msg ); 472 iu->pastebuf = NULL;473 iu->pastebuf_timer = 0;474 476 475 477 return FALSE; -
lib/Makefile
r17f057d r6e9ae72 13 13 14 14 # [SH] Program variables 15 objects = arc.o base64.o $(DES) $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o15 objects = arc.o base64.o $(DES) $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o 16 16 17 17 LFLAGS += -r -
lib/md5.c
r17f057d r6e9ae72 24 24 #include <sys/types.h> 25 25 #include <string.h> /* for memcpy() */ 26 #include <stdio.h> 26 27 #include "md5.h" 27 28 … … 160 161 memcpy(digest, ctx->buf, 16); 161 162 memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ 163 } 164 165 void md5_finish_ascii(struct MD5Context *context, char *ascii) 166 { 167 md5_byte_t bin[16]; 168 int i; 169 170 md5_finish(context, bin); 171 for (i = 0; i < 16; i ++) 172 sprintf(ascii + i * 2, "%02x", bin[i]); 162 173 } 163 174 -
lib/md5.h
r17f057d r6e9ae72 43 43 G_MODULE_EXPORT void md5_append(struct MD5Context *context, const md5_byte_t *buf, unsigned int len); 44 44 G_MODULE_EXPORT void md5_finish(struct MD5Context *context, md5_byte_t digest[16]); 45 G_MODULE_EXPORT void md5_finish_ascii(struct MD5Context *context, char *ascii); 45 46 46 47 #endif -
lib/oauth.c
r17f057d r6e9ae72 122 122 char *item; 123 123 124 if( !key || !value ) 125 return; 126 124 127 item = g_strdup_printf( "%s=%s", key, value ); 125 128 *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp ); … … 165 168 } 166 169 167 staticvoid oauth_params_parse( GSList **params, char *in )170 void oauth_params_parse( GSList **params, char *in ) 168 171 { 169 172 char *amp, *eq, *s; -
lib/oauth.h
r17f057d r6e9ae72 92 92 93 93 /* For reading misc. data. */ 94 void oauth_params_add( GSList **params, const char *key, const char *value ); 95 void oauth_params_parse( GSList **params, char *in ); 96 void oauth_params_free( GSList **params ); 97 char *oauth_params_string( GSList *params ); 94 98 const char *oauth_params_get( GSList **params, const char *key ); -
protocols/jabber/jabber.c
r17f057d r6e9ae72 60 60 s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc ); 61 61 62 s = set_add( &acc->set, "oauth", "false", set_eval_bool, acc ); 63 62 64 g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] ); 63 65 s = set_add( &acc->set, "port", str, set_eval_int, acc ); … … 99 101 struct im_connection *ic = imcb_new( acc ); 100 102 struct jabber_data *jd = g_new0( struct jabber_data, 1 ); 101 struct ns_srv_reply **srvl = NULL, *srv = NULL; 102 char *connect_to, *s; 103 int i; 103 char *s; 104 104 105 105 /* For now this is needed in the _connected() handlers if using … … 138 138 } 139 139 140 /* This code isn't really pretty. Backwards compatibility never is... */141 s = acc->server;142 while( s )143 {144 static int had_port = 0;145 146 if( strncmp( s, "ssl", 3 ) == 0 )147 {148 set_setstr( &acc->set, "ssl", "true" );149 150 /* Flush this part so that (if this was the first151 part of the server string) acc->server gets152 flushed. We don't want to have to do this another153 time. :-) */154 *s = 0;155 s ++;156 157 /* Only set this if the user didn't specify a custom158 port number already... */159 if( !had_port )160 set_setint( &acc->set, "port", 5223 );161 }162 else if( isdigit( *s ) )163 {164 int i;165 166 /* The first character is a digit. It could be an167 IP address though. Only accept this as a port#168 if there are only digits. */169 for( i = 0; isdigit( s[i] ); i ++ );170 171 /* If the first non-digit character is a colon or172 the end of the string, save the port number173 where it should be. */174 if( s[i] == ':' || s[i] == 0 )175 {176 sscanf( s, "%d", &i );177 set_setint( &acc->set, "port", i );178 179 /* See above. */180 *s = 0;181 s ++;182 }183 184 had_port = 1;185 }186 187 s = strchr( s, ':' );188 if( s )189 {190 *s = 0;191 s ++;192 }193 }194 195 140 jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free ); 196 141 jd->buddies = g_hash_table_new( g_str_hash, g_str_equal ); 142 143 if( set_getbool( &acc->set, "oauth" ) ) 144 { 145 jd->fd = jd->r_inpa = jd->w_inpa = -1; 146 147 /* For the first login with OAuth, we have to authenticate via the browser. 148 For subsequent logins, exchange the refresh token for a valid access 149 token (even though the last one maybe didn't expire yet). */ 150 if( strncmp( acc->pass, "refresh_token=", 14 ) != 0 ) 151 { 152 sasl_oauth2_init( ic ); 153 ic->flags |= OPT_SLOW_LOGIN; 154 } 155 else 156 sasl_oauth2_refresh( ic, acc->pass + 14 ); 157 } 158 else 159 jabber_connect( ic ); 160 } 161 162 /* Separate this from jabber_login() so we can do OAuth first if necessary. 163 Putting this in io.c would probably be more correct. */ 164 void jabber_connect( struct im_connection *ic ) 165 { 166 account_t *acc = ic->acc; 167 struct jabber_data *jd = ic->proto_data; 168 int i; 169 char *connect_to; 170 struct ns_srv_reply **srvl = NULL, *srv = NULL; 197 171 198 172 /* Figure out the hostname to connect to. */ … … 319 293 xt_free( jd->xt ); 320 294 295 g_free( jd->oauth2_access_token ); 321 296 g_free( jd->away_message ); 322 297 g_free( jd->username ); … … 336 311 if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 ) 337 312 return jabber_write( ic, message, strlen( message ) ); 313 314 if( g_strcasecmp( who, "jabber_oauth" ) == 0 ) 315 { 316 if( sasl_oauth2_get_refresh_token( ic, message ) ) 317 { 318 return 1; 319 } 320 else 321 { 322 imcb_error( ic, "OAuth failure" ); 323 imc_logout( ic, TRUE ); 324 return 0; 325 } 326 } 338 327 339 328 if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) ) -
protocols/jabber/jabber.h
r17f057d r6e9ae72 47 47 JFLAG_XMLCONSOLE = 64, /* If the user added an xmlconsole buddy. */ 48 48 JFLAG_STARTTLS_DONE = 128, /* If a plaintext session was converted to TLS. */ 49 50 JFLAG_SASL_FB = 0x10000, /* Trying Facebook authentication. */ 49 51 } jabber_flags_t; 50 52 … … 92 94 char *username; /* USERNAME@server */ 93 95 char *server; /* username@SERVER -=> server/domain, not hostname */ 96 97 char *oauth2_access_token; 94 98 95 99 /* After changing one of these two (or the priority setting), call … … 232 236 #define XMLNS_IBB "http://jabber.org/protocol/ibb" /* XEP-0047 */ 233 237 238 /* jabber.c */ 239 void jabber_connect( struct im_connection *ic ); 240 234 241 /* iq.c */ 235 242 xt_status jabber_pkt_iq( struct xt_node *node, gpointer data ); … … 316 323 xt_status sasl_pkt_result( struct xt_node *node, gpointer data ); 317 324 gboolean sasl_supported( struct im_connection *ic ); 325 void sasl_oauth2_init( struct im_connection *ic ); 326 int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg ); 327 int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token ); 318 328 319 329 /* conference.c */ -
protocols/jabber/sasl.c
r17f057d r6e9ae72 26 26 #include "jabber.h" 27 27 #include "base64.h" 28 #include "oauth2.h" 29 #include "oauth.h" 28 30 29 31 xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data ) … … 33 35 struct xt_node *c, *reply; 34 36 char *s; 35 int sup_plain = 0, sup_digest = 0 ;37 int sup_plain = 0, sup_digest = 0, sup_oauth2 = 0, sup_fb = 0; 36 38 37 39 if( !sasl_supported( ic ) ) … … 59 61 if( c->text && g_strcasecmp( c->text, "DIGEST-MD5" ) == 0 ) 60 62 sup_digest = 1; 63 if( c->text && g_strcasecmp( c->text, "X-OAUTH2" ) == 0 ) 64 sup_oauth2 = 1; 65 if( c->text && g_strcasecmp( c->text, "X-FACEBOOK-PLATFORM" ) == 0 ) 66 sup_fb = 1; 61 67 62 68 c = c->next; … … 73 79 xt_add_attr( reply, "xmlns", XMLNS_SASL ); 74 80 75 if( sup_digest ) 81 if( set_getbool( &ic->acc->set, "oauth" ) ) 82 { 83 int len; 84 85 if( !sup_oauth2 ) 86 { 87 imcb_error( ic, "OAuth requested, but not supported by server" ); 88 imc_logout( ic, FALSE ); 89 xt_free_node( reply ); 90 return XT_ABORT; 91 } 92 93 /* X-OAUTH2 is, not *the* standard OAuth2 SASL/XMPP implementation. 94 It's currently used by GTalk and vaguely documented on 95 http://code.google.com/apis/cloudprint/docs/rawxmpp.html . */ 96 xt_add_attr( reply, "mechanism", "X-OAUTH2" ); 97 98 len = strlen( jd->username ) + strlen( jd->oauth2_access_token ) + 2; 99 s = g_malloc( len + 1 ); 100 s[0] = 0; 101 strcpy( s + 1, jd->username ); 102 strcpy( s + 2 + strlen( jd->username ), jd->oauth2_access_token ); 103 reply->text = base64_encode( (unsigned char *)s, len ); 104 reply->text_len = strlen( reply->text ); 105 g_free( s ); 106 } 107 else if( sup_fb && strstr( ic->acc->pass, "session_key=" ) ) 108 { 109 xt_add_attr( reply, "mechanism", "X-FACEBOOK-PLATFORM" ); 110 jd->flags |= JFLAG_SASL_FB; 111 } 112 else if( sup_digest ) 76 113 { 77 114 xt_add_attr( reply, "mechanism", "DIGEST-MD5" ); … … 96 133 } 97 134 98 if( !jabber_write_packet( ic, reply ) )135 if( reply && !jabber_write_packet( ic, reply ) ) 99 136 { 100 137 xt_free_node( reply ); … … 197 234 struct im_connection *ic = data; 198 235 struct jabber_data *jd = ic->proto_data; 199 struct xt_node *reply = NULL;236 struct xt_node *reply_pkt = NULL; 200 237 char *nonce = NULL, *realm = NULL, *cnonce = NULL; 201 238 unsigned char cnonce_bin[30]; 202 239 char *digest_uri = NULL; 203 240 char *dec = NULL; 204 char *s = NULL ;241 char *s = NULL, *reply = NULL; 205 242 xt_status ret = XT_ABORT; 206 243 … … 210 247 dec = frombase64( node->text ); 211 248 212 if( !( s = sasl_get_part( dec, "rspauth" ) ) ) 249 if( jd->flags & JFLAG_SASL_FB ) 250 { 251 /* Facebook proprietary authentication. Not as useful as it seemed, but 252 the code's written now, may as well keep it.. 253 254 Mechanism is described on http://developers.facebook.com/docs/chat/ 255 and in their Python module. It's all mostly useless because the tokens 256 expire after 24h. */ 257 GSList *p_in = NULL, *p_out = NULL, *p; 258 md5_state_t md5; 259 char time[33], *token; 260 const char *secret; 261 262 oauth_params_parse( &p_in, dec ); 263 oauth_params_add( &p_out, "nonce", oauth_params_get( &p_in, "nonce" ) ); 264 oauth_params_add( &p_out, "method", oauth_params_get( &p_in, "method" ) ); 265 oauth_params_free( &p_in ); 266 267 token = g_strdup( ic->acc->pass ); 268 oauth_params_parse( &p_in, token ); 269 g_free( token ); 270 oauth_params_add( &p_out, "session_key", oauth_params_get( &p_in, "session_key" ) ); 271 272 g_snprintf( time, sizeof( time ), "%lld", (long long) ( gettime() * 1000 ) ); 273 oauth_params_add( &p_out, "call_id", time ); 274 oauth_params_add( &p_out, "api_key", oauth2_service_facebook.consumer_key ); 275 oauth_params_add( &p_out, "v", "1.0" ); 276 oauth_params_add( &p_out, "format", "XML" ); 277 278 md5_init( &md5 ); 279 for( p = p_out; p; p = p->next ) 280 md5_append( &md5, p->data, strlen( p->data ) ); 281 282 secret = oauth_params_get( &p_in, "secret" ); 283 if( secret ) 284 md5_append( &md5, (unsigned char*) secret, strlen( secret ) ); 285 md5_finish_ascii( &md5, time ); 286 oauth_params_add( &p_out, "sig", time ); 287 288 reply = oauth_params_string( p_out ); 289 oauth_params_free( &p_out ); 290 oauth_params_free( &p_in ); 291 } 292 else if( !( s = sasl_get_part( dec, "rspauth" ) ) ) 213 293 { 214 294 /* See RFC 2831 for for information. */ … … 271 351 272 352 /* Now build the SASL response string: */ 273 g_free( dec ); 274 dec = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\"," 275 "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s", 276 jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" ); 277 s = tobase64( dec ); 353 reply = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\"," 354 "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s", 355 jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" ); 278 356 } 279 357 else 280 358 { 281 359 /* We found rspauth, but don't really care... */ 282 g_free( s ); 283 s = NULL; 284 } 285 286 reply = xt_new_node( "response", s, NULL ); 287 xt_add_attr( reply, "xmlns", XMLNS_SASL ); 288 289 if( !jabber_write_packet( ic, reply ) ) 360 } 361 362 s = reply ? tobase64( reply ) : NULL; 363 reply_pkt = xt_new_node( "response", s, NULL ); 364 xt_add_attr( reply_pkt, "xmlns", XMLNS_SASL ); 365 366 if( !jabber_write_packet( ic, reply_pkt ) ) 290 367 goto silent_error; 291 368 … … 301 378 g_free( cnonce ); 302 379 g_free( nonce ); 380 g_free( reply ); 303 381 g_free( realm ); 304 382 g_free( dec ); 305 383 g_free( s ); 306 xt_free_node( reply );384 xt_free_node( reply_pkt ); 307 385 308 386 return ret; … … 347 425 return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0; 348 426 } 427 428 void sasl_oauth2_init( struct im_connection *ic ) 429 { 430 char *msg, *url; 431 432 imcb_log( ic, "Starting OAuth authentication" ); 433 434 /* Temporary contact, just used to receive the OAuth response. */ 435 imcb_add_buddy( ic, "jabber_oauth", NULL ); 436 url = oauth2_url( &oauth2_service_google, 437 "https://www.googleapis.com/auth/googletalk" ); 438 msg = g_strdup_printf( "Open this URL in your browser to authenticate: %s", url ); 439 imcb_buddy_msg( ic, "jabber_oauth", msg, 0, 0 ); 440 imcb_buddy_msg( ic, "jabber_oauth", "Respond to this message with the returned " 441 "authorization token.", 0, 0 ); 442 443 g_free( msg ); 444 g_free( url ); 445 } 446 447 static gboolean sasl_oauth2_remove_contact( gpointer data, gint fd, b_input_condition cond ) 448 { 449 struct im_connection *ic = data; 450 if( g_slist_find( jabber_connections, ic ) ) 451 imcb_remove_buddy( ic, "jabber_oauth", NULL ); 452 return FALSE; 453 } 454 455 static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token ); 456 457 int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg ) 458 { 459 char *code; 460 int ret; 461 462 imcb_log( ic, "Requesting OAuth access token" ); 463 464 /* Don't do it here because the caller may get confused if the contact 465 we're currently sending a message to is deleted. */ 466 b_timeout_add( 1, sasl_oauth2_remove_contact, ic ); 467 468 code = g_strdup( msg ); 469 g_strstrip( code ); 470 ret = oauth2_access_token( &oauth2_service_google, OAUTH2_AUTH_CODE, 471 code, sasl_oauth2_got_token, ic ); 472 473 g_free( code ); 474 return ret; 475 } 476 477 int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token ) 478 { 479 return oauth2_access_token( &oauth2_service_google, OAUTH2_AUTH_REFRESH, 480 refresh_token, sasl_oauth2_got_token, ic ); 481 } 482 483 static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token ) 484 { 485 struct im_connection *ic = data; 486 struct jabber_data *jd; 487 488 if( g_slist_find( jabber_connections, ic ) == NULL ) 489 return; 490 491 jd = ic->proto_data; 492 493 if( access_token == NULL ) 494 { 495 imcb_error( ic, "OAuth failure (missing access token)" ); 496 imc_logout( ic, TRUE ); 497 return; 498 } 499 if( refresh_token != NULL ) 500 { 501 g_free( ic->acc->pass ); 502 ic->acc->pass = g_strdup_printf( "refresh_token=%s", refresh_token ); 503 } 504 505 g_free( jd->oauth2_access_token ); 506 jd->oauth2_access_token = g_strdup( access_token ); 507 508 jabber_connect( ic ); 509 }
Note: See TracChangeset
for help on using the changeset viewer.