diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fc0e19c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.acton.lock
+.build
+out
+zig-cache
+zig-out
diff --git a/src/ssh.act b/src/ssh.act
old mode 100644
new mode 100755
index a8b0268..3b8e6b4
--- a/src/ssh.act
+++ b/src/ssh.act
@@ -1,12 +1,10 @@
import net
-import testing
+
def version() -> str:
- """Get the libssh version"""
+ """Get the acton-ssh version"""
NotImplemented
-def _test_version():
- testing.assertEqual("0.11.0", version())
actor Client(cap: net.TCPConnectCap,
host: str,
@@ -14,19 +12,34 @@ actor Client(cap: net.TCPConnectCap,
on_connect: action(Client) -> None,
on_close: action(Client, str) -> None,
key: ?str=None,
- password: ?str=None,
+ password: str,
port: u16=22,
+ subsystem: str
):
"""SSH Client"""
+ proc def _pin_affinity() -> None:
+ NotImplemented
+ _pin_affinity()
+
# haha, this is really a pointer :P
var _ssh_session: u64 = 0
+ def get_ssh_session():
+ return _ssh_session
+
+ def get_password() -> str:
+ return password
+
+ def get_subsystem() -> str:
+ return subsystem
+
proc def _init() -> None:
"""Initialize the SSH client"""
NotImplemented
_init()
- print("SSH Client connected")
+
+ # print("SSH Client connected")
# action def close(on_close: action(TLSConnection) -> None) -> None:
# """Close the connection"""
@@ -37,7 +50,11 @@ actor Client(cap: net.TCPConnectCap,
#
# def _connect(c):
# NotImplemented
-#
+
+ proc def disconnect() -> None:
+ """Disconnect the SSH client"""
+ NotImplemented
+
# TODO: implement support for channels
# AFAIK, all things over ssh are done via channels, so need some channel
@@ -45,18 +62,46 @@ actor Client(cap: net.TCPConnectCap,
# session? Prolly need some higher level wrappers for common things like
# starting a shell or running a single command. SFTP / SCP would be nice too,
# but for sometime in the future. Custom subsystems need to be supported too.
+actor Channel(client: Client):
+ """SSH Channel"""
+
+ var _ssh_channel: u64 = 0
+ var _ssh_session: u64 = client.get_ssh_session()
+ var _password: str = client.get_password()
+ var _subsystem: str = client.get_subsystem()
+ var payload = None
+
+ proc def _pin_affinity() -> None:
+ NotImplemented
+ _pin_affinity()
+ proc def _init() -> None:
+ """Initialize the SSH Channel"""
+ NotImplemented
+ _init()
+ # print("SSH Channel created")
+
+ def setPayload(p: str):
+ payload = p
+
+ def getPayload():
+ return payload
+
+ def sendNCPayload() -> str:
+ """Send payload"""
+ NotImplemented
actor main(env):
def on_connect(client: Client):
- print("Connected")
+ # print("Client connected")
+ return
def on_close(client: Client, error: str):
- print("Error", error)
+ # print("Connection closed", error)
+ return
- print(version())
c = Client(
net.TCPConnectCap(net.TCPCap(net.NetCap(env.cap))),
"localhost",
@@ -64,6 +109,22 @@ actor main(env):
on_connect,
on_close,
password="bar",
- port=2223,
+ port=830,
+ subsystem="netconf",
)
+
+ cc = Channel(c)
+
+ # get netconf server's capabilities
+ cc.setPayload(']]>]]>')
+ print("\n\npayload 1:\n", cc.getPayload(), "\n\npayload 1 end")
+ print("\nNC response 1:\n", cc.sendNCPayload(), "\n\nNC response 1 end \n\n")
+
+ # TODO do this in disconnect(): gracefully close a channel
+ cc.setPayload(']]>]]>')
+ print("\n\npayload 2:\n", cc.getPayload(), "\n\npayload 2 end")
+ print("\nNC response 2:\n", cc.sendNCPayload(), "\n\nNC response 2 end \n\n")
+
+ c.disconnect()
+
env.exit(0)
diff --git a/src/ssh.ext.c b/src/ssh.ext.c
index 2112598..feeffcf 100644
--- a/src/ssh.ext.c
+++ b/src/ssh.ext.c
@@ -1,6 +1,52 @@
+#include
#include
-#include
-// TODO: figure out how to include rts/log so we get access to log_error etc
+#include
+#include
+#include
+// acton includes
+#include
+
+#ifndef DEBUG_MODE
+// #define DEBUG_MODE /* uncomment for pretty prints */
+#endif
+
+// NETCONF message
+#define NETCONF_HELLO_MSG \
+ "\n" \
+ "\n" \
+ " \n" \
+ " urn:ietf:params:netconf:base:1.0\n" \
+ " \n" \
+ "]]>]]>"
+
+// NETCONF message
+#define NETCONF_CLOSE_SESSION_MSG \
+ "\n" \
+ "\n" \
+ " \n" \
+ "]]>]]>"
+
+#define BUF_SIZE 65536 // this will definitely not be enough, find a better way. maybe chunks like libyang does?
+#define TIMEOUT 1500000 // microseconds: 1.5 seconds
+#define USLEEP_INTERVAL 5000 // microseconds: 0.005 seconds
+
+void ssh_channel_close_free(ssh_channel channel) {
+ int err = ssh_channel_close(channel);
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_channel_close() error (%d)\n", __FUNCTION__, err);
+ }
+ ssh_channel_free(channel);
+}
+
+void ssh_channel_close_free_eof(ssh_channel channel) {
+ int err = ssh_channel_send_eof(channel);
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_channel_send_eof() error (%d)\n", __FUNCTION__, err);
+ }
+ ssh_channel_close_free(channel);
+}
void noop_free(void *ptr) {
}
@@ -11,110 +57,308 @@ void sshQ___ext_init__() {
// All things related to buffers for receiving data and similarly would have
// to be allocated on the GC-heap though since that data is passed outside
// of the SSH actor
- libssh_replace_allocator(
- acton_gc_malloc,
- acton_gc_realloc,
- acton_gc_calloc,
- noop_free,
- acton_gc_strdup,
- acton_gc_strndup);
+ // libssh_replace_allocator(
+ // acton_gc_malloc,
+ // acton_gc_realloc,
+ // acton_gc_calloc,
+ // noop_free,
+ // acton_gc_strdup,
+ // acton_gc_strndup);
int r = ssh_init();
- printf("SSH extension initialized %d\n", r);
+ if (r != SSH_OK)
+ printf("SSH init failed (%d)\n", r);
+#ifdef DEBUG_MODE
+ else
+ printf("SSH extension successfully initialized\n");
+#endif
}
-B_str sshQ_version () {
- if (LIBSSH_VERSION_MINOR != 11)
- return to$str("invalid");
- return to$str("0.11.0");
+B_str sshQ_version() {
+ return to$str("0.1.0");
}
-// TODO: crap function for test, to be replaced with something
-int show_remote_processes(ssh_session session)
+$R sshQ_ClientD__pin_affinityG_local (sshQ_Client self, $Cont c$cont) {
+ pin_actor_affinity();
+ return $R_CONT(c$cont, B_None);
+}
+
+$R sshQ_ChannelD__pin_affinityG_local (sshQ_Channel self, $Cont c$cont) {
+ pin_actor_affinity();
+ return $R_CONT(c$cont, B_None);
+}
+
+/**
+ * @brief Send netconf payload
+ *
+ * @param[in] channel ssh_channel
+ * @param[in] payload The Netconf Payload, for example the hello message
+ * @param[in,out] response response will not be returned if NULL
+ * @param[in] response_len buffer length
+ */
+int send_nc_payload(ssh_channel channel, const char *payload, char *response, size_t response_len)
{
- ssh_channel channel;
- int rc;
- char buffer[256];
- int nbytes;
+ int err = 0;
+ int nbytes = 0;
+ int len = 0;
+ size_t buflen = 0;
+ char tmp[1024] = {0};
- channel = ssh_channel_new(session);
- if (channel == NULL)
- return SSH_ERROR;
+ err = ssh_channel_write(channel, payload, (uint32_t)strlen(payload));
+ if (err == SSH_ERROR)
+ {
+ printf("%s: ssh_channel_write() error (%d)\n", __FUNCTION__, err);
+ goto error;
+ }
- rc = ssh_channel_open_session(channel);
- if (rc != SSH_OK)
- {
- ssh_channel_free(channel);
- return rc;
- }
+ nbytes = ssh_channel_read(channel, tmp, sizeof(tmp)-1, 0);
+ while (nbytes > 0)
+ {
+ // sometimes the string tmp has invalid characters from the 1024th element
+ // and the strlen reports more than what the sizeof() is
+ if (strlen(tmp) > sizeof(tmp)) {
+ tmp[sizeof(tmp)-1] = '\0';
+ }
- rc = ssh_channel_request_exec(channel, "ps aux");
- if (rc != SSH_OK)
- {
- ssh_channel_close(channel);
- ssh_channel_free(channel);
- return rc;
- }
+ if (response) {
+ // append string
+ len = snprintf(response + buflen, response_len - buflen, "%s", tmp);
+ buflen = strlen(response);
+
+ if (len > BUF_SIZE)
+ {
+ printf("%s: snprintf() error %lu\n", __FUNCTION__, buflen);
+ goto error;
+ }
+ }
+#ifdef DEBUG_MODE
+ if (write(STDOUT_FILENO, tmp, (size_t)nbytes) != (ssize_t) nbytes)
+ {
+ printf("%s: write() error (bytes written not matching expectation)\n", __FUNCTION__);
+ goto error;
+ }
+ fflush(stdout);
+#endif
+ // find end of netconf reply
+ if (strstr(tmp, "]]>]]>")) {
+ return SSH_OK;
+ }
+ memset(tmp, 0, sizeof(tmp));
+ nbytes = ssh_channel_read(channel, tmp, sizeof(tmp), 0);
+ }
- nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
- while (nbytes > 0)
- {
- if (write(1, buffer, nbytes) != (unsigned int) nbytes)
+ if (nbytes < 0)
{
- ssh_channel_close(channel);
- ssh_channel_free(channel);
- return SSH_ERROR;
+ printf("%s: ssh_channel_read() error (%d)\n", __FUNCTION__, errno);
+ goto error;
}
- nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0);
- }
- if (nbytes < 0)
- {
- ssh_channel_close(channel);
- ssh_channel_free(channel);
+ return SSH_OK;
+error:
+ ssh_channel_close_free(channel);
return SSH_ERROR;
- }
+}
+
+/**
+ * @brief Set subsystem
+ *
+ * @param[in] channel ssh_channel
+ * @param[in] subsystem The subsystem, for example "netconf"
+ */
+ int set_subsystem (ssh_channel channel, const char *subsystem) {
+ int err = 0;
+ int timeout = TIMEOUT;
- ssh_channel_send_eof(channel);
- ssh_channel_close(channel);
- ssh_channel_free(channel);
+ if (channel == NULL) {
+ printf("%s: channel is NULL\n", __FUNCTION__);
+ return SSH_ERROR;
+ }
+
+ if (subsystem && !strcmp(subsystem, "netconf")) {
+ while ((err = ssh_channel_request_subsystem(channel, "netconf")) == SSH_AGAIN && timeout > 0)
+ {
+ err = usleep(USLEEP_INTERVAL);
+ if (err) {
+ printf("%s: usleep() error '%s' (%d)\n", __FUNCTION__, strerror(errno), errno);
+ return SSH_ERROR;
+ }
+ timeout -= USLEEP_INTERVAL;
+ }
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_channel_request_subsystem() Error setting SSH subsystem 'netconf': %d\n", __FUNCTION__, err);
+ return SSH_ERROR;
+ }
+ }
- return SSH_OK;
+ return SSH_OK;
}
+// Client
+
$R sshQ_ClientD__initG_local (sshQ_Client self, $Cont c$cont) {
+ pin_actor_affinity();
+
+ int err = 0;
ssh_session session = ssh_new();
- if (session == NULL) {
- //log_error("Failed to create SSH session");
+ if (session == NULL)
+ {
+ printf("%s: ssh_new() Failed to create SSH session\n", __FUNCTION__);
return $R_CONT(c$cont, B_None);
}
- printf("session: %p\n", session);
+
self->_ssh_session = toB_u64((unsigned long)session);
- printf("init self->session: %p\n", self->_ssh_session);
-
- ssh_options_set(session, SSH_OPTIONS_HOST, fromB_str(self->host));
- ssh_options_set(session, SSH_OPTIONS_PORT, &self->port->val);
- ssh_options_set(session, SSH_OPTIONS_USER, fromB_str(self->username));
-
- ssh_set_blocking(session, 1);
- printf("Connecting to \n");
- int rc = ssh_connect(session);
- if (rc != SSH_OK) {
- //log_error("Error connecting to SSH server: %s", ssh_get_error(session));
- $action2 f = ($action2) self->on_close;
- f->$class->__asyn__(f, self, to$str(ssh_get_error(session)));
+
+#ifdef DEBUG_MODE
+ // available: SSH_LOG_NOLOG, SSH_LOG_WARNING, SSH_LOG_PROTOCOL, SSH_LOG_PACKET, SSH_LOG_FUNCTIONS
+ err = ssh_set_log_level(SSH_LOG_FUNCTIONS);
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_set_log_level() Error setting log level: %d\n", __FUNCTION__, err);
+ return $R_CONT(c$cont, B_None);
+ }
+#endif
+
+ err = ssh_session_set_disconnect_message(session, "Disconnecting SSH, powered by Acton");
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_session_set_disconnect_message() Error setting disconnect message: %d\n", __FUNCTION__, err);
+ return $R_CONT(c$cont, B_None);
+ }
+
+ err = ssh_options_set(session, SSH_OPTIONS_HOST, fromB_str(self->host));
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_options_set() Error setting SSH option 'SSH_OPTIONS_HOST': %d\n", __FUNCTION__, err);
+ return $R_CONT(c$cont, B_None);
+ }
+
+ err = ssh_options_set(session, SSH_OPTIONS_PORT, &self->port->val);
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_options_set() Error setting SSH option 'SSH_OPTIONS_PORT': %d\n", __FUNCTION__, err);
+ return $R_CONT(c$cont, B_None);
+ }
+
+ err = ssh_options_set(session, SSH_OPTIONS_USER, fromB_str(self->username));
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_options_set() Error setting SSH option 'SSH_OPTIONS_USER': %d\n", __FUNCTION__, err);
+ return $R_CONT(c$cont, B_None);
+ }
+
+ // should it auto-parse user config? for example from /home/user/.ssh/
+ // err = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, "0");
+ // if (err != SSH_OK)
+ // {
+ // printf("%s: ssh_options_set() Error setting SSH option 'SSH_OPTIONS_PROCESS_CONFIG': %d\n", __FUNCTION__, err);
+ // return $R_CONT(c$cont, B_None);
+ // }
+
+ err = ssh_connect(session);
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_connect() Error connecting to SSH server: %s\n", __FUNCTION__, ssh_get_error(session));
+ return $R_CONT(c$cont, B_None);
+ }
+
+ err = ssh_userauth_password(session, NULL, (const char *)fromB_str(self->password));
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_userauth_password() error: %s\n", __FUNCTION__, ssh_get_error(session));
+ return $R_CONT(c$cont, B_None);
+ }
+
+ $action f = ($action) self->on_connect;
+ f->$class->__asyn__(f, self);
+
+ return $R_CONT(c$cont, B_None);
+}
+
+$R sshQ_ClientD_disconnectG_local (sshQ_Client self, $Cont c$cont) {
+ ssh_disconnect((ssh_session)fromB_u64(self->_ssh_session));
+ ssh_free((ssh_session)fromB_u64(self->_ssh_session));
+ if (ssh_finalize()) {
+ printf("%s: ssh_finalize error", __FUNCTION__);
+ }
+
+ return $R_CONT(c$cont, B_None);
+}
+
+// Channel
+
+$R sshQ_ChannelD__initG_local (sshQ_Channel self, $Cont c$cont) {
+ pin_actor_affinity();
+
+ int err = 0;
+ ssh_session session = (struct ssh_session_struct *)fromB_u64(self->_ssh_session);
+ ssh_channel channel = { 0 };
+
+#ifdef DEBUG_MODE
+ printf("Connecting to SSH server\n");
+#endif
+
+ channel = ssh_channel_new(session);
+ if (channel == NULL)
+ {
+ printf("%s: ssh_channel_new() Failed to create SSH channel\n", __FUNCTION__);
+ return $R_CONT(c$cont, B_None);
+ }
+
+ self->_ssh_channel = toB_u64((unsigned long)channel);
+
+ err = ssh_channel_open_session(channel);
+ if (err != SSH_OK)
+ {
+ printf("%s: ssh_channel_open_session() error (%d)\n", __FUNCTION__, err);
+ return $R_CONT(c$cont, B_None);
+ }
+
+ if (self->_subsystem) {
+ err = set_subsystem(channel, (const char *)fromB_str(self->_subsystem));
+ if (err != SSH_OK) {
+ printf("%s: set_subsystem() setting subsystem failed error (%d)\n", __FUNCTION__, err);
+ return $R_CONT(c$cont, B_None);
+ }
+ }
+
+ // send hello message
+ err = send_nc_payload(channel, NETCONF_HELLO_MSG, NULL, 0);
+ if (err != SSH_OK)
+ {
+ printf("%s: send_nc_payload() error: %d\n", __FUNCTION__, err);
return $R_CONT(c$cont, B_None);
}
- rc = ssh_userauth_password(session, NULL, fromB_str(self->password));
- if (rc == SSH_OK) {
- printf("Connected\n");
- show_remote_processes(session);
- } else {
- printf("Error: %s\n", ssh_get_error(session));
+ return $R_CONT(c$cont, B_None);
+}
+
+$R sshQ_ChannelD_disconnectG_local (sshQ_Channel self, $Cont c$cont) {
+ int err = 0;
+ ssh_channel channel = (ssh_channel)fromB_u64(self->_ssh_channel);
+
+ // send close-session message
+ err = send_nc_payload(channel, NETCONF_CLOSE_SESSION_MSG, NULL, 0);
+ if (err != SSH_OK)
+ {
+ printf("%s: send_nc_payload() error: %d\n", __FUNCTION__, err);
}
-// self->_connected = true;
-// $action f = ($action) self->on_connect;
-// f->$class->__asyn__(f, self);
+ ssh_channel_close_free_eof(channel);
+
return $R_CONT(c$cont, B_None);
}
+
+$R sshQ_ChannelD_sendNCPayloadG_local (sshQ_Channel self, $Cont c$cont) {
+ int err = 0;
+ char response[BUF_SIZE] = {0};
+ ssh_channel channel = (ssh_channel)fromB_u64(self->_ssh_channel);
+
+ err = send_nc_payload(channel, (const char *)fromB_str(self->payload), response, sizeof(response));
+ if (err != SSH_OK)
+ {
+ printf("%s: send_nc_payload() error: %d\n", __FUNCTION__, err);
+ return $R_CONT(c$cont, B_None);
+ }
+
+ return $R_CONT(c$cont, to$str(response));
+}