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, *msg; if (info->request_token == NULL) { imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http)); imc_logout(ic, TRUE); return FALSE; } name = g_strdup_printf("%s_%s", td->prefix, 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(name); g_free(msg); } else if (info->stage == OAUTH_ACCESS_TOKEN) { const char *sn; if (info->token == NULL || info->token_secret == NULL) { imcb_error(ic, "OAuth error: %s", twitter_parse_error(info->http)); imc_logout(ic, TRUE); return FALSE; } if ((sn = oauth_params_get(&info->params, "screen_name"))) { if (ic->acc->prpl->handle_cmp(sn, ic->acc->user) != 0) imcb_log(ic, "Warning: You logged in via OAuth as %s " "instead of %s.", sn, ic->acc->user); g_free(td->user); td->user = g_strdup(sn); } /* 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_login_finish(ic); } return TRUE; }
xt_status sasl_pkt_challenge( struct xt_node *node, gpointer data ) { struct im_connection *ic = data; struct jabber_data *jd = ic->proto_data; struct xt_node *reply_pkt = NULL; char *nonce = NULL, *realm = NULL, *cnonce = NULL; unsigned char cnonce_bin[30]; char *digest_uri = NULL; char *dec = NULL; char *s = NULL, *reply = NULL; xt_status ret = XT_ABORT; if( node->text_len == 0 ) goto error; dec = frombase64( node->text ); if( jd->flags & JFLAG_SASL_FB ) { /* New-style Facebook OAauth2 support. Instead of sending a refresh token, they just send an access token that should never expire. */ GSList *p_in = NULL, *p_out = NULL; char time[33]; oauth_params_parse( &p_in, dec ); oauth_params_add( &p_out, "nonce", oauth_params_get( &p_in, "nonce" ) ); oauth_params_add( &p_out, "method", oauth_params_get( &p_in, "method" ) ); oauth_params_free( &p_in ); g_snprintf( time, sizeof( time ), "%lld", (long long) ( gettime() * 1000 ) ); oauth_params_add( &p_out, "call_id", time ); oauth_params_add( &p_out, "api_key", oauth2_service_facebook.consumer_key ); oauth_params_add( &p_out, "v", "1.0" ); oauth_params_add( &p_out, "format", "XML" ); oauth_params_add( &p_out, "access_token", jd->oauth2_access_token ); reply = oauth_params_string( p_out ); oauth_params_free( &p_out ); } else if( !( s = sasl_get_part( dec, "rspauth" ) ) ) { /* See RFC 2831 for for information. */ md5_state_t A1, A2, H; md5_byte_t A1r[16], A2r[16], Hr[16]; char A1h[33], A2h[33], Hh[33]; int i; nonce = sasl_get_part( dec, "nonce" ); realm = sasl_get_part( dec, "realm" ); if( !nonce ) goto error; /* Jabber.Org considers the realm part optional and doesn't specify one. Oh well, actually they're right, but still, don't know if this is right... */ if( !realm ) realm = g_strdup( jd->server ); random_bytes( cnonce_bin, sizeof( cnonce_bin ) ); cnonce = base64_encode( cnonce_bin, sizeof( cnonce_bin ) ); digest_uri = g_strdup_printf( "%s/%s", "xmpp", jd->server ); /* Generate the MD5 hash of username:realm:password, I decided to call it H. */ md5_init( &H ); s = g_strdup_printf( "%s:%s:%s", jd->username, realm, ic->acc->pass ); md5_append( &H, (unsigned char *) s, strlen( s ) ); g_free( s ); md5_finish( &H, Hr ); /* Now generate the hex. MD5 hash of H:nonce:cnonce, called A1. */ md5_init( &A1 ); s = g_strdup_printf( ":%s:%s", nonce, cnonce ); md5_append( &A1, Hr, 16 ); md5_append( &A1, (unsigned char *) s, strlen( s ) ); g_free( s ); md5_finish( &A1, A1r ); for( i = 0; i < 16; i ++ ) sprintf( A1h + i * 2, "%02x", A1r[i] ); /* A2... */ md5_init( &A2 ); s = g_strdup_printf( "%s:%s", "AUTHENTICATE", digest_uri ); md5_append( &A2, (unsigned char *) s, strlen( s ) ); g_free( s ); md5_finish( &A2, A2r ); for( i = 0; i < 16; i ++ ) sprintf( A2h + i * 2, "%02x", A2r[i] ); /* Final result: A1:nonce:00000001:cnonce:auth:A2. Let's reuse H for it. */ md5_init( &H ); s = g_strdup_printf( "%s:%s:%s:%s:%s:%s", A1h, nonce, "00000001", cnonce, "auth", A2h ); md5_append( &H, (unsigned char *) s, strlen( s ) ); g_free( s ); md5_finish( &H, Hr ); for( i = 0; i < 16; i ++ ) sprintf( Hh + i * 2, "%02x", Hr[i] ); /* Now build the SASL response string: */ reply = g_strdup_printf( "username=\"%s\",realm=\"%s\",nonce=\"%s\",cnonce=\"%s\"," "nc=%08x,qop=auth,digest-uri=\"%s\",response=%s,charset=%s", jd->username, realm, nonce, cnonce, 1, digest_uri, Hh, "utf-8" ); } else { /* We found rspauth, but don't really care... */ g_free( s ); } s = reply ? tobase64( reply ) : NULL; reply_pkt = xt_new_node( "response", s, NULL ); xt_add_attr( reply_pkt, "xmlns", XMLNS_SASL ); if( !jabber_write_packet( ic, reply_pkt ) ) goto silent_error; ret = XT_HANDLED; goto silent_error; error: imcb_error( ic, "Incorrect SASL challenge received" ); imc_logout( ic, FALSE ); silent_error: g_free( digest_uri ); g_free( cnonce ); g_free( nonce ); g_free( reply ); g_free( realm ); g_free( dec ); g_free( s ); xt_free_node( reply_pkt ); return ret; }
static void jabber_login( account_t *acc ) { struct im_connection *ic = imcb_new( acc ); struct jabber_data *jd = g_new0( struct jabber_data, 1 ); char *s; /* For now this is needed in the _connected() handlers if using GLib event handling, to make sure we're not handling events on dead connections. */ jabber_connections = g_slist_prepend( jabber_connections, ic ); jd->ic = ic; ic->proto_data = jd; jabber_set_me( ic, acc->user ); jd->fd = jd->r_inpa = jd->w_inpa = -1; if( jd->server == NULL ) { imcb_error( ic, "Incomplete account name (format it like <*****@*****.**>)" ); imc_logout( ic, FALSE ); return; } if( ( s = strchr( jd->server, '/' ) ) ) { *s = 0; set_setstr( &acc->set, "resource", s + 1 ); /* Also remove the /resource from the original variable so we won't have to do this again every time. */ s = strchr( acc->user, '/' ); *s = 0; } jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free ); jd->buddies = g_hash_table_new( g_str_hash, g_str_equal ); if( set_getbool( &acc->set, "oauth" ) ) { GSList *p_in = NULL; const char *tok; jd->fd = jd->r_inpa = jd->w_inpa = -1; if( strstr( jd->server, ".live.com" ) ) jd->oauth2_service = &oauth2_service_mslive; else if( strstr( jd->server, ".facebook.com" ) ) jd->oauth2_service = &oauth2_service_facebook; else jd->oauth2_service = &oauth2_service_google; oauth_params_parse( &p_in, ic->acc->pass ); /* First see if we have a refresh token, in which case any access token we *might* have has probably expired already anyway. */ if( ( tok = oauth_params_get( &p_in, "refresh_token" ) ) ) { sasl_oauth2_refresh( ic, tok ); } /* If we don't have a refresh token, let's hope the access token is still usable. */ else if( ( tok = oauth_params_get( &p_in, "access_token" ) ) ) { jd->oauth2_access_token = g_strdup( tok ); jabber_connect( ic ); } /* If we don't have any, start the OAuth process now. Don't even open an XMPP connection yet. */ else { sasl_oauth2_init( ic ); ic->flags |= OPT_SLOW_LOGIN; } oauth_params_free( &p_in ); } else jabber_connect( ic ); }