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

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

add some debugging messages for the RPC server

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