2005-07-05 05:21:44 +04:00
|
|
|
The new libssh 0.2 API
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
Version 1
|
|
|
|
|
|
|
|
A. Introduction
|
|
|
|
---------------
|
|
|
|
|
|
|
|
With the time from the first release of libssh, I have received lots of
|
|
|
|
comments about the current API. Myself, I found it quite limiting when doing
|
|
|
|
my first libssh-server drafts. Thus, I am moving to a stronger API.
|
|
|
|
This API must still be simple. I am not introducing complex changes. An API
|
|
|
|
well designed must hide the implementation details. Implementation can change
|
|
|
|
easily within bugfixes - but API cannot change each release.
|
|
|
|
|
|
|
|
To the people already using libssh 0.11 : sorry. Once I have the complete API
|
|
|
|
redesigned, I will write a migration paper. It won't be too hard normally.
|
|
|
|
|
|
|
|
Here are the things that were lacking in the previous API and *must* change:
|
|
|
|
|
|
|
|
* A non-blocking mode connection type
|
|
|
|
* Functions to relegate File descriptor listening to Calling functions and to
|
|
|
|
the programmer. (I'll explain later).
|
|
|
|
* Along with that, good buffering system (well, it's not an API but).
|
|
|
|
* Leave the "functions returns a pointer when it works and NULL when it does
|
|
|
|
not work". It gives serious problems to implement bindings (A C++
|
|
|
|
constructor should not fail and should not depend on a network thing
|
|
|
|
* Make the Session structure an abstract structure that can work with both
|
|
|
|
client and *servers*. That mean we should have a Server object which listen
|
|
|
|
to clients on a bound port, does the different handshakes and return a
|
|
|
|
session.
|
|
|
|
Since C is not per se an Object language, I won't use inheritance between
|
|
|
|
objects.
|
|
|
|
* This same server thing must provide the reverse capabilities than the
|
|
|
|
client. That is, accept the handshake, in a nonblocking way. Accept channel
|
|
|
|
requests, or send them to the controller program.
|
|
|
|
* Support for program forking : Imagine you have a Ssh server object. You
|
|
|
|
accept a connection and receive a session, then you receive a channel. You
|
|
|
|
may want to keep the good old days fork() tricks. Libssh will give a way to
|
|
|
|
destroy handlers from sessions which belong to an other process without
|
|
|
|
disturbing the session.
|
|
|
|
* So often I received the comment back saying that it was not clear why a
|
|
|
|
session or a channel was terminated. This is over.
|
|
|
|
* And of course I received lot of mails about the fact I'm doing namespace
|
|
|
|
polution. this will be resolved this time.
|
|
|
|
So, please read this draft not as a formal documentation but like a roadmap of
|
|
|
|
things that each kind of object must do.
|
|
|
|
|
|
|
|
B. Description of objects and functions
|
|
|
|
|
2006-07-09 14:36:44 +04:00
|
|
|
Initialization and finalization
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
Initialization is for now called automatically, so you don't have to take care
|
|
|
|
of that.
|
|
|
|
As for finalization, we need to finalize the underlying cryptographic library
|
|
|
|
(either OpenSSL or libgcrypt). Be sure that you call ssh_finalize when this
|
|
|
|
library won't be used anymore, even by other libraries (i.e. if you use libssh
|
|
|
|
and another library that uses OpenSSL, call ssh_finalize when any function of
|
|
|
|
both these libraries won't be called).
|
|
|
|
If you trust your operating system to clean up the mess after a process
|
|
|
|
terminates, you can skip this call.
|
|
|
|
|
2005-07-05 05:21:44 +04:00
|
|
|
Options structure
|
|
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
struct ssh_options *ssh_options_new()
|
|
|
|
|
|
|
|
ssh_options_getopt(options, *argc, argv)
|
|
|
|
|
|
|
|
ssh_options_copy(options)
|
|
|
|
|
|
|
|
char ** ssh_options_get_supported_algos(options,type)
|
|
|
|
returns a list of the algos supported by libssh, type being one of
|
|
|
|
SSH_HOSTKEYS, SSH_KEX, SSH_CRYPT, SSH_MAC, SSH_COMP, SSH_LANG
|
|
|
|
|
|
|
|
ssh_options_set_wanted_algos(options,type, char *list)
|
|
|
|
list being comma-separated list of algos, and type being the upper constants
|
|
|
|
but with _C_S or _S_V added to them.
|
|
|
|
|
|
|
|
ssh_options_set_port(options, port)
|
|
|
|
|
|
|
|
ssh_options_set_host(options, host)
|
|
|
|
|
|
|
|
ssh_options_set_fd(options, fd)
|
|
|
|
|
|
|
|
ssh_options_set_bind(options, bindaddr, port)
|
|
|
|
this options sets the address to bind for a client *or* a server. a port of
|
|
|
|
zero means whatever port is free (what most clients want).
|
|
|
|
|
|
|
|
ssh_options_set_username(options, username)
|
|
|
|
|
|
|
|
ssh_options_set_connect_timeout(options, seconds, usec)
|
|
|
|
|
|
|
|
ssh_options_set_ssh_dir(options, dir)
|
|
|
|
ssh_options_set_known_hosts_file(options, file)
|
|
|
|
ssh_options_set_identity(options, file)
|
|
|
|
|
|
|
|
ssh_options_set_banner(options, banner)
|
|
|
|
ssh_options_allow_ssh1(options, bool allow)
|
|
|
|
ssh_options_allow_ssh2(options, bool allow)
|
|
|
|
|
|
|
|
options_set_status_callback has moved into ssh_* functions.
|
|
|
|
|
|
|
|
ssh_session Structure
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
This session structure represents a ssh socket to a server *or* a client.
|
|
|
|
|
|
|
|
ssh_session *ssh_new()
|
|
|
|
|
|
|
|
ssh_set_options(ssh_session,ssh_options)
|
|
|
|
|
|
|
|
ssh_connect(session);
|
|
|
|
it will return some status describing at which point of the connection it is,
|
|
|
|
or an error code. If the connection method is non-blocking, the function
|
|
|
|
will be called more than once, though the return value SSH_AGAIN.
|
|
|
|
|
|
|
|
ssh_set_blocking(session, bool blocking)
|
|
|
|
set blocking mode or non blocking mode.
|
|
|
|
|
|
|
|
ssh_get_fd(session)
|
|
|
|
get the currently used connection file descriptor or equivalent (windows)
|
|
|
|
|
|
|
|
ssh_set_fd_toread(session)
|
|
|
|
ssh_set_fd_towrite(session)
|
|
|
|
ssh_set_fd_except(session)
|
|
|
|
Serve to notify the library that data is actualy available to be read on the
|
|
|
|
file descriptor socket. why ? because on most platforms select can't be done
|
|
|
|
twice on the same socket when the first reported data to read or to write
|
|
|
|
|
|
|
|
ssh_get_status(session)
|
|
|
|
Returns the current status bitmask : connection Open or closed, data
|
|
|
|
pending to read or not (even if connection closed), connection closed on
|
|
|
|
error or on an exit message
|
|
|
|
|
|
|
|
ssh_get_disconnect_message(session)
|
|
|
|
Returns the connection disconnect error/exit message
|
|
|
|
|
|
|
|
ssh_get_pubkey_hash(session, hash)
|
|
|
|
get the public key hash from the server.
|
|
|
|
|
|
|
|
ssh_is_server_known(session)
|
|
|
|
ssh_write_knownhost(session)
|
|
|
|
these 2 functions will be kept
|
|
|
|
|
|
|
|
ssh_disconnect(session)
|
|
|
|
standard disconnect
|
|
|
|
|
|
|
|
ssh_disconnect_error(session,error code, message)
|
|
|
|
disconnect with a message
|
|
|
|
|
|
|
|
ssh_set_username(session)
|
|
|
|
set the user name to log in
|
|
|
|
|
|
|
|
ssh_userauth_* functions will be kept as they are now, excepted the fact that
|
|
|
|
the username field will disapear.
|
|
|
|
the public key mechanism may get some more functions, like retrieving a public
|
|
|
|
key from a private key and authenticating without a public key.
|
|
|
|
|
|
|
|
ssh_get_issue_banner(session)
|
|
|
|
get the issue banner from the server, that is the welcome message.
|
|
|
|
|
|
|
|
ssh_silent_free(session)
|
|
|
|
This function silently free all data structures used by the session and
|
|
|
|
closes the socket. It may be used for instance when the process forked and
|
|
|
|
doesn't want to keep track of this session. This is obviously not possible to
|
|
|
|
do with separate channels.
|
|
|
|
|
|
|
|
The channel_struct structure
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
The channels will change a bit. the constructor thing will change, and the way
|
|
|
|
to multiplex different connections will change too. channel functions will be
|
|
|
|
prefixed with "ssh_"
|
|
|
|
|
|
|
|
struct channel_struct *ssh_channel_new()
|
|
|
|
|
|
|
|
ssh_channel_open_session(channel)
|
|
|
|
will return if the channel allocation failed or not.
|
|
|
|
|
|
|
|
ssh_channel_open_forward(channel, ...) won't change. it will report an error if
|
|
|
|
the channel allocation failed.
|
|
|
|
|
|
|
|
ssh_channel_send_eof(channel)
|
|
|
|
send EOF
|
|
|
|
ssh_channel_close(channel)
|
|
|
|
closes a channel but doesn't destroy it. you may read unread data still in
|
|
|
|
the buffer. Once you closed the buffer, the other party can't send you data,
|
|
|
|
while it could still do it if you only sent an EOF.
|
|
|
|
ssh_channel_is_closed(channel)
|
|
|
|
returns true if the channel was closed at one of both sides. a closed chan
|
|
|
|
may still have data to read, if you closed yourself the connection. otherwise
|
|
|
|
(you didn't close it) the closed notification only comes when you read the
|
|
|
|
last buffer byte, or when trying to write into the channel (the SIGPIPE-like
|
|
|
|
behaviour).
|
|
|
|
|
|
|
|
ssh_channel_is_eof(channel)
|
|
|
|
reports if the other side has sent an EOF. This functions returns FALSE if
|
|
|
|
there is still data to read. A closed channel is always EOF.
|
|
|
|
ssh_channel_free(channel)
|
|
|
|
completely free the channel. closes it before if it was not done.
|
|
|
|
|
|
|
|
ssh_channel_request_env(channel, name, value)
|
|
|
|
set an environment variable.
|
|
|
|
|
|
|
|
ssh_channel_request_pty(channel)
|
|
|
|
ssh_channel_request_pty_size()
|
|
|
|
ssh_channel_change_pty_size()
|
|
|
|
ssh_channel_request_shell()
|
|
|
|
ssh_channel_request_exec()
|
|
|
|
ssh_channel_request_subsystem()
|
|
|
|
These functions won't change.
|
|
|
|
|
|
|
|
int ssh_channel_write(channel,data, len,stderr)
|
|
|
|
Depending on the blocking/non blocking mode of the channel, the behaviour may
|
|
|
|
change.
|
|
|
|
stderr is the extended buffer. It's generaly only a server->client stream.
|
|
|
|
|
|
|
|
ssh_channel_set_blocking(bool blocking)
|
|
|
|
|
|
|
|
int ssh_channel_read(channel, buffer, maxlen, is_stderr)
|
|
|
|
the behaviour will be this one:
|
|
|
|
-if the chan is in non blocking mode, it will poll what's available to read
|
|
|
|
and return this. otherwise (nothing to read) it will return 0.
|
|
|
|
-if the chan is blocking, it will block until at least one byte is
|
|
|
|
available.
|
|
|
|
ssh_channel_nonblocking disapears for the later reason.
|
|
|
|
|
|
|
|
int channel_poll(channel, is_stderr)
|
|
|
|
polls the network and reports the number of bytes ready to be read in the
|
|
|
|
chan.
|
|
|
|
|
|
|
|
ssh_session ssh_channel_get_session(channel)
|
|
|
|
returns the session pointer associated to the channel, for simplicity
|
|
|
|
reasons.
|
|
|
|
|
|
|
|
int ssh_channel_select(CHANNELS *readchans, CHANNELS *writechans, CHANNELS
|
|
|
|
*exceptchans, struct timeval *timeout)
|
|
|
|
This function won't work the same way ssh_select did.
|
|
|
|
I removed the custom file descriptor thing for 2 reasons:
|
|
|
|
1- it's not windows compliant. D'ouh !
|
|
|
|
2- most programmers won't want to depend on libssh for socket multiplexing.
|
|
|
|
that's why i let the programmer poll the fds himself and then use
|
|
|
|
ssh_set_fd_toread, towrite or except. Then, he may use ssh_channel_select
|
|
|
|
with a NULL timeout to poll which channels have something to read, write or
|
|
|
|
error report.
|
|
|
|
Here is how it's going to work. The coder sets 3 different arrays with the
|
|
|
|
channels he wants to select(), the last entry being a NULL pointer. The
|
|
|
|
function will first poll them and return the chans that must be
|
|
|
|
read/write/excepted. If nothing has this state, the function will select()
|
|
|
|
using the timeout.
|
|
|
|
The function will return 0 if everything is ok, SSH_TIMEOUT or SSH_EINTR if
|
|
|
|
the select was interrupted by a signal. It is dangerous to execute any
|
|
|
|
channel-related functions into signal handlers. they should set a flag that
|
|
|
|
you read into your loop. this "trap" (SSH_EINTR) will permit you to catch
|
|
|
|
them faster and make your program responsive and look fast.
|
|
|
|
the function will return -1 if a serious problem happens.
|
|
|
|
|
|
|
|
|
|
|
|
Error handling
|
|
|
|
~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
when an error happens, the programmer can get the error code and description
|
|
|
|
with ssh_get_error(session). the creation of a failess constructor for
|
|
|
|
ssh_session was needed for this reason.
|
|
|
|
|
|
|
|
ssh_get_error_code(session) will return an error code into this subset:
|
|
|
|
SSH_NO_ERROR : no error :)
|
|
|
|
SSH_REQUEST_DENIED : you request for a functionality or a service that is not
|
|
|
|
allowed. The session can continue.
|
|
|
|
SSH_FATAL : Unrecoverable error. The session can't continue and you should
|
|
|
|
disconnect the session. It includes the connection being cut without a
|
|
|
|
disconnect() message.
|
|
|
|
If a disconnect() message or the channel was closed, a read on such a channel
|
|
|
|
won't produce an error. otherwise it will return -1 with a SSH_FATAL error
|
|
|
|
code.
|
|
|
|
|
|
|
|
Server socket binding
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
It is not possible to bind a socket for ssh with a SSH_SESSION type, because a
|
|
|
|
single bound port may lead to multiple ssh connections. That's why the
|
|
|
|
SSH_BIND structure must be created. It uses options from the SSH_OPTIONS
|
|
|
|
structure.
|
|
|
|
|
|
|
|
SSH_BIND *ssh_bind_new()
|
|
|
|
creates a structure
|
|
|
|
ssh_bind_set_options(bind, options)
|
|
|
|
set the option structure
|
|
|
|
int ssh_bind_listen(bind)
|
|
|
|
bind and listen to the port. This call is not blocking. if some error
|
|
|
|
happens, it returns -1 and the error code can be found with perror().
|
|
|
|
|
|
|
|
ssh_bind_set_blocking(bind, bool blocking)
|
|
|
|
should ssh_bind_accept() block or not.
|
|
|
|
|
|
|
|
int ssh_bind_get_fd(bind)
|
|
|
|
return the bound file descriptor, that is the listener socket. you may put it
|
|
|
|
into a select() in your code to detect a connection attempt.
|
|
|
|
|
|
|
|
ssh_bind_set_fd_toaccept(bind)
|
|
|
|
say that the listener socket has a connection to accept (to avoid
|
|
|
|
ssh_bind_accept() to do a select on it).
|
|
|
|
|
|
|
|
SSH_SESSION *ssh_bind_accept(bind)
|
|
|
|
return a server handle to a ssh session. if the mode is blocking, the
|
|
|
|
function will always return a pointer to a session. if the mode is not
|
|
|
|
blocking, the function can return NULL if there is no connection to accept.
|
|
|
|
|
|
|
|
This SSH_SESSION handle must then pass through the functions explained above.
|
|
|
|
|
|
|
|
|
|
|
|
*server functions *
|
|
|
|
|
|
|
|
int ssh_accept(session)
|
|
|
|
when a new connection is accepted, the handshake must be done. this function
|
|
|
|
will do the banner handshake and the key exchange.
|
|
|
|
it will return SSH_AGAIN if the session mode is non blocking, and the
|
|
|
|
function must be called again until an error occurs or the kex is done.
|
|
|
|
|
|
|
|
Here, I had a few choises about *how* to implement the message parsing as a
|
|
|
|
server. There are multiple ways to do it, one being callbacks and one being
|
|
|
|
"Message" reading, parsing and then choice going to the user to use it and
|
|
|
|
answer. I've choosen the latter because i believe it's the stronger method.
|
|
|
|
A ssh server can receive 30 different kind of messages having to be dealt by
|
|
|
|
the high level routines, like channel request_shell or authentication. Having
|
|
|
|
a callback for all of them would produce a huge kludge of callbacks, with
|
|
|
|
no relations on when there were called etc.
|
|
|
|
A message based parsing allows the user to filtrate the messages he's
|
|
|
|
interested into and to use a default answer for the others. Then, the callback
|
|
|
|
thing is still possible to handle through a simple message code/callback
|
|
|
|
function array.
|
|
|
|
|
|
|
|
I did not define yet what it would look like, but i'm sure there will be a
|
|
|
|
SSH_MESSAGE (they won't have a 1/1 correspondance with ssh packets) which will
|
|
|
|
be read through
|
|
|
|
SSH_MESSAGE *ssh_server_read_message(session).
|
|
|
|
with all of the non-blocking stuff in head like returning NULL if the message
|
|
|
|
is not full.
|
|
|
|
Then, the message can be parsed, ie
|
|
|
|
int ssh_message_get_code(message)
|
|
|
|
which will return SSH_MESSAGE_AUTH
|
|
|
|
then
|
|
|
|
int ssh_message_get_subcode(message)
|
|
|
|
which then will returh SSH_MESSAGE_AUTH_PASSWORD or _NONE or _PUBKEY etc.
|
|
|
|
|
|
|
|
Then, once the message was parsed, the message will have to be answered, ie
|
|
|
|
with the generic functions like
|
|
|
|
ssh_message_accept(message) which says 'Ok your request is accepted' or
|
|
|
|
ssh_message_deny(message) which says 'Your request is refused'.
|
|
|
|
|
|
|
|
There would be specific message answer functions for some kind of messages
|
|
|
|
like the authentication one. you may want to reply that the authentication is
|
|
|
|
Partial rather than denied, and that you still accept some kind of auths, like
|
|
|
|
ssh_message_auth_reply(message,SSH_AUTH_PARTIAL,SSH_AUTH_PASSWORD |
|
|
|
|
SSH_AUTH_PUBKEY | SSH_AUTH_KEYBINT);
|
|
|
|
|
|
|
|
I won't let the user have to deal with the channels himself. When a channel is
|
|
|
|
going to be created by the remote size, a message will come asking to open a
|
|
|
|
channel. the programmer can either deny or accept, in which case a CHANNEL
|
|
|
|
object will be created and returned to the programmer. then, all standard
|
|
|
|
channel functions will run.
|
|
|
|
|
|
|
|
C. Change log of this document
|
|
|
|
|
2006-07-09 14:36:44 +04:00
|
|
|
3. Add paragraph about initalization and finalization.
|
|
|
|
|
2005-07-05 05:21:44 +04:00
|
|
|
2. ssh_options_set_username finaly is kept into the options, because it can be
|
|
|
|
set by ssh_options_getopt()
|
|
|
|
|
|
|
|
1. first release
|
|
|
|
|
|
|
|
D. End notes
|
|
|
|
|
|
|
|
I think libssh must have a very simple to use, powerful and exhaustive API. It
|
|
|
|
must have no design flaw either.
|
|
|
|
While I got some good experience at the SSH protocol, I've never writen
|
|
|
|
more-than-100 lines programs than use libssh and I don't really know the
|
|
|
|
problems of the library. I'd like people who don't understand some detail into
|
|
|
|
the API I describe here, who have comments or opinions about it to write me
|
|
|
|
the soonest possible to limit the damages if I made something the completely
|
|
|
|
wrong way.
|
|
|
|
Thanks for your patience.
|
|
|
|
|