Changeset 8378


Ignore:
Timestamp:
May 11, 2009, 5:45:00 PM (12 years ago)
Author:
charles
Message:

(1.5x) backport r8358, 8359 to 1.5x/ branch for 1.53

Location:
branches/1.5x
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • branches/1.5x/daemon/remote.c

    r8357 r8378  
    1111 */
    1212
     13#include <ctype.h> /* isspace */
    1314#include <errno.h>
    1415#include <math.h>
     
    127128static char * auth = NULL;
    128129static char * netrc = NULL;
     130static char * sessionId = NULL;
    129131
    130132static char*
     
    12231225}
    12241226
     1227/* look for a session id in the header in case the server gives back a 409 */
     1228static size_t
     1229parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
     1230{
     1231    const char * line = ptr;
     1232    const size_t line_len = size * nmemb;
     1233    const char * key = TR_RPC_SESSION_ID_HEADER ": ";
     1234    const size_t key_len = strlen( key );
     1235
     1236    if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) )
     1237    {
     1238        const char * begin = line + key_len;
     1239        const char * end = begin;
     1240        while( !isspace( *end ) )
     1241            ++end;
     1242        tr_free( sessionId );
     1243        sessionId = tr_strndup( begin, end-begin );
     1244    }
     1245
     1246    return line_len;
     1247}
     1248
     1249static CURL*
     1250tr_curl_easy_init( struct evbuffer * writebuf )
     1251{
     1252    CURL * curl = curl_easy_init( );
     1253    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
     1254    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
     1255    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
     1256    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
     1257    curl_easy_setopt( curl, CURLOPT_POST, 1 );
     1258    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
     1259    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
     1260    curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
     1261    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
     1262#ifdef HAVE_ZLIB
     1263    curl_easy_setopt( curl, CURLOPT_ENCODING, "deflate" );
     1264#endif
     1265    if( netrc )
     1266        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
     1267    if( auth )
     1268        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
     1269    if( sessionId ) {
     1270        char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
     1271        struct curl_slist * custom_headers = curl_slist_append( NULL, h );
     1272        curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
     1273        /* fixme: leaks */
     1274    }
     1275    return curl;
     1276}
     1277   
     1278
    12251279static void
    12261280processRequests( const char *  host,
     
    12291283                 int           reqCount )
    12301284{
    1231     int               i;
    1232     CURL *            curl;
     1285    int i;
     1286    CURL * curl = NULL;
    12331287    struct evbuffer * buf = evbuffer_new( );
    1234     char *            url = tr_strdup_printf(
    1235         "http://%s:%d/transmission/rpc", host, port );
    1236 
    1237     curl = curl_easy_init( );
    1238     curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
    1239 #ifdef HAVE_LIBZ
    1240     curl_easy_setopt( curl, CURLOPT_ENCODING, "deflate" );
    1241 #endif
    1242     curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
    1243     curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
    1244     curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
    1245     curl_easy_setopt( curl, CURLOPT_POST, 1 );
    1246     curl_easy_setopt( curl, CURLOPT_URL, url );
    1247     curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
    1248     curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
    1249     curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
    1250     if( netrc )
    1251         curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
    1252     if( auth )
    1253         curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
    1254 
    1255     for( i = 0; i < reqCount; ++i )
     1288    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
     1289
     1290    for( i=0; i<reqCount; ++i )
    12561291    {
    12571292        CURLcode res;
     1293        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
     1294
     1295        if( curl == NULL )
     1296        {
     1297            curl = tr_curl_easy_init( buf );
     1298            curl_easy_setopt( curl, CURLOPT_URL, url );
     1299        }
     1300
    12581301        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
     1302
    12591303        if( debug )
    12601304            fprintf( stderr, "posting:\n--------\n%s\n--------\n", reqs[i] );
    12611305        if( ( res = curl_easy_perform( curl ) ) )
    1262             tr_nerr( MY_NAME, "(%s:%d) %s", host, port,
    1263                     curl_easy_strerror( res ) );
    1264         else
    1265             processResponse( host, port, EVBUFFER_DATA(
    1266                                 buf ), EVBUFFER_LENGTH( buf ) );
    1267 
    1268         evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
     1306            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
     1307        else {
     1308            long response;
     1309            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
     1310            switch( response ) {
     1311                case 200:
     1312                    processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
     1313                    break;
     1314                case 409:
     1315                    /* session id failed.  our curl header func has already
     1316                     * pulled the new session id from this response's headers,
     1317                     * build a new CURL* and try again */
     1318                    curl_easy_cleanup( curl );
     1319                    curl = NULL;
     1320                    --i;
     1321                    break;
     1322                default:
     1323                    fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
     1324                    break;
     1325            }
     1326        }
    12691327    }
    12701328
     
    12721330    tr_free( url );
    12731331    evbuffer_free( buf );
    1274     curl_easy_cleanup( curl );
     1332    if( curl != NULL )
     1333        curl_easy_cleanup( curl );
    12751334}
    12761335
  • branches/1.5x/libtransmission/peer-mgr.c

    r8218 r8378  
    12241224getPeerCount( const Torrent * t )
    12251225{
    1226     return tr_ptrArraySize( &t->peers );// + tr_ptrArraySize( &t->outgoingHandshakes );
     1226    return tr_ptrArraySize( &t->peers );/* + tr_ptrArraySize( &t->outgoingHandshakes ); */
    12271227}
    12281228
  • branches/1.5x/libtransmission/rpc-server.c

    r8357 r8378  
    3030#include "transmission.h"
    3131#include "bencode.h"
     32#include "crypto.h"
    3233#include "list.h"
    3334#include "platform.h"
     
    3738#include "utils.h"
    3839#include "web.h"
     40
     41/* session-id is used to make cross-site request forgery attacks difficult.
     42 * Don't disable this feature unless you really know what you're doing!
     43 * http://en.wikipedia.org/wiki/Cross-site_request_forgery
     44 * http://shiflett.org/articles/cross-site-request-forgeries
     45 * http://www.webappsec.org/lists/websecurity/archive/2008-04/msg00037.html */
     46#define REQUIRE_SESSION_ID
    3947
    4048#define MY_NAME "RPC Server"
     
    5866    char *             whitelistStr;
    5967    tr_list *          whitelist;
     68
     69    char *             sessionId;
     70    time_t             sessionIdExpiresAt;
    6071
    6172#ifdef HAVE_ZLIB
     
    446457}
    447458
    448 static void
    449 handle_request( struct evhttp_request * req,
    450                 void *                  arg )
     459static char*
     460get_current_session_id( struct tr_rpc_server * server )
     461{
     462    const time_t now = time( NULL );
     463
     464    if( !server->sessionId || ( now >= server->sessionIdExpiresAt ) )
     465    {
     466        int i;
     467        const int n = 48;
     468        const char * pool = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
     469        const size_t pool_size = strlen( pool );
     470        char * buf = tr_new( char, n+1 );
     471
     472        for( i=0; i<n; ++i )
     473            buf[i] = pool[ tr_cryptoRandInt( pool_size ) ];
     474        buf[n] = '\0';
     475
     476        tr_free( server->sessionId );
     477        server->sessionId = buf;
     478        server->sessionIdExpiresAt = now + (60*60); /* expire in an hour */
     479    }
     480
     481    return server->sessionId;
     482}
     483
     484
     485static tr_bool
     486test_session_id( struct tr_rpc_server * server, struct evhttp_request * req )
     487{
     488    const char * ours = get_current_session_id( server );
     489    const char * theirs = evhttp_find_header( req->input_headers, TR_RPC_SESSION_ID_HEADER );
     490    const tr_bool success =  theirs && !strcmp( theirs, ours );
     491    return success;
     492}
     493
     494static void
     495handle_request( struct evhttp_request * req, void * arg )
    451496{
    452497    struct tr_rpc_server * server = arg;
     
    455500    {
    456501        const char * auth;
    457         char *       user = NULL;
    458         char *       pass = NULL;
     502        char * user = NULL;
     503        char * pass = NULL;
    459504
    460505        evhttp_add_header( req->output_headers, "Server", MY_REALM );
    461506
    462507        auth = evhttp_find_header( req->input_headers, "Authorization" );
    463 
    464508        if( auth && !strncasecmp( auth, "basic ", 6 ) )
    465509        {
     
    475519        if( !isAddressAllowed( server, req->remote_host ) )
    476520        {
    477             send_simple_response( req, 401,
     521            send_simple_response( req, 403,
    478522                "<p>Unauthorized IP Address.</p>"
    479523                "<p>Either disable the IP address whitelist or add your address to it.</p>"
     
    506550            handle_clutch( req, server );
    507551        }
     552#ifdef REQUIRE_SESSION_ID
     553        else if( !test_session_id( server, req ) )
     554        {
     555            const char * sessionId = get_current_session_id( server );
     556            char * tmp = tr_strdup_printf(
     557                "<p>Please add this header to your requests:</p>"
     558                "<p><code>%s: %s</code></p>"
     559                "<p>This requirement is to make "
     560                "<a href=\"http://en.wikipedia.org/wiki/Cross-site_request_forgery\">CSRF</a>"
     561                " attacks more difficult.</p>",
     562                TR_RPC_SESSION_ID_HEADER, sessionId );
     563            evhttp_add_header( req->output_headers, TR_RPC_SESSION_ID_HEADER, sessionId );
     564            send_simple_response( req, 409, tmp );
     565            tr_free( tmp );
     566        }
     567#endif
    508568        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
    509569        {
  • branches/1.5x/libtransmission/transmission.h

    r8144 r8378  
    5959#define SHA_DIGEST_LENGTH 20
    6060#define TR_INET6_ADDRSTRLEN 46
     61
     62#define TR_RPC_SESSION_ID_HEADER "X-Transmission-Session-Id"
    6163
    6264typedef uint32_t tr_file_index_t;
  • branches/1.5x/web/javascript/transmission.remote.js

    r8240 r8378  
    3636                this._controller = controller;
    3737                this._error = '';
     38                this._token = '';
    3839        },
    3940
    4041        /*
    4142         * Display an error if an ajax request fails, and stop sending requests
     43         * or, on a 409, globally set the X-Transmission-Session-Id and resend
    4244         */
    43         ajaxError: function(request, error_string, exception) {
    44                 this._error = request.responseText
    45                             ? request.responseText.trim().replace(/(<([^>]+)>)/ig,"")
    46                             : "";
    47                 if( !this._error.length )
    48                         this._error = 'Server not responding';
     45        ajaxError: function(request, error_string, exception, ajaxObject) {
     46                remote = this;
     47
     48
     49                // set the Transmission-Session-Id on a 409
     50                if(request.status == 409 && (token = request.getResponseHeader('X-Transmission-Session-Id'))){
     51                        remote._token = token;
     52                        $.ajax(ajaxObject);
     53                        return;
     54                }
     55
     56                remote._error = request.responseText
     57                        ? request.responseText.trim().replace(/(<([^>]+)>)/ig,"")
     58                        : "";
     59                if( !remote._error.length )
     60                        remote._error = 'Server not responding';
    4961               
    5062                dialog.confirm('Connection Failed',
     
    5769        },
    5870
    59         sendRequest: function( url, data, success, contentType )
     71
     72        appendSessionId: function(XHR) {
     73                XHR.setRequestHeader('X-Transmission-Session-Id', this._token);
     74        },
     75
     76        sendRequest: function( data, success )
    6077        {
    61                 var o = { };
    62                 o.cache = false;
    63                 o.contentType = contentType;
    64                 o.data = data;
    65                 o.dataType = 'json';
    66                 o.error = this.ajaxError;
    67                 o.success = success;
    68                 o.type = 'POST';
    69                 o.url = url;
    70                 $.ajax( o );
     78                remote = this;
     79                $.ajax( {
     80                        url: RPC._Root,
     81                        type: 'POST',
     82                        contentType: 'json',
     83                        dataType: 'json',
     84                        cache: false,
     85                        data: $.toJSON(data),
     86                        beforeSend: function(XHR){ remote.appendSessionId(XHR) },
     87                        error: function(request, error_string, exception){ remote.ajaxError(request, error_string, exception, this) },
     88                        success: success
     89                } );
    7190        },
    7291
    7392        loadDaemonPrefs: function() {
    7493                var tr = this._controller;
    75                 var o = { };
    76                 o.method = 'session-get';
    77                 this.sendRequest( RPC._Root, $.toJSON(o), function(data) {
     94                var o = { method: 'session-get' };
     95                this.sendRequest( o, function(data) {
    7896                        var o = data.arguments;
    7997                        Prefs.getClutchPrefs( o );
    8098                        tr.updatePrefs( o );
    81                 }, "json" );
     99                } );
    82100        },
    83101
    84102        loadTorrents: function() {
    85103                var tr = this._controller;
    86                 var o = { };
    87                 o.method = 'torrent-get'
    88                 o.arguments = { };
    89                 o.arguments.fields = [
    90                         'addedDate', 'announceURL', 'comment', 'creator',
    91                         'dateCreated', 'downloadedEver', 'error', 'errorString',
    92                         'eta', 'hashString', 'haveUnchecked', 'haveValid', 'id',
    93                         'isPrivate', 'leechers', 'leftUntilDone', 'name',
    94                         'peersConnected', 'peersGettingFromUs', 'peersSendingToUs',
    95                         'rateDownload', 'rateUpload', 'seeders', 'sizeWhenDone',
    96                         'status', 'swarmSpeed', 'totalSize', 'uploadedEver' ];
    97                 this.sendRequest( RPC._Root, $.toJSON(o), function(data) {
     104                var o = {
     105                        method: 'torrent-get',
     106                        arguments: { fields: [
     107                                'addedDate', 'announceURL', 'comment', 'creator',
     108                                'dateCreated', 'downloadedEver', 'error', 'errorString',
     109                                'eta', 'hashString', 'haveUnchecked', 'haveValid', 'id',
     110                                'isPrivate', 'leechers', 'leftUntilDone', 'name',
     111                                'peersConnected', 'peersGettingFromUs', 'peersSendingToUs',
     112                                'rateDownload', 'rateUpload', 'seeders', 'sizeWhenDone',
     113                                'status', 'swarmSpeed', 'totalSize', 'uploadedEver' ]
     114                        }
     115                };
     116                this.sendRequest( o, function(data) {
    98117                        tr.updateTorrents( data.arguments.torrents );
    99118                }, "json" );
     
    102121        sendTorrentCommand: function( method, torrents ) {
    103122                var remote = this;
    104                 var o = { };
    105                 o.method = method;
    106                 o.arguments = { };
    107                 o.arguments.ids = [ ];
     123                var o = {
     124                        method: method,
     125                        arguments: { ids: [ ] }
     126                };
    108127                if( torrents != null )
    109128                        for( var i=0, len=torrents.length; i<len; ++i )
    110129                                o.arguments.ids.push( torrents[i].id() );
    111                 this.sendRequest( RPC._Root, $.toJSON(o), function( ) {
     130                this.sendRequest( o, function( ) {
    112131                        remote.loadTorrents();
    113                 }, "json" );
     132                } );
    114133        },
    115134        startTorrents: function( torrents ) {
     
    136155                                o.arguments.ids.push( torrents[i].id() );
    137156
    138                 this.sendRequest( RPC._Root, $.toJSON(o), function( ) {
     157                this.sendRequest( o, function( ) {
    139158                        remote.loadTorrents();
    140                 }, "json" );
     159                } );
    141160        },
    142161        addTorrentByUrl: function( url, options ) {
    143                 this.sendRequest( RPC._Root, $.toJSON({
     162                var remote = this;
     163                var o = {
    144164                        method: 'torrent-add',
    145165                        arguments: {
     
    147167                                filename: url
    148168                        }
    149                 }) );
    150         },
    151         savePrefs: function( args ) {
    152                 var remote = this;
    153                 var o = { };
    154                 o.method = 'session-set';
    155                 o.arguments = args;
    156                 this.sendRequest( RPC._Root, $.toJSON(o), function(){
     169                };
     170                remote.sendRequest(o, function() {
    157171                        remote.loadDaemonPrefs();
    158                 }, "json" );
     172                } );
    159173        }
    160174};
Note: See TracChangeset for help on using the changeset viewer.