# Author: An-Cheng Huang # Date: 2007 # Description: Type checking script # **** License **** # This program 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. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # This code was originally developed by Vyatta, Inc. # Portions created by Vyatta are Copyright (C) 2006, 2007, 2008 Vyatta, Inc. # All Rights Reserved. # **** End License **** # Perl module for type validation. # Usage 1: validate a value of a specific type. # use Vyatta::TypeChecker; # ... # if (validateType('ipv4', '1.1.1.1')) { # # valid # ... # } else { # # not valie # ... # } # # Usage 2: find the type of a value (from a list of candidates), returns # undef if the value is not valid for any of the candidates. # $valtype = findType('1.1.1.1', 'ipv4', 'ipv6'); # if (!defined($valtype)) { # # neither ipv4 nor ipv6 # ... # } else { # if ($valtype eq 'ipv4') { # ... # } else { # ... # } # } package Vyatta::TypeChecker; use strict; our @EXPORT = qw(findType validateType); use base qw(Exporter); my %type_handler = ( 'ipv4' => \&validate_ipv4, 'ipv4net' => \&validate_ipv4net, 'ipv4range' => \&validate_ipv4range, 'ipv4_negate' => \&validate_ipv4_negate, 'ipv4net_negate' => \&validate_ipv4net_negate, 'ipv4range_negate' => \&validate_ipv4range_negate, 'iptables4_addr' => \&validate_iptables4_addr, 'protocol' => \&validate_protocol, 'protocol_negate' => \&validate_protocol_negate, 'macaddr' => \&validate_macaddr, 'macaddr_negate' => \&validate_macaddr_negate, 'ipv6' => \&validate_ipv6, 'ipv6_negate' => \&validate_ipv6_negate, 'ipv6net' => \&validate_ipv6net, 'ipv6net_negate' => \&validate_ipv6net_negate, 'hex16' => \&validate_hex_16_bits, 'hex32' => \&validate_hex_32_bits, 'ipv6_addr_param' => \&validate_ipv6_addr_param, 'restrictive_filename' => \&validate_restrictive_filename, 'no_bash_special' => \&validate_no_bash_special, 'u32' => \&validate_u32, 'bool' => \&validate_bool ); sub validate_ipv4 { $_ = shift; return 0 if (!/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/); return 0 if ($1 > 255 || $2 > 255 || $3 > 255 || $4 > 255); return 1; } sub validate_u32 { my $val = shift; return ($val =~ /^\d+$/ and $val < 2**32); } sub validate_bool { my $val = shift; return ($val eq 'true' or $val eq 'false'); } sub validate_ipv4net { $_ = shift; return 0 if (!/^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/); return 0 if ($1 > 255 || $2 > 255 || $3 > 255 || $4 > 255 || $5 > 32); return 1; } sub validate_ipv4range { $_ = shift; return 0 if (!/^([^-]+)-([^-]+)$/); my ($a1, $a2) = ($1, $2); return 0 if (!validate_ipv4($a1) || !validate_ipv4($a2)); #need to check that range is in ascending order $a1 =~ m/^(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)/; my $v1 = $1*256*256*256+$2*256*256+$3*256+$4; $a2 =~ m/^(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)/; my $v2 = $1*256*256*256+$2*256*256+$3*256+$4; return 0 if ($v1 > $v2); return 1; } sub validate_ipv4_negate { my $value = shift; if ($value =~ m/^\!(.*)$/) { $value = $1; } return validate_ipv4($value); } sub validate_ipv4net_negate { my $value = shift; if ($value =~ m/^\!(.*)$/) { $value = $1; } return validate_ipv4net($value); } sub validate_ipv4range_negate { my $value = shift; if ($value =~ m/^\!(.*)$/) { $value = $1; } return validate_ipv4range($value); } sub validate_iptables4_addr { my $value = shift; return 0 if (!validate_ipv4_negate($value) && !validate_ipv4net_negate($value) && !validate_ipv4range_negate($value)); return 1; } sub validate_protocol { my $value = shift; $value = lc $value; return 1 if ($value eq 'all'); if ($value =~ /^\d+$/) { # 0 has special meaning to iptables return 1 if $value >= 1 and $value <= 255; } return defined getprotobyname($value); } sub validate_protocol_negate { my $value = shift; if ($value =~ m/^\!(.*)$/) { $value = $1; } return validate_protocol($value); } sub validate_macaddr { my $value = shift; $value = lc $value; my $byte = '[0-9a-f]{2}'; return 1 if ($value =~ /^$byte(:$byte){5}$/); } sub validate_macaddr_negate { my $value = shift; if ($value =~ m/^\!(.*)$/) { $value = $1; } return validate_macaddr($value); } # IPv6 syntax definition my $RE_IPV4_BYTE = '((25[0-5])|(2[0-4][0-9])|([01][0-9][0-9])|([0-9]{1,2}))'; my $RE_IPV4 = "$RE_IPV4_BYTE(\.$RE_IPV4_BYTE){3}"; my $RE_H16 = '([a-fA-F0-9]{1,4})'; my $RE_H16_COLON = "($RE_H16:)"; my $RE_LS32 = "(($RE_H16:$RE_H16)|($RE_IPV4))"; my $RE_IPV6_P1 = "($RE_H16_COLON)\{6\}$RE_LS32"; my $RE_IPV6_P2 = "::($RE_H16_COLON)\{5\}$RE_LS32"; my $RE_IPV6_P3 = "($RE_H16)?::($RE_H16_COLON)\{4\}$RE_LS32"; my $RE_IPV6_P4 = "(($RE_H16_COLON)\{0,1\}$RE_H16)?" . "::($RE_H16_COLON)\{3\}$RE_LS32"; my $RE_IPV6_P5 = "(($RE_H16_COLON)\{0,2\}$RE_H16)?" . "::($RE_H16_COLON)\{2\}$RE_LS32"; my $RE_IPV6_P6 = "(($RE_H16_COLON)\{0,3\}$RE_H16)?" . "::($RE_H16_COLON)\{1\}$RE_LS32"; my $RE_IPV6_P7 = "(($RE_H16_COLON)\{0,4\}$RE_H16)?::$RE_LS32"; my $RE_IPV6_P8 = "(($RE_H16_COLON)\{0,5\}$RE_H16)?::$RE_H16"; my $RE_IPV6_P9 = "(($RE_H16_COLON)\{0,6\}$RE_H16)?::"; my $RE_IPV6 = "($RE_IPV6_P1)|($RE_IPV6_P2)|($RE_IPV6_P3)|($RE_IPV6_P4)" . "|($RE_IPV6_P5)|($RE_IPV6_P6)|($RE_IPV6_P7)|($RE_IPV6_P8)" . "|($RE_IPV6_P9)"; sub validate_ipv6 { $_ = shift; return 0 if (!/^$RE_IPV6$/); return 1; } sub validate_ipv6_negate { my $value = shift; if ($value =~ m/^\!(.*)$/) { $value = $1; } return validate_ipv6($value); } sub validate_ipv6net { my $value = shift; if ($value =~ m/^(.*)\/(.*)$/) { my $ipv6_addr = $1; my $prefix_length = $2; if ($prefix_length < 0 || $prefix_length > 128) { return 0; } return validate_ipv6($ipv6_addr); } else { return 0; } } sub validate_ipv6net_negate { my $value = shift; if ($value =~ m/^\!(.*)$/) { $value = $1; } return validate_ipv6net($value); } # Validate a 16-bit hex value, no leading "0x" sub validate_hex_16_bits { my $value = shift; $value = lc $value; return 1 if ($value =~ /^[0-9a-f]{4}$/) } # Validate a 32-bit hex value, no leading "0x" sub validate_hex_32_bits { my $value = shift; $value = lc $value; return 1 if ($value =~ /^[0-9a-f]{8}$/) } # Validate the overloaded IPv6 source and destination address parameter in # the firewall configuration tree. sub validate_ipv6_addr_param { my $value = shift; # leading exclamation point is valid in all three formats if ($value =~ m/^\!(.*)$/) { $value = $1; } if ($value =~ m/^(.*)-(.*)$/) { # first format: - if (validate_ipv6($1)) { return validate_ipv6($2); } else { return 0; } } elsif ($value =~ m/^(.*)\/(.*)$/) { # Second format: / return validate_ipv6net($value); } else { # third format: return validate_ipv6($value) } } # validate a restrictive filename sub validate_restrictive_filename { my $value = shift; return (($value =~ /^[-_.a-zA-Z0-9]+$/) ? 1 : 0); } # validate that a string does not contain bash special chars sub validate_no_bash_special { my $value = shift; return (($value =~ /[;&"'`!\$><|]/) ? 0 : 1); } sub validateType { my ($type, $value, $quiet) = @_; if (!defined($type) || !defined($value)) { return 0; } if (!defined($type_handler{$type})) { print "type \"$type\" not defined\n" if (!defined($quiet)); return 0; } if (!&{$type_handler{$type}}($value)) { print "\"$value\" is not a valid value of type \"$type\"\n" if (!defined($quiet)); return 0; } return 1; } sub findType { my ($value, @candidates) = @_; return if (!defined($value) || ((scalar @candidates) < 1)); # undef foreach my $type (@candidates) { if (!defined($type_handler{$type})) { next; } if (&{$type_handler{$type}}($value)) { # the first valid type is returned return $type; } } } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # perl-indent-level: 2 # End: