mirror of https://github.com/rspamd/rspamd.git
				
				
			
				Rapid spam filtering system
				https://rspamd.com/
			
			
				
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							358 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							358 lines
						
					
					
						
							10 KiB
						
					
					
				
								#!/usr/bin/env perl
							 | 
						|
								
							 | 
						|
								use warnings;
							 | 
						|
								use strict;
							 | 
						|
								
							 | 
						|
								use File::Basename;
							 | 
						|
								use File::Fetch;
							 | 
						|
								use Getopt::Long;
							 | 
						|
								use IPC::Cmd qw/can_run/;
							 | 
						|
								use Pod::Usage;
							 | 
						|
								
							 | 
						|
								use LWP::Simple;
							 | 
						|
								use PerlIO::gzip;
							 | 
						|
								use URI;
							 | 
						|
								
							 | 
						|
								$LWP::Simple::ua->show_progress(1);
							 | 
						|
								
							 | 
						|
								my %config = (
							 | 
						|
								    asn_sources => [
							 | 
						|
								        'ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest',
							 | 
						|
								        'ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest',
							 | 
						|
								        'ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest',
							 | 
						|
								        'ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest',
							 | 
						|
								        'ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest'
							 | 
						|
								    ],
							 | 
						|
								    bgp_sources => ['http://data.ris.ripe.net/rrc00/latest-bview.gz']
							 | 
						|
								);
							 | 
						|
								
							 | 
						|
								my $download_asn    = 0;
							 | 
						|
								my $download_bgp    = 0;
							 | 
						|
								my $download_target = "./";
							 | 
						|
								my $help            = 0;
							 | 
						|
								my $man             = 0;
							 | 
						|
								my $v4              = 1;
							 | 
						|
								my $v6              = 1;
							 | 
						|
								my $parse           = 1;
							 | 
						|
								my $v4_zone         = "asn.rspamd.com";
							 | 
						|
								my $v6_zone         = "asn6.rspamd.com";
							 | 
						|
								my $v4_file         = "asn.zone";
							 | 
						|
								my $v6_file         = "asn6.zone";
							 | 
						|
								my $ns_servers      = [ "asn-ns.rspamd.com", "asn-ns2.rspamd.com" ];
							 | 
						|
								my $use_bgpdump     = 0;
							 | 
						|
								
							 | 
						|
								GetOptions(
							 | 
						|
								    "download-asn" => \$download_asn,
							 | 
						|
								    "download-bgp" => \$download_bgp,
							 | 
						|
								    "4!"           => \$v4,
							 | 
						|
								    "6!"           => \$v6,
							 | 
						|
								    "parse!"       => \$parse,
							 | 
						|
								    "target=s"     => \$download_target,
							 | 
						|
								    "zone-v4=s"    => \$v4_zone,
							 | 
						|
								    "zone-v6=s"    => \$v6_zone,
							 | 
						|
								    "file-v4=s"    => \$v4_file,
							 | 
						|
								    "file-v6=s"    => \$v6_file,
							 | 
						|
								    "ns-server=s@" => \$ns_servers,
							 | 
						|
								    "help|?"       => \$help,
							 | 
						|
								    "man"          => \$man,
							 | 
						|
								) or pod2usage(2);
							 | 
						|
								
							 | 
						|
								pod2usage(1) if $help;
							 | 
						|
								pod2usage( -exitval => 0, -verbose => 2 ) if $man;
							 | 
						|
								
							 | 
						|
								my $bgpdump_path = can_run('bgpdump')
							 | 
						|
								    or warn 'bgpdump is not found, will try to use Net::MRT instead; results can be incomplete';
							 | 
						|
								
							 | 
						|
								sub download_file {
							 | 
						|
								    my ($u) = @_;
							 | 
						|
								
							 | 
						|
								    print "Fetching $u\n";
							 | 
						|
								    my $ff    = File::Fetch->new( uri => $u );
							 | 
						|
								    my $where = $ff->fetch( to => $download_target ) or die $ff->error;
							 | 
						|
								
							 | 
						|
								    return $where;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								if ($download_asn) {
							 | 
						|
								    foreach my $u ( @{ $config{'asn_sources'} } ) {
							 | 
						|
								        download_file($u);
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								if ($download_bgp) {
							 | 
						|
								    foreach my $u ( @{ $config{'bgp_sources'} } ) {
							 | 
						|
								        download_file($u);
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								if ( !$parse ) {
							 | 
						|
								    exit 0;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								my $v4_fh;
							 | 
						|
								my $v6_fh;
							 | 
						|
								
							 | 
						|
								if ($v4) {
							 | 
						|
								    open( $v4_fh, ">", $v4_file ) or die "Cannot open $v4_file for writing: $!";
							 | 
						|
								    print $v4_fh "\$SOA 43200 $ns_servers->[0] support.rspamd.com 0 600 300 86400 300\n";
							 | 
						|
								    foreach my $ns ( @{$ns_servers} ) {
							 | 
						|
								        print $v4_fh "\$NS 43200 $ns\n";
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								if ($v6) {
							 | 
						|
								    open( $v6_fh, ">", $v6_file ) or die "Cannot open $v6_file for writing: $!";
							 | 
						|
								    print $v6_fh "\$SOA 43200 $ns_servers->[0] support.rspamd.com 0 600 300 86400 300\n";
							 | 
						|
								    foreach my $ns ( @{$ns_servers} ) {
							 | 
						|
								        print $v6_fh "\$NS 43200 $ns\n";
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								sub is_bougus_asn {
							 | 
						|
								    my $as = shift;
							 | 
						|
								    # 64496-64511 Reserved for use in documentation and sample code
							 | 
						|
								    # 64512-65534 Designated for private use
							 | 
						|
								    # 65535       Reserved
							 | 
						|
								    # 65536-65551 Reserved for use in documentation and sample code
							 | 
						|
								    # 65552-131071 Reserved
							 | 
						|
								    return 1 if $as >= 64496 && $as <= 131071;
							 | 
						|
								    # 4294967295
							 | 
						|
								    return 1 if $as == 4294967295;
							 | 
						|
								    # AS0 is reserved
							 | 
						|
								    # AS1 is legal AS, but in most cases used by others without permission
							 | 
						|
								    # of owner (probably lame admins use AS1 as private AS).
							 | 
						|
								    return 1 if $as <= 1;
							 | 
						|
								
							 | 
						|
								    return 0;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Now load BGP data
							 | 
						|
								my $networks = {};
							 | 
						|
								
							 | 
						|
								foreach my $u ( @{ $config{'bgp_sources'} } ) {
							 | 
						|
								    my $parsed = URI->new($u);
							 | 
						|
								    my $fname  = $download_target . '/' . basename( $parsed->path );
							 | 
						|
								
							 | 
						|
								    if ($bgpdump_path) {
							 | 
						|
								        use constant {
							 | 
						|
								          F_MARKER => 0,
							 | 
						|
								          F_TIMESTAMP => 1,
							 | 
						|
								          F_PEER_IP => 3,
							 | 
						|
								          F_PEER_AS => 4,
							 | 
						|
								          F_PREFIX => 5,
							 | 
						|
								          F_AS_PATH => 6,
							 | 
						|
								          F_ORIGIN => 7,
							 | 
						|
								        };
							 | 
						|
								
							 | 
						|
								        open(my $bgpd, '-|', "$bgpdump_path -v -M $fname") or die "can't start bgpdump: $!";
							 | 
						|
								
							 | 
						|
								        while (<$bgpd>) {
							 | 
						|
								            chomp;
							 | 
						|
								            my @e = split /\|/;
							 | 
						|
								            if ($e[F_MARKER] ne 'TABLE_DUMP2') {
							 | 
						|
								                warn "bad line: $_\n";
							 | 
						|
								                next;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            my $origin_as;
							 | 
						|
								            my $prefix = $e[F_PREFIX];
							 | 
						|
								            my $ipv6 = 0;
							 | 
						|
								
							 | 
						|
								            if ($prefix !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\/\d{1,2}$/) {
							 | 
						|
								                $ipv6 = 1;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            if ($e[F_AS_PATH]) {
							 | 
						|
								                # not empty AS_PATH
							 | 
						|
								                my @as_path = split /\s/, $e[F_AS_PATH];
							 | 
						|
								                $origin_as = pop @as_path;
							 | 
						|
								
							 | 
						|
								                if (substr($origin_as, 0, 1) eq '{') {
							 | 
						|
								                    # route is aggregated
							 | 
						|
								                    if ($origin_as =~ /^{(\d+)}$/) {
							 | 
						|
								                        # single AS aggregated, just remove { } around
							 | 
						|
								                        $origin_as = $1;
							 | 
						|
								                    } else {
							 | 
						|
								                        # use previous AS from AS_PATH
							 | 
						|
								                        $origin_as = pop @as_path;
							 | 
						|
								                    }
							 | 
						|
								                }
							 | 
						|
								                # strip bogus AS
							 | 
						|
								                while (is_bougus_asn($origin_as)) {
							 | 
						|
								                    $origin_as = pop @as_path;
							 | 
						|
								                    last if scalar @as_path == 0;
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								            # empty AS_PATH or all AS_PATH elements is stripped as bogus - use PEER_AS is origin AS
							 | 
						|
								            $origin_as //= $e[F_PEER_AS];
							 | 
						|
								
							 | 
						|
								            if (!$networks->{$origin_as}) {
							 | 
						|
								                if (!$ipv6) {
							 | 
						|
								                    $networks->{$origin_as} = { nets_v4 => [ $prefix ], nets_v6 => [] };
							 | 
						|
								                }
							 | 
						|
								                else {
							 | 
						|
								                    $networks->{$origin_as} = { nets_v6 => [ $prefix ], nets_v4 => [] };
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								            else {
							 | 
						|
								                if (!$ipv6) {
							 | 
						|
								                    push @{$networks->{$origin_as}->{'nets_v4'}}, $prefix;
							 | 
						|
								                }
							 | 
						|
								                else {
							 | 
						|
								                    push @{$networks->{$origin_as}->{'nets_v6'}}, $prefix;
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								    else {
							 | 
						|
								        require Net::MRT;
							 | 
						|
								        $Net::MRT::USE_RFC4760 = -1;
							 | 
						|
								
							 | 
						|
								        open( my $fh, "<:gzip", $fname )
							 | 
						|
								          or die "Cannot open $fname: $!";
							 | 
						|
								        while (my $dd = eval {Net::MRT::mrt_read_next($fh)}) {
							 | 
						|
								            if ($dd->{'prefix'} && $dd->{'bits'}) {
							 | 
						|
								                next if $dd->{'subtype'} == 2 and !$v4;
							 | 
						|
								                next if $dd->{'subtype'} == 4 and !$v6;
							 | 
						|
								                my $entry = $dd->{'entries'}->[0];
							 | 
						|
								                my $net = $dd->{'prefix'} . '/' . $dd->{'bits'};
							 | 
						|
								                if ($entry && $entry->{'AS_PATH'}) {
							 | 
						|
								                    my $as = $entry->{'AS_PATH'}->[-1];
							 | 
						|
								                    if (ref($as) eq "ARRAY") {
							 | 
						|
								                        $as = @{$as}[0];
							 | 
						|
								                    }
							 | 
						|
								
							 | 
						|
								                    next if (is_bougus_asn($as));
							 | 
						|
								
							 | 
						|
								                    if (!$networks->{$as}) {
							 | 
						|
								                        if ($dd->{'subtype'} == 2) {
							 | 
						|
								                            $networks->{$as} = { nets_v4 => [ $net ], nets_v6 => [] };
							 | 
						|
								                        }
							 | 
						|
								                        else {
							 | 
						|
								                            $networks->{$as} = { nets_v6 => [ $net ], nets_v4 => [] };
							 | 
						|
								                        }
							 | 
						|
								                    }
							 | 
						|
								                    else {
							 | 
						|
								
							 | 
						|
								                        if ($dd->{'subtype'} == 2) {
							 | 
						|
								                            push @{$networks->{$as}->{'nets_v4'}}, $net;
							 | 
						|
								                        }
							 | 
						|
								                        else {
							 | 
						|
								                            push @{$networks->{$as}->{'nets_v6'}}, $net;
							 | 
						|
								                        }
							 | 
						|
								                    }
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								# Now roughly detect countries
							 | 
						|
								foreach my $u ( @{ $config{'asn_sources'} } ) {
							 | 
						|
								    my $parsed = URI->new($u);
							 | 
						|
								    my $fname  = $download_target . '/' . basename( $parsed->path );
							 | 
						|
								    open( my $fh, "<", $fname ) or die "Cannot open $fname: $!";
							 | 
						|
								
							 | 
						|
								    while (<$fh>) {
							 | 
						|
								        next if /^\#/;
							 | 
						|
								        chomp;
							 | 
						|
								        my @elts = split /\|/;
							 | 
						|
								
							 | 
						|
								        if ( $elts[2] eq 'asn' && $elts[3] ne '*' ) {
							 | 
						|
								            my $as_start = int( $elts[3] );
							 | 
						|
								            my $as_end   = $as_start + int( $elts[4] );
							 | 
						|
								
							 | 
						|
								            for ( my $as = $as_start ; $as < $as_end ; $as++ ) {
							 | 
						|
								                my $real_as = $as;
							 | 
						|
								
							 | 
						|
								                if ( ref($as) eq "ARRAY" ) {
							 | 
						|
								                    $real_as = @{$as}[0];
							 | 
						|
								                }
							 | 
						|
								
							 | 
						|
								                if ( $networks->{"$real_as"} ) {
							 | 
						|
								                    $networks->{"$real_as"}->{'country'} = $elts[1];
							 | 
						|
								                    $networks->{"$real_as"}->{'rir'}     = $elts[0];
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								while ( my ( $k, $v ) = each( %{$networks} ) ) {
							 | 
						|
								    if ($v4) {
							 | 
						|
								        foreach my $n ( @{ $v->{'nets_v4'} } ) {
							 | 
						|
								
							 | 
						|
								            # "15169 | 8.8.8.0/24 | US | arin |" for 8.8.8.8
							 | 
						|
								            if ( $v->{'country'} ) {
							 | 
						|
								                printf $v4_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, $v->{'country'}, $v->{'rir'};
							 | 
						|
								            }
							 | 
						|
								            else {
							 | 
						|
								                printf $v4_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, 'UN', 'UN';
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								    if ($v6) {
							 | 
						|
								        foreach my $n ( @{ $v->{'nets_v6'} } ) {
							 | 
						|
								
							 | 
						|
								            # "15169 | 8.8.8.0/24 | US | arin |" for 8.8.8.8
							 | 
						|
								            if ( $v->{'country'} ) {
							 | 
						|
								                printf $v6_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, $v->{'country'}, $v->{'rir'};
							 | 
						|
								            }
							 | 
						|
								            else {
							 | 
						|
								                printf $v6_fh "%s %s|%s|%s|%s|\n", $n, $k, $n, 'UN', 'UN';
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								__END__
							 | 
						|
								
							 | 
						|
								=head1 NAME
							 | 
						|
								
							 | 
						|
								asn.pl - download and parse ASN data for Rspamd
							 | 
						|
								
							 | 
						|
								=head1 SYNOPSIS
							 | 
						|
								
							 | 
						|
								asn.pl [options]
							 | 
						|
								
							 | 
						|
								 Options:
							 | 
						|
								   --download-asn         Download ASN data from RIR
							 | 
						|
								   --download-bgp         Download GeoIP data from Maxmind
							 | 
						|
								   --target               Where to download files (default: current dir)
							 | 
						|
								   --zone-v4              IPv4 zone (default: asn.rspamd.com)
							 | 
						|
								   --zone-v6              IPv6 zone (default: asn6.rspamd.com)
							 | 
						|
								   --file-v4              IPv4 zone file (default: ./asn.zone)
							 | 
						|
								   --file-v6              IPv6 zone (default: ./asn6.zone)
							 | 
						|
								   --help                 Brief help message
							 | 
						|
								   --man                  Full documentation
							 | 
						|
								
							 | 
						|
								=head1 OPTIONS
							 | 
						|
								
							 | 
						|
								=over 8
							 | 
						|
								
							 | 
						|
								=item B<--download-asn>
							 | 
						|
								
							 | 
						|
								Download ASN data from RIR.
							 | 
						|
								
							 | 
						|
								=item B<--download-bgp>
							 | 
						|
								
							 | 
						|
								Download GeoIP data from Ripe
							 | 
						|
								
							 | 
						|
								=item B<--target>
							 | 
						|
								
							 | 
						|
								Specifies where to download files.
							 | 
						|
								
							 | 
						|
								=item B<--help>
							 | 
						|
								
							 | 
						|
								Print a brief help message and exits.
							 | 
						|
								
							 | 
						|
								=item B<--man>
							 | 
						|
								
							 | 
						|
								Prints the manual page and exits.
							 | 
						|
								
							 | 
						|
								=back
							 | 
						|
								
							 | 
						|
								=head1 DESCRIPTION
							 | 
						|
								
							 | 
						|
								B<asn.pl> is intended to download ASN data and GeoIP data and create a rbldnsd zone.
							 | 
						|
								
							 | 
						|
								=cut
							 |