source: trunk/libtransmission/blocklist.c @ 11888

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

(trunk libT) #4016 "blocklists don't handle overlapping/unsorted rules" -- fixed.

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