#!/usr/bin/perl -T
# Matija Nalis <mnalis-perl@axe.tomsoft.hr> GPLv3+  started 20140405
#
# gets list of RIPE IP adressess and creates ranges for vsftp pam_access limiting
#

use warnings;
use strict;

use POSIX qw(strftime);
use IO::Handle;

local $ENV{'PATH'} = '/bin:/usr/bin';

my $DEBUG = 0;		# Important! set to 0 for production!
my $OUR_COUNTRY='HR';	# generate accept list for this country code
my $IPV4_AS_FFFF=1;	# 0=use "127.0.0.1/32" format for IPv4, 1=use "::ffff:127.0.0.1/128" format for IPv4
my $LINE_PREFIX = '+ : ALL :';	# prepend at the beggining of the line

my $DYNAMIC_START = "### AUTOGENERATED RIPE START FOR $OUR_COUNTRY, **DO NOT CHANGE THIS LINE AND AFTER**";
my $DYNAMIC_END   = "### AUTOGENERATED RIPE END FOR $OUR_COUNTRY, **DO NOT CHANGE THIS LINE AND BEFORE**";
my $BUF_SIZE = 300 - length($LINE_PREFIX);	# 8192 - 100 - length($LINE_PREFIX);	# must at least few dozen bytes smaller than system BUFSIZ. it is inexact, so play it safe!
my $URL='ftp://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-latest';
my $URL_CMD = "wget -q -O- $URL";
#$URL_CMD = "cat delegated-ripencc-latest";	# DEBUG FIXME DELME
my $VSFTPD_FILE_FINAL = '/etc/vsftpd_pam_access.conf';
my $VSFTPD_FILE_TMP = $VSFTPD_FILE_FINAL . '.tmp';


open (my $f_url, '-|', $URL_CMD) or die "can't get data from $URL_CMD: $!";

# 2|ripencc|1396648799|85084|19830705|20140404|+0200
my ($db_ver, $db_sig, $db_ts, $db_count, undef, $db_date, $db_tz) = split /\|/, <$f_url>;

#  sanity checks
if ("$db_sig$db_ver" ne 'ripencc2') { die "invalid sig/version $db_sig/$db_ver" }

my $our_date = strftime "%Y%m%d", localtime;
if ($db_date < $our_date - 30) { die "database too old: $db_date << $our_date" }

# hardcoded... expect at least  50k of 80k+ in 2014/04
if ($db_count < 50000) { die "$db_count should be > 80000, but is only $db_count" }

$DEBUG and print STDERR "DB ver=$db_ver sig=$db_sig date=$db_date count=$db_count\n";

# base-2 logarithm (to convert number of IP adresses to number of CIDR bits)
sub log2($) {
	my $n=shift;
	return log($n)/log(2);
}

# ensures file reliability
sub fsync($) {
	my ($file) = @_;
    
	$file->flush or die 'fsync cannot flush';
	$file->sync or die 'fsync cannot fsync';
}

open (my $f_orig, '<', $VSFTPD_FILE_FINAL) or die "can't read $VSFTPD_FILE_FINAL: $!";
unlink $VSFTPD_FILE_TMP;
open (my $f_tmp, '>', $VSFTPD_FILE_TMP) or die "can't write to: $VSFTPD_FILE_TMP: $!";

# read static content before our dynamic section
while (<$f_orig>) {
	if (m{^\Q$DYNAMIC_START\E}) { last }
	print $f_tmp $_  or die "can't write to $VSFTPD_FILE_TMP: $!";;
}
print $f_tmp "$DYNAMIC_START\n" or die "can't write to $VSFTPD_FILE_TMP: $!";


# print one line
my $char_count = 0;
sub oneline($) {
	my $l = shift;
	if ($char_count == 0) {
		print $f_tmp "$LINE_PREFIX " or die "can't write to $VSFTPD_FILE_TMP: $!";
	}
#	print $f_tmp "$l\n" or die "can't write to $VSFTPD_FILE_TMP: $!"; return;	# each IP one line

	my $chars = length($l)+1;
	print $f_tmp "$l " or die "can't write to $VSFTPD_FILE_TMP: $!";
	$char_count += $chars;

	if ($char_count > $BUF_SIZE) {		# break lines next time if we're over $BUF_SIZE chars
		print $f_tmp "\n" or die "can't write to $VSFTPD_FILE_TMP: $!";
		$char_count = 0;
	}
}


# main loop: read data
# lines look like: "ripencc|FR|ipv4|2.0.0.0|1048576|20100712|allocated"
my $count = 0;
while (my $line = <$f_url>) {
	my ($l_sig, $l_country, $l_type, $l_ip, $l_size, $l_rest) = split /\|/, $line, 6;
	if ($l_sig ne 'ripencc') { die "invalid signature in line $line" }
	next if $l_country eq '*';	# skip summary lines
	$count++;
	next if $l_type !~ /^ipv[46]$/;	# only interested in IPv4/IPv6
	next if $l_country ne $OUR_COUNTRY;	# not interested in other countries

	my $cidr;
	if ($l_type eq 'ipv6') {	# IPv6
		$cidr = $l_size;
	} else {			# IPv4
		if ($IPV4_AS_FFFF) {	
			$cidr = 128-log2($l_size);
			$l_ip = '::ffff:' . $l_ip;
		} else {
			$cidr = 32-log2($l_size);
		}
	}
	oneline ("$l_ip/$cidr");
}

# final validity check
if ($count ne $db_count) { die "count mismatch $count != $db_count" }

# skip dynamic content before static trailer
while (<$f_orig>) {
	if (m{^\Q$DYNAMIC_END\E}) { last }
}
print $f_tmp "\n$DYNAMIC_END\n" or die "can't write to $VSFTPD_FILE_TMP: $!";

# add static trailer
while (<$f_orig>) {
	print $f_tmp $_;
}

fsync($f_tmp);	# make sure it's safely on disk
close $f_tmp or die "can't finish writing to $VSFTPD_FILE_TMP: $!";
close $f_orig;
rename $VSFTPD_FILE_TMP, $VSFTPD_FILE_FINAL or die "can't rename $VSFTPD_FILE_TMP to $VSFTPD_FILE_FINAL: $!";

$DEBUG and print STDERR "All checks OK\n";
