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

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

RPC ACL: (1) add a new call for testing ACLs (2) add wildcard notation support (3) add regression tests for the ACL tester and wildcard handler

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