rbmk 0.1.0.a

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 915c433f207ea0656ded0512c10507183ba35211
4
+ data.tar.gz: 8ee0b1ef7c46e215b298783d0597dd6d085368f0
5
+ SHA512:
6
+ metadata.gz: 9fe9a3bce2171a3b8466f6034d0569ccbb691fe2aee9a161b2ffed4d5695e9bbd68e7b8662d71284a01952cca1c6bbafee621a5f914f589918c3edb915ad359c
7
+ data.tar.gz: 8fa2b7a9794a4a6bbfb8d25b279445c87a4d20d8936e70fd57c292cd808895bc44e7dcde0eaf14f495c6d2d700b5067dbe57ed1ab1c276c2ad2d20680edbfd7a
data/LICENSE ADDED
@@ -0,0 +1,117 @@
1
+ CC0 1.0 Universal
2
+
3
+ Statement of Purpose
4
+
5
+ The laws of most jurisdictions throughout the world automatically confer
6
+ exclusive Copyright and Related Rights (defined below) upon the creator and
7
+ subsequent owner(s) (each and all, an "owner") of an original work of
8
+ authorship and/or a database (each, a "Work").
9
+
10
+ Certain owners wish to permanently relinquish those rights to a Work for the
11
+ purpose of contributing to a commons of creative, cultural and scientific
12
+ works ("Commons") that the public can reliably and without fear of later
13
+ claims of infringement build upon, modify, incorporate in other works, reuse
14
+ and redistribute as freely as possible in any form whatsoever and for any
15
+ purposes, including without limitation commercial purposes. These owners may
16
+ contribute to the Commons to promote the ideal of a free culture and the
17
+ further production of creative, cultural and scientific works, or to gain
18
+ reputation or greater distribution for their Work in part through the use and
19
+ efforts of others.
20
+
21
+ For these and/or other purposes and motivations, and without any expectation
22
+ of additional consideration or compensation, the person associating CC0 with a
23
+ Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24
+ and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25
+ and publicly distribute the Work under its terms, with knowledge of his or her
26
+ Copyright and Related Rights in the Work and the meaning and intended legal
27
+ effect of CC0 on those rights.
28
+
29
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
30
+ protected by copyright and related or neighboring rights ("Copyright and
31
+ Related Rights"). Copyright and Related Rights include, but are not limited
32
+ to, the following:
33
+
34
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
35
+ and translate a Work;
36
+
37
+ ii. moral rights retained by the original author(s) and/or performer(s);
38
+
39
+ iii. publicity and privacy rights pertaining to a person's image or likeness
40
+ depicted in a Work;
41
+
42
+ iv. rights protecting against unfair competition in regards to a Work,
43
+ subject to the limitations in paragraph 4(a), below;
44
+
45
+ v. rights protecting the extraction, dissemination, use and reuse of data in
46
+ a Work;
47
+
48
+ vi. database rights (such as those arising under Directive 96/9/EC of the
49
+ European Parliament and of the Council of 11 March 1996 on the legal
50
+ protection of databases, and under any national implementation thereof,
51
+ including any amended or successor version of such directive); and
52
+
53
+ vii. other similar, equivalent or corresponding rights throughout the world
54
+ based on applicable law or treaty, and any national implementations thereof.
55
+
56
+ 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59
+ and Related Rights and associated claims and causes of action, whether now
60
+ known or unknown (including existing as well as future claims and causes of
61
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
62
+ duration provided by applicable law or treaty (including future time
63
+ extensions), (iii) in any current or future medium and for any number of
64
+ copies, and (iv) for any purpose whatsoever, including without limitation
65
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66
+ the Waiver for the benefit of each member of the public at large and to the
67
+ detriment of Affirmer's heirs and successors, fully intending that such Waiver
68
+ shall not be subject to revocation, rescission, cancellation, termination, or
69
+ any other legal or equitable action to disrupt the quiet enjoyment of the Work
70
+ by the public as contemplated by Affirmer's express Statement of Purpose.
71
+
72
+ 3. Public License Fallback. Should any part of the Waiver for any reason be
73
+ judged legally invalid or ineffective under applicable law, then the Waiver
74
+ shall be preserved to the maximum extent permitted taking into account
75
+ Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76
+ is so judged Affirmer hereby grants to each affected person a royalty-free,
77
+ non transferable, non sublicensable, non exclusive, irrevocable and
78
+ unconditional license to exercise Affirmer's Copyright and Related Rights in
79
+ the Work (i) in all territories worldwide, (ii) for the maximum duration
80
+ provided by applicable law or treaty (including future time extensions), (iii)
81
+ in any current or future medium and for any number of copies, and (iv) for any
82
+ purpose whatsoever, including without limitation commercial, advertising or
83
+ promotional purposes (the "License"). The License shall be deemed effective as
84
+ of the date CC0 was applied by Affirmer to the Work. Should any part of the
85
+ License for any reason be judged legally invalid or ineffective under
86
+ applicable law, such partial invalidity or ineffectiveness shall not
87
+ invalidate the remainder of the License, and in such case Affirmer hereby
88
+ affirms that he or she will not (i) exercise any of his or her remaining
89
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
90
+ and causes of action with respect to the Work, in either case contrary to
91
+ Affirmer's express Statement of Purpose.
92
+
93
+ 4. Limitations and Disclaimers.
94
+
95
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
96
+ surrendered, licensed or otherwise affected by this document.
97
+
98
+ b. Affirmer offers the Work as-is and makes no representations or warranties
99
+ of any kind concerning the Work, express, implied, statutory or otherwise,
100
+ including without limitation warranties of title, merchantability, fitness
101
+ for a particular purpose, non infringement, or the absence of latent or
102
+ other defects, accuracy, or the present or absence of errors, whether or not
103
+ discoverable, all to the greatest extent permissible under applicable law.
104
+
105
+ c. Affirmer disclaims responsibility for clearing rights of other persons
106
+ that may apply to the Work or any use thereof, including without limitation
107
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
108
+ disclaims responsibility for obtaining any necessary consents, permissions
109
+ or other rights required for any use of the Work.
110
+
111
+ d. Affirmer understands and acknowledges that Creative Commons is not a
112
+ party to this document and has no duty or obligation with respect to this
113
+ CC0 or use of the Work.
114
+
115
+ For more information, please see
116
+ <http://creativecommons.org/publicdomain/zero/1.0/>
117
+
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ RBMK
2
+ ====
3
+ [//]: # (DESCRIPTION START)
4
+ This is a rather simple Ruby LDAP server that proxies operations upstream but
5
+ at the same time provides a facility to invoke your code at certain points in
6
+ the operation runtime. This may help to accomodate for some clients that
7
+ are not smart enough to implement the logic you need themselves.
8
+ LDAP is very rigid and static in its nature and although OpenLDAP provides some
9
+ very helpful overlays, it is far from enough.
10
+ [//]: # (DESCRIPTION STOP)
11
+
12
+ CAUTION
13
+ -------
14
+ Like its name suggests, `rbmk` is somewhat powerful, but is not very stable.
15
+ Expect random meltdowns! Please, **NEVER** run it as superuser. LDAP gems
16
+ that it uses are surprisingly feature-rich, but are not quite polished yet.
17
+ This user does not have the time to rewrite them and does not consider it
18
+ a huge problem. Remember, the best architecture is not the one that never fails,
19
+ but is instead the one that can handle failures gracefully.
20
+
21
+ LIMITATIONS
22
+ -----------
23
+ * This proxy is read-only, by design.
24
+ * This script does not detach from its terminal, again by design.
25
+ * Only tested with MRI 2.2, but will likely work with anything 1.9+.
26
+
27
+ INSTALL
28
+ -------
29
+ `gem install rbmk`, simple as that.
30
+
31
+ RUN
32
+ ---
33
+ As this script is not a daemon, you have two easy options besides anything
34
+ you may invent yourself:
35
+ 1. use any supervisor that are plenty nowadays: `supervisord`, `bluepill` etc.
36
+ 1. or just run it inside a `tmux` session and leave it there.
37
+
38
+ USAGE
39
+ -----
40
+ `rbmk FILENAME`, where *FILENAME* is a configuration file.
41
+
42
+ CONFIGURATION
43
+ -------------
44
+ Upon its invocation `rbmk` evals its first argument and thus is configured
45
+ by your Ruby code inside that file. Please refer to `examples/rbmk.rb` for an example
46
+ configuration file.
data/bin/rbmk ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ cfn = ARGV.first
4
+ (puts "Usage: #{File.basename $0} FILENAME"; exit) if cfn.nil? or cfn.empty?
5
+
6
+ require 'rbmk/logger'
7
+ require 'rbmk/upstream'
8
+ require 'rbmk/server'
9
+ require 'rbmk/worker'
10
+
11
+ load File.expand_path(cfn)
12
+
13
+ $log = RBMK::Logger.instance
14
+
15
+ RBMK::Server.new.start
data/examples/rbmk.rb ADDED
@@ -0,0 +1,43 @@
1
+ # Override the host and port of the upstream LDAP server
2
+ #
3
+ class RBMK::Upstream
4
+ def self.host; 'ldap.example.com' end
5
+ def self.port; 33389 end
6
+ end
7
+
8
+ # Override the host and port of RBMK server
9
+ #
10
+ class RBMK::Server
11
+ def self.host; '0.0.0.0' end
12
+ def self.port; 10389 end
13
+ end
14
+
15
+ # Override logger settings
16
+ #
17
+ module RBMK::Logger
18
+ def self.level; ::Logger::DEBUG end
19
+ end
20
+
21
+ # The magic! You can transform the found entries here
22
+ #
23
+ module RBMK
24
+ # For example, we can add a fooBar attribute to any resulting object
25
+ #
26
+ def self.hack_entries entries
27
+ entries.map do |entry|
28
+ entry.merge 'fooBar' => 'baz'
29
+ end
30
+ end
31
+
32
+ # In this example we drop fooBar attribute from anywhere in the search
33
+ #
34
+ def self.hack_filter filter
35
+ op = filter.shift
36
+ case op
37
+ when :true, :false, :undef then [op]
38
+ when :not, :and, :or then [op] + filter.map { |sf| hack_filter sf }.compact
39
+ else (filter.first =~ /\Afoobar\z/i) ? nil : [op] + filter
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,11 @@
1
+ class Exception
2
+
3
+ def log
4
+ $log.error sprintf('%s: %s (%s)', backtrace.first, message, self.class)
5
+ end
6
+
7
+ def log_debug
8
+ $log.debug sprintf('%s: %s (%s)', (to_i rescue 'n/a'), message, self.class)
9
+ end
10
+
11
+ end
@@ -0,0 +1,20 @@
1
+ require 'rbmk/exception'
2
+ module RBMK
3
+ module Logger
4
+
5
+ def self.level; ::Logger::INFO end
6
+
7
+ def self.format lvl, ts, prog, msg
8
+ sprintf "%s [%s:%5i] %-5s %s\n", ts.strftime('%F:%T'), ($master ? 'M' : 'w'), Process.pid, lvl, msg
9
+ end
10
+
11
+ def self.instance
12
+ require 'logger'
13
+ log = ::Logger.new STDERR
14
+ log.level = level
15
+ log.formatter = method :format
16
+ log
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,183 @@
1
+ require 'ldap/server/operation'
2
+
3
+
4
+
5
+ class LDAP::ResultError
6
+ @map = []
7
+ constants.each do |const|
8
+ c = const_get const
9
+ i = c.new.send :to_i rescue nil
10
+ @map[i] = c if i
11
+ end
12
+
13
+ def self.from_id id, msg = nil
14
+ @map[id].new msg
15
+ end
16
+ end
17
+
18
+
19
+
20
+ class LDAP::Server::Filter
21
+ def self.to_rfc filter
22
+ raise ArgumentError, 'Array expected' unless filter.is_a? Array
23
+ raise ArgumentError, 'Filter is empty' if filter.empty?
24
+ op = filter.shift
25
+ res = case op
26
+ when :not then
27
+ raise 'Empty subfilter' if (sf = to_rfc filter).empty?
28
+ '!%s' % sf
29
+ when :and then
30
+ raise 'Empty subfilter' if (sf = filter.map { |f| to_rfc(f) }.join).empty?
31
+ '&%s' % sf
32
+ when :or
33
+ raise 'Empty subfilter' if (sf = filter.map { |f| to_rfc(f) }.join).empty?
34
+ '!%s' % sf
35
+
36
+ when :true then 'objectClass=*'
37
+ when :false then '!(objectClass=*)'
38
+ when :undef then raise 'Undefined filter has no RFC representation'
39
+
40
+ when :present then sprintf '%s=*', filter.first
41
+ when :eq then sprintf '%s=%s', filter.first, filter.last
42
+ when :approx then sprintf '%s~=%s', filter.first, filter.last
43
+ when :ge then sprintf '%s>=%s', filter.first, filter.last
44
+ when :le then sprintf '%s<=%s', filter.first, filter.last
45
+ when :substrings then
46
+ attr = filter.shift
47
+ junk = filter.shift
48
+ '%s=%s' % [attr, filter.join('*')]
49
+ else raise 'Unknown op %s' % op.inspect
50
+ end
51
+ '(%s)' % res
52
+ rescue
53
+ $!.log_debug
54
+ ''
55
+ end
56
+ end
57
+
58
+
59
+
60
+ require 'rbmk'
61
+ module RBMK
62
+ class Operation < LDAP::Server::Operation
63
+
64
+ # First some patches
65
+ #
66
+ def send_SearchResultEntry(dn, avs, opt={})
67
+ @rescount += 1
68
+ if @sizelimit
69
+ raise LDAP::ResultError::SizeLimitExceeded if @rescount > @sizelimit
70
+ end
71
+
72
+ if @schema
73
+ @attributes = @attributes.map { |a| (['*', '+'].include? a) ? a : @schema.find_attrtype(a).to_s }
74
+ end
75
+
76
+ avseq = []
77
+
78
+ avs.each do |attr, vals|
79
+
80
+ send = if @attributes.include? '+' then
81
+ true
82
+ elsif @attributes.include? '*' then
83
+ if @schema then
84
+ a = @schema.find_attrtype(attr) rescue nil
85
+ a and (a.usage.nil? or a.usage == :userApplications)
86
+ else
87
+ true
88
+ end
89
+ else
90
+ @attributes.include? attr
91
+ end
92
+
93
+ next unless send
94
+
95
+ if @typesOnly
96
+ vals = []
97
+ else
98
+ vals = [vals] unless vals.kind_of?(Array)
99
+ end
100
+ avseq << OpenSSL::ASN1::Sequence([OpenSSL::ASN1::OctetString(attr), OpenSSL::ASN1::Set(vals.collect { |v| OpenSSL::ASN1::OctetString(v.to_s) })])
101
+ end
102
+
103
+ send_LDAPMessage(OpenSSL::ASN1::Sequence([OpenSSL::ASN1::OctetString(dn), OpenSSL::ASN1::Sequence(avseq)], 4, :IMPLICIT, :APPLICATION), opt)
104
+ end
105
+
106
+ def do_search op, controls
107
+ baseObject = op.value[0].value
108
+ scope = op.value[1].value
109
+ deref = op.value[2].value
110
+ client_sizelimit = op.value[3].value
111
+ client_timelimit = op.value[4].value.to_i
112
+ @typesOnly = op.value[5].value
113
+ filter = LDAP::Server::Filter.parse(op.value[6], @schema)
114
+ @attributes = op.value[7].value.collect {|x| x.value}
115
+
116
+ @rescount = 0
117
+ @sizelimit = server_sizelimit
118
+ @sizelimit = client_sizelimit if client_sizelimit > 0 and (@sizelimit.nil? or client_sizelimit < @sizelimit)
119
+
120
+ if baseObject.empty? and scope == LDAP::Server::BaseObject
121
+ send_SearchResultEntry('', @server.root_dse) if @server.root_dse and LDAP::Server::Filter.run(filter, @server.root_dse)
122
+ send_SearchResultDone(0)
123
+ return
124
+ elsif @schema and baseObject == @schema.subschema_dn
125
+ send_SearchResultEntry(baseObject, @schema.subschema_subentry) if @schema and @schema.subschema_subentry and LDAP::Server::Filter.run(filter, @schema.subschema_subentry)
126
+ send_SearchResultDone(0)
127
+ return
128
+ end
129
+
130
+ t = server_timelimit || 10
131
+ t = client_timelimit if client_timelimit > 0 and client_timelimit < t
132
+
133
+ Timeout::timeout(t, LDAP::ResultError::TimeLimitExceeded) { search baseObject, scope, deref, filter }
134
+ send_SearchResultDone(0)
135
+
136
+ rescue LDAP::Abandon
137
+ rescue LDAP::ResultError => e
138
+ log e.message
139
+ send_SearchResultDone(e.to_i, :errorMessage=>e.message)
140
+ rescue Exception => e
141
+ log_exception(e)
142
+ send_SearchResultDone(LDAP::ResultError::OperationsError.new.to_i, :errorMessage=>e.message)
143
+ end
144
+
145
+
146
+
147
+ # Okay, now the actual code
148
+ #
149
+ def simple_bind version, dn, password
150
+ RBMK.context[:binddn] = {orig: dn}
151
+ version, dn, password = transformed(simple_bind: [version, dn, password])
152
+ RBMK.context[:binddn][:hacked] = dn
153
+ $log.info sprintf('Bind v%i, dn: %p -> %p', version, RBMK.context[:binddn][:orig], RBMK.context[:binddn][:hacked])
154
+ @server.bind version, dn, password
155
+ rescue LDAP::ResultError
156
+ $!.log_debug
157
+ raise $!
158
+ end
159
+
160
+ def search basedn, scope, deref, filter
161
+ RBMK.context[:filter] = {orig: filter, hacked: transformed(filter: filter)}
162
+ filter = LDAP::Server::Filter.to_rfc RBMK.context[:filter][:hacked]
163
+ $log.info sprintf('Search %p from %p, scope: %i, deref: %i, attrs: %p, no_values: %s, max: %i', filter, basedn, scope, deref, @attributes, @typesOnly, (@sizelimit.to_i rescue 0))
164
+ entries = @server.ldap.search_ext2 basedn, scope, filter, ['*', '+'], @typesOnly, nil, nil, 0, 0, (@sizelimit.to_i rescue 0)
165
+ #require 'pp'
166
+ #pp entries
167
+ transformed(entries: entries).each { |entry| send_SearchResultEntry entry.delete('dn').first, entry }
168
+ rescue LDAP::ResultError
169
+ @server.handle_ldap_error
170
+ end
171
+
172
+ protected
173
+
174
+ def transformed spec
175
+ raise ArgumentError.new('Please provide a hash with exactly one key.') unless (spec.is_a? Hash) and (1 == spec.count)
176
+ spec.each { |type, object| return RBMK.send "hack_#{type}".to_sym, object }
177
+ rescue
178
+ $!.log
179
+ object
180
+ end
181
+
182
+ end
183
+ end
data/lib/rbmk/peer.rb ADDED
@@ -0,0 +1,14 @@
1
+ module RBMK
2
+ class Peer
3
+
4
+ def initialize client
5
+ @host = client.peeraddr[3]
6
+ @port = client.peeraddr[1]
7
+ end
8
+
9
+ def to_s
10
+ sprintf '%s:%s', @host, @port
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,86 @@
1
+ require 'timeout'
2
+ require 'rbmk/peer'
3
+ require 'rbmk/signal'
4
+ module RBMK
5
+ class Server
6
+
7
+ class Reaped < StandardError; end
8
+
9
+ def initialize
10
+ $master = true
11
+ @arvg0 = File.basename Process.argv0
12
+ @workers = {}
13
+ end
14
+
15
+ def start
16
+ require 'socket'
17
+ @upstream = self.class.upstream
18
+ $log.debug sprintf('Listening on %s:%s', self.class.host, self.class.port)
19
+ @socket = TCPServer.new self.class.host, self.class.port
20
+ Signal.constants.each { |sig| Signal.trap Signal.const_get(sig), method(:trap) }
21
+ $0 = sprintf '%s master at %s:%s', @arvg0, self.class.host, self.class.port
22
+ loop { accept }
23
+ ensure
24
+ @socket.close rescue nil
25
+ @workers.each { |pid| Process.kill 'TERM', pid rescue nil }
26
+ Process.waitall rescue nil
27
+ $log.debug 'Exiting'
28
+ end
29
+
30
+ protected
31
+
32
+ def self.host; '127.0.0.1' end
33
+ def self.port; 8389 end
34
+ def self.worker_timeout; 600 end # (in seconds) this is not per single request, this is for the whole session
35
+
36
+ def self.upstream
37
+ require 'rbmk/upstream'
38
+ RBMK::Upstream.new
39
+ end
40
+
41
+ def accept
42
+ peer = Peer.new(client = @socket.accept)
43
+ $log.info 'Connection from %s' % peer
44
+ if pid = fork then
45
+ client.close
46
+ @workers[pid] = peer
47
+ else
48
+ $log.debug 'Worker started'
49
+ act_as_a_child_for client, peer
50
+ end
51
+ rescue Reaped
52
+ $log.debug $!.message
53
+ end
54
+
55
+ def trap sig = nil
56
+ case sig
57
+ when nil then raise 'Something went wrong, trapped a nil.'
58
+ when Signal::CHLD then
59
+ pid, status = Process.wait2 -1, Process::WNOHANG
60
+ @workers.delete pid
61
+ raise Reaped.new('Reaped %s' % pid)
62
+ else raise 'Terminated on SIG%s' % Signal.signame(sig)
63
+ end
64
+ rescue Errno::ECHILD
65
+ # okay, nothing to do
66
+ end
67
+
68
+ def act_as_a_child_for client, peer
69
+ Signal.trap 'CHLD', 'DEFAULT'
70
+ $master = false
71
+ remove_instance_variable :@workers
72
+ $0 = sprintf '%s worker for %s', @arvg0, peer
73
+ Timeout.timeout(self.class.worker_timeout) { serve client } # FIXME shall move to master in the future
74
+ rescue Exception
75
+ $!.log
76
+ ensure
77
+ exit!
78
+ end
79
+
80
+ def serve client
81
+ require 'rbmk/worker'
82
+ Worker.hire client, @upstream
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,3 @@
1
+ module Signal
2
+ %w( CHLD INT HUP QUIT TERM ).each { |signame| const_set signame.to_sym, list[signame] }
3
+ end
@@ -0,0 +1,72 @@
1
+ require 'tempfile'
2
+ require 'ldap'
3
+ require 'ldap/schema'
4
+ require 'ldap/server/schema'
5
+ module RBMK
6
+ class Upstream
7
+ FILTER_PREFIX = '( 1.3.6.1.4.1.4203.1.12.2'
8
+ SPECIAL_ATS = {
9
+ subtreeSpecification: {s: 45, oid: '2.5.18.6', f: 's'},
10
+ dITStructureRules: {s: 17, oid: '2.5.21.1', eq: :integerFirstComponentMatch},
11
+ dITContentRules: {s: 16, oid: '2.5.21.2', eq: :objectIdentifierFirstComponentMatch},
12
+ nameForms: {s: 35, oid: '2.5.21.7', eq: :objectIdentifierFirstComponentMatch},
13
+ configContext: {s: 12, oid: '1.3.6.1.4.1.4203.1.12.2.1', f: 'sua'},
14
+ }
15
+
16
+ attr_reader :ldap, :root_dse, :schema
17
+ def initialize
18
+ @schema = LDAP::Server::Schema.new
19
+ SPECIAL_ATS.each { |name,at| @schema.add_attrtype format(name, at) }
20
+ ldap = LDAP::Conn.new self.class.host, self.class.port
21
+ ldap.set_option LDAP::LDAP_OPT_PROTOCOL_VERSION, 3
22
+ ldap.bind do |ldap|
23
+ @root_dse = ldap.root_dse.first
24
+ ssse = ldap.schema
25
+ {add_attrtype: 'attributeTypes', add_objectclass: 'objectClasses'}.each { |meth,id| ssse[id].each { |str| @schema.send meth, str unless str.start_with? FILTER_PREFIX } }
26
+ end
27
+ @schema.resolve_oids
28
+ end
29
+
30
+ def bind version, dn, password
31
+ @ldap = LDAP::Conn.new self.class.host, self.class.port
32
+ @ldap.set_option LDAP::LDAP_OPT_PROTOCOL_VERSION, version.to_i
33
+ dn ? @ldap.bind(dn, password) : @ldap.bind
34
+ rescue LDAP::ResultError
35
+ handle_ldap_error
36
+ end
37
+
38
+ def handle_ldap_error
39
+ stderr = from_stderr { @ldap.perror 'LDAP' } # WHY U NO?
40
+ message = stderr.match(/additional info:(.*)$/)[1].strip rescue nil # Seriously, how hard can it be to expose a server's message?
41
+ raise LDAP::ResultError.from_id(@ldap.err, message) # FUCK ME WHY SHOULD I EVER PARSE MY OWN STDERR
42
+ end
43
+
44
+ def mktemp
45
+ @temp = Tempfile.new 'rbmk'
46
+ File.unlink @temp
47
+ end
48
+
49
+ protected
50
+
51
+ def self.host; '127.0.0.1' end
52
+ def self.port; 389 end
53
+
54
+ def format name, at
55
+ sprintf '( %s NAME \'%s\'%s SYNTAX 1.3.6.1.4.1.1466.115.121.1.%s%s%s USAGE %s )', at[:oid], name,
56
+ (at[:eq] ? " EQUALITY #{at[:eq]}": ''), at[:s], ((at[:f] and at[:f].include?('s')) ? ' SINGLE-VALUE' : ''),
57
+ ((at[:f] and at[:f].include?('u')) ? ' NO-USER-MODIFICATION' : ''), ((at[:f] and at[:f].include?('a')) ? 'dSAOperation' : 'directoryOperation')
58
+ end
59
+
60
+ def from_stderr
61
+ saved = STDERR.dup
62
+ STDERR.reopen @temp
63
+ yield if block_given?
64
+ STDERR.rewind
65
+ STDERR.read
66
+ ensure
67
+ STDERR.reopen saved
68
+ saved.close
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,4 @@
1
+ module RBMK
2
+ VERSION = '0.1.0.a'
3
+ CODENAME = 'plan b'
4
+ end
@@ -0,0 +1,26 @@
1
+ require 'ldap/server'
2
+ require 'rbmk/operation'
3
+ module RBMK
4
+ class Worker
5
+
6
+ def self.hire client, upstream; new(client, upstream).serve end
7
+
8
+ def initialize client, upstream
9
+ upstream.mktemp
10
+ @socket = client
11
+ @conn = LDAP::Server::Connection.new @socket,
12
+ server: upstream,
13
+ logger: $log,
14
+ operation_class: RBMK::Operation,
15
+ schema: upstream.schema,
16
+ namingContexts: upstream.root_dse['namingContexts']
17
+ end
18
+
19
+ def serve
20
+ @conn.handle_requests
21
+ ensure
22
+ @socket.close
23
+ end
24
+
25
+ end
26
+ end
data/lib/rbmk.rb ADDED
@@ -0,0 +1,26 @@
1
+ module RBMK
2
+
3
+ def self.context
4
+ @context ||= {}
5
+ end
6
+
7
+ # Patch this method to hack incoming bind data
8
+ #
9
+ def self.hack_simple_bind data
10
+ # version, dn, password = data
11
+ data
12
+ end
13
+
14
+ # Patch this method to hack incoming search filters
15
+ #
16
+ def self.hack_filter filter
17
+ filter
18
+ end
19
+
20
+ # Patch this method to hack outbound found entries
21
+ #
22
+ def self.hack_entries entries
23
+ entries
24
+ end
25
+
26
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbmk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.a
5
+ platform: ruby
6
+ authors:
7
+ - stronny red
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-ldap
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.17
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.17
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-ldapserver
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.5.3
41
+ description: |-
42
+ This is a rather simple Ruby LDAP server that proxies operations upstream but
43
+ at the same time provides a facility to invoke your code at certain points in
44
+ the operation runtime. This may help to accomodate for some clients that
45
+ are not smart enough to implement the logic you need themselves.
46
+ LDAP is very rigid and static in its nature and although OpenLDAP provides some
47
+ very helpful overlays, it is far from enough.
48
+ email: stronny@celestia.ru
49
+ executables:
50
+ - rbmk
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - LICENSE
55
+ - README.md
56
+ - bin/rbmk
57
+ - examples/rbmk.rb
58
+ - lib/rbmk.rb
59
+ - lib/rbmk/exception.rb
60
+ - lib/rbmk/logger.rb
61
+ - lib/rbmk/operation.rb
62
+ - lib/rbmk/peer.rb
63
+ - lib/rbmk/server.rb
64
+ - lib/rbmk/signal.rb
65
+ - lib/rbmk/upstream.rb
66
+ - lib/rbmk/version.rb
67
+ - lib/rbmk/worker.rb
68
+ homepage: https://github.com/stronny/rbmk
69
+ licenses:
70
+ - CC0
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 1.9.0
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">"
84
+ - !ruby/object:Gem::Version
85
+ version: 1.3.1
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.4.8
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: a trivial LDAP read-only proxy that allows you to get inside the bind and
92
+ search operations
93
+ test_files: []