-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
asyncssh server with a cmd2 application #648
Comments
Thinking more about it, I guess if the stdin and stdout of |
Making some more progress: Adapting the IO redirection example: import asyncio, asyncssh, subprocess, sys
async def handle_client(process: asyncssh.SSHServerProcess) -> None:
bc_proc = subprocess.Popen('./script.py', shell=True, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
await process.redirect(stdin=bc_proc.stdin, stdout=bc_proc.stdout,
stderr=bc_proc.stderr)
await process.stdout.drain()
process.exit(0)
async def start_server() -> None:
await asyncssh.listen('', 8022, server_host_keys=['ssh_host_key'],
authorized_client_keys='ssh_user_ca',
process_factory=handle_client)
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(start_server())
except (OSError, asyncssh.Error) as exc:
sys.exit('Error starting server: ' + str(exc))
loop.run_forever() where Also, I am not sure if there are better ways to do this than calling two python programs as binaries in a chain to get this working. |
I don't think this will be straightforward. The cmd2 module is not async-friendly. There is some discussion about this at https://cmd2.readthedocs.io/en/latest/examples/alternate_event_loops.html, but it doesn't really go into detail about how you'd change all of its synchronous calls which are doing I/O to be able to be fed from async functions. I saw one suggestion online about running each async operation using loop.run_until_complete(), but that definitely won't work with AsyncSSH. If you just want line input editing and history, AsyncSSH actually has the capability built into it, for precisely a case like this where you want to set up AsyncSSH as a server and accept multiple connections from clients, allowing each of them to enter input lines which get delivered to the application a line at a time (allowing line editing until the user hits ). History is supported by this as well. However, there's no "shell" here, so you'd still need to implement your own parser for the input which gets delivered. More info on AsyncSSH's line editing can be found at https://asyncssh.readthedocs.io/en/latest/#line-editing. While it might be possible to spin off a separate process and PTY for each connection, that would largely defeat the purpose of using AsyncSSH here. At that point, you'd probably be better off just creating an account on the target system which had a shell of the cmd2 script you want to provide access to, and just let people SSH into that account using the system's SSH server. |
Reading your redirection example, you probably need to disable the AsyncSSH line editing I mentioned if you want the data passed through a character at a time to the remote script, so it can do the line editing. Just add "line_editor=False" when starting up the SSH listener. |
Thanks @ronf I have added As you have mentioned its true that spawning two separate processes for every connected client is not very optimal. But with asyncssh in picture, I have an advantage of python based user management and not to tinker with system users. I also have one more additional requirement, is it possible to select which shell I need to launch when I connect? It's a bit weird requirement, where I have two separate programs to manage two separate aspects of the system. While those two will eventually be combined into one, till that happens, I need to access both via ssh. So when I ssh into the system if there is a means to choose which program I want to launch, then I can choose between them at startup. And once I exit one of the shell, I should be able to choose the other. |
Are you thinking of having AsyncSSH prompt the user about which script to run on session start, and then ask again later when that script exits? That should be possible, but you'll probably want to leave the line_editor enabled in that case to do the initial interaction with the user. Then, right before you set up the redirects, you'd disable it, and later when the script exited you'd undo the redirects and turn the editor back on before looping back to prompting the user again. |
Here's an example of what I think you were asking about: import asyncio, asyncssh, sys
async def handle_client(process: asyncssh.SSHServerProcess) -> None:
process.stdout.write('Choose script to run: \n\n 1. foo\n 2. bar\n')
while True:
process.stdout.write('\nScript to run: ')
line = await process.stdin.readline()
if line == '1\n':
script = 'echo foo'
elif line == '2\n':
script = 'echo bar'
elif line in ('', 'q\n'):
break
else:
process.stdout.write('Invalid option, try again.\n')
continue
process.channel.set_line_mode(False)
bc_proc = await asyncio.create_subprocess_shell(
script, shell=True, stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
await process.redirect(stdin=bc_proc.stdin, stdout=bc_proc.stdout,
stderr=bc_proc.stderr, send_eof=False,
recv_eof=False)
await process.stdout.drain()
await process.redirect(stdin=asyncssh.PIPE, stdout=asyncssh.PIPE,
stderr=asyncssh.PIPE, send_eof=False,
recv_eof=False)
process.channel.set_line_mode(True)
process.stdout.write('\nGoodbye!\n')
process.exit(0)
async def start_server() -> None:
await asyncssh.listen('', 8022, server_host_keys=['ssh_host_key'],
authorized_client_keys='ssh_user_ca',
process_factory=handle_client)
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(start_server())
except (OSError, asyncssh.Error) as exc:
sys.exit('Error starting server: ' + str(exc))
loop.run_forever() |
I think this will be good! Thankyou! |
Hello,
I have a cmd2 application which I want to serve via ssh. As I understand, cmd2 application expects that its running in a pty, and I can wrap the application in a pty and run, which works perfectly. Now how to embed this in asycssh event loop is what I am looking for:
Here is a simple cmd2 application which i am using as example:
If the above script is named
cmd01.py
, you can use this script to run the above in a pty:Running the program will give an output like:
Normal command line expectations like tab completion and bash like cli navigation (
ctrl-a
,ctrl-e
) etc work fine in this.From the documentation of pty:
In the sample program above,
read
is the master_read and sees all input from and to theshell
Now, how could such a program be invoked by asyncssh? I have search far and wide and could not get a way to start. I am sure some one must have done this because
cmd2
is a good way to build an interactive command line application and next obvious step is to distribute it over ssh.Thanks!
The text was updated successfully, but these errors were encountered: