source: trunk/libtransmission/blocklist.c @ 13631

Last change on this file since 13631 was 13631, checked in by jordan, 9 years ago

(trunk, libT) #5165: fix r13625 oops

  • Property svn:keywords set to Date Rev Author Id
File size: 9.5 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 13631 2012-12-07 01:53:31Z 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.