Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 159 additions & 29 deletions apps/wolfsshd/auth.c

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions apps/wolfsshd/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ USER_NODE* AddNewUser(USER_NODE* list, byte type, const byte* username,
word32 usernameSz, const byte* value, word32 valueSz);
#endif

#ifndef _WIN32
#include <pwd.h>

#ifndef WOLFSSHD_PW_BUF_SZ
#define WOLFSSHD_PW_BUF_SZ 4096
#endif

/* Caller-owned storage for an account lookup, so no copy of the entry is
* left in libc's static storage. */
typedef struct WOLFSSHD_PW {
struct passwd pw;
char buf[WOLFSSHD_PW_BUF_SZ];
} WOLFSSHD_PW;

/* Looks up name into store. Returns &store->pw, or NULL when there is no such
* user (errno 0) or the lookup failed (errno set). Release the store with
* wolfSSHD_PwFree(), which wipes it, whatever the result. */
struct passwd* wolfSSHD_GetPwnam(const char* name, WOLFSSHD_PW* store);
void wolfSSHD_PwFree(WOLFSSHD_PW* store);
#endif

int DefaultUserAuth(byte authType, WS_UserAuthData* authData, void* ctx);
int DefaultUserAuthTypes(WOLFSSH* ssh, void* ctx);

Expand Down Expand Up @@ -124,6 +145,7 @@ extern int (*wsshd_setregid_cb)(WGID_T, WGID_T);
extern int (*wsshd_setreuid_cb)(WUID_T, WUID_T);
extern int (*wsshd_setegid_cb)(WGID_T);
extern int (*wsshd_seteuid_cb)(WUID_T);
/* NULL, the default, runs the real lookup; set, it replaces the lookup. */
extern struct passwd* (*wsshd_getpwnam_cb)(const char*);
extern int (*wsshd_setgroups_cb)(int, const WGID_T*);
#ifdef WOLFSSHD_HAVE_SHADOW
Expand Down
2 changes: 2 additions & 0 deletions apps/wolfsshd/test/run_all_sshd_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ test_cases=(
"sshd_sftp_idle_cpu_test.sh"
"sshd_bad_subsystem_test.sh"
"sshd_scp_fail.sh"
"sshd_scp_home_test.sh"
"sshd_term_close_test.sh"
"sshd_stdin_eof_test.sh"
"sshd_exit_signal_test.sh"
"sshd_stdin_stall_test.sh"
"sshd_channel_close_test.sh"
"ssh_kex_algos.sh"
Expand Down
72 changes: 72 additions & 0 deletions apps/wolfsshd/test/sshd_exit_signal_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash
# bash, unlike most of this directory: the option array below needs it.

# A remote command killed by a signal must not report success. wolfSSHd sends
# 128 plus the signal number as the exit status, as a shell reports it.
#
# Needs the OpenSSH client, which exits with the remote exit status.

if [ -z "$1" ] || [ -z "$2" ]; then
echo "expecting host and port as arguments"
echo "./sshd_exit_signal_test.sh 127.0.0.1 22222"
exit 1
fi

HOST="$1"
PORT="$2"
USER_NAME="${3:-`whoami`}"

command -v ssh >/dev/null 2>&1 || {
echo "ssh not found, skipping"
exit 77
}

command -v timeout >/dev/null 2>&1 || {
echo "timeout not found, skipping"
exit 77
}

# ssh refuses a group/world readable identity file.
KEYDIR=`mktemp -d 2>/dev/null` || KEYDIR=`mktemp -d -t sshdsig`
if [ -z "$KEYDIR" ] || [ ! -d "$KEYDIR" ]; then
echo "could not create temp dir"
exit 1
fi
trap 'rm -rf "$KEYDIR"' EXIT

cp ../../../keys/hansel-key-ecc.pem "$KEYDIR/id_ecdsa" || exit 1
chmod 600 "$KEYDIR/id_ecdsa"

SSH_OPTS=(-i "$KEYDIR/id_ecdsa" -p "$PORT"
-o IdentitiesOnly=yes
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
-o PreferredAuthentications=publickey -o PasswordAuthentication=no
-o BatchMode=yes -o ConnectTimeout=5 -o LogLevel=ERROR)

if ! timeout 20 ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" true >/dev/null 2>&1
then
echo "no session with these options, skipping"
exit 77
fi

# Control: a plain exit status comes through unchanged.
timeout 20 ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" 'exit 3' \
> /dev/null 2> "$KEYDIR/exit.err"
RESULT=$?
if [ "$RESULT" != 3 ]; then
echo "expected exit status 3, got $RESULT"
cat "$KEYDIR/exit.err"
exit 1
fi

# The shell running the command kills itself, so it is what the daemon reaps.
timeout 20 ssh "${SSH_OPTS[@]}" "$USER_NAME@$HOST" 'kill -KILL $$' \
> /dev/null 2> "$KEYDIR/kill.err"
RESULT=$?
if [ "$RESULT" != 137 ]; then
echo "expected exit status 137 for SIGKILL, got $RESULT"
cat "$KEYDIR/kill.err"
exit 1
fi

exit 0
65 changes: 65 additions & 0 deletions apps/wolfsshd/test/sshd_scp_home_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/bin/sh

# An SCP transfer with a relative remote path resolves it from the user's home
# directory, not from the daemon's working directory.

# Not named PWD: the shell rewrites that variable on every cd, so a saved
# copy would not survive the cd to the repository root below.
TESTDIR=`pwd`
. ./wolfssh_options.sh
cd ../../..

TEST_SCP_CLIENT="./examples/scpclient/wolfscp"
USER="${3:-`whoami`}"
PRIVATE_KEY="./keys/hansel-key-ecc.der"
PUBLIC_KEY="./keys/hansel-key-ecc.pub"

if [ -z "$1" ] || [ -z "$2" ]; then
echo "expecting host and port as arguments"
echo "./sshd_scp_home_test.sh 127.0.0.1 22222"
exit 1
fi

if ! wolfssh_has SCP || [ ! -x "$TEST_SCP_CLIENT" ]; then
echo "SCP client not available in this build, skipping"
exit 77
fi

# The check reads the file back from the home directory directly.
case "$1" in
127.0.0.1|localhost) ;;
*) echo "remote host, skipping"; exit 77 ;;
esac

