############################################################################# # Dicop::Security - routines for authentication, checks, security # # (c) Bundesamt fuer Sicherheit in der Informationstechnik 1998-2006 # # DiCoP is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License version 2 as published by the Free # Software Foundation. # # See the file LICENSE or L for more information. ############################################################################# package Dicop::Security; $VERSION = '2.02'; # Current version of this package require 5.004; # requires this Perl version or later require Exporter; @ISA = qw(Exporter); @EXPORT_OK = qw( valid_ip valid_net ip_is_in_net ip_is_in_net_list ip_matches hash_pwd valid_user ); use strict; use Digest::MD5; use Math::BigInt; sub valid_ip { # take one ip definition and return true if it is valid my ($ip) = shift; return 0 if !defined $ip; return 0 if $ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; my @parts = split /\./, $ip; foreach my $part (@parts) { return 0 if $part < 0 || $part > 255; # 280.1.1.2 is not valid } 1; # is valid } sub valid_net { # take one net definition and return true if it is valid my ($input) = shift; return 1 if $input =~ /^(any|none)$/; my ($ip,$net) = split /\//, $input; return 0 unless valid_ip($ip); $net = '32' if !defined $net; return 0 if $net eq '' || $net !~ /^(32|24|16|8|0)$/; return 0 if $net eq '0' && $ip ne '0.0.0.0'; my @parts = split /\./, $ip; if ($net eq '24') { return 0 if $parts[3] != 0; } if ($net eq '16') { return 0 if $parts[3] != 0 || $parts[2] != 0; } if ($net eq '8') { return 0 if $parts[3] != 0 || $parts[2] != 0 || $parts[3] != 0; } 1; # is valid } sub ip_is_in_net { # take one (source) ip, and one check ip/net and then check whether source # is inside the net (or matches the check ip). # so 127.0.0.1 matches 127.0.0.1/32 and 127.0.0.0/24 # return 0 for match, 1 for no match and <0 for error my ($ip,$net) = @_; return -1 unless valid_ip($ip); return -2 unless valid_net($net); return 0 if $net eq 'none'; # never okay return 1 if $net eq 'any'; # always okay my ($ip_match,$net_match) = split /\//, $net; $net_match = 32 if !defined $net_match; my @parts = split /\./, $ip; my @parts_match = split /\./, $ip_match; my $count = $net_match >> 3; # /32 => 4, /0 => 0 my $i = 0; while ($count-- > 0) { return 0 if $parts[$i] != $parts_match[$i]; # not matched $i++; } 1; # is okay } sub ip_is_in_net_list { # take one IP and a list of networks, and check whether the IP is in any # of the networks my ($ip,$nets) = @_; foreach my $net (@$nets) { my $r = ip_is_in_net($ip,$net); return $r if $r != 0; # error (<0) or match (1) } 0; # no match found } sub _ip2hex { # create a hex string from the IP (e.g. 1.2.3.4 => 01020304, 127.0.0.1 => 7f000001) my $ip = shift || ''; my @parts = split /\./, $ip; my $hex = ''; foreach (@parts) { $hex .= sprintf ("%02x", $_); } Math::BigInt->new('0x'. $hex); } sub ip_matches { my ($check, $ip, $mask) = @_; # don't check if no mask or IP were specified return 1 if $mask eq '' or $mask eq '0.0.0.0' or $ip eq '' or $ip eq 'none'; # create a hex string from the IP my $hex_ip = _ip2hex($ip); my $hex_check = _ip2hex($check); my $hex_mask = _ip2hex($mask); # and the check and the IP with the mask $hex_check &= $hex_mask; # 1.2.3.4 & 255.255.255.0 => 1.2.3.0 $hex_ip &= $hex_mask; # 1.2.3.4 & 255.255.255.0 => 1.2.3.0 ($hex_ip eq $hex_check) || 0; } sub hash_pwd { my ($pwd) = shift; my $hash = Digest::MD5->new(); $hash->add($pwd); $hash->hexdigest(); } 1; __END__ ############################################################################# =pod =head1 NAME Dicop::Security - routines for authentication, checks and security =head1 SYNOPSIS use Dicop::Security; $ip = '1.2.3.4'; print "invalid ip $ip" unless Dicop::Security::valid_ip($ip); =head1 REQUIRES perl5.005, Exporter =head1 EXPORTS Exports nothing on default. =head1 DESCRIPTION This modules contains some routines to implement authentication, security checks etc. These are in a seperate module to make testing and auditing easier. =head1 METHODS =head2 valid_ip Return true if the given IP is a valid (at this time IPv4) IP. =head2 valid_net Return true if the given net is a valid (at this time IPv4) net. Examples: print "oups!\n" unless Dicop::Security::valid_net('1.2.3.4/32'; =head2 ip_is_in_net Return true if the given IP is contained in the given net: print "oups!\n" unless Dicop::Security::ip_is_in_net('1.2.3.4','1.2.3.4/32'; =head2 ip_matches Return true if the given IP matches the given second IP and net mask. print "oups!\n" unless Dicop::Security::ip_matches('1.2.3.5', '1.2.3.0','255.255.255.0'); A mask of C<255.255.255.255> dictates that the IP must match exactly, a mask of C<255.255.255.0> means that the first 3 parts must match, and a mask of C<0.0.0.0> means that every IP would match (regardless of second IP). =head2 ip_is_in_net_list Take one IP and a list of networks, and check whether the IP is in any of the networks. Return 0 for IP is in one of the nets, 1 for IP is in none of the nets, and <0 for error. =head2 hash_pwd my $hash = hash_pwd($pwd); Return MD5 hash of the given password. =head2 valid_user if (valid_user(\@users, $user, $pwd) { # okay } else { # user unknown or wrong password } Takes reference to a hash (containig user => pwdhash), a username and a password hash. Returns 0 if the user exists in the list of users and the password matches. Returns -1 if the user does not exist, and -2 if the user exists, but the password hash does not match. =head1 BUGS None known yet. =head1 AUTHOR (c) Bundesamt fuer Sicherheit in der Informationstechnik 1998-2006 DiCoP is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. See the file LICENSE or L for more information. =cut