source: trunk/libtransmission/session.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: 20.8 KB
Line 
1/******************************************************************************
2 * $Id: session.c 6031 2008-06-04 17:14:58Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <assert.h>
26#include <stdlib.h>
27#include <string.h> /* memcpy */
28
29#include <signal.h>
30#include <sys/types.h> /* stat */
31#include <sys/stat.h> /* stat */
32#include <unistd.h> /* stat */
33#include <dirent.h> /* opendir */
34
35#include "transmission.h"
36#include "blocklist.h"
37#include "fdlimit.h"
38#include "list.h"
39#include "metainfo.h" /* tr_metainfoFree */
40#include "net.h"
41#include "peer-mgr.h"
42#include "platform.h" /* tr_lock */
43#include "port-forwarding.h"
44#include "ratecontrol.h"
45#include "rpc-server.h"
46#include "stats.h"
47#include "torrent.h"
48#include "tracker.h"
49#include "trevent.h"
50#include "utils.h"
51#include "web.h"
52
53/* Generate a peer id : "-TRxyzb-" + 12 random alphanumeric
54   characters, where x is the major version number, y is the
55   minor version number, z is the maintenance number, and b
56   designates beta (Azureus-style) */
57uint8_t*
58tr_peerIdNew( void )
59{
60    int i;
61    int val;
62    int total = 0;
63    uint8_t * buf = tr_new( uint8_t, 21 );
64    const char * pool = "0123456789abcdefghijklmnopqrstuvwxyz";
65    const int base = 36;
66
67    memcpy( buf, PEERID_PREFIX, 8 );
68
69    for( i=8; i<19; ++i ) {
70        val = tr_rand( base );
71        total += val;
72        buf[i] = pool[val];
73    }
74
75    val = total % base ? base - (total % base) : 0;
76    total += val;
77    buf[19] = pool[val];
78    buf[20] = '\0';
79
80    return buf;
81}
82
83const uint8_t*
84tr_getPeerId( void )
85{
86    static uint8_t * id = NULL;
87    if( id == NULL )
88        id = tr_peerIdNew( );
89    return id;
90}
91
92/***
93****
94***/
95
96tr_encryption_mode
97tr_sessionGetEncryption( tr_session * session )
98{
99    assert( session != NULL );
100
101    return session->encryptionMode;
102}
103
104void
105tr_sessionSetEncryption( tr_session * session, tr_encryption_mode mode )
106{
107    assert( session != NULL );
108    assert( mode==TR_ENCRYPTION_PREFERRED
109         || mode==TR_ENCRYPTION_REQUIRED
110         || mode==TR_PLAINTEXT_PREFERRED );
111
112    session->encryptionMode = mode;
113}
114
115/***
116****
117***/
118
119static void metainfoLookupRescan( tr_handle * h );
120
121tr_handle *
122tr_sessionInitFull( const char * configDir,
123                    const char * downloadDir,
124                    const char * tag,
125                    int          isPexEnabled,
126                    int          isPortForwardingEnabled,
127                    int          publicPort,
128                    int          encryptionMode,
129                    int          isUploadLimitEnabled,
130                    int          uploadLimit,
131                    int          isDownloadLimitEnabled,
132                    int          downloadLimit,
133                    int          globalPeerLimit,
134                    int          messageLevel,
135                    int          isMessageQueueingEnabled,
136                    int          isBlocklistEnabled,
137                    int          peerSocketTOS,
138                    int          rpcIsEnabled,
139                    int          rpcPort,
140                    const char * rpcACL )
141{
142    tr_handle * h;
143    char filename[MAX_PATH_LENGTH];
144
145#ifndef WIN32
146    /* Don't exit when writing on a broken socket */
147    signal( SIGPIPE, SIG_IGN );
148#endif
149
150    if( configDir == NULL )
151        configDir = tr_getDefaultConfigDir( );
152
153    tr_msgInit( );
154    tr_setMessageLevel( messageLevel );
155    tr_setMessageQueuing( isMessageQueueingEnabled );
156
157    h = tr_new0( tr_handle, 1 );
158    h->lock = tr_lockNew( );
159    h->isPexEnabled = isPexEnabled ? 1 : 0;
160    h->encryptionMode = encryptionMode;
161    h->peerSocketTOS = peerSocketTOS;
162    h->downloadDir = tr_strdup( downloadDir );
163
164    tr_setConfigDir( h, configDir );
165
166    tr_netInit(); /* must go before tr_eventInit */
167
168    tr_eventInit( h );
169    while( !h->events )
170        tr_wait( 50 );
171
172    h->tag = tr_strdup( tag );
173    h->peerMgr = tr_peerMgrNew( h );
174
175    /* Initialize rate and file descripts controls */
176
177    h->upload = tr_rcInit();
178    tr_rcSetLimit( h->upload, uploadLimit );
179    h->useUploadLimit = isUploadLimitEnabled;
180
181    h->download = tr_rcInit();
182    tr_rcSetLimit( h->download, downloadLimit );
183    h->useDownloadLimit = isDownloadLimitEnabled;
184
185    tr_fdInit( globalPeerLimit );
186    h->shared = tr_sharedInit( h, isPortForwardingEnabled, publicPort );
187    h->isPortSet = publicPort >= 0;
188
189    /* first %s is the application name
190       second %s is the version number */
191    tr_inf( _( "%s %s started" ), TR_NAME, LONG_VERSION_STRING );
192
193    /* initialize the blocklist */
194    tr_buildPath( filename, sizeof( filename ), h->configDir, "blocklists", NULL );
195    tr_mkdirp( filename, 0777 );
196    tr_buildPath( filename, sizeof( filename ), h->configDir, "blocklists", "level1.bin", NULL );
197    h->blocklist = _tr_blocklistNew( filename, isBlocklistEnabled );
198
199    tr_statsInit( h );
200
201    h->web = tr_webInit( h );
202    h->rpcServer = tr_rpcInit( h, rpcIsEnabled, rpcPort, rpcACL );
203
204    metainfoLookupRescan( h );
205
206    return h;
207}
208
209tr_handle *
210tr_sessionInit( const char * configDir,
211                const char * downloadDir,
212                const char * tag )
213{
214    return tr_sessionInitFull( configDir,
215                               downloadDir,
216                               tag,
217                               TR_DEFAULT_PEX_ENABLED,
218                               TR_DEFAULT_PORT_FORWARDING_ENABLED,
219                               -1, /* public port */
220                               TR_ENCRYPTION_PREFERRED, /* encryption mode */
221                               FALSE, /* use upload speed limit? */ 
222                               -1, /* upload speed limit */
223                               FALSE, /* use download speed limit? */
224                               -1, /* download speed limit */
225                               TR_DEFAULT_GLOBAL_PEER_LIMIT,
226                               TR_MSG_INF, /* message level */
227                               FALSE, /* is message queueing enabled? */
228                               FALSE, /* is the blocklist enabled? */
229                               TR_DEFAULT_PEER_SOCKET_TOS,
230                               TR_DEFAULT_RPC_ENABLED,
231                               TR_DEFAULT_RPC_PORT,
232                               TR_DEFAULT_RPC_ACL );
233}
234
235/***
236****
237***/
238
239void
240tr_sessionSetDownloadDir( tr_handle * handle, const char * dir )
241{
242    if( handle->downloadDir != dir )
243    {
244        tr_free( handle->downloadDir );
245        handle->downloadDir = tr_strdup( dir );
246    }
247}
248
249const char *
250tr_sessionGetDownloadDir( const tr_handle * handle )
251{
252    return handle->downloadDir;
253}
254
255/***
256****
257***/
258
259void
260tr_globalLock( struct tr_handle * handle )
261{
262    tr_lockLock( handle->lock );
263}
264
265void
266tr_globalUnlock( struct tr_handle * handle )
267{
268    tr_lockUnlock( handle->lock );
269}
270
271int
272tr_globalIsLocked( const struct tr_handle * handle )
273{
274    return handle && tr_lockHave( handle->lock );
275}
276
277/***********************************************************************
278 * tr_setBindPort
279 ***********************************************************************
280 *
281 **********************************************************************/
282
283struct bind_port_data
284{
285    tr_handle * handle;
286    int port;
287};
288
289static void
290tr_setBindPortImpl( void * vdata )
291{
292    struct bind_port_data * data = vdata;
293    tr_handle * handle = data->handle;
294    const int port = data->port;
295
296    handle->isPortSet = 1;
297    tr_sharedSetPort( handle->shared, port );
298
299    tr_free( data );
300}
301
302void
303tr_sessionSetPeerPort( tr_handle * handle, int port )
304{
305    struct bind_port_data * data = tr_new( struct bind_port_data, 1 );
306    data->handle = handle;
307    data->port = port;
308    tr_runInEventThread( handle, tr_setBindPortImpl, data );
309}
310
311int
312tr_sessionGetPeerPort( const tr_handle * h )
313{
314    assert( h != NULL );
315    return tr_sharedGetPeerPort( h->shared );
316}
317
318tr_port_forwarding
319tr_sessionGetPortForwarding( const tr_handle * h )
320{
321    return tr_sharedTraversalStatus( h->shared );
322}
323
324/***
325****
326***/
327
328void
329tr_sessionSetSpeedLimitEnabled( tr_handle  * h,
330                                int          up_or_down,
331                                int          use_flag )
332{
333    if( up_or_down == TR_UP )
334        h->useUploadLimit = use_flag ? 1 : 0;
335    else
336        h->useDownloadLimit = use_flag ? 1 : 0;
337}
338
339int
340tr_sessionIsSpeedLimitEnabled( const tr_handle * h, int up_or_down )
341{
342       return up_or_down==TR_UP ? h->useUploadLimit : h->useDownloadLimit;
343}
344
345void
346tr_sessionSetSpeedLimit( tr_handle  * h,
347                         int          up_or_down,
348                         int          KiB_sec )
349{
350    if( up_or_down == TR_DOWN )
351        tr_rcSetLimit( h->download, KiB_sec );
352    else
353        tr_rcSetLimit( h->upload, KiB_sec );
354}
355
356int
357tr_sessionGetSpeedLimit( const tr_handle * h, int up_or_down )
358{
359    return tr_rcGetLimit( up_or_down==TR_UP ? h->upload : h->download );
360}
361
362/***
363****
364***/
365
366void
367tr_sessionSetPeerLimit( tr_handle * handle UNUSED,
368                        uint16_t    maxGlobalPeers )
369{
370    tr_fdSetPeerLimit( maxGlobalPeers );
371}
372
373uint16_t
374tr_sessionGetPeerLimit( const tr_handle * handle UNUSED )
375{
376    return tr_fdGetPeerLimit( );
377}
378
379/***
380****
381***/
382
383void
384tr_sessionGetSpeed( const tr_handle  * session,
385                    float            * toClient,
386                    float            * toPeer )
387{
388    if( session && toClient )
389        *toClient = tr_rcRate( session->download );
390    if( session && toPeer )
391        *toPeer = tr_rcRate( session->upload );
392}
393
394int
395tr_sessionCountTorrents( const tr_handle * h )
396{
397    return h->torrentCount;
398}
399
400/* close the biggest torrents first */
401static int
402compareTorrentByCur( const void * va, const void * vb )
403{
404    const tr_torrent * a = *(const tr_torrent**)va;
405    const tr_torrent * b = *(const tr_torrent**)vb;
406    return -tr_compareUint64( a->downloadedCur + a->uploadedCur,
407                              b->downloadedCur + b->uploadedCur );
408}
409
410static void
411tr_closeAllConnections( void * vh )
412{
413    tr_handle * h = vh;
414    tr_torrent * tor;
415    int i, n;
416    tr_torrent ** torrents;
417
418    tr_sharedShuttingDown( h->shared );
419    tr_trackerShuttingDown( h );
420    tr_rpcClose( &h->rpcServer );
421
422    /* close the torrents.  get the most active ones first so that
423     * if we can't get them all closed in a reasonable amount of time,
424     * at least we get the most important ones first. */
425    tor = NULL;
426    n = h->torrentCount;
427    torrents = tr_new( tr_torrent*, h->torrentCount );
428    for( i=0; i<n; ++i )
429        torrents[i] = tor = tr_torrentNext( h, tor );
430    qsort( torrents, n, sizeof(tr_torrent*), compareTorrentByCur );
431    for( i=0; i<n; ++i )
432        tr_torrentFree( torrents[i] );
433    tr_free( torrents );
434
435    tr_peerMgrFree( h->peerMgr );
436
437    tr_rcClose( h->upload );
438    tr_rcClose( h->download );
439   
440    h->isClosed = TRUE;
441}
442
443static int
444deadlineReached( const uint64_t deadline )
445{
446    return tr_date( ) >= deadline;
447}
448
449#define SHUTDOWN_MAX_SECONDS 30
450
451void
452tr_sessionClose( tr_handle * h )
453{
454    int i;
455    const int maxwait_msec = SHUTDOWN_MAX_SECONDS * 1000;
456    const uint64_t deadline = tr_date( ) + maxwait_msec;
457
458    tr_deepLog( __FILE__, __LINE__, NULL, "shutting down transmission session %p", h );
459    tr_statsClose( h );
460
461    tr_runInEventThread( h, tr_closeAllConnections, h );
462    while( !h->isClosed && !deadlineReached( deadline ) )
463        tr_wait( 100 );
464
465    _tr_blocklistFree( h->blocklist );
466    h->blocklist = NULL;
467    tr_webClose( &h->web );
468
469    tr_eventClose( h );
470    while( h->events && !deadlineReached( deadline ) )
471        tr_wait( 100 );
472
473    tr_fdClose( );
474    tr_lockFree( h->lock );
475    for( i=0; i<h->metainfoLookupCount; ++i )
476        tr_free( h->metainfoLookup[i].filename );
477    tr_free( h->metainfoLookup );
478    tr_free( h->tag );
479    tr_free( h->configDir );
480    tr_free( h->resumeDir );
481    tr_free( h->torrentDir );
482    tr_free( h->downloadDir );
483    free( h );
484}
485
486tr_torrent **
487tr_sessionLoadTorrents ( tr_handle   * h,
488                         tr_ctor     * ctor,
489                         int         * setmeCount )
490{
491    int i, n = 0;
492    struct stat sb;
493    DIR * odir = NULL;
494    const char * dirname = tr_getTorrentDir( h );
495    tr_torrent ** torrents;
496    tr_list *l=NULL, *list=NULL;
497
498    tr_ctorSetSave( ctor, FALSE ); /* since we already have them */
499
500    if( !stat( dirname, &sb )
501        && S_ISDIR( sb.st_mode )
502        && (( odir = opendir ( dirname ) )) )
503    {
504        struct dirent *d;
505        for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
506        {
507            if( d->d_name && d->d_name[0]!='.' ) /* skip dotfiles, ., and .. */
508            {
509                tr_torrent * tor;
510                char filename[MAX_PATH_LENGTH];
511                tr_buildPath( filename, sizeof(filename), dirname, d->d_name, NULL );
512                tr_ctorSetMetainfoFromFile( ctor, filename );
513                tor = tr_torrentNew( h, ctor, NULL );
514                if( tor ) {
515                    tr_list_append( &list, tor );
516                    n++;
517                }
518            }
519        }
520        closedir( odir );
521    }
522
523    torrents = tr_new( tr_torrent*, n );
524    for( i=0, l=list; l!=NULL; l=l->next )
525        torrents[i++] = (tr_torrent*) l->data;
526    assert( i==n );
527
528    tr_list_free( &list, NULL );
529
530    if( n )
531        tr_inf( _( "Loaded %d torrents" ), n );
532
533    if( setmeCount )
534        *setmeCount = n;
535    return torrents;
536}
537
538/***
539****
540***/
541
542void
543tr_sessionSetPexEnabled( tr_handle * handle, int isPexEnabled )
544{
545    handle->isPexEnabled = isPexEnabled ? 1 : 0;
546}
547
548int
549tr_sessionIsPexEnabled( const tr_handle * handle )
550{
551    return handle->isPexEnabled;
552}
553
554/***
555****
556***/
557
558void
559tr_sessionSetPortForwardingEnabled( tr_handle * h, int enable )
560{
561    tr_globalLock( h );
562    tr_sharedTraversalEnable( h->shared, enable );
563    tr_globalUnlock( h );
564}
565
566int
567tr_sessionIsPortForwardingEnabled( const tr_handle * h )
568{
569    return tr_sharedTraversalIsEnabled( h->shared );
570}
571
572/***
573****
574***/
575
576int
577tr_blocklistGetRuleCount( tr_handle * handle )
578{
579    return _tr_blocklistGetRuleCount( handle->blocklist );
580}
581
582int
583tr_blocklistIsEnabled( const tr_handle * handle )
584{
585    return _tr_blocklistIsEnabled( handle->blocklist );
586}
587
588void
589tr_blocklistSetEnabled( tr_handle * handle, int isEnabled )
590{
591    _tr_blocklistSetEnabled( handle->blocklist, isEnabled );
592}
593
594int
595tr_blocklistExists( const tr_handle * handle )
596{
597    return _tr_blocklistExists( handle->blocklist );
598}
599
600int
601tr_blocklistSetContent( tr_handle  * handle, const char * filename )
602{
603    return _tr_blocklistSetContent( handle->blocklist, filename );
604}
605
606int
607tr_blocklistHasAddress( tr_handle * handle, const struct in_addr * addr )
608{
609    return _tr_blocklistHasAddress( handle->blocklist, addr );
610}
611
612/***
613****
614***/
615
616static int
617compareLookupEntries( const void * va, const void * vb )
618{
619    const struct tr_metainfo_lookup * a = va;
620    const struct tr_metainfo_lookup * b = vb;
621    return strcmp( a->hashString, b->hashString );
622}
623
624static void
625metainfoLookupResort( tr_handle * h )
626{
627    qsort( h->metainfoLookup, 
628           h->metainfoLookupCount,
629           sizeof( struct tr_metainfo_lookup ),
630           compareLookupEntries );
631}
632
633static int
634compareHashStringToLookupEntry( const void * va, const void * vb )
635{
636    const char * a = va;
637    const struct tr_metainfo_lookup * b = vb;
638    return strcmp( a, b->hashString );
639}
640
641const char*
642tr_sessionFindTorrentFile( const tr_handle  * h,
643                           const char       * hashStr )
644{
645    struct tr_metainfo_lookup * l = bsearch( hashStr,
646                                             h->metainfoLookup,
647                                             h->metainfoLookupCount,
648                                             sizeof( struct tr_metainfo_lookup ),
649                                             compareHashStringToLookupEntry );
650    return l ? l->filename : NULL;
651}
652
653static void
654metainfoLookupRescan( tr_handle * h )
655{
656    int i;
657    int n;
658    struct stat sb;
659    const char * dirname = tr_getTorrentDir( h );
660    DIR * odir = NULL;
661    tr_ctor * ctor = NULL;
662    tr_list * list = NULL;
663
664    /* walk through the directory and find the mappings */
665    ctor = tr_ctorNew( h );
666    tr_ctorSetSave( ctor, FALSE ); /* since we already have them */
667    if( !stat( dirname, &sb ) && S_ISDIR( sb.st_mode ) && (( odir = opendir( dirname ))))
668    {
669        struct dirent *d;
670        for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
671        {
672            if( d->d_name && d->d_name[0]!='.' ) /* skip dotfiles, ., and .. */
673            {
674                tr_info inf;
675                char filename[MAX_PATH_LENGTH];
676                tr_buildPath( filename, sizeof(filename), dirname, d->d_name, NULL );
677                tr_ctorSetMetainfoFromFile( ctor, filename );
678                if( !tr_torrentParse( h, ctor, &inf ) )
679                {
680                    tr_list_append( &list, tr_strdup( inf.hashString ) );
681                    tr_list_append( &list, tr_strdup( filename ) );
682                    tr_metainfoFree( &inf );
683                }
684            }
685        }
686        closedir( odir );
687    }
688    tr_ctorFree( ctor );
689
690    n = tr_list_size( list ) / 2;
691    h->metainfoLookup = tr_new0( struct tr_metainfo_lookup, n );
692    h->metainfoLookupCount = n;
693    for( i=0; i<n; ++i )
694    {
695        char * hashString = tr_list_pop_front( &list );
696        char * filename = tr_list_pop_front( &list );
697
698        memcpy( h->metainfoLookup[i].hashString, hashString, 2*SHA_DIGEST_LENGTH+1 );
699        tr_free( hashString );
700        h->metainfoLookup[i].filename = filename;
701    }
702
703    metainfoLookupResort( h );
704    tr_dbg( "Found %d torrents in \"%s\"", n, dirname );
705}
706
707void
708tr_sessionSetTorrentFile( tr_handle    * h,
709                          const char   * hashString,
710                          const char   * filename )
711{
712    struct tr_metainfo_lookup * l = bsearch( hashString,
713                                             h->metainfoLookup,
714                                             h->metainfoLookupCount,
715                                             sizeof( struct tr_metainfo_lookup ),
716                                             compareHashStringToLookupEntry );
717    if( l != NULL )
718    {
719        if( l->filename != filename )
720        {
721            tr_free( l->filename );
722            l->filename = tr_strdup( filename );
723        }
724    }
725    else
726    {
727        const int n = h->metainfoLookupCount++;
728        struct tr_metainfo_lookup * node;
729        h->metainfoLookup = tr_renew( struct tr_metainfo_lookup,
730                                      h->metainfoLookup,
731                                      h->metainfoLookupCount );
732        node = h->metainfoLookup + n;
733        memcpy( node->hashString, hashString, 2*SHA_DIGEST_LENGTH+1 );
734        node->filename = tr_strdup( filename );
735        metainfoLookupResort( h );
736    }
737}
738
739tr_torrent*
740tr_torrentNext( tr_handle * session, tr_torrent * tor )
741{
742    return tor ? tor->next : session->torrentList;
743}
744
745/***
746****
747***/
748
749void
750tr_sessionSetRPCEnabled( tr_handle * session, int isEnabled )
751{
752    tr_rpcSetEnabled( session->rpcServer, isEnabled );
753}
754int
755tr_sessionIsRPCEnabled( const tr_handle * session )
756{
757    return tr_rpcIsEnabled( session->rpcServer );
758}
759void
760tr_sessionSetRPCPort( tr_handle * session, int port )
761{
762    tr_rpcSetPort( session->rpcServer, port );
763}
764int
765tr_sessionGetRPCPort( const tr_handle * session )
766{
767    return tr_rpcGetPort( session->rpcServer );
768}
769void
770tr_sessionSetRPCCallback( tr_handle    * session,
771                          tr_rpc_func    func,
772                          void         * user_data )
773{
774    session->rpc_func = func;
775    session->rpc_func_user_data = user_data;
776}
777
778int
779tr_sessionTestRPCACL( const tr_handle  * session,
780                      const char       * acl,
781                      char            ** allocme_errmsg )
782{
783    return tr_rpcTestACL( session->rpcServer, acl, allocme_errmsg );
784}
785
786int
787tr_sessionSetRPCACL( tr_handle    * session,
788                     const char   * acl,
789                     char        ** allocme_errmsg )
790{
791    return tr_rpcSetACL( session->rpcServer, acl, allocme_errmsg );
792}
793
794const char*
795tr_sessionGetRPCACL( const tr_session * session )
796{
797    return tr_rpcGetACL( session->rpcServer );
798}
Note: See TracBrowser for help on using the repository browser.