diff options
Diffstat (limited to 'lib/ssh')
34 files changed, 1332 insertions, 1260 deletions
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index 9503060140..60f20c7c3f 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -30,6 +30,21 @@ <file>notes.xml</file> </header> +<section><title>Ssh 4.7.7</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + SSH uses the new crypto API.</p> + <p> + Own Id: OTP-15673</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.7.6</title> <section><title>Improvements and New Features</title> @@ -372,6 +387,24 @@ </section> </section> +<section><title>Ssh 4.6.9.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + If a client was connected to an server on an already open + socket, the callback <c>fun(PeerName,FingerPrint)</c> in + the <c>accept_callback</c> option passed the local name + in the argument PeerName instead of the remote name.</p> + <p> + Own Id: OTP-15763</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.6.9.3</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -2977,7 +3010,7 @@ </item> <item> <p> - Fixed internal error on when client and server can not + Fixed internal error on when client and server cannot agree o which authmethod to use.</p> <p> Own Id: OTP-10731 Aux Id: seq12237 </p> diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index 1a53a2ea98..8b7cb4dcd4 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -46,7 +46,7 @@ In that encrypted connection one or more channels could be opened with <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seealso>. </p> - <p>Each channel is an isolated "pipe" between a client-side process and a server-side process. Thoose process + <p>Each channel is an isolated "pipe" between a client-side process and a server-side process. Those process pairs could handle for example file transfers (sftp) or remote command execution (shell, exec and/or cli). If a custom shell is implemented, the user of the client could execute the special commands remotely. Note that the user is not necessarily a human but probably a system interfacing the SSH app. @@ -347,6 +347,7 @@ <datatype> <name name="subsystem_daemon_option"/> + <name name="subsystem_specs"/> <name name="subsystem_spec"/> <desc> <p>Defines a subsystem in the daemon.</p> diff --git a/lib/ssh/doc/src/ssh_client_channel.xml b/lib/ssh/doc/src/ssh_client_channel.xml index cd28b95fd3..e6683dbd0b 100644 --- a/lib/ssh/doc/src/ssh_client_channel.xml +++ b/lib/ssh/doc/src/ssh_client_channel.xml @@ -150,12 +150,12 @@ <tag><c>{init_args(), list()}</c></tag> <item><p>The list of arguments to the <c>init</c> function of the callback module.</p></item> - <tag><c>{cm, ssh:connection_ref()}</c></tag> + <tag><c>{cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>Reference to the <c>ssh</c> connection as returned by <seealso marker="ssh#connect-3">ssh:connect/3</seealso>. </p></item> - <tag><c>{channel_id, ssh:channel_id()}</c></tag> + <tag><c>{channel_id, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>Id of the <c>ssh</c> channel as returned by <seealso marker="ssh_connection#session_channel/2">ssh_connection:session_channel/2,4</seealso>. </p></item> @@ -198,7 +198,7 @@ {ok, ChannelRef} | {error, Reason}</name> <fsummary>Starts a process that handles an SSH channel.</fsummary> <type> - <v>SshConnection = ssh:connection_ref()</v> + <v>SshConnection = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> <d>As returned by <seealso marker="ssh#connect-3">ssh:connect/3</seealso></d> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> @@ -374,7 +374,7 @@ function and all channels are to handle the following message.</p> <taglist> - <tag><c>{ssh_channel_up, ssh:channel_id(), ssh:connection_ref()}</c></tag> + <tag><c>{ssh_channel_up, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>This is the first message that the channel receives. It is sent just before the <seealso marker="#init-1">init/1</seealso> function @@ -393,21 +393,21 @@ ChannelId, State}</name> <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> - <v>Msg = ssh_connection:event()</v> + <v>Msg = <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso></v> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>State = term()</v> </type> <desc> <p>Handles SSH Connection Protocol messages that may need service-specific attention. For details, - see <seealso marker="ssh_connection"> ssh_connection:event()</seealso>. + see <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso>. </p> <p>The following message is taken care of by the <c>ssh_client_channel</c> behavior.</p> <taglist> - <tag><c>{closed, ssh:channel_id()}</c></tag> + <tag><c>{closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>The channel behavior sends a close message to the other side, if such a message has not already been sent. Then it terminates the channel with reason <c>normal</c>.</p></item> diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml index 2a701929f6..9fa1da659c 100644 --- a/lib/ssh/doc/src/ssh_connection.xml +++ b/lib/ssh/doc/src/ssh_connection.xml @@ -40,128 +40,119 @@ <p>The <url href="http://www.ietf.org/rfc/rfc4254.txt">SSH Connection Protocol</url> is used by clients and servers, that is, SSH channels, to communicate over the SSH connection. The API functions in this module send SSH Connection Protocol events, - which are received as messages by the remote channel. - If the receiving channel is an Erlang process, the - messages have the format - <c><![CDATA[{ssh_cm, connection_ref(), ssh_event_msg()}]]></c>. + which are received as messages by the remote channel handling the remote channel. + The Erlang format of thoose messages is + (see also <seealso marker="#type-event">below</seealso>): + </p> + <p><c>{ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, </c><seealso marker="#type-channel_msg"><c>channel_msg()</c></seealso><c>}</c> + </p> + <p> If the <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior is used to implement the channel process, these messages are handled by <seealso marker="ssh_client_channel#Module:handle_ssh_msg-2">handle_ssh_msg/2</seealso>.</p> </description> - <section> - <title>DATA TYPES</title> - - <p>Type definitions that are used more than once in this module, - or abstractions to indicate the intended use of the data - type, or both:</p> - - <taglist> - <tag><c>boolean() =</c></tag> - <item><p><c>true | false </c></p></item> - <tag><c>string() =</c></tag> - <item><p>list of ASCII characters</p></item> - <tag><c>timeout() =</c></tag> - <item><p><c>infinity | integer()</c> in milliseconds</p></item> - <tag><c>connection_ref() =</c></tag> - <item><p>opaque() -as returned by - <c>ssh:connect/3</c> or sent to an SSH channel processes</p></item> - <tag><c>channel_id() =</c></tag> - <item><p><c>integer()</c></p></item> - <tag><c>ssh_data_type_code() =</c></tag> - <item><p><c>1</c> ("stderr") | <c>0</c> ("normal") are - valid values, see - <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> Section 5.2.</p></item> - <tag><c>ssh_request_status() =</c></tag> - <item><p> <c>success | failure</c></p></item> - <tag><c>event() =</c></tag> - <item><p><c>{ssh_cm, connection_ref(), ssh_event_msg()}</c></p></item> - <tag><c>ssh_event_msg() =</c></tag> - <item><p><c>data_events() | status_events() | terminal_events()</c></p></item> - <tag><c>reason() =</c></tag> - <item><p><c>timeout | closed</c></p></item> - </taglist> - - <taglist> - <tag><em>data_events()</em></tag> - <item> - <taglist> - <tag><c><![CDATA[{data, channel_id(), ssh_data_type_code(), Data :: binary()}]]></c></tag> - <item><p>Data has arrived on the channel. This event is sent as a - result of calling <seealso marker="ssh_connection#send-3"> - ssh_connection:send/[3,4,5]</seealso>.</p></item> - - <tag><c><![CDATA[{eof, channel_id()}]]></c></tag> - <item><p>Indicates that the other side sends no more data. - This event is sent as a result of calling <seealso - marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>. - </p></item> - </taglist> - </item> + <datatypes> + <datatype> + <name name="ssh_data_type_code"/> + <desc> + <p>The valid values are <c>0</c> ("normal") and <c>1</c> ("stderr"), see + <url href="https://tools.ietf.org/html/rfc4254#page-8">RFC 4254, Section 5.2</url>.</p> + </desc> + </datatype> - <tag><em>status_events()</em></tag> - <item> + <datatype> + <name name="result"/> + <name name="reason"/> + <desc> + <p>The result of a call.</p> + <p>If the request reached the peer, was handled and the response + reached the requesting node the <seealso marker="#type-req_status">req_status()</seealso> + is the status reported from the peer.</p> + <p>If not, the <seealso marker="#type-reason">reason()</seealso> indicates what went wrong:</p> + <taglist> + <tag><c>closed</c></tag> + <item>indicates that the channel or connection was closed when trying to send the request + </item> + <tag><c>timeout</c></tag> + <item>indicates that the operation exceeded a time limit + </item> + </taglist> + </desc> + </datatype> - <taglist> - <tag><c><![CDATA[{signal, channel_id(), ssh_signal()}]]></c></tag> - <item><p>A signal can be delivered to the remote process/service - using the following message. Some systems do not support - signals, in which case they are to ignore this message. There is - currently no function to generate this event as the signals - referred to are on OS-level and not something generated by an - Erlang program.</p></item> + <datatype> + <name name="req_status"/> + <desc> + <p>The status of a request. + Coresponds to the <c>SSH_MSG_CHANNEL_SUCCESS</c> and <c>SSH_MSG_CHANNEL_FAILURE</c> values in + <url href="https://tools.ietf.org/html/rfc4254#section-5.4">RFC 4254, Section 5.4</url>. + </p> + </desc> + </datatype> - <tag><c><![CDATA[{exit_signal, channel_id(), ExitSignal :: string(), ErrorMsg ::string(), - LanguageString :: string()}]]></c></tag> + <datatype_title>SSH Connection Protocol: General</datatype_title> + <datatype> + <name name="event"/> + <name name="channel_msg"/> + <desc> + <p>As mentioned in the introduction, the + <url href="https://tools.ietf.org/html/rfc4254">SSH Connection Protocol</url> + events are handled as messages. When writing a channel handling process without using + the support by the <seealso marker="ssh_client_channel">ssh_client_channel</seealso> + behavior the process must handle thoose messages. + </p> + </desc> + </datatype> - <item><p>A remote execution can terminate violently because of a signal. - Then this message can be received. For details on valid string - values, see <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url> - Section 6.10, which shows a special case of these signals.</p></item> + <datatype> + <name name="want_reply"/> + <desc> + <p>Messages that include a <c>WantReply</c> expect the channel handling + process to call <seealso marker="ssh_connection#reply_request-4"> + ssh_connection:reply_request/4</seealso> + with the boolean value of <c>WantReply</c> as the second argument.</p> + </desc> + </datatype> - <tag><c><![CDATA[{exit_status, channel_id(), ExitStatus :: integer()}]]></c></tag> - <item><p>When the command running at the other end terminates, the - following message can be sent to return the exit status of the - command. A zero <c>exit_status</c> usually means that the command - terminated successfully. This event is sent as a result of calling - <seealso marker="ssh_connection#exit_status-3"> - ssh_connection:exit_status/3</seealso>.</p></item> - <tag><c><![CDATA[{closed, channel_id()}]]></c></tag> - <item><p>This event is sent as a result of calling - <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>. - Both the handling of this event and sending it are taken care of by the - <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior.</p></item> - - </taglist> - </item> + <datatype_title>Data Transfer (RFC 4254, section 5.2)</datatype_title> + <datatype> + <name name="data_ch_msg"/> + <desc> + <p>Data has arrived on the channel. This event is sent as a result of calling + <seealso marker="ssh_connection#send-3"> ssh_connection:send/[3,4,5]</seealso>. + </p> + </desc> + </datatype> - <tag><em>terminal_events()</em></tag> - <item> - <p>Channels implementing a shell and command execution on the - server side are to handle the following messages that can be sent by client- - channel processes.</p> + <datatype_title>Closing a Channel (RFC 4254, section 5.3)</datatype_title> + <datatype> + <name name="eof_ch_msg"/> + <desc> + <p>Indicates that the other side sends no more data. This event is sent as a result of calling + <seealso marker="ssh_connection#send_eof-2"> ssh_connection:send_eof/2</seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="closed_ch_msg"/> + <desc> + <p>This event is sent as a result of calling + <seealso marker="ssh_connection#close-2">ssh_connection:close/2</seealso>. + Both the handling of this event and sending it are taken care of by the + <seealso marker="ssh_client_channel">ssh_client_channel</seealso> behavior.</p> + </desc> + </datatype> - <p>Events that include a <c>WantReply</c> expect the event handling - process to call <seealso marker="ssh_connection#reply_request-4"> - ssh_connection:reply_request/4</seealso> - with the boolean value of <c>WantReply</c> as the second argument.</p> - <taglist> - <tag><c><![CDATA[{env, channel_id(), WantReply :: boolean(), - Var ::string(), Value :: string()}]]></c></tag> - <item><p>Environment variables can be passed to the shell/command - to be started later. This event is sent as a result of calling <seealso - marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>. - </p></item> - - <tag><c><![CDATA[{pty, channel_id(), - WantReply :: boolean(), {Terminal :: string(), CharWidth :: integer(), - RowHeight :: integer(), PixelWidth :: integer(), PixelHeight :: integer(), - TerminalModes :: [{Opcode :: atom() | integer(), - Value :: integer()}]}}]]></c></tag> - <item><p>A pseudo-terminal has been requested for the + <datatype_title>Requesting a Pseudo-Terminal (RFC 4254, section 6.2)</datatype_title> + <datatype> + <name name="pty_ch_msg"/> + <name name="term_mode"/> + <desc> + <p>A pseudo-terminal has been requested for the session. <c>Terminal</c> is the value of the TERM environment variable value, that is, <c>vt100</c>. Zero dimension parameters must be ignored. The character/row dimensions override the pixel @@ -169,46 +160,103 @@ drawable area of the window. <c>Opcode</c> in the <c>TerminalModes</c> list is the mnemonic name, represented as a lowercase Erlang atom, defined in - <url href="http://www.ietf.org/rfc/rfc4254.txt">RFC 4254</url>, Section 8. + <url href="https://tools.ietf.org/html/rfc4254#section-8">RFC 4254</url>, Section 8. It can also be an <c>Opcode</c> if the mnemonic name is not listed in the RFC. Example: <c>OP code: 53, mnemonic name ECHO erlang atom: echo</c>. This event is sent as a result of calling <seealso - marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p></item> + marker="ssh_connection#ptty_alloc/4">ssh_connection:ptty_alloc/4</seealso>.</p> + </desc> + </datatype> + - <tag><c><![CDATA[{shell, WantReply :: boolean()}]]></c></tag> - <item><p>This message requests that the user default shell + <datatype_title>Environment Variable Passing (RFC 4254, section 6.4)</datatype_title> + <datatype> + <name name="env_ch_msg"/> + <desc> + <p>Environment variables can be passed to the shell/command + to be started later. This event is sent as a result of calling <seealso + marker="ssh_connection#setenv-5"> ssh_connection:setenv/5</seealso>. + </p> + </desc> + </datatype> + + + <datatype_title>Starting a Shell or Command (RFC 4254, section 6.5)</datatype_title> + <datatype> + <name name="shell_ch_msg"/> + <desc> + <p>This message requests that the user default shell is started at the other end. This event is sent as a result of calling <seealso marker="ssh_connection#shell-2"> ssh_connection:shell/2</seealso>. - </p></item> + </p> + </desc> + </datatype> + <datatype> + <name name="exec_ch_msg"/> + <desc> + <p>This message requests that the server starts + execution of the given command. This event is sent as a result of calling <seealso + marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>. + </p> + </desc> + </datatype> + - <tag><c><![CDATA[{window_change, channel_id(), CharWidth() :: integer(), - RowHeight :: integer(), PixWidth :: integer(), PixHeight :: integer()}]]></c></tag> - <item><p>When the window (terminal) size changes on the client + <datatype_title>Window Dimension Change Message (RFC 4254, section 6.7)</datatype_title> + <datatype> + <name name="window_change_ch_msg"/> + <desc> + <p>When the window (terminal) size changes on the client side, it <em>can</em> send a message to the server side to inform it of - the new dimensions. No API function generates this event.</p></item> + the new dimensions. No API function generates this event.</p> + </desc> + </datatype> - <tag><c><![CDATA[{exec, channel_id(), - WantReply :: boolean(), Cmd :: string()}]]></c></tag> - <item><p>This message requests that the server starts - execution of the given command. This event is sent as a result of calling <seealso - marker="ssh_connection#exec-4">ssh_connection:exec/4 </seealso>. - </p></item> - </taglist> - </item> - </taglist> - </section> + <datatype_title>Signals (RFC 4254, section 6.9)</datatype_title> + <datatype> + <name name="signal_ch_msg"/> + <desc> + <p>A signal can be delivered to the remote process/service + using the following message. Some systems do not support + signals, in which case they are to ignore this message. There is + currently no function to generate this event as the signals + referred to are on OS-level and not something generated by an + Erlang program.</p> + </desc> + </datatype> + + + <datatype_title>Returning Exit Status (RFC 4254, section 6.10)</datatype_title> + <datatype> + <name name="exit_status_ch_msg"/> + <desc> + <p>When the command running at the other end terminates, the + following message can be sent to return the exit status of the + command. A zero <c>exit_status</c> usually means that the command + terminated successfully. This event is sent as a result of calling + <seealso marker="ssh_connection#exit_status-3"> + ssh_connection:exit_status/3</seealso>.</p> + </desc> + </datatype> + <datatype> + <name name="exit_signal_ch_msg"/> + <desc> + <p>A remote execution can terminate violently because of a signal. + Then this message can be received. For details on valid string + values, see <url href="https://tools.ietf.org/html/rfc4254#section-6.10">RFC 4254</url> + Section 6.10, which shows a special case of these signals.</p> + </desc> + </datatype> + + </datatypes> + <funcs> <func> - <name since="">adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name> + <name since="" name="adjust_window" arity="3"/> <fsummary>Adjusts the SSH flow control window.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>NumOfBytes = integer()</v> - </type> - <desc> + <desc> <p>Adjusts the SSH flow control window. This is to be done by both the client- and server-side channel processes.</p> @@ -221,17 +269,12 @@ </func> <func> - <name since="">close(ConnectionRef, ChannelId) -> ok</name> + <name since="" name="close" arity="2"/> <fsummary>Sends a close message on the channel <c>ChannelId</c>.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>A server- or client-channel process can choose to close their session by sending a close event. </p> - <note><p>This function is called by the <c>ssh_client_channel</c> behavior when the channel is terminated, see <seealso marker="ssh_client_channel"> ssh_client_channel(3)</seealso>. Thus, channels implemented @@ -240,57 +283,61 @@ </func> <func> - <name since="">exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="exec" arity="4"/> <fsummary>Requests that the server starts the execution of the given command.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Command = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Is to be called by a client-channel process to request that the server starts executing the given command. The result is several messages according to the following pattern. The last message is a channel close message, as the <c>exec</c> request is a one-time execution that closes the channel when it is done.</p> - <taglist> - <tag><c>N x {ssh_cm, connection_ref(), - {data, channel_id(), ssh_data_type_code(), Data :: binary()}}</c></tag> + <!--taglist> + <tag><c>N x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {data, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="#type-ssh_data_type_code">ssh_data_type_code()</seealso><c>, Data :: binary()}}</c></tag> <item><p>The result of executing the command can be only one line or thousands of lines depending on the command.</p></item> - <tag><c>0 or 1 x {ssh_cm, connection_ref(), {eof, channel_id()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {eof, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}}</c></tag> <item><p>Indicates that no more data is to be sent.</p></item> - <tag><c>0 or 1 x {ssh_cm, - connection_ref(), {exit_signal, - channel_id(), ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {exit_signal, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, ExitSignal :: string(), ErrorMsg :: string(), LanguageString :: string()}}</c></tag> <item><p>Not all systems send signals. For details on valid string values, see RFC 4254, Section 6.10</p></item> - <tag><c>0 or 1 x {ssh_cm, connection_ref(), {exit_status, - channel_id(), ExitStatus :: integer()}}</c></tag> + <tag><c>0 or 1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {exit_status, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, ExitStatus :: integer()}}</c></tag> <item><p>It is recommended by the SSH Connection Protocol to send this message, but that is not always the case.</p></item> - <tag><c>1 x {ssh_cm, connection_ref(), - {closed, channel_id()}}</c></tag> + <tag><c>1 x {ssh_cm, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>, {closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}}</c></tag> <item><p>Indicates that the <c>ssh_client_channel</c> started for the execution of the command has now been shut down.</p></item> + </taglist--> + + <taglist> + <tag>N x <seealso marker="#type-data_ch_msg">data message(s)</seealso></tag> + <item><p>The result of executing the command can be only one line + or thousands of lines depending on the command.</p></item> + + <tag>0 or 1 x <seealso marker="#type-eof_ch_msg">eof message</seealso></tag> + <item><p>Indicates that no more data is to be sent.</p></item> + + <tag>0 or 1 x <seealso marker="#type-exit_signal_ch_msg">exit signal message</seealso></tag> + <item><p>Not all systems send signals. For details on valid string + values, see RFC 4254, Section 6.10</p></item> + + <tag>0 or 1 x <seealso marker="#type-exit_status_ch_msg">exit status message</seealso></tag> + <item><p>It is recommended by the SSH Connection Protocol to send this + message, but that is not always the case.</p></item> + + <tag>1 x <seealso marker="#type-closed_ch_msg">closed status message</seealso></tag> + <item><p>Indicates that the <c>ssh_client_channel</c> started for the + execution of the command has now been shut down.</p></item> </taglist> </desc> </func> <func> - <name since="">exit_status(ConnectionRef, ChannelId, Status) -> ok</name> + <name since="" name="exit_status" arity="3"/> <fsummary>Sends the exit status of a command to the client.</fsummary> - <type> - <v>ConnectionRef = connection_ref() </v> - <v>ChannelId = channel_id()</v> - <v>Status = integer()</v> - </type> <desc> <p>Is to be called by a server-channel process to send the exit status of a command to the client.</p> @@ -298,16 +345,10 @@ </func> <func> - <name since="OTP 17.5">ptty_alloc(ConnectionRef, ChannelId, Options) -></name> - <name since="OTP 17.4">ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | - {error, reason()}</name> + <name since="OTP 17.5" name="ptty_alloc" arity="3"/> + <name since="OTP 17.4" name="ptty_alloc" arity="4"/> <fsummary>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Options = proplists:proplist()</v> - </type> <desc> <p>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal. Is to be called by an SSH client process.</p> @@ -339,14 +380,8 @@ </func> <func> - <name since="">reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name> + <name since="" name="reply_request" arity="4"/> <fsummary>Sends status replies to requests that want such replies.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>WantReply = boolean()</v> - <v>Status = ssh_request_status()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Sends status replies to requests where the requester has stated that it wants a status report, that is, <c>WantReply = true</c>. @@ -361,14 +396,15 @@ <name since="">send(ConnectionRef, ChannelId, Data, Timeout) -></name> <name since="">send(ConnectionRef, ChannelId, Type, Data) -></name> <name since="">send(ConnectionRef, ChannelId, Type, Data, TimeOut) -> - ok | {error, timeout} | {error, closed}</name> + ok | Error</name> <fsummary>Sends channel data.</fsummary> <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> + <v>ConnectionRef = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> + <v>ChannelId = <seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>Data = binary()</v> - <v>Type = ssh_data_type_code()</v> + <v>Type = <seealso marker="#type-ssh_data_type_code">ssh_data_type_code()</seealso></v> <v>Timeout = timeout()</v> + <v>Error = {error, <seealso marker="#type-reason">reason()</seealso>}</v> </type> <desc> <p>Is to be called by client- and server-channel processes to send data to each other. @@ -380,29 +416,17 @@ </func> <func> - <name since="">send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name> + <name since="" name="send_eof" arity="2"/> <fsummary>Sends EOF on channel <c>ChannelId</c>.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Sends EOF on channel <c>ChannelId</c>.</p> </desc> </func> <func> - <name since="">session_channel(ConnectionRef, Timeout) -></name> - <name since="">session_channel(ConnectionRef, InitialWindowSize, - MaxPacketSize, Timeout) -> {ok, channel_id()} | {error, reason()}</name> + <name since="" name="session_channel" arity="2"/> + <name since="" name="session_channel" arity="4"/> <fsummary>Opens a channel for an SSH session.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>InitialWindowSize = integer()</v> - <v>MaxPacketSize = integer()</v> - <v>Timeout = timeout()</v> - <v>Reason = term()</v> - </type> <desc> <p>Opens a channel for an SSH session. The channel id returned from this function is the id used as input to the other functions in this module.</p> @@ -410,17 +434,9 @@ </func> <func> - <name since="">setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="setenv" arity="5"/> <fsummary>Environment variables can be passed to the shell/command to be started later.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Var = string()</v> - <v>Value = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Environment variables can be passed before starting the shell/command. Is to be called by a client channel processes.</p> @@ -428,14 +444,9 @@ </func> <func> - <name since="">shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed} - </name> + <name since="" name="shell" arity="2"/> <fsummary>Requests that the user default shell (typically defined in /etc/passwd in Unix systems) is to be executed at the server end.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - </type> <desc> <p>Is to be called by a client channel process to request that the user default shell (typically defined in /etc/passwd in Unix systems) is executed @@ -448,15 +459,8 @@ </func> <func> - <name since="">subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | - {error, reason()}</name> + <name since="" name="subsystem" arity="4"/> <fsummary>Requests to execute a predefined subsystem on the server.</fsummary> - <type> - <v>ConnectionRef = connection_ref()</v> - <v>ChannelId = channel_id()</v> - <v>Subsystem = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Is to be called by a client-channel process for requesting to execute a predefined subsystem on the server. diff --git a/lib/ssh/doc/src/ssh_server_channel.xml b/lib/ssh/doc/src/ssh_server_channel.xml index a4e18bbfbf..87c745c9fb 100644 --- a/lib/ssh/doc/src/ssh_server_channel.xml +++ b/lib/ssh/doc/src/ssh_server_channel.xml @@ -112,7 +112,7 @@ function and all channels are to handle the following message.</p> <taglist> - <tag><c>{ssh_channel_up, ssh:channel_id(), ssh:connection_ref()}</c></tag> + <tag><c>{ssh_channel_up, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>, </c><seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso><c>}</c></tag> <item><p>This is the first message that the channel receives. This is especially useful if the server wants to send a message to the client without first @@ -129,21 +129,21 @@ ChannelId, State}</name> <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> - <v>Msg = ssh_connection:event()</v> + <v>Msg = <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso></v> <v>ChannelId = <seealso marker="ssh#type-channel_id">ssh:channel_id()</seealso></v> <v>State = term()</v> </type> <desc> <p>Handles SSH Connection Protocol messages that may need service-specific attention. For details, - see <seealso marker="ssh_connection"> ssh_connection:event()</seealso>. + see <seealso marker="ssh_connection#type-event">ssh_connection:event()</seealso>. </p> <p>The following message is taken care of by the <c>ssh_server_channel</c> behavior.</p> <taglist> - <tag><c>{closed, ssh:channel_id()}</c></tag> + <tag><c>{closed, </c><seealso marker="ssh:ssh#type-channel_id">ssh:channel_id()</seealso><c>}</c></tag> <item><p>The channel behavior sends a close message to the other side, if such a message has not already been sent. Then it terminates the channel with reason <c>normal</c>.</p></item> diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml index c89092798d..f9f1e0953b 100644 --- a/lib/ssh/doc/src/ssh_sftp.xml +++ b/lib/ssh/doc/src/ssh_sftp.xml @@ -37,60 +37,112 @@ SSH.</p> </description> - <section> - <title>DATA TYPES</title> - <p>Type definitions that are used more than once in this module, - or abstractions to indicate the intended use of the data type, or both: - </p> - - <taglist> - <tag><c>reason()</c></tag> - <item> - <p>= <c>atom() | string() | tuple() </c>A description of the reason why an operation failed.</p> - <p> - The <c>atom()</c> value is formed from the sftp error codes in the protocol-level responses as defined in - <url href="https://tools.ietf.org/id/draft-ietf-secsh-filexfer-13.txt">draft-ietf-secsh-filexfer-13.txt</url> - section 9.1. - </p> - <p> + <datatypes> + <datatype> + <name name="sftp_option"/> + <desc> + </desc> + </datatype> + + <datatype_title>Error cause</datatype_title> + <datatype> + <name name="reason"/> + <desc> + <p>A description of the reason why an operation failed.</p> + <p>The <c>atom()</c> value is formed from the sftp error codes in the protocol-level responses as defined in + <url href="https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-49">draft-ietf-secsh-filexfer-13</url> + section 9.1. The codes are named as <c>SSH_FX_*</c> which are transformed into lowercase of the star-part. E.g. the error code <c>SSH_FX_NO_SUCH_FILE</c> will cause the <c>reason()</c> to be <c>no_such_file</c>. </p> <p>The <c>string()</c> reason is the error information from the server in case of an exit-signal. If that information is empty, the reason is the exit signal name. </p> - <p>The <c>tuple()</c> reason are other errors like the <c>{exit_status,integer()}</c> if the exit status is not 0. + <p>The <c>tuple()</c> reason are other errors like for example <c>{exit_status,1}</c>. </p> - </item> + </desc> + </datatype> - <tag><c>connection_ref() =</c></tag> - <item><p><c>opaque()</c> - as returned by - <seealso marker="ssh#connect-3"><c>ssh:connect/3</c></seealso></p></item> + <datatype_title>Crypto operations for open_tar</datatype_title> + <datatype> + <name name="tar_crypto_spec"/> + <name name="encrypt_spec"/> + <name name="decrypt_spec"/> + <desc> + <p>Specifies the encryption or decryption applied to tar files when using + <seealso marker="#open_tar/3">open_tar/3</seealso> or + <seealso marker="#open_tar/4">open_tar/4</seealso>. + </p> + <p>The encryption or decryption is applied to the generated stream of + bytes prior to sending the resulting stream to the SFTP server. + </p> + <p>For code examples see Section + <seealso marker="using_ssh#example-with-encryption">Example with encryption</seealso> + in the ssh Users Guide. + </p> + </desc> + </datatype> - <tag><c>timeout()</c></tag> - <item><p>= <c>infinity | integer()</c> in milliseconds. Default infinity.</p></item> - </taglist> - </section> + <datatype> + <name name="init_fun"/> + <name name="chunk_size"/> + <name name="crypto_state"/> + <desc> + <p>The <c>init_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso> + is applied once prior to any other <c>crypto</c> + operation. The intention is that this function initiates the encryption or + decryption for example by calling + <seealso marker="crypto:crypto#crypto_init/4">crypto:crypto_init/4</seealso> + or similar. The <c>crypto_state()</c> is the state such a function may return. + </p> + <p>If the selected cipher needs to have the input data partioned into + blocks of a certain size, the <c>init_fun()</c> should return the second + form of return value with the <c>chunk_size()</c> set to the block size. + If the <c>chunk_size()</c> is <c>undefined</c>, the size of the <c>PlainBin</c>s varies, + because this is intended for stream crypto, whereas a fixed <c>chunk_size()</c> is intended for block crypto. + A <c>chunk_size()</c> can be changed in the return from the <c>crypto_fun()</c>. + The value can be changed between <c>pos_integer()</c> and <c>undefined</c>. + </p> + </desc> + </datatype> - <section> - <title>Time-outs</title> - <p>If the request functions for the SFTP channel return <c>{error, timeout}</c>, - no answer was received from the server within the expected time.</p> - <p>The request may have reached the server and may have been performed. - However, no answer was received from the server within the expected time.</p> - </section> + <datatype> + <name name="crypto_fun"/> + <name name="crypto_result"/> + <desc> + <p>The initial <c>crypto_state()</c> returned from the + <seealso marker="#type-init_fun">init_fun()</seealso> + is folded into repeated applications of the <c>crypto_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso>. + The binary returned from that fun is sent to the remote SFTP server and + the new <c>crypto_state()</c> is used in the next call of the + <c>crypto_fun()</c>. + </p> + <p>If the <c>crypto_fun()</c> reurns a <c>chunk_size()</c>, that value + is as block size for further blocks in calls to <c>crypto_fun()</c>. + </p> + </desc> + </datatype> + + <datatype> + <name name="final_fun"/> + <desc> + <p>If doing encryption, + the <c>final_fun()</c> in the + <seealso marker="#type-tar_crypto_spec">tar_crypto_spec</seealso> + is applied to the last piece of data. + The <c>final_fun()</c> is responsible for padding (if needed) and + encryption of that last piece. + </p> + </desc> + </datatype> + </datatypes> <funcs> <func> - <name since="">apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name> + <name name="apread" arity="4" since=""/> <fsummary>Reads asynchronously from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>N = term()</v> - </type> <desc><p>The <c><![CDATA[apread/4]]></c> function reads from a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#aread-3"><c>aread/3</c></seealso> functions.</p> @@ -98,17 +150,8 @@ </func> <func> - <name since="">apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name> + <name name="apwrite" arity="4" since=""/> <fsummary>Writes asynchronously to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - <v>N = term()</v> - </type> <desc><p>The <c><![CDATA[apwrite/4]]></c> function writes to a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#awrite-3"><c>awrite/3</c></seealso> functions.</p> @@ -116,15 +159,8 @@ </func> <func> - <name since="">aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name> + <name name="aread" arity="3" since=""/> <fsummary>Reads asynchronously from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>N = term()</v> - </type> <desc> <p>Reads from an open file, without waiting for the result. If the handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c> @@ -137,16 +173,8 @@ </func> <func> - <name since="">awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name> + <name name="awrite" arity="3" since=""/> <fsummary>Writes asynchronously to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes to an open file, without waiting for the result. If the handle is valid, the function returns <c><![CDATA[{async, N}]]></c>, where <c>N</c> @@ -159,28 +187,18 @@ </func> <func> - <name since="">close(ChannelPid, Handle) -></name> - <name since="">close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name> + <name name="close" arity="2" since=""/> + <name name="close" arity="3" since=""/> <fsummary>Closes an open handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Closes a handle to an open file or directory on the server.</p> </desc> </func> <func> - <name since="">delete(ChannelPid, Name) -></name> - <name since="">delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="delete" arity="2" since=""/> + <name name="delete" arity="3" since=""/> <fsummary>Deletes a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Deletes the file specified by <c><![CDATA[Name]]></c>. </p> @@ -188,14 +206,9 @@ </func> <func> - <name since="">del_dir(ChannelPid, Name) -></name> - <name since="">del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="del_dir" arity="2" since=""/> + <name name="del_dir" arity="3" since=""/> <fsummary>Deletes an empty directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Deletes a directory specified by <c><![CDATA[Name]]></c>. The directory must be empty before it can be successfully deleted. @@ -204,16 +217,9 @@ </func> <func> - <name since="">list_dir(ChannelPid, Path) -></name> - <name since="">list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name> + <name name="list_dir" arity="2" since=""/> + <name name="list_dir" arity="3" since=""/> <fsummary>Lists the directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Filenames = [Filename]</v> - <v>Filename = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Lists the given directory on the server, returning the filenames as a list of strings.</p> @@ -221,14 +227,9 @@ </func> <func> - <name since="">make_dir(ChannelPid, Name) -></name> - <name since="">make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name name="make_dir" arity="2" since=""/> + <name name="make_dir" arity="3" since=""/> <fsummary>Creates a directory.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Creates a directory specified by <c><![CDATA[Name]]></c>. <c><![CDATA[Name]]></c> must be a full path to a new directory. The directory can only be @@ -237,14 +238,9 @@ </func> <func> - <name since="">make_symlink(ChannelPid, Name, Target) -></name> - <name since="">make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name> + <name name="make_symlink" arity="3" since=""/> + <name name="make_symlink" arity="4" since=""/> <fsummary>Creates a symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Target = string()</v> - </type> <desc> <p>Creates a symbolic link pointing to <c><![CDATA[Target]]></c> with the name <c><![CDATA[Name]]></c>. @@ -252,32 +248,19 @@ </desc> </func> - <func> - <name since="">open(ChannelPid, File, Mode) -></name> - <name since="">open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> + <func> + <name name="open" arity="3" since=""/> + <name name="open" arity="4" since=""/> <fsummary>Opens a file and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Mode = [Modeflag]</v> - <v>Modeflag = read | write | creat | trunc | append | binary</v> - <v>Timeout = timeout()</v> - <v>Handle = term()</v> - </type> <desc> <p>Opens a file on the server and returns a handle, which can be used for reading or writing.</p> </desc> </func> <func> - <name since="">opendir(ChannelPid, Path) -></name> - <name since="">opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name name="opendir" arity="2" since=""/> + <name name="opendir" arity="3" since=""/> <fsummary>Opens a directory and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Opens a handle to a directory on the server. The handle can be used for reading directory contents.</p> @@ -285,72 +268,36 @@ </func> <func> - <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode) -></name> - <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name name="open_tar" arity="3" since="OTP 17.4"/> + <name name="open_tar" arity="4" since="OTP 17.4"/> <fsummary>Opens a tar file on the server to which <c>ChannelPid</c> is connected and returns a handle.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Path = string()</v> - <v>Mode = [read] | [write] | [read,EncryptOpt] | [write,DecryptOpt]</v> - <v>EncryptOpt = {crypto,{InitFun,EncryptFun,CloseFun}}</v> - <v>DecryptOpt = {crypto,{InitFun,DecryptFun}}</v> - <v>InitFun = (fun() -> {ok,CryptoState}) | (fun() -> {ok,CryptoState,ChunkSize})</v> - <v>CryptoState = any()</v> - <v>ChunkSize = undefined | pos_integer()</v> - <v>EncryptFun = (fun(PlainBin,CryptoState) -> EncryptResult)</v> - <v>EncryptResult = {ok,EncryptedBin,CryptoState} | {ok,EncryptedBin,CryptoState,ChunkSize}</v> - <v>PlainBin = binary()</v> - <v>EncryptedBin = binary()</v> - <v>DecryptFun = (fun(EncryptedBin,CryptoState) -> DecryptResult)</v> - <v>DecryptResult = {ok,PlainBin,CryptoState} | {ok,PlainBin,CryptoState,ChunkSize}</v> - <v>CloseFun = (fun(PlainBin,CryptoState) -> {ok,EncryptedBin})</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Opens a handle to a tar file on the server, associated with <c>ChannelPid</c>. - The handle can be used for remote tar creation and extraction, as defined by the - <seealso marker="stdlib:erl_tar#init-3">erl_tar:init/3</seealso> function. - </p> - - <p> For code exampel see Section - <seealso marker="using_ssh">SFTP Client with TAR Compression and Encryption</seealso> in - the ssh Users Guide. </p> - - <p>The <c>crypto</c> mode option is applied to the generated stream of bytes prior to sending - them to the SFTP server. This is intended for encryption but can be used for other - purposes. + The handle can be used for remote tar creation and extraction. The actual writing + and reading is performed by calls to + <seealso marker="stdlib:erl_tar#add-3">erl_tar:add/3,4</seealso> and + <seealso marker="stdlib:erl_tar#extract-2">erl_tar:extract/2</seealso>. + Note: The + <seealso marker="stdlib:erl_tar#init-3">erl_tar:init/3</seealso> function should not + be called, that one is called by this open_tar function. </p> - <p>The <c>InitFun</c> is applied once - prior to any other <c>crypto</c> operation. The returned <c>CryptoState</c> is then folded into - repeated applications of the <c>EncryptFun</c> or <c>DecryptFun</c>. The binary returned - from those funs are sent further to the remote SFTP server. Finally, if doing encryption, - the <c>CloseFun</c> is applied to the last piece of data. The <c>CloseFun</c> is - responsible for padding (if needed) and encryption of that last piece. + <p>For code examples see Section + <seealso marker="using_ssh#sftp-client-with-tar-compression">SFTP Client with TAR Compression</seealso> + in the ssh Users Guide. </p> - <p>The <c>ChunkSize</c> defines the size of the <c>PlainBin</c>s that <c>EncodeFun</c> is applied - to. If the <c>ChunkSize</c> is <c>undefined</c>, the size of the <c>PlainBin</c>s varies, - because this is intended for stream crypto, whereas a fixed <c>ChunkSize</c> is intended for block crypto. - <c>ChunkSize</c>s can be changed in the return from the <c>EncryptFun</c> or - <c>DecryptFun</c>. The value can be changed between <c>pos_integer()</c> and <c>undefined</c>. + <p>The <c>crypto</c> mode option is explained in the data types section above, see + <seealso marker="#Crypto operations for open_tar">Crypto operations for open_tar</seealso>. + Encryption is assumed if the <c>Mode</c> contains <c>write</c>, and + decryption if the <c>Mode</c> contains <c>read</c>. </p> - </desc> </func> <func> - <name since="">position(ChannelPid, Handle, Location) -></name> - <name since="">position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name> + <name name="position" arity="3" since=""/> + <name name="position" arity="4" since=""/> <fsummary>Sets the file position of a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Location = Offset - | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof</v> - <v>Offset = integer()</v> - <v>Timeout = timeout()</v> - <v>NewPosition = integer()</v> - </type> <desc> <p>Sets the file position of the file referenced by <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, NewPosition}]]></c> (as an absolute offset) if @@ -384,17 +331,9 @@ </func> <func> - <name since="">pread(ChannelPid, Handle, Position, Len) -></name> - <name since="">pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> + <name name="pread" arity="4" since=""/> + <name name="pread" arity="5" since=""/> <fsummary>Reads from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Len = integer()</v> - <v>Timeout = timeout()</v> - <v>Data = string() | binary()</v> - </type> <desc><p>The <c><![CDATA[pread/3,4]]></c> function reads from a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#read-3"><c>read/3,4</c></seealso> functions.</p> @@ -402,16 +341,9 @@ </func> <func> - <name since="">pwrite(ChannelPid, Handle, Position, Data) -> ok</name> - <name since="">pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name> + <name name="pwrite" arity="4" since=""/> + <name name="pwrite" arity="5" since=""/> <fsummary>Writes to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Data = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc><p>The <c><![CDATA[pwrite/3,4]]></c> function writes to a specified position, combining the <seealso marker="#position-3"><c>position/3</c></seealso> and <seealso marker="#write-3"><c>write/3,4</c></seealso> functions.</p> @@ -419,16 +351,9 @@ </func> <func> - <name since="">read(ChannelPid, Handle, Len) -></name> - <name since="">read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> + <name name="read" arity="3" since=""/> + <name name="read" arity="4" since=""/> <fsummary>Reads from an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Len = integer()</v> - <v>Timeout = timeout()</v> - <v>Data = string() | binary()</v> - </type> <desc> <p>Reads <c><![CDATA[Len]]></c> bytes from the file referenced by <c><![CDATA[Handle]]></c>. Returns <c><![CDATA[{ok, Data}]]></c>, <c><![CDATA[eof]]></c>, or @@ -440,32 +365,19 @@ </desc> </func> - <func> - <name since="">read_file(ChannelPid, File) -></name> - <name since="">read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name> + <func> + <name name="read_file" arity="2" since=""/> + <name name="read_file" arity="3" since=""/> <fsummary>Reads a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Data = binary()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Reads a file from the server, and returns the data in a binary.</p> </desc> </func> - <func> - <name since="">read_file_info(ChannelPid, Name) -></name> - <name since="">read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> + <func> + <name name="read_file_info" arity="2" since=""/> + <name name="read_file_info" arity="3" since=""/> <fsummary>Gets information about a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - <v>FileInfo = record()</v> - </type> <desc> <p>Returns a <c><![CDATA[file_info]]></c> record from the file system object specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>. See @@ -474,38 +386,26 @@ </p> <p> Depending on the underlying OS:es links might be followed and info on the final file, directory - etc is returned. See <seealso marker="#read_link_info-2">ssh_sftp::read_link_info/2</seealso> + etc is returned. See <seealso marker="#read_link_info-2">read_link_info/2</seealso> on how to get information on links instead. </p> </desc> </func> <func> - <name since="">read_link(ChannelPid, Name) -></name> - <name since="">read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name> + <name name="read_link" arity="2" since=""/> + <name name="read_link" arity="3" since=""/> <fsummary>Reads symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Target = string()</v> - </type> <desc> <p>Reads the link target from the symbolic link specified by <c><![CDATA[name]]></c>. </p> </desc> </func> - <func> - <name since="">read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name> - <name since="">read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> + <func> + <name since="" name="read_link_info" arity="2"/> + <name since="" name="read_link_info" arity="3"/> <fsummary>Gets information about a symbolic link.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Handle = term()</v> - <v>Timeout = timeout()</v> - <v>FileInfo = record()</v> - </type> <desc> <p>Returns a <c><![CDATA[file_info]]></c> record from the symbolic link specified by <c><![CDATA[Name]]></c> or <c><![CDATA[Handle]]></c>. @@ -517,15 +417,9 @@ </func> <func> - <name since="">rename(ChannelPid, OldName, NewName) -> </name> - <name since="">rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name> + <name since="" name="rename" arity="3"/> + <name since="" name="rename" arity="4"/> <fsummary>Renames a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>OldName = string()</v> - <v>NewName = string()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Renames a file named <c><![CDATA[OldName]]></c> and gives it the name <c><![CDATA[NewName]]></c>. @@ -535,25 +429,27 @@ <func> <name since="">start_channel(ConnectionRef) -></name> - <name since="">start_channel(ConnectionRef, Options) -> - {ok, Pid} | {error, reason()|term()}</name> + <name since="">start_channel(ConnectionRef, SftpOptions) -> + {ok, ChannelPid} | Error</name> + <name since="">start_channel(Host) -></name> <name since="">start_channel(Host, Options) -></name> - <name since="">start_channel(Host, Port, Options) -> - {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> - + <name since="">start_channel(Host, Port, Options) -></name> <name since="">start_channel(TcpSocket) -></name> <name since="">start_channel(TcpSocket, Options) -> - {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> + {ok, ChannelPid, ConnectionRef} | Error</name> <fsummary>Starts an SFTP client.</fsummary> <type> - <v>Host = string()</v> - <v>ConnectionRef = connection_ref()</v> - <v>Port = integer()</v> - <v>TcpSocket = port()</v> - <d>The socket is supposed to be from <seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect</seealso> or <seealso marker="kernel:gen_tcp#accept-1">gen_tcp:accept</seealso> with option <c>{active,false}</c></d> - <v>Options = [{Option, Value}]</v> + <v>Host = <seealso marker="ssh:ssh#type-host">ssh:host()</seealso></v> + <v>Port = <seealso marker="kernel:inet#type-port_number">inet:port_number()</seealso></v> + <v>TcpSocket = <seealso marker="ssh:ssh#type-open_socket">ssh:open_socket()</seealso></v> + <v>Options = [ <seealso marker="#type-sftp_option">sftp_option()</seealso> + | <seealso marker="ssh:ssh#type-client_option">ssh:client_option()</seealso> ]</v> + <v>SftpOptions = [ <seealso marker="#type-sftp_option">sftp_option()</seealso> ]</v> + <v>ChannelPid = pid()</v> + <v>ConnectionRef = <seealso marker="ssh:ssh#type-connection_ref">ssh:connection_ref()</seealso></v> + <v>Error = {error, <seealso marker="#type-reason">reason()</seealso>}</v> </type> <desc> <p>If no connection reference is provided, a connection is set @@ -594,11 +490,8 @@ </func> <func> - <name since="">stop_channel(ChannelPid) -> ok</name> + <name since="" name="stop_channel" arity="1"/> <fsummary>Stops the SFTP client channel.</fsummary> - <type> - <v>ChannelPid = pid()</v> - </type> <desc> <p>Stops an SFTP channel. Does not close the SSH connection. Use <seealso marker="ssh#close-1">ssh:close/1</seealso> to close it.</p> @@ -606,16 +499,9 @@ </func> <func> - <name since="">write(ChannelPid, Handle, Data) -></name> - <name since="">write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write" arity="3"/> + <name since="" name="write" arity="4"/> <fsummary>Writes to an open file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Handle = term()</v> - <v>Position = integer()</v> - <v>Data = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes <c><![CDATA[data]]></c> to the file referenced by <c><![CDATA[Handle]]></c>. The file is to be opened with <c><![CDATA[write]]></c> or <c><![CDATA[append]]></c> @@ -625,15 +511,9 @@ </func> <func> - <name since="">write_file(ChannelPid, File, Iolist) -></name> - <name since="">write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write_file" arity="3"/> + <name since="" name="write_file" arity="4"/> <fsummary>Writes a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>File = string()</v> - <v>Iolist = iolist()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes a file to the server. The file is created if it does not exist but overwritten if it exists.</p> @@ -641,15 +521,9 @@ </func> <func> - <name since="">write_file_info(ChannelPid, Name, Info) -></name> - <name since="">write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name> + <name since="" name="write_file_info" arity="3"/> + <name since="" name="write_file_info" arity="4"/> <fsummary>Writes information for a file.</fsummary> - <type> - <v>ChannelPid = pid()</v> - <v>Name = string()</v> - <v>Info = record()</v> - <v>Timeout = timeout()</v> - </type> <desc> <p>Writes file information from a <c><![CDATA[file_info]]></c> record to the file specified by <c><![CDATA[Name]]></c>. See diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml index ee72784add..0d7b340399 100644 --- a/lib/ssh/doc/src/ssh_sftpd.xml +++ b/lib/ssh/doc/src/ssh_sftpd.xml @@ -35,36 +35,23 @@ <p>Specifies a channel process to handle an SFTP subsystem.</p> </description> - <section> - <title>DATA TYPES</title> - <taglist> - <tag><c>subsystem_spec() =</c></tag> - <item><p><c>{subsystem_name(), {channel_callback(), channel_init_args()}}</c></p></item> - <tag><c>subsystem_name() =</c></tag> - <item><p><c>"sftp"</c></p></item> - <tag><c>channel_callback() =</c></tag> - <item><p><c>atom()</c> - Name of the Erlang module implementing the subsystem using the - <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (replaces ssh_daemon_channel) behaviour.</p></item> - <tag><c>channel_init_args() =</c></tag> - <item><p><c>list()</c> - The one given as argument to function <c>subsystem_spec/1</c>.</p></item> - </taglist> - </section> <funcs> <func> - <name since="">subsystem_spec(Options) -> subsystem_spec()</name> + <name name="subsystem_spec" arity="1" since=""/> <fsummary>Returns the subsystem specification that allows an SSH daemon to handle the subsystem "sftp".</fsummary> - <type> - <v>Options = [{Option, Value}]</v> - </type> <desc> <p>Is to be used together with <c>ssh:daemon/[1,2,3]</c></p> + <p>The <c>Name</c> is <c>"sftp"</c> and + <c>CbMod</c> is the name of the Erlang module implementing the subsystem using the + <seealso marker="ssh_server_channel">ssh_server_channel</seealso> (replaces ssh_daemon_channel) behaviour. + </p> <p>Options:</p> <taglist> - <tag><c><![CDATA[{cwd, String}]]></c></tag> + <tag><c>cwd</c></tag> <item> <p>Sets the initial current working directory for the server.</p> </item> - <tag><c><![CDATA[{file_handler, CallbackModule}]]></c></tag> + <tag><c>file_handler</c></tag> <item> <p>Determines which module to call for accessing the file server. The default value is <c>ssh_sftpd_file</c>, which uses the @@ -72,13 +59,13 @@ APIs to access the standard OTP file server. This option can be used to plug in other file servers.</p> </item> - <tag><c><![CDATA[{max_files, Integer}]]></c></tag> + <tag><c>max_files</c></tag> <item> <p>The default value is <c>0</c>, which means that there is no upper limit. If supplied, the number of filenames returned to the SFTP client per <c>READDIR</c> request is limited to at most the given value.</p> </item> - <tag><c><![CDATA[{root, String}]]></c></tag> + <tag><c>root</c></tag> <item> <p>Sets the SFTP root directory. Then the user cannot see any files above this root. If, for example, the root directory is set to <c>/tmp</c>, @@ -86,7 +73,7 @@ <c>cd /etc</c>, the user moves to <c>/tmp/etc</c>. </p> </item> - <tag><c><![CDATA[{sftpd_vsn, integer()}]]></c></tag> + <tag><c>sftpd_vsn</c></tag> <item> <p>Sets the SFTP version to use. Defaults to 5. Version 6 is under development and limited.</p> diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml index 4455d5ecc5..5c56dee81d 100644 --- a/lib/ssh/doc/src/using_ssh.xml +++ b/lib/ssh/doc/src/using_ssh.xml @@ -232,9 +232,10 @@ </section> <section> - <title>SFTP Client with TAR Compression and Encryption</title> - - <p>Example of writing and then reading a tar file follows:</p> + <title>SFTP Client with TAR Compression</title> + <section> + <title>Basic example</title> + <p>This is an example of writing and then reading a tar file:</p> <code type="erl"> {ok,HandleWrite} = ssh_sftp:open_tar(ChannelPid, ?tar_file_name, [write]), ok = erl_tar:add(HandleWrite, .... ), @@ -248,8 +249,12 @@ {ok,NameValueList} = erl_tar:extract(HandleRead,[memory]), ok = erl_tar:close(HandleRead), </code> + </section> - <p>The previous write and read example can be extended with encryption and decryption as follows:</p> + <section> + <title>Example with encryption</title> + <p>The previous <seealso marker="using_ssh#basic-example">Basic example</seealso> + can be extended with encryption and decryption as follows:</p> <code type="erl"> %% First three parameters depending on which crypto type we select: Key = <<"This is a 256 bit key. abcdefghi">>, @@ -297,6 +302,7 @@ Cr = {InitFun,DecryptFun}, ok = erl_tar:close(HandleRead), </code> </section> + </section> <section> <marker id="usersguide_creating_a_subsystem"/> diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index 6d64a45112..9627b70eeb 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -99,7 +99,7 @@ APP_TARGET= $(EBIN)/$(APP_FILE) APPUP_SRC= $(APPUP_FILE).src APPUP_TARGET= $(EBIN)/$(APPUP_FILE) -INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_userauth.hrl ssh_xfer.hrl +INTERNAL_HRL_FILES = ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl ssh.hrl ssh_xfer.hrl # ---------------------------------------------------- # FLAGS diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 410061cded..2193c14611 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -44,10 +44,10 @@ {env, []}, {mod, {ssh_app, []}}, {runtime_dependencies, [ - "crypto-4.2", - "erts-6.0", - "kernel-3.0", - "public_key-1.5.2", - "stdlib-3.3" + "crypto-4.5", + "erts-9.0", + "kernel-5.3", + "public_key-1.6.1", + "stdlib-3.4.1" ]}]}. diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index ff5aee14d7..32f10c797d 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -66,6 +66,8 @@ cipher_alg/0, mac_alg/0, compression_alg/0, + host/0, + open_socket/0, ip_port/0 ]). diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl index 923e9309f4..a991f72cf2 100644 --- a/lib/ssh/src/ssh.hrl +++ b/lib/ssh/src/ssh.hrl @@ -68,6 +68,25 @@ -define(string(X), ?string_utf8(X)). -define(binary(X), << ?STRING(X) >>). +-define('2bin'(X), (if is_binary(X) -> X; + is_list(X) -> list_to_binary(X); + X==undefined -> <<>> + end) ). + +%% encoding macros +-define('E...'(X), ?'2bin'(X)/binary ). +-define(Eboolean(X), ?BOOLEAN(case X of + true -> ?TRUE; + false -> ?FALSE + end) ). +-define(Ebyte(X), ?BYTE(X) ). +-define(Euint32(X), ?UINT32(X) ). +-define(Estring(X), ?STRING(?'2bin'(X)) ). +-define(Estring_utf8(X), ?string_utf8(X)/binary ). +-define(Ename_list(X), ?STRING(ssh_bits:name_list(X)) ). +-define(Empint(X), (ssh_bits:mpint(X))/binary ). +-define(Ebinary(X), ?STRING(X) ). + %% Cipher details -define(SSH_CIPHER_NONE, 0). -define(SSH_CIPHER_3DES, 3). @@ -293,7 +312,8 @@ | gen_tcp:listen_option() | ?COMMON_OPTION . --type subsystem_daemon_option() :: {subsystems, subsystem_spec()}. +-type subsystem_daemon_option() :: {subsystems, subsystem_specs()}. +-type subsystem_specs() :: [ subsystem_spec() ]. -type shell_daemon_option() :: {shell, mod_fun_args() | 'shell_fun/1'() | 'shell_fun/2'() }. -type 'shell_fun/1'() :: fun((User::string()) -> pid()) . @@ -396,11 +416,13 @@ recv_mac_size = 0, encrypt = none, %% encrypt algorithm + encrypt_cipher, %% cipher. could be different from the algorithm encrypt_keys, %% encrypt keys encrypt_block_size = 8, encrypt_ctx, decrypt = none, %% decrypt algorithm + decrypt_cipher, %% cipher. could be different from the algorithm decrypt_keys, %% decrypt keys decrypt_block_size = 8, decrypt_ctx, %% Decryption context @@ -457,14 +479,6 @@ recv_ext_info }). --record(ssh_key, - { - type, - public, - private, - comment = "" - }). - -record(ssh_pty, {term = "", % e.g. "xterm" width = 80, height = 25, @@ -472,13 +486,6 @@ pixel_height = 768, modes = <<>>}). -%% assertion macro --define(ssh_assert(Expr, Reason), - case Expr of - true -> ok; - _ -> exit(Reason) - end). - %% dbg help macros -define(wr_record(N,BlackList), diff --git a/lib/ssh/src/ssh_client_channel.erl b/lib/ssh/src/ssh_client_channel.erl index f985d8e273..3bd1e1fdf1 100644 --- a/lib/ssh/src/ssh_client_channel.erl +++ b/lib/ssh/src/ssh_client_channel.erl @@ -52,7 +52,7 @@ -callback handle_msg(Msg ::term(), State :: term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, +-callback handle_ssh_msg(ssh_connection:event(), State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl index 9a060b8304..d6b50613f9 100644 --- a/lib/ssh/src/ssh_connect.hrl +++ b/lib/ssh/src/ssh_connect.hrl @@ -263,11 +263,8 @@ -record(connection, { requests = [], %% [{ChannelId, Pid}...] awaiting reply on request, channel_cache, - port_bindings, channel_id_seed, cli_spec, - address, - port, options, exec, system_supervisor, diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl index 83f85b1d8e..c5316bf133 100644 --- a/lib/ssh/src/ssh_connection.erl +++ b/lib/ssh/src/ssh_connection.erl @@ -60,13 +60,121 @@ request_failure_msg/0, request_success_msg/1, - bind/4, unbind/3, unbind_channel/2, - bound_channel/3, encode_ip/1 + encode_ip/1 ]). -type connection_ref() :: ssh:connection_ref(). -type channel_id() :: ssh:channel_id(). +-type req_status() :: success | failure . +-type reason() :: closed | timeout . + +-type result() :: req_status() | {error, reason()} . + +-type ssh_data_type_code() :: non_neg_integer(). % Only 0 and 1 are used + + +%%% The SSH Connection Protocol + +-export_type([event/0, + channel_msg/0, + want_reply/0, + data_ch_msg/0, + eof_ch_msg/0, + signal_ch_msg/0, + exit_signal_ch_msg/0, + exit_status_ch_msg/0, + closed_ch_msg/0, + env_ch_msg/0, + pty_ch_msg/0, + shell_ch_msg/0, + window_change_ch_msg/0, + exec_ch_msg/0 + ]). + +-type event() :: {ssh_cm, ssh:connection_ref(), channel_msg()}. +-type channel_msg() :: data_ch_msg() + | eof_ch_msg() + | closed_ch_msg() + | pty_ch_msg() + | env_ch_msg() + | shell_ch_msg() + | exec_ch_msg() + | signal_ch_msg() + | window_change_ch_msg() + | exit_status_ch_msg() + | exit_signal_ch_msg() + . + +-type want_reply() :: boolean(). + +-type data_ch_msg() :: {data, + ssh:channel_id(), + ssh_data_type_code(), + Data :: binary() + } . +-type eof_ch_msg() :: {eof, + ssh:channel_id() + } . +-type signal_ch_msg() :: {signal, + ssh:channel_id(), + SignalName :: string() + } . +-type exit_signal_ch_msg() :: {exit_signal, ssh:channel_id(), + ExitSignal :: string(), + ErrorMsg :: string(), + LanguageString :: string()} . +-type exit_status_ch_msg() :: {exit_status, + ssh:channel_id(), + ExitStatus :: non_neg_integer() + } . +-type closed_ch_msg() :: {closed, + ssh:channel_id() + } . +-type env_ch_msg() :: {env, + ssh:channel_id(), + want_reply(), + Var :: string(), + Value :: string() + } . +-type pty_ch_msg() :: {pty, + ssh:channel_id(), + want_reply(), + {Terminal :: string(), + CharWidth :: non_neg_integer(), + RowHeight :: non_neg_integer(), + PixelWidth :: non_neg_integer(), + PixelHeight :: non_neg_integer(), + TerminalModes :: [term_mode()] + } + } . + +-type term_mode() :: {Opcode :: atom() | byte(), + Value :: non_neg_integer()} . + +-type shell_ch_msg() :: {shell, + ssh:channel_id(), + want_reply() + } . +-type window_change_ch_msg() :: {window_change, + ssh:channel_id(), + CharWidth :: non_neg_integer(), + RowHeight :: non_neg_integer(), + PixelWidth :: non_neg_integer(), + PixelHeight :: non_neg_integer() + } . +-type exec_ch_msg() :: {exec, + ssh:channel_id(), + want_reply(), + Command :: string() + } . + +%%% This function is soley to convince all +%%% checks that the type event() exists... +-export([dummy/1]). +-spec dummy(event()) -> false. +dummy(_) -> false. + %%-------------------------------------------------------------------- %%% API %%-------------------------------------------------------------------- @@ -77,14 +185,21 @@ %% application, a system command, or some built-in subsystem. %% -------------------------------------------------------------------- --spec session_channel(connection_ref(), timeout()) -> - {ok, channel_id()} | {error, timeout | closed}. +-spec session_channel(ConnectionRef, Timeout) -> Result when + ConnectionRef :: ssh:connection_ref(), + Timeout :: timeout(), + Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, Timeout) -> session_channel(ConnectionHandler, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout). --spec session_channel(connection_ref(), integer(), integer(), timeout()) -> - {ok, channel_id()} | {error, timeout | closed}. + +-spec session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> Result when + ConnectionRef :: ssh:connection_ref(), + InitialWindowSize :: pos_integer(), + MaxPacketSize :: pos_integer(), + Timeout :: timeout(), + Result :: {ok, ssh:channel_id()} | {error, reason()} . session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> case ssh_connection_handler:open_channel(ConnectionHandler, "session", <<>>, @@ -100,8 +215,11 @@ session_channel(ConnectionHandler, InitialWindowSize, MaxPacketSize, Timeout) -> %% Description: Will request that the server start the %% execution of the given command. %%-------------------------------------------------------------------- --spec exec(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec exec(ConnectionRef, ChannelId, Command, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Command :: string(), + Timeout :: timeout(). exec(ConnectionHandler, ChannelId, Command, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, "exec", @@ -112,8 +230,10 @@ exec(ConnectionHandler, ChannelId, Command, TimeOut) -> %% defined in /etc/passwd in UNIX systems) be started at the other %% end. %%-------------------------------------------------------------------- --spec shell(connection_ref(), channel_id()) -> - ok | success | failure | {error, timeout}. +-spec shell(ConnectionRef, ChannelId) -> Result when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Result :: ok | success | failure | {error, timeout} . shell(ConnectionHandler, ChannelId) -> ssh_connection_handler:request(ConnectionHandler, self(), ChannelId, @@ -122,8 +242,11 @@ shell(ConnectionHandler, ChannelId) -> %% %% Description: Executes a predefined subsystem. %%-------------------------------------------------------------------- --spec subsystem(connection_ref(), channel_id(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Subsystem :: string(), + Timeout :: timeout(). subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> ssh_connection_handler:request(ConnectionHandler, self(), @@ -134,12 +257,13 @@ subsystem(ConnectionHandler, ChannelId, SubSystem, TimeOut) -> %%-------------------------------------------------------------------- -spec send(connection_ref(), channel_id(), iodata()) -> ok | {error, timeout | closed}. + send(ConnectionHandler, ChannelId, Data) -> send(ConnectionHandler, ChannelId, 0, Data, infinity). --spec send(connection_ref(), channel_id(), integer()| iodata(), timeout() | iodata()) -> - ok | {error, timeout | closed}. +-spec send(connection_ref(), channel_id(), iodata(), timeout()) -> ok | {error, reason()}; + (connection_ref(), channel_id(), ssh_data_type_code(), iodata()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Data, TimeOut) when is_integer(TimeOut) -> send(ConnectionHandler, ChannelId, 0, Data, TimeOut); @@ -151,14 +275,15 @@ send(ConnectionHandler, ChannelId, Type, Data) -> send(ConnectionHandler, ChannelId, Type, Data, infinity). --spec send(connection_ref(), channel_id(), integer(), iodata(), timeout()) -> - ok | {error, timeout | closed}. +-spec send(connection_ref(), channel_id(), ssh_data_type_code(), iodata(), timeout()) -> ok | {error, reason()}. send(ConnectionHandler, ChannelId, Type, Data, TimeOut) -> ssh_connection_handler:send(ConnectionHandler, ChannelId, Type, Data, TimeOut). %%-------------------------------------------------------------------- --spec send_eof(connection_ref(), channel_id()) -> ok | {error, closed}. +-spec send_eof(ConnectionRef, ChannelId) -> ok | {error, closed} when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Sends eof on the channel <ChannelId>. @@ -167,7 +292,10 @@ send_eof(ConnectionHandler, Channel) -> ssh_connection_handler:send_eof(ConnectionHandler, Channel). %%-------------------------------------------------------------------- --spec adjust_window(connection_ref(), channel_id(), integer()) -> ok. +-spec adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + NumOfBytes :: integer(). %% %% %% Description: Adjusts the ssh flowcontrol window. @@ -176,8 +304,12 @@ adjust_window(ConnectionHandler, Channel, Bytes) -> ssh_connection_handler:adjust_window(ConnectionHandler, Channel, Bytes). %%-------------------------------------------------------------------- --spec setenv(connection_ref(), channel_id(), string(), string(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Var :: string(), + Value :: string(), + Timeout :: timeout(). %% %% %% Description: Environment variables may be passed to the shell/command to be @@ -189,7 +321,9 @@ setenv(ConnectionHandler, ChannelId, Var, Value, TimeOut) -> %%-------------------------------------------------------------------- --spec close(connection_ref(), channel_id()) -> ok. +-spec close(ConnectionRef, ChannelId) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Sends a close message on the channel <ChannelId>. @@ -198,7 +332,11 @@ close(ConnectionHandler, ChannelId) -> ssh_connection_handler:close(ConnectionHandler, ChannelId). %%-------------------------------------------------------------------- --spec reply_request(connection_ref(), boolean(), success | failure, channel_id()) -> ok. +-spec reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok when + ConnectionRef :: ssh:connection_ref(), + WantReply :: boolean(), + Status :: req_status(), + ChannelId :: ssh:channel_id(). %% %% %% Description: Send status replies to requests that want such replies. @@ -211,15 +349,20 @@ reply_request(_,false, _, _) -> %%-------------------------------------------------------------------- %% Description: Sends a ssh connection protocol pty_req. %%-------------------------------------------------------------------- --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist()) -> - success | failure | {error, timeout}. +-spec ptty_alloc(ConnectionRef, ChannelId, Options) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Options :: proplists:proplist(). ptty_alloc(ConnectionHandler, Channel, Options) -> ptty_alloc(ConnectionHandler, Channel, Options, infinity). --spec ptty_alloc(connection_ref(), channel_id(), proplists:proplist(), timeout()) -> - success | failure | {error, timeout | closed}. +-spec ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> result() when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Options :: proplists:proplist(), + Timeout :: timeout(). ptty_alloc(ConnectionHandler, Channel, Options0, TimeOut) -> TermData = backwards_compatible(Options0, []), % FIXME @@ -252,6 +395,10 @@ signal(ConnectionHandler, Channel, Sig) -> "signal", false, [?string(Sig)], 0). +-spec exit_status(ConnectionRef, ChannelId, Status) -> ok when + ConnectionRef :: ssh:connection_ref(), + ChannelId :: ssh:channel_id(), + Status :: integer(). exit_status(ConnectionHandler, Channel, Status) -> ssh_connection_handler:request(ConnectionHandler, Channel, "exit-status", false, [?uint32(Status)], 0). @@ -713,29 +860,6 @@ request_success_msg(Data) -> %%%---------------------------------------------------------------- %%% %%% -bind(IP, Port, ChannelPid, Connection) -> - Binds = [{{IP, Port}, ChannelPid} - | lists:keydelete({IP, Port}, 1, - Connection#connection.port_bindings)], - Connection#connection{port_bindings = Binds}. - -unbind(IP, Port, Connection) -> - Connection#connection{ - port_bindings = - lists:keydelete({IP, Port}, 1, - Connection#connection.port_bindings)}. -unbind_channel(ChannelPid, Connection) -> - Binds = [{Bind, ChannelP} || {Bind, ChannelP} - <- Connection#connection.port_bindings, - ChannelP =/= ChannelPid], - Connection#connection{port_bindings = Binds}. - -bound_channel(IP, Port, Connection) -> - case lists:keysearch({IP, Port}, 1, Connection#connection.port_bindings) of - {value, {{IP, Port}, ChannelPid}} -> ChannelPid; - _ -> undefined - end. - encode_ip(Addr) when is_tuple(Addr) -> case catch inet_parse:ntoa(Addr) of {'EXIT',_} -> false; diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index 8f32966a12..e984cbb21b 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -386,16 +386,24 @@ init_connection_handler(Role, Socket, Opts) -> D); {stop, Error} -> - Sups = ?GET_INTERNAL_OPT(supervisors, Opts), - C = #connection{system_supervisor = proplists:get_value(system_sup, Sups), - sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), - connection_supervisor = proplists:get_value(connection_sup, Sups) - }, + D = try + %% Only servers have supervisorts defined in Opts + Sups = ?GET_INTERNAL_OPT(supervisors, Opts), + #connection{system_supervisor = proplists:get_value(system_sup, Sups), + sub_system_supervisor = proplists:get_value(subsystem_sup, Sups), + connection_supervisor = proplists:get_value(connection_sup, Sups) + } + of + C -> + #data{connection_state=C} + catch + _:_ -> + #data{connection_state=#connection{}} + end, gen_statem:enter_loop(?MODULE, [], {init_error,Error}, - #data{connection_state=C, - socket=Socket}) + D#data{socket=Socket}) end. @@ -406,7 +414,6 @@ init([Role,Socket,Opts]) -> {Protocol, Callback, CloseTag} = ?GET_OPT(transport, Opts), C = #connection{channel_cache = ssh_client_channel:cache_create(), channel_id_seed = 0, - port_bindings = [], requests = [], options = Opts}, D0 = #data{starter = ?GET_INTERNAL_OPT(user_pid, Opts), @@ -1550,7 +1557,7 @@ terminate({shutdown,"Connection closed"}, _StateName, D) -> terminate({shutdown,{init,Reason}}, StateName, D) -> %% Error in initiation. "This error should not occur". - log(error, D, io_lib:format("Shutdown in init (StateName=~p): ~p~n",[StateName,Reason])), + log(error, D, "Shutdown in init (StateName=~p): ~p~n", [StateName,Reason]), stop_subsystem(D), close_transport(D); @@ -1952,12 +1959,12 @@ send_disconnect(Code, Reason, DetailedText, Module, Line, StateName, D0) -> call_disconnectfun_and_log_cond(LogMsg, DetailedText, Module, Line, StateName, D) -> case disconnect_fun(LogMsg, D) of void -> - log(info, D, - io_lib:format("~s~n" - "State = ~p~n" - "Module = ~p, Line = ~p.~n" - "Details:~n ~s~n", - [LogMsg, StateName, Module, Line, DetailedText])); + log(info, D, + "~s~n" + "State = ~p~n" + "Module = ~p, Line = ~p.~n" + "Details:~n ~s~n", + [LogMsg, StateName, Module, Line, DetailedText]); _ -> ok end. @@ -2021,6 +2028,9 @@ fold_keys(Keys, Fun, Extra) -> end, [], Keys). %%%---------------------------------------------------------------- +log(Tag, D, Format, Args) -> + log(Tag, D, io_lib:format(Format,Args)). + log(Tag, D, Reason) -> case atom_to_list(Tag) of % Dialyzer-technical reasons... "error" -> do_log(error_msg, Reason, D); @@ -2028,36 +2038,56 @@ log(Tag, D, Reason) -> "info" -> do_log(info_msg, Reason, D) end. -do_log(F, Reason, #data{ssh_params = #ssh{role = Role} = S - }) -> - VSN = - case application:get_key(ssh,vsn) of - {ok,Vsn} -> Vsn; - undefined -> "" - end, - PeerVersion = - case Role of - server -> S#ssh.c_version; - client -> S#ssh.s_version - end, - CryptoInfo = - try - [{_,_,CI}] = crypto:info_lib(), - <<"(",CI/binary,")">> + +do_log(F, Reason0, #data{ssh_params = S}) -> + Reason = + try io_lib:format("~s",[Reason0]) + of _ -> Reason0 catch - _:_ -> "" - end, - Other = - case Role of - server -> "Client"; - client -> "Server" + _:_ -> io_lib:format("~p",[Reason0]) end, - error_logger:F("Erlang SSH ~p ~s ~s.~n" - "~s: ~p~n" - "~s~n", - [Role, VSN, CryptoInfo, - Other, PeerVersion, - Reason]). + case S of + #ssh{role = Role} when Role==server ; + Role==client -> + {PeerRole,PeerVersion} = + case Role of + server -> {"Client", S#ssh.c_version}; + client -> {"Server", S#ssh.s_version} + end, + error_logger:F("Erlang SSH ~p ~s ~s.~n" + "~s: ~p~n" + "~s~n", + [Role, + ssh_log_version(), crypto_log_info(), + PeerRole, PeerVersion, + Reason]); + _ -> + error_logger:F("Erlang SSH ~s ~s.~n" + "~s~n", + [ssh_log_version(), crypto_log_info(), + Reason]) + end. + +crypto_log_info() -> + try + [{_,_,CI}] = crypto:info_lib(), + case crypto:info_fips() of + enabled -> + <<"(",CI/binary,". FIPS enabled)">>; + not_enabled -> + <<"(",CI/binary,". FIPS available but not enabled)">>; + _ -> + <<"(",CI/binary,")">> + end + catch + _:_ -> "" + end. + +ssh_log_version() -> + case application:get_key(ssh,vsn) of + {ok,Vsn} -> Vsn; + undefined -> "" + end. %%%---------------------------------------------------------------- not_connected_filter({connection_reply, _Data}) -> true; diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index d95e58c1bb..7c86a81108 100644 --- a/lib/ssh/src/ssh_message.erl +++ b/lib/ssh/src/ssh_message.erl @@ -34,24 +34,6 @@ -export([dbg_trace/3]). --define('2bin'(X), (if is_binary(X) -> X; - is_list(X) -> list_to_binary(X); - X==undefined -> <<>> - end) ). - --define('E...'(X), ?'2bin'(X)/binary ). --define(Eboolean(X), ?BOOLEAN(case X of - true -> ?TRUE; - false -> ?FALSE - end) ). --define(Ebyte(X), ?BYTE(X) ). --define(Euint32(X), ?UINT32(X) ). --define(Estring(X), ?STRING(?'2bin'(X)) ). --define(Estring_utf8(X), ?string_utf8(X)/binary ). --define(Ename_list(X), ?STRING(ssh_bits:name_list(X)) ). --define(Empint(X), (ssh_bits:mpint(X))/binary ). --define(Ebinary(X), ?STRING(X) ). - ucl(B) -> try unicode:characters_to_list(B) of L when is_list(L) -> L; diff --git a/lib/ssh/src/ssh_server_channel.erl b/lib/ssh/src/ssh_server_channel.erl index 555080e9ee..1905c40c98 100644 --- a/lib/ssh/src/ssh_server_channel.erl +++ b/lib/ssh/src/ssh_server_channel.erl @@ -37,7 +37,7 @@ -callback handle_msg(Msg ::term(), State :: term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. --callback handle_ssh_msg({ssh_cm, ConnectionRef::ssh:connection_ref(), SshMsg::term()}, +-callback handle_ssh_msg(ssh_connection:event(), State::term()) -> {ok, State::term()} | {stop, ChannelId::ssh:channel_id(), State::term()}. diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl index 1b2ba5a50b..4b6e187c3a 100644 --- a/lib/ssh/src/ssh_sftp.erl +++ b/lib/ssh/src/ssh_sftp.erl @@ -92,24 +92,63 @@ -define(XF(S), S#state.xf). -define(REQID(S), S#state.req_id). +-type sftp_option() :: {timeout, timeout()} + | {sftp_vsn, pos_integer()} + | {window_size, pos_integer()} + | {packet_size, pos_integer()} . + +-type reason() :: atom() | string() | tuple() . + %%==================================================================== %% API %%==================================================================== + + +%%%================================================================ +%%% + +%%%---------------------------------------------------------------- +%%% start_channel/1 + start_channel(Cm) when is_pid(Cm) -> start_channel(Cm, []); + start_channel(Socket) when is_port(Socket) -> start_channel(Socket, []); -start_channel(Host) when is_list(Host) -> + +start_channel(Host) -> start_channel(Host, []). + +%%%---------------------------------------------------------------- +%%% start_channel/2 + +%%% -spec:s are as if Dialyzer handled signatures for separate +%%% function clauses. + +-spec start_channel(ssh:open_socket(), + [ssh:client_options() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()}; + + (ssh:connection_ref(), + [sftp_option()] + ) + -> {ok,pid()} | {ok,pid(),ssh:connection_ref()} | {error,reason()}; + + (ssh:host(), + [ssh:client_options() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()} . + start_channel(Socket, UserOptions) when is_port(Socket) -> - {SshOpts, _ChanOpts, SftpOpts} = handle_options(UserOptions), + {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: proplists:get_value(connect_timeout, SshOpts, proplists:get_value(timeout, SftpOpts, infinity)), case ssh:connect(Socket, SshOpts, Timeout) of {ok,Cm} -> - case start_channel(Cm, UserOptions) of + case start_channel(Cm, ChanOpts ++ SftpOpts) of {ok, Pid} -> {ok, Pid, Cm}; Error -> @@ -144,6 +183,16 @@ start_channel(Cm, UserOptions) when is_pid(Cm) -> start_channel(Host, UserOptions) -> start_channel(Host, 22, UserOptions). + +%%%---------------------------------------------------------------- +%%% start_channel/3 + +-spec start_channel(ssh:host(), + inet:port_number(), + [ssh:client_option() | sftp_option()] + ) + -> {ok,pid(),ssh:connection_ref()} | {error,reason()}. + start_channel(Host, Port, UserOptions) -> {SshOpts, ChanOpts, SftpOpts} = handle_options(UserOptions), Timeout = % A mixture of ssh:connect and ssh_sftp:start_channel: @@ -168,6 +217,15 @@ start_channel(Host, Port, UserOptions) -> Error end. +%%% Helper for start_channel + +wait_for_version_negotiation(Pid, Timeout) -> + call(Pid, wait_for_version_negotiation, Timeout). + +%%%---------------------------------------------------------------- +-spec stop_channel(ChannelPid) -> ok when + ChannelPid :: pid(). + stop_channel(Pid) -> case is_process_alive(Pid) of true -> @@ -185,20 +243,63 @@ stop_channel(Pid) -> ok end. -wait_for_version_negotiation(Pid, Timeout) -> - call(Pid, wait_for_version_negotiation, Timeout). - +%%%---------------------------------------------------------------- +-spec open(ChannelPid, Name, Mode) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Name :: string(), + Mode :: [read | write | append | binary | raw], + Handle :: term(), + Error :: {error, reason()} . open(Pid, File, Mode) -> open(Pid, File, Mode, ?FILEOP_TIMEOUT). +-spec open(ChannelPid, Name, Mode, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Name :: string(), + Mode :: [read | write | append | binary | raw], + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . open(Pid, File, Mode, FileOpTimeout) -> call(Pid, {open, false, File, Mode}, FileOpTimeout). + +-type tar_crypto_spec() :: encrypt_spec() | decrypt_spec() . + +-type encrypt_spec() :: {init_fun(), crypto_fun(), final_fun()} . +-type decrypt_spec() :: {init_fun(), crypto_fun()} . + +-type init_fun() :: fun(() -> {ok,crypto_state()}) + | fun(() -> {ok,crypto_state(),chunk_size()}) . + +-type crypto_fun() :: fun((TextIn::binary(), crypto_state()) -> crypto_result()) . +-type crypto_result() :: {ok,TextOut::binary(),crypto_state()} + | {ok,TextOut::binary(),crypto_state(),chunk_size()} . + +-type final_fun() :: fun((FinalTextIn::binary(),crypto_state()) -> {ok,FinalTextOut::binary()}) . + +-type chunk_size() :: undefined | pos_integer(). +-type crypto_state() :: any() . + + +-spec open_tar(ChannelPid, Path, Mode) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Mode :: [read | write | {crypto, tar_crypto_spec()} ], + Handle :: term(), + Error :: {error, reason()} . open_tar(Pid, File, Mode) -> open_tar(Pid, File, Mode, ?FILEOP_TIMEOUT). +-spec open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Mode :: [read | write | {crypto, tar_crypto_spec()} ], + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . open_tar(Pid, File, Mode, FileOpTimeout) -> case {lists:member(write,Mode), lists:member(read,Mode), - Mode -- [read,write]} of + Mode -- [write,read]} of {true,false,[]} -> {ok,Handle} = open(Pid, File, [write], FileOpTimeout), erl_tar:init(Pid, write, @@ -264,13 +365,33 @@ open_tar(Pid, File, Mode, FileOpTimeout) -> end. +-spec opendir(ChannelPid, Path) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Handle :: term(), + Error :: {error, reason()} . opendir(Pid, Path) -> opendir(Pid, Path, ?FILEOP_TIMEOUT). +-spec opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | Error when + ChannelPid :: pid(), + Path :: string(), + Timeout :: timeout(), + Handle :: term(), + Error :: {error, reason()} . opendir(Pid, Path, FileOpTimeout) -> call(Pid, {opendir, false, Path}, FileOpTimeout). +-spec close(ChannelPid, Handle) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Error :: {error, reason()} . close(Pid, Handle) -> close(Pid, Handle, ?FILEOP_TIMEOUT). +-spec close(ChannelPid, Handle, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Timeout :: timeout(), + Error :: {error, reason()} . close(Pid, Handle, FileOpTimeout) -> call(Pid, {close,false,Handle}, FileOpTimeout). @@ -279,47 +400,149 @@ readdir(Pid,Handle) -> readdir(Pid,Handle, FileOpTimeout) -> call(Pid, {readdir,false,Handle}, FileOpTimeout). +-spec pread(ChannelPid, Handle, Position, Len) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Data :: string() | binary(), + Error :: {error, reason()}. pread(Pid, Handle, Offset, Len) -> pread(Pid, Handle, Offset, Len, ?FILEOP_TIMEOUT). + +-spec pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Timeout :: timeout(), + Data :: string() | binary(), + Error :: {error, reason()}. pread(Pid, Handle, Offset, Len, FileOpTimeout) -> call(Pid, {pread,false,Handle, Offset, Len}, FileOpTimeout). + +-spec read(ChannelPid, Handle, Len) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Data :: string() | binary(), + Error :: {error, reason()}. read(Pid, Handle, Len) -> read(Pid, Handle, Len, ?FILEOP_TIMEOUT). + +-spec read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Timeout :: timeout(), + Data :: string() | binary(), + Error :: {error, reason()}. read(Pid, Handle, Len, FileOpTimeout) -> call(Pid, {read,false,Handle, Len}, FileOpTimeout). + %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec apread(ChannelPid, Handle, Position, Len) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Len :: integer(), + Error :: {error, reason()}, + N :: term() . apread(Pid, Handle, Offset, Len) -> call(Pid, {pread,true,Handle, Offset, Len}, infinity). %% TODO this ought to be a cast! +-spec aread(ChannelPid, Handle, Len) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Len :: integer(), + Error :: {error, reason()}, + N :: term() . aread(Pid, Handle, Len) -> call(Pid, {read,true,Handle, Len}, infinity). + +-spec pwrite(ChannelPid, Handle, Position, Data) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: iolist(), + Error :: {error, reason()}. pwrite(Pid, Handle, Offset, Data) -> pwrite(Pid, Handle, Offset, Data, ?FILEOP_TIMEOUT). + +-spec pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: iolist(), + Timeout :: timeout(), + Error :: {error, reason()}. pwrite(Pid, Handle, Offset, Data, FileOpTimeout) -> call(Pid, {pwrite,false,Handle,Offset,Data}, FileOpTimeout). + +-spec write(ChannelPid, Handle, Data) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: iodata(), + Error :: {error, reason()}. write(Pid, Handle, Data) -> write(Pid, Handle, Data, ?FILEOP_TIMEOUT). + +-spec write(ChannelPid, Handle, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: iodata(), + Timeout :: timeout(), + Error :: {error, reason()}. write(Pid, Handle, Data, FileOpTimeout) -> call(Pid, {write,false,Handle,Data}, FileOpTimeout). %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Position :: integer(), + Data :: binary(), + Error :: {error, reason()}, + N :: term() . apwrite(Pid, Handle, Offset, Data) -> call(Pid, {pwrite,true,Handle,Offset,Data}, infinity). %% TODO this ought to be a cast! Is so in all practical meaning %% even if it is obscure! +-spec awrite(ChannelPid, Handle, Data) -> {async, N} | Error when + ChannelPid :: pid(), + Handle :: term(), + Data :: binary(), + Error :: {error, reason()}, + N :: term() . awrite(Pid, Handle, Data) -> call(Pid, {write,true,Handle,Data}, infinity). +-spec position(ChannelPid, Handle, Location) -> {ok, NewPosition} | Error when + ChannelPid :: pid(), + Handle :: term(), + Location :: Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof, + Offset :: integer(), + NewPosition :: integer(), + Error :: {error, reason()}. position(Pid, Handle, Pos) -> position(Pid, Handle, Pos, ?FILEOP_TIMEOUT). + +-spec position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition} | Error when + ChannelPid :: pid(), + Handle :: term(), + Location :: Offset | {bof, Offset} | {cur, Offset} | {eof, Offset} | bof | cur | eof, + Timeout :: timeout(), + Offset :: integer(), + NewPosition :: integer(), + Error :: {error, reason()}. position(Pid, Handle, Pos, FileOpTimeout) -> call(Pid, {position, Handle, Pos}, FileOpTimeout). @@ -328,8 +551,21 @@ real_path(Pid, Path) -> real_path(Pid, Path, FileOpTimeout) -> call(Pid, {real_path, false, Path}, FileOpTimeout). + +-spec read_file_info(ChannelPid, Name) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_file_info(Pid, Name) -> read_file_info(Pid, Name, ?FILEOP_TIMEOUT). + +-spec read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_file_info(Pid, Name, FileOpTimeout) -> call(Pid, {read_file_info,false,Name}, FileOpTimeout). @@ -338,18 +574,57 @@ get_file_info(Pid, Handle) -> get_file_info(Pid, Handle, FileOpTimeout) -> call(Pid, {get_file_info,false,Handle}, FileOpTimeout). + +-spec write_file_info(ChannelPid, Name, FileInfo) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. write_file_info(Pid, Name, Info) -> write_file_info(Pid, Name, Info, ?FILEOP_TIMEOUT). + +-spec write_file_info(ChannelPid, Name, FileInfo, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Timeout :: timeout(), + Error :: {error, reason()}. write_file_info(Pid, Name, Info, FileOpTimeout) -> call(Pid, {write_file_info,false,Name, Info}, FileOpTimeout). + +-spec read_link_info(ChannelPid, Name) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Error :: {error, reason()}. read_link_info(Pid, Name) -> read_link_info(Pid, Name, ?FILEOP_TIMEOUT). + +-spec read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | Error when + ChannelPid :: pid(), + Name :: string(), + FileInfo :: file:file_info(), + Timeout :: timeout(), + Error :: {error, reason()}. read_link_info(Pid, Name, FileOpTimeout) -> call(Pid, {read_link_info,false,Name}, FileOpTimeout). + +-spec read_link(ChannelPid, Name) -> {ok, Target} | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Error :: {error, reason()}. read_link(Pid, LinkName) -> read_link(Pid, LinkName, ?FILEOP_TIMEOUT). + +-spec read_link(ChannelPid, Name, Timeout) -> {ok, Target} | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Timeout :: timeout(), + Error :: {error, reason()}. read_link(Pid, LinkName, FileOpTimeout) -> case call(Pid, {read_link,false,LinkName}, FileOpTimeout) of {ok, [{Name, _Attrs}]} -> @@ -358,28 +633,79 @@ read_link(Pid, LinkName, FileOpTimeout) -> ErrMsg end. +-spec make_symlink(ChannelPid, Name, Target) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Error :: {error, reason()} . make_symlink(Pid, Name, Target) -> make_symlink(Pid, Name, Target, ?FILEOP_TIMEOUT). +-spec make_symlink(ChannelPid, Name, Target, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Target :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . make_symlink(Pid, Name, Target, FileOpTimeout) -> call(Pid, {make_symlink,false, Name, Target}, FileOpTimeout). + +-spec rename(ChannelPid, OldName, NewName) -> ok | Error when + ChannelPid :: pid(), + OldName :: string(), + NewName :: string(), + Error :: {error, reason()}. rename(Pid, FromFile, ToFile) -> rename(Pid, FromFile, ToFile, ?FILEOP_TIMEOUT). + +-spec rename(ChannelPid, OldName, NewName, Timeout) -> ok | Error when + ChannelPid :: pid(), + OldName :: string(), + NewName :: string(), + Timeout :: timeout(), + Error :: {error, reason()}. rename(Pid, FromFile, ToFile, FileOpTimeout) -> call(Pid, {rename,false,FromFile, ToFile}, FileOpTimeout). +-spec delete(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . delete(Pid, Name) -> delete(Pid, Name, ?FILEOP_TIMEOUT). +-spec delete(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . delete(Pid, Name, FileOpTimeout) -> call(Pid, {delete,false,Name}, FileOpTimeout). +-spec make_dir(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . make_dir(Pid, Name) -> make_dir(Pid, Name, ?FILEOP_TIMEOUT). +-spec make_dir(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . make_dir(Pid, Name, FileOpTimeout) -> call(Pid, {make_dir,false,Name}, FileOpTimeout). +-spec del_dir(ChannelPid, Name) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Error :: {error, reason()} . del_dir(Pid, Name) -> del_dir(Pid, Name, ?FILEOP_TIMEOUT). +-spec del_dir(ChannelPid, Name, Timeout) -> ok | Error when + ChannelPid :: pid(), + Name :: string(), + Timeout :: timeout(), + Error :: {error, reason()} . del_dir(Pid, Name, FileOpTimeout) -> call(Pid, {del_dir,false,Name}, FileOpTimeout). @@ -396,9 +722,21 @@ recv_window(Pid, FileOpTimeout) -> call(Pid, recv_window, FileOpTimeout). +-spec list_dir(ChannelPid, Path) -> {ok,FileNames} | Error when + ChannelPid :: pid(), + Path :: string(), + FileNames :: [FileName], + FileName :: string(), + Error :: {error, reason()} . list_dir(Pid, Name) -> list_dir(Pid, Name, ?FILEOP_TIMEOUT). - +-spec list_dir(ChannelPid, Path, Timeout) -> {ok,FileNames} | Error when + ChannelPid :: pid(), + Path :: string(), + Timeout :: timeout(), + FileNames :: [FileName], + FileName :: string(), + Error :: {error, reason()} . list_dir(Pid, Name, FileOpTimeout) -> case opendir(Pid, Name, FileOpTimeout) of {ok,Handle} -> @@ -429,9 +767,20 @@ do_list_dir(Pid, Handle, FileOpTimeout, Acc) -> end. +-spec read_file(ChannelPid, File) -> {ok, Data} | Error when + ChannelPid :: pid(), + File :: string(), + Data :: binary(), + Error :: {error, reason()}. read_file(Pid, Name) -> read_file(Pid, Name, ?FILEOP_TIMEOUT). +-spec read_file(ChannelPid, File, Timeout) -> {ok, Data} | Error when + ChannelPid :: pid(), + File :: string(), + Data :: binary(), + Timeout :: timeout(), + Error :: {error, reason()}. read_file(Pid, Name, FileOpTimeout) -> case open(Pid, Name, [read, binary], FileOpTimeout) of {ok, Handle} -> @@ -453,9 +802,20 @@ read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, Acc) -> Error end. +-spec write_file(ChannelPid, File, Data) -> ok | Error when + ChannelPid :: pid(), + File :: string(), + Data :: iodata(), + Error :: {error, reason()}. write_file(Pid, Name, List) -> write_file(Pid, Name, List, ?FILEOP_TIMEOUT). +-spec write_file(ChannelPid, File, Data, Timeout) -> ok | Error when + ChannelPid :: pid(), + File :: string(), + Data :: iodata(), + Timeout :: timeout(), + Error :: {error, reason()}. write_file(Pid, Name, List, FileOpTimeout) when is_list(List) -> write_file(Pid, Name, list_to_binary(List), FileOpTimeout); write_file(Pid, Name, Bin, FileOpTimeout) -> diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index aa9ba0f9bb..bf921f0ff3 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -58,7 +58,17 @@ %%==================================================================== %% API %%==================================================================== --spec subsystem_spec(list()) -> subsystem_spec(). +-spec subsystem_spec(Options) -> Spec when + Options :: [ {cwd, string()} | + {file_handler, CallbackModule::string()} | + {max_files, integer()} | + {root, string()} | + {sftpd_vsn, integer()} + ], + Spec :: {Name, {CbMod,Options}}, + Name :: string(), + CbMod :: atom() . + subsystem_spec(Options) -> {"sftp", {?MODULE, Options}}. @@ -508,7 +518,7 @@ close_our_file({_,Fd}, FileMod, FS0) -> FS1. %%% stat: do the stat -stat(Vsn, ReqId, Data, State, F) -> +stat(_Vsn, ReqId, Data, State, F) -> <<?UINT32(BLen), BPath:BLen/binary, _/binary>> = Data, stat(ReqId, unicode:characters_to_list(BPath), State, F). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 9ff20454cd..a85926354e 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -61,14 +61,6 @@ -export([pack/3, adjust_algs_for_peer_version/2]). -export([decompress/2, decrypt_blocks/3, is_valid_mac/3 ]). % FIXME: remove --define(Estring(X), ?STRING((if is_binary(X) -> X; - is_list(X) -> list_to_binary(X); - X==undefined -> <<>> - end))). --define(Empint(X), (ssh_bits:mpint(X))/binary ). --define(Ebinary(X), ?STRING(X) ). --define(Euint32(X), ?UINT32(X) ). - %%%---------------------------------------------------------------------------- %%% %%% There is a difference between supported and default algorithms. The @@ -162,15 +154,15 @@ supported_algorithms(cipher) -> select_crypto_supported( [ {'[email protected]', [{ciphers,chacha20}, {macs,poly1305}]}, - {'[email protected]', [{ciphers,{aes_gcm,256}}]}, - {'aes256-ctr', [{ciphers,{aes_ctr,256}}]}, - {'aes192-ctr', [{ciphers,{aes_ctr,192}}]}, - {'[email protected]', [{ciphers,{aes_gcm,128}}]}, - {'aes128-ctr', [{ciphers,{aes_ctr,128}}]}, - {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]}, - {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]}, - {'aes128-cbc', [{ciphers,aes_cbc128}]}, - {'3des-cbc', [{ciphers,des3_cbc}]} + {'[email protected]', [{ciphers,aes_256_gcm}]}, + {'aes256-ctr', [{ciphers,aes_256_ctr}]}, + {'aes192-ctr', [{ciphers,aes_192_ctr}]}, + {'[email protected]', [{ciphers,aes_128_gcm}]}, + {'aes128-ctr', [{ciphers,aes_128_ctr}]}, + {'AEAD_AES_256_GCM', [{ciphers,aes_256_gcm}]}, + {'AEAD_AES_128_GCM', [{ciphers,aes_128_gcm}]}, + {'aes128-cbc', [{ciphers,aes_128_cbc}]}, + {'3des-cbc', [{ciphers,des_ede3_cbc}]} ] )); supported_algorithms(mac) -> @@ -179,8 +171,8 @@ supported_algorithms(mac) -> [{'hmac-sha2-256', [{macs,hmac}, {hashs,sha256}]}, {'hmac-sha2-512', [{macs,hmac}, {hashs,sha512}]}, {'hmac-sha1', [{macs,hmac}, {hashs,sha}]}, - {'AEAD_AES_128_GCM', [{ciphers,{aes_gcm,128}}]}, - {'AEAD_AES_256_GCM', [{ciphers,{aes_gcm,256}}]} + {'AEAD_AES_128_GCM', [{ciphers,aes_128_gcm}]}, + {'AEAD_AES_256_GCM', [{ciphers,aes_256_gcm}]} ] )); supported_algorithms(compression) -> @@ -1256,11 +1248,6 @@ get_length(aead, EncryptedBuffer, Ssh) -> end. -pkt_type('AEAD_AES_128_GCM') -> aead; -pkt_type('AEAD_AES_256_GCM') -> aead; -pkt_type('[email protected]') -> aead; -pkt_type(_) -> common. - payload(<<PacketLen:32, PaddingLen:8, PayloadAndPadding/binary>>) -> PayloadLen = PacketLen - PaddingLen - 1, <<Payload:PayloadLen/binary, _/binary>> = PayloadAndPadding, @@ -1323,231 +1310,163 @@ verify(PlainText, HashAlg, Sig, Key, _) -> %%% Unit: bytes --record(cipher_data, { - key_bytes, - iv_bytes, - block_bytes - }). +-record(cipher, { + impl, + key_bytes, + iv_bytes, + block_bytes, + pkt_type = common + }). %%% Start of a more parameterized crypto handling. cipher('AEAD_AES_128_GCM') -> - #cipher_data{key_bytes = 16, - iv_bytes = 12, - block_bytes = 16}; + #cipher{impl = aes_128_gcm, + key_bytes = 16, + iv_bytes = 12, + block_bytes = 16, + pkt_type = aead}; cipher('AEAD_AES_256_GCM') -> - #cipher_data{key_bytes = 32, - iv_bytes = 12, - block_bytes = 16}; + #cipher{impl = aes_256_gcm, + key_bytes = 32, + iv_bytes = 12, + block_bytes = 16, + pkt_type = aead}; cipher('3des-cbc') -> - #cipher_data{key_bytes = 24, - iv_bytes = 8, - block_bytes = 8}; + #cipher{impl = des_ede3_cbc, + key_bytes = 24, + iv_bytes = 8, + block_bytes = 8}; cipher('aes128-cbc') -> - #cipher_data{key_bytes = 16, - iv_bytes = 16, - block_bytes = 16}; + #cipher{impl = aes_128_cbc, + key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; cipher('aes128-ctr') -> - #cipher_data{key_bytes = 16, - iv_bytes = 16, - block_bytes = 16}; + #cipher{impl = aes_128_ctr, + key_bytes = 16, + iv_bytes = 16, + block_bytes = 16}; cipher('aes192-ctr') -> - #cipher_data{key_bytes = 24, - iv_bytes = 16, - block_bytes = 16}; + #cipher{impl = aes_192_ctr, + key_bytes = 24, + iv_bytes = 16, + block_bytes = 16}; cipher('aes256-ctr') -> - #cipher_data{key_bytes = 32, - iv_bytes = 16, - block_bytes = 16}; + #cipher{impl = aes_256_ctr, + key_bytes = 32, + iv_bytes = 16, + block_bytes = 16}; cipher('[email protected]') -> % FIXME: Verify!! - #cipher_data{key_bytes = 32, - iv_bytes = 12, - block_bytes = 8}. - + #cipher{impl = chacha20_poly1305, + key_bytes = 32, + iv_bytes = 12, + block_bytes = 8, + pkt_type = aead}; + +cipher(_) -> + #cipher{}. + + +pkt_type(SshCipher) -> (cipher(SshCipher))#cipher.pkt_type. + +decrypt_magic(server) -> {"A", "C"}; +decrypt_magic(client) -> {"B", "D"}. + +encrypt_magic(client) -> decrypt_magic(server); +encrypt_magic(server) -> decrypt_magic(client). + encrypt_init(#ssh{encrypt = none} = Ssh) -> {ok, Ssh}; -encrypt_init(#ssh{encrypt = '[email protected]', role = client} = Ssh) -> + +encrypt_init(#ssh{encrypt = '[email protected]', role = Role} = Ssh) -> %% [email protected] uses two independent crypto streams, one (chacha20) %% for the length used in stream mode, and the other (chacha20-poly1305) as AEAD for %% the payload and to MAC the length||payload. %% See draft-josefsson-ssh-chacha20-poly1305-openssh-00 - <<K2:32/binary,K1:32/binary>> = hash(Ssh, "C", 512), + {_, KeyMagic} = encrypt_magic(Role), + <<K2:32/binary,K1:32/binary>> = hash(Ssh, KeyMagic, 8*64), {ok, Ssh#ssh{encrypt_keys = {K1,K2} % encrypt_block_size = 16, %default = 8. What to set it to? 64 (openssl chacha.h) % ctx and iv is setup for each packet }}; -encrypt_init(#ssh{encrypt = '[email protected]', role = server} = Ssh) -> - <<K2:32/binary,K1:32/binary>> = hash(Ssh, "D", 512), - {ok, Ssh#ssh{encrypt_keys = {K1,K2} - % encrypt_block_size = 16, %default = 8. What to set it to? - }}; -encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> - IV = hash(Ssh, "A", 12*8), - <<K:16/binary>> = hash(Ssh, "C", 128), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = IV}}; -encrypt_init(#ssh{encrypt = 'AEAD_AES_128_GCM', role = server} = Ssh) -> - IV = hash(Ssh, "B", 12*8), - <<K:16/binary>> = hash(Ssh, "D", 128), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = IV}}; -encrypt_init(#ssh{encrypt = 'AEAD_AES_256_GCM', role = client} = Ssh) -> - IV = hash(Ssh, "A", 12*8), - <<K:32/binary>> = hash(Ssh, "C", 256), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = IV}}; -encrypt_init(#ssh{encrypt = 'AEAD_AES_256_GCM', role = server} = Ssh) -> - IV = hash(Ssh, "B", 12*8), - <<K:32/binary>> = hash(Ssh, "D", 256), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = IV}}; -encrypt_init(#ssh{encrypt = '3des-cbc', role = client} = Ssh) -> - IV = hash(Ssh, "A", 64), - <<K1:8/binary, K2:8/binary, K3:8/binary>> = hash(Ssh, "C", 192), - {ok, Ssh#ssh{encrypt_keys = {K1,K2,K3}, - encrypt_block_size = 8, - encrypt_ctx = IV}}; -encrypt_init(#ssh{encrypt = '3des-cbc', role = server} = Ssh) -> - IV = hash(Ssh, "B", 64), - <<K1:8/binary, K2:8/binary, K3:8/binary>> = hash(Ssh, "D", 192), - {ok, Ssh#ssh{encrypt_keys = {K1,K2,K3}, - encrypt_block_size = 8, - encrypt_ctx = IV}}; -encrypt_init(#ssh{encrypt = 'aes128-cbc', role = client} = Ssh) -> - IV = hash(Ssh, "A", 128), - <<K:16/binary>> = hash(Ssh, "C", 128), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, + +encrypt_init(#ssh{encrypt = SshCipher, role = Role} = Ssh) when SshCipher == 'AEAD_AES_128_GCM'; + SshCipher == 'AEAD_AES_256_GCM' -> + {IvMagic, KeyMagic} = encrypt_magic(Role), + #cipher{impl = CryptoCipher, + key_bytes = KeyBytes, + iv_bytes = IvBytes, + block_bytes = BlockBytes} = cipher(SshCipher), + IV = hash(Ssh, IvMagic, 8*IvBytes), + K = hash(Ssh, KeyMagic, 8*KeyBytes), + {ok, Ssh#ssh{encrypt_cipher = CryptoCipher, + encrypt_keys = K, + encrypt_block_size = BlockBytes, encrypt_ctx = IV}}; -encrypt_init(#ssh{encrypt = 'aes128-cbc', role = server} = Ssh) -> - IV = hash(Ssh, "B", 128), - <<K:16/binary>> = hash(Ssh, "D", 128), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = IV}}; -encrypt_init(#ssh{encrypt = 'aes128-ctr', role = client} = Ssh) -> - IV = hash(Ssh, "A", 128), - <<K:16/binary>> = hash(Ssh, "C", 128), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = State}}; -encrypt_init(#ssh{encrypt = 'aes192-ctr', role = client} = Ssh) -> - IV = hash(Ssh, "A", 128), - <<K:24/binary>> = hash(Ssh, "C", 192), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = State}}; -encrypt_init(#ssh{encrypt = 'aes256-ctr', role = client} = Ssh) -> - IV = hash(Ssh, "A", 128), - <<K:32/binary>> = hash(Ssh, "C", 256), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = State}}; -encrypt_init(#ssh{encrypt = 'aes128-ctr', role = server} = Ssh) -> - IV = hash(Ssh, "B", 128), - <<K:16/binary>> = hash(Ssh, "D", 128), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = State}}; -encrypt_init(#ssh{encrypt = 'aes192-ctr', role = server} = Ssh) -> - IV = hash(Ssh, "B", 128), - <<K:24/binary>> = hash(Ssh, "D", 192), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = State}}; -encrypt_init(#ssh{encrypt = 'aes256-ctr', role = server} = Ssh) -> - IV = hash(Ssh, "B", 128), - <<K:32/binary>> = hash(Ssh, "D", 256), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{encrypt_keys = K, - encrypt_block_size = 16, - encrypt_ctx = State}}. + +encrypt_init(#ssh{encrypt = SshCipher, role = Role} = Ssh) -> + {IvMagic, KeyMagic} = encrypt_magic(Role), + #cipher{impl = CryptoCipher, + key_bytes = KeyBytes, + iv_bytes = IvBytes, + block_bytes = BlockBytes} = cipher(SshCipher), + IV = hash(Ssh, IvMagic, 8*IvBytes), + K = hash(Ssh, KeyMagic, 8*KeyBytes), + Ctx0 = crypto:crypto_init(CryptoCipher, K, IV, true), + {ok, Ssh#ssh{encrypt_cipher = CryptoCipher, + encrypt_block_size = BlockBytes, + encrypt_ctx = Ctx0}}. encrypt_final(Ssh) -> - {ok, Ssh#ssh{encrypt = none, + {ok, Ssh#ssh{encrypt = none, encrypt_keys = undefined, encrypt_block_size = 8, encrypt_ctx = undefined }}. + encrypt(#ssh{encrypt = none} = Ssh, Data) -> {Ssh, Data}; + encrypt(#ssh{encrypt = '[email protected]', encrypt_keys = {K1,K2}, send_sequence = Seq} = Ssh, <<LenData:4/binary, PayloadData/binary>>) -> %% Encrypt length IV1 = <<0:8/unit:8, Seq:8/unit:8>>, - {_,EncLen} = crypto:stream_encrypt(crypto:stream_init(chacha20, K1, IV1), - LenData), + EncLen = crypto:crypto_one_time(chacha20, K1, IV1, LenData, true), %% Encrypt payload IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>, - {_,EncPayloadData} = crypto:stream_encrypt(crypto:stream_init(chacha20, K2, IV2), - PayloadData), - + EncPayloadData = crypto:crypto_one_time(chacha20, K2, IV2, PayloadData, true), %% MAC tag - {_,PolyKey} = crypto:stream_encrypt(crypto:stream_init(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>), - <<0:32/unit:8>>), + PolyKey = crypto:crypto_one_time(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, true), EncBytes = <<EncLen/binary,EncPayloadData/binary>>, Ctag = crypto:poly1305(PolyKey, EncBytes), %% Result {Ssh, {EncBytes,Ctag}}; -encrypt(#ssh{encrypt = 'AEAD_AES_128_GCM', - encrypt_keys = K, + +encrypt(#ssh{encrypt = SshCipher, + encrypt_cipher = CryptoCipher, + encrypt_keys = K, encrypt_ctx = IV0} = Ssh, - <<LenData:4/binary, PayloadData/binary>>) -> - {Ctext,Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, {LenData,PayloadData}), - IV = next_gcm_iv(IV0), - {Ssh#ssh{encrypt_ctx = IV}, {<<LenData/binary,Ctext/binary>>,Ctag}}; -encrypt(#ssh{encrypt = 'AEAD_AES_256_GCM', - encrypt_keys = K, - encrypt_ctx = IV0} = Ssh, - <<LenData:4/binary, PayloadData/binary>>) -> - {Ctext,Ctag} = crypto:block_encrypt(aes_gcm, K, IV0, {LenData,PayloadData}), + <<LenData:4/binary, PayloadData/binary>>) when SshCipher == 'AEAD_AES_128_GCM' ; + SshCipher == 'AEAD_AES_256_GCM' -> + {Ctext,Ctag} = crypto:crypto_one_time_aead(CryptoCipher, K, IV0, PayloadData, LenData, true), IV = next_gcm_iv(IV0), {Ssh#ssh{encrypt_ctx = IV}, {<<LenData/binary,Ctext/binary>>,Ctag}}; -encrypt(#ssh{encrypt = '3des-cbc', - encrypt_keys = {K1,K2,K3}, - encrypt_ctx = IV0} = Ssh, Data) -> - Enc = crypto:block_encrypt(des3_cbc, [K1,K2,K3], IV0, Data), - IV = crypto:next_iv(des3_cbc, Enc), - {Ssh#ssh{encrypt_ctx = IV}, Enc}; -encrypt(#ssh{encrypt = 'aes128-cbc', - encrypt_keys = K, - encrypt_ctx = IV0} = Ssh, Data) -> - Enc = crypto:block_encrypt(aes_cbc128, K,IV0,Data), - IV = crypto:next_iv(aes_cbc, Enc), - {Ssh#ssh{encrypt_ctx = IV}, Enc}; -encrypt(#ssh{encrypt = 'aes128-ctr', - encrypt_ctx = State0} = Ssh, Data) -> - {State, Enc} = crypto:stream_encrypt(State0,Data), - {Ssh#ssh{encrypt_ctx = State}, Enc}; -encrypt(#ssh{encrypt = 'aes192-ctr', - encrypt_ctx = State0} = Ssh, Data) -> - {State, Enc} = crypto:stream_encrypt(State0,Data), - {Ssh#ssh{encrypt_ctx = State}, Enc}; -encrypt(#ssh{encrypt = 'aes256-ctr', - encrypt_ctx = State0} = Ssh, Data) -> - {State, Enc} = crypto:stream_encrypt(State0,Data), - {Ssh#ssh{encrypt_ctx = State}, Enc}. - + +encrypt(#ssh{encrypt_ctx = Ctx0} = Ssh, Data) -> + Enc = crypto:crypto_update(Ctx0, Data), + {Ssh, Enc}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Decryption @@ -1555,181 +1474,92 @@ encrypt(#ssh{encrypt = 'aes256-ctr', decrypt_init(#ssh{decrypt = none} = Ssh) -> {ok, Ssh}; -decrypt_init(#ssh{decrypt = '[email protected]', role = client} = Ssh) -> - <<K2:32/binary,K1:32/binary>> = hash(Ssh, "D", 512), - {ok, Ssh#ssh{decrypt_keys = {K1,K2} - }}; -decrypt_init(#ssh{decrypt = '[email protected]', role = server} = Ssh) -> - <<K2:32/binary,K1:32/binary>> = hash(Ssh, "C", 512), + +decrypt_init(#ssh{decrypt = '[email protected]', role = Role} = Ssh) -> + {_, KeyMagic} = decrypt_magic(Role), + <<K2:32/binary,K1:32/binary>> = hash(Ssh, KeyMagic, 8*64), {ok, Ssh#ssh{decrypt_keys = {K1,K2} }}; -decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = client} = Ssh) -> - IV = hash(Ssh, "B", 12*8), - <<K:16/binary>> = hash(Ssh, "D", 128), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = IV}}; -decrypt_init(#ssh{decrypt = 'AEAD_AES_128_GCM', role = server} = Ssh) -> - IV = hash(Ssh, "A", 12*8), - <<K:16/binary>> = hash(Ssh, "C", 128), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = IV}}; -decrypt_init(#ssh{decrypt = 'AEAD_AES_256_GCM', role = client} = Ssh) -> - IV = hash(Ssh, "B", 12*8), - <<K:32/binary>> = hash(Ssh, "D", 256), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = IV}}; -decrypt_init(#ssh{decrypt = 'AEAD_AES_256_GCM', role = server} = Ssh) -> - IV = hash(Ssh, "A", 12*8), - <<K:32/binary>> = hash(Ssh, "C", 256), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, + +decrypt_init(#ssh{decrypt = SshCipher, role = Role} = Ssh) when SshCipher == 'AEAD_AES_128_GCM'; + SshCipher == 'AEAD_AES_256_GCM' -> + {IvMagic, KeyMagic} = decrypt_magic(Role), + #cipher{impl = CryptoCipher, + key_bytes = KeyBytes, + iv_bytes = IvBytes, + block_bytes = BlockBytes} = cipher(SshCipher), + IV = hash(Ssh, IvMagic, 8*IvBytes), + K = hash(Ssh, KeyMagic, 8*KeyBytes), + {ok, Ssh#ssh{decrypt_cipher = CryptoCipher, + decrypt_keys = K, + decrypt_block_size = BlockBytes, decrypt_ctx = IV}}; -decrypt_init(#ssh{decrypt = '3des-cbc', role = client} = Ssh) -> - {IV, KD} = {hash(Ssh, "B", 64), - hash(Ssh, "D", 192)}, - <<K1:8/binary, K2:8/binary, K3:8/binary>> = KD, - {ok, Ssh#ssh{decrypt_keys = {K1,K2,K3}, decrypt_ctx = IV, - decrypt_block_size = 8}}; -decrypt_init(#ssh{decrypt = '3des-cbc', role = server} = Ssh) -> - {IV, KD} = {hash(Ssh, "A", 64), - hash(Ssh, "C", 192)}, - <<K1:8/binary, K2:8/binary, K3:8/binary>> = KD, - {ok, Ssh#ssh{decrypt_keys = {K1, K2, K3}, decrypt_ctx = IV, - decrypt_block_size = 8}}; -decrypt_init(#ssh{decrypt = 'aes128-cbc', role = client} = Ssh) -> - {IV, KD} = {hash(Ssh, "B", 128), - hash(Ssh, "D", 128)}, - <<K:16/binary>> = KD, - {ok, Ssh#ssh{decrypt_keys = K, decrypt_ctx = IV, - decrypt_block_size = 16}}; -decrypt_init(#ssh{decrypt = 'aes128-cbc', role = server} = Ssh) -> - {IV, KD} = {hash(Ssh, "A", 128), - hash(Ssh, "C", 128)}, - <<K:16/binary>> = KD, - {ok, Ssh#ssh{decrypt_keys = K, decrypt_ctx = IV, - decrypt_block_size = 16}}; -decrypt_init(#ssh{decrypt = 'aes128-ctr', role = client} = Ssh) -> - IV = hash(Ssh, "B", 128), - <<K:16/binary>> = hash(Ssh, "D", 128), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = State}}; -decrypt_init(#ssh{decrypt = 'aes192-ctr', role = client} = Ssh) -> - IV = hash(Ssh, "B", 128), - <<K:24/binary>> = hash(Ssh, "D", 192), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = State}}; -decrypt_init(#ssh{decrypt = 'aes256-ctr', role = client} = Ssh) -> - IV = hash(Ssh, "B", 128), - <<K:32/binary>> = hash(Ssh, "D", 256), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = State}}; -decrypt_init(#ssh{decrypt = 'aes128-ctr', role = server} = Ssh) -> - IV = hash(Ssh, "A", 128), - <<K:16/binary>> = hash(Ssh, "C", 128), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = State}}; -decrypt_init(#ssh{decrypt = 'aes192-ctr', role = server} = Ssh) -> - IV = hash(Ssh, "A", 128), - <<K:24/binary>> = hash(Ssh, "C", 192), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = State}}; -decrypt_init(#ssh{decrypt = 'aes256-ctr', role = server} = Ssh) -> - IV = hash(Ssh, "A", 128), - <<K:32/binary>> = hash(Ssh, "C", 256), - State = crypto:stream_init(aes_ctr, K, IV), - {ok, Ssh#ssh{decrypt_keys = K, - decrypt_block_size = 16, - decrypt_ctx = State}}. - - + +decrypt_init(#ssh{decrypt = SshCipher, role = Role} = Ssh) -> + {IvMagic, KeyMagic} = decrypt_magic(Role), + #cipher{impl = CryptoCipher, + key_bytes = KeyBytes, + iv_bytes = IvBytes, + block_bytes = BlockBytes} = cipher(SshCipher), + IV = hash(Ssh, IvMagic, 8*IvBytes), + K = hash(Ssh, KeyMagic, 8*KeyBytes), + Ctx0 = crypto:crypto_init(CryptoCipher, K, IV, false), + {ok, Ssh#ssh{decrypt_cipher = CryptoCipher, + decrypt_block_size = BlockBytes, + decrypt_ctx = Ctx0}}. + + decrypt_final(Ssh) -> {ok, Ssh#ssh {decrypt = none, decrypt_keys = undefined, decrypt_ctx = undefined, decrypt_block_size = 8}}. + decrypt(Ssh, <<>>) -> {Ssh, <<>>}; + decrypt(#ssh{decrypt = '[email protected]', - decrypt_keys = {K1,_K2}, - recv_sequence = Seq} = Ssh, {length,EncryptedLen}) -> - {_State,PacketLenBin} = - crypto:stream_decrypt(crypto:stream_init(chacha20, K1, <<0:8/unit:8, Seq:8/unit:8>>), - EncryptedLen), - {Ssh, PacketLenBin}; -decrypt(#ssh{decrypt = '[email protected]', - decrypt_keys = {_K1,K2}, - recv_sequence = Seq} = Ssh, {AAD,Ctext,Ctag}) -> - %% The length is already decoded and used to divide the input - %% Check the mac (important that it is timing-safe): - {_,PolyKey} = - crypto:stream_encrypt(crypto:stream_init(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>), - <<0:32/unit:8>>), - case equal_const_time(Ctag, crypto:poly1305(PolyKey, <<AAD/binary,Ctext/binary>>)) of - true -> - %% MAC is ok, decode - IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>, - {_,PlainText} = - crypto:stream_decrypt(crypto:stream_init(chacha20,K2,IV2), Ctext), - {Ssh, PlainText}; - false -> - {Ssh,error} + decrypt_keys = {K1,K2}, + recv_sequence = Seq} = Ssh, Data) -> + case Data of + {length,EncryptedLen} -> + %% The length is decrypted separately in a first step + PacketLenBin = crypto:crypto_one_time(chacha20, K1, <<0:8/unit:8, Seq:8/unit:8>>, EncryptedLen, false), + {Ssh, PacketLenBin}; + {AAD,Ctext,Ctag} -> + %% The length is already decrypted and used to divide the input + %% Check the mac (important that it is timing-safe): + PolyKey = crypto:crypto_one_time(chacha20, K2, <<0:8/unit:8,Seq:8/unit:8>>, <<0:32/unit:8>>, false), + case equal_const_time(Ctag, crypto:poly1305(PolyKey, <<AAD/binary,Ctext/binary>>)) of + true -> + %% MAC is ok, decode + IV2 = <<1:8/little-unit:8, Seq:8/unit:8>>, + PlainText = crypto:crypto_one_time(chacha20, K2, IV2, Ctext, false), + {Ssh, PlainText}; + false -> + {Ssh,error} + end end; + decrypt(#ssh{decrypt = none} = Ssh, Data) -> {Ssh, Data}; -decrypt(#ssh{decrypt = 'AEAD_AES_128_GCM', - decrypt_keys = K, - decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) -> - Dec = crypto:block_decrypt(aes_gcm, K, IV0, Data), % Dec = PlainText | error - IV = next_gcm_iv(IV0), - {Ssh#ssh{decrypt_ctx = IV}, Dec}; -decrypt(#ssh{decrypt = 'AEAD_AES_256_GCM', + +decrypt(#ssh{decrypt = SshCipher, + decrypt_cipher = CryptoCipher, decrypt_keys = K, - decrypt_ctx = IV0} = Ssh, Data = {_AAD,_Ctext,_Ctag}) -> - Dec = crypto:block_decrypt(aes_gcm, K, IV0, Data), % Dec = PlainText | error + decrypt_ctx = IV0} = Ssh, {AAD,Ctext,Ctag}) when SshCipher == 'AEAD_AES_128_GCM' ; + SshCipher == 'AEAD_AES_256_GCM' -> + Dec = crypto:crypto_one_time_aead(CryptoCipher, K, IV0, Ctext, AAD, Ctag, false), IV = next_gcm_iv(IV0), {Ssh#ssh{decrypt_ctx = IV}, Dec}; -decrypt(#ssh{decrypt = '3des-cbc', decrypt_keys = Keys, - decrypt_ctx = IV0} = Ssh, Data) -> - {K1, K2, K3} = Keys, - Dec = crypto:block_decrypt(des3_cbc, [K1,K2,K3], IV0, Data), - IV = crypto:next_iv(des3_cbc, Data), - {Ssh#ssh{decrypt_ctx = IV}, Dec}; -decrypt(#ssh{decrypt = 'aes128-cbc', decrypt_keys = Key, - decrypt_ctx = IV0} = Ssh, Data) -> - Dec = crypto:block_decrypt(aes_cbc128, Key,IV0,Data), - IV = crypto:next_iv(aes_cbc, Data), - {Ssh#ssh{decrypt_ctx = IV}, Dec}; -decrypt(#ssh{decrypt = 'aes128-ctr', - decrypt_ctx = State0} = Ssh, Data) -> - {State, Enc} = crypto:stream_decrypt(State0,Data), - {Ssh#ssh{decrypt_ctx = State}, Enc}; -decrypt(#ssh{decrypt = 'aes192-ctr', - decrypt_ctx = State0} = Ssh, Data) -> - {State, Enc} = crypto:stream_decrypt(State0,Data), - {Ssh#ssh{decrypt_ctx = State}, Enc}; -decrypt(#ssh{decrypt = 'aes256-ctr', - decrypt_ctx = State0} = Ssh, Data) -> - {State, Enc} = crypto:stream_decrypt(State0,Data), - {Ssh#ssh{decrypt_ctx = State}, Enc}. +decrypt(#ssh{decrypt_ctx = Ctx0} = Ssh, Data) -> + Dec = crypto:crypto_update(Ctx0, Data), + {Ssh, Dec}. next_gcm_iv(<<Fixed:32, InvCtr:64>>) -> <<Fixed:32, (InvCtr+1):64>>. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Compression %% @@ -2058,9 +1888,9 @@ compute_key(Algorithm, OthersPublic, MyPrivate, Args) -> dh_bits(#alg{encrypt = Encrypt, send_mac = SendMac}) -> C = cipher(Encrypt), - 8 * lists:max([C#cipher_data.key_bytes, - C#cipher_data.block_bytes, - C#cipher_data.iv_bytes, + 8 * lists:max([C#cipher.key_bytes, + C#cipher.block_bytes, + C#cipher.iv_bytes, mac_key_bytes(SendMac) ]). @@ -2091,40 +1921,13 @@ select_crypto_supported(L) -> crypto_supported(Conditions, Supported) -> lists:all( fun({Tag,CryptoName}) when is_atom(CryptoName) -> - crypto_name_supported(Tag,CryptoName,Supported); - ({Tag,{Name,Len}}) when is_integer(Len) -> - crypto_name_supported(Tag,Name,Supported) andalso - len_supported(Name,Len) + crypto_name_supported(Tag,CryptoName,Supported) end, Conditions). crypto_name_supported(Tag, CryptoName, Supported) -> - Vs = case proplists:get_value(Tag,Supported,[]) of - [] when Tag == curves -> crypto:ec_curves(); - L -> L - end, + Vs = proplists:get_value(Tag,Supported,[]), lists:member(CryptoName, Vs). -len_supported(Name, Len) -> - try - case Name of - aes_ctr -> - {_, <<_/binary>>} = - %% Test encryption - crypto:stream_encrypt(crypto:stream_init(Name, <<0:Len>>, <<0:128>>), <<"">>); - aes_gcm -> - {<<_/binary>>, <<_/binary>>} = - crypto:block_encrypt(Name, - _Key = <<0:Len>>, - _IV = <<0:12/unsigned-unit:8>>, - {<<"AAD">>,"PT"}) - end - of - _ -> true - catch - _:_ -> false - end. - - same(Algs) -> [{client2server,Algs}, {server2client,Algs}]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lib/ssh/src/ssh_userauth.hrl b/lib/ssh/src/ssh_userauth.hrl deleted file mode 100644 index 2cfc1f0f83..0000000000 --- a/lib/ssh/src/ssh_userauth.hrl +++ /dev/null @@ -1,78 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - -%% - -%%% Description: user authentication protocol - --define(SSH_MSG_USERAUTH_REQUEST, 50). --define(SSH_MSG_USERAUTH_FAILURE, 51). --define(SSH_MSG_USERAUTH_SUCCESS, 52). --define(SSH_MSG_USERAUTH_BANNER, 53). --define(SSH_MSG_USERAUTH_PK_OK, 60). --define(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, 60). --define(SSH_MSG_USERAUTH_INFO_REQUEST, 60). --define(SSH_MSG_USERAUTH_INFO_RESPONSE, 61). - --record(ssh_msg_userauth_request, - { - user, %% string - service, %% string - method, %% string "publickey", "password" - data %% opaque - }). - --record(ssh_msg_userauth_failure, - { - authentications, %% string - partial_success %% boolean - }). - --record(ssh_msg_userauth_success, - { - }). - --record(ssh_msg_userauth_banner, - { - message, %% string - language %% string - }). - --record(ssh_msg_userauth_passwd_changereq, - { - prompt, %% string - languge %% string - }). - --record(ssh_msg_userauth_pk_ok, - { - algorithm_name, % string - key_blob % string - }). - --record(ssh_msg_userauth_info_request, - {name, - instruction, - language_tag, - num_prompts, - data}). --record(ssh_msg_userauth_info_response, - {num_responses, - data}). diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 5de6d52092..9b987dea5a 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -1399,7 +1399,7 @@ rekey_chk(Config, RLdaemon, RLclient) -> Kex1 = ssh_test_lib:get_kex_init(ConnectionRef), %% Make both sides send something: - {ok, SftpPid} = ssh_sftp:start_channel(ConnectionRef), + {ok, _SftpPid} = ssh_sftp:start_channel(ConnectionRef), %% Check rekeying timer:sleep(?REKEY_DATA_TMO), diff --git a/lib/ssh/test/ssh_bench_SUITE.erl b/lib/ssh/test/ssh_bench_SUITE.erl index 2ac4e5636a..5ff7a71c45 100644 --- a/lib/ssh/test/ssh_bench_SUITE.erl +++ b/lib/ssh/test/ssh_bench_SUITE.erl @@ -26,7 +26,7 @@ -include_lib("ssh/src/ssh.hrl"). -include_lib("ssh/src/ssh_transport.hrl"). -include_lib("ssh/src/ssh_connect.hrl"). --include_lib("ssh/src/ssh_userauth.hrl"). +-include_lib("ssh/src/ssh_auth.hrl"). %%%================================================================ %%% @@ -178,7 +178,7 @@ gen_data(DataSz) -> connect_measure(Port, Cipher, Mac, Data, Options) -> - AES_GCM = {cipher, + _AES_GCM = {cipher, []}, %% ['[email protected]', %% '[email protected]']}, @@ -187,22 +187,22 @@ connect_measure(Port, Cipher, Mac, Data, Options) -> {none,none} -> [{modify_algorithms,[{prepend, [{cipher,[Cipher]}, {mac,[Mac]}]} -%%% ,{rm,[AES_GCM]} +%%% ,{rm,[_AES_GCM]} ]}]; {none,_} -> [{modify_algorithms,[{prepend, [{cipher,[Cipher]}]} -%%% ,{rm,[AES_GCM]} +%%% ,{rm,[_AES_GCM]} ]}, {preferred_algorithms, [{mac,[Mac]}]}]; {_,none} -> [{modify_algorithms,[{prepend, [{mac,[Mac]}]} -%%% ,{rm,[AES_GCM]} +%%% ,{rm,[_AES_GCM]} ]}, {preferred_algorithms, [{cipher,[Cipher]}]}]; _ -> [{preferred_algorithms, [{cipher,[Cipher]}, {mac,[Mac]}]} -%%% ,{modify_algorithms, [{rm,[AES_GCM]}]} +%%% ,{modify_algorithms, [{rm,[_AES_GCM]}]} ] end, Times = diff --git a/lib/ssh/test/ssh_chan_behaviours_SUITE.erl b/lib/ssh/test/ssh_chan_behaviours_SUITE.erl index 16ed152bcd..103d7253fd 100644 --- a/lib/ssh/test/ssh_chan_behaviours_SUITE.erl +++ b/lib/ssh/test/ssh_chan_behaviours_SUITE.erl @@ -128,8 +128,8 @@ subsystem_client(Config) -> C = proplists:get_value(connref, Config), {ok,ChRef} = ssh_chan_behaviours_client:start_link(C), - IDclt = ?EXPECT({{C,Ch1clt}, {ssh_channel_up,Ch1clt,C}}, {C,Ch1clt}), - IDsrv = ?EXPECT({{_Csrv,Ch1srv}, {ssh_channel_up,Ch1srv,_Csrv}}, {_Csrv,Ch1srv}), + IDclt = ?EXPECT({{C,_Ch1clt}, {ssh_channel_up,_Ch1clt,C}}, {C,_Ch1clt}), + IDsrv = ?EXPECT({{_Csrv,_Ch1srv}, {ssh_channel_up,_Ch1srv,_Csrv}}, {_Csrv,_Ch1srv}), ok = ssh_chan_behaviours_client:stop(ChRef), ?EXPECT({IDclt, {terminate,normal}}, []), % From the proper channel handler diff --git a/lib/ssh/test/ssh_chan_behaviours_client.erl b/lib/ssh/test/ssh_chan_behaviours_client.erl index 15f17733d6..8dd18973ad 100644 --- a/lib/ssh/test/ssh_chan_behaviours_client.erl +++ b/lib/ssh/test/ssh_chan_behaviours_client.erl @@ -94,7 +94,7 @@ handle_ssh_msg({ssh_cm, C, {eof, Ch}}=M, #state{ch=Ch,cm=C} = State) -> ?DBG(State, "eof",[]), {ok, State}; -handle_ssh_msg({ssh_cm, C, {signal, _Ch, _SigNameStr}=Sig} = M, #state{ch=Ch,cm=C} = State) -> +handle_ssh_msg({ssh_cm, C, {signal, Ch, _SigNameStr}=Sig} = M, #state{ch=Ch,cm=C} = State) -> %% Ignore signals according to RFC 4254 section 6.9. tell_parent(M, State), ?DBG(State, "~p",[Sig]), diff --git a/lib/ssh/test/ssh_chan_behaviours_server.erl b/lib/ssh/test/ssh_chan_behaviours_server.erl index 1408675a6e..1d504b1bc6 100644 --- a/lib/ssh/test/ssh_chan_behaviours_server.erl +++ b/lib/ssh/test/ssh_chan_behaviours_server.erl @@ -65,7 +65,7 @@ handle_ssh_msg({ssh_cm, C, {eof, Ch}}=M, #state{ch=Ch,cm=C} = State) -> ?DBG(State, "eof",[]), {ok, State}; -handle_ssh_msg({ssh_cm, C, {signal, _Ch, _SigNameStr}=Sig} = M, #state{ch=Ch,cm=C} = State) -> +handle_ssh_msg({ssh_cm, C, {signal, Ch, _SigNameStr}=Sig} = M, #state{ch=Ch,cm=C} = State) -> %% Ignore signals according to RFC 4254 section 6.9. tell_parent(M, State), ?DBG(State, "~p",[Sig]), diff --git a/lib/ssh/test/ssh_compat_SUITE.erl b/lib/ssh/test/ssh_compat_SUITE.erl index 8e82527c6e..06ed9082cf 100644 --- a/lib/ssh/test/ssh_compat_SUITE.erl +++ b/lib/ssh/test/ssh_compat_SUITE.erl @@ -150,8 +150,7 @@ init_per_group(G, Config0) -> stop_docker(ID), {fail, "Can't contact docker sshd"} catch - Class:Exc -> - ST = erlang:get_stacktrace(), + Class:Exc:ST -> ct:log("common_algs: ~p:~p~n~p",[Class,Exc,ST]), stop_docker(ID), {fail, "Failed during setup"} @@ -160,8 +159,7 @@ init_per_group(G, Config0) -> cant_start_docker -> {skip, "Can't start docker"}; - C:E -> - ST = erlang:get_stacktrace(), + C:E:ST -> ct:log("No ~p~n~p:~p~n~p",[G,C,E,ST]), {skip, "Can't start docker"} end; @@ -1026,8 +1024,7 @@ receive_hello(S) -> Result -> Result catch - Class:Error -> - ST = erlang:get_stacktrace(), + Class:Error:ST -> {error, {Class,Error,ST}} end. @@ -1104,8 +1101,7 @@ sftp_tests_erl_server(Config, ServerIP, ServerPort, ServerRootDir, UserDir) -> call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir), check_local_directory(ServerRootDir) catch - Class:Error -> - ST = erlang:get_stacktrace(), + Class:Error:ST -> {error, {Class,Error,ST}} end. @@ -1133,7 +1129,7 @@ check_local_directory(ServerRootDir) -> check_local_directory(ServerRootDir, SleepTime, N) -> case do_check_local_directory(ServerRootDir) of - {error,Error} when N>0 -> + {error,_Error} when N>0 -> %% Could be that the erlang side is faster and the docker's operations %% are not yet finalized. %% Sleep for a while and retry a few times: @@ -1347,8 +1343,7 @@ one_test_erl_client(SFTP, Id, C) when SFTP==sftp ; SFTP==sftp_async -> catch ssh_sftp:stop_channel(Ch), R catch - Class:Error -> - ST = erlang:get_stacktrace(), + Class:Error:ST -> {error, {SFTP,Id,Class,Error,ST}} end. diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl index 778e4a5fc8..6aa587dc7f 100644 --- a/lib/ssh/test/ssh_connection_SUITE.erl +++ b/lib/ssh/test/ssh_connection_SUITE.erl @@ -1124,12 +1124,12 @@ start_our_shell(_User, _Peer) -> ssh_exec_echo(Cmd) -> spawn(fun() -> - io:format("echo "++Cmd ++ "\n") + io:format("echo ~s\n", [Cmd]) end). ssh_exec_echo(Cmd, User) -> spawn(fun() -> - io:format(io_lib:format("echo ~s ~s\n",[User,Cmd])) + io:format("echo ~s ~s\n",[User,Cmd]) end). ssh_exec_echo(Cmd, User, _PeerAddr) -> ssh_exec_echo(Cmd,User). diff --git a/lib/ssh/test/ssh_options_SUITE.erl b/lib/ssh/test/ssh_options_SUITE.erl index 60d0da2a39..bf90f74324 100644 --- a/lib/ssh/test/ssh_options_SUITE.erl +++ b/lib/ssh/test/ssh_options_SUITE.erl @@ -214,7 +214,7 @@ init_per_testcase(_TestCase, Config) -> file:make_dir(UserDir), [{user_dir,UserDir}|Config]. -end_per_testcase(_TestCase, Config) -> +end_per_testcase(_TestCase, _Config) -> ssh:stop(), ok. diff --git a/lib/ssh/test/ssh_protocol_SUITE.erl b/lib/ssh/test/ssh_protocol_SUITE.erl index 3e3e151781..b12ddfeef6 100644 --- a/lib/ssh/test/ssh_protocol_SUITE.erl +++ b/lib/ssh/test/ssh_protocol_SUITE.erl @@ -38,7 +38,11 @@ -define(EXTRA_KEX, 'diffie-hellman-group1-sha1'). -define(CIPHERS, ['aes256-ctr','aes192-ctr','aes128-ctr','aes128-cbc','3des-cbc']). --define(DEFAULT_CIPHERS, [{client2server,?CIPHERS}, {server2client,?CIPHERS}]). +-define(DEFAULT_CIPHERS, (fun() -> Ciphs = filter_supported(cipher, ?CIPHERS), + [{client2server,Ciphs}, {server2client,Ciphs}] + end)() + ). + -define(v(Key, Config), proplists:get_value(Key, Config)). -define(v(Key, Config, Default), proplists:get_value(Key, Config, Default)). diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index a1a7eebcde..1129303414 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -409,7 +409,7 @@ ct:log("DataDir ~p:~n ~p~n~nSystDir ~p:~n ~p~n~nUserDir ~p:~n ~p",[DataDir, file setup_ecdsa_auth_keys(Size, DataDir, UserDir). setup_eddsa(Alg, DataDir, UserDir) -> - {IdPriv, IdPub, HostPriv, HostPub} = + {IdPriv, _IdPub, HostPriv, HostPub} = case Alg of ed25519 -> {"id_ed25519", "id_ed25519.pub", "ssh_host_ed25519_key", "ssh_host_ed25519_key.pub"}; ed448 -> {"id_ed448", "id_ed448.pub", "ssh_host_ed448_key", "ssh_host_ed448_key.pub"} @@ -970,7 +970,7 @@ expected_state(_) -> false. %%%---------------------------------------------------------------- %%% Return a string with N random characters %%% -random_chars(N) -> [crypto:rand_uniform($a,$z) || _<-lists:duplicate(N,x)]. +random_chars(N) -> [($a-1)+rand:uniform($z-$a) || _<-lists:duplicate(N,x)]. create_random_dir(Config) -> diff --git a/lib/ssh/test/ssh_trpt_test_lib.erl b/lib/ssh/test/ssh_trpt_test_lib.erl index f2c9892f95..3f4df2c986 100644 --- a/lib/ssh/test/ssh_trpt_test_lib.erl +++ b/lib/ssh/test/ssh_trpt_test_lib.erl @@ -570,75 +570,6 @@ receive_binary_msg(S0=#s{}) -> -old_receive_binary_msg(S0=#s{ssh=C0=#ssh{decrypt_block_size = BlockSize, - recv_mac_size = MacSize - } - }) -> - case size(S0#s.encrypted_data_buffer) >= max(8,BlockSize) of - false -> - %% Need more bytes to decode the packet_length field - Remaining = max(8,BlockSize) - size(S0#s.encrypted_data_buffer), - receive_binary_msg( receive_wait(Remaining, S0) ); - true -> - %% Has enough bytes to decode the packet_length field - {_, <<?UINT32(PacketLen), _/binary>>, _} = - ssh_transport:decrypt_blocks(S0#s.encrypted_data_buffer, BlockSize, C0), % FIXME: BlockSize should be at least 4 - - %% FIXME: Check that ((4+PacketLen) rem BlockSize) == 0 ? - - S1 = if - PacketLen > ?SSH_MAX_PACKET_SIZE -> - fail({too_large_message,PacketLen},S0); % FIXME: disconnect - - ((4+PacketLen) rem BlockSize) =/= 0 -> - fail(bad_packet_length_modulo, S0); % FIXME: disconnect - - size(S0#s.encrypted_data_buffer) >= (4 + PacketLen + MacSize) -> - %% has the whole packet - S0; - - true -> - %% need more bytes to get have the whole packet - Remaining = (4 + PacketLen + MacSize) - size(S0#s.encrypted_data_buffer), - receive_wait(Remaining, S0) - end, - - %% Decrypt all, including the packet_length part (re-use the initial #ssh{}) - {C1, SshPacket = <<?UINT32(_),?BYTE(PadLen),Tail/binary>>, EncRest} = - ssh_transport:decrypt_blocks(S1#s.encrypted_data_buffer, PacketLen+4, C0), - - PayloadLen = PacketLen - 1 - PadLen, - <<CompressedPayload:PayloadLen/binary, _Padding:PadLen/binary>> = Tail, - - {C2, Payload} = ssh_transport:decompress(C1, CompressedPayload), - - <<Mac:MacSize/binary, Rest/binary>> = EncRest, - - case {ssh_transport:is_valid_mac(Mac, SshPacket, C2), - catch ssh_message:decode(set_prefix_if_trouble(Payload,S1))} - of - {false, _} -> fail(bad_mac,S1); - {_, {'EXIT',_}} -> fail(decode_failed,S1); - - {true, Msg} -> - C3 = case Msg of - #ssh_msg_kexinit{} -> - ssh_transport:key_init(opposite_role(C2), C2, Payload); - _ -> - C2 - end, - S2 = opt(print_messages, S1, - fun(X) when X==true;X==detail -> {"Recv~n~s~n",[format_msg(Msg)]} end), - S3 = opt(print_messages, S2, - fun(detail) -> {"decrypted bytes ~p~n",[SshPacket]} end), - S3#s{ssh = inc_recv_seq_num(C3), - encrypted_data_buffer = Rest, - return_value = Msg - } - end - end. - - set_prefix_if_trouble(Msg = <<?BYTE(Op),_/binary>>, #s{alg=#alg{kex=Kex}}) when Op == 30; Op == 31 diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index 837da27ab0..bb87dd388c 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,4 +1,4 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 4.7.6 +SSH_VSN = 4.7.7 APP_VSN = "ssh-$(SSH_VSN)" |