aboutsummaryrefslogtreecommitdiff
path: root/lib/mauve/source_list.rb
blob: caa9f9aac273954b9f6666067413b4cdd468e425 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# encoding: UTF-8
require 'log4r'
require 'ipaddr'
require 'uri'
require 'mauve/mauve_time'
require 'mauve/mauve_resolv'

module Mauve

  # A simple construct to match sources.
  #
  # This class stores mamed lists of IP addresses.  It stores them in a hash
  # indexed by the name of the list.  One can pass IPv4, IPv6 and hostnames
  # as list elements but they will all be converted into IP addresses at 
  # the time of the list creation.
  #
  # One can ask if an IPv4, IPv6, hostname or url (match on hostname only) is
  # contained within a list.  If the query is not an IP address, it will be
  # converted into one before the checks are made.
  #
  # Note that the matching is greedy.  When a hostname maps to several IP
  # addresses and only one of tbhose is included in the list, a match 
  # will occure.  
  #
  # @author Yann Golanski
  class SourceList 

    attr_reader :label, :list

    ## Default contructor.
    def initialize (label)
      @label            = label
      @last_resolved_at = nil
      @list = []
      @resolved_list = []
    end

    alias username label

    def +(l)
      arr = [l].flatten.collect do |h|
        # "*"              means [^\.]+
        # "(\d+)\.\.(\d+)" is expanded to every integer between $1 and $2
        #                  joined by a pipe, e.g. 1..5 means 1|2|3|4|5
        #  "."              is literal, not a single-character match
        if h.is_a?(String) and (h =~ /[\[\]\*]/ or h =~ /(\d+)\.\.(\d+)/)
          Regexp.new(
              h.
              gsub(/(\d+)\.\.(\d+)/) { |a,b|
                ($1.to_i..$2.to_i).collect.join("|")
              }.
              gsub(/\./, "\\.").
              gsub(/\*/, "[0-9a-z\\-]+") +
              "\\.?$")
        elsif h.is_a?(String) and h =~ /^[0-9a-f\.:\/]+$/i
          IPAddr.new(h)
        else
          h
        end
      end.flatten

      arr.each do |source|
        ##
        # I would use include? here, but IPAddr tries to convert "foreign"
        # classes to intgers, and RegExp doesn't have a to_i method..
        #
        if @list.any?{|e| source.is_a?(e.class) and source == e}
          logger.warn "#{source} is already on the #{self.label} list"
        else
          @list << source
        end
      end

      @resolved_list    = [] 
      @last_resolved_at = nil

      self
    end

    alias add_to_list +

    def logger
      @logger ||= Log4r::Logger.new self.class.to_s
    end

    ## 
    # Return whether or not a list contains a source.
    ##
    def includes?(host)
      #
      # Redo resolution every thirty minutes
      #
      resolve if @resolved_list.empty? or @last_resolved_at.nil? or (Time.now - 1800) > @last_resolved_at

      #
      # Pick out hostnames from URIs.
      #
      if host =~ /^[a-z][a-z0-9+-]+:\/\//
        begin      
          uri = URI.parse(host)
          host = uri.host unless uri.host.nil?
        rescue URI::InvalidURIError => ex
          # ugh
          logger.warn "Did not recognise URI #{host}"
        end
      end

      return true if @resolved_list.any? do |l|
        case l
          when String
            host == l
          when Regexp
            host =~ l
          when IPAddr 
            begin
              l.include?(IPAddr.new(host))
            rescue ArgumentError
              # rescue random IPAddr argument errors
              false
            end
          else
            false
        end
      end

      return false unless @resolved_list.any?{|l| l.is_a?(IPAddr)}

      ips = MauveResolv.get_ips_for(host).collect{|i| IPAddr.new(i)}

      return false if ips.empty?

      return @resolved_list.select{|i| i.is_a?(IPAddr)}.any? do |list_ip| 
        ips.any?{|ip| list_ip.include?(ip)}
      end
      
    end

    def resolve
      @last_resolved_at = Time.now

      new_list = @list.collect do |host| 
        if host.is_a?(String)
          ips = [host] + MauveResolv.get_ips_for(host).collect{|i| IPAddr.new(i)}
        else
          host
        end
      end
      @resolved_list = new_list.flatten
    end
  end

end