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

Last change on this file since 6814 was 6814, checked in by charles, 14 years ago

(libT) d'oh, left out an important line last night while testing the rpc server w/softwareelves

  • 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 6814 2008-09-30 16:04:41Z 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.