use bytes;
use errors;
use io;
use os;
use strings;
// The maximum size of an ordinary SSH packet. Some packets may be larger. This
// is the maximum amount of memory which is dynamically allocated per client by
// the implementation.
export def MAX_PACKETSIZE: size = 35000;
export type client = struct {
conn: io::handle,
rbuf: []u8,
rptr: size,
};
const protoversion: str = "SSH-2.0-hare::ssh-0.0\r\n";
// Creates a new SSH client. When done using the client, the caller should call
// [[client_finish]] to free associated resources.
//
// This function will transmit the initial protocol version string before
// returning. The caller should call [[client_verexch]] to process the server's
// version details, then may use [[client_read]] and [[client_write]] to receive
// or transmit SSH packets.
export fn newclient(conn: io::handle) (client | io::error) = {
io::writeall(conn, strings::toutf8(protoversion))?;
return client {
conn = conn,
rbuf = alloc([], os::BUFSIZ),
rptr = 0z,
};
};
// Frees resources associated with this client. Closes the underlying
// connection.
export fn client_finish(client: *client) void = {
io::close(client.conn)!;
free(client.rbuf);
};
// Completes the "Protocol Version Exchange" process by receiving and processing
// verison data from the server. This function returns the version string to the
// caller, which is borrowed from the client state and will be overwritten on
// subsequent calls to [[net::ssh]] functions. If the underlying connection is
// non-blocking, this function may return [[errors::again]], in which case it
// should be called again once the connection is readable.
//
// If the server's protocol version is not supported, [[unsupported]] is
// returned.
export fn client_verexch(
client: *client,
) (const str | error) = {
let buf: [os::BUFSIZ]u8 = [0...];
const z = match (io::read(client.conn, buf)?) {
case let z: size =>
yield z;
case io::EOF =>
return protoerror;
};
append(client.rbuf, buf[..z]...);
const i = match (bytes::index(client.rbuf, ['\r', '\n'])) {
case void =>
if (len(client.rbuf) >= 255) {
return protoerror;
} else {
return errors::again;
};
case let i: size =>
yield i;
};
const version = client.rbuf[..i];
client.rptr = i + 2;
const version = match (strings::try_fromutf8(version)) {
case let s: str =>
yield s;
case =>
return protoerror;
};
const tok = strings::tokenize(version, "-");
const ssh = toknext(&tok)?;
const protover = toknext(&tok)?;
// XXX: We could validate the rest of the version string matches the
// spec here if I cared
if (ssh != "SSH") {
return protoerror;
};
if (protover != "2.0") {
return unsupported;
};
return version;
};
fn toknext(tok: *strings::tokenizer) (str | protoerror) = {
match (strings::next_token(tok)) {
case void =>
return protoerror;
case let token: str =>
return token;
};
};