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

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

pass 0.0.0.0 in as the rpc server address

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