nuttx-apps/netutils/dropbear/dropbear_main.c
Felipe Moura fee2ddbf54 netutils/dropbear: add Dropbear SSH server port for NuttX
Integrated SSH daemon authenticating against FSUTILS_PASSWD, with an
ECDSA P-256 host key and an NSH session over a PTY per connection. Built
from the upstream Dropbear tarball (pinned commit) and patched for
NuttX, using Dropbear's bundled libtomcrypt for all crypto. setsid()
(apache/nuttx#19184) and link() now come from NuttX, not local stubs.

Signed-off-by: Felipe Moura <moura.fmo@gmail.com>
2026-06-27 08:42:00 -03:00

329 lines
8.4 KiB
C

/****************************************************************************
* apps/netutils/dropbear/dropbear_main.c
*
* SPDX-License-Identifier: Apache-2.0
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/socket.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "includes.h"
#include "algo.h"
#include "crypto_desc.h"
#define dropbear_main dropbear_multi_entry
#include "dbutil.h"
#undef dropbear_main
#include "dbrandom.h"
#include "netio.h"
#include "runopts.h"
#include "session.h"
#include "signkey.h"
#include "gensignkey.h"
#include "ssh.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define DROPBEAR_PORT_STRING_HELPER(n) #n
#define DROPBEAR_PORT_STRING(n) DROPBEAR_PORT_STRING_HELPER(n)
/****************************************************************************
* Private Types
****************************************************************************/
typedef void (*dropbear_exit_handler_t)(int exitcode, FAR const char *format,
va_list param) ATTRIB_NORETURN;
/****************************************************************************
* Private Data
****************************************************************************/
static jmp_buf g_session_exit_jmp;
static int g_session_exitcode;
/****************************************************************************
* Private Functions
****************************************************************************/
static void dropbear_session_exit(int exitcode, FAR const char *format,
va_list param) noreturn_function;
static void dropbear_session_exit(int exitcode, FAR const char *format,
va_list param)
{
char exitmsg[150];
char fullmsg[300];
char fromaddr[60];
int signal_pipe[2];
vsnprintf(exitmsg, sizeof(exitmsg), format, param);
fromaddr[0] = '\0';
if (svr_ses.addrstring != NULL)
{
snprintf(fromaddr, sizeof(fromaddr), " from <%s>", svr_ses.addrstring);
}
if (!ses.init_done)
{
snprintf(fullmsg, sizeof(fullmsg), "Early exit%s: %s", fromaddr,
exitmsg);
}
else if (ses.authstate.authdone)
{
snprintf(fullmsg, sizeof(fullmsg), "Exit (%s)%s: %s",
ses.authstate.pw_name, fromaddr, exitmsg);
}
else if (ses.authstate.pw_name != NULL)
{
snprintf(fullmsg, sizeof(fullmsg),
"Exit before auth%s: (user '%s', %u fails): %s",
fromaddr, ses.authstate.pw_name, ses.authstate.failcount,
exitmsg);
}
else
{
snprintf(fullmsg, sizeof(fullmsg), "Exit before auth%s: %s", fromaddr,
exitmsg);
}
dropbear_log(LOG_INFO, "%s", fullmsg);
signal_pipe[0] = ses.signal_pipe[0];
signal_pipe[1] = ses.signal_pipe[1];
session_cleanup();
if (signal_pipe[0] > STDERR_FILENO)
{
m_close(signal_pipe[0]);
}
if (signal_pipe[1] > STDERR_FILENO && signal_pipe[1] != signal_pipe[0])
{
m_close(signal_pipe[1]);
}
memset(&ses, 0, sizeof(ses));
memset(&svr_ses, 0, sizeof(svr_ses));
g_session_exitcode = exitcode;
longjmp(g_session_exit_jmp, 1);
while (1)
{
}
}
static void dropbear_setup(FAR const char *port)
{
/* Load the host key with -r so it lives in the persistent (epoch 0)
* allocation rather than being created lazily inside a per-session malloc
* epoch (-R), which would free it when the first session ends and leave a
* dangling svr_opts.hostkey for the next connection.
*/
FAR char *argv[] =
{
"dropbear",
"-F",
"-p",
(FAR char *)port,
"-r",
CONFIG_NETUTILS_DROPBEAR_HOSTKEY_PATH,
NULL
};
_dropbear_exit = svr_dropbear_exit;
_dropbear_log = svr_dropbear_log;
disallow_core();
svr_getopts(6, argv);
seedrandom();
crypto_init();
#ifdef CONFIG_NETUTILS_DROPBEAR_GENERATE_HOSTKEY
/* Generate the ECDSA host key now if it is missing. signkey_generate()
* with skip_exist=1 silently succeeds when the file already exists.
*/
if (signkey_generate(DROPBEAR_SIGNKEY_ECDSA_NISTP256, 0,
CONFIG_NETUTILS_DROPBEAR_HOSTKEY_PATH, 1)
== DROPBEAR_FAILURE)
{
dropbear_exit("failed to generate host key");
}
#endif
load_all_hostkeys();
if (dropbear_auth_initialize() < 0)
{
dropbear_exit("failed to initialize password auth");
}
}
static void dropbear_run_session(int childsock)
{
dropbear_exit_handler_t saved_exit;
saved_exit = _dropbear_exit;
_dropbear_exit = dropbear_session_exit;
m_malloc_set_epoch(1);
if (setjmp(g_session_exit_jmp) == 0)
{
svr_session(childsock, -1);
}
m_malloc_free_epoch(1, 1);
m_malloc_set_epoch(0);
_dropbear_exit = saved_exit;
if (g_session_exitcode != EXIT_SUCCESS)
{
dropbear_log(LOG_WARNING, "session exited with status %d",
g_session_exitcode);
}
}
static size_t dropbear_listen_sockets(FAR int *socks, size_t sockcount,
FAR int *maxfd)
{
FAR char *errstring = NULL;
size_t sockpos = 0;
unsigned int i;
for (i = 0; i < svr_opts.portcount; i++)
{
int nsock;
unsigned int n;
nsock = dropbear_listen(svr_opts.addresses[i], svr_opts.ports[i],
&socks[sockpos], sockcount - sockpos,
&errstring, maxfd, svr_opts.interface);
if (nsock < 0)
{
dropbear_log(LOG_WARNING, "failed listening on '%s': %s",
svr_opts.ports[i],
errstring != NULL ? errstring : "unknown error");
m_free(errstring);
errstring = NULL;
continue;
}
for (n = 0; n < (unsigned int)nsock; n++)
{
set_sock_priority(socks[sockpos + n], DROPBEAR_PRIO_LOWDELAY);
}
sockpos += (size_t)nsock;
}
return sockpos;
}
static size_t dropbear_wait_listen_sockets(FAR int *socks, size_t sockcount,
FAR int *maxfd)
{
int retries = 0;
int retry_delay = 1;
while (1)
{
size_t count;
*maxfd = -1;
count = dropbear_listen_sockets(socks, sockcount, maxfd);
if (count > 0)
{
return count;
}
#if CONFIG_NETUTILS_DROPBEAR_LISTEN_RETRIES > 0
if (retries++ >= CONFIG_NETUTILS_DROPBEAR_LISTEN_RETRIES)
{
dropbear_exit("no listening ports available");
}
#else
retries++;
#endif
dropbear_log(LOG_WARNING, "Retry %d in %d seconds...", retries,
retry_delay);
sleep(retry_delay);
retry_delay *= 2;
if (retry_delay > CONFIG_NETUTILS_DROPBEAR_LISTEN_RETRY_MAX)
{
retry_delay = CONFIG_NETUTILS_DROPBEAR_LISTEN_RETRY_MAX;
}
}
}
/****************************************************************************
* Public Functions
****************************************************************************/
int main(int argc, FAR char *argv[])
{
FAR const char *port = DROPBEAR_PORT_STRING(CONFIG_NETUTILS_DROPBEAR_PORT);
int listensocks[MAX_LISTEN_ADDR];
int maxfd = -1;
if (argc > 1)
{
port = argv[1];
}
dropbear_setup(port);
dropbear_wait_listen_sockets(listensocks, MAX_LISTEN_ADDR, &maxfd);
printf("dropbear: listening on port %s\n", port);
while (1)
{
struct sockaddr_storage remoteaddr;
socklen_t remoteaddrlen = sizeof(remoteaddr);
FAR char *remote_host = NULL;
FAR char *remote_port = NULL;
int childsock;
childsock = accept(listensocks[0], (FAR struct sockaddr *)&remoteaddr,
&remoteaddrlen);
if (childsock < 0)
{
continue;
}
getaddrstring(&remoteaddr, &remote_host, &remote_port, 0);
dropbear_log(LOG_INFO, "connection from %s:%s",
remote_host != NULL ? remote_host : "?",
remote_port != NULL ? remote_port : "?");
m_free(remote_host);
m_free(remote_port);
seedrandom();
dropbear_run_session(childsock);
close(childsock);
}
return EXIT_SUCCESS;
}