#include "config.h"
#include <string.h>
#include <stdlib.h>
#include "libssh/priv.h"
#include "libssh/libssh.h"
#include "libssh/crypto.h"
#include "libssh/server.h"
#include "libssh/socket.h"
#include "libssh/ssh2.h"
#include "libssh/agent.h"
#include "libssh/packet.h"
#include "libssh/session.h"
#include "libssh/misc.h"
#include "libssh/buffer.h"
#include "libssh/poll.h"
#include "libssh/pki.h"
#define FIRST_CHANNEL 42
ssh_session ssh_new(void)
{
ssh_session session;
char *id = NULL;
int rc;
session = calloc(1, sizeof (struct ssh_session_struct));
if (session == NULL) {
return NULL;
}
session->next_crypto = crypto_new();
if (session->next_crypto == NULL) {
goto err;
}
session->socket = ssh_socket_new(session);
if (session->socket == NULL) {
goto err;
}
session->out_buffer = ssh_buffer_new();
if (session->out_buffer == NULL) {
goto err;
}
session->in_buffer = ssh_buffer_new();
if (session->in_buffer == NULL) {
goto err;
}
session->out_queue = ssh_list_new();
if (session->out_queue == NULL) {
goto err;
}
session->alive = 0;
session->auth.supported_methods = 0;
ssh_set_blocking(session, 1);
session->maxchannel = FIRST_CHANNEL;
session->agent = ssh_agent_new(session);
if (session->agent == NULL) {
goto err;
}
session->opts.StrictHostKeyChecking = 1;
session->opts.port = 0;
session->opts.fd = -1;
session->opts.compressionlevel = 7;
session->opts.nodelay = 0;
session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH |
SSH_OPT_FLAG_PUBKEY_AUTH |
SSH_OPT_FLAG_KBDINT_AUTH |
SSH_OPT_FLAG_GSSAPI_AUTH;
session->opts.identity = ssh_list_new();
if (session->opts.identity == NULL) {
goto err;
}
id = strdup("%d/id_ed25519");
if (id == NULL) {
goto err;
}
rc = ssh_list_append(session->opts.identity, id);
if (rc == SSH_ERROR) {
goto err;
}
#ifdef HAVE_ECC
id = strdup("%d/id_ecdsa");
if (id == NULL) {
goto err;
}
rc = ssh_list_append(session->opts.identity, id);
if (rc == SSH_ERROR) {
goto err;
}
#endif
id = strdup("%d/id_rsa");
if (id == NULL) {
goto err;
}
rc = ssh_list_append(session->opts.identity, id);
if (rc == SSH_ERROR) {
goto err;
}
#ifdef HAVE_DSA
id = strdup("%d/id_dsa");
if (id == NULL) {
goto err;
}
rc = ssh_list_append(session->opts.identity, id);
if (rc == SSH_ERROR) {
goto err;
}
#endif
session->session_state = SSH_SESSION_STATE_NONE;
session->pending_call_state = SSH_PENDING_CALL_NONE;
session->packet_state = PACKET_STATE_INIT;
session->dh_handshake_state = DH_STATE_INIT;
session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE;
session->auth.state = SSH_AUTH_STATE_NONE;
session->auth.service_state = SSH_AUTH_SERVICE_NONE;
return session;
err:
free(id);
ssh_free(session);
return NULL;
}
void ssh_free(ssh_session session)
{
int i;
struct ssh_iterator *it = NULL;
struct ssh_buffer_struct *b = NULL;
if (session == NULL) {
return;
}
for (it = ssh_list_get_iterator(session->channels);
it != NULL;
it = ssh_list_get_iterator(session->channels)) {
ssh_channel_do_free(ssh_iterator_value(ssh_channel,it));
ssh_list_remove(session->channels, it);
}
ssh_list_free(session->channels);
session->channels = NULL;
#ifdef WITH_PCAP
if (session->pcap_ctx) {
ssh_pcap_context_free(session->pcap_ctx);
session->pcap_ctx = NULL;
}
#endif
ssh_socket_free(session->socket);
session->socket = NULL;
if (session->default_poll_ctx) {
ssh_poll_ctx_free(session->default_poll_ctx);
}
SSH_BUFFER_FREE(session->in_buffer);
SSH_BUFFER_FREE(session->out_buffer);
session->in_buffer = session->out_buffer = NULL;
if (session->in_hashbuf != NULL) {
SSH_BUFFER_FREE(session->in_hashbuf);
}
if (session->out_hashbuf != NULL) {
SSH_BUFFER_FREE(session->out_hashbuf);
}
crypto_free(session->current_crypto);
crypto_free(session->next_crypto);
#ifndef _WIN32
ssh_agent_free(session->agent);
#endif
ssh_key_free(session->srv.dsa_key);
session->srv.dsa_key = NULL;
ssh_key_free(session->srv.rsa_key);
session->srv.rsa_key = NULL;
ssh_key_free(session->srv.ecdsa_key);
session->srv.ecdsa_key = NULL;
ssh_key_free(session->srv.ed25519_key);
session->srv.ed25519_key = NULL;
if (session->ssh_message_list) {
ssh_message msg;
for (msg = ssh_list_pop_head(ssh_message, session->ssh_message_list);
msg != NULL;
msg = ssh_list_pop_head(ssh_message, session->ssh_message_list)) {
ssh_message_free(msg);
}
ssh_list_free(session->ssh_message_list);
}
if (session->kbdint != NULL) {
ssh_kbdint_free(session->kbdint);
}
if (session->packet_callbacks) {
ssh_list_free(session->packet_callbacks);
}
if (session->opts.identity) {
char *id;
for (id = ssh_list_pop_head(char *, session->opts.identity);
id != NULL;
id = ssh_list_pop_head(char *, session->opts.identity)) {
SAFE_FREE(id);
}
ssh_list_free(session->opts.identity);
}
while ((b = ssh_list_pop_head(struct ssh_buffer_struct *,
session->out_queue)) != NULL) {
SSH_BUFFER_FREE(b);
}
ssh_list_free(session->out_queue);
#ifndef _WIN32
ssh_agent_state_free (session->agent_state);
#endif
session->agent_state = NULL;
SAFE_FREE(session->auth.auto_state);
SAFE_FREE(session->serverbanner);
SAFE_FREE(session->clientbanner);
SAFE_FREE(session->banner);
SAFE_FREE(session->opts.bindaddr);
SAFE_FREE(session->opts.custombanner);
SAFE_FREE(session->opts.moduli_file);
SAFE_FREE(session->opts.username);
SAFE_FREE(session->opts.host);
SAFE_FREE(session->opts.sshdir);
SAFE_FREE(session->opts.knownhosts);
SAFE_FREE(session->opts.global_knownhosts);
SAFE_FREE(session->opts.ProxyCommand);
SAFE_FREE(session->opts.gss_server_identity);
SAFE_FREE(session->opts.gss_client_identity);
SAFE_FREE(session->opts.pubkey_accepted_types);
for (i = 0; i < SSH_KEX_METHODS; i++) {
if (session->opts.wanted_methods[i]) {
SAFE_FREE(session->opts.wanted_methods[i]);
}
}
explicit_bzero(session, sizeof(struct ssh_session_struct));
SAFE_FREE(session);
}
const char* ssh_get_clientbanner(ssh_session session) {
if (session == NULL) {
return NULL;
}
return session->clientbanner;
}
const char* ssh_get_serverbanner(ssh_session session) {
if(!session) {
return NULL;
}
return session->serverbanner;
}
const char* ssh_get_kex_algo(ssh_session session) {
if ((session == NULL) ||
(session->current_crypto == NULL)) {
return NULL;
}
switch (session->current_crypto->kex_type) {
case SSH_KEX_DH_GROUP1_SHA1:
return "diffie-hellman-group1-sha1";
case SSH_KEX_DH_GROUP14_SHA1:
return "diffie-hellman-group14-sha1";
case SSH_KEX_DH_GROUP14_SHA256:
return "diffie-hellman-group14-sha256";
case SSH_KEX_DH_GROUP16_SHA512:
return "diffie-hellman-group16-sha512";
case SSH_KEX_DH_GROUP18_SHA512:
return "diffie-hellman-group18-sha512";
case SSH_KEX_ECDH_SHA2_NISTP256:
return "ecdh-sha2-nistp256";
case SSH_KEX_ECDH_SHA2_NISTP384:
return "ecdh-sha2-nistp384";
case SSH_KEX_ECDH_SHA2_NISTP521:
return "ecdh-sha2-nistp521";
case SSH_KEX_CURVE25519_SHA256:
return "curve25519-sha256";
case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG:
return "curve25519-sha256@libssh.org";
default:
break;
}
return NULL;
}
const char* ssh_get_cipher_in(ssh_session session) {
if ((session != NULL) &&
(session->current_crypto != NULL) &&
(session->current_crypto->in_cipher != NULL)) {
return session->current_crypto->in_cipher->name;
}
return NULL;
}
const char* ssh_get_cipher_out(ssh_session session) {
if ((session != NULL) &&
(session->current_crypto != NULL) &&
(session->current_crypto->out_cipher != NULL)) {
return session->current_crypto->out_cipher->name;
}
return NULL;
}
const char* ssh_get_hmac_in(ssh_session session) {
if ((session != NULL) &&
(session->current_crypto != NULL)) {
return ssh_hmac_type_to_string(session->current_crypto->in_hmac, session->current_crypto->in_hmac_etm);
}
return NULL;
}
const char* ssh_get_hmac_out(ssh_session session) {
if ((session != NULL) &&
(session->current_crypto != NULL)) {
return ssh_hmac_type_to_string(session->current_crypto->out_hmac, session->current_crypto->out_hmac_etm);
}
return NULL;
}
void ssh_silent_disconnect(ssh_session session) {
if (session == NULL) {
return;
}
ssh_socket_close(session->socket);
session->alive = 0;
ssh_disconnect(session);
}
void ssh_set_blocking(ssh_session session, int blocking)
{
if (session == NULL) {
return;
}
session->flags &= ~SSH_SESSION_FLAG_BLOCKING;
session->flags |= blocking ? SSH_SESSION_FLAG_BLOCKING : 0;
}
int ssh_is_blocking(ssh_session session)
{
return (session->flags & SSH_SESSION_FLAG_BLOCKING) ? 1 : 0;
}
static int ssh_flush_termination(void *c){
ssh_session session = c;
if (ssh_socket_buffered_write_bytes(session->socket) == 0 ||
session->session_state == SSH_SESSION_STATE_ERROR)
return 1;
else
return 0;
}
int ssh_blocking_flush(ssh_session session, int timeout){
int rc;
if (session == NULL) {
return SSH_ERROR;
}
rc = ssh_handle_packets_termination(session, timeout,
ssh_flush_termination, session);
if (rc == SSH_ERROR) {
return rc;
}
if (!ssh_flush_termination(session)) {
rc = SSH_AGAIN;
}
return rc;
}
int ssh_is_connected(ssh_session session) {
if (session == NULL) {
return 0;
}
return session->alive;
}
socket_t ssh_get_fd(ssh_session session) {
if (session == NULL) {
return -1;
}
return ssh_socket_get_fd(session->socket);
}
void ssh_set_fd_toread(ssh_session session) {
if (session == NULL) {
return;
}
ssh_socket_set_read_wontblock(session->socket);
}
void ssh_set_fd_towrite(ssh_session session) {
if (session == NULL) {
return;
}
ssh_socket_set_write_wontblock(session->socket);
}
void ssh_set_fd_except(ssh_session session) {
if (session == NULL) {
return;
}
ssh_socket_set_except(session->socket);
}
int ssh_handle_packets(ssh_session session, int timeout) {
ssh_poll_handle spoll;
ssh_poll_ctx ctx;
int tm = timeout;
int rc;
if (session == NULL || session->socket == NULL) {
return SSH_ERROR;
}
spoll = ssh_socket_get_poll_handle(session->socket);
ssh_poll_add_events(spoll, POLLIN);
ctx = ssh_poll_get_ctx(spoll);
if (!ctx) {
ctx = ssh_poll_get_default_ctx(session);
ssh_poll_ctx_add(ctx, spoll);
}
if (timeout == SSH_TIMEOUT_USER) {
if (ssh_is_blocking(session))
tm = ssh_make_milliseconds(session->opts.timeout,
session->opts.timeout_usec);
else
tm = 0;
}
rc = ssh_poll_ctx_dopoll(ctx, tm);
if (rc == SSH_ERROR) {
session->session_state = SSH_SESSION_STATE_ERROR;
}
return rc;
}
int ssh_handle_packets_termination(ssh_session session,
long timeout,
ssh_termination_function fct,
void *user)
{
struct ssh_timestamp ts;
long timeout_ms = SSH_TIMEOUT_INFINITE;
long tm;
int ret = SSH_OK;
if (timeout >= 0) {
timeout_ms = timeout;
} else {
if (ssh_is_blocking(session)) {
if (timeout == SSH_TIMEOUT_USER || timeout == SSH_TIMEOUT_DEFAULT) {
if (session->opts.timeout > 0 ||
session->opts.timeout_usec > 0) {
timeout_ms =
ssh_make_milliseconds(session->opts.timeout,
session->opts.timeout_usec);
}
}
} else {
timeout_ms = SSH_TIMEOUT_NONBLOCKING;
}
}
if (timeout_ms != SSH_TIMEOUT_NONBLOCKING) {
ssh_timestamp_init(&ts);
}
tm = timeout_ms;
while(!fct(user)) {
ret = ssh_handle_packets(session, tm);
if (ret == SSH_ERROR) {
break;
}
if (ssh_timeout_elapsed(&ts, timeout_ms)) {
ret = fct(user) ? SSH_OK : SSH_AGAIN;
break;
}
tm = ssh_timeout_update(&ts, timeout_ms);
}
return ret;
}
int ssh_get_status(ssh_session session) {
int socketstate;
int r = 0;
if (session == NULL) {
return 0;
}
socketstate = ssh_socket_get_status(session->socket);
if (session->session_state == SSH_SESSION_STATE_DISCONNECTED) {
r |= SSH_CLOSED;
}
if (socketstate & SSH_READ_PENDING) {
r |= SSH_READ_PENDING;
}
if (socketstate & SSH_WRITE_PENDING) {
r |= SSH_WRITE_PENDING;
}
if ((session->session_state == SSH_SESSION_STATE_DISCONNECTED &&
(socketstate & SSH_CLOSED_ERROR)) ||
session->session_state == SSH_SESSION_STATE_ERROR) {
r |= SSH_CLOSED_ERROR;
}
return r;
}
int ssh_get_poll_flags(ssh_session session)
{
if (session == NULL) {
return 0;
}
return ssh_socket_get_poll_flags (session->socket);
}
const char *ssh_get_disconnect_message(ssh_session session) {
if (session == NULL) {
return NULL;
}
if (session->session_state != SSH_SESSION_STATE_DISCONNECTED) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"Connection not closed yet");
} else if(!session->discon_msg) {
ssh_set_error(session, SSH_FATAL,
"Connection correctly closed but no disconnect message");
} else {
return session->discon_msg;
}
return NULL;
}
int ssh_get_version(ssh_session session) {
if (session == NULL) {
return -1;
}
return 2;
}
void ssh_socket_exception_callback(int code, int errno_code, void *user){
ssh_session session=(ssh_session)user;
SSH_LOG(SSH_LOG_RARE,"Socket exception callback: %d (%d)",code, errno_code);
session->session_state = SSH_SESSION_STATE_ERROR;
if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) {
ssh_set_error(session, SSH_FATAL, "Socket error: disconnected");
} else {
ssh_set_error(session, SSH_FATAL, "Socket error: %s", strerror(errno_code));
}
session->ssh_connection_callback(session);
}
int ssh_send_ignore (ssh_session session, const char *data) {
const int type = SSH2_MSG_IGNORE;
int rc;
if (ssh_socket_is_open(session->socket)) {
rc = ssh_buffer_pack(session->out_buffer,
"bs",
type,
data);
if (rc != SSH_OK){
ssh_set_error_oom(session);
goto error;
}
ssh_packet_send(session);
ssh_handle_packets(session, 0);
}
return SSH_OK;
error:
ssh_buffer_reinit(session->out_buffer);
return SSH_ERROR;
}
int ssh_send_debug (ssh_session session, const char *message, int always_display) {
int rc;
if (ssh_socket_is_open(session->socket)) {
rc = ssh_buffer_pack(session->out_buffer,
"bbsd",
SSH2_MSG_DEBUG,
always_display != 0 ? 1 : 0,
message,
0);
if (rc != SSH_OK) {
ssh_set_error_oom(session);
goto error;
}
ssh_packet_send(session);
ssh_handle_packets(session, 0);
}
return SSH_OK;
error:
ssh_buffer_reinit(session->out_buffer);
return SSH_ERROR;
}
void ssh_set_counters(ssh_session session, ssh_counter scounter,
ssh_counter rcounter) {
if (session != NULL) {
session->socket_counter = scounter;
session->raw_counter = rcounter;
}
}
int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash)
{
ssh_key pubkey = NULL;
ssh_string pubkey_blob = NULL;
MD5CTX ctx;
unsigned char *h;
int rc;
if (session == NULL || hash == NULL) {
return SSH_ERROR;
}
if (ssh_fips_mode()) {
ssh_set_error(session,
SSH_FATAL,
"In FIPS mode MD5 is not allowed."
"Try ssh_get_publickey_hash() with"
"SSH_PUBLICKEY_HASH_SHA256");
return SSH_ERROR;
}
*hash = NULL;
if (session->current_crypto == NULL ||
session->current_crypto->server_pubkey == NULL) {
ssh_set_error(session,SSH_FATAL,"No current cryptographic context");
return SSH_ERROR;
}
h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char));
if (h == NULL) {
return SSH_ERROR;
}
ctx = md5_init();
if (ctx == NULL) {
SAFE_FREE(h);
return SSH_ERROR;
}
rc = ssh_get_server_publickey(session, &pubkey);
if (rc != SSH_OK) {
md5_final(h, ctx);
SAFE_FREE(h);
return SSH_ERROR;
}
rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob);
ssh_key_free(pubkey);
if (rc != SSH_OK) {
md5_final(h, ctx);
SAFE_FREE(h);
return SSH_ERROR;
}
md5_update(ctx, ssh_string_data(pubkey_blob), ssh_string_len(pubkey_blob));
SSH_STRING_FREE(pubkey_blob);
md5_final(h, ctx);
*hash = h;
return MD5_DIGEST_LEN;
}
void ssh_clean_pubkey_hash(unsigned char **hash) {
SAFE_FREE(*hash);
}
int ssh_get_server_publickey(ssh_session session, ssh_key *key)
{
ssh_key pubkey = NULL;
if (session == NULL ||
session->current_crypto == NULL ||
session->current_crypto->server_pubkey == NULL) {
return SSH_ERROR;
}
pubkey = ssh_key_dup(session->current_crypto->server_pubkey);
if (pubkey == NULL) {
return SSH_ERROR;
}
*key = pubkey;
return SSH_OK;
}
int ssh_get_publickey(ssh_session session, ssh_key *key)
{
return ssh_get_server_publickey(session, key);
}
int ssh_get_publickey_hash(const ssh_key key,
enum ssh_publickey_hash_type type,
unsigned char **hash,
size_t *hlen)
{
ssh_string blob;
unsigned char *h;
int rc;
rc = ssh_pki_export_pubkey_blob(key, &blob);
if (rc < 0) {
return rc;
}
switch (type) {
case SSH_PUBLICKEY_HASH_SHA1:
{
SHACTX ctx;
h = calloc(1, SHA_DIGEST_LEN);
if (h == NULL) {
rc = -1;
goto out;
}
ctx = sha1_init();
if (ctx == NULL) {
free(h);
rc = -1;
goto out;
}
sha1_update(ctx, ssh_string_data(blob), ssh_string_len(blob));
sha1_final(h, ctx);
*hlen = SHA_DIGEST_LEN;
}
break;
case SSH_PUBLICKEY_HASH_SHA256:
{
SHA256CTX ctx;
h = calloc(1, SHA256_DIGEST_LEN);
if (h == NULL) {
rc = -1;
goto out;
}
ctx = sha256_init();
if (ctx == NULL) {
free(h);
rc = -1;
goto out;
}
sha256_update(ctx, ssh_string_data(blob), ssh_string_len(blob));
sha256_final(h, ctx);
*hlen = SHA256_DIGEST_LEN;
}
break;
case SSH_PUBLICKEY_HASH_MD5:
{
MD5CTX ctx;
if (ssh_fips_mode()) {
SSH_LOG(SSH_LOG_WARN, "In FIPS mode MD5 is not allowed."
"Try using SSH_PUBLICKEY_HASH_SHA256");
rc = SSH_ERROR;
goto out;
}
h = calloc(1, MD5_DIGEST_LEN);
if (h == NULL) {
rc = -1;
goto out;
}
ctx = md5_init();
if (ctx == NULL) {
free(h);
rc = -1;
goto out;
}
md5_update(ctx, ssh_string_data(blob), ssh_string_len(blob));
md5_final(h, ctx);
*hlen = MD5_DIGEST_LEN;
}
break;
default:
rc = -1;
goto out;
}
*hash = h;
rc = 0;
out:
SSH_STRING_FREE(blob);
return rc;
}