forked from digininja/pipal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pipal.rb
executable file
·383 lines (327 loc) · 8.78 KB
/
pipal.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
#!/usr/bin/env ruby
# encoding: utf-8
# == Pipal: Statistical analysis on password dumps
#
# == Usage
#
# Usage: pipal [OPTION] ... FILENAME
# --help, -h: show help
# --top, -t X: show the top X results (default 10)
# --output, -o <filename>: output to file
# --gkey <Google Maps API key>: to allow zip code lookups (optional)
#
# FILENAME: The file to count
#
# Author:: Robin Wood ([email protected])
# Copyright:: Copyright (c) Robin Wood 2013
# Licence:: Creative Commons Attribution-Share Alike 2.0
# Speedbumped by Stefan Venken ([email protected])
#
require 'benchmark'
require'net/http'
require'uri'
require'json'
require "pathname"
VERSION = "3.1"
# Find out what our base path is
base_path = File.expand_path(File.dirname(__FILE__))
require File.join(base_path, 'base_checker.rb')
require File.join(base_path, 'progressbar.rb')
require File.join(base_path, 'pipal_getoptlong.rb')
require File.join(base_path, 'os.rb')
if RUBY_VERSION =~ /1\.8/
puts "Sorry, Pipal only works correctly on Ruby >= 1.9.x."
puts
exit
end
class PipalException < Exception
end
@checkers = []
def register_checker (class_name)
@checkers << class_name
end
trap("SIGINT") { throw :ctrl_c }
# uncomment this and its pair towards the end to benchmark the app
#time = Benchmark.measure do
class String
def is_numeric?
Integer self rescue false
end
end
def puts_msg_with_header (msg)
puts"pipal #{VERSION} Robin Wood ([email protected]) (http://digi.ninja)\n"
puts msg
puts "\n"
end
# Display the usage
def usage
puts"pipal #{VERSION} Robin Wood ([email protected]) (http://digi.ninja)
Usage: pipal [OPTION] ... FILENAME
--help, -h, -?: show help
--top, -t X: show the top X results (default 10)
--output, -o <filename>: output to file
--gkey <Google Maps API key>: to allow zip code lookups (optional)
--list-checkers: Show the available checkers and which are enabled
--verbose, -v: Verbose
"
@checkers.each do |class_name|
mod = Object::const_get(class_name).new
# Check if each Checker has any usage to add and if so add it
use = mod.usage
if !use.nil?
puts use
end
end
puts "
FILENAME: The file to count
"
exit
end
def list_checkers
all_checkers = {}
# Find out what our base path is
base_path = File.expand_path(File.dirname(__FILE__))
# doing it like this so that the files can be symlinked in
# in numeric order
checker_names = []
Dir.glob(base_path + '/checkers_enabled/*rb').select do |fn|
if !File.directory? fn
checker_names << fn
end
end
checker_names.sort.each do |name|
# Ruby doesn't seem to like doing a require
# on a symlink so this finds the ultimate target
# of the link (i.e. will travel multiple links)
# and require that instead
require Pathname.new(name).realpath
end
@checkers.each do |class_name|
mod = Object::const_get(class_name).new
all_checkers[class_name] = {'description' => mod.description, 'enabled' => true}
end
@checkers = []
Dir.glob(base_path + '/checkers_available/*rb').select do |fn|
if !File.directory? fn
# Ruby doesn't seem to like doing a require
# on a symlink so this finds the ultimate target
# of the link (i.e. will travel multiple links)
# and require that instead
require Pathname.new(fn).realpath
end
end
@checkers.each do |class_name|
mod = Object::const_get(class_name).new
all_checkers[class_name] = {'description' => mod.description, 'enabled' => false}
end
puts "pipal #{VERSION} Robin Wood ([email protected]) (http://digi.ninja)"
puts
puts "You have the following Checkers on your system"
puts "=============================================="
all_checkers.sort.each do |check|
puts "#{check[0]} - #{check[1]['description']}" + (check[1]['enabled']?" - Enabled":"")
end
puts
exit
end
# Defaults
verbose = false
cap_at = 10
output_file = STDOUT
custom_word_splitter = nil
# If there is a customer Splitter sylinked in then require it in
# and it will automatically be used
if File.exists?(File.join(base_path, "custom_splitter.rb"))
require Pathname.new(File.join(base_path, "custom_splitter.rb")).realpath
custom_word_splitter = Custom_word_splitter
end
# Loop thorugh all the Checkers which have been enabled and
# require them in
require_list = []
Dir.glob(base_path + '/checkers_enabled/*rb').select do |f|
if !File.directory? f
require_list << f
end
end
require_list.sort.each do |fn|
# Ruby doesn't seem to like doing a require
# on a symlink so this finds the ultimate target
# of the link (i.e. will travel multiple links)
# and require that instead
require Pathname.new(fn).realpath
end
if @checkers.count == 0
puts_msg_with_header("No Checkers enabled, please read README_modular for more information")
exit 1
end
modules = []
opts = PipalGetoptLong.new(
[ '--help', '-h', "-?", GetoptLong::NO_ARGUMENT ],
[ '--top', "-t" , GetoptLong::REQUIRED_ARGUMENT ],
[ '--output', "-o" , GetoptLong::REQUIRED_ARGUMENT ],
[ '--gkey', GetoptLong::REQUIRED_ARGUMENT ],
[ "--verbose", "-v" , GetoptLong::NO_ARGUMENT ],
[ "--list-checkers" , GetoptLong::NO_ARGUMENT ],
)
@checkers.each do |class_name|
mod = Object::const_get(class_name).new
modules << mod
# If the module has any parameters then they
# will be in the cli_params attribute as an array
# so go through it and add them all to the main options list
if !mod.cli_params.nil?
mod.cli_params.each do |param|
opts.add_option(param)
end
end
end
begin
# Having to store them as once you've parsed through opts once you can't do it
# again as far as I can tell, all the values disappear.
stored_opts = {}
opts.each do |opt, arg|
stored_opts[opt] = arg
case opt
when '--help'
usage
when "--list-checkers"
list_checkers
exit
when "--top"
if arg.is_numeric?
cap_at = arg.to_i
if cap_at <= 0
puts_msg_with_header("Please enter a positive number of lines")
exit 1
end
else
puts_msg_with_header("Invalid number of lines")
exit 1
end
when "--gkey"
google_maps_api_key = arg
when "--output"
begin
output_file = File.new(arg, "w")
rescue Errno::EACCES => e
puts_msg_with_header("Unable to open output file")
exit 1
end
when "--verbose"
verbose = true
end
end
# allow each of the modules to pull out the CLI params they require
# and pass through any global values
modules.each do |mod|
mod.verbose = verbose
mod.cap_at = cap_at
mod.parse_params stored_opts
end
rescue GetoptLong::InvalidOption => e
puts
usage
exit
rescue => e
puts "Something went wrong, please report it to [email protected] along with these messages:"
puts
puts e.message
puts
puts e.class.to_s
puts
puts "Backtrace:"
puts e.backtrace
puts
usage
exit 1
end
if ARGV.length != 1
puts_msg_with_header("Please specify the file to analyse")
exit 1
end
filename = ARGV.shift
if !File.exist? filename
puts_msg_with_header("Can't find the password file")
exit 2
end
puts "Generating stats, hit CTRL-C to finish early and dump stats on words already processed."
puts "Please wait..."
if (not OS.windows?) and %x{wc -l '#{filename}'}.match(/\s*([0-9]+).*/)
file_line_count = $1.to_i
else
filesize = File.stat(filename).size
file_line_count = (filesize / 8).to_i
puts "Can't find wc to calculate the number of lines so guessing as " + file_line_count.to_s + " based on file size"
end
pbar = ProgressBar.new("Processing", file_line_count)
catch :ctrl_c do
begin
File.open(filename, "r").each_line do |line|
begin
line.strip!
if line == ""
pbar.inc
next
end
if !custom_word_splitter.nil?
word, extras = Custom_word_splitter::split(line)
else
word = line
extras = {}
end
modules.each do |mod|
# allow the custom splitter to pass back nil
# which indicates that the line isn't to be parsed
if !word.nil?
mod.process_word(word, extras)
end
end
pbar.inc
rescue ArgumentError => e
puts "Encoding problem processing word: " + line
# puts e.inspect
# puts e.backtrace
# exit
pbar.inc
rescue => e
puts "Something went wrong, please report it to [email protected] along with these messages:"
puts
puts e.message
puts
puts e.class.to_s
puts
puts "Backtrace:"
puts e.backtrace
puts
exit 1
end
end
rescue Errno::EACCES => e
puts_msg_with_header("Unable to open the password file")
exit 1
rescue => e
puts "Something went wrong, please report it to [email protected] along with these messages:"
puts
puts e.message
puts
puts e.class.to_s
puts
puts "Backtrace:"
puts e.backtrace
puts
usage
exit 1
end
end
pbar.halt
# This is a screen puts to clear after the status bars in case the data is being written to the screen, do not add outfile to it
puts
puts
modules.each do |mod|
output_file.puts mod.get_results
output_file.puts
end
output_file.puts
# Companion to the commented out benchmark at the top
#end
#puts time