blob: e4da6a4cebb51c10e17083c0ad2c17d9defd5e86 [file]
/*
* Copyright (c) 2026 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L /* for strtok_r() */
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(net_shell);
#include <getopt.h>
#include <string.h>
#include <zephyr/sys/sys_getopt.h>
#include <zephyr/net/socketutils.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/shell/shell_ssh.h>
#include <zephyr/net/ssh/common.h>
#include <zephyr/net/ssh/server.h>
#include <zephyr/net/ssh/client.h>
#include "net_shell_private.h"
#if defined(CONFIG_SSH)
#include "ssh_transport.h"
#endif
#if defined(CONFIG_SSH_SHELL)
#define MAX_USERNAME_LEN 32
#define MAX_PASSWORD_LEN 32
#define MAX_ADDRESS_LEN 64
#define MAX_HOSTKEY_LEN 32
#if defined(CONFIG_SSH_MAX_HOST_KEYS)
#define MAX_HOST_KEYS CONFIG_SSH_MAX_HOST_KEYS
#else
#define MAX_HOST_KEYS 1
#endif
struct ssh_params {
char username[MAX_USERNAME_LEN + 1];
char password[MAX_PASSWORD_LEN + 1];
char address[MAX_ADDRESS_LEN + 1];
int instance;
int transport;
int hostkey_idx;
int auth_key_idx[MAX_HOST_KEYS];
int auth_key_count;
};
#if defined(CONFIG_SSH_CLIENT)
static void shell_bypass_password(const struct shell *sh,
uint8_t *data, size_t len,
void *user_data)
{
struct shell_ssh_context *ctx = user_data;
struct shell_ssh_passwd *password = &ctx->password;
while (len--) {
uint8_t c = *data++;
if (c == '\r' || c == '\n' ||
password->len >= sizeof(password->buf) - 1) {
password->buf[password->len] = 0;
shell_set_bypass(sh, NULL, NULL);
if (ctx->transport != NULL) {
const char *username;
username = ssh_transport_client_user_name(ctx->transport);
(void)ssh_transport_auth_password(ctx->transport,
username,
password->buf);
}
mbedtls_platform_zeroize(password->buf, sizeof(password->buf));
password->len = 0;
ctx->transport = NULL;
break;
}
password->buf[password->len++] = c;
}
}
static void shell_bypass_ssh_shell(const struct shell *sh,
uint8_t *data, size_t len,
void *user_data)
{
struct shell_ssh_context *ctx = user_data;
if (ctx->channel == NULL) {
return;
}
(void)ssh_channel_write(ctx->channel, data, len);
}
static int ssh_client_channel_event_callback(struct ssh_channel *channel,
const struct ssh_channel_event *event,
void *user_data)
{
struct shell_ssh_context *ctx = user_data;
const struct shell *sh = ctx->sh;
switch (event->type) {
case SSH_CHANNEL_EVENT_OPEN_RESULT: {
LOG_DBG("Client channel opened");
return ssh_channel_request_shell(channel);
}
case SSH_CHANNEL_EVENT_REQUEST_RESULT:
LOG_DBG("Client channel shell request complete");
ctx->channel = channel;
shell_set_bypass(sh, shell_bypass_ssh_shell, ctx);
__fallthrough;
case SSH_CHANNEL_EVENT_RX_DATA_READY: {
#define MAX_BUF_LEN 128
uint8_t buf[MAX_BUF_LEN];
while (true) {
int len;
len = ssh_channel_read(channel, buf, sizeof(buf));
if (len <= 0) {
return len;
}
shell_fprintf(sh, SHELL_NORMAL, "%.*s", len, buf);
}
break;
}
case SSH_CHANNEL_EVENT_TX_DATA_READY:
break;
case SSH_CHANNEL_EVENT_RX_STDERR_DATA_READY: {
uint8_t buf[MAX_BUF_LEN];
while (true) {
int len;
len = ssh_channel_read_stderr(channel, buf, sizeof(buf));
if (len <= 0) {
return len;
}
shell_fprintf(sh, SHELL_ERROR, "%.*s", len, buf);
}
break;
}
case SSH_CHANNEL_EVENT_EOF:
LOG_DBG("Client channel EOF");
break;
case SSH_CHANNEL_EVENT_CLOSED: {
struct ssh_client *sshc;
int ret;
sshc = ssh_client_instance(ctx->ssh_instance);
if (sshc != NULL) {
ret = ssh_client_stop(sshc);
if (ret < 0) {
LOG_DBG("Failed to stop SSH %s: %d\n", "client", ret);
} else {
LOG_INF("SSH %s %d stopped", "client", ctx->ssh_instance);
ctx->ssh_instance = -1;
}
} else {
LOG_DBG("Failed to get SSH %s instance for cleanup", "client");
}
LOG_DBG("Client channel closed");
shell_set_bypass(sh, NULL, NULL);
ctx->channel = NULL;
break;
}
default:
return -EINVAL;
}
return 0;
}
static int shell_ssh_client_transport_event_callback(struct ssh_transport *transport,
const struct ssh_transport_event *event,
void *user_data)
{
struct shell_ssh_context *ctx = user_data;
const struct shell *sh = ctx->sh;
ctx->transport = transport;
switch (event->type) {
case SSH_TRANSPORT_EVENT_SERVICE_ACCEPTED: {
const char *username;
username = ssh_transport_client_user_name(transport);
shell_set_bypass(sh, shell_bypass_password, ctx);
shell_fprintf(sh, SHELL_INFO, "%s's password: ", username);
return 0;
}
case SSH_TRANSPORT_EVENT_AUTHENTICATE_RESULT:
if (event->authenticate_result.success) {
return ssh_transport_channel_open(transport,
ssh_client_channel_event_callback,
ctx);
}
return -EPERM;
default:
return -EINVAL;
}
return -EINVAL;
}
#endif /* CONFIG_SSH_CLIENT */
#if defined(CONFIG_SSH_SERVER)
static int sshd_parse_args_to_params(const struct shell *sh,
int argc, char *argv[],
bool is_start,
bool is_kill,
struct ssh_params *config)
{
struct sys_getopt_state *state;
int option_index = 1;
int opt;
const char *short_options;
const struct sys_getopt_option *long_options;
static const char start_short_options[] = "a:b:i:k:p:u:h";
static const struct sys_getopt_option start_long_options[] = {
{ "authorized-keys", sys_getopt_required_argument, 0, 'a' },
{ "bind-address", sys_getopt_required_argument, 0, 'b' },
{ "instance", sys_getopt_required_argument, 0, 'i' },
{ "hostkey", sys_getopt_required_argument, 0, 'k' },
{ "password", sys_getopt_required_argument, 0, 'p' },
{ "username", sys_getopt_required_argument, 0, 'u' },
{ "help", sys_getopt_no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
static const char stop_short_options[] = "i:h";
static const struct sys_getopt_option stop_long_options[] = {
{ "instance", sys_getopt_required_argument, 0, 'i' },
{ "help", sys_getopt_no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
static const char kill_short_options[] = "i:t:h";
static const struct sys_getopt_option kill_long_options[] = {
{ "instance", sys_getopt_required_argument, 0, 'i' },
{ "transport", sys_getopt_required_argument, 0, 't' },
{ "help", sys_getopt_no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
if (is_kill) {
short_options = kill_short_options;
long_options = kill_long_options;
} else {
if (is_start) {
short_options = start_short_options;
long_options = start_long_options;
} else {
short_options = stop_short_options;
long_options = stop_long_options;
}
}
while ((opt = sys_getopt_long(argc, argv, short_options, long_options,
&option_index)) != -1) {
state = sys_getopt_state_get();
if (is_kill) {
switch (opt) {
case 'i': {
int err = 0;
int instance;
instance = shell_strtol(state->optarg, 10, &err);
if (err != 0) {
PR_WARNING("Invalid instance \"%s\" (%d)\n",
state->optarg, err);
return -EINVAL;
}
config->instance = instance;
break;
}
case 't': {
int err = 0;
int transport;
transport = shell_strtol(state->optarg, 10, &err);
if (err != 0) {
PR_WARNING("Invalid transport \"%s\" (%d)\n",
state->optarg, err);
return -EINVAL;
}
config->transport = transport;
break;
}
case 'h':
case '?':
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
default:
PR_ERROR("Invalid option %c\n", state->optopt);
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
continue;
}
if (is_start) {
/* Only start command supports all options */
switch (opt) {
case 'a': {
/* Parse comma-separated list of authorized key indices */
char *token, *saveptr;
size_t auth_key_idx = 0;
token = strtok_r(state->optarg, ",", &saveptr);
while (token != NULL && auth_key_idx < MAX_HOST_KEYS) {
int idx, err = 0;
idx = shell_strtol(token, 10, &err);
if (err != 0) {
PR_WARNING("Invalid auth key index \"%s\" (%d)\n",
token, err);
return -EINVAL;
}
config->auth_key_idx[auth_key_idx] = idx;
auth_key_idx++;
token = strtok_r(NULL, ",", &saveptr);
}
config->auth_key_count = auth_key_idx;
break;
}
case 'b':
strncpy(config->address, state->optarg, MAX_ADDRESS_LEN);
config->address[MAX_ADDRESS_LEN] = '\0';
break;
case 'i': {
int err = 0;
int instance;
instance = shell_strtol(state->optarg, 10, &err);
if (err != 0) {
PR_WARNING("Invalid instance \"%s\" (%d)\n",
state->optarg, err);
return -EINVAL;
}
config->instance = instance;
break;
}
case 'k': {
char hostkey[MAX_HOSTKEY_LEN + 1];
int err = 0;
int idx;
strncpy(hostkey, state->optarg, MAX_HOSTKEY_LEN);
hostkey[MAX_HOSTKEY_LEN] = '\0';
idx = shell_strtol(hostkey, 10, &err);
if (err != 0) {
PR_WARNING("Invalid host key index \"%s\" (%d)\n",
hostkey, err);
return -EINVAL;
}
config->hostkey_idx = idx;
break;
}
case 'p':
strncpy(config->password, state->optarg, MAX_PASSWORD_LEN);
config->password[MAX_PASSWORD_LEN] = '\0';
break;
case 'u':
strncpy(config->username, state->optarg, MAX_USERNAME_LEN);
config->username[MAX_USERNAME_LEN] = '\0';
break;
case 'h':
case '?':
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
default:
PR_ERROR("Invalid option %c\n", state->optopt);
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
} else {
/* Stop command, only instance option is supported for now. */
switch (opt) {
case 'i': {
int err = 0;
int instance;
instance = shell_strtol(state->optarg, 10, &err);
if (err != 0) {
PR_WARNING("Invalid instance \"%s\" (%d)\n",
state->optarg, err);
return -EINVAL;
}
config->instance = instance;
break;
}
case 'h':
case '?':
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
default:
PR_ERROR("Invalid option %c\n", state->optopt);
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
}
return 0;
}
#endif /* CONFIG_SSH_SERVER */
#if defined(CONFIG_SSH_CLIENT)
static int ssh_parse_args_to_params(const struct shell *sh,
int argc, char *argv[],
bool is_start,
struct ssh_params *config)
{
struct sys_getopt_state *state = NULL;
int option_index = 1;
int opt;
const char *short_options;
const struct sys_getopt_option *long_options;
static const char start_short_options[] = "i:k:h";
static const struct sys_getopt_option start_long_options[] = {
{ "instance", sys_getopt_required_argument, 0, 'i' },
{ "hostkey", sys_getopt_required_argument, 0, 'k' },
{ "help", sys_getopt_no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
static const char stop_short_options[] = "i:h";
static const struct sys_getopt_option stop_long_options[] = {
{ "instance", sys_getopt_required_argument, 0, 'i' },
{ "help", sys_getopt_no_argument, 0, 'h' },
{ 0, 0, 0, 0 }
};
if (is_start) {
short_options = start_short_options;
long_options = start_long_options;
} else {
short_options = stop_short_options;
long_options = stop_long_options;
}
while ((opt = sys_getopt_long(argc, argv, short_options, long_options,
&option_index)) != -1) {
state = sys_getopt_state_get();
if (is_start) {
/* Only start command supports all options */
switch (opt) {
case 'i': {
int err = 0;
int instance;
instance = shell_strtol(state->optarg, 10, &err);
if (err != 0) {
PR_WARNING("Invalid instance \"%s\" (%d)\n",
state->optarg, err);
return -EINVAL;
}
config->instance = instance;
break;
}
case 'k': {
char hostkey[MAX_HOSTKEY_LEN + 1];
int err = 0;
int idx;
strncpy(hostkey, state->optarg, MAX_HOSTKEY_LEN);
hostkey[MAX_HOSTKEY_LEN] = '\0';
idx = shell_strtol(hostkey, 10, &err);
if (err != 0) {
PR_WARNING("Invalid host key index \"%s\" (%d)\n",
hostkey, err);
return -EINVAL;
}
config->hostkey_idx = idx;
break;
}
case 'h':
case '?':
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
default:
PR_ERROR("Invalid option %c\n", state->optopt);
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
} else {
/* Stop command, only instance option is supported for now. */
switch (opt) {
case 'i': {
int err = 0;
int instance;
instance = shell_strtol(state->optarg, 10, &err);
if (err != 0) {
PR_WARNING("Invalid instance \"%s\" (%d)\n",
state->optarg, err);
return -EINVAL;
}
config->instance = instance;
break;
}
case 'h':
case '?':
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
default:
PR_ERROR("Invalid option %c\n", state->optopt);
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
}
if (is_start && sys_getopt_optind < argc) {
/* The first non-option argument is the destination address */
strncpy(config->address, argv[sys_getopt_optind], MAX_ADDRESS_LEN);
config->address[MAX_ADDRESS_LEN] = '\0';
}
return 0;
}
#endif /* CONFIG_SSH_CLIENT */
#endif /* CONFIG_SSH_SHELL */
#if defined(CONFIG_SSH_SHELL)
static void ssh_client_list_cb(struct ssh_client *ssh, int instance, void *user_data)
{
#if defined(CONFIG_SSH_CLIENT)
struct net_shell_user_data *data = user_data;
char addr_str[NET_IPV6_ADDR_LEN];
const struct shell *sh = data->sh;
int *count = data->user_data;
net_addr_ntop(ssh->addr.ss_family, &net_sin(net_sad(&ssh->addr))->sin_addr,
addr_str, sizeof(addr_str));
if (*count == 0) {
PR("Active SSH client connections:\n");
PR("Instance Peer Address\n");
}
PR("%8d %s%s%s:%d\n", instance,
ssh->addr.ss_family == NET_AF_INET6 ? "[" : "",
addr_str,
ssh->addr.ss_family == NET_AF_INET6 ? "]" : "",
net_ntohs(net_sin(net_sad(&ssh->addr))->sin_port));
(*count)++;
#endif /* CONFIG_SSH_CLIENT */
}
static void ssh_server_list_cb(struct ssh_server *sshd, int instance, void *user_data)
{
#if defined(CONFIG_SSH_SERVER)
struct net_shell_user_data *data = user_data;
char addr_str[NET_IPV6_ADDR_LEN];
const struct shell *sh = data->sh;
int *count = data->user_data;
ARRAY_FOR_EACH(sshd->transport, i) {
if (sshd->transport[i].running) {
net_addr_ntop(sshd->transport[i].addr.ss_family,
&net_sin(net_sad(&sshd->transport[i].addr))->sin_addr,
addr_str, sizeof(addr_str));
if (*count == 0) {
PR("Active SSH server connections:\n");
PR("Instance Transport Peer Address\n");
}
PR("%8d %9d %s%s%s:%d\n", instance, i,
sshd->transport[i].addr.ss_family == NET_AF_INET6 ? "[" : "",
addr_str,
sshd->transport[i].addr.ss_family == NET_AF_INET6 ? "]" : "",
net_ntohs(net_sin(net_sad(&sshd->transport[i].addr))->sin_port));
(*count)++;
}
}
#endif /* CONFIG_SSH_SERVER */
}
#endif /* CONFIG_SSH_SHELL */
static int cmd_ssh_list(const struct shell *sh, size_t argc, char **argv)
{
#if defined(CONFIG_SSH_SHELL)
struct net_shell_user_data user_data;
int count = 0;
user_data.sh = sh;
user_data.user_data = &count;
if (IS_ENABLED(CONFIG_SSH_CLIENT)) {
ssh_client_foreach(ssh_client_list_cb, &user_data);
if (count == 0) {
PR_INFO("No active SSH client connections\n");
}
}
count = 0;
if (IS_ENABLED(CONFIG_SSH_SERVER)) {
ssh_server_foreach(ssh_server_list_cb, &user_data);
if (count == 0) {
PR_INFO("No active SSH server connections\n");
}
}
return 0;
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_SSH_SHELL", "SSH");
return 0;
#endif /* CONFIG_SSH_SHELL */
}
static int cmd_ssh(const struct shell *sh, size_t argc, char **argv)
{
if (argc == 2 &&
(strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
return cmd_ssh_list(sh, argc, argv);
}
static int cmd_sshd_start(const struct shell *sh, size_t argc, char **argv)
{
#if defined(CONFIG_SSH_SHELL)
#if defined(CONFIG_SSH_SERVER)
struct ssh_params params = { 0 };
struct net_sockaddr_storage bind_addr;
struct ssh_server *sshd;
int server_instance, hostkey_idx;
int family;
int ret;
params.instance = -1;
params.hostkey_idx = -1;
ret = sshd_parse_args_to_params(sh, argc, argv, true, false, &params);
if (ret < 0) {
return ret;
}
if (params.instance < 0) {
server_instance = 0; /* Default instance */
} else {
server_instance = params.instance;
}
if (params.hostkey_idx < 0) {
hostkey_idx = 0; /* Default host key index */
} else {
hostkey_idx = params.hostkey_idx;
}
memset(&bind_addr, 0, sizeof(bind_addr));
net_sin(net_sad(&bind_addr))->sin_port = htons(CONFIG_SSH_PORT);
if (params.address[0] == '\0') {
if (IS_ENABLED(CONFIG_NET_IPV4_MAPPING_TO_IPV6)) {
family = NET_AF_INET6; /* Use IPv6 to support both IPv4 and IPv6 */
} else if (IS_ENABLED(CONFIG_NET_IPV4)) {
family = NET_AF_INET; /* Default to IPv4 */
} else if (IS_ENABLED(CONFIG_NET_IPV6)) {
family = NET_AF_INET6; /* Default to IPv6 */
} else {
PR_ERROR("No IP stack enabled\n");
return -ENOTSUP;
}
/* Bind to all interfaces */
bind_addr.ss_family = family;
} else {
bool st;
st = net_ipaddr_parse(params.address, strlen(params.address), net_sad(&bind_addr));
if (!st) {
PR_ERROR("Failed to parse bind address \"%s\"\n", params.address);
return -EINVAL;
}
}
sshd = ssh_server_instance(server_instance);
if (sshd == NULL) {
PR_ERROR("Failed to get SSH server instance\n");
return -EINVAL;
}
ret = ssh_server_start(sshd,
net_sad(&bind_addr),
hostkey_idx,
params.username[0] != '\0' ? params.username : NULL,
params.password[0] != '\0' ? params.password : NULL,
params.auth_key_idx,
params.auth_key_count,
shell_sshd_event_callback,
shell_sshd_transport_event_callback,
NULL);
if (ret != 0) {
PR_ERROR("Failed to start SSH server: %d\n", ret);
}
return ret;
#else
PR_INFO("SSH %s support is not enabled. Set %s to enable it.\n",
"server", "CONFIG_SSH_SERVER");
return -ENOTSUP;
#endif /* CONFIG_SSH_SERVER */
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_SSH_SHELL", "SSH");
return 0;
#endif /* CONFIG_SSH_SHELL */
}
static int cmd_sshd_stop(const struct shell *sh, size_t argc, char **argv)
{
#if defined(CONFIG_SSH_SHELL)
#if defined(CONFIG_SSH_SERVER)
struct ssh_params params = { 0 };
struct ssh_server *sshd;
int server_instance;
int ret;
params.instance = -1;
ret = sshd_parse_args_to_params(sh, argc, argv, false, false, &params);
if (ret < 0) {
return ret;
}
if (params.instance < 0) {
server_instance = 0; /* Default instance */
} else {
server_instance = params.instance;
}
sshd = ssh_server_instance(server_instance);
if (sshd == NULL) {
PR_ERROR("Failed to get SSH %s instance\n", "server");
return -ENOENT;
}
ret = ssh_server_stop(sshd);
if (ret < 0) {
PR_ERROR("Failed to stop SSH %s: %d\n", "server", ret);
}
return ret;
#else
PR_INFO("SSH %s support is not enabled. Set %s to enable it.\n",
"server", "CONFIG_SSH_SERVER");
return -ENOTSUP;
#endif /* CONFIG_SSH_SERVER */
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_SSH_SHELL", "SSH");
return 0;
#endif /* CONFIG_SSH_SHELL */
}
static int cmd_sshd_kill(const struct shell *sh, size_t argc, char **argv)
{
#if defined(CONFIG_SSH_SHELL)
#if defined(CONFIG_SSH_SERVER)
struct ssh_params params = { 0 };
struct ssh_server *sshd;
int server_instance;
int ret;
params.instance = -1;
params.transport = -1;
ret = sshd_parse_args_to_params(sh, argc, argv, false, true, &params);
if (ret < 0) {
return ret;
}
if (params.transport < 0) {
PR_ERROR("Transport instance is required\n");
return -EINVAL;
}
if (params.instance < 0) {
server_instance = 0; /* Default instance */
} else {
server_instance = params.instance;
}
sshd = ssh_server_instance(server_instance);
if (sshd == NULL) {
PR_ERROR("Failed to get SSH %s instance\n", "server");
return -ENOENT;
}
ret = ssh_server_transport_close(sshd, params.transport);
if (ret < 0) {
PR_ERROR("Failed to kill SSH connection: %d\n", ret);
}
return ret;
#else
PR_INFO("SSH %s support is not enabled. Set %s to enable it.\n",
"server", "CONFIG_SSH_SERVER");
return -ENOTSUP;
#endif /* CONFIG_SSH_SERVER */
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_SSH_SHELL", "SSH");
return 0;
#endif /* CONFIG_SSH_SHELL */
}
SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_sshd,
SHELL_CMD_ARG(start, NULL,
SHELL_HELP("Start ssh server so peer can connect to us",
"[-k <host-key-idx>] [-b <bind-address>[:<port>]] "
"[-i <instance>]\n"
"[-p <password>] [-a <auth-key-idx>,<auth-key-idx>,...]\n"
"If port is omitted, then the default value "
STRINGIFY(CONFIG_SSH_PORT) " is used.\n"
"The bind address can be IPv4/6 address. "
"IPv6 address should be enclosed in brackets, "
"e.g. [2001:db8::1] if port is also given\n"
"If the bind address is omitted, then the server "
"will bind to all interfaces.\n"
"If instance is omitted, then the default value 0 is used.\n"
"The password is optional, if not set, then password\n"
"authentication will be disabled."),
cmd_sshd_start, 1, 10),
SHELL_CMD_ARG(stop, NULL,
SHELL_HELP("Stop ssh server",
"[-i <instance>]\n"
"If instance is omitted, then the default value 0 is used."),
cmd_sshd_stop, 1, 3),
SHELL_CMD_ARG(kill, NULL,
SHELL_HELP("Kill active ssh server connection",
"[-i <server-instance>] -t <transport-instance>\n"),
cmd_sshd_kill, 3, 5),
SHELL_CMD_ARG(list, NULL,
SHELL_HELP("List active ssh connections", NULL),
cmd_ssh_list, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_SUBCMD_ADD((net), sshd, &net_cmd_sshd,
"SSH server commands", cmd_ssh, 0, 1);
static int cmd_ssh_start(const struct shell *sh, size_t argc, char **argv)
{
#if defined(CONFIG_SSH_SHELL)
#if defined(CONFIG_SSH_CLIENT)
struct ssh_params params = { 0 };
struct net_sockaddr_storage dest_addr;
struct ssh_client *sshc;
static struct shell_ssh_context ctx;
int client_instance, hostkey_idx;
char *dst, *username;
int ret;
bool st;
params.instance = -1;
params.hostkey_idx = -1;
ctx.sh = sh;
ret = ssh_parse_args_to_params(sh, argc, argv, true, &params);
if (ret < 0) {
return ret;
}
if (params.instance < 0) {
client_instance = 0; /* Default instance */
} else {
client_instance = params.instance;
}
hostkey_idx = params.hostkey_idx;
memset(&dest_addr, 0, sizeof(dest_addr));
net_sin(net_sad(&dest_addr))->sin_port = htons(CONFIG_SSH_PORT);
if (params.address[0] == '\0') {
PR_ERROR("Destination address is required\n");
return -EINVAL;
}
dst = strstr(params.address, "@");
if (dst == NULL) {
PR_ERROR("Invalid destination format, expected <user>@<address>\n");
return -EINVAL;
}
*dst = '\0'; /* Split user and address */
username = params.address;
st = net_ipaddr_parse(dst + 1, strlen(dst + 1), net_sad(&dest_addr));
if (!st) {
/* Address parsing failed, try to resolve it as a hostname */
struct zsock_addrinfo *addr;
struct zsock_addrinfo addrinfo_hint = {
.ai_family = NET_AF_UNSPEC,
.ai_socktype = NET_SOCK_STREAM,
.ai_protocol = NET_IPPROTO_TCP,
};
ret = net_getaddrinfo_addr_str(dst + 1,
STRINGIFY(CONFIG_SSH_PORT),
&addrinfo_hint,
&addr);
if (ret != 0) {
PR_ERROR("Failed to parse destination address \"%s\"\n",
params.address);
return -EINVAL;
}
memcpy(&dest_addr, addr->ai_addr, addr->ai_addrlen);
zsock_freeaddrinfo(addr);
}
sshc = ssh_client_instance(client_instance);
if (sshc == NULL) {
PR_ERROR("Failed to get SSH client instance\n");
return -ENOENT;
}
ctx.ssh_instance = client_instance;
ret = ssh_client_start(sshc,
username,
net_sad(&dest_addr),
hostkey_idx,
shell_ssh_client_transport_event_callback,
&ctx);
if (ret != 0) {
PR_ERROR("Failed to start SSH client: %d\n", ret);
} else {
PR_INFO("SSH client started on instance %d\n", client_instance);
}
return ret;
#else
PR_INFO("SSH %s support is not enabled. Set %s to enable it.\n",
"client", "CONFIG_SSH_CLIENT");
return -ENOTSUP;
#endif /* CONFIG_SSH_CLIENT */
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_SSH_SHELL", "SSH");
return 0;
#endif /* CONFIG_SSH_SHELL */
}
static int cmd_ssh_stop(const struct shell *sh, size_t argc, char **argv)
{
#if defined(CONFIG_SSH_SHELL)
#if defined(CONFIG_SSH_CLIENT)
struct ssh_params params = { 0 };
struct ssh_client *sshc;
int client_instance;
int ret;
params.instance = -1;
ret = ssh_parse_args_to_params(sh, argc, argv, false, &params);
if (ret < 0) {
return ret;
}
if (params.instance < 0) {
client_instance = 0; /* Default instance */
} else {
client_instance = params.instance;
}
sshc = ssh_client_instance(client_instance);
if (sshc == NULL) {
PR_ERROR("Failed to get SSH %s instance\n", "client");
return -ENOENT;
}
ret = ssh_client_stop(sshc);
if (ret < 0) {
PR_ERROR("Failed to stop SSH %s: %d\n", "client", ret);
}
return ret;
#else
PR_INFO("SSH %s support is not enabled. Set %s to enable it.\n",
"client", "CONFIG_SSH_CLIENT");
return -ENOTSUP;
#endif /* CONFIG_SSH_CLIENT */
#else
PR_INFO("Set %s to enable %s support.\n", "CONFIG_SSH_SHELL", "SSH");
return 0;
#endif /* CONFIG_SSH_SHELL */
}
SHELL_STATIC_SUBCMD_SET_CREATE(net_cmd_ssh,
SHELL_CMD_ARG(start, NULL,
SHELL_HELP("Connect to ssh peer",
"[-k <host-key-idx>] [-i <instance>] <destination>[:<port>]\n"
"If port is omitted, then the default value "
STRINGIFY(CONFIG_SSH_PORT) " is used.\n"
"The destination address can be either a hostname or "
"IPv4/6 address. "
"IPv6 address should be enclosed in brackets, "
"e.g. [2001:db8::1] if port is also given\n"
"If instance is omitted, then the default value 0 is used."),
cmd_ssh_start, 2, 6),
SHELL_CMD_ARG(stop, NULL,
SHELL_HELP("Stop ssh client connection",
"[-i <instance>]\n"
"If instance is omitted, then the default value 0 is used."),
cmd_ssh_stop, 1, 3),
SHELL_CMD_ARG(list, NULL,
SHELL_HELP("List active ssh connections", NULL),
cmd_ssh_list, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_SUBCMD_ADD((net), ssh, &net_cmd_ssh,
"SSH client commands", cmd_ssh, 0, 1);