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

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

(rpc server) fix ACL error reported by pea_

  • Property svn:keywords set to Date Rev Author Id
File size: 9.7 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 6046 2008-06-05 04:02:46Z charles $
11 */
12
13#include <assert.h>
14#include <ctype.h> /* isdigit */
15#include <errno.h>
16#include <stdlib.h> /* strtol */
17#include <string.h>
18
19#include <libevent/event.h>
20#include <shttpd/shttpd.h>
21
22#include "transmission.h"
23#include "rpc.h"
24#include "rpc-server.h"
25#include "utils.h"
26
27#define MY_NAME "RPC Server"
28
29#define BUSY_INTERVAL_MSEC 30
30#define IDLE_INTERVAL_MSEC 100
31#define UNUSED_INTERVAL_MSEC 1000
32
33struct tr_rpc_server
34{
35    int port;
36    time_t lastRequestTime;
37    struct shttpd_ctx * ctx;
38    tr_handle * session;
39    struct evbuffer * in;
40    struct evbuffer * out;
41    struct event timer;
42    char * acl;
43};
44
45#define dbgmsg(fmt...) tr_deepLog(__FILE__, __LINE__, MY_NAME, ##fmt )
46
47static void
48handle_rpc( struct shttpd_arg * arg )
49{
50    struct tr_rpc_server * s = arg->user_data;
51
52    if( !EVBUFFER_LENGTH( s->out ) )
53    {
54        int len = 0;
55        char * response = NULL;
56        const char * request_method = shttpd_get_env( arg, "REQUEST_METHOD" );
57        const char * query_string = shttpd_get_env( arg, "QUERY_STRING" );
58
59        if( query_string && *query_string )
60            response = tr_rpc_request_exec_uri( s->session,
61                                                query_string,
62                                                strlen( query_string ),
63                                                &len );
64        else if( !strcmp( request_method, "POST" ) )
65        {
66            evbuffer_add( s->in, arg->in.buf, arg->in.len );
67            arg->in.num_bytes = arg->in.len;
68            if( arg->flags & SHTTPD_MORE_POST_DATA )
69                return;
70            response = tr_rpc_request_exec_json( s->session,
71                                                 EVBUFFER_DATA( s->in ),
72                                                 EVBUFFER_LENGTH( s->in ),
73                                                 &len );
74            evbuffer_drain( s->in, EVBUFFER_LENGTH( s->in ) );
75        }
76
77        evbuffer_add_printf( s->out, "HTTP/1.1 200 OK\r\n"
78                                     "Content-Type: application/json\r\n"
79                                     "Content-Length: %d\r\n"
80                                     "\r\n"
81                                     "%*.*s\r\n", len, len, len, response );
82        tr_free( response );
83    }
84
85    if( EVBUFFER_LENGTH( s->out ) )
86    {
87        const int n = MIN( ( int )EVBUFFER_LENGTH( s->out ), arg->out.len );
88        memcpy( arg->out.buf, EVBUFFER_DATA( s->out ), n );
89        evbuffer_drain( s->out, n );
90        arg->out.num_bytes = n;
91    }
92
93    if( !EVBUFFER_LENGTH( s->out ) )
94        arg->flags |= SHTTPD_END_OF_OUTPUT;
95}
96
97static void
98rpcPulse( int socket UNUSED, short action UNUSED, void * vserver )
99{
100    int interval;
101    struct timeval tv;
102    tr_rpc_server * server = vserver;
103    const time_t now = time( NULL );
104
105    assert( server );
106
107    shttpd_poll( server->ctx, 1 );
108
109    /* set a timer for the next pulse */
110    if( EVBUFFER_LENGTH( server->in ) || EVBUFFER_LENGTH( server->out ) ) {
111        interval = BUSY_INTERVAL_MSEC;
112        server->lastRequestTime = now;
113    } else if( now - server->lastRequestTime < 300 ) {
114        interval = IDLE_INTERVAL_MSEC;
115    } else {
116        interval = UNUSED_INTERVAL_MSEC;
117    }
118    tv = tr_timevalMsec( interval );
119    evtimer_add( &server->timer, &tv );
120}
121
122static void
123startServer( tr_rpc_server * server )
124{
125    dbgmsg( "in startServer; current context is %p", server->ctx );
126
127    if( !server->ctx )
128    {
129        char ports[128];
130        struct timeval tv = tr_timevalMsec( UNUSED_INTERVAL_MSEC );
131
132        server->ctx = shttpd_init( );
133        snprintf( ports, sizeof( ports ), "%d", server->port );
134        shttpd_register_uri( server->ctx, "/transmission", handle_rpc, server );
135        shttpd_set_option( server->ctx, "ports", ports );
136        shttpd_set_option( server->ctx, "dir_list", "0" );
137        shttpd_set_option( server->ctx, "root", "/dev/null" );
138        if( server->acl ) {
139            dbgmsg( "setting acl [%s]", server->acl );
140            shttpd_set_option( server->ctx, "acl", server->acl );
141        }
142
143        evtimer_set( &server->timer, rpcPulse, server );
144        evtimer_add( &server->timer, &tv );
145    }
146}
147
148static void
149stopServer( tr_rpc_server * server )
150{
151    if( server->ctx )
152    {
153        evtimer_del( &server->timer );
154        shttpd_fini( server->ctx );
155        server->ctx = NULL;
156    }
157}
158
159void
160tr_rpcSetEnabled( tr_rpc_server * server, int isEnabled )
161{
162    if( !isEnabled && server->ctx )
163        stopServer( server );
164
165    if( isEnabled && !server->ctx )
166        startServer( server );
167}
168
169int
170tr_rpcIsEnabled( const tr_rpc_server * server )
171{
172    return server->ctx != NULL;
173}
174
175void
176tr_rpcSetPort( tr_rpc_server * server, int port )
177{
178    if( server->port != port )
179    {
180        server->port = port;
181
182        if( server->ctx )
183        {
184            stopServer( server );
185            startServer( server );
186        }
187    }
188}
189
190int
191tr_rpcGetPort( const tr_rpc_server * server )
192{
193    return server->port;
194}
195
196/*
197 * DELIM_CHARS, FOR_EACH_WORD_IN_LIST, isbyte, and testACL are from, or modified from,
198 * shttpd, written by Sergey Lyubka under this license:
199 * "THE BEER-WARE LICENSE" (Revision 42):
200 * Sergey Lyubka wrote this file.  As long as you retain this notice you
201 * can do whatever you want with this stuff. If we meet some day, and you think
202 * this stuff is worth it, you can buy me a beer in return.
203 */
204
205#define  DELIM_CHARS " ," /* Separators for lists */
206
207#define FOR_EACH_WORD_IN_LIST(s,len)                                    \
208        for (; s != NULL && (len = strcspn(s, DELIM_CHARS)) != 0;       \
209                        s += len, s+= strspn(s, DELIM_CHARS))
210
211static int isbyte(int n) { return (n >= 0 && n <= 255); }
212
213static char*
214testACL( const char * s )
215{
216    int len;
217
218    FOR_EACH_WORD_IN_LIST(s, len)
219    {
220
221        char flag;
222        int  a, b, c, d, n, mask;
223
224        if( sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5 )
225            return tr_strdup_printf( _( "[%s]: subnet must be [+|-]x.x.x.x[/x]" ), s );
226        if( flag != '+' && flag != '-')
227            return tr_strdup_printf( _( "[%s]: flag must be + or -" ), s );
228        if( !isbyte(a) || !isbyte(b) || !isbyte(c) || !isbyte(d) )
229            return tr_strdup_printf( _( "[%s]: bad ip address" ), s );
230        if( sscanf(s + n, "/%d", &mask) == 1 && ( mask<0 || mask>32 ) )
231            return tr_strdup_printf( _( "[%s]: bad subnet mask %d" ), s, n );
232    }
233
234    return NULL;
235}
236
237/* 192.*.*.* --> 192.0.0.0/8
238   192.64.*.* --> 192.64.0.0/16
239   192.64.1.* --> 192.64.1.0/24
240   192.64.1.2 --> 192.64.1.2/32 */
241static void
242cidrizeOne( const char * in, int len, struct evbuffer * out )
243{
244    int stars = 0;
245    const char * pch;
246    const char * end;
247    char zero = '0';
248    char huh = '?';
249
250    for( pch=in, end=pch+len; pch!=end; ++pch ) {
251        if( stars && isdigit(*pch) )
252            evbuffer_add( out, &huh, 1 ); 
253        else if( *pch!='*' )
254            evbuffer_add( out, pch, 1 );
255        else {
256            evbuffer_add( out, &zero, 1 );
257            ++stars;
258        }
259    }
260
261    evbuffer_add_printf( out, "/%d", (32-(stars*8)));
262}
263
264char*
265cidrize( const char * acl )
266{
267    int len;
268    const char * walk = acl;
269    char * ret;
270    struct evbuffer * out = evbuffer_new( );
271
272    FOR_EACH_WORD_IN_LIST( walk, len )
273    {
274        cidrizeOne( walk, len, out );
275        evbuffer_add_printf( out, ", " );
276    }
277
278    /* the -2 is to eat the final ", " */
279    ret = tr_strndup( (char*) EVBUFFER_DATA(out), EVBUFFER_LENGTH(out)-2 );
280    evbuffer_free( out );
281    return ret;
282}
283
284int
285tr_rpcTestACL( const tr_rpc_server  * server UNUSED,
286               const char           * acl,
287               char                ** setme_errmsg )
288{
289    int err = 0;
290    char * cidr = cidrize( acl );
291    char * errmsg = testACL( cidr );
292    if( errmsg )
293    {
294        if( setme_errmsg )
295            *setme_errmsg = errmsg;
296        else
297            tr_free( errmsg );
298        err = -1;
299    }
300    tr_free( cidr );
301    return err;
302}
303
304int
305tr_rpcSetACL( tr_rpc_server   * server,
306              const char      * acl,
307              char           ** setme_errmsg )
308{
309    char * cidr = cidrize( acl );
310    const int err = tr_rpcTestACL( server, cidr, setme_errmsg );
311
312    if( !err )
313    {
314        const int isRunning = server->ctx != NULL;
315
316        if( isRunning )
317            stopServer( server );
318
319        tr_free( server->acl );
320        server->acl = tr_strdup( cidr );
321        dbgmsg( "setting our ACL to [%s]", server->acl );
322
323        if( isRunning )
324            startServer( server );
325    }
326
327    return err;
328}
329
330const char*
331tr_rpcGetACL( const tr_rpc_server * server )
332{
333    return server->acl ? server->acl : "";
334}
335
336void
337tr_rpcClose( tr_rpc_server ** ps )
338{
339    tr_rpc_server * s = *ps;
340    *ps = NULL;
341
342    stopServer( s );
343    evbuffer_free( s->in );
344    evbuffer_free( s->out );
345    tr_free( s->acl );
346    tr_free( s );
347}
348
349tr_rpc_server *
350tr_rpcInit( tr_handle   * session,
351            int           isEnabled,
352            int           port,
353            const char  * acl )
354{
355    char * errmsg;
356    tr_rpc_server * s;
357
358    if(( errmsg = testACL ( acl )))
359    {
360        tr_nerr( MY_NAME, errmsg );
361        tr_free( errmsg );
362        acl = TR_DEFAULT_RPC_ACL;
363        tr_nerr( MY_NAME, "using fallback ACL \"%s\"", acl );
364    }
365
366    s = tr_new0( tr_rpc_server, 1 );
367    s->session = session;
368    s->port = port;
369    s->in = evbuffer_new( );
370    s->out = evbuffer_new( );
371    s->acl = tr_strdup( acl );
372   
373    if( isEnabled )
374        startServer( s );
375    return s;   
376}
Note: See TracBrowser for help on using the repository browser.