source: trunk/libtransmission/rpc-server.c @ 6846

Last change on this file since 6846 was 6846, checked in by charles, 13 years ago

(rpc) #1319 as pointed out by spry, deflate()'s output can sometimes be larger than the native form. In these cases, use the smaller of the two sources.

  • Property svn:keywords set to Date Rev Author Id
File size: 18.6 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: rpc-server.c 6846 2008-10-03 20:46:58Z charles $
11 */
12
13#include <assert.h>
14#include <string.h> /* memcpy */
15#include <limits.h> /* INT_MAX */
16
17#include <sys/types.h> /* open */
18#include <sys/stat.h>  /* open */
19#include <fcntl.h>     /* open */
20#include <unistd.h>    /* close */
21
22#ifdef HAVE_LIBZ
23 #include <zlib.h>
24#endif
25
26#include <libevent/event.h>
27#include <libevent/evhttp.h>
28
29#include "transmission.h"
30#include "bencode.h"
31#include "list.h"
32#include "platform.h"
33#include "rpcimpl.h"
34#include "rpc-server.h"
35#include "trevent.h"
36#include "utils.h"
37#include "web.h"
38
39#define MY_NAME "RPC Server"
40#define MY_REALM "Transmission"
41#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
42
43struct tr_rpc_server
44{
45    unsigned int       isEnabled          : 1;
46    unsigned int       isPasswordEnabled  : 1;
47    unsigned int       isWhitelistEnabled : 1;
48    uint16_t           port;
49    struct evhttp *    httpd;
50    tr_handle *        session;
51    char *             username;
52    char *             password;
53    char *             whitelist;
54};
55
56#define dbgmsg( fmt ... ) tr_deepLog( __FILE__, __LINE__, MY_NAME, ## fmt )
57
58/**
59***
60**/
61
62static void
63send_simple_response( struct evhttp_request * req,
64                      int                     code,
65                      const char *            text )
66{
67    const char *      code_text = tr_webGetResponseStr( code );
68    struct evbuffer * body = evbuffer_new( );
69
70    evbuffer_add_printf( body, "<h1>%s</h1>", code_text );
71    if( text )
72        evbuffer_add_printf( body, "<h2>%s</h2>", text );
73    evhttp_send_reply( req, code, code_text, body );
74    evbuffer_free( body );
75}
76
77static const char*
78tr_memmem( const char * s1,
79           size_t       l1,
80           const char * s2,
81           size_t       l2 )
82{
83    if( !l2 ) return s1;
84    while( l1 >= l2 )
85    {
86        l1--;
87        if( !memcmp( s1, s2, l2 ) )
88            return s1;
89        s1++;
90    }
91
92    return NULL;
93}
94
95static void
96handle_upload( struct evhttp_request * req,
97               struct tr_rpc_server *  server )
98{
99    if( req->type != EVHTTP_REQ_POST )
100    {
101        send_simple_response( req, 405, NULL );
102    }
103    else
104    {
105        const char * content_type = evhttp_find_header( req->input_headers,
106                                                        "Content-Type" );
107
108        const char * query = strchr( req->uri, '?' );
109        const int    paused = query && strstr( query + 1, "paused=true" );
110
111        const char * in = (const char *) EVBUFFER_DATA( req->input_buffer );
112        size_t       inlen = EVBUFFER_LENGTH( req->input_buffer );
113
114        const char * boundary_key = "boundary=";
115        const char * boundary_key_begin = strstr( content_type,
116                                                  boundary_key );
117        const char * boundary_val =
118            boundary_key_begin ? boundary_key_begin +
119            strlen( boundary_key ) : "arglebargle";
120
121        char *       boundary = tr_strdup_printf( "--%s", boundary_val );
122        const size_t boundary_len = strlen( boundary );
123
124        const char * delim = tr_memmem( in, inlen, boundary, boundary_len );
125        while( delim )
126        {
127            size_t       part_len;
128            const char * part = delim + boundary_len;
129            inlen -= ( part - in );
130            in = part;
131            delim = tr_memmem( in, inlen, boundary, boundary_len );
132            part_len = delim ? (size_t)( delim - part ) : inlen;
133
134            if( part_len )
135            {
136                char * text = tr_strndup( part, part_len );
137                if( strstr( text, "filename=\"" ) )
138                {
139                    const char * body = strstr( text, "\r\n\r\n" );
140                    if( body )
141                    {
142                        char *  b64, *json, *freeme;
143                        int     json_len;
144                        size_t  body_len;
145                        tr_benc top, *args;
146
147                        body += 4;
148                        body_len = part_len - ( body - text );
149                        if( body_len >= 2
150                          && !memcmp( &body[body_len - 2], "\r\n", 2 ) )
151                            body_len -= 2;
152
153                        tr_bencInitDict( &top, 2 );
154                        args = tr_bencDictAddDict( &top, "arguments", 2 );
155                        tr_bencDictAddStr( &top, "method", "torrent-add" );
156                        b64 = tr_base64_encode( body, body_len, NULL );
157                        tr_bencDictAddStr( args, "metainfo", b64 );
158                        tr_bencDictAddInt( args, "paused", paused );
159                        json = tr_bencSaveAsJSON( &top, &json_len );
160                        freeme = tr_rpc_request_exec_json( server->session,
161                                                           json, json_len,
162                                                           NULL );
163
164                        tr_free( freeme );
165                        tr_free( json );
166                        tr_free( b64 );
167                        tr_bencFree( &top );
168                    }
169                }
170                tr_free( text );
171            }
172        }
173
174        tr_free( boundary );
175
176        /* use xml here because json responses to file uploads is trouble.
177         * see http://www.malsup.com/jquery/form/#sample7 for details */
178        evhttp_add_header( req->output_headers, "Content-Type",
179                           "text/xml; charset=UTF-8" );
180        send_simple_response( req, HTTP_OK, NULL );
181    }
182}
183
184static const char*
185mimetype_guess( const char * path )
186{
187    unsigned int i;
188
189    const struct
190    {
191        const char *    suffix;
192        const char *    mime_type;
193    } types[] = {
194        /* these are just the ones we need for serving clutch... */
195        { "css",  "text/css"                       },
196        { "gif",  "image/gif"                      },
197        { "html", "text/html"                      },
198        { "ico",  "image/vnd.microsoft.icon"       },
199        { "js",   "application/javascript"         },
200        { "png",  "image/png"                      }
201    };
202    const char * dot = strrchr( path, '.' );
203
204    for( i = 0; dot && i < TR_N_ELEMENTS( types ); ++i )
205        if( !strcmp( dot + 1, types[i].suffix ) )
206            return types[i].mime_type;
207
208    return "application/octet-stream";
209}
210
211#ifdef HAVE_LIBZ
212static int
213compress_evbuf( struct evbuffer * evbuf )
214{
215    int err = 0;
216    struct evbuffer  * out;
217    static z_stream    stream;
218
219    stream.zalloc = Z_NULL;
220    stream.zfree = Z_NULL;
221    stream.opaque = Z_NULL;
222    deflateInit( &stream, Z_DEFAULT_COMPRESSION );
223
224    stream.next_in = EVBUFFER_DATA( evbuf );
225    stream.avail_in = EVBUFFER_LENGTH( evbuf );
226    out = evbuffer_new( );
227
228    while( !err ) {
229        unsigned char buf[1024];
230        int state;
231        stream.next_out = buf;
232        stream.avail_out = sizeof( buf );
233        state = deflate( &stream, Z_FINISH );
234        if( ( state != Z_OK ) && ( state != Z_STREAM_END ) ) {
235            tr_nerr( MY_NAME, _( "Error deflating file: %s" ), zError( err ) );
236            err = state;
237            break;
238        }
239        evbuffer_add( out, buf, sizeof(buf) - stream.avail_out );
240        if( state == Z_STREAM_END )
241            break;
242    }
243
244    /* if the deflated form is larger, then just use the original */
245    if( !err && ( EVBUFFER_LENGTH( out ) >= EVBUFFER_LENGTH( evbuf ) ) )
246        err = -1;
247
248    if( !err ) {
249        tr_ninf( MY_NAME, "deflated response from %zu bytes to %zu\n",
250                          EVBUFFER_LENGTH( evbuf ),
251                          EVBUFFER_LENGTH( out ) );
252        evbuffer_drain( evbuf, EVBUFFER_LENGTH( evbuf ) );
253        evbuffer_add_buffer( evbuf, out );
254    }
255
256    deflateEnd( &stream );
257    evbuffer_free( out );
258    return err;
259}
260#endif
261
262static void
263maybe_deflate_response( struct evhttp_request * req,
264                        struct evbuffer *       response )
265{
266#ifdef HAVE_LIBZ
267    const char * accept_encoding = evhttp_find_header( req->input_headers,
268                                                       "Accept-Encoding" );
269    const int    do_deflate = accept_encoding && strstr( accept_encoding,
270                                                         "deflate" );
271    if( do_deflate && !compress_evbuf( response ) )
272        evhttp_add_header( req->output_headers, "Content-Encoding", "deflate" );
273#endif
274}
275
276static void
277serve_file( struct evhttp_request * req,
278            const char *            path )
279{
280    if( req->type != EVHTTP_REQ_GET )
281    {
282        evhttp_add_header( req->output_headers, "Allow", "GET" );
283        send_simple_response( req, 405, NULL );
284    }
285    else
286    {
287        const int fd = open( path, O_RDONLY, 0 );
288        if( fd == -1 )
289        {
290            send_simple_response( req, HTTP_NOTFOUND, NULL );
291        }
292        else
293        {
294            struct evbuffer * buf = evbuffer_new( );
295            evbuffer_read( buf, fd, INT_MAX );
296            evhttp_add_header( req->output_headers, "Content-Type",
297                              mimetype_guess(
298                                  path ) );
299            maybe_deflate_response( req, buf );
300            evhttp_send_reply( req, HTTP_OK, "OK", buf );
301            evbuffer_free( buf );
302            close( fd );
303        }
304    }
305}
306
307static void
308handle_clutch( struct evhttp_request * req,
309               struct tr_rpc_server *  server )
310{
311    char * pch;
312    char * subpath;
313    char * filename;
314    const char * clutchDir = tr_getClutchDir( server->session );
315
316    assert( !strncmp( req->uri, "/transmission/web/", 18 ) );
317
318    subpath = tr_strdup( req->uri + 18 );
319    if(( pch = strchr( subpath, '?' )))
320        *pch = '\0';
321
322    filename = *subpath
323        ? tr_strdup_printf( "%s%s%s", clutchDir, TR_PATH_DELIMITER_STR, subpath )
324        : tr_strdup_printf( "%s%s%s", clutchDir, TR_PATH_DELIMITER_STR, "index.html" );
325
326    serve_file( req, filename );
327
328    tr_free( filename );
329    tr_free( subpath );
330}
331
332static void
333handle_rpc( struct evhttp_request * req,
334            struct tr_rpc_server *  server )
335{
336    int               len = 0;
337    char *            response = NULL;
338    struct evbuffer * buf;
339
340    if( req->type == EVHTTP_REQ_GET )
341    {
342        const char * q;
343        if( ( q = strchr( req->uri, '?' ) ) )
344            response = tr_rpc_request_exec_uri( server->session,
345                                                q + 1,
346                                                strlen( q + 1 ),
347                                                &len );
348    }
349    else if( req->type == EVHTTP_REQ_POST )
350    {
351        response = tr_rpc_request_exec_json( server->session,
352                                             EVBUFFER_DATA( req->
353                                                            input_buffer ),
354                                             EVBUFFER_LENGTH( req->
355                                                              input_buffer ),
356                                             &len );
357    }
358
359    buf = evbuffer_new( );
360    evbuffer_add( buf, response, len );
361    maybe_deflate_response( req, buf );
362    evhttp_add_header( req->output_headers, "Content-Type",
363                       "application/json; charset=UTF-8" );
364    evhttp_send_reply( req, HTTP_OK, "OK", buf );
365
366    /* cleanup */
367    evbuffer_free( buf );
368    tr_free( response );
369}
370
371static int
372isAddressAllowed( const tr_rpc_server * server,
373                  const char *          address )
374{
375    const char * str;
376
377    if( !server->isWhitelistEnabled )
378        return 1;
379
380    for( str = server->whitelist; str && *str; )
381    {
382        const char * delimiter = strchr( str, ',' );
383        const int    len = delimiter ? delimiter - str : (int)strlen( str );
384        char *       token = tr_strndup( str, len );
385        const int    match = tr_wildmat( address, token );
386        tr_free( token );
387        if( match )
388            return 1;
389        if( !delimiter )
390            break;
391        str = delimiter + 1;
392    }
393
394    return 0;
395}
396
397static void
398handle_request( struct evhttp_request * req,
399                void *                  arg )
400{
401    struct tr_rpc_server * server = arg;
402
403    if( req && req->evcon )
404    {
405        const char * auth;
406        char *       user = NULL;
407        char *       pass = NULL;
408
409        evhttp_add_header( req->output_headers, "Server", MY_REALM );
410
411        auth = evhttp_find_header( req->input_headers, "Authorization" );
412
413        if( auth && !strncasecmp( auth, "basic ", 6 ) )
414        {
415            int    plen;
416            char * p = tr_base64_decode( auth + 6, 0, &plen );
417            if( p && plen && ( ( pass = strchr( p, ':' ) ) ) )
418            {
419                user = p;
420                *pass++ = '\0';
421            }
422        }
423
424        if( !isAddressAllowed( server, req->remote_host ) )
425        {
426            send_simple_response( req, 401, "Unauthorized IP Address" );
427        }
428        else if( server->isPasswordEnabled && ( !pass
429                                              || !user
430                                              || strcmp( server->username,
431                                                         user )
432                                              || strcmp( server->password,
433                                                         pass ) ) )
434        {
435            evhttp_add_header( req->output_headers,
436                               "WWW-Authenticate",
437                               "Basic realm=\"" MY_REALM "\"" );
438            send_simple_response( req, 401, "Unauthorized User" );
439        }
440        else if( !strcmp( req->uri, "/transmission/web" )
441               || !strcmp( req->uri, "/transmission/clutch" )
442               || !strcmp( req->uri, "/" ) )
443        {
444            evhttp_add_header( req->output_headers, "Location",
445                               "/transmission/web/" );
446            send_simple_response( req, HTTP_MOVEPERM, NULL );
447        }
448        else if( !strncmp( req->uri, "/transmission/web/", 18 ) )
449        {
450            handle_clutch( req, server );
451        }
452        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
453        {
454            handle_rpc( req, server );
455        }
456        else if( !strncmp( req->uri, "/transmission/upload", 20 ) )
457        {
458            handle_upload( req, server );
459        }
460        else
461        {
462            send_simple_response( req, HTTP_NOTFOUND, NULL );
463        }
464
465        tr_free( user );
466    }
467}
468
469static void
470startServer( void * vserver )
471{
472    tr_rpc_server * server  = vserver;
473
474    if( !server->httpd )
475    {
476        server->httpd = evhttp_new( tr_eventGetBase( server->session ) );
477        evhttp_bind_socket( server->httpd, "0.0.0.0", server->port );
478        evhttp_set_gencb( server->httpd, handle_request, server );
479    }
480}
481
482static void
483stopServer( tr_rpc_server * server )
484{
485    if( server->httpd )
486    {
487        evhttp_free( server->httpd );
488        server->httpd = NULL;
489    }
490}
491
492static void
493onEnabledChanged( void * vserver )
494{
495    tr_rpc_server * server = vserver;
496
497    if( !server->isEnabled )
498        stopServer( server );
499    else
500        startServer( server );
501}
502
503void
504tr_rpcSetEnabled( tr_rpc_server * server,
505                  int             isEnabled )
506{
507    server->isEnabled = isEnabled != 0;
508
509    tr_runInEventThread( server->session, onEnabledChanged, server );
510}
511
512int
513tr_rpcIsEnabled( const tr_rpc_server * server )
514{
515    return server->isEnabled;
516}
517
518static void
519restartServer( void * vserver )
520{
521    tr_rpc_server * server = vserver;
522
523    if( server->isEnabled )
524    {
525        stopServer( server );
526        startServer( server );
527    }
528}
529
530void
531tr_rpcSetPort( tr_rpc_server * server,
532               uint16_t        port )
533{
534    if( server->port != port )
535    {
536        server->port = port;
537
538        if( server->isEnabled )
539            tr_runInEventThread( server->session, restartServer, server );
540    }
541}
542
543uint16_t
544tr_rpcGetPort( const tr_rpc_server * server )
545{
546    return server->port;
547}
548
549void
550tr_rpcSetWhitelist( tr_rpc_server * server,
551                    const char *    whitelist )
552{
553    tr_free( server->whitelist );
554    server->whitelist = tr_strdup( whitelist );
555}
556
557char*
558tr_rpcGetWhitelist( const tr_rpc_server * server )
559{
560    return tr_strdup( server->whitelist ? server->whitelist : "" );
561}
562
563void
564tr_rpcSetWhitelistEnabled( tr_rpc_server  * server,
565                           int              isEnabled )
566{
567    server->isWhitelistEnabled = isEnabled != 0;
568}
569
570int
571tr_rpcGetWhitelistEnabled( const tr_rpc_server * server )
572{
573    return server->isWhitelistEnabled;
574}
575
576/****
577*****  PASSWORD
578****/
579
580void
581tr_rpcSetUsername( tr_rpc_server * server,
582                   const char *    username )
583{
584    tr_free( server->username );
585    server->username = tr_strdup( username );
586    dbgmsg( "setting our Username to [%s]", server->username );
587}
588
589char*
590tr_rpcGetUsername( const tr_rpc_server * server )
591{
592    return tr_strdup( server->username ? server->username : "" );
593}
594
595void
596tr_rpcSetPassword( tr_rpc_server * server,
597                   const char *    password )
598{
599    tr_free( server->password );
600    server->password = tr_strdup( password );
601    dbgmsg( "setting our Password to [%s]", server->password );
602}
603
604char*
605tr_rpcGetPassword( const tr_rpc_server * server )
606{
607    return tr_strdup( server->password ? server->password : "" );
608}
609
610void
611tr_rpcSetPasswordEnabled( tr_rpc_server * server,
612                          int             isEnabled )
613{
614    server->isPasswordEnabled = isEnabled != 0;
615    dbgmsg( "setting 'password enabled' to %d", isEnabled );
616}
617
618int
619tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
620{
621    return server->isPasswordEnabled;
622}
623
624/****
625*****  LIFE CYCLE
626****/
627
628static void
629closeServer( void * vserver )
630{
631    tr_rpc_server * s = vserver;
632
633    stopServer( s );
634    tr_free( s->whitelist );
635    tr_free( s->username );
636    tr_free( s->password );
637    tr_free( s );
638}
639
640void
641tr_rpcClose( tr_rpc_server ** ps )
642{
643    tr_runInEventThread( ( *ps )->session, closeServer, *ps );
644    *ps = NULL;
645}
646
647tr_rpc_server *
648tr_rpcInit( tr_handle *  session,
649            int          isEnabled,
650            uint16_t     port,
651            int          isWhitelistEnabled,
652            const char * whitelist,
653            int          isPasswordEnabled,
654            const char * username,
655            const char * password )
656{
657    tr_rpc_server * s;
658
659    s = tr_new0( tr_rpc_server, 1 );
660    s->session = session;
661    s->port = port;
662    s->whitelist = tr_strdup( whitelist && *whitelist ? whitelist : TR_DEFAULT_RPC_WHITELIST );
663    s->username = tr_strdup( username );
664    s->password = tr_strdup( password );
665    s->isWhitelistEnabled = isWhitelistEnabled != 0;
666    s->isPasswordEnabled = isPasswordEnabled != 0;
667    s->isEnabled = isEnabled != 0;
668    if( isEnabled )
669        tr_runInEventThread( session, startServer, s );
670    return s;
671}
672
Note: See TracBrowser for help on using the repository browser.