1
0
mirror of https://git.libssh.org/projects/libssh.git synced 2025-12-15 18:32:26 +03:00
Files
libssh/src/bind.c
Gauravsingh Sisodia a9d1cfa9e2 feat: Handle hostkeys like OpenSSH
fix: memory leak
fix: add defaults after parsing
fix: set defaults in ssh_bind_listen
tests: add test for checking default hostkey paths
remove: null check for hostkey paths, can't happen since we set defaults now
examples: ssh_server remove "no default keys", default hostkeys set in ssh_bind_listen

Signed-off-by: Gauravsingh Sisodia <xaerru@gmail.com>
Reviewed-by: Sahana Prasad <sahana@redhat.com>
2024-03-19 10:26:44 +01:00

602 lines
17 KiB
C

/*
* bind.c : all ssh_bind functions
*
* This file is part of the SSH Library
*
* Copyright (c) 2004-2005 by Aris Adamantiadis
*
* The SSH Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or (at your
* option) any later version.
*
* The SSH Library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the SSH Library; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
* MA 02111-1307, USA.
*/
#include "config.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "libssh/priv.h"
#include "libssh/bind.h"
#include "libssh/libssh.h"
#include "libssh/server.h"
#include "libssh/pki.h"
#include "libssh/buffer.h"
#include "libssh/socket.h"
#include "libssh/session.h"
#include "libssh/token.h"
/**
* @addtogroup libssh_server
*
* @{
*/
#ifdef _WIN32
#include <io.h>
#include <winsock2.h>
#include <ws2tcpip.h>
/*
* <wspiapi.h> is necessary for getaddrinfo before Windows XP, but it isn't
* available on some platforms like MinGW.
*/
#ifdef HAVE_WSPIAPI_H
# include <wspiapi.h>
#endif
#define SOCKOPT_TYPE_ARG4 char
#else /* _WIN32 */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define SOCKOPT_TYPE_ARG4 int
#endif /* _WIN32 */
static socket_t bind_socket(ssh_bind sshbind, const char *hostname,
int port) {
char port_c[6];
struct addrinfo *ai;
struct addrinfo hints;
int opt = 1;
socket_t s;
int rc;
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
ZERO_STRUCT(hints);
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
snprintf(port_c, 6, "%d", port);
rc = getaddrinfo(hostname, port_c, &hints, &ai);
if (rc != 0) {
ssh_set_error(sshbind,
SSH_FATAL,
"Resolving %s: %s", hostname, gai_strerror(rc));
return -1;
}
s = socket (ai->ai_family,
ai->ai_socktype,
ai->ai_protocol);
if (s == SSH_INVALID_SOCKET) {
ssh_set_error(sshbind, SSH_FATAL, "%s",
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
freeaddrinfo (ai);
return -1;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
(char *)&opt, sizeof(opt)) < 0) {
ssh_set_error(sshbind,
SSH_FATAL,
"Setting socket options failed: %s",
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
freeaddrinfo (ai);
CLOSE_SOCKET(s);
return -1;
}
if (bind(s, ai->ai_addr, ai->ai_addrlen) != 0) {
ssh_set_error(sshbind,
SSH_FATAL,
"Binding to %s:%d: %s",
hostname,
port,
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
freeaddrinfo (ai);
CLOSE_SOCKET(s);
return -1;
}
freeaddrinfo (ai);
return s;
}
ssh_bind ssh_bind_new(void) {
ssh_bind ptr;
ptr = calloc(1, sizeof(struct ssh_bind_struct));
if (ptr == NULL) {
return NULL;
}
ptr->bindfd = SSH_INVALID_SOCKET;
ptr->bindport = 22;
ptr->common.log_verbosity = 0;
return ptr;
}
static int ssh_bind_import_keys(ssh_bind sshbind) {
int rc;
#ifdef HAVE_ECC
if (sshbind->ecdsa == NULL && sshbind->ecdsakey != NULL) {
rc = ssh_pki_import_privkey_file(sshbind->ecdsakey,
NULL,
NULL,
NULL,
&sshbind->ecdsa);
if (rc == SSH_ERROR || rc == SSH_EOF) {
ssh_set_error(sshbind, SSH_FATAL,
"Failed to import private ECDSA host key");
return SSH_ERROR;
}
if (!is_ecdsa_key_type(ssh_key_type(sshbind->ecdsa))) {
ssh_set_error(sshbind, SSH_FATAL,
"The ECDSA host key has the wrong type");
ssh_key_free(sshbind->ecdsa);
sshbind->ecdsa = NULL;
return SSH_ERROR;
}
}
#endif
if (sshbind->rsa == NULL && sshbind->rsakey != NULL) {
rc = ssh_pki_import_privkey_file(sshbind->rsakey,
NULL,
NULL,
NULL,
&sshbind->rsa);
if (rc == SSH_ERROR || rc == SSH_EOF) {
ssh_set_error(sshbind, SSH_FATAL,
"Failed to import private RSA host key");
return SSH_ERROR;
}
if (ssh_key_type(sshbind->rsa) != SSH_KEYTYPE_RSA) {
ssh_set_error(sshbind, SSH_FATAL,
"The RSA host key has the wrong type");
ssh_key_free(sshbind->rsa);
sshbind->rsa = NULL;
return SSH_ERROR;
}
}
if (sshbind->ed25519 == NULL && sshbind->ed25519key != NULL) {
rc = ssh_pki_import_privkey_file(sshbind->ed25519key,
NULL,
NULL,
NULL,
&sshbind->ed25519);
if (rc == SSH_ERROR || rc == SSH_EOF) {
ssh_set_error(sshbind, SSH_FATAL,
"Failed to import private ED25519 host key");
return SSH_ERROR;
}
if (ssh_key_type(sshbind->ed25519) != SSH_KEYTYPE_ED25519) {
ssh_set_error(sshbind, SSH_FATAL,
"The ED25519 host key has the wrong type");
ssh_key_free(sshbind->ed25519);
sshbind->ed25519 = NULL;
return SSH_ERROR;
}
}
return SSH_OK;
}
int ssh_bind_listen(ssh_bind sshbind) {
const char *host;
socket_t fd;
int rc;
/* Apply global bind configurations, if it hasn't been applied before */
rc = ssh_bind_options_parse_config(sshbind, NULL);
if (rc != 0) {
ssh_set_error(sshbind, SSH_FATAL,"Could not parse global config");
return SSH_ERROR;
}
/* Set default hostkey paths if no hostkey was found before */
if (sshbind->ecdsakey == NULL &&
sshbind->rsakey == NULL &&
sshbind->ed25519key == NULL) {
sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key");
sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key");
sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key");
}
/* Apply global bind configurations, if it hasn't been applied before */
rc = ssh_bind_options_parse_config(sshbind, NULL);
if (rc != 0) {
ssh_set_error(sshbind, SSH_FATAL, "Could not parse global config");
return SSH_ERROR;
}
/* Set default hostkey paths if no hostkey was found before */
if (sshbind->ecdsakey == NULL &&
sshbind->rsakey == NULL &&
sshbind->ed25519key == NULL) {
sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key");
sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key");
sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key");
}
if (sshbind->rsa == NULL &&
sshbind->ecdsa == NULL &&
sshbind->ed25519 == NULL) {
rc = ssh_bind_import_keys(sshbind);
if (rc != SSH_OK) {
return SSH_ERROR;
}
}
if (sshbind->bindfd == SSH_INVALID_SOCKET) {
host = sshbind->bindaddr;
if (host == NULL) {
host = "0.0.0.0";
}
fd = bind_socket(sshbind, host, sshbind->bindport);
if (fd == SSH_INVALID_SOCKET) {
return SSH_ERROR;
}
if (listen(fd, 10) < 0) {
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
ssh_set_error(sshbind,
SSH_FATAL,
"Listening to socket %d: %s",
fd,
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
CLOSE_SOCKET(fd);
return SSH_ERROR;
}
sshbind->bindfd = fd;
} else {
SSH_LOG(SSH_LOG_DEBUG, "Using app-provided bind socket");
}
return 0;
}
int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, void *userdata)
{
if (sshbind == NULL) {
return SSH_ERROR;
}
if (callbacks == NULL) {
ssh_set_error_invalid(sshbind);
return SSH_ERROR;
}
if (callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)) {
ssh_set_error(sshbind,
SSH_FATAL,
"Invalid callback passed in (badly initialized)");
return SSH_ERROR;
}
sshbind->bind_callbacks = callbacks;
sshbind->bind_callbacks_userdata = userdata;
return 0;
}
/** @internal
* @brief callback being called by poll when an event happens
*
*/
static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int revents, void *user)
{
ssh_bind sshbind = (ssh_bind)user;
(void)sshpoll;
(void)fd;
if (revents & POLLIN) {
/* new incoming connection */
if (ssh_callbacks_exists(sshbind->bind_callbacks, incoming_connection)) {
sshbind->bind_callbacks->incoming_connection(sshbind,
sshbind->bind_callbacks_userdata);
}
}
return 0;
}
/** @internal
* @brief returns the current poll handle, or creates it
* @param sshbind the ssh_bind object
* @returns a ssh_poll handle suitable for operation
*/
ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind)
{
short events = POLLIN;
if (sshbind->poll) {
return sshbind->poll;
}
#ifdef POLLRDHUP
events |= POLLRDHUP;
#endif /* POLLRDHUP */
sshbind->poll = ssh_poll_new(sshbind->bindfd,
events,
ssh_bind_poll_callback,
sshbind);
return sshbind->poll;
}
void ssh_bind_set_blocking(ssh_bind sshbind, int blocking)
{
sshbind->blocking = blocking ? 1 : 0;
}
socket_t ssh_bind_get_fd(ssh_bind sshbind)
{
return sshbind->bindfd;
}
void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd)
{
sshbind->bindfd = fd;
}
void ssh_bind_fd_toaccept(ssh_bind sshbind)
{
sshbind->toaccept = 1;
}
void ssh_bind_free(ssh_bind sshbind){
int i;
if (sshbind == NULL) {
return;
}
if (sshbind->bindfd >= 0) {
CLOSE_SOCKET(sshbind->bindfd);
}
sshbind->bindfd = SSH_INVALID_SOCKET;
/* options */
SAFE_FREE(sshbind->banner);
SAFE_FREE(sshbind->moduli_file);
SAFE_FREE(sshbind->bindaddr);
SAFE_FREE(sshbind->config_dir);
SAFE_FREE(sshbind->pubkey_accepted_key_types);
SAFE_FREE(sshbind->rsakey);
SAFE_FREE(sshbind->ecdsakey);
SAFE_FREE(sshbind->ed25519key);
ssh_key_free(sshbind->rsa);
sshbind->rsa = NULL;
ssh_key_free(sshbind->ecdsa);
sshbind->ecdsa = NULL;
ssh_key_free(sshbind->ed25519);
sshbind->ed25519 = NULL;
for (i = 0; i < SSH_KEX_METHODS; i++) {
if (sshbind->wanted_methods[i]) {
SAFE_FREE(sshbind->wanted_methods[i]);
}
}
SAFE_FREE(sshbind);
}
int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd)
{
ssh_poll_handle handle = NULL;
int i, rc;
if (sshbind == NULL) {
return SSH_ERROR;
}
if (session == NULL){
ssh_set_error(sshbind, SSH_FATAL,"session is null");
return SSH_ERROR;
}
session->server = 1;
/* Copy options from bind to session */
for (i = 0; i < SSH_KEX_METHODS; i++) {
if (sshbind->wanted_methods[i]) {
session->opts.wanted_methods[i] = strdup(sshbind->wanted_methods[i]);
if (session->opts.wanted_methods[i] == NULL) {
return SSH_ERROR;
}
}
}
if (sshbind->bindaddr == NULL)
session->opts.bindaddr = NULL;
else {
SAFE_FREE(session->opts.bindaddr);
session->opts.bindaddr = strdup(sshbind->bindaddr);
if (session->opts.bindaddr == NULL) {
return SSH_ERROR;
}
}
if (sshbind->pubkey_accepted_key_types != NULL) {
if (session->opts.pubkey_accepted_types == NULL) {
session->opts.pubkey_accepted_types = strdup(sshbind->pubkey_accepted_key_types);
if (session->opts.pubkey_accepted_types == NULL) {
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
} else {
char *p;
/* If something was set to the session prior to calling this
* function, keep only what is allowed by the options set in
* sshbind */
p = ssh_find_all_matching(sshbind->pubkey_accepted_key_types,
session->opts.pubkey_accepted_types);
if (p == NULL) {
return SSH_ERROR;
}
SAFE_FREE(session->opts.pubkey_accepted_types);
session->opts.pubkey_accepted_types = p;
}
}
session->common.log_verbosity = sshbind->common.log_verbosity;
if (sshbind->banner != NULL) {
session->opts.custombanner = strdup(sshbind->banner);
if (session->opts.custombanner == NULL) {
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
}
if (sshbind->moduli_file != NULL) {
session->opts.moduli_file = strdup(sshbind->moduli_file);
if (session->opts.moduli_file == NULL) {
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
}
session->opts.rsa_min_size = sshbind->rsa_min_size;
ssh_socket_free(session->socket);
session->socket = ssh_socket_new(session);
if (session->socket == NULL) {
/* perhaps it may be better to copy the error from session to sshbind */
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
ssh_socket_set_fd(session->socket, fd);
handle = ssh_socket_get_poll_handle(session->socket);
if (handle == NULL) {
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
ssh_socket_set_connected(session->socket, handle);
/* We must try to import any keys that could be imported in case
* we are not using ssh_bind_listen (which is the other place
* where keys can be imported) on this ssh_bind and are instead
* only using ssh_bind_accept_fd to manage sockets ourselves.
*/
if (sshbind->rsa == NULL &&
sshbind->ecdsa == NULL &&
sshbind->ed25519 == NULL) {
rc = ssh_bind_import_keys(sshbind);
if (rc != SSH_OK) {
return SSH_ERROR;
}
}
#ifdef HAVE_ECC
if (sshbind->ecdsa) {
session->srv.ecdsa_key = ssh_key_dup(sshbind->ecdsa);
if (session->srv.ecdsa_key == NULL) {
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
}
#endif
if (sshbind->rsa) {
session->srv.rsa_key = ssh_key_dup(sshbind->rsa);
if (session->srv.rsa_key == NULL) {
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
}
if (sshbind->ed25519 != NULL) {
session->srv.ed25519_key = ssh_key_dup(sshbind->ed25519);
if (session->srv.ed25519_key == NULL){
ssh_set_error_oom(sshbind);
return SSH_ERROR;
}
}
/* force PRNG to change state in case we fork after ssh_bind_accept */
ssh_reseed();
return SSH_OK;
}
int ssh_bind_accept(ssh_bind sshbind, ssh_session session)
{
socket_t fd = SSH_INVALID_SOCKET;
int rc;
if (sshbind->bindfd == SSH_INVALID_SOCKET) {
ssh_set_error(sshbind, SSH_FATAL,
"Can't accept new clients on a not bound socket.");
return SSH_ERROR;
}
if (session == NULL) {
ssh_set_error(sshbind, SSH_FATAL, "session is null");
return SSH_ERROR;
}
fd = accept(sshbind->bindfd, NULL, NULL);
if (fd == SSH_INVALID_SOCKET) {
char err_msg[SSH_ERRNO_MSG_MAX] = {0};
if (errno == EINTR) {
ssh_set_error(sshbind, SSH_EINTR,
"Accepting a new connection (child signal error): %s",
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
} else {
ssh_set_error(sshbind, SSH_FATAL,
"Accepting a new connection: %s",
ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX));
}
return SSH_ERROR;
}
rc = ssh_bind_accept_fd(sshbind, session, fd);
if (rc == SSH_ERROR) {
CLOSE_SOCKET(fd);
ssh_socket_free(session->socket);
}
return rc;
}
/**
* @}
*/