HOMEDIR=`getent passwd "$USER" 2>/dev/null | cut -d: -f6`
[ -n "$HOMEDIR" ] || HOMEDIR=`eval echo "~$USER"`
if [ -z "$HOMEDIR" ] || [ ! -d "$HOMEDIR" ]; then
echo "no home directory for $USER, skipping"
exit 77
fi

NAME="scp-home-$$.dat"
OUTDIR="`pwd`/test-$$"
mkdir "$OUTDIR" || exit 1
trap 'rm -rf "$OUTDIR"; rm -f "$HOMEDIR/$NAME"' EXIT

if ! dd if=/dev/urandom of="$OUTDIR/$NAME" bs=1024 count=64 2>/dev/null; then
echo "Could not create the test file $OUTDIR/$NAME"
exit 1
fi

echo "$TEST_SCP_CLIENT -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY -L$OUTDIR/$NAME:. -H $1 -p $2"
$TEST_SCP_CLIENT -u $USER -i $PRIVATE_KEY -j $PUBLIC_KEY -L"$OUTDIR/$NAME":. \
-H $1 -p $2
if [ "$?" != "0" ]; then
echo "Expecting to pass transfer"
exit 1
fi

if ! cmp -s "$OUTDIR/$NAME" "$HOMEDIR/$NAME"; then
echo "File did not land in $HOMEDIR"
exit 1
fi

cd "$TESTDIR"
exit 0
89 changes: 86 additions & 3 deletions apps/wolfsshd/test/test_configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <crypt.h>
#endif
#ifndef _WIN32
#include <errno.h>
#include <unistd.h>
#include <pwd.h>
#endif
Expand Down Expand Up @@ -2770,10 +2771,10 @@ static int test_AuthInit(void)

wolfSSHD_SetCachedFakeHashForTest(NULL);

/* Force the real getspnam(), independent of what earlier tests left
* this global as. */
/* Force the real lookup, independent of what earlier tests left this
* global as. */
savedGetspnam = wsshd_getspnam_cb;
wsshd_getspnam_cb = getspnam;
wsshd_getspnam_cb = NULL;

