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

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

(libT RPC server): poll more frequently for new commands after we've received the first one.

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