home *** CD-ROM | disk | FTP | other *** search
/ ftp.f-secure.com / 2014.06.ftp.f-secure.com.tar / ftp.f-secure.com / support / hotfix / fsis / IS-SpamControl.fsfix / iufssc / lib / FS / PhishBL / ParseURL.pm < prev   
Text File  |  2006-11-29  |  5KB  |  178 lines

  1. #!/usr/bin/perl
  2. #
  3. # $Id: ParseURL.pm 2957 2006-02-02 15:11:02Z kankri $
  4.  
  5. use strict;
  6. use warnings;
  7.  
  8. package FS::PhishBL::ParseURL;
  9.  
  10. =head1 NAME
  11.  
  12. FS::PhishBL::ParseURL - URL parsing functions for use with FS::PhishBL
  13.  
  14. =head1 SYNOPSIS
  15.  
  16. use FS::PhishBL::ParseURL qw(parse_ip_address url_to_blocklist_hostname);
  17.  
  18. =head1 AUTHOR
  19.  
  20. Dan Gardner <dan@netcraft.com>
  21.  
  22. IP address parsing functions taken from NetcraftToolbar::ParseURL
  23. by Nick Cleaton <njc@netcraft.com>
  24.  
  25. =head1 COPYRIGHT
  26.  
  27. Copyright (C) 2005 Netcraft Ltd, all rights reserved
  28.  
  29. =cut
  30.  
  31. use Exporter;
  32.  
  33. use vars qw(@EXPORT_OK $VERSION $allow_octal);
  34.  
  35. $VERSION = qw/$Revision: 2957 $/[1];
  36.  
  37. @EXPORT_OK = qw(parse_ip_address url_to_blocklist_hostname);
  38.  
  39. =item parse_ip_address ( ADDRESS )
  40.  
  41. ADDRESS is a numeric IP address, possibly obfuscated.  Returns
  42. the address converted to the canonical dotted quad form if a
  43. way to do that can be found, otherwise returns C<undef>.
  44.  
  45. The function errs on the side of returning an IP address if at
  46. all possible.  In edge cases, it emulates the way that the MS
  47. Windows resolver works as closely as possible, since obfuscated
  48. IP addresses in phishing URLs are most likely to target that
  49. platform.
  50.  
  51. =cut
  52.  
  53. sub parse_ip_address {
  54.     local $_ = shift;
  55.  
  56.     s/\.+$//;
  57.  
  58.     if (length $_ <= 15 and /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) /) {
  59.         $_ = $1;
  60.         # The windows resolver allows a four part dotted decimal IP address to
  61.         # have a space followed by any old rubbish appended, so long as the
  62.         # total length of the string doesn't get above 15 chars, e.g.
  63.         # <a href="http://10.192.95.89%20xy/">this goes to 10.192.95.89</a>.
  64.         # We cannot make our handling of this more general (e.g. with an
  65.         # unconditional s/ .*//) since space works in domain names, so for
  66.         # example:
  67.         # <a href="http://10.192.95.89%20xy.wildcard.example.com">this does not
  68.         # go to 10.192.95.89</a>.  The second example fails to be recognised
  69.         # as an IP (it's too long to be a dotted decimal address) and so falls
  70.         # through to DNS, where a wildcard can make it work.
  71.     }
  72.  
  73.     return unless m#^((?:0x[0-9a-f]+|[0-9\.])+)$#i;
  74.     $_ = $1;
  75.  
  76.     my @bits = split /\.+/, $_;
  77.     my @sizes;
  78.     if (@bits == 1) {
  79.         @sizes = (4);
  80.     } elsif (@bits == 2) {
  81.         @sizes = (1,3);
  82.     } elsif (@bits == 3) {
  83.         @sizes = (1,1,2);
  84.     } elsif (@bits == 4) {
  85.         @sizes = (1,1,1,1);
  86.     } else {
  87.         return;
  88.     }
  89.  
  90.     # If the IP address includes a number with a leading 0 and
  91.     # containing an 8 or a 9, then the octal format gets discarded
  92.     # (for all parts of the address) and only decimal and hex work.
  93.     local $allow_octal = (/(^|\.)0\d*[89]/ ? 0 : 1);
  94.  
  95.     my @decode;
  96.     for my $i (0..$#bits) {
  97.         my @octets = _canon_num($bits[$i],$sizes[$i]);
  98.         return unless @octets;
  99.         push @decode, @octets;
  100.     }
  101.  
  102.     return join '.', @decode;
  103. }
  104.  
  105. sub _canon_num {
  106.     my ($num, $bytes) = @_;
  107.  
  108.     if ($allow_octal and $num =~ /^0[0-7]*$/) {
  109.         # mod 2^32, only need the last 11 octal digits, == 33 bits
  110.         $num =~ s/.*(.{11})$/$1/;
  111.         $num = oct "0$num";
  112.     } elsif ($num =~ /^[0-9]+$/) {
  113.         # mod 2^32, only need the last 32 decimal digits, since 10^32
  114.         # is a multiple of 2^32.
  115.         $num =~ s/.*(.{32})$/$1/;
  116.         # If the number is very large then rounding errors when perl
  117.         # converts it to a double precision float and then does modulo
  118.         # 2^32 could leave us with the wrong IP address.  So we
  119.         # convert from string to mod32 integer in one operation.
  120.         my @digits = split //, $num;
  121.         $num = 0;
  122.         foreach my $digit (@digits) {
  123.             $num *= 10;
  124.             $num += $digit;
  125.             $num %= (2**32);
  126.         }
  127.     } elsif ($num =~ /^0[xX]([0-9a-zA-Z]+)$/) {
  128.         $num = $1;
  129.         # mod 2^32, only need the last 8 hex digits
  130.         $num =~ s/.*(.{8})$/$1/;
  131.         $num = hex "0x$num";
  132.     } else {
  133.         return;
  134.     }
  135.  
  136.     my @octets;
  137.     while ($bytes--) {
  138.         unshift @octets, $num%256;
  139.         $num -= $num%256;
  140.         $num /= 256;
  141.     }
  142.     return @octets;
  143. }
  144.  
  145. =item url_to_blocklist_hostname ( URL )
  146.  
  147. Extracts the hostname part of the URL, in a canonical form
  148. suitable for use as an index into the FS::PhishBL blocklist.
  149.  
  150. Returns the canonical hostname, or C<undef> if no hostname can be
  151. extracted from the URL.
  152.  
  153. =cut
  154.  
  155. sub url_to_blocklist_hostname {
  156.     my ($url) = @_;
  157.  
  158.     my $MAX_DOTS = 5; # constant
  159.  
  160.     return unless $url =~ m|^[^:]+://(?:[^\?/#]*@)?([^\\/\#?:]+)|;
  161.     my $hostname = $1;
  162.     $hostname =~ s/\%([a-f0-9]{2})/chr(hex($1))/ige; # unescape
  163.     $hostname =~ s/[\x00-\x1f\x7f-\xff]+//g; # strip dodgy chars
  164.     $hostname =~ s/^\.+|\.+$//g; # trim dots from lhs and rhs
  165.     $hostname =~ s/\.{2,}/./g; # collapse multiple dots
  166.     $hostname = parse_ip_address($hostname) || $hostname;
  167.     $hostname =~ s/([^A-Za-z0-9\-\.])/'%'.sprintf("%02x", ord($1))/ge; # re-escape
  168.  
  169.     my @hostparts = split /\./, $hostname;
  170.     my $start = $#hostparts - $MAX_DOTS;
  171.     $start = 0 if $start < 0;
  172.     $hostname = join('.', @hostparts[$start..$#hostparts]);
  173.  
  174.     return lc $hostname;
  175. }
  176.  
  177. 1;
  178.