source: trunk/libtransmission/blocklist.c @ 12918

Last change on this file since 12918 was 12229, checked in by jordan, 11 years ago

(trunk libT) API cleanup of the tr_address functions to make them more consistent.

This is loosely related to #2910, but only in the sense of laying the groundwork for #2910's fix...

  • Property svn:keywords set to Date Rev Author Id
File size: 9.6 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
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: blocklist.c 12229 2011-03-25 05:34:26Z jordan $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <stdio.h>
16#include <stdlib.h> /* bsearch(), qsort() */
17#include <string.h>
18
19#include <unistd.h> /* unlink() */
20
21#ifdef WIN32
22 #include <w32api.h>
23 #define WINVER  WindowsXP
24 #include <windows.h>
25 #define PROT_READ      PAGE_READONLY
26 #define MAP_PRIVATE    FILE_MAP_COPY
27#endif
28
29#ifndef WIN32
30 #include <sys/mman.h>
31#endif
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <fcntl.h>
35
36#include "transmission.h"
37#include "blocklist.h"
38#include "net.h"
39#include "utils.h"
40
41#ifndef O_BINARY
42 #define O_BINARY 0
43#endif
44
45
46/***
47****  PRIVATE
48***/
49
50struct tr_ipv4_range
51{
52    uint32_t    begin;
53    uint32_t    end;
54};
55
56struct tr_blocklist
57{
58    bool                   isEnabled;
59    int                    fd;
60    size_t                 ruleCount;
61    size_t                 byteCount;
62    char *                 filename;
63    struct tr_ipv4_range * rules;
64};
65
66static void
67blocklistClose( tr_blocklist * b )
68{
69    if( b->rules )
70    {
71        munmap( b->rules, b->byteCount );
72        close( b->fd );
73        b->rules = NULL;
74        b->ruleCount = 0;
75        b->byteCount = 0;
76        b->fd = -1;
77    }
78}
79
80static void
81blocklistLoad( tr_blocklist * b )
82{
83    int fd;
84    size_t byteCount;
85    struct stat st;
86    const char * err_fmt = _( "Couldn't read \"%1$s\": %2$s" );
87
88    blocklistClose( b );
89
90    if( stat( b->filename, &st ) == -1 )
91        return;
92
93    fd = open( b->filename, O_RDONLY | O_BINARY );
94    if( fd == -1 )
95    {
96        tr_err( err_fmt, b->filename, tr_strerror( errno ) );
97        return;
98    }
99
100    byteCount = (size_t) st.st_size;
101    b->rules = mmap( NULL, byteCount, PROT_READ, MAP_PRIVATE, fd, 0 );
102    if( !b->rules )
103    {
104        tr_err( err_fmt, b->filename, tr_strerror( errno ) );
105        close( fd );
106        return;
107    }
108
109    b->fd = fd;
110    b->byteCount = byteCount;
111    b->ruleCount = byteCount / sizeof( struct tr_ipv4_range );
112
113    {
114        char * base = tr_basename( b->filename );
115        tr_inf( _( "Blocklist \"%s\" contains %zu entries" ), base, b->ruleCount );
116        tr_free( base );
117    }
118}
119
120static void
121blocklistEnsureLoaded( tr_blocklist * b )
122{
123    if( !b->rules )
124        blocklistLoad( b );
125}
126
127static int
128compareAddressToRange( const void * va,
129                       const void * vb )
130{
131    const uint32_t *             a = va;
132    const struct tr_ipv4_range * b = vb;
133
134    if( *a < b->begin ) return -1;
135    if( *a > b->end ) return 1;
136    return 0;
137}
138
139static void
140blocklistDelete( tr_blocklist * b )
141{
142    blocklistClose( b );
143    unlink( b->filename );
144}
145
146/***
147****  PACKAGE-VISIBLE
148***/
149
150tr_blocklist *
151_tr_blocklistNew( const char * filename, bool isEnabled )
152{
153    tr_blocklist * b;
154
155    b = tr_new0( tr_blocklist, 1 );
156    b->fd = -1;
157    b->filename = tr_strdup( filename );
158    b->isEnabled = isEnabled;
159
160    return b;
161}
162
163const char*
164_tr_blocklistGetFilename( const tr_blocklist * b )
165{
166    return b->filename;
167}
168
169void
170_tr_blocklistFree( tr_blocklist * b )
171{
172    blocklistClose( b );
173    tr_free( b->filename );
174    tr_free( b );
175}
176
177int
178_tr_blocklistExists( const tr_blocklist * b )
179{
180    struct stat st;
181
182    return !stat( b->filename, &st );
183}
184
185int
186_tr_blocklistGetRuleCount( const tr_blocklist * b )
187{
188    blocklistEnsureLoaded( (tr_blocklist*)b );
189
190    return b->ruleCount;
191}
192
193int
194_tr_blocklistIsEnabled( tr_blocklist * b )
195{
196    return b->isEnabled;
197}
198
199void
200_tr_blocklistSetEnabled( tr_blocklist * b, bool isEnabled )
201{
202    b->isEnabled = isEnabled ? 1 : 0;
203}
204
205int
206_tr_blocklistHasAddress( tr_blocklist * b, const tr_address * addr )
207{
208    uint32_t                     needle;
209    const struct tr_ipv4_range * range;
210
211    assert( tr_address_is_valid( addr ) );
212
213    if( !b->isEnabled || addr->type == TR_AF_INET6 )
214        return 0;
215
216    blocklistEnsureLoaded( b );
217
218    if( !b->rules || !b->ruleCount )
219        return 0;
220
221    needle = ntohl( addr->addr.addr4.s_addr );
222
223    range = bsearch( &needle,
224                     b->rules,
225                     b->ruleCount,
226                     sizeof( struct tr_ipv4_range ),
227                     compareAddressToRange );
228
229    return range != NULL;
230}
231
232/*
233 * P2P plaintext format: "comment:x.x.x.x-y.y.y.y"
234 * http://wiki.phoenixlabs.org/wiki/P2P_Format
235 * http://en.wikipedia.org/wiki/PeerGuardian#P2P_plaintext_format
236 */
237static bool
238parseLine1( const char * line, struct tr_ipv4_range * range )
239{
240    char * walk;
241    int b[4];
242    int e[4];
243    char str[64];
244    tr_address addr;
245
246    walk = strrchr( line, ':' );
247    if( !walk )
248        return false;
249    ++walk; /* walk past the colon */
250
251    if( sscanf( walk, "%d.%d.%d.%d-%d.%d.%d.%d",
252                &b[0], &b[1], &b[2], &b[3],
253                &e[0], &e[1], &e[2], &e[3] ) != 8 )
254        return false;
255
256    tr_snprintf( str, sizeof( str ), "%d.%d.%d.%d", b[0], b[1], b[2], b[3] );
257    if( !tr_address_from_string( &addr, str ) )
258        return false;
259    range->begin = ntohl( addr.addr.addr4.s_addr );
260
261    tr_snprintf( str, sizeof( str ), "%d.%d.%d.%d", e[0], e[1], e[2], e[3] );
262    if( !tr_address_from_string( &addr, str ) )
263        return false;
264    range->end = ntohl( addr.addr.addr4.s_addr );
265
266    return true;
267}
268
269/*
270 * DAT format: "000.000.000.000 - 000.255.255.255 , 000 , invalid ip"
271 * http://wiki.phoenixlabs.org/wiki/DAT_Format
272 */
273static bool
274parseLine2( const char * line, struct tr_ipv4_range * range )
275{
276    int unk;
277    int a[4];
278    int b[4];
279    char str[32];
280    tr_address addr;
281
282    if( sscanf( line, "%3d.%3d.%3d.%3d - %3d.%3d.%3d.%3d , %3d , ",
283                &a[0], &a[1], &a[2], &a[3],
284                &b[0], &b[1], &b[2], &b[3],
285                &unk ) != 9 )
286        return false;
287
288    tr_snprintf( str, sizeof(str), "%d.%d.%d.%d", a[0], a[1], a[2], a[3] );
289    if( !tr_address_from_string( &addr, str ) )
290        return false;
291    range->begin = ntohl( addr.addr.addr4.s_addr );
292
293    tr_snprintf( str, sizeof(str), "%d.%d.%d.%d", b[0], b[1], b[2], b[3] );
294    if( !tr_address_from_string( &addr, str ) )
295        return false;
296    range->end = ntohl( addr.addr.addr4.s_addr );
297
298    return true;
299}
300
301static int
302parseLine( const char * line, struct tr_ipv4_range * range )
303{
304    return parseLine1( line, range )
305        || parseLine2( line, range );
306}
307
308static int
309compareAddressRangesByFirstAddress( const void * va, const void * vb )
310{
311    const struct tr_ipv4_range * a = va;
312    const struct tr_ipv4_range * b = vb;
313    if( a->begin != b->begin )
314        return a->begin < b->begin ? -1 : 1;
315    return 0;
316}
317
318int
319_tr_blocklistSetContent( tr_blocklist * b, const char * filename )
320{
321    FILE * in;
322    FILE * out;
323    int inCount = 0;
324    char line[2048];
325    const char * err_fmt = _( "Couldn't read \"%1$s\": %2$s" );
326    struct tr_ipv4_range * ranges = NULL;
327    size_t ranges_alloc = 0;
328    size_t ranges_count = 0;
329
330    if( !filename )
331    {
332        blocklistDelete( b );
333        return 0;
334    }
335
336    in = fopen( filename, "rb" );
337    if( !in )
338    {
339        tr_err( err_fmt, filename, tr_strerror( errno ) );
340        return 0;
341    }
342
343    blocklistClose( b );
344
345    out = fopen( b->filename, "wb+" );
346    if( !out )
347    {
348        tr_err( err_fmt, b->filename, tr_strerror( errno ) );
349        fclose( in );
350        return 0;
351    }
352
353    /* load the rules into memory */
354    while( fgets( line, sizeof( line ), in ) != NULL )
355    {
356        char * walk;
357        struct tr_ipv4_range range;
358
359        ++inCount;
360
361        /* zap the linefeed */
362        if(( walk = strchr( line, '\r' ))) *walk = '\0';
363        if(( walk = strchr( line, '\n' ))) *walk = '\0';
364
365        if( !parseLine( line, &range ) )
366        {
367            /* don't try to display the actual lines - it causes issues */
368            tr_err( _( "blocklist skipped invalid address at line %d" ), inCount );
369            continue;
370        }
371
372        if( ranges_alloc == ranges_count )
373        {
374            ranges_alloc += 4096; /* arbitrary */
375            ranges = tr_renew( struct tr_ipv4_range, ranges, ranges_alloc );
376        }
377
378        ranges[ranges_count++] = range;
379    }
380
381    if( ranges_count > 0 ) /* sort and merge */
382    {
383        struct tr_ipv4_range * r;
384        struct tr_ipv4_range * keep = ranges;
385        const struct tr_ipv4_range * end;
386
387        /* sort */
388        qsort( ranges, ranges_count, sizeof( struct tr_ipv4_range ),
389               compareAddressRangesByFirstAddress );
390
391        /* merge */
392        for( r=ranges+1, end=ranges+ranges_count; r!=end; ++r ) {
393            if( keep->end < r->begin )
394                *++keep = *r;
395            else if( keep->end < r->end )
396                keep->end = r->end;
397        }
398
399        ranges_count = keep + 1 - ranges;
400
401#ifndef NDEBUG
402        /* sanity checks: make sure the rules are sorted
403         * in ascending order and don't overlap */
404        {
405            size_t i;
406
407            for( i=0; i<ranges_count; ++i )
408                assert( ranges[i].begin <= ranges[i].end );
409
410            for( i=1; i<ranges_count; ++i )
411                assert( ranges[i-1].end < ranges[i].begin );
412        }
413#endif
414    }
415
416    if( fwrite( ranges, sizeof( struct tr_ipv4_range ), ranges_count, out ) != ranges_count )
417        tr_err( _( "Couldn't save file \"%1$s\": %2$s" ), b->filename, tr_strerror( errno ) );
418    else {
419        char * base = tr_basename( b->filename );
420        tr_inf( _( "Blocklist \"%s\" updated with %zu entries" ), base, ranges_count );
421        tr_free( base );
422    }
423
424    tr_free( ranges );
425    fclose( out );
426    fclose( in );
427
428    blocklistLoad( b );
429
430    return ranges_count;
431}
Note: See TracBrowser for help on using the repository browser.