sshmngr: configurable backend

Make the backend daemon configurable, please note, still need
to disable dropbear as default in config and choose the backend
based on config in sshmngr
This commit is contained in:
Rahul Thakur 2024-03-08 18:51:10 +05:30 committed by Mohd Husaam Mehdi
parent 8557dbc1a7
commit 1393125ceb
13 changed files with 387 additions and 1059 deletions

View file

@ -8,12 +8,18 @@ choice
config SSHMNGR_BACKEND_OPENSSH
bool "Use openssh for ssh"
help
Enable this option to use openssh for ssh.
config SSHMNGR_BACKEND_OPENSSH_PAM
bool "Use openssh with PAM for ssh"
help
Enable this option to use PAM for ssh.
config SSHMNGR_BACKEND_DROPBEAR
bool "Use dropbear for ssh"
help
Enable this option to use dropbear for ssh.
endchoice
endif

View file

@ -7,33 +7,31 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=sshmngr
PKG_VERSION:=1.0.0
LOCAL_DEV:=0
ifneq ($(LOCAL_DEV),1)
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://dev.iopsys.eu/network/sshmngr.git
PKG_SOURCE_VERSION:=9758a7a0f798ad2b19597f4ec161e82edbdb2753
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz
PKG_MIRROR_HASH:=skip
endif
PKG_LICENSE:=BSD-3-Clause
PKG_LICENSE_FILES:=LICENSE
include $(INCLUDE_DIR)/package.mk
include ../bbfdm/bbfdm.mk
BACKEND:=+dropbear
ifeq ($(CONFIG_SSHMNGR_BACKEND_OPENSSH),y)
BACKEND:=+openssh-server +openssh-client-utils
endif
ifeq ($(CONFIG_SSHMNGR_BACKEND_OPENSSH_PAM),y)
BACKEND:=+openssh-server-pam +openssh-client-utils
endif
ifeq ($(CONFIG_SSHMNGR_BACKEND_DROPBEAR),y)
BACKEND:=+dropbear
endif
$(info "BACKEND IS:")
$(info $(BACKEND))
MAKE_PATH:=src
define Package/sshmngr
SECTION:=utils
CATEGORY:=Utilities
SUBMENU:=TRx69
SECTION:=net
CATEGORY:=Network
TITLE:=Package to add Device.SSH data model support.
DEPENDS:=$(BACKEND) +libuci +libubox +libubus +libblobmsg-json +libjson-c +libbbfdm-api
$(info $(DEPENDS))
DEPENDS:=+libuci +libubox +libubus +libblobmsg-json +libjson-c +libbbfdm-api
DEPENDS+=+SSHMNGR_BACKEND_OPENSSH:openssh-server +SSHMNGR_BACKEND_OPENSSH:openssh-client-utils
DEPENDS+=+SSHMNGR_BACKEND_OPENSSH_PAM:openssh-server-pam +SSHMNGR_BACKEND_OPENSSH_PAM:openssh-client-utils
DEPENDS+=+SSHMNGR_BACKEND_DROPBEAR:dropbear
endef
define Package/sshmngr/description
@ -44,10 +42,21 @@ define Package/$(PKG_NAME)/config
source "$(SOURCE)/Config.in"
endef
ifeq ($(LOCAL_DEV),1)
define Build/Prepare
$(CP) -rf ./sshmngr/* $(PKG_BUILD_DIR)/
endef
endif
define Package/sshmngr/install
$(INSTALL_DIR) $(1)/etc/sshmngr
$(CP) ./files/* $(1)/
$(CP) $(PKG_BUILD_DIR)/libsshmngr.so $(1)/etc/sshmngr
$(CP) ./files/common/* $(1)/
ifeq ($(CONFIG_SSHMNGR_BACKEND_DROPBEAR),y)
$(CP) ./files/dropbear_backend/* $(1)/
else
$(CP) ./files/openssh_backend/* $(1)/
endif
$(CP) $(PKG_BUILD_DIR)/src/libsshmngr.so $(1)/etc/sshmngr
endef
$(eval $(call BuildPackage,sshmngr))

View file

@ -0,0 +1,6 @@
config server 'ssh1'
option enable '1'
option PasswordAuth '1'
option RootPasswordAuth '1'
option RootLogin '1'
option Port '22'

View file

@ -0,0 +1,15 @@
#!/bin/sh /etc/rc.common
START=18
USE_PROCD=1
. /lib/sshmngr/sshmngr.sh
start_service() {
start_sshmngr_service
}
service_triggers() {
configure_ssh
procd_add_reload_trigger sshmngr
}

View file

@ -0,0 +1,71 @@
#!/bin/sh
. /etc/bbfdm/bbfdm_services.sh
. /lib/sshmngr/backend.sh
SSHMNGR_JSON_INPUT="/etc/sshmngr/input.json"
TEMP_UCI_PATH="/tmp/sshmngr"
start_sshmngr_service()
{
bbfdm_add_service "bbfdm.sshmngr" "${SSHMNGR_JSON_INPUT}"
}
handle_server_section()
{
local cfg="$1"
local enable
local PasswordAuth=""
local Port=""
local RootPasswordAuth=""
local RootLogin=""
local Interface=""
local SSHKeepAlive=""
local IdleTimeout=""
local MaxAuthTries=""
local ServerName="${cfg}"
config_get_bool enable $cfg enable 0
[ $enable -eq 0 ] && return
config_get PasswordAuth $cfg PasswordAuth
config_get Port $cfg Port
config_get RootPasswordAuth $cfg RootPasswordAuth
config_get RootLogin $cfg RootLogin
config_get Interface $cfg Interface
config_get SSHKeepAlive $cfg SSHKeepAlive
config_get IdleTimeout $cfg IdleTimeout
config_get MaxAuthTries $cfg MaxAuthTries
# add section
uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName=$CONFIG
# set options
[ -n "$PasswordAuth" ] && uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName.PasswordAuth=$PasswordAuth
[ -n "$Port" ] && uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName.Port=$Port
[ -n "$RootPasswordAuth" ] && uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName.RootPasswordAuth=$RootPasswordAuth
[ -n "$RootLogin" ] && uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName.RootLogin=$RootLogin
[ -n "$Interface" ] && uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName.Interface=$Interface
[ -n "$SSHKeepAlive" ] && uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName.SSHKeepAlive=$SSHKeepAlive
[ -n "$IdleTimeout" ] && uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName.IdleTimeout=$IdleTimeout
[ -n "$MaxAuthTries" ] && uci -c "$TEMP_UCI_PATH" set $CONFIG.$ServerName.MaxAuthTries=$MaxAuthTries
}
configure_ssh()
{
# remove temp UCI
rm -rf "$TEMP_UCI_PATH"/$CONFIG 2>/dev/null
mkdir -p "$TEMP_UCI_PATH"
touch "$TEMP_UCI_PATH"/$CONFIG
# read all sshmngr server sections and then apply them to $CONFIG UCI
config_load sshmngr
config_foreach handle_server_section server
uci -c "$TEMP_UCI_PATH" commit $CONFIG
cp "$TEMP_UCI_PATH"/$CONFIG /etc/config/$CONFIG
/etc/init.d/$CONFIG reload
}

View file

@ -0,0 +1,222 @@
#!/bin/sh
. /usr/share/libubox/jshn.sh
. /lib/sshmngr/backend.sh
TEMP_KEY_FILE="/tmp/tempkeyfile"
add_server_name()
{
local server_sec="${1}"
config_get_bool enable "${server_sec}" enable 0
if [ "${enable}" -eq 0 ]; then
return
fi
server_names="${server_names} ${server_sec}"
}
get_all_servers()
{
server_names=""
config_load sshmngr
config_foreach add_server_name server
echo "${server_names}"
}
get_pid()
{
local server_name="$1"
local pid_file="$(get_pid_file "$server_name")"
local server_pid=0
# if proper file exists
if [ -f "${pid_file}" ] && [ -s "${pid_file}" ]; then
server_pid="$(cat "${pid_file}")"
fi
echo "$server_pid"
}
case "$1" in
list)
echo '{ "dump" : {"server_name":"string"}, "kill_session" : {"session_pid":"string","server_name":"string"}, "list_keys" : {}, "add_pubkey" : {"current_key":"string","new_key":"string"}, "remove_pubkey" : {"key":"string"} }'
;;
call)
case "$2" in
dump)
read -r input
json_load "${input}"
json_get_var server_name "server_name"
json_cleanup
if [ -z "$server_name" ]; then
servers="$(get_all_servers)"
else
servers="$server_name"
fi
json_init
for server in $servers; do
json_add_object "$server"
pid_file="$(get_pid_file "$server")"
server_pid="$(get_pid "$server")"
if [ "$server_pid" -eq 0 ]; then
return
fi
# get all current sessions
session_pids="$(get_session_pids "$pid_file")"
json_add_string "pid" "$server_pid"
json_add_array "sessions"
for session_pid in $session_pids; do
# if pid equals server pid then skip
[ "$session_pid" -eq "$server_pid" ] && continue
# get this session's ppid
session_ppid="$(grep PPid /proc/$session_pid/status | awk '{print $2}')"
# if session's parent is this server
if [ "$session_ppid" -eq "$server_pid" ]; then
session="$(netstat -ntp | grep $session_pid/ | awk '{print $5,$7}' | cut -d'/' -f 1)"
# return session IP, Port, PID
ip=$(echo "$session" | cut -d ':' -f 1)
port=$(echo "$session" | cut -d ':' -f 2 | cut -d ' ' -f 1)
pid=$(echo "$session" | cut -d ':' -f 2 | cut -d ' ' -f 2)
json_add_object
json_add_string "ip" "$ip"
json_add_string "port" "$port"
json_add_string "pid" "$pid"
json_close_object
fi
done
json_close_array
json_close_object
done
json_dump
;;
kill_session)
read -r input
json_load "${input}"
json_get_var session_pid "session_pid"
json_get_var server_name "server_name"
json_cleanup
if [ "$session_pid" -gt 0 ]; then
kill -15 "$session_pid"
else
[ -n "$server_name" ] || return
# if server_name is present
# get all current sessions
pid_file="$(get_pid_file "$server_name")"
server_pid="$(get_pid "$server_name")"
if [ "$server_pid" -eq 0 ]; then
return
fi
# get all current sessions
session_pids="$(get_session_pids "$pid_file")"
for session_pid in $session_pids; do
# if pid equals server pid then skip
[ "$session_pid" -eq "$server_pid" ] && continue
# get this session's ppid
session_ppid="$(grep PPid /proc/$session_pid/status | awk '{print $2}')"
# if session's parent is this server
if [ "$session_ppid" -eq "$server_pid" ]; then
kill -15 "$session_pid"
fi
done
fi
;;
list_keys)
# remove empty lines from file
sed -i '/^[[:space:]]*$/d' "$KEY_FILE"
json_init
json_add_array "keys"
while read line; do
json_add_string "key" "${line}"
done < "$KEY_FILE"
json_close_array
json_dump
;;
add_pubkey)
read -r input
json_load "${input}"
json_get_var current_key "current_key"
json_get_var new_key "new_key"
json_cleanup
if [ -n "${new_key}" ]; then
if [ -n "${current_key}" ]; then
rm -rf TEMP_KEY_FILE
touch TEMP_KEY_FILE
# sed -i "s/${current_key}/${new_key}/g" ${KEY_FILE}
# sed is not advisable because the separator ("/") or anything else
# can be present in the string
while read line; do
if [ "${line}" == "${current_key}" ]; then
echo "${new_key}" >> TEMP_KEY_FILE
else
echo "${line}" >> TEMP_KEY_FILE
fi
done < "$KEY_FILE"
mv TEMP_KEY_FILE "$KEY_FILE"
else
echo "${new_key}" >> ${KEY_FILE}
fi
fi
;;
remove_pubkey)
read -r input
json_load "${input}"
json_get_var key "key"
json_cleanup
if [ -n "${key}" ]; then
rm -rf TEMP_KEY_FILE
touch TEMP_KEY_FILE
# sed -i "/${key}/d" ${KEY_FILE}
# sed -i "s/${current_key}/${new_key}/g" ${KEY_FILE}
# sed is not advisable because the separator ("/") or anything else
# can be present in the string
while read line; do
if [ "${line}" != "${key}" ]; then
echo "${line}" >> TEMP_KEY_FILE
fi
done < "$KEY_FILE"
mv TEMP_KEY_FILE "$KEY_FILE"
fi
;;
esac
;;
esac

View file

@ -0,0 +1,20 @@
#!/bin/sh
. /lib/functions.sh
KEY_FILE="/etc/dropbear/authorized_keys"
CONFIG="dropbear"
get_pid_file()
{
local ServerName="$1"
echo "/var/run/$CONFIG.$ServerName.pid"
}
get_session_pids()
{
local PidFile="$1"
echo "$(ps -w | grep $PidFile | grep -v grep | awk '{print $1}')"
}

View file

@ -1,16 +0,0 @@
#!/bin/sh /etc/rc.common
START=18
USE_PROCD=1
. /etc/bbfdm/bbfdm_services.sh
SSHMNGR_JSON_INPUT="/etc/sshmngr/input.json"
start_service() {
bbfdm_add_service "bbfdm.sshmngr" "${SSHMNGR_JSON_INPUT}"
}
service_triggers() {
procd_add_reload_trigger sshmngr
}

View file

@ -0,0 +1,18 @@
#!/bin/sh
. /lib/functions.sh
CONFIG="sshd"
KEY_FILE="/root/.ssh/authorized_keys"
get_pid_file()
{
local ServerName="$1"
echo "/var/run/$CONFIG.$ServerName.pid"
}
get_session_pids()
{
echo "$(ps -w | grep sshd | grep pts | awk '{print $1}')"
}

View file

@ -1,17 +0,0 @@
LIB = libsshmngr.so
LIB_OBJS = sshmngr.o
PROG_CFLAGS = $(CFLAGS) -Wall -Werror -fPIC
LIB_LDFLAGS = $(LDFLAGS)
%.o: %.c
$(CC) $(PROG_CFLAGS) -c -o $@ $<
all: $(LIB)
$(LIB): $(LIB_OBJS)
$(CC) $(PROG_CFLAGS) -shared -o $@ $^ $(LIB_LDFLAGS)
clean:
rm -f *.o $(LIB)

View file

@ -1,982 +0,0 @@
/*
* Copyright (C) 2023 iopsys Software Solutions AB
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* Author Suvendhu Hansa <suvendhu.hansa@iopsys.eu>
*
*/
#include "sshmngr.h"
#define DROPBEAR_KEY_FILE "/etc/dropbear/authorized_keys"
struct ssh_session_args {
struct list_head list;
char ip[45];
char port[6];
char pid[15];
};
struct dmmap_ssh
{
struct list_head list;
struct uci_section *config_section;
struct uci_section *dmmap_section;
struct list_head *sessions;
};
static void add_pubkey(const char *cur, const char *new)
{
if (DM_STRLEN(cur) == 0) {
if (DM_STRLEN(new) == 0)
return;
FILE *fp = fopen(DROPBEAR_KEY_FILE, "a");
if (fp != NULL) {
fputs(new, fp);
fclose(fp);
}
} else {
char line[5120] = {0};
FILE *fp = fopen(DROPBEAR_KEY_FILE, "r");
FILE *tmp = fopen("replace.tmp", "w");
if (fp == NULL || tmp == NULL)
return;
while (fgets(line, sizeof(line), fp) != NULL) {
remove_new_line(line);
if (DM_STRCMP(line, cur) == 0 && DM_STRLEN(new) != 0)
fputs(new, tmp);
else
fputs(line, tmp);
}
fclose(fp);
fclose(tmp);
remove(DROPBEAR_KEY_FILE);
rename("replace.tmp", DROPBEAR_KEY_FILE);
}
}
static void remove_pubkey(const char *key)
{
if (DM_STRLEN(key) == 0)
return;
char line[5120] = {0};
FILE *fp = fopen(DROPBEAR_KEY_FILE, "r");
FILE *tmp = fopen("replace.tmp", "w");
if (fp == NULL || tmp == NULL)
return;
while (fgets(line, sizeof(line), fp) != NULL) {
remove_new_line(line);
if (DM_STRCMP(line, key) != 0)
fputs(line, tmp);
}
fclose(fp);
fclose(tmp);
remove(DROPBEAR_KEY_FILE);
rename("replace.tmp", DROPBEAR_KEY_FILE);
}
static bool key_exist_in_keyfile(const char *key)
{
if (DM_STRLEN(key) == 0)
return true;
char line[5120] = {0};
FILE *fp = fopen(DROPBEAR_KEY_FILE, "r");
if (fp == NULL)
return false;
bool ret = false;
while (fgets(line, sizeof(line), fp) != NULL) {
remove_new_line(line);
if (DM_STRCMP(line, key) == 0) {
ret = true;
break;
}
}
fclose(fp);
return ret;
}
static bool key_exists(const char *key)
{
struct uci_section *s = NULL, *stmp = NULL;
bool exists = false;
uci_path_foreach_sections_safe(bbfdm, "dmmap_dropbear", "authkey", stmp, s) {
char *val = NULL;
dmuci_get_value_by_section_string(s, "pubkey", &val);
if (DM_STRCMP(val, key) == 0) {
exists = true;
break;
}
}
return exists;
}
static void add_ssh_config_dup_list(struct list_head *dup_list, struct uci_section *config_section, struct uci_section *dmmap_section)
{
struct dmmap_ssh *dmmap_config;
dmmap_config = dmcalloc(1, sizeof(struct dmmap_ssh));
list_add_tail(&dmmap_config->list, dup_list);
dmmap_config->config_section = config_section;
dmmap_config->dmmap_section = dmmap_section;
}
static void free_ssh_config_dup_list(struct list_head *dup_list)
{
struct dmmap_ssh *dmmap_config = NULL, *tmp = NULL;
list_for_each_entry_safe(dmmap_config, tmp, dup_list, list) {
list_del(&dmmap_config->list);
dmfree(dmmap_config);
}
}
static void synchronize_ssh_config_sections_with_dmmap(char *package, char *section_type, char *dmmap_package, struct list_head *dup_list)
{
struct uci_section *s, *stmp, *dmmap_sect;
char *v;
uci_foreach_sections(package, section_type, s) {
/*
* create/update corresponding dmmap section that have same config_section link and using param_value_array
*/
if ((dmmap_sect = get_dup_section_in_dmmap(dmmap_package, section_type, section_name(s))) == NULL) {
dmuci_add_section_bbfdm(dmmap_package, section_type, &dmmap_sect);
dmuci_set_value_by_section_bbfdm(dmmap_sect, "section_name", section_name(s));
}
/*
* Add system and dmmap sections to the list
*/
add_ssh_config_dup_list(dup_list, s, dmmap_sect);
}
/*
* Delete unused dmmap sections
*/
uci_path_foreach_sections_safe(bbfdm, dmmap_package, section_type, stmp, s) {
dmuci_get_value_by_section_string(s, "section_name", &v);
if (get_origin_section_from_config(package, section_type, v) == NULL)
dmuci_delete_by_section(s, NULL, NULL);
}
}
static void ssh_server_session_init(struct list_head *sess_list)
{
char cmd[512] = {0};
snprintf(cmd, sizeof(cmd), "netstat -ntp | grep dropbear | awk \'{print $5,$7}\' | cut -d\'/\' -f 1");
FILE *pp = popen(cmd, "r");
if (pp == NULL)
return;
char line[256] = {0};
while (fgets(line, sizeof(line), pp) != NULL) {
remove_new_line(line);
if (DM_STRLEN(line) == 0)
continue;
char *port = NULL;
char *pid = NULL;
char *chr = strchr(line, ':');
if (chr) {
*chr = 0;
port = chr + 1;
}
if (DM_STRLEN(port) != 0) {
char *sp = strchr(port, ' ');
if (sp) {
*sp = 0;
pid = sp + 1;
}
}
struct ssh_session_args *session = dmcalloc(1, sizeof(struct ssh_session_args));
if (session == NULL)
break;
strncpy(session->ip, line, sizeof(session->ip));
if (DM_STRLEN(port) != 0) {
snprintf(session->port, sizeof(session->port), "%s", port);
}
if (DM_STRLEN(pid) != 0) {
snprintf(session->pid, sizeof(session->pid), "%s", pid);
}
list_add_tail(&session->list, sess_list);
}
pclose(pp);
}
static void free_ssh_session_list(struct list_head *sess_list)
{
struct ssh_session_args *session = NULL, *tmp = NULL;
list_for_each_entry_safe(session, tmp, sess_list, list) {
list_del(&session->list);
dmfree(session);
}
}
static void close_active_sessions(struct uci_section *s)
{
bool b;
char *value = dmuci_get_value_by_section_fallback_def(s, "enable", "1");
string_to_bool(value, &b);
if (!b)
return;
char pid_file[125] = {0};
char *sec_name = section_name(s);
if (DM_STRLEN(sec_name) == 0)
return;
snprintf(pid_file, sizeof(pid_file), "/var/run/dropbear.%s.pid", sec_name);
if (DM_STRLEN(pid_file) == 0)
return;
FILE *fp = fopen(pid_file, "r");
if (fp == NULL)
return;
unsigned int pid;
if (fscanf(fp, "%u", &pid) != 1) {
fclose(fp);
return;
}
fclose(fp);
char cmd[512] = {0};
snprintf(cmd, sizeof(cmd), "ps -w | grep %s | grep -v grep | awk \'{print $1}\'", pid_file);
FILE *pp = popen(cmd, "r");
if (pp == NULL)
return;
char line[15] = {0};
while (fgets(line, sizeof(line), pp) != NULL) {
remove_new_line(line);
if (DM_STRLEN(line) == 0)
continue;
if (strtoul(line, NULL, 10) == pid)
continue;
snprintf(cmd, sizeof(cmd), "kill -15 %s", line);
if (system(cmd) == -1) {
break;
}
}
pclose(pp);
}
/*************************************************************
* ADD & DEL OBJ
*************************************************************/
static int addObjSSHServer(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
struct uci_section *s = NULL, *dmmap_s = NULL;
char s_name[16];
snprintf(s_name, sizeof(s_name), "server_%s", *instance);
dmuci_add_section("dropbear", "dropbear", &s);
dmuci_rename_section_by_section(s, s_name);
dmuci_set_value_by_section(s, "enable", "0");
dmuci_set_value_by_section(s, "Port", "22");
dmuci_set_value_by_section(s, "IdleTimeout", "180");
dmuci_set_value_by_section(s, "SSHKeepAlive", "300");
dmuci_set_value_by_section(s, "RootLogin", "0");
dmuci_set_value_by_section(s, "PasswordAuth", "0");
dmuci_set_value_by_section(s, "RootPasswordAuth", "0");
dmuci_set_value_by_section(s, "MaxAuthTries", "3");
dmuci_add_section_bbfdm("dmmap_dropbear", "dropbear", &dmmap_s);
dmuci_set_value_by_section(dmmap_s, "section_name", s_name);
dmuci_set_value_by_section(dmmap_s, "server_instance", *instance);
return 0;
}
static int delObjSSHServer(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
struct uci_section *s = NULL, *stmp = NULL;
switch (del_action) {
case DEL_INST:
close_active_sessions(((struct dmmap_ssh *)data)->config_section);
dmuci_delete_by_section(((struct dmmap_ssh *)data)->config_section, NULL, NULL);
dmuci_delete_by_section(((struct dmmap_ssh *)data)->dmmap_section, NULL, NULL);
break;
case DEL_ALL:
uci_foreach_sections_safe("dropbear", "dropbear", stmp, s) {
struct uci_section *dmmap_section = NULL;
get_dmmap_section_of_config_section("dmmap_dropbear", "dropbear", section_name(s), &dmmap_section);
close_active_sessions(s);
dmuci_delete_by_section(s, NULL, NULL);
dmuci_delete_by_section(dmmap_section, NULL, NULL);
}
break;
}
return 0;
}
static int addObjSSHKey(char *refparam, struct dmctx *ctx, void *data, char **instance)
{
struct uci_section *s = NULL;
dmuci_add_section_bbfdm("dmmap_dropbear", "authkey", &s);
return 0;
}
static int delObjSSHKey(char *refparam, struct dmctx *ctx, void *data, char *instance, unsigned char del_action)
{
struct uci_section *s = NULL, *stmp = NULL;
char *value = NULL;
switch (del_action) {
case DEL_INST:
dmuci_get_value_by_section_string((struct uci_section *)data, "pubkey", &value);
remove_pubkey(value);
dmuci_delete_by_section((struct uci_section *)data, NULL, NULL);
break;
case DEL_ALL:
uci_path_foreach_sections_safe(bbfdm, "dmmap_dropbear", "authkey", stmp, s) {
dmuci_get_value_by_section_string(s, "pubkey", &value);
remove_pubkey(value);
dmuci_delete_by_section(s, NULL, NULL);
}
break;
}
return 0;
}
/*************************************************************
* ENTRY METHODS
*************************************************************/
static int browseSSHServerInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
struct dmmap_ssh *p = NULL;
LIST_HEAD(dup_list);
LIST_HEAD(session_list);
char *inst = NULL;
ssh_server_session_init(&session_list);
synchronize_ssh_config_sections_with_dmmap("dropbear", "dropbear", "dmmap_dropbear", &dup_list);
list_for_each_entry(p, &dup_list, list) {
bool b;
char *enable = dmuci_get_value_by_section_fallback_def(p->config_section, "enable", "1");
char *act_date = dmuci_get_value_by_section_fallback_def(p->dmmap_section, "activationdate", "");
string_to_bool(enable, &b);
if (b && DM_STRLEN(act_date) == 0) {
char *tm = NULL;
dm_time_format(time(NULL), &tm);
dmuci_set_value_by_section(p->dmmap_section, "activationdate", tm);
}
p->sessions = &session_list;
inst = handle_instance(dmctx, parent_node, p->dmmap_section, "server_instance", "server_alias");
if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)p, inst) == DM_STOP)
break;
}
free_ssh_session_list(&session_list);
free_ssh_config_dup_list(&dup_list);
return 0;
}
static int browseSSHKeyInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
struct uci_section *s = NULL, *stmp = NULL;
char *inst = NULL;
char line[5120] = {0};
/* create entries for the keys available in DROPBEAR_KEY_FILE but not in dmmap */
FILE *fp = fopen(DROPBEAR_KEY_FILE, "r");
if (fp != NULL) {
while (fgets(line, sizeof(line), fp) != NULL) {
struct uci_section *dmmap_sect = NULL;
remove_new_line(line);
bool exists = false;
uci_path_foreach_sections_safe(bbfdm, "dmmap_dropbear", "authkey", stmp, s) {
char *val = NULL;
dmuci_get_value_by_section_string(s, "pubkey", &val);
if (DM_STRCMP(val, line) == 0) {
exists = true;
break;
}
}
if (!exists) {
dmmap_sect = NULL;
dmuci_add_section_bbfdm("dmmap_dropbear", "authkey", &dmmap_sect);
dmuci_set_value_by_section(dmmap_sect, "pubkey", line);
}
}
fclose(fp);
}
/* delete keys from dmmap which are not available in DROPBEAR_KEY_FILE */
stmp = NULL;
s = NULL;
uci_path_foreach_sections_safe(bbfdm, "dmmap_dropbear", "authkey", stmp, s) {
char *val = NULL;
dmuci_get_value_by_section_string(s, "pubkey", &val);
if (!key_exist_in_keyfile(val)) {
dmuci_delete_by_section(s, NULL, NULL);
}
}
/* enlist objects */
stmp = NULL;
s = NULL;
uci_path_foreach_sections_safe(bbfdm, "dmmap_dropbear", "authkey", stmp, s) {
inst = handle_instance(dmctx, parent_node, s, "instance", "alias");
if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)s, inst) == DM_STOP)
break;
}
return 0;
}
static int browseSSHServerSessionInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
bool b;
char *inst = NULL;
int id = 0;
struct uci_section *s = ((struct dmmap_ssh *)prev_data)->config_section;
char *value = dmuci_get_value_by_section_fallback_def(s, "enable", "1");
string_to_bool(value, &b);
if (!b)
return 0;
char pid_file[125] = {0};
char *sec_name = section_name(s);
if (DM_STRLEN(sec_name) == 0)
return 0;
snprintf(pid_file, sizeof(pid_file), "/var/run/dropbear.%s.pid", sec_name);
if (DM_STRLEN(pid_file) == 0)
return 0;
FILE *fp = fopen(pid_file, "r");
if (fp == NULL)
return 0;
unsigned int pid;
if (fscanf(fp, "%u", &pid) != 1) {
fclose(fp);
return 0;
}
fclose(fp);
char cmd[512] = {0};
snprintf(cmd, sizeof(cmd), "ps -w | grep %s | grep -v grep | awk \'{print $1}\'", pid_file);
FILE *pp = popen(cmd, "r");
if (pp == NULL) {
return 0;
}
char line[15] = {0};
while (fgets(line, sizeof(line), pp) != NULL) {
remove_new_line(line);
if (DM_STRLEN(line) == 0)
continue;
if (strtoul(line, NULL, 10) == pid) {
continue;
}
struct ssh_session_args *session = NULL;
struct list_head *sess_list = ((struct dmmap_ssh *)prev_data)->sessions;
bool found = false;
list_for_each_entry(session, sess_list, list) {
if (DM_STRCMP(session->pid, line) == 0) {
found = true;
break;
}
}
if (found) {
inst = handle_instance_without_section(dmctx, parent_node, ++id);
if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)session, inst) == DM_STOP)
break;
}
}
pclose(pp);
return 0;
}
/*************************************************************
* GET & SET PARAM
**************************************************************/
static int get_ssh_server_num(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
int cnt = get_number_of_entries(ctx, data, instance, browseSSHServerInst);
dmasprintf(value, "%d", cnt);
return 0;
}
static int get_ssh_key_num(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
int cnt = get_number_of_entries(ctx, data, instance, browseSSHKeyInst);
dmasprintf(value, "%d", cnt);
return 0;
}
static int get_ssh_status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = "Enabled";
return 0;
}
static int get_ssh_server_session_num(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
int cnt = get_number_of_entries(ctx, data, instance, browseSSHServerSessionInst);
dmasprintf(value, "%d", cnt);
return 0;
}
static int get_ssh_server_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "enable", "1");
return 0;
}
static int set_ssh_server_enable(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
bool b;
switch (action) {
case VALUECHECK:
if (bbfdm_validate_boolean(ctx, value))
return FAULT_9007;
break;
case VALUESET:
string_to_bool(value, &b);
char *cur = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "enable", "1");
bool cur_val;
string_to_bool(cur, &cur_val);
if (b == cur_val)
break;
if (b) {
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "enable", "1");
char *tm = NULL;
dm_time_format(time(NULL), &tm);
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->dmmap_section, "activationdate", tm);
} else {
close_active_sessions(((struct dmmap_ssh *)data)->config_section);
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "enable", "0");
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->dmmap_section, "activationdate", "");
}
break;
}
return 0;
}
static int get_ssh_server_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
return bbf_get_alias(ctx, ((struct dmmap_ssh *)data)->dmmap_section, "server_alias", instance, value);
}
static int set_ssh_server_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
return bbf_set_alias(ctx, ((struct dmmap_ssh *)data)->dmmap_section, "server_alias", instance, value);
}
static int get_ssh_server_interface(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
char *linker = NULL;
dmuci_get_value_by_section_string(((struct dmmap_ssh *)data)->config_section, "Interface", &linker);
adm_entry_get_reference_param(ctx, "Device.IP.Interface.*.Name", linker, value);
return 0;
}
static int set_ssh_server_interface(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
char *allowed_objects[] = {"Device.IP.Interface.", NULL};
struct dm_reference reference = {0};
bbf_get_reference_args(value, &reference);
switch (action) {
case VALUECHECK:
if (bbfdm_validate_string(ctx, reference.path, -1, 256, NULL, NULL))
return FAULT_9007;
if (dm_validate_allowed_objects(ctx, &reference, allowed_objects))
return FAULT_9007;
break;
case VALUESET:
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "Interface", reference.value);
break;
}
return 0;
}
static int get_ssh_server_port(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "Port", "22");
return 0;
}
static int set_ssh_server_port(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
switch (action) {
case VALUECHECK:
if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{"1","65535"}},1))
return FAULT_9007;
break;
case VALUESET:
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "Port", value);
break;
}
return 0;
}
static int get_ssh_server_idle(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "IdleTimeout", "0");
return 0;
}
static int set_ssh_server_idle(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
switch (action) {
case VALUECHECK:
if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}},1))
return FAULT_9007;
break;
case VALUESET:
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "IdleTimeout", value);
break;
}
return 0;
}
static int get_ssh_server_keepalive(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "SSHKeepAlive", "300");
return 0;
}
static int set_ssh_server_keepalive(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
switch (action) {
case VALUECHECK:
if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}},1))
return FAULT_9007;
break;
case VALUESET:
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "SSHKeepAlive", value);
break;
}
return 0;
}
static int get_ssh_server_rootlogin(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "RootLogin", "1");
return 0;
}
static int set_ssh_server_rootlogin(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
bool b;
switch (action) {
case VALUECHECK:
if (bbfdm_validate_boolean(ctx, value))
return FAULT_9007;
break;
case VALUESET:
string_to_bool(value, &b);
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "RootLogin", b ? "1" : "0");
break;
}
return 0;
}
static int get_ssh_server_passwordlogin(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "PasswordAuth", "1");
return 0;
}
static int set_ssh_server_passwordlogin(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
bool b;
switch (action) {
case VALUECHECK:
if (bbfdm_validate_boolean(ctx, value))
return FAULT_9007;
break;
case VALUESET:
string_to_bool(value, &b);
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "PasswordAuth", b ? "1" : "0");
break;
}
return 0;
}
static int get_ssh_server_rootpasswordlogin(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "RootPasswordAuth", "1");
return 0;
}
static int set_ssh_server_rootpasswordlogin(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
bool b;
switch (action) {
case VALUECHECK:
if (bbfdm_validate_boolean(ctx, value))
return FAULT_9007;
break;
case VALUESET:
string_to_bool(value, &b);
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "RootPasswordAuth", b ? "1" : "0");
break;
}
return 0;
}
static int get_ssh_server_maxauthtries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
*value = dmuci_get_value_by_section_fallback_def(((struct dmmap_ssh *)data)->config_section, "MaxAuthTries", "3");
return 0;
}
static int set_ssh_server_maxauthtries(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
switch (action) {
case VALUECHECK:
if (bbfdm_validate_unsignedInt(ctx, value, RANGE_ARGS{{NULL,NULL}},1))
return FAULT_9007;
break;
case VALUESET:
dmuci_set_value_by_section(((struct dmmap_ssh *)data)->config_section, "MaxAuthTries", value);
break;
}
return 0;
}
static int get_ssh_server_activationdate(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
dmuci_get_value_by_section_string(((struct dmmap_ssh *)data)->dmmap_section, "activationdate", value);
return 0;
}
static int get_ssh_server_pid(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
bool b;
struct uci_section *s = ((struct dmmap_ssh *)data)->config_section;
char *en = dmuci_get_value_by_section_fallback_def(s, "enable", "1");
*value = "0";
string_to_bool(en, &b);
if (!b)
return 0;
char *sec_name = section_name(s);
if (DM_STRLEN(sec_name) == 0)
return 0;
char pid_file[256] = {0};
snprintf(pid_file, sizeof(pid_file), "/var/run/dropbear.%s.pid", sec_name);
if (DM_STRLEN(pid_file) == 0)
return 0;
FILE *fp = fopen(pid_file, "r");
if (fp == NULL)
return 0;
unsigned int pid;
if (fscanf(fp, "%u", &pid) != 1) {
fclose(fp);
return 0;
}
fclose(fp);
dmasprintf(value, "%u", pid);
return 0;
}
static int get_ssh_server_session_ip(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
struct ssh_session_args *args = (struct ssh_session_args *)data;
*value = DM_STRLEN(args->ip) ? dmstrdup(args->ip) : "";
return 0;
}
static int get_ssh_server_session_port(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
struct ssh_session_args *args = (struct ssh_session_args *)data;
*value = DM_STRLEN(args->port) ? dmstrdup(args->port) : "";
return 0;
}
static int get_ssh_key_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
return bbf_get_alias(ctx, (struct uci_section *)data, "alias", instance, value);
}
static int set_ssh_key_alias(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
return bbf_set_alias(ctx, (struct uci_section *)data, "alias", instance, value);
}
static int get_ssh_key_pubkey(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
dmuci_get_value_by_section_string((struct uci_section *)data, "pubkey", value);
return 0;
}
static int set_ssh_key_pubkey(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
char *cur_val = NULL;
switch (action) {
case VALUECHECK:
if (bbfdm_validate_string(ctx, value, -1, -1, NULL, NULL))
return FAULT_9007;
/* check if same as current key value */
dmuci_get_value_by_section_string((struct uci_section *)data, "pubkey", &cur_val);
if (DM_STRCMP(cur_val, value) == 0)
break;
if (key_exists(value))
return FAULT_9001;
break;
case VALUESET:
/* check if same as current key value then nothing to do */
dmuci_get_value_by_section_string((struct uci_section *)data, "pubkey", &cur_val);
if (DM_STRCMP(cur_val, value) == 0)
break;
add_pubkey(cur_val, value);
dmuci_set_value_by_section((struct uci_section *)data, "pubkey", value);
break;
}
return 0;
}
static int operate_session_delete(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action)
{
int ret = -1;
struct ssh_session_args *args = (struct ssh_session_args *)data;
if (DM_STRLEN(args->pid) != 0) {
char cmd[128] = {0};
snprintf(cmd, sizeof(cmd), "kill -15 %s", args->pid);
ret = system(cmd);
}
return (ret != -1) ? 0 : USP_FAULT_COMMAND_FAILURE;
}
/**********************************************************************************************************************************
* OBJ & LEAF DEFINITION
***********************************************************************************************************************************/
/* *** Device.SSH. *** */
DMOBJ tDeviceSSHObj[] = {
{"SSH", &DMREAD, NULL, NULL, "file:/etc/config/dropbear", NULL, NULL, NULL, tSSHObj, tSSHParams, NULL, BBFDM_BOTH, NULL},
{0}
};
DMOBJ tSSHObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"Server", &DMWRITE, addObjSSHServer, delObjSSHServer, NULL, browseSSHServerInst, NULL, NULL, tSSHServerObj, tSSHServerParams, NULL, BBFDM_BOTH, NULL},
{"AuthorizedKey", &DMWRITE, addObjSSHKey, delObjSSHKey, NULL, browseSSHKeyInst, NULL, NULL, NULL, tSSHKeyParams, NULL, BBFDM_BOTH, NULL},
{0}
};
DMOBJ tSSHServerObj[] = {
/* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys, version*/
{"Session", &DMREAD, NULL, NULL, NULL, browseSSHServerSessionInst, NULL, NULL, NULL, tSSHServerSessionParams, NULL, BBFDM_BOTH, NULL},
{0}
};
/* *** Device.SSH. *** */
DMLEAF tSSHParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"ServerNumberOfEntries", &DMREAD, DMT_UNINT, get_ssh_server_num, NULL, BBFDM_BOTH},
{"AuthorizedKeyNumberOfEntries", &DMREAD, DMT_UNINT, get_ssh_key_num, NULL, BBFDM_BOTH},
{"Status", &DMREAD, DMT_STRING, get_ssh_status, NULL, BBFDM_BOTH},
{0}
};
/* *** Device.SSH.Server. *** */
DMLEAF tSSHServerParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Enable", &DMWRITE, DMT_BOOL, get_ssh_server_enable, set_ssh_server_enable, BBFDM_BOTH},
{"Alias", &DMWRITE, DMT_STRING, get_ssh_server_alias, set_ssh_server_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Interface", &DMWRITE, DMT_STRING, get_ssh_server_interface, set_ssh_server_interface, BBFDM_BOTH, DM_FLAG_REFERENCE},
{"Port", &DMWRITE, DMT_UNINT, get_ssh_server_port, set_ssh_server_port, BBFDM_BOTH},
{"IdleTimeout", &DMWRITE, DMT_UNINT, get_ssh_server_idle, set_ssh_server_idle, BBFDM_BOTH},
{"KeepAlive", &DMWRITE, DMT_UNINT, get_ssh_server_keepalive, set_ssh_server_keepalive, BBFDM_BOTH},
{"AllowRootLogin", &DMWRITE, DMT_BOOL, get_ssh_server_rootlogin, set_ssh_server_rootlogin, BBFDM_BOTH},
{"AllowPasswordLogin", &DMWRITE, DMT_BOOL, get_ssh_server_passwordlogin, set_ssh_server_passwordlogin, BBFDM_BOTH},
{"AllowRootPasswordLogin", &DMWRITE, DMT_BOOL, get_ssh_server_rootpasswordlogin, set_ssh_server_rootpasswordlogin, BBFDM_BOTH},
{"MaxAuthTries", &DMWRITE, DMT_UNINT, get_ssh_server_maxauthtries, set_ssh_server_maxauthtries, BBFDM_BOTH},
{"ActivationDate", &DMREAD, DMT_TIME, get_ssh_server_activationdate, NULL, BBFDM_BOTH},
{"PID", &DMREAD, DMT_UNINT, get_ssh_server_pid, NULL, BBFDM_BOTH},
{"SessionNumberOfEntries", &DMREAD, DMT_UNINT, get_ssh_server_session_num, NULL, BBFDM_BOTH},
{0}
};
DMLEAF tSSHServerSessionParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"IPAddress", &DMREAD, DMT_STRING, get_ssh_server_session_ip, NULL, BBFDM_BOTH},
{"Port", &DMREAD, DMT_UNINT, get_ssh_server_session_port, NULL, BBFDM_BOTH},
{"Delete()", &DMSYNC, DMT_COMMAND, NULL, operate_session_delete, BBFDM_USP},
{0}
};
DMLEAF tSSHKeyParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"Alias", &DMWRITE, DMT_STRING, get_ssh_key_alias, set_ssh_key_alias, BBFDM_BOTH, DM_FLAG_UNIQUE},
{"Key", &DMWRITE, DMT_STRING, get_ssh_key_pubkey, set_ssh_key_pubkey, BBFDM_BOTH},
{0}
};
/* ********** DynamicObj ********** */
DM_MAP_OBJ tDynamicObj[] = {
/* parentobj, nextobject, parameter */
{"Device.", tDeviceSSHObj, NULL},
{0}
};

View file

@ -1,24 +0,0 @@
/*
* Copyright (C) 2023 iopsys Software Solutions AB
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation
*
* Author Suvendhu Hansa <suvendhu.hansa@iopsys.eu>
*
*/
#ifndef __SSH_H
#define __SSH_H
#include "libbbfdm-api/dmcommon.h"
extern DMOBJ tSSHObj[];
extern DMOBJ tSSHServerObj[];
extern DMLEAF tSSHParams[];
extern DMLEAF tSSHServerParams[];
extern DMLEAF tSSHServerSessionParams[];
extern DMLEAF tSSHKeyParams[];
#endif