Changes in / [17f057d:6e9ae72]


Ignore:
Files:
2 added
10 edited

Legend:

Unmodified
Added
Removed
  • doc/user-guide/commands.xml

    r17f057d r6e9ae72  
    11261126                <description>
    11271127                        <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 return 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.
     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.
    11371137                        </para>
    11381138                </description>
  • irc_im.c

    r17f057d r6e9ae72  
    442442{
    443443        irc_user_t *iu = data;
    444         char *msg = g_string_free( iu->pastebuf, FALSE );
     444        char *msg;
    445445        GSList *l;
     446       
     447        msg = g_string_free( iu->pastebuf, FALSE );
     448        iu->pastebuf = NULL;
     449        iu->pastebuf_timer = 0;
    446450       
    447451        for( l = irc_plugins; l; l = l->next )
     
    470474       
    471475        g_free( msg );
    472         iu->pastebuf = NULL;
    473         iu->pastebuf_timer = 0;
    474476       
    475477        return FALSE;
  • lib/Makefile

    r17f057d r6e9ae72  
    1313
    1414# [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.o
     15objects = 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
    1616
    1717LFLAGS += -r
  • lib/md5.c

    r17f057d r6e9ae72  
    2424#include <sys/types.h>
    2525#include <string.h>             /* for memcpy() */
     26#include <stdio.h>
    2627#include "md5.h"
    2728
     
    160161        memcpy(digest, ctx->buf, 16);
    161162        memset(ctx, 0, sizeof(ctx));    /* In case it's sensitive */
     163}
     164
     165void 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]);
    162173}
    163174
  • lib/md5.h

    r17f057d r6e9ae72  
    4343G_MODULE_EXPORT void md5_append(struct MD5Context *context, const md5_byte_t *buf, unsigned int len);
    4444G_MODULE_EXPORT void md5_finish(struct MD5Context *context, md5_byte_t digest[16]);
     45G_MODULE_EXPORT void md5_finish_ascii(struct MD5Context *context, char *ascii);
    4546
    4647#endif
  • lib/oauth.c

    r17f057d r6e9ae72  
    122122        char *item;
    123123       
     124        if( !key || !value )
     125                return;
     126       
    124127        item = g_strdup_printf( "%s=%s", key, value );
    125128        *params = g_slist_insert_sorted( *params, item, (GCompareFunc) strcmp );
     
    165168}
    166169
    167 static void oauth_params_parse( GSList **params, char *in )
     170void oauth_params_parse( GSList **params, char *in )
    168171{
    169172        char *amp, *eq, *s;
  • lib/oauth.h

    r17f057d r6e9ae72  
    9292
    9393/* For reading misc. data. */
     94void oauth_params_add( GSList **params, const char *key, const char *value );
     95void oauth_params_parse( GSList **params, char *in );
     96void oauth_params_free( GSList **params );
     97char *oauth_params_string( GSList *params );
    9498const char *oauth_params_get( GSList **params, const char *key );
  • protocols/jabber/jabber.c

    r17f057d r6e9ae72  
    6060        s = set_add( &acc->set, "activity_timeout", "600", set_eval_int, acc );
    6161       
     62        s = set_add( &acc->set, "oauth", "false", set_eval_bool, acc );
     63
    6264        g_snprintf( str, sizeof( str ), "%d", jabber_port_list[0] );
    6365        s = set_add( &acc->set, "port", str, set_eval_int, acc );
     
    99101        struct im_connection *ic = imcb_new( acc );
    100102        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;
    104104       
    105105        /* For now this is needed in the _connected() handlers if using
     
    138138        }
    139139       
    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 first
    151                            part of the server string) acc->server gets
    152                            flushed. We don't want to have to do this another
    153                            time. :-) */
    154                         *s = 0;
    155                         s ++;
    156                        
    157                         /* Only set this if the user didn't specify a custom
    158                            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 an
    167                            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 or
    172                            the end of the string, save the port number
    173                            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        
    195140        jd->node_cache = g_hash_table_new_full( g_str_hash, g_str_equal, NULL, jabber_cache_entry_free );
    196141        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. */
     164void 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;
    197171       
    198172        /* Figure out the hostname to connect to. */
     
    319293        xt_free( jd->xt );
    320294       
     295        g_free( jd->oauth2_access_token );
    321296        g_free( jd->away_message );
    322297        g_free( jd->username );
     
    336311        if( g_strcasecmp( who, JABBER_XMLCONSOLE_HANDLE ) == 0 )
    337312                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        }
    338327       
    339328        if( ( s = strchr( who, '=' ) ) && jabber_chat_by_jid( ic, s + 1 ) )
  • protocols/jabber/jabber.h

    r17f057d r6e9ae72  
    4747        JFLAG_XMLCONSOLE = 64,          /* If the user added an xmlconsole buddy. */
    4848        JFLAG_STARTTLS_DONE = 128,      /* If a plaintext session was converted to TLS. */
     49       
     50        JFLAG_SASL_FB = 0x10000,        /* Trying Facebook authentication. */
    4951} jabber_flags_t;
    5052
     
    9294        char *username;         /* USERNAME@server */
    9395        char *server;           /* username@SERVER -=> server/domain, not hostname */
     96       
     97        char *oauth2_access_token;
    9498       
    9599        /* After changing one of these two (or the priority setting), call
     
    232236#define XMLNS_IBB          "http://jabber.org/protocol/ibb"                      /* XEP-0047 */
    233237
     238/* jabber.c */
     239void jabber_connect( struct im_connection *ic );
     240
    234241/* iq.c */
    235242xt_status jabber_pkt_iq( struct xt_node *node, gpointer data );
     
    316323xt_status sasl_pkt_result( struct xt_node *node, gpointer data );
    317324gboolean sasl_supported( struct im_connection *ic );
     325void sasl_oauth2_init( struct im_connection *ic );
     326int sasl_oauth2_get_refresh_token( struct im_connection *ic, const char *msg );
     327int sasl_oauth2_refresh( struct im_connection *ic, const char *refresh_token );
    318328
    319329/* conference.c */
  • protocols/jabber/sasl.c

    r17f057d r6e9ae72  
    2626#include "jabber.h"
    2727#include "base64.h"
     28#include "oauth2.h"
     29#include "oauth.h"
    2830
    2931xt_status sasl_pkt_mechanisms( struct xt_node *node, gpointer data )
     
    3335        struct xt_node *c, *reply;
    3436        char *s;
    35         int sup_plain = 0, sup_digest = 0;
     37        int sup_plain = 0, sup_digest = 0, sup_oauth2 = 0, sup_fb = 0;
    3638       
    3739        if( !sasl_supported( ic ) )
     
    5961                if( c->text && g_strcasecmp( c->text, "DIGEST-MD5" ) == 0 )
    6062                        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;
    6167               
    6268                c = c->next;
     
    7379        xt_add_attr( reply, "xmlns", XMLNS_SASL );
    7480       
    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 )
    76113        {
    77114                xt_add_attr( reply, "mechanism", "DIGEST-MD5" );
     
    96133        }
    97134       
    98         if( !jabber_write_packet( ic, reply ) )
     135        if( reply && !jabber_write_packet( ic, reply ) )
    99136        {
    100137                xt_free_node( reply );
     
    197234        struct im_connection *ic = data;
    198235        struct jabber_data *jd = ic->proto_data;
    199         struct xt_node *reply = NULL;
     236        struct xt_node *reply_pkt = NULL;
    200237        char *nonce = NULL, *realm = NULL, *cnonce = NULL;
    201238        unsigned char cnonce_bin[30];
    202239        char *digest_uri = NULL;
    203240        char *dec = NULL;
    204         char *s = NULL;
     241        char *s = NULL, *reply = NULL;
    205242        xt_status ret = XT_ABORT;
    206243       
     
    210247        dec = frombase64( node->text );
    211248       
    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" ) ) )
    213293        {
    214294                /* See RFC 2831 for for information. */
     
    271351               
    272352                /* 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" );
    278356        }
    279357        else
    280358        {
    281359                /* 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 ) )
    290367                goto silent_error;
    291368       
     
    301378        g_free( cnonce );
    302379        g_free( nonce );
     380        g_free( reply );
    303381        g_free( realm );
    304382        g_free( dec );
    305383        g_free( s );
    306         xt_free_node( reply );
     384        xt_free_node( reply_pkt );
    307385       
    308386        return ret;
     
    347425        return ( jd->xt && jd->xt->root && xt_find_attr( jd->xt->root, "version" ) ) != 0;
    348426}
     427
     428void 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
     447static 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
     455static void sasl_oauth2_got_token( gpointer data, const char *access_token, const char *refresh_token );
     456
     457int 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
     477int 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
     483static 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.