rootShadow = getspnam("root");
if (rootShadow == NULL || rootShadow->sp_pwdp == NULL) {
Expand Down Expand Up @@ -3030,6 +3031,85 @@ static int test_ScanShadowFile_truncatedLine(void)
#endif /* WOLFSSHD_HAVE_SHADOW */
#endif /* WOLFSSH_HAVE_LIBCRYPT || WOLFSSH_HAVE_LIBLOGIN */

#ifndef _WIN32
/* The lookup fills the caller's store, and freeing it wipes the store. */
static int test_GetPwnam(void)
{
int ret = WS_SUCCESS;
struct passwd* (*savedGetpwnam)(const char*);
struct passwd* self;
struct passwd* pw;
WOLFSSHD_PW store;
char name[64];
word32 i;

savedGetpwnam = wsshd_getpwnam_cb;
wsshd_getpwnam_cb = NULL;

self = getpwuid(getuid());
if (self == NULL || self->pw_name == NULL ||
WSTRLEN(self->pw_name) >= sizeof(name)) {
Log(" Skipping test: no passwd entry for this uid.\n");
wsshd_getpwnam_cb = savedGetpwnam;
return WS_SUCCESS;
}
WSTRNCPY(name, self->pw_name, sizeof(name) - 1);
name[sizeof(name) - 1] = '\0';

Log(" Testing scenario: wolfSSHD_GetPwnam() for this user.");
pw = wolfSSHD_GetPwnam(name, &store);
if (pw != &store.pw || pw->pw_uid != getuid() ||
WSTRCMP(pw->pw_name, name) != 0 ||
(byte*)pw->pw_name < (byte*)store.buf ||
(byte*)pw->pw_name >= (byte*)store.buf + sizeof(store.buf)) {
Log(" FAILED: entry not in the caller's store.\n");
ret = WS_FATAL_ERROR;
}
wolfSSHD_PwFree(&store);
for (i = 0; ret == WS_SUCCESS && i < (word32)sizeof(store); i++) {
if (((byte*)&store)[i] != 0) {
Log(" FAILED: store not wiped.\n");
ret = WS_FATAL_ERROR;
}
}
if (ret == WS_SUCCESS) {
Log(" PASSED.\n");
Log(" Testing scenario: wolfSSHD_GetPwnam() for no such user.");
WMEMSET(&store, 0xA5, sizeof(store));
errno = EIO;
if (wolfSSHD_GetPwnam("no_such_wolfsshd_user_xyz", &store) != NULL ||
errno != 0) {
Log(" FAILED: expected NULL with errno 0.\n");
ret = WS_FATAL_ERROR;
}
for (i = 0; ret == WS_SUCCESS && i < (word32)sizeof(store); i++) {
if (((byte*)&store)[i] != 0) {
Log(" FAILED: store not wiped.\n");
ret = WS_FATAL_ERROR;
}
}
if (ret == WS_SUCCESS) {
Log(" PASSED.\n");
}
wolfSSHD_PwFree(&store);
}
if (ret == WS_SUCCESS) {
Log(" Testing scenario: wolfSSHD_GetPwnam() with no name.");
errno = 0;
if (wolfSSHD_GetPwnam(NULL, &store) != NULL || errno == 0) {
Log(" FAILED: expected NULL with errno set.\n");
ret = WS_FATAL_ERROR;
}
else {
Log(" PASSED.\n");
}
}

wsshd_getpwnam_cb = savedGetpwnam;
return ret;
}
#endif

#ifdef WOLFSSL_BASE64_ENCODE
/* Build SSH wire-format public key blob. Returns size or 0 if too small. */
static word32 BuildWireKeyBlob(const char* type, const byte* payload,
Expand Down Expand Up @@ -7364,6 +7444,9 @@ const TEST_CASE testCases[] = {
TEST_DECL(test_AuthSetGroups_setgroups_fail),
TEST_DECL(test_AuthSetGroups_getgrouplist_fail),
#endif
#ifndef _WIN32
TEST_DECL(test_GetPwnam),
#endif
#if defined(WOLFSSH_HAVE_LIBCRYPT) || defined(WOLFSSH_HAVE_LIBLOGIN)
TEST_DECL(test_CheckPasswordHashUnix),
#ifdef WOLFSSHD_HAVE_SHADOW
Expand Down
Loading
Loading