plunder 2.0.0a
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.
- checksums.yaml +7 -0
- data/bin/agent +48 -0
- data/bin/plunder +158 -0
- data/ext/fsdb/extconf.rb +13 -0
- data/ext/fsdb/fsdb-c.c +134 -0
- data/ext/fsdb/fsdb-c.h +49 -0
- data/ext/fsdb/fsdb.c +267 -0
- data/ext/fsdb/hash.c +140 -0
- data/ext/fsdb/hash.h +52 -0
- data/ext/fsdb/test.c +90 -0
- data/ext/fsdb/utilities.c +125 -0
- data/ext/fsdb/utilities.h +32 -0
- data/ext/fsdb/vault.c +78 -0
- data/ext/fsdb/vault.h +44 -0
- data/ext/smbclient/extconf.rb +32 -0
- data/ext/smbclient/smb.c +234 -0
- data/ext/smbclient/smb.h +61 -0
- data/ext/smbclient/smbclient.c +116 -0
- data/lib/core/agent.rb +67 -0
- data/lib/core/analyzer.rb +84 -0
- data/lib/core/client.rb +163 -0
- data/lib/core/commands.rb +53 -0
- data/lib/core/config.rb +103 -0
- data/lib/core/io.rb +84 -0
- data/lib/core/plunder.rb +208 -0
- data/lib/core/report.rb +78 -0
- data/lib/core/target.rb +33 -0
- data/lib/core/tree.rb +59 -0
- metadata +74 -0
data/lib/core/io.rb
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Plunder - SMB scanning and auditing tool
|
|
3
|
+
# Copyright (C) 2017 Joshua Stone
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
module Plunder
|
|
21
|
+
module PlunderIO
|
|
22
|
+
def banner
|
|
23
|
+
puts ""
|
|
24
|
+
puts "\x1b[34;1m------------------------------------------------------------------------------\x1b[0m"
|
|
25
|
+
puts "\x1b[32;1m Plunder - SMB Scanner - by Joshua Stone - yakovdk@gmail.com\x1b[0m"
|
|
26
|
+
puts "\x1b[34;1m------------------------------------------------------------------------------\x1b[0m"
|
|
27
|
+
puts ""
|
|
28
|
+
puts(Report.colorize(" <gra>Plunder, Copyright (C) 2017 Joshua Stone\n" +
|
|
29
|
+
" Plunder comes with ABSOLUTELY NO WARRANTY; for details\n" +
|
|
30
|
+
" refer to the file 'LICENSE' in the source distribution.<rst>"))
|
|
31
|
+
puts ""
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def usage
|
|
35
|
+
puts "usage: plunder <cmd> [args...]"
|
|
36
|
+
puts ""
|
|
37
|
+
puts " Description:"
|
|
38
|
+
puts " ------------"
|
|
39
|
+
puts ""
|
|
40
|
+
puts " Plunder is an SMB share enumeration and scanning tool that can be used for"
|
|
41
|
+
puts " security assessments, penetration tests, risk analysis, or auditing. The"
|
|
42
|
+
puts " core of Plunder is a tunable, highly efficient, breadth-first share scanner"
|
|
43
|
+
puts " that quickly indexes an environment according to a specified config file."
|
|
44
|
+
puts ""
|
|
45
|
+
puts " Commands:"
|
|
46
|
+
puts " ---------"
|
|
47
|
+
puts ""
|
|
48
|
+
puts " CMD ARGS DESCRIPTION"
|
|
49
|
+
puts " --- ---- -----------"
|
|
50
|
+
puts " init <name> Initialize config file for new project"
|
|
51
|
+
puts " scan <cfg> <depth> Scan using indicated config to indicated depth"
|
|
52
|
+
puts " target <cfg> <ip> Add <ip> to list of targets in config file"
|
|
53
|
+
puts " targets <cfg> [<file>] Add targets from STDIN or indicated file"
|
|
54
|
+
puts " search <cfg> <expr> Search filenames for <expr>"
|
|
55
|
+
puts " mirror <cfg> [<file>] Mirror files by ID from STDIN or indicated file"
|
|
56
|
+
puts " listing <cfg> List all files and IDs"
|
|
57
|
+
puts " summary <cfg> Describe current configuration"
|
|
58
|
+
puts " debug <cfg> Sets up scanner and enters pry (if 'pry' exists)"
|
|
59
|
+
puts " client <cfg> Rudimentary interactive SMB client"
|
|
60
|
+
puts ""
|
|
61
|
+
puts " creds <cfg> <dom> <user> <pass>"
|
|
62
|
+
puts " Set indicated credentials for scan config"
|
|
63
|
+
puts ""
|
|
64
|
+
puts " Note that for downloading purposes, files are to be referenced by ID, which"
|
|
65
|
+
puts " is a unique index into the FS database. This can be obtained via the "
|
|
66
|
+
puts " 'listing' command or by searching for matches with 'search'."
|
|
67
|
+
puts ""
|
|
68
|
+
puts " Procedure:"
|
|
69
|
+
puts " ----------"
|
|
70
|
+
puts ""
|
|
71
|
+
puts " 1. Run 'plunder init foo' to create a config file"
|
|
72
|
+
puts " 2. Edit the config file (foo.yaml)"
|
|
73
|
+
puts " 3. Add targets, e.g. with 'plunder targets foo ips.txt'"
|
|
74
|
+
puts " 4. Scan to desired depth: 'plunder scan foo 8'"
|
|
75
|
+
puts " 5. Search for interesting files: 'plunder search foo \"^web.config\$\""
|
|
76
|
+
puts " 6. Save IDs: \"... | grep ^FILE | awk '{print $2}'\" > ids.txt"
|
|
77
|
+
puts " 7. Mirror the interesting files: 'plunder mirror foo < ids.txt'"
|
|
78
|
+
puts ""
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
module_function :usage
|
|
82
|
+
module_function :banner
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/core/plunder.rb
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Plunder - SMB scanning and auditing tool
|
|
3
|
+
# Copyright (C) 2017 Joshua Stone
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
require 'fileutils'
|
|
21
|
+
require 'time'
|
|
22
|
+
|
|
23
|
+
require_relative '../../ext/fsdb/fsdb.so'
|
|
24
|
+
require_relative '../../ext/smbclient/smbclient.so'
|
|
25
|
+
require_relative 'report.rb'
|
|
26
|
+
require_relative 'analyzer.rb'
|
|
27
|
+
require_relative 'agent.rb'
|
|
28
|
+
require_relative 'target.rb'
|
|
29
|
+
|
|
30
|
+
module Plunder
|
|
31
|
+
class Plunder
|
|
32
|
+
attr_accessor :name, :client, :fsdb, :next, :depth, :clients, :pool, :agents, :targets, :config
|
|
33
|
+
|
|
34
|
+
def initialize(name)
|
|
35
|
+
@name = name
|
|
36
|
+
@config = YAML.load(File.read(name + ".yaml"))
|
|
37
|
+
@fsdb = FSDB.new
|
|
38
|
+
@targets = @config[:targets].map { |i| Target.new i }
|
|
39
|
+
@count = 0
|
|
40
|
+
@depth = 0
|
|
41
|
+
@dom = @config[:domain]
|
|
42
|
+
@user = @config[:user]
|
|
43
|
+
@pass = @config[:pass]
|
|
44
|
+
@dbmut = Mutex.new
|
|
45
|
+
@agmut = Mutex.new
|
|
46
|
+
@iomut = Mutex.new
|
|
47
|
+
@percent = 0
|
|
48
|
+
|
|
49
|
+
@analyzer = Analyzer.new(@config)
|
|
50
|
+
@analyzer.run
|
|
51
|
+
|
|
52
|
+
@config[:tarpit] = @config[:tarpit].to_i
|
|
53
|
+
@config[:threads] = @config[:threads].to_i
|
|
54
|
+
|
|
55
|
+
@agents = []
|
|
56
|
+
@config[:threads].times { @agents << Agent.new(@dom, @user, @pass) }
|
|
57
|
+
|
|
58
|
+
@client = SMBClient.new(@dom, @user, @pass)
|
|
59
|
+
|
|
60
|
+
Report.notify("Tarpit threshold is #{@config[:tarpit]} subdirs")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def load
|
|
64
|
+
if File.exists? @config[:file]
|
|
65
|
+
@fsdb = FSDB.read(@config[:file])
|
|
66
|
+
Report.notify("Loaded FSDB from '<grn>#{@config[:file]}<rst>'")
|
|
67
|
+
else
|
|
68
|
+
Report.notify("FSDB file at <grn>#{@config[:file]}<rst> does not exist yet")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def mirror(url)
|
|
73
|
+
fields = url.split(/\//)
|
|
74
|
+
localdir = "./mirror/" + fields[2..-2].join("/")
|
|
75
|
+
FileUtils::mkdir_p localdir
|
|
76
|
+
open(localdir + "/" + fields[-1], "w") do |file|
|
|
77
|
+
content = @client.slurp(url)
|
|
78
|
+
file.write(content)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def save
|
|
83
|
+
file = @config[:file]
|
|
84
|
+
@fsdb.write(file)
|
|
85
|
+
Report.notify("Wrote output to '<grn>#{file}<rst>'")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def scan(depth)
|
|
89
|
+
return if depth == 0
|
|
90
|
+
|
|
91
|
+
@depth += 1
|
|
92
|
+
|
|
93
|
+
Report.notify("<mag>#{Time::now.to_s}<rst> : Starting scan at depth <grn>#{@depth}<rst>" + " " * 25)
|
|
94
|
+
|
|
95
|
+
length = @targets.inject(0) { |i,j| i + j.paths.length }
|
|
96
|
+
count = 0
|
|
97
|
+
|
|
98
|
+
return if length == 0
|
|
99
|
+
|
|
100
|
+
@targets.each do |target|
|
|
101
|
+
agent = free_agent
|
|
102
|
+
Thread.new(target, agent) do |t, a|
|
|
103
|
+
t.paths.each do |path|
|
|
104
|
+
t.rest += scan_path(path, a)
|
|
105
|
+
count += 1
|
|
106
|
+
stats(count, length)
|
|
107
|
+
end
|
|
108
|
+
a.checkin
|
|
109
|
+
t.paths = t.rest
|
|
110
|
+
t.rest = []
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
sleep 0.1 until @agents.all? { |a| a.free }
|
|
115
|
+
|
|
116
|
+
clear_line
|
|
117
|
+
|
|
118
|
+
self.scan(depth - 1)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def free_agent
|
|
122
|
+
while true
|
|
123
|
+
@agents.each do |agent|
|
|
124
|
+
if agent.free
|
|
125
|
+
return agent.checkout
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
sleep 0.1
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def stats(count, total)
|
|
133
|
+
amount = (100.0 * count / total).floor
|
|
134
|
+
if amount != @last
|
|
135
|
+
@iomut.synchronize do
|
|
136
|
+
@last = amount
|
|
137
|
+
clear_line
|
|
138
|
+
print(" Progress: #{amount} % (#{@fsdb.length})")
|
|
139
|
+
STDOUT.flush
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def clear_line
|
|
145
|
+
100.times { print("\x08") }
|
|
146
|
+
STDOUT.flush
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def scan_path(path, agent)
|
|
150
|
+
path += "/" unless path.end_with? "/"
|
|
151
|
+
fields = path.split(/\//)[2..-1]
|
|
152
|
+
rest = []
|
|
153
|
+
|
|
154
|
+
listing = agent.list path
|
|
155
|
+
if listing == nil
|
|
156
|
+
Report.error("Authentication failure at #{path}, exiting for safety!")
|
|
157
|
+
exit!
|
|
158
|
+
end
|
|
159
|
+
(dirs, files) = listing
|
|
160
|
+
@count += files.length
|
|
161
|
+
|
|
162
|
+
files.each do |file|
|
|
163
|
+
url = "#{path}#{file}"
|
|
164
|
+
index = 0
|
|
165
|
+
@dbmut.synchronize { index = @fsdb.put("#{path}#{file}/") }
|
|
166
|
+
@analyzer << [path, file, url, index]
|
|
167
|
+
mirror(url) if @analyzer.mirror?(path, file)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if dirs.length > @config[:tarpit]
|
|
171
|
+
Report.warning("TARPIT: #{path}, truncating to #{@config[:tarpit]}")
|
|
172
|
+
dirs = dirs[0..@config[:tarpit]]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
dirs.each do |dir|
|
|
176
|
+
rest << "#{path}#{dir}" if scan_dir?(path, dir)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
return rest
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def filter(list, dir)
|
|
183
|
+
list.each do |entry|
|
|
184
|
+
return true if dir =~ /#{entry}/i
|
|
185
|
+
end
|
|
186
|
+
return false
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def scan_dir?(target, dir)
|
|
190
|
+
return false if filter(@config[:blacklist][:dirs], dir)
|
|
191
|
+
if @depth == 1
|
|
192
|
+
return false if filter(@config[:blacklist][:shares], dir)
|
|
193
|
+
end
|
|
194
|
+
return true
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def select_files(expr, &block)
|
|
198
|
+
@fsdb.each_name do |name, index|
|
|
199
|
+
if name =~ /#{expr}/i
|
|
200
|
+
fsdb.select_index(index) do |id|
|
|
201
|
+
yield(fsdb[id], id)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
|
208
|
+
end
|
data/lib/core/report.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Plunder - SMB scanning and auditing tool
|
|
3
|
+
# Copyright (C) 2017 Joshua Stone
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
require 'time'
|
|
21
|
+
require 'thread'
|
|
22
|
+
|
|
23
|
+
module Plunder
|
|
24
|
+
class Report
|
|
25
|
+
@@log = nil
|
|
26
|
+
@@mut = Mutex.new
|
|
27
|
+
|
|
28
|
+
def Report.init(file)
|
|
29
|
+
now = Time::now
|
|
30
|
+
stamp = sprintf("%04d-%02d-%02d-%02d:%02d:%02d",
|
|
31
|
+
now.year, now.month, now.day,
|
|
32
|
+
now.hour, now.min, now.sec)
|
|
33
|
+
name = "#{file}_#{stamp}.log"
|
|
34
|
+
self.notify(colorize("Logging hits to '<grn>#{name}<rst>'"))
|
|
35
|
+
@@log = open(name, "a") unless @@log
|
|
36
|
+
@@log.puts("CONTROL #{Time::now().to_s} init log")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def Report.notify(message)
|
|
40
|
+
@@mut.synchronize do
|
|
41
|
+
puts(" [-] #{colorize(message)}")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def Report.error(message)
|
|
46
|
+
@@mut.synchronize do
|
|
47
|
+
puts(colorize(" [!] <red>ERROR:<rst> #{message}"))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def Report.colorize(str)
|
|
52
|
+
return str.
|
|
53
|
+
gsub("<gra>", "\x1b[30;1m").
|
|
54
|
+
gsub("<red>", "\x1b[31;1m").
|
|
55
|
+
gsub("<grn>", "\x1b[32;1m").
|
|
56
|
+
gsub("<yel>", "\x1b[33;1m").
|
|
57
|
+
gsub("<blu>", "\x1b[34;1m").
|
|
58
|
+
gsub("<mag>", "\x1b[35;1m").
|
|
59
|
+
gsub("<cya>", "\x1b[36;1m").
|
|
60
|
+
gsub("<whi>", "\x1b[37;1m").
|
|
61
|
+
gsub("<rst>", "\x1b[0m")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def Report.warning(name)
|
|
65
|
+
@@mut.synchronize do
|
|
66
|
+
puts(colorize(" [!] <yel>WARNING:<rst> #{message}"))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def Report.alert(name)
|
|
71
|
+
@@mut.synchronize do
|
|
72
|
+
# puts(colorize(" [+] #{name}"))
|
|
73
|
+
@@log.puts("ALERT " + name) if @@log
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/core/target.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Plunder - SMB scanning and auditing tool
|
|
3
|
+
# Copyright (C) 2017 Joshua Stone
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
require 'uri'
|
|
21
|
+
|
|
22
|
+
module Plunder
|
|
23
|
+
class Target
|
|
24
|
+
attr_accessor :url, :paths, :rest, :host
|
|
25
|
+
|
|
26
|
+
def initialize(url)
|
|
27
|
+
@host = URI::parse(url).host
|
|
28
|
+
@url = url
|
|
29
|
+
@paths = [url]
|
|
30
|
+
@rest = []
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/core/tree.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Plunder - SMB scanning and auditing tool
|
|
3
|
+
# Copyright (C) 2017 Joshua Stone
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
class Tree
|
|
21
|
+
attr_accessor :root, :node, :children
|
|
22
|
+
|
|
23
|
+
def initialize(nodeid)
|
|
24
|
+
@node = nodeid
|
|
25
|
+
@children = {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def <<(child)
|
|
29
|
+
@children[child.node] << child
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def [](id)
|
|
33
|
+
return @children[id]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def insert(nodes)
|
|
37
|
+
return unless nodes.length > 0
|
|
38
|
+
@children[nodes[0]] = Tree.new(nodes[0]) unless @children[nodes[0]]
|
|
39
|
+
@children[nodes[0]].insert(nodes[1..-1])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def member?(id)
|
|
43
|
+
return @children[id] ? true : false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def pprint(fsdb=nil, depth=0)
|
|
47
|
+
if @node > 0
|
|
48
|
+
print " " + " + " * (depth - 1)
|
|
49
|
+
puts (fsdb ? fsdb.lookup(@node) : @node.to_s)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@children.each do |id,child|
|
|
53
|
+
child.pprint(fsdb, depth + 1)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
return true
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|