Changeset 8358


Ignore:
Timestamp:
May 8, 2009, 2:56:11 PM (12 years ago)
Author:
charles
Message:

(trunk) support an X-Transmission-Session-Id header in the RPC server. Yesterday's approach of including the session_id in posted forms -- which is a typical approach -- isn't sufficient for Transmission, since it also allows remote access via JSON/RPC. (part 1 of 2. part 2 is kjg's web ui patch)

Location:
trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/daemon/remote.c

    r8356 r8358  
    1111 */
    1212
     13#include <ctype.h> /* isspace */
    1314#include <errno.h>
    1415#include <math.h>
     
    133134static char * auth = NULL;
    134135static char * netrc = NULL;
     136static char * sessionId = NULL;
    135137
    136138static char*
     
    12741276}
    12751277
     1278/* look for a session id in the header in case the server gives back a 409 */
     1279static size_t
     1280parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
     1281{
     1282    const char * line = ptr;
     1283    const size_t line_len = size * nmemb;
     1284    const char * key = TR_RPC_SESSION_ID_HEADER ": ";
     1285    const size_t key_len = strlen( key );
     1286
     1287    if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) )
     1288    {
     1289        const char * begin = line + key_len;
     1290        const char * end = begin;
     1291        while( !isspace( *end ) )
     1292            ++end;
     1293        tr_free( sessionId );
     1294        sessionId = tr_strndup( begin, end-begin );
     1295    }
     1296
     1297    return line_len;
     1298}
     1299
     1300static CURL*
     1301tr_curl_easy_init( struct evbuffer * writebuf )
     1302{
     1303    CURL * curl = curl_easy_init( );
     1304    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
     1305    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
     1306    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
     1307    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
     1308    curl_easy_setopt( curl, CURLOPT_POST, 1 );
     1309    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
     1310    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
     1311    curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
     1312    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
     1313#ifdef HAVE_ZLIB
     1314    curl_easy_setopt( curl, CURLOPT_ENCODING, "deflate" );
     1315#endif
     1316    if( netrc )
     1317        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
     1318    if( auth )
     1319        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
     1320    if( sessionId ) {
     1321        char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
     1322        struct curl_slist * custom_headers = curl_slist_append( NULL, h );
     1323        curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
     1324        /* fixme: leaks */
     1325    }
     1326    return curl;
     1327}
     1328   
     1329
    12761330static void
    12771331processRequests( const char *  host,
     
    12801334                 int           reqCount )
    12811335{
    1282     int               i;
    1283     CURL *            curl;
     1336    int i;
     1337    CURL * curl = NULL;
    12841338    struct evbuffer * buf = evbuffer_new( );
    1285     char *            url = tr_strdup_printf(
    1286         "http://%s:%d/transmission/rpc", host, port );
    1287 
    1288     curl = curl_easy_init( );
    1289     curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
    1290 #ifdef HAVE_ZLIB
    1291     curl_easy_setopt( curl, CURLOPT_ENCODING, "deflate" );
    1292 #endif
    1293     curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
    1294     curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
    1295     curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
    1296     curl_easy_setopt( curl, CURLOPT_POST, 1 );
    1297     curl_easy_setopt( curl, CURLOPT_URL, url );
    1298     curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
    1299     curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
    1300     curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
    1301     if( netrc )
    1302         curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
    1303     if( auth )
    1304         curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
    1305 
    1306     for( i = 0; i < reqCount; ++i )
     1339    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
     1340
     1341    for( i=0; i<reqCount; ++i )
    13071342    {
    13081343        CURLcode res;
     1344        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
     1345
     1346        if( curl == NULL )
     1347        {
     1348            curl = tr_curl_easy_init( buf );
     1349            curl_easy_setopt( curl, CURLOPT_URL, url );
     1350        }
     1351
    13091352        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
     1353
    13101354        if( debug )
    13111355            fprintf( stderr, "posting:\n--------\n%s\n--------\n", reqs[i] );
    13121356        if( ( res = curl_easy_perform( curl ) ) )
    1313             tr_nerr( MY_NAME, "(%s:%d) %s", host, port,
    1314                     curl_easy_strerror( res ) );
    1315         else
    1316             processResponse( host, port, EVBUFFER_DATA(
    1317                                 buf ), EVBUFFER_LENGTH( buf ) );
    1318 
    1319         evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
     1357            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
     1358        else {
     1359            long response;
     1360            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
     1361            switch( response ) {
     1362                case 200:
     1363                    processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
     1364                    break;
     1365                case 409:
     1366                    /* session id failed.  our curl header func has already
     1367                     * pulled the new session id from this response's headers,
     1368                     * build a new CURL* and try again */
     1369                    curl_easy_cleanup( curl );
     1370                    curl = NULL;
     1371                    --i;
     1372                    break;
     1373                default:
     1374                    fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
     1375                    break;
     1376            }
     1377        }
    13201378    }
    13211379
     
    13231381    tr_free( url );
    13241382    evbuffer_free( buf );
    1325     curl_easy_cleanup( curl );
     1383    if( curl != NULL )
     1384        curl_easy_cleanup( curl );
    13261385}
    13271386
  • trunk/libtransmission/peer-mgr.c

    r8212 r8358  
    12271227getPeerCount( const Torrent * t )
    12281228{
    1229     return tr_ptrArraySize( &t->peers );// + tr_ptrArraySize( &t->outgoingHandshakes );
     1229    return tr_ptrArraySize( &t->peers );/* + tr_ptrArraySize( &t->outgoingHandshakes ); */
    12301230}
    12311231
  • trunk/libtransmission/rpc-server.c

    r8356 r8358  
    4040#include "net.h"
    4141
     42/* session-id is used to make cross-site request forgery attacks difficult.
     43 * Don't disable this feature unless you really know what you're doing!
     44 * http://en.wikipedia.org/wiki/Cross-site_request_forgery
     45 * http://shiflett.org/articles/cross-site-request-forgeries
     46 * http://www.webappsec.org/lists/websecurity/archive/2008-04/msg00037.html */
     47#define REQUIRE_SESSION_ID
     48
    4249#define MY_NAME "RPC Server"
    4350#define MY_REALM "Transmission"
     
    6168    char *             whitelistStr;
    6269    tr_list *          whitelist;
     70
     71    char *             sessionId;
     72    time_t             sessionIdExpiresAt;
    6373
    6474#ifdef HAVE_ZLIB
     
    451461}
    452462
    453 static void
    454 handle_request( struct evhttp_request * req,
    455                 void *                  arg )
     463static char*
     464get_current_session_id( struct tr_rpc_server * server )
     465{
     466    const time_t now = time( NULL );
     467
     468    if( !server->sessionId || ( now >= server->sessionIdExpiresAt ) )
     469    {
     470        int i;
     471        const int n = 48;
     472        const char * pool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
     473        const size_t pool_size = strlen( pool );
     474        char * buf = tr_new( char, n+1 );
     475
     476        for( i=0; i<n; ++i )
     477            buf[i] = pool[ tr_cryptoRandInt( pool_size ) ];
     478        buf[n] = '\0';
     479
     480        tr_free( server->sessionId );
     481        server->sessionId = buf;
     482        server->sessionIdExpiresAt = now + (60*60); /* expire in an hour */
     483    }
     484
     485    return server->sessionId;
     486}
     487
     488
     489static tr_bool
     490test_session_id( struct tr_rpc_server * server, struct evhttp_request * req )
     491{
     492    const char * ours = get_current_session_id( server );
     493    const char * theirs = evhttp_find_header( req->input_headers, TR_RPC_SESSION_ID_HEADER );
     494    const tr_bool success =  theirs && !strcmp( theirs, ours );
     495    return success;
     496}
     497
     498static void
     499handle_request( struct evhttp_request * req, void * arg )
    456500{
    457501    struct tr_rpc_server * server = arg;
     
    460504    {
    461505        const char * auth;
    462         char *       user = NULL;
    463         char *       pass = NULL;
     506        char * user = NULL;
     507        char * pass = NULL;
    464508
    465509        evhttp_add_header( req->output_headers, "Server", MY_REALM );
    466510
    467511        auth = evhttp_find_header( req->input_headers, "Authorization" );
    468 
    469512        if( auth && !strncasecmp( auth, "basic ", 6 ) )
    470513        {
     
    480523        if( !isAddressAllowed( server, req->remote_host ) )
    481524        {
    482             send_simple_response( req, 401,
     525            send_simple_response( req, 403,
    483526                "<p>Unauthorized IP Address.</p>"
    484527                "<p>Either disable the IP address whitelist or add your address to it.</p>"
     
    512555            handle_clutch( req, server );
    513556        }
     557#ifdef REQUIRE_SESSION_ID
     558        else if( !test_session_id( server, req ) )
     559        {
     560            const char * sessionId = get_current_session_id( server );
     561            char * tmp = tr_strdup_printf(
     562                "<p>Please add this header to your requests:</p>"
     563                "<p><code>%s: %s</code></p>"
     564                "<p>This requirement is to make "
     565                "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a>"
     566                " attacks more difficult.</p>",
     567                TR_RPC_SESSION_ID_HEADER, sessionId );
     568            evhttp_add_header( req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId );
     569            send_simple_response( req, 409, tmp );
     570            tr_free( tmp );
     571        }
     572#endif
    514573        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
    515574        {
  • trunk/libtransmission/transmission.h

    r8279 r8358  
    5757#define SHA_DIGEST_LENGTH 20
    5858#define TR_INET6_ADDRSTRLEN 46
     59
     60#define TR_RPC_SESSION_ID_HEADER "X-Transmission-Session-Id"
    5961
    6062typedef uint32_t tr_file_index_t;
  • trunk/qt/session.cc

    r8356 r8358  
    542542        header.setValue( "User-Agent", QCoreApplication::instance()->applicationName() + "/" + LONG_VERSION_STRING );
    543543        header.setValue( "Content-Type", "application/json; charset=UTF-8" );
    544         myHttp.request( header, data, &myBuffer );
     544        if( !mySessionId.isEmpty( ) )
     545            header.setValue( TR_RPC_SESSION_ID_HEADER, mySessionId );
     546        QBuffer * buf = new QBuffer;
     547        buf->setData( data );
     548        myHttp.request( header, buf, &myBuffer );
    545549#ifdef DEBUG_HTTP
    546550        std::cerr << "sending " << qPrintable(header.toString()) << "\nBody:\n" << request << std::endl;
     
    561565{
    562566    Q_UNUSED( id );
     567
     568    QHttpResponseHeader response = myHttp.lastResponse();
     569    QIODevice * sourceDevice = myHttp.currentSourceDevice( );
    563570
    564571#ifdef DEBUG_HTTP
     
    570577#endif
    571578
    572     if( error )
     579    if( ( response.statusCode() == 409 ) && response.hasKey( TR_RPC_SESSION_ID_HEADER ) )
     580    {
     581        // we got a 409 telling us our session id has expired.
     582        // update it and resubmit the request.
     583        mySessionId = response.value( TR_RPC_SESSION_ID_HEADER );
     584        exec( qobject_cast<QBuffer*>(sourceDevice)->buffer().constData( ) );
     585    }
     586    else if( error )
     587    {
    573588        std::cerr << "http error: " << qPrintable(myHttp.errorString()) << std::endl;
    574     else {
     589    }
     590    else
     591    {
    575592        const QByteArray& response( myBuffer.buffer( ) );
    576593        const char * json( response.constData( ) );
    577594        int jsonLength( response.size( ) );
    578595        if( jsonLength>0 && json[jsonLength-1] == '\n' ) --jsonLength;
    579 
    580596        parseResponse( json, jsonLength );
    581597    }
    582598
     599    delete sourceDevice;
    583600    myBuffer.buffer( ).clear( );
    584601    myBuffer.reset( );
  • trunk/qt/session.h

    r8356 r8358  
    133133        tr_session * mySession;
    134134        QString myConfigDir;
     135        QString mySessionId;
    135136        QUrl myUrl;
    136137        QBuffer myBuffer;
Note: See TracChangeset for help on using the changeset viewer.