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} 432