1# Copyright (c) 2000,2002,2003 Masatoshi SEKI 2# 3# acl.rb is copyrighted free software by Masatoshi SEKI. 4# You can redistribute it and/or modify it under the same terms as Ruby. 5 6require 'ipaddr' 7 8## 9# Simple Access Control Lists. 10# 11# Access control lists are composed of "allow" and "deny" halves to control 12# access. Use "all" or "*" to match any address. To match a specific address 13# use any address or address mask that IPAddr can understand. 14# 15# Example: 16# 17# list = %w[ 18# deny all 19# allow 192.168.1.1 20# allow ::ffff:192.168.1.2 21# allow 192.168.1.3 22# ] 23# 24# # From Socket#peeraddr, see also ACL#allow_socket? 25# addr = ["AF_INET", 10, "lc630", "192.168.1.3"] 26# 27# acl = ACL.new 28# p acl.allow_addr?(addr) # => true 29# 30# acl = ACL.new(list, ACL::DENY_ALLOW) 31# p acl.allow_addr?(addr) # => true 32 33class ACL 34 35 ## 36 # The current version of ACL 37 38 VERSION=["2.0.0"] 39 40 ## 41 # An entry in an ACL 42 43 class ACLEntry 44 45 ## 46 # Creates a new entry using +str+. 47 # 48 # +str+ may be "*" or "all" to match any address, an IP address string 49 # to match a specific address, an IP address mask per IPAddr, or one 50 # containing "*" to match part of an IPv4 address. 51 52 def initialize(str) 53 if str == '*' or str == 'all' 54 @pat = [:all] 55 elsif str.include?('*') 56 @pat = [:name, dot_pat(str)] 57 else 58 begin 59 @pat = [:ip, IPAddr.new(str)] 60 rescue ArgumentError 61 @pat = [:name, dot_pat(str)] 62 end 63 end 64 end 65 66 private 67 68 ## 69 # Creates a regular expression to match IPv4 addresses 70 71 def dot_pat_str(str) 72 list = str.split('.').collect { |s| 73 (s == '*') ? '.+' : s 74 } 75 list.join("\\.") 76 end 77 78 private 79 80 ## 81 # Creates a Regexp to match an address. 82 83 def dot_pat(str) 84 exp = "^" + dot_pat_str(str) + "$" 85 Regexp.new(exp) 86 end 87 88 public 89 90 ## 91 # Matches +addr+ against this entry. 92 93 def match(addr) 94 case @pat[0] 95 when :all 96 true 97 when :ip 98 begin 99 ipaddr = IPAddr.new(addr[3]) 100 ipaddr = ipaddr.ipv4_mapped if @pat[1].ipv6? && ipaddr.ipv4? 101 rescue ArgumentError 102 return false 103 end 104 (@pat[1].include?(ipaddr)) ? true : false 105 when :name 106 (@pat[1] =~ addr[2]) ? true : false 107 else 108 false 109 end 110 end 111 end 112 113 ## 114 # A list of ACLEntry objects. Used to implement the allow and deny halves 115 # of an ACL 116 117 class ACLList 118 119 ## 120 # Creates an empty ACLList 121 122 def initialize 123 @list = [] 124 end 125 126 public 127 128 ## 129 # Matches +addr+ against each ACLEntry in this list. 130 131 def match(addr) 132 @list.each do |e| 133 return true if e.match(addr) 134 end 135 false 136 end 137 138 public 139 140 ## 141 # Adds +str+ as an ACLEntry in this list 142 143 def add(str) 144 @list.push(ACLEntry.new(str)) 145 end 146 147 end 148 149 ## 150 # Default to deny 151 152 DENY_ALLOW = 0 153 154 ## 155 # Default to allow 156 157 ALLOW_DENY = 1 158 159 ## 160 # Creates a new ACL from +list+ with an evaluation +order+ of DENY_ALLOW or 161 # ALLOW_DENY. 162 # 163 # An ACL +list+ is an Array of "allow" or "deny" and an address or address 164 # mask or "all" or "*" to match any address: 165 # 166 # %w[ 167 # deny all 168 # allow 192.0.2.2 169 # allow 192.0.2.128/26 170 # ] 171 172 def initialize(list=nil, order = DENY_ALLOW) 173 @order = order 174 @deny = ACLList.new 175 @allow = ACLList.new 176 install_list(list) if list 177 end 178 179 public 180 181 ## 182 # Allow connections from Socket +soc+? 183 184 def allow_socket?(soc) 185 allow_addr?(soc.peeraddr) 186 end 187 188 public 189 190 ## 191 # Allow connections from addrinfo +addr+? It must be formatted like 192 # Socket#peeraddr: 193 # 194 # ["AF_INET", 10, "lc630", "192.0.2.1"] 195 196 def allow_addr?(addr) 197 case @order 198 when DENY_ALLOW 199 return true if @allow.match(addr) 200 return false if @deny.match(addr) 201 return true 202 when ALLOW_DENY 203 return false if @deny.match(addr) 204 return true if @allow.match(addr) 205 return false 206 else 207 false 208 end 209 end 210 211 public 212 213 ## 214 # Adds +list+ of ACL entries to this ACL. 215 216 def install_list(list) 217 i = 0 218 while i < list.size 219 permission, domain = list.slice(i,2) 220 case permission.downcase 221 when 'allow' 222 @allow.add(domain) 223 when 'deny' 224 @deny.add(domain) 225 else 226 raise "Invalid ACL entry #{list.to_s}" 227 end 228 i += 2 229 end 230 end 231 232end 233 234if __FILE__ == $0 235 # example 236 list = %w(deny all 237 allow 192.168.1.1 238 allow ::ffff:192.168.1.2 239 allow 192.168.1.3 240 ) 241 242 addr = ["AF_INET", 10, "lc630", "192.168.1.3"] 243 244 acl = ACL.new 245 p acl.allow_addr?(addr) 246 247 acl = ACL.new(list, ACL::DENY_ALLOW) 248 p acl.allow_addr?(addr) 249end 250 251