Commit 1a9d8fc0 authored by Nigel Kukard's avatar Nigel Kukard
Browse files

* Added full IPv6 support

- Robert Anderson <randerson@lbsd.net>
parent d41ac7b6
......@@ -600,38 +600,6 @@ sub getEmailKey
# Get key from IP spec
sub getIPKey
{
my ($spec,$ip) = @_;
my $key;
# Check if spec is ok...
if (defined($spec) && $spec =~ /^\/(\d+)$/) {
my $mask = $1;
# If we couldn't pull the mask, just return
return if (!defined($mask));
# Pull long for IP we going to test
my $ip_long = ip_to_long($ip);
# Convert mask to longs
my $mask_long = bits_to_mask($mask);
# AND with mask to get network addy
my $network_long = $ip_long & $mask_long;
# Convert to quad;/
my $cidr_network = long_to_ip($network_long);
# Create key
$key = sprintf("%s/%s",$cidr_network,$mask);
}
return $key;
}
# Get accounting records from policyID
sub getAccountings
{
......@@ -693,7 +661,7 @@ sub getTrackKey
# Check TrackSenderIP
} elsif ($method eq "senderip") {
my $key = getIPKey($spec,$sessionData->{'ClientAddress'});
my $key = getIPKey($spec,$sessionData->{'_ClientAddress'});
# Check for no key
if (defined($key)) {
......
......@@ -238,37 +238,35 @@ sub check {
# Loop with whitelist and calculate
foreach my $source (@{$whitelistSources}) {
# Check format is SenderIP
if ((my $address = $source) =~ s/^SenderIP://i) {
# Parse CIDR into its various peices
my $parsedIP = parseCIDR($address);
# Check if this is a valid cidr or IP
if (ref $parsedIP eq "HASH") {
# Check if IP is whitelisted
if ($sessionData->{'ParsedClientAddress'}->{'IP_Long'} >= $parsedIP->{'Network_Long'} &&
$sessionData->{'ParsedClientAddress'}->{'IP_Long'} <= $parsedIP->{'Broadcast_Long'}) {
# Cache positive result
my $cache_res = cacheStoreKeyPair('CheckHelo/Whitelist/IP',
$sessionData->{'ClientAddress'},1);
if ($cache_res) {
return $server->protocol_response(PROTO_ERROR);
}
# Log...
$server->maillog("module=CheckHelo, action=pass, host=%s, helo=%s, from=%s, to=%s, reason=whitelisted",
$sessionData->{'ClientAddress'},
$sessionData->{'Helo'},
$sessionData->{'Sender'},
$sessionData->{'Recipient'});
if ((my $raw_waddress = $source) =~ s/^SenderIP://i) {
return $server->protocol_response(PROTO_PASS);
}
# Cache negative result
my $cache_res = cacheStoreKeyPair('CheckHelo/Whitelist/IP',$sessionData->{'ClientAddress'},0);
# Create our IP object
my $waddress = new awitpt::netip($raw_waddress);
if (!defined($waddress)) {
$server->log(LOG_WARN,"[CHECKHELO] Skipping invalid address '$raw_waddress'.");
next;
}
# Check if IP is whitelisted
if ($sessionData->{'_ClientAddress'}->is_within($waddress)) {
# Cache positive result
my $cache_res = cacheStoreKeyPair('CheckHelo/Whitelist/IP',
$sessionData->{'ClientAddress'},1);
if ($cache_res) {
return $server->protocol_response(PROTO_ERROR);
}
} else {
$server->log(LOG_ERR,"[CHECKHELO] Failed to parse address '$address' is invalid.");
return $server->protocol_response(PROTO_DATA_ERROR);
# Log...
$server->maillog("module=CheckHelo, action=pass, host=%s, helo=%s, from=%s, to=%s, reason=whitelisted",
$sessionData->{'ClientAddress'},
$sessionData->{'Helo'},
$sessionData->{'Sender'},
$sessionData->{'Recipient'});
return $server->protocol_response(PROTO_PASS);
}
# Cache negative result
my $cache_res = cacheStoreKeyPair('CheckHelo/Whitelist/IP',$sessionData->{'ClientAddress'},0);
if ($cache_res) {
return $server->protocol_response(PROTO_ERROR);
}
} else {
......@@ -284,8 +282,11 @@ sub check {
#
if (defined($policy{'RejectInvalid'}) && $policy{'RejectInvalid'} eq "1") {
# Check if helo is an IP address
if ($sessionData->{'Helo'} =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
# Check if helo is an IPv4 or IPv6 address
if (
$sessionData->{'Helo'} =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ ||
$sessionData->{'Helo'} =~ /^(?:::(:?[a-f\d]{1,4}:){0,6}?[a-f\d]{0,4}|[a-f\d]{1,4}(?::[a-f\d]{1,4}){0,6}?::|[a-f\d]{1,4}(?::[a-f\d]{1,4}){0,6}?::(?:[a-f\d]{1,4}:){0,6}?[a-f\d]{1,4})$/i
) {
# Check if we must reject IP address HELO's
if (defined($policy{'RejectIP'}) && $policy{'RejectIP'} eq "1") {
......@@ -301,8 +302,11 @@ sub check {
}
# Address literal is valid
} elsif ($sessionData->{'Helo'} =~ /^\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\]$/) {
} elsif (
$sessionData->{'Helo'} =~ /^\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\]$/ ||
$sessionData->{'Helo'} =~ /^\[((?:::(:?[a-f\d]{1,4}:){0,6}?[a-f\d]{0,4}|[a-f\d]{1,4}(?::[a-f\d]{1,4}){0,6}?::|[a-f\d]{1,4}(?::[a-f\d]{1,4}){0,6}?::(?:[a-f\d]{1,4}:){0,6}?[a-f\d]{1,4}))\]$/i
) {
# Check if helo is a FQDN - Only valid characters in a domain is alnum and a -
} elsif ($sessionData->{'Helo'} =~ /^[\w-]+(\.[\w-]+)+$/) {
......
......@@ -26,6 +26,7 @@ use warnings;
use cbp::logging;
use awitpt::cache;
use awitpt::db::dblayer;
use awitpt::netip;
use cbp::system;
use cbp::protocols;
......@@ -211,26 +212,23 @@ sub check {
while (my $row = $sth->fetchrow_hashref()) {
# Check format is SenderIP
if ((my $address = $row->{'source'}) =~ s/^SenderIP://i) {
if ((my $raw_waddress = $row->{'source'}) =~ s/^SenderIP://i) {
# Parse CIDR into its various peices
my $parsedIP = parseCIDR($address);
# Check if this is a valid cidr or IP
if (ref $parsedIP eq "HASH") {
# Check if IP is whitelisted
if ($sessionData->{'ParsedClientAddress'}->{'IP_Long'} >= $parsedIP->{'Network_Long'} &&
$sessionData->{'ParsedClientAddress'}->{'IP_Long'} <= $parsedIP->{'Broadcast_Long'}) {
$server->maillog("module=Greylisting, action=pass, host=%s, helo=%s, from=%s, to=%s, reason=whitelisted",
$sessionData->{'ClientAddress'},
$sessionData->{'Helo'},
$sessionData->{'Sender'},
$sessionData->{'Recipient'});
DBFreeRes($sth);
return $server->protocol_response(PROTO_PASS);
}
} else {
$server->log(LOG_WARN,"[GREYLISTING] Skipping invalid address '$address'.");
# Create our IP object
my $waddress = new awitpt::netip($raw_waddress);
if (!defined($waddress)) {
$server->log(LOG_WARN,"[GREYLISTING] Skipping invalid address '$raw_waddress'.");
next;
}
# Check if IP is whitelisted
if ($sessionData->{'_ClientAddress'}->is_within($waddress)) {
$server->maillog("module=Greylisting, action=pass, host=%s, helo=%s, from=%s, to=%s, reason=whitelisted",
$sessionData->{'ClientAddress'},
$sessionData->{'Helo'},
$sessionData->{'Sender'},
$sessionData->{'Recipient'});
DBFreeRes($sth);
return $server->protocol_response(PROTO_PASS);
}
} else {
......@@ -843,7 +841,7 @@ sub getKey
# Check TrackSenderIP
if ($method eq "senderip") {
my $key = getIPKey($spec,$sessionData->{'ClientAddress'});
my $key = getIPKey($spec,$sessionData->{'_ClientAddress'});
# Check for no key
if (defined($key)) {
......@@ -862,37 +860,6 @@ sub getKey
}
# Get key from session
sub getIPKey
{
my ($spec,$ip) = @_;
my $key;
# Check if spec is ok...
if ($spec =~ /^\/(\d+)$/) {
my $mask = $1;
# If we couldn't pull the mask, just return
$mask = 32 if (!defined($mask));
# Pull long for IP we going to test
my $ip_long = ip_to_long($ip);
# Convert mask to longs
my $mask_long = bits_to_mask($mask);
# AND with mask to get network addy
my $network_long = $ip_long & $mask_long;
# Convert to quad;/
my $cidr_network = long_to_ip($network_long);
# Create key
$key = sprintf("%s/%s",$cidr_network,$mask);
}
return $key;
}
# Cleanup function
sub cleanup
{
......
......@@ -543,37 +543,6 @@ sub getEmailKey
}
# Get key from IP spec
sub getIPKey
{
my ($spec,$ip) = @_;
my $key;
# Check if spec is ok...
if (defined($spec) && $spec =~ /^\/(\d+)$/) {
my $mask = $1;
# If we couldn't pull the mask, just return
return if (!defined($mask));
# Pull long for IP we going to test
my $ip_long = ip_to_long($ip);
# Convert mask to longs
my $mask_long = bits_to_mask($mask);
# AND with mask to get network addy
my $network_long = $ip_long & $mask_long;
# Convert to quad;/
my $cidr_network = long_to_ip($network_long);
# Create key
$key = sprintf("%s/%s",$cidr_network,$mask);
}
return $key;
}
# Get quota from policyID
sub getQuotas
{
......@@ -629,7 +598,7 @@ sub getKey
# Check TrackSenderIP
} elsif ($method eq "senderip") {
my $key = getIPKey($spec,$sessionData->{'ClientAddress'});
my $key = getIPKey($spec,$sessionData->{'_ClientAddress'});
# Check for no key
if (defined($key)) {
......
......@@ -36,6 +36,7 @@ our (@ISA,@EXPORT);
use cbp::logging;
use awitpt::cache;
use awitpt::db::dblayer;
use awitpt::netip;
use cbp::system;
use Data::Dumper;
......@@ -365,8 +366,8 @@ sub policySourceItemMatches
# Match IPv4 or IPv6
if (
$item =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\/\d{1,2})?$/ ||
$item =~ /^(?:::(:?[a-f0-9]{1,4}:){0,6}?[a-f0-9]{0,4}|[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,6}?::|[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,6}?::(?:[a-f0-9]{1,4}:){0,6}?[a-f0-9]{1,4})(?:\/\d{1,3})?$/i
$item =~ /^(?:\d{1,3})(?:\.(?:\d{1,3})(?:\.(?:\d{1,3})(?:\.(?:\d{1,3}))?)?)?(?:\/(\d{1,2}))?$/ ||
$item =~ /^(?:::(:?[a-f\d]{1,4}:){0,7}?|(?::[a-f\d]{1,4}){0,7}?::|(?::[a-f\d]{1,4}){0,7}?::(?:[a-f\d]{1,4}:){0,7}?)(?:\/\d{1,3})?$/i
) {
# See if we get an object from
my $matchRange = new awitpt::netip($item);
......@@ -380,8 +381,8 @@ sub policySourceItemMatches
# Match peer IPv4 or IPv6 (the server requesting the policy)
} elsif (
$item =~ /^\[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\/\d{1,2})?)\]$/ ||
$item =~ /^\[((?:::(:?[a-f0-9]{1,4}:){0,6}?[a-f0-9]{0,4}|[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,6}?::|[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,6}?::(?:[a-f0-9]{1,4}:){0,6}?[a-f0-9]{1,4})(?:\/\d{1,3})?)\]$/i
$item =~ /^\[(?:\d{1,3})(?:\.(?:\d{1,3})(?:\.(?:\d{1,3})(?:\.(?:\d{1,3}))?)?)?(?:\/(\d{1,2}))?\]$/ ||
$item =~ /^\[((?:::(:?[a-f\d]{1,4}:){0,7}?|(?::[a-f\d]{1,4}){0,7}?::|(?::[a-f\d]{1,4}){0,7}?::(?:[a-f\d]{1,4}:){0,7}?)(?:\/\d{1,3})?)\]$/i
) {
# We don't want the [ and ]
my $cleanItem = $1;
......
......@@ -28,143 +28,28 @@ require Exporter;
our (@ISA,@EXPORT);
@ISA = qw(Exporter);
@EXPORT = qw(
isValidIP
ip_to_long
long_to_ip
bits_to_mask
getIPKey
parseCIDR
IPMASK
);
use Socket qw(
inet_ntoa
inet_aton
);
use constant {
IPMASK => 0xffffffff,
};
# Check for valid IP
sub isValidIP
# Get key from IP spec
sub getIPKey
{
my $ip = shift;
my ($spec,$ip) = @_;
my (@ip) = split(/\./, $ip);
return undef if (scalar(@ip) != 4);
my $key;
# Check if spec is ok...
if (defined($spec) && $spec =~ /^\/(\d+)$/) {
my $mask = $1;
foreach my $octet (@ip) {
return 0 unless $octet =~ /^\d+$/;
return 0 unless $octet >= 0 && $octet <= 255;
# Grab IP key, the clever way ;)
$key = $ip->copy('cidr' => $mask)->to_network()->to_str();
}
return 1;
}
# Get long int from IP
sub ip_to_long
{
my $ip = shift;
# Validate IP
return undef if (!isValidIP($ip));
# Unpack IP into a long
return unpack('N', inet_aton($ip));
}
# Convert IP to long int
sub long_to_ip {
my $long = shift;
# Pack into network and convert to IP
my $ip = inet_ntoa(pack('N', $long));
# Validate
return undef if (!isValidIP($ip));
return $ip;
}
# Get mask for ip bits
sub bits_to_mask {
my $nbits = shift;
# Get string to pass to pack
my $str = '1' x $nbits . '0' x (32 - $nbits);
# Grab long mask
my $mask = unpack('N', pack('B*', $str));
return $mask;
}
# Parse a CIDR into the various peices
sub parseCIDR
{
my $cidr = shift;
# Regex CIDR
if ($cidr =~ /^(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3})(?:\.(\d{1,3}))?)?)?(?:\/(\d{1,2}))?$/) {
# Strip any ip blocks and mask from string
my ($a,$b,$c,$d,$mask) = ($1,$2,$3,$4,$5);
# Set undefined ip blocks and mask if missing
if (!defined($b)) {
$b = 0;
$mask = 8 if !defined($mask);
}
if (!defined($c)) {
$c = 0;
$mask = 16 if !defined($mask);
}
if (!defined($d)) {
$d = 0;
$mask = 24 if !defined($mask);
}
# Default mask
$mask = ( defined($mask) && $mask >= 1 && $mask <= 32 ) ? $mask : 32;
# Build ip
my $ip = "$a.$b.$c.$d";
# Pull long for IP we going to test
my $ip_long = ip_to_long($ip);
# Convert mask to longs
my $mask_long = bits_to_mask($mask);
my $mask2_long = IPMASK ^ $mask_long;
# AND with mask to get network addy
my $network_long = $ip_long & $mask_long;
# AND with mask2 to get broadcast addy
my $bcast_long = $ip_long | $mask2_long;
# Retrun array of data
my $res = {
'IP_Long' => $ip_long,
'IP' => long_to_ip($ip_long),
'Mask_Long' => $mask_long,
'Network_Long' => $network_long,
'Network' => long_to_ip($network_long),
'Broadcast_Long' => $bcast_long,
'Broadcast' => long_to_ip($bcast_long),
};
return $res;
} else {
return undef;
}
return $key;
}
......
......@@ -37,7 +37,6 @@ use awitpt::db::dblayer;
use awitpt::netip;
use cbp::logging;
use cbp::policies;
use cbp::system qw(parseCIDR);
use Data::Dumper;
......@@ -220,7 +219,7 @@ sub getSessionDataFromRequest
# Requesting server address, we need this before the policy call
$sessionData->{'PeerAddress'} = $request->{'_peer_address'};
$sessionData->{'_PeerAddress'} = new awitpt::netip($request->{'PeerAddress'});
$sessionData->{'_PeerAddress'} = new awitpt::netip($sessionData->{'PeerAddress'});
if (!defined($sessionData->{'_PeerAddress'})) {
$server->log(LOG_ERR,"[TRACKING] Failed to understand PeerAddress: ".awitpt::netip::Error());
return -1;
......@@ -268,7 +267,7 @@ sub getSessionDataFromRequest
} elsif ($request->{'_protocol_transport'} eq "HTTP") {
# Requesting server address, we need this before the policy call
$sessionData->{'PeerAddress'} = $request->{'_peer_address'};
$sessionData->{'_PeerAddress'} = new awitpt::netip($request->{'PeerAddress'});
$sessionData->{'_PeerAddress'} = new awitpt::netip($sessionData->{'PeerAddress'});
if (!defined($sessionData->{'_PeerAddress'})) {
$server->log(LOG_ERR,"[TRACKING] Failed to understand PeerAddress: ".awitpt::netip::Error());
return -1;
......@@ -306,8 +305,6 @@ sub getSessionDataFromRequest
$sessionData->{'ProtocolTransport'} = $request->{'_protocol_transport'};
$sessionData->{'ProtocolState'} = $request->{'protocol_state'};
$sessionData->{'UnixTimestamp'} = $request->{'_timestamp'};
# XXX: redundant
$sessionData->{'ParsedClientAddress'} = parseCIDR($sessionData->{'ClientAddress'});
# Make sure HELO is clean...
$sessionData->{'Helo'} = defined($sessionData->{'Helo'}) ? $sessionData->{'Helo'} : '';
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment