ipaccess 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+