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 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. 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 3. Add paragraph about initalization and finalization. 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.