diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/Makefile | 2 | ||||
-rw-r--r-- | lib/ssh/src/ssh.appup.src | 12 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 1 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connect.hrl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection.erl | 82 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 52 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftp.erl | 316 | ||||
-rw-r--r-- | lib/ssh/src/ssh_xfer.erl | 9 |
8 files changed, 416 insertions, 62 deletions
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 04ce750371..90d71107ad 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -116,7 +116,7 @@ $(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES) debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) clean: - rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES) rm -f errs core *~ $(APP_TARGET): $(APP_SRC) ../vsn.mk diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 600c01454c..70f1cc487c 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -19,6 +19,12 @@ {"%VSN%", [ + {"3.0.8", [{load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_sftp, soft_purge, soft_purge, [erl_tar,ssh_xfer]}, + {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, + {load_module, ssh, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_xfer, soft_purge, soft_purge, []} + ]}, {"3.0.7", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]}, {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]}, {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]}, @@ -42,6 +48,12 @@ {<<".*">>, [{restart_application, ssh}]} ], [ + {"3.0.8", [{load_module, ssh_connection, soft_purge, soft_purge, [ssh_connection_handler]}, + {load_module, ssh_sftp, soft_purge, soft_purge, []}, + {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, + {load_module, ssh, soft_purge, soft_purge, []}, + {load_module, ssh_xfer, soft_purge, soft_purge, []} + ]}, {"3.0.7", [{load_module, ssh_auth, soft_purge, soft_purge, [ssh_connection_handler]}, {load_module, ssh_acceptor, soft_purge, soft_purge, [ssh_connection_handler]}, {load_module, ssh_channel, soft_purge, soft_purge, [ssh_connection_handler]}, diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index de047d3c83..eae33e3683 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -194,6 +194,7 @@ shell(Host, Port, Options) -> {ok, ConnectionRef} -> case ssh_connection:session_channel(ConnectionRef, infinity) of {ok,ChannelId} -> + success = ssh_connection:ptty_alloc(ConnectionRef, ChannelId, []), Args = [{channel_cb, ssh_shell}, {init_args,[ConnectionRef, ChannelId]}, {cm, ConnectionRef}, {channel_id, ChannelId}], diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 9307dbbad0..d14f7ce27d 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -165,6 +165,10 @@ recipient_channel }). +-define(TERMINAL_WIDTH, 80). +-define(TERMINAL_HEIGHT, 24). +-define(DEFAULT_TERMINAL, "vt100"). + -define(TTY_OP_END,0). %% Indicates end of options. -define(VINTR,1). %% Interrupt character; 255 if none. Similarly for the %% other characters. Not all of these characters are diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 83bfad7900..593443e11c 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -32,11 +32,11 @@ %% API -export([session_channel/2, session_channel/4, exec/4, shell/2, subsystem/4, send/3, send/4, send/5, - send_eof/2, adjust_window/3, setenv/5, close/2, reply_request/4]). + send_eof/2, adjust_window/3, setenv/5, close/2, reply_request/4, + ptty_alloc/3, ptty_alloc/4]). %% Potential API currently unsupported and not tested --export([open_pty/3, open_pty/7, - open_pty/9, window_change/4, window_change/6, +-export([window_change/4, window_change/6, direct_tcpip/6, direct_tcpip/8, tcpip_forward/3, cancel_tcpip_forward/3, signal/3, exit_status/3]). @@ -107,9 +107,15 @@ shell(ConnectionHandler, ChannelId) -> %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> - ssh_connection_handler:request(ConnectionHandler, self(), - ChannelId, "subsystem", - true, [?string(SubSystem)], TimeOut). + case ssh_connection_handler:request(ConnectionHandler, self(), + ChannelId, "subsystem", + true, [?string(SubSystem)], TimeOut) of + success -> success; + failure -> failure; + {error,timeout} -> {error,timeout}; + _ -> failure + end. + %%-------------------------------------------------------------------- -spec send(pid(), channel_id(), iodata()) -> ok | {error, closed}. @@ -183,6 +189,25 @@ reply_request(_,false, _, _) -> ok. %%-------------------------------------------------------------------- +-spec ptty_alloc(pid(), channel_id(), proplists:proplist()) -> success | failiure. +%% +%% +%% Description: Sends a ssh connection protocol pty_req. +%%-------------------------------------------------------------------- +ptty_alloc(ConnectionHandler, Channel, Options) -> + ptty_alloc(ConnectionHandler, Channel, Options, infinity). +ptty_alloc(ConnectionHandler, Channel, Options, TimeOut) -> + {Width, PixWidth} = pty_default_dimensions(width, Options), + {Hight, PixHight} = pty_default_dimensions(hight, Options), + pty_req(ConnectionHandler, Channel, + proplists:get_value(term, Options, default_term()), + proplists:get_value(width, Options, Width), + proplists:get_value(hight, Options, Hight), + proplists:get_value(pixel_widh, Options, PixWidth), + proplists:get_value(pixel_hight, Options, PixHight), + proplists:get_value(pty_opts, Options, []), TimeOut + ). +%%-------------------------------------------------------------------- %% Not yet officialy supported! The following functions are part of the %% initial contributed ssh application. They are untested. Do we want them? %% Should they be documented and tested? @@ -205,23 +230,6 @@ exit_status(ConnectionHandler, Channel, Status) -> ssh_connection_handler:request(ConnectionHandler, Channel, "exit-status", false, [?uint32(Status)], 0). -open_pty(ConnectionHandler, Channel, TimeOut) -> - open_pty(ConnectionHandler, Channel, - os:getenv("TERM"), 80, 24, [], TimeOut). - -open_pty(ConnectionHandler, Channel, Term, Width, Height, PtyOpts, TimeOut) -> - open_pty(ConnectionHandler, Channel, Term, Width, - Height, 0, 0, PtyOpts, TimeOut). - -open_pty(ConnectionHandler, Channel, Term, Width, Height, - PixWidth, PixHeight, PtyOpts, TimeOut) -> - ssh_connection_handler:request(ConnectionHandler, - Channel, "pty-req", true, - [?string(Term), - ?uint32(Width), ?uint32(Height), - ?uint32(PixWidth),?uint32(PixHeight), - encode_pty_opts(PtyOpts)], TimeOut). - direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort, Timeout) -> direct_tcpip(ConnectionHandler, RemoteHost, RemotePort, OrigIP, OrigPort, @@ -1074,6 +1082,27 @@ flow_control([_|_], #channel{flow_control = From, flow_control(_,_,_) -> []. +pty_req(ConnectionHandler, Channel, Term, Width, Height, + PixWidth, PixHeight, PtyOpts, TimeOut) -> + ssh_connection_handler:request(ConnectionHandler, + Channel, "pty-req", true, + [?string(Term), + ?uint32(Width), ?uint32(Height), + ?uint32(PixWidth),?uint32(PixHeight), + encode_pty_opts(PtyOpts)], TimeOut). + +pty_default_dimensions(Dimension, Options) -> + case proplists:get_value(Dimension, Options, 0) of + N when is_integer(N), N > 0 -> + {N, 0}; + _ -> + case proplists:get_value(list_to_atom("pixel_" ++ atom_to_list(Dimension)), Options, 0) of + N when is_integer(N), N > 0 -> + {0, N}; + _ -> + {?TERMINAL_WIDTH, 0} + end + end. encode_pty_opts(Opts) -> Bin = list_to_binary(encode_pty_opts2(Opts)), @@ -1271,3 +1300,10 @@ decode_ip(Addr) when is_binary(Addr) -> {ok,A} -> A end. +default_term() -> + case os:getenv("TERM") of + false -> + ?DEFAULT_TERMINAL; + Str when is_list(Str)-> + Str + end. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index fa107be1b1..fdb9d3b3e6 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -624,7 +624,7 @@ handle_event(renegotiate, connected, #state{ssh_params = Ssh0} renegotiate = true})}; handle_event(renegotiate, StateName, State) -> - timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiatie]), + timer:apply_after(?REKEY_TIMOUT, gen_fsm, send_all_state_event, [self(), renegotiate]), %% Allready in keyexcahange so ignore {next_state, StateName, State}; @@ -1299,9 +1299,9 @@ generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName, end; generate_event(Msg, StateName, State0, EncData) -> - Event = ssh_message:decode(Msg), - State = generate_event_new_state(State0, EncData), try + Event = ssh_message:decode(Msg), + State = generate_event_new_state(State0, EncData), case Event of #ssh_msg_kexinit{} -> %% We need payload for verification later. @@ -1315,7 +1315,7 @@ generate_event(Msg, StateName, State0, EncData) -> #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, description = "Encountered unexpected input", language = "en"}, - handle_disconnect(DisconnectMsg, State) + handle_disconnect(DisconnectMsg, State0) end. @@ -1475,25 +1475,35 @@ handle_ssh_packet(Length, StateName, #state{decoded_data_buffer = DecData0, ssh_params = Ssh0, transport_protocol = _Protocol, socket = _Socket} = State0) -> - {Ssh1, DecData, EncData, Mac} = - ssh_transport:unpack(EncData0, Length, Ssh0), - SshPacket = <<DecData0/binary, DecData/binary>>, - case ssh_transport:is_valid_mac(Mac, SshPacket, Ssh1) of - true -> - PacketData = ssh_transport:msg_data(SshPacket), - {Ssh1, Msg} = ssh_transport:decompress(Ssh1, PacketData), - generate_event(Msg, StateName, - State0#state{ssh_params = Ssh1, - %% Important to be set for - %% next_packet - decoded_data_buffer = <<>>}, EncData); - false -> - DisconnectMsg = + try + {Ssh1, DecData, EncData, Mac} = + ssh_transport:unpack(EncData0, Length, Ssh0), + SshPacket = <<DecData0/binary, DecData/binary>>, + case ssh_transport:is_valid_mac(Mac, SshPacket, Ssh1) of + true -> + PacketData = ssh_transport:msg_data(SshPacket), + {Ssh1, Msg} = ssh_transport:decompress(Ssh1, PacketData), + generate_event(Msg, StateName, + State0#state{ssh_params = Ssh1, + %% Important to be set for + %% next_packet + decoded_data_buffer = <<>>}, + EncData); + false -> + DisconnectMsg = + #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "Bad mac", + language = "en"}, + handle_disconnect(DisconnectMsg, State0) + end + catch _:_ -> + Disconnect = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Bad mac", + description = "Bad input", language = "en"}, - handle_disconnect(DisconnectMsg, State0) - end. + handle_disconnect(Disconnect, State0) + end. + handle_disconnect(DisconnectMsg, State) -> handle_disconnect(own, DisconnectMsg, State). diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 721146c509..613f8f25b2 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -33,8 +33,8 @@ -export([start_channel/1, start_channel/2, start_channel/3, stop_channel/1]). --export([open/3, opendir/2, close/2, readdir/2, pread/4, read/3, - open/4, opendir/3, close/3, readdir/3, pread/5, read/4, +-export([open/3, open_tar/3, opendir/2, close/2, readdir/2, pread/4, read/3, + open/4, open_tar/4, opendir/3, close/3, readdir/3, pread/5, read/4, apread/4, aread/3, pwrite/4, write/3, apwrite/4, awrite/3, pwrite/5, write/4, position/3, real_path/2, read_file_info/2, get_file_info/2, @@ -69,6 +69,18 @@ mode }). +-record(bufinf, + { + mode, % read | write (=from or to buffer by user) + crypto_state, + crypto_fun, % For encode or decode depending on the mode field + size = 0, % # bytes "before" the current buffer for the postion call + + chunksize, % The size of the chunks to be sent or received + enc_text_buf = <<>>, % Encrypted text + plain_text_buf = <<>> % Decrypted text + }). + -define(FILEOP_TIMEOUT, infinity). -define(NEXT_REQID(S), @@ -112,7 +124,7 @@ start_channel(Host, Opts) -> start_channel(Host, Port, Opts) -> {SshOpts, SftpOpts} = handle_options(Opts, [], []), Timeout = proplists:get_value(timeout, SftpOpts, infinity), - case ssh_xfer:connect(Host, Port, SshOpts) of + case ssh_xfer:connect(Host, Port, SshOpts, Timeout) of {ok, ChannelId, Cm} -> case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId, SftpOpts]) of @@ -162,6 +174,77 @@ open(Pid, File, Mode) -> open(Pid, File, Mode, FileOpTimeout) -> call(Pid, {open, false, File, Mode}, FileOpTimeout). +open_tar(Pid, File, Mode) -> + open_tar(Pid, File, Mode, ?FILEOP_TIMEOUT). +open_tar(Pid, File, Mode, FileOpTimeout) -> + case {lists:member(write,Mode), + lists:member(read,Mode), + Mode -- [read,write]} of + {true,false,[]} -> + {ok,Handle} = open(Pid, File, [write], FileOpTimeout), + erl_tar:init(Pid, write, + fun(write, {_,Data}) -> + write_to_remote_tar(Pid, Handle, to_bin(Data), FileOpTimeout); + (position, {_,Pos}) -> + position(Pid, Handle, Pos, FileOpTimeout); + (close, _) -> + close(Pid, Handle, FileOpTimeout) + end); + {true,false,[{crypto,{CryptoInitFun,CryptoEncryptFun,CryptoEndFun}}]} -> + {ok,SftpHandle} = open(Pid, File, [write], FileOpTimeout), + BI = #bufinf{mode = write, + crypto_fun = CryptoEncryptFun}, + {ok,BufHandle} = open_buf(Pid, CryptoInitFun, BI, FileOpTimeout), + erl_tar:init(Pid, write, + fun(write, {_,Data}) -> + write_buf(Pid, SftpHandle, BufHandle, to_bin(Data), FileOpTimeout); + (position, {_,Pos}) -> + position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout); + (close, _) -> + {ok,#bufinf{ + plain_text_buf = PlainBuf0, + enc_text_buf = EncBuf0, + crypto_state = CState0 + }} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout), + {ok,EncTextTail} = CryptoEndFun(PlainBuf0, CState0), + EncTextBuf = <<EncBuf0/binary, EncTextTail/binary>>, + case write(Pid, SftpHandle, EncTextBuf, FileOpTimeout) of + ok -> + call(Pid, {erase_bufinf,BufHandle}, FileOpTimeout), + close(Pid, SftpHandle, FileOpTimeout); + Other -> + Other + end + end); + {false,true,[]} -> + {ok,Handle} = open(Pid, File, [read,binary], FileOpTimeout), + erl_tar:init(Pid, read, + fun(read2, {_,Len}) -> + read_repeat(Pid, Handle, Len, FileOpTimeout); + (position, {_,Pos}) -> + position(Pid, Handle, Pos, FileOpTimeout); + (close, _) -> + close(Pid, Handle, FileOpTimeout) + end); + {false,true,[{crypto,{CryptoInitFun,CryptoDecryptFun}}]} -> + {ok,SftpHandle} = open(Pid, File, [read,binary], FileOpTimeout), + BI = #bufinf{mode = read, + crypto_fun = CryptoDecryptFun}, + {ok,BufHandle} = open_buf(Pid, CryptoInitFun, BI, FileOpTimeout), + erl_tar:init(Pid, read, + fun(read2, {_,Len}) -> + read_buf(Pid, SftpHandle, BufHandle, Len, FileOpTimeout); + (position, {_,Pos}) -> + position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout); + (close, _) -> + call(Pid, {erase_bufinf,BufHandle}, FileOpTimeout), + close(Pid, SftpHandle, FileOpTimeout) + end); + _ -> + {error,{illegal_mode,Mode}} + end. + + opendir(Pid, Path) -> opendir(Pid, Path, ?FILEOP_TIMEOUT). opendir(Pid, Path, FileOpTimeout) -> @@ -447,6 +530,15 @@ handle_cast(_,State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. +do_handle_call({get_bufinf,BufHandle}, _From, S=#state{inf=I0}) -> + {reply, dict:find(BufHandle,I0), S}; + +do_handle_call({put_bufinf,BufHandle,B}, _From, S=#state{inf=I0}) -> + {reply, ok, S#state{inf=dict:store(BufHandle,B,I0)}}; + +do_handle_call({erase_bufinf,BufHandle}, _From, S=#state{inf=I0}) -> + {reply, ok, S#state{inf=dict:erase(BufHandle,I0)}}; + do_handle_call({open, Async,FileName,Mode}, From, #state{xf = XF} = State) -> {Access,Flags,Attrs} = open_mode(XF#ssh_xfer.vsn, Mode), ReqID = State#state.req_id, @@ -551,12 +643,7 @@ do_handle_call({read,Async,Handle,Length}, From, State) -> do_handle_call({pwrite,Async,Handle,At,Data0}, From, State) -> case lseek_position(Handle, At, State) of {ok,Offset} -> - Data = if - is_binary(Data0) -> - Data0; - is_list(Data0) -> - list_to_binary(Data0) - end, + Data = to_bin(Data0), ReqID = State#state.req_id, Size = size(Data), ssh_xfer:write(?XF(State),ReqID,Handle,Offset,Data), @@ -569,12 +656,7 @@ do_handle_call({pwrite,Async,Handle,At,Data0}, From, State) -> do_handle_call({write,Async,Handle,Data0}, From, State) -> case lseek_position(Handle, cur, State) of {ok,Offset} -> - Data = if - is_binary(Data0) -> - Data0; - is_list(Data0) -> - list_to_binary(Data0) - end, + Data = to_bin(Data0), ReqID = State#state.req_id, Size = size(Data), ssh_xfer:write(?XF(State),ReqID,Handle,Offset,Data), @@ -1126,5 +1208,207 @@ lseek_pos({eof, Offset}, _CurOffset, CurSize) end; lseek_pos(_, _, _) -> {error, einval}. - +%%%================================================================ +%%% +to_bin(Data) when is_list(Data) -> list_to_binary(Data); +to_bin(Data) when is_binary(Data) -> Data. + + +read_repeat(Pid, Handle, Len, FileOpTimeout) -> + {ok,{_WindowSz,PacketSz}} = recv_window(Pid, FileOpTimeout), + read_rpt(Pid, Handle, Len, PacketSz, FileOpTimeout, <<>>). + +read_rpt(Pid, Handle, WantedLen, PacketSz, FileOpTimeout, Acc) when WantedLen > 0 -> + case read(Pid, Handle, min(WantedLen,PacketSz), FileOpTimeout) of + {ok, Data} -> + read_rpt(Pid, Handle, WantedLen-size(Data), PacketSz, FileOpTimeout, <<Acc/binary, Data/binary>>); + eof -> + {ok, Acc}; + Error -> + Error + end; +read_rpt(_Pid, _Handle, WantedLen, _PacketSz, _FileOpTimeout, Acc) when WantedLen >= 0 -> + {ok,Acc}. + + +write_to_remote_tar(_Pid, _SftpHandle, <<>>, _FileOpTimeout) -> + ok; +write_to_remote_tar(Pid, SftpHandle, Bin, FileOpTimeout) -> + {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout), + write_file_loop(Pid, SftpHandle, 0, Bin, size(Bin), Packet, FileOpTimeout). + +position_buf(Pid, SftpHandle, BufHandle, Pos, FileOpTimeout) -> + {ok,#bufinf{mode = Mode, + plain_text_buf = Buf0, + size = Size}} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout), + case Pos of + {cur,0} when Mode==write -> + {ok,Size+size(Buf0)}; + + {cur,0} when Mode==read -> + {ok,Size}; + + _ when Mode==read, is_integer(Pos) -> + Skip = Pos-Size, + if + Skip < 0 -> + {error, cannot_rewind}; + Skip == 0 -> + %% Optimization + {ok,Pos}; + Skip > 0 -> + case read_buf(Pid, SftpHandle, BufHandle, Skip, FileOpTimeout) of + %% A bit innefficient to fetch the bufinf again, but there are lots of + %% other more important optimizations waiting.... + {ok,_} -> + {ok,Pos}; + Other -> + Other + end + end; + + _ -> + {error,{not_yet_implemented,{pos,Pos}}} + end. + +read_buf(Pid, SftpHandle, BufHandle, WantedLen, FileOpTimeout) -> + {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout), + {ok,B0} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout), + case do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B0) of + {ok,ResultBin,B} -> + call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout), + {ok,ResultBin}; + {error,Error} -> + {error,Error}; + {eof,B} -> + call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout), + eof + end. + +do_the_read_buf(_Pid, _SftpHandle, WantedLen, _Packet, _FileOpTimeout, + B=#bufinf{plain_text_buf=PlainBuf0, + size = Size}) + when size(PlainBuf0) >= WantedLen -> + %% We already have the wanted number of bytes decoded and ready! + <<ResultBin:WantedLen/binary, PlainBuf/binary>> = PlainBuf0, + {ok,ResultBin,B#bufinf{plain_text_buf=PlainBuf, + size = Size + WantedLen}}; + +do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + B0=#bufinf{plain_text_buf = PlainBuf0, + enc_text_buf = EncBuf0, + chunksize = undefined + }) + when size(EncBuf0) > 0 -> + %% We have (at least) one decodable byte waiting for decodeing. + {ok,DecodedBin,B} = apply_crypto(EncBuf0, B0), + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + B#bufinf{plain_text_buf = <<PlainBuf0/binary, DecodedBin/binary>>, + enc_text_buf = <<>> + }); + +do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + B0=#bufinf{plain_text_buf = PlainBuf0, + enc_text_buf = EncBuf0, + chunksize = ChunkSize0 + }) + when size(EncBuf0) >= ChunkSize0 -> + %% We have (at least) one chunk of decodable bytes waiting for decodeing. + <<ToDecode:ChunkSize0/binary, EncBuf/binary>> = EncBuf0, + {ok,DecodedBin,B} = apply_crypto(ToDecode, B0), + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + B#bufinf{plain_text_buf = <<PlainBuf0/binary, DecodedBin/binary>>, + enc_text_buf = EncBuf + }); + +do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, B=#bufinf{enc_text_buf = EncBuf0}) -> + %% We must read more bytes and append to the buffer of encoded bytes. + case read(Pid, SftpHandle, Packet, FileOpTimeout) of + {ok,EncryptedBin} -> + do_the_read_buf(Pid, SftpHandle, WantedLen, Packet, FileOpTimeout, + B#bufinf{enc_text_buf = <<EncBuf0/binary, EncryptedBin/binary>>}); + eof -> + {eof,B}; + Other -> + Other + end. + + +write_buf(Pid, SftpHandle, BufHandle, PlainBin, FileOpTimeout) -> + {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout), + {ok,B0=#bufinf{plain_text_buf=PTB}} = call(Pid, {get_bufinf,BufHandle}, FileOpTimeout), + case do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + B0#bufinf{plain_text_buf = <<PTB/binary,PlainBin/binary>>}) of + {ok, B} -> + call(Pid, {put_bufinf,BufHandle,B}, FileOpTimeout), + ok; + {error,Error} -> + {error,Error} + end. + +do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + B=#bufinf{enc_text_buf = EncBuf0, + size = Size}) + when size(EncBuf0) >= Packet -> + <<BinToWrite:Packet/binary, EncBuf/binary>> = EncBuf0, + case write(Pid, SftpHandle, BinToWrite, FileOpTimeout) of + ok -> + do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + B#bufinf{enc_text_buf = EncBuf, + size = Size + Packet}); + Other -> + Other + end; + +do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + B0=#bufinf{plain_text_buf = PlainBuf0, + enc_text_buf = EncBuf0, + chunksize = undefined}) + when size(PlainBuf0) > 0 -> + {ok,EncodedBin,B} = apply_crypto(PlainBuf0, B0), + do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + B#bufinf{plain_text_buf = <<>>, + enc_text_buf = <<EncBuf0/binary, EncodedBin/binary>>}); + +do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + B0=#bufinf{plain_text_buf = PlainBuf0, + enc_text_buf = EncBuf0, + chunksize = ChunkSize0 + }) + when size(PlainBuf0) >= ChunkSize0 -> + <<ToEncode:ChunkSize0/binary, PlainBuf/binary>> = PlainBuf0, + {ok,EncodedBin,B} = apply_crypto(ToEncode, B0), + do_the_write_buf(Pid, SftpHandle, Packet, FileOpTimeout, + B#bufinf{plain_text_buf = PlainBuf, + enc_text_buf = <<EncBuf0/binary, EncodedBin/binary>>}); + +do_the_write_buf(_Pid, _SftpHandle, _Packet, _FileOpTimeout, B) -> + {ok,B}. + +apply_crypto(In, B=#bufinf{crypto_state = CState0, + crypto_fun = F}) -> + case F(In,CState0) of + {ok,EncodedBin,CState} -> + {ok, EncodedBin, B#bufinf{crypto_state=CState}}; + {ok,EncodedBin,CState,ChunkSize} -> + {ok, EncodedBin, B#bufinf{crypto_state=CState, + chunksize=ChunkSize}} + end. + +open_buf(Pid, CryptoInitFun, BufInfo0, FileOpTimeout) -> + case CryptoInitFun() of + {ok,CryptoState} -> + open_buf1(Pid, BufInfo0, FileOpTimeout, CryptoState, undefined); + {ok,CryptoState,ChunkSize} -> + open_buf1(Pid, BufInfo0, FileOpTimeout, CryptoState, ChunkSize); + Other -> + Other + end. + +open_buf1(Pid, BufInfo0, FileOpTimeout, CryptoState, ChunkSize) -> + BufInfo = BufInfo0#bufinf{crypto_state = CryptoState, + chunksize = ChunkSize}, + BufHandle = make_ref(), + call(Pid, {put_bufinf,BufHandle,BufInfo}, FileOpTimeout), + {ok,BufHandle}. diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index 1881392db8..2743b704f1 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -23,7 +23,7 @@ -module(ssh_xfer). --export([attach/2, connect/3]). +-export([attach/2, connect/3, connect/4]). -export([open/6, opendir/3, readdir/3, close/3, read/5, write/5, rename/5, remove/3, mkdir/4, rmdir/3, realpath/3, extended/4, stat/4, fstat/4, lstat/4, setstat/4, @@ -58,6 +58,13 @@ connect(Host, Port, Opts) -> Error -> Error end. +connect(Host, Port, Opts, Timeout) -> + case ssh:connect(Host, Port, Opts, Timeout) of + {ok, CM} -> open_xfer(CM, [{timeout, Timeout}|Opts]); + {error, Timeout} -> {error, timeout}; + Error -> Error + end. + open_xfer(CM, Opts) -> TMO = proplists:get_value(timeout, Opts, infinity), case ssh_connection:session_channel(CM, ?XFER_WINDOW_SIZE, ?XFER_PACKET_SIZE, TMO) of |