diff --git a/.gitignore b/.gitignore index d1f2bed..c40a619 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .crystal +.tool-versions diff --git a/spec/ssh2_spec.cr b/spec/ssh2_spec.cr index b01f1a8..4699550 100644 --- a/spec/ssh2_spec.cr +++ b/spec/ssh2_spec.cr @@ -3,7 +3,7 @@ require "spec" def connect_ssh SSH2::Session.open("localhost", 2222) do |session| - session.login_with_pubkey("root", "./spec/keys/id_rsa") + session.login_with_pubkey("root", "./spec/keys/id_rsa", "./spec/keys/id_rsa.pub") session.authenticated?.should be_true yield session end @@ -22,7 +22,7 @@ describe SSH2 do end it "should be able to scp transfer file" do - fn = "#{Time.now.epoch}.txt" + fn = "#{Time.utc.to_unix}.txt" connect_ssh do |session| session.scp_send(fn, 0o0644, 12) do |ch| ch.puts "hello world" @@ -59,7 +59,7 @@ describe SSH2::KnownHosts do known_hosts.size.should eq(2) known_hosts.map(&.name).includes?("localhost").should be_true known_hosts.write_file("known_hosts") - known_hosts.delete_if {|h| h.name == "localhost"} + known_hosts.delete_if { |h| h.name == "localhost" } known_hosts.size.should eq(1) end @@ -101,7 +101,7 @@ describe SSH2::SFTP do it "should be able to upload a file" do connect_ssh do |ssh| ssh.sftp_session do |sftp| - fn = "#{Time.now.epoch}_upload.txt" + fn = "#{Time.utc.to_unix}_upload.txt" file = sftp.open(fn, "wc", 0o644) file.puts "hello world!" attrs = file.fstat diff --git a/src/channel.cr b/src/channel.cr index b1eab2a..be39bec 100644 --- a/src/channel.cr +++ b/src/channel.cr @@ -1,9 +1,8 @@ require "./session" class SSH2::Channel < IO - - PROCESS_SHELL = "shell" - PROCESS_EXEC = "exec" + PROCESS_SHELL = "shell" + PROCESS_EXEC = "exec" PROCESS_SUBSYSTEM = "subsystem" getter session : Session @@ -63,7 +62,7 @@ class SSH2::Channel < IO def process_startup(request, message) ret = LibSSH2.channel_process_startup(self, request, request.bytesize.to_u32, - message, message ? message.bytesize.to_u32 : 0_u32) + message, message ? message.bytesize.to_u32 : 0_u32) check_error(ret) end @@ -71,7 +70,7 @@ class SSH2::Channel < IO # leading "SIG"), and the second field populated with the error message. def exit_signal ret = LibSSH2.channel_get_exit_signal(self, out exitsignal, out exitsignal_len, - out errmsg, out errmsg_len, nil, nil) + out errmsg, out errmsg_len, nil, nil) check_error(ret) exitsignal_str = String.new(exitsignal, exitsignal_len) if exitsignal errmsg_str = String.new(errmsg, errmsg_len) if errmsg @@ -110,6 +109,16 @@ class SSH2::Channel < IO read(0, slice) end + def read(slice : Slice(UInt32)) + return 0 if eof? + read(0, slice) + end + + def read(slice : Slice(UInt64)) + return 0 if eof? + read(0, slice) + end + def write(slice : Slice(UInt8)) write(0, slice) end @@ -167,8 +176,8 @@ class SSH2::Channel < IO def request_pty(term, modes = nil, width = LibSSH2::TERM_WIDTH, height = LibSSH2::TERM_HEIGHT, width_px = LibSSH2::TERM_WIDTH_PX, height_px = LibSSH2::TERM_HEIGHT_PX) ret = LibSSH2.channel_request_pty(self, term, term.bytesize.to_u32, - modes, modes ? modes.bytesize.to_u32 : 0_u32, - width, height, width_px, height_px) + modes, modes ? modes.bytesize.to_u32 : 0_u32, + width, height, width_px, height_px) check_error(ret) end @@ -219,7 +228,6 @@ class SSH2::Channel < IO end class StreamIO < IO - getter channel : Channel getter stream_id : Int32 diff --git a/src/lib_ssh2.cr b/src/lib_ssh2.cr index 5f0c0f4..2433a62 100644 --- a/src/lib_ssh2.cr +++ b/src/lib_ssh2.cr @@ -10,7 +10,7 @@ lib LibSSH2 @[Flags] enum BlockDirections - Inbound, + Inbound Outbound end diff --git a/src/session.cr b/src/session.cr index 3f0d64e..4e991e1 100644 --- a/src/session.cr +++ b/src/session.cr @@ -35,23 +35,23 @@ class SSH2::Session # Login with username and password def login(username, password) ret = LibSSH2.userauth_password(self, username, username.bytesize.to_u32, - password, password.bytesize.to_u32, nil) + password, password.bytesize.to_u32, nil) check_error(ret) end # Login with username using pub/priv key values def login_with_data(username, privkey, pubkey, passphrase = nil) ret = LibSSH2.userauth_publickey_frommemory(self, username, username.bytesize.to_u32, - pubkey, LibC::SizeT.new(pubkey.bytesize), - privkey, LibC::SizeT.new(privkey.bytesize), - passphrase) + pubkey, LibC::SizeT.new(pubkey.bytesize), + privkey, LibC::SizeT.new(privkey.bytesize), + passphrase) check_error(ret) end # Login with username using pub/priv key files def login_with_pubkey(username, privkey, pubkey = nil, passphrase = nil) ret = LibSSH2.userauth_publickey_fromfile(self, username, username.bytesize.to_u32, - pubkey, privkey, passphrase) + pubkey, privkey, passphrase) check_error(ret) end @@ -242,8 +242,8 @@ class SSH2::Session # Allocate a new channel for exchanging data with the server. def open_channel(channel_type, window_size, packet_size, message) handle = LibSSH2.channel_open(self, channel_type, channel_type.bytesize.to_u32, - window_size.to_u32, packet_size.to_u32, - message, message ? message.bytesize.to_u32 : 0_u32) + window_size.to_u32, packet_size.to_u32, + message, message ? message.bytesize.to_u32 : 0_u32) Channel.new self, handle end @@ -280,16 +280,16 @@ class SSH2::Session end # Send a file to the remote host via SCP. + # LibC::TimeT.new(mtime), LibC::TimeT.new(atime)) def scp_send(path, mode, size, mtime, atime) - handle = LibSSH2.scp_send(self, path, mode.to_i32, size.to_u64, - LibC::TimeT.new(mtime), LibC::TimeT.new(atime)) + handle = LibSSH2.scp_send(self, path, mode.to_i32, size.to_u64, mtime, atime) check_error(LibSSH2.session_last_errno(self)) Channel.new self, handle end # Send a file to the remote host via SCP. # A new channel is passed to the block and closed afterwards. - def scp_send(path, mode, size, mtime = Time.now.epoch, atime = Time.now.epoch) + def scp_send(path, mode, size, mtime = Time.utc.to_unix, atime = Time.utc.to_unix) channel = scp_send(path, mode, size, mtime, atime) begin yield channel @@ -304,13 +304,25 @@ class SSH2::Session raise Errno.new("Unable to get stat for '#{path}'") end scp_send(path, (stat.st_mode & 0x3ff).to_i32, stat.st_size.to_u64, - stat.st_mtimespec.tv_sec, stat.st_atimespec.tv_sec) do |ch| + stat.st_mtimespec.tv_sec, stat.st_atimespec.tv_sec) do |ch| File.open(path, "r") do |f| IO.copy(f, ch) end end end + # Send a file from a local filesystem to the remote host via SCP. + def scp_send_file(path, localpath) + if LibC.stat(localpath, out stat) != 0 + raise Errno.new("Unable to get stat for '#{path}'") + end + scp_send(path, (stat.st_mode & 0x3ff).to_i32, stat.st_size.to_u64) do |ch| + File.open(localpath, "r") do |f| + IO.copy(f, ch) + end + end + end + # Request a file from the remote host via SCP. def scp_recv(path) handle = LibSSH2.scp_recv(self, path, out stat) @@ -331,7 +343,7 @@ class SSH2::Session # Download a file from the remote host via SCP to the local filesystem. def scp_recv_file(path, local_path = path) - min = -> (x : Int32|Int64, y : Int32|Int64) { x < y ? x : y} + min = ->(x : Int64 | Int32, y : Int64 | Int32) { x < y ? x : y } # libssh2 scp_recv method has a bug where its channel's read method doesn't # return 0 value to indicate the end of file(EOF). The only way to find EOF @@ -341,15 +353,17 @@ class SSH2::Session file_size = stat.st_size read_bytes = 0 File.open(local_path, "w") do |f| - buf = uninitialized UInt8[1024] + buf = StaticArray(UInt8, 1024).new(0) # => 42#uninitialized UInt8[1024] while read_bytes < file_size - bytes_to_read = min.call(buf.length, file_size - read_bytes) - len = ch.read(buf.to_slice, bytes_to_read).to_i32 - f.write(buf.to_slice, len) + bytes_to_read = min.call(buf.size, file_size - read_bytes).to_i32 + buf2 = Slice(UInt8).new(bytes_to_read) + len = ch.read(buf2).to_i32 break if len <= 0 + f.write buf2.to_slice read_bytes += len end end + if file_size != read_bytes File.delete(local_path) raise SSH2Error.new "Premature end of file" diff --git a/src/sftp/attributes.cr b/src/sftp/attributes.cr index 3cd128f..133e923 100644 --- a/src/sftp/attributes.cr +++ b/src/sftp/attributes.cr @@ -52,7 +52,7 @@ class SSH2::SFTP::Attributes end def atime - Time.epoch(@stat.atime.to_i32) + Time.unix_ms(@stat.atime.to_i32) end def atime=(v : Time) @@ -60,7 +60,7 @@ class SSH2::SFTP::Attributes end def mtime - Time.epoch(@stat.mtime.to_i32) + Time.unix_ms(@stat.mtime.to_i32) end def mtime=(v : Time)