2777 lines
82 KiB
C
2777 lines
82 KiB
C
/*
|
|
******************************************************************************
|
|
* SOURCE FILE
|
|
*
|
|
* Document no: @(#) $Name: VXWORKS_ITER40_RCPL8_2024030303 $ $RCSfile: ipssh.c,v $ $Revision: 1.141.10.31 $
|
|
* $Source: /home/interpeak/CVSRoot/ipssh/src/ipssh.c,v $
|
|
* $Author: yli10 $ $Date: 2020-06-08 06:42:36 $
|
|
* $State: Exp $ $Locker: $
|
|
*
|
|
* INTERPEAK_COPYRIGHT_STRING
|
|
* Design and implementation by Roger Boden <roger@interpeak.se>
|
|
******************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 1 DESCRIPTION
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 2 CONFIGURATION
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 3 INCLUDE FILES
|
|
****************************************************************************
|
|
*/
|
|
#define IPCOM_USE_CLIB_PROTO
|
|
#include "ipssh.h"
|
|
#include "ipssh_h.h"
|
|
#include "ipssh_config.h"
|
|
#include <ipssh.h>
|
|
#include "ipssh_transport.h"
|
|
#ifdef IPSSH_USE_V1
|
|
#include "ipssh_transport_v1.h"
|
|
#include "ipssh_connection_v1.h"
|
|
#endif
|
|
#include "ipssh_connection.h"
|
|
#include "ipssh_sftp.h"
|
|
#include "ipssh_list.h"
|
|
|
|
#include <ipcom_clib.h>
|
|
#include <ipcom_syslog.h>
|
|
#include <ipcom_sock.h>
|
|
#include <ipcom_sock2.h>
|
|
#include <ipcom_buffer.h>
|
|
#include <ipcom_cstyle.h>
|
|
#include <ipcom_type.h>
|
|
#include <ipcom_syslog.h>
|
|
#include <ipcom_getopt.h>
|
|
#include <ipcom_err.h>
|
|
#include <ipcom_os.h>
|
|
#include <ipcom_shell.h>
|
|
#include <ipcom_sysvar.h>
|
|
#include <ipcom_ipd.h>
|
|
|
|
#include <openssl/pem.h>
|
|
#include <openssl/err.h>
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 4 EXTERN PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
IP_EXTERN void ipssh_term_data_free(void * term_data);
|
|
|
|
#ifdef IP_PORT_OSE5
|
|
extern int efs_donate_fd(int fd, Ip_pid_t to);
|
|
extern int efs_receive_fd(Ip_pid_t from);
|
|
#endif
|
|
|
|
#ifdef IP_PORT_VXWORKS
|
|
extern Ip_bool systemSecurityIsEnabled;
|
|
#include <shellHook.h>
|
|
#endif
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 5 DEFINES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 6 TYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
typedef struct Ipssh_config_map_st
|
|
{
|
|
const char *sysvar;
|
|
const char *str;
|
|
Ip_u8 id;
|
|
int flags;
|
|
}
|
|
Ipssh_config_map;
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 7 LOCAL PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
IP_STATIC Ipssh_conn_ctx *ipssh_conn_ctx_new(void);
|
|
IP_STATIC void ipssh_conn_ctx_free(Ipssh_conn_ctx *ctx);
|
|
IP_STATIC void ipssh_conn_ctx_set_srv_ctx(Ipssh_conn_ctx * ctx, Ipssh_srv_ctx *srv_ctx);
|
|
|
|
IP_STATIC void ipssh_close_conn(Ipssh_conn_ctx *ctx);
|
|
|
|
IP_STATIC Ip_err ipssh_activate_v2_alg(Ip_u8 *alg_array, Ip_size_t len, int alg_type);
|
|
|
|
IP_GLOBAL Ip_err ipssh_read_version_str(Ip_fd sock,
|
|
Ipssh_conn_ctx *ctx,
|
|
Ipssh_map_entry *map_entry);
|
|
|
|
IP_GLOBAL void ipssh_launch_server(Ip_fd sock, Ipssh_srv_ctx *srv_ctx);
|
|
|
|
IP_GLOBAL Ip_u8 ipssh_alg_str2id(const char *str);
|
|
|
|
IPCOM_PROCESS(ipssh_server);
|
|
|
|
IP_GLOBAL void ipssh_register_clt(Ipssh_conn_ctx *ctx);
|
|
|
|
IP_GLOBAL void ipssh_unregister_clt(Ipssh_conn_ctx *ctx);
|
|
|
|
IP_GLOBAL Ipssh_map_entry *ipssh_lookup_map_entry(Ipssh_ctx_map *map, Ip_fd sock);
|
|
IP_GLOBAL void ipssh_delete_map_entry(Ipssh_map_entry *entry);
|
|
IP_GLOBAL void ipssh_delete_map_entry_for_sock(Ipssh_ctx_map *map, Ip_fd sock);
|
|
IP_GLOBAL Ip_fd ipssh_map_get_max_sock(Ipssh_ctx_map* map);
|
|
|
|
IPCOM_PROCESS(ipssh);
|
|
|
|
IP_GLOBAL Ip_err ipssh_port_fwd_cb(struct Ipssh_conn_st *ssh_conn, Ip_fd port_fwd_sock, int event);
|
|
|
|
IP_PUBLIC Ip_err ipssh_start(void);
|
|
IP_PUBLIC Ip_err ipssh_create(void);
|
|
|
|
IP_GLOBAL Ip_err ipssh_port_fwd_cb_v1(Ipssh_conn_ctx *ctx, Ip_fd port_fwd_sock, int event);
|
|
IP_GLOBAL Ip_err ipssh_port_fwd_cb_v2(Ipssh_conn_ctx *ctx, Ip_fd port_fwd_sock, int event);
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 8 DATA
|
|
****************************************************************************
|
|
*/
|
|
|
|
IP_STATIC Ipssh_config_map ipssh_alg_ids[] =
|
|
{
|
|
/* Encryption algorithms */
|
|
#ifdef IPSSH_USE_AES
|
|
{"ipssh.enc.aes", IPSSH_ENC_AES128_CBC_STR, IPSSH_ENC_AES128_CBC_ID, IPSSH_FLAG_ALG_ENC},
|
|
{"ipssh.enc.aes192", IPSSH_ENC_AES192_CBC_STR, IPSSH_ENC_AES192_CBC_ID, IPSSH_FLAG_ALG_ENC},
|
|
{"ipssh.enc.aes256", IPSSH_ENC_AES256_CBC_STR, IPSSH_ENC_AES256_CBC_ID, IPSSH_FLAG_ALG_ENC},
|
|
#endif
|
|
#ifdef IPSSH_USE_BLOWFISH
|
|
{"ipssh.enc.blowfish", IPSSH_ENC_BLOWFISH_CBC_STR, IPSSH_ENC_BLOWFISH_CBC_ID, IPSSH_FLAG_ALG_ENC},
|
|
#endif
|
|
#ifdef IPSSH_USE_CAST
|
|
{"ipssh.enc.cast", IPSSH_ENC_CAST128_CBC_STR, IPSSH_ENC_CAST128_CBC_ID, IPSSH_FLAG_ALG_ENC},
|
|
#endif
|
|
#ifdef IPSSH_USE_DES
|
|
{"ipssh.enc.3des", IPSSH_ENC_3DES_CBC_STR, IPSSH_ENC_3DES_CBC_ID, IPSSH_FLAG_ALG_ENC},
|
|
{"ipssh.enc.des", IPSSH_ENC_DES_CBC_STR, IPSSH_ENC_DES_CBC_ID, IPSSH_FLAG_ALG_ENC},
|
|
{"ipssh.enc.des", IPSSH_ENC_DES_CBC_STR "@ssh.com", IPSSH_ENC_DES_CBC_ID, IPSSH_FLAG_ALG_ENC},
|
|
#endif
|
|
#ifdef IPSSH_USE_ARCFOUR
|
|
{"ipssh.enc.arcfour128", IPSSH_ENC_ARCFOUR128_STR, IPSSH_ENC_ARCFOUR_128_ID, IPSSH_FLAG_ALG_ENC},
|
|
{"ipssh.enc.arcfour", IPSSH_ENC_ARCFOUR_STR, IPSSH_ENC_ARCFOUR_ID, IPSSH_FLAG_ALG_ENC},
|
|
#endif
|
|
{"ipssh.enc.none", IPSSH_NONE_STR, IPSSH_ENC_NONE_ID, IPSSH_FLAG_ALG_ENC},
|
|
#ifdef _WRS_CONFIG_COMPONENT_IPSSH_SUPPORT_SHA2
|
|
/* MAC algorithms */
|
|
#ifdef IPSSH_USE_SHA256
|
|
{"ipssh.mac.sha256", IPSSH_MAC_SHA256_STR, IPSSH_MAC_SHA256_ID, IPSSH_FLAG_ALG_MAC},
|
|
#endif
|
|
#ifdef IPSSH_USE_SHA512
|
|
{"ipssh.mac.sha512", IPSSH_MAC_SHA512_STR, IPSSH_MAC_SHA512_ID, IPSSH_FLAG_ALG_MAC},
|
|
#endif
|
|
#endif
|
|
#ifdef IPSSH_USE_SHA1
|
|
{"ipssh.mac.sha1", IPSSH_MAC_SHA1_STR, IPSSH_MAC_SHA1_ID, IPSSH_FLAG_ALG_MAC},
|
|
{"ipssh.mac.sha1-96", IPSSH_MAC_SHA1_96_STR, IPSSH_MAC_SHA1_96_ID, IPSSH_FLAG_ALG_MAC},
|
|
#endif
|
|
#ifdef IPSSH_USE_MD5
|
|
{"ipssh.mac.md5", IPSSH_MAC_MD5_STR, IPSSH_MAC_MD5_ID, IPSSH_FLAG_ALG_MAC|IPSSH_FLAG_ALG_NOT_ALLOWED_IN_FIPS_MODE},
|
|
{"ipssh.mac.md5-96", IPSSH_MAC_MD5_96_STR, IPSSH_MAC_MD5_96_ID, IPSSH_FLAG_ALG_MAC|IPSSH_FLAG_ALG_NOT_ALLOWED_IN_FIPS_MODE},
|
|
#endif
|
|
{"ipssh.mac.none", IPSSH_MAC_NONE_STR, IPSSH_MAC_NONE_ID, IPSSH_FLAG_ALG_MAC},
|
|
|
|
/* key exchange algs */
|
|
{"ipssh.kex.rsa2048-sha256", IPSSH_KEYEX_RSA2048_SHA256_STR, IPSSH_KEYEX_RSA2048_SHA256_ID, IPSSH_FLAG_ALG_KEX},
|
|
{"ipssh.kex.rsa1024-sha1", IPSSH_KEYEX_RSA1024_SHA1_STR, IPSSH_KEYEX_RSA1024_SHA1_ID, IPSSH_FLAG_ALG_KEX},
|
|
{"ipssh.kex.dh_gr14_sha1", IPSSH_KEYEX_DH_GR14_SHA1_STR, IPSSH_KEYEX_DH_GR14_SHA1_ID, IPSSH_FLAG_ALG_KEX},
|
|
{"ipssh.kex.dh_gr1_sha1", IPSSH_KEYEX_DH_GR1_SHA1_STR, IPSSH_KEYEX_DH_GR1_SHA1_ID, IPSSH_FLAG_ALG_KEX},
|
|
#ifdef IPSSH_USE_DH_GROUP_EX
|
|
{"ipssh.kex.dh_gex.sha1", IPSSH_KEYEX_DH_GEX_SHA1_STR, IPSSH_KEYEX_DH_GEX_SHA1_ID, IPSSH_FLAG_ALG_KEX},
|
|
{"ipssh.kex.dh_gex.sha256", IPSSH_KEYEX_DH_GEX_SHA256_STR, IPSSH_KEYEX_DH_GEX_SHA256_ID, IPSSH_FLAG_ALG_KEX},
|
|
#endif
|
|
|
|
/* Host key algs */
|
|
#ifdef IPSSH_USE_RSA
|
|
{"ipssh.host_key.rsa", IPSSH_RSA_STR, IPSSH_RSA_ID, IPSSH_FLAG_ALG_SRV_KEY},
|
|
#endif
|
|
{"ipssh.host_key.dsa", IPSSH_DSA_STR, IPSSH_DSA_ID, IPSSH_FLAG_ALG_SRV_KEY},
|
|
|
|
/* Compression algs */
|
|
{IP_NULL, IPSSH_NONE_STR, IPSSH_COMPR_NONE_ID, IPSSH_FLAG_ALG_COMPR},
|
|
|
|
/* End marker */
|
|
{IP_NULL, IP_NULL, 0}
|
|
};
|
|
|
|
IP_GLOBAL Ipssh_data g_ipssh;
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 9 LOCAL FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
IP_STATIC Ip_err
|
|
ipssh_sysvar_get_as_int(const char *name, int *value, int default_value)
|
|
{
|
|
*value = ipcom_sysvar_get_as_int(name, default_value);
|
|
if (*value == -1)
|
|
return IPCOM_ERR_FAILED;
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_sysvar_get_as_u8
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC Ip_err
|
|
ipssh_sysvar_get_as_u8(const char *name, Ip_u8 *value)
|
|
{
|
|
int tmp;
|
|
tmp = ipcom_sysvar_get_as_int(name, -1);
|
|
if (tmp == -1)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_get_sysvar_get_as_int :: failed to get \"%s\"", name);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
|
|
*value = (Ip_u8) tmp;
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_get_banner_config
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_get_banner_config(Ipssh_conn_config* config)
|
|
{
|
|
Ip_err ret_val = IPCOM_SUCCESS;
|
|
Ip_size_t len = 0;
|
|
char *auth_banner = IP_NULL;
|
|
|
|
auth_banner = ipcom_sysvar_get("ipssh.auth.banner", IP_NULL, IP_NULL);
|
|
|
|
if (auth_banner)
|
|
{
|
|
#ifdef IPSSH_SUPPORT_LONG_BANNER
|
|
config->auth_banner = auth_banner;
|
|
#else
|
|
strncpy(config->auth_banner, auth_banner, IPSSH_AUTH_BANNER_MAX_LEN);
|
|
config->auth_banner[IPSSH_AUTH_BANNER_MAX_LEN-1] = 0x0;
|
|
ipcom_free(auth_banner);
|
|
#endif
|
|
}
|
|
#ifdef IP_PORT_VXWORKS
|
|
else
|
|
{
|
|
if(_func_loginBanner != IP_NULL)
|
|
{
|
|
if (_func_loginBanner(&auth_banner, (int *)&len) > 0)
|
|
{
|
|
#ifdef IPSSH_SUPPORT_LONG_BANNER
|
|
config->auth_banner = ipcom_strdup(auth_banner);
|
|
if(!config->auth_banner)
|
|
{
|
|
IPCOM_LOG0(ERR, "No enough memory for strdup");
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
}
|
|
#else
|
|
strncpy(config->auth_banner, auth_banner, IPSSH_AUTH_BANNER_MAX_LEN);
|
|
if (len >= IPSSH_AUTH_BANNER_MAX_LEN)
|
|
config->auth_banner[IPSSH_AUTH_BANNER_MAX_LEN-1] = 0x0;
|
|
else
|
|
config->auth_banner[len] = 0x0;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_get_config
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_get_config(Ipssh_conn_config* config)
|
|
{
|
|
Ip_err ret_val = IPCOM_SUCCESS;
|
|
Ip_size_t len = 0;
|
|
|
|
if (ipssh_sysvar_get_as_int("ipssh.max_channels", &config->max_channels, 10) != IPCOM_SUCCESS)
|
|
{
|
|
if (ipssh_sysvar_get_as_int("ipssh.max_shells", &config->max_channels, 10) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
}
|
|
if (ipssh_sysvar_get_as_u8("ipssh.service.terminal", &(config->term)) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
#ifdef IPSSH_USE_SFTP
|
|
if (ipssh_sysvar_get_as_u8("ipssh.service.sftp", &config->sftp) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
#endif
|
|
if (ipssh_sysvar_get_as_u8("ipssh.service.port_fwd", &config->port_fwd) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
#ifdef IP_PORT_VXWORKS
|
|
if (!systemSecurityIsEnabled)
|
|
{
|
|
config->auth_none_allowed = 1;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
config->auth_none_allowed = ipcom_sysvar_get_as_int("ipssh.auth.none.allowed", 0);
|
|
if (ipssh_sysvar_get_as_u8("ipssh.auth.pw.allowed", &config->pw_allowed) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_u8("ipssh.auth.pw.required", &config->pw_required) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_u8("ipssh.auth.pub_key.allowed", &config->pub_key_allowed) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_u8("ipssh.auth.pub_key.required", &config->pub_key_required) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_u8("ipssh.auth.pub_key_first", &config->pub_key_first) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_u8("ipssh.auth.hostbased.allowed", &config->hostbased_allowed) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_u8("ipssh.auth.hostbased.required", &config->hostbased_required) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_int("ipssh.auth.max_fail", &config->max_fail, 3) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
}
|
|
if (ipssh_sysvar_get_as_int("ipssh.kex.dh_gex.clt.min", &config->gex_min, 2048) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_int("ipssh.kex.dh_gex.clt.n", &config->gex_n, 2048) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_int("ipssh.kex.dh_gex.clt.max", &config->gex_max, 4096) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
|
|
if (ipssh_activate_v2_alg(config->v2_algs.kex_alg, sizeof(config->v2_algs.kex_alg), IPSSH_FLAG_ALG_KEX) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_activate_v2_alg(config->v2_algs.srv_host_key_alg, sizeof(config->v2_algs.srv_host_key_alg), IPSSH_FLAG_ALG_SRV_KEY) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_activate_v2_alg(config->v2_algs.enc_alg_clt2srv, sizeof(config->v2_algs.enc_alg_clt2srv), IPSSH_FLAG_ALG_ENC) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_activate_v2_alg(config->v2_algs.enc_alg_srv2clt, sizeof(config->v2_algs.enc_alg_srv2clt), IPSSH_FLAG_ALG_ENC) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_activate_v2_alg(config->v2_algs.mac_alg_clt2srv, sizeof(config->v2_algs.mac_alg_clt2srv), IPSSH_FLAG_ALG_MAC) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_activate_v2_alg(config->v2_algs.mac_alg_srv2clt, sizeof(config->v2_algs.mac_alg_srv2clt), IPSSH_FLAG_ALG_MAC) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_activate_v2_alg(config->v2_algs.compr_alg_clt2srv, sizeof(config->v2_algs.compr_alg_clt2srv), IPSSH_FLAG_ALG_COMPR) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_activate_v2_alg(config->v2_algs.compr_alg_srv2clt, sizeof(config->v2_algs.compr_alg_srv2clt), IPSSH_FLAG_ALG_COMPR) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
|
|
#ifdef IPSSH_USE_V1
|
|
if (ipssh_sysvar_get_as_u8("ipssh.v1", &config->v1) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_u8("ipssh.v2", &config->v2) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
|
|
if (ipcom_sysvar_get_as_int("ipssh.enc.3des", -1) == 1)
|
|
config->v1_enc_algs |= 1 << IPSSH_V1_CIPHER_3DES;
|
|
if (ipcom_sysvar_get_as_int("ipssh.enc.des", -1) == 1)
|
|
config->v1_enc_algs |= 1 << IPSSH_V1_CIPHER_DES;
|
|
|
|
if (ipcom_sysvar_get_as_int("ipssh.auth.pw.allowed", -1) == 1)
|
|
config->v1_auth_meth |= 1 << IPSSH_V1_PW_AUTH;
|
|
if (ipcom_sysvar_get_as_int("ipssh.auth.pub_key.allowed", -1) == 1)
|
|
config->v1_auth_meth |= 1 << IPSSH_V1_RSA_AUTH;
|
|
#else
|
|
config->v2 = 1;
|
|
#endif
|
|
|
|
ret_val = ipssh_get_banner_config(config);
|
|
|
|
len = sizeof(config->sftp_start_dir);
|
|
if (!ipcom_sysvar_get("ipssh.sftp.start_dir", config->sftp_start_dir, &len))
|
|
if (len > sizeof(config->sftp_start_dir))
|
|
{
|
|
IPCOM_LOG0(ERR, "Configured sftp start directory is too long");
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_get_srv_config
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC Ip_err
|
|
ipssh_get_srv_config(Ipssh_srv_ctx *srv_ctx)
|
|
{
|
|
Ip_err ret_val = IPCOM_SUCCESS;
|
|
char buf[16];
|
|
int i;
|
|
|
|
IPCOM_LOG0(DEBUG2, "ipssh :: getting server config");
|
|
if (ipssh_get_config(&srv_ctx->config) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
|
|
if (ipssh_sysvar_get_as_int("ipssh.port", &srv_ctx->port[0], 22) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
for (i=1; i<IPSSH_MAX_LISTEN_PORTS;i++)
|
|
{
|
|
ipcom_sprintf(buf, "ipssh.port%i", i+1);
|
|
if (ipssh_sysvar_get_as_int(buf, &srv_ctx->port[i], -1) != IPCOM_SUCCESS)
|
|
{
|
|
srv_ctx->port[i] = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (ipssh_sysvar_get_as_int("ipssh.idle_timeout", &srv_ctx->idle_timeout, 0) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
if (ipssh_sysvar_get_as_int("ipssh.max_clients", &srv_ctx->max_clients, 20) != IPCOM_SUCCESS)
|
|
ret_val = IPCOM_ERR_FAILED;
|
|
|
|
if (ipcom_sysvar_get_as_int("ipssh.host_key.dsa", 1) != 0)
|
|
{
|
|
IPCOM_LOG0(DEBUG2, "ipssh :: loading dsa key");
|
|
srv_ctx->dsa_key = ipssh_load_dsa_key_cb();
|
|
if (srv_ctx->dsa_key == IP_NULL)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_get_srv_config :: ipssh_load_dsa_key_cb() failed");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
}
|
|
#ifdef IPSSH_USE_RSA
|
|
if (ipcom_sysvar_get_as_int("ipssh.host_key.rsa", 1) != 0)
|
|
{
|
|
IPCOM_LOG0(DEBUG2, "ipssh :: loading rsa key");
|
|
srv_ctx->rsa_key = ipssh_load_rsa_key_cb();
|
|
if (srv_ctx->rsa_key == IP_NULL)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_get_srv_config :: ipssh_load_rsa_key_cb() failed");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
/* Check that the RSA key is long enough */
|
|
if (RSA_size(srv_ctx->rsa_key)*8 < 768)
|
|
{
|
|
IPCOM_LOG0(ERR, "Configured RSA key too short. Must be at least 768 bits.");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
}
|
|
if (ipcom_sysvar_get_as_int("ipssh.kex.rsa1024-sha1", 0) != 0
|
|
|| ipcom_sysvar_get_as_int("ipssh.kex.rsa2048-sha256", 0) != 0)
|
|
{
|
|
srv_ctx->rsa_keyex_key = ipssh_load_rsa_keyex_key_cb();
|
|
if (srv_ctx->rsa_keyex_key == IP_NULL)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_get_srv_config :: ipssh_load_rsa_keyex_key_cb() failed");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
/* Check that the RSA key exchange key is long enough */
|
|
if (ipcom_sysvar_get_as_int("ipssh.kex.rsa1024-sha1", 0) != 0 && RSA_size(srv_ctx->rsa_keyex_key)*8 < 1024 )
|
|
{
|
|
IPCOM_LOG0(ERR, "Configured RSA key exchange key too short. Must be at least 1024 bits in order to enable rsa1024-sha1.");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
if (ipcom_sysvar_get_as_int("ipssh.kex.rsa2048-sha256", 0) != 0 && RSA_size(srv_ctx->rsa_keyex_key)*8 < 2048 )
|
|
{
|
|
IPCOM_LOG0(ERR, "Configured RSA key exchange key too short. Must be at least 2048 bits in order to enable rsa2048-sha256.");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_activate_v2_alg
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC Ip_err
|
|
ipssh_activate_v2_alg(Ip_u8 *alg_array, Ip_size_t len, int alg_type)
|
|
{
|
|
Ipssh_config_map *alg_ids = ipssh_alg_ids;
|
|
Ip_size_t pos = 0;
|
|
#ifdef OPENSSL_FIPS
|
|
int fips_mode = FIPS_mode();
|
|
#endif
|
|
|
|
for (alg_ids = ipssh_alg_ids; alg_ids->str; alg_ids++)
|
|
{
|
|
ip_assert(pos<len);
|
|
if (alg_ids->flags & alg_type)
|
|
{
|
|
/* Check if this algorithm is possible to turn off in runtime */
|
|
if (alg_ids->sysvar)
|
|
{
|
|
int value;
|
|
if (ipssh_sysvar_get_as_int(alg_ids->sysvar, &value, -1) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(WARNING, "ipssh_activate_v2_alg :: failed to get sysvar: %s", alg_ids->sysvar);
|
|
continue; /* Sysvar not set, treat as algorithm not turned on. */
|
|
}
|
|
if (!value)
|
|
continue; /* This alg is not turned on, continue */
|
|
}
|
|
#ifdef OPENSSL_FIPS
|
|
/* Check if this algorithm is allowed in FIPS mode */
|
|
if (fips_mode && alg_ids->flags & IPSSH_FLAG_ALG_NOT_ALLOWED_IN_FIPS_MODE)
|
|
{
|
|
IPCOM_LOG1(INFO, "ipssh_activate_v2_alg :: %s not allowed in FIPS mode", alg_ids->str);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
alg_array[pos] = alg_ids->id;
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
#ifdef IPSSH_USE_STOP_SPAWN
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_connect_to_port
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC Ip_err
|
|
ipssh_connect_to_port(Ip_u16 port)
|
|
{
|
|
struct Ip_sockaddr_in addr;
|
|
Ip_fd sock;
|
|
|
|
ipcom_memset(&addr, 0, sizeof(addr));
|
|
IPCOM_SA_LEN_SET(&addr, sizeof(struct Ip_sockaddr_in));
|
|
addr.sin_addr.s_addr = ip_htonl(IP_INADDR_LOOPBACK);
|
|
addr.sin_port = port;
|
|
addr.sin_family = IP_AF_INET;
|
|
|
|
sock = ipcom_socket(IP_AF_INET, IP_SOCK_STREAM, 0);
|
|
if (sock == IP_INVALID_SOCKET)
|
|
return IPCOM_ERR_SOCKCALLFAILED;
|
|
|
|
if (ipcom_connect(sock, (struct Ip_sockaddr*) &addr, sizeof(addr)) != IPCOM_SUCCESS)
|
|
{
|
|
ipcom_socketclose(sock);
|
|
return IPCOM_ERR_SOCKCALLFAILED;
|
|
}
|
|
|
|
ipcom_socketclose(sock);
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_setup_stop_sock
|
|
*===========================================================================
|
|
* Description: Sets up a listen socket, bound to an arbitrary port.
|
|
* Parameters:
|
|
* Returns: The socket descriptor and the port number.
|
|
*
|
|
*/
|
|
IP_STATIC Ip_err
|
|
ipssh_setup_stop_sock(Ip_fd *sock, Ip_u16 *port)
|
|
{
|
|
struct Ip_sockaddr_in addr;
|
|
Ip_socklen_t addr_len;
|
|
int opt_val = 1;
|
|
|
|
*sock = ipcom_socket(IP_AF_INET, IP_SOCK_STREAM, 0);
|
|
if (*sock == IP_INVALID_SOCKET)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_setup_stop_sock :: ipcom_socket() failed, errno=%d", ipcom_errno);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
if (ipcom_setsockopt(*sock, IP_SOL_SOCKET, IP_SO_REUSEADDR, (void *) &opt_val, sizeof(opt_val)))
|
|
{
|
|
IPCOM_LOG1(INFO,"ipssh_setup_stop_sock :: ipcom_setsockopt(IP_SO_REUSEADDR) failed, error=%d", ipcom_errno);
|
|
}
|
|
ipcom_memset(&addr, 0, sizeof(addr));
|
|
addr.sin_addr.s_addr = ip_htonl(IP_INADDR_LOOPBACK);
|
|
addr.sin_family = IP_AF_INET;
|
|
addr.sin_len = sizeof(struct Ip_sockaddr_in);
|
|
addr_len = sizeof(addr);
|
|
if (ipcom_bind(*sock, (struct Ip_sockaddr *) &addr, addr_len) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_setup_stop_sock :: ipcom_bind() failed, errno=%d", ipcom_errno);
|
|
ipcom_socketclose(*sock);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
addr_len = sizeof(addr);
|
|
if (ipcom_getsockname(*sock, (struct Ip_sockaddr *) &addr, &addr_len) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_setup_stop_sock :: ipcom_getsockname() failed, errno=%d", ipcom_errno);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
*port = addr.sin_port;
|
|
|
|
if (ipcom_listen(*sock, 2) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_setup_stop_sock :: ipcom_listen() failed, errno=%d", ipcom_errno);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_stop_spawned_server
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC Ip_err
|
|
ipssh_stop_spawned_server(Ip_fd sock, Ipssh_conn_ctx *ctx, Ipssh_map_entry *map_entry)
|
|
{
|
|
/* Force the connection to be disconnected. */
|
|
(void) sock;
|
|
(void) ctx;
|
|
(void) map_entry;
|
|
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
#endif /* IPSSH_USE_STOP_SPAWN */
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_initiate_v1_connection
|
|
*===========================================================================
|
|
* Description: Read client's version string
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
#ifdef IPSSH_USE_V1
|
|
IP_STATIC Ip_err
|
|
ipssh_initiate_v1_connection(Ipssh_conn *ssh_conn, Ipssh_map_entry *map_entry)
|
|
{
|
|
/* initiate v1 key exchange */
|
|
ssh_conn->conn_state = IPSSH_CONN_STATE_SRV_KEY_EX_V1;
|
|
ssh_conn->server_key = ipssh_generate_server_key(ssh_conn->srv_rsa_key);
|
|
if (!ssh_conn->server_key)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_initiate_v1_connection :: ipssh_generate_server_key() failed");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
if (ipssh_send_msg_v1_public_key(ssh_conn) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_initiate_v1_connection :: ipssh_send_msg_v1_public_key() failed");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
map_entry->handler = ipssh_read_v1_pkt;
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
#endif /* IPSSH_USE_V1 */
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_initiate_v2_connection
|
|
*===========================================================================
|
|
* Description: Read client's version string
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC Ip_err
|
|
ipssh_initiate_v2_connection(Ipssh_conn *ssh_conn, Ipssh_map_entry *map_entry)
|
|
{
|
|
if (ipssh_send_kexinit(ssh_conn))
|
|
return IPCOM_ERR_FAILED;
|
|
map_entry->handler = ipssh_conn_v2_handler;
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 10 PUBLIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_srv_ctx_new
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ipssh_srv_ctx*
|
|
ipssh_srv_ctx_new(void)
|
|
{
|
|
Ipssh_srv_ctx* srv_ctx;
|
|
|
|
srv_ctx = ipcom_calloc(1, sizeof(Ipssh_srv_ctx));
|
|
if (!srv_ctx)
|
|
return IP_NULL;
|
|
|
|
srv_ctx->ref = 1;
|
|
|
|
#ifdef IPSSH_SUPPORT_LONG_BANNER
|
|
srv_ctx->config.auth_banner = IP_NULL;
|
|
#endif
|
|
|
|
return srv_ctx;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_srv_ctx_free
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipssh_srv_ctx_free(Ipssh_srv_ctx* srv_ctx)
|
|
{
|
|
ip_assert(srv_ctx->ref > 0);
|
|
srv_ctx->ref--;
|
|
if (srv_ctx->ref > 0)
|
|
return;
|
|
|
|
#ifdef IPSSH_USE_RSA
|
|
if (srv_ctx->rsa_key)
|
|
RSA_free(srv_ctx->rsa_key);
|
|
if (srv_ctx->rsa_keyex_key)
|
|
RSA_free(srv_ctx->rsa_keyex_key);
|
|
#endif
|
|
if (srv_ctx->dsa_key)
|
|
DSA_free(srv_ctx->dsa_key);
|
|
|
|
#ifdef IPSSH_SUPPORT_LONG_BANNER
|
|
if(srv_ctx->config.auth_banner)
|
|
ipcom_free(srv_ctx->config.auth_banner);
|
|
#endif
|
|
|
|
ipcom_free(srv_ctx);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_srv_ctx_dup
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ipssh_srv_ctx*
|
|
ipssh_srv_ctx_dup(Ipssh_srv_ctx* srv_ctx)
|
|
{
|
|
srv_ctx->ref++;
|
|
|
|
return srv_ctx;
|
|
}
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_alg_list_str2id
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_alg_list_str2id(const char *str_list_org, Ip_u8 *id_list, int id_list_len)
|
|
{
|
|
Ip_err rc = IPCOM_ERR_FAILED;
|
|
char *str_list;
|
|
char *alg;
|
|
char *last;
|
|
Ip_u8 id;
|
|
|
|
str_list = ipcom_strdup(str_list_org);
|
|
for (alg = ipcom_strtok_r(str_list, ",", &last); alg; alg = ipcom_strtok_r(IP_NULL, ",", &last))
|
|
{
|
|
id = ipssh_alg_str2id(alg);
|
|
if (id != IPSSH_ID_UNKNOWN)
|
|
{
|
|
id_list_len--;
|
|
ip_assert(id_list_len > 0);
|
|
if (id_list_len < 0)
|
|
goto cleanup;
|
|
*id_list = id;
|
|
id_list++;
|
|
}
|
|
else
|
|
{
|
|
IPCOM_LOG1(DEBUG2, "ipssh_alg_list_str2id :: unknown alg: %s", alg);
|
|
}
|
|
}
|
|
|
|
rc = IPCOM_SUCCESS;
|
|
|
|
cleanup:
|
|
ipcom_free(str_list);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_register_clt
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipssh_register_clt(Ipssh_conn_ctx *ctx)
|
|
{
|
|
ipcom_list_insert_last(&(g_ipssh.clt_list), (Ipcom_list*) ctx);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_unregister_clt
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipssh_unregister_clt(Ipssh_conn_ctx *ctx)
|
|
{
|
|
IPSSH_CODE_LOCK();
|
|
ipcom_list_remove((Ipcom_list*) ctx);
|
|
IPSSH_CODE_UNLOCK();
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_get_no_clients
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipssh_get_no_clients(void)
|
|
{
|
|
IPSSH_CODE_ISLOCKED_ASSERT();
|
|
return g_ipssh.clt_list.size;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_lookup_map_entry
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ipssh_map_entry*
|
|
ipssh_lookup_map_entry(Ipssh_ctx_map *map, Ip_fd sock)
|
|
{
|
|
Ipcom_list *node;
|
|
|
|
for (node = ipcom_list_first(map);
|
|
node != IP_NULL;
|
|
node = ipcom_list_next(node))
|
|
{
|
|
Ipssh_map_entry *entry = (Ipssh_map_entry*) node;
|
|
if (entry->sock == sock)
|
|
return entry;
|
|
}
|
|
|
|
return IP_NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_delete_map_entry
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipssh_delete_map_entry_for_sock(Ipssh_ctx_map *map, Ip_fd sock)
|
|
{
|
|
Ipssh_map_entry *entry;
|
|
|
|
entry = ipssh_lookup_map_entry(map, sock);
|
|
if (entry)
|
|
{
|
|
ipcom_list_remove((Ipcom_list*)entry);
|
|
ipcom_free(entry);
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef IPSSH_USE_STOP_SPAWN
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_stop_spawn
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_PUBLIC Ip_err
|
|
ipssh_stop_spawn(Ip_pid_t pid)
|
|
{
|
|
Ip_u16 port = 0;
|
|
Ipssh_conn_ctx *ctx;
|
|
Ip_bool found = IP_FALSE;
|
|
|
|
IPSSH_CODE_LOCK();
|
|
for (ctx = (Ipssh_conn_ctx*) ipcom_list_first(&(g_ipssh.clt_list));
|
|
ctx;
|
|
ctx = (Ipssh_conn_ctx*) ipcom_list_next(&(ctx->list)))
|
|
{
|
|
if (ctx->pid == pid)
|
|
{
|
|
port = ctx->stop_port;
|
|
found = IP_TRUE;
|
|
break;
|
|
}
|
|
}
|
|
IPSSH_CODE_UNLOCK();
|
|
if (!found)
|
|
return IPCOM_ERR_NOT_INSTALLED;
|
|
|
|
return ipssh_connect_to_port(port);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_stop_all_spawns
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_PUBLIC Ip_err
|
|
ipssh_stop_all_spawns(void)
|
|
{
|
|
Ipssh_conn_ctx *ctx;
|
|
|
|
IPSSH_CODE_LOCK();
|
|
|
|
for (ctx = (Ipssh_conn_ctx*) ipcom_list_first(&(g_ipssh.clt_list));
|
|
ctx;
|
|
ctx = (Ipssh_conn_ctx*) ipcom_list_next(&(ctx->list)))
|
|
{
|
|
(void) ipssh_connect_to_port(ctx->stop_port);
|
|
}
|
|
|
|
IPSSH_CODE_UNLOCK();
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_create
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_PUBLIC Ip_err
|
|
ipssh_create(void)
|
|
{
|
|
ipcom_list_init(&(g_ipssh.clt_list));
|
|
ipcom_list_init(&(g_ipssh.subsystem_init_cb_list));
|
|
g_ipssh.task_name_id = 0;
|
|
if (ipcom_mutex_create(&(g_ipssh.code_mutex)) != IPCOM_SUCCESS)
|
|
return IPCOM_ERR_FAILED;
|
|
ipssh_register_subsystem("sftp", ipssh_sftp_subsystem_init_cb);
|
|
#ifdef IPTESTENGINE
|
|
ipssh_register_subsystem("ipssh_test", ipssh_test_subsystem_init_cb);
|
|
#endif
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_store_map_entry
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ipssh_map_entry*
|
|
ipssh_store_map_entry(Ipssh_conn_ctx *ctx,
|
|
Ip_fd sock,
|
|
Ipssh_sock_handler handler,
|
|
int flags)
|
|
{
|
|
Ipssh_map_entry *map_entry;
|
|
|
|
IPCOM_LOG1(DEBUG2, "ipssh_store_map_entry :: sock=%d", sock);
|
|
map_entry = ipcom_malloc(sizeof(Ipssh_map_entry));
|
|
if (!map_entry)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_store_map_entry :: out of memory");
|
|
return IP_NULL;
|
|
}
|
|
map_entry->sock = sock;
|
|
map_entry->ctx = ctx;
|
|
map_entry->handler = handler;
|
|
map_entry->flags = flags;
|
|
ipcom_list_insert_first(&ctx->ctx_map, (Ipcom_list*) map_entry);
|
|
|
|
return map_entry;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_map_get_max_sock
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_fd
|
|
ipssh_map_get_max_sock(Ipssh_ctx_map* map)
|
|
{
|
|
Ipssh_map_entry *map_entry;
|
|
Ip_fd max_sock = -1;
|
|
|
|
ip_assert(map->size > 0);
|
|
|
|
for(map_entry = IPCOM_LIST_FIRST(map); map_entry; map_entry = IPCOM_LIST_NEXT(&(map_entry->list)))
|
|
{
|
|
if(max_sock<map_entry->sock)
|
|
max_sock = map_entry->sock;
|
|
}
|
|
|
|
return max_sock;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IPCOM_PROCESS(ipssh)
|
|
{
|
|
Ip_fd lst_sock[IPSSH_MAX_LISTEN_PORTS];
|
|
#ifdef IPSSH_V6_MAPPED_V4_ADDR_NOT_SUPPORTED
|
|
Ip_fd lst_sock2[IPSSH_MAX_LISTEN_PORTS];
|
|
#endif
|
|
Ip_fd clt_sock;
|
|
Ip_fd_set* read_set = IP_NULL, *tmp_read_set = IP_NULL;
|
|
Ip_bool restart = IP_FALSE;
|
|
int i;
|
|
Ip_fd max_sock;
|
|
Ip_fd fd_ipd = IP_INVALID_SOCKET;
|
|
Ipssh_srv_ctx* srv_ctx = IP_NULL;
|
|
|
|
IPCOM_LOG0(DEBUG2, "ipssh :: starting");
|
|
|
|
ipcom_proc_init();
|
|
IPSSH_CODE_LOCK();
|
|
|
|
for (i=0; i < IPSSH_MAX_LISTEN_PORTS; i++)
|
|
{
|
|
lst_sock[i] = IP_INVALID_SOCKET;
|
|
#ifdef IPSSH_V6_MAPPED_V4_ADDR_NOT_SUPPORTED
|
|
lst_sock2[i] = IP_INVALID_SOCKET;
|
|
#endif
|
|
}
|
|
|
|
restart:
|
|
/* Setup server ctx */
|
|
IPCOM_LOG0(DEBUG2, "ipssh :: setting up server context");
|
|
max_sock = -1;
|
|
srv_ctx = ipssh_srv_ctx_new();
|
|
if (!srv_ctx)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh :: ipssh_srv_ctx_new() failed");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ipssh_get_srv_config(srv_ctx) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh :: ipssh_get_srv_config() failed");
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(read_set = ipcom_malloc(sizeof(Ip_fd_set))) ||
|
|
!(tmp_read_set = ipcom_malloc(sizeof(Ip_fd_set))))
|
|
{
|
|
IPCOM_LOG0(CRIT, "ipssh :: out of memory");
|
|
goto cleanup;
|
|
}
|
|
ipcom_memset(read_set, 0, sizeof(Ip_fd_set));
|
|
|
|
ip_assert(srv_ctx->port[0] != 0);
|
|
|
|
/* Set up listen socket */
|
|
IPCOM_LOG0(DEBUG2, "ipssh :: setting up listen socket");
|
|
for (i=0; i<IPSSH_MAX_LISTEN_PORTS; i++)
|
|
{
|
|
if (srv_ctx->port[i] == 0)
|
|
break;
|
|
|
|
#ifdef IPCOM_USE_INET6
|
|
if (ipssh_setup_listen_sock("::", srv_ctx->port[i], &lst_sock[i]) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh :: ipssh_setup_listen_sock() failed (IPv6), errno=%d", ipcom_errno);
|
|
goto cleanup;
|
|
}
|
|
|
|
#ifdef IPSSH_V6_MAPPED_V4_ADDR_NOT_SUPPORTED
|
|
if (ipssh_setup_listen_sock("0.0.0.0", srv_ctx->port[i], &lst_sock2[i]) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh :: ipssh_setup_listen_sock() failed (IPv4), errno=%d", ipcom_errno);
|
|
goto cleanup;
|
|
}
|
|
IP_FD_SET(lst_sock2[i], read_set);
|
|
max_sock = IP_MAX(max_sock, lst_sock2[i]);
|
|
#endif
|
|
#else
|
|
if (ipssh_setup_listen_sock("0.0.0.0", srv_ctx->port[i], &lst_sock[i]) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh :: ipssh_setup_listen_sock() failed, errno=%d", ipcom_errno);
|
|
goto cleanup;
|
|
}
|
|
#endif
|
|
IP_FD_SET(lst_sock[i], read_set);
|
|
max_sock = IP_MAX(max_sock, lst_sock[i]);
|
|
}
|
|
|
|
if (fd_ipd == IP_INVALID_SOCKET) /* Only at start, not at reconfigure */
|
|
(void)ipcom_ipd_init("ipssh", IPCOM_SUCCESS, &fd_ipd);
|
|
|
|
IP_FD_SET(fd_ipd, read_set);
|
|
max_sock = IP_MAX(max_sock, fd_ipd);
|
|
|
|
while (1)
|
|
{
|
|
IPCOM_LOG0(DEBUG2, "ipssh :: waiting for connection");
|
|
ipcom_memcpy(tmp_read_set, read_set, sizeof(Ip_fd_set));
|
|
|
|
IPSSH_CODE_UNLOCK();
|
|
if (ipcom_socketselect(max_sock+1, tmp_read_set,
|
|
IP_NULL, IP_NULL, IP_NULL) == -1)
|
|
{
|
|
IPCOM_LOG1(CRIT, "ipssh :: ipcom_socketselect() failed, errno=%d", ipcom_errno);
|
|
IPSSH_CODE_LOCK();
|
|
goto cleanup;
|
|
}
|
|
IPSSH_CODE_LOCK();
|
|
|
|
for (i=0; i<IPSSH_MAX_LISTEN_PORTS; i++)
|
|
{
|
|
if (lst_sock[i] == IP_INVALID_SOCKET)
|
|
break;
|
|
|
|
if (IP_FD_ISSET(lst_sock[i], tmp_read_set))
|
|
{
|
|
clt_sock = ipcom_accept(lst_sock[i], IP_NULL, IP_NULL);
|
|
if (IP_INVALID_SOCKET == clt_sock)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh :: ipcom_accept() failed, errno=%d", ipcom_errno);
|
|
continue;
|
|
}
|
|
if (ipssh_get_no_clients() >= srv_ctx->max_clients)
|
|
{
|
|
IPCOM_LOG0(INFO, "ipssh :: maximum no of connected clients reached, disconnecting client");
|
|
ipcom_socketclose(clt_sock);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Disable Nagle (enable no delay) on this socket because
|
|
* we need small packet(<MSS) to be sent immediately.
|
|
*/
|
|
int enable_no_delay = 1;
|
|
ipcom_setsockopt(clt_sock, IP_IPPROTO_TCP, IP_TCP_NODELAY,
|
|
&enable_no_delay, sizeof(enable_no_delay));
|
|
|
|
ipssh_launch_server(clt_sock, srv_ctx);
|
|
}
|
|
}
|
|
#ifdef IPSSH_V6_MAPPED_V4_ADDR_NOT_SUPPORTED
|
|
if (IP_FD_ISSET(lst_sock2[i], tmp_read_set))
|
|
{
|
|
clt_sock = ipcom_accept(lst_sock2[i], IP_NULL, IP_NULL);
|
|
if (IP_INVALID_SOCKET == clt_sock)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh :: ipcom_accept() failed, errno=%d", ipcom_errno);
|
|
continue;
|
|
}
|
|
if (ipssh_get_no_clients() >= srv_ctx->max_clients)
|
|
{
|
|
IPCOM_LOG0(INFO, "ipssh :: maximum no of connected clients reached, disconnecting client");
|
|
ipcom_socketclose(clt_sock);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
ipssh_launch_server(clt_sock, srv_ctx);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
if (IP_FD_ISSET(fd_ipd, tmp_read_set))
|
|
{
|
|
int event;
|
|
|
|
event = ipcom_ipd_input(fd_ipd);
|
|
switch(event)
|
|
{
|
|
case IPCOM_IPD_EVENT_RECONFIGURE:
|
|
IPCOM_LOG0(INFO, "ipssh :: received IPD reconfigure message");
|
|
restart = IP_TRUE;
|
|
goto cleanup;
|
|
case IPCOM_IPD_EVENT_KILL:
|
|
IPCOM_LOG0(INFO, "ipssh :: received IPD kill message");
|
|
goto cleanup;
|
|
case IPCOM_IPD_EVENT_NONE:
|
|
break;
|
|
default:
|
|
IP_PANIC();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
for (i=0; i<IPSSH_MAX_LISTEN_PORTS; i++)
|
|
{
|
|
if (lst_sock[i] != IP_INVALID_SOCKET)
|
|
{
|
|
ipcom_socketclose(lst_sock[i]);
|
|
lst_sock[i] = IP_INVALID_SOCKET;
|
|
}
|
|
#ifdef IPSSH_V6_MAPPED_V4_ADDR_NOT_SUPPORTED
|
|
if (lst_sock2[i] != IP_INVALID_SOCKET)
|
|
{
|
|
ipcom_socketclose(lst_sock2[i]);
|
|
lst_sock2[i] = IP_INVALID_SOCKET;
|
|
}
|
|
#endif
|
|
}
|
|
if (read_set)
|
|
{
|
|
ipcom_free(read_set);
|
|
read_set = IP_NULL;
|
|
}
|
|
if (tmp_read_set)
|
|
{
|
|
ipcom_free(tmp_read_set);
|
|
tmp_read_set = IP_NULL;
|
|
}
|
|
if(srv_ctx)
|
|
{
|
|
ipssh_srv_ctx_free(srv_ctx);
|
|
srv_ctx = IP_NULL;
|
|
}
|
|
if (restart)
|
|
{
|
|
restart = IP_FALSE;
|
|
goto restart;
|
|
}
|
|
|
|
if (fd_ipd == IP_INVALID_SOCKET)
|
|
(void)ipcom_ipd_init("ipssh", IPCOM_ERR_FAILED, &fd_ipd);
|
|
(void)ipcom_ipd_exit("ipssh", fd_ipd);
|
|
|
|
IPSSH_CODE_UNLOCK();
|
|
ipcom_proc_exit();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_launch_server
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipssh_launch_server(Ip_fd sock, Ipssh_srv_ctx *srv_ctx)
|
|
{
|
|
char proc_name[IPCOM_PROC_NAME_MAX+1];
|
|
char addr_str[64];
|
|
union Ip_sockaddr_union addr;
|
|
Ip_socklen_t len = sizeof(union Ip_sockaddr_union);
|
|
Ip_pid_t pid;
|
|
Ipcom_ipc ipc;
|
|
Ipssh_spawn_args *spawn_args;
|
|
Ipcom_proc_attr proc_attr;
|
|
|
|
if (ipcom_getpeername(sock, &addr.sa, &len) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_launch_server :: ipcom_getpeername() failed, errno=%d", ipcom_errno);
|
|
ipcom_socketclose(sock);
|
|
return;
|
|
}
|
|
|
|
#ifdef IPCOM_USE_INET
|
|
if (addr.sa.sa_family == IP_AF_INET)
|
|
{
|
|
if (ipcom_inet_ntop(IP_AF_INET, &addr.sin.sin_addr, addr_str, sizeof(addr_str)) == IP_NULL)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_launch_server :: ipcom_inet_ntop failed(), errno=%d", ipcom_errno);
|
|
ipcom_socketclose(sock);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef IPCOM_USE_INET6
|
|
if (addr.sa.sa_family == IP_AF_INET6)
|
|
{
|
|
if (ipcom_inet_ntop(IP_AF_INET6, &addr.sin6.sin6_addr, addr_str, sizeof(addr_str)) == IP_NULL)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_launch_server :: ipcom_inet_ntop failed(), errno=%d", ipcom_errno);
|
|
ipcom_socketclose(sock);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if IPCOM_PROC_NAME_MAX < 51
|
|
IP_PANIC();
|
|
#endif
|
|
ipcom_sprintf(proc_name, "ipssh_%s_%d_%d", addr_str, ip_ntohs(addr.sin.sin_port), g_ipssh.task_name_id);
|
|
g_ipssh.task_name_id++;
|
|
|
|
ipcom_proc_attr_init(&proc_attr);
|
|
proc_attr.flags |= IPCOM_PROC_FLAG_FP;
|
|
proc_attr.stacksize = IPCOM_PROC_STACK_MAX;
|
|
proc_attr.priority = IPSSH_SPAWN_PROC_PRIO;
|
|
|
|
if (ipcom_proc_acreate(proc_name, ipssh_server, &proc_attr, &pid) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_launch_server :: failed to start process, errno=%d", ipcom_errno);
|
|
ipcom_socketclose(sock);
|
|
return;
|
|
}
|
|
|
|
if (ipcom_ipc_open(&ipc, proc_name, IPC_TIMEOUT_INFINITE) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_launch_server :: ipcom_ipc_open() failed");
|
|
ipcom_socketclose(sock);
|
|
return;
|
|
}
|
|
|
|
spawn_args = ipcom_ipc_malloc(sizeof(Ipssh_spawn_args));
|
|
if (!spawn_args)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_launch_server :: out of memory");
|
|
(void)ipcom_ipc_close(&ipc);
|
|
ipcom_socketclose(sock);
|
|
return;
|
|
}
|
|
|
|
#ifdef IPSSH_CHOWN_SOCKET_CB
|
|
IPSSH_CHOWN_SOCKET_CB(sock, pid);
|
|
#endif
|
|
/* First send and receive a sync signal to avoid race condition. Any signal will do, let's
|
|
* use spawn_args */
|
|
if (ipcom_ipc_send(&ipc, spawn_args))
|
|
{
|
|
ipcom_socketclose(sock);
|
|
ipcom_ipc_free(spawn_args);
|
|
IPCOM_LOG0(ERR, "ipssh_launch_server :: ipcom_ipc_send() failed");
|
|
return;
|
|
}
|
|
if (ipcom_ipc_receive(IP_NULL, &spawn_args, IPC_TIMEOUT_INFINITE) != IPCOM_SUCCESS)
|
|
{
|
|
ipcom_socketclose(sock);
|
|
IPCOM_LOG0(ERR, "ipssh_launch_server :: ipcom_ipc_receive() failed");
|
|
return;
|
|
}
|
|
|
|
/* Now send spawn_args */
|
|
spawn_args->sock = sock;
|
|
spawn_args->srv_ctx = ipssh_srv_ctx_dup(srv_ctx);
|
|
if (ipcom_ipc_send(&ipc, spawn_args))
|
|
{
|
|
ipcom_socketclose(sock);
|
|
ipcom_ipc_free(spawn_args);
|
|
IPCOM_LOG0(ERR, "ipssh_launch_server :: ipcom_ipc_send() failed");
|
|
}
|
|
|
|
#ifdef IP_PORT_OSE5
|
|
{
|
|
/* OSE5 has process specific sockets -> donate child fd */
|
|
Ip_err retval;
|
|
retval = (Ip_err)efs_donate_fd(sock, pid);
|
|
if (retval != 0)
|
|
{
|
|
ipcom_socketclose(sock);
|
|
IPCOM_LOG0(ERR, "ipssh_launch_server :: efs_donate_fd() failed");
|
|
}
|
|
}
|
|
#endif /* IP_PORT_OSE5 */
|
|
|
|
(void)ipcom_ipc_close(&ipc);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_server
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IPCOM_PROCESS(ipssh_server)
|
|
{
|
|
Ipssh_conn_ctx *ctx = IP_NULL;
|
|
Ipssh_map_entry *map_entry;
|
|
Ip_s32 on = 1;
|
|
Ip_fd sock;
|
|
struct Ip_timeval idle_timeout;
|
|
Ipssh_spawn_args *spawn_args;
|
|
void* sync;
|
|
|
|
ipcom_proc_init();
|
|
|
|
ctx = ipssh_conn_ctx_new();
|
|
if (!ctx)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipssh_conn_ctx_new() failed");
|
|
ipcom_proc_exit();
|
|
return;
|
|
}
|
|
|
|
if (ipcom_ipc_install() != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipcom_ipc_install() failed");
|
|
goto exit;
|
|
}
|
|
if (ipcom_ipc_receive(IP_NULL, &sync, IPC_TIMEOUT_INFINITE) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipcom_ipc_receive() failed");
|
|
goto exit;
|
|
}
|
|
ipssh_register_clt(ctx);
|
|
if (ipcom_ipc_send(IPCOM_IPC_SENDER, sync) != IPCOM_SUCCESS)
|
|
{
|
|
ipcom_ipc_free(sync);
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipcom_ipc_send() failed");
|
|
goto exit;
|
|
}
|
|
if (ipcom_ipc_receive(IP_NULL, &spawn_args, IPC_TIMEOUT_INFINITE) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipcom_ipc_receive() failed");
|
|
goto exit;
|
|
}
|
|
(void)ipcom_ipc_uninstall();
|
|
|
|
IPSSH_CODE_LOCK();
|
|
|
|
/* Change the owner of the telnet socket to this task */
|
|
#ifdef IP_PORT_OSE5
|
|
spawn_args->sock = efs_receive_fd(0);
|
|
if (spawn_args->sock == -1)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: efs_receive_fd() failed");
|
|
goto exit;
|
|
}
|
|
#endif
|
|
|
|
#if defined(IP_PORT_OSE) || defined(IP_PORT_OSE5)
|
|
{
|
|
Ip_pid_t pid;
|
|
pid = ipcom_getpid();
|
|
if (ipcom_socketioctl(spawn_args->sock, IP_SIOCSPGRP, &pid) < 0)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_server :: ipcom_socketioctl(IP_SIOCSPGRP) failed, errno = %d", ipcom_errno);
|
|
goto exit;
|
|
}
|
|
}
|
|
#endif /* IP_PORT_OSE */
|
|
|
|
sock = spawn_args->sock;
|
|
ipssh_conn_ctx_set_srv_ctx(ctx, spawn_args->srv_ctx);
|
|
ipssh_srv_ctx_free(spawn_args->srv_ctx);
|
|
|
|
ctx->ssh_conn->mode = IPSSH_MODE_SRV;
|
|
ctx->pid = ipcom_getpid();
|
|
ctx->ssh_conn->sock = spawn_args->sock;
|
|
ipcom_ipc_free(spawn_args);
|
|
|
|
IPSSH_CODE_UNLOCK();
|
|
|
|
idle_timeout.tv_sec = ctx->srv_ctx->idle_timeout;
|
|
ip_assert(idle_timeout.tv_sec != -1);
|
|
idle_timeout.tv_usec = 0;
|
|
|
|
map_entry = ipssh_store_map_entry(ctx, sock, ipssh_read_version_str, IPSSH_FLAG_NO_CLOSE);
|
|
if (!map_entry)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipssh_store_map_entry() failed");
|
|
goto exit;
|
|
}
|
|
|
|
if (ipcom_socketioctl(sock, IP_FIONBIO, (char *)&on))
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_server :: ipcom_socketioctl() failed, errno=%d", ipcom_errno);
|
|
goto exit;
|
|
}
|
|
IP_FD_SET(sock, &(ctx->read_set));
|
|
#ifdef IPSSH_USE_STOP_SPAWN
|
|
{
|
|
/* Set up a listen socket bound to localhost. When the spawned ssh server shall be stopped
|
|
* a connection is made to the listen socket. The select call below will then return and the
|
|
* handler function for this socket will return IPCOM_ERR_FAILED that will make the server
|
|
* close the connection with the client and terminate */
|
|
|
|
if (ipssh_setup_stop_sock(&(ctx->stop_sock), &(ctx->stop_port)) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipssh_setup_stop_sock() failed");
|
|
goto exit;
|
|
}
|
|
map_entry = ipssh_store_map_entry(ctx, ctx->stop_sock, ipssh_stop_spawned_server, 0);
|
|
if (!map_entry)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipssh_store_map_entry() failed");
|
|
goto exit;
|
|
}
|
|
IP_FD_SET(ctx->stop_sock, &(ctx->read_set));
|
|
}
|
|
#endif
|
|
|
|
/* Send version-strings */
|
|
{
|
|
char buf[32];
|
|
Ip_size_t str_len;
|
|
|
|
if (ipssh_make_version_str(ctx->srv_ctx->config.v1, ctx->srv_ctx->config.v2, buf, sizeof(buf)) != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipssh_make_version_str() failed");
|
|
goto exit;
|
|
}
|
|
if (ipcom_send(ctx->ssh_conn->sock, buf, ipcom_strlen(buf), 0) != (int)ipcom_strlen(buf))
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_server :: ipcom_send() failed, errno = %d", ipcom_errno);
|
|
goto exit;
|
|
}
|
|
/* Remove trailing \r\n, not to be included in digital signature */
|
|
str_len = ipcom_strlen(buf);
|
|
while(str_len > 0 && (buf[str_len-1] == '\r' || buf[str_len-1] == '\n'))
|
|
{
|
|
str_len--;
|
|
buf[str_len] = 0;
|
|
}
|
|
|
|
/* Store version string */
|
|
if (ipssh_msg2list((Ipssh_msg_list*)&(ctx->ssh_conn->handshake_msgs), IPSSH_STR, (Ip_u8 *)buf, str_len, 1))
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_server :: ipssh_msg2list() failed");
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
while(1)
|
|
{
|
|
Ip_fd_set tmp_read_set, tmp_write_set;
|
|
Ip_s32 rc;
|
|
Ipcom_list *entry;
|
|
|
|
again:
|
|
ipcom_memcpy(&tmp_read_set, &(ctx->read_set), sizeof(Ip_fd_set));
|
|
ipcom_memcpy(&tmp_write_set, &(ctx->write_set), sizeof(Ip_fd_set));
|
|
|
|
rc = ipcom_socketselect(ipssh_map_get_max_sock(&(ctx->ctx_map))+1, &tmp_read_set,
|
|
&tmp_write_set, IP_NULL, idle_timeout.tv_sec != 0 ? &idle_timeout : IP_NULL);
|
|
switch(rc)
|
|
{
|
|
case -1:
|
|
IPCOM_LOG1(DEBUG, "ipcom_socketselect() failed, errno=%d", ipcom_errno);
|
|
goto exit;
|
|
case 0:
|
|
IPCOM_LOG0(INFO, "disconnecting client due to idle timeout");
|
|
goto exit;
|
|
default:
|
|
IPCOM_LOG1(DEBUG2, "ipssh_server: ipcom_socketselect() return %d", rc);
|
|
break;
|
|
}
|
|
|
|
for (entry = ipcom_list_first(&ctx->ctx_map); entry != IP_NULL;)
|
|
{
|
|
map_entry = (Ipssh_map_entry*) entry;
|
|
|
|
/* Advance entry prior to handler invocation, since it may delete it */
|
|
entry = ipcom_list_next(entry);
|
|
|
|
if (IP_FD_ISSET(map_entry->sock, &tmp_read_set) ||
|
|
IP_FD_ISSET(map_entry->sock, &tmp_write_set))
|
|
{
|
|
if (map_entry->handler(map_entry->sock, ctx, map_entry))
|
|
{
|
|
/* close connection */
|
|
goto exit;
|
|
}
|
|
rc--;
|
|
if (rc==0)
|
|
goto again;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
ipssh_unregister_clt(ctx);
|
|
#ifdef IP_PORT_VXWORKS
|
|
if ( (_func_logoutEvent != IP_NULL) &&
|
|
(ctx->ssh_conn->conn_state == IPSSH_CONN_STATE_SRV_TERM) )
|
|
_func_logoutEvent(SEC_EVENT_SSH_LOGOUT, ctx->ssh_conn->user);
|
|
#endif
|
|
if (ctx)
|
|
{
|
|
ipssh_close_conn(ctx);
|
|
}
|
|
ERR_remove_state(0);
|
|
/*
|
|
* Increase the IPSSH process priority to prevent from IPSSH daemon task pending in creating
|
|
* another IPSSH process with same name while it has not been deleted completely.
|
|
*/
|
|
(void)ipcom_proc_setprio(ipcom_getpid(), IPSSH_EXIT_PROC_PRIO);
|
|
ipcom_proc_exit();
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_close_conn
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipssh_close_conn(Ipssh_conn_ctx *ctx)
|
|
{
|
|
ipssh_conn_free(ctx->ssh_conn);
|
|
ipssh_conn_ctx_free(ctx);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_make_version_str
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
|
|
ipssh_make_version_str(int v1, int v2, char *buf, int buf_len)
|
|
{
|
|
int vers_major;
|
|
int vers_minor;
|
|
int fips_mode;
|
|
|
|
ip_assert(v1 || v2);
|
|
|
|
#ifdef OPENSSL_FIPS
|
|
fips_mode = FIPS_mode();
|
|
#else
|
|
fips_mode = 0;
|
|
#endif
|
|
|
|
#ifdef IPSSH_USE_V1
|
|
if (v1 && !fips_mode && v2)
|
|
{
|
|
vers_major = 1;
|
|
vers_minor = 99;
|
|
}
|
|
else if (v2)
|
|
{
|
|
vers_major = 2;
|
|
vers_minor = 0;
|
|
}
|
|
else if (v1 && !fips_mode)
|
|
{
|
|
vers_major = 1;
|
|
vers_minor = 5;
|
|
}
|
|
else
|
|
{
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
#else
|
|
vers_major = 2;
|
|
vers_minor = 0;
|
|
#endif
|
|
|
|
ipcom_snprintf(buf, buf_len, "SSH-%d.%d-IPSSH-" IPSSH_VERSION "\r\n", vers_major, vers_minor);
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_read_version_str
|
|
*===========================================================================
|
|
* Description: Read client's version string
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_read_version_str(Ip_fd sock, Ipssh_conn_ctx *ctx, Ipssh_map_entry *map_entry)
|
|
{
|
|
Ip_size_t str_len;
|
|
union Ip_sockaddr_union sa;
|
|
Ip_socklen_t addr_len;
|
|
|
|
IPCOM_LOG1(DEBUG, "ipssh_read_version_str(), sock=%d", sock);
|
|
|
|
str_len = IPSSH_VERSION_STRING_MAX_LEN - 1;
|
|
if (ipssh_read_from_socket(ctx->ssh_conn->sock, ctx->version_str, &str_len, "\n") != IPCOM_SUCCESS)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_read_version_str :: ipssh_read_from_socket() failed");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
if (str_len < 9) /* A valid version string cannot be shorter than 9 bytes */
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_read_version_str :: too short version string, %d bytes", str_len);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
/* Strip off any CRLF */
|
|
ctx->version_str[str_len-1] = 0;
|
|
if (ctx->version_str[str_len-2] == '\r') /* strip off any CR */
|
|
ctx->version_str[str_len-2] = 0;
|
|
|
|
if (ipssh_msg2list((Ipssh_msg_list*)&(ctx->ssh_conn->handshake_msgs), IPSSH_STR, (Ip_u8 *)ctx->version_str,
|
|
(Ip_u32)ipcom_strlen(ctx->version_str), (Ip_u8)0))
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_read_version_str :: ipssh_msg2list() failed");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
|
|
/* Add local addr of TCP connection to shell params */
|
|
addr_len = sizeof(sa);
|
|
if (ipcom_getsockname(sock, (struct Ip_sockaddr *)&sa, &addr_len) == IP_SOCKERR)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_read_version_str :: ipcom_getsockname(client_fd) failed, errno = %d", ipcom_errno);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
if (ipcom_shell_ctx_add_local_addr(ctx->ssh_conn->sh_ctx, &sa) != IPCOM_SUCCESS)
|
|
return IPCOM_ERR_FAILED;
|
|
|
|
/* Add peer addr of TCP connection to shell params */
|
|
addr_len = sizeof(sa);
|
|
if (ipcom_getpeername(sock, (struct Ip_sockaddr *)&sa, &addr_len) == IP_SOCKERR)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_read_version_str :: ipcom_getpeername(client_fd) failed, errno = %d", ipcom_errno);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
if (ipcom_shell_ctx_add_peer_addr(ctx->ssh_conn->sh_ctx, &sa) != IPCOM_SUCCESS)
|
|
return IPCOM_ERR_FAILED;
|
|
if (ipcom_shell_ctx_add_int(ctx->ssh_conn->sh_ctx,
|
|
IPCOM_SHELL_CTX_INVOKER,
|
|
IPCOM_SHELL_CTX_INVOKER_SSH) != IPCOM_SUCCESS)
|
|
return IPCOM_ERR_FAILED;
|
|
if (ipcom_shell_ctx_add_int(ctx->ssh_conn->sh_ctx,
|
|
IPSSH_CLT_FD,
|
|
sock) != IPCOM_SUCCESS)
|
|
return IPCOM_ERR_FAILED;
|
|
|
|
|
|
/* Parse version string */
|
|
{
|
|
Ip_s32 count, proto_ver_major, proto_ver_minor;
|
|
|
|
count = ipcom_sscanf(ctx->version_str, "SSH-%d.%d-%s ",
|
|
(int*)&proto_ver_major, (int*)&proto_ver_minor, ctx->clt_id);
|
|
if (count != 3)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_read_version_str :: failed to parse version str");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
/* Add client specific flags here. */
|
|
if (ipcom_strncmp(ctx->clt_id, "PuTTY", ipcom_strlen("PuTTY")) == 0)
|
|
{
|
|
ctx->ssh_conn->flags = IPSSH_FLAG_BUG_UNRESTRICTED_SFTP_MSG_SIZE;
|
|
}
|
|
switch(ipssh_get_conn_version(proto_ver_major,
|
|
proto_ver_minor,
|
|
ctx->srv_ctx->config.v1,
|
|
ctx->srv_ctx->config.v2,
|
|
ctx->srv_ctx->config.v1 && ctx->srv_ctx->config.v2))
|
|
{
|
|
#ifdef IPSSH_USE_V1
|
|
case 1:
|
|
#ifdef OPENSSL_FIPS
|
|
if (FIPS_mode())
|
|
{
|
|
IPCOM_LOG0(INFO, "ipssh_read_version_str :: SSH v1 support not allowed in FIPS mode");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
#endif
|
|
ctx->ssh_conn->ssh_version = 1;
|
|
return ipssh_initiate_v1_connection(ctx->ssh_conn, map_entry);
|
|
#else
|
|
case 1:
|
|
IPCOM_LOG0(INFO, "ipssh_read_version_str :: SSH v1 support not compiled in");
|
|
return IPCOM_ERR_FAILED;
|
|
#endif
|
|
case 2:
|
|
ctx->ssh_conn->ssh_version = 2;
|
|
return ipssh_initiate_v2_connection(ctx->ssh_conn, map_entry);
|
|
default:
|
|
IPCOM_LOG2(ERR, "ipssh_read_version_str :: unsupported version, %d.%d", proto_ver_major, proto_ver_minor);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_get_conn_version
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL int
|
|
ipssh_get_conn_version(int major_ver, int minor_ver, int v1, int v2, Ip_bool comp_mode)
|
|
{
|
|
#ifdef OPENSSL_FIPS
|
|
int fips_mode = FIPS_mode();
|
|
#endif
|
|
|
|
switch(major_ver)
|
|
{
|
|
case 1:
|
|
/* This is a request to run v2, if possible */
|
|
if (minor_ver == 99)
|
|
{
|
|
if (!comp_mode)
|
|
{
|
|
IPCOM_LOG0(INFO, "ipssh_get_conn_version :: SSH compatibility mode not turned on");
|
|
return 0;
|
|
}
|
|
|
|
if (v2)
|
|
{
|
|
return 2;
|
|
}
|
|
}
|
|
#ifdef IPSSH_USE_V1
|
|
/* Check that SSH v1 is active */
|
|
if (!v1)
|
|
{
|
|
IPCOM_LOG0(INFO, "ipssh_get_conn_version :: SSH v1 support not turned on");
|
|
return 0;
|
|
}
|
|
#ifdef OPENSSL_FIPS
|
|
if (fips_mode)
|
|
{
|
|
IPCOM_LOG0(INFO, "ipssh_get_conn_version :: SSH v1 support not allowed in FIPS mode");
|
|
return 0;
|
|
}
|
|
#endif
|
|
return 1;
|
|
#else
|
|
IPCOM_LOG0(INFO, "ipssh_get_conn_version :: SSH v1 support not compiled in");
|
|
return 0;
|
|
#endif
|
|
case 2:
|
|
#ifdef IPSSH_USE_V1
|
|
/* Check that SSH v2 is turned on */
|
|
if (!v2)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_get_conn_version :: SSH v2 support not turned on");
|
|
return 0;
|
|
}
|
|
#endif
|
|
return 2;
|
|
default:
|
|
IPCOM_LOG2(ERR, "ipssh_get_conn_version :: unsupported version, %d.%d", major_ver, minor_ver);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_conn_ctx_new
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipssh_conn_ctx_set_srv_ctx(Ipssh_conn_ctx * ctx, Ipssh_srv_ctx *srv_ctx)
|
|
{
|
|
ipcom_memcpy(&ctx->ssh_conn->config, &srv_ctx->config, sizeof(struct Ipssh_conn_config_st));
|
|
|
|
ctx->srv_ctx = ipssh_srv_ctx_dup(srv_ctx);
|
|
|
|
|
|
if (srv_ctx->dsa_key)
|
|
{
|
|
ctx->ssh_conn->srv_dsa_key = srv_ctx->dsa_key;
|
|
(void)DSA_up_ref(srv_ctx->dsa_key);
|
|
}
|
|
#ifdef IPSSH_USE_RSA
|
|
if (srv_ctx->rsa_key)
|
|
{
|
|
ctx->ssh_conn->srv_rsa_key = srv_ctx->rsa_key;
|
|
(void)RSA_up_ref(srv_ctx->rsa_key);
|
|
}
|
|
if (srv_ctx->rsa_keyex_key)
|
|
{
|
|
ctx->ssh_conn->srv_rsa_keyex_key = srv_ctx->rsa_keyex_key;
|
|
(void)RSA_up_ref(srv_ctx->rsa_keyex_key);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_conn_ctx_new
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC Ipssh_conn_ctx *
|
|
ipssh_conn_ctx_new(void)
|
|
{
|
|
Ipssh_conn_ctx *ctx;
|
|
ctx = ipcom_malloc(sizeof(Ipssh_conn_ctx));
|
|
if (!ctx)
|
|
{
|
|
IPCOM_LOG1(ERR, "ipssh_conn_ctx_new :: ipcom_malloc() failed, errno=%d", ipcom_errno);
|
|
return IP_NULL;
|
|
}
|
|
ipcom_memset(ctx, 0, sizeof(Ipssh_conn_ctx));
|
|
ctx->version_str = ipcom_malloc(IPSSH_VERSION_STRING_MAX_LEN+1);
|
|
ctx->clt_id = ipcom_malloc(IPSSH_VERSION_STRING_MAX_LEN+1);
|
|
ctx->ssh_conn = ipssh_conn_new();
|
|
|
|
if (!ctx->version_str || !ctx->clt_id || !ctx->ssh_conn)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_conn_ctx_new :: ipcom_malloc() failed");
|
|
goto cleanup;
|
|
}
|
|
ipcom_list_init(&ctx->ctx_map);
|
|
ctx->ssh_conn->ssh_conn_ctx = ctx;
|
|
|
|
ipcom_memset(ctx->version_str, 0, IPSSH_VERSION_STRING_MAX_LEN+1);
|
|
ctx->ssh_conn->conn_state = IPSSH_CONN_STATE_SRV_KEX_INIT;
|
|
ctx->ssh_conn->port_fwd_cb = ipssh_port_fwd_cb;
|
|
ctx->ssh_conn->extra = ctx;
|
|
|
|
return ctx;
|
|
|
|
cleanup:
|
|
ipssh_conn_ctx_free(ctx);
|
|
return IP_NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_conn_ctx_free
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_STATIC void
|
|
ipssh_conn_ctx_free(Ipssh_conn_ctx *ctx)
|
|
{
|
|
Ipssh_map_entry *map_entry;
|
|
|
|
ip_assert(ctx);
|
|
|
|
if (ctx->version_str)
|
|
ipcom_free(ctx->version_str);
|
|
if (ctx->clt_id)
|
|
ipcom_free(ctx->clt_id);
|
|
|
|
for (map_entry = (Ipssh_map_entry*)ipcom_list_first(&ctx->ctx_map);
|
|
map_entry;
|
|
map_entry = (Ipssh_map_entry*)ipcom_list_first(&ctx->ctx_map))
|
|
{
|
|
if (map_entry->sock != IP_INVALID_SOCKET && !(map_entry->flags & IPSSH_FLAG_NO_CLOSE))
|
|
ipcom_socketclose(map_entry->sock);
|
|
ipssh_delete_map_entry_for_sock(&ctx->ctx_map, map_entry->sock);
|
|
}
|
|
|
|
if (ctx->srv_ctx)
|
|
ipssh_srv_ctx_free(ctx->srv_ctx);
|
|
|
|
ipcom_free(ctx);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_get_len
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_u32
|
|
ipssh_get_len(Ip_u8 *ptr)
|
|
{
|
|
Ip_u32 tmp;
|
|
|
|
tmp = ptr[0] << 24;
|
|
tmp += ptr[1] << 16;
|
|
tmp += ptr[2] << 8;
|
|
tmp += ptr[3];
|
|
|
|
return tmp;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_read_from_socket
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_read_from_socket(Ip_fd sock, char *buf, Ip_size_t* buf_len, const char *delim)
|
|
{
|
|
Ip_size_t pos;
|
|
Ip_ssize_t rc;
|
|
int tries = 0;
|
|
|
|
IPCOM_LOG1(DEBUG2, "ipssh_read_from_socket(), sock=%d", sock);
|
|
|
|
for (pos=0; pos<*buf_len; pos++)
|
|
{
|
|
retry:
|
|
rc = ipcom_socketread(sock, &(buf[pos]), 1);
|
|
|
|
if (rc < 0)
|
|
{
|
|
if (ipcom_errno == IP_ERRNO_EWOULDBLOCK)
|
|
{
|
|
ipcom_millisleep(100);
|
|
tries++;
|
|
if (tries < 100)
|
|
goto retry;
|
|
}
|
|
IPCOM_LOG1(ERR, "ipssh_read_from_socket :: ipcom_socketread() failed, errno=%d", ipcom_errno);
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
if (rc == 0)
|
|
{
|
|
IPCOM_LOG0(ERR, "ipssh_read_from_socket :: peer closed connection");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
if (ipcom_strstr(buf, delim) != IP_NULL)
|
|
{
|
|
*buf_len = pos + 1;
|
|
buf[pos+1] = 0;
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_get_clt_info
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_PUBLIC Ip_err
|
|
ipssh_get_clt_info(Ipssh_clt_info* clt_info, int pos)
|
|
{
|
|
Ipssh_conn_ctx *ctx;
|
|
union Ip_sockaddr_union addr;
|
|
Ip_socklen_t addr_len;
|
|
void *addr_ptr;
|
|
int i;
|
|
|
|
ip_assert(clt_info);
|
|
|
|
ipcom_memset(clt_info, 0, sizeof(Ipssh_clt_info));
|
|
|
|
IPSSH_CODE_LOCK();
|
|
|
|
if (g_ipssh.clt_list.size == 0 || (Ip_u32)pos >= g_ipssh.clt_list.size)
|
|
{
|
|
IPSSH_CODE_UNLOCK();
|
|
return IPCOM_ERR_NOT_FOUND;
|
|
}
|
|
|
|
/* Iterate to the requested position */
|
|
for (i=0, ctx = IPCOM_LIST_FIRST(&(g_ipssh.clt_list));
|
|
i<pos && ctx;
|
|
ctx = IPCOM_LIST_NEXT(&(ctx->list)), i++);
|
|
|
|
/* This check is unnecessary. Just to make Coverity happy */
|
|
if (!ctx)
|
|
{
|
|
IPSSH_CODE_UNLOCK();
|
|
return IPCOM_ERR_NOT_FOUND;
|
|
}
|
|
|
|
switch(ctx->ssh_conn->conn_state)
|
|
{
|
|
case IPSSH_CONN_STATE_SRV_KEX_INIT:
|
|
case IPSSH_CONN_STATE_SRV_KEX_DH_INIT:
|
|
case IPSSH_CONN_STATE_SRV_NEW_KEY:
|
|
case IPSSH_CONN_STATE_SERVICE_REQ:
|
|
case IPSSH_CONN_STATE_SRV_KEY_EX_V1:
|
|
case IPSSH_CONN_STATE_SRV_PENDING_REQUEST:
|
|
case IPSSH_CONN_STATE_SRV_USER_AUTH:
|
|
case IPSSH_CONN_STATE_SRV_KEX_GEX:
|
|
case IPSSH_CONN_STATE_SRV_KEX_RSA:
|
|
ipcom_strncpy(clt_info->service, "in handshake", sizeof(clt_info->service));
|
|
break;
|
|
case IPSSH_CONN_STATE_SRV_TERM:
|
|
ipcom_strncpy(clt_info->service, "terminal", sizeof(clt_info->service));
|
|
break;
|
|
case IPSSH_CONN_STATE_SRV_SSH_SFTP:
|
|
ipcom_strncpy(clt_info->service, "sftp", sizeof(clt_info->service));
|
|
break;
|
|
default:
|
|
IP_PANIC();
|
|
break;
|
|
}
|
|
|
|
addr_len = sizeof(addr);
|
|
(void)ipcom_getpeername(ctx->ssh_conn->sock, &addr.sa, &addr_len);
|
|
#ifdef IPCOM_USE_INET6
|
|
if(addr.sa.sa_family == IP_AF_INET6)
|
|
addr_ptr = &addr.sin6.sin6_addr;
|
|
else
|
|
#endif
|
|
addr_ptr = &addr.sin.sin_addr;
|
|
(void)ipcom_inet_ntop(addr.sa.sa_family, addr_ptr, clt_info->clt_addr, sizeof(clt_info->clt_addr));
|
|
|
|
clt_info->pid = ctx->pid;
|
|
|
|
if (ctx->ssh_conn->conn_state == IPSSH_CONN_STATE_SRV_TERM || ctx->ssh_conn->conn_state == IPSSH_CONN_STATE_SRV_SSH_SFTP)
|
|
{
|
|
ipcom_strncpy(clt_info->userid, ctx->ssh_conn->user, sizeof(clt_info->userid));
|
|
|
|
switch(ctx->ssh_conn->auth_meth)
|
|
{
|
|
case IPSSH_AUTH_METH_NONE:
|
|
ipcom_strncpy(clt_info->auth_method, "none", sizeof(clt_info->auth_method));
|
|
break;
|
|
case IPSSH_AUTH_METH_PUBLIC_KEY:
|
|
ipcom_strncpy(clt_info->auth_method, "public key", sizeof(clt_info->auth_method));
|
|
break;
|
|
case IPSSH_AUTH_METH_PW:
|
|
ipcom_strncpy(clt_info->auth_method, "password", sizeof(clt_info->auth_method));
|
|
break;
|
|
case IPSSH_AUTH_METH_HOSTBASED:
|
|
ipcom_strncpy(clt_info->auth_method, "hostbased", sizeof(clt_info->auth_method));
|
|
break;
|
|
default:
|
|
IP_PANIC();
|
|
}
|
|
|
|
if (ctx->ssh_conn->ssh_version == 2)
|
|
{
|
|
ipcom_strncpy(clt_info->cipher, ipssh_alg_id2str(ctx->ssh_conn->conn_prop.enc_alg_clt2srv[0]), sizeof(clt_info->cipher));
|
|
ipcom_strncpy(clt_info->mac, ipssh_alg_id2str(ctx->ssh_conn->conn_prop.mac_alg_clt2srv[0]), sizeof(clt_info->mac));
|
|
}
|
|
#ifdef IPSSH_USE_V1
|
|
else
|
|
{
|
|
switch(ctx->ssh_conn->cipher)
|
|
{
|
|
case IPSSH_V1_CIPHER_3DES:
|
|
ipcom_strncpy(clt_info->cipher, "3des", sizeof(clt_info->cipher));
|
|
break;
|
|
case IPSSH_V1_CIPHER_DES:
|
|
ipcom_strncpy(clt_info->cipher, "des", sizeof(clt_info->cipher));
|
|
break;
|
|
case IPSSH_V1_CIPHER_RC4:
|
|
ipcom_strncpy(clt_info->cipher, "RC4", sizeof(clt_info->cipher));
|
|
break;
|
|
case IPSSH_V1_CIPHER_BLOWFISH:
|
|
ipcom_strncpy(clt_info->cipher, "blowfish", sizeof(clt_info->cipher));
|
|
break;
|
|
case IPSSH_V1_CIPHER_NONE:
|
|
ipcom_strncpy(clt_info->cipher, "none", sizeof(clt_info->cipher));
|
|
break;
|
|
default:
|
|
IP_PANIC();
|
|
}
|
|
ipcom_strncpy(clt_info->mac, "crc32", sizeof(clt_info->mac));
|
|
}
|
|
#endif
|
|
|
|
ipcom_strncpy(clt_info->ssh_clt_sw, ctx->clt_id, sizeof(clt_info->ssh_clt_sw)-1);
|
|
}
|
|
|
|
IPSSH_CODE_UNLOCK();
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_conn_init
|
|
*===========================================================================
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_conn_init(Ipssh_conn *ssh_conn)
|
|
{
|
|
ipcom_memset(ssh_conn, 0, sizeof(Ipssh_conn));
|
|
|
|
(void)EVP_EncryptInit(&(ssh_conn->send_ctx), EVP_enc_null(), IP_NULL, IP_NULL);
|
|
(void)EVP_DecryptInit(&(ssh_conn->recv_ctx), EVP_enc_null(), IP_NULL, IP_NULL);
|
|
|
|
ssh_conn->sock = IP_INVALID_SOCKET;
|
|
ssh_conn->dh_data = ipcom_malloc(sizeof(Ipssh_kex_dh_data));
|
|
if (!ssh_conn->dh_data)
|
|
{
|
|
IPCOM_LOG0(WARNING, "ipssh_conn_init :: out of memory");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
ssh_conn->sh_ctx = ipcom_shell_ctx_new(128);
|
|
if (!ssh_conn->sh_ctx)
|
|
{
|
|
ipcom_free(ssh_conn->dh_data);
|
|
IPCOM_LOG0(WARNING, "ipssh_conn_init :: out of memory");
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
ipcom_memset(ssh_conn->dh_data, 0, sizeof(Ipssh_kex_dh_data));
|
|
ipcom_list_init((Ipcom_list*)&(ssh_conn->handshake_msgs));
|
|
|
|
ipcom_list_init(&ssh_conn->ch_map);
|
|
ipcom_list_init(&ssh_conn->port_fwd_addr_map);
|
|
|
|
return ipssh_get_config(&ssh_conn->config);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_conn_new
|
|
*===========================================================================
|
|
*/
|
|
IP_GLOBAL Ipssh_conn*
|
|
ipssh_conn_new(void)
|
|
{
|
|
Ipssh_conn *ssh_conn;
|
|
|
|
ssh_conn = ipcom_malloc(sizeof(Ipssh_conn));
|
|
if (!ssh_conn)
|
|
return IP_NULL;
|
|
|
|
if (ipssh_conn_init(ssh_conn) != IPCOM_SUCCESS)
|
|
{
|
|
ipcom_free(ssh_conn);
|
|
return IP_NULL;
|
|
}
|
|
|
|
return ssh_conn;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_free_handshake_msgs
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipssh_free_handshake_msgs(Ipssh_conn *ssh_conn, int flags)
|
|
{
|
|
Ipcom_list *entry;
|
|
|
|
for (entry = ipcom_list_first((Ipcom_list*)&(ssh_conn->handshake_msgs));
|
|
entry;
|
|
)
|
|
{
|
|
Ipssh_msg_list* msg_entry = (Ipssh_msg_list*) entry;
|
|
if (flags & IPSSH_HANDSHAKE_MSG_FLAG_SAVE_VER_STR)
|
|
{
|
|
/* The clt and srv version string have pos 1 and 2, respectively */
|
|
if (msg_entry->hash_pos == 0 || msg_entry->hash_pos == 1)
|
|
{
|
|
entry = ipcom_list_next(&msg_entry->list);
|
|
continue;
|
|
}
|
|
}
|
|
entry = ipcom_list_next(&msg_entry->list);
|
|
ipcom_list_remove((Ipcom_list*)msg_entry);
|
|
if (msg_entry->value)
|
|
ipcom_free(msg_entry->value);
|
|
ipcom_free(msg_entry);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_conn_free
|
|
*===========================================================================
|
|
*/
|
|
IP_GLOBAL void
|
|
ipssh_conn_free(Ipssh_conn *ssh_conn)
|
|
{
|
|
Ipcom_list *entry;
|
|
|
|
for (entry = ipcom_list_first((Ipcom_list*)&ssh_conn->ch_map);
|
|
entry;
|
|
entry = ipcom_list_first((Ipcom_list*)&ssh_conn->ch_map))
|
|
{
|
|
Ipssh_channel* ch = (Ipssh_channel*) entry;
|
|
|
|
ipssh_channel_close(ch);
|
|
}
|
|
|
|
if (ssh_conn->dh_data)
|
|
{
|
|
if (ssh_conn->dh_data->e)
|
|
BN_free(ssh_conn->dh_data->e);
|
|
if (ssh_conn->dh_data->f)
|
|
BN_free(ssh_conn->dh_data->f);
|
|
if (ssh_conn->dh_data->dh)
|
|
DH_free(ssh_conn->dh_data->dh);
|
|
ipcom_free(ssh_conn->dh_data);
|
|
}
|
|
|
|
ipssh_free_handshake_msgs(ssh_conn, 0);
|
|
|
|
HMAC_cleanup(&(ssh_conn->recv_hmac_ctx));
|
|
HMAC_cleanup(&(ssh_conn->send_hmac_ctx));
|
|
|
|
(void)EVP_CIPHER_CTX_cleanup(&(ssh_conn->send_ctx));
|
|
(void)EVP_CIPHER_CTX_cleanup(&(ssh_conn->recv_ctx));
|
|
|
|
if (ssh_conn->shared_secret)
|
|
ipcom_free(ssh_conn->shared_secret);
|
|
if (ssh_conn->hash)
|
|
ipcom_free(ssh_conn->hash);
|
|
|
|
if (ssh_conn->key_mtrl.iv_c2s.key)
|
|
ipcom_free(ssh_conn->key_mtrl.iv_c2s.key);
|
|
if (ssh_conn->key_mtrl.iv_s2c.key)
|
|
ipcom_free(ssh_conn->key_mtrl.iv_s2c.key);
|
|
if (ssh_conn->key_mtrl.key_c2s.key)
|
|
ipcom_free(ssh_conn->key_mtrl.key_c2s.key);
|
|
if (ssh_conn->key_mtrl.key_s2c.key)
|
|
ipcom_free(ssh_conn->key_mtrl.key_s2c.key);
|
|
if (ssh_conn->key_mtrl.mac_key_c2s.key)
|
|
ipcom_free(ssh_conn->key_mtrl.mac_key_c2s.key);
|
|
if (ssh_conn->key_mtrl.mac_key_c2s_active.key)
|
|
ipcom_free(ssh_conn->key_mtrl.mac_key_c2s_active.key);
|
|
if (ssh_conn->key_mtrl.mac_key_s2c.key)
|
|
ipcom_free(ssh_conn->key_mtrl.mac_key_s2c.key);
|
|
if (ssh_conn->key_mtrl.mac_key_s2c_active.key)
|
|
ipcom_free(ssh_conn->key_mtrl.mac_key_s2c_active.key);
|
|
|
|
|
|
/* v1 data */
|
|
/* 3DES in ssh v1 uses a 24 byte iv, which doesn't fit into the iv field of EVP_CIPHER_CTX.
|
|
* Instead it is stored in the app_data field */
|
|
if (ssh_conn->send_ctx.app_data)
|
|
ipcom_free(ssh_conn->send_ctx.app_data);
|
|
if (ssh_conn->recv_ctx.app_data)
|
|
ipcom_free(ssh_conn->recv_ctx.app_data);
|
|
|
|
if (ssh_conn->sess_id)
|
|
ipcom_free(ssh_conn->sess_id);
|
|
|
|
if (ssh_conn->user)
|
|
ipcom_free(ssh_conn->user);
|
|
if (ssh_conn->service)
|
|
ipcom_free(ssh_conn->service);
|
|
|
|
if (ssh_conn->recv_enc_buf)
|
|
ipcom_buffer_free(ssh_conn->recv_enc_buf);
|
|
if (ssh_conn->recv_plain_buf)
|
|
ipcom_buffer_free(ssh_conn->recv_plain_buf);
|
|
|
|
for (entry = ipcom_list_first(&ssh_conn->port_fwd_addr_map);
|
|
entry;
|
|
entry = ipcom_list_first(&ssh_conn->port_fwd_addr_map))
|
|
{
|
|
Ipssh_port_fwd_addr_map* addr_map = (Ipssh_port_fwd_addr_map*) entry;
|
|
|
|
ipcom_list_remove(entry);
|
|
ipcom_free(addr_map->addr);
|
|
ipcom_free(addr_map);
|
|
}
|
|
|
|
if (ssh_conn->srv_dsa_key)
|
|
DSA_free(ssh_conn->srv_dsa_key);
|
|
#ifdef IPSSH_USE_RSA
|
|
if (ssh_conn->srv_rsa_key)
|
|
RSA_free(ssh_conn->srv_rsa_key);
|
|
if (ssh_conn->srv_rsa_keyex_key)
|
|
RSA_free(ssh_conn->srv_rsa_keyex_key);
|
|
#endif
|
|
|
|
#ifdef IPSSH_USE_V1
|
|
if (ssh_conn->server_key)
|
|
RSA_free(ssh_conn->server_key);
|
|
if (ssh_conn->client_pub_key)
|
|
RSA_free(ssh_conn->client_pub_key);
|
|
#endif
|
|
|
|
if (ssh_conn->sock != IP_INVALID_SOCKET)
|
|
ipcom_socketclose(ssh_conn->sock);
|
|
|
|
if (ssh_conn->sh_ctx)
|
|
ipcom_shell_ctx_free(ssh_conn->sh_ctx);
|
|
|
|
/* SSH clt data */
|
|
if (ssh_conn->data.clt.key)
|
|
EVP_PKEY_free(ssh_conn->data.clt.key);
|
|
|
|
if (ssh_conn->data.clt.host_key)
|
|
EVP_PKEY_free(ssh_conn->data.clt.host_key);
|
|
|
|
if (ssh_conn->data.clt.srv_pub_key)
|
|
EVP_PKEY_free(ssh_conn->data.clt.srv_pub_key);
|
|
|
|
ipcom_free(ssh_conn);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_version
|
|
*===========================================================================
|
|
*/
|
|
IP_PUBLIC const char *
|
|
ipssh_version(void)
|
|
{
|
|
return "@(#) IPSSH $Name: VXWORKS_ITER40_RCPL8_2024030303 $ - INTERPEAK_COPYRIGHT_STRING";
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_start
|
|
*===========================================================================
|
|
*/
|
|
IP_PUBLIC Ip_err
|
|
ipssh_start(void)
|
|
{
|
|
Ip_err ret;
|
|
Ipcom_proc_attr attr;
|
|
|
|
/* Start daemon */
|
|
IPCOM_LOG0(INFO, "ipssh_start :: starting \"ipssh\" process");
|
|
|
|
ipcom_proc_attr_init(&attr);
|
|
attr.priority = IPSSH_DAEMON_PROC_PRIO;
|
|
attr.stacksize = IPCOM_PROC_STACK_MAX;
|
|
attr.flags = IPCOM_PROC_FLAG_FP;
|
|
ret = ipcom_proc_acreate(IPSSH_DAEMON_NAME, ipssh, &attr, IP_NULL);
|
|
ip_assert(ret == IPCOM_SUCCESS);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_alg_id2str
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL const char *
|
|
ipssh_alg_id2str(Ip_u8 id)
|
|
{
|
|
Ipssh_config_map* map;
|
|
|
|
for (map = ipssh_alg_ids; map->str != IP_NULL; map++)
|
|
{
|
|
if (id == map->id)
|
|
return map->str;
|
|
}
|
|
|
|
return IP_NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_alg_str2id
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_PUBLIC Ip_u8
|
|
ipssh_alg_str2id(const char *str)
|
|
{
|
|
Ipssh_config_map* map;
|
|
|
|
for (map = ipssh_alg_ids; map->str != IP_NULL; map++)
|
|
{
|
|
if (ipcom_strcmp(str, map->str) == 0)
|
|
return map->id;
|
|
}
|
|
|
|
/* No match found */
|
|
return IPSSH_ID_UNKNOWN;
|
|
}
|
|
|
|
|
|
#ifdef IPSSH_USE_V1
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_port_fwd_cb_v1
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_port_fwd_cb_v1(Ipssh_conn_ctx *ctx, Ip_fd port_fwd_sock, int event)
|
|
{
|
|
switch(event)
|
|
{
|
|
case IPSSH_PORT_FWD_EVENT_LISTEN:
|
|
if (!ipssh_store_map_entry(ctx, port_fwd_sock, ipssh_port_fwd_listen_handler_v1, 0))
|
|
return IPCOM_ERR_FAILED;
|
|
IP_FD_SET(port_fwd_sock, &(ctx->read_set));
|
|
break;
|
|
|
|
case IPSSH_PORT_FWD_EVENT_OPEN:
|
|
if (!ipssh_store_map_entry(ctx, port_fwd_sock, ipssh_port_fwd_handler_s2c_v1, 0))
|
|
return IPCOM_ERR_FAILED;
|
|
IP_FD_SET(port_fwd_sock, &(ctx->read_set));
|
|
break;
|
|
|
|
case IPSSH_PORT_FWD_EVENT_CLOSE:
|
|
IP_FD_CLR(port_fwd_sock, &(ctx->read_set));
|
|
ipssh_delete_map_entry_for_sock(&ctx->ctx_map, port_fwd_sock);
|
|
break;
|
|
|
|
|
|
default:
|
|
IP_PANIC();
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_port_fwd_cb_v2
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_port_fwd_cb_v2(Ipssh_conn_ctx *ctx, Ip_fd port_fwd_sock, int event)
|
|
{
|
|
switch(event)
|
|
{
|
|
case IPSSH_PORT_FWD_EVENT_LISTEN:
|
|
if (!ipssh_store_map_entry(ctx, port_fwd_sock, ipssh_port_fwd_listen_handler, 0))
|
|
return IPCOM_ERR_FAILED;
|
|
IP_FD_SET(port_fwd_sock, &(ctx->read_set));
|
|
break;
|
|
|
|
case IPSSH_PORT_FWD_EVENT_OPEN:
|
|
if (!ipssh_store_map_entry(ctx, port_fwd_sock, ipssh_port_fwd_handler_s2c, 0))
|
|
return IPCOM_ERR_FAILED;
|
|
IP_FD_SET(port_fwd_sock, &(ctx->read_set));
|
|
break;
|
|
|
|
case IPSSH_PORT_FWD_EVENT_SEND_WINDOW_OPEN:
|
|
IP_FD_SET(port_fwd_sock, &(ctx->read_set));
|
|
break;
|
|
case IPSSH_PORT_FWD_EVENT_SEND_WINDOW_CLOSED:
|
|
IP_FD_CLR(port_fwd_sock, &(ctx->read_set));
|
|
break;
|
|
case IPSSH_PORT_FWD_EVENT_CLOSE:
|
|
IP_FD_CLR(port_fwd_sock, &(ctx->read_set));
|
|
ipssh_delete_map_entry_for_sock(&ctx->ctx_map, port_fwd_sock);
|
|
break;
|
|
|
|
|
|
default:
|
|
IP_PANIC();
|
|
return IPCOM_ERR_FAILED;
|
|
}
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_port_fwd_cb
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL Ip_err
|
|
ipssh_port_fwd_cb(Ipssh_conn *ssh_conn, Ip_fd port_fwd_sock, int event)
|
|
{
|
|
Ipssh_conn_ctx *ctx;
|
|
|
|
ctx = (Ipssh_conn_ctx*)ssh_conn->extra;
|
|
|
|
#ifdef IPSSH_USE_V1
|
|
if (ssh_conn->ssh_version == 1)
|
|
return ipssh_port_fwd_cb_v1(ctx, port_fwd_sock, event);
|
|
else
|
|
#endif
|
|
return ipssh_port_fwd_cb_v2(ctx, port_fwd_sock, event);
|
|
}
|
|
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipssh_conn_get_fd
|
|
*===========================================================================
|
|
* Description:
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_PUBLIC Ip_fd
|
|
ipssh_conn_get_fd(Ipssh_conn* ssh_conn)
|
|
{
|
|
return ssh_conn->sock;
|
|
}
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* END OF FILE
|
|
****************************************************************************
|
|
*/
|