/*****************************************************************************
* file lib/management/cmd_SSH2.c
* author YuLiang
* version 1.0.0
* date 20-Mar-2025
* brief This file provides all the SSH2 cmd related operation functions.
******************************************************************************
* Attention
*
*
© COPYRIGHT(c) 2025 LandPower
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of LandPower nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************/
/* Includes ------------------------------------------------------------------*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
/* 标准C库头文件. */
#include
#include
#include
#include
/* 用户代码头文件. */
#include "main.h"
#include "vty.h"
#include "cmd.h"
#include "mtimer.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define VTYCMD_PORT "2222"
#define VTYCMD_RSA_PATH "/home/root/ssh/ssh_host_rsa_key"
#define VTYCMD_HOSTKEY_PATH "/home/root/ssh/ssh_host_ed25519_key"
#define VTYCMD_BUF_OUT_LEN 32768
typedef struct _vtycmd_ctrl
{
vty_t *vtycmd;
ssh_bind sshbind;
ssh_session session;
ssh_channel channel;
struct ssh_server_callbacks_struct server_cb;
struct ssh_channel_callbacks_struct channel_cb;
uint8_t is_connect;
uint8_t is_recv;
uint16_t cmd_idx;
char cmd[VTY_BUFSIZ];
uint16_t out_buf_start;
uint16_t out_buf_end;
char out_buf[VTYCMD_BUF_OUT_LEN];
} vtycmd_ctrl_t;
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* cmd vty结构体. */
static vtycmd_ctrl_t vtycmd_ctrl;
/* Private function prototypes -----------------------------------------------*/
/* 在终端上显示当前配置信息. */
CMD(show_sshcmd,
show_sshcmd_cmd,
"show sshcmd",
SHOW_STR
"SSHCMD\n")
{
vty_out(vty, "Connect: %s\r\n\n", vtycmd_ctrl.is_connect ? "yes" : "no");
return CMD_SUCCESS;
}
#if 0
/* Insert a word into vty interface with overwrite mode. */
void _vtycmd_insert_word(char *str)
{
int len = strlen(str);
ssh_channel_write(vtycmd_ctrl.channel, str, len);
strcpy(&vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx], str);
vtycmd_ctrl.cmd_idx += len;
vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0;
}
/* commandLine '?'响应函数. */
int _vtycmd_question(void)
{
array_t *cmd_line = NULL;
cmd_line = cmd_strs_create(vtycmd_ctrl.cmd);
if (NULL == cmd_line)
{
cmd_line = array_init(1, MTYPE_CLI);
array_append(cmd_line, '\0', MTYPE_CLI);
}
else
if (vtycmd_ctrl.cmd_idx && isspace((int)vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx - 1]))
array_append(cmd_line, '\0', MTYPE_CLI);
vty_question(vtycmd_ctrl.vtycmd, cmd_line);
cmd_strs_free(cmd_line);
vty_prompt(vtycmd_ctrl.vtycmd);
vty_out(vtycmd_ctrl.vtycmd, "%s", vtycmd_ctrl.cmd);
return 0;
}
/* 用于自动补齐命令行关键字. */
void _vtycmd_completion_append(array_t *cmd_line, char *word, int32_t status)
{
uint32_t index = array_active(cmd_line) - 1;
uint32_t len = 0;
uint32_t i = 0;
if (NULL == array_get(cmd_line, index))
{
_vtycmd_insert_word(word);
}
else
{
len = strlen(array_get(cmd_line, index));
for(i = 0; i < len; i++)
{
if (vtycmd_ctrl.cmd_idx)
{
vtycmd_ctrl.cmd_idx--;
vty_out(vtycmd_ctrl.vtycmd, "%c%c%c", 8, ' ', 8);
}
}
_vtycmd_insert_word(word);
}
if (CMD_COMPLETE_FULL_MATCH == status)
_vtycmd_insert_word(" ");
}
/* commandLine 'TAB'响应函数. */
int _vtycmd_completion(void)
{
array_t *cmd_line = NULL;
char **match_strs = NULL;
int32_t complete_status = CMD_ERR_NO_MATCH;
cmd_line = cmd_strs_create(vtycmd_ctrl.cmd);
if (NULL == cmd_line)
{
cmd_line = array_init(1, MTYPE_CLI);
array_append(cmd_line, '\0', MTYPE_CLI);
}
else
if (vtycmd_ctrl.cmd_idx && isspace((int)vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx - 1]))
array_append(cmd_line, '\0', MTYPE_CLI);
match_strs = cmd_complete_command(cmd_line, vtycmd_ctrl.vtycmd, &complete_status);
if (NULL == match_strs)
{
cmd_strs_free(cmd_line);
return 0;
}
if (CMD_COMPLETE_MATCH == complete_status ||
CMD_COMPLETE_FULL_MATCH == complete_status)
_vtycmd_completion_append(cmd_line, match_strs[0], complete_status);
else
{
vty_print_word(vtycmd_ctrl.vtycmd, match_strs);
vty_prompt(vtycmd_ctrl.vtycmd);
vty_out(vtycmd_ctrl.vtycmd, "%s", vtycmd_ctrl.cmd);
}
vty_free_match_strs(match_strs);
cmd_strs_free(cmd_line);
return 0;
}
/* Internal functions --------------------------------------------------------*/
/* Print command line history. This function is called from
vty_next_line and vty_previous_line. */
static void _vtycmd_history_print(vty_t *vty)
{
uint16_t i= 0;
for(i = 0; i < vtycmd_ctrl.cmd_idx; i++)
{
vty_out(vtycmd_ctrl.vtycmd, "%c%c%c", 8, ' ', 8);
}
vtycmd_ctrl.cmd_idx = 0;
/* Get previous line from history buffer */
vtycmd_ctrl.cmd_idx = strlen(vty->hist[vty->hp]);
memcpy(vtycmd_ctrl.cmd, vty->hist[vty->hp], vtycmd_ctrl.cmd_idx);
vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0;
/* Redraw current line */
vty_out(vtycmd_ctrl.vtycmd, "%s", vtycmd_ctrl.cmd);
}
/* Show previous command line history. */
static void _vtycmd_previous_line(vty_t *vty)
{
unsigned int try_index = 0;
try_index = vty->hp;
if (try_index == 0)
try_index = VTY_MAXHIST - 1;
else
try_index--;
if (vty->hist[try_index] == NULL)
return;
else
vty->hp = try_index;
_vtycmd_history_print(vty);
}
/* Show next command line history. */
static void _vtycmd_next_line(vty_t *vty)
{
unsigned int try_index = 0;
if (vty->hp == vty->hindex)
return;
/* Try is there history exist or not. */
try_index = vty->hp;
if (try_index == (VTY_MAXHIST - 1))
try_index = 0;
else
try_index++;
/* If there is not history return. */
if (vty->hist[try_index] == NULL)
return;
else
vty->hp = try_index;
_vtycmd_history_print(vty);
}
int _vtycmd_cmd(char *buf, uint32_t len)
{
uint16_t i = 0;
vty_t *vty = vtycmd_ctrl.vtycmd;
for (i = 0; i < len; i++)
{
switch (buf[i])
{
case '\033':
if (i + 2 < len && buf[i + 1] == '[')
{
if (buf[i + 2] == 'A')
{
_vtycmd_previous_line(vty);
}
else if (buf[i + 2] == 'B')
{
_vtycmd_next_line(vty);
}
i += 2;
}
break;
case CONTROL('H'):
case 0x7f:
if (vtycmd_ctrl.cmd_idx)
{
vtycmd_ctrl.cmd_idx--;
vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0;
vty_out(vty, "%c%c%c", 8, ' ', 8);
}
break;
case '\n':
case '\r':
vty_out(vty, "%s", VTY_NEWLINE);
/* 执行命令 */
vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0;
vty->length = vtycmd_ctrl.cmd_idx;
snprintf(vty->buf, VTY_BUFSIZ, "%s", vtycmd_ctrl.cmd);
vty_execute(vty);
vtycmd_ctrl.cmd_idx = 0;
vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0;
break;
case '\t':
_vtycmd_completion();
break;
case '?':
_vtycmd_question();
break;
default:
if (buf[i] > 31 && buf[i] < 127)
{
vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = buf[i];
vtycmd_ctrl.cmd_idx++;
vtycmd_ctrl.cmd[vtycmd_ctrl.cmd_idx] = 0;
vty_out(vty, "%c", buf[i]);
}
break;
}
}
return E_NONE;
}
/* 客户端通讯相关的回调 */
int _vtycmd_channel_pty_request(ssh_session session, ssh_channel channel,
const char *term, int cols, int rows, int py, int px,
void *userdata)
{
ssh_channel_write(vtycmd_ctrl.channel, "Username: ", 10);
ssh_channel_request_pty_size(channel, term, cols, rows);
return SSH_OK;
}
int _vtycmd_channel_pty_resize(ssh_session session, ssh_channel channel, int cols,
int rows, int py, int px, void *userdata)
{
return ssh_channel_change_pty_size(channel, cols, rows);
}
int _vtycmd_channel_shell_request(ssh_session session, ssh_channel channel,
void *userdata)
{
return SSH_OK;
}
int _vtycmd_channel_exec_request(ssh_session session, ssh_channel channel,
const char *command, void *userdata)
{
return ssh_channel_request_exec(channel, command);
}
int _vtycmd_channel_subsystem_request(ssh_session session, ssh_channel channel,
const char *subsystem, void *userdata)
{
return SSH_OK;
}
int _vtycmd_channel_env_request_function(ssh_session session, ssh_channel channel, const char *env_name,
const char *env_value, void *userdata)
{
return ssh_channel_request_env(channel, env_name, env_value);
}
/* 数据处理 */
int _vtycmd_channel_data_function(ssh_session session, ssh_channel channel, void *data,
uint32_t len, int is_stderr, void *userdata)
{
vtycmd_ctrl.is_recv = TRUE;
_vtycmd_cmd((char*)data, len);
return len;
}
void _vtycmd_channel_close(ssh_session session, ssh_channel channel, void *userdata)
{
vtycmd_ctrl.is_connect = FALSE;
}
/* 服务端认证通道相关回调 */
int _vtycmd_server_auth_pass(ssh_session session, const char *user, const char *password, void *userdata)
{
if (strcmp("root", user) == 0 && strcmp("123456", password) == 0)
{
return SSH_AUTH_SUCCESS;
}
else
{
return SSH_AUTH_DENIED;
}
}
/* 空认证方法 */
int _vtycmd_server_auth_none_callback(ssh_session session, const char *user, void *userdata)
{
return SSH_AUTH_SUCCESS;
}
/* pubkey 认证方法 */
int _vtycmd_server_auth_pubkey(ssh_session session, const char *user, struct ssh_key_struct *pubkey,
char signature_state, void *userdata)
{
return SSH_AUTH_SUCCESS;
}
int _vtycmd_server_request_callback(ssh_session session, const char *service, void *userdata)
{
return SSH_OK;
}
ssh_channel _vtycmd_server_open_channel(ssh_session session, void *userdata)
{
vtycmd_ctrl.channel = ssh_channel_new(session);
ssh_callbacks_init(&vtycmd_ctrl.channel_cb);
ssh_set_channel_callbacks(vtycmd_ctrl.channel, &vtycmd_ctrl.channel_cb);
return vtycmd_ctrl.channel;
}
/* SSH2 命令行主函数 */
void *_vtycmd_handle(void *arg)
{
/* 初始化 libssh */
if (ssh_init() < 0)
{
log_err(LOG_CLI, "ssh_init failed\r\n");
return NULL;
}
/* 初始化结构体 */
vtycmd_ctrl.server_cb.auth_password_function = _vtycmd_server_auth_pass;
vtycmd_ctrl.server_cb.auth_none_function = _vtycmd_server_auth_none_callback;
vtycmd_ctrl.server_cb.auth_pubkey_function = _vtycmd_server_auth_pubkey;
vtycmd_ctrl.server_cb.service_request_function = _vtycmd_server_request_callback;
vtycmd_ctrl.server_cb.channel_open_request_session_function = _vtycmd_server_open_channel;
vtycmd_ctrl.channel_cb.channel_data_function = _vtycmd_channel_data_function;
vtycmd_ctrl.channel_cb.channel_close_function = _vtycmd_channel_close;
vtycmd_ctrl.channel_cb.channel_pty_request_function = _vtycmd_channel_pty_request;
vtycmd_ctrl.channel_cb.channel_shell_request_function = _vtycmd_channel_shell_request;
vtycmd_ctrl.channel_cb.channel_pty_window_change_function = _vtycmd_channel_pty_resize;
vtycmd_ctrl.channel_cb.channel_exec_request_function = _vtycmd_channel_exec_request;
vtycmd_ctrl.channel_cb.channel_env_request_function = _vtycmd_channel_env_request_function;
vtycmd_ctrl.channel_cb.channel_subsystem_request_function = _vtycmd_channel_subsystem_request;
/* 创建 SSH2 接口 */
vtycmd_ctrl.sshbind = ssh_bind_new();
ssh_bind_options_set(vtycmd_ctrl.sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, VTYCMD_PORT);
ssh_bind_options_set(vtycmd_ctrl.sshbind, SSH_BIND_OPTIONS_RSAKEY, VTYCMD_RSA_PATH);
ssh_bind_options_set(vtycmd_ctrl.sshbind, SSH_BIND_OPTIONS_HOSTKEY, VTYCMD_HOSTKEY_PATH);
if (ssh_bind_listen(vtycmd_ctrl.sshbind) < 0)
{
log_err(LOG_CLI, "Error listening to socket: %s", ssh_get_error(vtycmd_ctrl.sshbind));
return NULL;
}
while(1)
{
/* 等待连接 */
ssh_session session = ssh_new();
if (ssh_bind_accept(vtycmd_ctrl.sshbind, session) != SSH_OK)
{
DBG(DBG_M_CLI, "Error accept to socket: %s\n", ssh_get_error(vtycmd_ctrl.sshbind));
continue;
}
/* 只能接收一个连接 */
if (vtycmd_ctrl.is_connect)
{
ssh_disconnect(session);
ssh_free(session);
continue;
}
/* 初始化连接 */
log_notice(LOG_CLI, "SSH2 command is connect.\n");
vtycmd_ctrl.is_connect = TRUE;
ssh_callbacks_init(&vtycmd_ctrl.server_cb);
ssh_set_server_callbacks(session, &vtycmd_ctrl.server_cb);
ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
if (ssh_handle_key_exchange(session) != SSH_OK)
{
DBG(DBG_M_CLI, "Error performing key exchange: %s\n", ssh_get_error(session));
ssh_disconnect(session);
ssh_free(session);
continue;
}
ssh_set_blocking(session, 0);
ssh_event event = ssh_event_new();
ssh_event_add_session(event, session);
/* 处理连接断开 */
while(1)
{
if (ssh_event_dopoll(event, 3000) == SSH_ERROR
|| !vtycmd_ctrl.is_connect)
{
/* 终端断开处理 */
log_notice(LOG_CLI, "SSH2 command is disconnect.\n");
ssh_channel_close(vtycmd_ctrl.channel);
ssh_channel_free(vtycmd_ctrl.channel);
ssh_disconnect(session);
ssh_event_free(event);
ssh_free(session);
vtycmd_ctrl.is_connect = FALSE;
if (CONFIG_NODE == vtycmd_ctrl.vtycmd->node)
{
vty_config_unlock(vtycmd_ctrl.vtycmd);
}
vtycmd_ctrl.vtycmd->node = USERNAME_NODE;
break;
}
}
}
return NULL;
}
/* 用于判断终端是否超时 */
void* _vtycmd_timer(void *arg)
{
if (!vtycmd_ctrl.is_recv && vtycmd_ctrl.is_connect)
{
vtycmd_ctrl.is_connect = FALSE;
}
vtycmd_ctrl.is_recv = FALSE;
/* 重新加入定时器. */
mtimer_add(_vtycmd_timer, NULL, 120, "VTYCMD_TIMER");
return NULL;
}
/* SSH2 命令行发送数据 */
void *_vtycmd_send_handle(void *arg)
{
char *buf = NULL;
uint16_t end = 0;
while(1)
{
usleep(100000);
if (!vtycmd_ctrl.is_connect)
{
continue;
}
end = vtycmd_ctrl.out_buf_end;
buf = vtycmd_ctrl.out_buf + vtycmd_ctrl.out_buf_start;
if (end == vtycmd_ctrl.out_buf_start)
{
continue;
}
if (end < vtycmd_ctrl.out_buf_start)
{
ssh_channel_write(vtycmd_ctrl.channel, buf, VTYCMD_BUF_OUT_LEN - vtycmd_ctrl.out_buf_start);
vtycmd_ctrl.out_buf_start = 0;
}
else
{
ssh_channel_write(vtycmd_ctrl.channel, buf, end - vtycmd_ctrl.out_buf_start);
vtycmd_ctrl.out_buf_start = end;
}
}
return NULL;
}
#endif
/* Interface functions -------------------------------------------------------*/
/* 初始化 SSH2 命令行 */
void vtycmd_init(void)
{
/* Make vty structure. */
vtycmd_ctrl.vtycmd = vty_create();
vtycmd_ctrl.vtycmd->type = VTY_CMD;
vtycmd_ctrl.vtycmd->node = USERNAME_NODE;
memset(vtycmd_ctrl.vtycmd->hist, 0, sizeof(vtycmd_ctrl.vtycmd->hist));
vtycmd_ctrl.vtycmd->hp = 0;
vtycmd_ctrl.vtycmd->hindex = 0;
cmd_install_element(COMMON_NODE, &show_sshcmd_cmd);
}
/* 启动 SSH2 命令行 */
void vtycmd_cmd_init(void)
{
return ;
#if 0
pthread_t pid;
struct sched_param param;
pthread_attr_t attr;
int32_t rv = 0;
/* 配置线程RR调度, 优先级75 */
pthread_attr_init(&attr);
param.sched_priority = 75;
pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setschedparam(&attr, ¶m);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
rv = pthread_create(&pid, &attr, _vtycmd_handle, NULL);
if (rv != 0)
{
log_err(LOG_DEFAULT, "vtycmd_cmd_init can't create pthread %d!", rv);
}
else
{
thread_m_add("CMD", pid);
}
pthread_attr_destroy(&attr);
mtimer_add(_vtycmd_timer, NULL, 120, "VTYCMD_TIMER");
/* 配置线程RR调度, 优先级75 */
pthread_attr_init(&attr);
param.sched_priority = 75;
pthread_attr_setschedpolicy(&attr, SCHED_RR);
pthread_attr_setschedparam(&attr, ¶m);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
rv = pthread_create(&pid, &attr, _vtycmd_send_handle, NULL);
if (rv != 0)
{
log_err(LOG_DEFAULT, "vtycmd_cmd_init can't create pthread %d!", rv);
}
else
{
thread_m_add("CMD_SNED", pid);
}
pthread_attr_destroy(&attr);
#endif
}
/* cmd 是否连接 */
bool vtycmd_connect(void)
{
return vtycmd_ctrl.is_connect;
}
/* 命令行输出 */
void vtycmd_print(const char *format, va_list va)
{
char *buf = vtycmd_ctrl.out_buf + vtycmd_ctrl.out_buf_end;
char buf_temp[VTY_BUFSIZ];
char *buf_temp_p = buf_temp;
uint16_t start = 0;;
int len = 0;
int len_remain = 0;
int len_temp = 0;
if (!vtycmd_ctrl.is_connect)
{
vtycmd_ctrl.out_buf_start = 0;
vtycmd_ctrl.out_buf_end = 0;
return;
}
len = vsnprintf(buf_temp, VTY_BUFSIZ, format, va);
if (len <= 0)
{
return;
}
/* 计算可写入的数据量 */
start = vtycmd_ctrl.out_buf_start;
if (vtycmd_ctrl.out_buf_end >= start)
{
len_remain = VTYCMD_BUF_OUT_LEN - 1 - (vtycmd_ctrl.out_buf_end - start);
}
else
{
len_remain = start - vtycmd_ctrl.out_buf_end - 1;
}
if (len_remain <= 0)
{
return;
}
if (len > len_remain)
{
len = len_remain;
}
/* 超过 out_buf 需要分段存储 */
if (len + vtycmd_ctrl.out_buf_end > VTYCMD_BUF_OUT_LEN)
{
len_temp = VTYCMD_BUF_OUT_LEN - vtycmd_ctrl.out_buf_end;
memcpy(buf, buf_temp_p, len_temp);
vtycmd_ctrl.out_buf_end = 0;
buf = vtycmd_ctrl.out_buf;
buf_temp_p = buf_temp_p + len_temp;
len -= len_temp;
memcpy(buf, buf_temp_p, len);
vtycmd_ctrl.out_buf_end += len;
}
else
{
memcpy(buf, buf_temp_p, len);
len_temp = vtycmd_ctrl.out_buf_end + len;
if (VTYCMD_BUF_OUT_LEN == len_temp)
{
vtycmd_ctrl.out_buf_end = 0;
}
else
{
vtycmd_ctrl.out_buf_end = len_temp;
}
}
}
/************************ (C) COPYRIGHT LandPower ***** END OF FILE ****/