554554
555555function on_auth_none (session, user, client):: ssh.AuthStatus
556556 _add_log_event! (client, :auth_none , true )
557- return ssh. AuthStatus_Denied
557+
558+ if client. allow_auth_none
559+ client. authenticated = true
560+ end
561+
562+ return client. authenticated ? ssh. AuthStatus_Success : ssh. AuthStatus_Denied
558563end
559564
560565function on_service_request (session, service, client):: Bool
@@ -580,9 +585,9 @@ function on_channel_env_request(session, sshchan, name, value, client)::Bool
580585end
581586
582587function on_channel_exec_request (session, sshchan, command, client):: Bool
583- _add_log_event! (client, :channel_exec_request , command)
588+ _add_log_event! (client, :channel_exec_request , " ' $ command' " )
584589 owning_sshchan = find_unclaimed_channel (client, sshchan)
585- push! (client. channel_operations, CommandExecutor (command, owning_sshchan, client. env))
590+ push! (client. channel_operations, CommandExecutor (client, command, owning_sshchan, client. env))
586591
587592 return true
588593end
695700 session:: ssh.Session
696701 verbose:: Bool
697702 password:: Union{String, Nothing}
703+ allow_auth_none:: Bool = false
698704 authenticated:: Bool = false
699705
700706 session_event:: Union{ssh.SessionEvent, Nothing} = nothing
@@ -773,6 +779,7 @@ $(TYPEDFIELDS)
773779 sshchan:: Union{ssh.SshChannel, Nothing} = nothing
774780 verbose:: Bool = false
775781 password:: Union{String, Nothing} = nothing
782+ allow_auth_none:: Bool = false
776783
777784 clients:: Vector{Client} = Client[]
778785end
@@ -794,13 +801,17 @@ Creates a [`DemoServer`](@ref).
794801 authentication etc. Useful for high-level debugging. The events can always be
795802 printed afterwards with [`Demo.print_timeline`](@ref).
796803- `password=nothing`: The password to use if password authentication is enabled.
804+ - `allow_auth_none`: Whether to allow authentication without any credentials
805+ being presented.
797806- `auth_methods=[AuthMethod_None, AuthMethod_Password]`: A list of
798807 authentication methods to enable. See [`ssh.AuthMethod`](@ref).
799808- `log_verbosity=nothing`: Controls the logging of libssh itself. This could be
800809 e.g. `lib.SSH_LOG_WARNING` (see the [upstream
801810 documentation](https://api.libssh.org/stable/group__libssh__log.html#ga06fc87d81c62e9abb8790b6e5713c55b)).
802811"""
803- function DemoServer (port:: Int ; verbose:: Bool = false , password:: Union{String, Nothing} = nothing ,
812+ function DemoServer (port:: Int ; verbose:: Bool = false ,
813+ password:: Union{String, Nothing} = nothing ,
814+ allow_auth_none= false ,
804815 auth_methods= [ssh. AuthMethod_None, ssh. AuthMethod_Password],
805816 log_verbosity= ssh. SSH_LOG_NOLOG)
806817 if ssh. AuthMethod_Password in auth_methods && isnothing (password)
@@ -810,7 +821,7 @@ function DemoServer(port::Int; verbose::Bool=false, password::Union{String, Noth
810821 key = pki. generate (pki. KeyType_ed25519)
811822 bind = ssh. Bind (port; auth_methods, key, log_verbosity)
812823
813- demo_server = DemoServer (; bind, verbose, password)
824+ demo_server = DemoServer (; bind, verbose, password, allow_auth_none )
814825
815826 ssh. set_message_callback (on_message, bind, demo_server)
816827
893904
894905function _handle_client (session:: ssh.Session , ds:: DemoServer )
895906 client = Client (; id= length (ds. clients) + 1 ,
896- session,
897- password= ds. password,
898- verbose= ds. verbose)
907+ session,
908+ password= ds. password,
909+ allow_auth_none= ds. allow_auth_none,
910+ verbose= ds. verbose)
899911 server_callbacks = ServerCallbacks (client;
900912 on_auth_password= on_auth_password,
901913 on_auth_none= on_auth_none,
@@ -1016,49 +1028,114 @@ end
10161028
10171029# # Execute commands
10181030
1031+ function on_exec_channel_eof (session, sshchan, executor)
1032+ _add_log_event! (executor. client, :exec_channel_eof , true )
1033+ end
1034+
1035+ function on_exec_channel_close (session, sshchan, executor)
1036+ _add_log_event! (executor. client, :exec_channel_close , true )
1037+ end
1038+
1039+ function on_exec_channel_data (session, sshchan, data, is_stderr, executor)
1040+ _add_log_event! (executor. client, :exec_channel_data , length (data))
1041+
1042+ # Wait for the command to have been started and the pipe to have been opened
1043+ timedwait (10 ) do
1044+ try
1045+ isopen (executor. stdin )
1046+ catch
1047+ false
1048+ end
1049+ end
1050+
1051+ write (executor. stdin , data)
1052+ return length (data)
1053+ end
1054+
10191055function exec_command (executor)
10201056 sshchan = executor. sshchan
1021- cmd_stdout = IOBuffer ( )
1022- cmd_stderr = IOBuffer ( )
1057+ cmd_stdout = ChannelBuffer (sshchan, false )
1058+ cmd_stderr = ChannelBuffer (sshchan, true )
10231059
10241060 # Start the process and wait for it
10251061 cmd_str = join (Base. shell_split (executor. command), " " )
10261062 cmd = setenv (ignorestatus (` sh -c $(cmd_str) ` ), executor. env)
1027- proc = run (pipeline (cmd; stdout = cmd_stdout, stderr = cmd_stderr); wait= false )
1063+ proc = run (pipeline (cmd; stdin = executor . stdin , stdout = cmd_stdout, stderr = cmd_stderr); wait= false )
10281064 executor. process = proc
10291065 notify (executor. _started_event)
10301066 wait (proc)
10311067
1032- # Write the output to the channel. We first check if the channel is open in
1033- # case it's been killed suddenly in the meantime.
1034- if isopen (sshchan)
1035- write (sshchan, String (take! (cmd_stdout)))
1036- write (sshchan, String (take! (cmd_stderr)); stderr = true )
1068+ close (executor. stdin )
1069+ close (cmd_stdout)
1070+ close (cmd_stderr)
10371071
1038- # Clean up
1072+ # Clean up
1073+ if isopen (sshchan)
10391074 ssh. channel_request_send_exit_status (sshchan, proc. exitcode)
10401075 closewrite (sshchan)
10411076 end
10421077
10431078 close (sshchan)
10441079end
10451080
1081+ # This is a helper IO type that exists for the sole purpose of asynchronously
1082+ # forwarding output from commands back to the SshChannel. Other containers like
1083+ # IOBuffer aren't thread-safe and can't be used so this implements a minimal,
1084+ # thread-safe IO type based on Channels.
1085+ mutable struct ChannelBuffer <: IO
1086+ channel:: Channel{Vector{UInt8}}
1087+ sshchan:: ssh.SshChannel
1088+ is_stderr:: Bool
1089+ task:: Task
1090+
1091+ function ChannelBuffer (sshchan, is_stderr)
1092+ self = new (Channel {Vector{UInt8}} (), sshchan, is_stderr)
1093+ self. task = Threads. @spawn for data in self. channel
1094+ # Write the output to the channel. We first check if the channel is open in
1095+ # case it's been killed suddenly in the meantime.
1096+ if isopen (sshchan)
1097+ write (self. sshchan, data; stderr = self. is_stderr)
1098+ end
1099+ end
1100+ errormonitor (self. task)
1101+
1102+ return self
1103+ end
1104+ end
1105+
1106+ function Base. write (chbuf:: ChannelBuffer , data:: Vector{UInt8} )
1107+ put! (chbuf. channel, data)
1108+ return length (data)
1109+ end
1110+
1111+ function Base. close (chbuf:: ChannelBuffer )
1112+ close (chbuf. channel)
1113+ wait (chbuf. task)
1114+ end
1115+
10461116@kwdef mutable struct CommandExecutor
1117+ client:: Client
10471118 command:: String
10481119 sshchan:: ssh.SshChannel
10491120 env:: Dict{String, String}
10501121 task:: Union{Task, Nothing} = nothing
10511122 process:: Union{Base.Process, Nothing} = nothing
1123+ stdin :: Base.PipeEndpoint = Base. PipeEndpoint ()
10521124
10531125 _started_event:: Base.Event = Base. Event ()
10541126end
10551127
1056- function CommandExecutor (command:: String , sshchan:: ssh.SshChannel , env)
1128+ function CommandExecutor (client :: Client , command:: String , sshchan:: ssh.SshChannel , env)
10571129 if ! sshchan. owning
10581130 throw (ArgumentError (" The passed SshChannel is non-owning, CommandExecutor requires an owning SshChannel" ))
10591131 end
10601132
1061- executor = CommandExecutor (; command, sshchan, env)
1133+ executor = CommandExecutor (; client, command, sshchan, env)
1134+ callbacks = ChannelCallbacks (executor;
1135+ on_data= on_exec_channel_data,
1136+ on_eof= on_exec_channel_eof,
1137+ on_close= on_exec_channel_close)
1138+ ssh. set_channel_callbacks (sshchan, callbacks)
10621139
10631140 executor. task = Threads. @spawn try
10641141 exec_command (executor)
0 commit comments