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

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

(libT): added an ACL tester to tr_sessionSetRPCACL() and return an error string if the ACL can't be parsed.

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