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

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

a little more cleanup of the rpc server stuff.

  • Property svn:keywords set to Date Rev Author Id
File size: 14.8 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 6801 2008-09-26 04:41:13Z 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#include <libevent/event.h>
23#include <libevent/evhttp.h>
24
25#include "transmission.h"
26#include "bencode.h"
27#include "list.h"
28#include "platform.h"
29#include "rpcimpl.h"
30#include "rpc-server.h"
31#include "utils.h"
32#include "web.h"
33
34#define MY_NAME "RPC Server"
35#define MY_REALM "Transmission"
36#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
37
38struct tr_rpc_server
39{
40    unsigned int         isEnabled         : 1;
41    unsigned int         isPasswordEnabled : 1;
42    uint16_t             port;
43    struct evhttp *      httpd;
44    tr_handle *          session;
45    char *               username;
46    char *               password;
47    char *               acl;
48};
49
50#define dbgmsg( fmt... ) tr_deepLog( __FILE__, __LINE__, MY_NAME, ## fmt )
51
52static const char*
53tr_memmem( const char * s1,
54           size_t       l1,
55           const char * s2,
56           size_t       l2 )
57{
58    if( !l2 ) return s1;
59    while( l1 >= l2 )
60    {
61        l1--;
62        if( !memcmp( s1, s2, l2 ) )
63            return s1;
64        s1++;
65    }
66
67    return NULL;
68}
69
70
71/****
72*****  ACL UTILITIES
73****/
74
75static int
76isAddressAllowed( const tr_rpc_server * server,
77                  const char * address )
78{
79    const char * acl;
80
81    for( acl=server->acl; acl && *acl; )
82    {
83        const char * delimiter = strchr( acl, ',' );
84        const int len = delimiter ? delimiter-acl : (int)strlen( acl );
85        char * token = tr_strndup( acl, len );
86        const int match = tr_wildmat( address, token+1 );
87        tr_free( token );
88        if( match )
89            return *acl == '+';
90        if( !delimiter )
91            break;
92        acl = delimiter + 1;
93    }
94
95    return 0;
96}
97
98/**
99***
100**/
101
102static void
103send_simple_response( struct evhttp_request * req, int code, const char * text )
104{
105    const char * code_text = tr_webGetResponseStr( code );
106    struct evbuffer * body = evbuffer_new( );
107    evbuffer_add_printf( body, "<h1>%s</h1>", code_text );
108    if( text )
109        evbuffer_add_printf( body, "<h2>%s</h2>", text );
110    evhttp_send_reply( req, code, code_text, body );
111    evbuffer_free( body );
112}
113
114static void
115handle_upload( struct evhttp_request * req, struct tr_rpc_server * server )
116{
117    if( req->type != EVHTTP_REQ_POST )
118    {
119        send_simple_response( req, 405, NULL );
120    }
121    else
122    {
123
124        const char * content_type = evhttp_find_header( req->input_headers, "Content-Type" );
125
126        const char * query = strchr( req->uri, '?' );
127        const int paused = query && strstr( query+1, "paused=true" );
128
129        const char * in = (const char *) EVBUFFER_DATA( req->input_buffer );
130        size_t inlen = EVBUFFER_LENGTH( req->input_buffer );
131
132        const char * boundary_key = "boundary=";
133        const char * boundary_key_begin = strstr( content_type, boundary_key );
134        const char * boundary_val = boundary_key_begin ? boundary_key_begin + strlen( boundary_key ) : "arglebargle";
135
136        char * boundary = tr_strdup_printf( "--%s", boundary_val );
137        const size_t boundary_len = strlen( boundary );
138
139        const char * delim = tr_memmem( in, inlen, boundary, boundary_len );
140        while( delim )
141        {
142            size_t       part_len;
143            const char * part = delim + boundary_len;
144            inlen -= ( part - in );
145            in = part;
146            delim = tr_memmem( in, inlen, boundary, boundary_len );
147            part_len = delim ? (size_t)( delim - part ) : inlen;
148
149            if( part_len )
150            {
151                char * text = tr_strndup( part, part_len );
152                if( strstr( text, "filename=\"" ) )
153                {
154                    const char * body = strstr( text, "\r\n\r\n" );
155                    if( body )
156                    {
157                        char *  b64, *json, *freeme;
158                        int     json_len;
159                        size_t  body_len;
160                        tr_benc top, *args;
161
162                        body += 4;
163                        body_len = part_len - ( body - text );
164                        if( body_len >= 2
165                              && !memcmp( &body[body_len - 2], "\r\n", 2 ) )
166                                body_len -= 2;
167
168                        tr_bencInitDict( &top, 2 );
169                        args = tr_bencDictAddDict( &top, "arguments", 2 );
170                        tr_bencDictAddStr( &top, "method", "torrent-add" );
171                        b64 = tr_base64_encode( body, body_len, NULL );
172                        tr_bencDictAddStr( args, "metainfo", b64 );
173                        tr_bencDictAddInt( args, "paused", paused );
174                        json = tr_bencSaveAsJSON( &top, &json_len );
175                        freeme = tr_rpc_request_exec_json( server->session,
176                                                           json, json_len,
177                                                           NULL );
178
179                        tr_free( freeme );
180                        tr_free( json );
181                        tr_free( b64 );
182                        tr_bencFree( &top );
183                    }
184                }
185                tr_free( text );
186            }
187        }
188
189        tr_free( boundary );
190
191        /* use xml here because json responses to file uploads is trouble.
192           * see http://www.malsup.com/jquery/form/#sample7 for details */
193        evhttp_add_header(req->output_headers, "Content-Type", "text/xml; charset=UTF-8" );
194        send_simple_response( req, HTTP_OK, NULL );
195    }
196}
197
198static const char*
199mimetype_guess( const char * path )
200{
201    unsigned int i;
202    const struct {
203        const char * suffix;
204        const char * mime_type;
205    } types[] = {
206        /* these are just the ones we need for serving clutch... */
207        { "css", "text/css" },
208        { "gif", "image/gif" },
209        { "html", "text/html" },
210        { "ico", "image/vnd.microsoft.icon" },
211        { "js", "application/javascript" },
212        { "png", "image/png" }
213    };
214    const char * dot = strrchr( path, '.' );
215
216    for( i=0; dot && i<TR_N_ELEMENTS(types); ++i )
217        if( !strcmp( dot+1, types[i].suffix ) )
218            return types[i].mime_type;
219
220    return "application/octet-stream";
221}
222
223static void
224serve_file( struct evhttp_request * req, const char * path )
225{
226    if( req->type != EVHTTP_REQ_GET )
227    {
228        evhttp_add_header(req->output_headers, "Allow", "GET");
229        send_simple_response( req, 405, NULL );
230    }
231    else
232    {
233        const int fd = open( path, O_RDONLY, 0 );
234        if( fd == -1 )
235        {
236            send_simple_response( req, HTTP_NOTFOUND, NULL );
237        }
238        else
239        {
240            struct evbuffer * buf = evbuffer_new( );
241            evbuffer_read(buf, fd, INT_MAX );
242            evhttp_add_header(req->output_headers, "Content-Type", mimetype_guess( path ) );
243            evhttp_send_reply( req, HTTP_OK, "OK", buf );
244            evbuffer_free(buf);
245            close( fd );
246        }
247    }
248}
249
250static void
251handle_clutch( struct evhttp_request * req, struct tr_rpc_server * server )
252{
253    const char * uri;
254    struct evbuffer * buf = evbuffer_new( );
255
256    assert( !strncmp( req->uri, "/transmission/web/", 18 ) );
257
258    evbuffer_add_printf( buf, "%s%s", tr_getClutchDir( server->session ), TR_PATH_DELIMITER_STR );
259    uri = req->uri + 18;
260    if( (*uri=='?') || (*uri=='\0') )
261        evbuffer_add_printf( buf, "index.html" );
262    else {
263        const char * pch = strchr( uri, '?' );
264        if( pch )
265            evbuffer_add_printf( buf, "%*.*s", (int)(pch-uri), (int)(pch-uri), uri );
266        else
267            evbuffer_add_printf( buf, "%s", uri );
268    }
269                             
270    if( strstr( (const char *)EVBUFFER_DATA( buf ), ".." ) )
271        send_simple_response( req, 401, NULL );
272    else
273        serve_file( req, (const char *)EVBUFFER_DATA( buf ) );
274
275    evbuffer_free( buf );
276}
277
278static void
279handle_rpc( struct evhttp_request * req, struct tr_rpc_server * server )
280{
281    int len = 0;
282    char * response;
283    struct evbuffer * buf;
284
285    if( req->type == EVHTTP_REQ_GET )
286    {
287        const char * q;
288        if(( q = strchr( req->uri, '?' )))
289            response = tr_rpc_request_exec_uri( server->session,
290                                                q + 1,
291                                                strlen( q + 1 ),
292                                                &len );
293    }
294    else if( req->type == EVHTTP_REQ_POST )
295    {
296        response = tr_rpc_request_exec_json( server->session,
297                                             EVBUFFER_DATA( req->input_buffer ),
298                                             EVBUFFER_LENGTH( req->input_buffer ),
299                                             &len );
300    }
301
302    buf = evbuffer_new( );
303    evbuffer_add( buf, response, len );
304    evhttp_add_header( req->output_headers, "Content-Type", "application/json; charset=UTF-8" );
305    evhttp_send_reply( req, HTTP_OK, "OK", buf );
306    evbuffer_free( buf );
307}
308
309static void
310handle_request( struct evhttp_request * req, void * arg )
311{
312    struct tr_rpc_server * server = arg;
313
314    if (req && req->evcon )
315    {
316        const char * auth;
317        char * user = NULL;
318        char * pass = NULL;
319
320        evhttp_add_header( req->output_headers, "Server", MY_REALM );
321       
322        auth = evhttp_find_header( req->input_headers, "Authorization" );
323
324        if( auth && !strncasecmp( auth, "basic ", 6 ) )
325        {
326            int plen;
327            char * p = tr_base64_decode( auth + 6, 0, &plen );
328            if( p && plen && (( pass = strchr( p, ':' )))) {
329                user = p;
330                *pass++ = '\0';
331            }
332        }
333
334        if( server->acl && !isAddressAllowed( server, req->remote_host ) )
335        {
336            send_simple_response( req, 401, "Unauthorized IP Address" );
337        }
338        else if( server->isPasswordEnabled && (    !pass
339                                                || !user
340                                                || strcmp( server->username, user )
341                                                || strcmp( server->password, pass ) ) )
342        {
343            evhttp_add_header( req->output_headers,
344                "WWW-Authenticate", "Basic realm=\"" MY_REALM "\"" );
345            send_simple_response( req, 401, "Unauthorized User" );
346        }
347        else if( !strcmp( req->uri, "/transmission/web" ) ||
348                 !strcmp( req->uri, "/transmission/clutch" ) ||
349                 !strcmp( req->uri, "/" ) )
350        {
351            evhttp_add_header( req->output_headers, "Location", "/transmission/web/" );
352            send_simple_response( req, HTTP_MOVEPERM, NULL );
353        }
354        else if( !strncmp( req->uri, "/transmission/web/", 18 ) )
355        {
356            handle_clutch( req, server );
357        }
358        else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
359        {
360            handle_rpc( req, server );
361        }
362        else if( !strncmp( req->uri, "/transmission/upload", 20 ) )
363        {
364            handle_upload( req, server );
365        }
366        else
367        {
368            send_simple_response( req, HTTP_NOTFOUND, NULL );
369        }
370
371        tr_free( user );
372    }
373}
374
375static void
376startServer( tr_rpc_server * server )
377{
378    dbgmsg( "in startServer; current context is %p", server->httpd );
379
380    if( !server->httpd )
381        if( ( server->httpd = evhttp_start( NULL, server->port ) ) )
382            evhttp_set_gencb( server->httpd, handle_request, server );
383}
384
385static void
386stopServer( tr_rpc_server * server )
387{
388    if( server->httpd )
389    {
390        evhttp_free( server->httpd );
391        server->httpd = NULL;
392    }
393}
394
395void
396tr_rpcSetEnabled( tr_rpc_server * server,
397                  int             isEnabled )
398{
399    server->isEnabled = isEnabled != 0;
400
401    if( !isEnabled )
402        stopServer( server );
403    else
404        startServer( server );
405}
406
407int
408tr_rpcIsEnabled( const tr_rpc_server * server )
409{
410    return server->isEnabled;
411}
412
413void
414tr_rpcSetPort( tr_rpc_server * server,
415               uint16_t        port )
416{
417    if( server->port != port )
418    {
419        server->port = port;
420
421        if( server->isEnabled )
422        {
423            stopServer( server );
424            startServer( server );
425        }
426    }
427}
428
429uint16_t
430tr_rpcGetPort( const tr_rpc_server * server )
431{
432    return server->port;
433}
434
435void
436tr_rpcSetACL( tr_rpc_server * server,
437              const char *    acl )
438{
439    tr_free( server->acl );
440    server->acl = tr_strdup( acl );
441}
442
443char*
444tr_rpcGetACL( const tr_rpc_server * server )
445{
446    return tr_strdup( server->acl ? server->acl : "" );
447}
448
449/****
450*****  PASSWORD
451****/
452
453void
454tr_rpcSetUsername( tr_rpc_server * server,
455                   const char *    username )
456{
457    tr_free( server->username );
458    server->username = tr_strdup( username );
459    dbgmsg( "setting our Username to [%s]", server->username );
460}
461
462char*
463tr_rpcGetUsername( const tr_rpc_server * server )
464{
465    return tr_strdup( server->username ? server->username : "" );
466}
467
468void
469tr_rpcSetPassword( tr_rpc_server * server,
470                   const char *    password )
471{
472    tr_free( server->password );
473    server->password = tr_strdup( password );
474    dbgmsg( "setting our Password to [%s]", server->password );
475}
476
477char*
478tr_rpcGetPassword( const tr_rpc_server * server )
479{
480    return tr_strdup( server->password ? server->password : "" );
481}
482
483void
484tr_rpcSetPasswordEnabled( tr_rpc_server * server,
485                          int             isEnabled )
486{
487    server->isPasswordEnabled = isEnabled != 0;
488    dbgmsg( "setting 'password enabled' to %d", isEnabled );
489}
490
491int
492tr_rpcIsPasswordEnabled( const tr_rpc_server * server )
493{
494    return server->isPasswordEnabled;
495}
496
497/****
498*****  LIFE CYCLE
499****/
500
501void
502tr_rpcClose( tr_rpc_server ** ps )
503{
504    tr_rpc_server * s = *ps;
505
506    *ps = NULL;
507
508    stopServer( s );
509    tr_free( s->acl );
510    tr_free( s->username );
511    tr_free( s->password );
512    tr_free( s );
513}
514
515tr_rpc_server *
516tr_rpcInit( tr_handle *  session,
517            int          isEnabled,
518            uint16_t     port,
519            const char * acl,
520            int          isPasswordEnabled,
521            const char * username,
522            const char * password )
523{
524    tr_rpc_server * s;
525
526    s = tr_new0( tr_rpc_server, 1 );
527    s->session = session;
528    s->port = port;
529    s->acl = tr_strdup( acl && *acl ? acl : TR_DEFAULT_RPC_ACL );
530    s->username = tr_strdup( username );
531    s->password = tr_strdup( password );
532    s->isPasswordEnabled = isPasswordEnabled != 0;
533    s->isEnabled = isEnabled != 0;
534
535    if( isEnabled )
536        startServer( s );
537    return s;
538}
Note: See TracBrowser for help on using the repository browser.