I work with a proprietary application that runs like a system service and has no user interface. However, there are a number of performance-related parameters and status values that are useful to inspect and possibly modify at run-time to perform diagnostics or help create particular test conditions. In this applications environment, low-security interfaces are, shall we say, frowned upon. And we did not have the resources to implement a full-blown HTML interface using HTTPS or anything like that. So recalling the simple TELNET command-line interfaces that some devices use, I thought that would make a good option.
The first version made use of a very handy library called Libcli created by David Parrish. This library is easily included with your application and takes care of setting up the socket and handling the whole TELNET part. You register as many different commands as you want, each mapping to a callback function that you provide. The library takes care of parsing the user input and invoking the registered callback. From the callback you can do whatever you want and print output that goes back to the client side for display.
That was great until the security folks said, "hey, you can't run a TELNET service". They were not willing to draw a distinction between a TELNET service allowing a user login to the system and an application allowing access to some diagnostic information. Their network scanner just saw "something" respond positively to a TELNET request. Compliance check failed. End of story.
So we had to disable it in deployed configurations and can only use it in the test lab. So my next logical thought was using SSH instead of TELNET for the authentication and transport but still let Libcli handle all the command processing after that. I didn't want to have to reverse-engineer the OpenSSH code to pull out what I needed. Had anyone written a library that made implementing the SSH protocol simple for embedding in other applications? Of course they have. I'm not the first person to think of any of this.
I found two obvious candidates, libssh and libssh2. So which one should I try first? The libssh2 folks already did my homework for me with this comparison chart of their library vs libssh. This made the decision very quick because libssh2 does not have server-side functionality, where libssh does. Done.
libssh takes care of almost every necessary SSH function for you. It sets up the socket, listens for connections, handles authentication (using host keys you provide), lets you set up a pty and open a secure channel. After that, you have a secure "pipe" to read/write data, whatever that data might be. That part is your business.
So the next question was how to meld the two libraries together? I wanted to use libssh to do all the SSH part but still let Libcli handle all the command processing over the secure channel. In order to read/write the secure channel you use the ssh_channel_read() and ssh_channel_write() functions. Other than the channel parameter, you just give them a pointer to your buffer and length and its just like a plain read()/write() as far as you're concerned. Now in Libcli, once you set up all your commands, you normally just call the cli_loop() function. But in this use-case, we cannot do that because cli_loop() encapsulates too much stuff we don't want. It handles accepting socket connections, all the TELNET stuff and then executing the appropriate callback function. We just need to parse the command and invoke the callback. Most of that is encapsulated in cli_run_command().
So that just leaves our code with the duty of processing the raw user input from the secure channel and assembling a line (until 'enter') and passing it to cli_run_command(). But how will output from the callback functions get back to the client side? All output from Libcli callback functions is done by calling cli_print(). Since that's part of the library, it has direct access to the file descriptor of the socket and sends your output back to the client for you. But in our case, we have to call ssh_channel_write(). Fortunately, Libcli has foreseen this type of situation and provided cli_print_callback() which, if set, will be invoked from cli_print() instead of trying to write to the internal file descriptor (which is NULL because we didn't use cli_loop() to accept a connection). So the command callback invokes the print function which calls my callback with the output text. Perfect. My print callback just passes the buffer on to ssh_channel_write().
So that's it in a nutshell. As my initial proof-of-concept demonstration, I simply modified one of the examples provided with libssh. Note that this uses the release candidate version 0.6.0 of libssh. The current stable release (0.5.5) does not have as many examples of SSH server code.
sshcli.c
A single, trivial command of "show stuff" is set up with Libcli and then the rest of the modification is in the do loop that reads from the channel. To build it, just compile with gcc and link with both Libcli and libssh.
gcc sshcli.c -o sshcli -lcli -lsshTo run it, you'll need to provide SSH host keys. I just copied the ones on my system. Also specify which port to listen on. The default port 22 is taken by your system's sshd service, of course. Your application will be listening on something else.
sshcli -d host_dsa_key -r host_rsa_key -p 2000 127.0.0.1Then from another shell, just connect with ssh. The example program uses password authentication with username "user" and password "password".
ssh -p 2000 user@127.0.0.1After authenticating, you'll get the welcome banner and the prompt. Note that I had to implement those too because they are normally taken care of by cli_loop(). Then start typing commands. Done.
This post maps to CompTIA SY0-301 exam objective 1.4.
Hi! sshcli.c is unavailable. Can you upload it again?
ReplyDeleteI would be glad to but I can't figure out how to put it back or upload a new file here. Have not used this in 9 years and they have changed things.
ReplyDeleteCan you send it on my email: 4[dot]shket[at]gmail[dot]com?
Delete