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

Last change on this file since 6005 was 6005, checked in by charles, 15 years ago

(libT) if the ACL passed in via tr_sessionInitFull() can't be parsed, log an tr_err() and use a TR_DEFAULT_RPC_ACL as the fallback.

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