ipaccess 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,435 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Paweł Wilk (mailto:pw@gnu.org)
4
+ # Copyright:: Copyright (c) 2009 Paweł Wilk
5
+ # License:: This program is licensed under the terms of {GNU Lesser General Public License}[link:docs/LGPL-LICENSE.html] or {Ruby License}[link:docs/COPYING.html].
6
+ #
7
+ # Modules contained in this file are meant for
8
+ # patching Ruby socket classes in order to add
9
+ # IP access control to them.
10
+ #
11
+ #--
12
+ #
13
+ # Copyright (C) 2009 by Paweł Wilk. All Rights Reserved.
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU Lesser General Public License
17
+ # as published by the Free Software Foundation; either version 3 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #++
23
+ #
24
+
25
+ require 'socket'
26
+ require 'singleton'
27
+ require 'ipaccess/ip_access_errors'
28
+
29
+ class IPAccess
30
+
31
+ # This is global access set, used by
32
+ # default by all socket handling
33
+ # classes with enabled IP access control.
34
+
35
+ Global = IPAccess.new 'global'
36
+
37
+ end
38
+
39
+ # :stopdoc:
40
+
41
+ # This modules contain patches for Ruby socket
42
+ # classes in order to enable IP access control
43
+ # for them.
44
+ #
45
+ # This module patches socket handling classes
46
+ # to use IP access control. Each patched socket
47
+ # class has acl member, which is an IPAccess object.
48
+
49
+ module IPAccess::Patches
50
+
51
+ # This class is a proxy that raises an exception when
52
+ # any method other than defined in Object class is called.
53
+ # It behaves like NilClass.
54
+
55
+ class GlobalSet
56
+
57
+ include Singleton
58
+
59
+ def nil?; true end
60
+
61
+ def method_missing(name, *args)
62
+ return nil.method(name).call(*args) if nil.respond_to?(name)
63
+ raise ArgumentError, "cannot access global set from object's scope, use IPAccess::Global"
64
+ end
65
+
66
+ end
67
+
68
+
69
+ # The IPSocketAccess module contains methods
70
+ # that are present in all classes handling
71
+ # sockets with IP access control enabled.
72
+
73
+ module IPSocketAccess
74
+
75
+ # This method enables usage of internal IP access list for object.
76
+ # If argument is IPAccess object then it is used.
77
+ #
78
+ # ==== Example
79
+ #
80
+ # socket.acl = :global # use global access set
81
+ # socket.acl = :private # create and use individual access set
82
+ # socket.acl = IPAccess.new # use external (shared) access set
83
+
84
+ def acl=(obj)
85
+ if obj.is_a?(Symbol)
86
+ case obj
87
+ when :global
88
+ @acl = GlobalSet.instance
89
+ when :private
90
+ @acl = IPAccess.new
91
+ else
92
+ raise ArgumentError, "bad access list selector, use: :global or :private"
93
+ end
94
+ elsif obj.is_a?(IPAccess)
95
+ @acl = obj
96
+ else
97
+ raise ArgumentError, "bad access list"
98
+ end
99
+ end
100
+
101
+ attr_reader :acl
102
+ alias_method :access=, :acl=
103
+ alias_method :access, :acl
104
+
105
+ end
106
+
107
+ ###################################################################
108
+ # Socket class with IP access control.
109
+ # It uses input and output access lists.
110
+
111
+ module Socket
112
+
113
+ include IPSocketAccess
114
+
115
+ def self.included(base)
116
+
117
+ marker = (base.name =~ /IPAccess/) ? base.superclass : base
118
+ return if marker.instance_variable_defined?(:@uses_ipaccess)
119
+ base.instance_variable_set(:@uses_ipaccess, true)
120
+
121
+ base.class_eval do
122
+
123
+ orig_initialize = self.instance_method :initialize
124
+ orig_accept = self.instance_method :accept
125
+ orig_accept_nonblock = self.instance_method :accept_nonblock
126
+ orig_connect = self.instance_method :connect
127
+ orig_recvfrom = self.instance_method :recvfrom
128
+ orig_recvfrom_nonblock = self.instance_method :recvfrom_nonblock
129
+ orig_sysaccept = self.instance_method :sysaccept
130
+
131
+ define_method :initialize do |*args|
132
+ @acl = GlobalSet.instance
133
+ orig_initialize(*args)
134
+ return self
135
+ end
136
+
137
+ # accept on steroids.
138
+ define_method :accept do |*args|
139
+ acl = @acl.nil? ? IPAccess::Global : @acl
140
+ ret = orig_accept.bind(self).call(*args)
141
+ acl.check_in_socket(ret.first)
142
+ return ret
143
+ end
144
+
145
+ # accept_nonblock on steroids.
146
+ define_method :accept_nonblock do |*args|
147
+ acl = @acl.nil? ? IPAccess::Global : @acl
148
+ ret = orig_accept_nonblock.bind(self).call(*args)
149
+ acl.check_in_socket(ret.first)
150
+ return ret
151
+ end
152
+
153
+ # sysaccept on steroids.
154
+ define_method :sysaccept do |*args|
155
+ acl = @acl.nil? ? IPAccess::Global : @acl
156
+ ret = orig_accept.bind(self).call(*args)
157
+ acl.check_in_sockaddr(ret.last)
158
+ return ret
159
+ end
160
+
161
+ # connect on steroids.
162
+ define_method :connect do |*args|
163
+ acl = @acl.nil? ? IPAccess::Global : @acl
164
+ acl.check_out_sockaddr(args.first)
165
+ return orig_connect.bind(self).call(*args)
166
+ end
167
+
168
+ # recvfrom on steroids.
169
+ define_method :recvfrom do |*args|
170
+ acl = @acl.nil? ? IPAccess::Global : @acl
171
+ ret = orig_recvfrom.bind(self).call(*args)
172
+ peer_ip = ret[1][3]
173
+ family = ret[1][0]
174
+ if (family == "AF_INET" || family == "AF_INET6")
175
+ acl.check_in_ipstring(peer_ip)
176
+ end
177
+ return ret
178
+ end
179
+
180
+ # recvfrom_nonblock on steroids.
181
+ define_method :recvfrom_nonblock do |*args|
182
+ acl = @acl.nil? ? IPAccess::Global : @acl
183
+ ret = orig_recvfrom_nonblock.bind(self).call(*args)
184
+ peer_ip = ret[1][3]
185
+ family = ret[1][0]
186
+ if (family == "AF_INET" || family == "AF_INET6")
187
+ acl.check_in_ipstring(peer_ip)
188
+ end
189
+ return ret
190
+ end
191
+
192
+ end # base.class_eval
193
+
194
+ end # self.included
195
+
196
+ end # module Socket
197
+
198
+ ###################################################################
199
+ # UDPSocket class with IP access control.
200
+ # It uses input and output access lists.
201
+
202
+ module UDPSocket
203
+
204
+ include IPSocketAccess
205
+
206
+ def self.included(base)
207
+
208
+ marker = (base.name =~ /IPAccess/) ? base.superclass : base
209
+ return if marker.instance_variable_defined?(:@uses_ipaccess)
210
+ base.instance_variable_set(:@uses_ipaccess, true)
211
+
212
+ base.class_eval do
213
+
214
+ orig_initialize = self.instance_method :initialize
215
+ orig_connect = self.instance_method :connect
216
+ orig_send = self.instance_method :send
217
+ orig_recvfrom = self.instance_method :recvfrom
218
+ orig_recvfrom_nonblock = self.instance_method :recvfrom_nonblock
219
+
220
+ define_method :initialize do |*args|
221
+ @acl = GlobalSet.instance
222
+ orig_initialize(*args)
223
+ return self
224
+ end
225
+
226
+ # connect on steroids.
227
+ define_method :connect do |*args|
228
+ acl = @acl.nil? ? IPAccess::Global : @acl
229
+ peer_ip = self.class.getaddress(args.shift)
230
+ acl.check_out_sockaddr(peer_ip)
231
+ return orig_connect.bind(self).call(peer_ip, *args)
232
+ end
233
+
234
+ # send on steroids.
235
+ define_method :send do |*args|
236
+ hostname = args[2]
237
+ return orig_send(*args) if hostname.nil?
238
+ acl = @acl.nil? ? IPAccess::Global : @acl
239
+ peer_ip = self.class.getaddress(hostname)
240
+ acl.check_out_sockaddr(peer_ip)
241
+ args[2] = peer_ip
242
+ return orig_send.bind(self).call(*args)
243
+ end
244
+
245
+ # recvfrom on steroids.
246
+ define_method :recvfrom do |*args|
247
+ acl = @acl.nil? ? IPAccess::Global : @acl
248
+ ret = orig_recvfrom.bind(self).call(*args)
249
+ peer_ip = ret[1][3]
250
+ family = ret[1][0]
251
+ if (family == "AF_INET" || family == "AF_INET6")
252
+ acl.check_in_ipstring(peer_ip)
253
+ end
254
+ return ret
255
+ end
256
+
257
+ # recvfrom_nonblock on steroids.
258
+ define_method :recvfrom_nonblock do |*args|
259
+ acl = @acl.nil? ? IPAccess::Global : @acl
260
+ ret = orig_recvfrom_nonblock.bind(self).call(*args)
261
+ peer_ip = ret[1][3]
262
+ family = ret[1][0]
263
+ if (family == "AF_INET" || family == "AF_INET6")
264
+ acl.check_in_ipstring(peer_ip)
265
+ end
266
+ return ret
267
+ end
268
+
269
+ end # base.class_eval
270
+
271
+ end # self.included
272
+
273
+ end # module UDPSocket
274
+
275
+ ###################################################################
276
+ # SOCKSSocket class with IP access control.
277
+ # It uses output access lists.
278
+
279
+ module SOCKSocket
280
+
281
+ include IPSocketAccess
282
+
283
+ def self.included(base)
284
+
285
+ marker = (base.name =~ /IPAccess/) ? base.superclass : base
286
+ return if marker.instance_variable_defined?(:@uses_ipaccess)
287
+ base.instance_variable_set(:@uses_ipaccess, true)
288
+
289
+ base.class_eval do
290
+
291
+ orig_initialize = self.instance_method :initialize
292
+
293
+ # initialize on steroids.
294
+ define_method :initialize do |*args|
295
+ self.acl = (args.size > 2) ? args.pop : :global
296
+ acl = @acl.nil? ? IPAccess::Global : @acl
297
+ args[0] = self.class.getaddress(args[0])
298
+ acl.check_out_ipstring args[0]
299
+ orig_initialize.bind(self).call(*args)
300
+ return self
301
+ end
302
+
303
+ end # base.class_eval
304
+
305
+ end # self.included
306
+
307
+ end # module SOCKSSocket
308
+
309
+ ###################################################################
310
+ # TCPSocket class with IP access control.
311
+ # It uses output access lists.
312
+
313
+ module TCPSocket
314
+
315
+ include IPSocketAccess
316
+
317
+ def self.included(base)
318
+
319
+ marker = (base.name =~ /IPAccess/) ? base.superclass : base
320
+ return if marker.instance_variable_defined?(:@uses_ipaccess)
321
+ base.instance_variable_set(:@uses_ipaccess, true)
322
+
323
+ base.class_eval do
324
+
325
+ orig_initialize = self.instance_method :initialize
326
+
327
+ # initialize on steroids.
328
+ define_method :initialize do |*args|
329
+ self.acl = (args.size > 2) ? args.pop : :global
330
+ acl = @acl.nil? ? IPAccess::Global : @acl
331
+ args[0] = self.class.getaddress(args[0])
332
+ acl.check_out_ipstring args[0]
333
+ orig_initialize.bind(self).call(*args)
334
+ return self
335
+ end
336
+
337
+ end # base.class_eval
338
+
339
+ end # self.included
340
+
341
+ end # module TCPSocket
342
+
343
+ ###################################################################
344
+ # TCPServer class with IP access control.
345
+ # It uses input access lists.
346
+
347
+ module TCPServer
348
+
349
+ include IPSocketAccess
350
+
351
+ def self.included(base)
352
+
353
+ marker = (base.name =~ /IPAccess/) ? base.superclass : base
354
+ return if marker.instance_variable_defined?(:@uses_ipaccess)
355
+ base.instance_variable_set(:@uses_ipaccess, true)
356
+
357
+ base.class_eval do
358
+
359
+ orig_initialize = self.instance_method :initialize
360
+ orig_accept = self.instance_method :accept
361
+ orig_accept_nonblock = self.instance_method :accept_nonblock
362
+ orig_sysaccept = self.instance_method :sysaccept
363
+
364
+ # initialize on steroids.
365
+ define_method :initialize do |*args|
366
+ @acl = GlobalSet.instance
367
+ return orig_initialize.bind(self).call(*args)
368
+ end
369
+
370
+ # accept on steroids.
371
+ define_method :accept do |*args|
372
+ acl = @acl.nil? ? IPAccess::Global : @acl
373
+ acl.check_in_socket orig_accept.bind(self).call(*args)
374
+ end
375
+
376
+ # accept_nonblock on steroids.
377
+ define_method :accept_nonblock do |*args|
378
+ acl = @acl.nil? ? IPAccess::Global : @acl
379
+ acl.check_in_socket orig_accept_nonblock.bind(self).call(*args)
380
+ end
381
+
382
+ # sysaccept on steroids.
383
+ define_method :sysaccept do |*args|
384
+ acl = @acl.nil? ? IPAccess::Global : @acl
385
+ acl.check_in_fd orig_sysaccept.bind(self).call(*args)
386
+ end
387
+
388
+ end # base.class_eval
389
+
390
+ end # self.included
391
+
392
+ end # module TCPServer
393
+
394
+ end # module IPAccess::Patches
395
+
396
+ # :startdoc:
397
+
398
+ class IPAccess
399
+
400
+ # This special method patches Ruby's standard
401
+ # library socket handling classes and enables
402
+ # IP access control for them. Instances of
403
+ # such altered classes will be equipped with
404
+ # member called +acl+ which is a kind of
405
+ # IPAccess and allows you to manipulate
406
+ # access rules.
407
+ #
408
+ # Passed argument may be a class object,
409
+ # a string representation of a class object
410
+ # or a symbol representing a class object.
411
+ #
412
+ # Currently supported classes are:
413
+ # +Socket+, +UDPSocket+, +SOCKSSocket+,
414
+ # +TCPSocket+ and +TCPServer+.
415
+ #
416
+ # Example:
417
+ #
418
+ # IPAccess.arm TCPSocket # arm TCPSocket class
419
+ # IPAccess::Global.output.blacklist 'randomseed.pl' # add host to black list of the global set
420
+ # TCPSocket.new('randomseed.pl', 80) # try to connect
421
+
422
+ def self.arm(klass)
423
+ klass_name = klass.name if klass.is_a?(Class)
424
+ klass_name = klass_name.to_s unless klass.is_a?(String)
425
+ klass_name = klass_name.to_sym
426
+ case klass_name
427
+ when :Socket, :UDPSocket, :SOCKSSocket, :TCPSocket, :TCPServer
428
+ klass.__send__(:include, Patches.const_get(klass_name))
429
+ else
430
+ raise ArgumentError, "cannot enable IP access control for class #{klass_name}"
431
+ end
432
+ end
433
+
434
+ end
435
+
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Paweł Wilk (mailto:pw@gnu.org)
4
+ # Copyright:: Copyright (c) 2009 Paweł Wilk
5
+ # License:: This program is licensed under the terms of {GNU Lesser General Public License}[link:docs/LGPL-LICENSE.html] or {Ruby License}[link:docs/COPYING.html].
6
+ #
7
+ # This file extends NetAddr by adding methods
8
+ # that bring some comfort into IPv6 handling.
9
+ #
10
+ #--
11
+ #
12
+ # Copyright (C) 2009 by Paweł Wilk. All Rights Reserved.
13
+ #
14
+ # This program is free software; you can redistribute it and/or modify
15
+ # it under the terms of either: 1) the GNU Lesser General Public License
16
+ # as published by the Free Software Foundation; either version 3 of the
17
+ # License, or (at your option) any later version; or 2) Ruby's License.
18
+ #
19
+ # See the file COPYING for complete licensing information.
20
+ #
21
+ #++
22
+
23
+ require 'netaddr'
24
+
25
+ # This module contains few new methods extending
26
+ # original NetAddr module.
27
+
28
+ module NetAddr
29
+
30
+ # :stopdoc:
31
+
32
+ class CIDR
33
+
34
+ # Returns +true+ if the IP address is an IPv4-mapped IPv6 address.
35
+
36
+ def ipv4_mapped?
37
+ return @version == 6 && (@ip >> 32) == 0xffff
38
+ end
39
+
40
+ # Returns +true+ if the IP address is an IPv4-compatible IPv6 address.
41
+
42
+ def ipv4_compat?
43
+ return false if @version != 6
44
+ return false if (@ip >> 32) != 0
45
+ a = (@ip & 0xffffffff)
46
+ return (a != 0 && a != 1)
47
+ end
48
+
49
+ # Returns +true+ if the IP address is an IPv4-compatible or
50
+ # IPv4-mapped IPv6 address.
51
+
52
+ def ipv4_compliant?
53
+ return false if @version != 6
54
+ a = (@ip >> 32)
55
+ return (a == 0xffff) if a.nonzero?
56
+ a = (@ip & 0xffffffff)
57
+ return (a != 0 && a != 1)
58
+ end
59
+
60
+ # This method duplicates CIDR and removes
61
+ # tags specified as symbols. It returns new
62
+ # Netaddr::CIDR object.
63
+
64
+ def safe_dup(*tags_to_remove)
65
+ tags = self.tag.dup
66
+ tags_to_remove.each { |t| tags.delete t }
67
+ return NetAddr.cidr_build(
68
+ @version,
69
+ @network,
70
+ @netmask,
71
+ tags,
72
+ @wildcard_mask)
73
+ end
74
+
75
+ end # class CIDR
76
+
77
+ class CIDRv4
78
+
79
+ # Returns a new NetAddr::CIDRv6 object built by converting
80
+ # the native IPv4 address to an IPv4-mapped IPv6 address.
81
+ # Mask is also converted.
82
+
83
+ def ipv4_mapped
84
+ return NetAddr::CIDR.create(@ip | 0xffff00000000,
85
+ :Mask => @netmask << 96,
86
+ :Version => 6)
87
+ end
88
+
89
+ alias_method :ipv6, :ipv4_mapped
90
+ alias_method :to_ipv6, :ipv4_mapped
91
+
92
+ # Returns a new NetAddr::CIDRv6 object built by converting
93
+ # the native IPv4 address to an IPv4-compatible IPv6 address.
94
+ # Mask is also converted.
95
+
96
+ def ipv4_compat
97
+ return NetAddr::CIDR.create(@ip,
98
+ :Mask => @netmask << 96,
99
+ :Version => 6)
100
+ end
101
+
102
+ end # class CIDRv4
103
+
104
+ class CIDRv6
105
+
106
+ def ipv4
107
+ if ipv4_mapped?
108
+ ip = @ip ^ 0xffff00000000
109
+ elsif ipv4_compat?
110
+ ip = @ip
111
+ else
112
+ raise VersionError, "Attempted to create version 4 CIDR " +
113
+ "with non-compliant CIDR item in version #{@version}."
114
+ end
115
+ return NetAddr::CIDR.create(ip,
116
+ :Mask => @netmask >> 96,
117
+ :Version => 4)
118
+ end
119
+
120
+ alias_method :to_ipv4, :ipv4
121
+
122
+ end # class CIDRv4
123
+
124
+ # :startdoc:
125
+
126
+ end # module NetAddr
127
+