This is an automated email from the ASF dual-hosted git repository.
acassis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
The following commit(s) were added to refs/heads/master by this push:
new fee2ddbf5 netutils/dropbear: add Dropbear SSH server port for NuttX
fee2ddbf5 is described below
commit fee2ddbf540ddd3639a30a2ed488b34fa10bf862
Author: Felipe Moura <[email protected]>
AuthorDate: Tue Jun 23 19:40:49 2026 -0300
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 <[email protected]>
---
fsutils/passwd/passwd_append.c | 2 +-
netutils/dropbear/.gitignore | 6 +
netutils/dropbear/CMakeLists.txt | 185 ++++++
netutils/dropbear/Kconfig | 120 ++++
netutils/dropbear/Make.defs | 25 +
netutils/dropbear/Makefile | 157 +++++
netutils/dropbear/dropbear_main.c | 329 +++++++++++
netutils/dropbear/dropbear_nshsession.c | 633 +++++++++++++++++++++
.../patch/0001-guard-platform-declarations.patch | 26 +
.../patch/0002-use-nuttx-passwd-auth.patch | 87 +++
...-localoptions-to-override-tracking-malloc.patch | 24 +
.../patch/0004-use-nuttx-unused-macro.patch | 202 +++++++
netutils/dropbear/port/config.h | 20 +
netutils/dropbear/port/dropbear_utils.c | 149 +++++
netutils/dropbear/port/dropbear_utils.h | 31 +
netutils/dropbear/port/localoptions.h | 81 +++
netutils/dropbear/port/nuttx_auth.c | 109 ++++
netutils/dropbear/port/nuttx_config.h | 142 +++++
nshlib/CMakeLists.txt | 4 +
nshlib/Kconfig | 22 +
nshlib/Makefile | 4 +
nshlib/nsh.h | 4 +
.../passwd_append.c => nshlib/nsh_dropbear.c | 79 +--
nshlib/nsh_init.c | 12 +
24 files changed, 2399 insertions(+), 54 deletions(-)
diff --git a/fsutils/passwd/passwd_append.c b/fsutils/passwd/passwd_append.c
index be77c9aed..125a4cfe6 100644
--- a/fsutils/passwd/passwd_append.c
+++ b/fsutils/passwd/passwd_append.c
@@ -69,7 +69,7 @@ int passwd_append(FAR const char *username, FAR const char
*password)
{
int errcode = errno;
DEBUGASSERT(errcode > 0);
- return errcode;
+ return -errcode;
}
/* The format of the password file is:
diff --git a/netutils/dropbear/.gitignore b/netutils/dropbear/.gitignore
new file mode 100644
index 000000000..1dc4834a1
--- /dev/null
+++ b/netutils/dropbear/.gitignore
@@ -0,0 +1,6 @@
+/dropbear
+/*.zip
+*.o
+.built
+.depend
+Make.dep
diff --git a/netutils/dropbear/CMakeLists.txt b/netutils/dropbear/CMakeLists.txt
new file mode 100644
index 000000000..c3187a32d
--- /dev/null
+++ b/netutils/dropbear/CMakeLists.txt
@@ -0,0 +1,185 @@
+#
##############################################################################
+# apps/netutils/dropbear/CMakeLists.txt
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
contributor
+# license agreements. See the NOTICE file distributed with this work for
+# additional information regarding copyright ownership. The ASF licenses this
+# file to you under the Apache License, Version 2.0 (the "License"); you may
not
+# use this file except in compliance with the License. You may obtain a copy
of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+#
+#
##############################################################################
+
+if(CONFIG_NETUTILS_DROPBEAR)
+
+ set(DROPBEAR_COMMIT "${CONFIG_NETUTILS_DROPBEAR_COMMIT}")
+ string(REPLACE "\"" "" DROPBEAR_COMMIT "${DROPBEAR_COMMIT}")
+
+ set(DROPBEAR_ZIP "${DROPBEAR_COMMIT}.zip")
+ set(DROPBEAR_URL "https://github.com/mkj/dropbear/archive")
+ set(DROPBEAR_UNPACKNAME "dropbear")
+
+ if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}")
+ if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_ZIP}")
+ message(STATUS "Downloading Dropbear: ${DROPBEAR_URL}/${DROPBEAR_ZIP}")
+ file(DOWNLOAD "${DROPBEAR_URL}/${DROPBEAR_ZIP}"
+ "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_ZIP}")
+ endif()
+ message(STATUS "Unpacking Dropbear: ${DROPBEAR_ZIP}")
+ execute_process(
+ COMMAND unzip -q -o "${DROPBEAR_ZIP}"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+ RESULT_VARIABLE result)
+ if(result EQUAL 0)
+ file(RENAME "${CMAKE_CURRENT_SOURCE_DIR}/dropbear-${DROPBEAR_COMMIT}"
+ "${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}")
+ execute_process(
+ COMMAND
+ patch -s -N -l -p1 -d "${DROPBEAR_UNPACKNAME}" -i
+
"${CMAKE_CURRENT_SOURCE_DIR}/patch/0001-guard-platform-declarations.patch"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+ execute_process(
+ COMMAND
+ patch -s -N -l -p1 -d "${DROPBEAR_UNPACKNAME}" -i
+ "${CMAKE_CURRENT_SOURCE_DIR}/patch/0002-use-nuttx-passwd-auth.patch"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+ execute_process(
+ COMMAND
+ patch -s -N -l -p1 -d "${DROPBEAR_UNPACKNAME}" -i
+
"${CMAKE_CURRENT_SOURCE_DIR}/patch/0003-allow-localoptions-to-override-tracking-malloc.patch"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+ execute_process(
+ COMMAND
+ patch -s -N -l -p1 -d "${DROPBEAR_UNPACKNAME}" -i
+ "${CMAKE_CURRENT_SOURCE_DIR}/patch/0004-use-nuttx-unused-macro.patch"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+ message(STATUS "Generating default_options_guard.h")
+ execute_process(
+ COMMAND
+ sh
+
"${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}/src/ifndef_wrapper.sh"
+ INPUT_FILE
+
"${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}/src/default_options.h"
+ OUTPUT_FILE
+
"${CMAKE_CURRENT_SOURCE_DIR}/${DROPBEAR_UNPACKNAME}/src/default_options_guard.h"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
+ endif()
+ endif()
+
+ set(PROGNAME "${CONFIG_NETUTILS_DROPBEAR_PROGNAME}")
+ string(REPLACE "\"" "" PROGNAME "${PROGNAME}")
+
+ set(DROPBEAR_SRCS
+ dropbear_nshsession.c
+ port/nuttx_auth.c
+ port/dropbear_utils.c
+ dropbear/src/dbutil.c
+ dropbear/src/buffer.c
+ dropbear/src/dbhelpers.c
+ dropbear/src/bignum.c
+ dropbear/src/signkey.c
+ dropbear/src/dbrandom.c
+ dropbear/src/queue.c
+ dropbear/src/atomicio.c
+ dropbear/src/compat.c
+ dropbear/src/fake-rfc2553.c
+ dropbear/src/curve25519.c
+ dropbear/src/chachapoly.c
+ dropbear/src/ltc_prng.c
+ dropbear/src/ecc.c
+ dropbear/src/ecdsa.c
+ dropbear/src/crypto_desc.c
+ dropbear/src/dbmalloc.c
+ dropbear/src/gensignkey.c
+ dropbear/src/common-session.c
+ dropbear/src/packet.c
+ dropbear/src/common-algo.c
+ dropbear/src/common-kex.c
+ dropbear/src/common-channel.c
+ dropbear/src/common-chansession.c
+ dropbear/src/termcodes.c
+ dropbear/src/tcp-accept.c
+ dropbear/src/listener.c
+ dropbear/src/process-packet.c
+ dropbear/src/common-runopts.c
+ dropbear/src/circbuffer.c
+ dropbear/src/list.c
+ dropbear/src/netio.c
+ dropbear/src/gcm.c
+ dropbear/src/kex-x25519.c
+ dropbear/src/svr-kex.c
+ dropbear/src/svr-auth.c
+ dropbear/src/svr-authpasswd.c
+ dropbear/src/svr-session.c
+ dropbear/src/svr-service.c
+ dropbear/src/svr-runopts.c
+ dropbear/src/svr-tcpfwd.c
+ dropbear/src/svr-forward.c
+ dropbear/src/svr-streamfwd.c
+ dropbear/src/svr-authpam.c)
+
+ file(GLOB LIBTOMMATH_SRCS CONFIGURE_DEPENDS
+ "${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtommath/*.c")
+ list(APPEND DROPBEAR_SRCS ${LIBTOMMATH_SRCS})
+
+ file(GLOB_RECURSE LIBTOMCRYPT_SRCS CONFIGURE_DEPENDS
+ "${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtomcrypt/src/*.c")
+ list(FILTER LIBTOMCRYPT_SRCS EXCLUDE REGEX ".*/prngs/sober128tab\\.c$")
+ list(APPEND DROPBEAR_SRCS ${LIBTOMCRYPT_SRCS})
+
+ nuttx_add_application(
+ NAME
+ ${PROGNAME}
+ SRCS
+ ${DROPBEAR_SRCS}
+ dropbear_main.c
+ STACKSIZE
+ ${CONFIG_NETUTILS_DROPBEAR_STACKSIZE}
+ PRIORITY
+ ${CONFIG_NETUTILS_DROPBEAR_PRIORITY}
+ DEPENDS
+ ${DROPBEAR_UNPACKNAME})
+
+ target_include_directories(
+ ${PROGNAME}
+ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/port
+ ${CMAKE_CURRENT_SOURCE_DIR}/dropbear
+ ${CMAKE_CURRENT_SOURCE_DIR}/dropbear/src
+ ${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtomcrypt/src/headers
+ ${CMAKE_CURRENT_SOURCE_DIR}/dropbear/libtommath
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../nshlib)
+
+ if(CONFIG_NETUTILS_DROPBEAR_COMPRESSION)
+ target_include_directories(
+ ${PROGNAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../system/zlib/zlib)
+ endif()
+
+ target_compile_definitions(
+ ${PROGNAME} PRIVATE LOCALOPTIONS_H_EXISTS=1 DROPBEAR_NUTTX=1
+ DROPBEAR_NUTTX_PASSWD=1)
+
+ set_source_files_properties(
+ dropbear_nshsession.c
+ PROPERTIES COMPILE_DEFINITIONS
+ "Channel=dropbear_channel;ChanType=dropbear_chantype")
+
+ # LTC_SOURCE must be set only for libtomcrypt sources.
+ set_source_files_properties(${LIBTOMCRYPT_SRCS} PROPERTIES
COMPILE_DEFINITIONS
+ LTC_SOURCE=1)
+
+ target_compile_options(${PROGNAME} PRIVATE -Wno-pointer-sign -Wno-format)
+
+ target_sources(apps PRIVATE ${DROPBEAR_SRCS})
+
+endif()
diff --git a/netutils/dropbear/Kconfig b/netutils/dropbear/Kconfig
new file mode 100644
index 000000000..507c85068
--- /dev/null
+++ b/netutils/dropbear/Kconfig
@@ -0,0 +1,120 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+menuconfig NETUTILS_DROPBEAR
+ tristate "Dropbear SSH server"
+ default n
+ depends on NET && NET_TCP
+ depends on !DISABLE_PSEUDOFS_OPERATIONS
+ depends on !DISABLE_PTHREAD
+ depends on SCHED_WAITPID
+ depends on NSH_LIBRARY
+ depends on FSUTILS_PASSWD
+ depends on PSEUDOTERM
+ depends on SERIAL
+ depends on ARCH_HAVE_RNG
+ depends on DEV_RANDOM
+ depends on DEV_URANDOM
+ depends on LIBC_NETDB
+ depends on LIBC_GAISTRERROR
+ select CRYPTO
+ select CRYPTO_RANDOM_POOL
+ ---help---
+ Enable a minimal Dropbear SSH server port for NuttX. This
initial
+ port is based on the ESP-IDF MCU test port and provides a single
+ foreground SSH server process with SSH sessions backed by NSH.
+
+if NETUTILS_DROPBEAR
+
+config NETUTILS_DROPBEAR_STACKSIZE
+ int "Dropbear main stack size"
+ default 65536
+ ---help---
+ Stack size for the Dropbear server built-in.
+ This is architecture-specific, so adjust it according to your
setup.
+
+config NETUTILS_DROPBEAR_PRIORITY
+ int "Dropbear main priority"
+ default 100
+
+config NETUTILS_DROPBEAR_SHELL_PRIORITY
+ int "Dropbear NSH session priority"
+ default 100
+
+config NETUTILS_DROPBEAR_PROGNAME
+ string "Dropbear program name"
+ default "dropbear"
+ ---help---
+ This is the name of the program that will be used when the NSH
ELF
+ program is installed.
+
+config NETUTILS_DROPBEAR_LISTEN_RETRIES
+ int "Dropbear listen retries"
+ default 0
+ ---help---
+ Number of times to retry listen setup when no listen socket
could
+ be opened. Zero means to retry forever.
+
+config NETUTILS_DROPBEAR_LISTEN_RETRY_MAX
+ int "Dropbear maximum listen retry interval"
+ default 120
+ range 1 3600
+ ---help---
+ Maximum number of seconds to wait between listen setup retries.
+ The retry delay starts at one second and doubles until it
reaches
+ this value.
+
+config NETUTILS_DROPBEAR_SHELL_STACKSIZE
+ int "Dropbear NSH session task stack size"
+ default 8192
+
+config NETUTILS_DROPBEAR_PORT
+ int "Dropbear listen port"
+ default 2222
+
+config NETUTILS_DROPBEAR_HOSTKEY_PATH
+ string "Dropbear ECDSA P-256 host key path"
+ default "/etc/dropbear/dropbear_ecdsa_host_key"
+ ---help---
+ Path to the persistent ECDSA P-256 host key used by the Dropbear
+ server. The file is stored in Dropbear's native host key
format.
+
+config NETUTILS_DROPBEAR_GENERATE_HOSTKEY
+ bool "Generate host key if missing"
+ default y
+ ---help---
+ Pass -R so Dropbear generates an ECDSA P-256 host key on demand
and
+ persists it at NETUTILS_DROPBEAR_HOSTKEY_PATH when it does not
exist.
+ Product builds can disable this and provision the host key
+ externally (loaded with -r).
+
+config NETUTILS_DROPBEAR_COMPRESSION
+ bool "Enable SSH compression (zlib)"
+ default n
+ depends on LIB_ZLIB
+ ---help---
+ Enable zlib compression for SSH sessions. Requires the zlib
+ library (LIB_ZLIB). When disabled, Dropbear is built with
+ DISABLE_ZLIB and negotiates no compression.
+
+ WARNING: each session allocates a zlib deflate state of about
+ 256 KiB (DROPBEAR_ZLIB_WINDOW_BITS=15,
DROPBEAR_ZLIB_MEM_LEVEL=8),
+ and the state is allocated even for the delayed [email protected]
+ method, right after key exchange.
+
+config NETUTILS_DROPBEAR_SYSLOG
+ bool "Log via syslog"
+ default n
+ ---help---
+ Route Dropbear log messages through syslog(). When disabled,
+ Dropbear is built with DISABLE_SYSLOG.
+
+config NETUTILS_DROPBEAR_COMMIT
+ string "Dropbear upstream commit"
+ default "54ef47adf8c99b422be6a8f694f2866e62f88b9e"
+ ---help---
+ Upstream Dropbear (mkj/dropbear) commit to download and build.
+
+endif
diff --git a/netutils/dropbear/Make.defs b/netutils/dropbear/Make.defs
new file mode 100644
index 000000000..9fcb635f7
--- /dev/null
+++ b/netutils/dropbear/Make.defs
@@ -0,0 +1,25 @@
+############################################################################
+# apps/netutils/dropbear/Make.defs
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+ifneq ($(CONFIG_NETUTILS_DROPBEAR),)
+CONFIGURED_APPS += $(APPDIR)/netutils/dropbear
+endif
diff --git a/netutils/dropbear/Makefile b/netutils/dropbear/Makefile
new file mode 100644
index 000000000..039b0d0b3
--- /dev/null
+++ b/netutils/dropbear/Makefile
@@ -0,0 +1,157 @@
+############################################################################
+# apps/netutils/dropbear/Makefile
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership. The
+# ASF licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the
+# License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+############################################################################
+
+include $(APPDIR)/Make.defs
+
+ifneq ($(CONFIG_NETUTILS_DROPBEAR),)
+
+DROPBEAR_COMMIT = $(patsubst "%",%,$(strip $(CONFIG_NETUTILS_DROPBEAR_COMMIT)))
+DROPBEAR_ZIP = $(DROPBEAR_COMMIT).zip
+DROPBEAR_UNPACKNAME = dropbear
+DROPBEAR_URL = https://github.com/mkj/dropbear/archive
+UNPACK ?= unzip -q -o
+ifeq ($(CONFIG_HOST_MACOS),y)
+PATCH ?= gpatch
+else
+PATCH ?= patch
+endif
+
+MODULE = $(CONFIG_NETUTILS_DROPBEAR)
+PROGNAME = $(CONFIG_NETUTILS_DROPBEAR_PROGNAME)
+PRIORITY = $(CONFIG_NETUTILS_DROPBEAR_PRIORITY)
+STACKSIZE = $(CONFIG_NETUTILS_DROPBEAR_STACKSIZE)
+
+CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear"
+CFLAGS +=
${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)port"
+CFLAGS +=
${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)dropbear"
+CFLAGS +=
${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)dropbear$(DELIM)src"
+CFLAGS +=
${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)dropbear$(DELIM)libtomcrypt$(DELIM)src$(DELIM)headers"
+CFLAGS +=
${INCDIR_PREFIX}"$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)dropbear$(DELIM)libtommath"
+CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)nshlib"
+
+ifneq ($(CONFIG_NETUTILS_DROPBEAR_COMPRESSION),)
+CFLAGS += ${INCDIR_PREFIX}"$(APPDIR)$(DELIM)system$(DELIM)zlib$(DELIM)zlib"
+endif
+
+CFLAGS += ${DEFINE_PREFIX}LOCALOPTIONS_H_EXISTS=1
+CFLAGS += ${DEFINE_PREFIX}DROPBEAR_NUTTX=1
+CFLAGS += ${DEFINE_PREFIX}DROPBEAR_NUTTX_PASSWD=1
+CFLAGS += -Wno-pointer-sign -Wno-format
+
+dropbear_nshsession.c_CFLAGS += ${DEFINE_PREFIX}Channel=dropbear_channel
+dropbear_nshsession.c_CFLAGS += ${DEFINE_PREFIX}ChanType=dropbear_chantype
+
+CSRCS = dropbear_nshsession.c
+CSRCS += port/nuttx_auth.c
+CSRCS += port/dropbear_utils.c
+
+CSRCS += \
+ dropbear/src/dbutil.c \
+ dropbear/src/buffer.c \
+ dropbear/src/dbhelpers.c \
+ dropbear/src/bignum.c \
+ dropbear/src/signkey.c \
+ dropbear/src/dbrandom.c \
+ dropbear/src/queue.c \
+ dropbear/src/atomicio.c \
+ dropbear/src/compat.c \
+ dropbear/src/fake-rfc2553.c \
+ dropbear/src/curve25519.c \
+ dropbear/src/chachapoly.c \
+ dropbear/src/ltc_prng.c \
+ dropbear/src/ecc.c \
+ dropbear/src/ecdsa.c \
+ dropbear/src/crypto_desc.c \
+ dropbear/src/dbmalloc.c \
+ dropbear/src/gensignkey.c \
+ dropbear/src/common-session.c \
+ dropbear/src/packet.c \
+ dropbear/src/common-algo.c \
+ dropbear/src/common-kex.c \
+ dropbear/src/common-channel.c \
+ dropbear/src/common-chansession.c \
+ dropbear/src/termcodes.c \
+ dropbear/src/tcp-accept.c \
+ dropbear/src/listener.c \
+ dropbear/src/process-packet.c \
+ dropbear/src/common-runopts.c \
+ dropbear/src/circbuffer.c \
+ dropbear/src/list.c \
+ dropbear/src/netio.c \
+ dropbear/src/gcm.c \
+ dropbear/src/kex-x25519.c \
+ dropbear/src/svr-kex.c \
+ dropbear/src/svr-auth.c \
+ dropbear/src/svr-authpasswd.c \
+ dropbear/src/svr-session.c \
+ dropbear/src/svr-service.c \
+ dropbear/src/svr-runopts.c \
+ dropbear/src/svr-tcpfwd.c \
+ dropbear/src/svr-forward.c \
+ dropbear/src/svr-streamfwd.c \
+ dropbear/src/svr-authpam.c
+
+TOMMATH_SRCS = $(shell if [ -d "$(DROPBEAR_UNPACKNAME)/libtommath" ]; then
find "$(DROPBEAR_UNPACKNAME)/libtommath" -name "*.c"; fi)
+TOMCRYPT_SRCS = $(shell if [ -d "$(DROPBEAR_UNPACKNAME)/libtomcrypt/src" ];
then find "$(DROPBEAR_UNPACKNAME)/libtomcrypt/src" -name "*.c" ! -name
"sober128tab.c"; fi)
+
+CSRCS += $(TOMMATH_SRCS)
+CSRCS += $(TOMCRYPT_SRCS)
+
+# Match the ESP-IDF port behavior: LTC_SOURCE is only for libtomcrypt sources.
+$(foreach src,$(TOMCRYPT_SRCS),$(eval $(src)_CFLAGS +=
${DEFINE_PREFIX}LTC_SOURCE=1))
+
+MAINSRC = dropbear_main.c
+
+$(DROPBEAR_ZIP):
+ @echo "Downloading: $(DROPBEAR_ZIP)"
+ $(Q) curl -L -o $(DROPBEAR_ZIP) $(DROPBEAR_URL)/$(DROPBEAR_ZIP)
+
+$(DROPBEAR_UNPACKNAME): $(DROPBEAR_ZIP)
+ @echo "Unpacking: $(DROPBEAR_ZIP) -> $(DROPBEAR_UNPACKNAME)"
+ $(Q) $(UNPACK) $(DROPBEAR_ZIP)
+ $(Q) rm -rf $(DROPBEAR_UNPACKNAME)
+ $(Q) mv dropbear-$(DROPBEAR_COMMIT) $(DROPBEAR_UNPACKNAME)
+ @echo "Patching $(DROPBEAR_UNPACKNAME)"
+ $(Q) $(PATCH) -s -N -l -p1 -d $(DROPBEAR_UNPACKNAME) -i
$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)patch$(DELIM)0001-guard-platform-declarations.patch;
true
+ $(Q) $(PATCH) -s -N -l -p1 -d $(DROPBEAR_UNPACKNAME) -i
$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)patch$(DELIM)0002-use-nuttx-passwd-auth.patch;
true
+ $(Q) $(PATCH) -s -N -l -p1 -d $(DROPBEAR_UNPACKNAME) -i
$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)patch$(DELIM)0003-allow-localoptions-to-override-tracking-malloc.patch;
true
+ $(Q) $(PATCH) -s -N -l -p1 -d $(DROPBEAR_UNPACKNAME) -i
$(APPDIR)$(DELIM)netutils$(DELIM)dropbear$(DELIM)patch$(DELIM)0004-use-nuttx-unused-macro.patch;
true
+ @echo "Generating default_options_guard.h"
+ $(Q) sh $(DROPBEAR_UNPACKNAME)$(DELIM)src$(DELIM)ifndef_wrapper.sh \
+ < $(DROPBEAR_UNPACKNAME)$(DELIM)src$(DELIM)default_options.h \
+ >
$(DROPBEAR_UNPACKNAME)$(DELIM)src$(DELIM)default_options_guard.h
+ $(Q) touch $(DROPBEAR_UNPACKNAME)
+
+ifeq ($(wildcard $(DROPBEAR_UNPACKNAME)/.git),)
+context:: $(DROPBEAR_UNPACKNAME)
+
+clean::
+ $(call DELFILE, port$(DELIM)*.o)
+
+distclean::
+ $(call DELDIR, $(DROPBEAR_UNPACKNAME))
+ $(call DELFILE, $(DROPBEAR_ZIP))
+endif
+
+endif
+
+include $(APPDIR)/Application.mk
diff --git a/netutils/dropbear/dropbear_main.c
b/netutils/dropbear/dropbear_main.c
new file mode 100644
index 000000000..798ba2b55
--- /dev/null
+++ b/netutils/dropbear/dropbear_main.c
@@ -0,0 +1,329 @@
+/****************************************************************************
+ * 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;
+}
diff --git a/netutils/dropbear/dropbear_nshsession.c
b/netutils/dropbear/dropbear_nshsession.c
new file mode 100644
index 000000000..3b2e6cdbd
--- /dev/null
+++ b/netutils/dropbear/dropbear_nshsession.c
@@ -0,0 +1,633 @@
+/****************************************************************************
+ * apps/netutils/dropbear/dropbear_nshsession.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include "includes.h"
+#include "channel.h"
+#include "chansession.h"
+#include "dbutil.h"
+#include "session.h"
+
+#include "nsh.h"
+#include "nsh_console.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pty.h>
+#include <pthread.h>
+#include <sched.h>
+#include <signal.h>
+#include <spawn.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <unistd.h>
+
+struct dropbear_nshsession_s
+{
+ int pty_readfd;
+ int pty_writefd;
+ pid_t nsh_pid;
+ volatile bool done;
+ pthread_t waiter;
+ bool waiter_started;
+ bool have_winsize;
+ struct winsize win;
+};
+
+struct dropbear_signal_name_s
+{
+ FAR const char *name;
+ int signo;
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct dropbear_signal_name_s g_dropbear_signals[] =
+{
+ { "ABRT", SIGABRT },
+ { "ALRM", SIGALRM },
+ { "FPE", SIGFPE },
+ { "HUP", SIGHUP },
+ { "ILL", SIGILL },
+ { "INT", SIGINT },
+ { "KILL", SIGKILL },
+ { "PIPE", SIGPIPE },
+ { "QUIT", SIGQUIT },
+ { "SEGV", SIGSEGV },
+ { "TERM", SIGTERM },
+ { "USR1", SIGUSR1 },
+ { "USR2", SIGUSR2 },
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static int dropbear_nsh_main(int argc, FAR char *argv[])
+{
+ FAR struct console_stdio_s *pstate;
+ FAR char *nsh_argv[] =
+ {
+ "nsh",
+ NULL
+ };
+
+ int ret;
+
+ (void)argc;
+ (void)argv;
+
+ pstate = nsh_newconsole(true);
+ if (pstate == NULL)
+ {
+ dropbear_log(LOG_WARNING, "failed to create NSH console");
+ return -ENOMEM;
+ }
+
+ ret = nsh_session(pstate, NSH_LOGIN_NONE, 1, nsh_argv);
+ dropbear_log(LOG_INFO, "NSH session exited: %d", ret);
+
+ nsh_exit(&pstate->cn_vtbl, ret);
+ return ret;
+}
+
+static FAR void *dropbear_nsh_waiter(FAR void *arg)
+{
+ FAR struct dropbear_nshsession_s *sess = arg;
+ unsigned char ch = 0;
+ int status;
+ int ret;
+
+ ret = waitpid(sess->nsh_pid, &status, 0);
+ if (ret < 0)
+ {
+ dropbear_log(LOG_WARNING, "NSH session wait failed: %s",
+ strerror(errno));
+ }
+
+ sess->nsh_pid = -1;
+ sess->done = true;
+
+ /* Match Dropbear's SIGCHLD wakeup path. The session loop checks channel
+ * close conditions when this pipe becomes readable.
+ */
+
+ if (ses.signal_pipe[1] >= 0)
+ {
+ write(ses.signal_pipe[1], &ch, sizeof(ch));
+ }
+
+ return NULL;
+}
+
+static int dropbear_newchansess(FAR struct dropbear_channel *channel)
+{
+ FAR struct dropbear_nshsession_s *sess;
+
+ sess = m_malloc(sizeof(*sess));
+ memset(sess, 0, sizeof(*sess));
+ sess->pty_readfd = -1;
+ sess->pty_writefd = -1;
+ sess->nsh_pid = -1;
+
+ channel->typedata = sess;
+ channel->prio = DROPBEAR_PRIO_LOWDELAY;
+ return 0;
+}
+
+static int dropbear_sesscheckclose(FAR struct dropbear_channel *channel)
+{
+ FAR struct dropbear_nshsession_s *sess = channel->typedata;
+
+ return sess != NULL && sess->done;
+}
+
+static int dropbear_setup_spawn_attrs(FAR posix_spawnattr_t *attr,
+ FAR const char **errmsg)
+{
+ struct sched_param param;
+ int rc;
+
+ param.sched_priority = CONFIG_NETUTILS_DROPBEAR_SHELL_PRIORITY;
+ *errmsg = "spawn priority setup";
+ rc = posix_spawnattr_setschedparam(attr, ¶m);
+ if (rc != 0)
+ {
+ return rc;
+ }
+
+ *errmsg = "spawn stack setup";
+ rc = posix_spawnattr_setstacksize(
+ attr, CONFIG_NETUTILS_DROPBEAR_SHELL_STACKSIZE);
+ if (rc != 0)
+ {
+ return rc;
+ }
+
+ *errmsg = "spawn flags setup";
+ return posix_spawnattr_setflags(attr, POSIX_SPAWN_SETSCHEDPARAM);
+}
+
+static int
+dropbear_setup_spawn_stdio(FAR posix_spawn_file_actions_t *actions,
+ int slavefd)
+{
+ static const int stdio_fds[] =
+ {
+ STDIN_FILENO,
+ STDOUT_FILENO,
+ STDERR_FILENO
+ };
+
+ int i;
+ int rc;
+
+ for (i = 0; i < nitems(stdio_fds); i++)
+ {
+ rc = posix_spawn_file_actions_adddup2(actions, slavefd, stdio_fds[i]);
+ if (rc != 0)
+ {
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int
+dropbear_setup_spawn_close(FAR posix_spawn_file_actions_t *actions,
+ int fd)
+{
+ if (fd <= STDERR_FILENO)
+ {
+ return 0;
+ }
+
+ return posix_spawn_file_actions_addclose(actions, fd);
+}
+
+static int dropbear_start_nsh(FAR struct dropbear_channel *channel,
+ FAR struct dropbear_nshsession_s *sess)
+{
+ posix_spawn_file_actions_t actions;
+ posix_spawnattr_t attr;
+ FAR const char *errmsg;
+ FAR char * const argv[] =
+ {
+ "nsh",
+ NULL
+ };
+
+ int masterfd;
+ int slavefd;
+ int writefd;
+ int rc;
+
+ if (sess->nsh_pid >= 0)
+ {
+ dropbear_log(LOG_WARNING, "NSH session already running");
+ return DROPBEAR_FAILURE;
+ }
+
+ rc = openpty(&masterfd, &slavefd, NULL, NULL,
+ sess->have_winsize ? &sess->win : NULL);
+ if (rc < 0)
+ {
+ dropbear_log(LOG_WARNING, "openpty failed: %s", strerror(errno));
+ return DROPBEAR_FAILURE;
+ }
+
+ writefd = dup(masterfd);
+ if (writefd < 0)
+ {
+ dropbear_log(LOG_WARNING, "pty dup failed: %s", strerror(errno));
+ close(masterfd);
+ close(slavefd);
+ return DROPBEAR_FAILURE;
+ }
+
+ rc = posix_spawn_file_actions_init(&actions);
+ if (rc != 0)
+ {
+ dropbear_log(LOG_WARNING, "spawn actions init failed: %s",
+ strerror(rc));
+ close(masterfd);
+ close(writefd);
+ close(slavefd);
+ return DROPBEAR_FAILURE;
+ }
+
+ rc = posix_spawnattr_init(&attr);
+ if (rc != 0)
+ {
+ dropbear_log(LOG_WARNING, "spawn attr init failed: %s", strerror(rc));
+ goto err_with_actions;
+ }
+
+ rc = dropbear_setup_spawn_attrs(&attr, &errmsg);
+ if (rc != 0)
+ {
+ dropbear_log(LOG_WARNING, "%s failed: %s", errmsg, strerror(rc));
+ goto err_with_attr;
+ }
+
+ rc = dropbear_setup_spawn_stdio(&actions, slavefd);
+ if (rc != 0)
+ {
+ dropbear_log(LOG_WARNING, "spawn stdio setup failed: %s",
+ strerror(rc));
+ goto err_with_attr;
+ }
+
+ rc = dropbear_setup_spawn_close(&actions, masterfd);
+ if (rc != 0)
+ {
+ dropbear_log(LOG_WARNING, "spawn master close setup failed: %s",
+ strerror(rc));
+ goto err_with_attr;
+ }
+
+ rc = dropbear_setup_spawn_close(&actions, writefd);
+ if (rc != 0)
+ {
+ dropbear_log(LOG_WARNING, "spawn write close setup failed: %s",
+ strerror(rc));
+ goto err_with_attr;
+ }
+
+ rc = dropbear_setup_spawn_close(&actions, slavefd);
+ if (rc != 0)
+ {
+ dropbear_log(LOG_WARNING, "spawn slave close setup failed: %s",
+ strerror(rc));
+ goto err_with_attr;
+ }
+
+ sess->nsh_pid = task_spawn("dropbear nsh", dropbear_nsh_main, &actions,
+ &attr, argv, NULL);
+ if (sess->nsh_pid < 0)
+ {
+ dropbear_log(LOG_WARNING, "failed to create NSH task: %s",
+ strerror(-sess->nsh_pid));
+ goto err_with_attr;
+ }
+
+ close(slavefd);
+ slavefd = -1;
+
+ rc = pthread_create(&sess->waiter, NULL, dropbear_nsh_waiter, sess);
+ if (rc != 0)
+ {
+ dropbear_log(LOG_WARNING, "failed to create NSH waiter: %s",
+ strerror(rc));
+ close(masterfd);
+ close(writefd);
+ masterfd = -1;
+ writefd = -1;
+ kill(sess->nsh_pid, SIGTERM);
+ waitpid(sess->nsh_pid, NULL, 0);
+ sess->nsh_pid = -1;
+ goto err_with_attr;
+ }
+
+ sess->waiter_started = true;
+ sess->pty_readfd = masterfd;
+ sess->pty_writefd = writefd;
+
+ channel->readfd = masterfd;
+ channel->writefd = writefd;
+ channel->bidir_fd = 0;
+
+ setnonblocking(channel->readfd);
+ setnonblocking(channel->writefd);
+ ses.maxfd = MAX(ses.maxfd, channel->readfd);
+ ses.maxfd = MAX(ses.maxfd, channel->writefd);
+
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&actions);
+
+ dropbear_log(LOG_INFO, "NSH PTY session started");
+ return DROPBEAR_SUCCESS;
+
+err_with_attr:
+ posix_spawnattr_destroy(&attr);
+
+err_with_actions:
+ posix_spawn_file_actions_destroy(&actions);
+
+ if (masterfd >= 0)
+ {
+ close(masterfd);
+ }
+
+ if (writefd >= 0)
+ {
+ close(writefd);
+ }
+
+ if (slavefd >= 0)
+ {
+ close(slavefd);
+ }
+
+ sess->nsh_pid = -1;
+ return DROPBEAR_FAILURE;
+}
+
+static void dropbear_parse_winsize(FAR struct dropbear_nshsession_s *sess)
+{
+ unsigned int cols = buf_getint(ses.payload);
+ unsigned int rows = buf_getint(ses.payload);
+ unsigned int width = buf_getint(ses.payload);
+ unsigned int height = buf_getint(ses.payload);
+
+ sess->win.ws_col = cols;
+ sess->win.ws_row = rows;
+ sess->win.ws_xpixel = width;
+ sess->win.ws_ypixel = height;
+ sess->have_winsize = true;
+
+ if (sess->pty_readfd >= 0)
+ {
+ ioctl(sess->pty_readfd, TIOCSWINSZ, (unsigned long)&sess->win);
+ }
+}
+
+static int dropbear_handle_pty_req(FAR struct dropbear_nshsession_s *sess)
+{
+ unsigned int len;
+ FAR char *term;
+ FAR char *modes;
+
+ term = buf_getstring(ses.payload, &len);
+ dropbear_parse_winsize(sess);
+ modes = buf_getstring(ses.payload, &len);
+
+ m_free(term);
+ m_free(modes);
+ return DROPBEAR_SUCCESS;
+}
+
+static int dropbear_signal_from_name(FAR const char *name)
+{
+ int i;
+
+ for (i = 0; i < nitems(g_dropbear_signals); i++)
+ {
+ if (strcmp(name, g_dropbear_signals[i].name) == 0)
+ {
+ return g_dropbear_signals[i].signo;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int dropbear_write_terminal_signal(
+ FAR struct dropbear_nshsession_s *sess,
+ int signo)
+{
+#ifdef CONFIG_TTY_SIGINT
+ unsigned char ch;
+ ssize_t nwritten;
+
+ if (signo == SIGINT && sess->pty_writefd >= 0)
+ {
+ ch = CONFIG_TTY_SIGINT_CHAR;
+ nwritten = write(sess->pty_writefd, &ch, sizeof(ch));
+ if (nwritten == sizeof(ch))
+ {
+ return DROPBEAR_SUCCESS;
+ }
+
+ dropbear_log(LOG_WARNING, "SSH terminal INT failed: %s",
+ strerror(errno));
+ return DROPBEAR_FAILURE;
+ }
+#endif
+
+ return DROPBEAR_FAILURE;
+}
+
+static int dropbear_handle_signal(FAR struct dropbear_nshsession_s *sess)
+{
+ unsigned int len;
+ FAR char *name;
+ int signo;
+ int ret;
+
+ name = buf_getstring(ses.payload, &len);
+ signo = dropbear_signal_from_name(name);
+ if (signo < 0)
+ {
+ dropbear_log(LOG_WARNING, "unsupported SSH signal '%s'", name);
+ m_free(name);
+ return DROPBEAR_FAILURE;
+ }
+
+ ret = dropbear_write_terminal_signal(sess, signo);
+ if (ret == DROPBEAR_SUCCESS)
+ {
+ dropbear_log(LOG_INFO, "SSH signal '%s' sent to PTY", name);
+ m_free(name);
+ return DROPBEAR_SUCCESS;
+ }
+
+ if (sess->nsh_pid <= 0)
+ {
+ dropbear_log(LOG_WARNING, "SSH signal '%s' has no NSH session", name);
+ m_free(name);
+ return DROPBEAR_FAILURE;
+ }
+
+ ret = kill(sess->nsh_pid, signo);
+ if (ret < 0)
+ {
+ dropbear_log(LOG_WARNING, "SSH signal '%s' failed: %s",
+ name, strerror(errno));
+ m_free(name);
+ return DROPBEAR_FAILURE;
+ }
+
+ dropbear_log(LOG_INFO, "SSH signal '%s' sent to NSH session", name);
+ m_free(name);
+ return DROPBEAR_SUCCESS;
+}
+
+static void dropbear_chansessionrequest(FAR struct dropbear_channel *channel)
+{
+ unsigned int typelen;
+ FAR char *type = buf_getstring(ses.payload, &typelen);
+ unsigned char wantreply = buf_getbool(ses.payload);
+ FAR struct dropbear_nshsession_s *sess = channel->typedata;
+ int ret = DROPBEAR_FAILURE;
+
+ TRACE(("dropbear_chansessionrequest: type='%s'", type))
+
+ if (strcmp(type, "pty-req") == 0)
+ {
+ ret = dropbear_handle_pty_req(sess);
+ }
+ else if (strcmp(type, "shell") == 0)
+ {
+ ret = dropbear_start_nsh(channel, sess);
+ }
+ else if (strcmp(type, "exec") == 0)
+ {
+ dropbear_log(LOG_WARNING, "SSH exec requests are not supported");
+ }
+ else if (strcmp(type, "window-change") == 0)
+ {
+ dropbear_parse_winsize(sess);
+ ret = DROPBEAR_SUCCESS;
+ }
+ else if (strcmp(type, "signal") == 0)
+ {
+ ret = dropbear_handle_signal(sess);
+ }
+ else if (strcmp(type, "break") == 0)
+ {
+ ret = DROPBEAR_SUCCESS;
+ }
+ else
+ {
+ TRACE(("dropbear_chansessionrequest: unhandled type '%s'", type))
+ }
+
+ if (wantreply)
+ {
+ if (ret == DROPBEAR_SUCCESS)
+ {
+ send_msg_channel_success(channel);
+ }
+ else
+ {
+ send_msg_channel_failure(channel);
+ }
+ }
+
+ m_free(type);
+}
+
+static void
+dropbear_closechansess(FAR const struct dropbear_channel *channel)
+{
+ FAR struct dropbear_nshsession_s *sess = channel->typedata;
+
+ if (sess != NULL)
+ {
+ sess->done = true;
+ }
+}
+
+static void
+dropbear_cleanupchansess(FAR const struct dropbear_channel *channel)
+{
+ FAR struct dropbear_nshsession_s *sess = channel->typedata;
+
+ if (sess == NULL)
+ {
+ return;
+ }
+
+ sess->done = true;
+
+ if (sess->nsh_pid > 0)
+ {
+ kill(sess->nsh_pid, SIGTERM);
+ }
+
+ if (sess->waiter_started)
+ {
+ pthread_join(sess->waiter, NULL);
+ sess->waiter_started = false;
+ }
+
+ m_free(sess);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/* Chansession lifecycle hooks invoked from svr-session.c. Upstream
+ * svr-chansession.c uses these to set up and reap the global childpids[] /
+ * SIGCHLD machinery; the NSH bridge tracks each session's child through its
+ * own waiter thread (dropbear_nsh_waiter), so both are no-ops here.
+ */
+
+void svr_chansessinitialise(void)
+{
+}
+
+void svr_chansess_checksignal(void)
+{
+}
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+const struct dropbear_chantype svrchansess =
+{
+ "session",
+ dropbear_newchansess,
+ dropbear_sesscheckclose,
+ dropbear_chansessionrequest,
+ dropbear_closechansess,
+ dropbear_cleanupchansess,
+};
diff --git a/netutils/dropbear/patch/0001-guard-platform-declarations.patch
b/netutils/dropbear/patch/0001-guard-platform-declarations.patch
new file mode 100644
index 000000000..7eb1dfcd2
--- /dev/null
+++ b/netutils/dropbear/patch/0001-guard-platform-declarations.patch
@@ -0,0 +1,26 @@
+--- a/src/dbutil.c
++++ b/src/dbutil.c
+@@ -66,6 +66,10 @@
+ #include "session.h"
+ #include "atomicio.h"
+
++#ifdef DROPBEAR_NUTTX
++int execv(const char *path, char * const argv[]);
++#endif
++
+ #define MAX_FMT 100
+
+ static void generic_dropbear_exit(int exitcode, const char* format,
+--- a/src/signkey.h
++++ b/src/signkey.h
+@@ -150,9 +150,9 @@
+ void buf_put_sign(buffer* buf, sign_key *key, enum signature_type sigtype,
const buffer *data_buf);
+ #if DROPBEAR_SIGNKEY_VERIFY
+ int buf_verify(buffer * buf, sign_key *key, enum signature_type
expect_sigtype, const buffer *data_buf);
+ int sk_buf_verify(buffer * buf, sign_key *key, enum signature_type
expect_sigtype, const buffer *data_buf, char* app, unsigned int applen);
+-char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int
keybloblen);
+ #endif
++char * sign_key_fingerprint(const unsigned char* keyblob, unsigned int
keybloblen);
+ int cmp_base64_key(const unsigned char* keyblob, unsigned int keybloblen,
+ const unsigned char* algoname, unsigned
int algolen,
+ const buffer * line, char **
fingerprint);
diff --git a/netutils/dropbear/patch/0002-use-nuttx-passwd-auth.patch
b/netutils/dropbear/patch/0002-use-nuttx-passwd-auth.patch
new file mode 100644
index 000000000..5e71d73da
--- /dev/null
+++ b/netutils/dropbear/patch/0002-use-nuttx-passwd-auth.patch
@@ -0,0 +1,87 @@
+--- a/src/svr-authpasswd.c
++++ b/src/svr-authpasswd.c
+@@ -33,6 +33,77 @@
+
+ #if DROPBEAR_SVR_PASSWORD_AUTH
+
++#if DROPBEAR_NUTTX_PASSWD
++
++/* Process a password auth request, sending success or failure messages as
++ * appropriate */
++void svr_auth_password(int valid_user) {
++
++ char * password = NULL;
++ unsigned int passwordlen;
++ unsigned int changepw;
++ int auth_ok = 0;
++
++ /* check if client wants to change password */
++ changepw = buf_getbool(ses.payload);
++ if (changepw) {
++ /* not implemented by this server */
++ send_msg_userauth_failure(0, 1);
++ return;
++ }
++
++ password = buf_getstring(ses.payload, &passwordlen);
++ if (valid_user && passwordlen <= DROPBEAR_MAX_PASSWORD_LEN &&
++ strlen(password) == passwordlen) {
++ auth_ok = dropbear_verify_password(ses.authstate.pw_name,
password);
++ }
++ m_burn(password, passwordlen);
++ m_free(password);
++
++ /* After we have got the payload contents we can exit if the username
++ is invalid. Invalid users have already been logged. */
++ if (!valid_user) {
++ send_msg_userauth_failure(0, 1);
++ return;
++ }
++
++ if (passwordlen > DROPBEAR_MAX_PASSWORD_LEN) {
++ dropbear_log(LOG_WARNING,
++ "Too-long password attempt for '%s' from %s",
++ ses.authstate.pw_name,
++ svr_ses.addrstring);
++ send_msg_userauth_failure(0, 1);
++ return;
++ }
++
++ if (auth_ok == DROPBEAR_SUCCESS) {
++ if (svr_opts.multiauthmethod && (ses.authstate.authtypes &
~AUTH_TYPE_PASSWORD)) {
++ /* successful password authentication, but extra auth
required */
++ dropbear_log(LOG_NOTICE,
++ "Password auth succeeded for '%s' from
%s, extra auth required",
++ ses.authstate.pw_name,
++ svr_ses.addrstring);
++ ses.authstate.authtypes &= ~AUTH_TYPE_PASSWORD; /*
password auth ok, delete the method flag */
++ send_msg_userauth_failure(1, 0); /* Send partial
success */
++ } else {
++ /* successful authentication */
++ dropbear_log(LOG_NOTICE,
++ "Password auth succeeded for '%s' from
%s",
++ ses.authstate.pw_name,
++ svr_ses.addrstring);
++ send_msg_userauth_success();
++ }
++ } else {
++ dropbear_log(LOG_WARNING,
++ "Bad password attempt for '%s' from %s",
++ ses.authstate.pw_name,
++ svr_ses.addrstring);
++ send_msg_userauth_failure(0, 1);
++ }
++}
++
++#else
++
+ /* not constant time when strings are differing lengths.
+ string content isn't leaked, and crypt hashes are predictable length. */
+ static int constant_time_strcmp(const char* a, const char* b) {
+@@ -131,4 +202,6 @@
+ }
+ }
+
++#endif /* DROPBEAR_NUTTX_PASSWD */
++
+ #endif
diff --git
a/netutils/dropbear/patch/0003-allow-localoptions-to-override-tracking-malloc.patch
b/netutils/dropbear/patch/0003-allow-localoptions-to-override-tracking-malloc.patch
new file mode 100644
index 000000000..b1dc3e52c
--- /dev/null
+++
b/netutils/dropbear/patch/0003-allow-localoptions-to-override-tracking-malloc.patch
@@ -0,0 +1,24 @@
+--- a/src/sysoptions.h
++++ b/src/sysoptions.h
+@@ -183,7 +183,9 @@ defined(__has_feature)
+ #define LTC_ECC521
+ #endif
+
+-#define DROPBEAR_LTC_PRNG (DROPBEAR_ECC)
++#ifndef DROPBEAR_LTC_PRNG
++#define DROPBEAR_LTC_PRNG (DROPBEAR_ECC)
++#endif
+
+ /* RSA can be vulnerable to timing attacks which use the time required for
+ * signing to guess the private key. Blinding avoids this attack, though makes
+@@ -436,7 +438,9 @@ defined(__has_feature)
+ #define DROPBEAR_CLIENT_TCP_FAST_OPEN 0
+ #endif
+
+-#define DROPBEAR_TRACKING_MALLOC (DROPBEAR_FUZZ)
++#ifndef DROPBEAR_TRACKING_MALLOC
++#define DROPBEAR_TRACKING_MALLOC (DROPBEAR_FUZZ)
++#endif
+
+ /* Used to work around Memory Sanitizer false positives */
+ #if defined(__has_feature)
diff --git a/netutils/dropbear/patch/0004-use-nuttx-unused-macro.patch
b/netutils/dropbear/patch/0004-use-nuttx-unused-macro.patch
new file mode 100644
index 000000000..2ea588057
--- /dev/null
+++ b/netutils/dropbear/patch/0004-use-nuttx-unused-macro.patch
@@ -0,0 +1,202 @@
+--- a/src/compat.c
++++ b/src/compat.c
+@@ -89,6 +89,6 @@
+ #ifndef HAVE_GETUSERSHELL
+ static char **curshell, **shells, *strings;
+-static char **initshells();
++static char **initshells(void);
+ #endif
+
+ #ifndef HAVE_STRLCPY
+@@ -234,7 +234,7 @@
+ curshell = initshells();
+ }
+
+-static char **initshells() {
++static char **initshells(void) {
+ static const char *okshells[] = { COMPAT_USER_SHELLS, NULL };
+ register char **sp, *cp;
+ register FILE *fp;
+--- a/src/common-algo.c
++++ b/src/common-algo.c
+@@ -39,16 +39,25 @@
+ /* This file (algo.c) organises the ciphers which can be used, and is used to
+ * decide which ciphers/hashes/compression/signing to use during key
exchange*/
+
+-static int void_cipher(const unsigned char* in, unsigned char* out,
+- unsigned long len, void* UNUSED(cipher_state)) {
++static int void_cipher(const unsigned char* in, unsigned char* out,
++ unsigned long len, void* cipher_state) {
++ UNUSED(cipher_state);
++
+ if (in != out) {
+ memmove(out, in, len);
+ }
+ return CRYPT_OK;
+ }
+
+-static int void_start(int UNUSED(cipher), const unsigned char* UNUSED(IV),
+- const unsigned char* UNUSED(key),
+- int UNUSED(keylen), int UNUSED(num_rounds), void*
UNUSED(cipher_state)) {
++static int void_start(int cipher, const unsigned char* IV,
++ const unsigned char* key,
++ int keylen, int num_rounds, void* cipher_state) {
++ UNUSED(cipher);
++ UNUSED(IV);
++ UNUSED(key);
++ UNUSED(keylen);
++ UNUSED(num_rounds);
++ UNUSED(cipher_state);
++
+ return CRYPT_OK;
+ }
+
+--- a/src/common-channel.c
++++ b/src/common-channel.c
+@@ -410,7 +410,9 @@
+
+ #ifndef HAVE_WRITEV
+ static int writechannel_fallback(struct Channel* channel, int fd, circbuffer
*cbuf,
+- const unsigned char *UNUSED(moredata), unsigned int *morelen) {
++ const unsigned char *moredata, unsigned int *morelen) {
++ UNUSED(moredata);
++
+
+ unsigned char *circ_p1, *circ_p2;
+ unsigned int circ_len1, circ_len2;
+--- a/src/dbutil.c
++++ b/src/dbutil.c
+@@ -138,7 +138,9 @@
+ }
+
+-static void generic_dropbear_log(int UNUSED(priority), const char* format,
++static void generic_dropbear_log(int priority, const char* format,
+ va_list param) {
++ UNUSED(priority);
++
+
+ char printbuf[1024];
+
+--- a/src/netio.c
++++ b/src/netio.c
+@@ -47,7 +47,10 @@
+ }
+ }
+
+-static void cancel_callback(int result, int sock, void* UNUSED(data), const
char* UNUSED(errstring)) {
++static void cancel_callback(int result, int sock, void* data, const char*
errstring) {
++ UNUSED(data);
++ UNUSED(errstring);
++
+ if (result == DROPBEAR_SUCCESS)
+ {
+ m_close(sock);
+--- a/src/ltc_prng.c
++++ b/src/ltc_prng.c
+@@ -32,8 +32,10 @@
+ @param prng [out] The PRNG state to initialize
+ @return CRYPT_OK if successful
+ */
+-int dropbear_prng_start(prng_state* UNUSED(prng))
++int dropbear_prng_start(prng_state* prng)
+ {
++ UNUSED(prng);
++
+ return CRYPT_OK;
+ }
+
+@@ -44,8 +46,12 @@
+ @param prng PRNG state to update
+ @return CRYPT_OK if successful
+ */
+-int dropbear_prng_add_entropy(const unsigned char* UNUSED(in), unsigned long
UNUSED(inlen), prng_state* UNUSED(prng))
++int dropbear_prng_add_entropy(const unsigned char* in, unsigned long inlen,
prng_state* prng)
+ {
++ UNUSED(in);
++ UNUSED(inlen);
++ UNUSED(prng);
++
+ return CRYPT_OK;
+ }
+
+@@ -54,8 +60,10 @@
+ @param prng The PRNG to make active
+ @return CRYPT_OK if successful
+ */
+-int dropbear_prng_ready(prng_state* UNUSED(prng))
++int dropbear_prng_ready(prng_state* prng)
+ {
++ UNUSED(prng);
++
+ return CRYPT_OK;
+ }
+
+@@ -66,8 +74,10 @@
+ @param prng The active PRNG to read from
+ @return Number of octets read
+ */
+-unsigned long dropbear_prng_read(unsigned char* out, unsigned long outlen,
prng_state* UNUSED(prng))
++unsigned long dropbear_prng_read(unsigned char* out, unsigned long outlen,
prng_state* prng)
+ {
++ UNUSED(prng);
++
+ LTC_ARGCHK(out != NULL);
+ genrandom(out, outlen);
+ return outlen;
+@@ -78,8 +88,10 @@
+ @param prng The PRNG to terminate
+ @return CRYPT_OK if successful
+ */
+-int dropbear_prng_done(prng_state* UNUSED(prng))
++int dropbear_prng_done(prng_state* prng)
+ {
++ UNUSED(prng);
++
+ return CRYPT_OK;
+ }
+
+@@ -90,8 +102,11 @@
+ @param prng The PRNG to export
+ @return CRYPT_OK if successful
+ */
+-int dropbear_prng_export(unsigned char* UNUSED(out), unsigned long* outlen,
prng_state* UNUSED(prng))
++int dropbear_prng_export(unsigned char* out, unsigned long* outlen,
prng_state* prng)
+ {
++ UNUSED(out);
++ UNUSED(prng);
++
+ LTC_ARGCHK(outlen != NULL);
+
+ *outlen = 0;
+@@ -105,8 +120,12 @@
+ @param prng The PRNG to import
+ @return CRYPT_OK if successful
+ */
+-int dropbear_prng_import(const unsigned char* UNUSED(in), unsigned long
UNUSED(inlen), prng_state* UNUSED(prng))
++int dropbear_prng_import(const unsigned char* in, unsigned long inlen,
prng_state* prng)
+ {
++ UNUSED(in);
++ UNUSED(inlen);
++ UNUSED(prng);
++
+ return CRYPT_OK;
+ }
+
+--- a/src/chachapoly.c
++++ b/src/chachapoly.c
+@@ -43,9 +43,13 @@
+ const struct dropbear_cipher dropbear_chachapoly =
+ {&dummy, CHACHA20_KEY_LEN*2, CHACHA20_BLOCKSIZE};
+
+-static int dropbear_chachapoly_start(int UNUSED(cipher), const unsigned char*
UNUSED(IV),
++static int dropbear_chachapoly_start(int cipher, const unsigned char* IV,
+ const unsigned char *key, int keylen,
+- int UNUSED(num_rounds), dropbear_chachapoly_state
*state) {
++ int num_rounds, dropbear_chachapoly_state *state) {
++ UNUSED(cipher);
++ UNUSED(IV);
++ UNUSED(num_rounds);
++
+ int err;
+
+ TRACE2(("enter dropbear_chachapoly_start"))
diff --git a/netutils/dropbear/port/config.h b/netutils/dropbear/port/config.h
new file mode 100644
index 000000000..0f5355fd7
--- /dev/null
+++ b/netutils/dropbear/port/config.h
@@ -0,0 +1,20 @@
+/****************************************************************************
+ * apps/netutils/dropbear/port/config.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ****************************************************************************/
+
+#ifndef __APPS_NETUTILS_DROPBEAR_PORT_CONFIG_H
+#define __APPS_NETUTILS_DROPBEAR_PORT_CONFIG_H
+
+/* Dropbear looks for a file named config.h. Keep this wrapper name stable;
+ * NuttX-specific autoconf replacements live in nuttx_config.h.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include "nuttx_config.h"
+
+#endif /* __APPS_NETUTILS_DROPBEAR_PORT_CONFIG_H */
diff --git a/netutils/dropbear/port/dropbear_utils.c
b/netutils/dropbear/port/dropbear_utils.c
new file mode 100644
index 000000000..73619cbe7
--- /dev/null
+++ b/netutils/dropbear/port/dropbear_utils.c
@@ -0,0 +1,149 @@
+/****************************************************************************
+ * apps/netutils/dropbear/port/dropbear_utils.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include "dropbear_utils.h"
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <limits.h>
+#include <string.h>
+#include <unistd.h>
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static int dropbear_hex_value(char ch)
+{
+ if (ch >= '0' && ch <= '9')
+ {
+ return ch - '0';
+ }
+
+ if (ch >= 'a' && ch <= 'f')
+ {
+ return ch - 'a' + 10;
+ }
+
+ if (ch >= 'A' && ch <= 'F')
+ {
+ return ch - 'A' + 10;
+ }
+
+ return -1;
+}
+
+static int dropbear_try_mkdir(FAR const char *path)
+{
+ if (mkdir(path, 0700) < 0 && errno != EEXIST)
+ {
+ return -errno;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+void dropbear_hex_encode(FAR char *dst, FAR const uint8_t *src,
+ size_t srclen)
+{
+ static const char hex[] = "0123456789abcdef";
+ size_t i;
+
+ for (i = 0; i < srclen; i++)
+ {
+ dst[i * 2] = hex[src[i] >> 4];
+ dst[i * 2 + 1] = hex[src[i] & 0x0f];
+ }
+
+ dst[srclen * 2] = '\0';
+}
+
+int dropbear_hex_decode(FAR const char *src, size_t srclen,
+ FAR uint8_t *dst, size_t dstlen)
+{
+ size_t i;
+
+ if (srclen != dstlen * 2)
+ {
+ return -EINVAL;
+ }
+
+ for (i = 0; i < dstlen; i++)
+ {
+ int hi = dropbear_hex_value(src[i * 2]);
+ int lo = dropbear_hex_value(src[i * 2 + 1]);
+
+ if (hi < 0 || lo < 0)
+ {
+ return -EINVAL;
+ }
+
+ dst[i] = (uint8_t)((hi << 4) | lo);
+ }
+
+ return OK;
+}
+
+int dropbear_try_prepare_parent(FAR const char *path)
+{
+ char dir[PATH_MAX];
+ struct stat st;
+ FAR char *slash;
+ FAR char *p;
+ int ret;
+
+ if (strlcpy(dir, path, sizeof(dir)) >= sizeof(dir))
+ {
+ return -ENAMETOOLONG;
+ }
+
+ slash = strrchr(dir, '/');
+ if (slash == NULL || slash == dir)
+ {
+ return OK;
+ }
+
+ *slash = '\0';
+ if (stat(dir, &st) == 0)
+ {
+ return OK;
+ }
+
+ for (p = dir + 1; *p != '\0'; p++)
+ {
+ if (*p == '/')
+ {
+ *p = '\0';
+ ret = dropbear_try_mkdir(dir);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ *p = '/';
+ }
+ }
+
+ return dropbear_try_mkdir(dir);
+}
+
+#ifndef CONFIG_SCHED_USER_IDENTITY
+int dropbear_getgroups(int size, gid_t list[])
+{
+ (void)size;
+ (void)list;
+ set_errno(ENOSYS);
+ return ERROR;
+}
+#endif
diff --git a/netutils/dropbear/port/dropbear_utils.h
b/netutils/dropbear/port/dropbear_utils.h
new file mode 100644
index 000000000..3e7d2722e
--- /dev/null
+++ b/netutils/dropbear/port/dropbear_utils.h
@@ -0,0 +1,31 @@
+/****************************************************************************
+ * apps/netutils/dropbear/port/dropbear_utils.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ****************************************************************************/
+
+#ifndef __APPS_NETUTILS_DROPBEAR_PORT_DROPBEAR_UTILS_H
+#define __APPS_NETUTILS_DROPBEAR_PORT_DROPBEAR_UTILS_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include "config.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+void dropbear_hex_encode(FAR char *dst, FAR const uint8_t *src,
+ size_t srclen);
+
+int dropbear_hex_decode(FAR const char *src, size_t srclen,
+ FAR uint8_t *dst, size_t dstlen);
+
+int dropbear_try_prepare_parent(FAR const char *path);
+
+#endif /* __APPS_NETUTILS_DROPBEAR_PORT_DROPBEAR_UTILS_H */
diff --git a/netutils/dropbear/port/localoptions.h
b/netutils/dropbear/port/localoptions.h
new file mode 100644
index 000000000..28ac5a3b5
--- /dev/null
+++ b/netutils/dropbear/port/localoptions.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+ * apps/netutils/dropbear/port/localoptions.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ****************************************************************************/
+
+#ifndef __APPS_NETUTILS_DROPBEAR_PORT_LOCALOPTIONS_H
+#define __APPS_NETUTILS_DROPBEAR_PORT_LOCALOPTIONS_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define DROPBEAR_TRACKING_MALLOC 1
+
+/* Persist the native Dropbear ECDSA host key at the configured path. */
+
+#define ECDSA_PRIV_FILENAME CONFIG_NETUTILS_DROPBEAR_HOSTKEY_PATH
+
+#define DROPBEAR_SVR_DROP_PRIVS 0
+
+#ifdef CONFIG_SCHED_USER_IDENTITY
+# define DROPBEAR_SVR_MULTIUSER 1
+#else
+# define DROPBEAR_SVR_MULTIUSER 0
+#endif
+
+#define DROPBEAR_SVR_PASSWORD_AUTH 1
+#define DROPBEAR_SVR_PUBKEY_AUTH 0
+#define DROPBEAR_SVR_PUBKEY_OPTIONS 0
+
+#define DROPBEAR_REEXEC 0
+#define DROPBEAR_SMALL_CODE 1
+#define DROPBEAR_USER_ALGO_LIST 0
+
+#define DROPBEAR_X11FWD 0
+#define DROPBEAR_SVR_AGENTFWD 0
+#define DROPBEAR_SVR_LOCALTCPFWD 0
+#define DROPBEAR_SVR_REMOTETCPFWD 0
+#define DROPBEAR_SVR_LOCALSTREAMFWD 0
+#define DROPBEAR_SVR_REMOTESTREAMFWD 0
+
+#define DROPBEAR_DSS 0
+#define DROPBEAR_RSA 0
+#define DROPBEAR_ECDSA 1
+#define DROPBEAR_ED25519 0
+#define DROPBEAR_SK_KEYS 0
+#define DROPBEAR_ECC_256 1
+#define DROPBEAR_ECC_384 0
+#define DROPBEAR_ECC_521 0
+
+#define DROPBEAR_AES128 1
+#define DROPBEAR_AES256 0
+#define DROPBEAR_CHACHA20POLY1305 1
+#define DROPBEAR_ENABLE_CBC_MODE 0
+#define DROPBEAR_ENABLE_GCM_MODE 0
+
+#define DROPBEAR_SHA1_HMAC 0
+#define DROPBEAR_SHA2_256_HMAC 1
+#define DROPBEAR_SHA2_512_HMAC 0
+#define DROPBEAR_SHA1_96_HMAC 0
+
+#define DROPBEAR_CURVE25519 1
+#define DROPBEAR_DH_GROUP14_SHA1 0
+#define DROPBEAR_DH_GROUP14_SHA256 0
+#define DROPBEAR_DH_GROUP16 0
+#define DROPBEAR_DH_GROUP1 0
+#define DROPBEAR_ECDH 0
+#define DROPBEAR_SNTRUP761 0
+#define DROPBEAR_MLKEM768 0
+
+#define DROPBEAR_DEFAULT_CLI_AUTHKEY "/etc/dropbear/authorized_keys"
+#define DROPBEAR_SFTPSERVER 0
+
+#endif /* __APPS_NETUTILS_DROPBEAR_PORT_LOCALOPTIONS_H */
diff --git a/netutils/dropbear/port/nuttx_auth.c
b/netutils/dropbear/port/nuttx_auth.c
new file mode 100644
index 000000000..83becdcae
--- /dev/null
+++ b/netutils/dropbear/port/nuttx_auth.c
@@ -0,0 +1,109 @@
+/****************************************************************************
+ * apps/netutils/dropbear/port/nuttx_auth.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "fsutils/passwd.h"
+
+#include "dbutil.h"
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static struct passwd g_dropbear_pw;
+static char g_dropbear_name[64];
+static char g_dropbear_dir[] = "/";
+static char g_dropbear_shell[] = "/bin/sh";
+static char g_dropbear_password_marker[] = "x";
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static FAR struct passwd *dropbear_fill_pw(FAR const char *name, uid_t uid)
+{
+ memset(&g_dropbear_pw, 0, sizeof(g_dropbear_pw));
+
+ snprintf(g_dropbear_name, sizeof(g_dropbear_name), "%s", name);
+
+ g_dropbear_pw.pw_uid = uid;
+ g_dropbear_pw.pw_gid = 0;
+ g_dropbear_pw.pw_name = g_dropbear_name;
+ g_dropbear_pw.pw_passwd = g_dropbear_password_marker;
+ g_dropbear_pw.pw_gecos = g_dropbear_name;
+ g_dropbear_pw.pw_dir = g_dropbear_dir;
+ g_dropbear_pw.pw_shell = g_dropbear_shell;
+
+ return &g_dropbear_pw;
+}
+
+FAR struct passwd *dropbear_getpwuid(uid_t uid)
+{
+ return dropbear_fill_pw("root", uid);
+}
+
+FAR struct passwd *dropbear_getpwnam(FAR const char *name)
+{
+ if (name == NULL || name[0] == '\0')
+ {
+ return NULL;
+ }
+
+ return dropbear_fill_pw(name, 0);
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+uid_t dropbear_getuid(void)
+{
+ return 0;
+}
+
+uid_t dropbear_geteuid(void)
+{
+ return 0;
+}
+
+int dropbear_auth_initialize(void)
+{
+ dropbear_log(LOG_INFO, "using NuttX passwd auth at %s",
+ CONFIG_FSUTILS_PASSWD_PATH);
+ return OK;
+}
+
+int dropbear_verify_password(FAR const char *username,
+ FAR const char *password)
+{
+ int ret;
+
+ ret = passwd_verify(username, password);
+ if (PASSWORD_VERIFY_MATCH(ret))
+ {
+ return DROPBEAR_SUCCESS;
+ }
+
+ if (PASSWORD_VERIFY_ERROR(ret) && ret != -ENOENT)
+ {
+ dropbear_log(LOG_WARNING, "passwd_verify failed for '%s': %d",
+ username, ret);
+ }
+
+ return DROPBEAR_FAILURE;
+}
diff --git a/netutils/dropbear/port/nuttx_config.h
b/netutils/dropbear/port/nuttx_config.h
new file mode 100644
index 000000000..d91709d1e
--- /dev/null
+++ b/netutils/dropbear/port/nuttx_config.h
@@ -0,0 +1,142 @@
+/****************************************************************************
+ * apps/netutils/dropbear/port/nuttx_config.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ****************************************************************************/
+
+#ifndef __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_CONFIG_H
+#define __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_CONFIG_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define DROPBEAR_SERVER 1
+#define BUNDLED_LIBTOM 1
+#define DISABLE_LASTLOG 1
+#define DISABLE_PAM 1
+#define DISABLE_PUTUTLINE 1
+#define DISABLE_PUTUTXLINE 1
+#define DISABLE_UTMP 1
+#define DISABLE_UTMPX 1
+#define DISABLE_WTMP 1
+#define DISABLE_WTMPX 1
+
+#ifndef CONFIG_NETUTILS_DROPBEAR_SYSLOG
+# define DISABLE_SYSLOG 1
+#endif
+
+#ifndef CONFIG_NETUTILS_DROPBEAR_COMPRESSION
+# define DISABLE_ZLIB 1
+#endif
+
+#define DROPBEAR_FUZZ 0
+#define DROPBEAR_PLUGIN 0
+
+#define HAVE_BASENAME 1
+#define HAVE_CLOCK_GETTIME 1
+#define HAVE_CONST_GAI_STRERROR_PROTO 1
+#define HAVE_CRYPT 1
+#define HAVE_DECL_HTOLE64 1
+#define HAVE_ENDIAN_H 1
+#define HAVE_EXPLICIT_BZERO 1
+#define HAVE_FREEADDRINFO 1
+#define HAVE_GAI_STRERROR 1
+#define HAVE_GETADDRINFO 1
+#define HAVE_GETNAMEINFO 1
+#define HAVE_GETRANDOM 1
+#define HAVE_INTTYPES_H 1
+#define HAVE_LIBGEN_H 1
+#define HAVE_NETDB_H 1
+#define HAVE_NETINET_IN_H 1
+#define HAVE_NETINET_TCP_H 1
+#define HAVE_PATHS_H 1
+#define HAVE_PUTENV 1
+#define HAVE_STATIC_ASSERT 1
+#define HAVE_STDINT_H 1
+#define HAVE_STDIO_H 1
+#define HAVE_STDLIB_H 1
+#define HAVE_STRINGS_H 1
+#define HAVE_STRING_H 1
+#define HAVE_STRLCAT 1
+#define HAVE_STRLCPY 1
+#define HAVE_STRUCT_ADDRINFO 1
+#define HAVE_STRUCT_IN6_ADDR 1
+#define HAVE_STRUCT_SOCKADDR_IN6 1
+#define HAVE_STRUCT_SOCKADDR_STORAGE 1
+#define HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY 1
+#define HAVE_SYS_RANDOM_H 1
+#define HAVE_SYS_SELECT_H 1
+#define HAVE_SYS_SOCKET_H 1
+#define HAVE_SYS_STAT_H 1
+#define HAVE_SYS_TYPES_H 1
+#define HAVE_SYS_UIO_H 1
+#define HAVE_SYS_WAIT_H 1
+#define HAVE_UINT16_T 1
+#define HAVE_UINT32_T 1
+#define HAVE_UINT8_T 1
+#define HAVE_U_INT16_T 1
+#define HAVE_U_INT32_T 1
+#define HAVE_U_INT8_T 1
+#define HAVE_UNDERSCORE_STATIC_ASSERT 1
+#define HAVE_UNISTD_H 1
+/* NuttX exposes writev(), but keep this port on Dropbear's simpler write()
+ * path until the SSH-to-NSH channel bridge is validated with vectored
+ * writes.
+ */
+
+#undef HAVE_WRITEV
+#define STDC_HEADERS 1
+
+#define PACKAGE_BUGREPORT ""
+#define PACKAGE_NAME ""
+#define PACKAGE_STRING ""
+#define PACKAGE_TARNAME ""
+#define PACKAGE_URL ""
+#define PACKAGE_VERSION ""
+
+#define SELECT_TYPE_ARG1 int
+#define SELECT_TYPE_ARG234 (fd_set *)
+#define SELECT_TYPE_ARG5 (struct timeval *)
+
+#ifndef PF_UNIX
+# define PF_UNIX AF_UNIX
+#endif
+
+#ifndef GRND_NONBLOCK
+# define GRND_NONBLOCK O_NONBLOCK
+#endif
+
+#define IPPORT_RESERVED 1024
+
+#define getuid dropbear_getuid
+#define geteuid dropbear_geteuid
+#define getpwuid dropbear_getpwuid
+#define getpwnam dropbear_getpwnam
+uid_t getuid(void);
+uid_t geteuid(void);
+struct passwd *getpwuid(uid_t uid);
+struct passwd *getpwnam(const char *name);
+int dropbear_auth_initialize(void);
+int dropbear_verify_password(const char *username, const char *password);
+
+#ifndef CONFIG_SCHED_USER_IDENTITY
+/* NuttX has no supplementary-group support, so getgroups() does not exist. */
+
+int dropbear_getgroups(int size, gid_t list[]);
+
+# define getgroups dropbear_getgroups
+#endif
+
+#endif /* __APPS_NETUTILS_DROPBEAR_PORT_NUTTX_CONFIG_H */
diff --git a/nshlib/CMakeLists.txt b/nshlib/CMakeLists.txt
index 8adab3168..866f8bb6c 100644
--- a/nshlib/CMakeLists.txt
+++ b/nshlib/CMakeLists.txt
@@ -101,6 +101,10 @@ if(CONFIG_NSH_LIBRARY)
endif()
endif()
+ if(CONFIG_NSH_DROPBEAR)
+ list(APPEND CSRCS nsh_dropbear.c)
+ endif()
+
if(NOT CONFIG_NSH_DISABLESCRIPT)
list(APPEND CSRCS nsh_test.c)
endif()
diff --git a/nshlib/Kconfig b/nshlib/Kconfig
index 80476e7f5..01103ee29 100644
--- a/nshlib/Kconfig
+++ b/nshlib/Kconfig
@@ -1201,6 +1201,28 @@ config NSH_DISABLE_TELNETSTART
endmenu # Telnet Configuration
+menu "Dropbear Configuration"
+
+config NSH_DROPBEAR
+ bool "Start Dropbear SSH server"
+ default n
+ depends on NETUTILS_DROPBEAR
+ depends on NSH_BUILTIN_APPS
+ depends on !NSH_DISABLEBG
+ select NSH_LOGIN
+ ---help---
+ If NSH_DROPBEAR is set to 'y', then NSH starts the Dropbear SSH
+ server automatically.
+
+config NSH_DISABLE_DROPBEARSTART
+ bool "Disable to start Dropbear"
+ default n
+ depends on NSH_DROPBEAR
+ ---help---
+ Determines if NSH starts Dropbear automatically.
+
+endmenu # Dropbear Configuration
+
config NSH_LOGIN
bool
default n
diff --git a/nshlib/Makefile b/nshlib/Makefile
index d15ca4aa3..4aba9e3a8 100644
--- a/nshlib/Makefile
+++ b/nshlib/Makefile
@@ -86,6 +86,10 @@ CSRCS += nsh_telnetlogin.c
endif
endif
+ifeq ($(CONFIG_NSH_DROPBEAR),y)
+CSRCS += nsh_dropbear.c
+endif
+
ifneq ($(CONFIG_NSH_DISABLESCRIPT),y)
CSRCS += nsh_test.c
endif
diff --git a/nshlib/nsh.h b/nshlib/nsh.h
index f20fcf773..872c1e4af 100644
--- a/nshlib/nsh.h
+++ b/nshlib/nsh.h
@@ -844,6 +844,10 @@ int nsh_login(FAR struct console_stdio_s *pstate);
int nsh_telnetlogin(FAR struct console_stdio_s *pstate);
#endif
+#if defined(CONFIG_NSH_DROPBEAR) && !defined(CONFIG_NSH_DISABLE_DROPBEARSTART)
+int nsh_dropbearstart(void);
+#endif
+
/* Application interface */
int nsh_command(FAR struct nsh_vtbl_s *vtbl, int argc, FAR char *argv[]);
diff --git a/fsutils/passwd/passwd_append.c b/nshlib/nsh_dropbear.c
similarity index 55%
copy from fsutils/passwd/passwd_append.c
copy to nshlib/nsh_dropbear.c
index be77c9aed..9e7753f11 100644
--- a/fsutils/passwd/passwd_append.c
+++ b/nshlib/nsh_dropbear.c
@@ -1,5 +1,5 @@
/****************************************************************************
- * apps/fsutils/passwd/passwd_append.c
+ * apps/nshlib/nsh_dropbear.c
*
* SPDX-License-Identifier: Apache-2.0
*
@@ -24,80 +24,53 @@
* Included Files
****************************************************************************/
-#include <stdio.h>
+#include <nuttx/config.h>
+
#include <assert.h>
-#include <errno.h>
+#include <debug.h>
+
+#include "nsh.h"
+#include "nsh_console.h"
-#include "passwd.h"
+#ifdef CONFIG_NSH_DROPBEAR
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
- * Name: passwd_append
+ * Name: nsh_dropbearstart
*
* Description:
- * Append a new record to the end of the /etc/passwd file
+ * nsh_dropbearstart() starts the Dropbear SSH server. This function
+ * returns immediately after the daemon has been started.
*
- * Input Parameters:
- *
- * Returned Value:
- * Zero (OK) is returned on success; a negated errno value is returned on
- * failure.
+ * Returned Values:
+ * Zero is returned if Dropbear was started. A negated errno value will be
+ * returned on failure.
*
****************************************************************************/
-int passwd_append(FAR const char *username, FAR const char *password)
+#ifndef CONFIG_NSH_DISABLE_DROPBEARSTART
+int nsh_dropbearstart(void)
{
- char encrypted[MAX_ENCRYPTED + 1];
- FILE *stream;
+ FAR struct console_stdio_s *pstate = nsh_newconsole(false);
+ char cmdline[] = CONFIG_NETUTILS_DROPBEAR_PROGNAME " &";
int ret;
- /* Encrypt the raw password */
-
- ret = passwd_encrypt(password, encrypted);
- if (ret < 0)
- {
- return ret;
- }
-
- /* Append the new user record to the end of the password file */
-
- stream = fopen(CONFIG_FSUTILS_PASSWD_PATH, "a");
- if (stream == NULL)
- {
- int errcode = errno;
- DEBUGASSERT(errcode > 0);
- return errcode;
- }
+ DEBUGASSERT(pstate != NULL);
- /* The format of the password file is:
- *
- * user:x:uid:gid:home
- *
- * Where:
- * user: User name
- * x: Encrypted password
- * uid: User ID (0 for now)
- * gid: Group ID (0 for now)
- * home: Login directory (/ for now)
- */
+ ninfo("Starting the Dropbear SSH server\n");
- ret = fprintf(stream, "%s:%s:0:0:/\n", username, encrypted);
+ ret = nsh_parse(&pstate->cn_vtbl, cmdline);
if (ret < 0)
{
- int errcode = errno;
- DEBUGASSERT(errcode > 0);
- ret = -errcode;
- goto errout_with_stream;
+ nerr("ERROR: Failed to start Dropbear: %d\n", ret);
}
- /* Return success */
-
- ret = OK;
-
-errout_with_stream:
- fclose(stream);
+ nsh_release(&pstate->cn_vtbl);
return ret;
}
+#endif
+
+#endif /* CONFIG_NSH_DROPBEAR */
diff --git a/nshlib/nsh_init.c b/nshlib/nsh_init.c
index aea095e34..49c1488e6 100644
--- a/nshlib/nsh_init.c
+++ b/nshlib/nsh_init.c
@@ -175,4 +175,16 @@ void nsh_initialize(void)
nsh_telnetstart(AF_UNSPEC);
#endif
+
+#if defined(CONFIG_NSH_DROPBEAR) && \
+ !defined(CONFIG_NSH_DISABLE_DROPBEARSTART) && \
+ !defined(CONFIG_NETINIT_NETLOCAL)
+ /* If Dropbear is selected as an SSH front-end, then start the daemon
+ * UNLESS network initialization is deferred via CONFIG_NETINIT_NETLOCAL.
+ * In that case, Dropbear must be started manually with the dropbear
+ * command after the network has been initialized.
+ */
+
+ nsh_dropbearstart();
+#endif
}