Skip to content
This repository was archived by the owner on Aug 21, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bf33852
Update session.cr
whidbey Sep 9, 2019
82c0f5b
Update channel.cr
whidbey Sep 9, 2019
648f8d1
Update session.cr
whidbey Sep 9, 2019
46f6c3c
Update session.cr
whidbey Sep 9, 2019
e13a387
Update session.cr
whidbey Sep 9, 2019
ee95ee2
Update session.cr
whidbey Sep 9, 2019
00c9f82
Update session.cr
whidbey Sep 9, 2019
5bfaf07
Update session.cr
whidbey Sep 9, 2019
e61d3f8
Update session.cr
whidbey Sep 9, 2019
87dbf68
Update session.cr
whidbey Sep 9, 2019
bbf54af
Update session.cr
whidbey Sep 9, 2019
a341e63
Update session.cr
whidbey Sep 9, 2019
4e0a5d1
Update session.cr
whidbey Sep 9, 2019
9438cfe
Update session.cr
whidbey Sep 9, 2019
c63453b
Update session.cr
whidbey Sep 9, 2019
36aa499
Update session.cr
whidbey Sep 9, 2019
be89d3e
Update session.cr
whidbey Sep 9, 2019
8af0c91
Update session.cr
whidbey Sep 9, 2019
55d12f9
Update session.cr
whidbey Sep 9, 2019
3b5fca3
Update session.cr
whidbey Sep 9, 2019
ad460b6
Update session.cr
whidbey Sep 9, 2019
feaf94e
Update session.cr
whidbey Sep 9, 2019
449a7df
Update session.cr
whidbey Sep 9, 2019
38854b9
Update session.cr
whidbey Sep 9, 2019
160c428
Update session.cr
whidbey Sep 9, 2019
2ce2cb6
Update session.cr
whidbey Sep 9, 2019
f811a16
Update session.cr
whidbey Sep 9, 2019
ab2c476
Update session.cr
whidbey Sep 9, 2019
ceacd6d
Update session.cr
whidbey Sep 9, 2019
ef53948
Update session.cr
whidbey Sep 9, 2019
049ac8e
Update session.cr
whidbey Sep 9, 2019
d0494e5
Update session.cr
whidbey Sep 9, 2019
b4daa15
Compatibility with crystal 0.35
medaved Jun 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.crystal
.tool-versions
8 changes: 4 additions & 4 deletions spec/ssh2_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
24 changes: 16 additions & 8 deletions src/channel.cr
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -63,15 +62,15 @@ 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

# Return a tuple with first field populated with the exit signal (without
# 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -219,7 +228,6 @@ class SSH2::Channel < IO
end

class StreamIO < IO

getter channel : Channel
getter stream_id : Int32

Expand Down
2 changes: 1 addition & 1 deletion src/lib_ssh2.cr
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ lib LibSSH2

@[Flags]
enum BlockDirections
Inbound,
Inbound
Outbound
end

Expand Down
46 changes: 30 additions & 16 deletions src/session.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions src/sftp/attributes.cr
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ 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)
@stat.atime = v.to_utc.to_i.to_u64
end

def mtime
Time.epoch(@stat.mtime.to_i32)
Time.unix_ms(@stat.mtime.to_i32)
end

def mtime=(v : Time)
Expand Down