checksum-tools 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'net-ssh-krb', :platform => :ruby_19
6
+ gem 'net-ssh-kerberos', :platform => :ruby_18
7
+
8
+ group :development do
9
+ gem "rcov", :platform => :mri_18
10
+ end
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ checksum-tools (1.1.0)
5
+ net-sftp
6
+ net-ssh
7
+ progressbar
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ diff-lcs (1.2.4)
13
+ ffi (1.9.0)
14
+ gssapi (1.1.2)
15
+ ffi (>= 1.0.1)
16
+ json (1.8.0)
17
+ net-sftp (2.1.2)
18
+ net-ssh (>= 2.6.5)
19
+ net-ssh (2.7.0)
20
+ net-ssh-kerberos (0.2.7)
21
+ net-ssh (>= 2.0)
22
+ net-ssh-krb (0.3.0)
23
+ gssapi (~> 1.1.2)
24
+ net-ssh (>= 2.0)
25
+ progressbar (0.21.0)
26
+ rake (10.1.0)
27
+ rcov (1.0.0)
28
+ rdoc (4.0.1)
29
+ json (~> 1.4)
30
+ rspec (2.14.1)
31
+ rspec-core (~> 2.14.0)
32
+ rspec-expectations (~> 2.14.0)
33
+ rspec-mocks (~> 2.14.0)
34
+ rspec-core (2.14.5)
35
+ rspec-expectations (2.14.3)
36
+ diff-lcs (>= 1.1.3, < 2.0)
37
+ rspec-mocks (2.14.3)
38
+ yard (0.8.7.2)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ checksum-tools!
45
+ net-ssh-kerberos
46
+ net-ssh-krb
47
+ rake (>= 0.8.7)
48
+ rcov
49
+ rdoc
50
+ rspec
51
+ yard
@@ -0,0 +1,109 @@
1
+ = Name
2
+
3
+ checksum-tools --- Generate or verify checksums for a set of files
4
+
5
+ = Setup
6
+
7
+ == Requirements
8
+
9
+ * Ruby 1.8.7
10
+ * Rubygems 1.3.6 or greater
11
+
12
+ == Installation
13
+
14
+ gem install checksum-tools
15
+
16
+ = Synopsis
17
+
18
+ Usage: checksum-tools [options] [path]
19
+ -a, --action ACTION Specify action to take
20
+ generate|verify
21
+ -c, --config FILE Load configuration from FILE
22
+ -d, --digest DIGEST Generate checksums of type DIGEST
23
+ -e, --extension EXT File extension for digest files
24
+ -f, --filemask MASK Include files matching MASK
25
+ -n, --no-action Dry run; don't execute
26
+ -o, --overwrite Overwrite existing digest files
27
+ -q, --quiet Hide the progress bar
28
+ -r, --recursive Recurse into subdirectories
29
+ -x, --exclude MASK Exclude files matching MASK
30
+ -D, --digest-types Show digest types
31
+ -v, --version Print version and exit
32
+ -h, --help Show this help message
33
+
34
+ = Description
35
+
36
+ checksum-tools is a dual-purpose tool to generate or verify checksums of various types for a set of files in a given directory.
37
+
38
+ = Options
39
+
40
+ <tt>-a, --action ACTION</tt>
41
+
42
+ Valid choices are <tt>generate</tt> and <tt>verify</tt>. In <tt>generate</tt> mode, a digest file is created for each content file processed. In <tt>verify</tt> mode, the digests in digest files are verified against the content files and PASS/FAIL information written to standard output. Default: <tt>verify</tt>.
43
+
44
+ <tt>-c, --config FILE</tt>
45
+
46
+ Load default configuration values from <tt>FILE</tt>. Default: a file called <tt>.checksum-tools</tt> in the user's home directory. Options specified on the command line will override any defaults in the configuration file.
47
+
48
+ <tt>-d, --digest DIGEST</tt>
49
+
50
+ In <tt>generate</tt> mode, specifies the type of digest to generate. Valid options depend on the Ruby libraries available on the system, but usually include <tt>md5</tt>, <tt>sha1</tt>, <tt>sha256</tt>, <tt>sha384</tt>, and <tt>sha512</tt>. Default: <tt>md5</tt>. Including this option multiple times causes multiple digests to be generated for each file.
51
+
52
+ <tt>-D, --digest-types</tt>
53
+
54
+ Display the available digest types for the specified target directory/path/system.
55
+
56
+ <tt>-e, --extension EXT</tt>
57
+
58
+ The file extension to be appended to the names of content files when generating digest files. Default: <tt>.digest</tt>.
59
+
60
+ <tt>-f, --filemask MASK</tt>
61
+
62
+ Generate or verify checksums for files matching <tt>MASK</tt>. Include this option multiple times to match multiple filemasks. Default: <tt>*</tt> (all files)
63
+
64
+ <tt>-n, --no-action</tt>
65
+
66
+ Dry run — display configuration options and exit.
67
+
68
+ <tt>-o, --overwrite</tt>
69
+
70
+ Overwrite existing digest files with newly-generated ones.
71
+
72
+ <tt>-q, --quiet</tt>
73
+
74
+ Do not display a progress bar for each file generated.
75
+
76
+ <tt>-r, --recursive</tt>
77
+
78
+ Recurse into subdirectories.
79
+
80
+ <tt>-v, --version</tt>
81
+
82
+ Print version number and exit.
83
+
84
+ <tt>-x, --exclude MASK</tt>
85
+
86
+ Exclude files matching <tt>MASK</tt> from the operation. Can be used to skip a certain subset of files otherwise included using the <tt>--filemask</tt> option.
87
+
88
+ <tt>-h, --help</tt>
89
+
90
+ Display a simple help message and exit.
91
+
92
+ == Releases
93
+ - <b>0.6.6</b> Initial release
94
+ - <b>0.7.0</b> Added ability to verify against digest files created by other tools
95
+ - <b>0.7.1</b> Added -v / --version switch
96
+ - <b>0.7.2</b> Fixed shift error when evaluating .digest files
97
+ - <b>0.9.0</b> Added remote openssl invocation capability to generate digests on non-Ruby systems
98
+ - <b>0.9.1</b> Slight refactoring of bin/checksum-tools; removed debugging code
99
+ - <b>0.9.2</b> Fix gem deployment problem with bundler
100
+ - <b>0.9.3</b> Hotfix to revert SFTP/SSH object changes in 0.9.2
101
+ - <b>0.9.4</b> [HYDRALABWARE-59] Checksum-tools anomalies
102
+ - <b>0.9.5</b> Minor bug fixes and spec tests
103
+ - <b>1.0.0</b> Add --openssl command line parameter for self-configuration of remotes
104
+ - <b>1.0.1</b> Hotfix for broken spec test
105
+ - <b>1.0.3</b> Hotfix for remote paths with spaces in them
106
+ - <b>1.0.4</b> Hotfix for remote paths with singles quotes in them
107
+ - <b>1.0.5</b> Ensure remote commands are always invoked through bash
108
+ - <b>1.0.6</b> Fix remote digest type introspection bug introduced in 1.0.5
109
+ - <b>1.0.7</b> Compensate for differences between OpenSSL 0.9.8 and OpenSSL 1.0.0
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'bundler'
4
+ require 'bundler/gem_tasks'
5
+
6
+ Dir.glob('lib/tasks/*.rake').each { |r| import r }
7
+
8
+ begin
9
+ Bundler.setup(:default, :development)
10
+ rescue Bundler::BundlerError => e
11
+ $stderr.puts e.message
12
+ $stderr.puts "Run `bundle install` to install missing gems"
13
+ exit e.status_code
14
+ end
15
+
16
+ require 'rspec/core/rake_task'
17
+ RSpec::Core::RakeTask.new(:spec)
18
+
19
+ RSpec::Core::RakeTask.new(:features) do |spec|
20
+ spec.libs << 'lib' << 'spec'
21
+ spec.spec_files = FileList['spec/features/**/*_spec.rb']
22
+ end
23
+
24
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.pattern = 'spec/unit_tests/**/*.rb'
27
+ spec.rcov = true
28
+ spec.rcov_opts = %w{--exclude spec\/*,gems\/*,ruby\/* --aggregate coverage.data}
29
+ end
30
+
31
+
32
+ task :clean do
33
+ puts 'Cleaning old coverage.data'
34
+ FileUtils.rm('coverage.data') if(File.exists? 'coverage.data')
35
+ end
36
+
37
+ task :default => [:spec]
38
+
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ begin
5
+ require 'checksum-tools'
6
+ rescue LoadError
7
+ require 'bundler/setup'
8
+ require 'checksum-tools'
9
+ end
10
+ require 'optparse'
11
+ require 'progressbar'
12
+ require 'yaml'
13
+
14
+ DEFAULT_OPTIONS = {
15
+ :action => 'verify',
16
+ :digests => [],
17
+ :filemasks => [],
18
+ :progress => true,
19
+ :exclude => [],
20
+ :extension => '.digest',
21
+ :openssl => nil,
22
+ :overwrite => false,
23
+ :recursive => false
24
+ }
25
+
26
+ @pbar = nil
27
+ def progress(filename, size, pos)
28
+ unless size.nil?
29
+ if pos == -1
30
+ @pbar.finish unless @pbar.nil?
31
+ @pbar = nil
32
+ else
33
+ if @pbar.nil?
34
+ @pbar = ProgressBar.new(File.basename(filename), size)
35
+ end
36
+ @pbar.set(pos)
37
+ end
38
+ end
39
+ end
40
+
41
+ def generate(tool, path, options)
42
+ tool.create_digest_files(path,options[:filemasks]) do |filename, size, pos|
43
+ progress(filename, size, pos) if options[:progress]
44
+ end
45
+ end
46
+
47
+ def verify(tool, path, options)
48
+ tool.verify_digest_files(path,options[:filemasks]) do |filename, size, pos, result|
49
+ if result.nil?
50
+ progress(filename, size, pos) if options[:progress]
51
+ else
52
+ progress(filename, -1, -1) if options[:progress]
53
+ result.delete_if { |k,v| v == true }
54
+ if result.length == 0
55
+ print "PASS"
56
+ else
57
+ print "FAIL [#{result.keys.join(',')}]"
58
+ end
59
+ puts " #{filename}"
60
+ end
61
+ end
62
+ end
63
+
64
+ VALID_ACTIONS = ['generate','verify','console']
65
+
66
+ config_file = File.join(ENV['HOME'], '.checksum-tools')
67
+ config_file_options = begin
68
+ YAML.load(File.read(config_file))
69
+ rescue
70
+ {}
71
+ end
72
+
73
+ cmdline_options = {}
74
+ dry_run = false
75
+
76
+ optparse = OptionParser.new do |opts|
77
+ opts.banner = "Usage: #{File.basename($0)} [options] [path]"
78
+
79
+ opts.on('-a', '--action ACTION', VALID_ACTIONS, "Specify action to take", "#{VALID_ACTIONS.join('|')}") do |action|
80
+ cmdline_options[:action] = action
81
+ end
82
+
83
+ opts.on('-c', '--config FILE', "Load configuration from FILE") do |filename|
84
+ config_file = filename
85
+ config_file_options = YAML.load(File.read(config_file))
86
+ end
87
+
88
+ opts.on('-d', '--digest DIGEST', "Generate checksums of type DIGEST") do |digest|
89
+ (cmdline_options[:digests] ||= []) << digest.to_sym
90
+ end
91
+
92
+ opts.on('-e', '--extension EXT', "File extension for digest files") do |ext|
93
+ cmdline_options[:extension] = ext
94
+ end
95
+
96
+ opts.on('-f', '--filemask MASK', "Include files matching MASK") do |mask|
97
+ (cmdline_options[:filemasks] ||= []) << mask
98
+ end
99
+
100
+ opts.on('-n', '--no-action', "Dry run; don't execute") do
101
+ dry_run = true
102
+ end
103
+
104
+ opts.on('-o', '--overwrite', "Overwrite existing digest files") do
105
+ cmdline_options[:overwrite] = true
106
+ end
107
+
108
+ opts.on('-q', '--quiet', "Hide the progress bar") do
109
+ cmdline_options[:progress] = false
110
+ end
111
+
112
+ opts.on('-r', '--recursive', "Recurse into subdirectories") do
113
+ cmdline_options[:recursive] = true
114
+ end
115
+
116
+ opts.on('-s', '--openssl PATH', "Specify location of openssl on remote server") do |arg|
117
+ cmdline_options[:openssl] = arg
118
+ end
119
+
120
+ opts.on('-x', '--exclude MASK', "Exclude files matching MASK") do |mask|
121
+ cmdline_options[:exclude] << mask
122
+ end
123
+
124
+ opts.on_tail('-D', '--digest-types', "Show digest types") do
125
+ path_info = Checksum::Tools.parse_path(ARGV.pop || '.')
126
+ tool = Checksum::Tools.new(path_info, cmdline_options)
127
+ puts "Available digest types for #{tool.host || 'local system'}:"
128
+ puts tool.digests.join(' ')
129
+ exit
130
+ end
131
+
132
+ opts.on_tail('-v', '--version', "Print version and exit") do
133
+ puts "checksum-tools #{Checksum::Tools::VERSION}"
134
+ exit
135
+ end
136
+
137
+ opts.on_tail('-h', '--help', "Show this help message") do
138
+ puts opts
139
+ exit
140
+ end
141
+ end
142
+
143
+ optparse.parse!
144
+
145
+ options = DEFAULT_OPTIONS.merge(config_file_options).merge(cmdline_options)
146
+ options[:filemasks] << '*' if options[:filemasks].length == 0
147
+ options[:digests] << :md5 if options[:digests].length == 0
148
+ options[:digests].uniq!
149
+ options[:filemasks].uniq!
150
+ options[:exclude].uniq!
151
+
152
+ if options[:extension] !~ /[A-Za-z0-9]/
153
+ puts optparse
154
+ exit
155
+ end
156
+
157
+ if dry_run
158
+ puts "Defaults loaded from #{config_file}"
159
+ puts YAML.dump(options)
160
+ exit
161
+ end
162
+
163
+ args = options[:digests].dup
164
+ args << options.dup.reject { |k,v| not [:exclude,:extension,:openssl,:overwrite,:recursive].include?(k) }
165
+ path_info = Checksum::Tools.parse_path(ARGV.pop || '.')
166
+ tool = Checksum::Tools.new(path_info, *args)
167
+
168
+ case options[:action]
169
+ when 'generate' then generate(tool, path_info[:dir], options)
170
+ when 'verify' then verify(tool, path_info[:dir], options)
171
+ when 'console' then require 'ripl'; Ripl.start :binding => binding
172
+ end
@@ -0,0 +1,180 @@
1
+ module Checksum
2
+ module Tools
3
+ VERSION = "1.1.0"
4
+
5
+ CHUNK_SIZE = 1048576 # 1M blocks
6
+ DEFAULT_OPTS = { :overwrite => false, :recursive => false, :exclude => [], :extension => '.digest' }
7
+
8
+ autoload :Local, File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), 'local')
9
+ autoload :Remote, File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), 'remote')
10
+
11
+ class Exception < ::Exception; end
12
+ class ConfigurationError < Exception; end
13
+
14
+ class << self
15
+ def new(path_info, *args)
16
+ remote = path_info[:remote]
17
+ if remote.nil? or remote[:host].nil?
18
+ Local.new(*args)
19
+ else
20
+ Remote.new(remote[:host],remote[:user],*args)
21
+ end
22
+ end
23
+
24
+ def parse_path(path)
25
+ user,host,dir = path.to_s.scan(/^(?:(.+)@)?(?:(.+):)?(?:(.+))?$/).flatten
26
+ dir ||= '.'
27
+ result = { :remote => { :user => user, :host => host }, :dir => dir }
28
+ return result
29
+ end
30
+ end
31
+
32
+ class Base
33
+ attr_reader :host
34
+
35
+ def initialize(*args)
36
+ @opts = DEFAULT_OPTS
37
+ if args.last.is_a?(Hash)
38
+ @opts.merge!(args.pop)
39
+ end
40
+ @opts[:exclude] << digest_filename("*")
41
+ @opts[:exclude].uniq!
42
+ @digest_types = args
43
+ end
44
+
45
+ def digest_filename(filename)
46
+ "#{filename}.#{opts[:extension].sub(/^\.+/,'')}"
47
+ end
48
+
49
+ def create_digest_files(base_dir, file_masks, &block)
50
+ process_files(base_dir, file_masks) do |filename|
51
+ yield(filename, -1, -1) if block_given?
52
+ create_digest_file(filename, &block)
53
+ end
54
+ end
55
+
56
+ def create_digest_file(filename, &block)
57
+ digest_file = digest_filename(filename)
58
+ if File.expand_path(filename) == File.expand_path(digest_file)
59
+ raise ArgumentError, "Digest file #{digest_file} will clobber content file #{filename}!"
60
+ end
61
+ if opts[:overwrite] or not file_exists?(digest_file)
62
+ result = digest_file(filename, &block)
63
+ file_open(digest_file,'w') do |cksum|
64
+ result.each_pair do |type,hexdigest|
65
+ cksum.puts("#{type.to_s.upcase}(#{File.basename(filename)})= #{hexdigest}")
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def digest_files(base_dir, file_masks, &block)
72
+ process_files(base_dir, file_masks) do |filename|
73
+ yield(filename, -1, -1) if block_given?
74
+ digest_file(filename, &block)
75
+ end
76
+ end
77
+
78
+ def verify_digest_files(base_dir, file_masks, &block)
79
+ process_files(base_dir, file_masks) do |filename|
80
+ yield(filename, -1, -1) if block_given?
81
+ result = verify_digest_file(filename, &block)
82
+ yield(filename, -1, 0, result)
83
+ result
84
+ end
85
+ end
86
+
87
+ def verify_digest_file(filename, &block)
88
+ digest_file = digest_filename(filename)
89
+ unless file_exists?(digest_file)
90
+ return { :digest_file => false }
91
+ end
92
+
93
+ hashes = {}
94
+ digest_data = file_read(digest_file)
95
+
96
+ ext_type = File.extname(digest_file)[1..-1].downcase.to_sym
97
+ # Check to see if the digest file extension implies a specific digest type (e.g., .md5)
98
+ if digests.include?(ext_type)
99
+ # Find a hex value of the correct length in the digest file
100
+ len = self.digest_length(ext_type)
101
+ digest = digest_data.scan(/\b((?:[0-9a-f]{4})+)\b/im).flatten.find { |d| d.length == len }
102
+ hashes[ext_type] = digest
103
+ else
104
+ digest_data = digest_data.split(/\n/)
105
+ hashes = digest_data.inject({}) do |collector,sum|
106
+ (hash,hashed_fname,digest) = sum.scan(/(.+)\((.+)\)= (.+)/).flatten
107
+ unless File.basename(hashed_fname) == File.basename(filename)
108
+ warn "WARNING: Filename mismatch in #{digest_file}: #{File.basename(hashed_fname)}"
109
+ end
110
+ collector[hash.downcase.to_sym] = digest
111
+ collector
112
+ end
113
+ end
114
+ verify_file(filename, hashes, &block)
115
+ end
116
+
117
+ def verify_file(filename, hashes, &block)
118
+ with_types(hashes.keys) do
119
+ actual = digest_file(filename, &block)
120
+ result = {}
121
+ actual.each_pair do |hash,digest|
122
+ result[hash] = hashes[hash].downcase == digest.downcase
123
+ end
124
+ result
125
+ end
126
+ end
127
+
128
+ protected
129
+
130
+ def glob_to_re(glob)
131
+ replacements = [
132
+ [/\./,'##DOT##'],[/\\\*/,'##STAR##'],[/\\\?/,'##QUEST##'],
133
+ [/\*/,'.*'],[/\?/,'.'],
134
+ [/##STAR##/,'\*'],[/##QUEST##/,'\?'],[/##DOT##/,'\.']
135
+ ]
136
+ result = glob.dup
137
+ replacements.each { |args| result.gsub!(*args) }
138
+ Regexp.new(result)
139
+ end
140
+
141
+ def file_read(filename)
142
+ if file_exists?(filename)
143
+ result = ''
144
+ file_open(filename) { |io| result = io.read }
145
+ return result
146
+ else
147
+ raise Errno::ENOENT, filename
148
+ end
149
+ end
150
+
151
+ def process_files(base_dir, include_masks = ["*"])
152
+ unless block_given?
153
+ raise ArgumentError, "no block given"
154
+ end
155
+
156
+ excludes = Array(opts[:exclude]).collect { |ex| glob_to_re(ex) }
157
+ targets = file_list(base_dir, *include_masks).reject { |f| excludes.any? { |re| f.match(re) } }
158
+ targets.sort!
159
+ targets.uniq!
160
+
161
+ result = {}
162
+ targets.each do |filename|
163
+ result[filename] = yield(filename)
164
+ end
165
+ return result
166
+ end
167
+
168
+ def with_types(new_types)
169
+ old_types = @digest_types
170
+ @digest_types = new_types
171
+ begin
172
+ return(yield)
173
+ ensure
174
+ @digest_types = old_types
175
+ end
176
+ end
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,122 @@
1
+ module Checksum::Tools
2
+
3
+ class Local < Base
4
+
5
+ class << self
6
+ @@digests = {}
7
+
8
+ def register_digest(key, file, &block)
9
+ if @@digests.has_key?(key)
10
+ return false
11
+ else
12
+ begin
13
+ require file
14
+ @@digests[key] = block
15
+ return key
16
+ rescue LoadError
17
+ return false
18
+ end
19
+ end
20
+ end
21
+
22
+ def digest_for(key)
23
+ handler = @@digests[key]
24
+ if handler.nil?
25
+ raise ArgumentError, "undefined digest class: #{key.to_s}"
26
+ end
27
+ return handler.call()
28
+ end
29
+
30
+ def digests
31
+ @@digests.keys
32
+ end
33
+ end
34
+
35
+ attr :digest_types
36
+ attr :opts
37
+
38
+ def initialize(*args)
39
+ super(*args)
40
+ @digest_types.each { |type| self.class.digest_for(type) }
41
+ end
42
+
43
+ def digests
44
+ self.class.digests
45
+ end
46
+
47
+ def digest_length(type)
48
+ self.class.digest_for(type).hexdigest.length
49
+ end
50
+
51
+ def digest_file(filename)
52
+ if File.exists?(filename)
53
+ size = file_size(filename)
54
+ block = block_given? ? lambda { |pos| yield(filename, size, pos) } : nil
55
+ File.open(filename, 'r') do |io|
56
+ yield(filename, size, 0) if block_given?
57
+ return digest_stream(io, &block)
58
+ end
59
+ else
60
+ raise Errno::ENOENT, filename
61
+ end
62
+ end
63
+
64
+ def digest_stream(io)
65
+ output = digest_types.inject({}) { |collector,key| collector[key] = self.class.digest_for(key); collector }
66
+ io.rewind
67
+ while chunk = io.read(CHUNK_SIZE)
68
+ output.values.each do |digest|
69
+ digest << chunk
70
+ end
71
+ if block_given?
72
+ yield(io.pos)
73
+ end
74
+ end
75
+ output.each_pair do |type,digest|
76
+ output[type] = digest.hexdigest
77
+ end
78
+ return output
79
+ end
80
+
81
+ def verify_stream(io, hashes, &block)
82
+ with_types(hashes.keys) do
83
+ actual = digest_stream(io, &block)
84
+ result = {}
85
+ actual.each_pair do |hash,digest|
86
+ result[hash] = hashes[hash] == digest
87
+ end
88
+ result
89
+ end
90
+ end
91
+
92
+ protected
93
+
94
+ def file_list(base_dir, *file_masks)
95
+ path = [base_dir, '*']
96
+ path.insert(1,'**') if opts[:recursive]
97
+ result = Dir[File.join(*path)]
98
+ result.reject! { |f| File.directory?(f) or (not file_masks.any? { |m| File.fnmatch?(m,File.basename(f)) }) }
99
+ result
100
+ end
101
+
102
+ def file_open(*args, &block)
103
+ File.open(*args, &block)
104
+ end
105
+
106
+ def file_exists?(filename)
107
+ File.exists?(filename)
108
+ end
109
+
110
+ def file_size(filename)
111
+ File.size(filename)
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ Checksum::Tools::Local.register_digest(:md5, 'digest/md5') { Digest::MD5.new }
119
+ Checksum::Tools::Local.register_digest(:sha1, 'digest/sha1') { Digest::SHA1.new }
120
+ Checksum::Tools::Local.register_digest(:sha256, 'digest/sha2') { Digest::SHA2.new(256) }
121
+ Checksum::Tools::Local.register_digest(:sha384, 'digest/sha2') { Digest::SHA2.new(384) }
122
+ Checksum::Tools::Local.register_digest(:sha512, 'digest/sha2') { Digest::SHA2.new(512) }
@@ -0,0 +1,148 @@
1
+ require 'net/ssh'
2
+ require 'net/sftp'
3
+
4
+ begin
5
+ require 'net/ssh/kerberos'
6
+ rescue LoadError
7
+ raise LoadError.new "Include 'net-ssh-kerberos' (ruby 1.8) or 'net-ssh-krb' (ruby 1.9) in your Gemfile"
8
+ end
9
+
10
+ module Checksum::Tools
11
+
12
+ class Remote < Base
13
+
14
+ attr :digest_types
15
+ attr :opts
16
+
17
+ def initialize(host, user, *args)
18
+ super(*args)
19
+ @host = host
20
+ @user = user
21
+ @digest_length_cache = {}
22
+ unless (ssl = opts.delete(:openssl)).nil?
23
+ begin
24
+ remote_properties[:openssl] = ssl
25
+ rescue ConfigurationError
26
+ @remote_properties = { :openssl => ssl }
27
+ end
28
+ write_remote_properties
29
+ end
30
+ end
31
+
32
+ def openssl
33
+ remote_properties[:openssl]
34
+ end
35
+
36
+ def sftp
37
+ @sftp ||= begin
38
+ auth_methods = %w(gssapi-with-mic publickey hostbased) if defined? Net::SSH::Kerberos
39
+ auth_methods ||= %w(publickey hostbased)
40
+ Net::SFTP.start(@host, @user, :auth_methods => auth_methods)
41
+ end
42
+ end
43
+
44
+ def ssh
45
+ result = sftp.session
46
+ if block_given?
47
+ channel = result.open_channel do |ch|
48
+ yield(ch)
49
+ end
50
+ channel.wait
51
+ end
52
+ result
53
+ end
54
+
55
+ def exec!(cmd)
56
+ result = ''
57
+ ssh do |ch|
58
+ ch.exec("bash -l") do |ch2, success|
59
+ ch2.on_data { |c,data| result += data }
60
+ ch2.send_data "#{cmd}\n"
61
+ ch2.send_data "exit\n"
62
+ end
63
+ end
64
+ result.chomp
65
+ end
66
+
67
+ def digests
68
+ resp = ''
69
+ resp = exec! "#{openssl} dgst -h 2>&1"
70
+ resp.scan(/-(.+?)\s+to use the .+ message digest algorithm/).flatten.collect { |d| d.to_sym }
71
+ end
72
+
73
+ def digest_length(type)
74
+ if @digest_length_cache[type].nil?
75
+ resp = exec! "echo - | #{openssl} dgst -#{type}"
76
+ @digest_length_cache[type] = resp.split(/\s/).last.chomp.length
77
+ end
78
+ @digest_length_cache[type]
79
+ end
80
+
81
+ def digest_file(filename)
82
+ if file_exists?(filename)
83
+ size = file_size(filename)
84
+ yield(filename, size, 0) if block_given?
85
+ output = {}
86
+ digest_types.each { |key|
87
+ resp = exec! "#{openssl} dgst -#{key} $'#{filename.gsub(/[']/,'\\\\\'')}'"
88
+ output[key] = resp.split(/\= /).last
89
+ }
90
+ yield(filename, size, size) if block_given?
91
+ return output
92
+ else
93
+ raise Errno::ENOENT, filename
94
+ end
95
+ end
96
+
97
+ protected
98
+
99
+ def file_list(base_dir, *file_masks)
100
+ path = ['*']
101
+ path.unshift('**') if opts[:recursive]
102
+ result = sftp.dir[base_dir, File.join(*path)]
103
+ result.reject! { |f| f.directory? or (not file_masks.any? { |m| File.fnmatch?(m,File.basename(f.name)) }) }
104
+ result.collect { |f| File.expand_path(File.join(base_dir, f.name)) }
105
+ end
106
+
107
+ def file_open(*args, &block)
108
+ sftp.file.open(*args, &block)
109
+ end
110
+
111
+ def file_exists?(filename)
112
+ sftp.stat!(filename)
113
+ return true
114
+ rescue Net::SFTP::StatusException
115
+ return false
116
+ end
117
+
118
+ def file_size(filename)
119
+ if file_exists?(filename)
120
+ sftp.stat!(filename).size
121
+ else
122
+ raise Errno::ENOENT, filename
123
+ end
124
+ end
125
+
126
+ def remote_properties
127
+ if @remote_properties.nil?
128
+ home_dir = exec!('echo $HOME')
129
+ settings_file = File.join(home_dir,".checksum-tools-system")
130
+ if file_exists?(settings_file)
131
+ @remote_properties = YAML.load(file_read(settings_file))
132
+ else
133
+ raise ConfigurationError, "Checksum Tools not configured for #{@user}@#{host}. Please use the --openssl parameter to specify the location of the remote openssl binary."
134
+ end
135
+ end
136
+ @remote_properties
137
+ end
138
+
139
+ def write_remote_properties
140
+ home_dir = exec!('echo $HOME')
141
+ settings_file = File.join(home_dir,".checksum-tools-system")
142
+ file_open(settings_file, 'w') { |io| YAML.dump(remote_properties, io) }
143
+ remote_properties
144
+ end
145
+
146
+ end
147
+
148
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: checksum-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Klein
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: progressbar
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: net-ssh
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: net-sftp
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.8.7
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.8.7
78
+ - !ruby/object:Gem::Dependency
79
+ name: rdoc
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: yard
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Contains classes and executable files to generate and verify checksums
127
+ email:
128
+ - mbklein@stanford.edu
129
+ executables:
130
+ - checksum-tools
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - lib/checksum-tools/local.rb
135
+ - lib/checksum-tools/remote.rb
136
+ - lib/checksum-tools.rb
137
+ - bin/checksum-tools
138
+ - Gemfile
139
+ - Gemfile.lock
140
+ - Rakefile
141
+ - README.rdoc
142
+ homepage:
143
+ licenses: []
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ segments:
155
+ - 0
156
+ hash: -3039575937498161987
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ! '>='
161
+ - !ruby/object:Gem::Version
162
+ version: 1.3.6
163
+ requirements: []
164
+ rubyforge_project:
165
+ rubygems_version: 1.8.23
166
+ signing_key:
167
+ specification_version: 3
168
+ summary: Checksum creation and verification tools
169
+ test_files: []
170
+ has_rdoc: