diff --git a/net/tinyssh/Makefile b/net/tinyssh/Makefile new file mode 100644 index 0000000000000..7989488db5912 --- /dev/null +++ b/net/tinyssh/Makefile @@ -0,0 +1,71 @@ +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=tinyssh +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/janmojzis/tinyssh +PKG_SOURCE_DATE:=2026-06-13 +PKG_SOURCE_VERSION:=3d93382cde06c109d5d274fa2dddea064e543c85 +PKG_MIRROR_HASH:=716bc30d70776be6b925fa0454a2597483b694635ee88f855ecbc8d6ac97eb01 + +PKG_MAINTAINER:=Mario Rugiero +PKG_LICENSE:=CC0-1.0 OR 0BSD OR MIT-0 OR MIT +PKG_LICENSE_FILES:=LICENSE.md + +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk + +define Package/tinyssh + SECTION:=net + CATEGORY:=Network + TITLE:=Small SSHv2-only server + URL:=https://tinyssh.org/ + DEPENDS:= +endef + +define Package/tinyssh/description + TinySSH is a minimalistic SSH server which implements only a subset + of SSHv2 features. +endef + +comma:=, +# RELRO bloats tinysshd-listen ~2.7kb -> ~65kb (ld.bfd/mips16 quirk); +# drop it there only, it parses no untrusted input. +TINYSSHD_LISTEN_CFLAGS:=$(filter-out -Wl$(comma)-z$(comma)relro,$(TARGET_CFLAGS)) +TINYSSHD_LISTEN_LDFLAGS:=$(filter-out -zrelro,$(TARGET_LDFLAGS)) + +define Build/Compile + +$(MAKE) -C $(PKG_BUILD_DIR) \ + CC="$(TARGET_CC)" \ + CFLAGS="$(TARGET_CFLAGS) -fwrapv -Icryptoint" \ + CPPFLAGS="$(TARGET_CPPFLAGS)" \ + LDFLAGS="$(TARGET_LDFLAGS)" \ + tinysshd tinysshd-makekey tinysshd-printkey tinysshnoneauthd + $(TARGET_CC) $(TINYSSHD_LISTEN_CFLAGS) $(TARGET_CPPFLAGS) $(TINYSSHD_LISTEN_LDFLAGS) \ + -o $(PKG_BUILD_DIR)/tinysshd-listen ./src/tinysshd-listen.c +endef + +define Package/tinyssh/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/tinysshd $(1)/usr/sbin/tinysshd + $(LN) tinysshd $(1)/usr/sbin/tinysshd-makekey + $(LN) tinysshd $(1)/usr/sbin/tinysshd-printkey + $(LN) tinysshd $(1)/usr/sbin/tinysshnoneauthd + $(INSTALL_BIN) $(PKG_BUILD_DIR)/tinysshd-listen $(1)/usr/sbin/tinysshd-listen + $(INSTALL_DIR) $(1)/etc/init.d $(1)/etc/config + $(INSTALL_BIN) ./files/tinyssh.init $(1)/etc/init.d/tinyssh + $(INSTALL_CONF) ./files/tinyssh.config $(1)/etc/config/tinyssh +endef + +define Package/tinyssh/conffiles +/etc/config/tinyssh +endef + +$(eval $(call BuildPackage,tinyssh)) diff --git a/net/tinyssh/files/tinyssh.config b/net/tinyssh/files/tinyssh.config new file mode 100644 index 0000000000000..0131e990e1e4c --- /dev/null +++ b/net/tinyssh/files/tinyssh.config @@ -0,0 +1,7 @@ +config tinyssh 'main' + option enabled '1' + option port '22' + option maxconnections '20' + option verbose '0' +# option interface 'lan' +# option keydir '/etc/tinyssh/sshkeydir' diff --git a/net/tinyssh/files/tinyssh.init b/net/tinyssh/files/tinyssh.init new file mode 100644 index 0000000000000..5ba5bad4ad162 --- /dev/null +++ b/net/tinyssh/files/tinyssh.init @@ -0,0 +1,75 @@ +#!/bin/sh /etc/rc.common +# tinyssh + +START=50 +STOP=10 + +USE_PROCD=1 + +NAME=tinyssh +LISTEN=/usr/sbin/tinysshd-listen +TINYSSHD=/usr/sbin/tinysshd + +ensure_keys() { + local keydir="$1" tmp + + [ -d "$keydir" ] && return 0 + + tmp=$(mktemp -d) || return 1 + /usr/sbin/tinysshd-makekey -q "$tmp/keys" || { + rm -rf "$tmp" + return 1 + } + mkdir -p "$(dirname "$keydir")" + mv "$tmp/keys" "$keydir" + rmdir "$tmp" +} + +start_service() { + local enabled interface port maxconnections verbose keydir bindaddr ipaddr + + . /lib/functions.sh + . /lib/functions/network.sh + + config_load tinyssh + config_get_bool enabled main enabled 1 + [ "$enabled" = 1 ] || return 0 + + config_get interface main interface + config_get port main port 22 + config_get maxconnections main maxconnections 20 + config_get_bool verbose main verbose 0 + config_get keydir main keydir /etc/tinyssh/sshkeydir + + ensure_keys "$keydir" || { + logger -s -t "$NAME" -p daemon.err "unable to generate host keys in '$keydir'" + return 1 + } + + bindaddr="0.0.0.0" + if [ -n "$interface" ]; then + if network_is_up "$interface"; then + network_get_ipaddr ipaddr "$interface" + [ -n "$ipaddr" ] && bindaddr="$ipaddr" + else + logger -t "$NAME" -p daemon.warn \ + "interface '$interface' is not up, binding to all interfaces instead" + fi + fi + + procd_open_instance + procd_set_param command "$LISTEN" + case "$maxconnections" in + ''|*[!0-9]*|0) ;; + *) procd_append_param command -c "$maxconnections" ;; + esac + procd_append_param command "$bindaddr" "$port" "$TINYSSHD" + [ "$verbose" = 1 ] && procd_append_param command -v + procd_append_param command "$keydir" + procd_set_param respawn + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "tinyssh" +} diff --git a/net/tinyssh/src/tinysshd-listen.c b/net/tinyssh/src/tinysshd-listen.c new file mode 100644 index 0000000000000..be973f61d2048 --- /dev/null +++ b/net/tinyssh/src/tinysshd-listen.c @@ -0,0 +1,88 @@ +/* + * tinysshd-listen: minimal IPv4/IPv6 accept-and-fork TCP supervisor. + * usage: tinysshd-listen [-c maxconns] ip port prog [args...] + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static volatile sig_atomic_t live = 0; + +static void reap(int sig) { + (void)sig; + while (waitpid(-1, NULL, WNOHANG) > 0) live--; +} + +int main(int argc, char **argv) { + long maxconns = 0; + + if (argc >= 3 && strcmp(argv[1], "-c") == 0) { + maxconns = atol(argv[2]); + argv += 2; + argc -= 2; + } + if (argc < 4) _exit(100); + + struct sockaddr_in a4 = {0}; + struct sockaddr_in6 a6 = {0}; + struct sockaddr *addr; + socklen_t addrlen; + int family; + + unsigned short port = (unsigned short)atoi(argv[2]); + + if (inet_pton(AF_INET, argv[1], &a4.sin_addr) == 1) { + family = AF_INET; + a4.sin_family = AF_INET; + a4.sin_port = htons(port); + addr = (struct sockaddr *)&a4; + addrlen = sizeof(a4); + } else if (inet_pton(AF_INET6, argv[1], &a6.sin6_addr) == 1) { + family = AF_INET6; + a6.sin6_family = AF_INET6; + a6.sin6_port = htons(port); + addr = (struct sockaddr *)&a6; + addrlen = sizeof(a6); + } else { + _exit(100); + } + + int lfd = socket(family, SOCK_STREAM, 0); + if (lfd < 0) _exit(111); + + int one = 1; + setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + + if (bind(lfd, addr, addrlen) < 0) _exit(111); + if (listen(lfd, 20) < 0) _exit(111); + + signal(SIGCHLD, reap); + + for (;;) { + int cfd = accept(lfd, NULL, NULL); + if (cfd < 0) continue; + + if (maxconns > 0 && live >= maxconns) { + close(cfd); + continue; + } + + pid_t pid = fork(); + if (pid == 0) { + close(lfd); + dup2(cfd, 0); + dup2(cfd, 1); + close(cfd); + execvp(argv[3], &argv[3]); + _exit(111); + } + if (pid > 0) live++; + close(cfd); + } +}